|
20 | 20 | from nova.objects import cell_mapping
|
21 | 21 | from nova.objects import instance
|
22 | 22 | from nova.objects import instance_mapping
|
| 23 | +from nova.objects import virtual_interface |
23 | 24 | from nova import test
|
24 | 25 | from nova.tests import fixtures
|
25 | 26 |
|
26 | 27 |
|
27 | 28 | sample_mapping = {'instance_uuid': '',
|
28 | 29 | 'cell_id': 3,
|
29 |
| - 'project_id': 'fake-project'} |
| 30 | + 'project_id': 'fake-project', |
| 31 | + 'user_id': 'fake-user'} |
30 | 32 |
|
31 | 33 |
|
32 | 34 | sample_cell_mapping = {'id': 3,
|
@@ -208,14 +210,194 @@ def test_populate_queued_for_delete(self):
|
208 | 210 |
|
209 | 211 | def test_user_id_not_set_if_null_from_db(self):
|
210 | 212 | # Create an instance mapping with user_id=None.
|
211 |
| - db_mapping = create_mapping() |
| 213 | + db_mapping = create_mapping(user_id=None) |
212 | 214 | self.assertIsNone(db_mapping['user_id'])
|
213 | 215 | # Get the mapping to run convert from db object to versioned object.
|
214 | 216 | im = instance_mapping.InstanceMapping.get_by_instance_uuid(
|
215 | 217 | self.context, db_mapping['instance_uuid'])
|
216 | 218 | # Verify the user_id is not set.
|
217 | 219 | self.assertNotIn('user_id', im)
|
218 | 220 |
|
| 221 | + @mock.patch('nova.objects.instance_mapping.LOG.warning') |
| 222 | + def test_populate_user_id(self, mock_log_warning): |
| 223 | + cells = [] |
| 224 | + celldbs = fixtures.CellDatabases() |
| 225 | + |
| 226 | + # Create two cell databases and map them |
| 227 | + for uuid in (uuidsentinel.cell1, uuidsentinel.cell2): |
| 228 | + cm = cell_mapping.CellMapping(context=self.context, uuid=uuid, |
| 229 | + database_connection=uuid, |
| 230 | + transport_url='fake://') |
| 231 | + cm.create() |
| 232 | + cells.append(cm) |
| 233 | + celldbs.add_cell_database(uuid) |
| 234 | + self.useFixture(celldbs) |
| 235 | + |
| 236 | + # Create 5 instances per cell |
| 237 | + for cell in cells: |
| 238 | + for i in range(0, 5): |
| 239 | + with context.target_cell(self.context, cell) as cctxt: |
| 240 | + inst = instance.Instance( |
| 241 | + cctxt, |
| 242 | + project_id=self.context.project_id, |
| 243 | + user_id=self.context.user_id) |
| 244 | + inst.create() |
| 245 | + # Make every other mapping have a NULL user_id |
| 246 | + # Will be a total of four mappings with NULL user_id |
| 247 | + user_id = self.context.user_id if i % 2 == 0 else None |
| 248 | + create_mapping(project_id=self.context.project_id, |
| 249 | + user_id=user_id, cell_id=cell.id, |
| 250 | + instance_uuid=inst.uuid) |
| 251 | + |
| 252 | + # Create a SOFT_DELETED instance with a user_id=None instance mapping. |
| 253 | + # This should get migrated. |
| 254 | + with context.target_cell(self.context, cells[0]) as cctxt: |
| 255 | + inst = instance.Instance( |
| 256 | + cctxt, project_id=self.context.project_id, |
| 257 | + user_id=self.context.user_id, vm_state=vm_states.SOFT_DELETED) |
| 258 | + inst.create() |
| 259 | + create_mapping(project_id=self.context.project_id, user_id=None, |
| 260 | + cell_id=cells[0].id, instance_uuid=inst.uuid, |
| 261 | + queued_for_delete=True) |
| 262 | + |
| 263 | + # Create a deleted instance with a user_id=None instance mapping. |
| 264 | + # This should get migrated. |
| 265 | + with context.target_cell(self.context, cells[1]) as cctxt: |
| 266 | + inst = instance.Instance( |
| 267 | + cctxt, project_id=self.context.project_id, |
| 268 | + user_id=self.context.user_id) |
| 269 | + inst.create() |
| 270 | + inst.destroy() |
| 271 | + create_mapping(project_id=self.context.project_id, user_id=None, |
| 272 | + cell_id=cells[1].id, instance_uuid=inst.uuid, |
| 273 | + queued_for_delete=True) |
| 274 | + |
| 275 | + # Create an instance mapping for an instance not yet scheduled. It |
| 276 | + # should not get migrated because we won't know what user_id to use. |
| 277 | + unscheduled = create_mapping(project_id=self.context.project_id, |
| 278 | + user_id=None, cell_id=None) |
| 279 | + |
| 280 | + # Create two instance mappings for instances that no longer exist. |
| 281 | + # Example: residue from a manual cleanup or after a periodic compute |
| 282 | + # purge and before a database archive. This record should not get |
| 283 | + # migrated. |
| 284 | + nonexistent = [] |
| 285 | + for i in range(2): |
| 286 | + nonexistent.append( |
| 287 | + create_mapping(project_id=self.context.project_id, |
| 288 | + user_id=None, cell_id=cells[i].id, |
| 289 | + instance_uuid=uuidutils.generate_uuid())) |
| 290 | + |
| 291 | + # Create an instance mapping simulating a virtual interface migration |
| 292 | + # marker instance which has had map_instances run on it. |
| 293 | + # This should not be found by the migration. |
| 294 | + create_mapping(project_id=virtual_interface.FAKE_UUID, user_id=None) |
| 295 | + |
| 296 | + found, done = instance_mapping.populate_user_id(self.context, 2) |
| 297 | + # Two needed fixing, and honored the limit. |
| 298 | + self.assertEqual(2, found) |
| 299 | + self.assertEqual(2, done) |
| 300 | + |
| 301 | + found, done = instance_mapping.populate_user_id(self.context, 1000) |
| 302 | + # Only four left were fixable. The fifth instance found has no |
| 303 | + # cell and cannot be migrated yet. The 6th and 7th instances found have |
| 304 | + # no corresponding instance records and cannot be migrated. |
| 305 | + self.assertEqual(7, found) |
| 306 | + self.assertEqual(4, done) |
| 307 | + |
| 308 | + # Verify the orphaned instance mappings warning log message was only |
| 309 | + # emitted once. |
| 310 | + mock_log_warning.assert_called_once() |
| 311 | + |
| 312 | + # Check that we have only the expected number of records with |
| 313 | + # user_id set. We created 10 instances (5 per cell with 2 per cell |
| 314 | + # with NULL user_id), 1 SOFT_DELETED instance with NULL user_id, |
| 315 | + # 1 deleted instance with NULL user_id, and 1 not-yet-scheduled |
| 316 | + # instance with NULL user_id. |
| 317 | + # We expect 12 of them to have user_id set after migration (15 total, |
| 318 | + # with the not-yet-scheduled instance and the orphaned instance |
| 319 | + # mappings ignored). |
| 320 | + ims = instance_mapping.InstanceMappingList.get_by_project_id( |
| 321 | + self.context, self.context.project_id) |
| 322 | + self.assertEqual(12, len( |
| 323 | + [im for im in ims if 'user_id' in im])) |
| 324 | + |
| 325 | + # Check that one instance mapping record (not yet scheduled) has not |
| 326 | + # been migrated by this script. |
| 327 | + # Check that two other instance mapping records (no longer existing |
| 328 | + # instances) have not been migrated by this script. |
| 329 | + self.assertEqual(15, len(ims)) |
| 330 | + |
| 331 | + # Set the cell and create the instance for the mapping without a cell, |
| 332 | + # then run the migration again. |
| 333 | + unscheduled = instance_mapping.InstanceMapping.get_by_instance_uuid( |
| 334 | + self.context, unscheduled['instance_uuid']) |
| 335 | + unscheduled.cell_mapping = cells[0] |
| 336 | + unscheduled.save() |
| 337 | + with context.target_cell(self.context, cells[0]) as cctxt: |
| 338 | + inst = instance.Instance( |
| 339 | + cctxt, |
| 340 | + uuid=unscheduled.instance_uuid, |
| 341 | + project_id=self.context.project_id, |
| 342 | + user_id=self.context.user_id) |
| 343 | + inst.create() |
| 344 | + found, done = instance_mapping.populate_user_id(self.context, 1000) |
| 345 | + # Should have found the not-yet-scheduled instance and the orphaned |
| 346 | + # instance mappings. |
| 347 | + self.assertEqual(3, found) |
| 348 | + # Should have only migrated the not-yet-schedule instance. |
| 349 | + self.assertEqual(1, done) |
| 350 | + |
| 351 | + # Delete the orphaned instance mapping (simulate manual cleanup by an |
| 352 | + # operator). |
| 353 | + for db_im in nonexistent: |
| 354 | + nonexist = instance_mapping.InstanceMapping.get_by_instance_uuid( |
| 355 | + self.context, db_im['instance_uuid']) |
| 356 | + nonexist.destroy() |
| 357 | + |
| 358 | + # Run the script one last time to make sure it finds nothing left to |
| 359 | + # migrate. |
| 360 | + found, done = instance_mapping.populate_user_id(self.context, 1000) |
| 361 | + self.assertEqual(0, found) |
| 362 | + self.assertEqual(0, done) |
| 363 | + |
| 364 | + @mock.patch('nova.objects.InstanceList.get_by_filters') |
| 365 | + def test_populate_user_id_instance_get_fail(self, mock_inst_get): |
| 366 | + cells = [] |
| 367 | + celldbs = fixtures.CellDatabases() |
| 368 | + |
| 369 | + # Create two cell databases and map them |
| 370 | + for uuid in (uuidsentinel.cell1, uuidsentinel.cell2): |
| 371 | + cm = cell_mapping.CellMapping(context=self.context, uuid=uuid, |
| 372 | + database_connection=uuid, |
| 373 | + transport_url='fake://') |
| 374 | + cm.create() |
| 375 | + cells.append(cm) |
| 376 | + celldbs.add_cell_database(uuid) |
| 377 | + self.useFixture(celldbs) |
| 378 | + |
| 379 | + # Create one instance per cell |
| 380 | + for cell in cells: |
| 381 | + with context.target_cell(self.context, cell) as cctxt: |
| 382 | + inst = instance.Instance( |
| 383 | + cctxt, |
| 384 | + project_id=self.context.project_id, |
| 385 | + user_id=self.context.user_id) |
| 386 | + inst.create() |
| 387 | + create_mapping(project_id=self.context.project_id, |
| 388 | + user_id=None, cell_id=cell.id, |
| 389 | + instance_uuid=inst.uuid) |
| 390 | + |
| 391 | + # Simulate the first cell is down/has some error |
| 392 | + mock_inst_get.side_effect = [test.TestingException(), |
| 393 | + instance.InstanceList(objects=[inst])] |
| 394 | + |
| 395 | + found, done = instance_mapping.populate_user_id(self.context, 1000) |
| 396 | + # Verify we continue to the next cell when a down/error cell is |
| 397 | + # encountered. |
| 398 | + self.assertEqual(2, found) |
| 399 | + self.assertEqual(1, done) |
| 400 | + |
219 | 401 |
|
220 | 402 | class InstanceMappingListTestCase(test.NoDBTestCase):
|
221 | 403 | USES_DB_SELF = True
|
|
0 commit comments