История одного бага (#1653967)

    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 (по кипрскому времени) я заканчиваю писать этот пост на Хабр.
    Share post

    Similar posts

    Comments 26

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

      В проприетарной среде — да, почти без исключений, это бы заняло от недели (в лучшем случае) до тех же десятилетий. И своя инициатива (fork/pull request) там уже не прокатит если нет знакомого высокого менеджера.
        +6
        Смотрите: и в opensource, и в проприетарной среде баги могут мариноваться десятилетиями. У меня самого много таких «success story», когда баги были открыты ещё в 200х году, и до сих пор актуальны. Более того, можно утверждать, что в проприетарной среде в условиях финансовой заинтересованности они могут фикситься быстрее (могут — не значит, что будут).

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

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

        Но мы его для себя исправили. Именно тут, а не в волшебном (оно быстро исправится) и находится сила opensource'а. Можешь пойти и сделать сам. Если не можешь — разницы между проприетарным и opensource'ным нет.
          +2
          Очень верно подмечено: если можешь исправить.
          Как-то нашел очень похожий источник бага — используется старая версия библиотеки, выпиливающей некорректные теги из Markdown.
          Для исправления нужно «всего лишь» обновить библиотеку, но сделать это с уверенностью, что ничего не сломается — не могу.
          Потому запостил и жду.
            0
            Невозможно быть уверенным, что изменение ничего не сломает. Это такая ловушка пассивного бездействия.
            –4
            Пока это питон поправить легко. А вот когда джава-скала-го то фикс уже не настолько тривиальный по времени и поддержке
              +1
              Какая разница? Мы не редактируем код на продакшене, он всегда должен проходить через CI/тесты перед деплоем. А в процессе сборки пакета компилируется он или нет — это уже не важно. В openstack тесты выполняются почти час, так что это всё равно сильно дольше, чем просто «скомпилировать».

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

          Что, простите? :)
            0
            Было странно, если при зависимости зависимости я не отказался бы от одинарного отрицания.
              +2

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


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


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


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

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

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

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

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

                  0
                  А патчик вы будете выкладывать? Пока что к багу не приложено ни одной ссылки на геррит.
                  Интересно, сколько патч провисит потом на ревью.
                    +2
                    Патчик к чему? К python-rfc3986? Вот он, два года как зарелизен: https://github.com/sigmavirus24/rfc3986/pull/12/files

                    Проблема-то была не в коде, а в том¸что зависимость слишком старая в дистрибутиве. Решается установкой правильной версии пакета python-rfc3986 (>=0.2.2), о чём в комментарии к ланчпадовому багу я и написал.
                      +1
                      Тогда понятно. А то смотрю ветку newton в опенстековском и убунтовском репозиториях Новы, а там везде уже свежая версия прописана в requirements.
                        0
                        О, кстати, да. Сейчас посмотрел — зависимость-то прописана. Видимо, когда бубунтоводы её пакетировали, они снасильничали зависимости. Сейчас проверю.

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

                        В 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'ах.
                          0
                          В 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 и упустив следующую актуальную.
                            0
                            У меня в связи с этим даже более серьёзный вопрос: а как я могу узнать какой коммит какой версии соответствует? Именно в oslo.config, где нет тегов в гите.
                              0
                              Есть теги: https://github.com/openstack/oslo.config/tags
                              И можно сравнить код под тегом и ветку, например:
                              https://github.com/openstack/oslo.config/compare/3.18.0...master
                                0
                                Вот теперь и у меня вопрос. Можно увидеть нужные изменения и в 3.18.0 и в stable/newton отдельно.
                                А если посмотреть сравнение 3.18.0 против stable/newton, то получается, что нужные изменения появляются только в stable/newton.
                                  0
                                  окей, насчёт тэгов я был неправ: они есть (не знаю что за глюк, когда смотрел, вроде бы не было). Но сейчас вижу.

                                  А вот разницу между релизом и его бранчем — вижу. Ща допишу в багрепорт в ланчпаде.
                      0
                      В проприетарной среде — сделать алиас без дефиса, отправить ошибку, [записать в wiki], забыть.
                        0
                        Подскажите, по каким причинам ваш выбор остановился именно на Ubuntu, а не на, скажем, Debian?

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

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