summaryrefslogtreecommitdiff
path: root/nova/virt/powervm/vif.py
blob: 8ab591a15d769ea36115862da768ed059c23e377 (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
# Copyright 2016, 2017 IBM Corp.
#
# All Rights Reserved.
#
#    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 abc

from oslo_log import log
from oslo_serialization import jsonutils
from oslo_utils import excutils
from oslo_utils import importutils
from pypowervm import exceptions as pvm_ex
from pypowervm.tasks import cna as pvm_cna
from pypowervm.tasks import partition as pvm_par
from pypowervm.wrappers import event as pvm_evt

from nova import exception
from nova.network import model as network_model
from nova.virt.powervm import vm

LOG = log.getLogger(__name__)

NOVALINK_VSWITCH = 'NovaLinkVEABridge'

# Provider tag for custom events from this module
EVENT_PROVIDER_ID = 'NOVA_PVM_VIF'

VIF_TYPE_PVM_SEA = 'pvm_sea'
VIF_TYPE_PVM_OVS = 'ovs'
VIF_MAPPING = {VIF_TYPE_PVM_SEA:
               'nova.virt.powervm.vif.PvmSeaVifDriver',
               VIF_TYPE_PVM_OVS:
               'nova.virt.powervm.vif.PvmOvsVifDriver'}


def _build_vif_driver(adapter, instance, vif):
    """Returns the appropriate VIF Driver for the given VIF.

    :param adapter: The pypowervm adapter API interface.
    :param instance: The nova instance.
    :param vif: The virtual interface.
    :return: The appropriate PvmVifDriver for the VIF.
    """
    if vif.get('type') is None:
        LOG.exception("Failed to build vif driver. Missing vif type.",
                      instance=instance)
        raise exception.VirtualInterfacePlugException()

    # Check the type to the implementations
    if VIF_MAPPING.get(vif['type']):
        return importutils.import_object(
            VIF_MAPPING.get(vif['type']), adapter, instance)

    # No matching implementation, raise error.
    LOG.exception("Failed to build vif driver. Invalid vif type provided.",
                  instance=instance)
    raise exception.VirtualInterfacePlugException()


def _push_vif_event(adapter, action, vif_w, instance, vif_type):
    """Push a custom event to the REST server for a vif action (plug/unplug).

    This event prompts the neutron agent to mark the port up or down. It is
    consumed by custom neutron agents (e.g. Shared Ethernet Adapter)

    :param adapter: The pypowervm adapter.
    :param action: The action taken on the vif - either 'plug' or 'unplug'
    :param vif_w: The pypowervm wrapper of the affected vif (CNA, VNIC, etc.)
    :param instance: The nova instance for the event
    :param vif_type: The type of event source (pvm_sea, ovs, bridge,
                     pvm_sriov etc)
    """
    data = vif_w.href
    detail = jsonutils.dumps(dict(provider=EVENT_PROVIDER_ID, action=action,
                                  mac=vif_w.mac, type=vif_type))
    event = pvm_evt.Event.bld(adapter, data, detail)
    try:
        event = event.create()
        LOG.debug('Pushed custom event for consumption by neutron agent: %s',
                  str(event), instance=instance)
    except Exception:
        with excutils.save_and_reraise_exception(logger=LOG):
            LOG.exception('Custom VIF event push failed.  %s', str(event),
            instance=instance)


def plug(adapter, instance, vif, new_vif=True):
    """Plugs a virtual interface (network) into a VM.

    :param adapter: The pypowervm adapter.
    :param instance: The nova instance object.
    :param vif: The virtual interface to plug into the instance.
    :param new_vif: (Optional, Default: True) If set, indicates that it is
                    a brand new VIF.  If False, it indicates that the VIF
                    is already on the client but should be treated on the
                    bridge.
    :return: The wrapper (CNA) representing the plugged virtual network. None
             if the vnet was not created.
    """
    vif_drv = _build_vif_driver(adapter, instance, vif)

    try:
        vnet_w = vif_drv.plug(vif, new_vif=new_vif)
    except pvm_ex.HttpError:
        LOG.exception('VIF plug failed for instance.', instance=instance)
        raise exception.VirtualInterfacePlugException()
    # Other exceptions are (hopefully) custom VirtualInterfacePlugException
    # generated lower in the call stack.

    # Push a custom event if we really plugged the vif
    if vnet_w is not None:
        _push_vif_event(adapter, 'plug', vnet_w, instance, vif['type'])

    return vnet_w


def unplug(adapter, instance, vif, cna_w_list=None):
    """Unplugs a virtual interface (network) from a VM.

    :param adapter: The pypowervm adapter.
    :param instance: The nova instance object.
    :param vif: The virtual interface to plug into the instance.
    :param cna_w_list: (Optional, Default: None) The list of Client Network
                       Adapters from pypowervm.  Providing this input
                       allows for an improvement in operation speed.
    """
    vif_drv = _build_vif_driver(adapter, instance, vif)
    try:
        vnet_w = vif_drv.unplug(vif, cna_w_list=cna_w_list)
    except pvm_ex.HttpError as he:
        LOG.exception('VIF unplug failed for instance', instance=instance)
        raise exception.VirtualInterfaceUnplugException(reason=he.args[0])

    # Push a custom event if we successfully unplugged the vif.
    if vnet_w:
        _push_vif_event(adapter, 'unplug', vnet_w, instance, vif['type'])


class PvmVifDriver(metaclass=abc.ABCMeta):
    """Represents an abstract class for a PowerVM Vif Driver.

    A VIF Driver understands a given virtual interface type (network).  It
    understands how to plug and unplug a given VIF for a virtual machine.
    """

    def __init__(self, adapter, instance):
        """Initializes a VIF Driver.
        :param adapter: The pypowervm adapter API interface.
        :param instance: The nova instance that the vif action will be run
                         against.
        """
        self.adapter = adapter
        self.instance = instance

    @abc.abstractmethod
    def plug(self, vif, new_vif=True):
        """Plugs a virtual interface (network) into a VM.

        :param vif: The virtual interface to plug into the instance.
        :param new_vif: (Optional, Default: True) If set, indicates that it is
                        a brand new VIF.  If False, it indicates that the VIF
                        is already on the client but should be treated on the
                        bridge.
        :return: The new vif that was created.  Only returned if new_vif is
                 set to True.  Otherwise None is expected.
        """
        pass

    def unplug(self, vif, cna_w_list=None):
        """Unplugs a virtual interface (network) from a VM.

        :param vif: The virtual interface to plug into the instance.
        :param cna_w_list: (Optional, Default: None) The list of Client Network
                           Adapters from pypowervm.  Providing this input
                           allows for an improvement in operation speed.
        :return cna_w: The deleted Client Network Adapter or None if the CNA
                       is not found.
        """
        # This is a default implementation that most implementations will
        # require.

        # Need to find the adapters if they were not provided
        if not cna_w_list:
            cna_w_list = vm.get_cnas(self.adapter, self.instance)

        cna_w = self._find_cna_for_vif(cna_w_list, vif)
        if not cna_w:
            LOG.warning('Unable to unplug VIF with mac %(mac)s.  The VIF was '
                        'not found on the instance.',
                        {'mac': vif['address']}, instance=self.instance)
            return None

        LOG.info('Deleting VIF with mac %(mac)s.',
                 {'mac': vif['address']}, instance=self.instance)
        try:
            cna_w.delete()
        except Exception as e:
            LOG.exception('Unable to unplug VIF with mac %(mac)s.',
                          {'mac': vif['address']}, instance=self.instance)
            raise exception.VirtualInterfaceUnplugException(
                reason=str(e))
        return cna_w

    @staticmethod
    def _find_cna_for_vif(cna_w_list, vif):
        """Finds the PowerVM CNA for a given Nova VIF.

        :param cna_w_list: The list of Client Network Adapter wrappers from
                           pypowervm.
        :param vif: The Nova Virtual Interface (virtual network interface).
        :return: The CNA that corresponds to the VIF.  None if one is not
                 part of the cna_w_list.
        """
        for cna_w in cna_w_list:
            if vm.norm_mac(cna_w.mac) == vif['address']:
                return cna_w
        return None


class PvmOvsVifDriver(PvmVifDriver):
    """The Open vSwitch VIF driver for PowerVM."""

    def plug(self, vif, new_vif=True):
        """Plugs a virtual interface (network) into a VM.

        Creates a 'peer to peer' connection between the Management partition
        hosting the Linux I/O and the client VM.  There will be one trunk
        adapter for a given client adapter.

        The device will be 'up' on the mgmt partition.

        Will make sure that the trunk device has the appropriate metadata (e.g.
        port id) set on it so that the Open vSwitch agent picks it up properly.

        :param vif: The virtual interface to plug into the instance.
        :param new_vif: (Optional, Default: True) If set, indicates that it is
                        a brand new VIF.  If False, it indicates that the VIF
                        is already on the client but should be treated on the
                        bridge.
        :return: The new vif that was created.  Only returned if new_vif is
                 set to True.  Otherwise None is expected.
        """

        # Create the trunk and client adapter.
        lpar_uuid = vm.get_pvm_uuid(self.instance)
        mgmt_uuid = pvm_par.get_this_partition(self.adapter).uuid

        mtu = vif['network'].get_meta('mtu')
        if 'devname' in vif:
            dev_name = vif['devname']
        else:
            dev_name = ("nic" + vif['id'])[:network_model.NIC_NAME_LEN]

        meta_attrs = ','.join([
                     'iface-id=%s' % (vif.get('ovs_interfaceid') or vif['id']),
                     'iface-status=active',
                     'attached-mac=%s' % vif['address'],
                     'vm-uuid=%s' % self.instance.uuid])

        if new_vif:
            return pvm_cna.crt_p2p_cna(
                self.adapter, None, lpar_uuid, [mgmt_uuid], NOVALINK_VSWITCH,
                crt_vswitch=True, mac_addr=vif['address'], dev_name=dev_name,
                ovs_bridge=vif['network']['bridge'],
                ovs_ext_ids=meta_attrs, configured_mtu=mtu)[0]
        else:
            # Bug : https://bugs.launchpad.net/nova-powervm/+bug/1731548
            # When a host is rebooted, something is discarding tap devices for
            # VMs deployed with OVS vif. To prevent VMs losing network
            # connectivity, this is fixed by recreating the tap devices during
            # init of the nova compute service, which will call vif plug with
            # new_vif==False.

            # Find the CNA for this vif.
            # TODO(esberglu) improve performance by caching VIOS wrapper(s) and
            # CNA lists (in case >1 vif per VM).
            cna_w_list = vm.get_cnas(self.adapter, self.instance)
            cna_w = self._find_cna_for_vif(cna_w_list, vif)
            if not cna_w:
                LOG.warning('Unable to plug VIF with mac %s for instance. The '
                            'VIF was not found on the instance.',
                            vif['address'], instance=self.instance)
                return None

            # Find the corresponding trunk adapter
            trunks = pvm_cna.find_trunks(self.adapter, cna_w)
            for trunk in trunks:
                # Set MTU, OVS external ids, and OVS bridge metadata
                trunk.configured_mtu = mtu
                trunk.ovs_ext_ids = meta_attrs
                trunk.ovs_bridge = vif['network']['bridge']
                # Updating the trunk adapter will cause NovaLink to reassociate
                # the tap device.
                trunk.update()

    def unplug(self, vif, cna_w_list=None):
        """Unplugs a virtual interface (network) from a VM.

        Extends the base implementation, but before calling it will remove
        the adapter from the Open vSwitch and delete the trunk.

        :param vif: The virtual interface to plug into the instance.
        :param cna_w_list: (Optional, Default: None) The list of Client Network
                           Adapters from pypowervm.  Providing this input
                           allows for an improvement in operation speed.
        :return cna_w: The deleted Client Network Adapter or None if the CNA
                       is not found.
        """
        # Need to find the adapters if they were not provided
        if not cna_w_list:
            cna_w_list = vm.get_cnas(self.adapter, self.instance)

        # Find the CNA for this vif.
        cna_w = self._find_cna_for_vif(cna_w_list, vif)

        if not cna_w:
            LOG.warning('Unable to unplug VIF with mac %s for instance. The '
                        'VIF was not found on the instance.', vif['address'],
                        instance=self.instance)
            return None

        # Find and delete the trunk adapters
        trunks = pvm_cna.find_trunks(self.adapter, cna_w)
        for trunk in trunks:
            trunk.delete()

        # Delete the client CNA
        return super(PvmOvsVifDriver, self).unplug(vif, cna_w_list=cna_w_list)


class PvmSeaVifDriver(PvmVifDriver):
    """The PowerVM Shared Ethernet Adapter VIF Driver."""

    def plug(self, vif, new_vif=True):
        """Plugs a virtual interface (network) into a VM.

        This method simply creates the client network adapter into the VM.

        :param vif: The virtual interface to plug into the instance.
        :param new_vif: (Optional, Default: True) If set, indicates that it is
                        a brand new VIF.  If False, it indicates that the VIF
                        is already on the client but should be treated on the
                        bridge.
        :return: The new vif that was created.  Only returned if new_vif is
                 set to True.  Otherwise None is expected.
        """
        # Do nothing if not a new VIF
        if not new_vif:
            return None

        lpar_uuid = vm.get_pvm_uuid(self.instance)

        # CNA's require a VLAN. The networking-powervm neutron agent puts this
        # in the vif details.
        vlan = int(vif['details']['vlan'])

        LOG.debug("Creating SEA-based VIF with VLAN %s", str(vlan),
                  instance=self.instance)
        cna_w = pvm_cna.crt_cna(self.adapter, None, lpar_uuid, vlan,
                                mac_addr=vif['address'])

        return cna_w