From eb0f5746bae84eee54d8c699e36bfd4a39eedcc9 Mon Sep 17 00:00:00 2001 From: e Date: Tue, 22 Sep 2020 18:31:31 +0800 Subject: [PATCH] =?UTF-8?q?pkgship=E5=88=9D=E5=A7=8B=E5=8C=96=E6=97=B6?= =?UTF-8?q?=E5=AF=B9=E4=BA=8E=E4=B9=8B=E5=89=8D=E5=AD=98=E5=9C=A8=E7=9A=84?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E5=A4=84=E7=90=86=E4=B8=8D=E5=90=88?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apps/lifecycle/function/download_yaml.py | 8 +- .../application/apps/lifecycle/view.py | 5 +- .../apps/package/function/constants.py | 2 + .../apps/package/function/searchdb.py | 25 ++-- .../application/apps/package/view.py | 40 +++--- .../application/initsystem/data_import.py | 122 ++++++++++-------- .../packageship/application/models/package.py | 10 ++ .../get_repodatas.json | 4 +- .../test/common_files/dbs/lifecycle.db | Bin 9990144 -> 9990144 bytes .../common_files/operate_dbs/lifecycle.db | Bin 19021824 -> 19021824 bytes .../init_system_tests/test_importdata.py | 108 +++++++--------- .../repodatas_test/test_delete_repodatas.py | 19 +-- 12 files changed, 176 insertions(+), 167 deletions(-) diff --git a/packageship/packageship/application/apps/lifecycle/function/download_yaml.py b/packageship/packageship/application/apps/lifecycle/function/download_yaml.py index 75061680..642a07cf 100644 --- a/packageship/packageship/application/apps/lifecycle/function/download_yaml.py +++ b/packageship/packageship/application/apps/lifecycle/function/download_yaml.py @@ -9,7 +9,7 @@ from concurrent.futures import ThreadPoolExecutor import datetime as date import requests import yaml -from retrying import retry + from sqlalchemy.exc import SQLAlchemyError from requests.exceptions import HTTPError from packageship import system_config @@ -124,8 +124,7 @@ class ParseYaml(): def _save_maintainer_info(maintainer_module): with DBHelper(db_name="lifecycle") as database: _packages_maintainer = database.session.query( - PackagesMaintainer).filter( - PackagesMaintainer.name == maintainer_module['name']).first() + PackagesMaintainer).filter(PackagesMaintainer.name == maintainer_module['name']).first() if _packages_maintainer: for key, val in maintainer_module.items(): setattr(_packages_maintainer, key, val) @@ -202,7 +201,8 @@ def update_pkg_info(pkg_info_update=True): # Open thread pool pool = ThreadPoolExecutor(max_workers=pool_workers) with DBHelper(db_name="lifecycle") as database: - for table_name in filter(lambda x: x not in ['packages_issue', 'packages_maintainer', 'database_info'], + for table_name in filter(lambda x: x not in ['packages_issue', 'packages_maintainer', + 'databases_info'], database.engine.table_names()): cls_model = Packages.package_meta(table_name) diff --git a/packageship/packageship/application/apps/lifecycle/view.py b/packageship/packageship/application/apps/lifecycle/view.py index d8ef03c0..f02b87c7 100644 --- a/packageship/packageship/application/apps/lifecycle/view.py +++ b/packageship/packageship/application/apps/lifecycle/view.py @@ -16,7 +16,6 @@ from flask import jsonify, make_response from flask import current_app from flask_restful import Resource from marshmallow import ValidationError - from sqlalchemy.exc import DisconnectionError, SQLAlchemyError from packageship import system_config @@ -245,6 +244,7 @@ class LifeTables(Resource): all_table_names = database_name.engine.table_names() all_table_names.remove("packages_issue") all_table_names.remove("packages_maintainer") + all_table_names.remove("databases_info") return jsonify( ResponseCode.response_json( ResponseCode.SUCCESS, data=all_table_names) @@ -458,7 +458,8 @@ class IssueCatch(Resource): pool_workers = 10 pool = ThreadPoolExecutor(max_workers=pool_workers) with DBHelper(db_name="lifecycle") as database: - for table_name in filter(lambda x: x not in ['packages_issue', 'packages_maintainer', 'database_info'], + for table_name in filter(lambda x: x not in ['packages_issue', 'packages_maintainer', + 'databases_info'], database.engine.table_names()): cls_model = Packages.package_meta(table_name) for package_item in database.session.query(cls_model).filter( diff --git a/packageship/packageship/application/apps/package/function/constants.py b/packageship/packageship/application/apps/package/function/constants.py index 083881ce..872632b9 100644 --- a/packageship/packageship/application/apps/package/function/constants.py +++ b/packageship/packageship/application/apps/package/function/constants.py @@ -44,6 +44,7 @@ class ResponseCode(): UPDATA_OR_ADD_DATA_FAILED = "4008" TABLE_NAME_NOT_EXIST = "4009" UPDATA_DATA_FAILED = "4010" + NOT_FOUND_DATABASE_INFO = "4011" # Database operation module error status code DELETE_DB_ERROR = "40051" SERVICE_ERROR = "50000" @@ -83,6 +84,7 @@ class ResponseCode(): TABLE_NAME_NOT_EXIST: "There is no such table in the database", UPDATA_DATA_FAILED: "Failed to update data", TABLE_NAME_NOT_EXIST_IN_DATABASE: "the table name dose not match the existed database", + NOT_FOUND_DATABASE_INFO: "Unable to get the generated database information", YAML_FILE_ERROR: "Data error in yaml file", EMPTY_FOLDER: "This is an empty folder, no yaml file" } diff --git a/packageship/packageship/application/apps/package/function/searchdb.py b/packageship/packageship/application/apps/package/function/searchdb.py index 1624e0d0..e6011524 100644 --- a/packageship/packageship/application/apps/package/function/searchdb.py +++ b/packageship/packageship/application/apps/package/function/searchdb.py @@ -15,9 +15,9 @@ from sqlalchemy import exists from packageship.libs.dbutils import DBHelper from packageship.libs.log import Log -from packageship.application.models.package import BinPack, SrcPack -from packageship.libs.exception import ContentNoneException, Error -from packageship.system_config import DATABASE_FILE_INFO +from packageship.application.models.package import BinPack +from packageship.application.models.package import SrcPack +from packageship.application.models.package import DatabaseInfo from packageship.application.apps.package.function.constants import ResponseCode LOGGER = Log(__name__) @@ -928,17 +928,10 @@ def db_priority(): Error: abnormal error """ try: - with open(DATABASE_FILE_INFO, 'r', encoding='utf-8') as file_context: - - init_database_date = yaml.load( - file_context.read(), Loader=yaml.FullLoader) - if init_database_date is None: - raise ContentNoneException( - "The content of the database initialization configuration file cannot be empty") - init_database_date.sort(key=lambda x: x['priority'], reverse=False) - db_list = [item.get('database_name') - for item in init_database_date] - return db_list - except (FileNotFoundError, Error) as file_not_found: - current_app.logger.error(file_not_found) + with DBHelper(db_name='lifecycle') as data_name: + name_list = data_name.session.query( + DatabaseInfo.name).order_by(DatabaseInfo.priority).all() + return [name[0] for name in name_list] + except SQLAlchemyError as error: + current_app.logger.error(error) return None diff --git a/packageship/packageship/application/apps/package/view.py b/packageship/packageship/application/apps/package/view.py index 24c1f183..d84492eb 100644 --- a/packageship/packageship/application/apps/package/view.py +++ b/packageship/packageship/application/apps/package/view.py @@ -10,21 +10,23 @@ from flask import jsonify from flask import current_app from flask_restful import Resource from sqlalchemy.exc import DisconnectionError +from sqlalchemy.exc import SQLAlchemyError from packageship import system_config from packageship.application.initsystem.data_import import InitDataBase from packageship.libs.configutils.readconfig import ReadConfig +from packageship.libs.dbutils import DBHelper from packageship.libs.exception import Error from packageship.libs.exception import ContentNoneException from packageship.libs.exception import DataMergeException from packageship.libs.exception import ConfigurationException from packageship.libs.log import Log -from packageship.system_config import DATABASE_FILE_INFO from .function.constants import ResponseCode from .function.packages import get_all_package_info from .function.packages import sing_pack from .function.searchdb import db_priority -from .serialize import AllPackagesSchema, SinglepackSchema +from .serialize import AllPackagesSchema +from .serialize import SinglepackSchema from .serialize import DeletedbSchema from .serialize import InitSystemSchema @@ -37,6 +39,7 @@ from .serialize import InstallDependSchema from .serialize import BuildDependSchema from .serialize import SelfDependSchema from .serialize import have_err_db_name +from ...models.package import DatabaseInfo LOGGER = Log(__name__) @@ -201,7 +204,7 @@ class InstallDepend(Resource): if not db_pri: return jsonify( ResponseCode.response_json( - ResponseCode.FILE_NOT_FIND_ERROR + ResponseCode.NOT_FOUND_DATABASE_INFO ) ) @@ -277,7 +280,7 @@ class BuildDepend(Resource): if not db_pri: return jsonify( ResponseCode.response_json( - ResponseCode.FILE_NOT_FIND_ERROR + ResponseCode.NOT_FOUND_DATABASE_INFO ) ) @@ -356,7 +359,7 @@ class SelfDepend(Resource): if not db_pri: return jsonify( ResponseCode.response_json( - ResponseCode.FILE_NOT_FIND_ERROR + ResponseCode.NOT_FOUND_DATABASE_INFO ) ) db_list = data.get("db_list") if data.get("db_list") \ @@ -481,23 +484,18 @@ class Repodatas(Resource): Error: abnormal Error """ try: - with open(DATABASE_FILE_INFO, 'r', encoding='utf-8') as file_context: - init_database_date = yaml.load( - file_context.read(), Loader=yaml.FullLoader) - if init_database_date is None: - raise ContentNoneException( - "The content of the database initialization configuration " - "file cannot be empty ") - init_database_date.sort( - key=lambda x: x['priority'], reverse=False) + with DBHelper(db_name='lifecycle') as data_name: + name_list = data_name.session.query( + DatabaseInfo.name, DatabaseInfo.priority).order_by(DatabaseInfo.priority).all() + data_list = [dict(zip(ven.keys(), ven)) for ven in name_list] return jsonify( ResponseCode.response_json( ResponseCode.SUCCESS, - data=init_database_date)) - except (FileNotFoundError, TypeError, Error) as file_not_found: - current_app.logger.error(file_not_found) + data=data_list)) + except (SQLAlchemyError, Error) as data_info_error: + current_app.logger.error(data_info_error) return jsonify( - ResponseCode.response_json(ResponseCode.FILE_NOT_FOUND) + ResponseCode.response_json(ResponseCode.NOT_FOUND_DATABASE_INFO) ) def delete(self): @@ -536,13 +534,13 @@ class Repodatas(Resource): try: drop_db = InitDataBase() del_result = drop_db.delete_db(db_name) - if del_result is False: + if not del_result: return jsonify( ResponseCode.response_json(ResponseCode.DELETE_DB_ERROR)) return jsonify( ResponseCode.response_json(ResponseCode.SUCCESS) ) - except (FileNotFoundError, TypeError, Error) as error: + except (SQLAlchemyError, TypeError, Error) as error: current_app.logger.error(error) return jsonify( ResponseCode.response_json(ResponseCode.DELETE_DB_ERROR) @@ -612,7 +610,7 @@ class InitSystem(Resource): except FileNotFoundError as file_not_found_error: LOGGER.logger.error(file_not_found_error) abnormal = ResponseCode.FILE_NOT_FIND_ERROR - except Error as error: + except (Error, SQLAlchemyError) as error: LOGGER.logger.error(error) abnormal = ResponseCode.FAILED_CREATE_DATABASE_TABLE if abnormal is not None: diff --git a/packageship/packageship/application/initsystem/data_import.py b/packageship/packageship/application/initsystem/data_import.py index a5846bd8..0045de0f 100644 --- a/packageship/packageship/application/initsystem/data_import.py +++ b/packageship/packageship/application/initsystem/data_import.py @@ -5,8 +5,8 @@ Description: Initialization of data import Class: InitDataBase,MysqlDatabaseOperations,SqliteDatabaseOperations """ import os -import pathlib import yaml +from sqlalchemy import text from sqlalchemy.exc import SQLAlchemyError, InternalError from packageship.libs.dbutils.sqlalchemy_helper import DBHelper from packageship.libs.exception import ContentNoneException @@ -16,6 +16,7 @@ from packageship.libs.exception import ConfigurationException from packageship.libs.configutils.readconfig import ReadConfig from packageship.libs.log import Log from packageship.application.models.package import SrcPack +from packageship.application.models.package import DatabaseInfo from packageship.application.models.package import BinPack from packageship.application.models.package import BinRequires from packageship.application.models.package import SrcRequires @@ -65,7 +66,8 @@ class InitDataBase(): # Create life cycle related databases and tables if not self.create_database(db_name='lifecycle', tables=['packages_issue', - 'packages_maintainer'], + 'packages_maintainer', + 'databases_info'], storage=True): raise SQLAlchemyError( 'Failed to create the specified database and table:lifecycle') @@ -129,9 +131,14 @@ class InitDataBase(): if self.__exists_repeat_database(): raise DatabaseRepeatException( 'There is a duplicate database configuration') - if not InitDataBase.delete_settings_file(): - raise IOError( - 'An error occurred while deleting the database configuration file') + + if not self.__clear_database(): + raise SQLAlchemyError( + 'Failed to delete the database, throw an exception') + + if not InitDataBase.__clear_database_info(): + raise SQLAlchemyError( + 'Failed to clear data in database_info or lifecycle database') for database_config in self.config_file_datas: if not database_config.get('dbname'): @@ -215,8 +222,7 @@ class InitDataBase(): 'database_name': _db_name, 'priority': database_config.get('priority'), } - InitDataBase.__updata_settings_file( - database_content=database_content) + InitDataBase.__update_database_info(database_content) except (SQLAlchemyError, ContentNoneException, TypeError, Error, FileNotFoundError) as error_msg: @@ -498,53 +504,66 @@ class InitDataBase(): return False @staticmethod - def __updata_settings_file(**Kwargs): + def __update_database_info(database_content): """ - update some configuration files related to the database in the system + Update the database_name table Args: - **Kwargs: data related to configuration file nodes - database_name: Name database + database_content: + Dictionary of database names and priorities Returns: - Raises: - FileNotFoundError: The specified file was not found - IOError: File or network operation io abnormal """ try: - if not os.path.exists(system_config.DATABASE_FILE_INFO): - pathlib.Path(system_config.DATABASE_FILE_INFO).touch() - with open(system_config.DATABASE_FILE_INFO, 'a+', encoding='utf8') as file_context: - setting_content = [] - if 'database_content' in Kwargs.keys(): - content = Kwargs.get('database_content') - if content: - setting_content.append(content) - yaml.dump(setting_content, file_context) - - except FileNotFoundError as not_found: - LOGGER.logger.error(not_found) - except IOError as exception_msg: - LOGGER.logger.error(exception_msg) + with DBHelper(db_name="lifecycle") as database_name: + name = database_content.get("database_name") + priority = database_content.get("priority") + database_name.add(DatabaseInfo( + name=name, priority=priority + )) + database_name.session.commit() + except (SQLAlchemyError, Error, AttributeError) as error: + LOGGER.logger.error(error) @staticmethod - def delete_settings_file(): + def __clear_database_info(): """ - Delete the configuration file of the database + Delete the tables in the lifecycle except for the specific three tables + Returns: - Args: + """ + try: + with DBHelper(db_name="lifecycle") as database_name: + clear_sql = """delete from databases_info;""" + database_name.session.execute(text(clear_sql)) + table_list = database_name.engine.table_names() + for item in table_list: + if item not in ['packages_maintainer', 'databases_info', 'packages_issue']: + drop_sql = '''DROP TABLE if exists `{table_name}`'''.format( + table_name=item) + database_name.session.execute(text(drop_sql)) + database_name.session.commit() + except SQLAlchemyError as sql_error: + LOGGER.logger.error(sql_error) + return False + else: + return True - Returns: - True if the deletion is successful, otherwise false - Raises: - IOError: File or network operation io abnormal + def __clear_database(self): """ + Delete database + Returns: + """ try: - if os.path.exists(system_config.DATABASE_FILE_INFO): - os.remove(system_config.DATABASE_FILE_INFO) - except (IOError, Error) as exception_msg: - LOGGER.logger.error(exception_msg) + with DBHelper(db_name='lifecycle') as data_name: + name_data_list = data_name.session.query( + DatabaseInfo.name).order_by(DatabaseInfo.priority).all() + name_list = [name[0] for name in name_data_list if name[0]] + for item in name_list: + self.__del_database(item) + except (SQLAlchemyError, Error, IOError) as error: + LOGGER.logger.error(error) return False else: return True @@ -562,25 +581,18 @@ class InitDataBase(): """ try: del_result = True - file_read = open( - system_config.DATABASE_FILE_INFO, 'r', encoding='utf-8') - _databases = yaml.load( - file_read.read(), Loader=yaml.FullLoader) - for database in _databases: - if database.get('database_name') == db_name: - _databases.remove(database) - # Delete the successfully imported database configuration node - with open(system_config.DATABASE_FILE_INFO, 'w+', encoding='utf-8') as file_context: - yaml.safe_dump(_databases, file_context) - except (IOError, Error) as del_config_error: - LOGGER.logger.error(del_config_error) + with DBHelper(db_name='lifecycle') as database_name: + database_name.session.query(DatabaseInfo).filter( + DatabaseInfo.name == db_name).delete() + drop_sql = '''DROP TABLE if exists `{table_name}`''' \ + .format(table_name=db_name) + database_name.session.execute(text(drop_sql)) + database_name.session.commit() + except SQLAlchemyError as sql_error: + LOGGER.logger.error(sql_error) del_result = False - finally: - file_read.close() - if del_result: del_result = self.__del_database(db_name) - return del_result def create_database(self, db_name, tables=None, storage=True): diff --git a/packageship/packageship/application/models/package.py b/packageship/packageship/application/models/package.py index f95bfcc2..2d9d2047 100644 --- a/packageship/packageship/application/models/package.py +++ b/packageship/packageship/application/models/package.py @@ -202,3 +202,13 @@ class PackagesMaintainer(DBHelper.BASE): name = Column(String(200), nullable=True) maintainer = Column(String(200), nullable=True) maintainlevel = Column(Integer, nullable=True) + + +class DatabaseInfo(DBHelper.BASE): + """ + Save the name and priority of the database + """ + __tablename__ = 'databases_info' + id = Column(Integer, primary_key=True) + name = Column(String(200), nullable=True) + priority = Column(Integer, nullable=True) diff --git a/packageship/test/common_files/correct_test_result_json/get_repodatas.json b/packageship/test/common_files/correct_test_result_json/get_repodatas.json index 8b8d0124..c977baa4 100644 --- a/packageship/test/common_files/correct_test_result_json/get_repodatas.json +++ b/packageship/test/common_files/correct_test_result_json/get_repodatas.json @@ -1,10 +1,10 @@ [ { - "database_name": "mainline", + "name": "mainline", "priority": 1 }, { - "database_name": "fedora30", + "name": "fedora30", "priority": 2 } ] \ No newline at end of file diff --git a/packageship/test/common_files/dbs/lifecycle.db b/packageship/test/common_files/dbs/lifecycle.db index 264f168a630bc3e8485bdf9e464d8a56e409b808..457902d611737b640179d876a3a9e5c67b68c3fa 100644 GIT binary patch delta 748 zcmY++O;^kT9L4eff0~-9rp5?S;#-8040+B|Wu!DA<(W*H7*b8;A&(VqD3l@AUP0%y zXr)(RV`a<98(?SWw{7t`_uO;Oxx+tyd56tpVQ9v2GRGXpmuK1LI8OOgMOD#^Q(65d zC(Y+%xKStjpZ|Bp4ez`g^@iN28%>SmexHrHUtKqAt~fmIbL-0kzWlN>cQiN{>*$Vm z#w=n3@qzY4Pgn2a=BaCm)U-xut*LE_P_lSTg#+Q#w?I~?uRqb-pBNk}p`p1oQXgqa zCDRLL=e@Pr)8VAo?+GqA!N0+U*$_X1ML8a?Q{;BVJA3SWWQr`2EdnA(%NaiJqDx#CH$+0*6t_gTxGj1_ujrd5>2o{g{3J)yJ^n9a6QxPhZwAbu zxnu5{d*;3wG7ro{^T<3lPs~#@Y(~ted1juQ7e;2xyfm-OYcp;p%p3F8yfc$#%Dguz R^TB*H)8^CMMCs?j#{c^l@QMHc delta 1786 zcmciB?Nd~B6bJBKU|AkSS~pTM^NU3RkuCTTMP!7VqCATzm9CfF3vBEIuoqnDK^FCk z3JMAeWkWB7nWiSpu?a=YzoD1S)J)BA7kgE28{c#mgIyu(8tVr0E##$ZSuFc{2MSsM%nL+<(fEgNPGn|J(_7H>9~jFpDezmgUbTN8dTR^I=O zTH=4aUTM5-w9l@!+l?pNG^NR-I@%Ryo6@2N9PU7%OLh1?E=QrQ&}J(t-1?|o+~_Z+ zb;tf)vk5sV$!-_QtLp8$>~*NBu1D4Wii&*5_RT<}cDuLK^q_*}n(%0rc&D#=t$5AukPv|$TNt?}c zPvn_QPG_hnZG6%@@%>V`DjKFn!<1;491W9}LTjXhd9@)FUy_j-innH1GcDs;hbH!= z_}f)qc|mpUfueC^TSrM&Z>!R(lr`8ROEBp3g|;WgmSsG=Y-3AInb#XSWLhq&m9rjR zRJ{9-`JwRe$im!Ic=CjPYc4!?H9QmwpPSZ)ujs=Q;mhBJuk?k#nD)1OW4bprg^nb| zbkF>c?t34*dq<@E;{4q2pUy_2?(Wc$_(vXKi#)30$3H-O@POXl(BZh{1F9_<%Xe^W zZe5|K=#5UIo} zB8^y0q!UjN7Ge!yC7vWQh^L6P#5y9Ac$!#GJVR_Co+UOC(m#vXL_9}4Ph=A>5HAur zL@tp>yhP*^FB6-IR|p$XKok;Nh^@pnVmt9FQAE5(yiODoZxAKKo5T*{EuxfonQeqDyp( zqarAJM6dWzd?Y>=pNM0kPlQCjI4(|zlY(MEoD!$S88Ik^#947p42uzQUR)5PVoY2V Nm&Ew&Q1*nj=Wo0@tn~l@ diff --git a/packageship/test/common_files/operate_dbs/lifecycle.db b/packageship/test/common_files/operate_dbs/lifecycle.db index 9053816339c706c36913a234b7e928c439529b21..27f42bd18c02efbd6be91f531faa4825fae77025 100644 GIT binary patch delta 1325 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