summaryrefslogtreecommitdiff
path: root/nova/tests/functional/db/test_virtual_interface.py
blob: 12a62f83ae764a2527be53b951ebe1f329a54b29 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import datetime
import mock
from oslo_config import cfg
from oslo_utils import timeutils

from nova import context
from nova import exception
from nova.network import model as network_model
from nova import objects
from nova.objects import virtual_interface
from nova.tests.functional import integrated_helpers
from nova.tests.unit import fake_network

CONF = cfg.CONF

FAKE_UUID = '00000000-0000-0000-0000-000000000000'


def _delete_vif_list(context, instance_uuid):
    vif_list = objects.VirtualInterfaceList.\
        get_by_instance_uuid(context, instance_uuid)

    # Set old VirtualInterfaces as deleted.
    for vif in vif_list:
        vif.destroy()


def _verify_list_fulfillment(context, instance_uuid):
    try:
        info_cache = objects.InstanceInfoCache.\
            get_by_instance_uuid(context, instance_uuid)
    except exception.InstanceInfoCacheNotFound:
        info_cache = []

    vif_list = objects.VirtualInterfaceList.\
        get_by_instance_uuid(context, instance_uuid)
    vif_list = filter(lambda x: not x.deleted,
                      vif_list)

    cached_vif_ids = [vif['id'] for vif in info_cache.network_info]
    db_vif_ids = [vif.uuid for vif in vif_list]
    return cached_vif_ids == db_vif_ids


class VirtualInterfaceListMigrationTestCase(
    integrated_helpers._IntegratedTestBase,
    integrated_helpers.InstanceHelperMixin):

    ADMIN_API = True
    USE_NEUTRON = True
    api_major_version = 'v2.1'

    _image_ref_parameter = 'imageRef'
    _flavor_ref_parameter = 'flavorRef'

    def setUp(self):
        super(VirtualInterfaceListMigrationTestCase, self).setUp()

        self.context = context.get_admin_context()
        fake_network.set_stub_network_methods(self)
        self.cells = objects.CellMappingList.get_all(self.context)

        self._start_compute('compute2')
        self.instances = []

    def _create_instances(self, pre_newton=2, deleted=0, total=5,
                          target_cell=None):
        if not target_cell:
            target_cell = self.cells[1]

        instances = []
        with context.target_cell(self.context, target_cell) as cctxt:
            flav_dict = objects.Flavor._flavor_get_from_db(cctxt, 1)
            flavor = objects.Flavor(**flav_dict)
            for i in range(0, total):
                inst = objects.Instance(
                    context=cctxt,
                    project_id=self.api.project_id,
                    user_id=FAKE_UUID,
                    vm_state='active',
                    flavor=flavor,
                    created_at=datetime.datetime(1985, 10, 25, 1, 21, 0),
                    launched_at=datetime.datetime(1985, 10, 25, 1, 22, 0),
                    host=self.computes['compute2'].host,
                    hostname='%s-inst%i' % (target_cell.name, i))
                inst.create()

                info_cache = objects.InstanceInfoCache(context=cctxt)
                info_cache.updated_at = timeutils.utcnow()
                info_cache.network_info = network_model.NetworkInfo()
                info_cache.instance_uuid = inst.uuid
                info_cache.save()

                instances.append(inst)

                im = objects.InstanceMapping(context=cctxt,
                    project_id=inst.project_id,
                    user_id=inst.user_id,
                    instance_uuid=inst.uuid,
                    cell_mapping=target_cell)
                im.create()

        # Attach fake interfaces to instances
        network_id = list(self.neutron._networks.keys())[0]
        for i in range(0, len(instances)):
            for k in range(0, 4):
                self.api.attach_interface(instances[i].uuid,
                    {"interfaceAttachment": {"net_id": network_id}})

        with context.target_cell(self.context, target_cell) as cctxt:
            # Fake the pre-newton behaviour by removing the
            # VirtualInterfacesList objects.
            if pre_newton:
                for i in range(0, pre_newton):
                    _delete_vif_list(cctxt, instances[i].uuid)

        if deleted:
            # Delete from the end of active instances list
            for i in range(total - deleted, total):
                instances[i].destroy()

        self.instances += instances

    def test_migration_nothing_to_migrate(self):
        """This test when there already populated VirtualInterfaceList
           objects for created instances.
        """
        self._create_instances(pre_newton=0, total=5)
        match, done = virtual_interface.fill_virtual_interface_list(
            self.context, 5)

        self.assertEqual(5, match)
        self.assertEqual(0, done)

    def test_migration_verify_max_count(self):
        """This verifies if max_count is respected to avoid migration
           of bigger set of data, than user specified.
        """
        self._create_instances(pre_newton=0, total=3)
        match, done = virtual_interface.fill_virtual_interface_list(
            self.context, 2)

        self.assertEqual(2, match)
        self.assertEqual(0, done)

    def test_migration_do_not_step_to_next_cell(self):
        """This verifies if script doesn't step into next cell
           when max_count is reached.
        """
        # Create 2 instances in cell0
        self._create_instances(
            pre_newton=0, total=2, target_cell=self.cells[0])

        # Create 2 instances in cell1
        self._create_instances(
            pre_newton=0, total=2, target_cell=self.cells[1])

        with mock.patch('nova.objects.InstanceList.get_by_filters',
                        side_effect=[self.instances[0:2],
                                     self.instances[2:]]) \
            as mock_get:
            match, done = virtual_interface.fill_virtual_interface_list(
                self.context, 2)

        self.assertEqual(2, match)
        self.assertEqual(0, done)
        mock_get.assert_called_once()

    def test_migration_pre_newton_instances(self):
        """This test when there is an instance created in release
           older than Newton. For those instances the VirtualInterfaceList
           needs to be re-created from cache.
        """
        # Lets spawn 3 pre-newton instances and 2 new ones
        self._create_instances(pre_newton=3, total=5)
        match, done = virtual_interface.fill_virtual_interface_list(
            self.context, 5)

        self.assertEqual(5, match)
        self.assertEqual(3, done)

        # Make sure we ran over all the instances - verify if marker works
        match, done = virtual_interface.fill_virtual_interface_list(
            self.context, 50)
        self.assertEqual(0, match)
        self.assertEqual(0, done)

        for i in range(0, 5):
            _verify_list_fulfillment(self.context, self.instances[i].uuid)

    def test_migration_pre_newton_instance_new_vifs(self):
        """This test when instance was created before Newton
           but in meantime new interfaces where attached and
           VirtualInterfaceList is not populated.
        """
        self._create_instances(pre_newton=0, total=1)

        vif_list = objects.VirtualInterfaceList.get_by_instance_uuid(
            self.context, self.instances[0].uuid)
        # Drop first vif from list to pretend old instance
        vif_list[0].destroy()

        match, done = virtual_interface.fill_virtual_interface_list(
            self.context, 5)

        # The whole VirtualInterfaceList should be rewritten and base
        # on cache.
        self.assertEqual(1, match)
        self.assertEqual(1, done)

        _verify_list_fulfillment(self.context, self.instances[0].uuid)

    def test_migration_attach_in_progress(self):
        """This test when number of vifs (db) is bigger than
           number taken from network cache. Potential
           port-attach is taking place.
        """
        self._create_instances(pre_newton=0, total=1)
        instance_info_cache = objects.InstanceInfoCache.get_by_instance_uuid(
            self.context, self.instances[0].uuid)

        # Delete last interface to pretend that's still in progress
        instance_info_cache.network_info.pop()
        instance_info_cache.updated_at = datetime.datetime(2015, 1, 1)

        instance_info_cache.save()

        match, done = virtual_interface.fill_virtual_interface_list(
            self.context, 5)

        # I don't know whats going on so instance VirtualInterfaceList
        # should stay untouched.
        self.assertEqual(1, match)
        self.assertEqual(0, done)

    def test_migration_empty_network_info(self):
        """This test if migration is not executed while
           NetworkInfo is empty, like instance without
           interfaces attached.
        """
        self._create_instances(pre_newton=0, total=1)
        instance_info_cache = objects.InstanceInfoCache.get_by_instance_uuid(
            self.context, self.instances[0].uuid)

        # Clean NetworkInfo. Pretend instance without interfaces.
        instance_info_cache.network_info = None
        instance_info_cache.save()

        match, done = virtual_interface.fill_virtual_interface_list(
            self.context, 5)

        self.assertEqual(0, match)
        self.assertEqual(0, done)

    def test_migration_inconsistent_data(self):
        """This test when vif (db) are in completely different
           comparing to network cache and we don't know how to
           deal with it. It's the corner-case.
        """
        self._create_instances(pre_newton=0, total=1)
        instance_info_cache = objects.InstanceInfoCache.get_by_instance_uuid(
            self.context, self.instances[0].uuid)

        # Change order of interfaces in NetworkInfo to fake
        # inconsistency between cache and db.
        nwinfo = instance_info_cache.network_info
        interface = nwinfo.pop()
        nwinfo.insert(0, interface)
        instance_info_cache.updated_at = datetime.datetime(2015, 1, 1)
        instance_info_cache.network_info = nwinfo

        # Update the cache
        instance_info_cache.save()

        match, done = virtual_interface.fill_virtual_interface_list(
            self.context, 5)

        # Cache is corrupted, so must be rewritten
        self.assertEqual(1, match)
        self.assertEqual(1, done)

    def test_migration_dont_touch_deleted_objects(self):
        """This test if deleted instances are skipped
           during migration.
        """
        self._create_instances(
            pre_newton=1, deleted=1, total=3)

        match, done = virtual_interface.fill_virtual_interface_list(
            self.context, 4)
        self.assertEqual(2, match)
        self.assertEqual(1, done)

    def test_migration_multiple_cells(self):
        """This test if marker and max_rows limit works properly while
           running in multi-cell environment.
        """
        # Create 2 instances in cell0
        self._create_instances(
            pre_newton=1, total=2, target_cell=self.cells[0])
        # Create 4 instances in cell1
        self._create_instances(
            pre_newton=3, total=5, target_cell=self.cells[1])

        # Fill vif list limiting to 4 instances - it should
        # touch cell0 and cell1 instances (migrate 3 due 1 is post newton).
        match, done = virtual_interface.fill_virtual_interface_list(
            self.context, 4)
        self.assertEqual(4, match)
        self.assertEqual(3, done)

        # Verify that the marker instance has project_id/user_id set properly.
        with context.target_cell(self.context, self.cells[1]) as cctxt:
            # The marker record is destroyed right after it's created, since
            # only the presence of the row is needed to satisfy the fkey
            # constraint.
            cctxt = cctxt.elevated(read_deleted='yes')
            marker_instance = objects.Instance.get_by_uuid(cctxt, FAKE_UUID)
        self.assertEqual(FAKE_UUID, marker_instance.project_id)
        self.assertEqual(FAKE_UUID, marker_instance.user_id)

        # Try again - should fill 3 left instances from cell1
        match, done = virtual_interface.fill_virtual_interface_list(
            self.context, 4)
        self.assertEqual(3, match)
        self.assertEqual(1, done)

        # Try again - should be nothing to migrate
        match, done = virtual_interface.fill_virtual_interface_list(
            self.context, 4)
        self.assertEqual(0, match)
        self.assertEqual(0, done)

    def test_migration_multiple_cells_new_instances_in_meantime(self):
        """This test if marker is created per-cell and we're able to
           verify instanced that were added in meantime.
        """
        # Create 2 instances in cell0
        self._create_instances(
            pre_newton=1, total=2, target_cell=self.cells[0])
        # Create 2 instances in cell1
        self._create_instances(
            pre_newton=1, total=2, target_cell=self.cells[1])

        # Migrate instances in both cells.
        match, done = virtual_interface.fill_virtual_interface_list(
            self.context, 4)
        self.assertEqual(4, match)
        self.assertEqual(2, done)

        # Add new instances to cell1
        self._create_instances(
            pre_newton=0, total=2, target_cell=self.cells[1])

        # Try again, should find instances in cell1
        match, done = virtual_interface.fill_virtual_interface_list(
            self.context, 4)
        self.assertEqual(2, match)
        self.assertEqual(0, done)

        # Try again - should be nothing to migrate
        match, done = virtual_interface.fill_virtual_interface_list(
            self.context, 4)
        self.assertEqual(0, match)
        self.assertEqual(0, done)