Непрерывная интеграция с помощью Jabber. Изобретаем велосипед?


    В любом серьезном проекте в определенный момент встает вопрос организации процесса CI и, следовательно, вопрос о выборе средства для его автоматизации из широкого ряда представленных сейчас на рынке. Часто бывает, что при всем богатстве выбора найти подходящий вариант непросто, а иногда готовое решение является попросту слишком сложным, и выделять ресурсы на его поддержку нет ни желания, ни возможности. Кроме того, не хочется класть все яйца в одну корзину и иметь единственный сервер для сборки, тестирования, хранения дистрибутивов…

    В нашей компании родилась идея — поддержать мировую тенденцию децентрализации и разделить CI-сервер на несколько машин, связав их между собой универсальным протоколом, доступным и машине, и человеку. Похоже на изобретение велосипеда? Вовсе нет.

    На роль подобного средства общения отлично подошел протокол XMPP, он же Jabber, который являлся стандартом внутренней коммуникации компании, и следовательно, каждый разработчик, тестировщик и любой другой участник CI имел на своем рабочем месте IM с поддержкой Jabber. Соответственно, и любое готовое CI-решение, имеющее поддержку Jabber-плагина (а таковой сейчас имеют чуть ли не все серверы непрерывной интеграции), — легко вписывается в общую схему.

    Для реализации этой идеи мы сделали следующее.

    • Зарегистрировали в ростере Jabber учетные записи серверов buildserver, testserver, deployserver, hudson, создали для каждого группы, которым доступны для общения данные «пользователи», наполнили эти группы, определив таким образом список реальных пользователей, которым будет доступен тот или иной функционал.

    Написали язык общения (на первое время — с минимальным набором команд), способный расширяться для реализации новых задач и простой в освоении для конечного пользователя. Команды: build, test, deploy, kill, ban, approve, help. Для всех этих команд в процессе внедрения CI добавлялись параметры, позволявшие существенно расширить их функциональность.

    • Разработали шаблон элемента CI, использующего библиотеку jabber-net.dll, содержащего обработчики входящих команд и осуществляющего отсылку исходящих по результатам выполнения данных обработчиков.

    Примерный шаблон на C#:

    using System;
    
    namespace Jabber
    {
    	public class Jabberclnt : IDisposable
    	{
    		private jabber.client.JabberClient clnt = null;
    
    		~Jabberclnt()
    		{
    			Dispose(false);
    		}
    
    		public void Dispose()
    		{
    			Dispose(true);
    			GC.SuppressFinalize(this);
    		}
    
    		public void Dispose(bool disposing)
    		{
    			if (clnt != null)
    			{
    				clnt.Close();
    				clnt.Dispose();
    				clnt = null;
    			}
    		}
    		
    		public void Init(string user, string pwd)
    		{
    			clnt = new jabber.client.JabberClient ();
    			clnt.PlaintextAuth = true;
    			clnt.Server = "pt.int";
    			clnt.NetworkHost = "jabber.ptsecurity.ru";
    			clnt.Port = 5222;
    			clnt.User = user;
    			clnt.Password = pwd;
    			clnt.AutoLogin = false;
    			clnt.OnMessage += clnt_OnMessage;
    			clnt.Connect ();
    		}
    
    		void clnt_OnMessage (object sender, jabber.protocol.client.Message msg)
    		{
    			if (null == msg.Body)
    				return;
    			Console.WriteLine(string.Format("[{0}] {1}:{2}", DateTime.Now.ToString(), msg.From.User, msg.Body));
    			//обработчик сообщения
    		}
    
    		public bool SendMessage(string user, string msg)
    		{
    			bool res = true;
    			try
    			{
    				var jabberuser =new jabber.JID(user); 
    				var jabberMessage = new jabber.protocol.client.Message(clnt.Document)
    				{
    					Body = msg,
    					To = jabberuser
    				};
    
    				clnt.Write(jabberMessage);
    			}
    			catch (System.Exception ex)
    			{
    				res = false;
    			}
    			return res;			
    		}
    	}
    
    	class Program
    	{
    		static void Main(string[] args)
    		{
    			Jabberclnt clnt = new Jabberclnt();
    			clnt.Init("username", "password");
    
    			System.Threading.Thread.Sleep(1000);
    			
    			clnt.SendMessage("buildserver@pt.int", "test");
    			System.Threading.Thread.Sleep(1000);	
    		}
    	}
    }	
    


    • Подняли сервер Hudson с плагинами im и jabber — для возможности общения с остальными участниками.

    Реализовав эту идею, мы получили такую схему:



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

    Как я уже отметил, разработчик получает в виде IM c Jabber (у нас это Miranda) готовое средство управления и нотификации любого сервера — с учетом, конечно, наличия прав доступа к нему, прописанных в ростере Jabber.

    Схема взаимодействия участников этой цепочки при полном цикле выглядит следующим образом.
    Разработчик инициирует процесс.



    BuildServer сообщает об успешности и передает управление TestServer.



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



    При завершении цикла оповещаются тот, кто запустил процесс, — и список заинтересованных лиц.



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



    Итак, данная схема помогает решить следующий ряд вопросов.

    • Нет ограничения в выборе готового CI в различных проектах и подразделениях: они легко интегрируются в общую структуру компании.
    • Разделение ресурсов: загруженность buildserver не влияет на задачи тестирования и развертывания.
    • Готовое средство управления (нотификации, автоматизации) при использовании библиотеки jabber CI становится доступным из любого приложения, библиотеки или сценария.
    • Единый формат команд позволяет видеть полную картину того, что происходит сейчас в CI, и дает возможность пользователю вмещаться в любой стадии, имитируя любой сервер и таким образом сохраняя чистоту процесса.

    Приглашаем обсудить достоинства и недостатки данного подхода. Если интересны подробности — задавайте вопросы: поделимся опытом :)

    P.S. Доклад на эту тему был представлен на Application Developer Days (презентация и видео).
    Positive Technologies
    Company

    Comments 25

      +25
      clnt = new jabber.client.JabberClient ()

      Мне всегда было интересно, в чём тайный смысл выбрасывать гласные буквы из названий переменных? Неужели clnt чем-то лучше, чем client?

      client = new jabber.client.JabberClient ()
        –2
        Если есть класс client, то переменную можно назвать cln clnt. С полным именем букв больше и писать уже нужно с разными префиксами/постфиксами, например, client_* а сокращать до гласных звучит некрасиво — clie
          +8
          Классы вообще-то с большой буквы начинаются: Client.
          В одной библиотеке вообще крайности в этом плане видел: grdntsnptrns
          Возможно автору оно кажется понятным, но я когда вижу такой ужас — сильно ломаю себе голову и глаза. И таки не вижу в чём проблема в топике было написать так, ведь слово client ни с чем не конфликтует:
          client = new jabber.client.JabberClient ()
          +15
          У нх здсь свяо атмсфера.
            +6
            нх здс св тмсфр
              +7
              И ещё никто про иврит не пошутил?
                +3
                Кажется, нет. Давай!
                  0
                  Сомневаюсь в том, что комментирующие в курсе особенностей еврейской письменности и других примеров консонантного письма.
              –2
              Часто так сокращаю локальные «одноразовые» переменные, т.к. это уменьшает объем кода — особенно там, где одна переменная появляется в одной и той же строчке более одного раза.
              Сам прием может быть вполне оправданным, вопрос в том, как его использовать.
                +4
                А смысл так сокращать объём кода? Не надо делать работу обфускатора заранее.
                  –3
                  Вопрос не в килобайтах, конечно. Часто улучшает читабельность — когда сокращение достаточно интуитивно + значение переменной понятно из контекста, а на выходе мы получаем, ну, скажем, уменьшение длины строчки кода на 5-10 символов. Меньше проблем с полосами прокрутки, разбивкой строк на несколько, просто беганьем глазами от начала до конца строки и т.д.
                  Особенно актуально для Java, где длина строчки кода имеет свойство разрастаться до каких-то совершенно неприличных (и нечитабельных, да) объемов.
                    +1
                    Но ведь значительно ухудшается поддержка кода. Особенно когда это свойство класса (пусть даже и приватное) и на экране не видно объявления и имени класса, как здесь:
                            public void Dispose(bool disposing)
                            {
                                if (clnt != null)
                                {
                                    clnt.Close();
                                    clnt.Dispose();
                                    clnt = null;
                                }
                            }
                      –1
                      Ну я же говорю, вопрос в том, как использовать. Прием, как прием, не хуже любого другого, есть подходящие и неподходящие юскейсы.
                  +1
                  Локальная «одноразовая» встречается в одной строчке несколько раз… назовите её «a».
                +2
                Хм…
                Берем Hudson на котором создаем проекты по сборке, статическому анализу и, например, развертыванию в тестовом окружении. Поднимаем ноды (сборочная/тестовая машина/машины и т.п.), например, по ssh, а в настройках проекта указываем, какие ноды при этом задействовать. Нотификация и аутентификация по вкусу.
                И в виде стандартного решения получаем распределении нагрузки по нодам при централизованное управлении.
                  0
                  Централизованное управление как раз то, чего хотелось избежать. Суть не в распределении нагрузки — она решается различными способами. Например, IncrediBuild-ом и ему подобными системами распараллеливания вычислений. Цель как раз децентрализация управления. Части народа из QA вообще в процессе работы не нужно общаться со сборочным сервером, и не хочется каждую задачу тестирования создавать на Hudson-е. А комбинацией параметров команды можно запустить множество различных конфигураций тестирования.
                    0
                    Ну не знаю…
                    В Jenkins, располагая вычислительными средствами и имея определенные задачи, я без труда могу управлять правами пользователей, задавать зависимости проектов, а так же создавать как параметризованные так и мульти-конфигурационные проекты и много другое. Благодаря этому ряд вопросов решается, так сказать, сам собой.
                    Например:
                    — пользователь Х должен иметь право запускать задачу на сборку проекта, в случае успешной сборки которого, автоматически запустится задача по его разворачиванию;
                    — предоставить возможность пользователю при запуске сборки определить параметры сборки (например Debug/Release), но при этом не иметь доступа к исходным кодам;
                    — пока идет сборка проекта А на ноде N, никакие проекты не запускать, а предварительно собрать проект C.
                    А как эти задачи будете решать вы?

                    Минусы? Да согласен проектов становиться очень много, но это не беда — создаются «вьюхи» где группируются проекты по смыслу и назначаются в качестве дефолтных соответствующим пользователям.
                    Но зато плюсы: простота реализации и администрирования.
                      0
                      Простота администрирования… Как раз мне кажется в данном случае сложность администрирования. Как Вы справедливо заметили, число проектов не упрощает процесс.

                      Простота реализации… Собственно наш способ он тоже не такой сложный. А поддержка постоянно меняющихся конфигураций стоит в Вашем случае больше. Ну а готовые фичи Jenkinsa (или, например, Bamboo, который захотели попробовать наши коллеги из смежного подразделения) никто же не отменяет. Вполне возможно, что мы придем в итоге к нескольким готовым CI в цепочке, дабы не лишать никого возможности поэкспериментировать.

                      По порядку:

                      1. Именно такую задачу мы и решаем, напрмер команда build /trunk /deploy, в случае успешной сборки BuildServer передает управление на DeployServer.

                      2. Ограничить доступ к исходникам — очень актуальная задача для нас. Правда не совсем понимаю при чем тут разные конфигурации.

                      Запуск проекта с разными конфигурациями — это собственно все так же, на обработчике Jabber сообщений, есть конфигурация по умолчанию, при указании конкретной конфигурации она передается собственно платформе сборке, в нашем случае msbuild. Проблема связанная с безопасностью исходников существует в таком виде, что на сборщике хранится весь код, и нужно исключить возможность внедрения инъекции в допустим vcproj (хотя они делаются cmake-ом, но не в этом суть) в виде, например, prebuild-step-ов. Это решается двумя путями:

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

                      — и собственно отсутствием какого-либо файлового доступа на BuildServer, внедренный код не сможет выложить исходники никуда далее этой машины (тем более по локалке), а на Storage выкладываются файлы только по опреденным правилам и проверкам

                      3.Если это A и С — это подпроекты в общем проекте и С является зависимостью A, то решение на уровне проекта, допустим так
                      <?xml version="1.0" encoding="windows-1251"?>

                      ...



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

                      Тогда, не дорабатывая ничего, у нас это решится так:

                      build /C build /A, управление после сборки одного проекта передастся опять на BuildServer

                      А паралеллятся не проекты между собой, а один проект (я имею ввиду проект в широком смысле, содержащий некоторое количество vcproj-ей, csprojt-ей и т.д.)

                      Даже если vcproj один, то распараллеляться cl-подпроцессы, и только линкер будет идти в один поток — это решается технологией unitybuild, успешно внедренной.
                        0
                        Прошу извинить, код в предыдущем сообщении не отобразился полностью, он ниже:
                          0
                          Спасибо за развернутый ответ. Но все-таки не все из описанного мне понятно — я списываю это на отсутствие у себя соответствующего опыта.
                    +5
                    Берём ботнет IRC-серверов, назначаем каналы каждому отделу, софт пусть общается по telnet. Profit!
                      +2
                      Критика в сторону написания статьи:
                      Я не знаком был с аббревиатурой CI и большой первый абзац прошел как шум ни о чем до самого конца, где все же есть объяснение «единственный сервер для сборки».
                        0
                        Согласен, наверное нужно было сделать вводную часть про CI.
                        0
                        Есть причины, почему джаббер, а не емейл, например? ;)
                          0
                          Джаббер — потому что просто и быстро, и Miranda была у всех на машинах, и потом в почту не так оперативно читать и писать.

                          Но направление мыслей у нас с Вами сходится :-) Сейчас у нас активно внедряют MS Lync как стандарт корпоративной связи, для которого предусмотрен Lync 2010 SDK, с помощь которого мы научим наш CI понимать кроме джаббера и Lync. И тогда появzтся дополнительные возможности интеграции с Exchange, управление CI из Outlook, сохранение пропущенных результатов и т.д.

                        Only users with full accounts can post comments. Log in, please.