вторник, мая 21, 2019

Жаркий день в аду

ИЛИ

Самый прикольный на этот день проект в моей жизни - система управления производством и заказами - состоит из пяти серверов в двух городах и проектировалась децентрализованной: серверы могут общаться, но отключение связи в любой точке не останавливает её работы: часть функций отпадает, основные действуют.

В каждом сервере торчит флешка, на которую еженощно бэкапится база данных.

Был один зияющий пробел, который меня беспокоил: однажды в одной из точек вылетит сервер. Это неизбежно случится. Что я буду делать?



К счастью, один из серверов чудовищно избыточен (настоящий, стоечный) и наименее загружен. Что если заготовить там работоспособные копии всех остальных систем и держать актуальные копии баз?

automysqlbackup создаёт бэкап базы в каталоге latest (при включенной опции latest), это хардлинк на последний файл в каталоге daily. Файл содержит в имени дату. Это неудобно, его надо переименовать (это значит, что файловая система должна поддерживать хардлинки, поэтому бэкапные флешки отформатированы в ext4).

Алгоритм:

1) монтируем флешку (на случай, если не);

2) бэкапим базу;

3) если удачно, удаляем daily.sql.qz в latest

4) переименовываем дамп базы в daily.sql.qz (в старых версиях дамп начинается с имени базы, в новых - с daily; соответственно, переименовываем файл, начинающийся на p или на d);

5) чмодим daily.sql.qz для чтения всеми (новая версия automysqlbackup создаёт его rw----).

Для этого создаём /root/backup с содержимым:

#!/bin/bash
mount /dev/sdb1 /mnt/backupflash; /usr/sbin/automysqlbackup && rm /mnt/backupflash/sql/latest/daily.sql.qz; mv $(ls /mnt/backupflash/sql/latest/p*) /mnt/backupflash/sql/latest/daily.sql.qz && chmod a+r /mnt/backupflash/sql/latest/daily.sql.qz

Выделено жирным: 1) automysqlbackup старых версий находится в /usr/local/bin, новых - в /usr/sbin; сделать whereis automysqlbackup от рута, чтобы узнать. 2) см. разницу p/d выше.

Делаем исполняемым chmod +x /root/backup и ставим в крон рута на ночное время.

0 3 * * * /root/backup >/dev/null 2>&1

Теперь на принимающей стороне. Здесь мы обходимся rsync в crontab:

0 7 * * * sshpass -p "PASSWORD" rsync LOGIN@HOST:/mnt/backupflash/sql/latest/daily.sql.gz /SOMEWHERE/HOST/ >/dev/null 2>&1

Чтобы это работало, надо предварительно соединиться по ssh с источником, чтобы сохранился ключ соединения. rsync должен иметься на обеих сторонах, конечно.

И копия рабочей системы:

5 7 * * * sshpass -p "PASSWORD" rsync -az LOGIN@HOST:/SOMEPATH /var/www/HOST >/dev/null 2>&1 

И резервные базы-то обновить, опустим подробности, это zcat и mysqladmin нужных файлов в нужные базы (их надо сперва создать).

Базы должны иметь различные между собой названия и такие же реквизиты доступа, как на оригинальных системах.

Дальше нам надо создать виртуальные серверы для каждой системы, я их делал на основе ip-адреса.

Я более-менее закончил с этим в понедельник вечером (нет предела совершенству: в идеале переключение между основной и резервной системой должно делаться в настройках центрального сервера - не успел).

Во вторник с утра вылетает один из серверов во Владивостоке.

Сука, едва успел пригнуться, а то бы убило.

Точка весь день работала на созданной накануне резервной системе. Если бы резерва не было, денежные потери потребовали бы для записи MEDIUMINT.

воскресенье, октября 22, 2017

Программируемый калькулятор: мой первый плагин WordPress



Задача: расставить на страницах сайта калькуляторы расчёта стоимости потолка, балкона, окна, кондиционирования.

Параметры должны быть настраиваемыми.

Сайт на WordPress. Соответственно, настройка в админке.

Яваскриптить четыре калькулятора было в лом, поэтому от себя добавил условие: один универсальный скрипт на все калькуляторы, расчёты программируются параметрами полей ввода.

Загуглил, как устроен плагин WP (и сам WP заодно). На Хабре хорошие примеры. Немного поковырял, получил страницу в админке.

Сохранять и получать массив параметров удобно через вордпрессовские  get_option() / update_option().

Вставлять калькулятор в страницу - через shortcode с параметром.

Перешёл к самому интересному: скриптингу расчётов.

Всё, что участвует в расчёте, обозначим классом factor.

Калькуляторы не отнимают и не делят. Только два действия. Соответственно, нужны поля двух классов: add и mul. Первое прибавляет к итогу, второе - умножает итог.

При событиях change, keyup со всем, имеющим класс factor, вызывается пересчёт.

Со сложением всё просто, а с умножением пришлось писать варианты. Если функции умножения передаётся один параметр, умножаем итог на него. Если два - перемножаем их и прибавляем к итогу.

Когда уже всё казалось красивым, возникла проблема:  как быть с набором опций, каждая из которых прибавляет процент к итогу? Калькулятор считает поле за полем,  итог будет расти, процент тоже.

Покрутил так и эдак, пришёл к схеме: если среди $('.factor') есть что-то с классом freeze, запомнить итог на этот момент. Если дальше что-то имеет класс use-frozen, оперировать не итогом, а его замороженным значением.

Всё гениальное frozen.

Заодно добавим класс cm2m, значения этих полей при пересчёте переводим из сантиметров в метры на лету.

Получился универсальный калькуляторокомплект. Что угодно для несложного расчёта с суммированием и коэффициентами сооружается буквально за несколько минут.

Если это хорошо упаковать (встроенный редактор калькуляторов, шаблоны внешнего вида, адаптив, i18n), годится как самостоятельный товар.

среда, декабря 28, 2016

Победа над SIP близка как никогда


Задача: операторы на телефонах (приём заказов) должны в момент ответа на звонок знать, с кем они говорят (или хотя бы не переспрашивать номер, если клиент новый).

Это было решено когда-то наколенными средствами, которые больше не годятся. Тем более что теперь имеется сервер, способный доставить звонок до операторских ноутбуков (они под Убунту).

Решение нашлось почти сразу: Linux-клиент SIP Twinkle, способный хорошо манипулировать с заголовками, а главное - поддерживающий скриптинг по событиям.

Опробовал у себя (Debian); работает. Написал крошечный скрипт на событие "call is answered":

#!/bin/bash

xdg-open http://host/callerid.php?$(echo ${SIP_FROM}  | cut -d '"' -f2 | cut -d '+' -f2) &
echo end

по частям:

xdg-open: выполнить дефолтное действие; для URL это открытие предпочитаемого браузера.

callerid.php: скрипт, ожидающий чего-то похожего на телефонный номер, приводящий его к нормальному виду (10 цифр) и выполняющий поиск в базе клиентов.

echo ${SIP_FROM}: сюда выводится переменная SIP_FROM, устанавливаемая в окружении скрипта Twinkle'ом. В начале её содержимого идёт номер в кавычках.
Первый cut разбивает строку по кавычкам, второе поле этого результата - телефонный номер с плюсом в начале; от плюса избавляет второй cut, результат идёт в http-запрос.

echo end: передать Twinkle, что скрипт закончил работу.

В момент ответа на звонок у оператора выскакивает браузер со страницей, где он видит телефон и имя клиента (если он повторный).

Дальше, как водится, был геморрой: в Убунту 14 Twinkle без GUI; после апгрейда до 16 выяснилось, что Twinkle тупо сегфолтит на звонке, что характерно, именно на Убунте (ох уж ейная самобытность, сколько крови выпила). Пришлось (благо на ноутах, кроме браузера и SIP-клиента, ничего нет) быстренько перескочить на Debian Stretch, после чего уже всё и заработало.


четверг, декабря 15, 2016

Linux -> Windows -> HP LaserJet

Потребовалось настроить печать из CUPS на HP LJ, зашаренный на Windows-машине. Пишу в том числе и для памяти: сколько раз приходилось повторно решать проблему, не сделав памятки.

1) На Linux должна быть установлена samba и HPLIP.

2) На Windows нужно зашарить принтер с удобным (коротким буквоцифровым) именем.

3) Проверяем, что Windows-хост виден Линуксу:

smbtree

Ниже информации о самом себе должен быть хост Windows, например:

\\DESKTOP-Q8VFISC

3) Проверяем, что принтер виден Линуксу. uname - имя пользователя на Windows-хосте:

smbclient -L \\DESKTOP-Q8VFISC -U uname

Sharename       Type      Comment
---------       ----      -------
ADMIN$          Disk      Удаленный Admin
C$              Disk      Стандартный общий ресурс
IPC$            IPC       Удаленный IPC
P2035           Printer   HP LaserJet P2035
print$          Disk      Драйверы принтеров

4) Открываем администрирование принтеров в CUPS, добавляем принтер, выбираем из сетевых принтеров опцию Windows Printer via SAMBA. Строка подключения:

smb://uname:pwd@host/printername

Если принтер зашарен без пароля, uname:pwd@ опустить. При повторных изменениях принтера имя-пароль будут сбрасываться!

5) Выбираем драйвер. Здесь 2 момента:

- Драйверы некоторых принтеров HPLIP требуют закрытого плагина (в имени драйвера указано requires proprietary plugin. Плагин устанавливается в консоли:

hp-plugin -i

- Всё готово, но в CUPS задание печати помечено Filter failed. В /var/log/cups/error.log написано:

Error: This module is designed to work with HP Printers only

У меня это решилось сменой драйвера с ZJS на PCL, т. е. даёт ошибку

HP LaserJet p2035 zjs, hpcups 3.16.11, requires proprietary plugin

но работает

HP LaserJet p2035 pcl3, hpcups 3.16.11, requires proprietary plugin

вторник, ноября 15, 2016

НЬЮЗЕНБЕРГИАДА, или Неожиданное путешествие по воздуху, суше и морю на ту сторону Курильской гряды и обратно


- У меня люди едут на Итуруп.

- Замечательное место, - заметил я. - Только летом там лучше.

Кончался октябрь.

- Там пароход сломался. Едет специалист, везёт запчасть.

- Ты сказал: люди.

- Ещё ты.

- ??!

- Помоги, он сам не доберётся. Ты местный.

- Я с Кунашира. Итуруп только с рейда видел.

- Какая, б***ь, разница? Все Курилы одинаковые.

- Запчасть тяжёлая?

- Килограмм десять-пятнадцать.

* * *

Это был пролог, а сейчас второе ноября, среда, мы с Родионом пьём лимонад из картонных стаканчиков с символикой авиакомпании «Аврора» на борту Bombardier Q-400 авиакомпании «Аврора», рейс 801 Южно-Сахалинск — Курильск. Стаканчики принесла девушка в форме авиакомпании «Аврора», та же самая девушка, что вчера приносила сэндвичи и апельсиновый сок на борту Bombardier Q-400 авиакомпании «Аврора», рейс из Владивостока в Южно-Сахалинск. Ещё за час до того, первого «Бомбардье», мы встретили в аэропорту рейс из Кореи, на котором некто Сергей привёз ту самую запчасть и вручил нам, как 11-килограммовую эстафетную палочку.

Родион — судомеханик. Запчасть — увесистая. Я — сопровождающий.


суббота, сентября 03, 2016

Лихтенштейновская женщина в Inkscape: паттерны - это просто

Нужно было картинку с девушкой в манере Роя Лихтенштейна. Сделать в Inkscape. Высотой 3 метра.

Сама девушка была подготовлена в Gimp и трассирована.




Теперь надо было сделать растр с точкой размером горошины. Очевидно, надо использовать паттерн. Это одна из возможностей Inkscape, которых я никогда или почти никогда не касался. А зря. Всё оказалось очень непринуждённо.

1) Надо сделать примерно 50% растр с круглой точкой. Рисуем квадрат, в нём  круг размером в 2 раза меньше, примерно так:


2) Круг делаем красным, квадрат - прозрачным. Выделяем и делаем Objects -> Pattern -> Objects to pattern.

3) Рисуем контур под заливку и заливаем своим паттерном. Берём инструмент Node. Контур должен быть активен. Пока он активен, операции над контролирующими точками оригинала будут влиять на заливку контура. Так очень легко получить нужную частоту и угол растра. 



Насыщенность "краски" растра в этом конкретном случае можно регулировать прозрачностью контура. Но в целом после создания паттерна с ним уже мало что можно сделать. Если вы унесёте оригинал паттерна в другое место рисунка, его контролирующие точки останутся там, где оригинал был создан. Можете вообще его удалить, это не повлияет ни на заливку, ни на местонахождение контролирующих точек.

Поскольку Inkscape рисует SVG, то заливка паттерном, думаю, внутренне реализована как background-image, параметры которого регулируются инструментом Node.

В целом, мы просто и ужасно быстро залили девушку растром нужного параметра, и нас совершенно не напрягает, что каждая точка в натуральном размере величиной в горошину (последняя картинка).









понедельник, августа 08, 2016

Ушёл на базу. Через шифрованную дырку.

С открытием второй пиццерии (с моей точки зрения - с развёртыванием второй системы управления производством) возникли вопросы взаимодействия:

Как дать возможность оператору отправить заказ на нужную пиццерию?

Как формировать консолидированные отчёты по двум точкам?

Как синхронизировать меню, цены, акции, дисконты, купоны - всё, что может измениться в любой момент?

Вариант master/slave в MySQL сразу не рассматривал, потому что схема должна быть такой, чтобы пережить атомную бомбардировку: уцелевшие пиццерии должны продолжить автономную работу несмотря на оборванные провода и исчезнувшие соединения.

Это можно сделать через REST: куча писанины и куча новых ошибок.

Либо обращаться к базе удалённо.

Пару дней обдумывал это. Второй вариант позволял максимально использовать уже написанный код. Но не хотелось создавать новых сетевых дырдочек, в которых так увлекательно колупаться фомкой.

Немножко почитал интернеты и понял, что понадобится:

autossh - надстройка над ssh, поддерживающая соединение в рабочем состоянии, пока это возможно;


И всё.

К слову, Гугл по поиску autossh в третьей позиции даёт ветку под названием "Чтоб вы знали: autossh - полное дерьмо". Я её внимательно изучил. Она меня не убедила.

Ещё нужен скрипт для управления autossh как службой, он находится здесь и отлично встраивается в Debian.

Смотав это всё синей изолентой, получаем на сервере А новый порт MySQL, открытый в тьму внешн базу данных сервера Б, без создания новых дырок, через зашифрованное, автоматически устанавливающееся и магически восстанавливающееся соединение.

Дальше - с отвёрткой в зубах - в PHP-класс работы с SQL. Здесь делаем выбор параметров соединения из массива предопределённых вариантов. Важно: если сервером указывать localhost, запрос такого-то порта будет проигнорирован. Поэтому указываем 127.0.0.1. Вроде хрен и редька, а вот хрен.

Настало время первой проверки. Пишем коротенький скрипт, который запрашивает максимальные идентификаторы из таблиц заказов серверов, разнесённых на некоторое количество километров:

Холишыт, это действительно работает!

Дальше так: в интерфейс операторов, принимающих заказы с доставкой, добавляем выбор пиццерии:


Чуть позже по умолчанию будет та, чей этот клиент географически.

И теперь, если запрошена недефолтная пиццерия, просто пересоздаём класс базы данных с указанием другого соединения, и наш старый код пишет заказ в таблицы нужной пиццерии.

Дальше надо поэкспериментировать и понять, как лучше строить консолидированные отчёты: federated table?

Ну и автоматические обновление настроечных данных: надо переписать методы класса работы с БД, чтобы запросы можно было автоматически проигрывать на выбранном или всех доступных соединениях.

Поиск по этому блогу