@base.remotable
    def save(self, expected_vm_state=None,
             expected_task_state=None, admin_state_reset=False):
        # Store this on the class because _cell_name_blocks_sync is useless
        # after the db update call below.
        self._sync_cells = not self._cell_name_blocks_sync()

        context = self._context
        cell_type = cells_opts.get_cell_type()

        if cell_type is not None:
            # NOTE(comstud): We need to stash a copy of ourselves
            # before any updates are applied.  When we call the save
            # methods on nested objects, we will lose any changes to
            # them.  But we need to make sure child cells can tell
            # what is changed.
            #
            # We also need to nuke any updates to vm_state and task_state
            # unless admin_state_reset is True.  compute cells are
            # authoritative for their view of vm_state and task_state.
            stale_instance = self.obj_clone()

        cells_update_from_api = (cell_type == 'api' and self.cell_name and
                                 self._sync_cells)

        if cells_update_from_api:
            def _handle_cell_update_from_api():
                cells_api = cells_rpcapi.CellsAPI()
                cells_api.instance_update_from_api(context, stale_instance,
                            expected_vm_state,
                            expected_task_state,
                            admin_state_reset)

        updates = {}
        changes = self.obj_what_changed()

        for field in self.fields:
            # NOTE(danms): For object fields, we construct and call a
            # helper method like self._save_$attrname()
            if (self.obj_attr_is_set(field) and
                    isinstance(self.fields[field], fields.ObjectField)):
                try:
                    getattr(self, '_save_%s' % field)(context)
                except AttributeError:
                    LOG.exception(_LE('No save handler for %s'), field,
                                  instance=self)
                except db_exc.DBReferenceError as exp:
                    if exp.key != 'instance_uuid':
                        raise
                    # NOTE(melwitt): This will happen if we instance.save()
                    # before an instance.create() and FK constraint fails.
                    # In practice, this occurs in cells during a delete of
                    # an unscheduled instance. Otherwise, it could happen
                    # as a result of bug.
                    raise exception.InstanceNotFound(instance_id=self.uuid)
            elif field in changes:
                if (field == 'cell_name' and self[field] is not None and
                        self[field].startswith(cells_utils.BLOCK_SYNC_FLAG)):
                    updates[field] = self[field].replace(
                            cells_utils.BLOCK_SYNC_FLAG, '', 1)
                else:
                    updates[field] = self[field]

        if not updates:
            if cells_update_from_api:
                _handle_cell_update_from_api()
            return

        # Cleaned needs to be turned back into an int here
        if 'cleaned' in updates:
            if updates['cleaned']:
                updates['cleaned'] = 1
            else:
                updates['cleaned'] = 0

        if expected_task_state is not None:
            updates['expected_task_state'] = expected_task_state
        if expected_vm_state is not None:
            updates['expected_vm_state'] = expected_vm_state

        expected_attrs = [attr for attr in _INSTANCE_OPTIONAL_JOINED_FIELDS
                               if self.obj_attr_is_set(attr)]
        if 'pci_devices' in expected_attrs:
            # NOTE(danms): We don't refresh pci_devices on save right now
            expected_attrs.remove('pci_devices')

        # NOTE(alaski): We need to pull system_metadata for the
        # notification.send_update() below.  If we don't there's a KeyError
        # when it tries to extract the flavor.
        # NOTE(danms): If we have sysmeta, we need flavor since the caller
        # might be expecting flavor information as a result
        if 'system_metadata' not in expected_attrs:
            expected_attrs.append('system_metadata')
            expected_attrs.append('flavor')
        old_ref, inst_ref = db.instance_update_and_get_original(
                context, self.uuid, updates,
                columns_to_join=_expected_cols(expected_attrs))
        self._from_db_object(context, self, inst_ref,
                             expected_attrs=expected_attrs)

        if cells_update_from_api:
            _handle_cell_update_from_api()
        elif cell_type == 'compute':
            if self._sync_cells:
                cells_api = cells_rpcapi.CellsAPI()
                cells_api.instance_update_at_top(context, stale_instance)

        def _notify():
            # NOTE(danms): We have to be super careful here not to trigger
            # any lazy-loads that will unmigrate or unbackport something. So,
            # make a copy of the instance for notifications first.
            new_ref = self.obj_clone()

            notifications.send_update(context, old_ref, new_ref)

        # NOTE(alaski): If cell synchronization is blocked it means we have
        # already run this block of code in either the parent or child of this
        # cell.  Therefore this notification has already been sent.
        if not self._sync_cells:
            _notify = lambda: None  # noqa: F811

        _notify()

        self.obj_reset_changes()
Save updates to this instance
dataset/ETHPy150Open BU-NU-CLOUD-SP16/Trusted-Platform-Module-nova/nova/objects/instance.py/Instance.save