说明
本文中所有内容仅作为学习使用,请勿用于任何商业用途。
本文为原创,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接和本声明。
上一章节,我们仅仅是做到在admin管理后台看见我们定义的所有models信息。本章节开始主要是实现CMDB中资产新增、新资产上线、资产更新、资产展示等功能。
#A 新增资产
首先需要更新视图文件view.py,先不要着急去撸代码,要搞清楚新增资产的大体逻辑再去撸代码(避免返工)下图为新增资产的逻辑图示及概述:
- 其中assets-data应该是通过POST发送至服务端的资产原始数据;
- 资产数据通过json转换为json数据类型;
- 进行接收到的数据进行合理检查(安全性、合理性、完整性等);
- 判断数据是否为空,若为空则返回错误信息并退出视图;
- 判断数据是否为字典类型(这里为了方便操作,定义资产数据为字典类型),若非字典则返回错误信息并退出视图;
- 判断数据是否带有SN,若非则返回错误信息并退出视图(SN是标示一个合法资产的唯一字段,不能缺少不能为空);
下面我们根据以上逻辑,编写新增资产的视图代码:
1 from django.shortcuts import HttpResponse 2 from django.views.decorators.csrf import csrf_exempt 3 import json 4 from assets import models 5 from assets import assets_handler 6 7 8 # Create your views here. 9 @csrf_exempt #这里为了方便暂且跳过Django的csrf安全机制(后期再完善安全相关配置) 10 def report(request): 11 if request.method == "POST": 12 assets_data = request.POST.get('assets_data') 13 print(assets_data) 14 data = json.loads(assets_data) 15 # 判断data是否为空 16 if not data: 17 return HttpResponse("提交的数据为空!") 18 if not issubclass(dict, type(data)): 19 return HttpResponse("提交的数据必须是字典格式!") 20 # 判断提交的数据是否有唯一键:sn 21 sn = data.get('sn', None) 22 if sn: 23 # 判断是否为线上资产中存在的资产 24 assets_obj = models.Assets.objects.filter(sn=sn) 25 if assets_obj: 26 # 更新线上资产信息 27 update_assets = assets_handler.UpdateAsset(request, assets_obj[0], data) 28 return HttpResponse("资产已更新!") 29 else: 30 # 进入待审批区域 31 obj = assets_handler.NewAssets(request, data) 32 response = obj.add_to_new_assets_zone() 33 return HttpResponse(response) 34 else: 35 return HttpResponse("提交的数据中未包含SN,请校验数据!") 36 return HttpResponse("怎么就200了!")
#B 资产数据处理
接下来,针对assets_data进行数据处理,在view文件中我们仅仅是针对数据进行了SN的判断和数据是否为空的判断。资产更新及提交资产到待审批区域还未解决,但这里我们还有几点需要明确的:
通过SN在已上线的资产进行查找,若存在则进入已上线资产的更新流程;
如果没有,说明这是个新资产,需要添加到新资产区;
创建一个assets_handler.py文件,其中新建四个类分别实现四个功能:
NewAssets(将资源新增到待审批区域)
ApproveAssets(审批待上线的资源)
UpdateAssets(更新已上线资产信息)
Log(记录资产操作日志)
整个数据处理的大体流程为:创建一个asset_handler.NewAsset()对象,然后调用它的obj.add_to_new_assets_zone()方法,将资产数据保存至models中的NewAssetsApprovalZone中(需要在models文件新增待审批区所需要的字段,models中代码会一并整理到第二章节中),根据实际情况,接收返回相应的结果;审批待上线资产则比较粗暴,一键审批并将待上线数据直接转正即可(从待审批区域移至资产表中);更新资产则是通过view中针对SN的判断来执行相应的数据更新即可。
新增资产分两种情况:
- 彻底的新资产,任何区域都不存在该资产,直接添加至待审批区域即可;
- 已上线资产中没有该资产,但是待审批区中存在该资产(审批人还未审批的资产),此时再次接收到相同SN的资产时,只需要更新待审批区域该资产的数据即可;
下面是assets_handler中各个功能模块的代码:
1 class ApproveAssets: 2 # 审批待上线资产 3 def __init__(self, request, assets_id): 4 self.request = request 5 self.new_assets = models.NewAssetApprovalZone.objects.get(id=assets_id) 6 self.data = json.loads(self.new_assets.data) 7 8 def assets_online(self): 9 # 预留接口 10 func = getattr(self, "_%s_online" % self.new_assets.assets_type) 11 ret = func() 12 return ret 13 14 def _server_online(self): 15 assets = self._create_assets() 16 try: 17 self._create_manufacturer(assets) # 创建厂商 18 self._create_server(assets) # 创建服务器 19 self._create_cpu(assets) # 创建CPU 20 self._create_ram(assets) # 创建内存 21 self._create_disk(assets) # 创建硬盘 22 self._create_nic(assets) # 创建网卡 23 self._delete_original_asset() # 从待审批资产区删除已审批上线的资产 24 except Exception as e: 25 assets.delete() 26 log('approve_failed', msg=e, new_assets=self.new_assets, request=self.request) 27 print(e) 28 return False 29 else: 30 # 添加日志 31 log("online", assets=assets, request=self.request) 32 print("新服务器上线!") 33 return True 34 35 def _create_assets(self): 36 # 创建资产并上线 37 assets = models.Assets.objects.create(assets_type=self.new_assets.assets_type, 38 assets_name="%s:%s" % (self.new_assets.assets_type, self.new_assets.sn), 39 sn=self.new_assets.sn, assets_approved=self.request.user,) 40 return assets 41 42 def _create_manufacturer(self, asset): 43 # 创建厂商 44 m = self.new_assets.manufacturer 45 if m: 46 manufacturer_obj, _ = models.ManufacturerAssets.objects.get_or_create(name=m) 47 asset.manufacturer = manufacturer_obj 48 asset.save() 49 50 def _create_server(self, asset): 51 # 创建服务器 52 models.ServerAssets.objects.create(assets=asset, model=self.new_assets.model, os_type=self.new_assets.os_type, 53 os_distribution=self.new_assets.os_distribution, 54 os_release=self.new_assets.os_release) 55 56 def _create_cpu(self, asset): 57 # 创建CPU 58 cpu = models.CPUAssets.objects.create(assets=asset) 59 cpu.cpu_model = self.new_assets.cpu_model 60 cpu.cpu_count = self.new_assets.cpu_count 61 cpu.cpu_core_count = self.new_assets.cpu_core_count 62 cpu.save() 63 64 def _create_ram(self, asset): 65 # 创建内存 66 ram_list = self.data.get('ram') 67 if not ram_list: 68 return 69 for ram_dict in ram_list: 70 if not ram_dict.get('slot'): 71 raise ValueError("内存插槽位置不存在!") 72 ram = models.RAMAssets() 73 ram.assets = asset 74 ram.slot = ram_dict.get('slot') 75 ram.sn = ram_dict.get('sn') 76 ram.model = ram_dict.get('model') 77 ram.manufacturer = ram_dict.get('manufacturer') 78 ram.volume = ram_dict.get('volume', 0) 79 ram.save() 80 81 def _create_disk(self, asset): 82 # 创建硬盘 83 disk_list = self.data.get('physical_disk_driver') 84 if not disk_list: # 一条硬盘数据都没有 85 return 86 for disk_dict in disk_list: 87 if not disk_dict.get('sn'): 88 raise ValueError("未知sn的硬盘!") # 根据sn确定具体某块硬盘。 89 disk = models.DiskAssets() 90 disk.assets = asset 91 disk.sn = disk_dict.get('sn') 92 disk.model = disk_dict.get('model') 93 disk.brand = disk_dict.get('brand'), 94 disk.slot = disk_dict.get('slot') 95 disk.volume = disk_dict.get('volume', 0) 96 iface = disk_dict.get('interface_type') 97 if iface in ['SATA', 'SAS', 'SCSI', 'SSD', 'unknown']: 98 disk.interface_type = iface 99 100 disk.save() 101 102 def _create_nic(self, asset): 103 # 创建网卡 104 nic_list = self.data.get("nic") 105 if not nic_list: 106 return 107 108 for nic_dict in nic_list: 109 if not nic_dict.get('mac'): 110 raise ValueError("网卡缺少mac地址!") 111 if not nic_dict.get('model'): 112 raise ValueError("网卡型号未知!") 113 114 nic = models.NICAssets() 115 nic.assets = asset 116 nic.name = nic_dict.get('name') 117 nic.model = nic_dict.get('model') 118 nic.mac = nic_dict.get('mac') 119 nic.ip_address = nic_dict.get('ip_address') 120 if nic_dict.get('net_mask'): 121 if len(nic_dict.get('net_mask')) > 0: 122 nic.net_mask = nic_dict.get('net_mask')[0] 123 nic.save() 124 125 def _delete_original_asset(self): 126 # 对审批通过的资产进行待审批区删除(其实不删除而是通过某个字段改变状态更合理,后期再优化吧) 127 self.new_assets.delete()
1 class UpdateAsset: 2 # 更新已上线资产信息 3 def __init__(self, request, assets, report_data): 4 self.request = request 5 self.assets = assets 6 self.report_data = report_data 7 self.asset_update() 8 9 def asset_update(self): 10 # 预留接口 11 func = getattr(self, "_%s_update" % self.report_data['assets_type']) 12 ret = func() 13 return ret 14 15 def _server_update(self): 16 try: 17 self._update_manufacturer() # 更新厂商 18 self._update_server() # 更新服务器 19 self._update_cpu() # 更新CPU 20 self._update_ram() # 更新内存 21 self._update_disk() # 更新硬盘 22 self._update_nic() # 更新网卡 23 self.assets.save() 24 except Exception as e: 25 log('update_failed', msg=e, assets=self.assets, request=self.request) 26 print(e) 27 return False 28 else: 29 # 添加日志 30 log("update_success", assets=self.assets) 31 print("资产数据被更新!") 32 return True 33 34 def _update_manufacturer(self): 35 # 更新厂商 36 37 m = self.report_data.get('manufacturer') 38 if m: 39 manufacturer_obj, _ = models.ManufacturerAssets.objects.get_or_create(name=m) 40 self.assets.manufacturer = manufacturer_obj 41 else: 42 self.assets.manufacturer = None 43 self.assets.manufacturer.save() 44 45 def _update_server(self): 46 # 更新服务器 47 self.assets.serverassets.model = self.report_data.get('model') 48 self.assets.serverassets.os_type = self.report_data.get('os_type') 49 self.assets.serverassets.os_distribution = self.report_data.get('os_distribution') 50 self.assets.serverassets.os_release = self.report_data.get('os_release') 51 self.assets.serverassets.save() 52 53 def _update_cpu(self): 54 # 更新CPU信息 55 self.assets.cpu_assets .cpu_model = self.report_data.get('cpu_model') 56 self.assets.cpu_assets.cpu_count = self.report_data.get('cpu_count') 57 self.assets.cpu_assets.cpu_core_count = self.report_data.get('cpu_core_count') 58 self.assets.cpu_assets.save() 59 60 def _update_ram(self): 61 """ 62 更新内存信息。 63 使用集合数据类型中差的概念,处理不同的情况。 64 如果新数据有,但原数据没有,则新增; 65 如果新数据没有,但原数据有,则删除原来多余的部分; 66 如果新的和原数据都有,则更新。 67 在原则上,下面的代码应该写成一个复用的函数, 68 但是由于内存、硬盘、网卡在某些方面的差别,导致很难提取出重用的代码。 69 :return: 70 """ 71 # 获取已有内存信息,并转成字典格式 72 old_rams = models.RAMAssets.objects.filter(assets=self.assets) 73 old_rams_dict = dict() 74 if old_rams: 75 for ram in old_rams: 76 old_rams_dict[ram.slot] = ram 77 # 获取新数据中的内存信息,并转成字典格式 78 new_rams_list = self.report_data['ram'] 79 new_rams_dict = dict() 80 if new_rams_list: 81 for item in new_rams_list: 82 new_rams_dict[item['slot']] = item 83 84 # 利用set类型的差集功能,获得需要删除的内存数据对象 85 need_deleted_keys = set(old_rams_dict.keys()) - set(new_rams_dict.keys()) 86 if need_deleted_keys: 87 for key in need_deleted_keys: 88 old_rams_dict[key].delete() 89 90 # 需要新增或更新的 91 if new_rams_dict: 92 for key in new_rams_dict: 93 defaults = { 94 'sn': new_rams_dict[key].get('sn'), 95 'model': new_rams_dict[key].get('model'), 96 'brand': new_rams_dict[key].get('brand'), 97 'volume': new_rams_dict[key].get('volume', 0), 98 } 99 models.RAMAssets.objects.update_or_create(assets=self.assets, slot=key, defaults=defaults) 100 101 def _update_disk(self): 102 """ 103 更新硬盘信息,类似更新内存。 104 """ 105 old_disks = models.DiskAssets.objects.filter(assets=self.assets) 106 old_disks_dict = dict() 107 if old_disks: 108 for disk in old_disks: 109 old_disks_dict[disk.sn] = disk 110 111 new_disks_list = self.report_data['physical_disk_driver'] 112 new_disks_dict = dict() 113 if new_disks_list: 114 for item in new_disks_list: 115 new_disks_dict[item['sn']] = item 116 117 # 需要删除的 118 need_deleted_keys = set(old_disks_dict.keys()) - set(new_disks_dict.keys()) 119 if need_deleted_keys: 120 for key in need_deleted_keys: 121 old_disks_dict[key].delete() 122 123 # 需要新增或更新的 124 if new_disks_dict: 125 for key in new_disks_dict: 126 interface_type = new_disks_dict[key].get('interface_type', 'unknown') 127 if interface_type not in ['SATA', 'SAS', 'SCSI', 'SSD', 'unknown']: 128 interface_type = 'unknown' 129 defaults = { 130 'slot': new_disks_dict[key].get('slot'), 131 'model': new_disks_dict[key].get('model'), 132 'brand': new_disks_dict[key].get('brand'), 133 'volume': new_disks_dict[key].get('volume', 0), 134 'interface_type': interface_type, 135 } 136 models.DiskAssets.objects.update_or_create(assets=self.assets, sn=key, defaults=defaults) 137 138 def _update_nic(self): 139 """ 140 更新网卡信息,类似更新内存。 141 """ 142 old_nics = models.NICAssets.objects.filter(assets=self.assets) 143 old_nics_dict = dict() 144 if old_nics: 145 for nic in old_nics: 146 old_nics_dict[nic.model + nic.mac] = nic 147 148 new_nics_list = self.report_data['nic'] 149 new_nics_dict = dict() 150 if new_nics_list: 151 for item in new_nics_list: 152 new_nics_dict[item['model'] + item['mac']] = item 153 154 # 需要删除的 155 need_deleted_keys = set(old_nics_dict.keys()) - set(new_nics_dict.keys()) 156 if need_deleted_keys: 157 for key in need_deleted_keys: 158 old_nics_dict[key].delete() 159 160 # 需要新增或更新的 161 if new_nics_dict: 162 for key in new_nics_dict: 163 if new_nics_dict[key].get('net_mask') and len(new_nics_dict[key].get('net_mask')) > 0: 164 net_mask = new_nics_dict[key].get('net_mask')[0] 165 else: 166 net_mask = "" 167 defaults = { 168 'name': new_nics_dict[key].get('name'), 169 'ip_address': new_nics_dict[key].get('ip_address'), 170 'net_mask': net_mask, 171 } 172 models.NICAssets.objects.update_or_create(assets=self.assets, model=new_nics_dict[key]['model'], 173 mac=new_nics_dict[key]['mac'], defaults=defaults) 174 175 print('更新成功!')
1 def log(log_type, msg=None, assets=None, new_assets=None, request=None): 2 # 记录日志 3 event = models.EventLog() 4 if log_type == "online": 5 event.name = "%s <%s> : 上线" % (assets.assets_name, assets.sn) 6 event.assets = assets 7 event.detail = "资产成功上线!" 8 event.user = request.user 9 elif log_type == "approve_failed": 10 event.name = "%s <%s> : 审批失败" % (new_assets.assets_type, new_assets.sn) 11 event.new_assets = new_assets 12 event.detail = "审批失败! %s" % msg 13 event.user = request.user 14 elif log_type == "update_success": 15 event.name = "%s [%s] <%s> : 数据更新!" % (assets.assets_type, assets.assets_name, assets.sn) 16 event.assets = assets 17 event.detail = "更新成功!" 18 elif log_type == "update_failed": 19 event.name = "%s [%s] <%s> : 更新失败" % (assets.assets_type, assets.assets_name, assets.sn) 20 event.assets = assets 21 event.detail = "更新失败! %s" % msg 22 # 更多日志类型..... 23 event.save()
#C 资产数据处理
最后还有几个工作需要完成,我们有个view但是还缺少url的配置,还需要一些测试数据来检测我们的各个功能是否ok。
首先我们完成rul的配置,为保证整个项目的代码可读性及高扩展性,我们更新IMU_DevOps目录下的urls文件:
1 from django.contrib import admin 2 from django.urls import path, include 3 4 urlpatterns = [ 5 path('admin/', admin.site.urls), 6 path('assets/', include('assets.urls')) 7 ]
嗯时间到,需要带娃去跨年,今年就写到这里吧。明年再更新!在这里祝大家新年快乐!2020各种顺!
接下来在assets包中新增urls.py这个文件并编写代码,主要是指定之前我们编写的view视图中的report方法。
1 from django.urls import path 2 from assets import views 3 4 5 app_name = 'assets' 6 7 urlpatterns = [ 8 path('report/', views.report, name='report') 9 ]
然后我们分别新建Conf,Log的python package,其中Conf中主要用来存放和该项目相关的配置文件,工程目录中的settings文件我们仅针对整个工程做基础的设置,而每个模块的配置我们都单独来写,这样不仅修改某个配置文件的时候不会影响到整个工程,同时整个工程的扩展性也会大大提升。在Log包目录下新建一个IMU_assets.log文件(暂时用来存放测试log,正式log还是通过assets_handler.py中的log方法将其存放在数据库中),接下来撸代码:
1 import os 2 3 # 远端接收数据的服务器 4 Params = { 5 "server": "127.0.0.1", 6 "port": 8000, 7 'url': '/assets/report/', 8 'request_timeout': 30, 9 } 10 11 # 日志文件配置 12 13 PATH = os.path.join(os.path.dirname(os.getcwd()), 'Log', 'IMU_assets.log') 14 print(PATH)
最后我们需要测试以上功能是否达到我们的需求,由于是测试我这边就临时写一个test的文件,在其中定义两台服务器的信息并将它发送至我们设置的服务端。
1 import json 2 import time 3 import urllib.request 4 import urllib.parse 5 import os 6 import sys 7 from Conf import assets_setting 8 9 BASE_DIR = os.path.dirname(os.getcwd()) 10 # 设置工作目录,使得包和模块能够正常导入 11 sys.path.append(BASE_DIR) 12 13 def update_test(data): 14 """ 15 创建测试用例 16 """ 17 # 将数据打包到一个字典内,并转换为json格式 18 data = {"assets_data": json.dumps(data)} 19 # print(data) 测试是否收到data 20 # 根据assets_setting.py中的配置,构造url 21 url = "http://%s:%s%s" % (assets_setting.Params['server'], assets_setting.Params['port'], 22 assets_setting.Params['url']) 23 print('正在将数据发送至: [%s] ......' % url + '100%') 24 data_encode = urllib.parse.urlencode(data).encode() 25 response = urllib.request.urlopen(url=url, data=data_encode, timeout=assets_setting.Params['request_timeout']) 26 print("