File: ovsdb_lib.py

package info (click to toggle)
python-os-vif 4.2.1-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 948 kB
  • sloc: python: 5,503; makefile: 25; sh: 2
file content (270 lines) | stat: -rw-r--r-- 11,350 bytes parent folder | download
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
#    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 uuid

from oslo_log import log as logging

from vif_plug_ovs import constants
from vif_plug_ovs import linux_net
from vif_plug_ovs import ovs
from vif_plug_ovs.ovsdb import api as ovsdb_api


LOG = logging.getLogger(__name__)
QOS_UUID_NAMESPACE = uuid.UUID("68da264a-847f-42a8-8ab0-5e774aee3d95")


class BaseOVS(object):

    def __init__(self, config):
        self.timeout = config.ovs_vsctl_timeout
        self.connection = config.ovsdb_connection
        self.interface = config.ovsdb_interface
        self._ovsdb = None

    # NOTE(sean-k-mooney): when using the native ovsdb bindings
    # creating an instance of the ovsdb api connects to the ovsdb
    # to initialize the library based on the schema version
    # of the ovsdb. To avoid that we lazy load the ovsdb
    # instance the first time we need it via a property.
    @property
    def ovsdb(self):
        if not self._ovsdb:
            self._ovsdb = ovsdb_api.get_instance(self)
        return self._ovsdb

    def _ovs_supports_mtu_requests(self):
        return self.ovsdb.has_table_column('Interface', 'mtu_request')

    def _set_mtu_request(self, txn, dev, mtu):
        txn.add(
            self.ovsdb.db_set(
                'Interface', dev, ('mtu_request', mtu)
            )
        )

    def update_device_mtu(self, txn, dev, mtu, interface_type=None):
        if not mtu:
            return
        if interface_type not in [
            constants.OVS_VHOSTUSER_INTERFACE_TYPE,
            constants.OVS_VHOSTUSER_CLIENT_INTERFACE_TYPE]:
            linux_net.set_device_mtu(dev, mtu)
        elif self._ovs_supports_mtu_requests():
            self._set_mtu_request(txn, dev, mtu)
        else:
            LOG.debug("MTU not set on %(interface_name)s interface "
                      "of type %(interface_type)s.",
                      {'interface_name': dev,
                       'interface_type': interface_type})

    def ensure_ovs_bridge(self, bridge, datapath_type):
        return self.ovsdb.add_br(bridge, may_exist=True,
                                 datapath_type=datapath_type).execute()

    def delete_ovs_bridge(self, bridge):
        """Delete ovs bridge by name

        :param bridge: bridge name as a string

        .. note:: Do Not call with br-int !!!
        """
        # TODO(sean-k-mooney): when we fix bug: #1914886
        # add a guard against deleting the integration bridge
        # after adding a config option to store its name.
        return self.ovsdb.del_br(bridge).execute()

    def create_patch_port_pair(
        self, port_bridge, port_bridge_port, int_bridge, int_bridge_port,
        iface_id, mac, instance_id, tag=None
    ):
        """Create a patch port pair between any two bridges.

        :param port_bridge: the source bridge name for the patch port pair.
        :param port_bridge_port: the name of the patch port on the
        source bridge.
        :param int_bridge: the target bridge name, typically br-int.
        :param int_bridge_port: the name of the patch port on the
        target bridge.
        :param iface_id: neutron port ID.
        :param mac: port MAC.
        :param instance_id: instance uuid.
        :param mtu: port MTU.
        :param tag: OVS interface tag used for vlan isolation.
        """

        # NOTE(sean-k-mooney): we use a transaction here for 2 reasons:
        # 1.) if using the vsctl client its faster
        # 2.) in all cases we either want to fully create the patch port
        # pair or not create it atomically. By using a transaction we know
        # that we will never be in a mixed state where it was partly created.
        with self.ovsdb.transaction() as txn:
            # create integration bridge patch peer
            external_ids = {
                'iface-id': iface_id, 'iface-status': 'active',
                'attached-mac': mac, 'vm-uuid': instance_id
            }
            col_values = [
                ('external_ids', external_ids),
                ('type', 'patch'),
                ('options', {'peer': port_bridge_port})
            ]

            txn.add(self.ovsdb.add_port(int_bridge, int_bridge_port))
            if tag:
                txn.add(
                    self.ovsdb.db_set('Port', int_bridge_port, ('tag', tag)))
            txn.add(
                self.ovsdb.db_set('Interface', int_bridge_port, *col_values))

            # create port bridge patch peer
            col_values = [
                ('type', 'patch'),
                ('options', {'peer': int_bridge_port})
            ]
            txn.add(self.ovsdb.add_port(port_bridge, port_bridge_port))
            txn.add(
                self.ovsdb.db_set('Interface', port_bridge_port, *col_values))

    def create_ovs_vif_port(
        self, bridge, dev, iface_id, mac, instance_id,
        mtu=None, interface_type=None, vhost_server_path=None,
        tag=None, pf_pci=None, vf_num=None, set_ids=True, datapath_type=None,
        qos_type=None, vlan_mode=None, trunks=None
    ):
        """Create OVS port

        :param bridge: bridge name to create the port on.
        :param dev: port name.
        :param iface_id: port ID.
        :param mac: port MAC.
        :param instance_id: VM ID on which the port is attached to.
        :param mtu: port MTU.
        :param interface_type: OVS interface type.
        :param vhost_server_path: path to socket file of vhost server.
        :param tag: OVS interface tag.
        :param pf_pci: PCI address of PF for dpdk representor port.
        :param vf_num: VF number of PF for dpdk representor port.
        :param set_ids: set external ids on port (bool).
        :param datapath_type: datapath type for port's bridge
        :param qos_type: qos type for a port

        .. note:: create DPDK representor port by setting all three values:
            `interface_type`, `pf_pci` and `vf_num`. if interface type is
            not `OVS_DPDK_INTERFACE_TYPE` then `pf_pci` and `vf_num` values
            are ignored.
        """
        external_ids = {'iface-id': iface_id,
                        'iface-status': 'active',
                        'attached-mac': mac,
                        'vm-uuid': instance_id}

        # Note(lajoskatona): Neutron fills external_ids for trunk, see:
        # https://opendev.org/openstack/neutron/src/commit/
        # 1bc4b526e9c743423069ab4cf6ef3883d5e48217/neutron/services/trunk/
        # drivers/openvswitch/agent/ovsdb_handler.py#L418
        # The following keys are added there: bridge_name, trunk_id and
        # subport_ids. These values are used during the cleanup after the
        # deletion of the trunk. It can happen that Neutron can't fill these
        # fields.
        # In os-vif when the plug happens we can use the same transaction to
        # add bridge_name to external_ids in case of it is a trunk.
        # By this Neutron can do the cleanup of trunk related interfaces.
        if ovs.is_trunk_bridge(bridge):
            external_ids['bridge_name'] = bridge

        col_values = [('external_ids', external_ids)] if set_ids else []
        if interface_type:
            col_values.append(('type', interface_type))
        if vhost_server_path:
            col_values.append(('options',
                               {'vhost-server-path': vhost_server_path}))
        if (interface_type == constants.OVS_DPDK_INTERFACE_TYPE and
                pf_pci and vf_num):
            devargs_string = "{PF_PCI},representor=[{VF_NUM}]".format(
                PF_PCI=pf_pci, VF_NUM=vf_num)
            col_values.append(('options',
                              {'dpdk-devargs': devargs_string}))
        # create qos record if qos type is specified
        # and get the qos id. This is done outside of the transaction
        # because we need the qos id to set the qos on the port.
        # The qos uuid cannot be set when creating the record so we
        # have to look it up after the record is created. this means
        # we need to create the qos record outside of the transaction
        # that creates the port.
        qid = None
        if qos_type:
            self.delete_qos_if_exists(dev, qos_type)
            qos_id = uuid.uuid5(QOS_UUID_NAMESPACE, dev)
            qos_external_ids = {'id': str(qos_id), '_type': qos_type}
            self.ovsdb.db_create(
                'QoS', type=qos_type, external_ids=qos_external_ids
                ).execute(check_error=True)
            record = self.get_qos(dev, qos_type)
            qid = record[0]['_uuid']

        with self.ovsdb.transaction() as txn:
            if datapath_type:
                txn.add(self.ovsdb.add_br(bridge, may_exist=True,
                                          datapath_type=datapath_type))
            txn.add(self.ovsdb.add_port(bridge, dev))
            if tag:
                txn.add(self.ovsdb.db_set('Port', dev, ('tag', tag)))
            if vlan_mode:
                txn.add(self.ovsdb.db_set('Port', dev,
                                          ('vlan_mode', vlan_mode)))
            if trunks:
                txn.add(self.ovsdb.db_set('Port', dev, ('trunks', trunks)))
            if qid:
                txn.add(self.ovsdb.db_set('Port', dev, ('qos', qid)))
            if col_values:
                txn.add(self.ovsdb.db_set('Interface', dev, *col_values))
            self.update_device_mtu(
                txn, dev, mtu, interface_type=interface_type
            )

    def port_exists(self, port_name, bridge):
        ports = self.ovsdb.list_ports(bridge).execute()
        return ports is not None and port_name in ports

    def get_qos(self, dev, qos_type):
        qos_id = uuid.uuid5(QOS_UUID_NAMESPACE, dev)
        external_ids = {'id': str(qos_id), '_type': qos_type}
        return self.ovsdb.db_find(
            'QoS', ('external_ids', '=', external_ids),
            colmuns=['_uuid']
        ).execute()

    def delete_qos_if_exists(self, dev, qos_type):
        qos_ids = self.get_qos(dev, qos_type)
        if qos_ids is not None and len(qos_ids) > 0:
            for qos_id in qos_ids:
                if '_uuid' in qos_id:
                    self.ovsdb.db_destroy(
                        'QoS', str(qos_id['_uuid'])
                    ).execute()

    def update_ovs_vif_port(self, dev, mtu=None, interface_type=None):
        with self.ovsdb.transaction() as txn:
            self.update_device_mtu(
                txn, dev, mtu, interface_type=interface_type
            )

    def delete_ovs_vif_port(
            self, bridge, dev, delete_netdev=True, qos_type=None
    ):
        self.ovsdb.del_port(dev, bridge=bridge, if_exists=True).execute()
        if qos_type:
            self.delete_qos_if_exists(dev, qos_type)
        if delete_netdev:
            linux_net.delete_net_dev(dev)