Didn't google "how to run horizon integration tests" unswer in 10 seconds. Making note how to do it.

My development environment usually looks like: macbook + VM with Ubuntu Server or CentOS without GUI. I try to run all tests inside VMs. In case of Selenium tests, I need some preparation for it:

  1. $ sudo apt-get install firefox
    this command will install FireFox. Selenium has WebDriver for it out of the box
  2. $ sudo apt-get install xvfb
    install Virtual Frame Buffer for X Server (https://en.wikipedia.org/wiki/Xvfb)
  3. Run tests:
    • Simple way for OpenStack Horizon:
      ./run_tests.sh --integration --selenium-headless
    • Hard way for any project:
      • Start xvfb:
        $ sudo Xvfb :10 -ac
      • Start headless FireFox:
        DISPLAY=:10 firefox
      • run tests
Useful links:

Всегда недолюбливал Apache из-за формата его конфига. Конфиг Nginx’а мне вседа было порще читать и писать. Да и статику им раздавать хорошо и быстро, поэтому от Apache я, по возможности, отказываюсь. Но частая проблема с nginx в том, что приходится собирать необходимые модули из исходников самому, со всемы вытикаюющими плюсами и минусами. И, как полагается любому популярному и быстро развивающемуся проекту, документация по сборке этих самих модулей не всегда полная и актуальная.

Ниже (очень) короткое изложение того, что мне пришлось сделать для сборки mod_security. Надеюсь, это сэкономит кому-то время и нервы.

Оффициальная документация расположена тут и тут. При этом сложилось впечатление, что на github’е она более актуальная и полная. По ним можно собрать все, при условии, что у вас уже стоят нужные пакеты. Мне понадобились:

$ sudo apt-get install autoconf automake libtool libcurl4-openssl-dev

Тажке, не забыть выполнить “./autogen.sh” перед “./configure”. Почему-то, в секции “Installation for NGINX” документации написаны только общие сведения. Мне, например, не хотелось и не нужно было собирать mod_security для apache, устанавливать пакет apache2-dev и т.д. Поэтому выполнил configure со следующими параметами:

$ ./configure --prefix=/usr --disable-apache2-module --enable-standalone-module

Далее сборка, установка и настройка у меня прошла без проблем.



Вот так неожиданно для себя узнал что Selenium Webdriver работает без Java. До этого всегда был уверен, что такого не может быть. Возможно, это произошло с релизом Selenium 2, возможно нет. Но мой мир уже не будет таким как прежде. 

Не поверил, пока сам не убедился в этом. Пришлось ставить виртуалку без Java и проверять следующий код:


Скорее заметка для себя. Постоянно забываю об этом ключе.

Иногда нужно очень быстро поднять HTTP-сервер с минимальным функционалом. Например, нужно проверить HTML+JavaScript, который работает только через веб-сервер. Я, конечно понимаю, что у меня под рукой всегда есть, как минимум nginx, и поднять на нем сайт, который будет раздавать статические фалы, делов на 5-10 минут. Но, во-первых, это долго, а во-вторых, этот самый nginx у меня находится на виртуалке, пусть и включенной в 90% времени на ноутбуке. А тут прийдется не только веб-сервер настроить, но и расшаренные папки в VirtualBox-е, и так далее...

Единственное, что у меня действительно всегда есть под рукой, это Python (уже несколько лет не пользуюсь ОС Windows, а на всех *nix он есть из коробки). Поднять простой тестовый веб-сервер на питоне можно всего одной командой:

$ python -m SimpleHTTPServer 8100

После этой команды, у вас локально запустится веб-сервер на 8100-м порту, который покажет содержимое текущей папки.

Работает это благодаря ключу -m, который указывает интерпритатору о том, что необходимо выполнить модуль как скрипт. Это идентично команде "$ python /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/SimpleHTTPServer.py", но писать значительно проще. Единственное ограничение, что этот модуль должен быть доступен в sys.path


Или мне так не везет, или для всех это совершенно очевидные вещи, но я, при выполнении простой fabric task'и копирования содержимого директорий, столкнулся на ряд проблем, которые хоть и казались простыми и распростаренными, но были описаны в найденных мной блогах и мануалах в плной мере.

Проблема #1 - неправильный пользователь

Не всегда бывает что имя локального пользователя совпадает с удаленным. Ситуация распространенная и описанная всеми, кому не лень. Решение простое - указывем нужного пользователя в переменной env.user:

from fabric.api import *
env.user = 'e0ne'

Проблема #2 - аутентификация

Самый простой способ аутентификации по SSH - имя пользователя и пароль. Самая распространенная проблема - нужно как-то безопасно хранить пароль. А так, как я предпочитаю все скрипты для развертывания хранить в VCS (читать как в git’е), то возникает проблема: нехорошо хранить пароль в репозитории, особенно в открытом на GitHub’е. Решение простое - использование пары public/private RSA ключей. Процесс генерации и настройки (ключевые слова: ssh-keygen, ~/.ssh/known_hosts) описывать сейчас не буду, только то, что касается Jenkins’а.

В моем случае, Jenkins был установлен на Ubuntu server 13.04 командой  “apt-get install jenkins”, поэтому пользователь jenkins в системе создался таким образом, что без подоплнительной настройки под ним не залогинишься. Поэтому использем sudo и генерируем ключи для подключения по SSH, примерно, так:

$ sudo -u jenkins -i ssh-keyget -t rsa

После чего в домашнем каталоге jenkins’а будет лежать пара ключей, публичный из которых нужно положить на наш сервер, на который fabric будет копировать файлы. Достатьid_rsa.pub можно так:

$ sudo -u jenkins -i cat ~/.ssh/id_rsa.pub

И положить этот ключ на сервер, в моем случае в файл /home/e0ne/.ssh/known_hosts.

Этот способ так же решает проблему того, что не нужно передавать в fabric путь к ключу, он будет браться из пути по умолчанию.

Проблема # 3 - не загружаются файлы

Ошибка тут у меня была совсем непонятной (привет paramiko и протоколу ssh), откатывать настройки или делать это с другим сервером сейчас нет возможности. Да и точно не помню как я пришел к решению. Наверное, после медитации на вывод команды “ls -al”. Задача jenkins job’ы была в том, что нужно было апдейтить простое веб-приложение, а у пользователя, под которым jenkins/fabric подключался к серверу не было прав на запись в нужную директорию. Решилось добавлением пользователя в группу www-data, в которой у меня находится пользователь nginx’а.

Пока все. Эти три проблемы были при попытке “простого копирования” файлов. Дальше будет должно быть интереснее. Или проще. Как повезет:).



Иногда приходится делать удивительные и неожиданные для себя вещи. Например, писать что-то на PHP. Но написать мало - надо еще проверить что написанный код работает. В моем случае приложение было немного сложнее классического "hello, world", но все-равно требовалось проверить его работоспособность.

Т.к. это всего-лишь тестовая версия/PoC или что-то такое, интересовала самая простая схема его запуска. Apache не виртуалке c Ubuntu не стоял, под руку попал Nginx. Далее все происходило так:

  • установка необходимого пакета (PHP уже стоял):
    $ sudo apt-get install php5-fpm
    Не знаю насколько FastCGI хорош и применим в мире PHP, но мне подошел
  • выключаем работу с unix socket и вешаем FastCGI процесс на локальный порт 9000; для этого правим конфигурационный файл "/etc/php5/fpm/pool.d/www.conf" следующим образом:
    ;listen = /var/run/php5-fpm.sock
    listen = 127.0.0.1:9000
  • настраиваем Nginx ("/etc/nginx/sites-available/default"):
    location ~ \.php$ {
                    try_files $uri =404;
                    fastcgi_split_path_info ^(.+\.php)(/.+)$;
                    fastcgi_pass 127.0.0.1:9000;
                    fastcgi_index index.php;
                    include fastcgi_params;
            }
  • копируем нужный PHP-скрипт в директорию, на которую смотрит веб-сервер
  • проверяем работу скрипта
Все. Решение не претендует на правильность и полноту. Просто иногда надо.


 

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

DNS (Domain Name Service) - служба доменных имен. С ним (DNS) все сталкивались, но далеко не все знают как он работает и что может. В простейшем случае - это сервис, который переводит доменное имя в IP адрес, а вот что у него внутри - читайте ниже.

Как и большинство протоколов, DNS имеет свою спецификацию, описанную в RFC 1035 и RFC 1706.

DNS имеет иерархическую структуру:

 

 

Корневые DNS серверы (“.”, root name servers, http://en.wikipedia.org/wiki/Root_name_server) отвечают за информазию о доменах верхнего (1-го) уровня, таких как .com, .net, .info и так далее. За ними идут DNS серверы второго уровня, которые содержат информацию о доменах 2-го уровня (поддомены). И так далее по иерархии

 

 

Записи в DNS имеют разны значения и время жизни (TTL, time to live). TTL означает то, сколько времени будет храниться кеш об DNS записи, что-бы DNS-сервере не нужно было лишний раз образаться с запросом к вышестоящему DNS.

Поля DNS записей:

 

  • NAME - собственно само доменное имя;
  • TYPE - тип домена;
  • CLASS - класс записи; в стандате RFC прописано, что DNS может работать не только в сетях по TCP/IP, класс записи определяет тип сети;
  • RDLEN, RDATA - длина и формат с данными о домене.

 

Типы записей DNS (с чем чаще всего приходится работать):

 

  • A record - определяет соответствие домена IP-адресу;
  • AAAA - тоже самое, что и  A, но для IPv6;
  • CNAME - каноническое имя, alias домена, используется для редиректов на другое (под)домен;
  • MX - mail exchage, указывает адреса серверов, которые отвечают за работу с email’ами для данного домена;
  • PTR - pointer, нужен для обратных (reverse) DNS-запросов, которые по IP адресу возвращают FQDN.

 

 

Отдельно стоит сказать про FQDN  - fullly qualified domain name, полное уникальное имя домена, включаещее также корневой домен и завершается точкой (“.”): blog.e0ne.info.

Если спуститься на уровень протокола, то формат сообщения между DNS сервером и клиентом выглядит так:

 

 

Бывает полезно при работе с TCP Dump и другими анализаторами траффика на разных уровнях модели OSI

Идентификатор:

  • 16-битовое поле для обозначения соответствия между запросами и откликами.
  • Q
  • 1-битовый флаг запроса (query).

Запрос:

  • 4-битовое описание типа сообщения:
  • 0   стандартный запрос (адрес по имени).
  • 1   обратный запрос (имя по адресу).
  • 2   запрос состояния сервера.

A:

  • Authoritative Answer - 1-битовый флаг, показывающий отклик от уполномоченного (authoritative) сервера имен.

T:

  • Truncation - отбрасывание. 1-битовый флаг, говорящий об отбрасывании сообщения.

R:

  • 1-битовый флаг, устанавливаемый устанавливаемый для разрешения запроса рекурсивным путем.

V:

  • 1-битовый флаг поддержки рекурсивного сервиса.

B:

  • 3-битовое поле, зарезервированное для использования в будущем (0).

RCode:

  • Код отклика - 4-битовое поле, устанавливаемое сервером имен для обозначения состояния запроса:
    • 0   нет ошибок.
    • 1   невозможно интерпретировать запрос из-за формальной ошибки.
    • 2   обработка невозможна из-за сбоя на сервере.
    • 3   запрошенное имя не существует.
    • 4   неподдерживаемый тип запроса.
    • 5   отказ от выполнения запроса.

Счетчик вопросов:

  • 16-битовое поле, содержащее число записей в разделе вопросов.

Счетчик ответов:

  • 16-битовое поле, содержащее число записей о ресурсах в разделе ответов.

Счетчик Authority:

  • 16-битовое поле, определяющее число записей о ресурсах сервера имен в разделе authority.

Счетчик дополнений:

  • 16-битовое поле, определяющее число записей о ресурсах сервера имен в дополнительном разделе.



4 января вышел релиз кандидат fullstack-фрейморка для разработки веб-приложения Django. Обзоры, наверено, не писали/читали только ленивые. Но пишут, в основном, про мажорные фичи, из-за которых и выпускают релиз. Я перевел свой небольшой прототипчик одного приложения на Django 1.5 RC и поюзал некоторые минорные нововведения, о которых пишут мало, но которые почти делают каждый релиз тем, из-за чего часто хочется использовать именно его. Из того, что мне понравилось - это:

  • изменения в template engine: теперь True, False, None воспринимаются так же, как и в python;
  • дополнительные батарейки для работы с временными зонами - мелочь, а очень приятно;
  • исправленна ошибка OutOfMemory при использовании команды dumpdata - особенно полезно на небольших хостингах;
  • mod_wsgi auth handler - для тех, кто все еще использует Apache и Basic авторизацию;
  • в debug конфигурации приложения логи дополнительно выводятся в консоль;
  • user_login_failed событие - понятно что это такое, +1 к секьюрити: легче блокировать ботов от перебора паролей и плюс к защите от DDoS;
  • loaddata имеет опцию для игнорирования колонок, которых больше нет в модели - просто в восторге от этой фичи, имхо, она для меня теперь станет неаменимой при разработке, кода модель активно меняется, а django south использовать еще рано (в момент разработки, а не при выходе в production).
Из всего вышесказанного, кроме mod_wsgi auth handler попробовал все и хоче сказать: дявол кроется в деталях, они делают любой продукт именно таким, чтоб им (не) хотелось пользоваться.

Ну и не могу сказать про одно мажорное изменения - эксперементальная поддержка Python 3.2+! Осталось подождать и/или портировать нужные зависимости для проектов и можно начинать использовать. На свой страх и риск, конечно, т.к. production код должен быть как можно стабильнее, а не в статусе "эксперементальная фича". Хотя, time to market никто не отменял...

Подробности на оффициальном сайте Django:


 

Статические файлы (картинки, скрипты, css), как правило кешируются для более быстрой работы сайтов (уменьшение трафика - это уже скорее побочное явление). И в этом, казалось бы, нет ничего плохого. За исколючением одного - когда это самое кеширование мешает разработке. В моем случае используется стандартная связка nginx для статики + Tornado для всего остального. Но так как создавать вторую версию конфигурации nginx’а с отключенным кешированием мне не хотелось, да и в процессе разработке во многих случаях можно обойтить самим лишь Tornado, то я решил отключить кеширование в Tornado.

За отдачу статических файлов в Tornado отвечает StaticFileHandler. То логичным было предположить что кеширование отключается где-то в его настройках. Но единственное что там можно сделать, это переопределить метод get_cache_time, который и так во всех случаях возвращает 0. Исключение составляет лишь случай, когда в параметрах запроса есть ключ “v” - тогда время жизни кеша будет 10 лет.

Если посмотреть внимательно на код StaticFileHandler’а, в частности метогда, то увидем что 304-й ответ (not modified) выдаются только при соблюдении такого условия: в заголовках запроса (request headers) есть “If-Modified-Since” и время модификации файла больше, чем значение “If-Modified-Since”. Казалось бы все хорошо, но вот только дата последней модификации файла никак не может нам гарантировать, что содержимое не поменялось. В качестве значения заголовка “If-Modified-Since” браузеры передают то, что они получили от сервера на предыдущий запрос к данному ресурсу в заголовке “Last-Modified”. Таким образом для отключения кеширования статики необходимо отдать заведомо старое значение Last-Modified. Делается жто так:

 

 

Тут я указываю дату модификации на год меньше текущей даты. Так же для большей уверености выставляю значение “Expires” в 0, что сообщит браузеру о необходимости заново загрузить файл. И последнее - “Cache-Control='no-cache, must-revalidate” для большей уверенности. 

Теперь останется лишь указать наш хэндлев к как обработчик запросов к статике и все готово. А учитывая что в testing и production окружениях наверняка будет использоваться nginx, то код можно не менять, т.к. до нашего обаботчика запросы не дойдут, их обработает nginx.

Исходный код описанного хэндлера: https://github.com/e0ne/BlogSamples/blob/master/tornado-nocache/handlers.py

 


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

  • подключения специальной версии CSS;
  • подключения нужных JavaScript’ов;
  • создание мобильных шаблонов (templates) с версткой (html).

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

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

{% if request.mobile %}
    Mobile
{% else %}
    Not mobile
{% endif %}

 

Или же наша view поменяет вид на такой:

def index(request):
    if not request.mobile:
        return render_to_response('index.html’)
    else:
        return render_to_response('mobile_index.html’)

Теперь дело за малым - сделать так, чтоб в объекте нашего запроса (request’а) появилось свойство mobile. Один из самых простых и достаточно эффективных способов - посмотреть какой USER_AGENT у браузера, который делает запрос. Для этих целей уже есть небольшой, но удобный компонент minidecector, который анализирует USER_AGENT из запроса и выставляет нужное значение свойства request.mobile.

minidetector можно подключать двумя способами:

  • добавление декоратора detect_mobile к нужной view;
  • добавление уже готового Middleware; в этом случае будут обрабатываться все запросы к нашему приложению.

Небольшой пример использования minidetector лежит на GitHub’e: https://github.com/e0ne/BlogSamples/tree/master/MobileTest

 

Другие ссылки по теме:

Напомню, что протестировать это на встроеном в Django веб-сервере не получится, т.к. он работает только локально и вы не зайдете на него со своего мобильного устройста. Настройка Django+Apache+mod_wsgi описана тут: https://code.djangoproject.com/wiki/django_apache_and_mod_wsgi