CMDB 资产管理
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}
客户端请求数据流程图

后续还要不断优化代码
到时再更新.....
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· DeepSeek 解答了困扰我五年的技术问题。时代确实变了!
· PPT革命!DeepSeek+Kimi=N小时工作5分钟完成?
· What?废柴, 还在本地部署DeepSeek吗?Are you kidding?
· DeepSeek企业级部署实战指南:从服务器选型到Dify私有化落地
· 程序员转型AI:行业分析