АДСМ6. Интерфейсы взаимодействия с сетевыми устройствами

Это вторая статья — техническо-практический разбор протоколов и интерфейсов автоматизации сети.

Если хочется исторической справки, я отсылаю читателя к статье-спутнику, в которой мы двигаемся от начала времён в будущее человечества. Какую роль сыграли в нашем настоящем IETF, ISO, олдовые и современные вендоры и даже просто люди.

В этой же мы раскрываем дерево XML, пробуем на вкус капабилити NETCONF, шлём первые RPC и наконец уже расставим в правильном порядке буквы YANG, OpenConfig, gNMI.

Практическую пользу вам принесут только обе прочитанные статьи. Вторая без первой будет непонятна. Первая без второй — беллетристика.

1a06bd31252bfd2e8651c4a4e8d8462a.png

Сразу предупреждаю, что это будет большая и нудная статья, потому что автор в очередной раз решил разобраться в чём-то, и опубликовать это разом. И вам, клянусь, ещё повезло, когда на двухсоттысячном символе я придумал, как её можно разделить на две части.
Тут разберём по косточкам все возможные способы взаимодействия с сетевым железом.
Лишь вскользь мы заденем CLI и SNMP, как не имеющие практической значимости в контексте этой статьи, разберёмся достаточно глубоко с NETCONF — это новый SNMP или всё же у него есть будущее хотя бы с YANG’ом, продолжим RESTCONF’ом и закончим на интригующем — gRPC.

Ну, а по ходу неминуемо разберёмся с тем, за что с нашими глазами так поступает XML, с концепцией RPC, моделями данных и успеем посмотреть на OpenConfig.

Все статьи АДСМ

Содержание

CLI — Command Line Interface

CLI — сиэлай, кли, сли, слай, слаи, консоль, терминал, командная строка. Этому механизму уже лет 60. И он никуда не делся. Он живее всех живых — где-то для отладки, где-то для эксплуатации, зачастую для конфигурации и даже для ежедневной работы.
На компьютерах, серверах, виртуальных машинах, коммутаторах, маршрутизаторах, фаерволах, АТС, базовых станциях. Трудно найти такое оборудование, где нет CLI, пусть даже хорошо спрятанного.

И в этом его сила — 100% функциональности на 100% сетевых устройств можно настроить через CLI. Ладно 99,9% — придётся выкинуть некоторое альтернативное оборудование.

Это породило миллионы строк кода на Perl, PHP, Python, Go, Ruby, развесистые джинджа-шаблоны и по 300 экспектов в каждом скрипте. И дало работу тысячам кодеров, выросших из сетевиков и админов.Вот уже лет 30, а то и больше мы старательно пишем скрипты, которые с той или иной степенью успеха прикидываются человеком перед сетевой коробкой.

И ещё долго мы не останемся без дела — выпускают всё новые версии софта, ещё более другие модели железа, постоянно меняется CLI, и там, где вчера был string, завтра будет integer. И там, где вчера было no some shitty service enable, завтра будет some shitty service disable. Там, где вчера на вопрос интерфейса надо было ответить yes, завтра вылезет ошибка.

Клянусь, это увлекательное путешествие продлится ещё десятилетия.

А чем же оно увлекательно?

  1. Модели конфигурации не формализованы.

  2. Модель и поведение не зафиксированы.

  3. CLI интерактивен.

  4. Формат данных не структурированный.

  5. Нет явного признака успешности операции.

  6. Сложно вычислять разницу между целевой и текущей конфигурацией.

  7. Сложно считать конфигурационный патч.

  8. Транзакционность не всегда доступна.

  9. Поддержание целевого состояния — задача инженера.

Выше 9 смертных грехов CLI, которые обусловили рождение моделей данных конфигурации, языков их описания, протоколов, как SNMP, NETCONF и gNMI.

Если всё по каждому из них понятно, просто пропускайте следующую секцию.

9 грехов CLI

1. Модели конфигурации не формализованы

Есть такое? Есть такое.

Как мы узнаём, какие команды с какими аргументами в каком порядке надо дать?
Правильно — идём в Command reference guide на сайте производителя и дальше методом проб и ошибок разбираемся в терминале. Или в обратном порядке. Но эти два способа (и ещё помощь друга) — это то, как мы узнаём модель данных конфигурации.

И скажем так: она наверняка есть — ведь каждый раз одна и та же команда приводит к одному и тому же результату (правда ведь? Правда?).

Знаем как настраивать интерфейсы, знаем как они должны называться, где будет IPv4, а где IPv6 адреса? Если мы введём что-то неправильно, CLI ругнётся, но мы не отправим OS в kernel panic или ASIC в рестарт?

Просто эта модель не формализована. Или по крайней мере нам об этом не говорят.
И да, своим естественным интеллектом мы рано или поздно такую модель в своей голове выстраиваем и научаемся ею пользоваться.

Но для того, чтобы написать код, нам придётся её хорошо или плохо самим формализовать — иерархия, порядок ввода команд, последовательность аргументов, типы значений. И скорее плохо, конечно.

2. Модель и поведение не зафиксированы

Всё, что мы изучили на предыдущем этапе может поменяться в новой версии — и мы сначала переобучаем себя, потом переписываем код.

3. CLI интерактивен

expect("Вы точно хотите выключить bgp-сессию, mpls на всей коробке [Y/n]?"] Yes!
expect("Вы точно хотите выключить электричество в серверной [Y/n]?"] No!

4. Формат данных не структурированный

Мы засылаем туда неструктурированный текст. Мы получаем оттуда неструктурированный текст. Мы его крафтим, мы его парсим. Мы пишем jinja-шаблоны и regexp’ы. Мы прожигаем свою жизнь.

Лучше json’ы перекладывать.

Строго говоря, будь-то json или вывод show version, в итоге это всё равно поток байтов и по сути текст. Только в одном случае в нём есть структура, а в другом — это просто набор символов.

5. Нет явного признака успешности операции

Вывод CLI не означает ни успех, ни провал. Warning ещё не означает, что что-то пошло не так. Отсутствие вывода — ещё не признак успешности.

6. Сложно вычислять разницу между целевой и текущей конфигурацией

Казалось бы нужно просто подифать два текста. Вот только в этих текстах имеет значение, порядок строк. Одна и та же конфигурация может быть (и будет) разной для разных версий ПО. Различающийся же регистр напротив может ничего не значить в одном месте, а в другом значить. Одна и та же команда, в разных контекстах может означать разное (выключите MPLS на интерфейсе или в глобальном режиме — посмотрите на последствия). Даже IPv6-адреса могут быть записаны в сокращённом или полном виде.

7. Сложно считать конфигурационный патч

Как следствие предыдущего пункта — выяснить, какие команды нужно применить — тоже нетривиально. Но не только это. Дело в том, что нужно уметь не только правильно добавлять, но и правильно удалять -, а способов — не один и не два. Обратная команда не всегда формируется как отрицание прямой. Часто нужны не все её параметры. В каком порядке отменять — и не поломает ли это чего-то ещё? Даже не всегда команды после применения выглядят так же, как их применяли.

8. Транзакционность не всегда доступна

В целом мы уже избалованы коммитами — многие вендоры его поддерживают. Но многие ещё нет. А те, кто поддерживает, может это делать тоже собственным уникальным способом, как например валидация ввода только при коммите, или коммит заключается в последовательном применении всех команд без вычисления дельты.

9. Поддержание целевого состояния — задача инженера

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

Но тут стоит быть чуть более честным — не всегда CLI настолько плох. Некоторые вендоры генерируют CLI-интерфейс из YANG-модели, что гарантирует чёткое соответствие между тем, что и как конфигурируется через CLI или любые другие интерфейсы.

Например, в Nokia SR Linux интерфейс командной строки, а так же gNMI, JSON-RPC и внутренние приложения работают с единым API — mgmt_srv — поэтому не только формализованы из одной и той же YANG-модели, но и имеют одинаковые возможности по чтению/записи конфигурации.

Дифы, коммиты, датасторы и прочее, тоже могут быть сделаны с умом — как у той же Nokia или у Juniper.

Но это всё, конечно, не отменяет факта работы с неструктурированным текстом.

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

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

Не исчерпывающий список можно сформулировать так:

  • Представление данных в структурированном виде,

  • Разделение конфигурационных и операционных данных,

  • Читаемость для человека исходных данных и самой конфигурации,

  • Воспроизводимость — задачу на исходных данных можно запустить повторно — проиграть,

  • Механизм основан на формальных моделях,

  • Транзакционность изменений и их откат,

  • Поддержание целевого состояния.

Не все они появились сразу. Не все они появились. Но это понятная и приятная цель.

И на замену SNMP, в подмогу CLI зародился NETCONF, эксплуатирующий идею RPC — Remote Procedure Call. Что за RPC, какое у него отношение с API вы так же можете узнать из первой статьи. Ну, только если коротко.

Концепция RPC — Russian Pravoslavnaya Church

RPC — клиент-серверный механизм, который позволяет запустить исполнение кода процедуры на другой машине так, словно бы он исполнялся локально. То есть разработчик просто привычным образом обращается к процедуре, не задумываясь о том, где и как она исполняется — главное, чтобы она ответ вернула. А программа уже сама реализует взаимодействие с удалённой машиной.

Прелесть этого подхода в том, что он, во-первых, позволяет скрыть удалённый характер работы. А, во-вторых, на той, другой, стороне совершенно неважно, какая операционная система, архитектура, язык программирования и окружение — главное, чтобы они подчинялись одному протоколу. Например из-под винды в exe-шнике, написанном на Delphi, вы можете исполнить удалённую программу, написанную на го, запущенную на линуксе. И никто вам не сможет помешать!

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

Но только представьте, как было бы восхитительно, если бы для вызова этого кода, не нужно было заходить на железку по SSH и вбивать команду?!

Постойте! Да ведь именно об этом мы и говорим в данном разделе.

Большую оставшуюся часть статьи мы посвятим именно RPC.

Пример

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

  1. Наша убер-платформа автоматизации вызывает некую функцию add_bgp_peer_stub(ip="10.1.1.1", as="12345").

  2. Функция add_bgp_peer_stub открывает спецификацию для протокола, реализующего RPC, и согласно ей упаковывает полученные параметры, которые станут payload’ом для сообщения. Такая упаковка называется маршалинг.

  3. Далее формирует пакет и передаёт его вниз по стеку и — в сеть.

  4. На другой стороне — на устройстве — приложение получает пакета.

  5. Функция, принявшая сообщение, вытаскивает из него параметры процедуры, согласно той же самой спецификации и формирует список параметров. Это называется демаршалинг.

  6. Приложение выполняет функцию — настраивает BGP-соседа 10.1.1.1 с AS 12345. Проверяет успешность выполнения.

  7. Далее функция формирует на основе всё той же спецификации сообщение-ответ и передаёт его в ответном пакете.

  8. Наша локальная сторона, с которой мы инициировали выполнение RPC, получает ответ, словно бы его вернула локальная функция.

  9. Воаля

Поподробнее про RPC.

В целом RPC — это концепция, не говорящая ничего о реализации. Она постулирует, что на стороне клиента есть так называемый стаб — фрагмент кода, который реализует взаимодействие по RPC. Именно стабы делают для разработчика прозрачным вызов функции. Ключевая часть RPC — спецификация — и ещё её называют IDL — Interface Definition Language — язык, описывающий как стабы должны маршалить и демаршалить (формировать и разбирать) сообщения. Иными словами, на основе IDL создаются клиентский и серверный стабы. Это может быть, например, набор классов в питоне, имеющих функции для удалённого вызова, с которыми разработчик работает так, словно всё происходит локально.

Мы дальше разберём два протокола, которые используются под капотом RPC и при этом позволяют управлять сетевым железом.

  • NETCONF

  • gRPC

NETCONF

Ох, как я вился вокруг этого нетконфа в своё время, ожидая, что это серебряная пуля, решающая если не все, то 99,99% всех проблем сетевых инженеров.

Спойлер: это не так.

ce6910131f3f9876242a9398f1595b29.jpeg

Если вам по какой-то причине кажется, что стандарты рождаются где-то в недрах институтов, оторванных от жизни, то вот вам контр-пример.

В 1996 был основан Juniper Networks, в недрах которого создали легендарный М40 и лучший в мире интерфейс командной строки. До сих пор никто не сделал ничего лучшего — все только повторяют. Операционка, предоставляющая клиенту обычный текстовый интерфейс, на самом деле перекладывает команды в XML, который фактически является интерфейсом для управления устройством. Если вы сейчас к любой show-команде на джуне добавите | display xml, то увидите ответ в формате XML:

eucariot@kzn-spine-0> show system uptime | display xml

    
        
            localre
            
                
                    2022-01-03 14:59:59 MSK
                
                 LOCAL CLOCK 
                
                    2021-03-04 16:54:06 MSK
                    43w3d 22:05
                
                
                    2021-03-04 16:55:01 MSK
                    43w3d 22:04
                
                
                    2021-12-07 19:19:22 MSK
                    3w5d 19:40
                    scamp
                
                
                    3:00PM
                    304 days, 22:06
                    1
                    0.20
                    0.17
                    0.20
                    
                
            
        
    
    
        {master:0}
    

В корне вы можете видеть , что означает, что был какой-то -request. И вот так вы можете увидеть, каким RPC-запросом можно получить такие данные:

eucariot@kzn-spine-0> show version | display xml rpc

    
        
        
    
    
        {master:0}
    

Внимание, работает только для Juniper!

Так вот, их CLI и способ взаимодействия его с системой оказался настолько естественным и удачным, что его и положили в основу стандарта. Не без участия Juniper Networks, конечно же, появился RFC4741. Будем честны, один только джунипер там и постарался. И то тут, то там будут проскакивать его куски, начиная с commit confirmed и заканчивая candidate config.

Вот как NETCONF был определён в 2006-м году:

Abstract
The Network Configuration Protocol (NETCONF) defined in this document
provides mechanisms to install, manipulate, and delete the
configuration of network devices. It uses an Extensible Markup
Language (XML)-based data encoding for the configuration data as well
as the protocol messages. The NETCONF protocol operations are
realized on top of a simple Remote Procedure Call (RPC) layer.

И определение с тех пор не менялось — вся суть NETCONF в этом параграфе.

А теперь давайте разбираться с очень непростым NETCONF и его составными частями.

NETCONF и его команды

Если совсем коротко, NETCONF — это четырёхуровневый стек, согласно которому через SSH передаётся RPC, где указана операция и конкретный набор действий (контент).

d4ff80a78d493f51e37a279292f78a94.png

Стек NETCONF

Итак, в качестве транспорта NETCONF использует SSH. На самом деле, там есть и другие протоколы: SSH, SOAP, BEEP, TLS -, но мы их опустим — SSH стал де-факто стандартом.

Каждый NETCONF запрос содержит элемент (или сообщение):

  •  — это собственно запрос на вызов процедуры с необходимыми параметрами.

  •  — ответ на RPC.

    •  — очевидно, ответная ошибка, когда RPC некорректен.

    •  — rpc корректен и отработал.

  •  — сообщение о событии, инициированное сетевой коробкой — аналог трапа в snmp (из RFC6241).

Это всё сообщения, внутри которых определённым образом сформированные XML.

Внутри сообщения определяется какая операция (действие) исполняется. В таблице ниже полный их список, определённый в RFC:

Operation

Description

Retrieve running configuration and device state information

Retrieve all or part of a specified configuration datastore

Edit a configuration datastore by creating, deleting, merging or replacing content

Copy an entire configuration datastore to another configuration datastore

Delete a configuration datastore

Lock an entire configuration datastore of a device

Release a configuration datastore lock previously obtained with the operation

Request graceful termination of a netconf session

Force the termination of a netconf session

Каждый вендор может расширять список операций хоть до бесконечности. Так, у кого-то, например, есть .

И далее уже сам контент. Это самая сложная часть. Но забегая вперёд — он никак не формализован, не описан, и, возможно, это величайшая претензия к нетконф, как стандарту, позволившему благую идею превратить в очередного зомби. Даже удивительно, что после опыта с SNMP, где необходимость языка моделирования стала очевидна со временем, NETCONF родился сам по себе без какого-либо языка спецификации для данных. Уже много позже для этого подтянули YANG.

Установка сессии и Capabilities

Так, сначала включаем SSH NETCONF. На примере джунипер.

set system services netconf

Это значит, что SSH будет использоваться как транспорт для указанной подсистемы.
Для netconf IANA установила специальный порт 830, хотя часто используется и обычный для SSH 22.

И пробуем подключиться. Для того, чтобы указать, что это не просто подключение по SSH, мы используем вызов подсистемы:

ssh kazan-spine-0.juniper -s netconf




  
    urn:ietf:params:netconf:base:1.0
    urn:ietf:params:netconf:capability:candidate:1.0
    urn:ietf:params:netconf:capability:confirmed-commit:1.0
    urn:ietf:params:netconf:capability:validate:1.0
    urn:ietf:params:netconf:capability:url:1.0?scheme=http,ftp,file
    urn:ietf:params:xml:ns:netconf:base:1.0
    urn:ietf:params:xml:ns:netconf:capability:candidate:1.0
    urn:ietf:params:xml:ns:netconf:capability:confirmed-commit:1.0
    urn:ietf:params:xml:ns:netconf:capability:validate:1.0
    urn:ietf:params:xml:ns:netconf:capability:url:1.0?protocol=http,ftp,file
    http://xml.juniper.net/netconf/junos/1.0
    http://xml.juniper.net/dmi/system/1.0
  
  15420

]]>]]>

Мы ещё ничего не успели сделать, а железка нам уже насыпала в терминал. Это сообщение NETCONF Hello, которое заставляет на берегу договориться, что поддерживается в данной сессии, а что нет. Внутри — список капабилитей — возможностей, поддерживаемых коробкой. RFC4741 определял базовый набор функций, который должен поддерживаться каждым клиентом и каждым сервером.

При этом базовые могут расширяться другими стандартизированными capability и даже проприетарными. Давайте рассмотрим сначала стандартные, а потом самые интересные расширенные. Ну и будем называть их «способностями», а то капабилитя — это почти как капибара.

NETCONF Standard Capabilities (стандартные способности)

  • Candidate configuration
    Эта способность говорит о том. Что коробка поддерживает отдельный кандидат-конфиг, содержащий полную конфигурацию, с которой можно работать без влияния на фактически применённую конфигурацию. Аналоги candidate-config на Juniper.

  • Confirmed commit
    Опять же аналог джуниперовоского commit confirmed — откат изменений после коммита, если не было подтверждения коммита.

  • Validate
    Способность проверить желаемую конфигурацию до её применения.

  • Rollback-on-error
    Способность отмены изменений при ошибке. Работает, если поддерживается способность candidate configuration.

  • Writable-running
    Такая способность говорит о том, что устройство позволяет писать непосредственно в running-конфигурацию, в обхода candidate.

  • Distinct startup
    Способность задавать startup конфигурацию отличную от running и candidate.

  • Notification
    Аналог SNMP-trap. Коробка может слать аварии и события клиенту.

И ещё несколько более других способностей, которыми грузить вас не хочу, ибо в лучшем виде они описаны в RFC. Посмотрите, кстати, какие способности отдал джунипер, а какие нет.

NETCONF Extended Capabilities (сверх-способности)

Их тьма. Из самых интересных:

  • YANG push
    Способность отсылать данные с коробки на клиент — периодически или по событию.

  • YANG-library
    Способность сервера сообщить клиенту о поддерживаемых параметрах относительно YANG: версия, модель, нейспейсы итд.

  • Commit-description
    Самоговорящее название.

Формат названия capability строго регламентирован:

urn:ietf:params:netconf:capability:{name}:1.0.

Последние два значения — это имя и версия — и только они могут меняться. Так urn:ietf:params:netconf:base:1.1 — это имя базовой капабилити для версии 1.1.

В ответ на  сервера клиент в свою очередь должен послать свои capability:


 
  urn:ietf:params:xml:ns:netconf:base:1.0
  urn:ietf:params:xml:ns:netconf:capability:candidate:1.0
  urn:ietf:params:xml:ns:netconf:capability:confirmed-commit:1.0
  urn:ietf:params:xml:ns:netconf:capability:validate:1.0
  urn:ietf:params:xml:ns:netconf:capability:url:1.0?protocol=http,ftp,file
  xml.juniper.net/netconf/junos/1.0
  xml.juniper.net/dmi/system/1.0
 

]]>]]>

Чего почти нигде не пишут, но что очень важно: если вы пробуете взаимодействовать с коробкой по нетконф руками, то нужно обязательно вручную отослать такую последовательность ]]>]]>, сообщающую, что ввод закончен. Она называется Framing Marker или Message Separator Sequence.

Есть важный нюанс, описанный в RFC6242, ]]>]]> — это старый End-of-Message Framing Marker, который был выбран из соображений, что такая последовательность не должна встречаться в well-formed XML. Однако жизнь показала, что она встречается. Поэтому в NETCONF 1.1 придумали новый механизм, который делит данные на блоки — чанки — и нумерует их. Так он и называется: Chunked Framing Mechanism.

Каждый чанк данных начинается с ##X, где X — это число октетов в нём.

Это одно из фундаментальных отличий между 1.0 и 1.1:). Другие менее значительны.

Сейчас NETCONF-сессия установлена и можно заслать какой-то RPC.

Посылаем свой первый RPC


  
   
     
   
   
     
       
          
       
     
   
  

]]>]]>



    
        kzn-spine-0
    


Мы отправили элемент , в котором запросили -конфигурацию с помощью операцию . И ещё на сервере отфильтровали по интересной ветке.

А в ответ пришёл с ответом. И в запросе, и в ответе можете найти message-id — по ним можно отслеживать на что именно ответ — ведь режим работы NETCONF асинхронный и можно засылать следующее сообщение, пока предыдущее ещё не было обработано.

Здесь вы видите некоторую структуру XML. Её легко можно скормить XML-парсеру, который превратит его в JSON или python dict или что угодно другое, с чем удобно работать в скриптах и программах. И далее извлечь по ключам нужные данные.

Но почему XML? За что? Как вообще с этим быть?

Ох. Зря вы спросили.

В общем дальше 10 000 знаков про XML. Если вы не готовы это выдержать, милости прошу дальше. Но будьте готовы, что практика NETCONF тогда пройдёт мимо вас. Или вы мимо неё. В общем разминётесь.

Так за что же так с нами?

По всей видимости наиболее точный и честный ответ — «исторически сложилось».
Судьба XML в чём-то похожа на MPLS — оба были созданы для одной задачи, а популярность снискали в другой.

XML намеревался стать метаязыком для создания языков разметки документов. Но очень быстро его адаптировали под формат сериализации данных при передаче. И к моменту, когда Juniper выбирал формат, в котором API будет принимать запросы, XML стал уже проверенным, зрелым кандидатом.

Сегодня, вероятно, победил бы JSON, но тогда он только начинал свой путь к славе.

186d2cec4143c177d260918f335d06c8.png

YAML и protobuf тогда ещё не существовали. Ну и вообще YAML подходит лучше для описания конфигураций, которые редактируются руками, нежели как формат обмена данными.

Прелюбопытная историческая справка по XML, JSON и YAML: YAML: The Missing Battery in Python.

В общем выбор в те дни был предопределён — XML был сверхсовременным и суперудобным,

Сложность читаемости XML компенсируется простотой его программной обработки. Чёткая иерархическая структура, понятные начало, конец и значение. В том же питоне xmltodict изящно любой валидный XML разворачивает в словарь. А вообще вот годная статья про то, как предполагается работать с XML средствами стандартной библиотеки.

Но давайте разбираться с тем, что же в себе интересного таит XML.

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

XML сам по себе не делает ничего — это только формат представления информации, в отличие от HTML, который как раз таки призван отрисовать содержимое.

XML описывает что за данные внутри, а его теги не определены заранее, опять же в отличие от HTML.

То есть это два брата, похожих друг на друга внешне, но очень разных внутри.

Давайте сначала на отвлечённом примере поразбираемся?



  
    Everyday Italian
    Giada De Laurentiis
    2005
    
    
  
  
    Harry Potter
    J K. Rowling
    2005
    
  

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

Всё начинается с

XML Prolog

Это так называемый XML Prolog. Он опционален, однако обычно присутствует и должен идти первой строкой. Версия всегда строго 1.0, кодировка по умолчанию — UTF-8.
Коль скоро он опциональный, далее мы его опускаем.

Дерево элементов

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

Детьми корневого элемента являются элементы . Разные элементы друг для друга являются сёстрами. Как такового понятия списка в XML нет, но по имени элемента мы (и код) понимаем, что они представляют из себя именно список. У элемента есть дочерние элементы. Их состав совсем не обязательно должен быть одинаковым — XML этого не требует, однако этого может (и скорее всего будет) требовать приложение.

Главное правило XML — каждый открывшийся тег должен быть закрыт: сказал  — говори и . Элемент может быть пустым, просто выражая факт своего существования, тогда запись можно заменить на просто .

Атрибуты

Взглянем на другой пример:


  
    Everyday Italian
    Giada De Laurentiis
    2005
  
  
    Harry Potter
    J K. Rowling
    2005
  

Теперь внутри тега появилась строка вида category="cooking". Она описывает дополнительные данные об элементе. Своего рода метаданные.

При этом вот эти две записи абсолютно равноправны с точки зрения XML:

  
    Everyday Italian
    Giada De Laurentiis
    2005
  

и

  
    cooking
    Everyday Italian
      <lang>en</lang>
    
    Giada De Laurentiis
    2005
  

То есть XML в терминах ни синтаксиса, ни семантики понятия дочерний элемент и атрибут не разделяет. Это остаётся исключительно на совести составителя/разработчика.

В целом к этому следует относиться именно как к метаданным — информации об информации. То есть если это не является неотъемлемым свойством объекта или нужно в служебных целях, то его можно вынести в атрибуты.

Чтобы далеко не уходить, вот пример из netconf:



 
   
 

Здесь message-id — это атрибут элемента RPC, который не имеет непосредственного отношения к передаваемым далее данным, но позволяет отследить по message-id ответ сервера (он вставит его в ).

Ещё один пример, который мы будем разбирать дальше: . Атрибут operation="replace" не является частью конфигурации интерфейса, он лишь говорит, что то, что существует сейчас на коробке в ветке , нужно заменить на то, что описано в данном XML.

Ну и замечу, что пусть с точки зрения XML атрибут и дочерний элемент взаимозаменяемы, когда вы придумываете свою схему обмена или хранения, однако NETCONF вам такого не простит. Да и любой другой интерфейс, в который вы встраиваетесь — ведь в нём уже определена схема XML.

Namespaces

Хух. Я откладывал много лет момент, когда придётся разобраться с неймспейсами в XML. На самом деле ничего тут нет хитрого. Если мы определили два разных элемента с одинаковыми именами, то появляется неоднозначность — какой именно элемент мы имеем в виду, обращаясь к нему по имени? Например, элемент может быть как у интерфейса, так и у пользователя и у влана итд. Их можно разнести в разные NS, хотя это не обязательно, потому что они находятся под разными родителями. А если на одном уровне могут оказаться совпадающие имена — это уже настоящая проблема. Например,


Moscow Novocheremushkinskaya, 50
2a01:ba80:e:20::32 185.127.149.137

В первом случае имеется в виду почтовый адрес, во втором — IP.

Здесь уже однозначно будет конфликт. Надо решать.

Сделать это можно несколькими способами.

  1. Прямо объявляем неймспейсы с префиксами:

    
    
      
        Moscow
        Novocheremushkinskaya, 50
      
    
    
      2a01:ba80:e:20::32
      185.127.149.137
    
    

    Теперь это полное, fully qualified, имя безо всяких ограничений. Обращаемся из приложений, соответственно, по полному имени.

    postal и ip — это короткие префиксы. Само имя namespace — это произвольная строка. Но негласная договорённость, что все используют URI. Он может вести на страницу с описанием этого неймспейса, а может и не вести.Но указание префикса в каждом теге может показаться не очень удобным, тогда есть второй способ.

  2. Определяем default namespace

    
    
    Moscow Novocheremushkinskaya, 50
    2a01:ba80:e:20::32 185.127.149.137

    Область действия дефолтного неймспейса — сам элемент и все его потомки, если он нигде не переопределяется.

Концепция namespace с одной стороны проста, с другой стороны и там есть место тёмным пятнам. Если хочется подетальнее изучить, то есть парочка полезных FAQ про них.

Xpath — XML Path

Сначала правильно, но непонятно: XPath — это способ выбрать ноды или множество нод из XML документа.

Теперь неправильно, но понятно: это способ представить иерархию XML в виде «привычного» нам пути, где элементы отделены друг от друга знаком »/».

Например, в XML из примера выше путь к элементу </code> будет записан в виде <code>/bookstore/book/title</code></p> <p>Ну, а теперь и правильно, и понятно, но долго.</p> <p>XPath — это очень гибкий и мощный инструмент, позволяющий внутри XML делать разнообразные запросы. Он поддерживает различные функции: <code>sum</code>, <code>count</code>, <code>avg</code>, <code>min</code>, <code>starts-with</code>, <code>contains</code>, <code>concat</code>, <code>true</code>, <code>false</code> — над разными типами данных: числа, строки, булевы.</p> <p>Так с помощью XPath можно выбрать названия всех книг с ценою выше 35: <code>/bookstore/book[price>35]/title</code></p> <p>XPath оперирует нодами, которыми являются элементы, атрибуты, текст, неймспейсы и другое.</p> <p>Соответственно помимо того, что мы можем запросить часть XML по конкретному пути, можно делать разные хитрые запросы.</p> <p>Например: </p> <p class="copyrights"><span class="source">© <a target="_blank" rel="nofollow" href="https://habr.com/ru/post/667440/?utm_campaign=667440&amp;utm_source=habrahabr&amp;utm_medium=rss">Habrahabr.ru</a></span></p> </div> <br> <!--<div align="left"> <script type="text/topadvert"> load_event: page_load feed_id: 12105 pattern_id: 8187 tech_model: </script><script type="text/javascript" charset="utf-8" defer="defer" async="async" src="//loader.topadvert.ru/load.js"></script> </div> <br>--> <div style="padding-left: 20px;"> <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-2514821055276660" crossorigin="anonymous"></script> <!-- PCNews 336x280 --> <ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-2514821055276660" data-ad-slot="1200562049" data-ad-format="auto"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div> <!-- comments --> <noindex> <div style="margin: 25px;" id="disqus_thread"></div> <script type="text/javascript"> var disqus_shortname = 'pcnewsru'; var disqus_identifier = '1174031'; var disqus_title = 'АДСМ6. Интерфейсы взаимодействия с сетевыми устройствами'; var disqus_url = 'http://pcnews.ru/blogs/adsm6_interfejsy_vzaimodejstvia_s_setevymi_ustrojstvami-1174031.html'; (function() { var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); })(); </script> <!--<noscript>Please enable JavaScript to view the <a rel="nofollow" href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>--> <!--<a href="http://disqus.com" rel="nofollow" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>--> </noindex> </div> <br class="clearer"/> </div> <br class="clearer"/> <div id="footer-2nd"></div> <div id="footer"> <br/><br/> <ul class="horz-menu"> <li class="about"><a href="/info/about.html" title="О проекте">О проекте</a></li> <li class="additional-menu"><a href="/archive.html" title="Архив материалов">Архив</a> </li> <li class="additional-menu"><a href="/info/reklama.html" title="Реклама" class="menu-item"><strong>Реклама</strong></a> <a href="/info/partners.html" title="Партнёры" class="menu-item">Партнёры</a> <a href="/info/legal.html" title="Правовая информация" class="menu-item">Правовая информация</a> <a href="/info/contacts.html" title="Контакты" class="menu-item">Контакты</a> <a href="/feedback.html" title="Обратная связь" class="menu-item">Обратная связь</a></li> <li class="email"><a href="mailto:pcnews@pcnews.ru" title="Пишите нам на pcnews@pcnews.ru"><img src="/media/i/email.gif" alt="e-mail"/></a></li> <li style="visibility: hidden"> <noindex> <!-- Rating@Mail.ru counter --> <script type="text/javascript"> var _tmr = window._tmr || (window._tmr = []); _tmr.push({id: "93125", type: "pageView", start: (new Date()).getTime()}); (function (d, w, id) { if (d.getElementById(id)) return; var ts = d.createElement("script"); ts.type = "text/javascript"; ts.async = true; ts.id = id; ts.src = (d.location.protocol == "https:" ? "https:" : "http:") + "//top-fwz1.mail.ru/js/code.js"; var f = function () { var s = d.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ts, s); }; if (w.opera == "[object Opera]") { d.addEventListener("DOMContentLoaded", f, false); } else { f(); } })(document, window, "topmailru-code"); </script> <noscript> <div style="position:absolute;left:-10000px;"> <img src="//top-fwz1.mail.ru/counter?id=93125;js=na" style="border:0;" height="1" width="1" alt="Рейтинг@Mail.ru"/> </div> </noscript> <!-- //Rating@Mail.ru counter --> </noindex> </li> </ul> </div> <!--[if lte IE 7]> <iframe id="popup-iframe" frameborder="0" scrolling="no"></iframe> <![endif]--> <!--<div id="robot-image"><img class="rbimg" src="i/robot-img.png" alt="" width="182" height="305" /></div>--> <!--[if IE 6]> <script>DD_belatedPNG.fix('#robot-image, .rbimg');</script><![endif]--> </div> <!--[if lte IE 7]> <iframe id="ie-popup-iframe" frameborder="0" scrolling="no"></iframe> <![endif]--> <div id="footer-adlinks"></div> <noindex> <!--LiveInternet counter--><script type="text/javascript"> document.write("<a rel='nofollow' href='//www.liveinternet.ru/click' "+ "target=_blank><img src='//counter.yadro.ru/hit?t45.6;r"+ escape(document.referrer)+((typeof(screen)=="undefined")?"": ";s"+screen.width+"*"+screen.height+"*"+(screen.colorDepth? screen.colorDepth:screen.pixelDepth))+";u"+escape(document.URL)+ ";"+Math.random()+ "' alt='' title='LiveInternet' "+ "border='0' width='1' height='1'><\/a>") </script><!--/LiveInternet--> <!-- Rating@Mail.ru counter --> <script type="text/javascript"> var _tmr = window._tmr || (window._tmr = []); _tmr.push({id: "93125", type: "pageView", start: (new Date()).getTime()}); (function (d, w, id) { if (d.getElementById(id)) return; var ts = d.createElement("script"); ts.type = "text/javascript"; ts.async = true; ts.id = id; ts.src = "https://top-fwz1.mail.ru/js/code.js"; var f = function () {var s = d.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ts, s);}; if (w.opera == "[object Opera]") { d.addEventListener("DOMContentLoaded", f, false); } else { f(); } })(document, window, "topmailru-code"); </script><noscript><div> <img src="https://top-fwz1.mail.ru/counter?id=93125;js=na" style="border:0;position:absolute;left:-9999px;" alt="Top.Mail.Ru" /> </div></noscript> <!-- //Rating@Mail.ru counter --> <!-- Yandex.Metrika counter --> <script type="text/javascript"> (function (d, w, c) { (w[c] = w[c] || []).push(function () { try { w.yaCounter23235610 = new Ya.Metrika({ id: 23235610, clickmap: true, trackLinks: true, accurateTrackBounce: true, webvisor: true, trackHash: true }); } catch (e) { } }); var n = d.getElementsByTagName("script")[0], s = d.createElement("script"), f = function () { n.parentNode.insertBefore(s, n); }; s.type = "text/javascript"; s.async = true; s.src = "https://mc.yandex.ru/metrika/watch.js"; if (w.opera == "[object Opera]") { d.addEventListener("DOMContentLoaded", f, false); } else { f(); } })(document, window, "yandex_metrika_callbacks"); </script> <noscript> <div><img src="https://mc.yandex.ru/watch/23235610" style="position:absolute; left:-9999px;" alt=""/> </div> </noscript> <!-- /Yandex.Metrika counter --> <!-- Default Statcounter code for PCNews.ru http://pcnews.ru--> <script type="text/javascript"> var sc_project=9446204; var sc_invisible=1; var sc_security="14d6509a"; </script> <script type="text/javascript" src="https://www.statcounter.com/counter/counter.js" async></script> <!-- End of Statcounter Code --> <script> (function (i, s, o, g, r, a, m) { i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () { (i[r].q = i[r].q || []).push(arguments) }, i[r].l = 1 * new Date(); a = s.createElement(o), m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m) })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga'); ga('create', 'UA-46280051-1', 'pcnews.ru'); ga('send', 'pageview'); </script> <script async="async" src="/assets/uptolike.js?pid=49295"></script> </noindex> <!--<div id="AdwolfBanner40x200_842695" ></div>--> <!--AdWolf Asynchronous Code Start --> <script type="text/javascript" src="https://pcnews.ru/js/blockAdblock.js"></script> <script type="text/javascript" src="/assets/jquery.min.js"></script> <script type="text/javascript" src="/assets/a70a9c7f/jquery/jquery.json.js"></script> <script type="text/javascript" src="/assets/a70a9c7f/jquery/jquery.form.js"></script> <script type="text/javascript" src="/assets/a70a9c7f/jquery/jquery.easing.1.2.js"></script> <script type="text/javascript" src="/assets/a70a9c7f/jquery/effects.core.js"></script> <script type="text/javascript" src="/assets/a70a9c7f/js/browser-sniff.js"></script> <script type="text/javascript" src="/assets/a70a9c7f/js/scripts.js"></script> <script type="text/javascript" src="/assets/a70a9c7f/js/pcnews-utils.js"></script> <script type="text/javascript" src="/assets/a70a9c7f/js/pcnews-auth.js"></script> <script type="text/javascript" src="/assets/a70a9c7f/js/pcnews-fiximg.js"></script> <script type="text/javascript" src="/assets/a70a9c7f/js/pcnews-infobox.js"></script> </body> </html>