вторник, 1 декабря 2015 г.

Проблемы с DNS внутри systemd-nspawn

Не так давно писал про легковесные контейнеры для разработчика и вот, спустя какое-то время, взялся за настройку ещё одного контейнера... И неожиданно убил кучу времени на проблему с пробросом DNS серверов внутрь этого контейнера. Самое неприятное, что помнишь, что в прошлый раз эту проблему уже решал, но уже забыл как. Ответ на самом деле не прост, а очень прост. Нужно запустить сервис systemd-resolved внутри контейнера, а он получит всю нужную инфу сам. Ну и не забыть подменить стандартный /etc/resolcv.conf на симлинк от systemd

systemctl enable systemd-resolved
systemctl start systemd-resolved
ln -fs /run/systemd/resolve/resolv.conf /etc/resolv.conf

На всякий случай повторюсь, что это делается внутри контейнера. Доп конфигурация хоста не требуется.

пятница, 27 ноября 2015 г.

Вектор развития cmake

Давным давно система сборки maven показала, что проект должен описываться легко, не таскать за собой зависимости как хлам управляемый руками и всегда собираться. С тех пор все новые языки программирования первым делом обзаводились подобными системами сборки. Dub, cargo, npm и многие другие. Напиши своё имя, опиши версии библиотек которые ты хочешьь использовать и пиши код. Найти и установить именно те версии именно тех библиотек предсказуемым и надёжным образом это уже дело автоматики. Исходники можно не перечислять, папка в которой они ожидаются по умолчанию мало у кого вызывает раздражение.

Но в мире древней магии всё не так. Cmake поверг autotools но здесь тебе надо на языке с неудобным синтаксисом в императивном коде описывать алгоритм построения графа сборки. Даже поиск зависимостей делается только на половину. После find_package всем просьба поглядеть в документацию по библиотеке которую вы хотите запользовать и узанть, какие директивы прероцессора, необходимые для сборки с использованием оной в каких переменнх будут сложены, а затем вам нужно ручками их включить. Такая же история с исходниками, да и с библиотеками от которых зависит та, единственная, ради использования которой вы яростно боролись с вышеописанным гемором.

Но время неумолимо заставляет двигаться вперёд и, потихоньку, cmake движется в от предоставления кривого скриптового языка описания сборки к описанию проекта. Во первых уже давно можно экспортировать цели, а во вторых на цели можно навешивать свойства, которые автоматам взводят нужные директивы препроцессора и поделючают нужные пути поиска заголовочных файлов. Между целями можно задавать зависимости. Всё вышеописанное позволяет плавно двигаться в сторону вот таких простых и ясных CMakeLists:

cmake_minimum_required(VERSION 3.0)
project(my)

find_package(A)
find_package(B)

add_executable(myprog
  main.cpp
  tools.cpp
  utils.cpp
)
target_link_libraries(myprog
  A::libA
  B::core
  B::net
)

Ненужные технические детали, являющиеся внутренними деталями пакетов A и B болеше не нужно прописывать в своём проекте. Причём библиотек с которыми мы линкуемся, физически, может не существовать. add_library(header_only INTERFACE) с последующим навешиванием требуемых путей для заголовков позволяет описывать через такой же механизм header-only библиотеки.

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

Итак, сегодняшний cmake позволяет сказать хочу библиотеку A, если она в твоей системе не установлена, то я загружу её из такого-то git/svn/hg/cvs репозитория (либо просто архивом по указанному урлу). И прозрачно пользоваться этой библиотекой только линкуясь с ней, но... Что если хочется выкачать эту библиотеку, поправить её и пособираться с модифицированной версией прежде чем отправить свои патчи в апстрим? И тут тоже уже всё готово (по крайней мере в теории). Существует понятие пользовательского репозитория куда может прописывать себя библиотека, после чего find_package в вашем проете будет подхватывать цели из дерева сборки выкачанной и модифицированной вами библиотеки. Выглядит довольно вкусно.

Внимание! Содержимое предыдущего абзаца уже чистая теория ибо этот функционал cmake'а я пока самостоятельно не опробовал!

И напоследок хочу привести пример рабочего кода. Вот так я описал опциональную зависимость от QJSON в совей библиотеке QRemoteSignal. Немного менее компактно как в системах сборки современных языков, но намного компактней и удобней чем можно было ожидать в мире C++. А главное, вектор развития cmake намекает на то, что когда-нибудь и в C++ проектах зависимости будут управляться автоматически системой сборки с минимальными усилиями. Ну хотя бы лет через 5-10 :)

четверг, 5 ноября 2015 г.

Безопасные и удобные битовые маски

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

Сама переменная имет численный тип и глядя на неё совершенно неочевидно, что её значения это комбинации битов описанных вон тем перечислением. Эти знания оказались не записаны в коде и должны как-то иначе быть доведены до коллег по команде. Из за этого в современном мире C++ многие предпочитают std::vector<MyEnum> либо std::set<MyEnum>, что с учётом ограниченных удобств предоставляемых STL контейнерами, в коде, всё же, проигрывает по читаемости битовым маскам.

Но... Выход существует. Немного подумав, можно написать следующий шаблонный класс:

template<typename E, typename T = uint64_t>
class Bitmask {
public:
  constexpr
  Bitmask(E e): val(mask(e)) {}
  constexpr
  Bitmask(const Bitmask &other) = default;
  constexpr
  Bitmask & operator= (const Bitmask &other) = default;

  constexpr
  Bitmask operator| (Bitmask rhs) const {
    return Bitmask(val | rhs.val);
  }
  constexpr
  Bitmask operator| (E e) const {
    return Bitmask(val | mask(e));
  }

  constexpr
  Bitmask operator& (Bitmask rhs) const {
    return Bitmask(val & rhs.val);
  }
  constexpr
  Bitmask operator& (E e) const {
    return Bitmask(val & mask(e));
  }

  constexpr
  operator bool () const {
    return val != 0;
  }

private:
  constexpr
  Bitmask(T t): val(t) {}

  constexpr
  static T mask(E e) {
    return T(1) << static_cast<T>(e);
  }
private:
  T val;
};

template<typename E, typename T = uint64_t>
constexpr
Bitmask<E, T> operator| (E lhs, Bitmask<E, T> rhs) {
  return Bitmask<E, T>(lhs) | rhs;
}

template<typename E, typename T = uint64_t>
constexpr
Bitmask<E, T> operator& (E lhs, Bitmask<E, T> rhs) {
  return Bitmask<E, T>(lhs) & rhs;
}

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

enum class Perm {Read, Write, Exec};
constexpr
Bitmask<Perm> operator| (Perm lhs, Perm rhs) {
  return Bitmask<Perm>(lhs) | Bitmask<Perm>(rhs);
}

Ну и, собствнно, можно совершенно просто и удобно делать вот так:

Bitmask<Perm> mask = Perm::Read | Perm::Write;
if (mask & Perm::Exec)
  std::cout << "Executable" << std::endl;

Единственное, что не получается сделать в этом подходе, так это корректно написать оператор инверсии. Просто выполнив ~val мы взведём биты, которые невозможно взвести используя элементы enum'а и нарушим работу опертора приведения к bool. Правильным решением была бы следующая реализация инверсии:

template<typename E>
constexpr
std::initializer_list<E> all_elements();

template<typename E, typename T = uint64_t>
class Bitmask {
public:
...
  constexpr
  Bitmask operator~ () const {
    return ~val & allowed();
  }
private:
  constexpr
  static T allowed() {
    T res;
    for (E e: all_elements<E>())
      res |= mask(e);
    return res;
  }
...
};

Единственная проблема, это реализация функции all_elements. К сожалению в современном C++ она невозможна, а посему ждём и верим что SG7 успеет определиться с тем как такие вещи делать до выхода C++17. Например N4428 уже позволяет при помощи std::index_sequence и variadic template сдлеть невозможное возможным.


среда, 15 июля 2015 г.

Дебажим компиляцию

Представте себе ситуацию... Пишется обобщённый код на C++, в зависимости от шаблонного типа или типов выводятся типы каких-то внутренних переменных или дочерних типов, используя для этого как стандартные трансформаторы типов из type_traits, так и самописные. И вот, в результате система ведёт себя так, будто финальный тип оказался не таким как ожидалось. Я, разумеется, имею в виду ситуацию когда код скомпилировался в режиме -Wall -Werror. Как бы на этапе компиляции посмотреть что кого? Выход есть и он прост:

template<typename T>
void dbgtype(T) {
  static_assert(std::is_same<void, T>::value, "!!!");
}

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

З.Ы. Эта шутка здорово сегодя помогла, когда я писал кое какие геометрические алгоритмы которые должны работать как числами с плавающими точками (координаты в проекции географических данных на плоскость), так и с целочисленными типами (экранные координаты в пикселях). Для целочисленных типов мне нужно было найти соответствующий им беззнаковый тип, под размеры областей, а для чисел с плавающей точкой требовалось использовать немодифицированный тип. std::make_unsigned делал для меня первую часть задачи правильно, но не компилился для double, пришлось его дорабоать, а потом поискать баги в том что получилось.

четверг, 21 мая 2015 г.

systemd-nspawn для разработчика

На данный момент я работаю в проекте который в качестве целевой платформы установки использует Debian 7 при этом хочется переехать на Debain 8, а сам я сижу на Arch Linux. В результате часто требуется быстренько оказаться в правильном окружении и пособирать свои последние правки. Чаще всего для этого я использовал VirtualBox как наиболее просто конфигурируемое решение, но всегда хотелось побаловаться с лёгкими контейнерами. Правда, всегда отпугивала скорость разворачивания таких решений. Я экспериментировал с LXC и systemd-nspawn, но в первом случае разворачивание виртуалки занимает больше времени чем хочется, а во втором случае всё гладко только если гостевая система использует systemd.

Но вот, свершилось, Debain 8 использует systemd по умолчанию и его можно быстренько разворачивать. Правда, кто-то успел немного подгадить с другой стороны. Теперь в арче сервис systemd-nspawn@ по умолчанию предлагает иметь противоестественные сношения с настройкой идеологически правильного разделения сети между контейнерами добавив ключ --network-veth. Раньше этот сервис позволял запустить контейнер так, что он просто видел хостовый интерфейс и им пользовался, что совершенно не подходит для облачных решений (для которых есть отличные и мощные проекты, такие как OpenVZ и LXC), но очень удобно для нужд разработчиков (а именно для этих целей и позиционировалась разработка systemd-nspawn). Дабы раз и навсегда искоренить этот печальный недостаток, копируем этот сервис из /usr/lib/systemd/system в /etc/systemd/system и уничтожаем использование ненужной "идеологической правильности".

Итак создаём гостевуху:

yaourt -S debootstrap # Если ещё не установлен
sudo debootstrap jessie /var/lib/container/debian8 http://mirror.yandex.ru/debian
sudo systemd-nspawn -D /var/lib/container/debian8
Мы оказались в сверх-минималистичном дебиане с целью задать пароль пользователю root и установить пакет dbus (без него утилита machinectl не сможет подключиться к запущенному контейнеру). Выполнив эти две нехитрые операции нажимаем Ctrl+D чтобы вернуться в основное окружение и честно загрузив контейнер входим в него через парадное крыльцо для дальнейшей настройки:
sudo systemctl start systemd-nspawn@debian8
sudo machinectl login debian8

Вот готово почти всё. Точнее для одноразового использования готово вообще всё. А вот если контейнер планируется использовать для тестов регулярно, то имеет смысл поставить туда ssh и научить systemd автоматически запускать виртуалку при попытке в неё зайти по этому самому ssh. Для этого пишем файл на хосте
/etc/systemd/system/systemd-nspawn@debian8.socket:

[Unit]
Description=The SSH socket of debian8-nspawn container

[Socket]
ListenStream=4022

[Install]
WantedBy=sockets.target
А в гостевой ситеме надо отключить безусловный автозапуск SSH и настроить его активацию через сокет добавив два файла:
/etc/systemd/system/sshd.socket:
[Unit]
Description=SSH Socket for Per-Connection Servers

[Socket]
ListenStream=4022
Accept=yes

[Install]
WantedBy=sockets.target
и /etc/systemd/system/sshd@.service:
[Unit]
Description=SSH Per-Connection Server for %I

[Service]
ExecStart=-/usr/sbin/sshd -i
StandardInput=socket

[Install]
WantedBy=multi-user.target
Alias=sshd.service
После чего в контейнере переключаем SSH на активацию через сокет:
sudo systemctl disable sshd.service
sudo systemctl enable sshd.socket
И на хосте активируем сокет запуска машины:
sudo systemctl enable systemd-nspawn@debian8.socket

Ну вот теперь можно настроить профиль в Konsole и быстренько заходить в виртуалку в два клика. Запуск контейнера до состояния принятия ssh соединения у меня занимает около 2х секунд, что несравнимо меньше времени ожидания старта VirtualBox виртуалки, а сборка кода с помощью ninja эффективно параллелиться по всем процессорам демонстрируя великолепную производительность.

понедельник, 11 мая 2015 г.

RAII вокруг C в одну строку

Работая C++ программистом я очень часто сталкиваюсь с библиотеками написанными на C. В этом языке (в отличии от C++) есть простой и удобный способ поддерживать прямую ABI совместимость библиотек. В публичных хедерах имеем только forward-declaration структуры и функции принимающие её через указатель. А все её потроха оставляем исключительно внутри библиотеки. Так работает libevent, sqlite и очень многие другие беблиотеки написанные на C. Шаблон pImpl стремиться скопировать эту модель достижения стабильности бинарного интерфейса в C++, но не закрывает всех проблем.

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

В C++11 появляетя удобный класс которые способен устранить необходимость написания таких RAII обёрток для каждой новой сущности, это std::unique_ptr. Так на хабре можно найти пример exception-safe обёртки вокруг libevent'овского веб сервера использующего только unique_ptr для своевременной чистки ресурсов. Правда код при этом получается не особо читабельным, ибо всюду мы видим множество длинных специализаций этого шаблонного типа использующих decltype для проведение сеансов гадания по руке указателю на функцию с целью выведения её типа.

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

auto dir = scoped(::opendir(path), ::closedir);  
for (dirent *entry = ::readdir(dir.get()); entry != nullptr; entry = ::readdir(dir.get())) {  
   ...  
}  
выглядит куда понятней чем такой:
 std::unique_ptr<DIR, decltype(::closedir)> dir{::opendir(path), ::closedir};  
 for (dirent *entry = ::readdir(dir.get()); entry != nullptr; entry = ::readdir(dir.get())) {  
   ...  
 }  
Определение переменной dir в первом случае компакто и мгновенно разбирается глазами. Да и функция scoped пишется один раз и всегда спокойно кочует из проекта в проект позволяя в одну строку прямо по месту создавать RAII обёртки вокруг C-шных структур:
 template<typename T, typename Del = std::default_delete<T>>  
 inline auto scoped(T *t, Del &&del = std::default_delete<T>()) {  
   return std::unique_ptr<T, Del>{t, del};  
 }