Beispiel #1
0
class ComputePort(base.MoganObject, object_base.VersionedObjectDictCompat):
    # Version 1.0: Initial version
    VERSION = '1.0'

    dbapi = dbapi.get_instance()

    fields = {
        'id': object_fields.IntegerField(read_only=True),
        'address': object_fields.MACAddressField(nullable=False),
        'port_uuid': object_fields.UUIDField(read_only=True),
        'node_uuid': object_fields.UUIDField(read_only=True),
        'extra_specs': object_fields.FlexibleDictField(nullable=True),
    }

    @classmethod
    def list(cls, context):
        """Return a list of ComputePort objects."""
        db_compute_ports = cls.dbapi.compute_port_get_all(context)
        return cls._from_db_object_list(context, db_compute_ports)

    @classmethod
    def get(cls, context, port_uuid):
        """Find a compute port and return a ComputePort object."""
        db_compute_port = cls.dbapi.compute_port_get(context, port_uuid)
        compute_port = cls._from_db_object(context, cls(context),
                                           db_compute_port)
        return compute_port

    def create(self, context=None):
        """Create a ComputePort record in the DB."""
        values = self.obj_get_changes()
        db_compute_port = self.dbapi.compute_port_create(context, values)
        self._from_db_object(context, self, db_compute_port)

    def destroy(self, context=None):
        """Delete the ComputePort from the DB."""
        self.dbapi.compute_port_destroy(context, self.port_uuid)
        self.obj_reset_changes()

    def save(self, context=None):
        """Save updates to this ComputePort."""
        updates = self.obj_get_changes()
        self.dbapi.compute_port_update(context, self.port_uuid, updates)
        self.obj_reset_changes()

    def refresh(self, context=None):
        """Refresh the object by re-fetching from the DB."""
        current = self.__class__.get(context, self.port_uuid)
        self.obj_refresh(current)

    def update_from_driver(self, port):
        keys = ["address", "port_uuid", "node_uuid", "extra_specs"]
        for key in keys:
            if key in port:
                setattr(self, key, port[key])
Beispiel #2
0
class ServerNic(base.MoganObject, object_base.VersionedObjectDictCompat):
    # Version 1.0: Initial version
    VERSION = '1.0'

    dbapi = dbapi.get_instance()

    fields = {
        'port_id': object_fields.UUIDField(nullable=False),
        'server_uuid': object_fields.UUIDField(nullable=True),
        'mac_address': object_fields.MACAddressField(nullable=True),
        'network_id': object_fields.UUIDField(nullable=True),
        'fixed_ips':
        object_fields.ListOfDictOfNullableStringsField(nullable=True),
        'floating_ip': object_fields.StringField(nullable=True),
        'preserve_on_delete': object_fields.BooleanField(),
    }

    @staticmethod
    def _from_db_object(context, obj, db_object, server_uuid=None):
        if server_uuid:
            db_object = copy.deepcopy(db_object)
            db_object.update(server_uuid=server_uuid)
        if not isinstance(db_object, dict):
            db_object_dict = db_object.as_dict()
        else:
            db_object_dict = db_object
        obj = ServerNic(context)
        obj.update(db_object_dict)
        obj.obj_reset_changes()
        return obj

    @classmethod
    def delete_by_port_id(cls, context, port_id):
        cls.dbapi.server_nic_delete(context, port_id)

    @classmethod
    def get_by_port_id(cls, context, port_id):
        return cls.dbapi.server_nic_get(context, port_id)

    def save(self, context):
        updates = self.obj_get_changes()
        self.dbapi.server_nic_update_or_create(context, self.port_id, updates)

    def create(self, context):
        values = self.obj_to_primitive()['mogan_object.data']
        self.dbapi.server_nic_update_or_create(context, self.port_id, values)

    def delete(self, context):
        self.dbapi.server_nic_delete(context, self.port_id)
Beispiel #3
0
class ServerGroup(base.MoganObject, object_base.VersionedObjectDictCompat):
    # Version 1.0: Initial version
    VERSION = '1.0'

    dbapi = dbapi.get_instance()

    fields = {
        'id': object_fields.IntegerField(),
        'user_id': object_fields.StringField(nullable=True),
        'project_id': object_fields.StringField(nullable=True),
        'uuid': object_fields.UUIDField(),
        'name': object_fields.StringField(nullable=True),
        'policies': object_fields.ListOfStringsField(nullable=True),
        'members': object_fields.ListOfStringsField(nullable=True),
    }

    @staticmethod
    def _from_db_object(context, server_group, db_server):
        """Method to help with migration to objects.

        Converts a database entity to a formal object.
        """
        for field in server_group.fields:
            server_group[field] = db_server[field]
        server_group._context = context
        server_group.obj_reset_changes()
        return server_group

    def create(self):
        values = self.obj_get_changes()
        policies = values.pop('policies', None)
        members = values.pop('members', None)
        db_group = self.dbapi.server_group_create(self._context,
                                                  values,
                                                  policies=policies,
                                                  members=members)
        self._from_db_object(self._context, self, db_group)

    @classmethod
    def get_by_uuid(cls, context, uuid):
        db_group = cls.dbapi.server_group_get(context, uuid)
        return cls._from_db_object(context, cls(), db_group)

    def destroy(self):
        self.dbapi.server_group_delete(self._context, self.uuid)
        self.obj_reset_changes()

    @classmethod
    def add_members(cls, context, group_uuid, members):
        cls.dbapi.server_group_members_add(context, group_uuid, members)

    def save(self, context=None):
        updates = self.obj_get_changes()
        self.dbapi.server_group_update(context, self.uuid, updates)
        self.obj_reset_changes()
Beispiel #4
0
class ServerPayload(base.NotificationPayloadBase):
    SCHEMA = {
        'name': ('server', 'name'),
        'uuid': ('server', 'uuid'),
        'user_id': ('server', 'user_id'),
        'project_id': ('server', 'project_id'),
        'availability_zone': ('server', 'availability_zone'),
        'image_uuid': ('server', 'image_uuid'),
        'created_at': ('server', 'created_at'),
        'launched_at': ('server', 'launched_at'),
        'updated_at': ('server', 'updated_at'),
        'status': ('server', 'status'),
        'power_state': ('server', 'power_state'),
        'flavor_uuid': ('server', 'flavor_uuid'),
        'description': ('server', 'description')
    }
    # Version 1.0: Initial version
    VERSION = '1.0'
    fields = {
        'name': fields.StringField(nullable=False),
        'uuid': fields.UUIDField(nullable=False),
        'user_id': fields.StringField(nullable=True),
        'project_id': fields.StringField(nullable=True),
        'description': fields.StringField(nullable=True),
        'flavor_uuid': fields.UUIDField(nullable=False),
        'image_uuid': fields.UUIDField(nullable=True),
        'availability_zone': fields.StringField(nullable=True),
        'power_state': fields.StringField(nullable=True),
        'created_at': fields.DateTimeField(nullable=True),
        'launched_at': fields.DateTimeField(nullable=True),
        'updated_at': fields.DateTimeField(nullable=True),
        'status': fields.StringField(nullable=True),
        # 'network_info'
        # 'extra'
    }

    def __init__(self, server, **kwargs):
        super(ServerPayload, self).__init__(**kwargs)
        self.populate_schema(server=server)
Beispiel #5
0
class ServerFault(base.MoganObject, object_base.VersionedObjectDictCompat):
    # Version 1.0: Initial version
    VERSION = '1.0'

    dbapi = dbapi.get_instance()

    fields = {
        'id': object_fields.IntegerField(),
        'server_uuid': object_fields.UUIDField(),
        'code': object_fields.IntegerField(),
        'message': object_fields.StringField(nullable=True),
        'detail': object_fields.StringField(nullable=True),
    }

    def return_dict(self):
        return dict((k, getattr(self, k))
                    for k in ['code', 'message', 'detail']
                    if hasattr(self, k))

    @staticmethod
    def _from_db_object(context, fault, db_fault):
        for key in fault.fields:
            fault[key] = db_fault[key]
        fault._context = context
        fault.obj_reset_changes()
        return fault

    @classmethod
    def get_latest_for_server(cls, context, server_uuid):
        db_faults = cls.dbapi.server_fault_get_by_server_uuids(
            context, [server_uuid])
        if server_uuid in db_faults and db_faults[server_uuid]:
            return cls._from_db_object(context, cls(),
                                       db_faults[server_uuid][0])

    def create(self):
        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason='already created')
        values = {
            'server_uuid': self.server_uuid,
            'code': self.code,
            'message': self.message,
            'detail': self.detail,
        }
        db_fault = self.dbapi.server_fault_create(self._context, values)
        self._from_db_object(self._context, self, db_fault)
        self.obj_reset_changes()
Beispiel #6
0
class Server(base.MoganObject, object_base.VersionedObjectDictCompat):
    # Version 1.0: Initial version
    VERSION = '1.0'

    dbapi = dbapi.get_instance()

    fields = {
        'id': object_fields.IntegerField(),
        'uuid': object_fields.UUIDField(nullable=True),
        'name': object_fields.StringField(nullable=True),
        'description': object_fields.StringField(nullable=True),
        'project_id': object_fields.UUIDField(nullable=True),
        'user_id': object_fields.UUIDField(nullable=True),
        'status': object_fields.StringField(nullable=True),
        'power_state': object_fields.StringField(nullable=True),
        'flavor_uuid': object_fields.UUIDField(nullable=True),
        'availability_zone': object_fields.StringField(nullable=True),
        'image_uuid': object_fields.UUIDField(nullable=True),
        'nics': object_fields.ObjectField('ServerNics', nullable=True),
        'fault': object_fields.ObjectField('ServerFault', nullable=True),
        'node_uuid': object_fields.UUIDField(nullable=True),
        'launched_at': object_fields.DateTimeField(nullable=True),
        'metadata': object_fields.FlexibleDictField(nullable=True),
        'locked': object_fields.BooleanField(default=False),
        'locked_by': object_fields.StringField(nullable=True),
    }

    def __init__(self, context=None, **kwargs):
        server_nics = kwargs.pop('nics', None)
        if server_nics and isinstance(server_nics, list):
            nics_obj = objects.ServerNics(context)
            for nic in server_nics:
                nic_obj = objects.ServerNic(context,
                                            server_uuid=kwargs['uuid'],
                                            **nic)
                nics_obj.objects.append(nic_obj)
            kwargs['nics'] = nics_obj
        super(Server, self).__init__(context=context, **kwargs)

    @staticmethod
    def _from_db_object(server, db_server, expected_attrs=None):
        """Method to help with migration to objects.

        Converts a database entity to a formal object.

        :param server: An object of the Server class.
        :param db_server: A DB Server model of the object
        :return: The object of the class with the database entity added
        """
        for field in set(server.fields) - set(OPTIONAL_ATTRS):
            if field == 'metadata':
                server[field] = db_server['extra']
            else:
                server[field] = db_server[field]

        if expected_attrs is None:
            expected_attrs = []
        if 'nics' in expected_attrs:
            server._load_server_nics(server._context, server.uuid)
        else:
            server.nics = None
        if 'fault' in expected_attrs:
            server._load_fault(server._context, server.uuid)

        server.obj_reset_changes()
        return server

    def _load_server_nics(self, context, server_uuid):
        self.nics = objects.ServerNics.get_by_server_uuid(
            context=context, server_uuid=server_uuid)

    @staticmethod
    def _from_db_object_list(db_objects, cls, context):
        """Converts a list of database entities to a list of formal objects."""
        servers = []
        for obj in db_objects:
            expected_attrs = ['nics', 'fault']
            servers.append(
                Server._from_db_object(cls(context), obj, expected_attrs))
        return servers

    def _load_fault(self, context, server_uuid):
        self.fault = objects.ServerFault.get_latest_for_server(
            context=context, server_uuid=server_uuid)

    def _save_nics(self, context):
        for nic_obj in self.nics or []:
            nic_obj.save(context)

    def as_dict(self):
        data = dict(self.items())
        if 'nics' in data:
            data.update(nics=data['nics'].as_list_of_dict())
        return data

    @classmethod
    def list(cls, context, project_only=False, filters=None):
        """Return a list of Server objects."""
        db_servers = cls.dbapi.server_get_all(context,
                                              project_only=project_only,
                                              filters=filters)
        return Server._from_db_object_list(db_servers, cls, context)

    @classmethod
    def get(cls, context, uuid):
        """Find a server and return a Server object."""
        expected_attrs = ['nics', 'fault']
        db_server = cls.dbapi.server_get(context, uuid)
        server = Server._from_db_object(cls(context), db_server,
                                        expected_attrs)
        return server

    def create(self, context=None):
        """Create a Server record in the DB."""
        values = self.obj_get_changes()
        metadata = values.pop('metadata', None)
        if metadata is not None:
            values['extra'] = metadata
        server_nics = values.pop('nics', None)
        if server_nics:
            values['nics'] = server_nics.as_list_of_dict()
        db_server = self.dbapi.server_create(context, values)
        expected_attrs = None
        if server_nics:
            expected_attrs = ['nics']
        self._from_db_object(self, db_server, expected_attrs)

    def destroy(self, context=None):
        """Delete the Server from the DB."""
        self.dbapi.server_destroy(context, self.uuid)
        self.obj_reset_changes()

    def save(self, context=None):
        """Save updates to this Server."""
        updates = self.obj_get_changes()
        for field in list(updates):
            if (self.obj_attr_is_set(field) and isinstance(
                    self.fields[field], object_fields.ObjectField)
                    and getattr(self, field, None) is not None):
                try:
                    getattr(self, '_save_%s' % field)(context)
                except AttributeError:
                    LOG.exception('No save handler for %s', field, server=self)
                except db_exc.DBReferenceError as exp:
                    if exp.key != 'server_uuid':
                        raise
                updates.pop(field)

        metadata = updates.pop('metadata', None)
        if metadata is not None:
            updates['extra'] = metadata
        self.dbapi.server_update(context, self.uuid, updates)
        self.obj_reset_changes()

    def refresh(self, context=None):
        """Refresh the object by re-fetching from the DB."""
        current = self.__class__.get(context, self.uuid)
        self.obj_refresh(current)
        self.obj_reset_changes()
Beispiel #7
0
class Quota(base.MoganObject, object_base.VersionedObjectDictCompat):
    # Version 1.0: Initial version
    VERSION = '1.0'

    dbapi = dbapi.get_instance()

    fields = {
        'id': object_fields.IntegerField(),
        'project_id': object_fields.UUIDField(nullable=True),
        'resource_name': object_fields.StringField(nullable=True),
        'hard_limit': object_fields.IntegerField(nullable=True),
        'allocated': object_fields.IntegerField(default=0),
    }

    def __init__(self, *args, **kwargs):
        super(Quota, self).__init__(*args, **kwargs)
        self.quota_driver = driver.DriverManager('mogan.quota.backend_driver',
                                                 CONF.quota.quota_driver,
                                                 invoke_on_load=True).driver
        self._resources = {}

    @property
    def resources(self):
        return self._resources

    @staticmethod
    def _from_db_object_list(db_objects, cls, context):
        """Converts a list of database entities to a list of formal objects."""
        return [Quota._from_db_object(cls(context), obj) for obj in db_objects]

    @classmethod
    def list(cls, context, project_only=False):
        """Return a list of Quota objects."""
        db_quotas = cls.dbapi.quota_get_all(context, project_only=project_only)
        return Quota._from_db_object_list(db_quotas, cls, context)

    @classmethod
    def get(cls, context, project_id, resource_name):
        """Find a quota of resource and return a Quota object."""
        db_quota = cls.dbapi.quota_get(context, project_id, resource_name)
        quota = Quota._from_db_object(cls(context), db_quota)
        return quota

    def create(self, context):
        """Create a Quota record in the DB."""
        values = self.obj_get_changes()
        # Since we need to avoid passing False down to the DB layer
        # (which uses an integer), we can always default it to zero here.
        values['deleted'] = 0

        db_quota = self.dbapi.quota_create(context, values)
        self._from_db_object(self, db_quota)

    def destroy(self, context, project_id, resource_name):
        """Delete the Quota from the DB."""
        self.dbapi.quota_destroy(context, project_id, resource_name)
        self.obj_reset_changes()

    def save(self, context, project_id, resource_name):
        """Save updates to this Quota."""
        updates = self.obj_get_changes()
        self.dbapi.quota_update(context, project_id, resource_name, updates)
        self.obj_reset_changes()

    def refresh(self, context, project_id, resource_name):
        """Refresh the object by re-fetching from the DB."""
        current = self.__class__.get(context, project_id, resource_name)
        self.obj_refresh(current)
        self.obj_reset_changes()

    def reserve(self, context, expire=None, project_id=None, **deltas):
        """reserve the Quota."""
        return self.quota_driver.reserve(context,
                                         self.resources,
                                         deltas,
                                         expire=expire,
                                         project_id=project_id)

    def commit(self, context, reservations, project_id=None):
        self.quota_driver.commit(context, reservations, project_id=project_id)

    def rollback(self, context, reservations, project_id=None):
        self.quota_driver.rollback(context,
                                   reservations,
                                   project_id=project_id)

    def expire(self, context):
        return self.quota_driver.expire(context)

    def count(self, context, resource, *args, **kwargs):
        """Count a resource.

        For countable resources, invokes the count() function and
        returns its result.  Arguments following the context and
        resource are passed directly to the count function declared by
        the resource.

        :param context: The request context, for access checks.
        :param resource: The name of the resource, as a string.
        """

        # Get the resource
        res = self.resources.get(resource)
        if not res or not hasattr(res, 'count'):
            raise exception.QuotaResourceUnknown(unknown=[resource])

        return res.count(context, *args, **kwargs)

    def register_resource(self, resource):
        """Register a resource."""

        self._resources[resource.name] = resource

    def register_resources(self, resources):
        """Register a list of resources."""

        for resource in resources:
            self.register_resource(resource)

    def get_quota_limit_and_usage(self, context, resources, project_id):
        return self.quota_driver.get_project_quotas(context,
                                                    resources,
                                                    project_id,
                                                    usages=True)
Beispiel #8
0
class Flavor(base.MoganObject, object_base.VersionedObjectDictCompat):
    # Version 1.0: Initial version
    VERSION = '1.0'

    dbapi = dbapi.get_instance()

    fields = {
        'uuid': object_fields.UUIDField(nullable=True),
        'name': object_fields.StringField(nullable=True),
        'description': object_fields.StringField(nullable=True),
        'is_public': object_fields.BooleanField(),
        'disabled': object_fields.BooleanField(),
        'resources': object_fields.FlexibleDictField(nullable=True),
        'resource_traits': object_fields.FlexibleDictField(nullable=True),
        'projects': object_fields.ListOfStringsField(),
    }

    def __init__(self, *args, **kwargs):
        super(Flavor, self).__init__(*args, **kwargs)
        self._orig_projects = {}

    @staticmethod
    def _from_db_object(context, flavor, db_flavor, expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = []

        for name, field in flavor.fields.items():
            if name in OPTIONAL_FIELDS:
                continue
            value = db_flavor[name]
            if isinstance(field, object_fields.IntegerField):
                value = value if value is not None else 0
            flavor[name] = value

        if 'projects' in expected_attrs:
            flavor._load_projects(context)

        flavor.obj_reset_changes()
        return flavor

    def _load_projects(self, context):
        self.projects = [x['project_id'] for x in
                         self.dbapi.flavor_access_get(context, self.uuid)]
        self.obj_reset_changes(['projects'])

    def obj_reset_changes(self, fields=None, recursive=False):
        super(Flavor, self).obj_reset_changes(fields=fields,
                                              recursive=recursive)
        if fields is None or 'projects' in fields:
            self._orig_projects = (list(self.projects)
                                   if self.obj_attr_is_set('projects')
                                   else [])

    def obj_what_changed(self):
        changes = super(Flavor, self).obj_what_changed()
        if 'projects' in self and self.projects != self._orig_projects:
            changes.add('projects')
        return changes

    @staticmethod
    def _from_db_object_list(db_objects, cls, context):
        """Converts a list of database entities to a list of formal objects."""
        return [Flavor._from_db_object(context, cls(context), obj)
                for obj in db_objects]

    @classmethod
    def list(cls, context):
        """Return a list of Flavor objects."""
        db_flavors = cls.dbapi.flavor_get_all(context)
        return Flavor._from_db_object_list(db_flavors, cls, context)

    @classmethod
    def get(cls, context, flavor_uuid):
        """Find a Flavor and return a Flavor object."""
        db_flavor = cls.dbapi.flavor_get(context, flavor_uuid)
        flavor = Flavor._from_db_object(
            context, cls(context), db_flavor,
            expected_attrs=['projects'])
        return flavor

    def create(self, context=None):
        """Create a Flavor record in the DB."""
        values = self.obj_get_changes()
        db_flavor = self.dbapi.flavor_create(context, values)
        self._from_db_object(context, self, db_flavor)

    def destroy(self, context=None):
        """Delete the Flavor from the DB."""
        self.dbapi.flavor_destroy(context, self.uuid)
        self.obj_reset_changes()

    def save(self, context=None):
        updates = self.obj_get_changes()
        projects = updates.pop('projects', None)

        # access projects
        if projects is not None:
            deleted_projects = set(self._orig_projects) - set(projects)
            added_projects = set(projects) - set(self._orig_projects)
        else:
            added_projects = deleted_projects = None

        if added_projects or deleted_projects:
            self.save_projects(context, added_projects, deleted_projects)

        self.dbapi.flavor_update(context, self.uuid, updates)

    def save_projects(self, context, to_add=None, to_delete=None):
        """Add or delete projects.

        :param:to_add: A list of projects to add
        :param:to_delete: A list of projects to remove
        """
        ident = self.uuid

        to_add = to_add if to_add is not None else []
        to_delete = to_delete if to_delete is not None else []

        for project_id in to_add:
            self.dbapi.flavor_access_add(context, ident, project_id)

        for project_id in to_delete:
            self.dbapi.flavor_access_remove(context, ident, project_id)
        self.obj_reset_changes(['projects'])
Beispiel #9
0
class Aggregate(base.MoganObject, object_base.VersionedObjectDictCompat):
    # Version 1.0: Initial version
    VERSION = '1.0'

    dbapi = dbapi.get_instance()

    fields = {
        'id': object_fields.IntegerField(read_only=True),
        'uuid': object_fields.UUIDField(read_only=True),
        'name': object_fields.StringField(),
        'metadata': object_fields.FlexibleDictField(nullable=True),
    }

    def __init__(self, *args, **kwargs):
        super(Aggregate, self).__init__(*args, **kwargs)
        self._orig_metadata = {}

    @staticmethod
    def _from_db_object(context, aggregate, db_aggregate):
        """Converts a database entity to a formal object."""
        for field in aggregate.fields:
            if field == 'metadata':
                aggregate[field] = db_aggregate['metadetails']
            else:
                aggregate[field] = db_aggregate[field]
        aggregate.obj_reset_changes()
        return aggregate

    def obj_reset_changes(self, fields=None, recursive=False):
        super(Aggregate, self).obj_reset_changes(fields=fields,
                                                 recursive=recursive)
        if fields is None or 'metadata' in fields:
            self._orig_metadata = (dict(self.metadata)
                                   if self.obj_attr_is_set('metadata') else {})

    def obj_what_changed(self):
        changes = super(Aggregate, self).obj_what_changed()
        if ('metadata' in self and self.metadata != self._orig_metadata):
            changes.add('metadata')
        return changes

    @classmethod
    def get(cls, context, aggregate_id):
        """Find an aggregate and return an Aggregate object."""
        db_aggregate = cls.dbapi.aggregate_get(context, aggregate_id)
        aggregate = cls._from_db_object(context, cls(context), db_aggregate)
        return aggregate

    def create(self, context=None):
        """Create an Aggregate record in the DB."""
        values = self.obj_get_changes()
        db_aggregate = self.dbapi.aggregate_create(context, values)
        self._from_db_object(context, self, db_aggregate)

    def destroy(self, context=None):
        """Delete the Aggregate from the DB."""
        self.dbapi.aggregate_destroy(context, self.id)
        self.obj_reset_changes()

    def save(self, context=None):
        """Save updates to this Aggregate."""
        updates = self.obj_get_changes()
        metadata = updates.pop('metadata', None)

        # metadata
        if metadata is not None:
            deleted_keys = (set(self._orig_metadata.keys()) -
                            set(metadata.keys()))
            added_keys = self.metadata
        else:
            added_keys = deleted_keys = None
        if added_keys or deleted_keys:
            self.save_metadata(context, self.metadata, deleted_keys)

        self.dbapi.aggregate_update(context, self.id, updates)
        self.obj_reset_changes()

    def save_metadata(self, context, to_add=None, to_delete=None):
        """Add or delete metadata.

        :param:to_add: A dict of new keys to add/update
        :param:to_delete: A list of keys to remove
        """
        ident = self.id

        to_add = to_add if to_add is not None else {}
        to_delete = to_delete if to_delete is not None else []

        if to_add:
            self.dbapi.aggregate_metadata_update_or_create(
                context, ident, to_add)

        for key in to_delete:
            self.dbapi.aggregate_metadata_delete(context, ident, key)
        self.obj_reset_changes(['metadata'])