【Python之旅】第六篇(七):开发简易主机批量管理工具

python 软件开发 Paramiko模块 批量主机管理

摘要: 通过前面对Paramiko模块的学习与使用,以及Python中多线程与多进程的了解,依此,就可以开发简易的主机批量管理工具了。     显然批量管理主机时,程序如果能并发执行功能是最好的,因为这样可以最大程度地利用CPU的性能,因此这就需要使用Python多线程或者多进程,基于学习的需要,这里主要...

通过前面对Paramiko模块的学习与使用,以及Python中多线程与多进程的了解,依此,就可以开发简易的主机批量管理工具了。

显然批量管理主机时,程序如果能并发执行功能是最好的,因为这样可以最大程度地利用CPU的性能,因此这就需要使用Python多线程或者多进程,基于学习的需要,这里主要使用多进程来进行开发,当然,这会存在一定问题,后面会说。

主要内容如下:

1
2
3
4
5
6
7
1.主机批量管理工具功能
2.设计框架
3.实现:数据库信息与程序源代码
4.实战演示
5.程序的不足
6.在写程序过程中的经验教训
7.往后的改进思路

1.主机批量管理工具功能

这里的主机主要是指Linux服务器,需要的功能如下:

(1)批量命令执行

能够通过该程序对管理列表中的主机批量执行管理员输入的命令。

(2)批量文件分发

对于多台服务器主机需要同一文件时,可以通过该程序远程批量分发指定的文件。

(3)支持自定义端口

实现(1)(2)的功能都依赖于Paramiko模块,而Paramiko模块是基于SSH来完成的,虽然大多数Linux服务器的SSH端口号都默认使用22,但出于安全的考虑,也有修改默认端口号的情况,比如将SSH远程端口号修改为52113等。

(4)自定义用户

这里的自定义用户主要是指该程序的用户,把该程序理解为一个批量管理系统,要使用该系统就必然要有该系统的账号与用户名,而每个账号与用户名根据权限的需要,都应该有自己可以管理的主机列表,比如普通运维人员只能管理部分服务器主机,而运维总监则应该可以管理更多的主机,并且他们的管理权限也应该是不一样的,因此,他们分别对应的管理系统的账号的权限就不一样了。

(5)日志记录功能

运维人员登陆该系统后,对远程服务器主机进行了什么操作、时间、成功与否等信息都要以日志形式记录下来。

2.设计框架

基于上面几个功能的需要,设计的思路如下:

3.实现:数据库信息与程序源代码

根据需求与设计框架,做如下的工作:

(1)数据库信息

1)管理系统登陆信息数据库

这里存放的是该系统可以登陆的用户名密码等信息,只有在这里存在的用户名才能进行登陆,如下:

创建了manager_system数据库:

1
2
3
4
5
6
7
8
9
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| manager_system     |
| mysql              |
+--------------------+
3 rows in set (0.03 sec)

在manager_system数据库中创建了两种类型不同的表:

1
2
3
4
5
6
7
8
9
10
mysql> use manager_system
mysql> show tables;
+--------------------------+
| Tables_in_manager_system |
+--------------------------+
| manager1_server          |
| manager2_server          |
| users                    |
+--------------------------+
3 rows in set (0.00 sec)

表users用来存放用户信息,表manager1_server等就是用来存放用户对应的可以管理的主机列表,下面会讲。

表users就是用来存放系统用户信息的:

1
2
3
4
5
6
7
8
9
10
mysql> describe users;
+-----------+------------------+------+-----+---------+----------------+
| Field     | Type             | Null | Key | Default | Extra          |
+-----------+------------------+------+-----+---------+----------------+
| id        | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| username  | char(20)         | NO   |     | NULL    |                |
| password  | char(20)         | NO   |     | NULL    |                |
| real_name | char(20)         | NO   |     | NULL    |                |
+-----------+------------------+------+-----+---------+----------------+
4 rows in set (0.01 sec)

表users中存放了两个用户信息:

1
2
3
4
5
6
7
8
mysql> select * from users;
+----+----------+----------+-----------+
| id | username | password | real_name |
+----+----------+----------+-----------+
|  1 | manager1 | 123456   | zhangsan  |
|  2 | manager2 | 123456   | lisi      |
+----+----------+----------+-----------+
2 rows in set (0.00 sec)

也就是说,只能用户manager1和manager2才能登陆该系统,其他用户除非向管理员申请注册,否则是无法登陆该系统的。

2)管理系统用户主机列表数据库

其实还是使用了manager_system的数据库,只是在该数据库中创建了基于用户的不同表,如下:

两种类型不同的表:

1
2
3
4
5
6
7
8
9
10
mysql> use manager_system
mysql> show tables;
+--------------------------+
| Tables_in_manager_system |
+--------------------------+
| manager1_server          |
| manager2_server          |
| users                    |
+--------------------------+
3 rows in set (0.00 sec)

表[name]_server就是用来存放用户对应的主机列表:

1
2
3
4
5
6
7
8
9
10
11
12
mysql> describe manager1_server;
+-------------+------------------+------+-----+---------+----------------+
| Field       | Type             | Null | Key | Default | Extra          |
+-------------+------------------+------+-----+---------+----------------+
| id          | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| ip          | char(20)         | NO   |     | NULL    |                |
| username    | char(20)         | NO   |     | NULL    |                |
| password    | char(20)         | NO   |     | NULL    |                |
| port        | int(11)          | NO   |     | NULL    |                |
| server_type | char(20)         | NO   |     | NULL    |                |
+-------------+------------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)

表中存放了用户可以管理的主机相关信息:

1
2
3
4
5
6
7
8
mysql> select * from manager1_server;
+----+---------------+-----------+----------+-------+-------------+
| id | ip            | username  | password | port  | server_type |
+----+---------------+-----------+----------+-------+-------------+
|  1 192.168.1.124 | oldboy    | 123456   |    22 | DNS Server  |
|  2 192.168.1.134 | yonghaoye | 123456   52113 | DHCP Server |
+----+---------------+-----------+----------+-------+-------------+
2 rows in set (0.00 sec)

其中这里的ip就是远程主机的IP地址了,server_type就是服务器类型,username和password是远程主机ssh登陆的用户密码,这也说明,只要管理系统用户进入了管理系统,在对远程主机进行管理时,就不需要输入远程主机的用户名和密码了,除了方便外,这也有一定的安全性。

需要说明的是这里的port端口号,可以看到这里有台主机的port为22,而另一台则为52113,就是前面所说的自定义端口号了,因此,这需要管理员在添加主机时手动指定。

(2)程序源代码

有了上面的基本数据准备后,再看一下该程序的源代码,其中部分注释会给出,但是基于前面的介绍,代码应该也是比较容易理解的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import MySQLdb,os,paramiko,sys,time
from multiprocessing import Process,Pool
 
#数据库连接类
class Connect_mysql:
    conn = MySQLdb.connect(host = 'localhost', user = 'root',passwd = '123456', db = 'manager_system', port = 3306)
    cur = conn.cursor()
    def __init__(self,username,password='NULL'):
        self.username = username
        self.password = password
    #contect to the login table    
    def login_check(self):    #连接管理系统账号信息数据库并验证用户名密码信息
        try:
            self.cur.execute("select * from users where username = '%s' and password = '%s'" % (self.username,self.password))
            qur_result = self.cur.fetchall()  #return the tuple
             
            if qur_result == (): #database do not have this user
                return 0        
            else:
                return 1            #database has this user
            self.cur.close()
            self.conn.close()
 
        except MySQLdb.Error,e:
            print '\033[31;1mMysql Error Msg:%s\033[0m' % e
    #contect to the server table
    def return_server(self):    #连接用户主机列表数据库并返回表信息
        self.cur.execute("select * from %s_server" %  self.username)
        qur_result = self.cur.fetchall()
        return qur_result
 
def ssh_run(host_info,cmd,sysname):    #批量远程命令执行程序
    ip,username,password,port= host_info[1],host_info[2],host_info[3],host_info[4]
    date = time.strftime('%Y_%m_%d')
    date_detial = time.strftime('%Y_%m_%d %H:%M:%S')    
    f = file('./log/%s_%s_record.log' % (sysname,date),'a+')    #操作日志记录,记录程序所有目录的/log目录里
    try:
        s.connect(ip,int(port),username,password,timeout=5)
        stdin,stdout,stderr = s.exec_command(cmd)
 
        cmd_result = stdout.read(),stderr.read()
 
        print '\033[32;1m-------------%s--------------\033[0m' % ip
        for line in cmd_result:
            print line,
        print '\033[32;1m-----------------------------\033[0m'
    except:
        log = "Time:%s | Type:%s | Detial:%s | Server:%s | Result:%s\n" % (date_detial,'cmd batch',cmd,ip,'failed')
        f.write(log)
        f.close()
        print '\033[31;1mSomething is wrong of %s\033[0m' % ip
    else:
        log = "Time:%s | Type:%s | Detial:%s | Server:%s | Result:%s\n" % (date_detial,'cmd batch',cmd,ip,'success')
        f.write(log)
        f.close()
        return 1
 
def distribute_file(host_info,file_name,sysname):    #批量文件分发函数
    ip,username,password,port = host_info[1],host_info[2],host_info[3],int(host_info[4])
    date = time.strftime('%Y_%m_%d')
    date_detial = time.strftime('%Y_%m_%d %H:%M:%S')
    f = file('./log/%s_%s_record.log' % (sysname,date),'a+')    #日志记录
    try:
        t = paramiko.Transport((ip,port))
        t.connect(username=username,password=password)
        sftp = paramiko.SFTPClient.from_transport(t)
        sftp.put(file_name,'/tmp/%s' % file_name)
        t.close()
    except:
        log = "Time:%s | Type:%s | Detial:%s | Server:%s | Result:%s\n" % (date_detial,'distribute file',file_name,ip,'failed')
        f.write(log)
        f.close()
        print '\033[31;1mSomething is wrong of %s\033[0m' % ip
    else:
        log = "Time:%s | Type:%s | Detial:%s | Server:%s | Result:%s\n" % (date_detial,'distribute file',file_name,ip,'success')
        f.write(log)
        f.close()
        print "\033[32;1mDistribute '%s' to %s Successfully!\033[0m" % (file_name,ip)
 
os.system('clear')
print '\033[32;1mWelcome to the Manager System!\033[0m'
 
while True:    #程序主程序
    username = raw_input('Username:').strip()
    password = raw_input('Password:').strip()
    if len(username) <= 3 or len(password) < 6:
        print '\033[31;1mInvalid username or password!\033[0m'
        continue
    #Begin to login
    p = Connect_mysql(username,password)
    mark = p.login_check()
    if mark == 0:        #login failed
        print '\033[31;1mUsername or password wrong!Please try again!\033[0m'
    elif mark == 1:      #login success
        print '\033[32;1mLogin Success!\033[0m'
        print 'The server list are as follow:'
        #seek for the server list managed by the system user
        p = Connect_mysql(username)
        server_list = p.return_server()
        for server in server_list:
            print '%s:%s' % (server[5],server[1])
        while True:
            print '''What do you want to do?    #程序主菜单
1.Execute the command batch.
2.Distribute file(s) batch.
3.Exit.'''
            choice = raw_input('\033[32;1mYour choice:\033[0m').strip()
            if '1' <= choice <= '4':pass
            else:continue
 
            #Execute the command batch.
            if choice == '1':    #批量执行命令程序块
                s = paramiko.SSHClient()    #调用Paramiko模块
                s.load_system_host_keys()
                s.set_missing_host_key_policy(paramiko.AutoAddPolicy())
 
                p = Pool(processes=3)    #设定进程池数据
 
                result_list = []
                while True:
                    cmd = raw_input('\033[32;0mEnter the command(or quit to quit):\033[0m')
                    if cmd == 'quit':break
                    for in server_list:
                        result_list.append(p.apply_async(ssh_run,[h,cmd,username])) #the usename is system name
            #调用相关功能函数,并执行多进程并发
                    for res in result_list:
                        res.get()
                s.close()
 
            #Distribute file(s) batch.
            elif choice == '2':    #批量分发文件程序块
                s = paramiko.SSHClient()    #调用Paramiko模块
                s.load_system_host_keys()
                s.set_missing_host_key_policy(paramiko.AutoAddPolicy())
 
                p = Pool(processes=3)
 
                result_list = []  #save the suanfa that come from the apply_async
                while True:
                    file_name = raw_input('The file you want to distribute(or quit to quit):').strip()
                    start = time.time()
                    if file_name == 'quit':break
                    file_chcek = os.path.isfile(file_name)
                    log_list = []
                    if file_chcek == False:
                        print '\033[31;1mThe file does not exist or it is a directory!\033[0m'
                        continue
                    for in server_list:
                        result_list.append(p.apply_async(distribute_file,[h,file_name,username]))   #the list save the suanfa
                    for res in result_list:
                        res.get()   #run the suanfa
                    end = time.time()
                    print '\033[31;1mCost time:%ss\033[0m' % str(end - start)
                s.close()
 
            #Exit the system
            elif choice == '3':    #退出系统
                sys.exit('\033[32;1mWelcome to use our system!\033[0m')

程序的代码量不多,主要功能也有注释,只要的paramiko模拟的SSH命令执行与SFTP文件分发、Python多进程以及进程池的使用有所了解,还是比较容易理解的。

4.实战演示

基于几个主要功能:批量命令执行、文件分发、日志记录,下面来做个演示:

(1)登陆系统

(2)批量执行命令

(3)批量分发文件

(4)退出系统

(5)用户操作日志查看

查看日志文件列表:

可以看到,操作日志是基于不同用户的(这里会有manager2的日志文件是我后来用manager2登陆后执行操作产生的,上面没有给出manager2的操作过程),并且是按天生成的,只要用户登陆并执行相关操作,日志文件是自动生成的。

查看详细日志内容:

可以查看该用户在具体时间所做的具体操作,以及成功与否等更细节的信息。

5.程序的不足

(1)细节上的问题

由于未投入生产使用,所以会存在一些意想不到的Bug,主要是在连接远程主机时可能引发的各种异常。

(2)使用了多进程

虽然使用了进程池了限制同一时间内并发的进程数,但仍然不可避免会出现文件资源抢占的情况,只是这里因为并发的进程只有两个,所以影响并不会很大,基于该程序比较小型,因此往后也可继续使用多进程,当然,使用多线程效果会更好,这里只是出于学习的需要而使用多进程。

(3)程序代码简洁程序

程序的代码简洁程度可以进一步优化,比如两个功能:批量执行命令和分发文件,其实里面就有很多重复的代码,本来是考虑用类的方法来重写的,但是对面向对象编程又不够熟悉,所以就没有使用了。

当然,这其实只是非常小的的一个程序,不过倒是可以作为以后开发特定功能的监控系统时的某一模块来使用,不管怎么说,框架在这里的话应该还是正确的,由于时间和实际需要情况的考虑,在这里并没有做太多的界面优化,因为以后是考虑做成Web界面的。当然,就从学习Python的角度来考虑,这个程序还是可以练练手,各种小的积累才能沉淀出优秀的大型开源软件。

6.在写程序过程中的经验教训

一开始函数有参数file作为我传入的文件字,但是函数中又使用了file来打开文件,所以执行程序时一直显示TypeError: 'str' object is not callable的错误,后来无意将file改变open发现又正常了,于是才很意识到这个错误。如果一开始就有这个意识,后面也不会浪费这么多时间,所以这一点要尤其注意了。

7.往后的改进思路

(1)在以后学习了Django的相关知识后,可以考虑将其写成是基于Web界面的管理系统。

(2)加强测试,尽可能找出程序中低级的Bug。

(3)改用多线程来写程序,并注意文件资源的抢占问题。

(4)将功能重复的代码用函数方式或面向对象编程方式来重写。

分享给大家,希望对同在Python学习路上的新手带来实际性的帮助!

【Python之旅】第六篇(七):开发简易主机批量管理工具的更多相关文章

  1. Python开发程序:简单主机批量管理工具

    题目:简单主机批量管理工具 需求: 主机分组 登录后显示主机分组,选择分组后查看主机列表 可批量执行命令.发送文件,结果实时返回 主机用户名密码可以不同 流程图: 说明: ### 作者介绍: * au ...

  2. Python简单主机批量管理工具

    一.程序介绍 需求: 简单主机批量管理工具 需求: 1.主机分组 2.主机信息使用配置文件 3.可批量执行命令.发送文件,结果实时返回 4.主机用户名密码.端口可以不同 5.执行远程命令使用param ...

  3. python之简单主机批量管理工具

    今天做了一个很简单的小项目,感受到paramiko模块的强大. 一.需求 二.简单需求分析及流程图 需求很少,我就简单地说下: 1. 主机分组可以配置文件实现(我用字典存数据的). 2. 登陆功能不做 ...

  4. python 简单主机批量管理工具

    需求: 主机分组 主机信息配置文件用configparser解析 可批量执行命令.发送文件,结果实时返回,执行格式如下  batch_run  -h h1,h2,h3   -g web_cluster ...

  5. C#调用OpenCV开发简易版美图工具

    前言 在C#调用OpenCV其实非常简单,因为C#中有很多OPenCV的开源类库. 本文主要介绍在WPF项目中使用OpenCVSharp3-AnyCPU开源类库处理图片,下面我们先来做开发前的准备工作 ...

  6. Python Django 编写一个简易的后台管理工具1-安装环境

    安装python环境 MAC 一般都会自带 Python2.x版本 的环境,你也可以在链接 https://www.python.org/downloads/mac-osx/ 上下载最新版安装. 安装 ...

  7. SpringBoot之旅第四篇-web开发

    一.引言 有了自动配置,springboot使web开发变得简单,这个在springboot之旅中的第一篇中就有体现,实际的开发中当然不会这么简单,很多时候我们都需要自己去定制一些东西.web开发的东 ...

  8. SpringBoot之旅第六篇-启动原理及自定义starter

    一.引言 SpringBoot的一大优势就是Starter,由于SpringBoot有很多开箱即用的Starter依赖,使得我们开发变得简单,我们不需要过多的关注框架的配置. 在日常开发中,我们也会自 ...

  9. Python 学习 第十六篇:networkx

    networkx是Python的一个包,用于构建和操作复杂的图结构,提供分析图的算法.图是由顶点.边和可选的属性构成的数据结构,顶点表示数据,边是由两个顶点唯一确定的,表示两个顶点之间的关系.顶点和边 ...

随机推荐

  1. 解读为什么有符号的char可表示范围是-128~+127

    问:为什么有符号的char可表示范围是-128~+127? 要明白这个问题,首先要明白一下几点: 对于char和int计算机中以补码形式存在. 严格来说计算机就是傻逼,它只知道某个位上是0还是1. 我 ...

  2. iscc2016-basic-明察秋毫

    查看源代码,找到maybe not flag : Jr1p0zr2VfPp 移位密码,注意判断字母大小写,并且数字无变化 s = "Jr1p0zr2VfPp" p = list(s ...

  3. 简明解释算法中的大O符号

    伯乐在线导读:2009年1月28日Arec Barrwin在StackOverflow上提问,“有没有关于大O符号(Big O notation)的简单解释?尽量别用那么正式的定义,用尽可能简单的数学 ...

  4. C#中&与&&的区别

    刚刚翻书发现这个问题,在网上找了一下,我的理解吧. 他俩的区别就是“&”和“|”不执行短路计算,而&&和||执行了短路计算. &不执行短路计算 ——————表达式A&a ...

  5. AMD:浏览器中的模块规范

    前面提到,为实现与Node.js相同方式的模块写法,大牛们做了很多努力. 但浏览器环境不同于服务器端,它的模块有一个HTTP请求过程(而Node.js的模块文件就在本地),这个请求过程多数使用scri ...

  6. All in All

    Crawling in process... Crawling failed Description You have devised a new encryption technique which ...

  7. JAVA存取对象属性时,如果开程多线程,记得对相关存取方法作原子化操作定义

    最显著的应用当然是银行存款和取款,不要存在存取数字和实际发生不一样的情况. synchronized关键字. class BankAccount { private int balance = 100 ...

  8. BZOJ1636: [Usaco2007 Jan]Balanced Lineup

    1636: [Usaco2007 Jan]Balanced Lineup Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 476  Solved: 345[ ...

  9. 【转】java参数传递(超经典)

    原文网址:http://blog.sina.com.cn/s/blog_4b622a8e0100c1bo.html Java中的参数传递机制一直以来大家都争论不休,究竟是“传值”还是“传址(传引用)” ...

  10. 数学(矩阵乘法):HDU 4565 So Easy!

    So Easy! Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Su ...