пятница, 24 февраля 2012 г.

Управление конфигурацией в it инфраструктуре ч.2

Начнем воплощать в жизнь вторую часть. Ранее я писал скрипты которые работают с интерактивными приложениями или устройствами на expect'е. Трудность такой реализации это то что логику скрипта надо будет писать на TCL, или дробить задачу - писать логику на shell и из него вызывать кусочки на expect. Это не самый удобный путь. Ну а второе, почему мне не хотелось писать на expect'е - когда то давно я похвастался одному знакомому java программисту, что я пишу на tcl. На что он мне сказал -  "На нем пишут бородатые дядьки", отчасти это так.
Для python есть модуль реализующий функциональность expect - pyexpect. Соответственно скрипт я написал на python. Скрипт достаточно легко изменить под собственные нужды, используя тот же самый алгоритм. На python я ранее ничего не писал, поэтому буду рад услышать конструктивные замечания. Справедливости ради надо сказать, что для perl тоже есть аналогичный модуль. Ничто не запрещает реализовать все на нем.
Установим модули которые нам понадобятся:

[root@server ~]# yum install pexpect pysvn python-iniparse

Модуль pysvn будет использоваться для работы с репозиторием, а iniparse для разбора конфигурационного файла.
Что бы сократить размер приведенного здесь скрипта, я уменьшил количество видов устройств до двух, обладающих наиболее отличными друг от друга механизмами резервного копирования конфигурации. Это коммутаторы Brocade Silkworm (мне доступны коммутаторы двух моделей Brocade 5380 и входящие в HP C7000 - Brocade 4024) и пара кластеров IBM SAN Volume Controller(сокращенно svc). Опишу как выполняется резервное копирование конфигурации на этих устройствах. Резервирование конфигурации на коммутаторах Brocade можно выполнить командой (для версии Fabric OS: v6.2.2d):

RUMOWDRCENC005_LEFT:admin> configupload -all -scp host,user,path

где host, user, path - сервер, пользователь и путь на файловой системе сервера, на который будет записан конфигурационный файл. Передача конфигурационного файла осуществляется по протоколу scp.
Резервирование конфигурации кластера svc выполняется в два приема. Мне доступны два кластера с разной прошивкой - v5.1.0.1 и v6.3.0.1(один обновил, а второй еще нет). Сначала формируются файлы конфигурации:

IBM_2145:SVCMOS1P1:admin> svcconfig backup

которые затем надо забрать c svc (в отличие от резервирования конфигурации на коммутаторах Brocade):

[root@server ~]# scp -i ~/.ssh/svc.key svc.config.backup.xml admin@svc_ip:/tmp/svc.config.backup.*

Как вы заметили, аутентификация осуществляется по ключу. Конфигурационных файлов у svc -  три: svc.config.backup.sh, svc.config.backup.log и svc.config.backup.xml.
Дополнительно мы будем следующую информацию: состояние портов на свичах:

RUMOWDRCENC005_LEFT:admin> switchshow

и состояние пулов схд на svc:

IBM_2145:SVCMOS1P1:admin> svcinfo lsmdiskgrp

Листинг конфигурационного файла:

[confrepuser@server ~]$ cat config.ini 
[system]
host=192.168.1.100
password = cfgbackup
path = confrepo/devices/

[brocade1]
login = admin
password = password1
RUMOWDRCENC005_LEFT = Yes
RUMOWDRCENC005_RIGHT = yes

[brocade2]
login = admin
password = password2
BDMOS101L01 = True
BDMOS101R01 = 1
BDMOS201L01 = 0
BDMOS201R01 = False

[svc1]
login = admin
key = /home/confrepuser/.ssh/svc.key
svcmos1 = Yes
svcmos2 = No

Конфигурационный файл разбит на секции, обязательная из которых system. Параметры секции  system: host - ip или днс имя сервера на котором содержится рабочая копия репозитория; password - пароль пользователя от имени которого выполняется скрипт; path - путь на файловой системе, куда будут записываться конфигурационные файлы устройств.
Остальные секции именуются в соответствии с классом устройства (секции устройств), т.е. все секции, название которых начинается с "brocade" будут рассматриваться как секции содержащие коммутаторы Brocade. Абсолютно аналогично с svc. Параметры секций устройств логически состоят из двух частей, параметры используемые при аутентификации на устройстве (для примера: login - имя пользователя используемое при входе на устройство) и на параметры, название которых указывает на устройства (к примеру svcmos1 - название кластера svc, которое должно иметь соответствующую запись в днс или запись в файле /etc/hosts). Параметр указывающий на устройство является булевой переменной, положительное значение которого означает то, что с устройства будет выполнятся резервирование конфигурации.
Итак, листинг скрипта:

[confrepuser@server ~]$ cat geconfig.py 
#!/usr/bin/python
#-*- coding: utf-8 -*-

import pexpect,sys,pysvn,re,os
from iniparse import ConfigParser

# Check directory exist, and make if not.
def ensure_dir(dir):
    if not os.path.exists(dir):
       os.makedirs(dir)
       client.add(dir)
       client.checkin(dir, 'Add dir '+dir)

# Get brocade config
def getbrconf(login, password, brocade, dir):
    ssh_newkey = 'Are you sure you want to continue connecting'
    list_files = []
    child = pexpect.spawn("ssh "+login+"@"+brocade)
    #For debug purpose
    #child.logfile = sys.stdout

    i=child.expect([pexpect.EOF,ssh_newkey,'password:'],timeout=60)
    if i==0:
       print "Configuration file from", brocade,"is not received"
       return
    if i==1:
       child.sendline('yes')
       i=child.expect([ssh_newkey,'password:',pexpect.EOF],timeout=60)
    if i==2:
       child.sendline(password)

    child.expect (':'+login+'> ')
    child.sendline('configupload -all -scp '+host+','+username+','+devicepath+dir+'/'+brocade)
    child.expect ('password:')
    child.sendline(userpassword)
    child.expect (':'+login+'> ')
    # Get some state
    f = open(dir+'/'+brocade+'-state', 'w')
    child.sendline ('switchshow')
    child.expect (':'+login+'> ')
    f.write(child.before)
    child.sendline ('exit')
    list_files.extend(['./'+dir+'/'+brocade, './'+dir+'/'+brocade+'-state'])
    for file in list_files:
        if not client.info(file):
           # File added with "segmentation filed" bug!
           #client.add(file)
           child = pexpect.spawn('/usr/bin/svn add '+file)
           child.expect ('\r\n')

# Get ibm svc config
def getsvcconf(login, key, svc, dir):
    ssh_newkey = 'Are you sure you want to continue connecting'
    svc_files = ['svc.config.backup.sh', 'svc.config.backup.log', 'svc.config.backup.xml']
    list_files = []
    child = pexpect.spawn("ssh -i"+key+" "+login+"@"+svc)
    #For debug purpose
    #child.logfile = sys.stdout

    i=child.expect([pexpect.EOF,ssh_newkey,':'+login+'>'],timeout=60)
    if i==0:
       print "Configuration file from", svc,"is not received"
       return
    if i==1:
       child.sendline('yes')
    if i==2:
       child.sendline('svcconfig backup')
       child.expect (':'+login+'>')
       # Get some state
       f = open(dir+'/'+svc+'-state', 'w')
       child.sendline ('svcinfo lsmdiskgrp')
       child.expect (':'+login+'>')
       f.write(child.before)
       child.sendline ('exit')
       list_files.append('./'+dir+'/'+svc+'-state')
    # Get config from svc
    for file in svc_files:
        child = pexpect.spawn('/usr/bin/scp -i'+key+' '+login+'@'+svc+':/tmp/'+file+' '+dir+'/'+svc+'-'+file)
        # Expect new line
        child.expect ('\r\n')
        list_files.append('./'+dir+'/'+svc+'-'+file)
    # Check if file exist in svn and add file if not.
    for file in list_files:
        if not client.info(file):
           # File added with "segmentation filed" bug! 
           #client.add(file)
           child = pexpect.spawn('/usr/bin/svn add '+file)
           child.expect ('\r\n')


# Main program
# Read configuration file
cfgpr = ConfigParser()
cfgpr.read('config.ini')
client = pysvn.Client()

username = os.getenv('LOGNAME')
host = cfgpr.get('system','host')
userpassword = cfgpr.get('system','password')
devicepath = cfgpr.get('system','path')

# Get configurations
for section in cfgpr.sections():
    if re.match("brocade", section):
       ensure_dir(section)
       password = cfgpr.get(section,'password')
       login = cfgpr.get(section,'login')
       for option in cfgpr.options(section):
           if option != "password":
              if option != "login":
                 if cfgpr.getboolean(section, option):
                    getbrconf(login, password, option, section)
    if re.match("svc", section):
       ensure_dir(section)
       key = cfgpr.get(section,'key')
       login = cfgpr.get(section,'login')
       for option in cfgpr.options(section):
           if option != "key":
              if option != "login":
                 if cfgpr.getboolean(section, option):
                    getsvcconf(login, key, option, section)

# Update repo
client.checkin('./', 'Update repository')


В скрипте производится последовательный разбор конфигурационного файла с последующим вызовом соответствующей процедуры (т.е. или забираем конфигурационный файл с коммутатора - процедура getbrconf или с svc - getsvcconf). В целом, исходный текст скрипта достаточно подробно комментирован, так что я думаю разобраться в нем не составит труда. К сожалению модуль pysvn имеет ошибку, добавление файла в репозиторий заканчивается "Segmentation fault", поэтому добавление файлов реализовано средствами команды svn.
Создадим рабочую копию репозитария:

[confrepuser@server ~]$ svn co file:///var/www/svn/confrepo

Теперь для завершения задачи, достаточно поместить файлы config.ini и geconfig.py в ~/confrepo/devices и добавить задание в cron для запуска скрипта. Файл скрипта и конфигурационный файл так же можно поместить в репозиторий. Будьте осторожны, поскольку в config.ini находятся пароли!