CMDB 资产管理
环境 : Linux 7.4 Django 1.11+ Python 3.6
CMDB

代码的发布/监控/堡垒机
资产管理。
- 代替execl,将execl表里的内容导入我们系统中
- 和其他系统交互 通过 client端进行采集数据。salt-stack/ ansible/ 阿里云封装的API
后台管理系统需要一个接受工具层发送的数据的逻辑(API),CMDB最重要的就是选择采集工具的功能
每天的数据更改需要记录日志,如果没变化就不记录,变化及记录日志,放入定时任务里。
1.针对云服务 -> 包括以下这些内容
硬件类型, --> 服务器,交换机
环境, --> 测试,上线,灰度 等到
运行的应用, --> nginx, apache,docker, 等等
来源, --> 阿里云,腾讯云,物理机
上线状态, --> 上线,关机, 下线,待机
内网IP, --> 内网IP
外网IP, --> 外网IP
主机名, --> hostname
内存, --> memcache
CPU, --> CPUINFO
硬盘, --> DISKS
内核, --> 内核版本
操作系统, --> OS:centos,Redhat,Debian,
ecs_name, --> 云主机的标识
标识名, --> 标识名
区域/城市, --> 华南,华北,东北 等
机房/机柜 --> 机房/机柜 属性
这些内容都是作为models.py里的表信息
class Host(models.Model): # 最基础的主机表 '''主机,阿里云eth0 内网网卡, eth1 公网网卡''' hostname = models.CharField(max_length=64, blank=True, null=True, verbose_name='主机名') ecsname = models.CharField(max_length=64, blank=True, null=True, verbose_name='实例名') login_port = models.CharField(max_length=16, default='22', blank=True, null=True, verbose_name='登录端口') cpu = models.CharField(max_length=8, blank=True, null=True, verbose_name='CPU') mem = models.CharField(max_length=8, blank=True, null=True, verbose_name='内存') speed = models.CharField(max_length=8, blank=True, default='5', null=True, verbose_name='带宽') eth1_network = models.CharField(max_length=32, blank=True, null=True, verbose_name='公网IP') eth0_network = models.CharField(max_length=32, verbose_name='私网IP') sn = models.CharField(max_length=64, blank=True, null=True, verbose_name='SN') kernel = models.CharField(max_length=64, blank=True, null=True, verbose_name='内核版本') # 内核+版本号 remarks = models.CharField(max_length=2048, blank=True, null=True, verbose_name='备注') createtime = models.CharField(max_length=32, blank=True, null=True, verbose_name='创建时间') expirytime = models.CharField(max_length=32, blank=True, null=True, verbose_name='到期时间') #ForeignKey lab = models.ForeignKey(to='Lable',default=1,blank=True, null=True, verbose_name='标签') os = models.ForeignKey(to='Os',default=1,blank=True, null=True, verbose_name='操作系统') # os+版本号 # the_upper = models.ForeignKey(to='Host', blank=True, null=True, verbose_name='宿主机', related_name='upper') source = models.ForeignKey(to='Source',default=1,blank=True, null=True, verbose_name='来源IP') # ManyToManyField logining = models.ManyToManyField(to='Login',default=1, verbose_name='所属用户') disks = models.ManyToManyField(to='Disk',default=1, verbose_name='磁盘') #这个字段只运行再内存里。这些信息不占磁盘的信息。 state_choices = ( (1, 'Running'), (2, '下线'), (3, '关机'), (4, '删除'), (5, '故障'), ) state = models.SmallIntegerField(verbose_name='主机状态', choices=state_choices, blank=True, null=True, ) def __str__(self): return self.hostname class Meta: verbose_name_plural = "主机表"
管理系统 list
注意 手动创建APP 时,是不会自动创建url.py文件的,需要手动来创建
form django.views import View class List(View): def post(self, *args, **kwargs): pass def get(self, *args, **kwargs): # 这里的 queryset_list 可以切分来取值。数据太多需要分页,分页也是用到这种方式 host_list = models.Host.object.all() return render(request, 'host.html', locals())
通过 template
模板渲染可以将 queryset_list
内的数据点出来
比如{{ host_list.hostname }}
-> 数据就出来了
models.py
class 里面定义的__str__
方法,
当你查询使用的时候不用点字段信息
def __str__(self): return self.hostname
直接输出时,直接return指定字段信息为hostname的属性
关于 取state_choices
的值。在template里的新写法get_state_display
即可
管理系统增改删
class Add(View): def post(self, *args, **kwargs): pass def get(self, *args, **kwargs): # 这里的 queryset_list 可以切分来取值。数据太多需要分页,分页也是用到这种方式 host_list = models.Host.object.all() return render(request, 'host.html', locals()) class Delete(View): def post(self, *args, **kwargs): id = request.GET.get('id') # 通过get ID 来直接删除你需要删除的主机 delete = models.Host.object.filter(id=int(id)).delete() return render(request,'host.html',locals()) def get(self, *args, **kwargs): return render(request, 'host.html', locals())
基于FORM 表单来做渲染
class Update(View): # POST def post(self, request, pk): #request 后面的值 pk 是 url传入的值,类似于 ->url 里 host.id print (pk) #这里传入的就是ID 值 form = form_class.HostForm(data=request.POST) if form.is_valid(): # 验证信息 print(form.cleaned_data) models.Host.object.filter(id=pk).update(**form.cleaned_data) #修改的信息 # print ('提交正常') return redirect('/host/list') else: print (form.errors) return render(request, 'edit.html',locals()) #这里是错误信息 # GET def get(self, request,pk): # get_id = int(request.GET.get('id')) obj = models.Host.objects.filter(id=pk).first() # queryset 类型 通过ID 取值 # obj = models.Host.objects.filter(id=get_id) # queryset_list 类型,但是只有一个值 form = from_class.HostForm( #这里可以渲染到前端的内容, initial = {'hostname': obj.hostname, # initial 固定写法!这里的值是直接渲染到前端的 'ecsname':obj.ecsname, #需要增加或者删除都修改这里 'cpu':obj.cpu, 'mem':obj.mem, 'speed':obj.speed, 'network':obj.network, 'source_id':obj.source_id, 'region_id':obj.region_id, } )
通过上面的initial 的内容传入template模板渲染出
<form method='post' role='form' > {% csrf_token %} <p id='hsotname'>阿里主机名: {{form.hostname}} {{form.errors.hostname.0}}</p> # 对应上面的 initial 内的值 <p id='ecsname'>实例ID: {{form.ecsname}} {{form.errors.ecsname.0}}</p> <p id='cpu'>CPU: {{form.cpu}} {{form.errors.cpu.0}}</p> <p id='mem'>内存/G: {{form.mem}} {{form.errors.mem.0}}</p> <p id='speed'>带宽/M: {{form.speed}} {{form.errors.speed.0}}</p> <p id='network'>IP: {{form.network}} {{form.errors.network.0}}</p> <p id='source'>来源类型: {{form.source_id}} {{form.errors.source_id.0}}</p> <p id='region'>所属区域: {{form.region_id}} {{form.errors.region_id.0}}</p> # 还没增加 M2M 类型的 <input type='submit' value='提交' </form>
管理系统 (后台):
增删改查-> form ---> 完成
样式使用 bootstrap --> 最后写
form-M2M ---> 最后写
分页 ---> 数据多了要使用分页
form 表单问题
from django.forms import Form from hc import models class HostForm(Form): hostname = fields.CharField( required = True, # error_messages = ['required':'不能为空'], widget = widgets.TextInput=(attrs={'class':'form-control'}) ) ecsname = fields.CharField( required = True, # error_messages = ['required':'不能为空'], widget = widgets.TextInput=(attrs={'class':'form-control'}) # 加样式是 通过 form-control 修改 ) cpu = fields.CharField( required = True, # error_messages = ['required':'不能为空'], widget = widgets.TextInput=(attrs={'class':'form-control'}) ) mem = fields.CharField( required = True, # error_messages = ['required':'不能为空'], widget = widgets.TextInput=(attrs={'class':'form-control'}) ) speed = fields.CharField( required = True, # error_messages = ['required':'不能为空'], widget = widgets.TextInput=(attrs={'class':'form-control'}) ) network = fields.CharField( required = True, # error_messages = ['required':'不能为空'], widget = widgets.TextInput=(attrs={'class':'form-control'}) ) # 一对多关系 的问题 source_id = fields.CharField( # 一对多的 source_id 字段不加_id 时会报错 required = True, # 因为 fields.CharField 渲染的input的框是,输入的都是str()类型,ID 又是唯一的所有这里 choices = [], # 所有插数据的时候 str() 和int() 数据类型不一样会报错 # choices = models.Sourec.objects.values_list('id','name'), # values_list这里取到的还是int() # values_list 通过这个方法可以拿到元组 即 ('id','name') # form.cleaned_data['source'] = int(form.cleaned_data['source']) # 可以这么理解。但是最好别这样用,一对多数据多了的时候 就很麻烦 # 所有最好的方法就是 在source 后面 加个 _id 即可 -> source_id 插入数据时就不会报错 widget = widgets.Select={'class':'form-control'} ) region_id = fields.CharField( required = True, choices = [], widget = widgets.Select={'class':'form-control'} ) # 初始化方法 -> 每次实例化时都要执行这个 def __init__(self, *args, **kwargs): super(HostForm, self).__init__(*args, **kwargs) # 先执行 父类的 __init__方法,也就是 View类的__init__方法 self.fields['source_id'].choices = models.Sourec.objects.values_list('id','name') self.fields['region_id'].choices = models.Region.objects.values_list('id','name') # 这个 self.fields 值 是执行父类初始化__init__时产生的,会把当前这个子类的所有字段值,当成属性做了次深度copy。 # 成一个字典,然后用过切片取值。重新将.choices 赋值,赋值的是后面一对多查表的值。 #这种方法 不用重启服务,当DB数据更新时,也会自动刷入表单渲染
form 表单问题 2
template
<form method='post' novalidate role='form' > # novalidate 是不希望浏览器自动渲染出样式,加了后就可以显示自定义的样式 {% csrf_token %} <p id='hsotname'>阿里主机名: {{form.hostname}} {{form.errors.hostname.0}}</p> # 对应上面的 initial 内的值 <p id='ecsname'>实例ID: {{form.ecsname}} {{form.errors.ecsname.0}}</p> # 点0 是取列表的第一个值 <p id='cpu'>CPU: {{form.cpu}} {{form.errors.cpu.0}}</p> #错误信息直接都集成到 form里面了 <p id='mem'>内存/G: {{form.mem}} {{form.errors.mem.0}}</p> <p id='speed'>带宽/M: {{form.speed}} {{form.errors.speed.0}}</p> <p id='network'>IP: {{form.network}} {{form.errors.network.0}}</p> <p id='source'>来源类型: {{form.source_id}} {{form.errors.source_id.0}}</p> <p id='region'>所属区域: {{form.region_id}} {{form.errors.region_id.0}}</p> # 还没增加 M2M 类型的 <input type='submit' value='提交' </form>
cmdb流程小结
API 等到 客户端完成了再说
API
- 入库。
- 客户端上传的格式进行解析。
按钮
和add 操作一样。url去获取工具拿到的数据

客户端
底层 -> 封装 + 结合 -> 优化

客户端底层模式执行方式讲解
yum 安装 salt-stack
客户端:
底层:
服务端 控制salt-master : 两种方案 # 一般用第二种,别人封装好的API 用起来就很方便 1:CMDB 服务端和 salt-master permiko 模块 -> 执行 salt-master 命令 --> 然后获取结果 2:salt-master 使用源生的salt-api CMDB 服务端 -> requests模块(POST.GET)请求salt-api 执行需要的命令 salt-api 就是 ip:port 的形式 salt-api 必须加 安全认证 token,提高安全性
salt-stack 安装
salt-stack
官网下载最新版本
http://www.cnblogs.com/onda/p/7929609.html
yum -y install salt-stack
要通过官网的最新包去安装
salt-stack
yum 安装的路径 /etc/salt
-
master -> 部署 服务端 1次
-
api -> 部署 服务端 1次
-
minion -> 部署 客户端 很多次
首先去官网选择 下载的版本。
https://repo.saltstack.com/#rhel
根据现有的系统判断出 我需要的使用的版本
centos 6 + py2 yum install -y https://repo.saltstack.com/yum/redhat/salt-repo-latest-2.el6.noarch.rpm yum clean expire-cache yum install -y salt-master salt-minion salt-ssh salt-syndic salt-cloud salt-api service salt-minion restart
centos 7 + py 2 官网地址 yum install -y https://repo.saltstack.com/yum/redhat/salt-repo-latest-2.el7.noarch.rpm yum install -y salt-master salt-minion salt-ssh salt-syndic salt-cloud salt-api systemctl restart salt-minion
master 的IP是: 192.168.1.8 master 基本不用动
操作 minion端
vim /etc/salt/minion
# Set the location of the salt master server. If the master server cannot be # resolved, then the minion will fail to start. #master: salt master: 192.168.1.8 # : 冒号后面必须有空格 不然报错 #id: #这个ID 很重要 记住,不允许重复! id: cmdb_test
保存后
启动服务systemctl restart salt-minion
salt-key -L # 查看所有客户端的 ID salt-key -D # 删除所有授权用户 -d 指定 salt-key -A # 授权所有客户端 salt 'cmdb_test' cmd.run 'df -h' # 让所有客户端执行`df -h`命令 #'cmdb_test' 这里是通过ID 控制, 支持正则 salt '*' test.ping # ping 命令测试是否连通
salt--master 和 salt-minion
控制端 被控制端
通过 salt-api 访问 salt-master 来控制salt-minion 执行 命令 返回结果
服务端监听4505和4506两个端口,4505为消息发布的端口,4506为和客户端通信的端口
总结
1. 总结 学会看报错 -> salt-minion debug 或者 systemctl status salt-minion -l
2. 通信不正常 都是秘钥的问题,不行就删掉秘钥重启自动生成密码。
ID 也是对应秘钥的,ID不一致也会影响秘钥的正确性
3. 秘钥位置 /etc/salt/pki/ 不开心就删删删 rm -rf /etc/salt/pki/*
4. 关掉你的防火墙 和 selinux 否则也会影响正常的通信
master --- API 安装
yum install -y salt-master
yum install -y salt-api pyOpenSSL
# pip install salt-api --> yum 装过了salt-api , pip 就不用了 pip install cherrypy==3.2.3 cd /etc/pki/tls/certs/ make testcert --> 密码要记住 '123123' 是用这个密码来获取 token的 -->设置秘钥密码,(3次) ,剩下回车 cd ../private/ openssl rsa -in localhost.key -out localhost_nopass.key chmod 755 /etc/pki/tls/certs/localhost.crt chmod 755 /etc/pki/tls/private/localhost.key chmod 755 /etc/pki/tls/private/localhost_nopass.key useradd -M -s /sbin/nologin saltapi passwd saltapi sed -i '/#default_include/s/#default/default/g' /etc/salt/master mkdir -p /etc/salt/master.d cd /etc/salt/master.d
vim api.conf
rest_cherrypy:
port: 8001 # 这个端口可以 改, 需要重启 salt-api ssl_crt: /etc/pki/tls/certs/localhost.crt ssl_key: /etc/pki/tls/private/localhost_nopass.key
vim eauch.conf
# 注意空格 external_auth: pam: saltapi: # 用户名 - .* # 该配置文件给予saltapi用户所有模块使用权限,出于安全考虑一般只给予特定模块使用权限 - '@runner' - '@wheel'
systemctl restart salt-master
systemctl start salt-api
netstat -tlnp #查看 8001 端口是否启动,启动了证明 运行正常 Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:4505 0.0.0.0:* LISTEN 1153/python tcp 0 0 0.0.0.0:4506 0.0.0.0:* LISTEN 1159/python tcp 0 0 0.0.0.0:8001 0.0.0.0:* LISTEN 1340/python
python调用salt-api执行命令
python调用salt-api执行命令
import json
salt_api = 'https://192.168.1.8:8001' class SaltApi: def __init__(self, url): # 初始化 参数 self.url = url self.username = 'saltapi' # 设置的用户名和密码 salt-api的 self.password = '123213' self.headers = { #headers 写死的 请求的头部 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0' 'Content-type': 'application/json' # 固定的写法,Agent的浏览器参数 } self.params = {'client':'local', 'fun':'', 'tgt':''} # 固定key值 self.login_url = salt_api + 'login' # 拼接 登录 self.login_params = {'username': self.username, 'password': self.password, 'eauth': 'pam'} # 登录的字典信息,参数的封装 self.token = self.get_data(self.login_url, self.login_params).get('token') # 这是个字典还可以切片取值['token']。 推荐get方式取值,取不到只有空,不会报错。切片取值就能获取到报错信息 # token 值 调用get_data 取值,传过去了 url 和 登录信息相关的参数 self.headers['X-Auth-Token'] = self.token def get_data(self, url, params): # get 数据 + 格式转换 send_data = json.dumps(params) # 先将字典 通过json 变成 str格式 request = requests.post(url, data=send_data, headers=self,headers, verify=False) #发送请求 response = request.json() # 将返回的数据 字符串 str 转成 json格式 给 response result = dict(response) # 再转换成字典 return result['retrun'][0] #最好return出去需要的值 def salt_command(self, tat, method, arg=None): #执行发送请求, 传入的时候有地三个参数就传arg,没有就none if arg: #params = {'client': 'local', 'fun': method, 'tgt': tgt, 'arg':arg, 'expr_form':'list'} params = {'client': 'local', 'fun': method, 'tgt': tgt, 'arg':arg,} else: params = {'client': 'local', 'fun': method, 'tgt': tgt} #这是salt的规则,必须是这样 result = self.get_data(self.url, params) # 往 get_data方法里传入两个值,url和params 的字典 return result def main(): salt = SaltApi(salt_api) #实例化对象 salt_client = 'hc-02' # 这里是 minion端的ID 多个值可以是 ['*'] selt_test = 'test.ping' # 这是 测试主机连通性,是salt执行的命令 #selt_test = 'grains.items' # 这个 items 抓了很多数据 selt_method = 'grains.get' # 抓主机信息 #salt_method = 'cmd.run' # 执行shell命令 #salt_method = 'disk.usage' # salt 'hc-02' disk.usage 取磁盘信息 #salt_method = 'svn.update' # 要使用SVN的模块 salt 'hc-02' svn.update selt_params = ['ip_interfaces', ] # ip网卡信息 #salt_params = ['ip_interfaces', 'hwaddr_interfaces'] # 取 IP网卡 和 MAC地址 #salt_params = 'free -m' # salt 'hc-02' cmd.run 'free -m' 取内存信息 #salt_params = ['free -m', ] #salt_params = ['/', '/data/www', 'root', ] result1 = salt.salt_command(salt_client, salt_test) # 传入两个值, 一个是minion的ID值 'hc-02' ,二个是salt执行的命令 'test.ping' print (result1) # result1 传入的是 test.ping ,返回的是命令的返回值 result2 = salt.salt_command(salt_client, salt_method, salt_params) print (result2) # result2 传入的是 grains.get ip_interfaces ,返回的我主机的ip网卡信息 if __name__ == '__main__': main()
测试命令 salt 'hc-03' grains.get ip_interfaces
[root@hc master.d]# salt 'hc-03' grains.get ip_interfaces hc-03: ---------- eth0: - 192.168.1.15 eth1: lo: - 127.0.0.1 # 做了桥接网卡的 需要特殊处理 [root@hc master.d]# salt 'hc-02' grains.get ip_interfaces hc-02: ---------- br0: - 192.168.1.9 - fe80::ca60:ff:fe8e:6f93 eth0: - fe80::ca60:ff:fe8e:6f93 eth1: lo: - 127.0.0.1 - ::1 vnet0: - fe80::fc54:ff:fe1d:dab6 #返回的我主机的ip网卡信息
importlib反射
唯一标识问题
IP 和 minion 端的ID 对应
minion 端的ID 是在master里 是唯一的
salt 'hc-02' grains.items -> 返回的是一个字典。
通过 grains.get key 取到KEY的值
salt 'hc-02' grains.get ip_interfaces -> 通过 grains.get -> key 是 ip_interfaces 取到KEY的值
satl 'hc-02' cmd.run 'ls /root' -> 通过执行shell命令执行
中国SaltStack用户组 网站 学习使用salt-stack 命令
http://www.saltstack.cn/
面向对象python2-3的区别
-
python 2 类 不写 object 就是 经典类 ,写 object 就是 新式类
-
python 3 类 写不写object 都是 新式类
-
他们不一样的地方在 继承时候的顺序不一样
优先级 深度优先 和 广度优先
python 2 类
- 不继承 object 经典类 -> 深度优先
- 继承 object 新式类 -> 并非广度优先 通过C3算法 变成类似于广度优先
python 3 类 : 无论继不继承 object 都是 新式类
-
新式类,继承优先级:c3算法,非即从左到右,去继承
-
所有不管是python2 还是python3 写类的时候都要写 object
importlib 反射代码
import importlib
import os, sys
Host_func_dic = {
'disk': 'func.hosts.disk.Disk', 'cpu': 'func.hosts.cpu.Cpu', 'mem': 'func.hosts.mem.Men', } # 写个字典 path = Host_func_dic.get('disk') #path == 'func.hosts.disk.Disk' module_path, class_name = path.resplit('.', maxsplit=1) # resplit右边开始分割,只分割1次, print ( module_path ) -> func.hosts.disk print ( class_name ) -> Disk module = importlib.import_module(module_path) # 这里相当于 form ... import # from func.hosts import disk 上的代码执行完 就是这样的 导入 # module 实质上就是导入了这个模块 disk_class = getattr(module, class_name) # getattr 反射 还有 hasattr反射判断 setattr 反射赋值 delattr 反射删值 # disk_class = getattr(disk, 'Disk') #getattr 反射的作用就是, # 这里的module 就是具体导入的文件名, disk #如果在disk文件里或者这个变量里,有后面这个字符串'Disk'的内容,就会反射出来这个'Disk', # Disk是可以当命令来执行的 JG = disk_class() #实例化 JG.run() # 执行这个对象方法
1. 这样 就是批量执行的方法
2. 基于 importlib 和 getattr 方法
3. 把字符串 通过一定的格式用 importlib 导入,再用getattr 反射取值的方法 那对对应的类名。
4. 最后通过实例化对象 即可执行 他们统一的方法!
import importlib import os, sys Host_func_dic = { 'disk': 'func.hosts.disk.Disk', 'cpu': 'func.hosts.cpu.Cpu', 'mem': 'func.hosts.mem.Men', 'ip': 'func.hosts.ip.IP', } # 写个字典 #path = Host_func_dic.get('disk') # path == 'func.hosts.disk.Disk' host_list = ['cpu', 'disk', 'mem', 'ip'] for i in host_list: path = Host_func_dic.get( i ) module_path, class_name = path.resplit('.', maxsplit=1) # resplit右边开始分割,只分割1次, module = importlib.import_module(module_path) # 这里相当于 form ... import # from func.hosts import disk 上的代码执行完 就是这样的 导入 # module 实质上就是导入了这个模块 disk_class = getattr(module, class_name) # getattr 反射 还有 hasattr反射判断 setattr 反射赋值 delattr 反射删值 # disk_class = getattr(disk, 'Disk') # getattr 反射的作用就是, # 这里的module 就是具体导入的文件名, disk # 如果在disk文件里或者这个变量里,有后面这个字符串'Disk'的内容,就会反射出来这个'Disk', # Disk是可以当命令来执行的 JG = disk_class() #实例化 JG.run() # 执行这个对象方法 ,执行他们通用方法,方法必须统一
客户端
客户端批量执行实例
settins.py
# 把配置文件,同一放在一个文件中,好做修改, Host_func_dic = { 'disk': 'func.hosts.disk.Disk', 'cpu': 'func.hosts.cpu.Cpu', 'mem': 'func.hosts.mem.Men', 'ip': 'func.hosts.ip.IP', } # 写个字典 #path = Host_func_dic.get('disk') # path == 'func.hosts.disk.Disk' host_list = ['cpu', 'disk', 'mem', 'ip']
demo_run.py
import importlib improt os, sys # 将文件加入环境变量, BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(BASEDIR) #再 from ... import ... 导入 form settings import Host_func_dic , host_list for i in host_list: path = Host_func_dic.get( i ) module_path, class_name = path.resplit('.', maxsplit=1) # resplit右边开始分割,只分割1次, module = importlib.import_module(module_path) # 这里相当于 form ... import # from func.hosts import disk 上的代码执行完 就是这样的 导入 # module 实质上就是导入了这个模块 disk_class = getattr(module, class_name) # getattr 反射, hasattr反射判断,setattr 反射赋值,delattr 反射删值 # disk_class = getattr(disk, 'Disk') #getattr 反射的作用就是, # 这里的module 就是具体导入的文件名, disk #如果在disk文件里或者这个变量里,有后面这个字符串'Disk'的内容 #就会反射出来这个'Disk',Disk是可以当命令来执行的 JG = disk_class() #实例化 JG.run() # 执行这个对象方法 ,执行他们通用方法,方法必须统一
func/main.py
# 做总入口继承,当没有run方法时,直接报错 Class Main(object): def __init__(self) self.method = '' self.tgt = '' selg.arg = '' def run(self): #当没有run方法时,直接报错 raise KeyError('this class not run functions') #raise NotImplementedError('this class not run functions') def windows(self): pass def linux(self): pass
func/mem.py
# 下面的都做salt命令的拼接,并return出去 from func import Main Class Mem(Mian) def run(self) self.method = 'cmd.run' self.tgt = 'hc-02' self.agr = 'free -m' #print ('run...mem') retrun {'client':'local', 'func':self.method, 'tgt':self.tgt, 'arg':self.arg}
func/cpu.py
from func import Main
Class Cpu(Mian)
def run(self) self.method = 'cmd.run' self.tgt = 'hc-02' self.agr = 'cat /proc/cpuinfo' #print ('run...cpu') retrun {'client':'local', 'func':self.method, 'tgt':self.tgt, 'arg':self.arg}
func/disk.py
from func import Main
Class Disk(Mian)
def run(self) self.method = 'cmd.run' self.tgt = 'hc-02' self.agr = 'df -h' #print ('run...disk') retrun {'client':'local', 'func':self.method, 'tgt':self.tgt, 'arg':self.arg}
func/ip.py
from func import Main
Class Ip(Mian)
pass
def run(self) self.method = 'cmd.run' self.tgt = 'hc-02' self.agr = 'ip addr' #print ('run...ip') retrun {'client':'local', 'func':self.method, 'tgt':self.tgt, 'arg':self.arg}
客户端请求数据流程图

后续还要不断优化代码
到时再更新.....