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
,存在多个外键字段,其中idc
、cabinet
允许为空,manufacturer
、model_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"}
五.遍历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"}
七. 数据存在时候更新数据
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 也是个列表,特殊的列表
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()
last_device_inst_qs: <QuerySet [<NetworkDevice: eth0>, <NetworkDevice: eth1>]>
eth1
八. 增加网卡 增加IP都可以
九.存在问题
- 1.管理卡IP采集不到,唯一键
- 2.如果数据都没有变化,根本不需要提交数据更改
- 3.变更的数据前后记录没有记录下来