def update_asd(asd, update_data): """ Updates an ASD with the 'update_data' provided :param asd: ASD to update :type asd: source.dal.objects.asd.ASD :param update_data: Data to update :type update_data: dict :raises ValueError: - When ASD configuration key is not present - When an unsupported key is passed in via 'update_data' :return: None :rtype: NoneType """ key_map = {'ips': 'hosts'} if not Configuration.exists(asd.config_key): raise ValueError( 'Failed to the configuration at location {0}'.format( asd.config_key)) config = Configuration.get(asd.config_key) for key, value in update_data.iteritems(): if key not in key_map: # Only updating IPs is supported for now raise ValueError( 'Unsupported property provided: {0}. Only IPs can be updated for now' .format(key)) setattr(asd, key_map[key], value) config[key] = value asd.save() Configuration.set(key=asd.config_key, value=config)
def get_logging_info(cls): """ Retrieve logging information from the Configuration management :return: Dictionary containing logging information :rtype: dict """ try: return Configuration.get('/ovs/alba/logging') except (IOError, NotFoundException): return {}
def export(self): """ Exports the ASD information to a dict structure :return: Representation of the ASD as dict :rtype: dict """ if not self.has_config: raise RuntimeError('No configuration found for ASD {0}'.format( self.asd_id)) data = Configuration.get(self.config_key) for prop in self._properties: if prop.name == 'hosts': data['ips'] = getattr(self, prop.name) else: data[prop.name] = getattr(self, prop.name) if self.disk.state == 'MISSING': data.update({'state': 'error', 'state_detail': 'missing'}) else: output, error = ASD._local_client.run( ['ls', '{0}/{1}/'.format(self.disk.mountpoint, self.folder)], allow_nonzero=True, return_stderr=True) output += error if 'Input/output error' in output: data.update({'state': 'error', 'state_detail': 'io_error'}) elif ASD._service_manager.has_service(self.service_name, ASD._local_client): service_state = ASD._service_manager.get_service_status( self.service_name, ASD._local_client) if service_state == 'activating': data.update({ 'state': 'warning', 'state_detail': 'service_activating' }) elif service_state == 'active': data.update({'state': 'ok', 'state_detail': None}) else: data.update({ 'state': 'error', 'state_detail': 'service_failure' }) else: data.update({ 'state': 'error', 'state_detail': 'service_failure' }) return data
def authorized(cls): """ Indicates whether a call is authenticated """ # For backwards compatibility we first try to retrieve the node ID by using the bootstrap file try: with open(BOOTSTRAP_FILE) as bstr_file: node_id = json.load(bstr_file)['node_id'] except: node_id = SettingList.get_setting_by_code(code='node_id').value node_config = Configuration.get( ASD_NODE_CONFIG_MAIN_LOCATION.format(node_id)) username = node_config['username'] password = node_config['password'] auth = request.authorization return auth and auth.username == username and auth.password == password
def sync_disks(): # type: () -> None """ Syncs the disks Changes made to this code should be reflected in the framework DiskController.sync_with_reality call. :return: None :rtype: NoneType """ node_id = SettingList.get_setting_by_code(code='node_id').value s3 = Configuration.get( ASD_NODE_CONFIG_MAIN_LOCATION_S3.format(node_id), default=False) disks, name_alias_mapping = DiskTools.model_devices(s3=s3) disks_by_name = dict((disk.name, disk) for disk in disks) alias_name_mapping = name_alias_mapping.reverse_mapping() # Specific for the asd-manager: handle unique constraint exception DiskController._prepare_for_name_switch(disks) # Sync the model for disk in DiskList.get_disks(): generic_disk_model = None # type: GenericDisk for alias in disk.aliases: # IBS wont have alias if alias in alias_name_mapping: name = alias_name_mapping[alias].replace('/dev/', '') if name in disks_by_name: generic_disk_model = disks_by_name.pop(name) break # Partitioned loop, nvme devices no longer show up in alias_name_mapping if generic_disk_model is None and disk.name in disks_by_name and ( disk.name.startswith(tuple(['fio', 'loop', 'nvme']))): generic_disk_model = disks_by_name.pop(disk.name) if not generic_disk_model: # Remove disk / partitions if not reported by 'lsblk' DiskController._remove_disk_model(disk) else: # Update existing disks and their partitions DiskController._sync_disk_with_model(disk, generic_disk_model) # Create all disks and their partitions not yet modeled for disk_name, generic_disk_model in disks_by_name.iteritems(): DiskController._model_disk(generic_disk_model)
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 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 create_asd(disk): """ Creates and starts an ASD on a given disk :param disk: Disk on which to create an ASD :type disk: source.dal.objects.disk.Disk :return: None :rtype: NoneType """ # Validations if disk.state == 'MISSING': raise RuntimeError( 'Cannot create an ASD on missing disk {0}'.format(disk.name)) _node_id = SettingList.get_setting_by_code(code='node_id').value ipaddresses = Configuration.get('{0}|ips'.format( ASD_NODE_CONFIG_NETWORK_LOCATION.format(_node_id))) if len(ipaddresses) == 0: ipaddresses = OSFactory.get_manager().get_ip_addresses( client=ASDController._local_client) if len(ipaddresses) == 0: raise RuntimeError('Could not find any IP on the local node') alba_pkg_name, alba_version_cmd = PackageFactory.get_package_and_version_cmd_for( component='alba' ) # Call here, because this potentially raises error, which should happen before actually making changes # Fetch disk information disk_size = int( ASDController._local_client.run( ['df', '-B', '1', '--output=size', disk.mountpoint], timeout=5).splitlines()[1]) # Find out appropriate disk size asd_size = int(math.floor(disk_size / (len(disk.asds) + 1))) for asd in disk.asds: if asd.has_config: config = Configuration.get(asd.config_key) config['capacity'] = asd_size cache_size = ASDController.calculate_rocksdb_cache_size( is_ssd=disk.is_ssd) if cache_size: config.update({'rocksdb_block_cache_size': cache_size}) Configuration.set(asd.config_key, config) try: ASDController._service_manager.send_signal( asd.service_name, signal.SIGUSR1, ASDController._local_client) except Exception as ex: ASDController._logger.info( 'Could not send signal to ASD for reloading the quota: {0}' .format(ex)) used_ports = [] for asd in ASDList.get_asds(): if asd.has_config: config = Configuration.get(asd.config_key) used_ports.append(config['port']) if 'rora_port' in config: used_ports.append(config['rora_port']) # Prepare & start service ASDController._logger.info('Setting up service for disk {0}'.format( disk.name)) asd_id = ''.join( random.choice(string.ascii_letters + string.digits) for _ in range(32)) homedir = '{0}/{1}'.format(disk.mountpoint, asd_id) base_port = Configuration.get('{0}|port'.format( ASD_NODE_CONFIG_NETWORK_LOCATION.format(_node_id))) asd_port = base_port rora_port = base_port + 1 while asd_port in used_ports: asd_port += 1 used_ports.append(asd_port) while rora_port in used_ports: rora_port += 1 asd_config = { 'ips': ipaddresses, 'home': homedir, 'port': asd_port, 'asd_id': asd_id, 'node_id': _node_id, 'capacity': asd_size, 'multicast': None, 'transport': 'tcp', 'log_level': 'info' } cache_size = ASDController.calculate_rocksdb_cache_size( is_ssd=disk.is_ssd) if cache_size: asd_config.update({'rocksdb_block_cache_size': cache_size}) if Configuration.get('/ovs/framework/rdma'): asd_config['rora_port'] = rora_port asd_config['rora_transport'] = 'rdma' if Configuration.exists('{0}/extra'.format( ASD_NODE_CONFIG_LOCATION.format(_node_id))): data = Configuration.get('{0}/extra'.format( ASD_NODE_CONFIG_LOCATION.format(_node_id))) asd_config.update(data) asd = ASD() asd.disk = disk asd.port = asd_port asd.hosts = ipaddresses asd.asd_id = asd_id asd.folder = asd_id asd.save() Configuration.set(asd.config_key, asd_config) params = { 'LOG_SINK': Logger.get_sink_path('alba-asd_{0}'.format(asd_id)), 'CONFIG_PATH': Configuration.get_configuration_path(asd.config_key), 'SERVICE_NAME': asd.service_name, 'ALBA_PKG_NAME': alba_pkg_name, 'ALBA_VERSION_CMD': alba_version_cmd } os.mkdir(homedir) ASDController._local_client.run(['chown', '-R', 'alba:alba', homedir]) ASDController._service_manager.add_service( name=ASDController.ASD_PREFIX, client=ASDController._local_client, params=params, target_name=asd.service_name) ASDController.start_asd(asd)
def _sync_disks(): from source.controllers.disk import DiskController while True: DiskController.sync_disks() time.sleep(60) try: node_id = SettingList.get_setting_by_code(code='node_id').value except: # For backwards compatibility # After update SettingList has not been populated yet and post-update script of package will restart asd-manager with open(BOOTSTRAP_FILE) as bstr_file: node_id = json.load(bstr_file)['node_id'] try: asd_manager_config = Configuration.get( ASD_NODE_CONFIG_MAIN_LOCATION.format(node_id)) except: raise RuntimeError('Configuration management unavailable') if 'ip' not in asd_manager_config or 'port' not in asd_manager_config: raise RuntimeError( 'IP and/or port not available in configuration for ALBA node {0}'. format(node_id)) from source.app import app @app.before_first_request def setup_logging(): """ Configure logging :return: None