接上篇博文
尝试添加一张主机表,用于管理主机信息
无非表的内容是host,那么肯定是在schema中建立,直接写描述即可
schema 填入id,id默认是1,name对应的是host
这里虚拟表所对应的东西是1,描述的字段还暂时没有
如下所示:
MariaDB [cmdb]> insert into `schema`(name) values('hosts'); MariaDB [cmdb]> select * from `schema`; +----+-------+------+ | id | name | desc | +----+-------+------+ | 1 | hosts | NULL | +----+-------+------+ 1 row in set (0.00 sec)
描述字段
通过filed表中添加两个字段 hostname,schema_id 这俩字段与host与关,其实就是与id与关联
这样自增的话,会产生第一个索引,那如果再进行添加ip 的话,则id是2
MariaDB [cmdb]> insert into `field` (name,schema_id) value ('hostname',1); MariaDB [cmdb]> insert into `field` (name,schema_id) value ('ip',1); MariaDB [cmdb]> select * from field; +----+----------+------+-----------+ | id | name | meta | schema_id | +----+----------+------+-----------+ | 1 | hostname | NULL | 1 | | 5 | ip | NULL | 1 | +----+----------+------+-----------+ 2 rows in set (0.00 sec)
如上,这里value对应的schema_id 都是1,也就是找到schema表中与id=1对应相关的表,也就是属hosts所管辖
MariaDB [cmdb]> select * from `schema`; +----+-------+------+ | id | name | desc | +----+-------+------+ | 1 | hosts | NULL | +----+-------+------+
这样创建假设自增的话,产生第一个主键
查询
查出对应的虚拟表所有的字段,只要指定schemaid就可以查出当前信息
MariaDB [cmdb]> select * from `schema`,`field` where 1 = 1 and `field`.schema_id = `schema`.id and `schema`.id = 1; +----+-------+------+----+----------+------+-----------+ | id | name | desc | id | name | meta | schema_id | +----+-------+------+----+----------+------+-----------+ | 1 | hosts | NULL | 1 | hostname | NULL | 1 | | 1 | hosts | NULL | 5 | ip | NULL | 1 | +----+-------+------+----+----------+------+-----------+ 2 rows in set (0.00 sec)
查出它所有字段,只要能知名schema id 相当于虚拟表已经被查出
记录一个主机信息,entity
MariaDB [cmdb]> insert into entity (id,`key`,schema_id) values(1,'0123123',1);
对value表填入数据
当前entity 的值是1,那么对应entity_id = 1就可以了,字段id是
MariaDB [cmdb]> insert into `value`(entity_id,filed_id,`value`) values(1,1,'webserver'); MariaDB [cmdb]> insert into `value`(entity_id,filed_id,`value`) values(1,2,'182.168.1.1'); MariaDB [cmdb]> select * from value; +----+-------------+----------+-----------+ | id | value | field_id | entity_id | +----+-------------+----------+-----------+ | 1 | webser | 1 | 1 | | 2 | 182.168.1.1 | 2 | 1 | +----+-------------+----------+-----------+ 2 rows in set (0.01 sec)
可以看到其field对应的表字段
MariaDB [cmdb]> select * from field; +----+----------+------+-----------+ | id | name | meta | schema_id | +----+----------+------+-----------+ | 1 | hostname | NULL | 1 | | 2 | ip | NULL | 1 | +----+----------+------+-----------+ 2 rows in set (0.00 sec)
插入第二个主机信息:
通过唯一key来区别主机,以及还需要对entity 进行添加行进行id对应
MariaDB [cmdb]> insert into entity(`key`,schema_id) values ('0456456',1); Query OK, 1 row affected, 1 warning (0.00 sec) MariaDB [cmdb]> select * from entity; +----+---------+-----------+ | id | key | schema_id | +----+---------+-----------+ | 0 | 0456456 | 1 | | 1 | 0123123 | 1 | +----+---------+-----------+ 2 rows in set (0.00 sec)
插入数据 ,对应entity 第二个id
insert into `value`(entity_id,filed_id,`value`) VALUES (1,1,'DBser'); insert into `value`(entity_id,filed_id,`value`) VALUES (1,2,'127.0.0.1');
以上存入了两个主机信息
查看
select entity.id as entity_id,entity.`key`, entity.schema_id, `schema`.`name`, field.id,field.`name` as fname, `value`.`value` FROM entity INNER JOIN `value` on `value`.entity_id = entity_id INNER JOIN `schema` on `value`.schema_id = `schema`.id INNER JOIN field on `value`.field_id = field_id
利与弊
好处:之前每个类都会生成一个表,现在无非是在scaehma添加一行记录而已
坏处:关系复杂、表结构复杂,多长关系表组成的关系链,复杂的同时带来了灵活性,ORM不识别这样的表,只能自己封装进行实现
可否在value 的value段用约束进行?
比如记录ip,那么ip不允许重复如何去写?
这样相当于所有表都互相干扰了,所以不允许加唯一键约束
可否建立一个ip 池的表,如果存放的主机信息的,对于资产管理,将所有的服务配置,那么就肯定涉及到ip
如果互不干扰的话,那么可否通过sechma id进行判断,需要考虑重复的时候判断问题,那么无非是插入的时候判断
单独写类型非常有限,既然类型不合适,那么ip地址肯定不合适,总需要方法来解决这些
那么可否通过正则表达式,但是比较难掌握
通过meta进行限制,meta是text类型,是否可以使用json?
将json中的字符串转为python代码,通过反射动态加载运行,这样实现的话需要约定好调用的接口
考虑的问题点:
1. 如何存放?如何描述?描述什么?
2. 如果用到反射的话,那可否存放一个类名,一个模块名 直接加载它 直接调用这个方法
开发
设计一个类 ,通过反射来判断这个值,进行字段类型来验证
存储之前通过类转为特殊类型转为字符串,来进行校验,如果校验成功则存入数据库
建立约束类型,目录结构如下:
建立基类,用于校验,功能方法冗余性全部在基类中实现,子类用于增强
校验字符合法性
class BaseType: def stringify(self,value): raise NotImplementedError() def destringify(self,value): raise NotImplementedError() class Int(BaseType): def stringify(self,value): str(int(value)) def destringify(self,value): pass class Int(BaseType): def stringify(self,value): return str(int(value)) def destringify(self,value): pass
ip地址校验
通过ipaddress 模块进行校验ip地址合法性 import ipaddress class BaseType: def stringify(self,value): raise NotImplementedError() def destringify(self,value): raise NotImplementedError() class Int(BaseType): def stringify(self,value): return int(str(value)) def destringify(self,value): return value class IP(BaseType): def stringify(self,value): return str(ipaddress.ip_address(value)) def destringify(self,value): return value
反射
既然拿到了类型和值了,接下来如何操作?
通过getattr反射进行找到对应的方法
通过name返回object属性值,当属性不存在,将使用default返回,如果没有默认,则抛出AttributeError
通过getattr进行反射
当前模块还未导入,而且字符串还没被分段
import json jsonstr = """ { "type":"cmdb.types.Int", "value":300 } """ obj = json.loads(jsonstr) print(obj) m,c = obj['type'].rsplit('.',maxsplit=1) print(m,c) # 返回如下 {'type': 'cmdb.types.Int', 'value': 300} types.Int cmdb
导入模块importlib
通过获取的值进行反射,这里m对应的是cmdb.types, 正是init.py,将其导入
我们看到看init是类方法,那么需要扔一个值进去
进行实例化,传入一个参数,那么这个值是value
# 导入cmdb.types mod = importlib.import_module(m) cls = getattr(mod,c) cls().stringify(obj['value']) # 返回如下 {'value': 300, 'type': 'cmdb.types.Int'} cmdb.types Int
校验IP
一般要求分4段式
这里用于测试 value 不应该写在这里,切记
抽象函数
一般抽象的时候都将其返回一个对象为止,所以需要改进如下:
import json import importlib jsonstr = """ { "type":"cmdb.types.IP", "value":"10.10.10.1" } """ obj = json.loads(jsonstr) def get_instance(type:str): # 这里要约定好,不然无法判断 m,c = type.rsplit('.',maxsplit=1) mod = importlib.import_module(m) cls = getattr(mod,c) return cls() print(get_instance(obj['type']).stringify(obj['value']))
给予一个类模块取出,将类加载起来之后返回类的实例
调用它的方法,将函数get_instance导入到init中 import ipaddress import importlib def get_instance(type:str): m,c = type.rsplit('.',maxsplit=1) mod = importlib.import_module(m) cls = getattr(mod,c) return cls() class BaseType: def stringify(self,value): raise NotImplementedError() def destringify(self,value): raise NotImplementedError() class Int(BaseType): def stringify(self,value): return int(str(value)) def destringify(self,value): return value class IP(BaseType): def stringify(self,value): return str(ipaddress.ip_address(value)) def destringify(self,value): return value
这样就可以完美解决数据类型的问题,通过传递进来的json串来找到对应方法并解析,从而进行判断类型并return