Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
xin053 committed Jun 9, 2020
1 parent a957932 commit 0302d0a
Show file tree
Hide file tree
Showing 5 changed files with 280 additions and 0 deletions.
71 changes: 71 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
## 交换机配置自动备份

使用 `python3` 多线程 `ssh` 批量登录交换机并获取到交换机配置,自动删除 `30` 天以前的配置,将命令配置到 `crontab` 实现每天备份,支持新华三,华为,锐捷,思科交换机

### 安装依赖

```shell
# python3 环境
# ubuntu
apt install -y python3-pip
# centos
yum install -y python3-pip

# pip3 更新并设置源
pip3 install pip --upgrade -i https://mirrors.aliyun.com/pypi/simple/
pip3 config set global.index-url https://mirrors.aliyun.com/pypi/simple/

cd /opt
git clone https://github.com/xin053/switchbackup
cd switchbackup
pip3 install -r requirements.txt
```

### 修改配置文件 `hosts.yaml`

按照以下格式, 注意缩进, `yaml` 文件对缩进要求很严格

支持的 `type``h3c, huawei, ruijie, cisco`

```yaml
backup_path: '/home/xin053/swConfigBackup'
keep_time: 30
hosts:
- name: xxxH3C6800
type: h3c
ip: xxx.xxx.xxx.xxx
port: 22
username: xxx
password: xxx
- name: xxxCE6810-01
type: huawei
ip: xxx.xxx.xxx.xxx
port: 22
username: xxx
password: xxx
```
### 使用
```shell
# 命令格式
python3 switchbackup.py [ip] [ip] ...

cd /opt/switchbackup
# 备份配置文件中的全部交换机
python3 switchbackup.py
# 备份配置文件中指定交换机
python3 switchbackup.py xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx
```

### 配置 `crontab`

每天凌晨执行备份:

```shell
0 0 * * * cd /opt/switchbackup && python3 switchbackup.py
```

### 效果图

![](./images/switch.png)
15 changes: 15 additions & 0 deletions hosts.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
backup_path: '/home/xin053/swConfigBackup'
keep_time: 30
hosts:
- name: xxxH3C6800
type: h3c
ip: xxx.xxx.xxx.xxx
port: 22
username: xxx
password: xxx
- name: xxxCE6810-01
type: huawei
ip: xxx.xxx.xxx.xxx
port: 22
username: xxx
password: xxx
Binary file added images/switch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pexpect==4.8.0
ruamel.yaml==0.16.10
192 changes: 192 additions & 0 deletions switchbackup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import concurrent.futures
import datetime
import logging
from multiprocessing import cpu_count
import os
import pathlib
import pexpect
import sys
import time
from ruamel.yaml import YAML


# h3c 交换机配置备份
def H3CAutoConfig(host):
ssh = pexpect.spawn('ssh -o StrictHostKeyChecking=no -p %s %s@%s ' %
(host['port'], host['username'], host['ip']))

try:
ssh.expect('[Pp]assword:', timeout=30)
ssh.sendline(host['password'])
ssh_info = ssh.expect(['>$', '[Pp]assword:'])
if ssh_info == 0:
ssh.sendline('screen-length disable')
ssh.expect(['>$', ']$'])
ssh.sendline('dis cur')
ssh.expect(['>$', ']$'])
with open(log_filename + '/' + host['ip'] + '.txt', 'w') as f:
print(ssh.before.decode('utf8', 'ignore').replace('\r', ''),
file=f)
ssh.close()
logger.info('backup switch {}[{}:{}] successful \n'.format(
host['name'], host['ip'], host['port']))
else:
ssh.close()
logger.error('switch {}[{}:{}] password error \n'.format(
host['name'], host['ip'], host['port']))
except pexpect.EOF:
ssh.close()
logger.error('switch {}[{}:{}] ssh failed.(EOF) \n'.format(
host['name'], host['ip'], host['port']))
except pexpect.TIMEOUT:
ssh.close()
logger.error('switch {}[{}:{}] ssh failed.(TIMEOUT) \n'.format(
host['name'], host['ip'], host['port']))


# 华为 交换机配置备份
def HuaweiAutoConfig(host):
ssh = pexpect.spawn('ssh -o StrictHostKeyChecking=no -p %s %s@%s ' %
(host['port'], host['username'], host['ip']))

try:
ssh.expect('[Pp]assword:', timeout=30)
ssh.sendline(host['password'])
ssh_info = ssh.expect(['>$', '[Pp]assword:'])
if ssh_info == 0:
ssh.sendline('screen-length 0 temporary')
ssh.expect(['>$', ']$'])
ssh.sendline('dis cur')
ssh.expect(['>$', ']$'])
with open(log_filename + '/' + host['ip'] + '.txt', 'w') as f:
print(ssh.before.decode('utf8', 'ignore').replace('\r', ''),
file=f)
ssh.close()
logger.info('backup switch {}[{}:{}] successful \n'.format(
host['name'], host['ip'], host['port']))
else:
ssh.close()
logger.error('switch {}[{}:{}] password error \n'.format(
host['name'], host['ip'], host['port']))
except pexpect.EOF:
ssh.close()
logger.error('switch {}[{}:{}] ssh failed.(EOF) \n'.format(
host['name'], host['ip'], host['port']))
except pexpect.TIMEOUT:
ssh.close()
logger.error('switch {}[{}:{}] ssh failed.(TIMEOUT) \n'.format(
host['name'], host['ip'], host['port']))


# 锐捷/思科 交换机配置备份
def RuijieAutoConfig(host):
ssh = pexpect.spawn('ssh -o StrictHostKeyChecking=no -p %s %s@%s ' %
(host['port'], host['username'], host['ip']))

try:
ssh.expect('[Pp]assword: ', timeout=30)
ssh.sendline(host['password'])
ssh_info = ssh.expect(['#$', '[Pp]assword:'])
if ssh_info == 0:
ssh.sendline('terminal length 0')
ssh.expect('#$')
ssh.sendline('sh run')
ssh.expect('#$')
with open(log_filename + '/' + host['ip'] + '.txt', 'w') as f:
print(ssh.before.decode('utf8', 'ignore').replace('\r', ''),
file=f)
ssh.close()
logger.info('backup switch {}[{}:{}] successful \n'.format(
host['name'], host['ip'], host['port']))
else:
ssh.close()
logger.error('switch {}[{}:{}] password error \n'.format(
host['name'], host['ip'], host['port']))
except pexpect.EOF:
ssh.close()
logger.error('switch {}[{}:{}] ssh failed.(EOF) \n'.format(
host['name'], host['ip'], host['port']))
except pexpect.TIMEOUT:
ssh.close()
logger.error('switch {}[{}:{}] ssh failed.(TIMEOUT) \n'.format(
host['name'], host['ip'], host['port']))


# 检测主机端口是否连通
def host_alive(host):
i = os.system('nc -zv {} {} -w 5'.format(host['ip'], host['port']))
if i != 0:
logger.error('switch {}[{}:{}] is unreachable \n'.format(
host['name'], host['ip'], host['port']))
return False
return True


def backup(host):
if host['type'] == 'h3c':
H3CAutoConfig(host)
elif host['type'] == 'huawei':
HuaweiAutoConfig(host)
elif host['type'] == 'ruijie':
RuijieAutoConfig(host)
elif host['type'] == 'cisco':
RuijieAutoConfig(host)
else:
logger.error('switch {}[{}:{}] is not support \n'.format(
host['name'], host['ip'], host['port']))


# load config file
config_file = pathlib.Path(r'./hosts.yaml')
yaml = YAML(typ='safe')
config = yaml.load(config_file)

date_date = datetime.datetime.now().strftime('%Y-%m-%d')
log_filename = config['backup_path'] + os.sep + date_date
os.system('mkdir -p %s' % log_filename)

# init logger
logger = logging.getLogger()
logger.setLevel('DEBUG')
BASIC_FORMAT = "%(asctime)s:%(levelname)s:%(message)s"
DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
formatter = logging.Formatter(BASIC_FORMAT, DATE_FORMAT)
chlr = logging.StreamHandler() # 输出到控制台的handler
chlr.setFormatter(formatter)
chlr.setLevel('DEBUG') # 也可以不设置,不设置就默认用logger的level
fhlr = logging.FileHandler(filename=log_filename + '/error-' + str(date_date) +
'.log',
mode='a+',
encoding='utf8') # 输出到文件的handler
fhlr.setFormatter(formatter)
logger.addHandler(chlr)
logger.addHandler(fhlr)

cpus = cpu_count()

alive_host = []

if len(sys.argv) > 1:
for host in config['hosts']:
if host['ip'] in sys.argv[1:] and host_alive(host):
alive_host.append(host)
else:
for host in config['hosts']:
if host_alive(host):
alive_host.append(host)

with concurrent.futures.ThreadPoolExecutor(max_workers=cpus) as executor:
submits = [executor.submit(backup, host) for host in alive_host]
for future in concurrent.futures.as_completed(submits):
future.result()

nowtime = datetime.datetime.now()

backup_filelist = list(os.listdir(config['backup_path']))
for backup_file in backup_filelist:
filetime = datetime.datetime.fromtimestamp(
os.path.getmtime(config['backup_path'] + os.sep + backup_file))
if (nowtime - filetime).seconds > config['keep_time'] * 24 * 3600:
logger.info('delete backup files before {} days[{}]'.format(
config['keep_time'], filetime))
os.system('rm -rf ' + config['backup_path'] + os.sep + backup_file)

0 comments on commit 0302d0a

Please sign in to comment.