Abstract: Реальная история из жизни реальных администраторов по отлову идиотского бага.
Поучительная часть: Никогда не недооценивай зависимости зависимостей.

Вступление


Рядовой апгрейд в лаборатории с Openstack Mitaka до Openstack Newton (более новая версия). Несколько deprecated options в файлах конфигурации, keystone переехал с eventlet на WSGI и поломал существующую конфигурацию с haproxy; из-за типового «ipv6 listen» apache не стал конфликтовать с haproxy за одинаковые используемые порты на звезде (один слушал ipv6, другой ipv4 only), так что запросы уходили в haproxy вместо апача, где умирали с 503, т.к. апстрима не было… Впрочем, история не об этом.

После того, как основные проблемы были пофишкены, Nova (одна из компонент Openstack) при запуске начала падать с ошибкой: ConfigFileValueError: Value for option url is not valid: invalid URI: 'http://neutron-server.example.com:21345'.. Это было очень странно. С учётом, что в конфиге поменялось 100500 опций, возникло подозрение, что мы используем устаревшую опцию, которую больше не надо использовать. Однако, документация говорила, что пример опции — url = http://controller:9696.

Отладка


Очевидные шаги отладки:
  • Закомментировать опцию — не падает
  • Повторить опцию из примера — не падает
  • Заменить в опции порт на «наш» — возможно, нельзя использовать слишком большой номер порта — не падает
  • Заменить в опции url на наш — падает
  • Вернуть «controller» на место — не падает
  • Подозрение: не умеет fqdn: заменить controller на controller.dns — не падает
  • Подозрение: слишком много точек (у нас в реальном коде было 8 точек в url) — controller.dns1.dns2.dns3.dns4 — не падает
  • Оставить из нашего имени только первую часть: http://neutron-server:9696 — падает! гипотеза уже понятна.
  • Проверка1: http://neutronserver:9696 — не падает
  • Проверка2: http://with-dashes:9696 — падает!

Итого, бага: наличие дефиса в hostname вызывает ConfigFileValueError. Баг репортится: bugs.launchpad.net/ubuntu/+source/nova/+bug/1653967

Проверка, что это баг: RFC3986 утвержает, что:
unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
reg-name      = *( unreserved / pct-encoded / sub-delims ) 
host          = IP-literal / IPv4address / reg-name

(это такая BNF-нотация, которая говорит, что в host может использоваться дефис).

Мы это все и так знаем, но всегда лучше перепроверить.

Изучение кода, который сообщает об ошибке:

            try:
                return convert(opt._get_from_namespace(namespace, group_name))
            except KeyError:  # nosec: Valid control flow instruction
                pass
            except ValueError as ve:
                raise ConfigFileValueError(
                    "Value for option %s is not valid: %s"
                    % (opt.name, str(ve)))

Ошибка возникала на двух опциях: url и novncproxy_base_url. Ошибка идентичная, хотя grep'ать удобнее по второму. Начинаем искать второе. Вот как она определяется в коде:

    cfg.URIOpt(
        'novncproxy_base_url',
        default='http://127.0.0.1:6080/vnc_auto.html',
        deprecated_group='DEFAULT',
        help="""

Ага. А cfg — это из from oslo_config import cfg. oslo.config — это библиотека Openstack для работы с конфигами. Смотрим сырцы.

Видим:

class URI(ConfigType):
...
    def __call__(self, value):
        if not rfc3986.is_valid_uri(value, require_scheme=True,
                                    require_authority=True):
            raise ValueError('invalid URI: %r' % value)

Внезапно:

>>> import rfc3986
>>> rfc3986.is_valid_uri('http://test.com')
True
>>> rfc3986.is_valid_uri('http://test-test.com')
False

Упс. Непорядок. Зато есть: github.com/sigmavirus24/rfc3986/issues/11
Бага давно пофикшена. В версии 0.2.2. А на хосте у нас:

apt-cache policy python-rfc3986
python-rfc3986:
  Installed: 0.2.0-2
  Candidate: 0.2.0-2
  Version table:
 *** 0.2.0-2 500
        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages
        100 /var/lib/dpkg/status

Зато в более свежей версии в zesty есть версия 0.3.1-2, которая такой проблемой не страдает.

Дальнейшее разбирательство


Давным-давно был сделан Баг. Он был некоторое время, потом его пофиксили. Но за это время запакетировали Код, в котором был Баг, и никто не обратил внимание на Фикс Бага, и версия с Багом оставалась в deb-репозитории годами. Она никого не волновала — пока не случились два коммита в oslo.config и nova:

commit 45ee2bed52a57b9801435b43ad45d8f50204580d
Author: Masaki Matsushita <glass.saga@gmail.com>
Date:   Mon Sep 28 20:28:28 2015 +0900

    Add URIOpt
    
    This change add URIOpt which validates string as URI.
    
    Closes-Bug: #1500398
    Change-Id: Ie8736b8654b9feb2a2b174159f08dbea03568d84


commit 6091de77eda12286786e28ae4f0779e7efc54634
Author: Maciej Szankin <maciej.szankin@intel.com>
Date:   Thu Jul 28 10:30:59 2016 -0500

    Improve consistency in VNC opts
    
    * Updated header flags
    * Moved all vars to list
    * Removed possible values and related options sections where they were not
      needed
    * Changed IntOpt to PortOpt where needed
    
    Change-Id: I3255a867091f8e14c907c7fde9a2aa3abc249ae9
    Implements: Blueprint centralize-config-options-newton

Этот коммит сделал из StrOpt UriOpt и начал использовать (через oslo.conf) python-rfc3986. Из-за того, что была запакетирована старая версия python-rfc3986, в программном ПО возникла неожиданная регрессия.

Бонусное: как мы это будем фиксить


Обычно в таких случаях, если апгрейд на более новую версию даётся легко (и не вызывает других проблем), то мы просто забираем пакет из более новой версии дистрибутива (в данном случае — ещё не вышедшей zesty, ака ubuntu-17.04). Мы положим его в наш приватный репозиторий под управлением aptly (как есть) и будем его использовать при установке/настройки сервером. Если бы такого пакета не было в природе — мы бы настроили на CI джобу на его пакетирование и публикацию (в репозиторий aptly). Если бы этот вариант не был доступен (например, несовместимые изменения), то мы бы добавили в наш patchqueue для nova ещё один патч, который бы делал StrOpt вместо UriOpt. Это подразумевает, что мы будем пересобирать nova из ubuntu-пакета с нашими собственными патчами. Этим занимается CI, который публикует пакеты в тот самый наш приватный репозиторий.

Немного флейма


И как бы эта проблема решалась в проприетарной среде? Ошибки допускают все (иначе бы у нас было ПО без багов). После того, как ошибка была зарепорчена в поддержку первого уровня, после боданий про установленные версии, обновления и контракты, оно дошло бы до саппорта второго уровня, третьего уровня, и так вплоть до человека с реальной квалификацией, который может смотреть в код. Он нашёл и исправил проблему. Какой estimate для того баг-фикса? Два часа на первый уровень, ещё час на второй, business day на исследование проблемы, ещё один business day на фикс, возможно ещё один день на релиз и тестирование. Это идеальный сценарий. На практике, мои самые оптимистичные оценки говорят про недели, превращающиеся в «следующем релизе через пол-года поправили».

Сколько это заняло у меня, в opensource проекте, исправить проблему своими силами? ~14:30, сегодня проблема обнаружилась, и я зарепортил его на launchpad. В 15:20 уже было известно про проблему с зависимостью, в 15:30 было проверено, что с новой версией python-rfc3986 этой проблемы нет. В 16:50 (по кипрскому времени) я заканчиваю писать этот пост на Хабр.
Поделиться с друзьями
-->

Комментарии (26)


  1. baldr
    04.01.2017 19:34
    +13

    Ну, допустим, в OpenSource-среде это тоже не всегда быстро чинится.
    Можно найти кучу багов, которые висят десятилетиями, с кучей жалоб в комментах, 100% reproducibility, даже с пулл-реквестами, но без попыток починить/вкоммитить это в mainline. Или даже с попытками reject по идеологическим причинам.
    Растут форки и форки форков, которые начинают использовать, а потом их перестают поддерживать.
    Простите, не буду гуглить и приводить примеры, но, думаю, многие приведут примеры с наболевшим.

    В проприетарной среде — да, почти без исключений, это бы заняло от недели (в лучшем случае) до тех же десятилетий. И своя инициатива (fork/pull request) там уже не прокатит если нет знакомого высокого менеджера.


    1. amarao
      04.01.2017 19:54
      +6

      Смотрите: и в opensource, и в проприетарной среде баги могут мариноваться десятилетиями. У меня самого много таких «success story», когда баги были открыты ещё в 200х году, и до сих пор актуальны. Более того, можно утверждать, что в проприетарной среде в условиях финансовой заинтересованности они могут фикситься быстрее (могут — не значит, что будут).

      Я про другое. Я про то, что opensource позволяет исправить проблему не ожидая реакции апстрима/реселлера/саппорта.

      Собственно, баг из поста на launchpad'е хорошо показывает: его ещё никто даже не confirm. И зная ubuntu, он таким и будет. Возможно, его никогда и не закроют, потому что через 3 месяца выйдет новый openstack и эту версию пометят obsolete.

      Но мы его для себя исправили. Именно тут, а не в волшебном (оно быстро исправится) и находится сила opensource'а. Можешь пойти и сделать сам. Если не можешь — разницы между проприетарным и opensource'ным нет.


      1. lolikandr
        05.01.2017 10:07
        +2

        Очень верно подмечено: если можешь исправить.
        Как-то нашел очень похожий источник бага — используется старая версия библиотеки, выпиливающей некорректные теги из Markdown.
        Для исправления нужно «всего лишь» обновить библиотеку, но сделать это с уверенностью, что ничего не сломается — не могу.
        Потому запостил и жду.


        1. amarao
          05.01.2017 15:02

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


      1. crazylh
        05.01.2017 14:09
        -4

        Пока это питон поправить легко. А вот когда джава-скала-го то фикс уже не настолько тривиальный по времени и поддержке


        1. amarao
          05.01.2017 15:04
          +1

          Какая разница? Мы не редактируем код на продакшене, он всегда должен проходить через CI/тесты перед деплоем. А в процессе сборки пакета компилируется он или нет — это уже не важно. В openstack тесты выполняются почти час, так что это всё равно сильно дольше, чем просто «скомпилировать».

          Тут вопрос в том, что нужно иметь рабочий delivery pipeline из гита в продакшен — и его чаще всего не так уж и просто сделать, особенно, если часть сборки выполняется вручную и авторы ничего такого про CI не думали.


  1. sshikov
    04.01.2017 20:02
    +4

    >Поучительная часть: Никогда недооценивай зависимости зависимостей.

    Что, простите? :)


    1. amarao
      04.01.2017 20:17

      Было странно, если при зависимости зависимости я не отказался бы от одинарного отрицания.


      1. a1111exe
        05.01.2017 10:07
        +2

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


        Попробуйте заменить слово "недооценивай" на другое. Например:
        "никогда оценивай зависимости зависимостей". Звучит по-русски?
        Лично я не смог в ресурсах по правилам русского языка сходу найти примеры, когда "никогда" с глаголом используется без отрицания. Противоположных примеров тьма.


        Примерный синоним слова "недооценивать" — преуменьшать значение. Тогда: "никогда преуменьшай значение зависимости зависимостей". Если целью было сказать, что преуменьшать значение зависимости зависимостей — это не есть хорошо, то нужно было использовать отрицание: "никогда не недооценивай зависимости зависимостей".


        P.S. Спасибо за статью.


      1. Starche
        05.01.2017 10:21
        +3

        Правильно будет «никогда не недооценивай». Т.к. конструкция «никогда не» как раз таки и просит нас перестать что-то делать (или вовсе не начинать). И получится как раз почти эквивалент «перестань недооценивать», чего вы вроде и хотели добиться.
        Ну и такое количество «не» ещё больше навернет фразу, к чему вы и стремились.


        1. amarao
          05.01.2017 15:00

          А, понял. Как всё сложно в этих русских языках.


  1. AVX
    04.01.2017 20:14

    Надо читать: всегда учитывай зависимости.


    1. amarao
      04.01.2017 20:20
      +4

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


      1. Tortortor
        04.01.2017 20:54

        я думал я один такой


      1. AVX
        04.01.2017 21:31

        Никогда не — Так пишется правильно. Не важно, какие и сколько там отрицаний. Либо сформулировать как-то иначе.


  1. Marsikus
    04.01.2017 21:01

    А патчик вы будете выкладывать? Пока что к багу не приложено ни одной ссылки на геррит.
    Интересно, сколько патч провисит потом на ревью.


    1. amarao
      05.01.2017 01:03
      +2

      Патчик к чему? К python-rfc3986? Вот он, два года как зарелизен: https://github.com/sigmavirus24/rfc3986/pull/12/files

      Проблема-то была не в коде, а в том?что зависимость слишком старая в дистрибутиве. Решается установкой правильной версии пакета python-rfc3986 (>=0.2.2), о чём в комментарии к ланчпадовому багу я и написал.


      1. Marsikus
        05.01.2017 19:15
        +1

        Тогда понятно. А то смотрю ветку newton в опенстековском и убунтовском репозиториях Новы, а там везде уже свежая версия прописана в requirements.


        1. amarao
          05.01.2017 19:35

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

          Вообще, картинка потрясающая.

          В ubuntu apt-get source выдаёт в requirements.txt rfc3986>=0.2.0
          А в oslo.config stable/newton написано rfc3986>=0.2.2

          И это не вина бубунты.

          На https://releases.openstack.org/newton/index.html#oslo-config в newton идёт версия 3.17, (и там 0.2.0), а в git'е в бранче stable/newton что-то другое. И я не вижу ни тегов ни каких-то других методов понять, какая версия какому коммиту соответствует.

          Халтурка со стороны авторов oslo.config, да. Более того, в git'е даже нет слова 3.17 нигде — ни в коде, ни в тегах, ни в git log'ах.


          1. Marsikus
            05.01.2017 22:48

            В oslo.config обновили requirements из global requirements до релиза Newton.
            https://github.com/openstack/oslo.config/commit/2f2c1839b7423185a6a48e7b3ca3c3274d5ba8f3
            Также наличие этих изменений соответствует тегу 3.18.0 в oslo.config, а в 3.17.0 действительно их еще нет.
            Возможно, что ошибся мейнтейнер Openstack релиза Newton, приняв в него старые версии oslo.config и упустив следующую актуальную.


            1. amarao
              05.01.2017 23:22

              У меня в связи с этим даже более серьёзный вопрос: а как я могу узнать какой коммит какой версии соответствует? Именно в oslo.config, где нет тегов в гите.


              1. Marsikus
                05.01.2017 23:29

                Есть теги: https://github.com/openstack/oslo.config/tags
                И можно сравнить код под тегом и ветку, например:
                https://github.com/openstack/oslo.config/compare/3.18.0...master


              1. Marsikus
                05.01.2017 23:36

                Вот теперь и у меня вопрос. Можно увидеть нужные изменения и в 3.18.0 и в stable/newton отдельно.
                А если посмотреть сравнение 3.18.0 против stable/newton, то получается, что нужные изменения появляются только в stable/newton.


                1. amarao
                  05.01.2017 23:39

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

                  А вот разницу между релизом и его бранчем — вижу. Ща допишу в багрепорт в ланчпаде.


  1. molnij
    05.01.2017 21:36

    В проприетарной среде — сделать алиас без дефиса, отправить ошибку, [записать в wiki], забыть.


  1. lovecraft
    09.01.2017 07:10

    Подскажите, по каким причинам ваш выбор остановился именно на Ubuntu, а не на, скажем, Debian?

    Впрочем, в Debian та же петрушка — в Jessie 0.2.0-2, в не вышедшем еще Stretch — 0.3.1-2