Coverage for nova/objects/migration.py: 96%
172 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-17 15:08 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-17 15:08 +0000
1# Copyright 2013 IBM Corp.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
15from oslo_db import exception as db_exc
16from oslo_log import log as logging
17from oslo_utils import uuidutils
18from oslo_utils import versionutils
20from nova.db.main import api as db
21from nova import exception
22from nova.i18n import _
23from nova import objects
24from nova.objects import base
25from nova.objects import fields
28LOG = logging.getLogger(__name__)
31def determine_migration_type(migration):
32 if isinstance(migration, dict):
33 old_instance_type_id = migration['old_instance_type_id']
34 new_instance_type_id = migration['new_instance_type_id']
35 else:
36 old_instance_type_id = migration.old_instance_type_id
37 new_instance_type_id = migration.new_instance_type_id
39 if old_instance_type_id != new_instance_type_id:
40 return 'resize'
42 return 'migration'
45@base.NovaObjectRegistry.register
46class Migration(base.NovaPersistentObject, base.NovaObject):
47 # Version 1.0: Initial version
48 # Version 1.1: String attributes updated to support unicode
49 # Version 1.2: Added migration_type and hidden
50 # Version 1.3: Added get_by_id_and_instance()
51 # Version 1.4: Added migration progress detail
52 # Version 1.5: Added uuid
53 # Version 1.6: Added cross_cell_move and get_by_uuid().
54 # Version 1.7: Added user_id and project_id
55 # Version 1.8: Added dest_compute_id
56 VERSION = '1.8'
58 fields = {
59 'id': fields.IntegerField(),
60 'uuid': fields.UUIDField(),
61 'source_compute': fields.StringField(nullable=True), # source hostname
62 'dest_compute': fields.StringField(nullable=True), # dest hostname
63 'source_node': fields.StringField(nullable=True), # source nodename
64 'dest_node': fields.StringField(nullable=True), # dest nodename
65 # ID of ComputeNode matching dest_node
66 'dest_compute_id': fields.IntegerField(nullable=True),
67 'dest_host': fields.StringField(nullable=True), # dest host IP
68 # TODO(stephenfin): Rename these to old_flavor_id, new_flavor_id in
69 # v2.0
70 'old_instance_type_id': fields.IntegerField(nullable=True),
71 'new_instance_type_id': fields.IntegerField(nullable=True),
72 'instance_uuid': fields.StringField(nullable=True),
73 'status': fields.StringField(nullable=True),
74 'migration_type': fields.MigrationTypeField(nullable=False),
75 'hidden': fields.BooleanField(nullable=False, default=False),
76 'memory_total': fields.IntegerField(nullable=True),
77 'memory_processed': fields.IntegerField(nullable=True),
78 'memory_remaining': fields.IntegerField(nullable=True),
79 'disk_total': fields.IntegerField(nullable=True),
80 'disk_processed': fields.IntegerField(nullable=True),
81 'disk_remaining': fields.IntegerField(nullable=True),
82 'cross_cell_move': fields.BooleanField(default=False),
83 # request context user id
84 'user_id': fields.StringField(nullable=True),
85 # request context project id
86 'project_id': fields.StringField(nullable=True),
87 }
89 @staticmethod
90 def _from_db_object(context, migration, db_migration):
91 for key in migration.fields:
92 value = db_migration[key]
93 if key == 'migration_type' and value is None:
94 value = determine_migration_type(db_migration)
95 elif key == 'uuid' and value is None:
96 continue
97 setattr(migration, key, value)
99 migration._context = context
100 migration.obj_reset_changes()
101 migration._ensure_uuid()
102 return migration
104 def obj_make_compatible(self, primitive, target_version):
105 super(Migration, self).obj_make_compatible(primitive, target_version)
106 target_version = versionutils.convert_version_to_tuple(target_version)
107 if target_version < (1, 2):
108 if 'migration_type' in primitive:
109 del primitive['migration_type']
110 del primitive['hidden']
111 if target_version < (1, 4):
112 if 'memory_total' in primitive:
113 del primitive['memory_total']
114 del primitive['memory_processed']
115 del primitive['memory_remaining']
116 del primitive['disk_total']
117 del primitive['disk_processed']
118 del primitive['disk_remaining']
119 if target_version < (1, 5):
120 if 'uuid' in primitive:
121 del primitive['uuid']
122 if target_version < (1, 6) and 'cross_cell_move' in primitive:
123 del primitive['cross_cell_move']
124 if target_version < (1, 7):
125 if 'user_id' in primitive: 125 ↛ 126line 125 didn't jump to line 126 because the condition on line 125 was never true
126 del primitive['user_id']
127 if 'project_id' in primitive: 127 ↛ 128line 127 didn't jump to line 128 because the condition on line 127 was never true
128 del primitive['project_id']
129 if target_version < (1, 8):
130 primitive.pop('dest_compute_id', None)
132 @base.lazy_load_counter
133 def obj_load_attr(self, attrname):
134 if attrname == 'migration_type':
135 # NOTE(danms): The only reason we'd need to load this is if
136 # some older node sent us one. So, guess the type.
137 self.migration_type = determine_migration_type(self)
138 elif attrname in ['hidden', 'cross_cell_move']: 138 ↛ 141line 138 didn't jump to line 141 because the condition on line 138 was always true
139 self.obj_set_defaults(attrname)
140 else:
141 super(Migration, self).obj_load_attr(attrname)
143 def _ensure_uuid(self):
144 if 'uuid' in self:
145 return
147 self.uuid = uuidutils.generate_uuid()
148 try:
149 self.save()
150 except db_exc.DBDuplicateEntry:
151 # NOTE(danms) We raced to generate a uuid for this,
152 # so fetch the winner and use that uuid
153 fresh = self.__class__.get_by_id(self.context, self.id)
154 self.uuid = fresh.uuid
156 @base.remotable_classmethod
157 def get_by_uuid(cls, context, migration_uuid):
158 db_migration = db.migration_get_by_uuid(context, migration_uuid)
159 return cls._from_db_object(context, cls(), db_migration)
161 @base.remotable_classmethod
162 def get_by_id(cls, context, migration_id):
163 db_migration = db.migration_get(context, migration_id)
164 return cls._from_db_object(context, cls(), db_migration)
166 @base.remotable_classmethod
167 def get_by_id_and_instance(cls, context, migration_id, instance_uuid):
168 db_migration = db.migration_get_by_id_and_instance(
169 context, migration_id, instance_uuid)
170 return cls._from_db_object(context, cls(), db_migration)
172 @base.remotable_classmethod
173 def get_by_instance_and_status(cls, context, instance_uuid, status):
174 db_migration = db.migration_get_by_instance_and_status(
175 context, instance_uuid, status)
176 return cls._from_db_object(context, cls(), db_migration)
178 @base.remotable
179 def create(self):
180 if self.obj_attr_is_set('id'):
181 raise exception.ObjectActionError(action='create',
182 reason='already created')
183 if 'uuid' not in self:
184 self.uuid = uuidutils.generate_uuid()
185 # Record who is initiating the migration which is
186 # not necessarily the owner of the instance.
187 if 'user_id' not in self:
188 self.user_id = self._context.user_id
189 if 'project_id' not in self:
190 self.project_id = self._context.project_id
191 updates = self.obj_get_changes()
192 if 'migration_type' not in updates:
193 raise exception.ObjectActionError(
194 action="create",
195 reason=_("cannot create a Migration object without a "
196 "migration_type set"))
197 version = versionutils.convert_version_to_tuple(self.VERSION)
198 if 'dest_node' in updates and 'dest_compute_id' not in updates:
199 # NOTE(danms): This is not really the best idea, as we should try
200 # not to have different behavior based on the version of the
201 # object. However, this exception helps us find cases in testing
202 # where these may not be updated together. We can remove this
203 # later.
204 if version >= (1, 8):
205 raise exception.ObjectActionError(
206 action='create',
207 reason=_('cannot create a Migration object with a '
208 'dest_node but no dest_compute_id'))
209 else:
210 LOG.warning('Migration is being created for %s but no '
211 'compute_id is set', self.dest_node)
212 db_migration = db.migration_create(self._context, updates)
213 self._from_db_object(self._context, self, db_migration)
215 @base.remotable
216 def save(self):
217 updates = self.obj_get_changes()
218 updates.pop('id', None)
219 db_migration = db.migration_update(self._context, self.id, updates)
220 self._from_db_object(self._context, self, db_migration)
221 self.obj_reset_changes()
223 @property
224 def instance(self):
225 if not hasattr(self, '_cached_instance'):
226 self._cached_instance = objects.Instance.get_by_uuid(
227 self._context, self.instance_uuid,
228 expected_attrs=['migration_context', 'flavor'])
229 return self._cached_instance
231 @instance.setter
232 def instance(self, instance):
233 self._cached_instance = instance
235 @property
236 def is_live_migration(self):
237 return self.migration_type == fields.MigrationType.LIVE_MIGRATION
239 @property
240 def is_resize(self):
241 return self.migration_type == fields.MigrationType.RESIZE
243 @property
244 def is_same_host_resize(self):
245 return self.is_resize and self.source_node == self.dest_node
247 def get_dest_compute_id(self):
248 """Try to determine the ComputeNode id this migration targets.
250 This should be just the dest_compute_id field, but for migrations
251 created by older compute nodes, we may not have that set. If not,
252 look up the compute the old way for compatibility.
254 :raises:ComputeHostNotFound if the destination compute is missing
255 """
256 if 'dest_compute_id' not in self:
257 self.dest_compute_id = (
258 objects.ComputeNode.get_by_host_and_nodename(
259 self._context,
260 self.dest_compute,
261 self.dest_node).id)
262 return self.dest_compute_id
265@base.NovaObjectRegistry.register
266class MigrationList(base.ObjectListBase, base.NovaObject):
267 # Version 1.0: Initial version
268 # Migration <= 1.1
269 # Version 1.1: Added use_slave to get_unconfirmed_by_dest_compute
270 # Version 1.2: Migration version 1.2
271 # Version 1.3: Added a new function to get in progress migrations
272 # for an instance.
273 # Version 1.4: Added sort_keys, sort_dirs, limit, marker kwargs to
274 # get_by_filters for migrations pagination support.
275 # Version 1.5: Added a new function to get in progress migrations
276 # and error migrations for a given host + node.
277 VERSION = '1.5'
279 fields = {
280 'objects': fields.ListOfObjectsField('Migration'),
281 }
283 @staticmethod
284 @db.select_db_reader_mode
285 def _db_migration_get_unconfirmed_by_dest_compute(
286 context, confirm_window, dest_compute, use_slave=False):
287 return db.migration_get_unconfirmed_by_dest_compute(
288 context, confirm_window, dest_compute)
290 @base.remotable_classmethod
291 def get_unconfirmed_by_dest_compute(cls, context, confirm_window,
292 dest_compute, use_slave=False):
293 db_migrations = cls._db_migration_get_unconfirmed_by_dest_compute(
294 context, confirm_window, dest_compute, use_slave=use_slave)
295 return base.obj_make_list(context, cls(context), objects.Migration,
296 db_migrations)
298 @base.remotable_classmethod
299 def get_in_progress_by_host_and_node(cls, context, host, node):
300 db_migrations = db.migration_get_in_progress_by_host_and_node(
301 context, host, node)
302 return base.obj_make_list(context, cls(context), objects.Migration,
303 db_migrations)
305 @base.remotable_classmethod
306 def get_by_filters(cls, context, filters, sort_keys=None, sort_dirs=None,
307 limit=None, marker=None):
308 db_migrations = db.migration_get_all_by_filters(
309 context, filters, sort_keys=sort_keys, sort_dirs=sort_dirs,
310 limit=limit, marker=marker)
311 return base.obj_make_list(context, cls(context), objects.Migration,
312 db_migrations)
314 @base.remotable_classmethod
315 def get_in_progress_by_instance(cls, context, instance_uuid,
316 migration_type=None):
317 db_migrations = db.migration_get_in_progress_by_instance(
318 context, instance_uuid, migration_type)
319 return base.obj_make_list(context, cls(context), objects.Migration,
320 db_migrations)
322 @base.remotable_classmethod
323 def get_in_progress_and_error(cls, context, host, node):
324 db_migrations = \
325 db.migration_get_in_progress_and_error_by_host_and_node(
326 context, host, node)
327 return base.obj_make_list(context, cls(context), objects.Migration,
328 db_migrations)