新增数据库实例中每个db的管理接口

调整日志打印,视图中的写入到views.log
This commit is contained in:
chenzuoqing 2021-12-21 15:55:59 +08:00
parent 5b03e4add4
commit a351fc500b
8 changed files with 175 additions and 13 deletions

View File

@ -53,6 +53,15 @@ LOGGING = {
'backupCount': 5, 'backupCount': 5,
'formatter': 'verbose', 'formatter': 'verbose',
}, },
'views': {
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(os.path.dirname(BASE_DIR), 'logs/views.log'),
'encoding': 'utf-8',
'maxBytes': 1024 * 1024 * 5,
'backupCount': 5,
'formatter': 'verbose',
},
}, },
'loggers': { 'loggers': {
'root': { 'root': {
@ -64,6 +73,11 @@ LOGGING = {
'handlers': ['common'], 'handlers': ['common'],
'level': 'DEBUG', 'level': 'DEBUG',
'propagate': False, 'propagate': False,
},
'views': { # 视图的日志
'handlers': ['views'],
'level': 'DEBUG',
'propagate': False,
} }
} }
} }

View File

@ -13,6 +13,9 @@ api.add_resource(views.HostDetailViews, '/host/<string:pk>/', endpoint="host-det
api.add_resource(views.MySQLInstanceViews, '/database/mysql/', endpoint="db-mysql") api.add_resource(views.MySQLInstanceViews, '/database/mysql/', endpoint="db-mysql")
api.add_resource(views.MySQLInstanceDetail, '/database/mysql/<string:pk>/', endpoint="db-mysql-detail") api.add_resource(views.MySQLInstanceDetail, '/database/mysql/<string:pk>/', endpoint="db-mysql-detail")
# 数据实例中的库详情
api.add_resource(views.DatabaseViews, '/database/<string:pk>/db/', endpoint="db-database")
api.add_resource(views.DatabaseDetailViews, '/database/<string:pk>/db/<string:db>/', endpoint="db-database-detail")
api.add_resource(views.RedisInstanceViews, '/database/redis/', endpoint="db-redis") api.add_resource(views.RedisInstanceViews, '/database/redis/', endpoint="db-redis")
api.add_resource(views.RedisInstanceDetail, '/database/redis/<string:pk>/', endpoint="db-redis-detail") api.add_resource(views.RedisInstanceDetail, '/database/redis/<string:pk>/', endpoint="db-redis-detail")

View File

@ -1,10 +1,13 @@
import logging
from typing import List, Dict from typing import List, Dict
from flask import request, current_app as app from flask import request
from flask_restful import abort, Resource, marshal, fields, reqparse from flask_restful import abort, Resource, marshal, fields, reqparse
from common.utils import abort_response from common.utils import abort_response
logger = logging.getLogger("views")
def parse_uniq_fields(args, many_field) -> List[Dict]: def parse_uniq_fields(args, many_field) -> List[Dict]:
""" 注意,请再请求解析中判断必须的字段!!! """ 注意,请再请求解析中判断必须的字段!!!
@ -132,7 +135,7 @@ class ModelViewBase(Resource):
assert int(val, 16), f"{key} 不合法" assert int(val, 16), f"{key} 不合法"
assert model.objects(**{pk: val}).first(), f"{key} 不存在" assert model.objects(**{pk: val}).first(), f"{key} 不存在"
except (AssertionError, ValueError, TypeError): except (AssertionError, ValueError, TypeError):
app.logger.exception(f"{key} 异常") logger.exception(f"{key} 异常")
abort_response(400, 1002, msg=f"{key} 不存在") abort_response(400, 1002, msg=f"{key} 不存在")
def validate_relation_fields(self, args): def validate_relation_fields(self, args):
@ -190,7 +193,7 @@ class ListMixin(ModelViewBase):
except: except:
# 返回空 queryset # 返回空 queryset
queryset = self.model.objects.none() queryset = self.model.objects.none()
app.logger.exception(f"查询出错 {self.model} query_params={query_params}") logger.exception(f"查询出错 {self.model} query_params={query_params}")
return queryset return queryset
def get(self): def get(self):
@ -216,7 +219,7 @@ class CreateMixin(ModelViewBase):
"""创建前钩子,接收参数,可以对参数进行处理,最后保存此方法返回的数据""" """创建前钩子,接收参数,可以对参数进行处理,最后保存此方法返回的数据"""
return args return args
def post(self): def post(self, *args, **kwargs):
"""创建对象""" """创建对象"""
assert self.request_parse is not None, "缺少校验规则" assert self.request_parse is not None, "缺少校验规则"
@ -239,7 +242,7 @@ class CreateMixin(ModelViewBase):
obj = self.model(**validated_data) obj = self.model(**validated_data)
obj.save() obj.save()
except Exception as e: except Exception as e:
app.logger.exception(f"{self.model} 创建对象失败! data={args}") logger.exception(f"{self.model} 创建对象失败! data={args}")
abort_response(500, 1500, msg=f"保存对象失败!{str(e)}") abort_response(500, 1500, msg=f"保存对象失败!{str(e)}")
return return
# 返回创建信息 # 返回创建信息
@ -285,7 +288,7 @@ class UpdateMixin(ModelViewBase):
# 重新读取数据 # 重新读取数据
obj.reload() obj.reload()
except Exception as e: except Exception as e:
app.logger.exception(f"{self.model} 保存对象失败pk={pk} data={args}") logger.exception(f"{self.model} 保存对象失败pk={pk} data={args}")
abort_response(500, 1500, msg=f"保存对象失败!{str(e)}") abort_response(500, 1500, msg=f"保存对象失败!{str(e)}")
return return

View File

@ -44,6 +44,19 @@ class DatabaseServerParse:
self.request_parse.add_argument("labels", required=False, type=dict, location='json') self.request_parse.add_argument("labels", required=False, type=dict, location='json')
class DatabaseParse:
request_parse = None
def init_parse(self):
self.request_parse = reqparse.RequestParser()
self.request_parse.add_argument("username", type=str, location='json')
self.request_parse.add_argument("password", type=str, location='json')
self.request_parse.add_argument("data", required=False, type=dict, 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 MiddlewareParse: class MiddlewareParse:
request_parse = None request_parse = None
# 给子类用的唯一字段,用于校验 # 给子类用的唯一字段,用于校验

View File

@ -1,12 +1,18 @@
import datetime import datetime
import logging
from flask_restful import reqparse from flask_restful import reqparse, marshal, Resource
from models.asset import fields as assetField from models.asset import fields as assetField
from models.asset import models as assetModel from models.asset import models as assetModel
from common.views import ListCreateViewSet, DetailViewSet from common.views import ListCreateViewSet, DetailViewSet, CreateMixin, UpdateMixin, DestroyMixin
from common.permission import session_or_token_required from common.permission import session_or_token_required
from common.utils import abort_response
from controller.asset import parsers from controller.asset import parsers
from common.crypto import quick_crypto
from settings.common import SECRET_KEY
logger = logging.getLogger("views")
class HostViews(parsers.HostParse, ListCreateViewSet): class HostViews(parsers.HostParse, ListCreateViewSet):
@ -37,7 +43,8 @@ class MySQLInstanceViews(parsers.DatabaseServerParse, ListCreateViewSet):
model = assetModel.MySQLInstance model = assetModel.MySQLInstance
fields = assetField.MySQLInstanceFields fields = assetField.MySQLInstanceFields
method_decorators = [session_or_token_required] method_decorators = [session_or_token_required]
filter_fields = (("name", "icontains"), ("host", "icontains"), ("manage", ""), ("tags", "")) filter_fields = (("name", "icontains"), ("host", "icontains"), ("manage", ""), ("tags", ""),
("databases__name", ""), )
def __init__(self): def __init__(self):
self.init_parse() self.init_parse()
@ -53,6 +60,14 @@ class MySQLInstanceViews(parsers.DatabaseServerParse, ListCreateViewSet):
self.request_parse.add_argument("core", required=True, type=int, location='json') self.request_parse.add_argument("core", required=True, type=int, location='json')
super(MySQLInstanceViews, self).__init__() super(MySQLInstanceViews, self).__init__()
def pre_create(self, args):
"""加密保存密码,若提交了密码,必须加密存储"""
args = super().pre_create(args)
if "password" in args:
password = args.get("password")
args["password"] = quick_crypto(password)
return args
class MySQLInstanceDetail(parsers.DatabaseServerParse, DetailViewSet): class MySQLInstanceDetail(parsers.DatabaseServerParse, DetailViewSet):
model = assetModel.MySQLInstance model = assetModel.MySQLInstance
@ -74,6 +89,112 @@ class MySQLInstanceDetail(parsers.DatabaseServerParse, DetailViewSet):
self.request_parse.add_argument("core", required=False, type=int, location='json') self.request_parse.add_argument("core", required=False, type=int, location='json')
super(MySQLInstanceDetail, self).__init__() super(MySQLInstanceDetail, self).__init__()
def pre_update(self, obj: assetModel.MySQLInstance, args: dict):
"""加密保存密码,若提交了密码,必须加密存储"""
data = super().pre_update(obj, args)
if "password" in data:
password = data.get("password")
if password != obj.password:
data["password"] = quick_crypto(password)
return data
class DatabaseViews(parsers.DatabaseParse, Resource):
model = assetModel.DatabaseServer
db_model = assetModel.Database
db_fields = assetField.DatabaseFields
def __init__(self):
self.init_parse()
self.request_parse.add_argument("name", type=str, location='json', required=True)
def post(self, pk):
"""创建实例中的内嵌数据库对象"""
# 解析参数
args = self.request_parse.parse_args()
# 找到数据库服务器示例传入的pk是数据库实例的id
try:
db_server = assetModel.DatabaseServer.objects(id=pk).first_or_404(message="db instance not found.")
except:
abort_response(404, code=1404, msg="db instance not found.")
return
# 库名不能重复
name = args.get("name")
exists = db_server.databases.filter(name=name).first()
if exists:
abort_response(400, 1400, msg=f"数据库{name}在实例中已存在!")
# 加密存储密码
if "password" in args:
password = args["password"]
if password:
args["password"] = quick_crypto(password)
# 保存对象,追加到实例的数据库列表中
try:
obj = self.db_model(**args)
db_server.databases.append(obj)
db_server.save()
except Exception as e:
logger.exception(f"{self.db_model} 创建对象失败! data={args}")
abort_response(500, 1500, msg=f"保存对象失败!{str(e)}")
return
# 返回创建信息
return marshal(obj, self.db_fields)
class DatabaseDetailViews(parsers.DatabaseParse, Resource):
model = assetModel.DatabaseServer
db_model = assetModel.Database
db_fields = assetField.DatabaseFields
def __init__(self):
self.init_parse()
self.request_parse.add_argument("name", type=str, location='json', required=True)
def put(self, pk, db):
"""创建实例中的内嵌数据库对象"""
# 解析参数
args = self.request_parse.parse_args()
# 找到数据库服务器示例传入的pk是数据库实例的iddb为内嵌数据库列表的对象id
try:
db_server = assetModel.DatabaseServer.objects(id=pk).first_or_404(message="db instance not found.")
db_obj = db_server.databases.filter(id=db).first()
assert db_obj
except Exception as e:
print(e)
abort_response(404, code=1404, msg="db not found.")
return
name = args.get("name")
if name:
exists = db_server.databases.filter(name=name).first()
if exists and exists.id != db_obj.id:
abort_response(400, 1400, msg=f"数据库{name}在实例中已存在!")
# 若更新密码,重新加密保存
if "password" in args:
password = args["password"]
if password != db_obj.password:
args["password"] = quick_crypto(password)
# 去除id
args.pop("id", "")
# 更新内嵌对象
try:
db_server.databases.filter(id=db).update(**args)
db_server.save()
db_server.reload()
except Exception as e:
logger.exception(f"{self.db_model} 更新对象失败! data={args}")
abort_response(500, 1500, msg=f"更新对象失败!{str(e)}")
return
# 重载数据
db_obj = db_server.databases.filter(id=db).first()
# 返回信息
return marshal(db_obj, self.db_fields)
class RedisInstanceViews(parsers.DatabaseServerParse, ListCreateViewSet): class RedisInstanceViews(parsers.DatabaseServerParse, ListCreateViewSet):
model = assetModel.RedisInstance model = assetModel.RedisInstance

View File

@ -1,6 +1,6 @@
import logging
import datetime import datetime
from flask import current_app as app
from flask_restful import Resource, reqparse, marshal, fields as F from flask_restful import Resource, reqparse, marshal, fields as F
from models.asset.models import Host from models.asset.models import Host
@ -12,6 +12,8 @@ from common.permission import token_header_required
from common.utils import make_response from common.utils import make_response
from service.project.sync import sync_project_for_ops1 from service.project.sync import sync_project_for_ops1
logger = logging.getLogger("views")
class ProjectSyncView(Resource): class ProjectSyncView(Resource):
"""从 OPS1 同步项目,触发接口""" """从 OPS1 同步项目,触发接口"""
@ -112,12 +114,12 @@ class ServerSyncView(CreateMixin):
defaults = dict(num=num, channel_id=channel, host_id=host, version=version, port=port, status=status) 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) obj, created = Server.update_or_create(defaults=defaults, num=num, channel_id=channel)
if created: # 创建 if created: # 创建
app.logger.debug(f"创建 {spid}_{num} 区服信息成功") logger.debug(f"创建 {spid}_{num} 区服信息成功")
continue continue
# 更新对象日志 # 更新对象日志
app.logger.debug(f"更新 {spid}_{num} 区服信息成功") logger.debug(f"更新 {spid}_{num} 区服信息成功")
except: except:
app.logger.exception("同步区服出错") logger.exception("同步区服出错")
errors.append(f"{spid}_s{num}") errors.append(f"{spid}_s{num}")
continue continue

View File

@ -43,6 +43,9 @@ DatabaseFields = {
"name": fields.String, "name": fields.String,
"username": fields.String, "username": fields.String,
"password": fields.String, "password": fields.String,
"data": fields.Raw,
"tags": fields.List(fields.String),
"labels": fields.Raw,
} }
MySQLInstanceFields = { MySQLInstanceFields = {

View File

@ -52,6 +52,9 @@ class Database(mongo.EmbeddedDocument):
name = mongo.StringField(required=True) name = mongo.StringField(required=True)
username = mongo.StringField(default="") username = mongo.StringField(default="")
password = mongo.StringField(default="") password = mongo.StringField(default="")
data = mongo.DictField(default=dict)
tags = mongo.ListField(mongo.StringField(), default=list) # tags 默认是空列表
labels = mongo.DictField(default=dict)
class MySQLInstance(DatabaseServer): class MySQLInstance(DatabaseServer):