def rolling_configuration_remove(self, apply_on_all=True): cluster_notification = self.context.notification request_info = cluster_notification.serialize(self.context) self.validate_cluster_available() self.db_info.update(task_status=ClusterTasks.UPDATING_CLUSTER) try: instances = [ inst_models.Instance.load(self.context, instance.id) for instance in self.instances ] LOG.debug("Removing changes from cluster nodes.") for instance in instances: if instance.configuration: self.context.notification = ( DBaaSInstanceDetachConfiguration( self.context, **request_info)) with StartNotification(self.context, instance_id=instance.id): with EndNotification(self.context): instance.delete_configuration() else: LOG.debug("Node '%s' has no configuration attached.", instance.id) # The cluster is in a consistent state with all nodes # requiring restart. # New configuration can be safely attached at this point. configuration_id = self.configuration_id self.update_db(configuration_id=None) LOG.debug("Applying runtime configuration changes.") if instances[0].reset_configuration(configuration_id): LOG.debug( "Runtime changes have been applied successfully to the " "first node.") remaining_nodes = instances[1:] if apply_on_all: LOG.debug("Applying the changes to the remaining nodes.") for instance in remaining_nodes: instance.reset_configuration(configuration_id) else: LOG.debug( "Releasing restart-required task on the remaining " "nodes.") for instance in remaining_nodes: instance.update_db(task_status=InstanceTasks.NONE) finally: self.update_db(task_status=ClusterTasks.NONE) return self.__class__(self.context, self.db_info, self.ds, self.ds_version)
def create_backup(self, context, backup_info): """ Entry point for initiating a backup for this guest agents db instance. The call currently blocks until the backup is complete or errors. If device_path is specified, it will be mounted based to a point specified in configuration. :param context: User context object. :param backup_info: a dictionary containing the db instance id of the backup task, location, type, and other data. """ with EndNotification(context): self.app.create_backup(context, backup_info)
def create_instance(self, context, instance_id, name, flavor, image_id, databases, users, datastore_manager, packages, volume_size, backup_id, availability_zone, root_password, nics, overrides, slave_of_id, cluster_config, volume_type, modules, locality): with EndNotification(context, instance_id=(instance_id[0] if isinstance( instance_id, list) else instance_id)): self._create_instance(context, instance_id, name, flavor, image_id, databases, users, datastore_manager, packages, volume_size, backup_id, availability_zone, root_password, nics, overrides, slave_of_id, cluster_config, volume_type, modules, locality)
def update_attributes(self, context, username, hostname, user_attrs): """Change the attributes of one existing user. The username and hostname parameters are strings. The user_attrs parameter is a dictionary in the following form: {"password": "", "name": ""} Each key/value pair in user_attrs is optional. """ with EndNotification(context): if user_attrs.get('password') is not None: self.change_passwords( context, ({ "name": username, "password": user_attrs['password'], }, ), ) if user_attrs.get('name') is not None: access = self.list_access(context, username, None) LOG.info( _("{guest_id}: Changing username for {old} to {new}."). format( guest_id=CONF.guest_id, old=username, new=user_attrs['name'], )) pgutil.psql( pgutil.psql.UserQuery.update_name( old=username, new=user_attrs['name'], ), timeout=30, ) # Regrant all previous access after the name change. LOG.info( _("{guest_id}: Regranting permissions from {old} " "to {new}.").format( guest_id=CONF.guest_id, old=username, new=user_attrs['name'], )) self.grant_access(context, username=user_attrs['name'], hostname=None, databases=(db['_name'] for db in access))
def detach_replica(self, context, instance_id): with EndNotification(context): slave = models.BuiltInstanceTasks.load(context, instance_id) master_id = slave.slave_of_id master = models.BuiltInstanceTasks.load(context, master_id) slave.detach_replica(master) if master.post_processing_required_for_replication(): slave_instances = [ BuiltInstanceTasks.load(context, slave_model.id) for slave_model in master.slaves ] slave_detail = [ slave_instance.get_replication_detail() for slave_instance in slave_instances ] master.complete_master_setup(slave_detail)
def create(self, req, body, tenant_id): LOG.debug("req : '%s'\n\n" % req) LOG.debug("body : '%s'\n\n" % req) context = req.environ[wsgi.CONTEXT_KEY] policy.authorize_on_tenant(context, 'configuration:create') context.notification = notification.DBaaSConfigurationCreate( context, request=req) name = body['configuration']['name'] description = body['configuration'].get('description') values = body['configuration']['values'] msg = _("Creating configuration group on tenant " "%(tenant_id)s with name: %(cfg_name)s") LOG.info(msg % {"tenant_id": tenant_id, "cfg_name": name}) datastore_args = body['configuration'].get('datastore', {}) datastore, datastore_version = ( ds_models.get_datastore_version(**datastore_args)) with StartNotification(context, name=name, datastore=datastore.name, datastore_version=datastore_version.name): configItems = [] if values: # validate that the values passed in are permitted by the # operator. ConfigurationsController._validate_configuration( body['configuration']['values'], datastore_version, models.DatastoreConfigurationParameters.load_parameters( datastore_version.id)) for k, v in values.items(): configItems.append(DBConfigurationParameter( configuration_key=k, configuration_value=v)) cfg_group = models.Configuration.create(name, description, tenant_id, datastore.id, datastore_version.id) with EndNotification(context, configuration_id=cfg_group.id): cfg_group_items = models.Configuration.create_items( cfg_group.id, values) view_data = views.DetailedConfigurationView(cfg_group, cfg_group_items) return wsgi.Result(view_data.data(), 200)
def delete_database(self, context, database): """Delete the specified database. The database parameter is a dictionary in the following form: {"_name": ""} """ with EndNotification(context): LOG.info( _("{guest_id}: Dropping database {name}.").format( guest_id=CONF.guest_id, name=database['_name'], )) pgutil.psql( pgutil.DatabaseQuery.drop(name=database['_name']), timeout=30, )
def delete_user(self, context, user): """Delete the specified user. The user parameter is a dictionary in the following form: {"_name": ""} """ with EndNotification(context): LOG.info( _("{guest_id}: Dropping user {name}.").format( guest_id=CONF.guest_id, name=user['_name'], )) pgutil.psql( pgutil.UserQuery.drop(name=user['_name']), timeout=30, )
def prepare(self, context, packages, databases, memory_mb, users, device_path=None, mount_point=None, backup_info=None, config_contents=None, root_password=None, overrides=None, cluster_config=None, snapshot=None): """Set up datastore on a Guest Instance.""" LOG.info(_("Starting datastore prepare.")) with EndNotification(context): self.status.begin_install() if (cluster_config or self._require_post_processing(snapshot)): post_processing = True else: post_processing = False try: self.do_prepare(context, packages, databases, memory_mb, users, device_path=device_path, mount_point=mount_point, backup_info=backup_info, config_contents=config_contents, root_password=root_password, overrides=overrides, cluster_config=cluster_config, snapshot=snapshot) except Exception: self.prepare_error = True LOG.exception("An error occurred preparing datastore") raise finally: LOG.info(_("Ending datastore prepare.")) self.status.end_install(error_occurred=self.prepare_error, post_processing=post_processing) LOG.info(_('Completed setup of datastore successfully.'))
def prepare(self, context, packages, databases, memory_mb, users, device_path=None, mount_point=None, backup_info=None, config_contents=None, root_password=None, overrides=None, cluster_config=None, snapshot=None, modules=None): """Set up datastore on a Guest Instance.""" with EndNotification(context, instance_id=CONF.guest_id): self._prepare(context, packages, databases, memory_mb, users, device_path, mount_point, backup_info, config_contents, root_password, overrides, cluster_config, snapshot, modules)
def create_backup(self, context, backup_info): """Create backup for the database. :param context: User context object. :param backup_info: a dictionary containing the db instance id of the backup task, location, type, and other data. """ LOG.info(f"Creating backup {backup_info['id']}") with EndNotification(context): volumes_mapping = { '/var/lib/postgresql/data': { 'bind': '/var/lib/postgresql/data', 'mode': 'rw' }, "/var/run/postgresql": {"bind": "/var/run/postgresql", "mode": "ro"}, } extra_params = f"--pg-wal-archive-dir {service.WAL_ARCHIVE_DIR}" self.app.create_backup(context, backup_info, volumes_mapping=volumes_mapping, need_dbuser=False, extra_params=extra_params)
def create_backup(self, context, backup_info): """Create backup for the database. :param context: User context object. :param backup_info: a dictionary containing the db instance id of the backup task, location, type, and other data. """ LOG.info(f"Creating backup {backup_info['id']}") with EndNotification(context): volumes_mapping = { '/var/lib/mysql': { 'bind': '/var/lib/mysql', 'mode': 'rw' }, '/tmp': { 'bind': '/tmp', 'mode': 'rw' } } self.app.create_backup(context, backup_info, volumes_mapping=volumes_mapping, need_dbuser=True)
def update_attributes(self, context, username, hostname, user_attrs): """Change the attributes of one existing user. The username and hostname parameters are strings. The user_attrs parameter is a dictionary in the following form: {"password": "", "name": ""} Each key/value pair in user_attrs is optional. """ with EndNotification(context): user = self._build_user(context, username) new_username = user_attrs.get('name') new_password = user_attrs.get('password') if new_username is not None: self._rename_user(context, user, new_username) # Make sure we can retrieve the renamed user. user = self._find_user(context, new_username) if new_password is not None: user.password = new_password self.alter_user(context, user)
def delete_user(self, context, user): LOG.debug("Deleting user.") with EndNotification(context): raise exception.DatastoreOperationNotSupported( operation='delete_user', datastore=MANAGER)
def delete_database(self, context, database): LOG.debug("Deleting database.") with EndNotification(context): raise exception.DatastoreOperationNotSupported( operation='delete_database', datastore=MANAGER)
def update_attributes(self, context, username, hostname, user_attrs): LOG.debug("Updating attributes.") with EndNotification(context): raise exception.DatastoreOperationNotSupported( operation='update_attributes', datastore=MANAGER)
def create_backup(self, context, backup_info): """Create a backup of the database.""" LOG.debug("Creating backup.") with EndNotification(context): backup.backup(context, backup_info)
def promote_to_replica_source(self, context, instance_id): # TODO(atomic77) Promote and eject need to be able to handle the case # where a datastore like Postgresql needs to treat the slave to be # promoted differently from the old master and the slaves which will # be simply reassigned to a new master. See: # https://bugs.launchpad.net/trove/+bug/1553339 def _promote_to_replica_source(old_master, master_candidate, replica_models): # First, we transition from the old master to new as quickly as # possible to minimize the scope of unrecoverable error old_master.make_read_only(True) master_ips = old_master.detach_public_ips() slave_ips = master_candidate.detach_public_ips() latest_txn_id = old_master.get_latest_txn_id() master_candidate.wait_for_txn(latest_txn_id) master_candidate.detach_replica(old_master, for_failover=True) master_candidate.enable_as_master() old_master.attach_replica(master_candidate) master_candidate.attach_public_ips(master_ips) master_candidate.make_read_only(False) old_master.attach_public_ips(slave_ips) # At this point, should something go wrong, there # should be a working master with some number of working slaves, # and possibly some number of "orphaned" slaves exception_replicas = [] error_messages = "" for replica in replica_models: try: if replica.id != master_candidate.id: replica.detach_replica(old_master, for_failover=True) replica.attach_replica(master_candidate) except exception.TroveError as ex: msg = (_("Unable to migrate replica %(slave)s from " "old replica source %(old_master)s to " "new source %(new_master)s on promote.") % { "slave": replica.id, "old_master": old_master.id, "new_master": master_candidate.id }) LOG.exception(msg) exception_replicas.append(replica) error_messages += "%s (%s)\n" % (msg, ex) try: old_master.demote_replication_master() except Exception as ex: msg = (_("Exception demoting old replica source %s.") % old_master.id) LOG.exception(msg) exception_replicas.append(old_master) error_messages += "%s (%s)\n" % (msg, ex) self._set_task_status([old_master] + replica_models, InstanceTasks.NONE) if exception_replicas: self._set_task_status(exception_replicas, InstanceTasks.PROMOTION_ERROR) msg = ( _("promote-to-replica-source %(id)s: The following " "replicas may not have been switched: %(replicas)s:" "\n%(err)s") % { "id": master_candidate.id, "replicas": [repl.id for repl in exception_replicas], "err": error_messages }) raise ReplicationSlaveAttachError(msg) with EndNotification(context): master_candidate = BuiltInstanceTasks.load(context, instance_id) old_master = BuiltInstanceTasks.load(context, master_candidate.slave_of_id) replicas = [] for replica_dbinfo in old_master.slaves: if replica_dbinfo.id == instance_id: replica = master_candidate else: replica = BuiltInstanceTasks.load(context, replica_dbinfo.id) replicas.append(replica) try: _promote_to_replica_source(old_master, master_candidate, replicas) except ReplicationSlaveAttachError: raise except Exception: self._set_task_status([old_master] + replicas, InstanceTasks.PROMOTION_ERROR) raise
def detach_replica(self, context, instance_id): with EndNotification(context): slave = models.BuiltInstanceTasks.load(context, instance_id) master_id = slave.slave_of_id master = models.BuiltInstanceTasks.load(context, master_id) slave.detach_replica(master)
def restart(self, context, instance_id): with EndNotification(context): instance_tasks = models.BuiltInstanceTasks.load( context, instance_id) instance_tasks.restart()
def resize_flavor(self, context, instance_id, old_flavor, new_flavor): with EndNotification(context): instance_tasks = models.BuiltInstanceTasks.load( context, instance_id) instance_tasks.resize_flavor(old_flavor, new_flavor)
def delete_user(self, context, user): with EndNotification(context): self.adm.delete_user(user)
def change_passwords(self, context, users): with EndNotification(context): self.adm.change_passwords(users)
def create_database(self, context, databases): with EndNotification(context): return self.adm.create_databases(databases)
def delete_database(self, context, database): with EndNotification(context): return self.adm.delete_database(database)
def create_cluster(self, context, cluster_id): with EndNotification(context, cluster_id=cluster_id): cluster_tasks = models.load_cluster_tasks(context, cluster_id) cluster_tasks.create_cluster(context, cluster_id)
def create_user(self, context, users): with EndNotification(context): self.adm.create_users(users)
def delete_cluster(self, context, cluster_id): with EndNotification(context): cluster_tasks = models.load_cluster_tasks(context, cluster_id) cluster_tasks.delete_cluster(context, cluster_id)
def update_attributes(self, context, username, hostname, user_attrs): with EndNotification(context): self.adm.update_attributes(username, hostname, user_attrs)
def resize_volume(self, context, instance_id, new_size): with EndNotification(context): instance_tasks = models.BuiltInstanceTasks.load( context, instance_id) instance_tasks.resize_volume(new_size)