Это сайт — моя персональная записная книжка. Интересна мне, по большей части, история, своя жизнь и немного программирование.

«Переговорки»: панихида

Проект «Переговорки» — внутренний веб-сервис «Яндекса» для бронирования переговорок, синхронизированный с Microsoft Exchange. Это, наверное, самый сложный проект, который мне приходилось делать за всю жизнь.

Используется он теми, кто не может установить себе Outlook — ребятами с «Маками» и разнообразными «Линуксами» (а таких очень много). Кроме того, что от Exchange мы будем постепенно уходить, а интерфейс бронирования останется без изменений.

Никогда я ещё так не выкладывался, так не радовался, не расстраивался, не оставался с таким упорством допоздна в офисе. Звучит просто — вытаскивать время от времени данные из Exchange и отсылать обратно новую бронь. К сожалению, MS Exchange, который использовался тогда в компании, не умел общаться с внешним миром никак иначе, кроме протокола MAPI — бинарному исчадию ада, причём не просто через MAPI, а через так называемый MAPI через RPC — диалект DCE-RPC, который называется MSRPC.

Помню, первое, что пришло на ум использовать — impacket, модуль для Python, который позволял работать с «сырыми» пакетами, но к нему не было документации, а примеров в сети нашлось всего ничего — несколько эксплоитов и только. Впрочем, разобраться всё-таки удалось. Правда, попробовав описать вызовы верхнего уровня, я убедился, что это работа на года и решил поискать что-нубудь ещё.

Из бесплатных библиотек уровнем повыше нашлась одна-единственная — libmapi, поскольку она требовала наличия альфы Samba4 строго определённой версии, а на машине уже стояла Samba3 (через которую мы работали), решено было попробовать её скомпилировать. Оказалось, что такие попытки в компании уже предпринимались, но закончились неудачей. Решив что попытка не пытка, за несколько дней я её всё-таки собрал и запустил.

Это и положило начало проекта «Переговорки».

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

Через несколько дней после того как я в первый раз скомпилировал libmapi, тестовые утилиты на Си благополучно подключились к Exchange и уже умели отпределять дают им правильные данные для авторизации или нет. Эти несколько дней ушли на то, чтобы разобраться как создавать профиль, как настраивать библиотеку и Samba4 для работы и так далее. Оставалось как-то состыковать всё это дело с Python.

1275 строк основного модуля, 647 строк файла описаний структур MAPI, 4046 констант, 457 строк для описания Pid-свойств. А сколько не заработало, сколько пришлось переписать. Самая рутинная работа была — искать по всем исходникам libmapi и Samba описания структур, пытаться сообразить как их переписать на Python и состыковать со всеми остальными. Ребята, придумавшие модуль ctypes для Python, большие молодцы.Правда мне пришлось придумать и реализовать парсер для расширенного синтаксиса, чтобы описывать динамические структуры MAPI, не знаю как сейчас, а тогда libmapi не умел читать повторяющиеся встречи (кажется, и теперь не умеет), так что читать и описывать их приходилось на самом низком уровне, а значит как-то хранить эти данные и уметь ими манипулировать.

Ничего, пара дней корпения над бинарной распечаткой с последующими уточнениями в противоречивой документацией от Microsoft и я написал класс, который такие встречи умеет разбирать. Впрочем, это было уже позднее, а сначала надо было догадаться что делать после логина. Из документации у libmapi — только перечень функций и исходники на Си. Пришлось внимательно просматривать первое и читать второе. Тут нельзя не упомянуть Сергея Андрюхина, несколько очень ценных его идей и исследований вошли в тот код. Кстати, местами исходники на Си написаны так плохо, что приходилось упрощать код — не всё, что можно позволить себе в Си, можно позволить себе в менее шустром «Пайтоне».

Разобраться с порядком вызова этого великолепия было непросто, плюс ещё множество более мелких проблем, вроде странного формата хранения времени (Exchange хранит число минут с 1 января 1601 года). Плюс утечки libmapi, которые пришлось изолировать, решать проблему с передачей данных в родительский процесс, мучаться с Python 2.4, который, как оказалось, никогда не освобождает память, выделением памяти на куче «про запас» через Python API (спасибо ctypes), определением по косвенным признакам, что память закончилась, пора отдать данные в родительский и умереть, и так далее.

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

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

Спасибо Саше Покатилову, который правил мой код, пока я был в отпуске и занимался Django-частью проекта, с ним же мы проектировали базу. Например, мы решили просто размножать повторяющиеся встречи на год вперёд, избежав тем самым множество проблем. Год — это так называемое «окно бронирования», дальше этого срока наш Exchange встречи не видит. Каждый день Сашин механизм достаточно оптимально размножал всречи ещё на один день дальше, мой класс при помощи полезнейшего модуля dateutil.rrule переводил по достаточно хитрым правилам повторяющиеся встречи Exchange в список дат.

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

Думаю, если попробовать вспомнить всё, хватит на небольшую книгу… Я рад, что эту невероятную задачу удалось решить. С моей стороны, как мне кажется, это 100% моей «мощности» как программиста. Будущее покажет, но пока все задачи я буду оценивать по этой.

К счастью, в новом Exchange, который появился у нас в компании, появилась поддержка SOAP и «Переговорки» переделали с использованием этого протокола. Есть предварительное согласие на выкладывание модуля, который у меня тогда получился в интернет. Пожалуй, сейчас он бесполезен (хотя, может авторы libmapi запрограммируют на его примере чтение повторяющихся встреч, попробую им его кинуть), но пусть он всё равно полежит у меня, как памятник полугоду моей жизни.

31 комментарий
almalinka.blogspot.com 2009

На Сашу лучше поставить ссылку на его твиттер :-)

Евгений Степанищев (bolknote.ru) 2009

Комментарий для almalinka.blogspot.com:

Гм… а чего это он стёр ярушку? Ок, пусть будет на твиттер.

Азат Разетдинов (razetdinov.ya.ru) 2009

Поздравляю. А ведь кто-то мне говорил, что Болка не брали в Яндекс, т. к. не могли предложить достойные задачи. Значит, всё-таки нашли :)

hazan (hazan.ya.ru) 2009

У нас был просто еще один тип контакта — «переговорка», и при бронировании встречи этот контакт включался в список участников. При просмотре списка участников было сразу видно — занята переговорка или нет.

Евгений Степанищев (bolknote.ru) 2009

Комментарий для razetdinov.ya.ru:

Я рассказывал кому-то (может и тебе тоже), что не представлял чем тут буду заниматься :)

Евгений Степанищев (bolknote.ru) 2009

Комментарий для hazan.ya.ru:

У нас только на одном этаже три переговорки, а этажей шесть и есть ещё другие офисы.

zencd.livejournal.com 2009

Сколько себя помню — люди всё трахаются с эксчейнджем (лучшего глагола не подобрал). Это какое-то отдельностоящее явление.

А была возможность отказаться от него?

Евгений Степанищев (bolknote.ru) 2009

Комментарий для zencd.livejournal.com:

Чтобы отказаться, нужно предложить что-то взамен. В одночасье этого не сделаешь, но потихоньку к этому придём.

zencd.livejournal.com 2009

А какие есть альтернативы, интересно? (Я не в теме.)

Евгений Степанищев (bolknote.ru) 2009

Комментарий для zencd.livejournal.com:

Обычный MAPI-сервер + CalDAV

astur (astur.net.ru) 2009

Комментарий для Евгения Степанищева:

Триллер года, однозначно ;)

Hazan (hazan.ya.ru) 2009

Комментарий для Евгения Степанищева:

Я, наверное, не совсем корректно выразился. У нас КАЖДАЯ переговорка в каждом офисе по всему миру имеет собственный контакт в аутлуке. Соответственно в аутлуке в любой момент можно посмотреть бронирование любой переговорки по всему миру, и кто ее забронировал.

hshhhhh.name 2009

Комментарий для Евгения Степанищева:

Я поофтоплю.
Надо выбрать дни рождения пользователей с сегодня и в течении недели.
В базе ( mysql 5 ) хранится дата рождения пользователя (с годом).
Я делаю выборку по номер дня в году дня рождения больше сегодняшней даты и меньше сегодняшней даты + неделя.

Проблема возникает когда получается пограничное состояние: 28 декабря — 4 января.
То есть 363 день меньше чем 4.

Я сделал запрос который выбирает как бы дни рождений, но он жуток: http://paste.ubuntu.com/348139/

А есть ли какой-нибудь более адекватный вариант?

BresSergey.com 2009

Комментарий для hshhhhh.name:

А не нравится «до mysql-запроса» сгенерить на сервере список из семи строк (для сегодня это «2812», «2912», «3012», «3112», «0101», ...) а потом where date_format(bla) in (этотсписок)?

Евгений Степанищев (bolknote.ru) 2009

Комментарий для hazan.ya.ru:

У нас так же.

hshhhhh.name 2009

Комментарий для bressergey.com:

хм. ну это нравится больше в плане скорости, правда я бы все равно делал через мускульный запрос: where DATEFORMAT(userBirthdate, ’%c%e’) IN (DATEFORMAT(CURDATE(), ’%c%e’), DATEFORMAT( ADDDATE(CURDATE()), ’%c%e’), INTERVAL 1 DAY,
DATEFORMAT( ADDDATE(CURDATE()), ’%c%e’), INTERVAL 2 DAY)

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

allbefine.livejournal.com 2009

Комментарий для Евгения Степанищева:

Ты уж, Женя, прости, но у нас — далеко не супер айти компании — проблем таких никогда не было — переговорки бронируются через Эксчендж без всяких проблем, задержек во времени нет. Как? Хз :) Спросить у админов надо.

Переговорок — по две-три на семи этажах + на одном этаже 8 штук. Никаких проблем компания (~2000 человек) не испытывает.

У меня ощущение что вы сами придумали проблему :)

Hazan (hazan.ya.ru) 2009

Комментарий для allbefine.livejournal.com:

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

Евгений Степанищев (bolknote.ru) 2009

Комментарий для allbefine.livejournal.com:

Я ожидал, что люди сходят по ссылке и почитают. Проблема в том, что в компании много (если не большинство) людей с «Маками» и разнообразными «Линуксами».

Кроме того, от Exchange потихоньку будем избавляться. Он уйдёт, а интерфейс бронирования останется.

Евгений Степанищев (bolknote.ru) 2009

Комментарий для hazan.ya.ru:

Дописал второй абзац для тех, кто ленится сходить по первой ссылке.

allbefine.livejournal.com 2009

Комментарий для Евгения Степанищева:

Странно, через owa можно бронировать что угодно и откуда угодно — мак, линукс. Тот же веб-интерфейс. Для мака вообще есть нативное приложение кстати.

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

Евгений Степанищев (bolknote.ru) 2009

Комментарий для allbefine.livejournal.com:

Это не ты мне звонил 15 минут назад? Для Мака ничего не было, когда мы летом этот проект запускали. OWA нормально работает только в IE (или работал в Exchange 2004, который мы использовали), да и я уже написал — есть потребность вообще отказаться от Exchange.

hshhhhh.name 2009

Комментарий для Евгения Степанищева:

Да вы не злитесь, Болк! Возьмите ка отпуск на 2 дня!

allbefine.livejournal.com 2009

Комментарий для Евгения Степанищева:

я, я звонил :)

шумно у вас, корпоративка :)

Евгений Степанищев (bolknote.ru) 2009

Комментарий для hshhhhh.name:

Так я и не злюсь, вон allbefine мне звонил, подтвердит.

allbefine.livejournal.com 2009

Комментарий для Евгения Степанищева:

Да, да, не злишься :)

Особенно смешно было запустить яндекс и написать запрос «яндекс офис телефон» :)

Евгений Степанищев (bolknote.ru) 2009

Комментарий для allbefine.livejournal.com:

А чего звонил-то?

Owner (companyowner.myopenid.com) 2009

Я обычно на это говорю — что не делается, то к лучшему. В конце-концов вы можете переиспользовать UI от этой системы (что вы и сделали подменив backend SOAP’ом).

Но имхо можно было уже после 5 недель секса с Exchange задуматься, может есть альтернативные решения — купить, например, новый exchange. Чисто экономически это было не выгодно для компании:

  1. Затрата на программиста — зп + налоги * 6 месяцев будет явно выше чем новая версия exchange.
  2. Было затрачено много времени и сил на Технологию Прошлого. Весь experience с MAPI и старым exchange’ом можно уже забыть как страшный сон.

Это, кстати, класcика ошибки management’a, которая должна была быть исправлена уже в середине цикла. Я бы выпорол руководителя проекта за это... А вот твоя заслуга тут бесспорно огромна и выложился ты действительно на все 100%. Снимаем шляпы, господа комментаторы!

Евгений Степанищев (bolknote.ru) 2009

Комментарий для companyowner.myopenid.com:

Ты себе не представляешь сколько стоит лицензия на Exchange для такой большой компании. Многие десятки тысяч долларов, возможно — сотни тысяч, плюс работа департамента экплуатации (нужно всё настроить, оттестировать всех роботов и плавно переключить). Это очень дорого.

Max 2015

Не ваш ли случайно продукт? https://www.yarooms.com/

Евгений Степанищев (bolknote.ru) 2015

Комментарий для Max:

Нет.