summaryrefslogtreecommitdiff
path: root/manila/share/drivers/netapp/dataontap/cluster_mode/lib_multi_svm.py
blob: 7dafd814af96d641040249c6ffb8c117d83df0d5 (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
# Copyright (c) 2015 Clinton Knight.  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.
"""
NetApp Data ONTAP cDOT multi-SVM storage driver library.

This library extends the abstract base library and completes the multi-SVM
functionality needed by the cDOT multi-SVM Manila driver.  This library
variant creates Data ONTAP storage virtual machines (i.e. 'vservers')
as needed to provision shares.
"""

import re

from oslo_log import log
from oslo_utils import excutils

from manila import exception
from manila.i18n import _
from manila.share.drivers.netapp.dataontap.client import client_cmode
from manila.share.drivers.netapp.dataontap.cluster_mode import lib_base
from manila.share.drivers.netapp import utils as na_utils
from manila import utils


LOG = log.getLogger(__name__)
SUPPORTED_NETWORK_TYPES = (None, 'flat', 'vlan')
SEGMENTED_NETWORK_TYPES = ('vlan',)
DEFAULT_MTU = 1500


class NetAppCmodeMultiSVMFileStorageLibrary(
        lib_base.NetAppCmodeFileStorageLibrary):

    @na_utils.trace
    def check_for_setup_error(self):

        if self._have_cluster_creds:
            if self.configuration.netapp_vserver:
                msg = ('Vserver is specified in the configuration. This is '
                       'ignored when the driver is managing share servers.')
                LOG.warning(msg)

        else:  # only have vserver creds, which is an error in multi_svm mode
            msg = _('Cluster credentials must be specified in the '
                    'configuration when the driver is managing share servers.')
            raise exception.InvalidInput(reason=msg)

        # Ensure one or more aggregates are available.
        if not self._find_matching_aggregates():
            msg = _('No aggregates are available for provisioning shares. '
                    'Ensure that the configuration option '
                    'netapp_aggregate_name_search_pattern is set correctly.')
            raise exception.NetAppException(msg)

        super(NetAppCmodeMultiSVMFileStorageLibrary, self).\
            check_for_setup_error()

    @na_utils.trace
    def _get_vserver(self, share_server=None):

        if not share_server:
            msg = _('Share server not provided')
            raise exception.InvalidInput(reason=msg)

        backend_details = share_server.get('backend_details')
        vserver = backend_details.get(
            'vserver_name') if backend_details else None

        if not vserver:
            msg = _('Vserver name is absent in backend details. Please '
                    'check whether Vserver was created properly.')
            raise exception.VserverNotSpecified(msg)

        if not self._client.vserver_exists(vserver):
            raise exception.VserverNotFound(vserver=vserver)

        vserver_client = self._get_api_client(vserver)
        return vserver, vserver_client

    def _get_ems_pool_info(self):
        return {
            'pools': {
                'vserver': None,
                'aggregates': self._find_matching_aggregates(),
            },
        }

    @na_utils.trace
    def _handle_housekeeping_tasks(self):
        """Handle various cleanup activities."""
        self._client.prune_deleted_nfs_export_policies()
        self._client.prune_deleted_snapshots()

        super(NetAppCmodeMultiSVMFileStorageLibrary, self).\
            _handle_housekeeping_tasks()

    @na_utils.trace
    def _find_matching_aggregates(self):
        """Find all aggregates match pattern."""
        aggregate_names = self._client.list_non_root_aggregates()
        pattern = self.configuration.netapp_aggregate_name_search_pattern
        return [aggr_name for aggr_name in aggregate_names
                if re.match(pattern, aggr_name)]

    @na_utils.trace
    def setup_server(self, network_info, metadata=None):
        """Creates and configures new Vserver."""

        vlan = network_info['segmentation_id']

        @utils.synchronized('netapp-VLAN-%s' % vlan, external=True)
        def setup_server_with_lock():
            LOG.debug('Creating server %s', network_info['server_id'])
            self._validate_network_type(network_info)

            vserver_name = self._get_vserver_name(network_info['server_id'])
            server_details = {'vserver_name': vserver_name}

            try:
                self._create_vserver(vserver_name, network_info)
            except Exception as e:
                e.detail_data = {'server_details': server_details}
                raise

            return server_details

        return setup_server_with_lock()

    @na_utils.trace
    def _validate_network_type(self, network_info):
        """Raises exception if the segmentation type is incorrect."""
        if network_info['network_type'] not in SUPPORTED_NETWORK_TYPES:
            msg = _('The specified network type %s is unsupported by the '
                    'NetApp clustered Data ONTAP driver')
            raise exception.NetworkBadConfigurationException(
                reason=msg % network_info['network_type'])

    @na_utils.trace
    def _get_vserver_name(self, server_id):
        return self.configuration.netapp_vserver_name_template % server_id

    @na_utils.trace
    def _create_vserver(self, vserver_name, network_info):
        """Creates Vserver with given parameters if it doesn't exist."""

        if self._client.vserver_exists(vserver_name):
            msg = _('Vserver %s already exists.')
            raise exception.NetAppException(msg % vserver_name)

        ipspace_name = self._create_ipspace(network_info)

        LOG.debug('Vserver %s does not exist, creating.', vserver_name)
        self._client.create_vserver(
            vserver_name,
            self.configuration.netapp_root_volume_aggregate,
            self.configuration.netapp_root_volume,
            self._find_matching_aggregates(),
            ipspace_name)

        vserver_client = self._get_api_client(vserver=vserver_name)
        security_services = None
        try:
            self._create_vserver_lifs(vserver_name,
                                      vserver_client,
                                      network_info,
                                      ipspace_name)

            self._create_vserver_admin_lif(vserver_name,
                                           vserver_client,
                                           network_info,
                                           ipspace_name)

            vserver_client.enable_nfs(
                self.configuration.netapp_enabled_share_protocols)

            security_services = network_info.get('security_services')
            if security_services:
                self._client.setup_security_services(security_services,
                                                     vserver_client,
                                                     vserver_name)
        except Exception:
            with excutils.save_and_reraise_exception():
                LOG.error("Failed to configure Vserver.")
                self._delete_vserver(vserver_name,
                                     security_services=security_services)

    def _get_valid_ipspace_name(self, network_id):
        """Get IPspace name according to network id."""
        return 'ipspace_' + network_id.replace('-', '_')

    @na_utils.trace
    def _create_ipspace(self, network_info):
        """If supported, create an IPspace for a new Vserver."""

        if not self._client.features.IPSPACES:
            return None

        if (network_info['network_allocations'][0]['network_type']
                not in SEGMENTED_NETWORK_TYPES):
            return client_cmode.DEFAULT_IPSPACE

        # NOTE(cknight): Neutron needs cDOT IP spaces because it can provide
        # overlapping IP address ranges for different subnets.  That is not
        # believed to be an issue for any of Manila's other network plugins.
        ipspace_id = network_info.get('neutron_subnet_id')
        if not ipspace_id:
            return client_cmode.DEFAULT_IPSPACE

        ipspace_name = self._get_valid_ipspace_name(ipspace_id)
        if not self._client.ipspace_exists(ipspace_name):
            self._client.create_ipspace(ipspace_name)

        return ipspace_name

    @na_utils.trace
    def _create_vserver_lifs(self, vserver_name, vserver_client, network_info,
                             ipspace_name):
        """Create Vserver data logical interfaces (LIFs)."""

        nodes = self._client.list_cluster_nodes()
        node_network_info = zip(nodes, network_info['network_allocations'])

        for node_name, network_allocation in node_network_info:
            lif_name = self._get_lif_name(node_name, network_allocation)
            self._create_lif(vserver_client, vserver_name, ipspace_name,
                             node_name, lif_name, network_allocation)

    @na_utils.trace
    def _create_vserver_admin_lif(self, vserver_name, vserver_client,
                                  network_info, ipspace_name):
        """Create Vserver admin LIF, if defined."""

        network_allocations = network_info.get('admin_network_allocations')
        if not network_allocations:
            LOG.info('No admin network defined for Vserver %s.' %
                     vserver_name)
            return

        node_name = self._client.list_cluster_nodes()[0]
        network_allocation = network_allocations[0]
        lif_name = self._get_lif_name(node_name, network_allocation)

        self._create_lif(vserver_client, vserver_name, ipspace_name,
                         node_name, lif_name, network_allocation)

    @na_utils.trace
    def _get_node_data_port(self, node):
        port_names = self._client.list_node_data_ports(node)
        pattern = self.configuration.netapp_port_name_search_pattern
        matched_port_names = [port_name for port_name in port_names
                              if re.match(pattern, port_name)]
        if not matched_port_names:
            raise exception.NetAppException(
                _('Could not find eligible network ports on node %s on which '
                  'to create Vserver LIFs.') % node)
        return matched_port_names[0]

    def _get_lif_name(self, node_name, network_allocation):
        """Get LIF name based on template from manila.conf file."""
        lif_name_args = {
            'node': node_name,
            'net_allocation_id': network_allocation['id'],
        }
        return self.configuration.netapp_lif_name_template % lif_name_args

    @na_utils.trace
    def _create_lif(self, vserver_client, vserver_name, ipspace_name,
                    node_name, lif_name, network_allocation):
        """Creates LIF for Vserver."""

        port = self._get_node_data_port(node_name)
        ip_address = network_allocation['ip_address']
        netmask = utils.cidr_to_netmask(network_allocation['cidr'])
        vlan = network_allocation['segmentation_id']
        network_mtu = network_allocation.get('mtu')
        mtu = network_mtu or DEFAULT_MTU

        if not vserver_client.network_interface_exists(
                vserver_name, node_name, port, ip_address, netmask, vlan):

            self._client.create_network_interface(
                ip_address, netmask, vlan, node_name, port, vserver_name,
                lif_name, ipspace_name, mtu)

    @na_utils.trace
    def get_network_allocations_number(self):
        """Get number of network interfaces to be created."""
        return len(self._client.list_cluster_nodes())

    @na_utils.trace
    def get_admin_network_allocations_number(self, admin_network_api):
        """Get number of network allocations for creating admin LIFs."""
        return 1 if admin_network_api else 0

    @na_utils.trace
    def teardown_server(self, server_details, security_services=None):
        """Teardown share server."""
        vserver = server_details.get(
            'vserver_name') if server_details else None

        if not vserver:
            LOG.warning("Vserver not specified for share server being "
                        "deleted. Deletion of share server record will "
                        "proceed anyway.")
            return

        elif not self._client.vserver_exists(vserver):
            LOG.warning("Could not find Vserver for share server being "
                        "deleted: %s. Deletion of share server "
                        "record will proceed anyway.", vserver)
            return

        self._delete_vserver(vserver, security_services=security_services)

    @na_utils.trace
    def _delete_vserver(self, vserver, security_services=None):
        """Delete a Vserver plus IPspace and security services as needed."""

        ipspace_name = self._client.get_vserver_ipspace(vserver)

        vserver_client = self._get_api_client(vserver=vserver)
        network_interfaces = vserver_client.get_network_interfaces()

        vlan = None
        if network_interfaces:
            home_port = network_interfaces[0]['home-port']
            vlan = home_port.split('-')[1]

        @utils.synchronized('netapp-VLAN-%s' % vlan, external=True)
        def _delete_vserver_with_lock():
            self._client.delete_vserver(vserver,
                                        vserver_client,
                                        security_services=security_services)

            if ipspace_name and not self._client.ipspace_has_data_vservers(
                    ipspace_name):
                self._client.delete_ipspace(ipspace_name)

            self._delete_vserver_vlan(network_interfaces)

        return _delete_vserver_with_lock()

    @na_utils.trace
    def _delete_vserver_vlan(self, vserver_network_interfaces):
        """Delete Vserver's VLAN configuration from ports"""

        for interface in vserver_network_interfaces:
            try:
                home_port = interface['home-port']
                port, vlan = home_port.split('-')
                node = interface['home-node']
                self._client.delete_vlan(node, port, vlan)
            except exception.NetAppException:
                LOG.exception("Deleting Vserver VLAN failed.")