03.主机资源采集反序列化

一.models

class Manufacturer(models.Model):
    vendor_name = models.CharField("厂商名称", max_length=32, db_index=True, unique=True, help_text="厂商名称")
    tel = models.CharField("联系电话", null=True, max_length=15, help_text="联系电话")
    mail = models.CharField("联系邮件", null=True, max_length=32, help_text="联系邮件")
    remark = models.CharField("备注", max_length=300, null=True, help_text="备注")

    def __str__(self):
        return self.vendor_name

    class Meta:
        db_table = "resources_manufacturer"
        ordering = ["id"]


class ProductModel(models.Model):
    model_name = models.CharField("型号名称", max_length=20, help_text="型号名称")
    vendor = models.ForeignKey(Manufacturer, verbose_name="所属制造商", help_text="所属制造商",on_delete=models.CASCADE)

    def __str__(self):
        return self.model_name

    class Meta:
        db_table = "resources_productmodel"
        ordering = ["id"]


class Server(models.Model):
    ip = models.CharField("管理ip", max_length=15, db_index=True, unique=True, help_text="管理ip")
    hostname = models.CharField("主机名", max_length=20, db_index=True, unique=True, help_text="主机名")
    cpu = models.CharField("CPU", max_length=50, help_text="CPU")
    mem = models.CharField("内存", max_length=32, help_text="内存")
    disk = models.CharField("磁盘", max_length=200, help_text="磁盘")
    os = models.CharField("OS", max_length=50, help_text="OS")
    sn = models.CharField("SN", max_length=50, db_index=True, help_text="SN")
    manufacturer = models.ForeignKey(Manufacturer, verbose_name="制造商", help_text="制造商",on_delete=models.CASCADE)
    model_name = models.ForeignKey(ProductModel, verbose_name="服务型号", help_text="服务器型号",on_delete=models.CASCADE)
    rmt_card_ip = models.CharField("管理管理卡IP", max_length=15, db_index=True, unique=True, help_text="管理管理卡IP")
    idc = models.ForeignKey(Idc, null=True, verbose_name="所在机房", help_text="所在机房",on_delete=models.CASCADE)
    cabinet = models.ForeignKey(Cabinet, null=True, verbose_name="所在机柜", help_text="所在机柜",on_delete=models.CASCADE)
    cabinet_position = models.CharField("机柜内位置", null=True, max_length=20, help_text="机柜内位置")
    uuid = models.CharField("UUID", db_index=True, unique=True, max_length=50, help_text="UUID")
    last_check = models.DateTimeField("检测时间", db_index=True, auto_now=True, help_text="检测时间")
    remark = models.CharField("备注", max_length=200, help_text="备注", null=True)

    def __str__(self):
        return self.ip

    class Meta:
        db_table = "resources_server"
        ordering = ["id"]
        # permissions = (
        #     ("view_server", "can view server"),
        # )


class NetworkDevice(models.Model):
    """
    网卡模型
    """
    name = models.CharField("网卡设备名", max_length=20, help_text="网卡设备名")
    mac_address = models.CharField("MAC地址", max_length=30, help_text="MAC地址")
    host = models.ForeignKey(Server, verbose_name="所在服务器", help_text="所在服务器",on_delete=models.CASCADE)
    remark = models.CharField("备注", max_length=200, help_text="备注", null=True)

    def __str__(self):
        return self.name

    class Meta:
        db_table = "resources_network_device"
        ordering = ["id"]


class IP(models.Model):
    """
    IP模型
    """
    ip_addr = models.CharField("ip地址", max_length=15, db_index=True, unique=True, help_text="ip地址")
    netmask = models.CharField("子网掩码", max_length=15, help_text="子网掩码")
    device = models.ForeignKey(NetworkDevice, verbose_name="所在网卡", help_text="所在网卡",on_delete=models.CASCADE)
    remark = models.CharField("备注", max_length=200, help_text="备注", null=True)

    def __str__(self):
        return self.ip_addr

    class Meta:
        db_table = "resources_ip"
        ordering = ["id"]

说明
  • 1.厂家表Manufacturer,可直接反序列化
  • 2.厂家设备表ProductModel,存在外键,反序列化时需要传入外键表的实例化对象
  • 3.服务器表Server,存在多个外键字段,其中idccabinet允许为空,manufacturermodel_name不允许为空,反序列化时要先取这两个字段的实例
  • 4.反序列化ProductModel表时,需要先反序列化Manufacturer获取实例化对象

二.来个最简单的,先反序列化创建一个主机,数据如下

{
    "ip": "192.168.1.1",
    "hostname": "yz_cabinet_row5_01",
    "cpu": "32",
    "mem": "100G",
    "disk": "1000G",
    "os": "Centos 7.5",
    "sn": "54443398-17a9-4b4e-b536-7edac0a7c8e7",
    "manufacturer": "Oracle Corporation",
    "model_name": "VirtualBox",
    "uuid": "54443398-17a9-4b4e-b536-7edac0a7c8e7"
}
  • 2.1 serializer 数据
class ServerAutoReportSerializer(serializers.Serializer):
    """
    服务器同步序列化类
    """
    ip = serializers.IPAddressField(required=True)
    hostname = serializers.CharField(required=True, max_length=20)
    cpu = serializers.CharField(required=True, max_length=50)
    mem = serializers.CharField(required=True, max_length=20)
    disk = serializers.CharField(required=True, max_length=200)
    os = serializers.CharField(required=True, max_length=50)
    sn = serializers.CharField(required=True, max_length=50)
    manufacturer = serializers.CharField(required=True)
    model_name = serializers.CharField(required=True)
    uuid = serializers.CharField(required=True, max_length=50)
  • 2.2 重写 create 方法
    def create(self, validated_data):
        Server.objects.create(**validated_data) 

发现需要传入 manufacturer model_name 实例化对象才能完成 ORM的 save 操作

  • 2.3 在校验单个字段时,完成manufacturer字段实例传入
def validate_manufacturer(self,manufacturer):
        try:
            manufacturer_inst = Manufacturer.objects.get(vendor_name=manufacturer)
        except Manufacturer.DoesNotExist:
            manufacturer_inst = Manufacturer.objects.create(vendor_name=manufacturer)
        # print(manufacturer_inst,type(manufacturer_inst))
        return manufacturer_inst
  • 2.4 在总校验时,完成model_name字段实例的传入
    def validate(self, attrs):
        model_name = attrs['model_name']
        manfacturer_inst = attrs['manufacturer']
        try:
            produce_inst = manfacturer_inst.productmodel_set.get(model_name=model_name)
            # 这里需要注意,需要通过父级实例利用model_set反查子实例是否存在数据,反查返回的是被反查的对象实例
        except ProductModel.DoesNotExist:
            produce_inst = ProductModel.objects.create(vendor=manfacturer_inst,model_name=model_name)
        attrs['model_name']=produce_inst
        print(attrs)
        return attrs
  • 2.4 打印发现attrs已经是这两个字段的实例对象
OrderedDict([('ip', '192.168.1.1'), ('hostname', 'yz_cabinet_row5_01'), ('cpu', '32'), ('mem', '100G'), ('disk', '1000G'), ('os', 'Centos 7.5'), ('sn', '54443398-17a9-4b4e-b536-7edac0a7c8e7'), ('manufacturer', <Manufacturer: Oracle Corporation>), ('model_name', <ProductModel: VirtualBox>), ('uuid', '54443398-17a9-4b4e-b536-7edac0a7c8e7')])
  • 2.5 此时直接在create中完成主机的创建
Server.objects.create(**validated_data) 

三. 自动采集的话,都是post数据,可以在create方法中同时完成更新动作

第一次采集,写本地文件的方式确定唯一更可取,判断的话可能需要枚举所有虚拟环境的uuid和sn情况

    def create(self, validated_data):
        uuid = validated_data['uuid'].lower()
        sn = validated_data['sn'].lower()
        try:
            if sn == uuid or sn =='' or sn.startswith('vmware'):
                server_inst = Server.objects.get(uuid=uuid)
                # 虚拟机
            else:
                # 物理机
                server_inst = Server.objects.get(sn=sn)
        except Server.DoesNotExist:
            return self.create_server(validated_data)
        else:
            return self.update_server(server_inst, validated_data)

四.反序列化增加不在model中的字段,serializer如下

    ip = serializers.IPAddressField(required=True)
    hostname = serializers.CharField(required=True, max_length=20)
    cpu = serializers.CharField(required=True, max_length=50)
    mem = serializers.CharField(required=True, max_length=20)
    disk = serializers.CharField(required=True, max_length=200)
    os = serializers.CharField(required=True, max_length=50)
    sn = serializers.CharField(required=True, max_length=50)
    manufacturer = serializers.CharField(required=True)
    model_name = serializers.CharField(required=True)
    uuid = serializers.CharField(required=True, max_length=50)
    network = serializers.JSONField()  # 这个是额外增加的

增加一个json字段,携带network信息,多网卡,一张网卡多IP,post 数据如下

{
    "ip": "192.168.1.1",
    "hostname": "yz_cabinet_row5_01",
    "cpu": "32",
    "mem": "100G",
    "disk": "1000G",
    "os": "Centos 7.5",
    "sn": "54443398-17a9-4b4e-b536-7edac0a7c8e7",
    "manufacturer": "Oracle Corporation",
    "model_name": "VirtualBox",
    "uuid": "54443398-17a9-4b4e-b536-7edac0a7c8e7",
    "network": [
		{
			"name":"eth0",
			"ips":[
				{"ip_addr":"192.168.1.1","netmask":"255.255.255.0"}
			],
			"mac":"08:00:27:9b:99:bc"
		},
				{
			"name":"eth1",
			"ips":[
				{"ip_addr":"192.168.1.2","netmask":"255.255.255.0"},
				{"ip_addr":"192.168.1.3","netmask":"255.255.255.0"}
			],
			"mac":"08:00:27:9b:99:b1"
		}
	]
}
思路:在真正创建 server 后,拿着 server 的实例去创建网卡,拿着网卡的实例,去创建IP数据对象
  • 4.1 在create中增加网卡的处理
    def create_server(self,validated_data):
        network = validated_data.pop('network')
        server_inst = Server.objects.create(**validated_data)
        self.check_network_device(server_inst,network)
        return server_inst

    def check_network_device(self,server_inst,network):
        print('处理network:',server_inst,type(server_inst),network)
  • 4.2 post 之后已经能获取到network的反序列化数据
处理network: 192.168.1.1 <class 'servers.models.Server'> [{'name': 'eth0', 'ips': [{'ip_addr': '192.168.1.1', 'netmask': '255.255.255.0'}], 'mac': '08:00:27:9b:99:bc'}, {'name': 'eth1', 'ips': [{'ip_addr': '192.168.1.1', 'netmask': '255.255.255.0'}], 'mac': '08:00:27:9b:99:b1'}, {'name': 'eth2', 'ips': [{'ip_addr': '192.168.1.2', 'netmask': '255.255.255.0'}], 'mac': '08:00:27:9b:99:b2'}]
  • 4.3 但是会报错,因为序列化的时候这个network根本不存在模型中
AttributeError: 'Server' object has no attribute 'network'
  • 4.4 post 之后 drf 会调用to_representation函数,重写之
    def to_representation(self, instance):
        ret = {
            "hostname": instance.hostname,
            "ip": instance.ip
        }
        return ret
  • 4.5 此时返回
{"hostname":"yz_cabinet_row5_01","ip":"192.168.1.1"}

image

五.遍历network变量,创建网卡

    def check_network_device(self,server_inst,network):
        print('处理network:',server_inst,type(server_inst),network)
        # 创建网卡
        for device in network:
            ips = device.pop('ips')
            try:
                device_inst = server_inst.networkdevice_set.get(name=device['mac'])
            except NetworkDevice.DoesNotExist:
                device['host'] = server_inst
                device['mac_address'] = device.pop('mac')
                device_inst  = NetworkDevice.objects.create(**device)
            # 创建完网卡,需要创建IP
            self.check_ip(device_inst,ips)

    def check_ip(self,device_inst,ips):
        print(device_inst,ips)

六. 遍历 ip,创建IP

    def check_ip(self,device_inst,ips):
        print(device_inst,ips)
        for ip in ips:
            try:
                ip_inst = device_inst.ip_set.get(ip_addr=ip['ip_addr'])
            except IP.DoesNotExist:
                ip['device'] = device_inst
                ip_inst = IP.objects.create(**ip)

此时 主机、网卡、IP 都可以创建
{"hostname":"yz_cabinet_row5_01","ip":"192.168.1.1"}

image
image
image

七. 数据存在时候更新数据

7.1 第一种情况,IP更改了
  • 7.1.1 使用集合,删除多余的IP,提交输入如下,一张网卡少了1个IP
{
    "ip": "192.168.1.1",
    "hostname": "yz_cabinet_row5_01",
    "cpu": "32",
    "mem": "100G",
    "disk": "1000G",
    "os": "Centos 7.5",
    "sn": "54443398-17a9-4b4e-b536-7edac0a7c8e7",
    "manufacturer": "Oracle Corporation",
    "model_name": "VirtualBox",
    "uuid": "54443398-17a9-4b4e-b536-7edac0a7c8e7",
    "network": [
		{
			"name":"eth0",
			"ips":[
				{"ip_addr":"192.168.1.1","netmask":"255.255.255.0"}
			],
			"mac":"08:00:27:9b:99:bc"
		},
				{
			"name":"eth1",
			"ips":[
				{"ip_addr":"192.168.1.2","netmask":"255.255.255.0"}
			],
			"mac":"08:00:27:9b:99:b1"
		}
	]
}
  • 7.1.2 保存数据库的IP集合,与当前采集的IP集合做差集
    def check_ip(self,device_inst,ips):
        # 不管三七二一,先把数据库的IP集合临存
        last_ip_qs= device_inst.ip_set.all()
        # 用个列表把当前采集的IP集合起来
        current_ip = []
        for ip in ips:
            try:
                ip_inst = last_ip_qs.get(ip_addr=ip['ip_addr'])

            except IP.DoesNotExist:
                ip['device'] = device_inst
                ip_inst = IP.objects.create(**ip)
            current_ip.append(ip_inst)
        for ip_inst in set(last_ip_qs) - set(current_ip):
            print(ip_inst)
            # ip_inst.delete()

输出如下:

<class 'django.db.models.query.QuerySet'> <class 'list'>
192.168.1.3

queryset 也是个列表,特殊的列表

image

7.2 第二种情况,删除一个网卡
{
    "ip": "192.168.1.1",
    "hostname": "yz_cabinet_row5_01",
    "cpu": "32",
    "mem": "100G",
    "disk": "1000G",
    "os": "Centos 7.5",
    "sn": "54443398-17a9-4b4e-b536-7edac0a7c8e7",
    "manufacturer": "Oracle Corporation",
    "model_name": "VirtualBox",
    "uuid": "54443398-17a9-4b4e-b536-7edac0a7c8e7",
    "network": [
		{
			"name":"eth0",
			"ips":[
				{"ip_addr":"192.168.1.1","netmask":"255.255.255.0"}
			],
			"mac":"08:00:27:9b:99:bc"
		}
	]
}
  • 代码如下
    def check_network_device(self,server_inst,network):
        last_device_inst_qs = server_inst.networkdevice_set.all()
        current_device = []
        for device in network:
            ips = device.pop('ips')
            try:
                device_inst = last_device_inst_qs.get(mac_address=device['mac'])

            except NetworkDevice.DoesNotExist:
                device['host'] = server_inst
                device['mac_address'] = device.pop('mac')
                device_inst  = NetworkDevice.objects.create(**device)
            # 创建完网卡,需要创建IP
            self.check_ip(device_inst,ips)
            current_device.append(device_inst)
        for device_inst in set(last_device_inst_qs) - set(current_device):
            print(device_inst)
            device_inst.delete()

image

last_device_inst_qs: <QuerySet [<NetworkDevice: eth0>, <NetworkDevice: eth1>]>
eth1

八. 增加网卡 增加IP都可以

image
image

九.存在问题

  • 1.管理卡IP采集不到,唯一键
  • 2.如果数据都没有变化,根本不需要提交数据更改
  • 3.变更的数据前后记录没有记录下来
posted @ 2020-05-19 23:32  Jenvid  阅读(226)  评论(0编辑  收藏  举报