一.6.序列化应用之服务器同步功能

 一.服务器同步

  采集服务器信息是此运维平台基础的功能之一,可通过saltstack/ansible等采集,并把收集的信息整理成如下的测试数据样的json格式,然后提交到运维平台的某个接口上就可以了,这个接口拿到数据后怎么处理才是重点。

服务器同步伪代码分析:
需求: 从服务器上采集数据,并整理成想要的格式,然后通过api插入到数据库中(数据库中无此条数据:那需要新增加一条完整的数据。有此条数据则类型巡检,那更新下服务器此条记录的部分字段
--哪些字段可更新哪些字段不可更新,同时要修改它的时间来保证从运维平台上可看到此服务器新况)。 采集服务器数据,并同步到运维平台: 1.api接口拿到数据 2.检查mysql中有无此条记录 怎么检查: ip-->经常变,所以不适合。 SN-->物理机有,虚拟机没有(云服务器有,它的SN就是uuid,但vmware/virtbox都没有)。 uuid--->针对虚拟机 所以在数据库里查询有没有此台服务器关键就是uuid/SN.所以判断依据是:如果SN=uuid那此机为虚拟机 3.获取这条记录: (1)存在这条记录,则如下处理法: 则更新数据:hostname ip os mem disk cpu network 注意:network: 存在多网卡,多ip的变动 (2)不存在这条记录,则如下处理法: 先层层验证 创建这条记录--如下这些都需全新创建并关联上彼此间关系--先创建一的对象再创建多的对象(一对多): server记录 服务器厂商记录 服务器型号记录 网卡记录 ip记录 注意创建顺序(按foreginkey所在模型的foreginkey字段允许不允许为空),若不允许为空(即此字段为必填)则先创建多的对象: 先厂商(无任何foreignkey依赖)--再型号(关联厂商)--ip(关联/指向网卡)--网卡(关联/指向服务器)--再服务器 验证条件: 厂商无任何依赖,所以可在自定义字段时就做验证--把厂商转成instance 型号因需一厂商对象,所以需在对象级别/整个表级别时做验证

(1)(python36env) [vagrant@CentOS7 apps]$ django-admin startapp servers

(2)激活  'servers.apps.ServersConfig'

(3)模型servers/models.py:

from django.db import models
from manufacturer.models import Manufacturer, ProductModel
from idcs.models import Idc
from cabinet.models import Cabinet

#制造商(一)与服务器间(多):foreignkey放在多方
#sn是物理机的唯一标识,uuid是虚拟机的唯一标识
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"]

(python36env) [vagrant@CentOS7 devops]$ python manage.py makemigrations servers

(python36env) [vagrant@CentOS7 devops]$ python manage.py migrate servers

在服务器上用一脚本把上述表的那些数据信息采集出来准备成一个json,并丢给我的运维平台去同步。

(4)序列化servers/serializers.py:

from rest_framework import serializers
from .models import Manufacturer, ProductModel
from rest_framework import  serializers
from .models import Server,NetworkDevice,IP

#这是第一步验证:因为涉及到多张表,所以使用模型序列化不合适(模型序列化它会去校验你的每一字段必须在模型中存在,而我这里的网卡,ip字段是不存在的,都有关联关系),就用序列化--则每个字段要定义一遍
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)

#注意提交过来的上述数据中manufacturer是一个普通字符串数据,所以可在验证时就转换--把提交过来的字符串数据转换成对象
#这是第二步验证:自定义字段级别验证--验证传过来的这个制造商数据存在制造商表中否--此方法会返回一个制造商的对象出来
    def validate_manufacturer(self, value):
        #存在则直接返回制造商对象,不存在则创建
        try:
            return Manufacturer.objects.get(vendor_name__exact=value)
        except Manufacturer.DoesNotExist:
            return self.create_manufacturer(value)
#这是第三步验证:
    def validate(self, attrs):
        #拿到一制造商对象
        manufacturer_obj = attrs["manufacturer"]
        #拿到制造商表中制造商所有型号并验证传来的制造商是否存在表中:
        try:
            attrs["model_name"] = manufacturer_obj.productmodel_set.get(model_name__exact=attrs["model_name"])
        #没则报此异常并去创建:
        except ProductModel.DoesNotExist:
            attrs["model_name"] = self.create_product_model(manufacturer_obj, attrs["model_name"])
        return attrs
    #此时的数据是已经经过验证的数据了:
    def create(self, validated_data):
        return Server.objects.create(**validated_data)

    # 创建一个制造商记录(拿着传过来的制造商名字就创建一制造商记录)---它会返回一制造商对象来--注意这manufacturer这张表只人vendor是不为空,其它字段都可为空:
    def create_manufacturer(self, vendor_name):
        return Manufacturer.objects.create(vendor_name=vendor_name)
    #同理:传一制造商和模型
    def create_product_model(self,manufacturer_obj, model_name):
        return ProductModel.objects.create(model_name=model_name, vendor=manufacturer_obj)

class ServerSerializer(serializers.ModelSerializer):
    """
    服务器序列化类
    """
    class Meta:
        model =  Server
        fields = "__all__"

class NetworkDeviceSerializer(serializers.ModelSerializer):
    """
    网卡序列化
    """
    class Meta:
        model =  NetworkDevice
        fields = "__all__"

class IPSerializer(serializers.ModelSerializer):
    """
    IP序列化
    """
    class Meta:
        model =  IP
        fields = "__all__"

(5)视图servers/views.py:

from django.shortcuts import render

# Create your views here.
from rest_framework import viewsets, mixins
from .models import Server,NetworkDevice,IP
from .serializers import ServerAutoReportSerializer,NetworkDeviceSerializer,IPSerializer, ServerSerializer
# from django_filters.rest_framework import DjangoFilterBackend
# from .filter import ServerFilter

#服务器们自动同步接--只支持POST数据(如准备好一脚本每10分钟同步服务器数据一次)--这里已服务器为准,服务器数据变更,我运维平台立即变更
class ServerAutoReportViewset(mixins.CreateModelMixin,viewsets.GenericViewSet):
    """
    create:
        创建一个服务器
    """
    queryset = Server.objects.all()
    serializer_class = ServerAutoReportSerializer
class ServerViewset(viewsets.ReadOnlyModelViewSet):
    """
    list:
        列出所有服务器信息
    retrieve:
        读取一个服务器信息
    """
    queryset = Server.objects.all()
    serializer_class = ServerSerializer

#网卡是只读的
class NetworkDeviceViewset(viewsets.ReadOnlyModelViewSet):
    """
    list:
        列出所有网卡信息
    retrieve:
        读取一个网卡信息
    """
    queryset = NetworkDevice.objects.all()
    serializer_class = NetworkDeviceSerializer


#ip是只读的
class IPViewset(viewsets.ReadOnlyModelViewSet):
    """
    retrieve:
        返回一个ip信息
    list:
        列出所有IP信息
    """
    queryset = IP.objects.all()
    serializer_class = IPSerializer

(6)路由urls.py:

from django.conf.urls import include, url
from django.contrib import admin
from rest_framework.routers import DefaultRouter
from idcs.views import IdcViewset
from users.views import UserViewset
from cabinet.views import CabinetViewset
from manufacturer.views import ManufacturerViewset,ProductModelViewset
from rest_framework.documentation import include_docs_urls
from servers.views import ServerAutoReportViewset,NetworkDeviceViewset,IPViewset,ServerViewset

route = DefaultRouter()
route.register("idcs", IdcViewset, basename="idcs")
#注册时三个参数:资源定位符,类,别名
route.register("users", UserViewset, basename="users")
route.register("cabinet", CabinetViewset, basename="cabinet")
route.register("manufacturer", ManufacturerViewset, basename="manufacturer")
route.register("productmodel", ProductModelViewset, basename="productmodel")
route.register("ServerAutoReport", ServerAutoReportViewset, basename="ServerAutoReport")
# route.register("Servers", ServerViewset, base_name="Servers")
route.register("NetworkDevice", NetworkDeviceViewset, basename="NetworkDevice")
route.register("IP", IPViewset, basename="IP")
route.register("Servers", ServerViewset, basename="Servers")
urlpatterns = [
    url(r'^', include(route.urls)),
    url(r'^docs/', include_docs_urls("lizhihua运维平台接口文档"))
]

最终如下:

测试:取到服务器以下数据并到测试页面提交测试:

(python36env) [vagrant@CentOS7 devops]$ cat /proc/cpuinfo ---取cpu
(python36env) [vagrant@CentOS7 devops]$ sudo dmidecode --string system-uuid  取uuid
(python36env) [vagrant@CentOS7 devops]$ sudo dmidecode --string baseboard-manufacturer 取制造商
(python36env) [vagrant@CentOS7 devops]$ sudo dmidecode --string baseboard-product-name 取产品型号
最终取到如下的数据:

{
    "ip": "192.168.1.2",
    "hostname": "yz-ms-web-01",
    "cpu": "Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz 1",
    "mem": "2G",
    "disk": "100G",
    "os": "centos 7",
    "sn": "B49479B2-D774-4B09-B7B9-F29384350C01",
    "manufacturer": "Oracle Corporation",
    "model_name": "VirtualBox",
    "uuid": "B49479B2-D774-4B09-B7B9-F29384350C01"
}

 

 效果如下成功了:

二.添加服务器之处理网卡

上述效果中还没有处理网卡,如下图中:网卡是一个列表,列表中有多条记录,每条记录是一个设备,此网卡设备包含设备名,ip(可能有多个,所以它是一个列表存放在ip_addr这个数据中),子网掩码,mac地址。这样就可以处理了,是我想要的数据,但注意这不是模型想要的数据,得经过处理,否则无法直接添加。

 准备序列化servers/seriliaizers.py:

1添加网卡字段:它的数据类型是json

class ServerAutoReportSerializer(serializers.Serializer):
        #添加网卡
        network = serializers.JSONField(required=True)

2验证:

(1)先取到网卡

    def validate(self, attrs):
        #取到网卡对象
        network = attrs["network"]
        #取完后一定要删除网卡,因为到下面的create方法,交给Server,server发现里面有一个叫network的字段,服务器表没有此字段,所以会报错
        del attrs["network"]

(2)开始处理网卡:处理归处理,怎样关联上才是最重要的。

  因为服务器创建完后Server.objects.create(**validated_data)才拿到实例,而服务器要跟网卡设备进行关联上(因为它们是一对多关系)---可以在验证网卡时(network字段上)把所有数据给转换成实例然后返回甚至说转换成几个网卡的实例或者queryset集合,但最终这个queryset网卡要跟我们服务器进行关联上(一对多关系),准确的来说是在create方法中才能关联上。

  所以create方法中还要传一个把network转换成一个queryset集合/列表,

    def create(self, validated_data):
        #把network的集合传进来:
        network_queryset = validated_data.pop("network")
        #创建服务器对象记录:
        server_obj = Server.objects.create(**validated_data)
        #给它设置一个集合(给它一个列表就可)--给服务器与网卡做一对多关联关系:
        server_obj.networkdevice_set = network_queryset
        return server_obj

 方式二:或者在创建server时不用管它的网卡字段,但是在创建网卡时一定要传host字段对象,也就是说可用如下方法:做检查------检查我这个服务器有没有你指定的这些网卡network设备并做关联.首先得确定一个问题,这个network是现在是一json数据/字典,然后我需要把我的网卡转换成network_device实例---在(两个地方可转换,字段验证和check时可转换),转换时mac地址作它的唯一标识,

servers.serilaizers.py中:

from rest_framework import serializers
from .models import Manufacturer, ProductModel
from rest_framework import  serializers
from .models import Server,NetworkDevice,IP

#这是第一步验证:因为涉及到多张表,所以使用模型序列化不合适(模型序列化它会去校验你的每一字段必须在模型中存在,而我这里的网卡,ip字段是不存在的,都有关联关系),就用序列化--则每个字段要定义一遍
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)
    #添加网卡
    network = serializers.JSONField(required=True)

#注意提交过来的上述数据中manufacturer是一个普通字符串数据,所以可在验证时就转换--把提交过来的字符串数据转换成对象
#这是第二步验证:自定义字段级别验证--验证传过来的这个制造商数据存在制造商表中否--此方法会返回一个制造商的对象出来
    def validate_manufacturer(self, value):
        #存在则直接返回制造商对象,不存在则创建
        try:
            return Manufacturer.objects.get(vendor_name__exact=value)
        except Manufacturer.DoesNotExist:
            return self.create_manufacturer(value)
#这是第三步验证:
    def validate(self, attrs):
        #拿到一制造商对象
        manufacturer_obj = attrs["manufacturer"]
        #拿到制造商表中制造商所有型号并验证传来的制造商是否存在表中:
        try:
            attrs["model_name"] = manufacturer_obj.productmodel_set.get(model_name__exact=attrs["model_name"])
        #没则报此异常并去创建:
        except ProductModel.DoesNotExist:
            attrs["model_name"] = self.create_product_model(manufacturer_obj, attrs["model_name"])
        return attrs

    #此时的数据是已经经过验证的数据了:
    def create(self, validated_data):
        #把network的集合传进来:
        network = validated_data.pop("network")
        #创建服务器对象记录:
        server_obj = Server.objects.create(**validated_data)
        #做关联
        self.check_server_network_device(server_obj, network)
        return server_obj
    #检查我这个服务器有没有你指定的这些网卡network设备并做关联
    def check_server_network_device(self,server_obj,network):
        #先取出此服务器的所有网卡设备
        network_device_queryset = server_obj.networkdevice_set.all()
        #再做一匹配:因为取到所网卡设备数据是一个字典列表,所以要for循环处理
        for device in network:
            try:
                #查询网卡名:存在
                network_device_obj = network_device_queryset.get(name__exact=device["name"])
            #不存在则创建一网卡设备:
            except NetworkDevice.DoesNotExist:
                self.create_network_device(server_obj,device)
        #检查网卡的ip:ifnets是一条完整的ips记录列表(里边是ip和netmask,mac_address):
    def check_ip(self,network_device_obj, ifnets):
            #(1)拿到此网卡的所有ip
            ip_queryset = network_device_obj.ip_set.all()
            #(2)检查ip:
            for ifnet in ifnets:
                #检查网卡中有无此ip,无则创建
                try:
                    ip_queryset.get(ip_addr__exact=ifnet["ip_addr"])
                except IP.DoesNotExist:
                    ip_obj = self.create_ip(network_device_obj,ifnet)
        #创建ip:
    def create_ip(self,network_device_obj,ifnet):
            #传给ifnet一device数据:
            ifnet["device"] = network_device_obj
            return IP.objects.create(**ifnet)

        #创建网卡设备:device设备信息是一条完整的网卡数据
    def create_network_device(self,server_obj,device):
            #ips不要
            ips = device.pop("ips")
            #写入device中host数据:
            device["host"] = server_obj
            #创建设备--无ip:
            network_device_obj=NetworkDevice.objects.create(**device)
            #处理网卡的ip:
            self.check_ip(network_device_obj, ips)
            return  network_device_obj

    # 创建一个制造商记录(拿着传过来的制造商名字就创建一制造商记录)---它会返回一制造商对象来--注意这manufacturer这张表只人vendor是不为空,其它字段都可为空:
    def create_manufacturer(self, vendor_name):
        return Manufacturer.objects.create(vendor_name=vendor_name)
    #同理:传一制造商和模型
    def create_product_model(self,manufacturer_obj, model_name):
        return ProductModel.objects.create(model_name=model_name, vendor=manufacturer_obj)

    # 此序列化是把network传入实例
    def to_representation(self, instance):
        ret = {
            "hostname": instance.hostname,
            "ip": instance.ip
        }
        return ret

class ServerSerializer(serializers.ModelSerializer):
    """
    服务器序列化类
    """
    class Meta:
        model =  Server
        fields = "__all__"

class NetworkDeviceSerializer(serializers.ModelSerializer):
    """
    网卡序列化
    """
    class Meta:
        model =  NetworkDevice
        fields = "__all__"

class IPSerializer(serializers.ModelSerializer):
    """
    IP序列化
    """
    class Meta:
        model =  IP
        fields = "__all__"

测试用的数据如下图页面中提交如下数据:

{
    "ip": "192.168.1.2",
    "hostname": "yz-ms-web-02",
    "cpu": "Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz 1",
    "mem": "2G",
    "disk": "100G",
    "os": "centos 7",
    "sn": "B49479B2-D774-4B09-B7B9-F29384350C01",
    "manufacturer": "Oracle Corporation",
    "model_name": "VirtualBox",
    "uuid": "B49479B2-D774-4B09-B7B9-F29384350C01",
    "network": [
        {
            "name":"eth0",
            "ips": [
                {"ip_addr": "123123.123.131","netmask":"255.255.255.0"}
            ],
            "mac_address":"a4:ba:db:21:4f:b4"
        },
        {
            "name":"eth1",
            "ips": [
                {"ip_addr":"10.3.0.131","netmask":"255.255.255.0"}
            ],
            "mac_address": "a4:ba:db:21:4f:b6"
        },
        {
            "name":"eth2",
            "ips": [
                {"ip_addr":"172.32.252.252","netmask":"255.255.255.0"}
            ],
            "mac_address": "a4:ba:db:21:4f:b8"
        }    
    ]
}
(python36env) [vagrant@CentOS7 devops]$ python manage.py shell
In [1]: from servers.models import Server                                   
In [2]: Server.objects.all()                                                
Out[2]: <QuerySet [<Server: 192.168.1.2>]>
In [3]: s = Server.objects.all()[0]                                         
In [4]: s.networkdevice_set                                                 
Out[4]: <django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager at 0x7f203ef78a58>
In [5]: s.networkdevice_set.all()                                           
Out[5]: <QuerySet [<NetworkDevice: eth0>, <NetworkDevice: eth1>, <NetworkDevice: eth2>]>
In [6]: Server.objects.all().delete()    #----若数据有重复有用此命令先清空下数据再提交上述页面中数据                                   

最后效果如下图有数据了:

下图中也没有任何问题--服务器添加了: 

 

如下图网卡中也有数据了,只是还没有ip:

 

如下图中的ip有了,且属于哪张网卡也显示了,只是没传给网卡:

 三.添加服务器之处理新加的ip,网卡

  1. 如服务器网卡的ip有变更(ip数量没变但地址变。新增加了ip。删除了一ip。),则怎么处理?

 需求就是:我要拿到之前网卡ip列表中有而当前网卡ip列表中没有的ip(这种就包含了可增加/删除/修改ip),并把它删除掉-----取两集合的差集,如下演示中:

(python36env) [vagrant@CentOS7 devops]$ ipython
In [1]: x = ["ip1","ip2"]                                                   

In [2]: y = ["ip2","ip3"]                                                   

In [3]: z = set(x) - set(y)   差集--->得到的结果是x中有而y中没有的数据ip1                                              

In [4]: print(z)                                                            
{'ip1'}

In [10]: z1 = set(x) & set(y)  交集

In [11]: print(z1)
{'ip2'}

In [12]: z2 = set(x) | set(y)  并集

In [13]: print (z2)
{'ip1', 'ip2', 'ip3'}

 2.网卡设备也是一样的道理,一但改名,就需要知道当前网卡跟之前网卡做一差集并删除就可--这个删除会删除网卡对应的所有ip地址

servers/serializers.py:---这样就实现了

.....
#检查我这个服务器有没有你指定的这些网卡network设备并做关联
    def check_server_network_device(self,server_obj,network):
        #先取出此服务器的所有网卡设备
        network_device_queryset = server_obj.networkdevice_set.all()
        current_network_device_queryset = []

        #再做一匹配:因为取到所网卡设备数据是一个字典列表,所以要for循环处理
        for device in network:
            try:
                #查询网卡名:存在
                network_device_obj = network_device_queryset.get(name__exact=device["name"])
            #不存在则创建一网卡设备:
            except NetworkDevice.DoesNotExist:
               network_device_obj = self.create_network_device(server_obj,device)
            current_network_device_queryset.append(network_device_obj)
        for network_device_obj in list(set(network_device_queryset) - set(current_network_device_queryset)):
            network_device_obj.delete()
        #检查网卡的ip:ifnets是一条完整的ips记录列表(里边是ip和netmask,mac_address):
    def check_ip(self,network_device_obj, ifnets):
            #(1)拿到此网卡之前的所有ip--注意这是queryset类型继承列表所以它也是列表
            ip_queryset = network_device_obj.ip_set.all()
            #拿到此网卡的当前所有ip---这是列表类型
            current_ip_queryset = []
            #(2)检查ip:
            for ifnet in ifnets:
                #检查网卡中有无此ip,有则说明是当前网卡的ip,无则创建
                try:
                    ip_obj = ip_queryset.get(ip_addr__exact=ifnet["ip_addr"])
                except IP.DoesNotExist:
                    ip_obj = self.create_ip(network_device_obj,ifnet)
                #保存此网卡的当前所有ip
                current_ip_queryset.append(ip_obj)
            #拿到要删除的ip--遍历列表
            for ib_obj in list(set(ip_queryset) - set(current_ip_queryset)):
                ip_obj.delete()
.......

四.服务器之更新数据

   添加/创建一条记录是走POST方法,更新一条记录是走PUT方法,但是这样的话,你就得在客户端知道你是第一次添加数据且走post还是二次更新数据且走put,所以这就不太现实。所以可以让客户端不管理你做什么操作都让你走post然后让我后端逻辑来判定你是添加还是更新操作。入口是create方法,因为post过来就到了create方法,在create方法中做判断是更新还是添加操作即可---查询SN或UUID有则更新无则添加。这里我把入口改成了自定的create_server方法中(存放的是create之前的逻辑代码),那查询并判断的操作逻辑放create方法中。

servers/serilaizer.py:

from rest_framework import serializers
from .models import Manufacturer, ProductModel
from rest_framework import  serializers
from .models import Server,NetworkDevice,IP

#这是第一步验证:因为涉及到多张表,所以使用模型序列化不合适(模型序列化它会去校验你的每一字段必须在模型中存在,而我这里的网卡,ip字段是不存在的,都有关联关系),就用序列化--则每个字段要定义一遍
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)
    #添加网卡
    network = serializers.JSONField(required=True)

#注意提交过来的上述数据中manufacturer是一个普通字符串数据,所以可在验证时就转换--把提交过来的字符串数据转换成对象
#这是第二步验证:自定义字段级别验证--验证传过来的这个制造商数据存在制造商表中否--此方法会返回一个制造商的对象出来
    def validate_manufacturer(self, value):
        #存在则直接返回制造商对象,不存在则创建
        try:
            return Manufacturer.objects.get(vendor_name__exact=value)
        except Manufacturer.DoesNotExist:
            return self.create_manufacturer(value)
#这是第三步验证:
    def validate(self, attrs):
        #拿到一制造商对象
        manufacturer_obj = attrs["manufacturer"]
        #拿到制造商表中制造商所有型号并验证传来的制造商是否存在表中:
        try:
            attrs["model_name"] = manufacturer_obj.productmodel_set.get(model_name__exact=attrs["model_name"])
        #没则报此异常并去创建:
        except ProductModel.DoesNotExist:
            attrs["model_name"] = self.create_product_model(manufacturer_obj, attrs["model_name"])
        return attrs
    def create_server(self,validated_data):
        # 把network的集合传进来:
        network = validated_data.pop("network")
        # 创建服务器对象记录:
        server_obj = Server.objects.create(**validated_data)
        # 做关联
        self.check_server_network_device(server_obj, network)
        return server_obj


    #此时的数据是已经经过验证的数据了:
    def create(self, validated_data):
    # 通过SN/UUID获取到对象(sn为空或sn=uuid或sn以vmware开头的者是虚拟机走uuid,剩余情况都走sn)
        #拿uuid和sn并转小写:
        uuid = validated_data["uuid"].lower()
        sn = validated_data["sn"].lower()
        try:
            if sn == uuid or sn == "" or sn.startswith("vmware"):
                #拿到虚拟机--icontains是忽略大小写
                server_obj = Server.objects.get(uuid__icontains=uuid)
            else:
                #拿到物理机
                server_obj = Server.objects.get(sn__icontains=sn)
        except Server.DoesNotExist:
            return self.create_server(validated_data)
        else:
            return self.update_server(server_obj,validated_data)
    def update_server(self, instance, validated_data):
        #服务器的哪些地方可以改:
        instance.hostname = validated_data.get("hostname", instance.hostname)
        instance.cpu = validated_data.get("cpu", instance.cpu)
        instance.ip = validated_data.get("ip", instance.ip)
        instance.mem = validated_data.get("mem", instance.mem)
        instance.disk = validated_data.get("disk", instance.disk)
        instance.os = validated_data.get("os", instance.os)
        instance.save()
        self.check_server_network_device(instance, validated_data["network"])
        return instance



    #检查我这个服务器有没有你指定的这些网卡network设备并做关联
    def check_server_network_device(self,server_obj,network):
        #先取出此服务器的所有网卡设备
        network_device_queryset = server_obj.networkdevice_set.all()
        current_network_device_queryset = []
        #再做一匹配:因为取到所网卡设备数据是一个字典列表,所以要for循环处理
        for device in network:
            try:
                #查询网卡名:存在
                network_device_obj = network_device_queryset.get(name__exact=device["name"])
            #不存在则创建一网卡设备:
            except NetworkDevice.DoesNotExist:
               network_device_obj = self.create_network_device(server_obj,device)
            current_network_device_queryset.append(network_device_obj)
            self.check_ip(network_device_obj, device["ips"])
        for network_device_obj in list(set(network_device_queryset) - set(current_network_device_queryset)):
            network_device_obj.delete()
        #检查网卡的ip:ifnets是一条完整的ips记录列表(里边是ip和netmask,mac_address):
    def check_ip(self,network_device_obj, ifnets):
            #(1)拿到此网卡之前的所有ip--注意这是queryset类型继承列表所以它也是列表
            ip_queryset = network_device_obj.ip_set.all()
            #拿到此网卡的当前所有ip---这是列表类型
            current_ip_queryset = []
            #(2)检查ip:
            for ifnet in ifnets:
                #检查网卡中有无此ip,有则说明是当前网卡的ip,无则创建
                try:
                    ip_obj = ip_queryset.get(ip_addr__exact=ifnet["ip_addr"])
                except IP.DoesNotExist:
                    ip_obj = self.create_ip(network_device_obj,ifnet)
                #保存此网卡的当前所有ip
                current_ip_queryset.append(ip_obj)
            #拿到要删除的ip--遍历列表
            for ib_obj in list(set(ip_queryset) - set(current_ip_queryset)):
                ip_obj.delete()
    #创建ip:
    def create_ip(self,network_device_obj,ifnet):
            #传给ifnet一device数据:
            ifnet["device"] = network_device_obj
            return IP.objects.create(**ifnet)

        #创建网卡设备:device设备信息是一条完整的网卡数据
    def create_network_device(self,server_obj,device):
            #ips不要
            ips = device.pop("ips")
            #写入device中host数据:
            device["host"] = server_obj
            #创建设备--无ip:
            network_device_obj=NetworkDevice.objects.create(**device)
            return  network_device_obj

    # 创建一个制造商记录(拿着传过来的制造商名字就创建一制造商记录)---它会返回一制造商对象来--注意这manufacturer这张表只人vendor是不为空,其它字段都可为空:
    def create_manufacturer(self, vendor_name):
        return Manufacturer.objects.create(vendor_name=vendor_name)
    #同理:传一制造商和模型
    def create_product_model(self,manufacturer_obj, model_name):
        return ProductModel.objects.create(model_name=model_name, vendor=manufacturer_obj)

    # 此序列化是把network传入实例
    def to_representation(self, instance):
        ret = {
            "hostname": instance.hostname,
            "ip": instance.ip
        }
        return ret

class ServerSerializer(serializers.ModelSerializer):
    """
    服务器序列化类
    """
    class Meta:
        model =  Server
        fields = "__all__"

class NetworkDeviceSerializer(serializers.ModelSerializer):
    """
    网卡序列化
    """
    class Meta:
        model =  NetworkDevice
        fields = "__all__"

class IPSerializer(serializers.ModelSerializer):
    """
    IP序列化
    """
    class Meta:
        model =  IP
        fields = "__all__"

1.如下测试:

2.用pycharm debug调试:需求我判断我在前面post提交数据时后端走的create_server方法还是update_server方法:在serilaizer.py中下图两方法中添加两断点

 

 在pycharm中manage.py右击debug manage.py运行,后效果如下:

 再如下清空数据:并重新在测试页面提交一次:如下图中这次走的是create

(python36env) [vagrant@CentOS7 devops]$ python manage.py shell
In [1]: from servers.models import Server                                   

In [2]: Server.objects.all().delete()  

 3.分别在测试页面测试它的ip和网卡:

提交post数据时做相应修改---少一块网卡或添加网卡提交然后到网卡测试页面网卡设备有无变更。给某网卡少一个ip或添加一ip然后提交并到ip测试页面ip有无变更

11

22

33

原文地址:https://www.cnblogs.com/dbslinux/p/13098378.html