summaryrefslogtreecommitdiff
path: root/nova/objects/virtual_interface.py
blob: 7ce418aca2024c17ab191bd0dbc39e66a393f5c3 (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
# Copyright (C) 2014, Red Hat, Inc.
#
#    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.

from oslo_log import log as logging
from oslo_utils import versionutils

from nova import context as nova_context
from nova.db.api import api as api_db_api
from nova.db.main import api as main_db_api
from nova.db.main import models as main_db_models
from nova import exception
from nova import objects
from nova.objects import base
from nova.objects import fields

LOG = logging.getLogger(__name__)

VIF_OPTIONAL_FIELDS = ['network_id']
FAKE_UUID = '00000000-0000-0000-0000-000000000000'


@base.NovaObjectRegistry.register
class VirtualInterface(base.NovaPersistentObject, base.NovaObject):
    # Version 1.0: Initial version
    # Version 1.1: Add tag field
    # Version 1.2: Adding a save method
    # Version 1.3: Added destroy() method
    VERSION = '1.3'

    fields = {
        'id': fields.IntegerField(),
        # This is a MAC address.
        'address': fields.StringField(nullable=True),
        'network_id': fields.IntegerField(),
        'instance_uuid': fields.UUIDField(),
        'uuid': fields.UUIDField(),
        'tag': fields.StringField(nullable=True),
    }

    def obj_make_compatible(self, primitive, target_version):
        target_version = versionutils.convert_version_to_tuple(target_version)
        if target_version < (1, 1) and 'tag' in primitive:
            del primitive['tag']

    @staticmethod
    def _from_db_object(context, vif, db_vif):
        for field in vif.fields:
            if not db_vif[field] and field in VIF_OPTIONAL_FIELDS:
                continue
            else:
                setattr(vif, field, db_vif[field])
        # NOTE(danms): The neutronv2 module namespaces mac addresses
        # with port id to avoid uniqueness constraints currently on
        # our table. Strip that out here so nobody else needs to care.
        if 'address' in vif and '/' in vif.address:
            vif.address, _ = vif.address.split('/', 1)
        vif._context = context
        vif.obj_reset_changes()
        return vif

    @base.remotable_classmethod
    def get_by_id(cls, context, vif_id):
        db_vif = main_db_api.virtual_interface_get(context, vif_id)
        if db_vif:
            return cls._from_db_object(context, cls(), db_vif)

    @base.remotable_classmethod
    def get_by_uuid(cls, context, vif_uuid):
        db_vif = main_db_api.virtual_interface_get_by_uuid(context, vif_uuid)
        if db_vif:
            return cls._from_db_object(context, cls(), db_vif)

    @base.remotable_classmethod
    def get_by_address(cls, context, address):
        db_vif = main_db_api.virtual_interface_get_by_address(context, address)
        if db_vif:
            return cls._from_db_object(context, cls(), db_vif)

    @base.remotable_classmethod
    def get_by_instance_and_network(cls, context, instance_uuid, network_id):
        db_vif = main_db_api.virtual_interface_get_by_instance_and_network(
            context, instance_uuid, network_id)
        if db_vif:
            return cls._from_db_object(context, cls(), db_vif)

    @base.remotable
    def create(self):
        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason='already created')
        updates = self.obj_get_changes()
        db_vif = main_db_api.virtual_interface_create(self._context, updates)
        self._from_db_object(self._context, self, db_vif)

    @base.remotable
    def save(self):
        updates = self.obj_get_changes()
        if 'address' in updates:
            raise exception.ObjectActionError(action='save',
                                              reason='address is not mutable')
        db_vif = main_db_api.virtual_interface_update(
            self._context, self.address, updates)
        return self._from_db_object(self._context, self, db_vif)

    @base.remotable_classmethod
    def delete_by_instance_uuid(cls, context, instance_uuid):
        main_db_api.virtual_interface_delete_by_instance(
            context, instance_uuid)

    @base.remotable
    def destroy(self):
        main_db_api.virtual_interface_delete(self._context, self.id)


@base.NovaObjectRegistry.register
class VirtualInterfaceList(base.ObjectListBase, base.NovaObject):
    # Version 1.0: Initial version
    VERSION = '1.0'
    fields = {
        'objects': fields.ListOfObjectsField('VirtualInterface'),
    }

    @base.remotable_classmethod
    def get_all(cls, context):
        db_vifs = main_db_api.virtual_interface_get_all(context)
        return base.obj_make_list(context, cls(context),
                                  objects.VirtualInterface, db_vifs)

    @staticmethod
    @main_db_api.select_db_reader_mode
    def _db_virtual_interface_get_by_instance(context, instance_uuid,
                                              use_slave=False):
        return main_db_api.virtual_interface_get_by_instance(
            context, instance_uuid)

    @base.remotable_classmethod
    def get_by_instance_uuid(cls, context, instance_uuid, use_slave=False):
        db_vifs = cls._db_virtual_interface_get_by_instance(
            context, instance_uuid, use_slave=use_slave)
        return base.obj_make_list(context, cls(context),
                                  objects.VirtualInterface, db_vifs)


@api_db_api.context_manager.writer
def fill_virtual_interface_list(context, max_count):
    """This fills missing VirtualInterface Objects in Nova DB"""
    count_hit = 0
    count_all = 0

    def _regenerate_vif_list_base_on_cache(context,
                                           instance,
                                           old_vif_list,
                                           nw_info):
        # Set old VirtualInterfaces as deleted.
        for vif in old_vif_list:
            vif.destroy()

        # Generate list based on current cache:
        for vif in nw_info:
            vif_obj = objects.VirtualInterface(context)
            vif_obj.uuid = vif['id']
            vif_obj.address = "%s/%s" % (vif['address'], vif['id'])
            vif_obj.instance_uuid = instance['uuid']
            # Find tag from previous VirtualInterface object if exist.
            old_vif = [x for x in old_vif_list if x.uuid == vif['id']]
            vif_obj.tag = old_vif[0].tag if len(old_vif) > 0 else None
            vif_obj.create()

    cells = objects.CellMappingList.get_all(context)
    for cell in cells:
        if count_all == max_count:
            # We reached the limit of checked instances per
            # this function run.
            # Stop, do not go to other cell.
            break

        with nova_context.target_cell(context, cell) as cctxt:
            marker = _get_marker_for_migrate_instances(cctxt)
            filters = {'deleted': False}

            # Adjust the limit of migrated instances.
            # If user wants to process a total of 100 instances
            # and we did a 75 in cell1, then we only need to
            # verify 25 more in cell2, no more.
            adjusted_limit = max_count - count_all

            instances = objects.InstanceList.get_by_filters(
                cctxt,
                filters=filters,
                sort_key='created_at',
                sort_dir='asc',
                marker=marker,
                limit=adjusted_limit)

            for instance in instances:
                # We don't want to fill vif for FAKE instance.
                if instance.uuid == FAKE_UUID:
                    continue

                try:
                    info_cache = objects.InstanceInfoCache.\
                        get_by_instance_uuid(cctxt, instance.get('uuid'))
                    if not info_cache.network_info:
                        LOG.info('InstanceInfoCache object has not set '
                                 'NetworkInfo field. '
                                 'Skipping build of VirtualInterfaceList.')
                        continue
                except exception.InstanceInfoCacheNotFound:
                    LOG.info('Instance has no InstanceInfoCache object. '
                             'Skipping build of VirtualInterfaceList for it.')
                    continue

                # It by design filters out deleted vifs.
                vif_list = VirtualInterfaceList.\
                    get_by_instance_uuid(cctxt, instance.get('uuid'))

                nw_info = info_cache.network_info
                # This should be list with proper order of vifs,
                # but we're not sure about that.
                cached_vif_ids = [vif['id'] for vif in nw_info]
                # This is ordered list of vifs taken from db.
                db_vif_ids = [vif.uuid for vif in vif_list]

                count_all += 1
                if cached_vif_ids == db_vif_ids:
                    # The list of vifs and its order in cache and in
                    # virtual_interfaces is the same. So we could end here.
                    continue
                elif len(db_vif_ids) < len(cached_vif_ids):
                    # Seems to be an instance from release older than
                    # Newton and we don't have full VirtualInterfaceList for
                    # it. Rewrite whole VirtualInterfaceList using interface
                    # order from InstanceInfoCache.
                    count_hit += 1
                    LOG.info('Got an instance %s with less VIFs defined in DB '
                             'than in cache. Could be Pre-Newton instance. '
                             'Building new VirtualInterfaceList for it.',
                             instance.uuid)
                    _regenerate_vif_list_base_on_cache(cctxt,
                                                       instance,
                                                       vif_list,
                                                       nw_info)
                elif len(db_vif_ids) > len(cached_vif_ids):
                    # Seems vif list is inconsistent with cache.
                    # it could be a broken cache or interface
                    # during attach. Do nothing.
                    LOG.info('Got an unexpected number of VIF records in the '
                             'database compared to what was stored in the '
                             'instance_info_caches table for instance %s. '
                             'Perhaps it is an instance during interface '
                             'attach. Do nothing.', instance.uuid)
                    continue
                else:
                    # The order is different between lists.
                    # We need a source of truth, so rebuild order
                    # from cache.
                    count_hit += 1
                    LOG.info('Got an instance %s with different order of '
                             'VIFs between DB and cache. '
                             'We need a source of truth, so rebuild order '
                             'from cache.', instance.uuid)
                    _regenerate_vif_list_base_on_cache(cctxt,
                                                       instance,
                                                       vif_list,
                                                       nw_info)

            # Set marker to point last checked instance.
            if instances:
                marker = instances[-1].uuid
                _set_or_delete_marker_for_migrate_instances(cctxt, marker)

    return count_all, count_hit


# NOTE(mjozefcz): This is similiar to marker mechanism made for
# RequestSpecs object creation.
# Since we have a lot of instances to be check this
# will add a FAKE row that points to last instance
# we checked.
# Please notice that because of virtual_interfaces_instance_uuid_fkey
# we need to have FAKE_UUID instance object, even deleted one.
@main_db_api.pick_context_manager_writer
def _set_or_delete_marker_for_migrate_instances(context, marker=None):
    context.session.query(main_db_models.VirtualInterface).filter_by(
        instance_uuid=FAKE_UUID).delete()

    # Create FAKE_UUID instance objects, only for marker, if doesn't exist.
    # It is needed due constraint: virtual_interfaces_instance_uuid_fkey
    instance = context.session.query(main_db_models.Instance).filter_by(
        uuid=FAKE_UUID).first()
    if not instance:
        instance = objects.Instance(context)
        instance.uuid = FAKE_UUID
        instance.project_id = FAKE_UUID
        instance.user_id = FAKE_UUID
        instance.create()
        # Thats fake instance, lets destroy it.
        # We need only its row to solve constraint issue.
        instance.destroy()

    if marker is not None:
        # ... but there can be a new marker to set
        db_mapping = objects.VirtualInterface(context)
        db_mapping.instance_uuid = FAKE_UUID
        db_mapping.uuid = FAKE_UUID
        db_mapping.tag = marker
        db_mapping.address = 'ff:ff:ff:ff:ff:ff/%s' % FAKE_UUID
        db_mapping.create()


@main_db_api.pick_context_manager_reader
def _get_marker_for_migrate_instances(context):
    vif = (context.session.query(main_db_models.VirtualInterface).filter_by(
           instance_uuid=FAKE_UUID)).first()
    marker = vif['tag'] if vif else None
    return marker