Академический Документы
Профессиональный Документы
Культура Документы
Pyneng PDF
Pyneng PDF
Введение 1.1
О курсе 1.2
Как учиться по этому курсу 1.3
Пример плана обучения 1.4
FAQ 1.5
Благодарности 1.6
1. Подготовка к работе 1.7
ОС и редактор 1.7.1
Система управления пакетами pip 1.7.2
virtualenv, virtualenvwrapper 1.7.3
Интерпретатор Python (проверка) 1.7.4
Задания 1.7.5
2. Начало работы с Python 1.8
Синтаксис Python 1.8.1
Интерпретатор Python. iPython 1.8.2
Магия iPython 1.8.2.1
Переменные 1.8.3
3. Типы данных в Python 1.9
Числа 1.9.1
Строки (Strings) 1.9.2
Полезные методы для работы со строками 1.9.2.1
Форматирование строк 1.9.2.2
Список (List) 1.9.3
Полезные методы для работы со списками 1.9.3.1
Варианты создания списка 1.9.3.2
Словарь (Dictionary) 1.9.4
Полезные методы для работы со словарями 1.9.4.1
Варианты создания словаря 1.9.4.2
Словарь из двух списков (advanced) 1.9.4.2.1
Кортеж (Tuple) 1.9.5
2
Множество (Set) 1.9.6
Полезные методы для работы с множествами 1.9.6.1
Операции с множествами 1.9.6.2
Варианты создания множества 1.9.6.3
Преобразование типов 1.9.7
Проверка типов 1.9.8
Задания 1.9.9
4. Создание базовых скриптов 1.10
Передача аргументов скрипту 1.10.1
Ввод информации пользователем 1.10.2
Задания 1.10.3
5. Контроль хода программы 1.11
if/elif/else 1.11.1
for 1.11.2
Вложенные for 1.11.2.1
Совмещение for и if 1.11.2.2
Итератор enumerate 1.11.2.3
while 1.11.3
break, continue, pass 1.11.4
for/else, while/else 1.11.5
Работа с исключениями try/except/else/finally 1.11.6
Задания 1.11.7
6. Работа с файлами 1.12
Открытие файлов 1.12.1
Чтение файлов 1.12.2
Запись файлов 1.12.3
Закрытие файлов 1.12.4
Конструкция with 1.12.5
Задания 1.12.6
7. Функции 1.13
Создание функций 1.13.1
Пространства имен. Области видимости 1.13.2
Параметры и аргументы функций 1.13.3
Типы параметров 1.13.3.1
3
Типы аргументов 1.13.3.2
Аргументы переменной длины 1.13.3.3
Распаковка аргументов 1.13.3.4
Пример использования 1.13.3.5
Задания 1.13.4
8. Модули 1.14
Импорт модуля 1.14.1
Создание своих модулей 1.14.2
Задания 1.14.3
9. Регулярные выражения 1.15
Модуль re 1.15.1
Специальные символы 1.15.2
Жадность регулярных выражений 1.15.3
Группировка выражений 1.15.4
Разбор вывода команды show ip dhcp snooping с помощью именованных
групп 1.15.4.1
Задания 1.15.5
10. Сериализация данных 1.16
CSV 1.16.1
JSON 1.16.2
YAML 1.16.3
Задания 1.16.4
11. Работа с базами данных 1.17
SQL 1.17.1
SQLite 1.17.2
Основы SQL (в sqlite3 CLI) 1.17.3
CREATE 1.17.3.1
DROP 1.17.3.2
INSERT 1.17.3.3
SELECT 1.17.3.4
WHERE 1.17.3.5
ALTER 1.17.3.6
UPDATE 1.17.3.7
REPLACE 1.17.3.8
4
DELETE 1.17.3.9
ORDER BY 1.17.3.10
Модуль sqlite3 1.17.4
Пример использования SQLite 1.17.4.1
Задания 1.17.5
12. Подключение к оборудованию 1.18
Ввод пароля 1.18.1
Pexpect 1.18.2
Telnetlib 1.18.3
Paramiko 1.18.4
Netmiko 1.18.5
Возможности netmiko 1.18.5.1
Одновременное подключение к нескольким устройствам 1.18.6
Модуль threading 1.18.6.1
Модуль multiprocessing 1.18.6.2
Задания 1.18.7
13. Шаблоны конфигураций с Jinja 1.19
Пример использования Jinja 1.19.1
Пример использования Jinja с корректным использованием программного
интерфейса 1.19.2
Синтаксис шаблонов Jinja2 1.19.3
Контроль символов whitespace 1.19.3.1
Переменные 1.19.3.2
for 1.19.3.3
if/elif/else 1.19.3.4
Фильтры 1.19.3.5
Тесты 1.19.3.6
Присваивание (set) 1.19.3.7
Include 1.19.3.8
Наследование шаблонов 1.19.4
Задания 1.19.5
14. TextFSM. Обработка вывода команд 1.20
Синтаксис шаблонов TextFSM 1.20.1
Примеры использования TextFSM 1.20.2
5
CLI Table 1.20.3
Задания 1.20.4
15. Ansible 1.21
Основы Ansible 1.21.1
Инвентарный файл 1.21.1.1
Ad-Hoc команды 1.21.1.2
Конфигурационный файл 1.21.1.3
Модули 1.21.1.4
Основы playbook 1.21.2
Переменные 1.21.2.1
Результат выполнения модуля 1.21.2.2
Сетевые модули 1.21.3
ios_command 1.21.3.1
ios_facts 1.21.3.2
ios_config 1.21.3.3
lines (commands) 1.21.3.3.1
parents 1.21.3.3.2
Отображение обновлений 1.21.3.3.3
save 1.21.3.3.4
backup 1.21.3.3.5
defaults 1.21.3.3.6
after 1.21.3.3.7
before 1.21.3.3.8
match 1.21.3.3.9
replace 1.21.3.3.10
src 1.21.3.3.11
ntc_ansible 1.21.3.4
Подробнее об Ansible 1.21.4
Задания 1.21.5
Дополнительная информация 1.22
Полезные модули 1.22.1
Модуль subprocess 1.22.1.1
Модуль os 1.22.1.2
6
Модуль argparse 1.22.1.3
Модуль ipaddress 1.22.1.4
Полезные функции 1.22.2
Функция sorted 1.22.2.1
Функция lambda 1.22.2.2
Функция zip 1.22.2.3
Функция map 1.22.2.4
Функция filter 1.22.2.5
Функции any и all 1.22.2.6
Соглашение об именах 1.22.3
Подчеркивание в именах 1.22.3.1
Полезные ссылки 1.23
Материалы по темам курса 1.23.1
Продолжение обучения 1.23.2
Отзывы 1.24
7
Введение
О курсе
Если в двух словах, то это такой “CCNA” по питону. С одной стороны, курс достаточно
базовый, чтобы его мог одолеть любой желающий, с другой стороны, в курсе
рассматриваются все основные темы, которые позволят дальше расти
самостоятельно.
Курс не ставит своей целью глубокое рассмотрение языка Python. Задача курса,
объяснить понятным языком основы Python и дать необходимые инструменты для его
практического использования.
8
Введение
Этот курс будет полезен для сетевиков, которые хотят автоматизировать задачи с
которыми сталкиваются каждый день и/или хотели заняться программированием, но
не знали с какой стороны подойти.
9
О курсе
О курсе
Курс всегда будет оставаться бесплатным. Поэтому вам не нужно переживать, что он
будет удален.
ОС, Python
В курсе используются:
Debian Linux
Python 2.7
10
О курсе
Для курса подготовлены две виртуальные машины на выбор: Vagrant или VMware. В
них установлены все пакеты, которые используются в курсе.
Примеры
Все примеры, которые используются в курсе, можно скачать одним архивом в zip
или tar.gz.
Если такой возможности нет, например, потому что вы читаете курс в дороге, можно
попробовать повторить примеры самостоятельно позже.
Задания
Все задания и вспомогательные файлы можно скачать одним архивом в zip или
tar.gz.
Конечно, иногда возникает ситуация, когда никак не получается решить задание. Тогда
попробуйте отложить его и сделать какое-то другое.
11
О курсе
На stackoverflow есть ответы практически на любые вопросы. Так что, если гугл
отправил вас туда, значит, с большой вероятностью, ответ найден. Запросы,
конечно же, лучше писать на английском. По Python очень много материалов и,
как правило, подсказку найти легко.
Ответы могли бы показать, как ещё можно выполнить задание или как более
правильно/короче выполнить его. Но, на этот счет, тоже не переживайте, так как,
скорее всего, в следующих разделах встретится пример, в котором будет показано, как
писать такой код.
Презентации
Для части тем курса есть презентации. По ним удобно быстро просматривать
информацию и повторять. Они особенно полезны для базовых тем.
Обновление курса
Курс был полностью завершен 07.02.2017.
Gitbook отображает, когда были сделаны последние изменения, поэтому легко можно
определить были ли изменения за последнее время.
12
О курсе
Форматы курса
Курс можно читать в нескольких форматах:
онлайн
PDF/ePub/Mobi
Комментарии
GitBook позволяет оставлять комментарии у соответствующей части курса. Для этого
нужно зарегистрироваться на GitBook и зайти под своим акаунтом.
В онлайн версии курса, если навести курсор на какой-то абзац текста, справа появится
плюс:
13
О курсе
14
О курсе
15
Как учиться по этому курсу
После этого, можно использовать Ansible для отправки команд, которые не влияют на
передачу трафика:
подпись интерфейсов
настройка общих параметров, таких как, например, alias
генерация конфигурации по шаблону, для первичной настройки оборудования
Если какие-то задачи не получается решить с Ansible, или вы просто захотите изучить
Python, переходите к варианту 2.
И хотя многие вещи, особенно в начале курса, будут базовыми, не пропускайте их,
если вы их не знаете. Эти базовые структуры данных, управляющие структуры, будут
основой всего, что мы будем изучать дальше.
Обязательно практикуйтесь:
16
Как учиться по этому курсу
План занятий можно изменить под себя, но не стоит делать большие перерывы между
темами и не стоит заниматься слишком много:
если делать большие перерывы, то всё, что изучено раньше, начнет забываться
если торопиться, то скорее всего, всё сведется к чтению текста, без выполнения
заданий. В таком случае, от курса будет очень мало пользы
По ходу курса, скорее всего, будут возникать идеи, что сделать по работе. Это
отлично! Делайте обязательно. Записывайте идеи, чтобы не забыть, если не можете
сразу сделать это.
Если же вы чувствуете, что читаете, но пока никаких идей нет, ничего страшного. В
курсе достаточно упражнений, с которыми вы сможете потренироваться писать код. И
которые, весьма вероятно, натолкнут на какие-то идеи.
После курса
К сожалению, новые знания очень быстро забываются без применения и без
повторения.
Не делайте слишком большой перерыв после курса. Если оставить новые знания без
применения на 2-4 недели, то большая часть из них существенно выветрится.
Если вам удалось напридумывать себе задач, по ходу курса - отлично. Реализуйте их.
Напишите список и делайте задачи постепенно. Это отличный способ изучать дальше
и повторять пройденное.
Идеи сами будут двигать вас дальше, вы будете изучать новые темы, новые
возможности естественно, по ходу развития ваших программ.
17
Как учиться по этому курсу
дальше.
18
Пример плана обучения
В плане всё разбито по неделям. Можно идти быстрее или медленнее, в зависимости
от уровня и наличия времени. По возможности, попробуйте придерживаться темпа,
возможно, он вам подойдет.
Подключение к
7 Подключение к оборудованию
оборудованию
8 Шаблоны конфигураций с Jinja Jinja2
10 Ansible Ansible
19
FAQ
Но, если его нет, или если вы просто ещё о таком не думали, попробуйте начать с
простого. Ansible, например, даст сразу довольно много.
Зачем тогда учить python? Проблема в том, что тот же Ansible не решит все вопросы.
И, возможно, вам понадобится добавить какой-то функционал самостоятельно.
20
FAQ
Если же вы потом войдете во вкус и захотите добавить что-то свое, чего нет в Ansible,
возвращайтесь :)
И ещё, этот курс не только о том как использовать Python для подключения на
оборудование и настройки его. Он и о том, как решать задачи, которые не касаются
подключения к оборудованию. Например, изменить что-то в нескольких файлах
конфигурации. Или обработать log-файл.
Почему Python?
В контексте работы с сетевым оборудованием часто используется именно python.
В некотором оборудовании python встроен или есть API, которые
поддерживают Python
Python достаточно прост для изучения.
Конечно, это всё относительно и кому-то более простым может быть другой
язык, но скорее это будет из-за опыта работы с языком, а не потому, что
Python сложный
С Python вы вряд ли быстро дойдете до границ возможностей языка.
Python может использоваться не только для скриптинга, но и для разработки
приложений. Конечно, для этого курса это не важно, но, по крайней мере, вы
потратите время на язык, который позволит вам легко шагнуть дальше, чем
простые скрипты
21
FAQ
То есть, никакой идеологической подоплеки тут нет. Никакого "только Python" тоже.
Никакого "поклонения" тем более.
Тут всё просто. Есть хороший удобный инструмент, который подойдет к разным
задачам. Он не лучший во всем и далеко не единственный язык в принципе.
Но да, совсем не факт, что вам захочется погружаться в это всё. Поэтому, для начала,
попробуйте разобраться с Ansible. Возможно, вам хватит его достаточно надолго.
22
FAQ
рутинными задачами
с проблемами и решениями, которые надо протестировать
с большим объемом однотипных задач
с большим количеством повторяющихся задач
с большим количеством оборудования
И да, я не считаю, что всем надо бежать учиться программировать. Но считаю, что для
инженера это очень важный навык. Да, для инженера. Не для всех на свете.
23
FAQ
А то иногда складывается впечатление, что достаточно пройти "вот эти вот курсы" и
через 3 месяца вы крутой программист с высокой зарплатой. Нет, этот курс не об этом
:)
Это не значит, что всем остальным "не дано". Просто инженерам это будет проще.
Я читаю платно онлайн курс "Python для сетевых инженеров". Но это не будет влиять
на этот курс. Он всегда будет бесплатным.
24
Благодарности
Благодарности
Спасибо всем, кто проявил интерес к первому анонсу курса. Ваш интерес подтвердил,
что это будет кому-то нужно.
Павел Пасынок, спасибо тебе за то, что согласился на курс. С вами было интересно
работать и это добавило мне мотивации завершить курс. И я особенно рада, что
знания, которые вы получили на курсе, нашли практическое применение.
Алексей Кириллов, самое большое спасибо тебе :) Я всегда могла обсудить с тобой
любой вопрос по курсу. Ты помогал мне поддерживать мотивацию и не уходить в
дебри. Общение с тобой вдохновляло меня продолжать, особенно в сложные
моменты. Спасибо тебе за вдохновение, положительные эмоции и поддержку!
25
1. Подготовка к работе
Подготовка к работе
До начала работы с Python, нужно убедиться, что Python установлен в операционной
системе.
Если вы используете Linux/Unix/Mac OS, то, скорее всего, Python уже установлен. И
нужно только проверить, что установлена версия 2.7 (которая используется в курсе).
Если вы используете Windows, то, скорее всего, Python нужно будет установить. Один
из самых простых вариантов для Windows, установить окружение Anaconda. В
окружении также есть IDE Spyder, который можно использовать вместо редактора.
IDE это хорошая вещь, но, не стоит переходить на IDE из-за таких вещей как:
подсветка кода
подсказки синтаксиса
автоматические отступы (важно для Python)
Всё это есть в любом хорошем редакторе. Как правило, для этого может
потребоваться установить дополнительные модули.
В начале работы, может получиться так, что IDE будет только отвлекать обилием
возможностей.
Список IDE для Python можно можно посмотреть тут. Например, можно выбрать
PyCharm или (для Windows) Spyder.
26
ОС и редактор
ОС и редактор
Для того, чтобы начать работать с Python, надо определиться с несколькими вещами:
Для курса желательно использовать не Windows, так как, например, Ansible можно
установить только на Linux/Unix.
Так как почти для всех разделов, подойдет любая ОС, можно отложить вопрос до
раздела по Ansible.
В курсе используются:
Debian Linux
vim (редактор не имеет принципиального значения, лучше выбрать наиболее
удобный)
Python 2.7
Linux:
gEdit
nano
Sublime Text
geany
Mac OS
TextMate
TextWrangler
Windows:
Notepad++
27
ОС и редактор
28
Система управления пакетами pip
pip - это система управления пакетами, которая используется для установки пакетов
из Python Package Index (PyPi).
Проверка pip:
$ pip --version
pip 1.1 from /usr/lib/python2.7/dist-packages (python 2.7)
Если команда выдала ошибку, значит pip не установлен. Установить его можно так:
29
virtualenv, virtualenvwrapper
virtualenv, virtualenvwrapper
virtualenv - это инструмент, который позволяет создавать виртуальные окружения.
Виртуальные окружения:
export WORKON_HOME=~/venv
. /usr/local/bin/virtualenvwrapper.sh
exec bash
30
virtualenv, virtualenvwrapper
$ mkvirtualenv PyNEng
New python executable in PyNEng/bin/python
Installing distribute........................done.
Installing pip...............done.
(PyNEng)$
(PyNEng)$ deactivate
$
$ workon PyNEng
(PyNEng)$
$ workon Test
(Test)$ workon PyNEng
(PyNEng)$
31
virtualenv, virtualenvwrapper
$ rmvirtualenv Test
Removing Test...
$
(PyNEng)$ lssitepackages
ANSI.py pexpect-3.3-py2.7.egg-info
ANSI.pyc pickleshare-0.5-py2.7.egg-info
decorator-4.0.4-py2.7.egg-info pickleshare.py
decorator.py pickleshare.pyc
decorator.pyc pip-1.1-py2.7.egg
distribute-0.6.24-py2.7.egg pxssh.py
easy-install.pth pxssh.pyc
fdpexpect.py requests
fdpexpect.pyc requests-2.7.0-py2.7.egg-info
FSM.py screen.py
FSM.pyc screen.pyc
IPython setuptools.pth
ipython-4.0.0-py2.7.egg-info simplegeneric-0.8.1-py2.7.egg-info
ipython_genutils simplegeneric.py
ipython_genutils-0.1.0-py2.7.egg-info simplegeneric.pyc
path.py test_path.py
path.py-8.1.1-py2.7.egg-info test_path.pyc
path.pyc traitlets
pexpect traitlets-4.0.0-py2.7.egg-info
Зависимости (requirements)
При работе над проектом, со временем, в виртуальном окружении находится всё
больше установленных пакетов. И, при необходимости поделиться проектом или
скопировать его на другой сервер, нужно будет заново установить все зависимости.
32
virtualenv, virtualenvwrapper
Jinja2==2.8
pexpect==4.0.1
tornado==4.3
...
После этого, при необходимости установить все зависимости, надо перейти в новое
виртуальное окружение, которое вы создали на сервере или просто на новый сервер и
дать команду:
Установка пакетов
Например, установим в виртуальном окружении пакет simplejson.
(PyNEng)$ ipython
In [2]: simplejson
simplejson
In [2]: simplejson.
simplejson.Decimal simplejson.decoder
simplejson.JSONDecodeError simplejson.dump
simplejson.JSONDecoder simplejson.dumps
simplejson.JSONEncoder simplejson.encoder
simplejson.JSONEncoderForHTML simplejson.load
simplejson.OrderedDict simplejson.loads
simplejson.absolute_import simplejson.scanner
simplejson.compat simplejson.simple_first
33
virtualenv, virtualenvwrapper
(PyNEng)$ deactivate
$ ipython
34
Интерпретатор Python (проверка)
$ python
Python 2.7.3 (default, Mar 13 2014, 11:03:55)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>
Вывод показывает, что установлен Python 2.7. Приглашение >>> это стандартное
приглашение интерпретатора Python.
35
Задания
Задания
Все задания и вспомогательные файлы можно скачать одним архивом zip или tar.gz.
Также для курса подготовлены две виртуальные машины на выбор: Vagrant или
VMware. В них установлены все пакеты, которые используются в курсе.
Например, если в разделе есть задания: 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала,
можно выполнить задания 5.1, 5.2, 5.3. А затем 5.2a, 5.2b, 5.3a.
Задание 1.1
Единственное задание в этом разделе: подготовка к работе.
36
2. Начало работы с Python
синтаксис Python
работа в интерактивном режиме
переменные в Python
37
Синтаксис Python
Синтаксис Python
Первое, что, как правило, бросается в глаза, если говорить о синтаксисе Python, это
то, что отступы имеют значение:
a = 10
b = 5
if a > b:
print "A больше B"
print "Тут можно выполнить ещё что-то"
else:
print "B больше или равно A"
print "Продолжаем тут же"
Несмотря на то, что еще не рассматривалась конструкция if/else, всё должно быть
понятно.
if a > b .
38
Синтаксис Python
Для того чтобы проблем с отступами не было, надо сразу настроить редактор
таким образом, чтобы отступы делались автоматически.
Еще одна особенность приведенного примера кода: пустые строки. Таким образом код
форматируется, чтобы его было проще читать.
Комментарии
При написании кода, часто нужно написать комментарий. Например, чтобы описать
особенности работы кода.
Обратите внимание, что коментарий может быть и в строке где находится код, и сам по
себе.
"""Очень важный
и длинный комментарий
"""
a = 10
b = 5
39
Интерпретатор Python. iPython
Можно сказать, что интерпретатор работает как командная строка сетевых устройств:
каждая команда будет выполняться сразу после нажатия enter (по крайней мере,
похоже на cisco).
40
Интерпретатор Python. iPython
wget https://bootstrap.pypa.io/get-pip.py
python get-pip.py
apt-get install python-dev
pip install ipython
In [1]: 1 + 2
Out[1]: 3
In [2]: 22*45
Out[2]: 990
In [3]: 2**3
Out[3]: 8
41
Интерпретатор Python. iPython
print
Оператор print позволяет вывести информацию на стандартный поток вывода.
Допустим, есть файл file4.py (обратите внимание на запятую после первой строки):
print 'Hello!',
print 'Hello!'
Тогда при выполнении скрипта, вывод будет выглядеть так (после первого Hello нет
перевода строки, но есть пробел):
Hello! Hello!
dir
Команда dir() может использоваться для того, чтобы посмотреть какие атрибуты и
методы есть у объекта.
42
Интерпретатор Python. iPython
Например, для числа вывод будет таким (обратите внимание на различные методы,
которые позволяют делать арифметические операции):
In [10]: dir(5)
Out[10]:
['__abs__',
'__add__',
'__and__',
...
'bit_length',
'conjugate',
'denominator',
'imag',
'numerator',
'real']
Для строки:
In [11]: dir('hello')
Out[11]:
['__add__',
'__class__',
'__contains__',
...
'startswith',
'strip',
'swapcase',
'title',
'translate',
'upper',
'zfill']
43
Интерпретатор Python. iPython
In [12]: dir()
Out[12]:
[ '__builtin__',
'__builtins__',
'__doc__',
'__name__',
'_dh',
...
'_oh',
'_sh',
'exit',
'get_ipython',
'i',
'quit']
In [13]: a = 'hello'
In [15]: dir()
Out[15]:
...
'a',
'exit',
'get_ipython',
'i',
'quit',
'test']
44
Магия iPython
Magic commands
В IPython есть специальные команды, которые упрощают работу с интерпретатором.
Все они начинаются на %.
%history
In [1]: a = 10
In [2]: b = 5
In [3]: if a > b:
...: print "A is bigger"
...:
A is bigger
In [4]: %history
a = 10
b = 5
if a > b:
print "A is bigger"
%history
%cpaste
При вставке кода с отступами в IPython, из-за автоматических отступов самого ipython,
начинает сдвигаться код:
45
Магия iPython
In [1]: a = 10
In [2]: b = 5
In [3]: if a > b:
...: print "A is bigger"
...: else:
...: print "A is less or equal"
...:
A is bigger
In [4]: %hist
a = 10
b = 5
if a > b:
print "A is bigger"
else:
print "A is less or equal"
%hist
In [5]: if a > b:
...: print "A is bigger"
...: else:
...: print "A is less or equal"
...:
File "<ipython-input-8-4d18ff094f5c>", line 3
else:
^
IndentationError: unindent does not match any outer indentation level
If you want to paste code into IPython, try the %paste and %cpaste magic functions.
%cpaste (после того как все строки скопированы, надо завершить работу команды
набрав '--'):
In [9]: %cpaste
Pasting code; enter '--' alone on the line to stop or use Ctrl-D.
:if a > b:
: print "A is bigger"
:else:
: print "A is less or equal"
:--
A is bigger
46
Магия iPython
In [10]: %paste
if a > b:
print "A is bigger"
else:
print "A is less or equal"
47
Магия iPython
obj?, obj?? : Get help, or more help for object (also works as
?obj, ??obj).
?foo.*abc* : List names in 'foo' containing 'abc' in them.
%magic : Information about IPython's 'magic' % functions.
Magic functions are prefixed by % or %%, and typically take their arguments
without parentheses, quotes or even commas for convenience. Line magics take a
single % and cell magics are prefixed with two %%.
System commands:
History:
_i, _ii, _iii : Previous, next previous, next next previous input
_i4, _ih[2:5] : Input history line 4, lines 2-4
exec _i81 : Execute input history line #81 again
%rep 81 : Edit input history line #81
_, __, ___ : previous, next previous, next next previous output
_dh : Directory history
_oh : Output history
%hist : Command history of current session.
%hist -g foo : Search command history of (almost) all sessions for 'foo'.
%hist -g : Command history of (almost) all sessions.
%hist 1/2-8 : Command history containing lines 2-8 of session 1.
%hist 1/ ~2/ : Command history of session 1 and 2 sessions before current.
48
Переменные
Переменные
Переменные в Python:
In [1]: a = 3
In [2]: b = 'Hello'
In [3]: c, d = 9, 'Test'
In [4]: print a,b,c,d
3 Hello 9 Test
Обратите внимание, что в Python не нужно указывать, что a это число, а b это строка.
In [5]: a = b = c = 33
In [6]: id(a)
Out[6]: 31671480
In [7]: id(b)
Out[7]: 31671480
In [8]: id(c)
Out[8]: 31671480
В этом примере видно, что все три имени ссылаются на один и тот же идентификатор.
То есть, это один и тот же объект, на который указывают три ссылки a, b и c.
С числами, у Python есть ещё одна особенность, которая может немного сбить. Числа
от -5 до 256 заранее созданы и хранятся в массиве (списке). Поэтому, при создании
числа из этого диапазона, фактически создается ссылка на число в созданном
массиве.
49
Переменные
In [9]: a = 3
In [10]: b = 3
In [11]: id(a)
Out[11]: 4400936168
In [12]: id(b)
Out[12]: 4400936168
In [13]: id(3)
Out[13]: 4400936168
In [14]: a = 500
In [15]: b = 500
In [16]: id(a)
Out[16]: 140239990503056
In [17]: id(b)
Out[17]: 140239990503032
In [18]: id(500)
Out[18]: 140239990502960
In [19]: a = b = c = 500
50
Переменные
In [20]: id(a)
Out[20]: 140239990503080
In [21]: id(b)
Out[21]: 140239990503080
In [22]: id(c)
Out[22]: 140239990503080
Имена переменных
Имена переменных не должны пересекаться с операторами и названиями модулей
или других зарезервированных значений.
51
3. Типы данных в Python
Numbers (числа)
Strings (строки)
Lists (списки)
Dictionary (словари)
Tuples (кортежи)
Sets (множества)
Boolean
Изменяемые:
Списки
Словари
Множества
Неизменяемые
Числа
Строки
Кортежи
Упорядоченные:
Списки
Кортежи
Строки
Неупорядоченные:
Словари
Множества
52
Числа
Числа
Пример различных типов числовых значений:
In [1]: 1 + 2
Out[1]: 3
In [2]: 1.0 + 2
Out[2]: 3.0
In [3]: 10 - 4
Out[3]: 6
In [4]: 2**3
Out[4]: 8
In [5]: 10/3
Out[5]: 3
In [6]: 10/3.0
Out[6]: 3.3333333333333335
In [7]: 10 / float(3)
Out[7]: 3.3333333333333335
In [8]: float(10) / 3
Out[8]: 3.3333333333333335
In [9]: round(10/3.0, 2)
Out[9]: 3.33
In [10]: round(10/3.0, 4)
Out[10]: 3.3333
Остаток от деления:
53
Числа
In [11]: 10 % 3
Out[11]: 1
Операторы сравнения
In [13]: 10 < 3
Out[13]: False
In [14]: 10 == 3
Out[14]: False
In [15]: 10 == 10
Out[15]: True
In [16]: 10 <= 10
Out[16]: True
In [17]: 10.0 == 10
Out[17]: True
Функция int() позволяет выполнять конвертацию в тип int. Во втором аргументе можно
указывать систему исчисления:
In [18]: a = '11'
In [19]: int(a)
Out[19]: 11
Если указать, что строку a надо воспринимать как двоичное число, то результат будет
таким:
In [20]: int(a, 2)
Out[20]: 3
In [21]: int(3.333)
Out[21]: 3
In [22]: int(3.9)
Out[22]: 3
54
Числа
In [23]: bin(8)
Out[23]: '0b1000'
In [24]: bin(255)
Out[24]: '0b11111111'
In [25]: hex(10)
Out[25]: '0xa'
In [29]: math.sqrt(9)
Out[29]: 3.0
In [30]: math.sqrt(10)
Out[30]: 3.1622776601683795
In [31]: math.factorial(3)
Out[31]: 6
In [32]: math.pi
Out[32]: 3.141592653589793
55
Строки (Strings)
Строки (Strings)
Строка в Python - это последовательность символов, заключенная в кавычки.
Примеры строк:
In [9]: 'Hello'
Out[9]: 'Hello'
In [10]: "Hello"
Out[10]: 'Hello'
In [12]: tunnel
Out[12]: '\ninterface Tunnel0\n ip address 10.10.10.1 255.255.255.0\n ip mtu 1416\n ip
ospf hello-interval 5\n tunnel source FastEthernet1/0\n tunnel protection ipsec profi
le DMVPN\n'
interface Tunnel0
ip address 10.10.10.1 255.255.255.0
ip mtu 1416
ip ospf hello-interval 5
tunnel source FastEthernet1/0
tunnel protection ipsec profile DMVPN
56
Строки (Strings)
In [18]: intf * 5
Out[18]: 'interfaceinterfaceinterfaceinterfaceinterface'
In [19]: '#' * 40
Out[19]: '########################################'
In [21]: string1[0]
Out[21]: 'i'
Нумерация всех символов в строке идет с нуля. Но, если нужно обратиться к какому-то
по счету символу, начиная с конца, то можно указывать отрицательные значения (на
этот раз с единицы).
In [22]: string1[1]
Out[22]: 'n'
In [23]: string1[-1]
Out[23]: '0'
In [24]: string1[0:9]
Out[24]: 'interface'
In [25]: string1[10:22]
Out[25]: 'FastEthernet'
In [26]: string1[10:]
Out[26]: 'FastEthernet1/0'
57
Строки (Strings)
In [27]: string1[-3:]
Out[27]: '1/0'
In [28]: a = '0123456789'
In [29]: a[::]
Out[29]: '0123456789'
In [30]: a[::-1]
Out[30]: '9876543210'
In [31]: a[::2]
Out[31]: '02468'
In [32]: a[1::2]
Out[32]: '13579'
58
Полезные методы для работы со строками
Знание различных методов (то есть, действий), которые можно применять к строкам,
помогает более эффективно работать с ними.
Чаще всего эти методы будут применяться в циклах, при обработке файла.
In [26]: string1.upper()
Out[26]: 'FASTETHERNET'
In [27]: string1.lower()
Out[27]: 'fastethernet'
In [28]: string1.swapcase()
Out[28]: 'fASTeTHERNET'
In [30]: string2.capitalize()
Out[30]: 'Tunnel 0'
Так происходит из-за того, что строка это неизменяемый тип данных.
count()
59
Полезные методы для работы со строками
Метод count() используется для подсчета того, сколько раз символ или подстрока,
встречаются в строке:
In [34]: string1.count('hello')
Out[34]: 3
In [35]: string1.count('ello')
Out[35]: 4
In [36]: string1.count('l')
Out[36]: 8
find()
Методу find() можно передать подстроку или символ и он покажет на какой позиции
находится первый символ подстроки (для первого совпадения):
In [38]: string1.find('Fast')
Out[38]: 10
In [39]: string1[string1.find('Fast')::]
Out[39]: 'FastEthernet0/1'
startswith(), endswith()
In [41]: string1.startswith('Fast')
Out[41]: True
In [42]: string1.startswith('fast')
Out[42]: False
In [43]: string1.endswith('0/1')
Out[43]: True
In [44]: string1.endswith('0/2')
Out[44]: False
60
Полезные методы для работы со строками
replace()
strip()
Часто при обработке файла, файл открывается построчно. Но в конце каждой строки,
как правило, есть какие-то спецсимволы (а могут быть и вначале). Например, перевод
строки.
Для того, чтобы избавиться от них, очень удобно использовать метод strip() :
interface FastEthernet0/1
In [49]: string1
Out[49]: '\n\tinterface FastEthernet0/1\n'
In [50]: string1.strip()
Out[50]: 'interface FastEthernet0/1'
split()
Метод split() разбивает строку на части, используя как разделитель какой-то символ
(или символы). По умолчанию, в качестве разделителя используется пробел. Но в
скобках можно указать любой разделитель.
61
Полезные методы для работы со строками
In [52]: string1.split()
Out[52]: ['switchport', 'trunk', 'allowed', 'vlan', '10,20,30,100-200']
В строке string1 был символ пробела в начале и символ перевода строки в конце. В
строке номер 54, с помощью метода strip() , эти символы удаляются.
In [59]: sh_ip_int_br.split()
Out[59]: ['FastEthernet0/0', '15.0.15.1', 'YES', 'manual', 'up', 'up']
А вот так выглядит разделение той же строки, когда один пробел используется как
разделитель:
62
Полезные методы для работы со строками
63
Форматирование строк
Форматирование строк
При работе со строками, часто возникают ситуации, когда в шаблон строки надо
подставить разные данные.
Это можно делать объединяя части строки и данные, но в Python есть более удобный
способ: форматирование строк.
%d - integer
%f - float
64
Форматирование строк
In [10]: '{:08b}'.format(10)
Out[10]: '00001010'
In [11]: bin(10)[2:].zfill(8)
Out[11]: '00001010'
65
Список (List)
Список (List)
Список это изменяемый упорядоченный тип данных.
Примеры списков:
Так как список - это упорядоченный тип данных, то, как и в строках, в списках можно
обращаться к элементу по номеру, делать срезы:
In [5]: list3[1]
Out[5]: 20
In [6]: list3[1::]
Out[6]: [20, 4.0, 'word']
In [7]: list3[-1]
Out[7]: 'word'
In [8]: list3[::-1]
Out[8]: ['word', 4.0, 20, 1]
In [11]: vlans.reverse()
In [12]: vlans
Out[12]: ['100-200', '30', '20', '15', '10']
66
Список (List)
In [13]: list3
Out[13]: [1, 20, 4.0, 'word']
In [15]: list3
Out[15]: ['test', 20, 4.0, 'word']
In [17]: interfaces[0][0]
Out[17]: 'FastEthernet0/0'
In [18]: interfaces[2][0]
Out[18]: 'FastEthernet0/2'
In [19]: interfaces[2][1]
Out[19]: '10.0.2.1'
67
Полезные методы для работы со списками
join()
Метод join() собирает список строк в одну строку с разделителем, который указан в
join():
In [17]: ','.join(vlans[:-1])
Out[17]: '10,20,30'
append()
In [19]: vlans.append('300')
In [20]: vlans
Out[20]: ['10', '20', '30', '100-200', '300']
extend()
Если нужно объединить два списка, то можно использовать два способа. Метод
extend() и операцию сложения:
In [23]: vlans.extend(vlans2)
In [24]: vlans
Out[24]: ['10', '20', '30', '100-200', '300', '400', '500']
In [26]: vlans
Out[26]: ['10', '20', '30', '100-200', '300', '400', '500']
68
Полезные методы для работы со списками
Но при этом метод extend() расширяет список "на месте", а при операции сложения
выводится итоговый суммарный список, но исходные списки не меняются.
pop()
Метод pop() удаляет элемент, который соответствует указанному номеру. Но, что
важно, при этом метод возвращает этот элемент:
In [29]: vlans.pop(-1)
Out[29]: '100-200'
In [30]: vlans
Out[30]: ['10', '20', '30']
remove()
In [32]: vlans.remove('20')
In [33]: vlans
Out[33]: ['10', '30', '100-200']
В методе remove надо указывать сам элемент, который надо удалить, а не его номер в
списке. Если указать номер элемента, возникнет ошибка:
In [34]: vlans.remove(-1)
-------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-32-f4ee38810cb7> in <module>()
----> 1 vlans.remove(-1)
index()
69
Полезные методы для работы со списками
Метод index() используется для того, чтобы проверить под каким номером в списке
хранится элемент:
In [36]: vlans.index('30')
Out[36]: 2
insert()
In [38]: vlans.insert(1,'15')
In [39]: vlans
Out[39]: ['10', '15', '20', '30', '100-200']
70
Варианты создания списка
Генераторы списков:
In [5]: list2
Out[6]:
['FastEthernet0/0',
'FastEthernet0/1',
'FastEthernet0/2',
'FastEthernet0/3',
'FastEthernet0/4',
'FastEthernet0/5',
'FastEthernet0/6',
'FastEthernet0/7',
'FastEthernet0/8',
'FastEthernet0/9']
71
Словарь (Dictionary)
Словарь (Dictionary)
Словари - это изменяемый, неупорядоченный тип данных (к слову, в модуле collections
доступны упорядоченные объекты, внешне идентичные словарям OrderedDict).
Пример словаря:
london = {'name': 'London1', 'location': 'London Str', 'vendor': 'Cisco', 'model': '44
51', 'IOS': '15.4'}
london = {
'id': 1,
'name':'London',
'IT_VLAN':320,
'User_VLAN':1010,
'Mngmt_VLAN':99,
'to_name': None,
'to_id': None,
'port':'G1/0/11'
}
Для того, чтобы получить значение из словаря, надо обратиться по ключу, таким же
образом, как это было в списках, только вместо номера, будет использоваться ключ:
72
Словарь (Dictionary)
In [2]: london['name']
Out[2]: 'London1'
In [3]: london['location']
Out[3]: 'London Str'
london_co = {
'r1' : {
'hostname': 'london_r1',
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'IOS': '15.4',
'IP': '10.255.0.1'
},
'r2' : {
'hostname': 'london_r2',
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'IOS': '15.4',
'IP': '10.255.0.2'
},
'sw1' : {
'hostname': 'london_sw1',
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '3850',
'IOS': '3.6.XE',
'IP': '10.255.0.101'
}
}
73
Словарь (Dictionary)
In [7]: london_co['r1']['IOS']
Out[7]: '15.4'
In [8]: london_co['r1']['model']
Out[8]: '4451'
In [9]: london_co['sw1']['IP']
Out[9]: '10.255.0.101'
74
Полезные методы для работы со словарями
clear()
In [1]: london = {'name': 'London1', 'location': 'London Str', 'vendor': 'Cisco', 'mod
el': '4451', 'IOS': '15.4'}
In [2]: london.clear()
In [3]: london
Out[3]: {}
copy()
In [6]: id(london)
Out[6]: 25489072
In [7]: id(london2)
Out[7]: 25489072
In [9]: london2['vendor']
Out[9]: 'Juniper'
В этом случае london2 это еще одно имя, которое ссылается на словарь. И, при
изменениях словаря london, меняется и словарь london2, так как это ссылки на один и
тот же объект.
Поэтому, если нужно сделать копию словаря, надо использовать метод copy():
75
Полезные методы для работы со словарями
In [12]: id(london)
Out[12]: 25524512
In [13]: id(london2)
Out[13]: 25563296
In [15]: london2['vendor']
Out[15]: 'Cisco'
get()
Если при обращении к словарю указывается ключ, которого нет в словаре, возникает
ошибка:
In [17]: london['IOS']
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-17-b4fae8480b21> in <module>()
----> 1 london['IOS']
KeyError: 'IOS'
Метод get() запрашивает ключ и, если его нет, вместо ошибки возвращает None .
76
Полезные методы для работы со словарями
In [25]: london.keys()
Out[25]: ['vendor', 'name', 'location']
In [26]: london.values()
Out[26]: ['Cisco', 'London1', 'London Str']
In [27]: london.items()
Out[27]: [('vendor', 'Cisco'), ('name', 'London1'), ('location', 'London Str')]
del
In [29]: del(london['name'])
In [30]: london
Out[30]: {'location': 'London Str', 'vendor': 'Cisco'}
77
Варианты создания словаря
Если в роли ключей используются строки, можно использовать такой вариант создания
словаря:
In [3]: r1
Out[3]: {'IOS': '15.4', 'model': '4451'}
In [5]: r1
Out[5]: {'IOS': '15.4', 'model': '4451'}
В ситуации, когда надо создать словарь с известными ключами, но, пока что, пустыми
значениями (или одинаковыми значениями), очень удобен метод fromkeys():
In [7]: r1
Out[7]:
{'IOS': None,
'IP': None,
'hostname': None,
'location': None,
'model': None,
'vendor': None}
78
Варианты создания словаря
In [18]: d
Out[18]:
{'IOS': None,
'IP': None,
'hostname': None,
'location': None,
'model': None,
'vendor': None}
79
Варианты создания словаря
In [5]: r1
Out[5]: {'IOS': '15.4', 'model': '4451'}
Как правило, такой способ создания используется не в том случае, когда словарь
создается вручную в текстовом файле, а когда словарь создается из каких-то других
промежуточных объектов.
In [1]: a = [1,2,3]
In [2]: b = [100,200,300]
In [3]: zip(a,b)
Out[3]: [(1, 100), (2, 200), (3, 300)]
80
Варианты создания словаря
In [6]: zip(d_keys,d_values)
Out[6]:
[('hostname', 'london_r1'),
('location', '21 New Globe Walk'),
('vendor', 'Cisco'),
('model', '4451'),
('IOS', '15.4'),
('IP', '10.255.0.1')]
In [7]: dict(zip(d_keys,d_values))
Out[7]:
{'IOS': '15.4',
'IP': '10.255.0.1',
'hostname': 'london_r1',
'location': '21 New Globe Walk',
'model': '4451',
'vendor': 'Cisco'}
In [8]: r1 = dict(zip(d_keys,d_values))
In [9]: r1
Out[9]:
{'IOS': '15.4',
'IP': '10.255.0.1',
'hostname': 'london_r1',
'location': '21 New Globe Walk',
'model': '4451',
'vendor': 'Cisco'}
81
Варианты создания словаря
In [11]: data = {
....: 'r1': ['london_r1', '21 New Globe Walk', 'Cisco', '4451', '15.4', '10.255.0.1'
],
....: 'r2': ['london_r2', '21 New Globe Walk', 'Cisco', '4451', '15.4', '10.255.0.2'
],
....: 'sw1': ['london_sw1', '21 New Globe Walk', 'Cisco', '3850', '3.6.XE', '10.255
.0.101']
....: }
In [12]: london_co = {}
In [14]: london_co
Out[14]:
{'r1': {'IOS': '15.4',
'IP': '10.255.0.1',
'hostname': 'london_r1',
'location': '21 New Globe Walk',
'model': '4451',
'vendor': 'Cisco'},
'r2': {'IOS': '15.4',
'IP': '10.255.0.2',
'hostname': 'london_r2',
'location': '21 New Globe Walk',
'model': '4451',
'vendor': 'Cisco'},
'sw1': {'IOS': '3.6.XE',
'IP': '10.255.0.101',
'hostname': 'london_sw1',
'location': '21 New Globe Walk',
'model': '3850',
'vendor': 'Cisco'}}
82
Кортеж (Tuple)
Кортеж (Tuple)
Кортеж это неизменяемый упорядоченный тип данных.
Грубо говоря, кортеж - это список, который нельзя изменить. То есть, в кортеже есть
только права на чтение. Это может быть защитой от случайных изменений.
Кортеж из списка:
In [6]: tuple_keys
Out[6]: ('hostname', 'location', 'vendor', 'model', 'IOS', 'IP')
In [7]: tuple_keys[0]
Out[7]: 'hostname'
83
Кортеж (Tuple)
84
Множество (Set)
Множество (Set)
Множество - это изменяемый неупорядоченный тип данных. В множестве всегда
содержатся только уникальные элементы.
In [2]: set(vlans)
Out[2]: {10, 20, 30, 40, 100}
85
Полезные методы для работы с множествами
add()
In [2]: set1.add(50)
In [3]: set1
Out[3]: {10, 20, 30, 40, 50}
discard()
In [3]: set1
Out[3]: {10, 20, 30, 40, 50}
In [4]: set1.discard(55)
In [5]: set1
Out[5]: {10, 20, 30, 40, 50}
In [6]: set1.discard(50)
In [7]: set1
Out[7]: {10, 20, 30, 40}
clear()
In [9]: set1.clear()
In [10]: set1
Out[10]: set()
86
Полезные методы для работы с множествами
87
Операции с множествами
Операции с множествами
Множества полезны тем, что с ними можно делать различные операции и находить
объединение множеств, пересечение и так далее.
In [3]: vlans1.union(vlans2)
Out[3]: {10, 20, 30, 50, 100, 101, 102, 200}
In [7]: vlans1.intersection(vlans2)
Out[7]: {100}
88
Варианты создания множества
In [1]: set1 = {}
In [2]: type(set1)
Out[2]: dict
In [4]: type(set2)
Out[4]: set
Множество из строки:
Множество из списка:
In [6]: set([10,20,30,10,10,30])
Out[6]: {10, 20, 30}
Генератор множеств:
In [8]: set2
Out[8]: {100, 101, 102, 103, 104, 105, 106, 107, 108, 109}
89
Преобразование типов
Преобразование типов
В Python есть несколько полезных встроенных функций, которые позволяют
преобразовать данные из одного типа в другой.
int()
int() - преобразует строку в int:
In [1]: int("10")
Out[1]: 10
In [2]: int("11111111", 2)
Out[2]: 255
bin()
Преобразовать десятичное число в двоичный формат можно с помощью bin() :
In [3]: bin(10)
Out[3]: '0b1010'
In [4]: bin(255)
Out[4]: '0b11111111'
hex()
Аналогичная функция есть и для преобразования в шестнадцатиричный формат:
In [5]: hex(10)
Out[5]: '0xa'
In [6]: hex(255)
Out[6]: '0xff'
list()
90
Преобразование типов
In [7]: list("string")
Out[7]: ['s', 't', 'r', 'i', 'n', 'g']
In [8]: list({1,2,3})
Out[8]: [1, 2, 3]
In [9]: list((1,2,3,4))
Out[9]: [1, 2, 3, 4]
set()
Функция set() преобразует аргумент в множество:
In [10]: set([1,2,3,3,4,4,4,4])
Out[10]: {1, 2, 3, 4}
In [11]: set((1,2,3,3,4,4,4,4))
Out[11]: {1, 2, 3, 4}
tuple()
Функция tuple() преобразует аргумент в кортеж:
In [13]: tuple([1,2,3,4])
Out[13]: (1, 2, 3, 4)
In [14]: tuple({1,2,3,4})
Out[14]: (1, 2, 3, 4)
In [15]: tuple("string")
Out[15]: ('s', 't', 'r', 'i', 'n', 'g')
Это может пригодится в том случае, если нужно получить неизменяемый объект.
str()
Функция str() преобразует аргумент в строку:
91
Преобразование типов
In [16]: str(10)
Out[16]: '10'
Например, она пригодится в ситуации, когда есть список VLANов, который надо
преобразовать в одну строку, где номера перечислены через запятую.
In [18]: ','.join(vlans)
------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-39-d705aed3f1b3> in <module>()
----> 1 ','.join(vlans)
Чтобы исправить это, нужно преобразовать числа в строки. Это удобно делать с
помощью list comprehensions:
92
Проверка типов
Проверка типов
При преобразовании типов данных, могут возникнуть ошибки такого рода:
In [1]: int('a')
------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-42-b3c3f4515dd4> in <module>()
----> 1 int('a')
И если тут пример выглядит, возможно, глупым, тем не менее, когда нужно, например,
пройтись по списку строк и преобразовать в числа те из них, которые содержат числа,
можно получить такую ошибку.
isdigit()
В Python такие методы есть. Например, чтобы проверить состоит ли строка из одних
цифр, можно использовать метод isdigit() :
In [2]: "a".isdigit()
Out[2]: False
In [3]: "a10".isdigit()
Out[3]: False
In [4]: "10".isdigit()
Out[4]: True
isalpha()
93
Проверка типов
In [7]: "a".isalpha()
Out[7]: True
In [8]: "a100".isalpha()
Out[8]: False
isalnum()
In [11]: "a".isalnum()
Out[1]: True
In [12]: "a10".isalnum()
Out[12]: True
type()
In [13]: type("string")
Out[13]: str
94
Проверка типов
In [15]: type((1,2,3))
Out[15]: tuple
95
Задания
Задания
Все задания и вспомогательные файлы можно скачать одним архивом zip или tar.gz.
Также для курса подготовлены две виртуальные машины на выбор: Vagrant или
VMware. В них установлены все пакеты, которые используются в курсе.
Например, если в разделе есть задания: 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала,
можно выполнить задания 5.1, 5.2, 5.3. А затем 5.2a, 5.2b, 5.3a.
Задание 3.1
Обработать строку ospf_route и вывести информацию в виде:
Protocol: OSPF
Prefix: 10.0.24.0/24
AD/Metric: 110/41
Next-Hop: 10.0.13.3
Last update: 3d18h
Outbound Interface: FastEthernet0/0
Задание 3.2
Преобразовать строку MAC с формата XXXX:XXXX:XXXX в XXXX.XXXX.XXXX
MAC = "AAAA:BBBB:CCCC"
96
Задания
Задание 3.3
Получить из строки CONFIG список VLAN вида ['1', '3', '10', '20', '30', '100'].
Задание 3.4
Из строк command1 и command2 получить список VLAN, которые есть и в команде
command1 и в команде command2. Не использовать для решения задачи циклы,
оператор if.
Задание 3.5
Список VLANS - это список VLANов, собранных со всех устройств сети, поэтому в
списке есть повторяющиеся номера VLAN.
Задание 3.6
Обработать строку NAT таким образом, чтобы в имени интерфейса вместо
FastEthernet было GigabitEthernet.
NAT = "ip nat inside source list ACL interface FastEthernet0/1 overload"
Задание 3.7
Преобразовать MAC-адрес в двоичную строку (без двоеточий).
MAC = "AAAA:BBBB:CCCC"
97
Задания
Задание 3.8
Преобразовать IP-адрес (переменная IP) в двоичный формат и вывести вывод
столбцами, таким образом:
столбцами
ширина столбца 10 символов
Пример вывода:
10 1 1 1
00001010 00000001 00000001 00000001
IP = '192.168.3.1'
Задание 3.9
Найти индекс последнего вхождения элемента с конца.
98
4. Создание базовых скриптов
print '\n'.join(access_template) % 5
$ python access_template.py
switchport mode access
switchport access vlan 5
switchport nonegotiate
spanning-tree portfast
spanning-tree bpduguard enable
Но, если вы используете windows, то это желательно делать, так как Windows
использует расширение файла, для определения того как обрабатывать файл.
Кодировка
99
4. Создание базовых скриптов
$ python access_template_encoding.py
File "access_template_encoding.py", line 7
SyntaxError: Non-ASCII character '\xd0' in file access_template_encoding.py on line 7,
but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
Для того чтобы не было такой ошибки, необходимо добавить в начале файла такую
строку:
100
4. Создание базовых скриптов
$ python access_template_encoding.py
Конфигурация интерфейса в режиме access:
switchport mode access
switchport access vlan 5
switchport nonegotiate
spanning-tree portfast
spanning-tree bpduguard enable
Исполняемый файл
Для того, чтобы файл был исполняемым и не нужно было каждый раз писать python
перед вызовом файла, нужно:
#!/usr/bin/env python
print '\n'.join(access_template) % 5
После этого:
chmod +x access_template_exec.py
$ ./access_template_exec.py
101
Передача аргументов скрипту
Гораздо лучше будет передавать имя файла как аргумент скрипта и затем
использовать уже указанный файл.
В Python, в модуле sys есть очень простой и удобный способ для работы с
аргументами - argv.
102
Передача аргументов скрипту
Выражение argv[1:] должно быть знакомым. Это срез списка. То есть, в правой
стороне остается список, с двумя элементами: ['Gi0/7', '4'] .
In [16]: a = 5
In [17]: b = 6
In [18]: c, d = 5, 6
In [19]: c
Out[19]: 5
In [20]: d
Out[20]: 6
In [23]: interface
Out[23]: 'Gi0/7'
In [24]: vlan
Out[24]: '4'
Обратите внимание, что argv всегда возвращает аргументы в виде строки. А, так
как в списке access_template в синтаксисе форматирования строк, указано число,
переменную vlan нужно преобразовать в число (последняя строка).
103
Передача аргументов скрипту
104
Ввод информации пользователем
105
Ввод информации пользователем
преобразуем в число.
Еще появилась строка print '\n' + '-' * 30 . Она используется просто для того,
чтобы отделить запрос информации от вывода.
Выполняем скрипт:
$ python access_template_raw_input.py
Enter interface type and number: Gi0/3
Enter VLAN number: 55
------------------------------
interface Gi0/3
switchport mode access
switchport access vlan 55
switchport nonegotiate
spanning-tree portfast
spanning-tree bpduguard enable
106
Задания
Задания
Все задания и вспомогательные файлы можно скачать одним архивом zip или tar.gz.
Также для курса подготовлены две виртуальные машины на выбор: Vagrant или
VMware. В них установлены все пакеты, которые используются в курсе.
Например, если в разделе есть задания: 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала,
можно выполнить задания 5.1, 5.2, 5.3. А затем 5.2a, 5.2b, 5.3a.
Задание 4.1
Запросить у пользователя ввод IP-сети в формате: 10.1.1.0/24
Network:
10 1 1 0
00001010 00000001 00000001 00000000
Mask:
/24
255 255 255 0
11111111 11111111 11111111 00000000
Задание 4.1a
Всё, как в задании 4.1. Но, если пользователь ввел адрес хоста, а не адрес сети, то
надо адрес хоста преобразовать в адрес сети и вывести адрес сети и маску, как в
задании 4.1.
107
Задания
10.0.1.0/24
190.1.0.0/16
Network:
10 1 1 0
00001010 00000001 00000001 00000000
Mask:
/24
255 255 255 0
11111111 11111111 11111111 00000000
Задание 4.1b
Преобразовать скрипт из задания 4.1a таким образом, чтобы сеть/маска не
запрашивались у пользователя, а передавались как аргумент скрипту.
Задание 4.2
В этой задаче нельзя использовать условие if и нельзя изменять словарь london_co.
Вам нужно запросить у пользователя ввод имени устройства (r1, r2 или sw1). И
вывести информацию о соответствующем устройстве на стандартный поток вывода
(информация будет в виде словаря).
$ python task_4_2.py
Enter device name: r1
{'ios': '15.4', 'model': '4451', 'vendor': 'Cisco', 'location': '21 New Globe Walk', '
ip': '10.255.0.1'}
108
Задания
london_co = {
'r1' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.1'
},
'r2' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.2'
},
'sw1' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '3850',
'ios': '3.6.XE',
'ip': '10.255.0.101',
'vlans': '10,20,30',
'routing': True
}
}
Задание 4.2a
В этой задаче нельзя использовать условие if и нельзя изменять словарь london_co.
Переделать скрипт из задания 4.2 таким образом, чтобы, кроме имени устройства,
запрашивался также параметр устройства, который нужно отобразить.
$ python task_4_2a.py
Enter device name: r1
Enter parameter name: ios
15.4
109
Задания
london_co = {
'r1' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.1'
},
'r2' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.2'
},
'sw1' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '3850',
'ios': '3.6.XE',
'ip': '10.255.0.101',
'vlans': '10,20,30',
'routing': True
}
}
Задание 4.2b
В этой задаче нельзя использовать условие if и нельзя изменять словарь london_co.
Переделать скрипт из задания 4.2a таким образом, чтобы, при запросе параметра,
отображался список возможных параметров.
$ python task_4_2b.py
Enter device name: r1
Enter parameter name (ios,model,vendor,location,ip): ip
10.255.0.1
110
Задания
london_co = {
'r1' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.1'
},
'r2' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.2'
},
'sw1' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '3850',
'ios': '3.6.XE',
'ip': '10.255.0.101',
'vlans': '10,20,30',
'routing': True
}
}
Задание 4.2c
В этой задаче нельзя использовать условие if и нельзя изменять словарь london_co.
Переделать скрипт из задания 4.2b таким образом, чтобы, при запросе параметра,
которого нет в словаре устройства, отображалось сообщение 'Такого параметра нет'.
$ python task_4_2c.py
Enter device name: r1
Enter parameter name (ios,model,vendor,location,ip): io
Такого параметра нет
111
Задания
london_co = {
'r1' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.1'
},
'r2' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.2'
},
'sw1' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '3850',
'ios': '3.6.XE',
'ip': '10.255.0.101',
'vlans': '10,20,30',
'routing': True
}
}
Задание 4.2d
В этой задаче нельзя использовать условие if и нельзя изменять словарь london_co.
Переделать скрипт из задания 4.2c таким образом, чтобы, при запросе параметра,
пользователь мог вводить название параметра в любом регистре.
$ python task_4_2d.py
Enter device name: r1
Enter parameter name (ios,model,vendor,location,ip): IOS
15.4
112
Задания
london_co = {
'r1' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.1'
},
'r2' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '4451',
'ios': '15.4',
'ip': '10.255.0.2'
},
'sw1' : {
'location': '21 New Globe Walk',
'vendor': 'Cisco',
'model': '3850',
'ios': '3.6.XE',
'ip': '10.255.0.101',
'vlans': '10,20,30',
'routing': True
}
}
Задание 4.3
(Задача на основе примеров в разделе)
При этом, сначала должна идти строка interface и подставлен номер интерфейса, а
затем соответствующий шаблон, в который подставлен номер VLANа (или список
VLANов).
113
Задания
$ python task_4_3.py
Enter interface mode (access/trunk): access
Enter interface type and number: Fa0/6
Enter vlan(s): 3
interface Fa0/6
switchport mode access
switchport access vlan 3
switchport nonegotiate
spanning-tree portfast
spanning-tree bpduguard enable
$ python task_4_3.py
Enter interface mode (access/trunk): trunk
Enter interface type and number: Fa0/7
Enter vlan(s): 2,3,4,5
interface Fa0/7
switchport trunk encapsulation dot1q
switchport mode trunk
switchport trunk allowed vlan 2,3,4,5
Задание 4.3a
В этой задаче нельзя использовать условие if.
114
Задания
115
5. Контроль хода программы
if/elif/else
циклом for
циклом while
Раздел про конструкции for/else и while/else, возможно, будет проще понять, если
прочесть их после раздела об обработке исключений.
116
if/elif/else
if/elif/else
Конструкция if/elif/else дает возможность выполнять различные действия в
зависимости от условий.
Пример конструкции:
In [1]: a = 9
In [2]: if a == 0:
...: print 'a равно 0'
...: elif a < 10:
...: print 'a меньше 10'
...: else:
...: print 'a больше 10'
...:
a меньше 10
117
if/elif/else
In [7]: 5 > 3
Out[7]: True
In [8]: 5 == 5
Out[8]: True
In [10]: 1 in [ 1, 2, 3 ]
Out[10]: True
In [11]: 0 in [ 1, 2, 3 ]
Out[11]: False
True и False
В Python:
True (истина)
любое ненулевое число
любая не пустая строка
любой не пустой объект
False (ложь)
0
None
пустая строка
пустой объект
Остальные значения True или False, как правило, логически следуют из условия.
Например, так как пустой список это ложь, проверить пустой ли список можно таким
образом:
In [13]: if list_to_test:
....: print "В списке есть объекты"
....:
В списке есть объекты
118
if/elif/else
In [14]: if len(list_to_test) != 0:
....: print "В списке есть объекты"
....:
В списке есть объекты
Операторы сравнения
Операторы сравнения, которые могут использоваться в условиях:
In [3]: 5 > 6
Out[3]: False
In [4]: 5 > 2
Out[4]: True
In [5]: 5 < 2
Out[5]: False
In [6]: 5 == 2
Out[6]: False
In [7]: 5 == 5
Out[7]: True
In [8]: 5 >= 5
Out[8]: True
In [9]: 5 <= 10
Out[9]: True
In [10]: 8 != 10
Out[10]: True
Оператор in
Оператор in позволяет выполнять проверку на наличие элемента в
последовательности (например, элемента в списке или подстроки в строке):
119
if/elif/else
In [11]: 10 in vlan
Out[11]: True
In [12]: 50 in vlan
Out[12]: False
In [15]: r1 = {
....: 'IOS': '15.4',
....: 'IP': '10.255.0.1',
....: 'hostname': 'london_r1',
....: 'location': '21 New Globe Walk',
....: 'model': '4451',
....: 'vendor': 'Cisco'}
In [16]: 'IOS' in r1
Out[16]: True
In [17]: '4451' in r1
Out[17]: False
120
if/elif/else
In [15]: r1 = {
....: 'IOS': '15.4',
....: 'IP': '10.255.0.1',
....: 'hostname': 'london_r1',
....: 'location': '21 New Globe Walk',
....: 'model': '4451',
....: 'vendor': 'Cisco'}
Оператор and
В Python оператор and возвращает не булево значение, а значение одного из
операторов.
121
if/elif/else
Оператор or
Оператор or , как и оператор and, возвращает значение одного из операторов.
In [31]: '' or [] or {}
Out[31]: {}
122
if/elif/else
if len(password) < 8:
print 'Пароль слишком короткий'
elif username in password:
print 'Пароль содержит имя пользователя'
else:
print 'Пароль для пользователя %s установлен' % username
Проверка скрипта:
$ python check_password.py
Введите имя пользователя: nata
Введите пароль: nata1234
Пароль содержит имя пользователя
$ python check_password.py
Введите имя пользователя: nata
Введите пароль: 123nata123
Пароль содержит имя пользователя
$ python check_password.py
Введите имя пользователя: nata
Введите пароль: 1234
Пароль слишком короткий
$ python check_password.py
Введите имя пользователя: nata
Введите пароль: 123456789
Пароль для пользователя nata установлен
s = [1, 2, 3, 4]
result = True if len(s) > 5 else False
123
for
for
Цикл for проходится по указанной последовательности и выполняет действия, которые
указаны в блоке for.
строка
список
словарь
функция '''range()''' или итератор '''xrange()'''
любой другой итератор (например, '''sorted()''', '''enumerate()''')
В курсе не рассматривается, что такое итератор, но, если в двух словах, это такой
специальный объект, который дает следующее значение только тогда, когда оно
нужно. Например, если при использовании выражения range(10) , функция вернет
список чисел от 0 до 9 (включительно) сразу. Если же воспользоваться
итератором xrange() , то он вернет специальный объект. И не будет возвращать
числа, пока к нему не будут обращаться, например, в цикле for. Из-за таких
особенностей, всегда, когда надо работать с большим количеством объектов,
лучше использовать итератор. В общем случае, его лучше использовать везде,
где он подходит по функциональности.
s
t
r
i
n
g
В цикле используется переменная с именем letter. Хотя имя может быть любое,
удобно, когда имя подсказывает через какие объекты проходит цикл.
124
for
В этом примере, цикл проходит по списку VLANов, поэтому переменную можно назвать
vlan:
125
for
In [5]: r1 = {
'IOS': '15.4',
'IP': '10.255.0.1',
'hostname': 'london_r1',
'location': '21 New Globe Walk',
'model': '4451',
'vendor': 'Cisco'}
В словаре есть специальный метод items, который позволяет проходится в цикле сразу
по паре ключ, значение:
126
for
In [9]: r1.items()
Out[9]:
[('vendor', 'Cisco'),
('IP', '10.255.0.1'),
('hostname', 'london_r1'),
('IOS', '15.4'),
('location', '21 New Globe Walk'),
('model', '4451')]
In [10]: r1.iteritems()
Out[10]: <dictionary-itemiterator at 0x102daff18>
127
Вложенные for
Вложенные for
Циклы for можно вкладывать друг в друга.
В этом примере, в списке commands хранятся команды, которые надо выполнить для
каждого из интерфейсов в списке fast_int:
128
Совмещение for и if
Совмещение for и if
Рассмотрим пример совмещения for и if.
Файл generate_access_port_config.py:
Комментарии к коду:
129
Совмещение for и if
$ python generate_access_port_config.py
interface FastEthernet0/12
switchport mode access
switchport access vlan 10
spanning-tree portfast
spanning-tree bpduguard enable
interface FastEthernet0/14
switchport mode access
switchport access vlan 11
spanning-tree portfast
spanning-tree bpduguard enable
interface FastEthernet0/16
switchport mode access
switchport access vlan 17
spanning-tree portfast
spanning-tree bpduguard enable
130
Итератор enumerate
Итератор enumerate()
Иногда, при переборе объектов в цикле for, нужно не только получить сам объект, но и
его порядковый номер. Это можно сделать, создав дополнительную переменную,
которая будет расти на единицу с каждым прохождением цикла.
Базовый пример:
В этом примере используется Cisco EEM. Если в двух словах, то EEM позволяет
выполнять какие-то действия (action) в ответ на событие (event).
131
Итератор enumerate
В EEM, в ситуации, когда действий выполнить нужно много, неудобно каждый раз
набирать action x cli command . Плюс, чаще всего, уже есть готовый кусок
конфигурации, который должен выполнить EEM.
import sys
config = sys.argv[1]
en
conf t
no int Gi0/0/0.300
no int Gi0/0/0.301
no int Gi0/0/0.302
int range gi0/0/0-2
channel-group 1 mode active
interface Port-channel1.300
encapsulation dot1Q 300
vrf forwarding Management
ip address 10.16.19.35 255.255.255.248
132
Итератор enumerate
133
Итератор enumerate
134
while
while
Цикл while это еще одна разновидность цикла в Python.
В цикле while, как и в выражении if, надо писать условие. Если условие истинно,
выполняются действия внутри блока while. Но, в отличии от if, после выполнения while
возвращается в начало цикла.
In [1]: a = 5
Затем, в цикле while указано условие a > 0. То есть, пока значение а больше 0, будут
выполняться действия в теле цикла. В данном случае, будет выводиться значение
переменной а.
Кроме того, в теле цикла, при каждом прохождении, значение а становится на единицу
меньше.
135
while
С помощью цикла while, можно сделать так, что скрипт сам будет запрашивать пароль
заново, если он не соответствует требованиям.
Файл check_password_with_while.py:
pass_OK = False
В этом случае цикл while полезен, так как он возвращает скрипт снова в начало
проверок, позволяет снова набрать пароль, но при этом не требует перезапуска самого
скрипта.
$ python check_password_with_while.py
Введите имя пользователя: nata
Введите пароль: nata
Пароль слишком короткий
136
break, continue, pass
Оператор break
Оператор break позволяет досрочно прервать цикл:
In [2]: i = 0
In [3]: while i < 10:
...: if i == 5:
...: break
...: else:
...: print i
...: i += 1
...:
0
1
2
3
4
137
break, continue, pass
Ещё пример:
while True:
if len(password) < 8:
print 'Пароль слишком короткий\n'
password = raw_input('Введите пароль еще раз: ' )
elif username in password:
print 'Пароль содержит имя пользователя\n'
password = raw_input('Введите пароль еще раз: ' )
else:
print 'Пароль для пользователя %s установлен' % username
break
Оператор continue
Оператор continue возвращает управление в начало цикла. То есть, continue позволяет
"перепрыгнуть" оставшиеся выражения в цикле и перейти к следующей итерации.
138
break, continue, pass
In [5]: i = 0
In [6]: while i < 6:
....: i += 1
....: if i == 3:
....: print "Пропускаем 3"
....: continue
....: print "Это никто не увидит"
....: else:
....: print "Текущее значение: ", i
....:
Текущее значение: 1
Текущее значение: 2
Пропускаем 3
Текущее значение: 4
Текущее значение: 5
Текущее значение: 6
Оператор pass
Оператор pass ничего не делает. Фактически это такая заглушка для объектов.
Например, pass может помочь в ситуации, когда нужно прописать структуру скрипта.
Его можно ставить в циклах, функциях, классах. И это не будет влиять на исполнение
кода.
139
for/else, while/else
for/else, while/else
В циклах for и while опционально может использоваться блок else.
for/else
В цикле for:
блок else выполняется в том случае, если цикл завершил итерацию списка
но else не выполняется, если в цикле был выполнен break
Пример цикла for с else (блок else выполняется после завершения цикла for):
Пример цикла for с else и break в цикле (из-за break, блок else не выполняется):
Пример цикла for с else и continue в цикле (continue не влияет на блок else):
140
for/else, while/else
while/else
В цикле while:
блок else выполняется в том случае, если цикл завершил итерацию списка
но else не выполняется, если в цикле был выполнен break
Пример цикла while с else (блок else выполняется после завершения цикла while):
In [4]: i = 0
In [5]: while i < 5:
....: print i
....: i += 1
....: else:
....: print "Конец"
....:
0
1
2
3
4
Конец
Пример цикла while с else и break в цикле (из-за break, блок else не выполняется):
141
for/else, while/else
In [6]: i = 0
142
Работа с исключениями try/except/else/finally
Но, даже если код синтаксически написан правильно, все равно могут возникать
ошибки. Эти ошибки называются исключения (exceptions).
Примеры исключений:
In [1]: 2/0
-----------------------------------------------------
ZeroDivisionError: integer division or modulo by zero
In [2]: 'test' + 2
-----------------------------------------------------
TypeError: cannot concatenate 'str' and 'int' objects
Например, если программа на вход ожидает два числа, а на выходе выдает их сумму,
а пользователь ввел вместо одного числа, строку, появится ошибка TypeError, как в
примере выше.
143
Работа с исключениями try/except/else/finally
In [3]: try:
...: 2/0
...: except ZeroDivisionError:
...: print "You can't divide by zero"
...:
You can't divide by zero
In [4]: try:
...: print "Let's divide some numbers"
...: 2/0
...: print 'Cool!'
...: except ZeroDivisionError:
...: print "You can't divide by zero"
...:
Let's divide some numbers
You can't divide by zero
В конструкции try/except может быть много except, если нужны разные действия, в
зависимости от ошибки.
try:
a = raw_input("Введите первое число: ")
b = raw_input("Введите второе число: ")
print "Результат: ", int(a)/int(b)
except ValueError:
print "Пожалуйста, вводите только числа"
except ZeroDivisionError:
print "На ноль делить нельзя"
144
Работа с исключениями try/except/else/finally
$ python divide.py
Введите первое число: 3
Введите второе число: 1
Результат: 3
$ python divide.py
Введите первое число: 5
Введите второе число: 0
Результат: На ноль делить нельзя
$ python divide.py
Введите первое число: qewr
Введите второе число: 3
Результат: Пожалуйста, вводите только числа
try:
a = raw_input("Введите первое число: ")
b = raw_input("Введите второе число: ")
print "Результат: ", int(a)/int(b)
except (ValueError, ZeroDivisionError):
print "Что-то пошло не так..."
Проверка:
$ python divide_ver2.py
Введите первое число: wer
Введите второе число: 4
Результат: Что-то пошло не так...
$ python divide_ver2.py
Введите первое число: 5
Введите второе число: 0
Результат: Что-то пошло не так...
145
Работа с исключениями try/except/else/finally
try/except/else
В конструкции try/except есть опциональный блок else. Он выполняется в том случае,
если не было исключения.
try:
a = raw_input("Введите первое число: ")
b = raw_input("Введите второе число: ")
result = int(a)/int(b)
except (ValueError, ZeroDivisionError):
print "Что-то пошло не так..."
else:
print "Результат в квадрате: ", result**2
Пример выполнения:
$ python divide_ver3.py
Введите первое число: 10
Введите второе число: 2
Результат в квадрате: 25
$ python divide_ver3.py
Введите первое число: werq
Введите второе число: 3
Что-то пошло не так...
try/except/finally
Блок finally это еще один опциональный блок в конструкции try. Он выполняется
всегда, независимо от того, было ли исключение или нет.
Сюда ставятся действия, которые надо выполнить в любом случае. Например, это
может быть закрытие файла.
146
Работа с исключениями try/except/else/finally
try:
a = raw_input("Введите первое число: ")
b = raw_input("Введите второе число: ")
result = int(a)/int(b)
except (ValueError, ZeroDivisionError):
print "Что-то пошло не так..."
else:
print "Результат в квадрате: ", result**2
finally:
print "Вот и сказочке конец, а кто слушал - молодец."
Проверка:
$ python divide_ver4.py
Введите первое число: 10
Введите второе число: 2
Результат в квадрате: 25
Вот и сказочке конец, а кто слушал - молодец.
$ python divide_ver4.py
Введите первое число: qwerewr
Введите второе число: 3
Что-то пошло не так...
Вот и сказочке конец, а кто слушал - молодец.
$ python divide_ver4.py
Введите первое число: 4
Введите второе число: 0
Что-то пошло не так...
Вот и сказочке конец, а кто слушал - молодец.
147
Задания
Задания
Все задания и вспомогательные файлы можно скачать одним архивом zip или tar.gz.
Также для курса подготовлены две виртуальные машины на выбор: Vagrant или
VMware. В них установлены все пакеты, которые используются в курсе.
Например, если в разделе есть задания: 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала,
можно выполнить задания 5.1, 5.2, 5.3. А затем 5.2a, 5.2b, 5.3a.
Задание 5.1
1. Запросить у пользователя ввод IP-адреса в десятично-точечном формате.
2. Определить какому классу принадлежит IP-адрес.
3. В зависимости от класса адреса, вывести на стандартный поток вывода:
'unicast' - если IP-адрес принадлежит классу A, B или C
'multicast' - если IP-адрес принадлежит классу D
'local broadcast' - если IP-адрес равен 255.255.255.255
'unassigned' - если IP-адрес равен 0.0.0.0
'unused' - во всех остальных случаях
A: 1-127
B: 128-191
C: 192-223
D: 224-239
Задание 5.1a
Сделать копию скрипта задания 5.1.
148
Задания
Дополнить скрипт:
Задание 5.1b
Сделать копию скрипта задания 5.1a.
Дополнить скрипт:
Задание 5.2
Список mac содержит MAC-адреса в формате XXXX:XXXX:XXXX.
mac_cisco = []
Задание 5.3
В скрипте сделан генератор конфигурации для access-портов.
В транках ситуация усложняется тем, что VLANов может быть много, и надо понимать,
что с ними делать.
149
Задания
add - значит VLANы надо будет добавить (команда switchport trunk allowed vlan add
10,20)
del - значит VLANы надо удалить из списка разрешенных (команда switchport trunk
allowed vlan remove 17)
only - значит, что на интерфейсе должны остаться разрешенными только
указанные VLANы (команда switchport trunk allowed vlan 11,30)
fast_int = {'access':{'0/12':'10','0/14':'11','0/16':'17','0/17':'150'},
'trunk':{'0/1':['add','10','20'],
'0/2':['only','11','30'],
'0/4':['del','17']} }
150
6. Работа с файлами
Работа с файлами
В реальной жизни, для того чтобы полноценно использовать всё, что рассматривалось
до этого раздела, надо разобраться как работать с файлами.
открытие/закрытие
чтение
запись
151
Открытие файлов
Открытие файлов
Для начала работы с файлом, его надо открыть.
open()
В функции open():
Функция open() создает объект file, к которому потом можно применять различные
методы, для работы с ним.
152
Чтение файлов
Чтение файлов
В Python есть несколько методов чтения файла:
!
service timestamps debug datetime msec localtime show-timezone year
service timestamps log datetime msec localtime show-timezone year
service password-encryption
service sequence-numbers
!
no ip domain lookup
!
ip ssh version 2
!
read()
In [1]: f = open('r1.txt')
In [2]: f.read()
Out[2]: '!\nservice timestamps debug datetime msec localtime show-timezone year\nservi
ce timestamps log datetime msec localtime show-timezone year\nservice password-encrypt
ion\nservice sequence-numbers\n!\nno ip domain lookup\n!\nip ssh version 2\n!\n'
In [3]: f.read()
Out[3]: ''
При повторном чтении файла в 3 строке, отображается пустая строка. Так происходит
из-за того, что при вызове метода read() , считывается весь файл. И после того, как
файл был считан, курсор остается в конце файла. Управлять положением курсора
можно с помощью метода seek() .
readline()
153
Чтение файлов
In [4]: f = open('r1.txt')
In [5]: f.readline()
Out[5]: '!\n'
In [6]: f.readline()
Out[6]: 'service timestamps debug datetime msec localtime show-timezone year\n'
Но, чаще всего, проще пройтись по объекту file в цикле, не используя методы read... :
In [7]: f = open('r1.txt')
service password-encryption
service sequence-numbers
no ip domain lookup
ip ssh version 2
readlines()
154
Чтение файлов
In [9]: f = open('r1.txt')
In [10]: f.readlines()
Out[10]:
['!\n',
'service timestamps debug datetime msec localtime show-timezone year\n',
'service timestamps log datetime msec localtime show-timezone year\n',
'service password-encryption\n',
'service sequence-numbers\n',
'!\n',
'no ip domain lookup\n',
'!\n',
'ip ssh version 2\n',
'!\n']
Если нужно получить строки файла, но без перевода строки в конце, можно
воспользоваться методом split и как разделитель, указать символ \n :
In [11]: f = open('r1.txt')
In [12]: f.read().split('\n')
Out[12]:
['!',
'service timestamps debug datetime msec localtime show-timezone year',
'service timestamps log datetime msec localtime show-timezone year',
'service password-encryption',
'service sequence-numbers',
'!',
'no ip domain lookup',
'!',
'ip ssh version 2',
'!',
'']
155
Чтение файлов
In [13]: f = open('r1.txt')
In [14]: f.read().rstrip().split('\n')
Out[14]:
['!',
'service timestamps debug datetime msec localtime show-timezone year',
'service timestamps log datetime msec localtime show-timezone year',
'service password-encryption',
'service sequence-numbers',
'!',
'no ip domain lookup',
'!',
'ip ssh version 2',
'!']
seek()
До сих пор, файл каждый раз приходилось открывать заново, чтобы снова его считать.
Так происходит из-за того, что после методов чтения, курсор находится в конце файла.
И повторное чтение возвращает пустую строку.
In [15]: f = open('r1.txt')
Но, с помощью метода seek , можно перейти в начало файла (0 означает начало
файла):
156
Чтение файлов
In [18]: f.seek(0)
После того, как, с помощью seek , курсор был переведен в начало файла, можно
опять считывать содержимое:
157
Запись файлов
Запись файлов
При записи, очень важно определиться с режимом открытия файла, чтобы случайно
его не удалить:
удаляется
write()
158
Запись файлов
In [4]: cfg_lines_as_string
Out[4]: '!\nservice timestamps debug datetime msec localtime show-timezone year\nservi
ce timestamps log datetime msec localtime show-timezone year\nservice password-encrypt
ion\nservice sequence-numbers\n!\nno ip domain lookup\n!\nip ssh version 2\n!'
In [5]: f.write(cfg_lines_as_string)
In [7]: f.close()
Так как ipython поддерживает команду cat, можно легко посмотреть содержимое
файла:
writelines()
159
Запись файлов
In [10]: f.writelines(cfg_lines)
In [11]: f.close()
В результате, все строки из списка, записались в одну строку файла, так как в конце
строк не было символа \n .
In [13]: cfg_lines2 = []
In [15]: cfg_lines2
Out[15]:
['!\n',
'service timestamps debug datetime msec localtime show-timezone year\n',
'service timestamps log datetime msec localtime show-timezone year\n',
'service password-encryption\n',
'service sequence-numbers\n',
'!\n',
'no ip domain lookup\n',
'!\n',
'ip ssh version 2\n',
160
Запись файлов
In [17]: cfg_lines3
Out[17]:
['!\n',
'service timestamps debug datetime msec localtime show-timezone year\n',
'service timestamps log datetime msec localtime show-timezone year\n',
'service password-encryption\n',
'service sequence-numbers\n',
'!\n',
'no ip domain lookup\n',
'!\n',
'ip ssh version 2\n',
'!\n']
Если любой, из получившихся списков записать заново в файл, то в нем уже будут
переводы строк:
In [19]: f.writelines(cfg_lines3)
In [20]: f.close()
161
Закрытие файлов
Закрытие файлов
В реальной жизни, для закрытия файлов, чаще всего, используется конструкция
with . Её намного удобней использовать, чем закрывать файл явно. Но, так как в
жизни можно встретить и метод close , в этом разделе рассматривается как его
использовать.
После завершения работы с файлом, его нужно закрыть. В некоторых случаях, Python
может самостоятельно закрыть файл. Но лучше на это не рассчитывать и закрывать
файл явно.
close()
Метод close встречался в разделе запись файлов. Там он был нужен для того, чтобы
содержимое файла было записано на диск.
Для этого, в Python есть отдельный метод flush() . Но, так как, в примере с записью
файлов, не нужно было больше выполнять никаких операций, файл можно было
закрыть.
У объекта file есть специальный атрибут closed , который позволяет проверить закрыт
файл или нет. Если файл открыт, он возвращает False :
162
Закрытие файлов
In [3]: f.closed
Out[3]: False
In [4]: f.close()
In [5]: f.closed
Out[5]: True
Если попытаться открыть для чтения файл, которого не существует, возникнет такое
исключение:
163
Закрытие файлов
In [8]: try:
....: f = open('r3.txt', 'r')
....: except IOError:
....: print 'No such file'
....:
No such file
In [9]: try:
....: f = open('r1.txt', 'r')
....: print f.read()
....: except IOError:
....: print 'No such file'
....: finally:
....: f.close()
....:
!
service timestamps debug datetime msec localtime show-timezone year
service timestamps log datetime msec localtime show-timezone year
service password-encryption
service sequence-numbers
!
no ip domain lookup
!
ip ssh version 2
!
In [10]: f.closed
Out[10]: True
164
Конструкция with
Конструкция with
Конструкция with называется менеджер контекста.
В Python существует более удобный способ работы с файлами, чем те, которые
использовались до сих пор - конструкция with :
service password-encryption
service sequence-numbers
no ip domain lookup
ip ssh version 2
for line in f:
print line
В предыдущем выводе, между строками файла были лишние пустые строки, так как
print добавляет ещё один перевод строки.
Чтобы избавиться от этого, можно поставить запятую в конце выражения print или
вызвать метод rstrip :
165
Конструкция with
In [3]: f.closed
Out[3]: True
166
Задания
Задания
Все задания и вспомогательные файлы можно скачать одним архивом zip или tar.gz.
Также для курса подготовлены две виртуальные машины на выбор: Vagrant или
VMware. В них установлены все пакеты, которые используются в курсе.
Например, если в разделе есть задания: 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала,
можно выполнить задания 5.1, 5.2, 5.3. А затем 5.2a, 5.2b, 5.3a.
Задание 6.1
Аналогично заданию 3.1 обработать строки из файла ospf.txt и вывести информацию
по каждой в таком виде:
Protocol: OSPF
Prefix: 10.0.24.0/24
AD/Metric: 110/41
Next-Hop: 10.0.13.3
Last update: 3d18h
Outbound Interface: FastEthernet0/0
Так как это первое задание с открытием файла, заготовка для открытия файла уже
сделана.
Задание 6.2
Создать скрипт, который будет обрабатывать конфигурационный файл config_sw1.txt:
167
Задания
Задание 6.2a
Сделать копию скрипта задания 6.2.
Дополнить скрипт:
Задание 6.2b
Дополнить скрипт из задания 6.2a:
При этом, должны быть отфильтрованы строки, которые содержатся в списке ignore.
Задание 6.2c
Переделать скрипт из задания 6.2b:
168
Задания
Задание 6.3
Скрипт должен обрабатывать записи в файле CAM_table.txt таким образом чтобы:
Задание 6.3a
Сделать копию скрипта задания 6.3
Дополнить скрипт:
Задание 6.3b
Сделать копию скрипта задания 6.3a
Дополнить скрипт:
169
7. Функции
Функции
Функция - это блок кода, выполняющий определенные действия:
у функции есть имя, с помощью которого можно запускать этот блок кода сколько
угодно раз
запуск кода функции, называется вызовом функции
при создании функции, как правило, определяются параметры функции.
параметры функции определяют какие аргументы функция может принимать
функциям можно передавать аргументы
соответственно, код функции будет выполняться с учетом указанных
аргументов
открытие файла
удаление (или пропуск) строк, которые начинаются на знак восклицания (для
Cisco)
удаление (или пропуск) пустых строк
удаление символов перевода строки в конце строк
преобразование полученного результата в список
Часто получается, что есть кусок кода, который повторяется. Конечно, его можно
копировать из одного скрипта в другой. Но это очень неудобно, так как, при внесении
изменений в код, нужно будет обновить его во всех файлах, в которые он скопирован.
Гораздо проще и правильней вынести этот код в функцию (это может быть и несколько
функций).
И тогда, в этом файле, или каком-то другом, эта функция просто будет использоваться.
170
7. Функции
171
Создание функций
Создание функций
Создание функции:
Пример функции:
Когда функция создана, она ещё ничего не выполняет. Только при вызыве
функции, действия, которые в ней перечислены, будут выполняться. Это чем-то
похоже на ACL в сетевом оборудовании: при создании ACL в конфигурации, он
ничего не делает до тех пор, пока не будет куда-то применен.
Вызов функции
При вызове функции, нужно указать её имя и передать аргументы, если нужно.
Эта функция ожидает имя файла, в качестве аргумента, и затем выводит содержимое
файла:
172
Создание функций
In [2]: open_file('r1.txt')
!
service timestamps debug datetime msec localtime show-timezone year
service timestamps log datetime msec localtime show-timezone year
service password-encryption
service sequence-numbers
!
no ip domain lookup
!
ip ssh version 2
!
In [3]: open_file('ospf.txt')
router ospf 1
router-id 10.0.0.3
auto-cost reference-bandwidth 10000
network 10.0.1.0 0.0.0.255 area 0
network 10.0.2.0 0.0.0.255 area 2
network 10.1.1.0 0.0.0.255 area 0
In [4]: open_file.__doc__
Out[4]: 'Documentation string'
Оператор return
Оператор return используется для прекращения работы функции, выхода из нее, и, как
правило, возврата какого-то значения. Функция может возвращать любой объект
Python.
В данном случае, если присвоить вывод функции переменной result, результат будет
таким:
173
Создание функций
Переменная result равна None . Так получилось из-за того, что функция ничего не
возвращает. Она просто выводит сообщение на стандартный поток вывода.
Для того, чтобы функция возвращала значение, которое потом можно, например,
присвоить переменной, используется оператор return :
Ещё один важный аспект работы оператора return: выражения, которые идут после
return, не выполняются.
То есть, в функции ниже, строка "Done" не будет выводиться, так как она стоит после
return:
174
Создание функций
175
Пространства имен. Области видимости
При использовании имен переменных в программе, Python каждый раз, ищет, создает
или изменяет эти имена в соответствующем пространстве имен. Пространство имен,
которое доступно в каждый момент, зависит от области в которой находится код.
локальные переменные:
переменные, которые определены внутри функции
эти переменные становятся недоступными после выхода из функции
глобальные переменные
переменные, которые определены вне функции
эти переменные 'глобальны' только в пределах модуля
например, чтобы они были доступны в другом модуле, их надо
импортировать
176
Пространства имен. Области видимости
In [3]: open_file('r1.txt')
Out[3]: '!\nservice timestamps debug datetime msec localtime show-timezone year\nservi
ce timestamps log datetime msec localtime show-timezone year\nservice password-encrypt
ion\nservice sequence-numbers\n!\nno ip domain lookup\n!\nip ssh version 2\n!\n'
In [4]: result
Out[4]: 'test string'
Обратите внимание, что переменная result по-прежнему осталась равной 'test string'.
Несмотря на то, что внутри функции ей присвоено содержимое файла.
177
Параметры и аргументы функций
Для того чтобы функция могла принимать входящие значения, ее нужно создать с
параметрами:
Функция открывает файл in_cfg, читает содержимое в список; затем открывает файл
out_cfg и записывает в него только те строки, которые не начинаются на знак
восклицания.
178
Параметры и аргументы функций
При таком определении функции, надо обязательно передать оба аргумента. Если
передать только один аргумент, возникнет ошибка. Аналогично, возникнет ошибка,
если передать три и больше аргументов:
In [5]: delete_exclamation_from_cfg('r1.txt')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-7-66ae381f1c4f> in <module>()
----> 1 delete_exclamation_from_cfg('r1.txt')
179
Типы параметров
обязательными
необязательными (опциональными, параметрами со значением по умолчанию)
Обязательные параметры
Обязательные параметры - определяют какие аргументы нужно передать функции
обязательно. При этом, их нужно передать ровно столько, сколько указано параметров
функции (нельзя указать большее или меньшее количество аргументов)
Внутри, она открывает файл cfg_file для чтения, проходится по всем строкам и, если
аргумент delete_exclamation истина и строка начинается с восклицательного знака,
строка пропускается. Оператор pass означает, что ничего не выполняется.
180
Типы параметров
Так как аргументу delete_exclamation передано значение True, в итоговом словаре нет
строк с восклицательными знаками.
181
Типы параметров
In [5]: cfg_to_list('r1.txt')
Out[5]:
['service timestamps debug datetime msec localtime show-timezone year',
'service timestamps log datetime msec localtime show-timezone year',
'service password-encryption',
'service sequence-numbers',
'no ip domain lookup',
'ip ssh version 2']
182
Типы аргументов
Позиционные аргументы
Позиционные аргументы, при вызове функции, надо передать в правильном порядке
(поэтому они и называются позиционные)
183
Типы аргументов
Если при вызове функции поменять аргументы местами, скорее всего, возникнет
ошибка, в зависимости от конкретной функции.
Ключевые аргументы
Ключевые аргументы:
Если передать оба аргумента, как ключевые, можно передавать их в любом порядке:
184
Типы аргументов
185
Аргументы переменной длины
Даже если вы не будете использовать этот прием в своих скриптах, есть большая
вероятность, что, вы встретите его в чужом коде.
Пример функции:
параметр a
если передается как позиционный аргумент, должен идти первым
если передается как ключевой аргумент, то порядок не важен
параметр *args - ожидает аргументы переменной длины
сюда попадут все остальные аргументы в виде кортежа
эти аргументы могут отсутствовать
186
Аргументы переменной длины
In [2]: sum_arg(1,10,20,30)
1 (10, 20, 30)
Out[2]: 61
In [3]: sum_arg(1,10)
1 (10,)
Out[3]: 11
In [4]: sum_arg(1)
1 ()
Out[4]: 1
In [7]: sum_arg()
()
Out[7]: 0
Пример функции:
параметр a
если передается как позиционный аргумент, должен идти первым
если передается как ключевой аргумент, то порядок не важен
187
Аргументы переменной длины
In [9]: sum_arg(a=10,b=10,c=20,d=30)
10 {'c': 20, 'b': 10, 'd': 30}
Out[9]: 70
In [10]: sum_arg(b=10,c=20,d=30,a=10)
10 {'c': 20, 'b': 10, 'd': 30}
Out[10]: 70
Обратите внимание, что, хотя a можно указывать как позиционный аргумент, нельзя
указывать позиционный аргумент после ключевого:
In [11]: sum_arg(10,b=10,c=20,d=30)
10 {'c': 20, 'b': 10, 'd': 30}
Out[11]: 70
In [12]: sum_arg(b=10,c=20,d=30,10)
File "<ipython-input-6-71c121dc2cf7>", line 1
sum_arg(b=10,c=20,d=30,10)
SyntaxError: non-keyword arg after keyword arg
188
Распаковка аргументов
Распаковка аргументов
В Python, выражения *args и **kwargs позволяют выполнять ещё одну задачу -
распаковку аргументов.
mask_bits = int(cidr_mask.split('/')[-1])
bin_mask = '1'*mask_bits + '0'*(32-mask_bits)
dec_mask = '.'.join([ str(int(bin_mask[i:i+8], 2)) for i in [0,8,16,24] ])
Например:
189
Распаковка аргументов
190
Распаковка аргументов
Python сам 'распакует' список info и передаст в функцию элементы списка, как
аргументы.
Функция config_to_list:
Пример использования:
191
Распаковка аргументов
In [9]: config_to_list('r1.txt')
Out[9]:
['service timestamps debug datetime msec localtime show-timezone year',
'service timestamps log datetime msec localtime show-timezone year',
'service password-encryption',
'service sequence-numbers',
'no ip domain lookup',
'ip ssh version 2']
Ошибка такая, так как все параметры, кроме имени файла, опциональны. И на стадии
открытия файла, возникает ошибка, так как вместо файла, передан словарь.
192
Распаковка аргументов
193
Пример использования
In [1]: config_to_list('r1.txt')
Out[1]:
['service timestamps debug datetime msec localtime show-timezone year',
'service timestamps log datetime msec localtime show-timezone year',
'service password-encryption',
'service sequence-numbers',
'no ip domain lookup',
'ip ssh version 2']
194
Пример использования
Но, при этом, мы не хотим терять возможность управлять тем, какие строки будут
отброшены. То есть, необходимо чтобы функция clear_cfg_and_write_to_file
поддерживала те же параметры, что и функция config_to_list.
195
Пример использования
В этом примере, **kwargs используется и для того, чтобы указать, что функция
clear_cfg_and_write_to_file может принимать аргументы переменной длины, и для
того, чтобы 'распаковать' словарь kwargs, когда мы передаем его в функцию
config_to_list.
196
Задания
Задания
Все задания и вспомогательные файлы можно скачать одним архивом zip или tar.gz.
Также для курса подготовлены две виртуальные машины на выбор: Vagrant или
VMware. В них установлены все пакеты, которые используются в курсе.
Например, если в разделе есть задания: 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала,
можно выполнить задания 5.1, 5.2, 5.3. А затем 5.2a, 5.2b, 5.3a.
Задание 7.1
Создать функцию, которая генерирует конфигурацию для access-портов.
{'FastEthernet0/12':10,
'FastEthernet0/14':11,
'FastEthernet0/16':17,
'FastEthernet0/17':150}
197
Задания
[
'interface FastEthernet0/12',
'switchport mode access',
'switchport access vlan 10',
'switchport nonegotiate',
'spanning-tree portfast',
'spanning-tree bpduguard enable',
'interface FastEthernet0/17',
'switchport mode access',
'switchport access vlan 150',
'switchport nonegotiate',
'spanning-tree portfast',
'spanning-tree bpduguard enable',
...]
def generate_access_config(access):
"""
access - словарь access-портов,
для которых необходимо сгенерировать конфигурацию, вида:
{ 'FastEthernet0/12':10,
'FastEthernet0/14':11,
'FastEthernet0/16':17}
access_dict = { 'FastEthernet0/12':10,
'FastEthernet0/14':11,
'FastEthernet0/16':17,
'FastEthernet0/17':150 }
Задание 7.1a
Сделать копию скрипта задания 7.1.
Дополнить скрипт:
198
Задания
def generate_access_config(access):
"""
access - словарь access-портов,
для которых необходимо сгенерировать конфигурацию, вида:
{ 'FastEthernet0/12':10,
'FastEthernet0/14':11,
'FastEthernet0/16':17 }
access_dict = { 'FastEthernet0/12':10,
'FastEthernet0/14':11,
'FastEthernet0/16':17,
'FastEthernet0/17':150 }
Задание 7.1b
Сделать копию скрипта задания 7.1a.
199
Задания
def generate_access_config(access):
"""
access - словарь access-портов,
для которых необходимо сгенерировать конфигурацию, вида:
{ 'FastEthernet0/12':10,
'FastEthernet0/14':11,
'FastEthernet0/16':17 }
access_dict = { 'FastEthernet0/12':10,
'FastEthernet0/14':11,
'FastEthernet0/16':17,
'FastEthernet0/17':150 }
Задание 7.2
Создать функцию, которая генерирует конфигурацию для trunk-портов.
200
Задания
Словарь trunk имеет такой формат (тестовый словарь trunk_dict уже создан):
{ 'FastEthernet0/1':[10,20],
'FastEthernet0/2':[11,30],
'FastEthernet0/4':[17] }
def generate_trunk_config(trunk):
"""
trunk - словарь trunk-портов для которых необходимо сгенерировать конфигурацию.
trunk_dict = { 'FastEthernet0/1':[10,20,30],
'FastEthernet0/2':[11,30],
'FastEthernet0/4':[17] }
Задание 7.2a
Сделать копию скрипта задания 7.2
201
Задания
def generate_trunk_config(trunk):
"""
trunk - словарь trunk-портов,
для которых необходимо сгенерировать конфигурацию, вида:
{ 'FastEthernet0/1':[10,20],
'FastEthernet0/2':[11,30],
'FastEthernet0/4':[17] }
Возвращает словарь:
- ключи: имена интерфейсов, вида 'FastEthernet0/1'
- значения: список команд, который надо выполнить на этом интерфейсе
"""
trunk_template = ['switchport trunk encapsulation dot1q',
'switchport mode trunk',
'switchport trunk native vlan 999',
'switchport trunk allowed vlan']
trunk_dict = { 'FastEthernet0/1':[10,20,30],
'FastEthernet0/2':[11,30],
'FastEthernet0/4':[17] }
Задание 7.3
Создать функцию get_int_vlan_map, которая обрабатывает конфигурационный файл
коммутатора и возвращает два объекта:
словарь портов в режиме access, где ключи номера портов, а значения access
VLAN:
{'FastEthernet0/12':10,
'FastEthernet0/14':11,
'FastEthernet0/16':17}
словарь портов в режиме trunk, где ключи номера портов, а значения список
разрешенных VLAN:
{'FastEthernet0/1':[10,20],
'FastEthernet0/2':[11,30],
'FastEthernet0/4':[17]}
Задание 7.3a
202
Задания
Дополнить скрипт:
interface FastEthernet0/20
switchport mode access
duplex auto
В таком случае, в словарь портов должна добавляться информация, что порт в VLAN 1
Пример словаря:
{'FastEthernet0/12':10,
'FastEthernet0/14':11,
'FastEthernet0/20':1 }
Задание 7.4
Создать функцию, которая обрабатывает конфигурационный файл коммутатора и
возвращает словарь:
203
Задания
Возвращает True, если в команде содержится слово из списка ignore, False - если не
т
"""
ignore_command = False
def config_to_dict(config):
"""
config - имя конфигурационного файла коммутатора
Возвращает словарь:
- Все команды верхнего уровня (глобального режима конфигурации), будут ключами.
- Если у команды верхнего уровня есть подкоманды,
они должны быть в значении у соответствующего ключа, в виде списка (пробелы внач
але можно оставлять).
- Если у команды верхнего уровня нет подкоманд, то значение будет пустым списком
"""
pass
Задание 7.4a
Задача такая же, как и задании 7.4. Проверить работу функции надо на примере
файла config_r1.txt
interface Ethernet0/3.100
router bgp 100
Теперь:
204
Задания
{'interface Ethernet0/3.100':{
'encapsulation dot1Q 100':[],
'xconnect 10.2.2.2 12100 encapsulation mpls':
['backup peer 10.4.4.4 14100',
'backup delay 1 1']}}
Возвращает True, если в команде содержится слово из списка ignore, False - если не
т
"""
ignore_command = False
def config_to_dict(config):
"""
config - имя конфигурационного файла
"""
pass
205
8. Модули
Модули
Модуль в Python это обычный текстовый файл с кодом Python и расширением .py. Он
позволяет логически упорядочить и сгруппировать код.
Модули хороши тем, что позволяют повторно использовать уже написанный код и не
копировать его (например, не копировать когда-то написанную функцию).
206
Импорт модуля
Импорт модуля
В Python есть несколько способов импорта модуля:
import module
import module as
import module
Вариант import module:
In [1]: dir()
Out[1]:
['In',
'Out',
...
'exit',
'get_ipython',
'quit']
In [2]: import os
In [3]: dir()
Out[3]:
['In',
'Out',
...
'exit',
'get_ipython',
'os',
'quit']
После импорта, модуль os появился в выводе dir(). Это значит, что он теперь в
текущем именном пространстве.
Чтобы вызвать какую-то функцию или метод из модуля os, надо указать os. и затем
имя объекта:
In [4]: os.getlogin()
Out[4]: 'natasha'
207
Импорт модуля
Этот способ импорта хорош тем, что объекты модуля не попадают в именное
пространство текущей программы. То есть, если создать функцию с именем getlogin(),
она не будет конфликтовать с аналогичной функцией модуля os.
import module as
Конструкция import module as позволяет импортировать модуль под другим именем
(как правило, более коротким):
In [2]: dir()
Out[2]:
['In',
'Out',
...
'exit',
'get_ipython',
'getcwd',
'getlogin',
'quit']
208
Импорт модуля
In [3]: getlogin()
Out[3]: 'natasha'
In [4]: getcwd()
Out[4]: '/Users/natasha/Desktop/Py_net_eng/code_test'
In [2]: dir()
Out[2]:
['EX_CANTCREAT',
'EX_CONFIG',
...
'wait',
'wait3',
'wait4',
'waitpid',
'walk',
'write']
In [3]: len(dir())
Out[3]: 218
В модуле os очень много объектов, поэтому вывод сокращен. В конце указана длина
списка имен текущего именного пространства.
209
Создание своих модулей
Файл sw_int_templates.py:
Файл sw_data.py:
sw1_fast_int = {
'access':{
'0/12':'10',
'0/14':'11',
'0/16':'17'}}
210
Создание своих модулей
import sw_int_templates
from sw_data import sw1_fast_int
def generate_access_cfg(sw_dict):
result = []
for intf in sw_dict['access']:
result.append('interface FastEthernet' + intf)
for command in sw_int_templates.access_template:
if command.endswith('access vlan'):
result.append(' %s %s' % (command, sw_dict['access'][intf]))
else:
result.append(' %s' % command)
return result
print '\n'.join(generate_access_cfg(sw1_fast_int))
sw1_fast_int
при таком импорте, можно напрямую обращаться к имени sw1_fast_int
$ python generate_sw_int_cfg.py
interface FastEthernet0/12
switchport mode access
switchport access vlan 10
spanning-tree portfast
spanning-tree bpduguard enable
interface FastEthernet0/14
switchport mode access
switchport access vlan 11
spanning-tree portfast
spanning-tree bpduguard enable
interface FastEthernet0/16
switchport mode access
switchport access vlan 17
spanning-tree portfast
spanning-tree bpduguard enable
if __name__ == "__main__"
211
Создание своих модулей
basic_cfg = """
service timestamps debug datetime msec localtime show-timezone year
service timestamps log datetime msec localtime show-timezone year
service password-encryption
service sequence-numbers
!
no ip domain lookup
!
"""
lines_cfg = """
!
line con 0
logging synchronous
history size 100
line vty 0 4
logging synchronous
history size 100
transport input ssh
!
"""
print basic_cfg
print '\n'.join(generate_access_cfg(sw1_fast_int))
print lines_cfg
212
Создание своих модулей
Результат выполнения:
213
Создание своих модулей
$ python generate_sw_cfg.py
interface FastEthernet0/12
switchport mode access
switchport access vlan 10
spanning-tree portfast
spanning-tree bpduguard enable
interface FastEthernet0/14
switchport mode access
switchport access vlan 11
spanning-tree portfast
spanning-tree bpduguard enable
interface FastEthernet0/16
switchport mode access
switchport access vlan 17
spanning-tree portfast
spanning-tree bpduguard enable
interface FastEthernet0/12
switchport mode access
switchport access vlan 10
spanning-tree portfast
spanning-tree bpduguard enable
interface FastEthernet0/14
switchport mode access
switchport access vlan 11
spanning-tree portfast
spanning-tree bpduguard enable
interface FastEthernet0/16
switchport mode access
switchport access vlan 17
spanning-tree portfast
spanning-tree bpduguard enable
!
line con 0
logging synchronous
history size 100
line vty 0 4
logging synchronous
history size 100
transport input ssh
!
214
Создание своих модулей
print '\n'.join(generate_access_cfg(sw1_fast_int))
Когда скрипт импортирует какой-то модуль, всё, что находится в модуле, выполняется.
И, так как в данном случае, в файле generate_sw_int_cfg.py есть строка с print, на
стандартный поток вывода попадает результат выполнения этого выражения, при
запуске файла generate_sw_int_cfg.py.
В Python есть специальный прием, который позволяет указать, что какой-то код
должен выполняться, только когда файл запускается напрямую.
Файл generate_sw_int_cfg2.py:
import sw_int_templates
from sw_data import sw1_fast_int
def generate_access_cfg(sw_dict):
result = []
for intf in sw_dict['access']:
result.append('interface FastEthernet' + intf)
for command in sw_int_templates.access_template:
if command.endswith('access vlan'):
result.append(' %s %s' % (command, sw_dict['access'][intf]))
else:
result.append(' %s' % command)
return result
if __name__ == "__main__":
print '\n'.join(generate_access_cfg(sw1_fast_int))
if __name__ == "__main__":
print '\n'.join(generate_access_cfg(sw1_fast_int))
215
Создание своих модулей
на строку:
$ python generate_sw_cfg.py
interface FastEthernet0/12
switchport mode access
switchport access vlan 10
spanning-tree portfast
spanning-tree bpduguard enable
interface FastEthernet0/14
switchport mode access
switchport access vlan 11
spanning-tree portfast
spanning-tree bpduguard enable
interface FastEthernet0/16
switchport mode access
switchport access vlan 17
spanning-tree portfast
spanning-tree bpduguard enable
!
line con 0
logging synchronous
history size 100
line vty 0 4
logging synchronous
history size 100
transport input ssh
!
216
Создание своих модулей
217
Задания
Задания
Все задания и вспомогательные файлы можно скачать одним архивом zip или tar.gz.
Также для курса подготовлены две виртуальные машины на выбор: Vagrant или
VMware. В них установлены все пакеты, которые используются в курсе.
Например, если в разделе есть задания: 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала,
можно выполнить задания 5.1, 5.2, 5.3. А затем 5.2a, 5.2b, 5.3a.
Задание 8.1
Создать отдельный файл my_func.py.
218
Задания
Задание 8.2
Создать функцию parse_cdp_neighbors, которая обрабатывает вывод команды show
cdp neighbors.
Задание 8.2a
Для выполнения этого задания, должен быть установлен graphviz:
219
Задания
Задание 8.2b
Для выполнения этого задания, должен быть установлен graphviz:
apt-get install graphviz
sh_cdp_n_sw1.txt
sh_cdp_n_r1.txt
sh_cdp_n_r2.txt
sh_cdp_n_r3.txt
220
Задания
221
9. Регулярные выражения
Регулярные выражения
Регулярное выражение это последовательность из обычных и специальных символов.
Эта последовательность задает шаблон, который позже используется для поиска
подстрок.
222
Модуль re
Модуль re
В Python для работы с регулярными выражениями используется модуль re.
виде списка
finditer() - ищет все совпадения с шаблоном. Выдает итератор
search()
Функция search() :
In [1]: import re
223
Модуль re
In [6]: match.span()
Out[6]: (49, 53)
In [7]: line[49:53]
Out[7]: 'dhcp'
In [15]: match.group()
Out[15]: 'dhcp'
Важный момент в использовании функции search() , то что она ищет только первое
совпадение в строке, которое соответствует шаблону:
In [18]: match.group()
Out[18]: 'dhcp'
In [19]: match.span()
Out[19]: (5, 9)
findall()
Для того чтобы найти все совпадения, можно использовать функцию findall() :
Особенность функции findall() в том, что она возвращает список подстрок, которые
соответствуют шаблону, а не объект Match. Поэтому нельзя вызвать методы, которые
использовались в функции search() .
224
Модуль re
finditer()
Для того чтобы получить все совпадения, но при этом, получить совпадения в виде
объекта Match(), можно использовать функцию finditer() :
In [27]: line2[5:9]
Out[27]: 'dhcp'
In [28]: line2[17:21]
Out[28]: 'dhcp'
compile()
225
Модуль re
226
Специальные символы
Специальные символы
Полностью возможности регулярных выражений проявляются при использовании
специальных символов.
Специальные символы:
$ - конец строки
выражением, запоминается
Повторение:
\d - любая цифра
\s - whitespace (\t\n\r\f\v)
In [1]: import re
227
Специальные символы
228
Специальные символы
229
Жадность регулярных выражений
In [1]: import re
In [2]: line = '<text line> some text>'
In [3]: match = re.search('<.*>', line)
In [4]: match.group()
Out[4]: '<text line> some text>'
Если нужно отключить жадность, достаточно добавить знак вопроса после символов
повторения:
In [7]: match.group()
Out[7]: '<text line>'
230
Группировка выражений
Группировка выражений
Нумерованные группы
С помощью определения групп элементов в шаблоне, можно изолировать части
текста, которые соответствуют шаблону.
Вторую группу можно было описать так же, как и первую. Другой вариант сделан
просто для примера
In [10]: match.group(0)
Out[10]: 'FastEthernet0/1 10.0.12.1 YES manual up
up'
In [11]: match.group(1)
Out[11]: 'FastEthernet0/1'
In [12]: match.group(2)
Out[12]: '10.0.12.1'
231
Группировка выражений
In [13]: match.groups()
Out[13]: ('FastEthernet0/1', '10.0.12.1')
Именованные группы
Когда выражение сложное, не очень удобно определять номер группы. Плюс, при
дополнении выражения, может получиться так, что порядок групп изменился. И
придется изменить и код, который ссылается на группы.
In [15]: match.group('intf')
Out[15]: 'FastEthernet0/1'
In [16]: match.group('address')
Out[16]: '10.0.12.1'
Также очень полезно то, что с помощью метода groupdict(), можно получить словарь,
где ключи - имена групп, а значения - подстроки, которые им соответствуют:
In [17]: match.groupdict()
Out[17]: {'address': '10.0.12.1', 'intf': 'FastEthernet0/1'}
In [19]: match.groupdict()
Out[19]:
{'address': '10.0.12.1',
'intf': 'FastEthernet0/1',
'protocol': 'up',
'status': 'up'}
232
Группировка выражений
233
Разбор вывода команды show ip dhcp snooping с помощью именованных групп
более пробелов
сюда попадет значение Lease
\S+ + - последовательность любых символов, кроме whitespace
234
Разбор вывода команды show ip dhcp snooping с помощью именованных групп
whitespace
In [3]: match.groupdict()
Out[3]:
{'int': 'FastEthernet0/1',
'ip': '10.1.10.2',
'mac': '00:09:BB:3D:D6:58',
'vlan': '10'}
Так как регулярное выражение отработало как нужно, можно создавать скрипт. В
скрипте, перебираются все строки файла dhcp_snooping.txt и на стандартный поток
вывода, выводится информация об устройствах.
Файл parse_dhcp_snooping.py:
Результат выполнения:
235
Разбор вывода команды show ip dhcp snooping с помощью именованных групп
$ python parse_dhcp_snooping.py
К коммутатору подключено 4 устройства
Параметры устройства 1:
int: FastEthernet0/1
ip: 10.1.10.2
mac: 00:09:BB:3D:D6:58
vlan: 10
Параметры устройства 2:
int: FastEthernet0/10
ip: 10.1.5.2
mac: 00:04:A3:3E:5B:69
vlan: 5
Параметры устройства 3:
int: FastEthernet0/9
ip: 10.1.5.4
mac: 00:05:B3:7E:9B:60
vlan: 5
Параметры устройства 4:
int: FastEthernet0/3
ip: 10.1.10.6
mac: 00:09:BC:3F:A6:50
vlan: 10
236
Задания
Задания
Все задания и вспомогательные файлы можно скачать одним архивом zip или tar.gz.
Также для курса подготовлены две виртуальные машины на выбор: Vagrant или
VMware. В них установлены все пакеты, которые используются в курсе.
Например, если в разделе есть задания: 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала,
можно выполнить задания 5.1, 5.2, 5.3. А затем 5.2a, 5.2b, 5.3a.
Задание 9.1
Создать скрипт, который будет ожидать два аргумента:
В данном случае, скрипт будет работать как фильтр include в CLI Cisco. Вы можете
попробовать использовать регулярные выражения для фильтрации вывода
команд show.
Задание 9.1a
237
Задания
Задание 9.1b
Переделайте регулярное выражение из задания 9.1a таким образом, чтобы оно, по-
прежнему, отображало строки с интерфейсами 0/1 и 0/3, но, при этом, в регулярном
выражении было не более 7 символов (не считая кавычки вокруг регулярного
выражения).
Задание 9.1c
Проверить работу скрипта из задания 9.1 и регулярного выражения из задания 9.1a
или 9.1b на выводе sh ip int br из файла sh_ip_int_br_switch.txt.
Задание 9.2
Создать функцию return_match, которая ожидает два аргумента:
238
Задания
Задание 9.3
Создать функцию parse_cfg, которая ожидает как аргумент имя файла, в котором
находится конфигурация устройства.
Задание 9.3a
Переделать функцию parse_cfg из задания 9.3 таким образом, чтобы она возвращала
словарь:
239
Задания
Задание 9.3b
Проверить работу функции parse_cfg из задания 9.3a на конфигурации config_r2.txt.
interface Ethernet0/1
ip address 10.255.2.2 255.255.255.0
ip address 10.254.2.2 255.255.255.0 secondary
Задание 9.4
Создать функцию parse_sh_ip_int_br, которая ожидает как аргумент имя файла, в
котором находится вывод команды show
Функция должна обрабатывать вывод команды show ip int br и возвращать такие поля:
Interface
IP-Address
Status
Protocol
Задание 9.4a
Создать функцию convert_to_dict, которая ожидает два аргумента:
240
Задания
Функция возвращает результат в виде списка словарей (порядок полей может быть
другой): [{'interface': 'FastEthernet0/0', 'status': 'up', 'protocol': 'up', 'address': '10.0.1.1'},
{'interface': 'FastEthernet0/1', 'status': 'up', 'protocol': 'up', 'address': '10.0.2.1'}]
241
10. Сериализация данных
Сериализация данных
Сериализация данных - это сохранение данных в каком-то формате. Чаще всего, это
сохранение в каком-то структурированном формате.
Кроме того, Python позволяет записывать объекты самого языка (этот аспект в курсе
не рассматривается, но, если вам интересно, посмотрите на модуль Pickle).
Для каждого из этих форматов в Python есть модуль, который существенно упрощает
работу с ними.
CSV
JSON
242
10. Сериализация данных
YAML
243
CSV
В этом формате, каждая строка файла - это строка таблицы. Но, несмотря на название
формата, разделителем может быть не только запятая.
hostname,vendor,model,location
sw1,Cisco,3750,London
sw2,Cisco,3850,Liverpool
sw3,Cisco,3650,Liverpool
sw4,Cisco,3650,London
Чтение
Пример использования модуля csv (файл csv_read.py):
import csv
with open('sw_data.csv') as f:
reader = csv.reader(f)
for row in reader:
print row
$ python csv_read.py
['hostname', 'vendor', 'model', 'location']
['sw1', 'Cisco', '3750', 'London']
['sw2', 'Cisco', '3850', 'Liverpool']
['sw3', 'Cisco', '3650', 'Liverpool']
['sw4', 'Cisco', '3650', 'London']
244
CSV
import csv
with open('sw_data.csv') as f:
reader = csv.DictReader(f)
for row in reader:
print row
$ python csv_read_dict.py
{'model': '3750', 'hostname': 'sw1', 'vendor': 'Cisco', 'location': 'London'}
{'model': '3850', 'hostname': 'sw2', 'vendor': 'Cisco', 'location': 'Liverpool'}
{'model': '3650', 'hostname': 'sw3', 'vendor': 'Cisco', 'location': 'Liverpool'}
{'model': '3650', 'hostname': 'sw4', 'vendor': 'Cisco', 'location': 'London'}
Обратите внимание, что сам reader это итератор. Поэтому, если просто вывести reader,
то вывод будет таким:
Но, если нужно все объекты передать куда-то дальше, его можно превратить в список
таким образом:
245
CSV
Запись
Аналогичным образом, с помощью модуля csv, можно и записать файл в формате CSV
(файл csv_write.py):
import csv
with open('sw_data_new.csv') as f:
print f.read()
$ python csv_write.py
hostname,vendor,model,location
sw1,Cisco,3750,"London, Best str"
sw2,Cisco,3850,"Liverpool, Better str"
sw3,Cisco,3650,"Liverpool, Better str"
sw4,Cisco,3650,"London, Best str"
Так получилось из-за того, что в последней строке есть запятая. И кавычки указывают
на то, что именно является целой строкой. Когда запятая находятся в кавычках,
модуль csv не воспринимает её как разделитель.
Иногда, лучше, чтобы все строки были в кавычках. Конечно, в этом случае, достаточно
простой пример, но когда в строках больше значений, то кавычки позволяют указать
где начинается и заканчивается значение.
Модуль csv позволяет управлять этим. Для того, чтобы все строки записывались в
файл csv с кавычками, надо изменить скрипт таким образом (файл csv_write_ver2.py):
246
CSV
import csv
with open('sw_data_new.csv') as f:
print f.read()
$ python csv_write_ver2.py
"hostname","vendor","model","location"
"sw1","Cisco","3750","London, Best str"
"sw2","Cisco","3850","Liverpool, Better str"
"sw3","Cisco","3650","Liverpool, Better str"
"sw4","Cisco","3650","London, Best str"
Теперь все значения с кавычками. И, так как номер модели задан как строка, в
изначальном списке, тут он тоже в кавычках.
Указание разделителя
Иногда, в качестве разделителя используются другие значения. В таком случае,
должна быть возможность подсказать модулю, какой именно разделитель
использовать.
hostname;vendor;model;location
sw1;Cisco;3750;London
sw2;Cisco;3850;Liverpool
sw3;Cisco;3650;Liverpool
sw4;Cisco;3650;London
247
CSV
import csv
with open('sw_data2.csv') as f:
reader = csv.reader(f, delimiter=';')
for row in reader:
print row
248
JSON
Как и в случае с CSV, в Python есть модуль, который позволяет легко записывать и
читать данные в формате JSON.
Чтение
Файл sw_templates.json:
{
"access": [
"switchport mode access",
"switchport access vlan",
"switchport nonegotiate",
"spanning-tree portfast",
"spanning-tree bpduguard enable"
],
"trunk": [
"switchport trunk encapsulation dot1q",
"switchport mode trunk",
"switchport trunk native vlan 999",
"switchport trunk allowed vlan"
]
}
json.load()
Чтение файла в объект Python:
249
JSON
In [3]: templates
Out[3]:
{u'access': [u'switchport mode access',
u'switchport access vlan',
u'switchport nonegotiate',
u'spanning-tree portfast',
u'spanning-tree bpduguard enable'],
u'trunk': [u'switchport trunk encapsulation dot1q',
u'switchport mode trunk',
u'switchport trunk native vlan 999',
u'switchport trunk allowed vlan']}
Результат чтения - словарь. Обратите внимание, что при чтении из файла в формате
JSON, строки будут в unicode.
json.loads()
Считывание строки в формате JSON в объект Python:
250
JSON
In [6]: templates
Out[6]:
{u'access': [u'switchport mode access',
u'switchport access vlan',
u'switchport nonegotiate',
u'spanning-tree portfast',
u'spanning-tree bpduguard enable'],
u'trunk': [u'switchport trunk encapsulation dot1q',
u'switchport mode trunk',
u'switchport trunk native vlan 999',
u'switchport trunk allowed vlan']}
Запись
Запись файла в формате JSON также осуществляется достаточно легко.
Для записи информации в формате JSON в модуле json также два метода:
json.dump()
Запись объекта Python в файл:
251
JSON
json.dumps()
Преобразование объекта в строку в формате JSON:
Метод json.dumps() подходит для ситуаций, когда надо вернуть строку в формате
JSON.
252
JSON
Такая ситуация уже возникала со строками - они были в формате unicode. Кроме того,
при записи в JSON кортежей, они превращаются в списки.
Например:
253
JSON
In [21]: type(templates)
Out[21]: list
254
JSON
255
YAML
YAML более приятен для восприятия человеком, чем JSON, поэтому его часто
используют для описания сценариев в ПО. Например, в Ansible.
Синтаксис YAML
Как и Python, YAML использует отступы для указания структуры документа. Но в YAML
можно использовать только пробелы и нельзя использовать знаки табуляции.
Список
Список может быть записан в одну строку:
Когда список записан таким блоком, каждая строка должна начинаться с - (минуса и
пробела). И все строки в списке должны быть на одном уровне отступа.
Словарь
Словарь также может быть записан в одну строку:
256
YAML
Или блоком:
vlan: 100
name: IT
Строки
Строки в YAML не обязательно брать в кавычки. Это удобно, но иногда всё же следует
использовать кавычки. Например, когда в строке используется какой-то специальный
символ (специальный для YAML).
Такую строку, например, нужно взять в кавычки, чтобы она была корректно воспринята
YAML:
Комбинация элементов
Словарь, в котором есть два ключа: access и trunk. Значения, которые соответствуют
этим ключам - списки команд:
access:
- switchport mode access
- switchport access vlan
- switchport nonegotiate
- spanning-tree portfast
- spanning-tree bpduguard enable
trunk:
- switchport trunk encapsulation dot1q
- switchport mode trunk
- switchport trunk native vlan 999
- switchport trunk allowed vlan
Список словарей:
257
YAML
- BS: 1550
IT: 791
id: 11
name: Liverpool
to_id: 1
to_name: LONDON
- BS: 1510
IT: 793
id: 12
name: Bristol
to_id: 1
to_name: LONDON
- BS: 1650
IT: 892
id: 14
name: Coventry
to_id: 2
to_name: Manchester
Модуль PyYAML
Для работы с YAML в Python используется модуль PyYAML. Он не входит в
стандартную библиотеку модулей, поэтому его нужно установить:
Чтение из YAML
Попробуем преобразовать данные из файла YAML в объекты Python.
Файл info.yaml:
258
YAML
- BS: 1550
IT: 791
id: 11
name: Liverpool
to_id: 1
to_name: LONDON
- BS: 1510
IT: 793
id: 12
name: Bristol
to_id: 1
to_name: LONDON
- BS: 1650
IT: 892
id: 14
name: Coventry
to_id: 2
to_name: Manchester
In [3]: templates
Out[3]:
[{'BS': 1550,
'IT': 791,
'id': 11,
'name': 'Liverpool',
'to_id': 1,
'to_name': 'LONDON'},
{'BS': 1510,
'IT': 793,
'id': 12,
'name': 'Bristol',
'to_id': 1,
'to_name': 'LONDON'},
{'BS': 1650,
'IT': 892,
'id': 14,
'name': 'Coventry',
'to_id': 2,
'to_name': 'Manchester'}]
259
YAML
Формат YAML очень удобен для хранения различных параметров. Особенно, если они
заполняются вручную.
Запись в YAML
Запись объектов Python в YAML (файл yaml_write.py):
import yaml
with open('sw_templates.yaml') as f:
print f.read()
access: [switchport mode access, switchport access vlan, switchport nonegotiate, spann
ing-tree
portfast, spanning-tree bpduguard enable]
trunk: [switchport trunk encapsulation dot1q, switchport mode trunk, switchport trunk
native vlan 999, switchport trunk allowed vlan]
260
YAML
import yaml
with open('sw_templates.yaml') as f:
print f.read()
access:
- switchport mode access
- switchport access vlan
- switchport nonegotiate
- spanning-tree portfast
- spanning-tree bpduguard enable
trunk:
- switchport trunk encapsulation dot1q
- switchport mode trunk
- switchport trunk native vlan 999
- switchport trunk allowed vlan
261
Задания
Задания
Все задания и вспомогательные файлы можно скачать одним архивом zip или tar.gz.
Также для курса подготовлены две виртуальные машины на выбор: Vagrant или
VMware. В них установлены все пакеты, которые используются в курсе.
Например, если в разделе есть задания: 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала,
можно выполнить задания 5.1, 5.2, 5.3. А затем 5.2a, 5.2b, 5.3a.
Задание 10.1
В этом задании нужно:
Функция parse_sh_version:
Функция write_to_csv:
262
Задания
Остальное содержимое скрипта может быть в скрипте, а может быть в ещё одной
функции.
Скрипт должен:
В скрипте, с помощью модуля glob, создан список файлов, имя которых начинается на
sh_vers. Вы можете раскомментировать строку print sh_version_files, чтобы посмотреть
содержимое списка.
Кроме того, создан список заголовков (headers), который должен быть записан в CSV.
import glob
sh_version_files = glob.glob('sh_vers*')
#print sh_version_files
Задание 10.2
Создать функции:
263
Задания
файла templates.yaml
generate_switch_config - генерирует конфигурацию коммутатора, в зависимости от
переданных параметров, использует для этого остальные функции
import yaml
access_dict = { 'FastEthernet0/12':10,
'FastEthernet0/14':11,
'FastEthernet0/16':17,
'FastEthernet0/17':150 }
trunk_dict = { 'FastEthernet0/1':[10,20,30],
'FastEthernet0/2':[11,30],
'FastEthernet0/4':[17] }
def generate_trunk_config(trunk):
"""
trunk - словарь trunk-портов,
для которых необходимо сгенерировать конфигурацию, вида:
{ 'FastEthernet0/1':[10,20],
'FastEthernet0/2':[11,30],
'FastEthernet0/4':[17] }
def generate_ospf_config(filename):
"""
filename - имя файла в формате YAML, в котором находится шаблон ospf.
264
Задания
templates = yaml.load(open(filename))
def generate_mngmt_config(filename):
"""
filename - имя файла в формате YAML, в котором находится шаблон mngmt.
def generate_alias_config(filename):
"""
filename - имя файла в формате YAML, в котором находится шаблон alias.
sw1 = generate_switch_config()
sw2 = generate_switch_config(psecurity=True, alias=True)
sw3 = generate_switch_config(ospf=False)
Задание 10.3
Создать функцию parse_sh_cdp_neighbors, которая обрабатывает вывод команды show
cdp neighbors.
265
Задания
Задание 10.3a
С помощью функции parse_sh_cdp_neighbors из задания 10.3, обработать вывод
команды sh cdp neighbor из файлов:
sh_cdp_n_sw1.txt
sh_cdp_n_r1.txt
sh_cdp_n_r2.txt
sh_cdp_n_r3.txt
sh_cdp_n_r4.txt
sh_cdp_n_r5.txt
sh_cdp_n_r6.txt
Задание 10.3b
Переделать функциональность скрипта из задания 10.3a, в функцию
generate_topology_from_cdp.
266
Задания
sh_cdp_n_sw1.txt
sh_cdp_n_r1.txt
sh_cdp_n_r2.txt
sh_cdp_n_r3.txt
sh_cdp_n_r4.txt
sh_cdp_n_r5.txt
sh_cdp_n_r6.txt
Задание 10.3c
С помощью функции draw_topology из файла draw_network_graph.py сгенерировать
топологию, которая соответствует описанию в файле topology.yaml
Для выполнения этого задания, должен быть установлен graphviz: pip install
graphviz
267
Задания
268
11. Работа с базами данных
269
SQL
SQL
SQL (structured query language) - используется для описания структуры БД,
управления данными (добавление, изменение, удаление, получение), управления
правами доступа к БД и ее объектам, управления транзакциями.
DDL
CREATE - создание новой таблицы, СУБД, схемы
ALTER - изменение существующей таблицы, колонки
DROP - удаление существующих объектов из СУБД
DML
SELECT - выбор данных
INSERT - добавление новых данных
UPDATE - обновление существующих данных
DELETE - удаление данных
DCL
GRANT - предоставление пользователям разрешения на чтение/запись
определенных объектов в СУБД
REVOKE - отзыв ранее предоставленых разрешений
TCL
COMMIT Transaction - применение транзакции
ROLLBACK Transaction - откат всех изменений сделанных в текущей
транзакции
SQL и Python
Для работы с реляционной СУБД в Python можно использовать два подхода:
270
SQL
работы с БД
Например, SQLAlchemy
271
SQLite
SQLite
SQLite — встраиваемая в процесс реализация SQL-машины.
Слово SQL-сервер здесь не используем, потому что как таковой сервер там не
нужен — весь функционал, который встраивается в SQL-сервер, реализован
внутри библиотеки (и, соответственно, внутри программы, которая её использует).
SQLite CLI
В комплекте поставки SQLite идёт также утилита для работы с SQLite в командной
строке. Утилита представлена в виде исполняемого файла sqlite3 (sqlite3.exe для
Windows) и с ее помощью можно вручную выполнять команды SQL.
С помощью этой утилиты очень удобно проверять правильность команд SQL, а также
в целом знакомиться с языком SQL.
Если вы используете Linux или Mac OS, то, скорее всего, sqlite3 установлен. Если
вы используете Windows, то можно скачать sqlite3 тут.
Для того чтобы создать БД (или открыть уже созданную) надо просто вызвать sqlite3
таким образом:
$ sqlite3 testDB.db
SQLite version 3.7.13 2012-06-11 02:05:22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite>
Внутри sqlite3 можно выполнять команды SQL или, так называемые, метакоманды (или
dot-команды).
Метакоманды
272
SQLite
Примеры метакоманд:
Примеры выполнения:
sqlite> .help
.backup ?DB? FILE Backup DB (default "main") to FILE
.bail ON|OFF Stop after hitting an error. Default OFF
.databases List names and files of attached databases
...
sqlite> .databases
seq name file
--- -------- ----------------------------------
0 main /home/nata/py_for_ne/db/db_article/testDB.db
273
Основы SQL (в sqlite3 CLI)
Если вы знакомы с базовым синтаксисом SQL, этот раздел можно пропустить и сразу
перейти к разделу "Модуль sqlite3".
274
CREATE
CREATE
Оператор create позволяет создавать таблицы.
sqlite> create table switch (mac text primary key, hostname text, model text, location
text);
В данном примере мы описали таблицу switch: определили какие поля будут в таблице
и значения какого типа будут в них находиться.
Кроме того, поле mac является первичным ключом. Это автоматически значит, что:
В этом примере это вполне логично, так как MAC-адрес должен быть уникальным.
275
DROP
DROP
Оператор drop удаляет таблицу вместе со схемой и всеми данными.
276
INSERT
INSERT
Оператор insert используется для добавления данных в таблицу.
Если указываются значения для всех полей, добавить запись можно таким образом
(порядок полей должен соблюдаться):
sqlite> INSERT into switch values ('0000.AAAA.CCCC', 'sw1', 'Cisco 3750', 'London, Gre
en Str');
Если нужно указать не все поля, или указать их в произвольном порядке, используется
такая запись:
277
SELECT
SELECT
Оператор select позволяет запрашивать информацию в таблице.
Например:
select * означает, что нужно вывести все поля таблицы. Следом, указывается из
sqlite> .headers ON
sqlite> SELECT * from switch;
mac|hostname|model|location
0000.AAAA.CCCC|sw1|Cisco 3750|London, Green Str
0000.BBBB.CCCC|sw5|Cisco 3850|London, Green Str
При желании, можно выставить и ширину колонок. Для этого используется команда
.width . Например, попробуйте выставить .width 20 .
278
SELECT
.headers on
.mode column
279
WHERE
WHERE
Оператор WHERE используется для уточнения запроса. С помощью этого оператора
можно указывать определенные условия, по которым отбираются данные. Если
условие выполнено, возвращается соответствующее значение из таблицы, если нет,
не возвращается.
280
WHERE
281
ALTER
ALTER
Оператор alter позволяет менять существующую таблицу: добавлять новые колонки
или переименовывать таблицу.
282
UPDATE
UPDATE
Оператор update используется для изменения существующей записи таблицы.
Например, предположим, что sw1 был заменен с модели 3750 на модель 3850.
Соответственно, изменилось не только поле модель, но и поле MAC-адрес.
Внесение изменений:
sqlite> UPDATE switch set model = 'Cisco 3850' where hostname = 'sw1';
sqlite> UPDATE switch set mac = '0000.DDDD.DDDD' where hostname = 'sw1';
sqlite> UPDATE switch set mac = '0000.EEEE.EEEE', model = 'Cisco 3750' where hostname
= 'sw1';
283
REPLACE
REPLACE
Оператор replace используется для добавления или замены данных в таблице.
sqlite> create table switch (mac text primary key, hostname text, model text, location
text);
sqlite> INSERT into switch values ('0000.AAAA.CCCC', 'sw1', 'Cisco 3750', 'London, Gre
en Str');
sqlite> INSERT into switch values ('0000.BBBB.CCCC', 'sw2', 'Cisco 3850', 'London, Gre
en Str');
sqlite> INSERT into switch values ('0000.CCCC.CCCC', 'sw3', 'Cisco 3850', 'London, Gre
en Str');
284
REPLACE
sqlite> replace into switch values ('0000.DDDD.CCCC', 'sw4', 'Cisco 3850', 'London, Gr
een Str');
sqlite> insert or replace into switch values ('0000.DDDD.CCCC', 'sw5', 'Cisco 3850', '
London, Green Str');
Если были указаны не все поля, в новой записи будут только те поля, которые были
указаны:
285
DELETE
DELETE
Оператор delete используется для удаления записей.
Например:
286
ORDER BY
ORDER BY
И еще один полезный оператор - ORDER BY. Он используется для сортировки вывода
по определенному полю, по возрастанию или убыванию.
287
Модуль sqlite3
Модуль sqlite3
Для работы с SQLite в Python используется модуль sqlite3.
Connection
Объект Connection - это подключение к конкретной БД. Можно сказать, что этот
объект представляет БД.
import sqlite3
connection = sqlite3.connect('dhcp_snooping.db')
Cursor
После создания соединения, надо создать объект Cursor - это основной способ работы
с БД.
connection = sqlite3.connect('dhcp_snooping.db')
cursor = connection.cursor()
раз
Метод execute
Метод execute позволяет выполнить одну команду SQL.
288
Модуль sqlite3
In [4]: cursor.execute("create table switch (mac text primary key, hostname text, mode
l text, location text)")
Out[4]: <sqlite3.Cursor at 0x1085be880>
In [5]: data = [
...: ('0000.AAAA.CCCC', 'sw1', 'Cisco 3750', 'London, Green Str'),
...: ('0000.BBBB.CCCC', 'sw2', 'Cisco 3780', 'London, Green Str'),
...: ('0000.AAAA.DDDD', 'sw3', 'Cisco 2960', 'London, Green Str'),
...: ('0011.AAAA.CCCC', 'sw4', 'Cisco 3750', 'London, Green Str')]
Второй аргумент, который передается методу execute, должен быть кортежем. Если
нужно передать кортеж с одним элементом, используется запись (value, ) .
289
Модуль sqlite3
In [8]: connection.commit()
Теперь, при запросе из командной строки sqlite3, можно увидеть эти строки в таблице
switch:
$ sqlite3 sw_inventory.db
Метод executemany
Метод executemany позволяет выполнить одну команду SQL для последовательности
параметров (или для итератора).
In [9]: data2 = [
...: ('0000.1111.0001', 'sw5', 'Cisco 3750', 'London, Green Str'),
...: ('0000.1111.0002', 'sw6', 'Cisco 3750', 'London, Green Str'),
...: ('0000.1111.0003', 'sw7', 'Cisco 3750', 'London, Green Str'),
...: ('0000.1111.0004', 'sw8', 'Cisco 3750', 'London, Green Str')]
In [12]: connection.commit()
290
Модуль sqlite3
Метод executescript
Метод executescript позволяет выполнить несколько выражений SQL за один раз.
In [16]: cursor.executescript("""
...: create table switches(
...: hostname text primary key,
...: location text
...: );
...:
...: create table dhcp(
...: mac text primary key,
...: ip text,
...: vlan text,
...: interface text,
...: switch text not null references switches(hostname)
...: );
...: """)
Out[16]: <sqlite3.Cursor at 0x10efd67a0>
291
Модуль sqlite3
Метод fetchone
Метод fetchone возвращает одну строку данных.
In [5]: cursor.fetchone()
Out[5]: (u'0000.AAAA.CCCC', u'sw1', u'Cisco 3750', u'London, Green Str')
Обратите внимание, что хотя запрос SQL подразумевает, что запрашивалось всё
содержимое таблицы, метод fetchone вернул только одну строку.
292
Модуль sqlite3
Метод fetchmany
Метод fetchmany возвращает возвращает список строк данных.
Синтаксис метода:
cursor.fetchmany([size=cursor.arraysize])
293
Модуль sqlite3
Метод выдает нужное количество строк, а если строк осталось меньше чем параметр
size, то оставшиеся строки.
Метод fetchall
Метод fetchall возвращает все строки в виде списка:
In [13]: cursor.fetchall()
Out[13]:
[(u'0000.AAAA.CCCC', u'sw1', u'Cisco 3750', u'London, Green Str'),
(u'0000.BBBB.CCCC', u'sw2', u'Cisco 3780', u'London, Green Str'),
(u'0000.AAAA.DDDD', u'sw3', u'Cisco 2960', u'London, Green Str'),
(u'0011.AAAA.CCCC', u'sw4', u'Cisco 3750', u'London, Green Str'),
(u'0000.1111.0001', u'sw5', u'Cisco 3750', u'London, Green Str'),
(u'0000.1111.0002', u'sw6', u'Cisco 3750', u'London, Green Str'),
(u'0000.1111.0003', u'sw7', u'Cisco 3750', u'London, Green Str'),
(u'0000.1111.0004', u'sw8', u'Cisco 3750', u'London, Green Str')]
294
Модуль sqlite3
In [15]: cursor.fetchone()
Out[15]: (u'0000.AAAA.CCCC', u'sw1', u'Cisco 3750', u'London, Green Str')
In [16]: cursor.fetchone()
Out[16]: (u'0000.BBBB.CCCC', u'sw2', u'Cisco 3780', u'London, Green Str')
In [17]: cursor.fetchall()
Out[17]:
[(u'0000.AAAA.DDDD', u'sw3', u'Cisco 2960', u'London, Green Str'),
(u'0011.AAAA.CCCC', u'sw4', u'Cisco 3750', u'London, Green Str'),
(u'0000.1111.0001', u'sw5', u'Cisco 3750', u'London, Green Str'),
(u'0000.1111.0002', u'sw6', u'Cisco 3750', u'London, Green Str'),
(u'0000.1111.0003', u'sw7', u'Cisco 3750', u'London, Green Str'),
(u'0000.1111.0004', u'sw8', u'Cisco 3750', u'London, Green Str')]
При использовании методов execute, возвращается курсор. А, так как курсор можно
использовать как итератор, можно использовать его, например, в цикле for:
295
Модуль sqlite3
Но, если использовать курсор, который возвращают методы execute, как итератор,
методы fetch могут и не понадобиться.
con = sqlite3.connect('sw_inventory2.db')
con.execute("create table switch (mac text primary key, hostname text, model text, loc
ation text)")
con.close()
296
Модуль sqlite3
$ python create_sw_inventory_ver1.py
(u'0000.AAAA.CCCC', u'sw1', u'Cisco 3750', u'London, Green Str')
(u'0000.BBBB.CCCC', u'sw2', u'Cisco 3780', u'London, Green Str')
(u'0000.AAAA.DDDD', u'sw3', u'Cisco 2960', u'London, Green Str')
(u'0011.AAAA.CCCC', u'sw4', u'Cisco 3750', u'London, Green Str')
Обработка исключений
Посмотрим на пример использования метода execute, при возникновении ошибки.
В таблице switch поле mac должно быть уникальным. И, если попытаться записать
пересекающийся MAC-адрес, возникнет ошибка:
In [23]: query = "INSERT into switch values ('0000.AAAA.DDDD', 'sw7', 'Cisco 2960', 'L
ondon, Green Str')"
In [24]: con.execute(query)
---------------------------------------------------------------------------
IntegrityError Traceback (most recent call last)
<ipython-input-56-ad34d83a8a84> in <module>()
----> 1 con.execute(query)
In [25]: try:
...: con.execute(query)
...: except sqlite3.IntegrityError as e:
...: print "Error occured: ", e
...:
Error occured: UNIQUE constraint failed: switch.mac
297
Модуль sqlite3
con = sqlite3.connect('sw_inventory3.db')
con.execute("create table switch (mac text primary key, hostname text, model text, loc
ation text)")
try:
with con:
query = "INSERT into switch values (?, ?, ?, ?)"
con.executemany(query, data)
except sqlite3.IntegrityError as e:
print "Error occured: ", e
298
Модуль sqlite3
con = sqlite3.connect('sw_inventory3.db')
con.execute("create table switch (mac text primary key, hostname text, model text, loc
ation text)")
try:
with con:
query = "INSERT into switch values (?, ?, ?, ?)"
con.executemany(query, data)
except sqlite3.IntegrityError as e:
print "Error occured: ", e
print '-'*30
try:
with con:
query = "INSERT into switch values (?, ?, ?, ?)"
con.executemany(query, data2)
except sqlite3.IntegrityError as e:
print "Error occured: ", e
299
Модуль sqlite3
$ python create_sw_inventory_ver3.py
(u'0000.AAAA.CCCC', u'sw1', u'Cisco 3750', u'London, Green Str')
(u'0000.BBBB.CCCC', u'sw2', u'Cisco 3780', u'London, Green Str')
(u'0000.AAAA.DDDD', u'sw3', u'Cisco 2960', u'London, Green Str')
(u'0011.AAAA.CCCC', u'sw4', u'Cisco 3750', u'London, Green Str')
------------------------------
Error occured: UNIQUE constraint failed: switch.mac
(u'0000.AAAA.CCCC', u'sw1', u'Cisco 3750', u'London, Green Str')
(u'0000.BBBB.CCCC', u'sw2', u'Cisco 3780', u'London, Green Str')
(u'0000.AAAA.DDDD', u'sw3', u'Cisco 2960', u'London, Green Str')
(u'0011.AAAA.CCCC', u'sw4', u'Cisco 3750', u'London, Green Str')
Так получилось из-за того, что используется метод executemany и в пределах одной
транзакции мы пытаемся записать все 4 строки. Если возникает ошибка с одной из них
- откатываются все изменения.
Иногда, это именно то поведение, которое нужно. Если же надо чтобы игнорировались
только строки с ошибками, надо использовать метод execute и записывать каждую
строку отдельно.
Файл create_sw_inventory_ver4.py:
300
Модуль sqlite3
con = sqlite3.connect('sw_inventory3.db')
con.execute("create table switch (mac text primary key, hostname text, model text, loc
ation text)")
try:
with con:
query = "INSERT into switch values (?, ?, ?, ?)"
con.executemany(query, data)
except sqlite3.IntegrityError as e:
print "Error occured: ", e
print '-'*30
301
Модуль sqlite3
$ python create_sw_inventory_ver4.py
(u'0000.AAAA.CCCC', u'sw1', u'Cisco 3750', u'London, Green Str')
(u'0000.BBBB.CCCC', u'sw2', u'Cisco 3780', u'London, Green Str')
(u'0000.AAAA.DDDD', u'sw3', u'Cisco 2960', u'London, Green Str')
(u'0011.AAAA.CCCC', u'sw4', u'Cisco 3750', u'London, Green Str')
------------------------------
Error occured: UNIQUE constraint failed: switch.mac
(u'0000.AAAA.CCCC', u'sw1', u'Cisco 3750', u'London, Green Str')
(u'0000.BBBB.CCCC', u'sw2', u'Cisco 3780', u'London, Green Str')
(u'0000.AAAA.DDDD', u'sw3', u'Cisco 2960', u'London, Green Str')
(u'0011.AAAA.CCCC', u'sw4', u'Cisco 3750', u'London, Green Str')
(u'0055.AAAA.CCCC', u'sw5', u'Cisco 3750', u'London, Green Str')
(u'0066.BBBB.CCCC', u'sw6', u'Cisco 3780', u'London, Green Str')
(u'0088.AAAA.CCCC', u'sw8', u'Cisco 3750', u'London, Green Str')
302
Пример использования SQLite
Для этого примера, достаточно создать одну таблицу, где будет храниться
информация.
Для всех полей определен тип данных "текст". И MAC-адрес является первичным
ключом нашей таблицы. Что вполне логично, так как, MAC-адрес должен быть
уникальным.
Теперь надо создать файл БД, подключиться к базе данных и создать таблицу (файл
create_sqlite_ver1.py):
303
Пример использования SQLite
import sqlite3
Комментарии к файлу:
Выполняем скрипт:
$ python create_sqlite_ver1.py
Creating schema...
Done
Выведем список созданных таблиц (запрос такого вида позволяет проверить какие
таблицы созданы в DB):
304
Пример использования SQLite
import sqlite3
import re
Пока что, файл БД каждый раз надо удалять, так как скрипт пытается его создать
при каждом запуске.
Комментарии к скрипту:
305
Пример использования SQLite
Выполняем скрипт:
$ python create_sqlite_ver2.py
Creating schema...
Done
Inserting DHCP Snooping data
Переделаем наш скрипт таким образом, чтобы в нём была проверка на наличие
файла dhcp_snooping.db. Если файл БД есть, то не надо создавать таблицу, считаем,
что она уже создана.
Файл create_sqlite_ver3.py:
306
Пример использования SQLite
import os
import sqlite3
import re
data_filename = 'dhcp_snooping.txt'
db_filename = 'dhcp_snooping.db'
schema_filename = 'dhcp_snooping_schema.sql'
db_exists = os.path.exists(db_filename)
Теперь есть проверка наличия файла БД, и файл dhcp_snooping.db будет создаваться
только в том случае, если его нет. Данные также записываются только в том случае,
если не создан файл dhcp_snooping.db.
$ python create_sqlite_ver3.py
Database exists, assume dhcp table does, too.
$ rm dhcp_snooping.db
$ python create_sqlite_ver3.py
Creating schema...
Done
Inserting DHCP Snooping data
307
Пример использования SQLite
Файл get_data_ver1.py:
db_filename = 'dhcp_snooping.db'
Комментарии к скрипту:
308
Пример использования SQLite
Файл get_data_ver2.py:
309
Пример использования SQLite
db_filename = 'dhcp_snooping.db'
query = query_dict[key]
result = conn.execute(query, (value,))
# метод description позволяет получить заголовки полученных столбцов.
# В нем будут находиться только те столбцы, который соответствуют запросу.
all_rows = [r[0] for r in result.description]
310
Задания
Задания
Все задания и вспомогательные файлы можно скачать одним архивом zip или tar.gz.
Также для курса подготовлены две виртуальные машины на выбор: Vagrant или
VMware. В них установлены все пакеты, которые используются в курсе.
Например, если в разделе есть задания: 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала,
можно выполнить задания 5.1, 5.2, 5.3. А затем 5.2a, 5.2b, 5.3a.
Задание 11.1
На основе файла create_sqlite_ver3.py из раздела, необходимо создать два скрипта:
create_db.py
сюда должна быть вынесена функциональность по созданию БД:
должна выполняться проверка наличия файла БД
если файла нет, согласно описанию схемы БД в файле
dhcp_snooping_schema.sql, должна быть создана БД (БД отличается от
примера в разделе)
add_data.py
с помощью этого скрипта, выполняется добавление данных в БД
добавлять надо не только данные из вывода sh ip dhcp snooping binding, но и
информацию о коммутаторах
Код в скриптах должен быть разбит на функции. Какие именно функции и как
разделить код, надо решить самостоятельно. Часть кода может быть глобальной.
311
Задания
Задание 11.1a
Добавить в файл add_data.py, из задания 11.1, проверку на наличие БД:
Задание 11.2
На основе файла get_data_ver1.py из раздела, создать скрипт get_data.py.
Код в скрипте должен быть разбит на функции. Какие именно функции и как разделить
код, надо решить самостоятельно. Часть кода может быть глобальной.
если скрипт был вызван без аргументов, вывести всё содержимое таблицы dhcp
отформатировать вывод в виде столбцов
если скрипт был вызван с двумя аргументами, вывести информацию из таблицы
dhcp, которая соответствует полю и значению
если скрипт был вызван с любым другим количеством аргументов, вывести
сообщение, что скрипт поддерживает только два или ноль аргументов
312
Задания
$ python get_data.py
Задание 11.2a
313
Задания
Задание 11.3
В прошлых заданиях информация добавлялась в пустую БД. В этом задании,
разбирается ситуация, когда в БД уже есть информация.
При создании схемы БД, было явно указано, что поле MAC-адрес, должно быть
уникальным. Поэтому, при добавлении записи с таким же MAC-адресом, возникает
ошибка.
Но, нужно каким-то образом обновлять БД, чтобы в ней хранилась актуальная
информация.
Поэтому, мы будем делать немного по-другому. Создадим новое поле active, которое
будет указывать является ли запись актуальной.
0 - означает False. И используется для того, чтобы отметить запись как неактивную
1 - True. Используется чтобы указать, что запись активна
314
Задания
Код в скрипте должен быть разбит на функции. Какие именно функции и как разделить
код, надо решить самостоятельно. Часть кода может быть глобальной.
Для проверки задания и работы нового поля, попробуйте удалить пару строк из одного
из фалов с выводом dhcp snooping. И после этого проверить, что удаленне строки
отображаются в таблице как неактивные.
Задание 11.4
Обновить файл get_data из задания 11.2 или 11.2a.
Например:
=======================================
Inactive values:
----------------------------------------
mac : 00:09:23:34:16:18
vlan : 10
interface : FastEthernet0/4
switch : sw1
----------------------------------------
Задание 11.5
315
Задания
И, хотя это может быть полезно, чтобы посмотреть, где MAC-адрес находился в
последний раз, постоянно хранить эту информацию не очень полезно.
Для того, чтобы сделать такой критерий, нужно ввести новое поле, в которое будет
записываться последнее время добавления записи.
import datetime
now = str(datetime.datetime.today().replace(microsecond=0))
#print now
Задание 11.5a
После выполнения задания 11.5, в таблице dhcp есть новое поле last_active.
Обновите скрипт add_data.py, таким образом, чтобы он удалял все записи, которые
были активными более 7 дней назад.
Для того, чтобы получить такие записи, можно просто вручную обновить поле
last_active.
316
Задания
now = datetime.today().replace(microsecond=0)
week_ago = now - timedelta(days = 7)
#print now
#print week_ago
#print now > week_ago
#print str(now) > str(week_ago)
Задание 11.6
В этом задании выложен файл parse_dhcp_snooping.py.
И задача этого задания в том, чтобы создать все необходимые функции, в файле
parse_dhcp_snooping_functions.py на основе информации в файле
parse_dhcp_snooping.py.
317
Задания
Для того, чтобы было проще начать, попробуйте создать необходимые функции в
файле parse_dhcp_snooping_functions.py и, например, просто выведите аргументы
функций, используя print.
создание БД
добавление информации о коммутаторах
добавление информации на основании вывода sh ip dhcp snooping binding из
файлов
выборку информации из БД (по параметру и всю информацию)
Чтобы было проще понять, как будет выглядеть вызов скрипта, ниже несколько
примеров.
$ python parse_dhcp_snooping.py -h
usage: parse_dhcp_snooping.py [-h] {create_db,add,get} ...
optional arguments:
-h, --help show this help message and exit
subcommands:
valid subcommands
optional arguments:
-h, --help show this help message and exit
--db DB_FILE db name
-k {mac,ip,vlan,interface,switch}
host key (parameter) to search
-v VALUE value of key
-a show db content
318
Задания
positional arguments:
filename file(s) to add to db
optional arguments:
-h, --help show this help message and exit
--db DB_FILE db name
-s add switch data if set, else add normal data
optional arguments:
-h, --help show this help message and exit
-n NAME db filename
-s SCHEMA db schema filename
319
Задания
----------------------------------------
320
12. Подключение к оборудованию
Подключение к оборудованию
В этом разделе рассматривается как подключиться к оборудованию по протоколам:
SSH
Telnet
пользователь: cisco
пароль: cisco
321
12. Подключение к оборудованию
322
Ввод пароля
Ввод пароля
При подключении к оборудованию вручную, как правило, пароль также вводится
вручную.
Как правило, один и тот же пользователь использует одинаковый логин и пароль для
подключения к оборудованию.
И, как правило, будет достаточно запросить логин и пароль при старте скрипта, а
затем использовать их для подключения на разные устройства.
Модуль getpass
Модуль getpass позволяет запрашивать пароль, не отображая вводимые символы:
Переменные окружения
Еще один вариант хранения пароля (а можно и пользователя) - переменные
окружения.
323
Ввод пароля
$ export SSH_USER=user
$ export SSH_PASSWORD=userpass
import os
USERNAME = os.environ.get('SSH_USER')
PASSWORD = os.environ.get('SSH_PASSWORD')
324
Pexpect
Модуль pexpect
Модуль pexpect позволяет автоматизировать интерактивные подключения, такие как:
telnet
ssh
ftp
При этом, сам pexpect не реализует различные утилиты, а использует уже готовые.
функция run()
класс spawn
pexpect.run()
Функция run() позволяет легко вызвать какую-то программу и вернуть её вывод.
Например:
325
Pexpect
pexpect.spawn
Класс spawn поддерживает больше возможностей. Он позволяет взаимодействовать с
вызванной программой, отправляя данные и ожидая ответ.
t = pexpect.spawn('ssh user@10.1.1.1')
t.expect('Password:')
t.sendline("userpass")
t.expect('>')
326
Pexpect
import pexpect
import getpass
import sys
COMMAND = sys.argv[1]
USER = raw_input("Username: ")
PASSWORD = getpass.getpass()
ENABLE_PASS = getpass.getpass(prompt='Enter enable password: ')
DEVICES_IP = ['192.168.100.1','192.168.100.2','192.168.100.3']
for IP in DEVICES_IP:
print "Connection to device %s" % IP
t = pexpect.spawn('ssh %s@%s' % (USER, IP))
t.expect('Password:')
t.sendline(PASSWORD)
t.expect('>')
t.sendline('enable')
t.expect('Password:')
t.sendline(ENABLE_PASS)
t.expect('#')
t.sendline("terminal length 0")
t.expect('#')
t.sendline(COMMAND)
t.expect('#')
print t.before
Комментарии с скрипту:
подключение
в цикле, выполняется подключение к устройствам из списка
в классе spawn выполняется подключение по SSH к текущему адресу, используя
указанное имя пользователя
после этого, начинают чередоваться пары методов: expect и sendline
expect - ожидание подстроки
327
Pexpect
в expect
Обратите внимание, что, так как в последнем expect указано, что надо ожидать
подстроку # , метод before показал и команду и имя хоста.
328
Pexpect
Для того чтобы, например, команда ls -ls | grep SUMMARY отработала, нужно
запустить shell таким образом:
In [3]: p.expect(pexpect.EOF)
Out[3]: 0
pexpect.EOF
В предыдущем примере встретилось использование pexpect.EOF.
In [6]: p.expect('nattaur')
---------------------------------------------------------------------------
EOF Traceback (most recent call last)
<ipython-input-9-9c71777698c2> in <module>()
----> 1 p.expect('nattaur')
329
Pexpect
Возможности pexpect.expect
330
Pexpect
строка
EOF - этот шаблон позволяет среагировать на исключение EOF
TIMEOUT - исключение timeout (по умолчанию значение timeout = 30 секунд)
compiled re
Например:
Документация pexpect
Документация модуля: pexpect.
331
Telnetlib
Модуль telnetlib
Модуль telnetlib входит в стандартную библиотеку Python. Это реализация клиента
telnet.
Подключиться по telnet можно и используя pexpect. Плюс telnetlib в том, что этот
модуль входит в стандартную библиотеку Python.
Принцип работы telnetlib напоминает pexpect, поэтому пример ниже должен быть
понятен.
Файл 2_telnetlib.py:
import telnetlib
import time
import getpass
import sys
COMMAND = sys.argv[1]
USER = raw_input("Username: ")
PASSWORD = getpass.getpass()
ENABLE_PASS = getpass.getpass(prompt='Enter enable password: ')
DEVICES_IP = ['192.168.100.1','192.168.100.2','192.168.100.3']
for IP in DEVICES_IP:
print "Connection to device %s" % IP
t = telnetlib.Telnet(IP)
t.read_until("Username:")
t.write(USER + '\n')
t.read_until("Password:")
t.write(PASSWORD + '\n')
t.write("enable\n")
t.read_until("Password:")
t.write(ENABLE_PASS + '\n')
t.write("terminal length 0\n")
t.write(COMMAND + '\n')
time.sleep(5)
output = t.read_very_eager()
print output
332
Telnetlib
Выполнение скрипт:
333
Telnetlib
R1#terminal length 0
R1#sh ip int br
Interface IP-Address OK? Method Status Protocol
FastEthernet0/0 192.168.100.1 YES NVRAM up up
FastEthernet0/1 unassigned YES NVRAM up up
FastEthernet0/1.10 10.1.10.1 YES manual up up
FastEthernet0/1.20 10.1.20.1 YES manual up up
FastEthernet0/1.30 10.1.30.1 YES manual up up
FastEthernet0/1.40 10.1.40.1 YES manual up up
FastEthernet0/1.50 10.1.50.1 YES manual up up
FastEthernet0/1.60 10.1.60.1 YES manual up up
FastEthernet0/1.70 10.1.70.1 YES manual up up
R1#
Connection to device 192.168.100.2
R2#terminal length 0
R2#sh ip int br
Interface IP-Address OK? Method Status Protocol
FastEthernet0/0 192.168.100.2 YES NVRAM up up
FastEthernet0/1 unassigned YES NVRAM up up
FastEthernet0/1.10 10.2.10.1 YES manual up up
FastEthernet0/1.20 10.2.20.1 YES manual up up
FastEthernet0/1.30 10.2.30.1 YES manual up up
FastEthernet0/1.40 10.2.40.1 YES manual up up
FastEthernet0/1.50 10.2.50.1 YES manual up up
FastEthernet0/1.60 10.2.60.1 YES manual up up
FastEthernet0/1.70 10.2.70.1 YES manual up up
R2#
Connection to device 192.168.100.3
R3#terminal length 0
R3#sh ip int br
Interface IP-Address OK? Method Status Protocol
FastEthernet0/0 192.168.100.3 YES NVRAM up up
FastEthernet0/1 unassigned YES NVRAM up up
FastEthernet0/1.10 10.3.10.1 YES manual up up
FastEthernet0/1.20 10.3.20.1 YES manual up up
FastEthernet0/1.30 10.3.30.1 YES manual up up
FastEthernet0/1.40 10.3.40.1 YES manual up up
FastEthernet0/1.50 10.3.50.1 YES manual up up
FastEthernet0/1.60 10.3.60.1 YES manual up up
FastEthernet0/1.70 10.3.70.1 YES manual up up
R3#
334
Telnetlib
335
Paramiko
Модуль paramiko
Paramiko это реализация протокола SSHv2 на Python. Paramiko предоставляет
функциональность клиента и сервера. Мы будем рассматривать только
функциональность клиента.
Так как Paramiko не входит в стандартную библиотеку модулей Python, его нужно
установить:
import paramiko
import getpass
import sys
import time
COMMAND = sys.argv[1]
USER = raw_input("Username: ")
PASSWORD = getpass.getpass()
ENABLE_PASS = getpass.getpass(prompt='Enter enable password: ')
DEVICES_IP = ['192.168.100.1','192.168.100.2','192.168.100.3']
for IP in DEVICES_IP:
print "Connection to device %s" % IP
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.send("enable\n")
ssh.send(ENABLE_PASS + '\n')
time.sleep(1)
ssh.send(COMMAND + "\n")
time.sleep(2)
result = ssh.recv(5000)
print result
336
Paramiko
Комментарии к скрипту:
client = paramiko.SSHClient()
аутентифицирует подключение
hostname - имя хоста или IP-адрес
password - пароль
ОС. Это нужно при работе с ключами, а так как, в данном случае,
аутентификация выполняется по логину/паролю, это нужно отключить.
ssh = client.invoke_shell()
337
Paramiko
R1>enable
Password:
R1#terminal length 0
R1#
sh ip int br
Interface IP-Address OK? Method Status Protocol
FastEthernet0/0 192.168.100.1 YES NVRAM up up
FastEthernet0/1 unassigned YES NVRAM up up
FastEthernet0/1.10 10.1.10.1 YES manual up up
FastEthernet0/1.20 10.1.20.1 YES manual up up
FastEthernet0/1.30 10.1.30.1 YES manual up up
FastEthernet0/1.40 10.1.40.1 YES manual up up
FastEthernet0/1.50 10.1.50.1 YES manual up up
FastEthernet0/1.60 10.1.60.1 YES manual up up
FastEthernet0/1.70 10.1.70.1 YES manual up up
R1#
Connection to device 192.168.100.2
R2>enable
Password:
R2#terminal length 0
R2#
sh ip int br
FastEthernet0/0 192.168.100.2 YES NVRAM up up
FastEthernet0/1 unassigned YES NVRAM up up
FastEthernet0/1.10 10.2.10.1 YES manual up up
FastEthernet0/1.20 10.2.20.1 YES manual up up
FastEthernet0/1.30 10.2.30.1 YES manual up up
FastEthernet0/1.40 10.2.40.1 YES manual up up
FastEthernet0/1.50 10.2.50.1 YES manual up up
FastEthernet0/1.60 10.2.60.1 YES manual up up
FastEthernet0/1.70 10.2.70.1 YES manual up up
R2#
Connection to device 192.168.100.3
R3>enable
Password:
R3#terminal length 0
R3#
sh ip int br
Interface IP-Address OK? Method Status Protocol
FastEthernet0/0 192.168.100.3 YES NVRAM up up
FastEthernet0/1 unassigned YES NVRAM up up
FastEthernet0/1.10 10.3.10.1 YES manual up up
FastEthernet0/1.20 10.3.20.1 YES manual up up
FastEthernet0/1.30 10.3.30.1 YES manual up up
FastEthernet0/1.40 10.3.40.1 YES manual up up
FastEthernet0/1.50 10.3.50.1 YES manual up up
338
Paramiko
Обратите внимание, что в вывод попал и процесс ввода пароля enable и команда
terminal length.
Это связано с тем, что paramiko собирает весь вывод в буфер. И, при вызове метода
recv (например, ssh.recv(1000) ), paramiko возвращает всё, что есть в буфере. После
Поэтому, если нужно получить только вывод команды sh ip int br, то надо оставить
recv , но не делать print:
ssh.send("enable\n")
ssh.send(ENABLE_PASS + '\n')
time.sleep(1)
ssh.send(COMMAND + "\n")
time.sleep(3)
result = ssh.recv(5000)
print result
Документация Paramiko
Всё, что касается клиента: Paramiko Client
Всё, что касается соединения (в нашем примере, всё, что относится к переменной
ssh): Paramiko Channel
339
Netmiko
Модуль netmiko
Netmiko это модуль, который позволяет упростить использование paramiko для
сетевых устройств.
COMMAND = sys.argv[1]
USER = raw_input("Username: ")
PASSWORD = getpass.getpass()
ENABLE_PASS = getpass.getpass(prompt='Enter enable password: ')
DEVICES_IP = ['192.168.100.1','192.168.100.2','192.168.100.3']
for IP in DEVICES_IP:
print "Connection to device %s" % IP
DEVICE_PARAMS = {'device_type': 'cisco_ios',
'ip': IP,
'username':USER,
'password':PASSWORD,
'secret':ENABLE_PASS }
ssh = ConnectHandler(**DEVICE_PARAMS)
ssh.enable()
result = ssh.send_command(COMMAND)
print result
340
Netmiko
В этом примере не передается команда terminal length, так как netmiko по умолчанию,
выполняет эту команду.
341
Netmiko
В выводе нет никаких лишних приглашений, только вывод команды sh ip int br.
Так как netmiko наиболее удобный модуль для подключения к сетевому оборудования,
разберемся с ним подробней.
342
Возможности netmiko
Arista vEOS
Cisco ASA
Cisco IOS
Cisco IOS-XR
Cisco SG300
HP Comware7
HP ProCurve
Juniper Junos
Linux
и другие
Подключение по SSH
ssh = ConnectHandler(**cisco_router)
Режим enable
Перейти в режим enable:
343
Возможности netmiko
ssh.enable()
ssh.exit_enable_mode()
Отправка команд
В netmiko есть несколько способов отправки команд:
send_config_set )
таймера
send_command
Метод send_command позволяет отправить одну команду на устройство.
Например:
344
Возможности netmiko
send_config_set
Метод send_config_set позволяет отправить несколько команд конфигурационного
режима.
Пример использования:
result = ssh.send_config_set(commands)
send_config_from_file
Метод send_config_from_file отправляет команды из указанного файла в
конфигурационный режим.
Пример использования:
result = ssh.send_config_from_file("config_ospf.txt")
Дополнительные методы
345
Возможности netmiko
ssh.config_mode()
ssh.exit_config_mode()
ssh.find_prompt()
ssh.commit()
Telnet
С версии 1.0.0 netmiko поддерживает подключения по Telnet. Пока что, только для
Cisco IOS устройств.
Внутри, netmiko использует telnetlib, для подключения по Telnet. Но, при этом,
предоставляет тот же интерфейс для работы, что и подключение по SSH.
346
Возможности netmiko
COMMAND = sys.argv[1]
USER = raw_input("Username: ")
PASSWORD = getpass.getpass()
ENABLE_PASS = getpass.getpass(prompt='Enter enable password: ')
DEVICES_IP = ['192.168.100.1','192.168.100.2','192.168.100.3']
for IP in DEVICES_IP:
print "Connection to device %s" % IP
DEVICE_PARAMS = {'device_type': 'cisco_ios_telnet',
'ip': IP,
'username':USER,
'password':PASSWORD,
'secret':ENABLE_PASS,
'verbose': True}
ssh = ConnectHandler(**DEVICE_PARAMS)
ssh.enable()
result = ssh.send_command(COMMAND)
print result
send_command_timing()
find_prompt()
send_config_set()
send_config_from_file()
check_enable_mode()
disconnect()
347
Одновременное подключение к нескольким устройствам
Параллельные сессии
Когда нужно опросить много устройств, выполнение подключений поочередно, будет
достаточно долгим. Конечно, это будет быстрее, чем подключение вручную. Но,
хотелось бы получать отклик как можно быстрее.
threading
multiprocessing
time
Утилита time в Linux позволяет замерить время выполнения скрипта. Например:
Для использования утилиты time достаточно написать time перед строкой запуска
скрипта.
348
Одновременное подключение к нескольким устройствам
datetime
Второй вариант - модуль datetime. Этот модуль позволяет работать с временем и
датами в Python.
Пример использования:
start_time = datetime.now()
Результат выполнения:
$ python test.py
0:00:05.004949
349
Одновременное подключение к нескольким устройствам
Флаг передается либо каждые сколько-то инструкций Python, либо, например, когда
выполняются какие-то операции ввода-вывода.
Но, не всё так плохо. Если в программе есть некое "ожидание": пакетов из сети,
запроса пользователя, пауза типа sleep, то в такой программе потоки будут
выполняться как-будто параллельно. А всё потому, что, во время таких пауз, флаг
(GIL) можно передать другому потоку.
Но, тут также нужно быть осторожным, так как такой результат может наблюдаться на
небольшом количестве сессий, но может ухудшиться с ростом количества сессий.
Процессы
Обычно, чтобы не погружаться во все эти тонкости с GIL и потоками в Python, проще
просто использовать модуль multiprocessing и всё.
Если же вы столкнетесь с ситуацией где вам нужно будет использовать потоки, скорее
всего, к тому времени вы уже разберетесь с этим вопросом. Как минимум достаточно в
целом знать об описанных выше особенностях.
Дополнительная информация
Если вы хотите подробнее разобраться с этими вопросами, или захотите вернуться к
ним позже, несколько ссылок:
GIL:
GIL (на русском)
Inside the Python GIL
Отличная статья Python threads and the GIL
Коротко о GIL, threads, processes:
350
Одновременное подключение к нескольким устройствам
http://stackoverflow.com/questions/3044580/multiprocessing-vs-threading-python
http://stackoverflow.com/questions/18114285/python-what-are-the-differences-
between-the-threading-and-multiprocessing-modul
351
Модуль threading
Модуль threading
Модуль threading может быть полезен для таких задач:
Так как для работы с threading, удобнее использовать функции, код изменен:
Файл netmiko_function.py:
COMMAND = sys.argv[1]
devices = yaml.load(open('devices.yaml'))
ssh = ConnectHandler(**device_dict)
ssh.enable()
result = ssh.send_command(command)
print result
352
Модуль threading
routers:
- device_type: cisco_ios
ip: 192.168.100.1
username: cisco
password: cisco
secret: cisco
- device_type: cisco_ios
ip: 192.168.100.2
username: cisco
password: cisco
secret: cisco
- device_type: cisco_ios
ip: 192.168.100.3
username: cisco
password: cisco
secret: cisco
353
Модуль threading
COMMAND = sys.argv[1]
devices = yaml.load(open('devices.yaml'))
for th in threads:
th.join()
...
real 0m2.229s
user 0m0.408s
sys 0m0.068s
Время почти в три раза меньше. Но, надо учесть, что такая ситуация не будет
повторяться при большом количестве подключений.
354
Модуль threading
метод join выполняется для каждого потока в списке. Таким образом основная
программа завершится только когда завершат работу все потоки
по умолчанию, join ждет завершения работы потока бесконечно. Но, можно
ограничить время ожидания передав join время в секундах. В таком случае,
join завершится после указанного количества секунд.
В Python есть модуль Queue, который позволяет создавать разные типы очередей.
355
Модуль threading
COMMAND = sys.argv[1]
devices = yaml.load(open('devices.yaml'))
for th in threads:
th.join()
results = []
# Берем результаты из очереди и добавляем их в список results
for t in threads:
results.append(q.get())
return results
356
Модуль threading
Пример со списком, скорее всего, будет проще понять. Поэтому ниже аналогичный
код, но с использованием обычного списка, вместо очереди (файл
netmiko_threading_data_list.py):
COMMAND = sys.argv[1]
devices = yaml.load(open('devices.yaml'))
for th in threads:
th.join()
return q
357
Модуль threading
358
Модуль multiprocessing
Модуль multiprocessing
Модуль multiprocessing использует интерфейс подобный модулю threading. Поэтому
перенести код с использования потоков на использование процессов, обычно,
достаточно легко.
Каждому процессу выделяются свои ресурсы. Кроме того, у каждого процесса свой
GIL, а значит, нет тех проблем, которые были с потоками и код может выполняться
параллельно и задействовать ядра/процессоры компьютера.
359
Модуль multiprocessing
import multiprocessing
from netmiko import ConnectHandler
import sys
import yaml
COMMAND = sys.argv[1]
devices = yaml.load(open('devices.yaml'))
for p in processes:
p.join()
results = []
for p in processes:
results.append(queue.get())
return results
Если проверить время исполнения этого скрипта, аналогичного для модуля threading и
последовательного подключения, то получаем такую картину:
360
Модуль multiprocessing
последовательное: 5.833s
threading: 2.225s
multiprocessing: 2.365s
Время выполнения для модуля multiprocessing немного больше. Но это связано с тем,
что на создание процессов уходит больше времени, чем на создание потоков. Если бы
скрипт был сложнее и выполнялось больше задач, или было бы больше подключений,
тогда бы multiprocessing начал бы существенно выигрывать у модуля threading.
361
Задания
Задания
Все задания и вспомогательные файлы можно скачать одним архивом zip или tar.gz.
Также для курса подготовлены две виртуальные машины на выбор: Vagrant или
VMware. В них установлены все пакеты, которые используются в курсе.
Например, если в разделе есть задания: 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала,
можно выполнить задания 5.1, 5.2, 5.3. А затем 5.2a, 5.2b, 5.3a.
Задание 12.1
Создать функцию send_show_command.
Параметры функции:
ключ - IP устройства
значение - результат выполнения команды
362
Задания
import netmiko
Параметры функции:
- devices_list - список словарей с параметрами подключения к устройствам,
которым надо передать команды
- command - команда, которую надо выполнить
Задание 12.2
Создать функцию send_config_commands
Параметры функции:
ключ - IP устройства
значение - вывод с выполнением команд
363
Задания
import netmiko
Параметры функции:
- devices_list - список словарей с параметрами подключения к устройствам, кото
рым надо передать команды
- config_commands - список команд, которые надо выполнить
Задание 12.2a
Дополнить функцию send_config_commands из задания 12.2
Задание 12.2b
Дополнить функцию send_config_commands из задания 12.2a или 12.2
Если при выполнении какой-то из команд возникла ошибка, функция должна выводить
сообщение на стандартный поток вывода с информацией о том, какая ошибка
возникла, при выполнении какой команды и на каком устройстве.
Задание 12.3
364
Задания
Параметры функции:
ключ - IP устройства
значение - вывод с выполнением команд
365
Задания
Задание 12.3a
Изменить функцию send_commands таким образом, чтобы в списке словарей
device_list не надо было указывать имя пользователя, пароль, и пароль на enable.
Задание 12.3b
Дополнить функцию send_commands таким образом, чтобы перед подключением к
устройствам по SSH, выполнялась проверка доступности устройства pingом (можно
вызвать команду ping в ОС).
Как выполнять команды ОС, описано в разделе subprocess. Там же есть пример
функции с отправкой ping.
Для удобства можно сделать отдельную функцию для проверки доступности и затем
использовать ее в функции send_commands.
366
Задания
Задание 12.4
В задании используется пример из раздела про модуль threading.
Пример из раздела:
367
Задания
COMMAND = sys.argv[1]
devices = yaml.load(open('devices.yaml'))
ssh = ConnectHandler(**device_dict)
ssh.enable()
result = ssh.send_command(command)
print "Connection to device %s" % device_dict['ip']
for th in threads:
th.join()
results = []
for t in threads:
results.append(q.get())
return results
Задание 12.5
Использовать функции полученные в результате выполнения задания 12.4.
368
Задания
Задание 12.6
В задании используется пример из раздела про модуль multiprocessing.
Пример из раздела:
369
Задания
import multiprocessing
from netmiko import ConnectHandler
import sys
import yaml
COMMAND = sys.argv[1]
devices = yaml.load(open('devices.yaml'))
for p in processes:
p.join()
results = []
for p in processes:
results.append(queue.get())
return results
Задание 12.7
Использовать функции полученные в результате выполнения задания 12.6.
370
Задания
371
13. Шаблоны конфигураций с Jinja
Примеры использования:
Идея Jinja очень проста: разделение данных и шаблона. Это позволяет использовать
один и тот же шаблон, но подставлять в него разные данные.
В самом простом случае, шаблон это просто текстовый файл, в котором указаны
места подстановки значений, с помощью переменных Jinja.
hostname {{name}}
!
interface Loopback255
description Management loopback
ip address 10.255.{{id}}.1 255.255.255.255
!
interface GigabitEthernet0/0
description LAN to {{name}} sw1 {{int}}
ip address {{ip}} 255.255.255.0
!
router ospf 10
router-id 10.255.{{id}}.1
auto-cost reference-bandwidth 10000
network 10.0.0.0 0.255.255.255 area 0
372
13. Шаблоны конфигураций с Jinja
Комментарии к шаблону:
template = Template(u"""
hostname {{name}}
!
interface Loopback255
description Management loopback
ip address 10.255.{{id}}.1 255.255.255.255
!
interface GigabitEthernet0/0
description LAN to {{name}} sw1 {{int}}
ip address {{ip}} 255.255.255.0
!
router ospf 10
router-id 10.255.{{id}}.1
auto-cost reference-bandwidth 10000
network 10.0.0.0 0.255.255.255 area 0
""")
373
13. Шаблоны конфигураций с Jinja
$ python basic_generator.py
hostname Liverpool
!
interface Loopback255
description Management loopback
ip address 10.255.11.1 255.255.255.255
!
interface GigabitEthernet0/0
description LAN to Liverpool sw1 Gi1/0/17
ip address 10.1.1.10 255.255.255.0
!
router ospf 10
router-id 10.255.11.1
auto-cost reference-bandwidth 10000
network 10.0.0.0 0.255.255.255 area 0
374
Пример использования Jinja
router_template.py - шаблон
routers_info.yml - в этом файле, в виде списка словарей (в формате YAML),
находится информация о маршрутизаторах, для которых нужно сгенерировать
конфигурационный файл
router_config_generator.py - в этом скрипте импортируется файл с шаблоном и
считывается информация из файла в формате YAML, а затем генерируются
конфигурационные файлы маршрутизаторов
Файл router_template.py
375
Пример использования Jinja
template_r1 = Template(u"""
hostname {{name}}
!
interface Loopback10
description MPLS loopback
ip address 10.10.{{id}}.1 255.255.255.255
!
interface GigabitEthernet0/0
description WAN to {{name}} sw1 G0/1
!
interface GigabitEthernet0/0.1{{id}}1
description MPLS to {{to_name}}
encapsulation dot1Q 1{{id}}1
ip address 10.{{id}}.1.2 255.255.255.252
ip ospf network point-to-point
ip ospf hello-interval 1
ip ospf cost 10
!
interface GigabitEthernet0/1
description LAN {{name}} to sw1 G0/2 !
interface GigabitEthernet0/1.{{IT}}
description PW IT {{name}} - {{to_name}}
encapsulation dot1Q {{IT}}
xconnect 10.10.{{to_id}}.1 {{id}}11 encapsulation mpls
backup peer 10.10.{{to_id}}.2 {{id}}21
backup delay 1 1
!
interface GigabitEthernet0/1.{{BS}}
description PW BS {{name}} - {{to_name}}
encapsulation dot1Q {{BS}}
xconnect 10.10.{{to_id}}.1 {{to_id}}{{id}}11 encapsulation mpls
backup peer 10.10.{{to_id}}.2 {{to_id}}{{id}}21
backup delay 1 1
!
router ospf 10
router-id 10.10.{{id}}.1
auto-cost reference-bandwidth 10000
network 10.0.0.0 0.255.255.255 area 0
!
""")
376
Пример использования Jinja
reload(sys)
sys.setdefaultencoding('utf-8')
Файл routers_info.yml
- id: 11
name: Liverpool
to_name: LONDON
IT: 791
BS: 1550
to_id: 1
- id: 12
name: Bristol
to_name: LONDON
IT: 793
BS: 1510
to_id: 1
- id: 14
name: Coventry
to_name: Manchester
IT: 892
BS: 1650
to_id: 2
Файл router_config_generator.py
routers = yaml.load(open('routers_info.yml'))
Файл router_config_generator.py:
377
Пример использования Jinja
$ python router_config_generator.py
Liverpool_r1.txt
Bristol_r1.txt
Coventry_r1.txt
378
Пример использования Jinja
hostname Liverpool
!
interface Loopback10
description MPLS loopback
ip address 10.10.11.1 255.255.255.255
!
interface GigabitEthernet0/0
description WAN to Liverpool sw1 G0/1
!
interface GigabitEthernet0/0.1111
description MPLS to LONDON
encapsulation dot1Q 1111
ip address 10.11.1.2 255.255.255.252
ip ospf network point-to-point
ip ospf hello-interval 1
ip ospf cost 10
!
interface GigabitEthernet0/1
description LAN Liverpool to sw1 G0/2
!
interface GigabitEthernet0/1.791
description PW IT Liverpool - LONDON
encapsulation dot1Q 791
xconnect 10.10.1.1 1111 encapsulation mpls
backup peer 10.10.1.2 1121
backup delay 1 1
!
interface GigabitEthernet0/1.1550
description PW BS Liverpool - LONDON
encapsulation dot1Q 1550
xconnect 10.10.1.1 11111 encapsulation mpls
backup peer 10.10.1.2 11121
backup delay 1 1
!
router ospf 10
router-id 10.10.11.1
auto-cost reference-bandwidth 10000
network 10.0.0.0 0.255.255.255 area 0
!
379
Пример использования Jinja
hostname Bristol
!
interface Loopback10
description MPLS loopback
ip address 10.10.12.1 255.255.255.255
!
interface GigabitEthernet0/0
description WAN to Bristol sw1 G0/1
!
interface GigabitEthernet0/0.1121
description MPLS to LONDON
encapsulation dot1Q 1121
ip address 10.12.1.2 255.255.255.252
ip ospf network point-to-point
ip ospf hello-interval 1
ip ospf cost 10
!
interface GigabitEthernet0/1
description LAN Bristol to sw1 G0/2
!
interface GigabitEthernet0/1.793
description PW IT Bristol - LONDON
encapsulation dot1Q 793
xconnect 10.10.1.1 1211 encapsulation mpls
backup peer 10.10.1.2 1221
backup delay 1 1
!
interface GigabitEthernet0/1.1510
description PW BS Bristol - LONDON
encapsulation dot1Q 1510
xconnect 10.10.1.1 11211 encapsulation mpls
backup peer 10.10.1.2 11221
backup delay 1 1
!
router ospf 10
router-id 10.10.12.1
auto-cost reference-bandwidth 10000
network 10.0.0.0 0.255.255.255 area 0
!
380
Пример использования Jinja
hostname Coventry
!
interface Loopback10
description MPLS loopback
ip address 10.10.14.1 255.255.255.255
!
interface GigabitEthernet0/0
description WAN to Coventry sw1 G0/1
!
interface GigabitEthernet0/0.1141
description MPLS to Manchester
encapsulation dot1Q 1141
ip address 10.14.1.2 255.255.255.252
ip ospf network point-to-point
ip ospf hello-interval 1
ip ospf cost 10
!
interface GigabitEthernet0/1
description LAN Coventry to sw1 G0/2
!
interface GigabitEthernet0/1.892
description PW IT Coventry - Manchester
encapsulation dot1Q 892
xconnect 10.10.2.1 1411 encapsulation mpls
backup peer 10.10.2.2 1421
backup delay 1 1
!
interface GigabitEthernet0/1.1650
description PW BS Coventry - Manchester
encapsulation dot1Q 1650
xconnect 10.10.2.1 21411 encapsulation mpls
backup peer 10.10.2.2 21421
backup delay 1 1
!
router ospf 10
router-id 10.10.14.1
auto-cost reference-bandwidth 10000
network 10.0.0.0 0.255.255.255 area 0
!
381
Пример использования Jinja с корректным использованием программного интерфейса
382
Пример использования Jinja с корректным использованием программного интерфейса
hostname {{name}}
!
interface Loopback10
description MPLS loopback
ip address 10.10.{{id}}.1 255.255.255.255
!
interface GigabitEthernet0/0
description WAN to {{name}} sw1 G0/1
!
interface GigabitEthernet0/0.1{{id}}1
description MPLS to {{to_name}}
encapsulation dot1Q 1{{id}}1
ip address 10.{{id}}.1.2 255.255.255.252
ip ospf network point-to-point
ip ospf hello-interval 1
ip ospf cost 10
!
interface GigabitEthernet0/1
description LAN {{name}} to sw1 G0/2 !
interface GigabitEthernet0/1.{{IT}}
description PW IT {{name}} - {{to_name}}
encapsulation dot1Q {{IT}}
xconnect 10.10.{{to_id}}.1 {{id}}11 encapsulation mpls
backup peer 10.10.{{to_id}}.2 {{id}}21
backup delay 1 1
!
interface GigabitEthernet0/1.{{BS}}
description PW BS {{name}} - {{to_name}}
encapsulation dot1Q {{BS}}
xconnect 10.10.{{to_id}}.1 {{to_id}}{{id}}11 encapsulation mpls
backup peer 10.10.{{to_id}}.2 {{to_id}}{{id}}21
backup delay 1 1
!
router ospf 10
router-id 10.10.{{id}}.1
auto-cost reference-bandwidth 10000
network 10.0.0.0 0.255.255.255 area 0
!
383
Пример использования Jinja с корректным использованием программного интерфейса
- id: 11
name: Liverpool
to_name: LONDON
IT: 791
BS: 1550
to_id: 1
- id: 12
name: Bristol
to_name: LONDON
IT: 793
BS: 1510
to_id: 1
- id: 14
name: Coventry
to_name: Manchester
IT: 892
BS: 1650
to_id: 2
routers = yaml.load(open('routers_info.yml'))
384
Пример использования Jinja с корректным использованием программного интерфейса
Если шаблоны находятся в текущем каталоге, надо добавить пару строк и изменить
значение в загрузчике:
import os
curr_dir = os.path.dirname(os.path.abspath(__file__))
env = Environment(loader = FileSystemLoader(curr_dir))
385
Синтаксис шаблонов Jinja2
переменные
условия (if/else)
циклы (for)
фильтры - специальные встроенные методы, которые позволяют делать
преобразования переменных
тесты - используются для проверки соответствует ли переменная какому-то
условию
386
Синтаксис шаблонов Jinja2
Для того, чтобы посмотреть на результат, нужно вызвать скрипт и передать ему два
аргумента:
шаблон
файл с переменными, в формате YAML
387
Контроль символов whitespace
388
Контроль символов whitespace
Но перед строками neighbor ... remote-as появились два пробела. Так получилось из-
за того, что перед блоком {% for ibgp in bgp.ibgp_neighbors %} стоит пробел. После
того, как был отключен лишний перевод строки, пробелы и табы перед блоком
добавляются к первой строке блока.
389
Контроль символов whitespace
Плюс после знака процента отключает lstrip_blocks для блока. В данном случае,
только для начала блока.
Если сделать таким образом (плюс добавлен в выражении для завершения блока):
390
Контроль символов whitespace
Шаблон templates/env_flags3.txt:
Обратите внимание на минус в начале второго блока. Минут удаляет все whitespace
символы. В данном случае, в начале блока.
391
Контроль символов whitespace
392
Контроль символов whitespace
393
Переменные
Переменные
Переменные в шаблоне указываются в двойных фигурных скобках:
hostname {{ name }}
interface Loopback0
ip address 10.0.0.{{ id }} 255.255.255.255
hostname {{ name }}
interface Loopback0
ip address 10.0.0.{{ id }} 255.255.255.255
vlan {{ vlans[0] }}
router ospf 1
router-id 10.0.0.{{ id }}
auto-cost reference-bandwidth 10000
network {{ ospf.network }} area {{ ospf['area'] }}
id: 3
name: R3
vlans:
- 10
- 20
- 30
ospf:
network: 10.0.1.0 0.0.0.255
area: 0
394
Переменные
так как переменная vlans это список, можно указывать какой именно элемент из
списка нам нужен
Если передается словарь (как в случае с переменной ospf), то внутри шаблона можно
обращаться к объектам словаря, используя один из вариантов:
interface Loopback0
ip address 10.0.0.3 255.255.255.255
vlan 10
router ospf 1
router-id 10.0.0.3
auto-cost reference-bandwidth 10000
network 10.0.1.0 0.0.0.255 area 0
395
for
Цикл for
Цикл for позволяет проходиться по элементам последовательности.
Цикл for должен находиться внутри символов {% %} . Кроме того, нужно явно
указывать завершение цикла:
hostname {{ name }}
interface Loopback0
ip address 10.0.0.{{ id }} 255.255.255.255
router ospf 1
router-id 10.0.0.{{ id }}
auto-cost reference-bandwidth 10000
{% for networks in ospf %}
network {{ networks.network }} area {{ networks.area }}
{% endfor %}
id: 3
name: R3
vlans:
10: Marketing
20: Voice
30: Management
ospf:
- network: 10.0.1.0 0.0.0.255
area: 0
- network: 10.0.2.0 0.0.0.255
area: 2
- network: 10.1.1.0 0.0.0.255
area: 0
396
for
В цикле for можно проходиться как по элементам списка (например, список ospf), так и
по словарю (словарь vlans). И, аналогичным образом, по любой последовательности.
interface Loopback0
ip address 10.0.0.3 255.255.255.255
vlan 10
name Marketing
vlan 20
name Voice
vlan 30
name Management
router ospf 1
router-id 10.0.0.3
auto-cost reference-bandwidth 10000
network 10.0.1.0 0.0.0.255 area 0
network 10.0.2.0 0.0.0.255 area 2
network 10.1.1.0 0.0.0.255 area 0
397
if/elif/else
if/elif/else
if позволяет добавлять условие в шаблон. Например, можно использовать if чтобы
добавлять какие-то части шаблона, в зависимости от наличия переменных в словаре с
данными.
{% if ospf %}
router ospf 1
router-id 10.0.0.{{ id }}
auto-cost reference-bandwidth 10000
{% endif %}
hostname {{ name }}
interface Loopback0
ip address 10.0.0.{{ id }} 255.255.255.255
{% if ospf %}
router ospf 1
router-id 10.0.0.{{ id }}
auto-cost reference-bandwidth 10000
{% for networks in ospf %}
network {{ networks.network }} area {{ networks.area }}
{% endfor %}
{% endif %}
Выражение if ospf работает так же, как в Python: если переменная существует и не
пустая, результат будет True. Если переменной нет, или она пустая, результат будет
False.
То есть, в этом шаблоне конфигурация OSPF генерируется только в том случае, если
переменная ospf существует и не пустая.
398
if/elif/else
id: 3
name: R3
vlans:
10: Marketing
20: Voice
30: Management
hostname R3
interface Loopback0
ip address 10.0.0.3 255.255.255.255
vlan 10
name Marketing
vlan 20
name Voice
vlan 30
name Management
id: 3
name: R3
vlans:
10: Marketing
20: Voice
30: Management
ospf:
- network: 10.0.1.0 0.0.0.255
area: 0
- network: 10.0.2.0 0.0.0.255
area: 2
- network: 10.1.1.0 0.0.0.255
area: 0
399
if/elif/else
hostname R3
interface Loopback0
ip address 10.0.0.3 255.255.255.255
vlan 10
name Marketing
vlan 20
name Voice
vlan 30
name Management
router ospf 1
router-id 10.0.0.3
auto-cost reference-bandwidth 10000
network 10.0.1.0 0.0.0.255 area 0
network 10.0.2.0 0.0.0.255 area 2
network 10.1.1.0 0.0.0.255 area 0
trunks:
Fa0/1:
action: add
vlans: 10,20
Fa0/2:
action: only
vlans: 10,30
Fa0/3:
action: delete
vlans: 10
400
if/elif/else
vlans:
10: Marketing
20: Voice
30: Management
Результат выполнения:
401
if/elif/else
402
Фильтры
Фильтры
В Jinja переменные можно изменять с помощью фильтров. Фильтры отделяются от
переменной вертикальной чертой (pipe | ) и могут содержать дополнительные
аргументы.
Кроме того, к переменной могут быть применены несколько фильтров. В таком случае,
фильтры просто пишутся последовательно, и каждый из них отделен вертикальной
чертой.
default
Фильтр default позволяет указать для переменной значение по умолчанию. Если
переменная определена, будет выводиться переменная, если переменная не
определена, будет выводиться значение, которое указано в фильтре default.
router ospf 1
auto-cost reference-bandwidth {{ ref_bw | default(10000) }}
{% for networks in ospf %}
network {{ networks.network }} area {{ networks.area }}
{% endfor %}
ospf:
- network: 10.0.1.0 0.0.0.255
area: 0
- network: 10.0.2.0 0.0.0.255
area: 2
- network: 10.1.1.0 0.0.0.255
area: 0
403
Фильтры
Результат выполнения:
Если нужно сделать так, чтобы значение по умолчанию подставлялось и в том случае,
когда переменная пустая (то есть, обрабатывается как False в Python), надо указать
дополнительный параметр boolean=true .
ref_bw: ''
ospf:
- network: 10.0.1.0 0.0.0.255
area: 0
- network: 10.0.2.0 0.0.0.255
area: 2
- network: 10.1.1.0 0.0.0.255
area: 0
Если же, при таком же файле данных, изменить шаблон таким образом:
router ospf 1
auto-cost reference-bandwidth {{ ref_bw | default(10000, boolean=true) }}
{% for networks in ospf %}
network {{ networks.network }} area {{ networks.area }}
{% endfor %}
404
Фильтры
dictsort
Фильтр dictsort позволяет сортировать словарь. По умолчанию, сортировка
выполняется по ключам. Но, изменив параметры фильтра, можно выполнять
сортировку по значениям.
Синтаксис фильтра:
405
Фильтры
trunks:
Fa0/1:
action: add
vlans: 10,20
Fa0/2:
action: only
vlans: 10,30
Fa0/3:
action: delete
vlans: 10
join
Фильтр join работает так же, как и метод join в Python.
406
Фильтры
trunks:
Fa0/1:
action: add
vlans:
- 10
- 20
Fa0/2:
action: only
vlans:
- 10
- 30
Fa0/3:
action: delete
vlans:
- 10
Результат выполнения:
407
Тесты
Тесты
Кроме фильтров, Jinja также поддерживает тесты. Тесты позволяют проверять
переменные на какое-то условие.
defined
Тест defined позволяет проверить есть ли переменная в словаре данных.
router ospf 1
{% if ref_bw is defined %}
auto-cost reference-bandwidth {{ ref_bw }}
{% else %}
auto-cost reference-bandwidth 10000
{% endif %}
{% for networks in ospf %}
network {{ networks.network }} area {{ networks.area }}
{% endfor %}
ospf:
- network: 10.0.1.0 0.0.0.255
area: 0
- network: 10.0.2.0 0.0.0.255
area: 2
- network: 10.1.1.0 0.0.0.255
area: 0
Результат выполнения:
408
Тесты
iterable
Тест iterable проверяет является ли объект итератором.
409
Тесты
trunks:
Fa0/1:
action: add
vlans:
- 10
- 20
Fa0/2:
action: only
vlans:
- 10
- 30
Fa0/3:
action: delete
vlans: 10
else.
Результат выполнения:
410
Присваивание (set)
set
Внутри шаблона можно присваивать значения переменным. Это могут быть новые
переменные, а могут быть измененные значения переменных, которые были переданы
шаблону.
interface {{ intf }}
{% if vlans is iterable %}
{% if action == 'add' %}
switchport trunk allowed vlan add {{ vlans | join(',') }}
{% elif action == 'delete' %}
switchport trunk allowed vlan remove {{ vlans | join(',') }}
{% else %}
switchport trunk allowed vlan {{ vlans | join(',') }}
{% endif %}
{% else %}
{% if action == 'add' %}
switchport trunk allowed vlan add {{ vlans }}
{% elif action == 'delete' %}
switchport trunk allowed vlan remove {{ vlans }}
{% else %}
switchport trunk allowed vlan {{ vlans }}
{% endif %}
{% endif %}
{% endfor %}
Таким образом созданы новые переменные и дальше используются уже эти новые
значения. Так шаблон выглядит понятней.
411
Присваивание (set)
trunks:
Fa0/1:
action: add
vlans:
- 10
- 20
Fa0/2:
action: only
vlans:
- 10
- 30
Fa0/3:
action: delete
vlans: 10
Результат выполнения:
interface Fa0/1
switchport trunk allowed vlan add 10,20
interface Fa0/2
switchport trunk allowed vlan 10,30
interface Fa0/3
switchport trunk allowed vlan remove 10
412
Include
include
Выражение include позволяет добавить один шаблон в другой.
Переменные, которые передаются как данные, должны содержать все данные и для
основного шаблона, и для того, который добавлен через include.
Шаблон templates/vlans.txt:
Шаблон templates/ospf.txt:
router ospf 1
auto-cost reference-bandwidth 10000
{% for networks in ospf %}
network {{ networks.network }} area {{ networks.area }}
{% endfor %}
Шаблон templates/bgp.txt:
{% include 'vlans.txt' %}
{% include 'ospf.txt' %}
413
Include
vlans:
10: Marketing
20: Voice
30: Management
ospf:
- network: 10.0.1.0 0.0.0.255
area: 0
- network: 10.0.2.0 0.0.0.255
area: 2
- network: 10.1.1.0 0.0.0.255
area: 0
router ospf 1
auto-cost reference-bandwidth 10000
network 10.0.1.0 0.0.0.255 area 0
network 10.0.2.0 0.0.0.255 area 2
network 10.1.1.0 0.0.0.255 area 0
Шаблон templates/router.txt:
{% include 'ospf.txt' %}
{% include 'bgp.txt' %}
logging {{ log_server }}
В данном случае, кроме include, добавлена ещё одна строка в шаблон, чтобы
показать, что выражения include могут идти вперемешку с обычным шаблоном.
414
Include
ospf:
- network: 10.0.1.0 0.0.0.255
area: 0
- network: 10.0.2.0 0.0.0.255
area: 2
- network: 10.1.1.0 0.0.0.255
area: 0
bgp:
local_as: 100
loopback: lo100
ibgp_neighbors:
- 10.0.0.2
- 10.0.0.3
ebgp_neighbors:
90.1.1.1: 500
80.1.1.1: 600
log_server: 10.1.1.1
logging 10.1.1.1
415
Наследование шаблонов
Наследование шаблонов
Наследование шаблонов это очень мощный функционал, который позволяет избежать
повторения одного и того же в разных шаблонах.
416
Наследование шаблонов
!
{% block services %}
service timestamps debug datetime msec localtime show-timezone year
service timestamps log datetime msec localtime show-timezone year
service password-encryption
service sequence-numbers
{% endblock %}
!
no ip domain lookup
!
ip ssh version 2
!
{% block ospf %}
router ospf 1
auto-cost reference-bandwidth 10000
{% endblock %}
!
{% block bgp %}
{% endblock %}
!
{% block alias %}
{% endblock %}
!
line con 0
logging synchronous
history size 100
line vty 0 4
logging synchronous
history size 100
transport input ssh
!
417
Наследование шаблонов
{% block services %}
service timestamps debug datetime msec localtime show-timezone year
service timestamps log datetime msec localtime show-timezone year
service password-encryption
service sequence-numbers
{% endblock %}
!
{% block ospf %}
router ospf 1
auto-cost reference-bandwidth 10000
{% endblock %}
!
{% block bgp %}
{% endblock %}
!
{% block alias %}
{% endblock %}
{% extends "base_router.txt" %}
{% block ospf %}
{{ super() }}
{% for networks in ospf %}
network {{ networks.network }} area {{ networks.area }}
{% endfor %}
{% endblock %}
{% block alias %}
alias configure sh do sh
alias exec ospf sh run | s ^router ospf
alias exec bri show ip int bri | exc unass
alias exec id show int desc
alias exec top sh proc cpu sorted | excl 0.00%__0.00%__0.00%
alias exec c conf t
alias exec diff sh archive config differences nvram:startup-config system:running-conf
ig
alias exec desc sh int desc | ex down
{% endblock %}
{% extends "base_router.txt" %}
418
Наследование шаблонов
Именно она говорит о том, что шаблон hq_router.txt будет построен на основе шаблона
base_router.txt.
Внутри дочернего шаблона всё происходит внутри блоков. Засчет блоков, которые
были определены в базовом шаблоне, дочерний шаблон может расширять
родительский шаблон.
В базовом шаблоне четыре блока: services, ospf, bgp, alias. В дочернем шаблоне
заполнены только два из них: ospf и alias.
При этом, блоки ospf и alias используются по-разному. В базовом шаблоне, в блоке
ospf уже была часть конфигурации:
{% block ospf %}
router ospf 1
auto-cost reference-bandwidth 10000
{% endblock %}
{% block ospf %}
{{ super() }}
{% for networks in ospf %}
network {{ networks.network }} area {{ networks.area }}
{% endfor %}
{% endblock %}
419
Наследование шаблонов
В блоке alias просто описаны нужные alias. И, даже если бы в родительском шаблоне
были какие-то настройки, они были бы затерты содержимым дочернего шаблона.
ospf:
- network: 10.0.1.0 0.0.0.255
area: 0
- network: 10.0.2.0 0.0.0.255
area: 2
- network: 10.1.1.0 0.0.0.255
area: 0
420
Наследование шаблонов
Обратите внимание, что в блоке ospf есть и команды из базового шаблона, и команды
из дочернего шаблона.
421
Задания
Задания
Все задания и вспомогательные файлы можно скачать одним архивом zip или tar.gz.
Также для курса подготовлены две виртуальные машины на выбор: Vagrant или
VMware. В них установлены все пакеты, которые используются в курсе.
Например, если в разделе есть задания: 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала,
можно выполнить задания 5.1, 5.2, 5.3. А затем 5.2a, 5.2b, 5.3a.
Задание 13.1
Переделать скрипт cfg_gen.py в функцию generate_cfg_from_template.
путь к шаблону
файл с переменными в формате YAML
422
Задания
Задание 13.1a
Переделать функцию generate_cfg_from_template:
Задание 13.1b
Дополнить функцию generate_cfg_from_template из задания 13.1 или 13.1a:
trim_blocks
lstrip_blocks
Задание 13.1c
423
Задания
YAML
JSON
словарь Python
YAML - yaml_file
JSON - json_file
словарь Python - py_dict
data_files/for.yml
data_files/for.json
словаре data_dict
data_dict = {'vlans': {
10: 'Marketing',
20: 'Voice',
30: 'Management'},
'ospf': [{'network': '10.0.1.0 0.0.0.255', 'area': 0},
{'network': '10.0.2.0 0.0.0.255', 'area': 2},
{'network': '10.1.1.0 0.0.0.255', 'area': 0}],
'id': 3,
'name': 'R3'}
Задание 13.1d
Переделать функцию generate_cfg_from_template из задания 13.1, 13.1a, 13.1b или
13.1c:
424
Задания
data_files/for.yml
data_files/for.json
словаре data_dict
error_message = """
Не получилось определить формат данных.
Поддерживаются файлы с расширением .json, .yml, .yaml и словари Python
"""
data_dict = {'vlans': {
10: 'Marketing',
20: 'Voice',
30: 'Management'},
'ospf': [{'network': '10.0.1.0 0.0.0.255', 'area': 0},
{'network': '10.0.2.0 0.0.0.255', 'area': 2},
{'network': '10.1.1.0 0.0.0.255', 'area': 0}],
'id': 3,
'name': 'R3'}
Задание 13.2
На основе конфигурации config_r1.txt, создать шаблоны:
templates/cisco_base.txt
templates/alias.txt
templates/eem_int_desc.txt
425
Задания
Задание 13.3
Создайте шаблон templates/ospf.txt на основе конфигурации OSPF в файле
cisco_ospf.txt. Пример конфигурации дан, чтобы напомнить синтаксис.
False
passive-interface x
ip ospf hello-interval 1
426
Задания
Задание 13.3a
Измените шаблон templates/ospf.txt таким образом, чтобы для перечисленных
переменных были указаны значения по умолчанию, которые используются в том
случае, если переменная не задана.
Задание 13.3b
Измените шаблон templates/ospf.txt из задания 13.3a таким образом, чтобы для
перечисленных переменных были указаны значения по умолчанию, которые
используются в том случае, если переменная не задана или, если в переменной
пустое значение.
Задание 13.4
Создайте шаблон templates/add_vlan_to_switch.txt, который будет использоваться при
необходимости добавить VLAN на коммутатор.
427
Задания
Если VLAN необходимо добавить как access, то надо настроить и режим интерфейса и
добавить его в VLAN:
interface Gi0/1
switchport mode access
switchport access vlan 5
interface Gi0/10
switchport trunk allowed vlan add 5
428
14. TextFSM. Обработка вывода команд
TextFSM это библиотека созданная Google для обработки вывода с сетевых устройств.
Она позволяет создавать шаблоны, по которым будет обрабатываться вывод команды.
429
14. TextFSM. Обработка вывода команд
Value ID (\d+)
Value Hop (\d+(\.\d+){3})
Start
^ ${ID} ${Hop} -> Record
После строки Start начинается сам шаблон. В данном случае, он очень простой:
import textfsm
traceroute = """
r2#traceroute 90.0.0.9 source 33.0.0.2
traceroute 90.0.0.9 source 33.0.0.2
Type escape sequence to abort.
Tracing the route to 90.0.0.9
VRF info: (vrf in name/id, vrf out name/id)
1 10.0.12.1 1 msec 0 msec 0 msec
2 15.0.0.5 0 msec 5 msec 4 msec
3 57.0.0.7 4 msec 1 msec 4 msec
4 79.0.0.9 4 msec * 1 msec
"""
template = open('traceroute.textfsm')
fsm = textfsm.TextFSM(template)
result = fsm.ParseText(traceroute)
print fsm.header
print result
430
14. TextFSM. Обработка вывода команд
$ python parse_traceroute.py
['ID', 'Hop']
[['1', '10.0.12.1'], ['2', '15.0.0.5'], ['3', '57.0.0.7'], ['4', '79.0.0.9']]
431
Синтаксис шаблонов TextFSM
определения переменных
эти переменные описывают какие столбцы будут в табличном представлении
определения состояний
# Определение переменных:
Value ID (\d+)
Value Hop (\d+(\.\d+){3})
Определение переменных
В секции с переменными должны идти только определения переменных.
Единственное исключение - в этом разделе могут быть комментарии.
В этом разделе не должно быть пустых строк. Для TextFSM пустая строка означает
завершение секции определения переменных.
Value - это ключевое слово, которое указывает, что создается переменная. Его
432
Синтаксис шаблонов TextFSM
Определение состояний
После определения переменных, нужно описать состояния:
Зарезервированные состояния
Зарезервированы такие состояния:
433
Синтаксис шаблонов TextFSM
Start - это состояние обязательно должно быть указано. Без него шаблон не будет
работать.
End - это состояние завершает обработку входящих строк и не выполняет
состояние EOF.
EOF - это неявное состояние, которое выполняется всегда, когда обработка дошла
до конца файла. Выглядит оно таким образом:
EOF
^.* -> Record
EOF записывает текущую строку, прежде чем обработка завершается. Если это
поведение нужно изменить, надо явно, в конце шаблона, написать EOF:
EOF
Правила состояний
Каждое состояние состоит из одного или более правил:
В правиле:
Действия в правилах
434
Синтаксис шаблонов TextFSM
Line Actions
Line Actions:
Record Action
Record Action - опциональное действие, которое может быть указано после Line
Action. Они должны быть разделены точкой. Типы действий:
Разделять действия точкой нужно только в том случае, если нужно указать и Line
и Record действия. Если нужно указать только одно из них, точку ставить не
нужно.
State Transition
После действия, может быть указано новое состояние:
435
Синтаксис шаблонов TextFSM
Error Action
Специальное действие Error останавливает всю обработку строк, отбрасывает все
строки, которые были собраны до сих пор и возвращает исключение.
436
Примеры использования TextFSM
import sys
import textfsm
from tabulate import tabulate
template = sys.argv[1]
output_file = sys.argv[2]
f = open(template)
output = open(output_file).read()
re_table = textfsm.TextFSM(f)
header = re_table.header
result = re_table.ParseText(output)
show clock
Первый пример - разбор вывода команды sh clock (файл output/sh_clock.txt):
437
Примеры использования TextFSM
Подсказка по спецсимволам:
. - любой символ
\d - любая цифра
Start
^${Time}.* ${Timezone} ${WeekDay} ${Month} ${MonthDay} ${Year} -> Record
438
Примеры использования TextFSM
Так как, в данном случае, в выводе всего одна строка, можно не писать в шаблоне
действие Record. Но лучше его использовать в ситуациях, когда надо записать
значения, чтобы привыкать к этому синтаксису и не ошибиться, когда нужна
обработка нескольких строк.
Когда это регулярное выражение применяется в выводу show clock, в каждой группе
регулярного выражения, будет находиться соответствующее значение:
1 группа: 15:10:44
2 группа: UTC
3 группа: Sun
4 группа: Nov
5 группа: 13
6 группа: 2016
В правиле, кроме явного действия Record, которое указывает, что запись надо
поместить в финальную таблицу, по умолчанию также используется правило Next. Оно
указывает, что надо перейти к следующей строке текста. Так как в выводе команды sh
clock, только одна строка, обработка завершается.
Особенность этой команды в том, что данные находятся не в одной строке, а в разных.
439
Примеры использования TextFSM
Version :
Cisco IOS Software, C2960 Software (C2960-LANBASEK9-M), Version 12.2(55)SE9, RELEASE S
OFTWARE (fc1)
Technical Support: http://www.cisco.com/techsupport
Copyright (c) 1986-2014 by Cisco Systems, Inc.
Compiled Mon 03-Mar-14 22:53 by prod_rel_team
advertisement version: 2
VTP Management Domain: ''
Native VLAN: 1
Duplex: full
Management address(es):
IP address: 10.1.1.2
-------------------------
Device ID: R1
Entry address(es):
IP address: 10.1.1.1
Platform: Cisco 3825, Capabilities: Router Switch IGMP
Interface: GigabitEthernet1/0/22, Port ID (outgoing port): GigabitEthernet0/0
Holdtime : 156 sec
Version :
Cisco IOS Software, 3800 Software (C3825-ADVENTERPRISEK9-M), Version 12.4(24)T1, RELEA
SE SOFTWARE (fc3)
Technical Support: http://www.cisco.com/techsupport
Copyright (c) 1986-2009 by Cisco Systems, Inc.
Compiled Fri 19-Jun-09 18:40 by prod_rel_team
advertisement version: 2
VTP Management Domain: ''
Duplex: full
Management address(es):
-------------------------
Device ID: R2
Entry address(es):
IP address: 10.2.2.2
Platform: Cisco 2911, Capabilities: Router Switch IGMP
Interface: GigabitEthernet1/0/21, Port ID (outgoing port): GigabitEthernet0/0
Holdtime : 156 sec
Version :
Cisco IOS Software, 2900 Software (C3825-ADVENTERPRISEK9-M), Version 15.2(2)T1, RELEAS
E SOFTWARE (fc3)
Technical Support: http://www.cisco.com/techsupport
Copyright (c) 1986-2009 by Cisco Systems, Inc.
Compiled Fri 19-Jun-09 18:40 by prod_rel_team
440
Примеры использования TextFSM
advertisement version: 2
VTP Management Domain: ''
Duplex: full
Management address(es):
Start
^${LOCAL_HOST}[>#].
^Device ID: ${DEST_HOST}
^.*IP address: ${MGMNT_IP}
^Platform: ${PLATFORM},
^Interface: ${LOCAL_PORT}, Port ID \(outgoing port\): ${REMOTE_PORT}
^.*Version ${IOS_VERSION},
441
Примеры использования TextFSM
итоговой таблицы.
Record
Так получилось из-за того, что в шаблоне не указано действие Record. И в итоге, в
финальной таблице осталась только последняя строка.
Исправленый шаблон:
Start
^${LOCAL_HOST}[>#].
^Device ID: ${DEST_HOST}
^.*IP address: ${MGMNT_IP}
^Platform: ${PLATFORM},
^Interface: ${LOCAL_PORT}, Port ID \(outgoing port\): ${REMOTE_PORT}
^.*Version ${IOS_VERSION}, -> Record
Filldown
442
Примеры использования TextFSM
Start
^${LOCAL_HOST}[>#].
^Device ID: ${DEST_HOST}
^.*IP address: ${MGMNT_IP}
^Platform: ${PLATFORM},
^Interface: ${LOCAL_PORT}, Port ID \(outgoing port\): ${REMOTE_PORT}
^.*Version ${IOS_VERSION}, -> Record
Required
Дело в том, что все переменные, которые мы определили, опциональны. К тому же,
одна переменная с параметром Filldown. И, чтобы избавиться от последней строки,
нужно сделать хотя бы одну переменную обязательной, с помощью параметра
Required:
443
Примеры использования TextFSM
Start
^${LOCAL_HOST}[>#].
^Device ID: ${DEST_HOST}
^.*IP address: ${MGMNT_IP}
^Platform: ${PLATFORM},
^Interface: ${LOCAL_PORT}, Port ID \(outgoing port\): ${REMOTE_PORT}
^.*Version ${IOS_VERSION}, -> Record
Start
^${INTF}\s+${ADDR}\s+\w+\s+\w+\s+${STATUS}\s+${PROTO} -> Record
444
Примеры использования TextFSM
Для маршрутов к одной и той же сети, вместо нескольких строк, где будет повторяться
сеть, будет создана одна запись, в которой все доступные next-hop адреса собраны в
список.
445
Примеры использования TextFSM
Для этого примера упрощаем задачу и считаем, что маршруты могут быть только
OSPF и с обозначением, только O (то есть, только внутризональные маршруты).
Start
^O +${Network}${Mask}\s\[${Distance}\/${Metric}\]\svia\s${NextHop}, -> Record
446
Примеры использования TextFSM
Всё нормально, но потерялись варианты путей для маршрута 10.4.4.4/32. Это логично,
ведь нет правила, которое подошло бы для такой строки.
List
Воспользуемся опцией List для переменной NextHop:
Start
^O +${Network}${Mask}\s\[${Distance}\/${Metric}\]\svia\s${NextHop}, -> Record
Так как, перед записью маршрута, для которого есть несколько путей, надо добавить к
нему все доступные адреса NextHop, надо перенести действие Record.
^O -> Continue.Record
В ней действие Record говорит, что надо записать текущее значение переменных. А,
так как в этом правиле нет переменных, записывается то, что было в предыдущих
значениях.
Действие Continue говорит, что надо продолжить работать с текущей строкой так, как
будто совпадения не было. Засчет этого, сработает следующая строка.
447
Примеры использования TextFSM
^\s+\[${Distance}\/${Metric}\]\svia\s${NextHop},
Start
^O -> Continue.Record
^O +${Network}${Mask}\s\[${Distance}\/${Metric}\]\svia\s${NextHop},
^\s+\[${Distance}\/${Metric}\]\svia\s${NextHop},
448
Примеры использования TextFSM
Сложность тут в том, что порты находятся в одной строке, а в TextFSM нельзя
указывать одну и ту же переменную несколько раз в строке. Но, есть возможность
несколько раз искать совпадение в строке.
Start
^\d+ +${CHANNEL}\(\S+ +[\w-]+ +[\w ]+ +${MEMBERS}\( -> Record
Результат:
449
Примеры использования TextFSM
CHANNEL MEMBERS
--------- ----------
Po1 ['Fa0/1']
Po3 ['Fa0/11']
Пока что в выводе только первый порт, а нужно чтобы попали все порты. В данном
случае, надо продолжить обработку строки с портами, после найденного совпадения.
То есть, использовать действие Continue и описать следующее выражение.
Единственная строка, которая есть в шаблоне, описывает первый порт. Надо добавить
строку, которая описывает следующий порт.
Start
^\d+ +${CHANNEL}\(\S+ +[\w-]+ +[\w ]+ +${MEMBERS}\( -> Continue
^\d+ +${CHANNEL}\(\S+ +[\w-]+ +[\w ]+ +\S+ +${MEMBERS}\( -> Record
Результат:
CHANNEL MEMBERS
--------- --------------------
Po1 ['Fa0/1', 'Fa0/2']
Po3 ['Fa0/11', 'Fa0/12']
450
Примеры использования TextFSM
Start
^\d+.* -> Continue.Record
^\d+ +${CHANNEL}\(\S+ +[\w-]+ +[\w ]+ +\S+ +${MEMBERS}\( -> Continue
^\d+ +${CHANNEL}\(\S+ +[\w-]+ +[\w ]+ +(\S+ +){2} +${MEMBERS}\( -> Continue
^\d+ +${CHANNEL}\(\S+ +[\w-]+ +[\w ]+ +(\S+ +){3} +${MEMBERS}\( -> Continue
Результат обработки:
CHANNEL MEMBERS
--------- ----------------------------------------
Po1 ['Fa0/1', 'Fa0/2', 'Fa0/3']
Po3 ['Fa0/11', 'Fa0/12', 'Fa0/13', 'Fa0/14']
Шаблон предполагает, что в одной строке будет максимум четыре порта. Если
портов может быть больше, надо добавить соответствующие строки в шаблон.
451
Примеры использования TextFSM
Для того чтобы шаблон обрабатывал и этот вариант, надо его модифицировать (файл
templates/sh_etherchannel_summary2.txt):
Start
^\d+.* -> Continue.Record
^\d+ +${CHANNEL}\(\S+ +[\w-]+ +[\w ]+ +${MEMBERS}\( -> Continue
^\d+ +${CHANNEL}\(\S+ +[\w-]+ +[\w ]+ +\S+ +${MEMBERS}\( -> Continue
^\d+ +${CHANNEL}\(\S+ +[\w-]+ +[\w ]+ +(\S+ +){2} +${MEMBERS}\( -> Continue
^\d+ +${CHANNEL}\(\S+ +[\w-]+ +[\w ]+ +(\S+ +){3} +${MEMBERS}\( -> Continue
^ +${MEMBERS} -> Continue
^ +\S+ +${MEMBERS} -> Continue
^ +(\S+ +){2} +${MEMBERS} -> Continue
^ +(\S+ +){3} +${MEMBERS} -> Continue
CHANNEL MEMBERS
--------- ------------------------------------------------------------
Po1 ['Fa0/1', 'Fa0/2', 'Fa0/3']
Po3 ['Fa0/11', 'Fa0/12', 'Fa0/13', 'Fa0/14', 'Fa0/15', 'Fa0/16']
452
CLI Table
Для того, чтобы ей можно было воспользоваться, надо создать файл в котором
описаны соответствия между командами и шаблонами. В TextFSM он называется
index.
Этот файл должен находиться в каталоге с шаблонами и должен иметь такой формат:
453
CLI Table
sh_cdp_n_det.template
sh_clock.template
sh_ip_int_br.template
sh_ip_route_ospf.template
index
Если при работе с clitable возникнет ошибка, что нет модуля terminal, посмотрите в
каталоге, в котором находится установленный TextFSM (в зависимости от ОС),
файл terminal.py. Если его нет, просто создайте его вручную и скопируйте
содержимое этого файла из репозитория TextFSM. Определить, где находится
модуль, можно таким образом: print textfsm.__file__
454
CLI Table
Сначала надо инициализировать класс, передав ему имя файла, в котором хранится
соответствие между шаблонами и командами, и указать имя каталога, в котором
хранятся шаблоны:
455
CLI Table
In [6]: cli_table.
cli_table.AddColumn cli_table.NewRow cli_table.index cli_t
able.size
cli_table.AddKeys cli_table.ParseCmd cli_table.index_file cli_t
able.sort
cli_table.Append cli_table.ReadIndex cli_table.next cli_t
able.superkey
cli_table.CsvToTable cli_table.Remove cli_table.raw cli_t
able.synchronised
cli_table.FormattedTable cli_table.Reset cli_table.row cli_t
able.table
cli_table.INDEX cli_table.RowWith cli_table.row_class cli_t
able.template_dir
cli_table.KeyValue cli_table.extend cli_table.row_index
cli_table.LabelValueTable cli_table.header cli_table.separator
456
CLI Table
In [9]: data_rows = []
In [11]: data_rows
Out[11]:
[['10.0.24.0', '/24', '110', '20', ['10.0.12.2']],
['10.0.34.0', '/24', '110', '20', ['10.0.13.3']],
['10.2.2.2', '/32', '110', '11', ['10.0.12.2']],
['10.3.3.3', '/32', '110', '11', ['10.0.13.3']],
['10.4.4.4', '/32', '110', '21', ['10.0.13.3', '10.0.12.2', '10.0.14.4']],
['10.5.35.0', '/24', '110', '20', ['10.0.13.3']]]
In [12]: cli_table.header.viewvalues()
Out[12]: dict_values([])
In [13]: header = []
In [14]: header
Out[14]: ['Network', 'Mask', 'Distance', 'Metric', 'NextHop']
457
CLI Table
output_sh_ip_route_ospf = open('output/sh_ip_route_ospf.txt').read()
cli_table = clitable.CliTable('index', 'templates')
attributes = {'Command': 'show ip route ospf' , 'Vendor': 'Cisco'}
cli_table.ParseCmd(output_sh_ip_route_ospf, attributes)
data_rows = []
header = []
for name in cli_table.header:
header.append(name)
print header
for row in data_rows:
print row
458
CLI Table
$ python textfsm_clitable.py
CLI Table output:
Network, Mask, Distance, Metric, NextHop
10.0.24.0, /24, 110, 20, ['10.0.12.2']
10.0.34.0, /24, 110, 20, ['10.0.13.3']
10.2.2.2, /32, 110, 11, ['10.0.12.2']
10.3.3.3, /32, 110, 11, ['10.0.13.3']
10.4.4.4, /32, 110, 21, ['10.0.13.3', '10.0.12.2', '10.0.14.4']
10.5.35.0, /24, 110, 20, ['10.0.13.3']
Formatted Table:
Network Mask Distance Metric NextHop
====================================================================
10.0.24.0 /24 110 20 10.0.12.2
10.0.34.0 /24 110 20 10.0.13.3
10.2.2.2 /32 110 11 10.0.12.2
10.3.3.3 /32 110 11 10.0.13.3
10.4.4.4 /32 110 21 10.0.13.3, 10.0.12.2, 10.0.14.4
10.5.35.0 /24 110 20 10.0.13.3
459
Задания
Задания
Все задания и вспомогательные файлы можно скачать одним архивом zip или tar.gz.
Также для курса подготовлены две виртуальные машины на выбор: Vagrant или
VMware. В них установлены все пакеты, которые используются в курсе.
Например, если в разделе есть задания: 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала,
можно выполнить задания 5.1, 5.2, 5.3. А затем 5.2a, 5.2b, 5.3a.
Задание 14.1
Переделать пример, который использовался в разделе TextFSM, в функцию.
template - шаблон TextFSM (это должно быть имя файла, в котором находится
шаблон)
output - вывод соответствующей команды show (строка)
Пример из раздела:
460
Задания
import sys
import textfsm
from tabulate import tabulate
template = sys.argv[1]
output_file = sys.argv[2]
f = open(template)
output = open(output_file).read()
re_table = textfsm.TextFSM(f)
header = re_table.header
result = re_table.ParseText(output)
Задание 14.1a
Переделать функцию parse_output из задания 14.1 таким образом, чтобы, вместо
списка списков, она возвращала один список словарей:
Задание 14.2
В этом задании нужно использовать функцию parse_output из задания 14.1. Она
используется для того, чтобы получить структурированный вывод в результате
обработки вывода команды.
Для записи вывода в CSV, нужно создать функцию list_to_csv, которая ожидает как
аргументы:
список:
первый элемент - это список с названиями заголовков
остальные элементы это списки, в котором находятся результаты обработки
вывода
имя файла, в который нужно записать данные в CSV формате
461
Задания
Задание 14.3
Сделать шаблон TextFSM для обработки вывода sh ip dhcp snooping binding. Вывод
команды находится в файле output/sh_ip_dhcp_snooping.txt.
MacAddress
IpAddress
VLAN
Interface
Задание 14.4
На основе примера из раздела clitable сделать функцию parse_command_dynamic.
Параметры функции:
Пример из раздела:
462
Задания
output_sh_ip_route_ospf = open('output/sh_ip_route_ospf.txt').read()
cli_table = clitable.CliTable('index', 'templates')
attributes = {'Command': 'show ip route ospf' , 'Vendor': 'Cisco'}
cli_table.ParseCmd(output_sh_ip_route_ospf, attributes)
data_rows = []
header = []
for name in cli_table.header:
header.append(name)
print header
for row in data_rows:
print row
Задание 14.4a
Переделать функцию из задания 14.4:
Задание 14.5
В этом задании соединяется функциональность TextFSM и подключение к
оборудованию.
Задача такая:
подключиться к оборудованию
выполнить команду show
полученный вывод передавать на обработку TextFSM
вернуть результат обработки
463
Задания
Задание 14.6
Это задание похоже на задание 14.5, но в этом задании подключения надо выполнять
параллельно.
464
Задания
465
Задания
import multiprocessing
from netmiko import ConnectHandler
import sys
import yaml
COMMAND = sys.argv[1]
devices = yaml.load(open('devices.yaml'))
for p in processes:
p.join()
results = []
for p in processes:
results.append(queue.get())
return results
466
15. Ansible
Ansible
Ansible - это система управления конфигурациями. Ansible позволяет
автоматизировать и упростить настройку, обслуживание и развертывание серверов,
служб, ПО и др.
Установка Ansible
467
15. Ansible
Пока что, лучше поставить Ansible с явным указанием версии, так как в версии
2.2.1.0 были внесены изменения из-за которых часть функционала работает с
ошибками. Скорее всего, это скоро исправят, но при моей последней проверке
(05.02.2017), были проблемы.
Параметры оборудования
В примерах раздела используются три маршрутизатора и один коммутатор. К ним нет
никаких требований, только настроенный SSH.
пользователь: cisco
пароль: cisco
пароль на режим enable: cisco
SSH версии 2
IP-адреса:
R1: 192.168.100.1
R2: 192.168.100.2
R3: 192.168.100.3
SW1: 192.168.100.100
468
15. Ansible
469
Основы Ansible
Основы Ansible
Ansible:
Терминология
Control machine — управляющий хост. Сервер Ansible, с которого происходит
управление другими хостами
Manage node — управляемые хосты
Inventory — инвентарный файл. В этом файле описываются хосты, группы хостов.
А также могут быть созданы переменные
Playbook — файл сценариев
Play — сценарий (набор задач). Связывает задачи с хостами, для которых эти
задачи надо выполнить
Task — задача. Вызывает модуль с указанными параметрами и переменными
Module — модуль Ansible. Реализует определенные функции
Quick start
С Ansible очень просто начать работать. Минимум, который нужен для начала работы:
470
Основы Ansible
471
Инвентарный файл
Инвентарный файл
Инвентарный файл - это файл, в котором описываются устройства, к которым Ansible
будет подключаться.
Хосты и группы
В инвентарном файле устройства могут указываться используя IP-адреса или имена.
Устройства могут быть указаны по одному или разбиты на группы.
r5.example.com
[cisco-routers]
192.168.255.1
192.168.255.2
192.168.255.3
192.168.255.4
[cisco-edge-routers]
192.168.255.1
192.168.255.2
Таким образом можно применять отдельно какие-то политики для группы cisco-edge-
routers, но в то же время, когда необходимо настроить что-то, что касается всех
маршрутизаторов, можно использовать группу cisco-routers.
Но можно создавать свой инвентарный файл и использовать его. Для этого нужно,
либо указать его при запуске ansible, используя опцию -i <путь> , либо указать файл в
конфигурационном файле Ansible.
Если какое-то из устройств использует нестандартный порт SSH, порт можно указать
после имени или адреса устройства, через двоеточие (ниже показан пример).
472
Инвентарный файл
[cisco-routers]
192.168.255.1:22022
192.168.255.2:22022
192.168.255.3:22022
[cisco-switches]
192.168.254.1
192.168.254.2
[cisco-routers]
192.168.255.[1-5]
Группа из групп
Ansible также позволяет объединять группы устройств в общую группу. Для этого
используется специальный синтаксис:
[cisco-routers]
192.168.255.1
192.168.255.2
192.168.255.3
[cisco-switches]
192.168.254.1
192.168.254.2
[cisco-devices:children]
cisco-routers
cisco-switches
473
Ad-Hoc команды
Ad Hoc команды
Ad-hoc команды - это возможность запустить какое-то действие Ansible из командной
строки.
Такой вариант используется, как правило, в тех случаях, когда надо что-то проверить,
например, работу модуля. Или просто выполнить какое-то разовое действие, которое
не нужно сохранять.
[cisco-routers]
192.168.100.1
192.168.100.2
192.168.100.3
[cisco-switches]
192.168.100.100
474
Ad-Hoc команды
-m raw -a "sh ip int br" - параметр -m raw означает, что используется модуль
raw
этот модуль позволяет отправлять команды в SSH сессии, но при этом не
загружает на хост модуль Python. То есть, этот модуль просто отправляет
указанную команду как строку и всё
плюс модуля raw в том, что он может использоваться для любой системы,
которую поддерживает Ansible
-a "sh ip int br" - параметр -a указывает какую команду отправить
паролю, а не по ключам
Ошибка значит, что нужно установить программу sshpass. Эта особенность возникает
только когда используется аутентификацию по паролю.
Установка sshpass:
475
Ad-Hoc команды
476
Конфигурационный файл
Конфигурационный файл
Настройки Ansible можно менять в конфигурационном файле.
[cisco-routers]
192.168.100.1
192.168.100.2
192.168.100.3
[cisco-switches]
192.168.100.100
[defaults]
inventory = ./myhosts
remote_user = cisco
ask_pass = True
инвентарного файла.
Если настроить этот параметр, не придется указывать, где находится файл,
при каждом запуске Ansible
477
Конфигурационный файл
gathering
По умолчанию, Ansible собирает факты об устройствах.
Факты - это информация о хостах, к которым подключается Ansible. Эти факты можно
использовать в playbook и шаблонах как переменные.
Но, для сетевого оборудования, модуль setup не подходит, поэтому сбор фактов надо
отключить. Это можно сделать в конфигурационном файле Ansible или в playbook.
gathering = explicit
host_key_checking
Параметр host_key_checking отвечает за проверкy ключей, при подключении по SSH.
Если указать в конфигурационном файле host_key_checking=False , проверка будет
отключена.
Чтобы проверить этот функционал, надо удалить сохраненные ключи для устройств
Cisco, к которым уже выполнялось подкление.
Если выполнить ad-hoc команду, после удаления ключей, вывод будет таким:
478
Конфигурационный файл
[defaults]
inventory = ./myhosts
remote_user = cisco
ask_pass = True
host_key_checking=False
479
Конфигурационный файл
480
Конфигурационный файл
481
Модули
Модули Ansible
Вместе с установкой Ansible устанавливается также большое количество модулей
(библиотека модулей). В текущей библиотеке модулей, находится порядка 200
модулей.
Модули отвечают за действия, которые выполняет Ansible. При этом, каждый модуль,
как правило, отвечает за свою конкретную и небольшую задачу.
Как правило, при вызове модуля, ему нужно передать аргументы. Какие-то аргументы
будут управлять поведением и параметрами модуля, а какие-то передавать, например,
команду, которую надо выполнить.
Модули Ansible, как правило, идемпотентны. Это означает, что модуль можно
выполнять сколько угодно раз, но при этом модуль будет выполнять изменения, только
если система не находится в желаемом состоянии.
482
Модули
483
Основы playbook
Основы playbooks
Playbook (файл сценариев) — это файл в котором описываются действия, которые
нужно выполнить на какой-то группе хостов.
Внутри playbook:
play - это набор задач, которые нужно выполнить для группы хостов
task - это конкретная задача. В задаче есть, как минимум:
описание (название задачи можно не писать, но очень рекомендуется)
модуль и команда (действие в модуле)
Синтаксис playbook
Playbook описываются в формате YAML.
484
Основы playbook
---
tasks:
tasks:
name: Run show commands on routers - имя сценария (play). Этот параметр
routers
тут может быть указано и несколько групп, например, таким образом: hosts:
cisco-routers:cisco-switches . Подробнее, в документации
обычно, в play надо указывать параметр remote_user. Но, так как мы указали его в
конфигурационном файле Ansible, можно не указывать его в play.
gather_facts: false - отключение сбора фактов об устройстве, так как для
485
Основы playbook
$ ansible-playbook 1_show_commands_with_raw.yml
486
Основы playbook
Обратите внимание, что для запуска playbook используется другая команда. Для
ad-hoc команды, использовалась команда ansible. А для playbook - ansible-
playbook.
Для того, чтобы убедиться, что команды, которые указаны в задачах, выполнились на
устройствах, запустите playbook с опцией -v (вывод сокращен):
$ ansible-playbook 1_show_commands_with_raw.yml -v
487
Основы playbook
Если в сценарии, например, две задачи, то сначала первая задача должна быть
выполнена для всех устройств, которые указаны в параметре hosts. Только после того,
как первая задача была выполнена для всех хостов, начинается выполнение второй
задачи.
$ ansible-playbook 1_show_commands_with_raw.yml
488
Основы playbook
В этом файле хранится имя или адрес устройства на котором возникла ошибка. Так
выглядит файл 1_show_commands_with_raw.retry сейчас:
192.168.100.1
Создается этот файл для того, чтобы можно было перезапустить playbook заново
только для проблемного устройства (устройств). То есть, надо исправить проблему с
устройством, и заново запустить playbook.
489
Основы playbook
Можно было запустить playbook и так (то есть, писать не полный путь к файлу retry):
Параметр --limit очень полезная вещь. Он позволяет ограничивать, для каких хостов
или групп будет выполняться playbook, при этом, не меняя сам playbook.
Идемпотентность
Модули Ansible идемпотентны. Это означает, что модуль можно выполнять сколько
угодно раз, но при этом модуль будет выполнять изменения, только если система не
находится в желаемом состоянии.
Но, есть исключения из такого поведения. Например, модуль raw всегда вносит
изменения. Поэтому в выполнении playbook выше, всегда отображалось состояние
changed.
Но, если, например, в задаче указано, что на сервер Linux надо установить пакет httpd,
то он будет установлен только в том случае, если его нет. То есть, действие не будет
повторяться снова и снова, при каждом запуске. А лишь тогда, когда пакета нет.
490
Переменные
Переменные
Переменной может быть, например:
Имена переменных
В Ansible есть определенные ограничения по формату имен переменных:
R1:
IP: 10.1.1.1/24
DG: 10.1.1.100
R1['IP']
R1.IP
Правда, при использовании второго варианта, могут быть проблемы, если название
ключа совпадает с зарезервированным словом (методом или атрибутом) в Python или
Ansible.
в инвентарном файле
в playbook
в специальных файлах для группы/устройства
в отдельных файлах, которые добавляются в playbook через include (как в Jinja2)
491
Переменные
Также можно использовать факты, которые были собраны про устройство, как
переменные.
[cisco-routers]
192.168.100.1
192.168.100.2
192.168.100.3
[cisco-switches]
192.168.100.100
[cisco-routers:vars]
ntp_server=192.168.255.100
log_server=10.255.100.1
Переменные в playbook
Переменные можно задавать прямо в playbook. Это может быть удобно тем, что
переменные находятся там же, где все действия.
492
Переменные
---
vars:
ntp_server: 192.168.255.100
log_server: 10.255.100.1
tasks:
493
Переменные
[cisco-routers]
192.168.100.1
192.168.100.2
192.168.100.3
[cisco-switches]
192.168.100.100
├── group_vars _
│ ├── all.yml |
│ ├── cisco-routers.yml | Каталог с переменными для групп устройств
│ └── cisco-switches.yml _|
|
├── host_vars _
│ ├── 192.168.100.1 |
│ ├── 192.168.100.2 |
│ ├── 192.168.100.3 | Каталог с переменными для устройств
│ └── 192.168.100.100 _|
|
└── myhosts | Инвентарный файл
Ниже пример содержимого файлов переменных для групп устройств и для отдельных
хостов.
---
cli:
host: "{{ inventory_hostname }}"
username: "cisco"
password: "cisco"
transport: cli
authorize: yes
auth_pass: "cisco"
494
Переменные
group_vars/cisco-routers.yml
---
log_server: 10.255.100.1
ntp_server: 10.255.100.1
users:
user1: pass1
user2: pass2
user3: pass3
group_vars/cisco-switches.yml
---
vlans:
- 10
- 20
- 30
Файл host_vars/192.168.100.1
---
hostname: london_r1
mgmnt_loopback: 100
mgmnt_ip: 10.0.0.1
ospf_ints:
- 192.168.100.1
- 10.0.0.1
- 10.255.1.1
495
Переменные
Файл host_vars/192.168.100.2
---
hostname: london_r2
mgmnt_loopback: 100
mgmnt_ip: 10.0.0.2
ospf_ints:
- 192.168.100.2
- 10.0.0.2
- 10.255.2.2
Файл host_vars/192.168.100.3
---
hostname: london_r3
mgmnt_loopback: 100
mgmnt_ip: 10.0.0.3
ospf_ints:
- 192.168.100.3
- 10.0.0.3
- 10.255.3.3
Файл host_vars/192.168.100.100
---
hostname: london_sw1
mgmnt_int: VLAN100
mgmnt_ip: 10.0.0.100
Приоритетность переменных
В этом разделе не рассматривается размещение переменных:
496
Переменные
Чаще всего, переменная с определенным именем только одна. Но, иногда может
понадобиться создать переменную в разных местах и тогда нужно понимать, в каком
порядке Ansible перезаписывает переменные.
497
Результат выполнения модуля
verbose
В предыдущих разделах, один из способов отобразить результат выполнения команд,
уже использовался - флаг verbose.
Конечно, вывод не очень удобно читать, но, как минимум, он позволяет увидеть, что
команды выполнились. Также этот флаг позволяет подробно посмотреть какие шаги
выполняет Ansible.
ansible-playbook 1_show_commands_with_raw.yml -v
498
Результат выполнения модуля
register
Параметр register сохраняет результат выполнения модуля в переменную. Затем эта
переменная может использоваться в шаблонах, в принятии решений о ходе сценария
или для отображения вывода.
---
tasks:
Если запустить этот playbook, вывод не будет отличаться, так как вывод только
записан в переменную, но с переменной не выполяется никаких действий. Следующий
шаг - отобразить результат выполнения команды, с помощью модуля debug.
debug
Модуль debug позволяет отображать информацию на стандартный поток вывода. Это
может быть произвольная строка, переменная, факты об устройстве.
499
Результат выполнения модуля
---
tasks:
$ ansible-playbook 2_register_vars.yml
500
Результат выполнения модуля
501
Результат выполнения модуля
---
tasks:
Выполнение playbook:
$ ansible-playbook 3_register_debug_when.yml
502
Результат выполнения модуля
---
tasks:
$ ansible-playbook 3_register_debug_when.yml
503
Результат выполнения модуля
Так как команда была с ошибкой, сработало условие, которое описано в when и задача
вывела сообщение, с помощью модуля debug.
504
Сетевые модули
Если оборудование поддерживает API, как например, NXOS, то для него создано
большое количество модулей, которые выполняют конкретные действия, по настройке
функционала (например, для NXOS создано более 60 модулей).
Для оборудования, которое работает только через CLI, Ansible поддерживает такие три
типа модулей:
ios_command
ios_config
ios_facts
Dellos10
Dellos6
Dellos9
EOS
IOS
IOS XR
JUNOS
505
Сетевые модули
SR OS
VyOS
ios_command
ios_config
ios_facts
Варианты подключения
Ansible поддерживает такие типы подключений:
paramiko
SSH - OpenSSH. Используется по умолчанию
local - действия выполняются локально, на управляющем хосте
506
Сетевые модули
gather_facts: false
connection: local
Пример:
---
[defaults]
gathering = explicit
Такой вариант подходит в том случае, когда Ansible используется больше для
подключения к сетевым устройствам (или, локальные playbook используются для
подключения к сетевому оборудованию).
В таком случае, нужно будет наоборот явно включать сбор фактов, если он нужен.
В инвентарном файле:
507
Сетевые модули
[cisco-routers]
192.168.100.1
192.168.100.2
192.168.100.3
[cisco-switches]
192.168.100.100
[cisco-routers:vars]
ansible_connection=local
---
ansible_connection: local
---
В реальной жизни нужно выбрать тот вариант, который наиболее удобен для работы.
Аргумент provider
Модули, которые используются для работы с сетевым оборудованием, требуют
задания нескольких аргументов.
508
Сетевые модули
tasks:
vars:
cli:
host: "{{ inventory_hostname }}"
username: cisco
password: cisco
transport: cli
tasks:
- name: run show version
ios_command:
commands: show version
provider: "{{ cli }}"
---
cli:
host: "{{ inventory_hostname }}"
username: cisco
password: cisco
transport: cli
authorize: yes
auth_pass: cisco
509
Сетевые модули
tasks:
- name: run show version
ios_command:
commands: show version
provider: "{{ cli }}"
значения по умолчанию
значения переменных окружения
параметр provider
аргументы задачи (task)
[cisco-routers]
192.168.100.1
192.168.100.2
192.168.100.3
[cisco-switches]
192.168.100.100
[defaults]
inventory = ./myhosts
remote_user = cisco
ask_pass = True
510
Сетевые модули
---
cli:
host: "{{ inventory_hostname }}"
username: "cisco"
password: "cisco"
transport: cli
authorize: yes
auth_pass: "cisco"
511
ios_command
Модуль ios_command
Модуль ios_command - отправляет команду show на устройство под управлением IOS
и возвращает результат выполнения команды.
---
tasks:
512
ios_command
$ ansible-playbook 1_ios_command.yml
513
ios_command
---
tasks:
В первой задаче указываются две команды, поэтому синтаксис должен быть немного
другим - команды должны быть указаны как список, в формате YAML.
$ ansible-playbook 2_ios_command.yml
514
ios_command
Обработка ошибок
515
ios_command
$ ansible-playbook 2_ios_command.yml
Ambiguous command
Incomplete command
516
ios_facts
Модуль ios_facts
Модуль ios_facts - собирает информацию с устройств под управлением IOS.
dir
show version
show memory statistics
show interfaces
show ipv6 interface
show lldp
show lldp neighbors detail
show running-config
Для того, чтобы видеть какие команды Ansible выполняет на оборудовании, можно
настроить EEM applet, который будет генерировать лог сообщения о выполненных
командах.
all
hardware
dir
show version
show memory statistics
config
show version
show running-config
interfaces
dir
show version
show interfaces
show ipv6 interface
show lldp
517
ios_facts
- ios_facts:
gather_subset: all
provider: "{{ cli }}"
- ios_facts:
gather_subset:
- interfaces
provider: "{{ cli }}"
- ios_facts:
gather_subset:
- "!hardware"
provider: "{{ cli }}"
Использование модуля
518
ios_facts
---
tasks:
- name: Facts
ios_facts:
gather_subset: all
provider: "{{ cli }}"
$ ansible-playbook 1_ios_facts.yml
Для того, чтобы посмотреть, какие именно факты собираются с устройства, можно
добавить флаг -v (информация сокращена):
$ ansible-playbook 1_ios_facts.yml -v
Using /home/nata/pyneng_course/chapter15/ansible.cfg as config file
519
ios_facts
После того, как Ansible собрал факты с устройства, все факты доступны как
переменные в playbook, шаблонах и т.д.
---
tasks:
- name: Facts
ios_facts:
gather_subset: all
provider: "{{ cli }}"
$ ansible-playbook 2_ios_facts_debug.yml
520
ios_facts
Сохранение фактов
В том виде, в котором информация отображается в режиме verbose, довольно сложно
понять какая информация собирается об устройствах. Для того, чтобы лучше понять
какая информация собирается об устройствах, в каком формате, скопируем
полученную информацию в файл.
521
ios_facts
---
tasks:
- name: Facts
ios_facts:
gather_subset: all
provider: "{{ cli }}"
register: ios_facts_result
- copy:
src: /srv/myfiles/foo.conf
dest: /etc/foo.conf
Но, в данном случае, нет исходного файла, содержимое которого нужно скопировать.
Вместо этого, есть содержимое переменной ios_facts_result, которое нужно перенести
в файл all_facts/{{inventory_hostname}}_facts.json.
Для того чтобы перенести содержимое переменной в файл, в модуле copy, вместо src,
используется параметр content.
522
ios_facts
Так как в пути dest используются имена устройств, будут сгенерированы уникальные
файлы для каждого устройства.
$ ansible-playbook 3_ios_facts.yml
192.168.100.1_facts.json
192.168.100.2_facts.json
192.168.100.3_facts.json
{
"ansible_facts": {
"ansible_net_all_ipv4_addresses": [
"192.168.200.1",
"192.168.100.1",
"10.1.1.1"
],
"ansible_net_all_ipv6_addresses": [],
"ansible_net_config": "Building configuration...\n\nCurrent configuration :
...
523
ios_facts
$ ansible-playbook 3_ios_facts.yml
524
ios_facts
В этом выводе видно не только то, что были внесены изменения, но и то, на каком
устройстве и какие именно изменения.
525
ios_config
Модуль ios_config
Модуль ios_config - позволяет настраивать устройства под управлением IOS, а также,
генерировать шаблоны конфигураций или отправлять команды на основании шаблона.
Параметры модуля:
526
ios_config
lines (commands)
Самый простой способ использовать модуль ios_config - отправлять команды
глобального конфигурационного режима с параметром lines.
Для параметра lines есть alias commands, то есть, можно вместо lines писать
commands.
---
tasks:
$ ansible-playbook 1_ios_config_lines.yml
527
ios_config
terminal length 0
enable
show running-config - чтобы проверить есть ли эта команда на устройстве. Если
команда есть, задача выполняться не будет. Если команды нет, задача выполнится
если команды, которая указана в задаче нет в конфигурации:
configure terminal
service password-encryption
end
Так как модуль каждый раз проверяет конфигурацию, прежде чем применит команду,
модуль идемпотентен. То есть, если ещё раз запустить playbook, изменения не будут
выполнены:
$ ansible-playbook 1_ios_config_lines.yml
528
ios_config
---
tasks:
Результат выполнения:
$ ansible-playbook 1_ios_config_mult_lines.yml
529
ios_config
parents
Параметр parents используется, чтобы указать в каком подрежиме применить
команды.
line vty 0 4
login local
transport input ssh
---
tasks:
$ ansible-playbook 2_ios_config_parents_basic.yml
530
ios_config
policy-map OUT_QOS
class class-default
shape average 100000000 1000000
---
tasks:
531
ios_config
Отображение обновлений
В этом разделе рассматриваются варианты отображения информацию об
обновлениях, которые выполнил модуль ios_config.
Playbook 2_ios_config_parents_basic.yml:
---
tasks:
Для того, чтобы playbook что-то менял, нужно сначала отменить команды. Либо
вручную, либо изменив playbook. Например, на маршрутизаторе 192.168.100.1,
вместо строки transport input ssh, вручную прописать строку transport input all.
$ ansible-playbook 2_ios_config_parents_basic.yml -v
532
ios_config
Обратите внимание, что команда login local не отправлялась, так как она настроена.
Поле updates в выводе есть только в том случае, когда есть изменения.
В режиме verbose, информация видна обо всех устройствах. Но, было бы удобней,
чтобы информация отображалась только для тех устройств, для которых произошли
изменения.
---
tasks:
533
ios_config
Изменения в playbook:
Если запустить повторно playbook, когда изменений не было, задача Show config
updates, пропускается:
$ ansible-playbook 3_ios_config_debug.yml
$ ansible-playbook 3_ios_config_debug.yml
534
ios_config
Теперь второе задание отображает информацию о том, какие именно изменения были
внесены на маршрутизаторе.
535
ios_config
save
Параметр save позволяет указать нужно ли сохранять текущую конфигурацию в
стартовую. По умолчанию, значение параметра - no.
no (или false)
yes (или true)
$ ansible-playbook 4_ios_config_save.yml
Playbook 4_ios_config_save.yml:
536
ios_config
---
tasks:
Выполнение playbook:
$ ansible-playbook 4_ios_config_save.yml
537
ios_config
538
ios_config
backup
Параметр backup указывает нужно ли делать резервную копию текущей конфигурации
устройства перед внесением изменений. Файл будет копироваться в каталог backup,
относительно каталога в котором находится playbook (если каталог не существует, он
будет создан).
Playbook 5_ios_config_backup.yml:
---
tasks:
Теперь, каждый раз, когда выполняется playbook (даже если не нужно вносить
изменения в конфигурацию), в каталог backup будет копироваться текущая
конфигурация:
$ ansible-playbook 5_ios_config_backup.yml -v
539
ios_config
В каталоге backup теперь находятся файлы такого вида (при каждом запуске playbook
они перезаписываются):
192.168.100.1_config.2016-12-10@10:42:34
192.168.100.2_config.2016-12-10@10:42:34
192.168.100.3_config.2016-12-10@10:42:34
540
ios_config
defaults
Параметр defaults указывает нужно ли собирать всю информацию с устройства, в том
числе и значения по умолчанию. Если включить этот параметр, модуль будет собирать
текущую кофигурацию с помощью команды sh run all. По умолчанию этот параметр
отключен и конфигурация проверяется командой sh run.
Этот параметр полезен в том случае, если в настройках указывается команда, которая
не видна в конфигурации. Например, такое может быть, когда указан параметр,
который и так используется по умолчанию.
Присходит это потому, что Ansible каждый раз вначале проверяет наличие команд в
соответствующем режиме. Если команд нет, то соответствующая задача выполняется.
---
tasks:
Если добавить параметр defaults: yes , изменения уже не будут внесены, если не
хватало только команды ip mtu 1500 (playbook 6_ios_config_defaults.yml):
541
ios_config
---
tasks:
Запуск playbook:
$ ansible-playbook 6_ios_config_defaults.yml
542
ios_config
after
Параметр after указывает какие команды выполнить после команд в списке lines (или
commands).
Но, если написать команду no shutdown в списке after, то она будет применена только
в том случае, если нужно вносить изменения (согласно списка lines).
---
tasks:
$ ansible-playbook 7_ios_config_after.yml -v
543
ios_config
$ ansible-playbook 7_ios_config_after.yml -v
544
ios_config
---
tasks:
$ ansible-playbook 7_ios_config_after_save.yml -v
545
ios_config
before
Параметр before указывает какие действия выполнить до команд в списке lines.
При этом, как и after, параметр before не влияет на то, какие команды сравниваются с
конфигурацией. То есть, по-прежнему, сравниваются только команды в списке lines.
Playbook 8_ios_config_before.yml:
---
tasks:
Таким образом в ACL всегда находятся только те строки, которые заданы в списке
lines.
546
ios_config
$ ansible-playbook 8_ios_config_before.yml -v
$ ansible-playbook 8_ios_config_before.yml -v
547
ios_config
match
Параметр match указывает как именно нужно сравнивать команды (что считается
изменением):
match: line
Режим match: line используется по умолчанию.
В этом режиме, модуль проверяет только наличие строк, перечисленных в списке lines
в соответствующем режиме. При этом, не проверяется порядок строк.
548
ios_config
---
tasks:
$ ansible-playbook 9_ios_config_match_line.yml -v
Обратите внимание, что в списке updates только две из трёх строк ACL. Так как в
режиме lines модуль сравнивает команды независимо друг от друга, он обнаружил, что
не хватает только двух команд из трех.
549
ios_config
То есть, порядок команд поменялся. И, хотя в этом случае, это не важно, иногда это
может привести совсем не к тем результатам, которые ожидались.
match: exact
Пример, в котором порядок команд важен.
ACL на маршрутизаторе:
---
tasks:
$ ansible-playbook 9_ios_config_match_exact.yml -v
550
ios_config
Можно добавить к этому playbook параметр before и сначала удалить ACL, а затем
применять команды:
---
tasks:
551
ios_config
$ ansible-playbook 9_ios_config_match_exact.yml -v
И, соответственно, на маршрутизаторе:
Модуль проверил каких команд не хватает в ACL (так как режим по умолчанию
match: line),
обнаружил, что не хватает команды permit icmp any any и добавил её
Но, так как в playbook ACL сначала удаляется, а затем применяется список команд
lines, получилось, что в итоге в ACL одна строка.
552
ios_config
---
tasks:
$ ansible-playbook 9_ios_config_match_exact.yml -v
553
ios_config
То есть, теперь ACL выглядит точно так же, как и строки в списке lines и в том же
порядке.
В версии Ansible 2.1 match: exact работал по-другому и такой результат достигался
комбинацией параметров match: exact и replace: block. В версии 2.2 достаточно
match: exact.
И, для того чтобы окончательно разобраться с параметром match: exact , ещё один
пример.
---
tasks:
554
ios_config
То есть, последние 4 строки выглядят так, как нужно, и в том порядке, котором нужно.
Но, при этом, есть лишняя строка. Для варианта match: exact - это уже несовпадение.
В таком варианте, playbook будет выполняться каждый раз и пытаться применить все
команды из списка lines, что не будет влиять на содержимое ACL:
$ ansible-playbook 9_ios_config_match_exact.yml -v
Это значит, что при использовании match:exact , важно, чтобы был какой-то способ
удалить конфигурацию, если она не соответствует тому, что должно быть (или чтобы
команды перезаписывались). Иначе, эта задача будет выполняться каждый раз, при
запуске playbook.
match: strict
Вариант match: strict не требует, чтобы объект был в точности как указано в задаче,
но, команды, которые указаны в списке lines, должны быть в том же порядке.
Если указан список parents, команды в списке lines должны идти сразу за командами
parents.
555
ios_config
Playbook 9_ios_config_match_strict.yml:
---
tasks:
Выполнение playbook:
$ ansible-playbook 9_ios_config_match_strict.yml -v
556
ios_config
match: none
Использование match: none отключает идемпотентность задачи: каждый раз при
выполнении playbook, будут отправляться команды, которые указаны в задаче.
---
tasks:
$ ansible-playbook 9_ios_config_match_none.yml -v
557
ios_config
558
ios_config
replace
Параметр replace указывает как именно нужно заменять конфигурацию:
replace: line
Режим replace: line - это режим работы по умолчанию. В этом режиме, если были
обнаружены изменения, отправляются только недостающие строки.
---
tasks:
559
ios_config
Выполнение playbook:
$ ansible-playbook 10_ios_config_replace_line.yml -v
В данном случае, модуль проверил каких команд не хватает в ACL (так как режим по
умолчанию match: line), обнаружил, что не хватает команды deny ip any any и добавил
её. Но, так как ACL сначала удаляется, а затем применяется список команд lines,
получилось, что у теперь ACL с одной строкой.
replace: block
В режиме replace: block отправляются все команды из списка lines (и parents), если
на устройстве нет хотя бы одной из этих команд.
ACL на маршрутизаторе:
Playbook 10_ios_config_replace_block.yml:
560
ios_config
---
tasks:
Выполнение playbook:
$ ansible-playbook 10_ios_config_replace_block.yml -v
561
ios_config
562
ios_config
src
Параметр src позволяет указывать путь к файлу конфигурации или шаблону
конфигурации, которую нужно загрузить на устройство.
Этот параметр взаимоисключающий с lines (то есть, можно указывать или lines или
src). Он заменяет модуль ios_template, который скоро будет удален.
Конфигурация
Пример playbook 11_ios_config_src.yml:
---
tasks:
$ ansible-playbook 11_ios_config_src.yml -v
563
ios_config
Неприятная особенность параметра src в том, что не видно какие изменения были
внесены. Но, возможно, в следующих версиях Ansible это будет исправлено.
Если запустить playbook ещё раз, но никаких изменений не будет, так как этот
параметр также идемпотентен:
$ ansible-playbook 11_ios_config_src.yml -v
Шаблон Jinja2
В параметре src можно указывать шаблон Jinja2.
564
ios_config
router ospf 1
router-id {{ mgmnt_ip }}
ispf
auto-cost reference-bandwidth 10000
{% for ip in ospf_ints %}
network {{ ip }} 0.0.0.0 area 0
{% endfor %}
В каталоге host_vars нужно создать такие файлы (если они ещё не созданы):
Файл host_vars/192.168.100.1:
---
hostname: london_r1
mgmnt_loopback: 100
mgmnt_ip: 10.0.0.1
ospf_ints:
- 192.168.100.1
- 10.0.0.1
- 10.255.1.1
Файл host_vars/192.168.100.2:
---
hostname: london_r2
mgmnt_loopback: 100
mgmnt_ip: 10.0.0.2
ospf_ints:
- 192.168.100.2
- 10.0.0.2
- 10.255.2.2
Файл host_vars/192.168.100.3:
565
ios_config
---
hostname: london_r3
mgmnt_loopback: 100
mgmnt_ip: 10.0.0.3
ospf_ints:
- 192.168.100.3
- 10.0.0.3
- 10.255.3.3
---
tasks:
Так как Ansible сам найдет переменные в каталоге host_vars, их не нужно указывать.
Можно сразу запускать playbook:
$ ansible-playbook 11_ios_config_src_jinja.yml -v
566
ios_config
router ospf 1
router-id 10.0.0.3
ispf
auto-cost reference-bandwidth 10000
network 10.0.0.3 0.0.0.0 area 0
network 10.255.3.3 0.0.0.0 area 0
network 192.168.100.3 0.0.0.0 area 0
$ ansible-playbook 11_ios_config_src_jinja.yml -v
567
ios_config
backup
config
defaults
save (но у самого save в Ansible 2.2 проблемы с работой)
568
ntc_ansible
ntc-ansible
ntc-ansible - это модуль для работы с сетевым оборудованием, который не только
выполняет команды на оборудовании, но и обрабатывает вывод команд и преобразует
с помощью TextFSM.
Этот модуль не входит в число core модулей Ansible, поэтому его нужно установить.
Но прежде нужно указать Ansible, где искать сторонние модули. Указывается путь в
файле ansible.cfg:
[defaults]
inventory = ./myhosts
remote_user = cisco
ask_pass = True
library = ./library
[~/pyneng_course/chapter15/library]
$ git clone https://github.com/networktocode/ntc-ansible --recursive
Cloning into 'ntc-ansible'...
remote: Counting objects: 2063, done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 2063 (delta 1), reused 0 (delta 0), pack-reused 2058
Receiving objects: 100% (2063/2063), 332.15 KiB | 334.00 KiB/s, done.
Resolving deltas: 100% (1157/1157), done.
Checking connectivity... done.
Submodule 'ntc-templates' (https://github.com/networktocode/ntc-templates) registered
for path 'ntc-templates'
Cloning into 'ntc-templates'...
remote: Counting objects: 902, done.
remote: Compressing objects: 100% (34/34), done.
remote: Total 902 (delta 16), reused 0 (delta 0), pack-reused 868
Receiving objects: 100% (902/902), 161.11 KiB | 0 bytes/s, done.
Resolving deltas: 100% (362/362), done.
Checking connectivity... done.
Submodule path 'ntc-templates': checked out '89c57342b47c9990f0708226fb3f268c6b8c1549'
569
ntc_ansible
Так как в текущей версии Ansible уже есть модули, которые работают с сетевым
оборудованием и позволяют выполнять команды, из всех возможностей ntc-ansible,
наиболее полезной будет отправка команд show и получение структурированного
вывода. За это отвечает модуль ntc_show_command.
ntc_show_command
Модуль использует netmiko для подключения к оборудованию (netmiko должен быть
установлен) и, после выполнения команды, преобразует вывод команды show с
помощью TextFSM в структурированный вывод (список словарей).
Преобразование будет выполняться в том случае, если в файле index была найдена
команда и для команды был найден шаблон.
connection - тут возможны два варианта: ssh (подключение netmiko) или offline
(чтение из файла для тестовых целей)
platform - платформа, которая существует в index файле (library/ntc-ansible/ntc-
templates/templates/index)
command - команда, которую нужно выполнить на устройстве
host - IP-адрес или имя устройства
username - имя пользователя
password - пароль
template_dir - путь к каталогу в котором находятся шаблоны (в текущем варианте
установки они находятся в каталоге library/ntc-ansible/ntc-templates/templates
570
ntc_ansible
---
tasks:
- debug: var=result
$ ansible-playbook 1_ntc-ansible.yml
571
ntc_ansible
572
ntc_ansible
Start
^${INTF}\s+${IPADDR}\s+\w+\s+\w+\s+${STATUS}\s+${PROTO} -> Record
Для того, чтобы получить вывод про первый интерфейс, можно поменять вывод
модуля debug, таким образом:
- debug: var=result.response[0]
---
tasks:
Результат выполнения:
573
ntc_ansible
$ ansible-playbook 2_ntc-ansible_save.yml
574
ntc_ansible
[
{
"intf": "Ethernet0/0",
"ipaddr": "192.168.100.1",
"proto": "up",
"status": "up"
},
{
"intf": "Ethernet0/1",
"ipaddr": "192.168.200.1",
"proto": "up",
"status": "up"
},
{
"intf": "Ethernet0/2",
"ipaddr": "unassigned",
"proto": "down",
"status": "administratively down"
},
{
"intf": "Ethernet0/3",
"ipaddr": "unassigned",
"proto": "up",
"status": "up"
},
{
"intf": "Loopback0",
"ipaddr": "10.1.1.1",
"proto": "up",
"status": "up"
}
]
Шаблоны Jinja2
Для Cisco IOS в ntc-ansible есть такие шаблоны:
575
ntc_ansible
cisco_ios_dir.template
cisco_ios_show_access-list.template
cisco_ios_show_aliases.template
cisco_ios_show_archive.template
cisco_ios_show_capability_feature_routing.template
cisco_ios_show_cdp_neighbors_detail.template
cisco_ios_show_cdp_neighbors.template
cisco_ios_show_clock.template
cisco_ios_show_interfaces_status.template
cisco_ios_show_interfaces.template
cisco_ios_show_interface_transceiver.template
cisco_ios_show_inventory.template
cisco_ios_show_ip_arp.template
cisco_ios_show_ip_bgp_summary.template
cisco_ios_show_ip_bgp.template
cisco_ios_show_ip_int_brief.template
cisco_ios_show_ip_ospf_neighbor.template
cisco_ios_show_ip_route.template
cisco_ios_show_lldp_neighbors.template
cisco_ios_show_mac-address-table.template
cisco_ios_show_processes_cpu.template
cisco_ios_show_snmp_community.template
cisco_ios_show_spanning-tree.template
cisco_ios_show_standby_brief.template
cisco_ios_show_version.template
cisco_ios_show_vlan.template
cisco_ios_show_vtp_status.template
ls -ls library/ntc-ansible/ntc-templates/templates/
576
ntc_ansible
577
Подробнее об Ansible
Подробнее об Ansible
Мы рассмотрели основные аспекты Ansible, которые нужны для работы с сетевым
оборудованием. Их достаточно чтобы начать работать с Ansible, но, скорее всего, в
процессе работы вам понадобится больше информации.
Например, как сделать так, чтобы не нужно было повторять одни и те же задачи или
сценарии снова и снова, или как организовывать более сложные playbook.
Всё это Ansible позволяет сделать, но это выходит за рамки этого курса. Эта
информация вынесена в отдельный курс Ansible для сетевых инженеров. Основы,
которые рассматриваются тут, в курсе повторяются, поэтому, если вы прочитали весь
раздел Ansible в этом курсе, можете начать сразу с четвертого раздела Playbook.
578
Задания
Задания
Все задания и вспомогательные файлы можно скачать одним архивом zip или tar.gz.
Также для курса подготовлены две виртуальные машины на выбор: Vagrant или
VMware. В них установлены все пакеты, которые используются в курсе.
Например, если в разделе есть задания: 5.1, 5.2, 5.2a, 5.2b, 5.3, 5.3a. Сначала,
можно выполнить задания 5.1, 5.2, 5.3. А затем 5.2a, 5.2b, 5.3a.
Задание 15.1
Создайте playbook, который выполняет такие задачи:
Задание 15.1a
Создайте playbook, который выполняет такие задачи:
Задание 15.1b
579
Задания
Задание 15.1c
Создайте playbook, который выполняет такие задачи:
Вторая и третяя задачи должны отображать вывод команды в виде списка строк.
Задание 15.2
Создайте playbook, который выполняет такие задачи:
Задание 15.2a
Создайте playbook, который выполняет такие задачи:
580
Задания
человеком
Задание 15.2b
Создайте playbook, который выполняет такие задачи:
Задание 15.3
В playbook task_15_3.yml описана одна задача.
Попробуйте выполнить его, как минимум, два раза. Обратите внимание, что изменения
вносились каждый раз.
Измените playbook таким образом, чтобы изменения вносились только в том случае,
когда настроен неправильный режим логирования в консоль.
Задание 15.4
Создайте playbook, который выполняет такую задачу:
581
Задания
Задание 15.4a
Проверьте работу playbook из задания 15.4, в ситуации, когда в ACL добавлена ещё
одна строка.
Задание 15.4b
Добавьте в playbook из задания 15.4a ещё одну задачу:
Задание 15.4c
Измените playbook из задания 15.4b таким образом, чтобы имя интерфейса, который
указывается в задаче, указывалось как переменная outside_intf.
582
Задания
583
Дополнительная информация
Дополнительная информация
В этом разделе собрана информация, которая не вошла в основные разделы курса, но
которая, тем не менее, может быть полезна.
584
Полезные модули
Полезные модули
В этом разделе описаны такие модули:
subprocess
os
argparse
ipaddress
585
Модуль subprocess
Модуль subprocess
Модуль subprocess позволяет создавать новые процессы. При этом, он может
подключаться к стандартным потокам ввода/вывода/ошибок и получать код возврата.
Функция subprocess.call()
Функция call() :
В переменной result теперь содержится код возврата (код 0 означает, что программа
выполнилась успешно):
586
Модуль subprocess
Возникла ошибка.
587
Модуль subprocess
Ещё один аспект работы функции call() , она выводит результат выполнения
команды, на стандартный поток вывода.
Файл subprocess_call.py:
import subprocess
if reply == 0:
print "Alive"
else:
print "Unreachable"
588
Модуль subprocess
$ python subprocess_call.py
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: icmp_seq=0 ttl=48 time=49.930 ms
64 bytes from 8.8.8.8: icmp_seq=1 ttl=48 time=48.981 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=48 time=48.360 ms
import subprocess
import os
if reply == 0:
print "Alive"
else:
print "Unreachable"
$ python subprocess_call_devnull.py
Alive
Функция subprocess.check_output()
Функция check_output() :
589
Модуль subprocess
import subprocess
print "Result:"
print reply
Результат выполнения (если убрать строку print reply , на стандартный поток вывода
ничего не будет выведено):
$ python subprocess_check_output.py
Result:
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: icmp_seq=0 ttl=48 time=49.785 ms
64 bytes from 8.8.8.8: icmp_seq=1 ttl=48 time=57.231 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=48 time=51.071 ms
$ python subprocess_check_output_catch_exception.py
ping: cannot resolve a: Unknown host
Traceback (most recent call last):
File "subprocess_check_output_catch_exception.py", line 3, in <module>
reply = subprocess.check_output(['ping', '-c', '3', '-n', 'a'])
File "/usr/local/Cellar/python/2.7.11/Frameworks/Python.framework/Versions/2.7/lib/p
ython2.7/subprocess.py", line 573, in check_output
raise CalledProcessError(retcode, cmd, output=output)
subprocess.CalledProcessError: Command '['ping', '-c', '3', '-n', 'a']' returned non-z
ero exit status 68
Функция check_output() всегда будет возвращать это исключение, когда код возврата
не равен 0.
Это значит, что в скрипте можно написать выражение try/except, с помощью которого
будет выполняться проверка корректно ли отработала команда (дополняем файл
subprocess_check_output_catch_exception.py):
590
Модуль subprocess
import subprocess
try:
reply = subprocess.check_output(['ping', '-c', '3', '-n', 'a'])
except subprocess.CalledProcessError as e:
print "Error occurred"
print "Return code:", e.returncode
Результат выполнения:
$ python subprocess_check_output_catch_exception.py
ping: cannot resolve a: Unknown host
Error occurred
Return code: 68
import subprocess
from tempfile import TemporaryFile
def ping_ip(ip_address):
"""
Ping IP address and return tuple:
On success:
* return code = 0
* command output
On failure:
* return code
* error output (stderr)
"""
with TemporaryFile() as temp:
try:
output = subprocess.check_output(['ping', '-c', '3', '-n', ip_address],
stderr=temp)
return 0, output
except subprocess.CalledProcessError as e:
temp.seek(0)
return e.returncode, temp.read()
print ping_ip('8.8.8.8')
print ping_ip('a')
591
Модуль subprocess
$ python subprocess_ping_function.py
(0, 'PING 8.8.8.8 (8.8.8.8): 56 data bytes\n64 bytes from 8.8.8.8: icmp_seq=0 ttl=48 t
ime=46.106 ms\n64 bytes from 8.8.8.8: icmp_seq=1 ttl=48 time=46.114 ms\n64 bytes from
8.8.8.8: icmp_seq=2 ttl=48 time=47.390 ms\n\n--- 8.8.8.8 ping statistics ---\n3 packet
s transmitted, 3 packets received, 0.0% packet loss\nround-trip min/avg/max/stddev = 4
6.106/46.537/47.390/0.603 ms\n')
Модуль tempfile входит в стандартную библиотеку Python и используется тут для того,
чтобы сохранить сообщение об ошибке. Функция TemporaryFile создает временный
файл и удаляет его автоматически, после того, как файл закрывается.
На основе этой функции, можно сделать функцию, которая будет проверять список IP-
адресов и возвращать, в результате выполнения, два списка: доступные и
недоступные адреса.
Это далеко не все возможности модуля subprocess. Подробнее о нём можно почитать
в документации или в статье PyMOTW
592
Модуль os
Модуль os
Модуль os позволяет работать с файловой системой, с окружением, управлять
процессами.
In [1]: import os
In [2]: os.mkdir('test')
In [3]: ls -ls
total 0
0 drwxr-xr-x 2 nata nata 68 Jan 23 18:58 test/
In [4]: os.mkdir('test')
---------------------------------------------------------------------------
OSError Traceback (most recent call last)
<ipython-input-4-cbf3b897c095> in <module>()
----> 1 os.mkdir('test')
In [5]: os.path.exists('test')
Out[5]: True
In [7]: os.listdir('.')
Out[7]: ['cover3.png', 'dir2', 'dir3', 'README.txt', 'test']
593
Модуль os
In [9]: dirs
Out[9]: ['dir2', 'dir3', 'test']
In [11]: files
Out[11]: ['cover3.png', 'README.txt']
In [12]: os.path.basename(file)
Out[12]: 'README.md'
In [13]: os.path.dirname(file)
Out[13]: 'Programming/PyNEng/book/16_additional_info'
In [14]: os.path.split(file)
Out[14]: ('Programming/PyNEng/book/16_additional_info', 'README.md')
594
Модуль argparse
Модуль argparse
argparse - это модуль для обработки аргументов командной строки.
595
Модуль argparse
import subprocess
from tempfile import TemporaryFile
import argparse
args = parser.parse_args()
print args
Создание парсера:
Добавление аргументов:
596
Модуль argparse
597
Модуль argparse
$ python ping_function.py
Namespace(count=2, ip=None)
Traceback (most recent call last):
File "ping_function.py", line 31, in <module>
rc, message = ping_ip( args.ip, args.count )
File "ping_function.py", line 16, in ping_ip
stderr=temp)
File "/usr/local/Cellar/python/2.7.11/Frameworks/Python.framework/Versions/2.7/lib/p
ython2.7/subprocess.py", line 566, in check_output
process = Popen(stdout=PIPE, *popenargs, **kwargs)
File "/usr/local/Cellar/python/2.7.11/Frameworks/Python.framework/Versions/2.7/lib/p
ython2.7/subprocess.py", line 710, in __init__
errread, errwrite)
File "/usr/local/Cellar/python/2.7.11/Frameworks/Python.framework/Versions/2.7/lib/p
ython2.7/subprocess.py", line 1335, in _execute_child
raise child_exception
TypeError: execv() arg 2 must contain only strings
Но, из-за argparse, фактически аргумент передается, только он равен None . Это видно
в строке Namespace(count=2, ip=None) .
$ python ping_function.py
usage: ping_function.py [-h] -a IP [-c COUNT]
ping_function.py: error: argument -a is required
598
Модуль argparse
$ python ping_function.py -h
usage: ping_function.py [-h] -a IP [-c COUNT]
Ping script
optional arguments:
-h, --help show this help message and exit
-a IP
-c COUNT
Файл ping_function_ver2.py:
599
Модуль argparse
import subprocess
from tempfile import TemporaryFile
import argparse
args = parser.parse_args()
print args
Кроме того, в скрипте указаны сообщения, которые будут выводиться, при вызове help.
600
Модуль argparse
$ python ping_function_ver2.py -h
usage: ping_function_ver2.py [-h] [-c COUNT] host
Ping script
positional arguments:
host IP or name to ping
optional arguments:
-h, --help show this help message and exit
-c COUNT Number of packets
Вложенные парсеры
Рассмотрим один из способов организации более сложной иерархии аргументов.
Файл parse_dhcp_snooping.py:
# Default values:
DFLT_DB_NAME = 'dhcp_snooping.db'
DFLT_DB_SCHEMA = 'dhcp_snooping_schema.sql'
def create(args):
print "Creating DB %s with DB schema %s" % (args.name, args.schema)
def add(args):
if args.sw_true:
601
Модуль argparse
def get(args):
if args.key and args.value:
print "Geting data from DB: %s" % args.db_file
print "Request data for host(s) with %s %s" % (args.key, args.value)
elif args.key or args.value:
print "Please give two or zero args\n"
else:
print "Showing %s content..." % args.db_file
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(title='subcommands',
description='valid subcommands',
help='description')
if __name__ == '__main__':
args = parser.parse_args()
args.func(args)
602
Модуль argparse
Метод add_argument добавляет аргумент. Тут синтаксис точно такой же, как и без
использования вложенных парсеров.
Функция create получает как аргумент, все аргументы, которые были переданы. И,
внутри функции, можно обращаться к нужным:
def create(args):
print "Creating DB %s with DB schema %s" % (args.name, args.schema)
603
Модуль argparse
$ python parse_dhcp_snooping.py -h
usage: parse_dhcp_snooping.py [-h] {create_db,add,get} ...
optional arguments:
-h, --help show this help message and exit
subcommands:
valid subcommands
{create_db,add,get} description
create_db create new db
add add data to db
get get data from db
optional arguments:
-h, --help show this help message and exit
-n db-filename db filename
-s SCHEMA db schema filename
metavar
Аргумент metavar позволяет указывать имя аргумента для вывода в сообщении usage
и help:
604
Модуль argparse
optional arguments:
-h, --help show this help message and exit
-n db-filename db filename
-s SCHEMA db schema filename
nargs
positional arguments:
filename file(s) to add to db
optional arguments:
-h, --help show this help message and exit
--db DB_FILE db name
-s add switch data if set, else add normal data
Если передать несколько файлов, они попадут в список. А, так как функция add,
просто выводит имена файлов, вывод получится таким:
605
Модуль argparse
+ - все аргумнеты попадут в список, но должен быть передан, хотя бы, один
аргумент
choices
get_parser.add_argument('-k', dest="key",
choices=['mac', 'ip', 'vlan', 'interface', 'switch'],
help='host key (parameter) to search')
optional arguments:
-h, --help show this help message and exit
--db DB_FILE db name
-k {mac,ip,vlan,interface,switch}
host key (parameter) to search
-v VALUE value of key
-a show db content
606
Модуль argparse
В данном примере важно указать варианты на выбор, так как затем, на основании
выбранного варианта, генерируется SQL-запрос. И, благодаря choices , нет
возможно указать какой-то параметр, кроме разрешенных.
Импорт парсера
В файле parse_dhcp_snooping.py, последние две строки будут выполняться только в
том случае, если скрипт был вызван как основной.
if __name__ == '__main__':
args = parser.parse_args()
args.func(args)
args = parser.parse_args()
args.func(args)
$ python call_pds.py -h
usage: call_pds.py [-h] {create_db,add,get} ...
optional arguments:
-h, --help show this help message and exit
subcommands:
valid subcommands
{create_db,add,get} description
create_db create new db
add add data to db
get get data from db
607
Модуль argparse
Вызов аргумента:
Аргументы можно передать как список, при вызове метода parse_args() (файл
call_pds2.py):
Необходимо использовать метод split(), так как метод parse_args , ожидает список
аргументов.
$ python call_pds2.py
Reading info from file(s)
test.txt, test2.txt
608
Модуль ipaddress
Модуль ipaddress
Модуль ipaddress может пригодится для работы с IP-адресами.
Для работы с модулем ipaddress, нужно, чтобы строки, в которых описывается IP-
адрес, были в формате unicode.
Для этого, надо либо конвертировать строки в unicode (если адрес задается вручную):
ipaddress.ip_address(u'1.2.3.4')
или так:
ipaddress.ip_address(unicode('1.2.3.4'))
Такое особенности связаны с тем, что модуль ipaddress создавался для Python3.
А в нём все строки unicode.
ipaddress.ip_address()
Функция ipaddress.ip_address() позволяет создавать объект IPv4Address или
IPv6Address, соответственно.
IPv4 адрес:
609
Модуль ipaddress
In [3]: ipv4
Out[3]: IPv4Address(u'10.0.1.1')
In [5]: ipv4.
ipv4.compressed ipv4.is_loopback ipv4.is_unspecified ipv4.version
ipv4.exploded ipv4.is_multicast ipv4.max_prefixlen
ipv4.is_global ipv4.is_private ipv4.packed
ipv4.is_link_local ipv4.is_reserved ipv4.reverse_pointer
In [6]: ipv4.is_loopback
Out[6]: False
In [7]: ipv4.is_multicast
Out[7]: False
In [8]: ipv4.is_reserved
Out[8]: False
In [9]: ipv4.is_private
Out[9]: True
610
Модуль ipaddress
In [16]: str(ip1)
Out[16]: '10.0.1.1'
In [17]: int(ip1)
Out[17]: 167772417
In [18]: ip1 + 5
Out[18]: IPv4Address(u'10.0.1.6')
In [19]: ip1 - 5
Out[19]: IPv4Address(u'10.0.0.252')
ipaddress.ip_network()
Функция ipaddress.ip_network() позволяет создать объект, который описывает сеть
(IPv4 или IPv6).
Сеть IPv4:
611
Модуль ipaddress
In [21]: subnet1.broadcast_address
Out[21]: IPv4Address(u'80.0.1.15')
In [22]: subnet1.with_netmask
Out[22]: u'80.0.1.0/255.255.255.240'
In [23]: subnet1.with_hostmask
Out[23]: u'80.0.1.0/0.0.0.15'
In [24]: subnet1.prefixlen
Out[24]: 28
In [25]: subnet1.num_addresses
Out[25]: 16
Метод hosts() возвращает генератор, поэтому, чтобы посмотреть все хосты, надо
применить функцию list:
In [26]: list(subnet1.hosts())
Out[26]:
[IPv4Address(u'80.0.1.1'),
IPv4Address(u'80.0.1.2'),
IPv4Address(u'80.0.1.3'),
IPv4Address(u'80.0.1.4'),
IPv4Address(u'80.0.1.5'),
IPv4Address(u'80.0.1.6'),
IPv4Address(u'80.0.1.7'),
IPv4Address(u'80.0.1.8'),
IPv4Address(u'80.0.1.9'),
IPv4Address(u'80.0.1.10'),
IPv4Address(u'80.0.1.11'),
IPv4Address(u'80.0.1.12'),
IPv4Address(u'80.0.1.13'),
IPv4Address(u'80.0.1.14')]
In [27]: list(subnet1.subnets())
Out[27]: [IPv4Network(u'80.0.1.0/29'), IPv4Network(u'80.0.1.8/29')]
Но, можно передать параметр prefixlen_diff, чтобы указать количество бит для
подсетей:
612
Модуль ipaddress
In [28]: list(subnet1.subnets(prefixlen_diff=2))
Out[28]:
[IPv4Network(u'80.0.1.0/30'),
IPv4Network(u'80.0.1.4/30'),
IPv4Network(u'80.0.1.8/30'),
IPv4Network(u'80.0.1.12/30')]
Или, с помощью параметра new_prefix, просто указать какая маска должна быть у
подсетей:
In [29]: list(subnet1.subnets(new_prefix=30))
Out[29]:
[IPv4Network(u'80.0.1.0/30'),
IPv4Network(u'80.0.1.4/30'),
IPv4Network(u'80.0.1.8/30'),
IPv4Network(u'80.0.1.12/30')]
In [30]: list(subnet1.subnets(new_prefix=29))
Out[30]: [IPv4Network(u'80.0.1.0/29'), IPv4Network(u'80.0.1.8/29')]
613
Модуль ipaddress
In [32]: subnet1[0]
Out[32]: IPv4Address(u'80.0.1.0')
In [33]: subnet1[5]
Out[33]: IPv4Address(u'80.0.1.5')
ipaddress.ip_interface()
Функция ipaddress.ip_interface() позволяет создавать объект IPv4Interface или
IPv6Interface, соответственно.
Используя методы объекта IPv4Interface, можно получать адрес, маску или сеть
интерфеса:
In [37]: int1.ip
Out[37]: IPv4Address(u'10.0.1.1')
In [38]: int1.network
Out[38]: IPv4Network(u'10.0.1.0/24')
In [39]: int1.netmask
Out[39]: IPv4Address(u'255.255.255.0')
614
Модуль ipaddress
In [43]: check_if_ip_is_network(IP1)
Out[43]: False
In [44]: check_if_ip_is_network(IP2)
Out[44]: True
615
Полезные функции
Полезные функции
В этом разделе описаны такие функции:
format
sorted
lambda
zip
map
filter
all, any
616
Функция sorted
Функция sorted
Функция sorted() - возвращает новый отсортированный список, который получен из
последовательности, которая была передана как аргумент. Функция также
поддерживает дополнительные параметры, которые позволяют управлять
сортировкой.
In [2]: sorted(list_of_words)
Out[2]: ['', 'dict', 'list', 'one', 'two']
Сортировка кортежа:
In [4]: sorted(tuple_of_words)
Out[4]: ['', 'dict', 'list', 'one', 'two']
Сортировка множества:
In [6]: sorted(set_of_words)
Out[6]: ['', 'dict', 'list', 'one', 'two']
Сортировка строки:
In [8]: sorted(string_to_sort)
Out[8]: [' ', 'g', 'g', 'i', 'l', 'n', 'n', 'o', 'r', 's', 't']
617
Функция sorted
In [9]: dict_for_sort = {
...: 'id': 1,
...: 'name':'London',
...: 'IT_VLAN':320,
...: 'User_VLAN':1010,
...: 'Mngmt_VLAN':99,
...: 'to_name': None,
...: 'to_id': None,
...: 'port':'G1/0/11'
...: }
In [10]: sorted(dict_for_sort)
Out[10]:
['IT_VLAN',
'Mngmt_VLAN',
'User_VLAN',
'id',
'name',
'port',
'to_id',
'to_name']
reverse
Флаг reverse позволяет управлять порядком сортировки. По умолчанию сортировка
будет по возрастанию элементов.
In [12]: sorted(list_of_words)
Out[12]: ['', 'dict', 'list', 'one', 'two']
key
С помощью параметра key можно указывать как именно выполнять сортировку.
Параметр key ожидает функцию, с помощью которой должно быть выполнено
сравнение.
618
Функция sorted
Если нужно отсортировать ключи словаря, но при этом игнорировать регистр строк:
In [16]: dict_for_sort = {
...: 'id': 1,
...: 'name':'London',
...: 'IT_VLAN':320,
...: 'User_VLAN':1010,
...: 'Mngmt_VLAN':99,
...: 'to_name': None,
...: 'to_id': None,
...: 'port':'G1/0/11'
...: }
Кроме встроенных функций, можно также использовать свои функции. Также тут
удобно использовать анонимную функцию lambda.
619
Функция sorted
620
Функция lambda
Стандартная функция:
In [2]: sum_arg(1,2)
Out[2]: 3
In [4]: sum_arg(1,2)
Out[4]: 3
Обратите внимание, что в определении lambda нет оператора return, так как в этой
функции может быть только одно выражение, которое всегда возвращает значение и
завершает работу функции.
Например, в функции sorted lambda можно использовать для указания ключа для
сортировки:
621
Функция lambda
622
Функция zip
Функция zip
Функция zip():
In [1]: a = [1,2,3]
In [2]: b = [100,200,300]
In [3]: zip(a,b)
Out[3]: [(1, 100), (2, 200), (3, 300)]
In [4]: a = [1,2,3,4,5]
In [5]: b = [10,20,30,40,50]
In [6]: c = [100,200,300]
In [7]: zip(a,b,c)
Out[7]: [(1, 10, 100), (2, 20, 200), (3, 30, 300)]
623
Функция zip
In [9]: zip(d_keys,d_values)
Out[9]:
[('hostname', 'london_r1'),
('location', '21 New Globe Walk'),
('vendor', 'Cisco'),
('model', '4451'),
('IOS', '15.4'),
('IP', '10.255.0.1')]
In [10]: dict(zip(d_keys,d_values))
Out[10]:
{'IOS': '15.4',
'IP': '10.255.0.1',
'hostname': 'london_r1',
'location': '21 New Globe Walk',
'model': '4451',
'vendor': 'Cisco'}
In [11]: r1 = dict(zip(d_keys,d_values))
624
Функция map
Функция map
Функция map применяет функцию к каждому элементу последовательности и
возвращает список результатов.
Конвертация в числа:
Если функция, которую использует map(), ожидает два аргумента, то передаются два
списка:
625
Функция filter
Функция filter
Функция filter() применяет функцию ко всем элементам последовательности, и
возвращает те объекты, для которых функция вернула True.
In [1]: list_of_strings = ['one', 'two', 'list', '', 'dict', '100', '1', '50']
In [4]: filter(lambda x: not x%2, [10, 111, 102, 213, 314, 515])
Out[4]: [10, 102, 314]
Из списка слов оставить только те, у которых количество букв больше двух:
626
Функции any и all
Функция all
Функция all() возвращает True, если все элементы истина (или объект пустой).
In [3]: all([])
Out[3]: True
In [4]: IP = '10.0.1.1'
Функция any
Функция any() возвращает True, если хотя бы один элемент истина (или объект
пустой).
In [9]: any([])
Out[9]: False
627
Функции any и all
628
Соглашение об именах
Соглашение об именах
В Python есть определенные соглашения об именовании объектов.
Имена переменных
Имена переменных не должны пересекаться с операторами и названиями модулей
или других зарезервированных значений.
DB_NAME = 'dhcp_snooping.db'
TESTING = True
db_name = 'dhcp_snooping.db'
testing = True
Модули могут использовать подчеркивания между словами для того чтобы имена были
более понятными. Для пакетов лучше выбирать короткие имена.
Имена функций
Имена функций задаются маленькими буквами, с подчеркиваниями между словами.
629
Соглашение об именах
ignore_command = False
Имена классов
Имена классов задаются словами с заглавными буквами, без пробелов.
class CiscoSwitch:
630
Подчеркивание в именах
Подчеркивание в именах
В Python подчеркивание в начале или в конце имени указывает на специальные
имена. Чаще всего, это всего лишь договоренность, но иногда это действительно
влияет на поведение объекта.
Например, если из строки line надо получить MAC-адрес, IP-адрес, VLAN и интерфейс
и отбросить остальные поля, можно использовать такой вариант:
Такая запись говорит о том, что нам не нужны третий и четвертый элементы.
Подчеркивание в интерпретаторе
В интерпретаторе python и ipython подчеркивание используется для получения
результата последнего выражения
631
Подчеркивание в именах
In [7]: _
Out[7]: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
In [8]: a = _
In [9]: a
Out[9]: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Одно подчеркивание
Например, если одно подчеркивание указано в имени функции или метода, это
означает, то этот объект является внутренней особенностью реализации и не стоит его
использовать напрямую.
Но, кроме того, при импорте вида from module import * не будут импортироваться
объекты, которые начинаются с подчеркивания.
db_name = 'dhcp_snooping.db'
_path = '/home/nata/pyneng/'
def func1(arg):
print arg
def _func2(arg):
print arg
632
Подчеркивание в именах
In [8]: db_name
Out[8]: 'dhcp_snooping.db'
In [9]: _path
...
NameError: name '_path' is not defined
In [10]: func1(1)
1
In [11]: _func2(1)
...
NameError: name '_func2' is not defined
Пример:
Два подчеркивания
Такое преобразование выполняется только в том случае, если в конце менее двух
подчеркиваний или нет подчеркиваний.
633
Подчеркивание в именах
In [15]: dir(Switch)
Out[15]:
['_Switch__configure', '_Switch__quantity', ...]
In [17]: dir(CiscoSwitch)
Out[17]:
['_CiscoSwitch__configure', '_CiscoSwitch__quantity', '_Switch__configure', '_Switch__
quantity', ...]
634
Подчеркивание в именах
return a * b
if __name__ == "__main__":
print multiply(3, 5)
import os
__file__ example2.py
/Users/natasha/Desktop/current/pyneng_things/example2.py
Кроме того, таким образом в Python обозначаются специальные методы. Эти методы
вызываются при использовании функций и операторов Python и позволяют
реализовать определенный функционал.
Как правило, такие методы не нужно вызывать напрямую. Но, например, при создании
своего класса, может понадобиться описать такой метод, чтобы объект поддерживал
какие-то операции в Python.
Например, для того чтобы можно было получить длину объекта, он должен
поддерживать метод __len__ .
Ещё один специальный метод __str__ вызывается, когда используется оператор print
или вызывается функция str(). Если необходимо, чтобы при этом отображение было в
определенном виде, надо создать этот метод в классе:
635
Подчеркивание в именах
In [12]: sw1.set_name('sw1')
In [14]: str(sw1)
Out[14]: 'Switch sw1'
Таких специальных методов в Python очень много. Несколько полезных ссылок, где
можно почитать про конкретный метод:
документация
Dive Into Python 3 - хотя тут примеры для Python3, большинство методов работают
аналогично. В этой книге отлично расписаны эты методы.
636
Полезные ссылки
Дополнительные ресурсы
Как правило, информацию тяжело усвоить с первого раза. Особенно, новую
информацию.
Packet Pushers Show 176 – Intro To Python & Automation For Network Engineers
Packet Pushers Show 198 – Kirk Byers On Network Automation With Python & Ansible
Packet Pushers Show 270: Design & Build 9: Automation With Python And Netmiko
PQ Show 99: Netmiko & NAPALM For Network Automation
Network automation tools with Jason Edelman on Sofware Gone Wild
Packet Pushers Show 332: Don’t Believe The Programming Hype - отличный подкаст
об автоматизации, программировании, скриптинге и как всё это относится к
сетевым инженерам.
Packet Pushers Show 333: Automation & Orchestration In Networking
637
Полезные ссылки
Бесплатные курсы:
Платные курсы
638
Материалы по темам курса
A Byte of Python
эта же книга на русском
Python 101
Learn Python the Hard Way
Программирование на Python
Регулярные выражения
Статьи:
regex101
Базы данных
Более подробное описание возможностей SQLite:
SQLite tutorial
Telnet, SSH
Статьи:
639
Материалы по темам курса
Netmiko Library
Automate SSH connections with netmiko
Network Automation Using Python: BGP Configuration
Jinja2
Статьи:
TextFSM
Статьи:
Модуль ntc-ansible
ntc-templates
Ansible
У Ansible очень хорошая документация:
http://docs.ansible.com/ansible/
640
Материалы по темам курса
https://sysadmincasts.com/episodes/43-19-minutes-with-ansible-part-1-4
https://github.com/ansible/ansible-examples
https://github.com/ansible/ansible-examples/tree/master/language_features
https://pynet.twb-tech.com/blog/ansible/ansible-cfg-template.html
https://pynet.twb-tech.com/blog/ansible/ansible-cfg-template-p2.html
https://pynet.twb-tech.com/blog/ansible/ansible-cfg-template-p3.html
Статьи:
http://jedelman.com/home/ansible-for-networking/
http://jedelman.com/home/network-automation-with-ansible-dynamically-configuring-
interface-descriptions/
http://www.packetgeek.net/2015/08/using-ansible-to-push-cisco-ios-configurations/
http://networkop.github.io/blog/2015/06/24/ansible-intro/
http://networkop.github.io/blog/2015/07/03/parser-modules/
http://networkop.github.io/blog/2015/07/10/test-verification/
http://networkop.github.io/blog/2015/07/17/tdd-quickstart/
http://networkop.github.io/blog/2015/08/14/automating-legacy-networks/
http://networkop.github.io/blog/2015/08/26/automating-network-build-p1/
641
Материалы по темам курса
http://networkop.github.io/blog/2015/09/03/automating-bgp-config/
http://networkop.github.io/blog/2015/11/13/automating-flexvpn-config/
Ссылки на документацию
Документация модулей, которые использовались в курсе:
re
YAML
CSV
JSON
sqlite3
Telnetlib
Pexpect
Paramiko
Netmiko
threading
multiprocessing
Jinja2
TextFSM
Ansible
642
Продолжение обучения
Продолжение обучения
Python без привязки к сетевому оборудованию
Если вам интересно развивать свои знания по Python в целом, ниже несколько
полезных ссылок.
Онлайн курсы:
Видео курсы:
Сайты с задачами:
CodeEval
HackerRank
Python + network
Лучше всего после курса попытаться применить полученные знания в работе. Этот
вариант позволит лучше запомнить то, что вы уже выучили. Кроме того, таким образом
знания расширить будет проще, так как у вас будет практическая задача, которую надо
решить.
Проекты:
Scapy
CiscoConfParse
643
Продолжение обучения
NAPALM
NOC Project
Requests
NetworkX
Twisted
AutoNetKit
644
Отзывы
Для большинства из нас это было первое знакомство с python. Но благодаря отличной
подаче материала, а так же заданиям с разным уровнем сложности, обучение
проходило весьма интересно и продуктивно. К сожалению, не все темы нашли
применение в нашей работе, но главная цель была достигнута - мы начали создавать
систему автоматизированного тестирования. Причем эти знания пригодились не
только для одной конретной задачи, но также позволили решить множество рутинных
задач. А из некоторых скриптов выросли отдельные проекты.
645
Отзывы
646