Русский
Русский
English
Статистика
Реклама

Скрипты

Пасхалка в ionCube попытка разработчиков замести мусор под ковер?

16.06.2020 14:11:48 | Автор: admin

Веб-разработчик знает, что скрипты, созданные в коммерческих целях, могут пойти гулять по сети с затёртыми копирайтами; не исключено, что скрипт начнут перепродавать от чужого имени. Чтобы скрыть исходный код скрипта и препятствовать его изменению, применяются обфускаторы, минификаторы и т.д. Один из самых давних и известных инструментов для шифрования скриптов на PHP это ionCube. Появившийся в 2002, он продолжает следить за развитием PHP и заявляет о поддержке последних версий платформы. Как я покажу в этой статье, с поддержкой PHP 7 у ionCube далеко не всё в порядке...

Модель использования PHP-шифровщиков такая, что программист продаёт зашифрованный скрипт, а покупатель скрипта на своём сервере должен установить модуль расширения, который позволит выполнять зашифрованные скрипты. Скрипт, зашифрованный ionCube, выглядит примерно так:

<?php //0059b// 10.2 72// // IONCUBE ONLINE ENCODER EVALUATION// THIS FILE IS LICENSED TO BE USED FOR ENCODER TESTING// PURPOSES ONLY AND SHOULD NOT BE DISTRIBUTED// if(!extension_loaded('ionCube Loader')){$__oc=strtolower(substr(php_uname(),//skipped?>HR+cPn5yR+EksbFLjyZwm7EQh7Q0Y6YO6pLddgsuLRlBWUC5JWhAm3KcPBcRdP9D0zkMmdPNk5VGrMP1GxIwsA5NinHkQjWqG2pHL5nIZUvatUW+XMas3Knjf4wz9+DJoq47N1qZLDXwVzpOOupqa+Y4k8PPXt8WNYXL4gbJnVu6NrqBqqwOrtlHUE9Sc30fMfAAEDTAVfa7ADHT2egTb5xxy9RGlDCjGlmaRxoL1LvxvYcfe48f44x/H+GVTM7dPaYyy9DozcJjt3l8EDxcD73d67cWOtDgQGixQEmBlYJO7CvhIAfeCBywIrDMgWfCC80uEIX+WtSmt/PuI7OXMgsNG3yVZu2HXJvXFRmXvc6748uxr+Zh0uZnAqeLpkJB5K9H5qbMr4YM/Aig+7MhwVG3KJ0kQCEhKxJe7+7Un/jSGcwQ8HKa/90ePzH2EXazm3T87pf2hXL/exl4L7hutt/MfDGjculaEOCaoDLlUJjJqeXJL3kFDUsiPFfEL/BwAYUqe2pJAMjWXn7YIUt7Y1DdTUD4ob/5fwE9wQwfG6PfDLPFkrGVKFpkBa95sRuA7qgtXATacXAVzsfYMxZgbwF3RcI5IxQoHTgnCg57vWmM/u6swJrgkz+747DWZRS1TfJZnKbdbmWIHAW11HG2FloKdWWSIronfqnuXTI/j2/R9hX1Uim6mQowBwjS5zHZY8WFU9xE1KgETkCTsaDZODg9NYTICKs5aAdujzAtzxLWSicHZCfmpgzdFRhqYTYE1B9wktZsItkssDaq+xlyTZ+0LGnXAC6eaH7npS7w3NRBRj9ySVTRYPXBraVuJViMIX+U4IzHJDFSNiT818GtS7erlLKcbGn4OZ40Ee3XEiicFzVrOfOvH0rJT3LZgVqY+KMtjqaQike2P4DdA0SOuqOlFgQitYoo

Пользователи давно обратили внимание, что когда модуль ionCube Loader загружен, то в PHP появляются две глобальные функции с очень странными именами: _dyuweyrj4и _dyuweyrj4r. Если вызвать одну из этих функций, то PHP напечатает один из двух китайских афоризмов, и завершит выполнение:

C:\php>php -r "_dyuweyrj4(); echo 'Hmm..';"
A rat who gnaws at a cat's tail invites destruction.

C:\php>php -r "_dyuweyrj4(); echo 'Hmm..';"
Do good, reap good; do evil, reap evil.

C:\php>php -r "_dyuweyrj4r(); echo 'Hmm..';"
Do good, reap good; do evil, reap evil.


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

Напрашивается вопрос: зачем эти функции нужны, и что они делают?

Простые эксперименты


Для начала посмотрим, какие параметры _dyuweyrj4 принимает:

C:\php>php -r "_dyuweyrj4(1,2,3);"
PHP Warning: _dyuweyrj4() expects at most 2 parameters, 3 given in Standard input code on line 1

C:\php>php -r "_dyuweyrj4('foo', 'bar');"
PHP Warning: _dyuweyrj4() expects parameter 1 to be int, string given in Standard input code on line 1

C:\php>php -r "_dyuweyrj4(1, 'bar');"
PHP Warning: _dyuweyrj4() expects parameter 2 to be int, string given in Standard input code on line 1

C:\php>php -r "_dyuweyrj4(1, 2);"
A rat who gnaws at a cat's tail invites destruction.


Похоже, что принимает два числа, но какие бы числа ни были, печатает те же самые афоризмы.

Поиск использования


На каком-то мутном индонезийском форуме удаётся найти запощенный в 2013 пример скорее всего, полученный каким-то дизассемблером байткода PHP:

function tconnect( ){    $__tmp = _dyuweyrj4( 21711392, 920173696 );    return $__tmp[0];    return 1;}function tvariable( ){    $__tmp = _dyuweyrj4( 21720496, 920165136 );    return $__tmp[0];    return 1;}

Что интересного в парах чисел (21711392, 920173696) и (21720496, 920165136)? Внимательный исследователь заметит, что XOR чисел в каждой паре даёт 932443808. Попробуем сами вызвать _dyuweyrj4 с парой чисел, дающих в результате XOR 932443808:

C:\php>php -r "_dyuweyrj4(0, 932443808);"

Не напечаталось ничего!

C:\php>php -r "_dyuweyrj4(932443808, 0);"



Погружаемся в отладчик




Видим, что выполняется попытка чтения по адресу [ecx+40h], причём ecxравен 0x3793f6a0 переданному нами в функцию числу. Значит, функция ожидает получить в качестве параметра значение адреса в памяти процесса PHP, и к dword по адресу [ecx+40h]прибавит единицу (команда inc dword ptr [eax]видна чуть ниже точки крэша). Попробуем передать такой адрес: для этого обратим внимание, что адрес, по которому загружается основной модуль php.exe, не изменяется до перезагрузки Windows. В моём случае это 0x00980000. Открыв php.exe в IDA, смотрим, какие данные доступны для перезаписи:



В качестве эксперимента попробуем перезаписать первый указатель в структуре cli_sapi_module.На него есть ссылка из main+22; при загрузке php.exe по адресу 0x00980000эта ссылка будет находиться по адресу 0x00982d63. Значит, в функцию _dyuweyrj4нам надо передать значение, меньшее на 0x40, т.е. 0x00982d23:

C:\php>php -r "_dyuweyrj4(0x00982d23, 0x00982d23 ^ 0x3793f6a0);"



Опять крэш; но уже в другой функции внутри ioncube_loader_win_7.3.dll. Что интереснее, адрес, по которому был прочитан нулевой указатель 0x14c32820+0x78 не имеет ничего общего с переданным в функцию значением. (Парадоксально, что проверка на нулевой указатель test ebx, ebx осуществляется сразу же послеобращения по этому указателю.) Заглянув в память по адресу 0x14c32820, находим там структуру _zend_op_array, т.е. определение функции. Заглянуть внутрь него удобнее всего через Immediate Window:

(_zend_op_array*)0x14c32820
0x14c32820 {type=0x01 '\x1' arg_flags=0x14c32821 "" fn_flags=0x00000100 ...}
type: 0x01 '\x1'
arg_flags: 0x14c32821 ""
fn_flags: 0x00000100
function_name: 0x06f66850 {gc={refcount=0x00000001 u={type_info=0x000001c6 } } h=0xacaf1bdb len=0x0000000a ...}
scope: 0x00000000 <NULL>
prototype: 0x00000000 <NULL>
num_args: 0x00000000
required_num_args: 0x00000000
arg_info: 0x00000000 <NULL>
cache_size: 0x131183d0
last_var: 0x06ee0e98
T: 0x00000000
last: 0x00000000
opcodes: 0x00000000 <NULL>
run_time_cache: 0x00000000 {???}
static_variables: 0x00000000 <NULL>
vars: 0x00000000 {???}
refcount: 0x600df45e {???}
last_live_range: 0x88008c00
last_try_catch: 0x00000001
live_range: 0x00000100 {var=??? start=??? end=??? }
try_catch_array: 0x14c2d958 {try_op=0x00000001 catch_op=0x000001c6 finally_op=0xddab5409 ...}
filename: 0x14c1a538 {gc={refcount=0x00000001 u={type_info=0x14c00668 } } h=0x00000000 len=0x00000001 ...}
line_start: 0x00000000
line_end: 0x00000002
doc_comment: 0x00000002 {gc={refcount=??? u={type_info=??? } } h=??? len=??? ...}
last_literal: 0x714beccc
literals: 0x71180110 {php7.dll!zif_xmlwriter_write_pi(_zend_execute_data *, _zval_struct *)} {value={lval=0x3314ec83 ...} ...}
reserved: 0x14c3288c {0x06ee0bc0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}

((_zend_op_array*)0x14c32820)->function_name
0x06f66850 {gc={refcount=0x00000001 u={type_info=0x000001c6 } } h=0xacaf1bdb len=0x0000000a ...}
gc: {refcount=0x00000001 u={type_info=0x000001c6 } }
h: 0xacaf1bdb
len: 0x0000000a
val: 0x06f66860 "_dyuweyrj4"

&((_zend_op_array*)0)->reserved[3]
0x00000078 {???}


Как видим, нулевой указатель, вызвавший крэш это поле _zend_op_array.reserved[3]в определении функции _dyuweyrj4. Видимо, это поле используется ionCube Loader в каких-то своих внутренних целях. Для проверки прогоним файл из одной строчки

<?php _dyuweyrj4(0x00982d23, 0x00982d23 ^ 0x3793f6a0);
через их Online PHP Encoder. (Результат шифрования приведён в самом начале поста.) К сожалению, это не помогает: _zend_op_array.reserved[3]остаётся нулевым. Зато убеждаемся, что у выполняющейся (безымянной) функции _zend_op_array.reserved[3] теперь заполняется:

executor_globals.current_execute_data->func->op_array
{type=0x02 '\x2' arg_flags=0x14a72141 "" fn_flags=0x08000000 ...}
type: 0x02 '\x2'
arg_flags: 0x14a72141 ""
fn_flags: 0x08000000
function_name: 0x00000000 <NULL>
scope: 0x00000000 <NULL>
prototype: 0x00000000 <NULL>
num_args: 0x00000000
required_num_args: 0x00000000
arg_info: 0x00000000 <NULL>
cache_size: 0x00000004
last_var: 0x00000000
T: 0x00000001
last: 0x00000005
opcodes: 0x14a72280 {handler=0x999fc7ce op1={constant=0x00000000 var=0x00000000 num=0x00000000 ...} op2={constant=...} ...}
run_time_cache: 0x14a74018 {0x14c27ba0}
static_variables: 0x00000000 <NULL>
vars: 0x00000000 {???}
refcount: 0x14a74030 {0x00000002}
last_live_range: 0x00000000
last_try_catch: 0x00000000
live_range: 0x00000000 <NULL>
try_catch_array: 0x00000000 <NULL>
filename: 0x14a66230 {gc={refcount=0x00000001 u={type_info=0x00000006 } } h=0x00000000 len=0x00000032 ...}
line_start: 0x00200001
line_end: 0x00000001
doc_comment: 0x00000000 <NULL>
last_literal: 0x00000005
literals: 0x14a66280 {value={lval=0x0716a738 dval=5.875681226702e-316#DEN counted=0x0716a738 {gc={refcount=0x00000001 ...} } ...} ...}
reserved: 0x14a721ac {0x00000000, 0x00000000, 0x00000000, 0x14a6b200, 0x00000000, 0x00000000}


Баг багом вышибают


Как мы видим, _zend_op_array.reserved[3]заполнен только у зашифрованных функций. (Это не единственная их отличительная черта: на распечатке выше можно заметить ещё и line_start=0x00200001вместо правдоподобного номера строки.) С другой стороны, указатель на _zend_op_array, который приходит в крэшащуюся функцию, берётся из execute_data, переданного в _dyuweyrj4неявным первым параметром так что этот _zend_op_arrayвсегда соответствует вызываемой функции, а именно, самой _dyuweyrj4. Эта функция не зашифрована, и поэтому у неё _zend_op_array.reserved[3]всегда будет нулевым. Отсюда делаем вывод: вызов _dyuweyrj4с правильными параметрами неизбежно ведёт к крэшу(а с неправильными, как мы видели к печати китайских афоризмов). Замечательно в этом то, что получающийся крэш не преднамерен, а вызывается использованием нулевого указателя перед его проверкой. Такой баг в коде поймал бы любой инструмент статического анализа; но видимо, в ionCube ничем подобным не пользуются.

Что получится, если пофиксить баг в ioncube_loader_win_7.3.dll, поставив проверку указателя перед его использованием? Для этого удобнее всего использовать x64dbg:



Запускаем php -r "_dyuweyrj4(0x00982d23, 0x00982d23 ^ 0x3793f6a0);"с пропатченным ionCube Loader, и получаем крэш уже в новом месте опять при использовании нулевого указателя из _zend_op_array.reserved[3]:



Значит, от (некорректной) проверки на нулевой указатель толку всё равно не было: в следующей вызываемой функции этот же указатель используется уже без проверки. Делаем вывод, что потенциальная уязвимость, позволявшая бы нам изменять память процесса PHP по произвольному адресу, и посредством этого сбежать из сэндбокса например, вызывать функции, запрещённые администратором сервера в ionCube Loader закрыта последовательностью багов, приводящих к непреднамеренному крэшу php.exe.

Что имел в виду автор?


Я полагаю, что изначально _dyuweyrj4появилась для того, чтобы привязать к зашифрованным функциям какой-то формально корректный массив "zend_op-ов прикрытия" потому что ionCube Loader далеко не единственный модуль расширения, который залазит в эти массивы. (Один из распространённых случаев, когда расширения залазят в zend_op-ы функций это кэширование этих zend_op-ов между запусками одного и того же скрипта; другой PHP-дизассемблеры вроде того, вывод которого запощен на индонезийском форуме.) В качестве аргумента _dyuweyrj4получала указатель на _zend_op_arrayзашифрованной функции, и передавала управление расшифровщику, которым ionCube Loader заменяет стандартную функцию zend_execute_ex. Единственный сценарий, когда _dyuweyrj4могла бы вызываться это если посторонний модуль расширения закэширует "zend_op-ы прикрытия" отдельно от зашифрованной функции, и потом попытается эти zend_op-ы выполнить. В этом случае вызов _dyuweyrj4с указателем на _zend_op_arrayзашифрованной функции превратится в расшифровку и запуск самой этой функции.

При переходе к PHP 7 изменилсяABI функций расширения: вместо четырёх неявных параметров ht, return_value_ptr, this_ptr, return_value_usedстала использоваться структура _zend_execute_data. Тут программисты ionCube запутались, потому что _dyuweyrj4теперь получает два указателя на _zend_op_array: один через поле _zend_execute_data.func, второй явно переданным параметром. Первый соответствует самой _dyuweyrj4, второй зашифрованной функции, которую требуется вызвать. И вот тут мы встречаем очередной баг: инкрементировав поле refcountзашифрованной функции, _dyuweyrj4полностью о ней забывает,и в дальнейшем работает только со своим собственным _zend_op_array. Естественно, что попытка вызвать функцию расширения, как если бы это была зашифрованная функция PHP, приводит ко крэшу и хорошо ещё, что не к бесконечной рекурсии, потому что _dyuweyrj4пытается вызывать сама себя!

Напрашивается вопрос: как QA в ionCube пропустил в релиз функцию, которая в принципе никогда не способна работать как задумано? То, что в план тестирования она не попала, видно ещё и потому, что в 64-битной версии ionCube Loader параметры у _dyuweyrj4остаются 32-битными это значит, что указатель на _zend_op_arrayзашифрованной функции обрезается до 32 бит ещё до инкремента refcount, и тот крэш, который мы поймали самым первым, гарантированно случается вообще при любом вызове _dyuweyrj4.



Подробнее..

Почему при Covid-19 увеличилась переподписка, и как это проверить

17.06.2020 10:12:43 | Автор: admin
image
Photo by Victor Rodriguez on Unsplash

Часто мы получаем от клиентов (включая даже крупных) сообщения, в которых сквозит общий мотив: У %provider_name% нам не хватало 192 ядер, а у вас и 120 достаточно. Почему так?. Причем в последнее время из-за пандемии таких запросов стало больше. То ли потому что клиенты вышли в онлайн и почувствовали нехватку ресурсов из-за ажиотажного спроса и у других клиентов тоже, то ли потому что некоторые провайдеры из-за все того же высокого спроса на услуги стали плотнее упаковывать в облаке заказчиков.
Вот эта переподписка, которая обострилась, судя по всему, из-за Covid-19, сейчас волнует очень многих облачных пользователей. Поэтому мы постараемся ответить на наиболее распространенные вопросы и рассказать про инструмент, который позволит проверить наличие переподписки у вашего провайдера.
Может показаться, что эта тема уже не раз поднималась на Хабре и за его пределами, а статья будет полезной только совсем зеленым новичкам. Но мы не писали бы этот материал, если бы предполагаемый уровень осведомленности клиентов об этом явлении совпадал с реальным.

Что такое переподписка


Здесь все достаточно просто. К примеру, заказываете вы у провайдера определенные мощности ядра CPU, оперативную память, дисковое пространство. При этом вы не планируете постоянно использовать его на все 100%, следовательно, утилизация мощностей будет очень невелика. Провайдер тоже человек. Ему хочется зарабатывать, а значит, важно повышать утилизацию оборудования. Поэтому на свой страх и риск некоторые провайдеры перепродают выделенные для вас мощности другим клиентам из расчета, что ваши пиковые моменты никогда не произойдут в одно и то же время. А если и произойдут, то без какого-то сильного ущерба для бизнеса. Иными словами, может оказаться, что вы рассчитываете на конкретные мощности, проводите тесты и живете в уверенности, что в условную Черную пятницу приложение выстоит. А по факту информация от провайдера не соответствует действительности. Что в итоге? Падение, нервы, выяснение отношений?

Не все так однозначно.

Провайдеры: взгляд изнутри


Бесконечно можно делать три вещи: обновлять ленту инстаграма, скучать по офису на карантине и сравнивать облачных провайдеров.
Допустим, можно зайти с позиции цены. Каждый провайдер обозначает стоимость одного vCPU. Но что собой представляет этот vCPU, как его сопоставить с реальной производительностью? Проблема актуальна и для компаний, живущих у единственного провайдера, и для тех, кто арендует мощности сразу у нескольких. В последнем случае у этой задачки появляется дополнительная звездочка: нужно сопоставить тарифы разных провайдеров и привести их к единой сетке. У кого-то цены пониже, у кого-то процессоры помощнее.
Почему сложно сравнивать провайдеров? Во-первых, разные провайдеры по-разному распределяют ресурсы. Используют разное железо например, те же процессоры могут значительно отличаться. Конечно, надо сверить модель процессоров насколько они свежие. Однако, главное у всех разная переподписка. Она многократно усложняет расчеты: чтобы понять, сколько именно денег вы платите за конкретные мощности, требуется провести несколько замеров о них и поговорим.

Зная реальные цифры, уже можно сравнивать.
В ИТ-среде, особенно у клиентов, которые непосредственно в технической нише не работают, бытует мнение, что переподписка это чистое, рафинированное зло. На самом деле, это не так. Практически все клиенты, закупая мощности, закладывают некоторую их часть на компенсацию возможной переподписки. Если посмотреть на ситуацию со стороны провайдера, выходит, что средняя утилизация даже у очень крупных компаний (торговая сеть, банк) составляет буквально 5-7%. Это ничтожно мало. Как провайдеру бороться с такой низкой утилизацией?

Самое первое, что приходит на ум hyper-threading. Режим многопоточности включен абсолютно у всех провайдеров. В некотором смысле, он уже является переподпиской. Кто-то на этом останавливается, кто-то идет дальше. Здесь все зависит от целевого сегмента провайдера. Заказчики enterprise-уровня тщательно следят за показателями, умеют мониторить загрузку процессоров. Мыслят они приблизительно так: на своем сервере хочется иметь загрузку ЦП на уровне 15-20%. В облаке можно не скромничать и выжимать все 40-50%. И действительно нагружают. Переподписывать в такой ситуации как минимум некультурно. В некоторых случаях имеет смысл разве что перебалансировать нагрузку. Перенести сильно нагруженную машину на хост к менее загруженной, чтобы физический сервер был сбалансирован.
Если целевая аудитория компании частные лица, ситуация меняется. Допустим, Васе Доу (или Джону Пупкину) понадобилась виртуальная машина. Он хочет разместить на ней сайт мистервася.рф. Потенциальное количество его уникальных посетителей 3 с половиной человека в год. То есть сайт должен работать стабильно, но нагрузка у него будет очень скромная. Исходя из этих требований, Вася-Джон не захочет платить за сайт много денег. А крупному провайдеру будет нерентабельно отдавать виртуальную машину за сумму, комфортную для нашего героя. Компания, специализирующаяся на небольших клиентах, с радостью предоставит ему ВМ. Не потому что у нее процессоры намного дешевле или стойки хуже. Дело в переподписке. Enterprise-провайдер на одном физическом хосте может разместить 25-30 виртуальных машин, а компания поменьше 120-130. Чем менее требовательна к ресурсам целевая аудитория, тем большую переподписку можно себе позволить. И тем дешевле будет обходится каждая виртуальная машина для всех действующих лиц.
На самом деле, величина переподписки в чистом виде заказчику не интересна. Для него важно, сколько ресурсов он получит, нагружая систему. Так зачем же, если все устраивает, измерять переподписку своего провайдера?
Объективных причин всего две. Первая если вы чувствуете, что переплачиваете за мощности. Вторая если тормозит то, что тормозить не должно. В обоих случаях действительно имеет смысл замерять переподписку у нескольких провайдеров и переехать туда, где она ниже. Причем настолько ниже, чтобы решить все возникшие проблемы за те же или меньшие деньги.

Инструменты измерения переподписки


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

Итак, поехали.
Нам понадобится две виртуальные машины CentOS 7.x по 1 vCPU, так как мы тестируем производительность одного ядра.
Предварительно скачаем сам скрипт тестирования:

@vm1: curl https://storage.cloud.croc.ru/cloud-tests/perf.sh > perf.sh; chmod +x perf.sh@vm2: curl https://storage.cloud.croc.ru/cloud-tests/iperf.sh > iperf.sh; bash iperf.sh


@VM1 на данной ВМ будем запускать все необходимые нам тесты
@VM2 потребуется нам для тестирования сетевой подсистемы, поэтому установим сюда только необходимые нам утилиты
C помощью утилиты NBench замеряется скорость выполнения различных математических операций. Соответственно, чем она выше, тем лучше.

Запускаем тест на производительность CPU:

@vm1./perf.sh cpu


После первого шага будут созданы следующие файлы:
./testresults/cpuinfo.txt в него будет помещена информация о физическом процессоре, которую можно будет расшифровать на сайте вендора. Если эта информация не идет в разрез с информацией от провайдера, все в порядке.
./testresults/cpu_bench.txt результаты теста производительности процессора утилитой NBench

image

Тут нас интересует сумма этих трех значений. Чем она больше, тем лучше производительность
./testresults/steal.log ежесекундные результаты замера параметра steal time
./testresults/average-steal.log среднее значение steal time

Steal time это процент времени, в течение которого виртуальный процессор (vCPU) ожидает физический CPU, пока гипервизор обслуживает другой vCPU.
В случае когда виртуальная машина совместно использует ресурсы с другими экземплярами на одном хосте в виртуализированной среде, этот параметр определяет потерю времени за счет ожидания выполнения задач других виртуальных машин.
Steal time измеряется в процентах и указывает на то, что процессам внутри ВМ не хватает процессорного времени. Этот показатель напрямую влияет на производительность ваших приложений и по-хорошему должен быть равен нулю. Однако здесь есть нюанс: если провайдер пользуется ПО от VMware, виртуальная машина может показывать ноль из-за того, что гипервизор не отдает ей актуальные данные. Так что, если steal time отсутствует, а ВМ очевидно притормаживает, имеет смысл потребовать объяснений у провайдера. Возможна переподписка, по коням!

Далее обратимся к дискам. Помимо очевидного IOPS и Bandwidth, нас будет интересовать показатель времени отклика. Он отражает время, которое проходит с момента обращения к диску до того, как он начнет передавать данные. В нормальной ситуации должно проходить <5мс.

@vm1./perf.sh disk


Тестирование диска проводится при помощи утилиты fio. Эта утилита симулирует желаемую нагрузку операциями ввода\вывода. Существует несколько основных задаваемых параметров, о которых ниже:
1. I/O type (тип ввода\вывода)
Определяет основной алгоритм проверки. Последовательное или случайное чтение\запись или, возможно, даже смешанный режим. Буферизированный ввод-вывод или прямой (raw).
2.Block size (размер блока)
Очевидно, определяет размер блока (ов), используемого в тестах. Это может быть единственное значение или диапазон.
3. I/O size (размер ввода/вывода)
Определяет количество данных, используемых в тесте.
I/O engine (движок)
Определяет использование технологий, таких как: Memory mapping, splice, async I/O, SG.
I/O depth (глубина)
При использовании операций асинхронного ввода/вывода определяет количество потоков, которые используются в тесте.

После данного шага будут создан следующий файл:
./testresults/fio-results.txt данные о производительности дисков (input/output). Разумеется, чем быстрее, тем лучше, но стоит учесть, что если во время тестирования на ВМ проводились операции чтения/записи, эти показатели могут быть хуже, чем реальные.

image

Если по результатам этого теста какие-то параметры оказались хуже, чем вы ожидали, не спешите огорчаться. Есть автомобили гоночные, а есть грузовые. Болид Формулы легко обгонит тяжелую фуру, но при этом не сможет везти многотонный груз. Так что при оценке результатов исходите из своих задач и цифр, прописанных в SLA с провайдером.

Далее мы замеряем скорость обмена данными между виртуальными машинами. Здесь все точно так же, как и в случае с дисками: чем быстрее, тем лучше. Чтобы понять, насколько ваши параметры близки к нормальным, обратитесь к SLA. Оптимальные результаты выглядят следующим образом:

Скорость между BM (замеряем пропускную способность сети (gbit/sec)):
@vm1:     iperf3 -s -p 5201 @vm2:     iperf3 -c <vm1_ip> -p 5201 -t 300 -V


image

Тут необходимо обратить внимание на следующие показатели:
Bandwidth средняя скорость передачи данных за интервал времени. Чем выше этот показатель, тем больше пропускная способность сети
Retr количество повторно отправленных TCP-сегментов. Чем ниже этот показатель, тем лучше. В идеале он должен равняться нулю.

Разумеется, по итогам этих тестов вы не сможете со 100% гарантией утверждать, что переподписки у вашего провайдера нет совсем или что она душит ваше приложение. По-настоящему важно не то, что происходит на бэкграунде провайдера, а то, как фактически ведут себя ваши приложения в его инфраструктуре. Если вас все устраивает, запускать тесты и тратить уйму времени на сопоставление результатов будет странно. Если что-то не так замеряйте, ищите причины и разговаривайте с провайдером.
Подробнее..

Тиражирование Fedora из-под Fedora

04.02.2021 18:19:53 | Автор: admin

Многие руководства по установке слишком переусложнены из-за использования специфических утилит типа anaconda, livecd-tools, Fedora Media Writer и других, или создания файла сценария kickstart. В системе Fedora уже имеются необходимые средства для настраиваемой установки. Эта статья покажет пример для установки на USB флешку и на жёсткий диск

Внимание! Всё, что вы делаете, вы делаете на свой страх и риск, т.к. операции из этого руководства могут привести к частичной или полной потере данных.

Примечание: Предположительно используется базовая система, установленная на /dev/sda, а целевой диск или флешка подключены к /dev/sdb. В вашем случае это может быть другой диск, к примеру SSD M.2 нкаопитель, распознанный системой как /dev/nvme0n1

Разметка диска

GPT для жёсткого диска большого размера

sgdisk --zap-all /dev/sdbsgdisk -o /dev/sdbsgdisk -a 4096 -n 1:0:+1M --typecode=1:ef02 /dev/sdb # BIOS Bootsgdisk -a 4096 -n 2:0:+1G --typecode=2:8300 /dev/sdb # /bootsgdisk -a 4096 -n 3:0:+8G --typecode=3:8200 /dev/sdb # swapsgdisk -a 4096 --largest-new=4 --typecode=4:8300 /dev/sdb # XFSsgdisk -A 1:set:2 /dev/sdb

MBR для USB флешки

sfdisk /dev/sdb << EOFlabel: dosdevice: /dev/sdbunit: sectorssector-size: 512/dev/sdb1 : start=        2048, type=83, bootableEOF

Форматирование разделов

XFS для жёсткого диска

Почему XFS? Потому, что динамической выделение inode, удобно для большого количества мелких файлов, например если у вас много git проектов

mkfs.ext4 -F /dev/sdb2mkswap --force /dev/sdb3mkfs.xfs -f /dev/sdb4

EXT4 для USB флешки

mkfs.ext4 -b 1024 /dev/sdb1

Монтируем файловые системы

Для жёсткого диска

mount /dev/sdb4 /mntmkdir /mnt/{boot,dev,sys,proc}mount /dev/sdb2 /mnt/boot

Для USB флешки

mount /dev/sdb1 /mntmkdir /mnt/{boot,dev,sys,proc}

Установка базовой системы

Мой джентльменский набор содержит немного вспомогательных утилит для системного администрирования

dnf -y --installroot=/mnt --releasever=33 group install standard core \hardware-support development-libs development-toolsdnf -y --installroot=/mnt install iptables gpart gdisk rsync nano tcpdump \tcsh grub2-pc net-tools bind-utils sysstat xfsprogs

Устанавливаем загрузчик

mount --bind /dev /mnt/devmount --bind /sys /mnt/sysmount --bind /proc /mnt/proccp /etc/resolv.conf /mnt/etcchroot /mnt /bin/tcshdnf -y install kernelgrub2-install /dev/sdbgrub2-mkconfig -o /boot/grub2/grub.cfgexit

Внимание! С копированием resolv.conf будьте внимательны после, т.к. на текущий момент есть особенности использования с демоном systemd-resolved

Создание файла fstab

uuid очень удобен и хорош для поиска разделов в "мутной водице" среди правильно или неправильного распознания дисков в системе.

Для жёсткого диска

blkid --output export /dev/sdb2 | grep ^UUID= | xargs -I '{}' echo {} /boot ext4 rw,relatime 1 2 > /mnt/etc/fstabblkid --output export /dev/sdb4 | grep ^UUID= | xargs -I '{}' echo {} / xfs rw,relatime 0 0 > /mnt/etc/fstabblkid --output export /dev/sdb3 | grep ^UUID= | xargs -I '{}' echo {} swap swap defaults 0 0 > /mnt/etc/fstab

Для USB флешки

blkid --output export /dev/sdb1 | grep ^UUID= | xargs -I '{}' echo {} / ext4 rw,relatime 0 0 > /mnt/etc/fstab

Дорисовываем сову

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

echo 'root:123' | chpasswd --root /mnt

Размонтируем файловые системы

Для жёсткого диска

umount /mnt/devumount /mnt/sysumount /mnt/procumount /mnt/bootumount /mnt

Для USB флешки

umount /mnt/devumount /mnt/sysumount /mnt/procumount /mnt

Заключение

Такой вариант установки может подойти для bare-metall установки, подготовки инструмента для восстановления систем

Подробнее..

Основы Armory. Traits

07.12.2020 14:17:59 | Автор: admin

Traits, как вы уже успели догадаться из прошлых постов - это система скриптов в Armory. Если вы работали с движком Source то наверно помните такие розовые кубики - там они именовались Entities. Так и у нас, суть одна и та же. В нашем случае используемые traits можно посмотреть только в Outliner, переключившившись на режим просмотра Orphan Data в Collections. Это крайне неудобно, потому что нет визуального отображения в скриптов в сцене.

Типы traits

Traits делятся на несколько видов:

  • Haxe - написание скриптов с нуля на Haxe.

  • Wasm - см. WebAssembly.

  • UI - работа пользовательским интерфейсом.

  • Bundled - готовые / связанные сценарии Haxe.

  • Nodes - создание скриптов в Logic Editor (как в blueprints UE4).

Все они могут и (будут!) использоваться в вашем проекте.

Fake User

Если вы написали или составили в Logic Editor traits и не привязали к объекту - при экспорте игры она не сохранится. Чтобы не допустить пропажу ценных часов потраченных на составление хитроумного скрипта, в Blender есть одноименная функция. Нажмите значок щита рядом с вашей trait и она будет экспортирована. Функции или ноды требующих имени, должны быть экспортированы.

Traits Events

Trait могут запускать ивенты с жизненным циклом:

  • Trait.notifyOnAdd() - к объекту добавлен trait.

  • Trait.notifyOnInit() - объект добавлен в сцену.

  • Trait.notifyOnRemove() - объект удаляется со сцены.

  • Trait.notifyOnUpdate() - обновляет логику игры.

  • Trait.notifyOnRender() - обновляет рендеринг.

  • Trait.notifyOnRender2D() - обновляет рендер 2D.

Если сцена построена асинхронно т.е если в ней присутствуют не все объекты, то onInit может добавлять их. Если в сцене у вас есть trait которая зависит от других объектов, то используйте Scene.active.notifyOnInit() - этот ивент вызывается когда сцена полностью построена и в ней присутствуют все необходимые объекты.

Свойства traits

Скриптам можно задавать параметры и свойства прямо из Blender. Перед переменной в исходном коде нужно вставить метаданную @prop. Переменная var поддерживается только с ключевым словом. Final не будет работать, потому что свойства trait устанавливаются через Haxe Reflection API, после сборки игры.

Поддерживаются следующие типы данных:

Пример исходного кода:

Код
 package arm;  // See below ("Object data types")import iron.object.CameraObject; import iron.math.Vec2;import iron.math.Vec3;import iron.math.Vec4; class MyTrait extends iron.Trait {// Primitive data types@propvar intValue: Int = 40; // Type annotation possible, but not required@propvar floatValue = 3.14;@propvar stringValue = "Hello world!";@propvar booleanValue = true; // Object data types@propvar objValue: iron.object.Object; // Needs type annotation to be recognized@propvar camObjValue: CameraObject; // Type can be imported (see above)...@propvar lightObjValue: iron.object.LightObject; // .. or not, both will work@propvar meshObjValue: iron.object.MeshObject;@propvar speakerObjValue: iron.object.SpeakerObject; // Vector data types@propvar vector2DValue: Vec2 = new Vec2(0.2, 0.5); // Initialization possible...@propvar vector3DValue: Vec3; //... but not required@propvar vector4DValue = new Vec4(1, 2, 3, 4); // Not visible in Blender, `@prop` is missingvar notVisibleValue = 0.0; // ...}

Вот что дает этот исходник:

Предупреждения и ошибки

Движок будет предупреждать о неверных @prop:

  • Каждый @prop должен объявлять следующую переменную

  • Должна быть поддержка свойств

  • Синтаксис свойств должен быть правильным

  • Не лучшим решением будет делать статические свойства, потому что несколько объектов могут записать одно и то же свойство, что может привести к ошибкам.

Упорядочивание

По дефолту все traits создаются и хранятся в папке sources/ arm. Сваливать все в одну кучу - не самое умное решение, вам же потом поддерживать проект и искать битый скрипт среди сотен других - то еще занятие. Сохранять лучше в отдельных папках и подпапках для каждой сцены.

Можно упорядочить иерархически, для этого используем Haxe package syntax при наименовании какой нибудь traits. Например создадим trait с именем general.BoxBehavior - она сохранится в подкаталоге Sources/arm/general с именем BoxBehavior.hx

Вложенные подпапки можно создавать бесконечно, добавляя имя traits через точку: general.terrain.TerrainCollider создаст файл с именем TerrainCollider.hx по пути Sources/arm/ general/terrain.

Если вдруг понадобилось присвоить объекту другую характеристику, то нажмите на Class под списком свойств и выберите в выпадающем списке необходимое имя.

Подробнее..

Из песочницы Проверка скорости интернета библиотекой Requests в мультипроцессинге

05.08.2020 18:06:10 | Автор: admin
Доброго времени, уважаемые жители Хабра!

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

Начну со списка используемых библиотек:

  • os
  • multiprocessing(Process, Pipe)
  • time
  • requests
  • pandas
  • datetime

В первую очередь, записываем текущее время:

dt = datetime.datetime.now()

Далее нам нужен список серверов, я предпочел создать для этого словарь:

server_list = [    {        'server_id': 3682,        'download': 'http://moscow.speedtest.rt.ru:8080/speedtest/random7000x7000.jpg',        'upload': 'http://moscow.speedtest.rt.ru:8080/speedtest/upload.php'    }]

Пишем первую функцию:

def download(id, path):    start = time()    file_name = str(id) + str(path.split('/')[-1])    r = requests.get(path, stream=True)    size = int(r.headers.get('Content-Length', 0))    with open(file_name, 'wb') as f:        for chunk in r.iter_content(chunk_size=1024):            if chunk:                f.write(chunk)    end = time()    duration = end - start    sp = (((size * 8) / 1024) / 1024) / duration    return sp

Теперь подробнее о том, что происходит.

В функции есть время старта и время окончания(в секундах), из которых в дальнейшем мы получаем время жизни. В имя файла записываем id сервера и название изображения(сделано для того, чтобы не возникало конфликтов при загрузке из множества источников). Далее делаем GET запрос, получаем размер файла(в байтах) и сохраняем его на диск. Переводим байты в биты, еще немного магии с формулами и на выходе имеем скорость в MBit/s.

Следующая функция отдача файла на сервер:

def upload(id, path):    start = time()    file_name = str(id) + 'random7000x7000.jpg'    with open(file_name, 'rb') as f:        files = {'Upload': (file_name, f.read())}    requests.post(path, files=files)    size = os.path.getsize(file_name)    end = time()    duration = end - start    sp = (((size * 8) / 1024) / 1024) / duration    return sp

Здесь принцип тот же, только мы берем файл из локальной папки и POST запросом отправляем.

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

def test_f(conn, server):    speed_download = download(server['server_id'], server['download'])    speed_upload = upload(server['server_id'], server['upload'])    conn.send([server['server_id'], speed_download, speed_upload])    conn.close()

Осталось дело за малым, все это обернуть в цикл и прикрутить мультипроцессинг:

d = 0if __name__ == '__main__':    for server in server_list:        parent_conn, child_conn = Pipe()        try:            p = Process(target=test_f, args=(child_conn, server))            p.start()            d = (parent_conn.recv())        except:            d[1] = d[2] = 0        df = pd.DataFrame({                'Date': dt,                'Server': d[0],                'Download': "%.2f" % d[1],                'Upload': "%.2f" % d[2]        }, index=[0])        print(df)

Скрипт готов к использованию, для удобства вывода я использовал библиотеку pandas. Так же вывод можно поместить в базу и собирать статистику для анализа.

Спасибо за внимание!
Подробнее..

C и Python мост между мирами

14.03.2021 12:14:15 | Автор: admin

Подход к снаряду

Не так давно, в феврале, у меня случился замечательный день: ещё один проект-долгострой "полетел". О чём речь? Речь о моей давней задумке на тему использования интерпретатора Python в программах на C: я реализовал добавление хуков на Python в ReOpenLDAP. Сама по себе тема, понятное дело, большая, поэтому в таких ситуациях я пишу минимальный код на C, который служит как раз для проверки концепта и его обкатки - его очень удобно запускать под инструментами типа Valgrind, которые незамедлительно укажут на явные ошибки ляпы в работе с памятью. Однако после окончания работы я понял, что сам по себе минимальный код может быть полезен кому-то ещё, кроме меня. Почему? Потому перед началом работы я наивно предполагал, что официальная документация по C API поможет всё сделать легко и быстро, но увы! - внятного примера с пошаговым разбором не нашёл. Что ж, это open source, детка, не нравится - сделай сам.

Для большей точности: пример разрабатывался на CentOS 7 с установленными пакетами python3, python3-devel, то есть всё описанное было написано, отлажено и проделано запущено именно в этом окружении.

Для удобства весь разбираемый код лежит в репозитории на моём ГитХабе.

Инициализация

Начало начал - подключение к нашей программе заголовочного файла:

#include <Python.h>

Далее готовим нужные константы (понятно, что у вас их значения будут другими, просто следите за смыслом значений):

char hook_file_path[] = "./";char hook_file[] = "ldap_hooks";char *hook_functions[] = {  "add_hook","bind_hook","unbind_hook","compare_hook","delete_hook","modify_hook",  "modrdn_hook","search_hook","abandon_hook","extended_hook","response_hook",NULL };PyObject *pName, *pModule, *pFunc, *pValue, *sys, *path, *newPaths;

Здесь:

  • hook_file_path - каталог в файловой системе, в котором вы хотите хранить свой код на Python;

  • hook_file- имя файла с кодом, расширение .py указывать не надо;

  • hook_functions - массив с названиями функций в файле, которые мы будем искать и вызывать; последний элемент, NULL, использован как костыль для обозначения конца массива.

Дальше объявляем несколько указателей на PyObject - пока что они нам не принципиальны, просто имейте в виду, что они есть.

Готовим интерпретатор к работе:

Py_Initialize();
HERE BE DRAGONS

Помните, что в программе фактически придётся управлять памятью сразу в двух местах: на уровне кучи (malloc/free), и на уровне чёрного ящика интерпретатора Питона. Объекты, возвращаемые функциями интерпретатора, будут размещёны в памяти, им же и управляемой, поэтому придётся периодически сообщать интерпретатору Python, что тот или иной объект мы больше не используем, и можно его добавить в список для garbage collector'а. Для этого нам пригодится вызов Py_XDECREF(*Py_Object).Он умеет сам проверять, не NULL ли передан в параметре, и если да - функция не делает ничего, в отличие от Py_DECREF(*Py_Object), которая в этом случае вернёт ошибку.

Далее загружаем модуль sys и добавляем в его список path нужный нам путь:

// credits to https://stackoverflow.com/questions/50198057/python-c-api-free-errors-after-using-py-setpath-and-py-getpath// get handle to python sys.path objectsys = PyImport_ImportModule("sys");path = PyObject_GetAttrString(sys, "path");// make a list of paths to add to sys.pathnewPaths = PyUnicode_Split(PyUnicode_FromString(hook_file_path), PyUnicode_FromWideChar(L":", 1), -1);// iterate through list and add all pathsfor(i=0; i<PyList_Size(newPaths); i++) {    PyList_Append(path, PyList_GetItem(newPaths, i));}Py_XDECREF(newPaths);Py_XDECREF(path);Py_XDECREF(sys);

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

Загрузка файла с Python-кодом

Дальше будет чуть попроще - всего лишь выполним importдля нашего модуля. Почему так - ровно потому, что налицо проблема курицы и яйца: некому вызвать import ldap_hooks.

pName = PyUnicode_DecodeFSDefault(hook_file);if (pName == NULL){  fprintf(stderr,"No Python hook file found\n");  return 1;}pModule = PyImport_Import(pName);Py_DECREF(pName);// fprintf(stderr,"No C errors until now\n");

Поиск функций в файле и их вызов

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

Далее получаем объект нужной функции и вызываем её:

pFunc = PyObject_GetAttrString(pModule,hook_functions[i]);if (pFunc && PyCallable_Check(pFunc)) {    fprintf(stderr,"function %s exists and can be called\n", hook_functions[i]);    fprintf(stderr, "Calling %s\n", hook_functions[i]);    pValue = PyObject_CallFunction(pFunc, "s", hook_functions[i]);

Обратите внимание: после получения объекта по имени всегда полезно проверить, можем ли мы к нему обратиться. Именно это делает вторая строка. А пятая строка этого фрагмента вызывает функцию, передавая ей аргумент типа "строка" (на это указывает "s"). Для удобства каждая функция нашего кода на Python будет вызываться с единственным строковым аргументом, равным названию этой самой функции.

Вообще по документацииPyObject_CallFunction ровно так и вызывается:

  • первый параметр - объект вызываемой функции в Python-коде, ранее полученный через PyObject_GetAttrString;

  • второй, строка - сообщает интерпретатору тип и количество аргументов (более подробно об этой строке - в документации);

  • третий и далее аргументы - аргументы, то есть то, что наша Python-функция получит внутри входного кортежа (питонистам это известно как *args).

Итак, ссылка на объект, содержащий в себе то, что вернул наш код на Python - в pyValue. Можно праздновать?... Нет, рано. Переходим к следующей части.

Разбор результата

Всё многообразие возвращаемых результатов можно свести к базовым типам - их и будем разбирать. Очередной фрагмент кода из-за длины под спойлером.

Осторожно, код
if (pValue != NULL) {  if (pValue == Py_None) {   fprintf(stderr,"==> Дружище, это None, тут правда ничего нет\n");  }  else if ((pValue == Py_False) || (pValue == Py_True)) {    fprintf(stderr,"==> Bool:\n");    if (pValue == Py_False) {      fprintf(stderr, " False\n");    } else {      fprintf(stderr, " True \n");    }  } else if (PyUnicode_Check(pValue)) {    fprintf(stderr,"==> String:\n");    const char* newstr = PyUnicode_AsUTF8(pValue);    fprintf(stderr,"\"%s\"\n", newstr);  } else if (PyDict_Check(pValue)) {    PyObject *key, *value;    Py_ssize_t pos =0;    fprintf(stderr,"==> Dict:\n");    while (PyDict_Next(pValue, &pos, &key, &value)) {     fprintf(stderr, "%s: %s\n", PyUnicode_AsUTF8(key), PyUnicode_AsUTF8(value));    }  } else if (PyList_Check(pValue)) {    fprintf(stderr,"==> List:\n");    Py_ssize_t i, seq_len;    PyObject *item;    seq_len = PyList_Size(pValue);    for (i=0; i<seq_len; i++) {      item = PyList_GetItem(pValue, i);      fprintf(stderr, " %s\n", PyUnicode_AsUTF8(item));      // !!!--> NOT NEEDED <--!!!  Py_DECREF(item);      }  } else if (PyTuple_Check(pValue)) {    fprintf(stderr,"==> Tuple:\n");    Py_ssize_t i, seq_len;    PyObject *item;    seq_len = PyTuple_Size(pValue);    for (i=0; i<seq_len; i++) {      item = PyTuple_GetItem(pValue, i);      fprintf(stderr, " %s\n", PyUnicode_AsUTF8(item));      // !!!--> NOT NEEDED <--!!! Py_DECREF(item);      }  } else if (PyFloat_Check(pValue)) {    fprintf(stderr, "==> Float: %f\n", PyFloat_AsDouble(pValue));  } else if (PyLong_Check(pValue)) {    fprintf(stderr, "==> Long: %ld\n", PyLong_AsLong(pValue));  } else if (PySet_Check(pValue)) {    fprintf(stderr,"==> Set:\n");    PyObject *str_repr = PyObject_Repr(pValue);    fprintf(stderr, " %s\n", PyUnicode_AsUTF8(str_repr));    Py_XDECREF(str_repr);  } else {    fprintf(stderr, "==> Какая-то дичь! Проверь-ка тип результата функции %s\n", hook_functions[i]);  }  Py_XDECREF(pValue);} else {  fprintf(stderr, "WTF");}

Некоторые важные моменты по поводу разбора результатов, возвращаемых функциями:

  • особняком стоят значения None, True и False: для них нет каких-то отдельных проверочных функций, и мы в коде на C проверяем, не они ли это, простым сравнением со специальными константами: Py_None, Py_True, Py_False;

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

  • для списков и кортежей функции вида PyXXXX_GetItem возвращают "чужие" ссылки - то есть вместе с ними вашему коду на C не передаётся ни владение объектом, ни обязанность этот объект уничтожить через Py_DECREF()

  • если реализовать поддержку не конкретных типов, а протоколов - ваш C-код получит способность поддерживать питонячью утиную типизацию.

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

Функции обработки результатов

Значение в Python

Проверка

Использование в C

None

True

False

pValue == Py_None

pValue == PyTrue

pValue == Py_False

нет

строка

PyUnicode_Check(pValue)

PyUnicode_AsUTF8(pValue)

словарь

PyDict_Check(pValue)

PyObject *key, *value;
Py_ssize_t pos =0;
while ( PyDict_Next(
pValue, &pos, &key, &value)) {
.....
}

список

PyList_Check(pValue)

Py_ssize_t i, seq_len;
PyObject *item;
seq_len = PyList_Size(pValue);
for (i=0; i<seq_len; i++) {
item = PyList_GetItem(pValue, i);
.....}

кортеж

PyTuple_Check(pValue)

Py_ssize_t i, seq_len;
PyObject *item;
seq_len = PyTuple_Size(pValue);
for (i=0; i<seq_len; i++) {
item = PyTuple_GetItem(pValue, i);
.....}

число с плавающей точкой

PyFloat_Check(pValue)

PyFloat_AsDouble(pValue)

целое число

PyLong_Check(pValue)

PyLong_AsLong(pValue)

Заключение

В статье намеренно не освещались вопросы передачи каких-нибудь хитросложенных аргументов, объявления объектов-типов внутри интерпретатора и тому подобные вещи, включая обработку ошибок Python-кода - это всё-таки crash-course, а не олимпиада. Поэтому на этом откланиваюсь, и могу только добавить, что весь код лежит в репозитории на моём ГитХабе, а в комментариях попробую ответить на вопросы по теме статьи.

UPD. Поправил очепятку (Py_DECREF(*Py_Object) -> Py_DECREF(*Py_Object)).

Подробнее..

Eще один бэкап больше, чем скрипт, проще, чем система

28.07.2020 10:08:24 | Автор: admin
Систем резервного копирования множество, но что делать, если обслуживаемые сервера разбросаны по разным регионам и клиентам и нужно обходиться средствами операционной системы?



Добрый день, Habr!
Меня зовут Наталья. Я тимлид группы администраторов приложений в НПО Криста. Мы Ops для группы проектов нашей компании. У нас довольно своеобразная ситуация: мы устанавливаем и сопровождаем наше ПО как на серверах нашей компании, так и на серверах, расположенных у клиентов. При этом бэкапить сервер целиком нет необходимости. Важны лишь существенные данные: СУБД и отдельные каталоги файловой системы. Конечно, клиенты имеют (или не имеют) свои регламенты резервного копирования и часто предоставляют некое внешнее хранилище для складывания туда резервных копий. В этом случае после создания бэкапа мы обеспечиваем отправку во внешнее хранилище.

Какое-то время для целей бэкапа мы обходились bash-скриптом, но по мере разрастания вариантов настроек пропорционально росла и запутанность этого скрипта, и в один прекрасный момент мы пришли к необходимости его разрушить до основанья, а затем.....

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

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

Условия задачи были следующие:
1. базовый инстанс бэкапа автономен, работает локально
2. хранение резервных копий и логов всегда в пределах сети клиента
3. инстанс состоит из модулей такой своеобразный конструктор
4. необходима совместимость с используемыми дистрибутивами Linux, включая устаревшие, желательна потенциальная кроссплатформенность
5. для работы с инстансом достаточно доступа по ssh, открытие дополнительных портов необязательно
6. максимальная простота настройки и эксплуатации
7. возможно (но не обязательно) существование отдельного инстанса, позволяющего централизованно просмотреть состояние бэкапов с разных серверов

То, что у нас получилось, можно посмотреть здесь: github.com/javister/krista-backup
ПО написано на python3; работает на Debian, Ubuntu, CentOS, AstraLinux 1.6.
Документация выложена в каталоге docs репозитария.

Основные понятия, которыми оперирует система:
action действие, реализующее одну атомарную операцию (бэкап БД, бэкап каталога, перенос из каталога А в каталог Б и т. д.). Существующие actions лежат в каталоге core/actions
task задание, набор actions, описывающий одну логическую задачу бэкапа
schedule расписание, набор task с опциональным указанием времени выполнения задачи

Конфигурация бэкапа хранится в yaml-файле; общая структура конфига:
общие настройки
раздел actions: описание действий, используемых на этом сервере
раздел schedule: описание всех заданий (наборов действий) и расписание их запуска по крону, если такой запуск требуется
Пример конфига можно посмотреть здесь: github.com/javister/krista-backup/blob/master/KristaBackup/config.yaml.example

Что умеет приложение на текущий момент:
поддерживаются основные для нас операции: бэкап PostgreSQL, бэкап каталога файловой системы через tar; операции с внешним хранилищем; rsync между каталогами; ротация бэкапов (удаление старых копий)
вызов внешнего скрипта
выполнение вручную отдельного задания
/opt/KristaBackup/KristaBackup.py run make_full_dump

можно добавить (или убрать) в кронтабе отдельное задание или все расписание
/opt/KristaBackup/KristaBackup.py enable all

генерация триггер-файла по результатам бэкапа. Эта функция полезна в связке с Zabbix для мониторинга бэкапов
может работать в фоне в режиме webapi или web
/opt/KristaBackup/KristaBackup.py web start [--api]


Разница между режимами: в webapi нет собственно веб-интерфейса, но приложение отвечает на запросы другого инстанса. Для режима web нужно установить flask и несколько дополнительных пакетов, а это не везде приемлемо, например в сертифицированной AstraLinux SE.

Через веб-интерфейс можно посмотреть состояние и логи бэкапов подключенных серверов: веб-инстанс запрашивает данные с бэкап-инстансов через API. Доступ к web требует авторизации, доступ к webapi нет.



Логи некорректно прошедших бэкапов размечаются цветом: warning желтым, error красным.





Если администратору не нужна шпаргалка по параметрам и серверные ОС однородны, можно скомпилировать файл и распространять уже готовый пакет.

Распространяем мы эту утилиту в основном через Ansible, выкатывая сначала на часть наименее важных серверов, а после тестирования на все остальные.

В итоге мы получили компактную автономную утилиту копирования, поддающуюся автоматизации и пригодную для эксплуатации даже малоопытными администраторами. Нам удобно может, пригодится и вам?
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru