def remove_maintenance_service(name): """ Remove a maintenance service with a specific name :param name: Name of the maintenance service :type name: str """ MaintenanceController.remove_maintenance_service(name)
def add_maintenance_service(name): """ Add a maintenance service with a specific name :param name: Name of the maintenance service :type name: str """ alba_backend_guid = request.form['alba_backend_guid'] abm_name = request.form['abm_name'] MaintenanceController.add_maintenance_service(name, alba_backend_guid, abm_name)
def remove_maintenance_service(name, request_data): # type: (str, dict) -> None """ Remove a maintenance service with a specific name :param name: Name of the maintenance service :type name: str :param request_data: Data about the request (given by the decorator) :type request_data: dict :return: None :rtype: NoneType """ MaintenanceController.remove_maintenance_service( name=name, alba_backend_guid=request_data.get('alba_backend_guid'))
def add_maintenance_service(name, request_data): # type: (str, dict) -> None """ Add a maintenance service with a specific name :param name: Name of the maintenance service :type name: str :param request_data: Data about the request (given by the decorator) :type request_data: dict :return: None :rtype: NoneType """ MaintenanceController.add_maintenance_service( name=name, abm_name=request_data['abm_name'], alba_backend_guid=request_data['alba_backend_guid'], read_preferences=request_data.get('read_preferences', []))
def list_maintenance_services(): # type: () -> List[str] """ List all maintenance information :return: The names of all maintenance services found on the system :rtype: list """ return list(MaintenanceController.get_services())
def restart_services(): """ Restart the services ASD services and the Maintenance services :return: None """ service_names = [service_name for service_name in ASDController.list_asd_services()] service_names.extend([service_name for service_name in MaintenanceController.get_services()]) for service_name in service_names: status, _ = ServiceManager.get_service_status(service_name, SDMUpdateController._local_client) if status is False: SDMUpdateController._logger.warning('Found stopped service {0}. Will not start it.'.format(service_name)) continue SDMUpdateController._logger.debug('Restarting service {0}'.format(service_name)) try: ServiceManager.restart_service(service_name, SDMUpdateController._local_client) except CalledProcessError as cpe: SDMUpdateController._logger.debug('Failed to restart service {0} {1}'.format(service_name, cpe))
def restart_services(cls, service_names): """ Restart the services specified :param service_names: Names of the services to restart :type service_names: list[str] :return: None :rtype: NoneType """ if len(service_names) == 0: service_names = [service_name for service_name in ASDController.list_asd_services()] service_names.extend([service_name for service_name in MaintenanceController.get_services()]) for service_name in service_names: cls._logger.warning('Verifying whether service {0} needs to be restarted'.format(service_name)) if cls._service_manager.get_service_status(service_name, cls._local_client) != 'active': cls._logger.warning('Found stopped service {0}. Will not start it.'.format(service_name)) continue cls._logger.info('Restarting service {0}'.format(service_name)) try: cls._service_manager.restart_service(service_name, cls._local_client) except CalledProcessError: cls._logger.exception('Failed to restart service {0}'.format(service_name))
def migrate(cls): # type: () -> None """ Execute the migration logic. :return: None :rtype: NoneType """ with file_mutex('package_update_pu'): local_client = SSHClient(endpoint='127.0.0.1', username='******') # Override the created openvstorage_sdm_id during package install, with currently available SDM ID if local_client.file_exists(BOOTSTRAP_FILE): with open(BOOTSTRAP_FILE) as bstr_file: node_id = json.load(bstr_file)['node_id'] local_client.file_write(filename='/etc/openvstorage_sdm_id', contents=node_id + '\n') else: with open('/etc/openvstorage_sdm_id', 'r') as id_file: node_id = id_file.read().strip() key = '{0}/versions'.format( ASD_NODE_CONFIG_LOCATION.format(node_id)) version = Configuration.get(key) if Configuration.exists( key) else 0 asd_manager_service_name = 'asd-manager' if cls.service_manager.has_service( asd_manager_service_name, local_client) and cls.service_manager.get_service_status( asd_manager_service_name, local_client) == 'active': cls.logger.info('Stopping asd-manager service') cls.service_manager.stop_service(asd_manager_service_name, local_client) # @TODO: Move these migrations to alba_node.client.update_execute_migration_code() if version < cls.CURRENT_VERSION: try: # DB migrations from source.controllers.asd import ASDController from source.controllers.disk import DiskController from source.dal.asdbase import ASDBase from source.dal.lists.asdlist import ASDList from source.dal.lists.disklist import DiskList from source.dal.objects.asd import ASD if not local_client.file_exists('{0}/main.db'.format( ASDBase.DATABASE_FOLDER)): local_client.dir_create([ASDBase.DATABASE_FOLDER]) asd_map = dict( (asd.asd_id, asd) for asd in ASDList.get_asds()) DiskController.sync_disks() for disk in DiskList.get_usable_disks(): if disk.state == 'MISSING' or disk.mountpoint is None: continue for asd_id in local_client.dir_list(disk.mountpoint): if asd_id in asd_map: asd = asd_map[asd_id] else: asd = ASD() asd.disk = disk asd.asd_id = asd_id asd.folder = asd_id if asd.has_config: if asd.port is None or asd.hosts is None: config = Configuration.get( key=asd.config_key) asd.port = config['port'] asd.hosts = config.get('ips', []) asd.save() # Adjustment of open file descriptors for ASD/maintenance services to 8192 asd_service_names = list(ASDController.list_asd_services()) maintenance_service_names = list( MaintenanceController.get_services()) for service_name in asd_service_names + maintenance_service_names: if cls.service_manager.has_service( name=service_name, client=local_client): if cls.service_manager.__class__ == Systemd: path = '/lib/systemd/system/{0}.service'.format( service_name) check = 'LimitNOFILE=8192' else: path = '/etc/init/{0}.conf'.format( service_name) check = 'limit nofile 8192 8192' restart_required = False if os.path.exists(path): with open(path, 'r') as system_file: if check not in system_file.read(): restart_required = True if restart_required is False: continue configuration_key = ServiceFactory.SERVICE_CONFIG_KEY.format( node_id, service_name) if Configuration.exists(configuration_key): # Rewrite the service file cls.service_manager.add_service( name=ASDController.ASD_PREFIX if service_name in asd_service_names else MaintenanceController.MAINTENANCE_PREFIX, client=local_client, params=Configuration.get( configuration_key), target_name=service_name) # Let the update know that the ASD / maintenance services need to be restarted # Inside `if Configuration.exists`, because useless to rapport restart if we haven't rewritten service file ExtensionsToolbox.edit_version_file( client=local_client, package_name='alba', old_run_file='{0}/{1}.version'.format( ServiceFactory.RUN_FILE_DIR, service_name)) if cls.service_manager.__class__ == Systemd: local_client.run(['systemctl', 'daemon-reload']) # Version 3: Addition of 'ExecReload' for ASD/maintenance SystemD services if cls.service_manager.__class__ == Systemd: # Upstart does not have functionality to reload a process' configuration reload_daemon = False asd_service_names = list( ASDController.list_asd_services()) maintenance_service_names = list( MaintenanceController.get_services()) for service_name in asd_service_names + maintenance_service_names: if not cls.service_manager.has_service( name=service_name, client=local_client): continue path = '/lib/systemd/system/{0}.service'.format( service_name) if os.path.exists(path): with open(path, 'r') as system_file: if 'ExecReload' not in system_file.read(): reload_daemon = True configuration_key = ServiceFactory.SERVICE_CONFIG_KEY.format( node_id, service_name) if Configuration.exists( configuration_key): # No need to edit the service version file, since this change only requires a daemon-reload cls.service_manager.add_service( name=ASDController.ASD_PREFIX if service_name in asd_service_names else MaintenanceController. MAINTENANCE_PREFIX, client=local_client, params=Configuration.get( configuration_key), target_name=service_name) if reload_daemon is True: local_client.run(['systemctl', 'daemon-reload']) # Version 6: Introduction of Active Drive all_local_ips = OSFactory.get_manager().get_ip_addresses( client=local_client) for asd in ASDList.get_asds(): if asd.has_config: asd_config = Configuration.get(asd.config_key) if 'multicast' not in asd_config: asd_config['multicast'] = None if 'ips' in asd_config: asd_ips = asd_config['ips'] or all_local_ips else: asd_ips = all_local_ips asd.hosts = asd_ips asd_config['ips'] = asd_ips Configuration.set(asd.config_key, asd_config) asd.save() # Version 7: Moving flask certificate files to config dir for file_name in [ 'passphrase', 'server.crt', 'server.csr', 'server.key' ]: if local_client.file_exists( '/opt/asd-manager/source/{0}'.format( file_name)): local_client.file_move( source_file_name='/opt/asd-manager/source/{0}'. format(file_name), destination_file_name= '/opt/asd-manager/config/{0}'.format( file_name)) except: cls.logger.exception( 'Error while executing post-update code on node {0}'. format(node_id)) Configuration.set(key, cls.CURRENT_VERSION) if cls.service_manager.has_service( asd_manager_service_name, local_client) and cls.service_manager.get_service_status( asd_manager_service_name, local_client) != 'active': cls.logger.info('Starting asd-manager service') cls.service_manager.start_service(asd_manager_service_name, local_client) cls.logger.info('Post-update logic executed')
def get_package_information(cls): """ Retrieve the installed and candidate versions of all packages relevant for this repository (See PackageFactory.get_package_info) If installed version is lower than candidate version, this information is stored If installed version is equal or higher than candidate version we verify whether all relevant services have the correct binary active Whether a service has the correct binary version in use, we use the ServiceFactory.get_service_update_versions functionality In this function the services for each component / package combination are defined This service information consists out of: * Services to stop (before update) and start (after update of packages) -> 'services_stop_start' * Services to restart after update (post-update logic) -> 'services_post_update' * Down-times which will be caused due to service restarts -> 'downtime' * Prerequisites that have not been met -> 'prerequisites' The installed vs candidate version which is displayed always gives priority to the versions effectively installed on the system and not the versions as reported by the service files This combined information is then stored in the 'package_information' of the ALBA Node DAL object :return: Update information :rtype: dict """ cls._logger.info('Refreshing update information') binaries = cls._package_manager.get_binary_versions(client=cls._local_client) update_info = {} package_info = PackageFactory.get_packages_to_update(client=cls._local_client) # {'alba': {'openvstorage-sdm': {'installed': 'ee-1.6.1', 'candidate': 'ee-1.6.2'}}} cls._logger.debug('Binary versions found: {0}'.format(binaries)) cls._logger.debug('Package info found: {0}'.format(package_info)) for component, package_names in PackageFactory.get_package_info()['names'].iteritems(): package_names = sorted(package_names) cls._logger.debug('Validating component {0} and related packages: {1}'.format(component, package_names)) if component not in update_info: update_info[component] = copy.deepcopy(ServiceFactory.DEFAULT_UPDATE_ENTRY) svc_component_info = update_info[component] pkg_component_info = package_info.get(component, {}) for package_name in package_names: cls._logger.debug('Validating package {0}'.format(package_name)) if package_name in [PackageFactory.PKG_ALBA, PackageFactory.PKG_ALBA_EE]: for service_name in sorted(list(ASDController.list_asd_services())) + sorted(list(MaintenanceController.get_services())): service_version = ServiceFactory.get_service_update_versions(client=cls._local_client, service_name=service_name, binary_versions=binaries) cls._logger.debug('Service {0} has version: {1}'.format(service_name, service_version)) # If package_name in pkg_component_info --> update available (installed <--> candidate) # If service_version is not None --> service is running an older binary version if package_name in pkg_component_info or service_version is not None: svc_component_info['services_post_update'][20].append(service_name) if service_version is not None and package_name not in svc_component_info['packages']: svc_component_info['packages'][package_name] = service_version # Extend the service information with the package information related to this repository for current ALBA Node if package_name in pkg_component_info and package_name not in svc_component_info['packages']: cls._logger.debug('Adding package {0} because it has an update available'.format(package_name)) svc_component_info['packages'][package_name] = pkg_component_info[package_name] cls._logger.info('Refreshed update information') return update_info
def execute_migration_code(cls): # type: () -> None """ Run some migration code after an update has been done :return: None :rtype: NoneType """ cls._logger.info('Starting out of band migrations for SDM nodes') ########################### # Start crucial migration # ########################### # Removal of bootstrap file and store API IP, API port and node ID in SQLite DB try: if cls._local_client.file_exists(BOOTSTRAP_FILE): cls._logger.info('Bootstrap file still exists. Retrieving node ID') with open(BOOTSTRAP_FILE) as bstr_file: node_id = json.load(bstr_file)['node_id'] else: node_id = SettingList.get_setting_by_code(code='node_id').value except Exception: cls._logger.exception('Unable to determine the node ID, cannot migrate') raise try: api_settings_map = {'api_ip': 'ip', 'api_port': 'port'} # Map settings code to keys in the Config management required_settings = ['node_id', 'migration_version'] + api_settings_map.keys() for settings_code in required_settings: try: _ = SettingList.get_setting_by_code(settings_code) except ObjectNotFoundException: cls._logger.info('Missing required settings: {0}'.format(settings_code)) if settings_code == 'node_id': value = node_id elif settings_code in api_settings_map.keys(): # Information must be extracted from Configuration main_config = Configuration.get(ASD_NODE_CONFIG_MAIN_LOCATION.format(node_id)) value = main_config[api_settings_map[settings_code]] elif settings_code == 'migration_version': # Introduce version for ASD Manager migration code value = 0 else: raise NotImplementedError('No action implemented for setting {0}'.format(settings_code)) cls._logger.info('Modeling Setting with code {0} and value {1}'.format(settings_code, value)) setting = Setting() setting.code = settings_code setting.value = value setting.save() if cls._local_client.file_exists(BOOTSTRAP_FILE): cls._logger.info('Removing the bootstrap file') cls._local_client.file_delete(BOOTSTRAP_FILE) except Exception: cls._logger.exception('Error during migration of code settings. Unable to proceed') raise ############################### # Start non-crucial migration # ############################### errors = [] migration_setting = SettingList.get_setting_by_code(code='migration_version') # Add installed package_name in version files and additional string replacements in service files try: if migration_setting.value < 1: cls._logger.info('Adding additional information to service files') edition = Configuration.get_edition() if edition == PackageFactory.EDITION_ENTERPRISE: for version_file_name in cls._local_client.file_list(directory=ServiceFactory.RUN_FILE_DIR): version_file_path = '{0}/{1}'.format(ServiceFactory.RUN_FILE_DIR, version_file_name) contents = cls._local_client.file_read(filename=version_file_path) if '{0}='.format(PackageFactory.PKG_ALBA) in contents: contents = contents.replace(PackageFactory.PKG_ALBA, PackageFactory.PKG_ALBA_EE) cls._local_client.file_write(filename=version_file_path, contents=contents) node_id = SettingList.get_setting_by_code(code='node_id').value asd_services = list(ASDController.list_asd_services()) maint_services = list(MaintenanceController.get_services()) for service_name in asd_services + maint_services: config_key = ServiceFactory.SERVICE_CONFIG_KEY.format(node_id, service_name) if Configuration.exists(key=config_key): config = Configuration.get(key=config_key) if 'RUN_FILE_DIR' in config: continue config['RUN_FILE_DIR'] = ServiceFactory.RUN_FILE_DIR config['ALBA_PKG_NAME'] = PackageFactory.PKG_ALBA_EE config['ALBA_VERSION_CMD'] = PackageFactory.VERSION_CMD_ALBA Configuration.set(key=config_key, value=config) cls._service_manager.regenerate_service(name=ASDController.ASD_PREFIX if service_name in asd_services else MaintenanceController.MAINTENANCE_PREFIX, client=cls._local_client, target_name=service_name) except Exception as ex: cls._logger.exception('Failed to regenerate the ASD and Maintenance services') errors.append(ex) try: if migration_setting.value < 2: if System.get_component_identifier() not in Configuration.get(Configuration.get_registration_key(), default=[]): Configuration.register_usage(System.get_component_identifier()) except Exception as ex: cls._logger.exception('Failed to register the asd-manager') errors.append(ex) if len(errors) == 0: cls._logger.info('No errors during non-crucial migration. Saving the migration setting') # Save migration settings when no errors occurred migration_setting = SettingList.get_setting_by_code(code='migration_version') migration_setting.value = 2 migration_setting.save() cls._logger.info('Finished out of band migrations for SDM nodes')
def remove(silent=None): """ Interactive removal part for the ASD manager :param silent: If silent == '--force-yes' no question will be asked to confirm the removal :type silent: str :return: None :rtype: NoneType """ _print_and_log(message='\n' + Interactive.boxed_message(['ASD Manager removal'])) local_client = SSHClient(endpoint='127.0.0.1', username='******') if not local_client.file_exists( filename='{0}/main.db'.format(Setting.DATABASE_FOLDER)): _print_and_log(level='error', message='\n' + Interactive.boxed_message( ['The ASD Manager has already been removed'])) sys.exit(1) _print_and_log(message=' - Validating configuration management') try: Configuration.list(key='ovs') except: _print_and_log( level='exception', message='\n' + Interactive.boxed_message(['Could not connect to Arakoon'])) sys.exit(1) _print_and_log(message=' - Retrieving ASD information') all_asds = {} try: all_asds = ASDList.get_asds() except: _print_and_log(level='exception', message=' - Failed to retrieve the ASD information') interactive = silent != '--force-yes' if interactive is True: message = 'Are you sure you want to continue?' if len(all_asds) > 0: _print_and_log(message='\n\n+++ ALERT +++\n', level='warning') message = 'DATA LOSS possible if proceeding! Continue?' proceed = Interactive.ask_yesno(message=message, default_value=False) if proceed is False: _print_and_log(level='error', message='\n' + Interactive.boxed_message(['Abort removal'])) sys.exit(1) if len(all_asds) > 0: _print_and_log(message=' - Removing disks') for disk in DiskList.get_disks(): if disk.available is True: continue try: _print_and_log( message=' - Retrieving ASD information for disk {0}'. format(disk.name)) for asd in disk.asds: _print_and_log( message=' - Removing ASD {0}'.format(asd.name)) ASDController.remove_asd(asd) DiskController.clean_disk(disk) except Exception: _print_and_log(level='exception', message=' - Deleting ASDs failed') _print_and_log(message=' - Removing services') service_manager = ServiceFactory.get_manager() for service in MaintenanceController.get_services(): service_name = service _print_and_log( message=' - Removing service {0}'.format(service_name)) guid = None for alba_backend_guid in Configuration.list(key='/ovs/alba/backends'): for maintenance_service_name in Configuration.list( key='/ovs/alba/backends/{0}/maintenance/'.format( alba_backend_guid)): if maintenance_service_name == service_name: guid = alba_backend_guid break MaintenanceController.remove_maintenance_service( name=service_name, alba_backend_guid=guid) for service_name in [WATCHER_SERVICE, MANAGER_SERVICE]: if service_manager.has_service(name=service_name, client=local_client): _print_and_log( message=' - Removing service {0}'.format(service_name)) service_manager.stop_service(name=service_name, client=local_client) service_manager.remove_service(name=service_name, client=local_client) _print_and_log(message=' - Removing from configuration management') remaining_users = Configuration.uninitialize() if not remaining_users: local_client.file_delete(filenames=CACC_LOCATION) local_client.file_delete( filenames='{0}/main.db'.format(Setting.DATABASE_FOLDER)) _print_and_log( message='\n' + Interactive.boxed_message(['ASD Manager removal completed']))
def list_maintenance_services(): """ List all maintenance information """ API._logger.info('Listing maintenance services') return {'services': list(MaintenanceController.get_services())}
def get_package_information(): """ Retrieve information about the currently installed versions of the core packages Retrieve information about the versions to which each package can potentially be updated If installed version is different from candidate version --> store this information in model Additionally if installed version is identical to candidate version, check the services with a 'run' file Verify whether the running version is identical to the candidate version If different --> store this information in the model Result: Every package with updates or which requires services to be restarted is stored in the model :return: Package information :rtype: dict """ binaries = PackageManager.get_binary_versions(client=SDMUpdateController._local_client, package_names=['alba']) installed = PackageManager.get_installed_versions(client=SDMUpdateController._local_client, package_names=PackageManager.SDM_PACKAGE_NAMES) candidate = PackageManager.get_candidate_versions(client=SDMUpdateController._local_client, package_names=PackageManager.SDM_PACKAGE_NAMES) if set(installed.keys()) != set(PackageManager.SDM_PACKAGE_NAMES) or set(candidate.keys()) != set(PackageManager.SDM_PACKAGE_NAMES): raise RuntimeError('Failed to retrieve the installed and candidate versions for packages: {0}'.format(', '.join(PackageManager.SDM_PACKAGE_NAMES))) package_info = {} default_entry = {'candidate': None, 'installed': None, 'services_to_restart': []} # component: package_name: services_with_run_file for component, info in {'alba': {'alba': list(ASDController.list_asd_services()) + list(MaintenanceController.get_services()), 'openvstorage-sdm': []}}.iteritems(): component_info = {} for package, services in info.iteritems(): for service in services: version_file = '/opt/asd-manager/run/{0}.version'.format(service) if not SDMUpdateController._local_client.file_exists(version_file): SDMUpdateController._logger.warning('Failed to find a version file in /opt/asd-manager/run for service {0}'.format(service)) continue package_name = package running_versions = SDMUpdateController._local_client.file_read(version_file).strip() for version in running_versions.split(';'): if '=' in version: package_name = version.split('=')[0] running_version = version.split('=')[1] else: running_version = version if package_name not in PackageManager.SDM_PACKAGE_NAMES: raise ValueError('Unknown package dependency found in {0}'.format(version_file)) if package_name not in binaries: raise RuntimeError('Binary version for package {0} was not retrieved'.format(package_name)) if running_version != binaries[package_name]: if package_name not in component_info: component_info[package_name] = copy.deepcopy(default_entry) component_info[package_name]['installed'] = running_version component_info[package_name]['candidate'] = binaries[package_name] component_info[package_name]['services_to_restart'].append(service) if installed[package] != candidate[package] and package not in component_info: component_info[package] = copy.deepcopy(default_entry) component_info[package]['installed'] = installed[package] component_info[package]['candidate'] = candidate[package] if component_info: package_info[component] = component_info return package_info
def list_maintenance_services(): """ List all maintenance information """ API._log('Listing maintenance services') data = MaintenanceController.get_services() return {'services': list(data)}