diff --git a/host-information-service/zeus/host_information_service/manage.py b/host-information-service/zeus/host_information_service/manage.py index 49af54e0804e2512a820ebff3b746e2a99416cf6..4bd9b2b662faba4b3532db75c9af6bc1bf280603 100644 --- a/host-information-service/zeus/host_information_service/manage.py +++ b/host-information-service/zeus/host_information_service/manage.py @@ -21,14 +21,25 @@ import socket from vulcanus import init_application from vulcanus.database.proxy import RedisProxy +from vulcanus.restful.resp import state +from vulcanus.restful.response import make_response from vulcanus.log.log import LOGGER from vulcanus.registry.register_service.zookeeper import ZookeeperRegisterCenter +from zeus.host_information_service.app import cache from zeus.host_information_service.app.core.subscription import TaskCallbackSubscribe from zeus.host_information_service.app.settings import configuration from zeus.host_information_service.urls import URLS -app = init_application(name="zeus.host_information_service", settings=configuration, register_urls=URLS) +app_name = "zeus.host_information_service" +app = init_application(name=app_name, settings=configuration, register_urls=URLS) + + +@app.before_request +def validate_feature(): + if app_name not in cache.activated_features: + return make_response(label=state.PERMESSION_ERROR, message="Service not activated") + return None def register_service(): diff --git a/user-access-service/zeus/user_access_service/app/proxy/feature.py b/user-access-service/zeus/user_access_service/app/proxy/feature.py new file mode 100644 index 0000000000000000000000000000000000000000..17ddbc0874dafc8fd74fc8d9b6054b6a818436be --- /dev/null +++ b/user-access-service/zeus/user_access_service/app/proxy/feature.py @@ -0,0 +1,84 @@ +#!/usr/bin/python3 +# ****************************************************************************** +# Copyright (c) Huawei Technologies Co., Ltd. 2021-2024. All rights reserved. +# licensed under the Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN 'AS IS' BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# PURPOSE. +# See the Mulan PSL v2 for more details. +# ******************************************************************************/ +""" +Time: 2024/08/17 +Author: +Description: +""" +from typing import List, Tuple + +import sqlalchemy +from vulcanus.database.proxy import MysqlProxy, RedisProxy +from vulcanus.log.log import LOGGER +from vulcanus.restful.resp.state import SUCCEED, DATABASE_QUERY_ERROR, DATABASE_UPDATE_ERROR +from vulcanus.restful.response import BaseResponse +from zeus.user_access_service.database.table import Feature +from zeus.user_access_service.app import cache + + +class FeatureProxy(MysqlProxy): + """ + feature related table operation + """ + + def query_activated_features(self) -> Tuple[str, List[dict]]: + """ + Query all activated features from the database. + + Returns: + A tuple containing a status code and a list of dictionaries, each representing a feature. + """ + try: + rows = self.session.query(Feature).filter(Feature.enable == True).all() + RedisProxy.redis_connect.delete(cache.ACTIVATED_FEATURES) + RedisProxy.redis_connect.sadd(cache.ACTIVATED_FEATURES, *[row.feature_name for row in rows]) + return SUCCEED, [{"feature_name": row.feature_name, "feature_id": row.feature_id} for row in rows if rows] + except sqlalchemy.exc.SQLAlchemyError as error: + LOGGER.error(error) + LOGGER.error("Query active feature info failed.") + return DATABASE_QUERY_ERROR, [] + + def query_all_features(self) -> Tuple[str, List[dict]]: + """ + Query all features from the database. + + Returns: + A tuple containing a status code and a list of dictionaries, each representing a feature. + """ + try: + rows = self.session.query(Feature).all() + return SUCCEED, [row.to_dict() for row in rows if rows] + except sqlalchemy.exc.SQLAlchemyError as error: + LOGGER.error(error) + LOGGER.error("Query active feature info failed.") + return DATABASE_QUERY_ERROR, [] + + def update_feature(self, updated_features: List[dict]) -> str: + """ + Update the features in the database. + + Args: + updated_features: A list of dictionaries, each representing a feature to be updated. + + Returns: + A status code indicating whether the update was successful. + """ + try: + self.session.bulk_update_mappings(Feature, updated_features) + self.session.commit() + RedisProxy.redis_connect.delete(cache.ACTIVATED_FEATURES) + return SUCCEED + except sqlalchemy.exc.SQLAlchemyError as error: + LOGGER.error(error) + self.session.rollback() + return DATABASE_UPDATE_ERROR diff --git a/user-access-service/zeus/user_access_service/app/serialize/feature.py b/user-access-service/zeus/user_access_service/app/serialize/feature.py new file mode 100644 index 0000000000000000000000000000000000000000..d02d4802c267c21c0358f907165e25c817c6dd86 --- /dev/null +++ b/user-access-service/zeus/user_access_service/app/serialize/feature.py @@ -0,0 +1,31 @@ +#!/usr/bin/python3 +# ****************************************************************************** +# Copyright (c) Huawei Technologies Co., Ltd. 2021-2024. All rights reserved. +# licensed under the Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN 'AS IS' BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# PURPOSE. +# See the Mulan PSL v2 for more details. +# ******************************************************************************/ +""" +Time: 2024/08/17 +Author: +Description: Restful APIs for user +""" +from marshmallow import Schema, fields, validate + + +class FeatureStatusSchema(Schema): + feature_id = fields.String(required=True, validate=validate.Length(min=1, max=36)) + enable = fields.Boolean(required=True) + + +class UpdateFeatureSchema(Schema): + """ + Update feature schema + """ + + feature = fields.List(fields.Nested(FeatureStatusSchema), required=True, validate=validate.Length(min=1)) diff --git a/user-access-service/zeus/user_access_service/app/views/feature.py b/user-access-service/zeus/user_access_service/app/views/feature.py new file mode 100644 index 0000000000000000000000000000000000000000..52462a563927b79717caedd08adf8d1578230f6e --- /dev/null +++ b/user-access-service/zeus/user_access_service/app/views/feature.py @@ -0,0 +1,77 @@ +#!/usr/bin/python3 +# ****************************************************************************** +# Copyright (c) Huawei Technologies Co., Ltd. 2021-2024. All rights reserved. +# licensed under the Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN 'AS IS' BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# PURPOSE. +# See the Mulan PSL v2 for more details. +# ******************************************************************************/ +""" +Time: 2024/08/17 +Author: +Description: Restful APIs for feature +""" + +from vulcanus.database.proxy import RedisProxy +from vulcanus.restful.resp import state +from vulcanus.restful.response import BaseResponse +from zeus.user_access_service.app.proxy.feature import FeatureProxy +from zeus.user_access_service.app.serialize.feature import UpdateFeatureSchema + + +class Features(BaseResponse): + """ + Interface for features + """ + + @BaseResponse.handle(schema=UpdateFeatureSchema, token=False, proxy=FeatureProxy) + def post(self, callback: FeatureProxy, **params): + """ + update features status(on or off) + + Args: + feature (List): feature id with its status. e.g [{feature_id:id_string,enable:true}] + + Returns: + dict: response body + """ + update_res = callback.update_feature(params["feature"]) + if update_res != state.SUCCEED: + return self.response(code=update_res, message="Failed to switch features status。") + return self.response(code=state.SUCCEED) + + @BaseResponse.handle(token=False, proxy=FeatureProxy) + def get(self, callback: FeatureProxy): + """ + get features info + + Returns: + dict: response body + """ + status, features_info = callback.query_all_features() + if status != state.SUCCEED: + return self.response(code=status, message="Failed to switch features status。") + return self.response(code=state.SUCCEED, data=features_info) + + +class QueryActiveFeatures(BaseResponse): + """ + Interface for querying active features + """ + + @BaseResponse.handle(token=False, proxy=FeatureProxy) + def get(self, callback: FeatureProxy): + """ + get active features info + + Returns: + dict: response body + """ + status, features_info = callback.query_activated_features() + if status != state.SUCCEED: + return self.response(code=status, message="Failed to switch features status。") + return self.response(code=state.SUCCEED, data=features_info) diff --git a/user-access-service/zeus/user_access_service/database/__init__.py b/user-access-service/zeus/user_access_service/database/__init__.py index 46800dae9475c36f4483589ff7e11d54892b94bc..39dcf6f04deec2f3d47433aaf513ae7a3cc972fe 100644 --- a/user-access-service/zeus/user_access_service/database/__init__.py +++ b/user-access-service/zeus/user_access_service/database/__init__.py @@ -20,6 +20,7 @@ from zeus.user_access_service.database.table import ( UserClusterAssociation, UserMap, UserRoleAssociation, + Feature, ) __all__ = [ @@ -32,4 +33,5 @@ __all__ = [ "UserClusterAssociation", "UserMap", "UserRoleAssociation", + "Feature", ] diff --git a/user-access-service/zeus/user_access_service/database/table.py b/user-access-service/zeus/user_access_service/database/table.py index 6b20a6324f5399a993404f882fc0dec8f97f5d5c..cdb33c5b808000275ad7d5c9bcc867d37bb8328f 100644 --- a/user-access-service/zeus/user_access_service/database/table.py +++ b/user-access-service/zeus/user_access_service/database/table.py @@ -18,6 +18,21 @@ from werkzeug.security import check_password_hash, generate_password_hash Base = declarative_base() +class MyBase: # pylint: disable=R0903 + """ + Class that provide helper function + """ + + def to_dict(self): + """ + Transfer query data to dict + + Returns: + dict + """ + return {col.name: getattr(self, col.name) for col in self.__table__.columns} # pylint: disable=E1101 + + class Route(Base): """ web route table, each route_id is a record stored in Permission table. @@ -135,3 +150,12 @@ class UserMap(Base): manager_cluster_id = Column(String(36)) manager_username = Column(String(36), primary_key=True) public_key = Column(String(4096)) + + +class Feature(Base, MyBase): + __tablename__ = "feature" + + feature_id = Column(String(36), primary_key=True) + feature_name = Column(String(20), nullable=False) + description = Column(String(120)) + enable = Column(Boolean, default=False, nullable=False) diff --git a/user-access-service/zeus/user_access_service/database/zeus-user-access.sql b/user-access-service/zeus/user_access_service/database/zeus-user-access.sql index d70e99729e36fa8d7d1a98bec0fb835c14a2e537..323b41be59c90c716c11fc9b1aea54dca64c025a 100644 --- a/user-access-service/zeus/user_access_service/database/zeus-user-access.sql +++ b/user-access-service/zeus/user_access_service/database/zeus-user-access.sql @@ -76,6 +76,15 @@ CREATE TABLE IF NOT EXISTS `user_map` ( PRIMARY KEY (`username`, `manager_username`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic; +DROP TABLE IF EXISTS `feature`; +CREATE TABLE `feature` ( + `feature_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, + `feature_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, + `enable` tinyint(1) NOT NULL, + `description` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL, + PRIMARY KEY (`feature_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = DYNAMIC; + SET @role_type := "administrator"; SET @username := "admin"; SET @password := "pbkdf2:sha256:260000$LEwtriXN8UQ1UIA7$4de6cc1d67263c6579907eab7c1cba7c7e857b32e957f9ff5429592529d7d1b0"; diff --git a/user-access-service/zeus/user_access_service/urls.py b/user-access-service/zeus/user_access_service/urls.py index cdb43e41469ce6796cbda898b67300cc353b3106..162580e6275540354b13363d25e848bb022486c1 100644 --- a/user-access-service/zeus/user_access_service/urls.py +++ b/user-access-service/zeus/user_access_service/urls.py @@ -29,6 +29,7 @@ from zeus.user_access_service.app.views.account import ( RegisterClusterAPI, UnbindManagerUserAPI, ) +from zeus.user_access_service.app.views.feature import Features, QueryActiveFeatures from zeus.user_access_service.app.views.permission import AccountPageAPI, PermissionAccountBindAPI, PermissionAPI URLS = [ @@ -51,4 +52,6 @@ URLS = [ (PermissionAccountBindAPI, constant.PERMISSION_BIND), (AccountsAllAPI, constant.USERS_ALL), (ClusterSync, constant.CLUSTER_SYNC), + (Features, constant.FEATURES), + (QueryActiveFeatures, constant.ACTIVATED_FEATURES), ]