diff --git a/src/asset/fields.py b/src/asset/fields.py index c60578d..024bea1 100644 --- a/src/asset/fields.py +++ b/src/asset/fields.py @@ -15,6 +15,7 @@ HostFields = { "cpu_num": fields.Integer, "cpu_core": fields.Integer, "memory": fields.Integer, + "data": fields.Raw, "tags": fields.List(fields.String), "labels": fields.Raw, "created": DateTime, @@ -25,3 +26,56 @@ HostSimpleFields = { "public_ip": fields.String, "private_ip": fields.String, } + +DatabaseServerFields = { + "id": fields.String, + "name": fields.String, + "domain": fields.String, + "host": fields.String, + "manage": fields.String, + "data": fields.Raw, + "tags": fields.List(fields.String), + "labels": fields.Raw, +} + +DatabaseFields = { + "id": fields.String, + "name": fields.String, + "username": fields.String, + "password": fields.String, +} + +MySQLInstanceFields = { + "port": fields.Integer, + "username": fields.String, + "password": fields.String, + "storage": fields.Integer, + "memory": fields.Integer, + "core": fields.Integer, + "databases": fields.List(fields.Nested(DatabaseFields)) +} +# 合并 DatabaseServerFields +MySQLInstanceFields.update(DatabaseServerFields) + +RedisInstanceFields = { + "port": fields.Integer, + "memory": fields.Integer, + "replicas": fields.Integer, + "password": fields.String, +} +# 合并 DatabaseServerFields +RedisInstanceFields.update(DatabaseServerFields) + +MiddlewareFields = { + "host": fields.String, + "manage": fields.String, + "data": fields.Raw, + "tags": fields.List(fields.String), + "labels": fields.Raw, +} + +NginxInstance = { + "port": fields.Integer, + "url": fields.String, +} +NginxInstance.update(MiddlewareFields) diff --git a/src/asset/models.py b/src/asset/models.py index a6cfcbf..a4b2c22 100644 --- a/src/asset/models.py +++ b/src/asset/models.py @@ -1,7 +1,8 @@ +from bson import ObjectId import mongoengine as mongo from common.document import DocumentBase -from common.validator import is_ipaddr +from common.validator import is_ipaddr, is_hex_string class Host(DocumentBase): @@ -20,9 +21,83 @@ class Host(DocumentBase): cpu_num = mongo.IntField(default=1) # cpu物理个数 cpu_core = mongo.IntField(default=1) # 每个cpu的核心数 memory = mongo.IntField(default=0) # 内存大小,单位GB + data = mongo.DictField(default=dict) # 标记和标签 tags = mongo.ListField(mongo.StringField(), default=list) # tags 默认是空列表 labels = mongo.DictField(default=dict) created = mongo.DateTimeField() + + +class DatabaseServer(DocumentBase): + """数据库服务器实例,包括mysql、redis、mongodb""" + meta = {'allow_inheritance': True} + name = mongo.StringField(required=True) + domain = mongo.StringField(required=False) # 域名连接的地址 + host = mongo.StringField(required=True) # 内网连接地址 + # 管理者,预留 + manage = mongo.StringField(max_length=128, required=True) + data = mongo.DictField(default=dict) + # 标记和标签 + tags = mongo.ListField(mongo.StringField(), default=list) # tags 默认是空列表 + labels = mongo.DictField(default=dict) + + +class Database(mongo.EmbeddedDocument): + """数据库对象的结构描述,一个数据库实例下包含多个数据库,并授权不同用户""" + # 为数据库对象生成id,非主键 + # 查询方式: MySQLInstance.objects.filter(databases__id='61adfcd5726440e0eb8b28bc').first() + id = mongo.ObjectIdField(required=True, default=ObjectId) + name = mongo.StringField(required=True) + username = mongo.StringField(default="") + password = mongo.StringField(default="") + + +class MySQLInstance(DatabaseServer): + """MySQL数据库实例""" + # 库名、连接的用户名密码 + port = mongo.IntField(default=3306) + username = mongo.StringField(max_length=32, required=True) + password = mongo.StringField(max_length=128, required=True) + # 配置 + storage = mongo.IntField(required=True) + memory = mongo.IntField(required=True) + core = mongo.IntField(required=True) + # 实例数据库,包含多个数据库 + databases = mongo.EmbeddedDocumentListField(Database) + + +class RedisInstance(DatabaseServer): + """Redis数据库实例""" + # 内存大小、副本数、密码 + port = mongo.IntField(default=6379) + memory = mongo.IntField(required=True) + replicas = mongo.IntField(required=True, default=0) + password = mongo.StringField(max_length=128, required=True) + + +class Middleware(DocumentBase): + """中间件""" + meta = {'allow_inheritance': True} + host = mongo.StringField(required=True) + # 管理者,预留 + manage = mongo.StringField(max_length=128, required=True) + data = mongo.DictField(default=dict) + # 标记和标签 + tags = mongo.ListField(mongo.StringField(), default=list) # tags 默认是空列表 + labels = mongo.DictField(default=dict) + + +class NginxInstance(Middleware): + """nginx服务器""" + port = mongo.IntField(default=80) + url = mongo.StringField(required=True) + + +class CDN(DocumentBase): + """cdn域名""" + domain = mongo.StringField(max_length=256, required=True) # cdn域名 + # 标记和标签 + tags = mongo.ListField(mongo.StringField(), default=list) # tags 默认是空列表 + labels = mongo.DictField(default=dict) diff --git a/src/asset/routes.py b/src/asset/routes.py index 75e649b..00cf0e0 100644 --- a/src/asset/routes.py +++ b/src/asset/routes.py @@ -1,7 +1,7 @@ from flask import Blueprint from flask_restful import Api -from asset import views +from asset.views import views # 当前app的蓝图,以app名为前缀 @@ -11,3 +11,6 @@ asset_v1 = Blueprint('asset', __name__, url_prefix="/asset") api = Api(asset_v1) api.add_resource(views.HostViews, '/host/', endpoint="host") api.add_resource(views.HostDetailViews, '/host//', endpoint="host-detail") + +api.add_resource(views.MySQLInstanceViews, '/database/mysql/', endpoint="db-mysql") +api.add_resource(views.MySQLInstanceDetail, '/database/mysql//', endpoint="db-mysql-detail") diff --git a/src/asset/views.py b/src/asset/views.py deleted file mode 100644 index 45a0321..0000000 --- a/src/asset/views.py +++ /dev/null @@ -1,14 +0,0 @@ -from asset import fields -from asset.models import Host - -from common.views import ListCreateViewSet, DetailViewSet - - -class HostViews(ListCreateViewSet): - model = Host - fields = fields.HostFields - - -class HostDetailViews(DetailViewSet): - model = Host - fields = fields.HostFields diff --git a/src/asset/views/__init__.py b/src/asset/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/asset/views/views.py b/src/asset/views/views.py new file mode 100644 index 0000000..081b877 --- /dev/null +++ b/src/asset/views/views.py @@ -0,0 +1,127 @@ +import datetime + +from flask_restful import reqparse + +from asset import fields +from asset import models + +from common.views import ListCreateViewSet, DetailViewSet +from common.utils import abort_response + + +class HostParse: + model = None + request_parse = None + + def init_parse(self): + self.request_parse = reqparse.RequestParser() + # 创建时必须,修改时可选 + # self.request_parse.add_argument("public_ip", type=str, required=True, + # help='not public_ip provided', location='json') + self.request_parse.add_argument("private_ip", required=False, type=str, location='json') + self.request_parse.add_argument("minion_id", required=False, type=str, location='json') + self.request_parse.add_argument("weights", required=False, type=int, location='json') + self.request_parse.add_argument("cpu_num", required=False, type=int, location='json') + self.request_parse.add_argument("cpu_core", required=False, type=int, location='json') + self.request_parse.add_argument("memory", required=False, type=int, location='json') + self.request_parse.add_argument("tags", required=False, type=list, location='json') + self.request_parse.add_argument("labels", required=False, type=dict, location='json') + + def validate_or_abort(self, args, obj=None): + """判断是否冲突""" + exists = self.model.objects(public_ip=args.get('public_ip', '')).first() + if exists: + if obj and exists.id == obj.id: # 同个对象,不算冲突 + return + # 截断请求 + abort_response(400, 1001, msg="ip已存在") + + def pre_create(self, args): + # 标记创建时间 + args["created"] = datetime.datetime.now() + self.validate_or_abort(args) + return args + + def pre_update(self, obj, args): + if "created" in args: + args.pop("created") + self.validate_or_abort(args, obj) + return args + + +class HostViews(HostParse, ListCreateViewSet): + model = models.Host + fields = fields.HostFields + + def __init__(self): + self.init_parse() + self.request_parse.add_argument("public_ip", type=str, required=True, + help='not public_ip provided', location='json') + super(HostViews, self).__init__() + + +class HostDetailViews(HostParse, DetailViewSet): + model = models.Host + fields = fields.HostFields + + def __init__(self): + self.init_parse() + self.request_parse.add_argument("public_ip", type=str, location='json') + super(HostDetailViews, self).__init__() + + +class DatabaseServerParse: + model = None + request_parse = None + + def init_parse(self): + self.request_parse = reqparse.RequestParser() + self.request_parse.add_argument("domain", required=False, type=str, location='json') + self.request_parse.add_argument("tags", required=False, type=list, location='json') + self.request_parse.add_argument("labels", required=False, type=dict, location='json') + + +class RedisInstanceParse(DatabaseServerParse): + + def init_parse(self): + super(RedisInstanceParse, self).init_parse() + self.request_parse.add_argument("port", required=False, type=int, location='json') + + +class MySQLInstanceViews(DatabaseServerParse, ListCreateViewSet): + model = models.MySQLInstance + fields = fields.MySQLInstanceFields + + def __init__(self): + self.init_parse() + self.request_parse.add_argument("name", required=True, type=str, location='json') + self.request_parse.add_argument("host", required=True, type=str, location='json') + self.request_parse.add_argument("manage", required=True, type=str, location='json') + + self.request_parse.add_argument("port", required=False, type=str, location='json') + self.request_parse.add_argument("username", required=True, type=str, location='json') + self.request_parse.add_argument("password", required=True, type=str, location='json') + self.request_parse.add_argument("storage", required=True, type=int, location='json') + self.request_parse.add_argument("memory", required=True, type=int, location='json') + self.request_parse.add_argument("core", required=True, type=int, location='json') + super(MySQLInstanceViews, self).__init__() + + +class MySQLInstanceDetail(DatabaseServerParse, DetailViewSet): + model = models.MySQLInstance + fields = fields.MySQLInstanceFields + + def __init__(self): + """对象修改的参数解析""" + self.init_parse() + self.request_parse.add_argument("host", required=False, type=str, location='json') + self.request_parse.add_argument("name", required=False, type=str, location='json') + self.request_parse.add_argument("manage", required=False, type=str, location='json') + + self.request_parse.add_argument("port", required=False, type=str, location='json') + self.request_parse.add_argument("username", required=False, type=str, location='json') + self.request_parse.add_argument("password", required=False, type=str, location='json') + self.request_parse.add_argument("storage", required=False, type=int, location='json') + self.request_parse.add_argument("memory", required=False, type=int, location='json') + self.request_parse.add_argument("core", required=False, type=int, location='json') + super(MySQLInstanceDetail, self).__init__() diff --git a/src/common/utils.py b/src/common/utils.py index d0d172b..18b22cd 100644 --- a/src/common/utils.py +++ b/src/common/utils.py @@ -1,4 +1,5 @@ from flask import jsonify +from flask_restful import abort def make_response(status, code, msg, **kwargs): @@ -16,3 +17,9 @@ def make_response(status, code, msg, **kwargs): response = jsonify(content) response.status_code = status return response + + +def abort_response(status, code, **kwargs): + """中断请求返回响应""" + abort(status, code=code, **kwargs) + diff --git a/src/common/views.py b/src/common/views.py index 7974831..807ccd3 100644 --- a/src/common/views.py +++ b/src/common/views.py @@ -125,7 +125,7 @@ class RetrieveMixin(ModelViewBase): class UpdateMixin(ModelViewBase): - def pre_update(self, obj, args): + def pre_update(self, obj, args: dict): """更新前钩子""" data = {} for k, v in args.items():