CMDB 资产管理

CMDB 资产管理

环境 : Linux 7.4  Django 1.11+  Python 3.6

CMDB

 

 
CMDB工作流程图.png

代码的发布/监控/堡垒机
资产管理。

  1. 代替execl,将execl表里的内容导入我们系统中
  2. 和其他系统交互 通过 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

  1. 入库。
  2. 客户端上传的格式进行解析。

按钮
和add 操作一样。url去获取工具拿到的数据

 
CMDB客服端-流程图1
 

客户端

底层 -> 封装 + 结合 -> 优化

CMDB客服端-流程图2
 
 

客户端底层模式执行方式讲解
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

  1. master -> 部署 服务端 1次

  2. api -> 部署 服务端 1次

  3. 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的区别

  1. python 2 类 不写 object 就是 经典类 ,写 object 就是 新式类

  2. python 3 类 写不写object 都是 新式类

  1. 他们不一样的地方在 继承时候的顺序不一样

    优先级 深度优先 和 广度优先

python 2 类

  1. 不继承 object 经典类 -> 深度优先
  2. 继承 object 新式类 -> 并非广度优先 通过C3算法 变成类似于广度优先

python 3 类 : 无论继不继承 object 都是 新式类

  1. 新式类,继承优先级:c3算法,非即从左到右,去继承

  2. 所有不管是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}


客户端请求数据流程图

数据一键获取流程图
 

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

 

 
 
posted @ 2019-04-23 18:07  H·c  阅读(1013)  评论(4编辑  收藏  举报