中间件、实例模型和视图调整

This commit is contained in:
chenzuoqing 2021-12-28 16:21:53 +08:00
parent 065aa9d34c
commit 3ad65d90ca
12 changed files with 198 additions and 90 deletions

View File

@ -1,4 +1,4 @@
from flask_restful import fields from flask_restful import fields, marshal
class DateTime(fields.DateTime): class DateTime(fields.DateTime):
@ -18,3 +18,20 @@ class DateTime(fields.DateTime):
return value.strftime(self.dt_format) return value.strftime(self.dt_format)
except AttributeError as ae: except AttributeError as ae:
raise fields.MarshallingException(ae) raise fields.MarshallingException(ae)
class NestedOrEmpty(fields.Nested):
"""若嵌套信息为空,不返回嵌套结构全部字段,可仅返回空字典"""
def __init__(self, nested, allow_empty=False, **kwargs):
self.allow_empty = allow_empty
super().__init__(nested, **kwargs)
def output(self, key, obj):
value = fields.get_value(key if self.attribute is None else self.attribute, obj)
if value is None:
if self.allow_null:
return None
elif self.allow_empty:
return {}
return marshal(value, self.nested)

View File

@ -44,6 +44,7 @@ class ModelViewBase(Resource):
model = None model = None
# marshal 字段 # marshal 字段
fields = {} fields = {}
fields_detail = {}
# 参数解析校验 # 参数解析校验
request_parse: reqparse.RequestParser = None request_parse: reqparse.RequestParser = None
# 是否分页 # 是否分页
@ -153,9 +154,13 @@ class ModelViewBase(Resource):
for field, model, required in self.relation_fields: for field, model, required in self.relation_fields:
val = args.get(field) val = args.get(field)
# 判断 required: 参数有提交值才校验,未在参数中或零值不校验 # 判断 required: 参数有提交值才校验,未在参数中或零值不校验
if (not required or required == "many") and not val:
if not required and not val:
continue continue
if required == "many" and isinstance(val, list):
for v in val:
# 遍历列表检查是否存在
self.validate_relation_pk(v, model, field)
return
self.validate_relation_pk(val, model, field) self.validate_relation_pk(val, model, field)
@ -203,14 +208,19 @@ class ListMixin(ModelViewBase):
# 过滤后的数据, # 过滤后的数据,
self.get_queryset(*args, **kwargs) self.get_queryset(*args, **kwargs)
self.queryset = self.filter_queryset() self.queryset = self.filter_queryset()
count = self.queryset.count()
# 根据请求头返回不同解析的fields
if request.headers.get("Accept") == "salt":
field_parse = self.fields_detail if self.fields_detail else self.fields
return formatter.key_indexed_map(self.queryset, field_parse)
limit = request.args.get("limit") limit = request.args.get("limit")
if self.paging and limit != "0": if self.paging and limit != "0":
return self.paginate_queryset(self. queryset) return self.paginate_queryset(self. queryset)
# 转为对象列表 # 转为对象列表
if self.queryset.count() > 0: if count > 0:
return formatter.to_list(self.queryset, self.fields, only_exist=True) return formatter.to_list(self.queryset, self.fields, only_exist=True)
# return marshal(self.queryset, self.fields, envelope="id") # return marshal(self.queryset, self.fields, envelope="id")
# return formatter.key_indexed_map(self.queryset, self.fields) # return formatter.key_indexed_map(self.queryset, self.fields)

View File

@ -4,7 +4,7 @@ import logging
from flask import request from flask import request
from flask_restful import reqparse from flask_restful import reqparse
from models.asset import fields as assetField from models.asset import hostFields as assetField
from models.asset import host as assetModel from models.asset import host as assetModel
from common.views import ListCreateViewSet, DetailViewSet from common.views import ListCreateViewSet, DetailViewSet
from common.permission import session_or_token_required from common.permission import session_or_token_required

View File

@ -1,6 +1,7 @@
from flask import request from flask import request
from flask_restful import reqparse from flask_restful import reqparse
from models.asset.credential import Credential
from models.asset.middleware import Middleware from models.asset.middleware import Middleware
from models.asset.instance import Instance from models.asset.instance import Instance
from models.asset.instanceFields import InstanceFields, InstanceDetailFields from models.asset.instanceFields import InstanceFields, InstanceDetailFields
@ -15,7 +16,9 @@ class InstanceParse:
""" """
request_parse = None request_parse = None
# 给子类用的唯一字段,用于校验 # 给子类用的唯一字段,用于校验
uniq_fields = (("name", "class_name"),) uniq_fields = (("name", "server"), )
# 需要检查的关联字段对象
relation_fields = (("server", Middleware, ""), ("credential", Credential, "many"), ("dependents", Middleware, "many"))
def __init__(self): def __init__(self):
# 创建时必要的参数用此变量判断,更新默认都可以不传 # 创建时必要的参数用此变量判断,更新默认都可以不传
@ -23,7 +26,9 @@ class InstanceParse:
self.request_parse = reqparse.RequestParser() self.request_parse = reqparse.RequestParser()
self.request_parse.add_argument("name", required=required, type=str, location='json') self.request_parse.add_argument("name", required=required, type=str, location='json')
self.request_parse.add_argument("class_name", required=False, type=str, location='json') self.request_parse.add_argument("class_name", required=False, type=str, location='json')
self.request_parse.add_argument("middleware_id", required=False, type=str, location='json')
self.request_parse.add_argument("server", required=False, type=str, location='json')
self.request_parse.add_argument("dependents", required=False, type=list, location='json')
self.request_parse.add_argument("credentials", required=False, type=list, location='json') self.request_parse.add_argument("credentials", required=False, type=list, location='json')
self.request_parse.add_argument("extra", required=False, type=dict, location='json') self.request_parse.add_argument("extra", required=False, type=dict, location='json')
@ -42,14 +47,6 @@ class InstanceClassNameValidate:
if class_name_params and class_name_params != class_name: if class_name_params and class_name_params != class_name:
return abort_response(400, 1400, msg="URL中的类别与内容中的类别字段有差异!") return abort_response(400, 1400, msg="URL中的类别与内容中的类别字段有差异!")
args["class_name"] = class_name args["class_name"] = class_name
# 校验 middleware_id 若设置值,检查 middleware 对象是否存在,且类型与 instance 一致
middleware_id = args.get("middleware_id")
if middleware_id:
try:
assert Middleware.objects(id=middleware_id, class_name=class_name).first()
except:
return abort_response(400, 1400, msg="参数中的同类型 middleware 对象不存在")
return args return args
@ -68,6 +65,7 @@ class InstanceClassViews(InstanceParse, InstanceClassNameValidate, ListCreateVie
"""某类别的实例列表""" """某类别的实例列表"""
model = Instance model = Instance
fields = InstanceFields fields = InstanceFields
fields_detail = InstanceDetailFields
def get_queryset(self, *args, **kwargs): def get_queryset(self, *args, **kwargs):
self.queryset = self.model.objects(**kwargs) self.queryset = self.model.objects(**kwargs)

View File

@ -1,8 +1,10 @@
from flask import request from flask import request
from flask_restful import reqparse from flask_restful import reqparse
from models.asset.middleware import Middleware from models.asset.host import Host
from models.asset.middlewareFields import MiddlewareFields from models.asset.credential import Credential
from models.asset.middleware import Middleware, Network
from models.asset.middlewareFields import MiddlewareFields, MiddlewareDetailFields
from common.utils import abort_response from common.utils import abort_response
from common.views import (ListCreateViewSet, ListMixin, DetailViewSet, from common.views import (ListCreateViewSet, ListMixin, DetailViewSet,
RetrieveClassMixin, DestroyClassMixin, UpdateClassMixin) RetrieveClassMixin, DestroyClassMixin, UpdateClassMixin)
@ -13,19 +15,23 @@ class MiddlewareParse:
中间件模型公共字段的校验类 中间件模型公共字段的校验类
""" """
request_parse = None request_parse = None
# 给子类用的唯一字段,用于校验 # uniq_fields = ("name",)
uniq_fields = ("name", "host") relation_fields = (("server", Host, ""), ("credential", Credential, "many"))
def init_parse(self): def __init__(self):
# 创建时必要的参数用此变量判断,更新默认都可以不传 # 创建时必要的参数用此变量判断,更新默认都可以不传
required = request.method == "POST" required = request.method == "POST"
self.request_parse = reqparse.RequestParser() self.request_parse = reqparse.RequestParser()
self.request_parse.add_argument("name", required=required, type=str, location='json') self.request_parse.add_argument("name", required=required, type=str, location='json')
self.request_parse.add_argument("class_name", required=False, type=str, location='json') self.request_parse.add_argument("class_name", required=False, type=str, location='json')
self.request_parse.add_argument("host", required=False, type=str, location='json') self.request_parse.add_argument("network", required=False, type=list, location='json')
self.request_parse.add_argument("port", required=False, type=int, location='json') self.request_parse.add_argument("capacity", required=False, type=int, location='json')
self.request_parse.add_argument("manage", required=False, type=str, location='json') self.request_parse.add_argument("version", required=False, type=str, location='json')
self.request_parse.add_argument("status", type=str, location='json', choices=Middleware.STATUS.keys())
self.request_parse.add_argument("server", required=False, type=str, location='json')
self.request_parse.add_argument("credentials", required=False, type=list, location='json')
self.request_parse.add_argument("extra", required=False, type=dict, location='json') self.request_parse.add_argument("extra", required=False, type=dict, location='json')
self.request_parse.add_argument("data", required=False, type=dict, location='json') self.request_parse.add_argument("data", required=False, type=dict, location='json')
@ -41,28 +47,31 @@ class MiddlewareClassNameValidate:
if class_name_params and class_name_params != class_name: if class_name_params and class_name_params != class_name:
return abort_response(400, 1400, msg="URL中的类别与内容中的类别字段有差异!") return abort_response(400, 1400, msg="URL中的类别与内容中的类别字段有差异!")
args["class_name"] = class_name args["class_name"] = class_name
network = args.get("network", [])
if network:
network_list = [Network(**n) for n in network if isinstance(n, dict)]
args["network"] = network_list
return args return args
class MiddlewareViews(MiddlewareParse, ListCreateViewSet): class MiddlewareViews(MiddlewareParse, ListCreateViewSet):
model = Middleware model = Middleware
fields = MiddlewareFields fields = MiddlewareFields
uniq_fields = ("name",) filter_fields = (("name", "icontains"), ("class_name", ""))
filter_fields = (("name", "icontains"), ("host", "icontains"), ("manage", "icontains"), ("class_name", ""))
class MiddlewareDetail(MiddlewareParse, DetailViewSet): class MiddlewareDetail(MiddlewareParse, DetailViewSet):
"""分类别的视图""" """分类别的视图"""
model = Middleware model = Middleware
fields = MiddlewareFields fields = MiddlewareDetailFields
uniq_fields = ("name",)
class MiddlewareClassViews(MiddlewareParse, MiddlewareClassNameValidate, ListCreateViewSet): class MiddlewareClassViews(MiddlewareParse, MiddlewareClassNameValidate, ListCreateViewSet):
"""按分类的中间件视图,带 class_name 参数""" """按分类的中间件视图,带 class_name 参数"""
model = Middleware model = Middleware
fields = MiddlewareFields fields = MiddlewareFields
uniq_fields = ("name",) fields_detail = MiddlewareDetailFields
filter_fields = (("name", "icontains"), ("host", "icontains"), ("manage", "icontains")) filter_fields = (("name", "icontains"), ("host", "icontains"), ("manage", "icontains"))
def get_queryset(self, *args, **kwargs): def get_queryset(self, *args, **kwargs):
@ -74,5 +83,4 @@ class MiddlewareClassDetail(MiddlewareParse, MiddlewareClassNameValidate,
RetrieveClassMixin, DestroyClassMixin, UpdateClassMixin): RetrieveClassMixin, DestroyClassMixin, UpdateClassMixin):
"""分类别的详情视图,带 class_name 和 pk 参数""" """分类别的详情视图,带 class_name 和 pk 参数"""
model = Middleware model = Middleware
fields = MiddlewareFields fields = MiddlewareDetailFields
uniq_fields = ("name",)

View File

@ -4,7 +4,7 @@ import datetime
from flask_restful import Resource, reqparse, marshal, fields as F from flask_restful import Resource, reqparse, marshal, fields as F
from models.asset.host import Host from models.asset.host import Host
from models.asset.fields import HostFields from models.asset.hostFields import HostFields
from models.project import fields from models.project import fields
from models.project.models import Project, Channel, Server, Version from models.project.models import Project, Channel, Server, Version
from common.views import CreateMixin, RetrieveMixin from common.views import CreateMixin, RetrieveMixin

View File

@ -2,34 +2,52 @@ import mongoengine as mongo
from common.document import DocumentBase from common.document import DocumentBase
from models.asset.middleware import Middleware from models.asset.middleware import Middleware
from models.asset.credential import Credential
class Instance(DocumentBase): class Instance(DocumentBase):
"""""" """实例集合"""
meta = {'allow_inheritance': True, 'collection': 'instance'} STATUS = {
# TODO 完善状态
"none": "未规划", # 默认状态
"prepare": "待部署",
"normal": "正常",
}
meta = {'allow_inheritance': True, 'collection': 'instance', 'strict': False}
# 实例的分类名称 # 实例的分类名称
class_name = mongo.StringField(required=False, default="") class_name = mongo.StringField(required=False, default="")
# 实例的名称,可能是库名、仓库名等 # 实例的名称,可能是库名、仓库名等
name = mongo.StringField(required=True) name = mongo.StringField(required=True)
status = mongo.StringField(choices=STATUS.keys(), default="none")
# 关联的 middleware # 关联的 middleware是当前实例的父级
middleware_id = mongo.ObjectIdField(required=False) server = mongo.ObjectIdField(required=False)
# 依赖的 middleware 列表
dependents = mongo.ListField(mongo.ObjectIdField(), default=list)
# 对应可以有多个凭据 # 对应可以有多个凭据
credentials = mongo.ListField(mongo.ObjectIdField, default=list) credentials = mongo.ListField(mongo.ObjectIdField(), default=list)
# 按类型不同的信息存放到 extra 字典 # 按类型不同的信息存放到 extra 字典
extra = mongo.DictField(default=dict) extra = mongo.DictField(default=dict)
data = mongo.DictField(default=dict) data = mongo.DictField(default=dict)
tags = mongo.ListField(mongo.StringField(), default=list) # tags 默认是空列表 tags = mongo.ListField(mongo.StringField(), default=list) # tags 默认是空列表
labels = mongo.DictField(default=dict) labels = mongo.DictField(default=dict)
@property @property
def middleware(self): def server_object(self):
if self.middleware_id: if self.server:
try: return Middleware.objects(id=self.server).first()
return Middleware.objects(id=self.middleware_id).first() return
except:
pass @property
return None def dependents_object(self):
if self.dependents:
return Middleware.objects(id__in=self.dependents)
return
@property
def credentials_object(self):
if self.credentials:
return Credential.objects(id__in=self.credentials)
return

View File

@ -7,7 +7,11 @@ InstanceFields = {
"id": fields.String, "id": fields.String,
"name": fields.String, "name": fields.String,
"class_name": fields.String, "class_name": fields.String,
"middleware_id": fields.String, "status": fields.String,
"server": fields.String,
"dependents": fields.List(fields.String),
"credentials": fields.List(fields.String),
"extra": fields.Raw, "extra": fields.Raw,
"data": fields.Raw, "data": fields.Raw,
@ -20,8 +24,11 @@ InstanceDetailFields = {
"id": fields.String, "id": fields.String,
"name": fields.String, "name": fields.String,
"class_name": fields.String, "class_name": fields.String,
"middleware": fields.Nested(MiddlewareFields), "status": fields.String,
"credentials": fields.List(fields.String),
"server": fields.Nested(MiddlewareFields, allow_null=True, attribute="server_object"), # 若没有关联信息返回 null
"dependents": fields.List(fields.Nested(MiddlewareFields), attribute="dependents_object"),
"credentials": fields.List(fields.Nested(MiddlewareFields), attribute="credentials_object"),
"extra": fields.Raw, "extra": fields.Raw,
"data": fields.Raw, "data": fields.Raw,

View File

@ -1,33 +1,70 @@
import mongoengine as mongo import mongoengine as mongo
from common.document import DocumentBase from common.document import DocumentBase
from models.asset.host import Host
from models.asset.credential import Credential
class Network(mongo.EmbeddedDocument):
"""网络信息"""
# 地址 端口 是否对外
address = mongo.StringField(default="")
port = mongo.IntField(default=-1)
class Middleware(DocumentBase): class Middleware(DocumentBase):
"""中间件""" """中间件"""
meta = {'allow_inheritance': True, 'collection': 'mdw', 'strict': False} STATUS = {
# TODO 完善状态
"none": "未规划", # 默认状态
"prepare": "待部署",
"normal": "正常",
}
meta = {'allow_inheritance': True, 'collection': 'middleware', 'strict': False}
# 分类名称 # 分类名称
class_name = mongo.StringField(required=False, default="") class_name = mongo.StringField(required=False, default="")
name = mongo.StringField(required=True) name = mongo.StringField(required=True)
host = mongo.StringField(required=False, default="")
port = mongo.IntField(default=0) # 网络,监听地址和端口,可能多个
# 管理者,预留 network = mongo.EmbeddedDocumentListField(Network)
manage = mongo.StringField(max_length=128, required=False, default="") # 容量
capacity = mongo.IntField(default=-1)
status = mongo.StringField(choices=STATUS.keys(), default="none")
# 中间件程序的版本
version = mongo.StringField(default="")
# 服务器,对应 Host
server = mongo.ObjectIdField(required=False)
# 对应可以有多个凭据
credentials = mongo.ListField(mongo.ObjectIdField(), default=list)
extra = mongo.DictField(default=dict) extra = mongo.DictField(default=dict)
data = mongo.DictField(default=dict) data = mongo.DictField(default=dict)
# 标记和标签 # 标记和标签
tags = mongo.ListField(mongo.StringField(), default=list) # tags 默认是空列表 tags = mongo.ListField(mongo.StringField(), default=list) # tags 默认是空列表
labels = mongo.DictField(default=dict) labels = mongo.DictField(default=dict)
@property
def minion(self):
"""返回机器的 minion_id"""
if self.server:
host: Host = self.server_object
if host:
return host.minion_id
return
# class DBServer(Middleware): @property
# """数据库服务器""" def server_object(self):
# meta = {'allow_inheritance': True, 'strict': False} """返回 host 对象"""
# domain = mongo.StringField(required=False) # 域名连接的地址 if self.server:
# credentials = mongo.ListField(mongo.ObjectIdField, default=list) try:
return Host.objects(id=self.server).first()
except:
pass
return
# 配置 @property
# storage = mongo.IntField(required=False, default=0) def credentials_object(self):
# memory = mongo.IntField(required=False, default=0) if self.credentials:
# core = mongo.IntField(required=False, default=0) return Credential.objects(id__in=self.credentials)
return

View File

@ -1,38 +1,51 @@
from flask_restful import fields from flask_restful import fields
from models.asset.hostFields import HostFields
from models.asset.credentialFields import CredentialFields
NetworkFields = {
"address": fields.String,
"port": fields.Integer,
}
# 中间件列表解析字段
MiddlewareFields = { MiddlewareFields = {
"id": fields.String, "id": fields.String,
"name": fields.String, "name": fields.String,
"host": fields.String,
"port": fields.Integer,
"manage": fields.String,
"class_name": fields.String, "class_name": fields.String,
"extra": fields.Raw,
"network": fields.List(fields.Nested(NetworkFields)),
"capacity": fields.Integer,
"status": fields.String,
"version": fields.String,
"server": fields.String,
"credentials": fields.List(fields.String), "credentials": fields.List(fields.String),
"extra": fields.Raw,
"data": fields.Raw, "data": fields.Raw,
"tags": fields.List(fields.String), "tags": fields.List(fields.String),
"labels": fields.Raw, "labels": fields.Raw,
} }
# DatabaseFields = { # 中间件详情解析的字段
# "id": fields.String, MiddlewareDetailFields = {
# "name": fields.String, "id": fields.String,
# "username": fields.String, "name": fields.String,
# "password": fields.String, "class_name": fields.String,
# "data": fields.Raw,
# "tags": fields.List(fields.String), "network": fields.List(fields.Nested(NetworkFields)),
# "labels": fields.Raw, "capacity": fields.Integer,
# } "status": fields.String,
# "version": fields.String,
# DBServerFields = {
# "domain": fields.String, "minion": fields.String(attribute="minion"),
# "port": fields.Integer, "server": fields.Nested(HostFields, attribute="server_object", allow_null=True),
# "username": fields.String, "credentials": fields.List(fields.Nested(CredentialFields), attribute="credentials_object"),
# "password": fields.String,
# "storage": fields.Integer, "extra": fields.Raw,
# "memory": fields.Integer, "data": fields.Raw,
# "core": fields.Integer, "tags": fields.List(fields.String),
# "databases": fields.List(fields.Nested(DatabaseFields)) "labels": fields.Raw,
# } }
#
# DBServerFields.update(MiddlewareFields)

View File

@ -3,7 +3,7 @@ project marshal fields
""" """
from flask_restful import fields from flask_restful import fields
from models.asset.fields import HostSimpleFields from models.asset.hostFields import HostSimpleFields
ProjectFields = { ProjectFields = {
"id": fields.String, "id": fields.String,