summaryrefslogtreecommitdiff
path: root/ironic/drivers/modules/network/neutron.py
blob: 2693b603e2eff4da342a4d2a15431a5a5e7263cd (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
# Copyright 2015 Rackspace, Inc.
# 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.


from oslo_config import cfg
from oslo_log import log

from ironic.common import exception
from ironic.common.i18n import _
from ironic.common import neutron
from ironic.drivers import base
from ironic.drivers.modules.network import common

LOG = log.getLogger(__name__)

CONF = cfg.CONF


class NeutronNetwork(common.NeutronVIFPortIDMixin,
                     neutron.NeutronNetworkInterfaceMixin,
                     base.NetworkInterface):
    """Neutron v2 network interface"""

    def __init__(self):
        failures = []
        cleaning_net = CONF.neutron.cleaning_network
        if not cleaning_net:
            failures.append('cleaning_network')

        provisioning_net = CONF.neutron.provisioning_network
        if not provisioning_net:
            failures.append('provisioning_network')

        if failures:
            raise exception.DriverLoadError(
                driver=self.__class__.__name__,
                reason=(_('The following [neutron] group configuration '
                          'options are missing: %s') % ', '.join(failures)))

    def validate(self, task):
        """Validates the network interface.

        :param task: a TaskManager instance.
        :raises: InvalidParameterValue, if the network interface configuration
            is invalid.
        :raises: MissingParameterValue, if some parameters are missing.
        """
        self.get_cleaning_network_uuid(task)
        self.get_provisioning_network_uuid(task)

    def _add_network(self, task, network, security_groups, process):
        # If we have left over ports from a previous process, remove them
        neutron.rollback_ports(task, network)
        LOG.info('Adding %s network to node %s', process, task.node.uuid)
        vifs = neutron.add_ports_to_network(task, network,
                                            security_groups=security_groups)
        field = '%s_vif_port_id' % process
        for port in task.ports:
            if port.uuid in vifs:
                internal_info = port.internal_info
                internal_info[field] = vifs[port.uuid]
                port.internal_info = internal_info
                port.save()
        return vifs

    def _remove_network(self, task, network, process):
        LOG.info('Removing ports from %s network for node %s',
                 process, task.node.uuid)
        neutron.remove_ports_from_network(task, network)
        field = '%s_vif_port_id' % process
        for port in task.ports:
            if field in port.internal_info:
                internal_info = port.internal_info
                del internal_info[field]
                port.internal_info = internal_info
                port.save()

    def add_provisioning_network(self, task):
        """Add the provisioning network to a node.

        :param task: A TaskManager instance.
        :raises: NetworkError
        """
        self._add_network(
            task, self.get_provisioning_network_uuid(task),
            CONF.neutron.provisioning_network_security_groups,
            'provisioning')

    def remove_provisioning_network(self, task):
        """Remove the provisioning network from a node.

        :param task: A TaskManager instance.
        :raises: NetworkError
        """
        return self._remove_network(
            task, self.get_provisioning_network_uuid(task), 'provisioning')

    def add_cleaning_network(self, task):
        """Create neutron ports for each port on task.node to boot the ramdisk.

        :param task: a TaskManager instance.
        :raises: NetworkError
        :returns: a dictionary in the form {port.uuid: neutron_port['id']}
        """
        return self._add_network(
            task, self.get_cleaning_network_uuid(task),
            CONF.neutron.cleaning_network_security_groups,
            'cleaning')

    def remove_cleaning_network(self, task):
        """Deletes the neutron port created for booting the ramdisk.

        :param task: a TaskManager instance.
        :raises: NetworkError
        """
        return self._remove_network(
            task, self.get_cleaning_network_uuid(task), 'cleaning')

    def validate_rescue(self, task):
        """Validates the network interface for rescue operation.

        :param task: a TaskManager instance.
        :raises: InvalidParameterValue, if the network interface configuration
            is invalid.
        :raises: MissingParameterValue, if some parameters are missing.
        """
        self.get_rescuing_network_uuid(task)

    def add_rescuing_network(self, task):
        """Create neutron ports for each port to boot the rescue ramdisk.

        :param task: a TaskManager instance.
        :returns: a dictionary in the form {port.uuid: neutron_port['id']}
        """
        return self._add_network(
            task, self.get_rescuing_network_uuid(task),
            CONF.neutron.rescuing_network_security_groups,
            'rescuing')

    def remove_rescuing_network(self, task):
        """Deletes neutron port created for booting the rescue ramdisk.

        :param task: a TaskManager instance.
        :raises: NetworkError
        """
        return self._remove_network(
            task, self.get_rescuing_network_uuid(task), 'rescuing')

    def configure_tenant_networks(self, task):
        """Configure tenant networks for a node.

        :param task: A TaskManager instance.
        :raises: NetworkError
        """
        node = task.node
        ports = task.ports
        LOG.info('Mapping instance ports to %s', node.uuid)

        # TODO(russell_h): this is based on the broken assumption that the
        # number of Neutron ports will match the number of physical ports.
        # Instead, we should probably list ports for this instance in
        # Neutron and update all of those with the appropriate portmap.
        if not ports:
            msg = _("No ports are associated with node %s") % node.uuid
            LOG.error(msg)
            raise exception.NetworkError(msg)
        ports = [p for p in ports if not p.portgroup_id]
        portgroups = task.portgroups

        client = neutron.get_client(context=task.context)
        pobj_without_vif = 0
        for port_like_obj in ports + portgroups:

            try:
                common.plug_port_to_tenant_network(task, port_like_obj,
                                                   client=client)
            except exception.VifNotAttached:
                pobj_without_vif += 1
                continue

        if pobj_without_vif == len(ports + portgroups):
            msg = _("No neutron ports or portgroups are associated with "
                    "node %s") % node.uuid
            LOG.error(msg)
            raise exception.NetworkError(msg)

    def unconfigure_tenant_networks(self, task):
        """Unconfigure tenant networks for a node.

        Nova takes care of port removal from tenant network, we unbind it
        here/now to avoid the possibility of the ironic port being bound to the
        tenant and cleaning networks at the same time.

        :param task: A TaskManager instance.
        :raises: NetworkError
        """
        node = task.node
        LOG.info('Unbinding instance ports from node %s', node.uuid)

        ports = [p for p in task.ports if not p.portgroup_id]
        portgroups = task.portgroups
        for port_like_obj in ports + portgroups:
            vif_port_id = (
                port_like_obj.internal_info.get(common.TENANT_VIF_KEY)
            )
            if not vif_port_id:
                continue

            is_smart_nic = neutron.is_smartnic_port(port_like_obj)
            if is_smart_nic:
                client = neutron.get_client(context=task.context)
                link_info = port_like_obj.local_link_connection
                neutron.wait_for_host_agent(client, link_info['hostname'])

            # NOTE(kaifeng) address is optional for port group, avoid to
            # regenerate mac when the address is absent.
            reset_mac = bool(port_like_obj.address)
            neutron.unbind_neutron_port(vif_port_id, context=task.context,
                                        reset_mac=reset_mac)

    def need_power_on(self, task):
        """Check if the node has any Smart NIC ports

        :param task: A TaskManager instance.
        :return: A boolean to indicate Smart NIC port presence
        """
        for port in task.ports:
            if neutron.is_smartnic_port(port):
                return True
        return False

    def add_inspection_network(self, task):
        """Add the inspection network to the node.

        :param task: A TaskManager instance.
        :returns: a dictionary in the form {port.uuid: neutron_port['id']}
        :raises: NetworkError
        :raises: InvalidParameterValue, if the network interface configuration
            is invalid.
        """
        return self._add_network(
            task, self.get_inspection_network_uuid(task),
            CONF.neutron.inspection_network_security_groups,
            'inspection')

    def remove_inspection_network(self, task):
        """Removes the inspection network from a node.

        :param task: A TaskManager instance.
        :raises: InvalidParameterValue, if the network interface configuration
            is invalid.
        :raises: MissingParameterValue, if some parameters are missing.
        """
        return self._remove_network(
            task, self.get_inspection_network_uuid(task), 'inspection')