-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
280 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
pexpect==4.8.0 | ||
ruamel.yaml==0.16.10 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |