diff --git a/src/common/validator.py b/src/common/validator.py index 7279a33..85dc1dd 100644 --- a/src/common/validator.py +++ b/src/common/validator.py @@ -13,3 +13,11 @@ def is_ipaddr(ip): ipaddress.ip_address(ip) except: raise mongo.ValidationError('The value must be a ip address') + + +def is_hex_string(string: str): + """校验 hex 字符串(十六进制)""" + try: + int(string, 16) + except: + raise mongo.ValidationError('The value must be a hex string') diff --git a/src/project/fields.py b/src/project/fields.py index f41a397..a8c75ff 100644 --- a/src/project/fields.py +++ b/src/project/fields.py @@ -83,6 +83,8 @@ AgentServerFields = { "is_cross": fields.Boolean, # "channel_id": fields.String, "project": fields.String, + "project_name": fields.String, + "project_fork": fields.String, "public_ip": fields.String, "private_ip": fields.String, # "host_id": fields.String, diff --git a/src/project/models.py b/src/project/models.py index e5730a3..87e7930 100644 --- a/src/project/models.py +++ b/src/project/models.py @@ -2,7 +2,7 @@ import mongoengine as mongo from settings import common from common.document import DocumentBase -from common.validator import isalnum +from common.validator import isalnum, is_hex_string from asset.models import Host @@ -44,9 +44,9 @@ class Version(mongo.EmbeddedDocument): class Channel(DocumentBase): """渠道""" # spid项目内唯一 - project = mongo.ReferenceField(Project, reverse_delete_rule=mongo.NULLIFY) + project_id = mongo.StringField(max_length=128, required=True, validation=is_hex_string) name = mongo.StringField(max_length=32, default="") - spid = mongo.StringField(max_length=3, min_length=3, required=True, unique_with="project", validation=isalnum) + spid = mongo.StringField(max_length=3, min_length=3, required=True, unique_with="project_id", validation=isalnum) version = mongo.EmbeddedDocumentField(Version) # 缺失时获取对象的此字段为 None # 项目的代码仓库地址 @@ -64,6 +64,20 @@ class Channel(DocumentBase): """渠道 spid 在 CROSS_SPID_SET,表示跨服""" return self.spid in common.CROSS_SPID_SET + @property + def project(self): + if self.project_id: + return Project.objects(id=self.project_id).first() + return None + + @project.setter + def project(self, val): + if isinstance(val, str): + assert Project.objects(id=val).first(), f"对象不存在,id={val}" + self.project_id = val + if isinstance(val, Project): + self.project_id = str(val.id) + class Server(DocumentBase): """服务""" @@ -77,12 +91,13 @@ class Server(DocumentBase): "error": "异常" } - num = mongo.IntField(required=True, unique_with="channel") - channel = mongo.ReferenceField(Channel) + num = mongo.IntField(required=True, unique_with="channel_id") + # 关联channel表,保存是一个channel._id的hex字符串 + channel_id = mongo.StringField(max_length=128, required=True, validation=is_hex_string) status = mongo.StringField(max_length=12, choices=STATUS.keys(), required=True, default="running") # 机器字段,TODO 先允许为空 - host = mongo.ReferenceField(Host, null=True) + host_id = mongo.StringField(max_length=128, null=True, validation=is_hex_string) domain = mongo.StringField(max_length=128, required=False, default="") port = mongo.IntField() version = mongo.EmbeddedDocumentField(Version) @@ -106,17 +121,38 @@ class Server(DocumentBase): return "" @property - def channel_id(self): - if self.channel: - return self.channel.id + def channel(self): + """非强关联,方便取出channel对象""" + if self.channel_id: + return Channel.objects(id=self.channel_id).first() return "" + @channel.setter + def channel(self, val): + if isinstance(val, str): + assert Channel.objects(id=val).first(), f"对象不存在,id={val}" + self.channel_id = val + if isinstance(val, Channel): + self.channel_id = str(val.id) + @property def project(self): - if self.channel: + if self.channel and self.channel.project: return self.channel.project.fullname return "" + @property + def project_name(self): + if self.channel and self.channel.project: + return self.channel.project.name + return "" + + @property + def project_fork(self): + if self.channel and self.channel.project: + return self.channel.project.fork + return "" + @property def public_ip(self): if self.host: @@ -130,11 +166,20 @@ class Server(DocumentBase): return "" @property - def host_id(self): - if self.host: - return self.host.id + def host(self): + """非强关联,为了取出host对象""" + if self.host_id: + return Host.objects(id=self.host_id).first() return "" + @host.setter + def host(self, val): + if isinstance(val, str): + assert Host.objects(id=val).first(), f"对象不存在,id={val}" + self.host_id = val + if isinstance(val, Host): + self.host_id = str(val.id) + @property def is_cross(self): if self.channel: diff --git a/src/project/views/operation.py b/src/project/views/operation.py index ebebb62..2d514fe 100644 --- a/src/project/views/operation.py +++ b/src/project/views/operation.py @@ -65,7 +65,7 @@ class ServerSyncView(CreateMixin): if count != len(data): return make_response(400, 1001, "quantity mismatch") - + project_id = str(self.project.id) hosts = {} channels = {} errors = [] @@ -89,17 +89,17 @@ class ServerSyncView(CreateMixin): if not created and proj not in hostObj.tags: hostObj.tags.append(proj) hostObj.save() - host = hostObj.id - hosts[ip] = hostObj.id + host = str(hostObj.id) + hosts[ip] = host if not channel: channelObj, _ = Channel.get_or_create( - project=self.project, spid=spid, defaults=dict(project=self.project, spid=spid)) - channel = channelObj.id - channels[spid] = channelObj.id + project_id=project_id, spid=spid, defaults=dict(project_id=project_id, spid=spid)) + channel = str(channelObj.id) + channels[spid] = channel # 更新、创建的参数 - defaults = dict(num=num, channel=channel, host=host, version=version, port=port, status=status) - obj, created = Server.update_or_create(defaults=defaults, num=num, channel=channel) + defaults = dict(num=num, channel_id=channel, host_id=host, version=version, port=port, status=status) + obj, created = Server.update_or_create(defaults=defaults, num=num, channel_id=channel) if created: # 创建 app.logger.debug(f"创建 {spid}_{num} 区服信息成功") continue @@ -119,6 +119,8 @@ class ServerSyncView(CreateMixin): class AgentInfo(RetrieveMixin): """返回对应机器的所有信息:项目、渠道、区服内容""" fields = { + # marshal schedule dict + "schedule": F.Raw, "host": F.Nested(HostFields), "project": F.List(F.Nested(fields.ProjectFields)), "channel": F.List(F.Nested(fields.ChannelFields)), @@ -133,11 +135,19 @@ class AgentInfo(RetrieveMixin): # 找到机器,不存在就404 host = Host.objects(minion_id=agent_id).first_or_404(message="resource not found") # 根据机器找区服集合,返回 - servers = Server.objects(host=host).all() - channels = servers.values_list("channel").distinct("channel") - projects = [channel.project for channel in channels] + servers = Server.objects(host_id=str(host.id)).all() + channels = Channel.objects(id__in=servers.values_list("channel_id").distinct("channel_id")) + projects = {channel.project for channel in channels} # 填充数据 data = { + # TODO schedule 这里的内容可以动态配置,为 minion 增加计划任务 + # "schedule": { + # # 如下为刷新pillar,自动请求本接口,先测试用20s + # "job_refresh": { + # "function": "saltutil.refresh_pillar", + # "seconds": 20, + # } + # }, "host": host, "project": projects, "channel": channels,