1# Copyright (c) 2017 DataCore Software Corp. All Rights Reserved.
2#
3#    Licensed under the Apache License, Version 2.0 (the "License"); you may
4#    not use this file except in compliance with the License. You may obtain
5#    a copy of the License at
6#
7#         http://www.apache.org/licenses/LICENSE-2.0
8#
9#    Unless required by applicable law or agreed to in writing, software
10#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12#    License for the specific language governing permissions and limitations
13#    under the License.
14
15"""Classes to invoke DataCore SANsymphony API."""
16
17import copy
18import sys
19import uuid
20
21from oslo_log import log as logging
22from oslo_utils import excutils
23from oslo_utils import importutils
24import retrying
25import six
26import socket
27import suds
28from suds import client as suds_client
29from suds import plugin
30from suds.sax import attribute
31from suds.sax import element
32from suds import wsdl
33from suds import wsse
34from suds import xsd
35
36from cinder.i18n import _
37from cinder import utils as cinder_utils
38from cinder.volume.drivers.datacore import exception as datacore_exceptions
39from cinder.volume.drivers.datacore import utils as datacore_utils
40
41websocket = importutils.try_import('websocket')
42
43
44LOG = logging.getLogger(__name__)
45
46
47class FaultDefinitionsFilter(plugin.DocumentPlugin):
48    """Plugin to process the DataCore API WSDL document.
49
50    The document plugin removes fault definitions for callback operations
51    from the DataCore API WSDL.
52    """
53
54    def parsed(self, context):
55        document = context.document
56        tns = self._get_tns(document)
57
58        message_qrefs = set()
59        for message in self._get_wsdl_messages(document):
60            message_qrefs.add((message.get('name'), tns[1]))
61
62        bindings = self._get_wsdl_operation_bindings(document)
63
64        for port_type in self._get_wsdl_port_types(document):
65            for operation in self._get_wsdl_operations(port_type):
66                self._filter_faults(
67                    document, operation, bindings, message_qrefs, tns)
68
69    @staticmethod
70    def _get_tns(document):
71        target_namespace = document.get('targetNamespace')
72        prefix = document.findPrefix(target_namespace) or 'tns'
73        return prefix, target_namespace
74
75    @staticmethod
76    def _get_wsdl_port_types(document):
77        return document.getChildren('portType', wsdl.wsdlns)
78
79    @staticmethod
80    def _get_wsdl_operations(port_type):
81        return port_type.getChildren('operation', wsdl.wsdlns)
82
83    @staticmethod
84    def _get_wsdl_messages(document):
85        return document.getChildren('message', wsdl.wsdlns)
86
87    @staticmethod
88    def _get_wsdl_operation_bindings(document):
89        bindings = []
90        for binding in document.getChildren('binding', wsdl.wsdlns):
91            operations = {}
92            for operation in binding.getChildren('operation', wsdl.wsdlns):
93                operations[operation.get('name')] = operation
94            bindings.append(operations)
95        return bindings
96
97    @staticmethod
98    def _filter_faults(document, operation, operation_bindings,
99                       message_qrefs, tns):
100        filtered_faults = {}
101        for fault in operation.getChildren('fault', wsdl.wsdlns):
102            fault_message = fault.get('message')
103            qref = xsd.qualify(fault_message, document, tns)
104            if qref not in message_qrefs:
105                filtered_faults[fault.get('name')] = fault
106        for fault in filtered_faults.values():
107            operation.remove(fault)
108        if filtered_faults:
109            for binding in operation_bindings:
110                filtered_binding_faults = []
111                faults = binding[operation.get('name')].getChildren(
112                    'fault', wsdl.wsdlns)
113                for binding_fault in faults:
114                    if binding_fault.get('name') in filtered_faults:
115                        filtered_binding_faults.append(binding_fault)
116                for binding_fault in filtered_binding_faults:
117                    binding[operation.get('name')].remove(binding_fault)
118
119
120class DataCoreClient(object):
121    """DataCore SANsymphony client."""
122
123    API_RETRY_INTERVAL = 10
124
125    DATACORE_EXECUTIVE_PORT = '3794'
126
127    STORAGE_SERVICES = 'IStorageServices'
128    STORAGE_SERVICES_BINDING = 'CustomBinding_IStorageServices'
129
130    EXECUTIVE_SERVICE = 'IExecutiveServiceEx'
131    EXECUTIVE_SERVICE_BINDING = 'CustomBinding_IExecutiveServiceEx'
132
133    NS_WSA = ('wsa', 'http://www.w3.org/2005/08/addressing')
134    WSA_ANONYMOUS = 'http://www.w3.org/2005/08/addressing/anonymous'
135    MUST_UNDERSTAND = attribute.Attribute('SOAP-ENV:mustUnderstand', '1')
136
137    # Namespaces that are defined within DataCore API WSDL
138    NS_DATACORE_EXECUTIVE = ('http://schemas.datacontract.org/2004/07/'
139                             'DataCore.Executive')
140    NS_DATACORE_EXECUTIVE_SCSI = ('http://schemas.datacontract.org/2004/07/'
141                                  'DataCore.Executive.Scsi')
142    NS_DATACORE_EXECUTIVE_ISCSI = ('http://schemas.datacontract.org/2004/07/'
143                                   'DataCore.Executive.iSCSI')
144    NS_SERIALIZATION_ARRAYS = ('http://schemas.microsoft.com/2003/10/'
145                               'Serialization/Arrays')
146
147    # Fully qualified names of objects that are defined within
148    # DataCore API WSDL
149    O_ACCESS_TOKEN = '{%s}AccessToken' % NS_DATACORE_EXECUTIVE_ISCSI
150    O_ARRAY_OF_PERFORMANCE_TYPE = ('{%s}ArrayOfPerformanceType'
151                                   % NS_DATACORE_EXECUTIVE)
152    O_ARRAY_OF_STRING = '{%s}ArrayOfstring' % NS_SERIALIZATION_ARRAYS
153    O_CLIENT_MACHINE_TYPE = '{%s}ClientMachineType' % NS_DATACORE_EXECUTIVE
154    O_DATA_SIZE = '{%s}DataSize' % NS_DATACORE_EXECUTIVE
155    O_LOGICAL_DISK_ROLE = '{%s}LogicalDiskRole' % NS_DATACORE_EXECUTIVE
156    O_LOGICAL_UNIT_TYPE = '{%s}LogicalUnitType' % NS_DATACORE_EXECUTIVE
157    O_MIRROR_RECOVERY_PRIORITY = ('{%s}MirrorRecoveryPriority'
158                                  % NS_DATACORE_EXECUTIVE)
159    O_PATH_POLICY = '{%s}PathPolicy' % NS_DATACORE_EXECUTIVE
160    O_PERFORMANCE_TYPE = '{%s}PerformanceType' % NS_DATACORE_EXECUTIVE
161    O_POOL_VOLUME_TYPE = '{%s}PoolVolumeType' % NS_DATACORE_EXECUTIVE
162    O_SNAPSHOT_TYPE = '{%s}SnapshotType' % NS_DATACORE_EXECUTIVE
163    O_SCSI_MODE = '{%s}ScsiMode' % NS_DATACORE_EXECUTIVE_SCSI
164    O_SCSI_PORT_DATA = '{%s}ScsiPortData' % NS_DATACORE_EXECUTIVE
165    O_SCSI_PORT_NEXUS_DATA = '{%s}ScsiPortNexusData' % NS_DATACORE_EXECUTIVE
166    O_SCSI_PORT_TYPE = '{%s}ScsiPortType' % NS_DATACORE_EXECUTIVE_SCSI
167    O_VIRTUAL_DISK_DATA = '{%s}VirtualDiskData' % NS_DATACORE_EXECUTIVE
168    O_VIRTUAL_DISK_STATUS = '{%s}VirtualDiskStatus' % NS_DATACORE_EXECUTIVE
169    O_VIRTUAL_DISK_SUB_TYPE = '{%s}VirtualDiskSubType' % NS_DATACORE_EXECUTIVE
170    O_VIRTUAL_DISK_TYPE = '{%s}VirtualDiskType' % NS_DATACORE_EXECUTIVE
171
172    def __init__(self, host, username, password, timeout):
173        if websocket is None:
174            msg = _("Failed to import websocket-client python module."
175                    " Please, ensure the module is installed.")
176            raise datacore_exceptions.DataCoreException(msg)
177
178        self.timeout = timeout
179
180        executive_service_net_addr = datacore_utils.build_network_address(
181            host, self.DATACORE_EXECUTIVE_PORT)
182        executive_service_endpoint = self._build_service_endpoint(
183            executive_service_net_addr, self.EXECUTIVE_SERVICE)
184
185        security_options = wsse.Security()
186        username_token = wsse.UsernameToken(username, password)
187        security_options.tokens.append(username_token)
188
189        self._executive_service_client = suds_client.Client(
190            executive_service_endpoint['http_endpoint'] + '?singlewsdl',
191            nosend=True,
192            timeout=self.timeout,
193            wsse=security_options,
194            plugins=[FaultDefinitionsFilter()])
195
196        self._update_storage_services_endpoint(executive_service_endpoint)
197
198        storage_services_endpoint = self._get_storage_services_endpoint()
199
200        self._storage_services_client = suds_client.Client(
201            storage_services_endpoint['http_endpoint'] + '?singlewsdl',
202            nosend=True,
203            timeout=self.timeout,
204            wsse=security_options,
205            plugins=[FaultDefinitionsFilter()])
206
207        self._update_executive_service_endpoints(storage_services_endpoint)
208
209    @staticmethod
210    def _get_list_data(obj, attribute_name):
211        return getattr(obj, attribute_name, [])
212
213    @staticmethod
214    def _build_service_endpoint(network_address, path):
215        return {
216            'network_address': network_address,
217            'http_endpoint': '%s://%s/%s' % ('http', network_address, path),
218            'ws_endpoint': '%s://%s/%s' % ('ws', network_address, path),
219        }
220
221    @cinder_utils.synchronized('datacore-api-request_context')
222    def _get_soap_context(self, service_client, service_binding, method,
223                          message_id, *args, **kwargs):
224        soap_action = (service_client.wsdl.services[0].port(service_binding)
225                       .methods[method].soap.action)
226
227        soap_headers = self._get_soap_headers(soap_action, message_id)
228
229        service_client.set_options(soapheaders=soap_headers)
230        context = service_client.service[service_binding][method](
231            *args, **kwargs)
232
233        return context
234
235    def _get_soap_headers(self, soap_action, message_id):
236        headers = [
237            element.Element('Action', ns=self.NS_WSA)
238            .setText(soap_action.replace('"', ''))
239            .append(self.MUST_UNDERSTAND),
240
241            element.Element('To', ns=self.NS_WSA)
242            .setText(self.WSA_ANONYMOUS)
243            .append(self.MUST_UNDERSTAND),
244
245            element.Element('MessageID', ns=self.NS_WSA)
246            .setText(message_id),
247
248            element.Element('ReplyTo', ns=self.NS_WSA)
249            .insert(element.Element('Address', ns=self.NS_WSA)
250                    .setText(self.WSA_ANONYMOUS)),
251        ]
252        return headers
253
254    def _process_request(self, service_client, service_binding,
255                         service_endpoint, method, *args, **kwargs):
256        message_id = uuid.uuid4().urn
257
258        context = self._get_soap_context(
259            service_client, service_binding,
260            method, message_id, *args, **kwargs)
261
262        channel = None
263        try:
264            channel = websocket.create_connection(
265                service_endpoint,
266                timeout=self.timeout,
267                subprotocols=['soap'],
268                header=['soap-content-type: text/xml'])
269            channel.send(context.envelope)
270            response = channel.recv()
271            if isinstance(response, six.text_type):
272                response = response.encode('utf-8')
273            return context.process_reply(response)
274        except (socket.error, websocket.WebSocketException) as e:
275            traceback = sys.exc_info()[2]
276            error = datacore_exceptions.DataCoreConnectionException(reason=e)
277            six.reraise(datacore_exceptions.DataCoreConnectionException,
278                        error,
279                        traceback)
280        except suds.WebFault as e:
281            traceback = sys.exc_info()[2]
282            fault = datacore_exceptions.DataCoreFaultException(reason=e)
283            six.reraise(datacore_exceptions.DataCoreFaultException,
284                        fault,
285                        traceback)
286        finally:
287            if channel and channel.connected:
288                try:
289                    channel.close()
290                except (socket.error, websocket.WebSocketException) as e:
291                    LOG.debug("Closing a connection to "
292                              "DataCore server failed. %s", e)
293
294    def _invoke_storage_services(self, method, *args, **kwargs):
295
296        @retrying.retry(
297            retry_on_exception=lambda e:
298                isinstance(e, datacore_exceptions.DataCoreConnectionException),
299            wait_fixed=self.API_RETRY_INTERVAL * 1000,
300            stop_max_delay=self.timeout * 1000)
301        def retry_call():
302            storage_services_endpoint = self._get_storage_services_endpoint()
303            try:
304                result = self._process_request(
305                    self._storage_services_client,
306                    self.STORAGE_SERVICES_BINDING,
307                    storage_services_endpoint['ws_endpoint'],
308                    method, *args, **kwargs)
309                return result
310            except datacore_exceptions.DataCoreConnectionException:
311                with excutils.save_and_reraise_exception():
312                    self._update_api_endpoints()
313
314        return retry_call()
315
316    def _update_api_endpoints(self):
317        executive_service_endpoints = self._get_executive_service_endpoints()
318        for endpoint in executive_service_endpoints:
319            try:
320                self._update_storage_services_endpoint(endpoint)
321                break
322            except datacore_exceptions.DataCoreConnectionException as e:
323                LOG.warning("Failed to update DataCore Server Group "
324                            "endpoints. %s.", e)
325
326        storage_services_endpoint = self._get_storage_services_endpoint()
327        try:
328            self._update_executive_service_endpoints(
329                storage_services_endpoint)
330        except datacore_exceptions.DataCoreConnectionException as e:
331            LOG.warning("Failed to update DataCore Server Group "
332                        "endpoints. %s.", e)
333
334    @cinder_utils.synchronized('datacore-api-storage_services_endpoint')
335    def _get_storage_services_endpoint(self):
336        if self._storage_services_endpoint:
337            return copy.copy(self._storage_services_endpoint)
338        return None
339
340    @cinder_utils.synchronized('datacore-api-storage_services_endpoint')
341    def _update_storage_services_endpoint(self, executive_service_endpoint):
342        controller_address = self._process_request(
343            self._executive_service_client,
344            self.EXECUTIVE_SERVICE_BINDING,
345            executive_service_endpoint['ws_endpoint'],
346            'GetControllerAddress')
347
348        if not controller_address:
349            msg = _("Could not determine controller node.")
350            raise datacore_exceptions.DataCoreConnectionException(reason=msg)
351
352        controller_host = controller_address.rsplit(':', 1)[0].strip('[]')
353        controller_net_addr = datacore_utils.build_network_address(
354            controller_host,
355            self.DATACORE_EXECUTIVE_PORT)
356
357        self._storage_services_endpoint = self._build_service_endpoint(
358            controller_net_addr,
359            self.STORAGE_SERVICES)
360
361    @cinder_utils.synchronized('datacore-api-executive_service_endpoints')
362    def _get_executive_service_endpoints(self):
363        if self._executive_service_endpoints:
364            return self._executive_service_endpoints[:]
365        return []
366
367    @cinder_utils.synchronized('datacore-api-executive_service_endpoints')
368    def _update_executive_service_endpoints(self, storage_services_endpoint):
369        endpoints = []
370        nodes = self._get_list_data(
371            self._process_request(self._storage_services_client,
372                                  self.STORAGE_SERVICES_BINDING,
373                                  storage_services_endpoint['ws_endpoint'],
374                                  'GetNodes'),
375            'RegionNodeData')
376
377        if not nodes:
378            msg = _("Could not determine executive nodes.")
379            raise datacore_exceptions.DataCoreConnectionException(reason=msg)
380
381        for node in nodes:
382            host = node.HostAddress.rsplit(':', 1)[0].strip('[]')
383            endpoint = self._build_service_endpoint(
384                datacore_utils.build_network_address(
385                    host, self.DATACORE_EXECUTIVE_PORT),
386                self.EXECUTIVE_SERVICE)
387            endpoints.append(endpoint)
388
389        self._executive_service_endpoints = endpoints
390
391    def get_server_groups(self):
392        """Get all the server groups in the configuration.
393
394        :return: A list of server group data.
395        """
396
397        return self._get_list_data(
398            self._invoke_storage_services('GetServerGroups'),
399            'ServerHostGroupData')
400
401    def get_servers(self):
402        """Get all the server hosts in the configuration.
403
404        :return: A list of server host data
405        """
406
407        return self._get_list_data(
408            self._invoke_storage_services('GetServers'),
409            'ServerHostData')
410
411    def get_disk_pools(self):
412        """Get all the pools in the server group.
413
414        :return: A list of disk pool data
415        """
416
417        return self._get_list_data(
418            self._invoke_storage_services('GetDiskPools'),
419            'DiskPoolData')
420
421    def get_logical_disks(self):
422        """Get all the logical disks defined in the system.
423
424        :return: A list of logical disks
425        """
426
427        return self._get_list_data(
428            self._invoke_storage_services('GetLogicalDisks'),
429            'LogicalDiskData')
430
431    def create_pool_logical_disk(self, pool_id, pool_volume_type, size,
432                                 min_quota=None, max_quota=None):
433        """Create the pool logical disk.
434
435        :param pool_id: Pool id
436        :param pool_volume_type: Type, either striped or spanned
437        :param size: Size
438        :param min_quota: Min quota
439        :param max_quota: Max quota
440        :return: New logical disk data
441        """
442
443        volume_type = getattr(self._storage_services_client.factory
444                              .create(self.O_POOL_VOLUME_TYPE),
445                              pool_volume_type)
446
447        data_size = (self._storage_services_client.factory
448                     .create(self.O_DATA_SIZE))
449        data_size.Value = size
450
451        data_size_min_quota = None
452        if min_quota:
453            data_size_min_quota = (self._storage_services_client.factory
454                                   .create(self.O_DATA_SIZE))
455            data_size_min_quota.Value = min_quota
456
457        data_size_max_quota = None
458        if max_quota:
459            data_size_max_quota = (self._storage_services_client.factory
460                                   .create(self.O_DATA_SIZE))
461            data_size_max_quota.Value = max_quota
462
463        return self._invoke_storage_services('CreatePoolLogicalDisk',
464                                             poolId=pool_id,
465                                             type=volume_type,
466                                             size=data_size,
467                                             minQuota=data_size_min_quota,
468                                             maxQuota=data_size_max_quota)
469
470    def delete_logical_disk(self, logical_disk_id):
471        """Delete the logical disk.
472
473        :param logical_disk_id: Logical disk id
474        """
475
476        self._invoke_storage_services('DeleteLogicalDisk',
477                                      logicalDiskId=logical_disk_id)
478
479    def get_logical_disk_chunk_allocation_map(self, logical_disk_id):
480        """Get the logical disk chunk allocation map.
481
482        The logical disk allocation map details all the physical disk chunks
483        that are currently allocated to this logical disk.
484
485        :param logical_disk_id: Logical disk id
486        :return: A list of member allocation maps, restricted to chunks
487                 allocated on to this logical disk
488        """
489
490        return self._get_list_data(
491            self._invoke_storage_services('GetLogicalDiskChunkAllocationMap',
492                                          logicalDiskId=logical_disk_id),
493            'MemberAllocationInfoData')
494
495    def get_next_virtual_disk_alias(self, base_alias):
496        """Get the next available (unused) virtual disk alias.
497
498        :param base_alias: Base string of the new alias
499        :return: New alias
500        """
501
502        return self._invoke_storage_services('GetNextVirtualDiskAlias',
503                                             baseAlias=base_alias)
504
505    def get_virtual_disks(self):
506        """Get all the virtual disks in the configuration.
507
508        :return: A list of virtual disk's data
509        """
510
511        return self._get_list_data(
512            self._invoke_storage_services('GetVirtualDisks'),
513            'VirtualDiskData')
514
515    def build_virtual_disk_data(self, virtual_disk_alias, virtual_disk_type,
516                                size, description, storage_profile_id):
517        """Create VirtualDiskData object.
518
519        :param virtual_disk_alias: User-visible alias of the virtual disk,
520                                   which must be unique
521        :param virtual_disk_type: Virtual disk type
522        :param size: Virtual disk size
523        :param description: A user-readable description of the virtual disk
524        :param storage_profile_id: Virtual disk storage profile
525        :return: VirtualDiskData object
526        """
527
528        vd_data = (self._storage_services_client.factory
529                   .create(self.O_VIRTUAL_DISK_DATA))
530        vd_data.Size = (self._storage_services_client.factory
531                        .create(self.O_DATA_SIZE))
532        vd_data.Size.Value = size
533        vd_data.Alias = virtual_disk_alias
534        vd_data.Description = description
535        vd_data.Type = getattr(self._storage_services_client.factory
536                               .create(self.O_VIRTUAL_DISK_TYPE),
537                               virtual_disk_type)
538        vd_data.SubType = getattr(self._storage_services_client.factory
539                                  .create(self.O_VIRTUAL_DISK_SUB_TYPE),
540                                  'Standard')
541        vd_data.DiskStatus = getattr(self._storage_services_client.factory
542                                     .create(self.O_VIRTUAL_DISK_STATUS),
543                                     'Online')
544        vd_data.RecoveryPriority = getattr(
545            self._storage_services_client.factory
546            .create(self.O_MIRROR_RECOVERY_PRIORITY),
547            'Unset')
548        vd_data.StorageProfileId = storage_profile_id
549
550        return vd_data
551
552    def create_virtual_disk_ex2(self, virtual_disk_data, first_logical_disk_id,
553                                second_logical_disk_id, add_redundancy):
554        """Create a virtual disk specifying the both logical disks.
555
556        :param virtual_disk_data: Virtual disk's properties
557        :param first_logical_disk_id: Id of the logical disk to use
558        :param second_logical_disk_id: Id of the second logical disk to use
559        :param add_redundancy: If True, the mirror has redundant mirror paths
560        :return: New virtual disk's data
561        """
562
563        return self._invoke_storage_services(
564            'CreateVirtualDiskEx2',
565            virtualDisk=virtual_disk_data,
566            firstLogicalDiskId=first_logical_disk_id,
567            secondLogicalDiskId=second_logical_disk_id,
568            addRedundancy=add_redundancy)
569
570    def set_virtual_disk_size(self, virtual_disk_id, size):
571        """Change the size of a virtual disk.
572
573        :param virtual_disk_id: Id of the virtual disk
574        :param size: New size
575        :return: Virtual disk's data
576        """
577
578        data_size = (self._storage_services_client.factory
579                     .create(self.O_DATA_SIZE))
580        data_size.Value = size
581
582        return self._invoke_storage_services('SetVirtualDiskSize',
583                                             virtualDiskId=virtual_disk_id,
584                                             size=data_size)
585
586    def delete_virtual_disk(self, virtual_disk_id, delete_logical_disks):
587        """Delete a virtual disk.
588
589        :param virtual_disk_id: Id of the virtual disk
590        :param delete_logical_disks: If True, delete the associated
591                                     logical disks
592        """
593
594        self._invoke_storage_services('DeleteVirtualDisk',
595                                      virtualDiskId=virtual_disk_id,
596                                      deleteLogicalDisks=delete_logical_disks)
597
598    def serve_virtual_disks_to_host(self, host_id, virtual_disks):
599        """Serve multiple virtual disks to a specified host.
600
601        :param host_id: Id of the host machine
602        :param virtual_disks: A list of virtual disks to serve
603        :return: A list of the virtual disks actually served to the host
604        """
605
606        virtual_disk_array = (self._storage_services_client.factory
607                              .create(self.O_ARRAY_OF_STRING))
608        virtual_disk_array.string = virtual_disks
609
610        return self._get_list_data(
611            self._invoke_storage_services('ServeVirtualDisksToHost',
612                                          hostId=host_id,
613                                          virtualDisks=virtual_disk_array),
614            'VirtualLogicalUnitData')
615
616    def unserve_virtual_disks_from_host(self, host_id, virtual_disks):
617        """Unserve multiple virtual disks from a specified host.
618
619        :param host_id: Id of the host machine
620        :param virtual_disks: A list of virtual disks to unserve
621        """
622
623        virtual_disk_array = (self._storage_services_client.factory
624                              .create(self.O_ARRAY_OF_STRING))
625        virtual_disk_array.string = virtual_disks
626
627        self._invoke_storage_services('UnserveVirtualDisksFromHost',
628                                      hostId=host_id,
629                                      virtualDisks=virtual_disk_array)
630
631    def unserve_virtual_disks_from_port(self, port_id, virtual_disks):
632        """Unserve multiple virtual disks from a specified initiator port.
633
634        :param port_id: Id of the initiator port
635        :param virtual_disks: A list of virtual disks to unserve
636        """
637
638        virtual_disk_array = (self._storage_services_client.factory
639                              .create(self.O_ARRAY_OF_STRING))
640        virtual_disk_array.string = virtual_disks
641
642        self._invoke_storage_services('UnserveVirtualDisksFromPort',
643                                      portId=port_id,
644                                      virtualDisks=virtual_disk_array)
645
646    def bind_logical_disk(self, virtual_disk_id, logical_disk_id, role,
647                          create_mirror_mappings, create_client_mappings,
648                          add_redundancy):
649        """Bind (add) a logical disk to a virtual disk.
650
651        :param virtual_disk_id: Id of the virtual disk to bind to
652        :param logical_disk_id: Id of the logical disk being bound
653        :param role: logical disk's role
654        :param create_mirror_mappings: If True, automatically create the
655                                       mirror mappings to this disk, assuming
656                                       there is already another logical disk
657                                       bound
658        :param create_client_mappings: If True, automatically create mappings
659                                       from mapped hosts to the new disk
660        :param add_redundancy: If True, the mirror has redundant mirror paths
661        :return: Updated virtual disk data
662        """
663
664        logical_disk_role = getattr(self._storage_services_client.factory
665                                    .create(self.O_LOGICAL_DISK_ROLE),
666                                    role)
667
668        return self._invoke_storage_services(
669            'BindLogicalDisk',
670            virtualDiskId=virtual_disk_id,
671            logicalDiskId=logical_disk_id,
672            role=logical_disk_role,
673            createMirrorMappings=create_mirror_mappings,
674            createClientMappings=create_client_mappings,
675            addRedundancy=add_redundancy)
676
677    def get_snapshots(self):
678        """Get all the snapshots on all the servers in the region.
679
680        :return: A list of snapshot data.
681        """
682
683        return self._get_list_data(
684            self._invoke_storage_services('GetSnapshots'),
685            'SnapshotData')
686
687    def create_snapshot(self, virtual_disk_id, name, description,
688                        destination_pool_id, snapshot_type,
689                        duplicate_disk_id, storage_profile_id):
690        """Create a snapshot relationship.
691
692        :param virtual_disk_id: Virtual disk id
693        :param name: Name of snapshot
694        :param description: Description
695        :param destination_pool_id: Destination pool id
696        :param snapshot_type: Type of snapshot
697        :param duplicate_disk_id: If set to True then the destination virtual
698                                  disk's SCSI id will be a duplicate of the
699                                  source's
700        :param storage_profile_id: Specifies the destination virtual disk's
701                                   storage profile
702        :return: New snapshot data
703        """
704
705        st_type = getattr(self._storage_services_client.factory
706                          .create(self.O_SNAPSHOT_TYPE),
707                          snapshot_type)
708
709        return self._invoke_storage_services(
710            'CreateSnapshot',
711            virtualDiskId=virtual_disk_id,
712            name=name,
713            description=description,
714            destinationPoolId=destination_pool_id,
715            type=st_type,
716            duplicateDiskId=duplicate_disk_id,
717            storageProfileId=storage_profile_id)
718
719    def delete_snapshot(self, snapshot_id):
720        """Delete the snapshot.
721
722        :param snapshot_id: Snapshot id
723        """
724
725        self._invoke_storage_services('DeleteSnapshot', snapshotId=snapshot_id)
726
727    def get_storage_profiles(self):
728        """Get all the all the defined storage profiles.
729
730        :return: A list of storage profiles
731        """
732
733        return self._get_list_data(
734            self._invoke_storage_services('GetStorageProfiles'),
735            'StorageProfileData')
736
737    def designate_map_store(self, pool_id):
738        """Designate which pool the snapshot mapstore will be allocated from.
739
740        :param pool_id: Pool id
741        :return: Updated server host data, which includes the mapstore pool id
742        """
743
744        return self._invoke_storage_services('DesignateMapStore',
745                                             poolId=pool_id)
746
747    def get_performance_by_type(self, performance_types):
748        """Get performance data for specific types of performance counters.
749
750        :param performance_types: A list of performance counter types
751        :return: A list of performance data points
752        """
753
754        prfm_type_array = (self._storage_services_client.factory
755                           .create(self.O_ARRAY_OF_PERFORMANCE_TYPE))
756        prfm_type_array.PerformanceType = list(
757            getattr(self._storage_services_client.factory
758                    .create(self.O_PERFORMANCE_TYPE),
759                    performance_type)
760            for performance_type in performance_types)
761
762        return self._get_list_data(
763            self._invoke_storage_services('GetPerformanceByType',
764                                          types=prfm_type_array),
765            'CollectionPointData')
766
767    def get_ports(self):
768        """Get all ports in the configuration.
769
770        :return: A list of SCSI ports
771        """
772
773        return self._get_list_data(
774            self._invoke_storage_services('GetPorts'),
775            'ScsiPortData')
776
777    def build_scsi_port_data(self, host_id, port_name, port_mode, port_type):
778        """Create ScsiPortData object that represents SCSI port, of any type.
779
780        :param host_id: Id of the port's host computer
781        :param port_name: Unique name of the port.
782        :param port_mode: Mode of port: initiator or target
783        :param port_type: Type of port, Fc, iSCSI or loopback
784        :return: ScsiPortData object
785        """
786
787        scsi_port_data = (self._storage_services_client.factory
788                          .create(self.O_SCSI_PORT_DATA))
789        scsi_port_data.HostId = host_id
790        scsi_port_data.PortName = port_name
791        scsi_port_data.PortMode = getattr(self._storage_services_client.factory
792                                          .create(self.O_SCSI_MODE),
793                                          port_mode)
794        scsi_port_data.PortType = getattr(self._storage_services_client.factory
795                                          .create(self.O_SCSI_PORT_TYPE),
796                                          port_type)
797
798        return scsi_port_data
799
800    def register_port(self, scsi_port_data):
801        """Register a port in the configuration.
802
803        :param scsi_port_data: Port data
804        :return: Updated port data
805        """
806
807        return self._invoke_storage_services('RegisterPort',
808                                             port=scsi_port_data)
809
810    def assign_port(self, client_id, port_id):
811        """Assign a port to a client.
812
813        :param client_id: Client id
814        :param port_id: Port id
815        :return: Updated port data,
816                 which will now have its host id set to the client id
817        """
818
819        return self._invoke_storage_services('AssignPort',
820                                             clientId=client_id,
821                                             portId=port_id)
822
823    def set_server_port_properties(self, port_id, properties):
824        """Set a server port's properties.
825
826        :param port_id: Port id
827        :param properties: New properties
828        :return: Updated port data
829        """
830
831        return self._invoke_storage_services('SetServerPortProperties',
832                                             portId=port_id,
833                                             properties=properties)
834
835    def build_access_token(self, initiator_node_name, initiator_username,
836                           initiator_password, mutual_authentication,
837                           target_username, target_password):
838        """Create an AccessToken object.
839
840        :param initiator_node_name: Initiator node name
841        :param initiator_username: Initiator user name
842        :param initiator_password: Initiator password
843        :param mutual_authentication: If True the target and the initiator
844                                      authenticate each other.
845                                      A separate secret is set for each target
846                                      and for each initiator in the storage
847                                      area network (SAN).
848        :param target_username: Target user name
849        :param target_password: Target password
850        :return: AccessToken object
851        """
852
853        access_token = (self._storage_services_client.factory
854                        .create(self.O_ACCESS_TOKEN))
855        access_token.InitiatorNodeName = initiator_node_name
856        access_token.InitiatorUsername = initiator_username
857        access_token.InitiatorPassword = initiator_password
858        access_token.MutualAuthentication = mutual_authentication
859        access_token.TargetUsername = target_username
860        access_token.TargetPassword = target_password
861
862        return access_token
863
864    def set_access_token(self, iscsi_port_id, access_token):
865        """Set the access token.
866
867        The access token allows access to a specific network node
868        from a specific iSCSI port.
869
870        :param iscsi_port_id: Id of the initiator iSCSI port
871        :param access_token: Access token to be validated
872        :return: Port data
873        """
874
875        return self._invoke_storage_services('SetAccessToken',
876                                             iScsiPortId=iscsi_port_id,
877                                             inputToken=access_token)
878
879    def get_clients(self):
880        """Get all the clients in the configuration.
881
882        :return: A list of client data
883        """
884
885        return self._get_list_data(
886            self._invoke_storage_services('GetClients'),
887            'ClientHostData')
888
889    def register_client(self, host_name, description, machine_type,
890                        mode, preferred_server_ids):
891        """Register the client, creating a client object in the configuration.
892
893        :param host_name: Name of the client
894        :param description: Description
895        :param machine_type: Type of client
896        :param mode: Path policy mode of the client
897        :param preferred_server_ids: Preferred server ids
898        :return: New client data
899        """
900
901        client_machine_type = getattr(self._storage_services_client.factory
902                                      .create(self.O_CLIENT_MACHINE_TYPE),
903                                      machine_type)
904        client_mode = getattr(self._storage_services_client.factory
905                              .create(self.O_PATH_POLICY),
906                              mode)
907
908        return self._invoke_storage_services(
909            'RegisterClient',
910            hostName=host_name,
911            description=description,
912            type=client_machine_type,
913            mode=client_mode,
914            preferredServerIds=preferred_server_ids)
915
916    def set_client_capabilities(self, client_id, mpio, alua):
917        """Set the client capabilities for MPIO and ALUA.
918
919        :param client_id: Client id
920        :param mpio: If set to True then MPIO-capable
921        :param alua: If set to True then ALUA-capable
922        :return: Updated client data
923        """
924
925        return self._invoke_storage_services('SetClientCapabilities',
926                                             clientId=client_id,
927                                             mpio=mpio,
928                                             alua=alua)
929
930    def get_target_domains(self):
931        """Get all the target domains in the configuration.
932
933        :return: A list of target domains
934        """
935
936        return self._get_list_data(
937            self._invoke_storage_services('GetTargetDomains'),
938            'VirtualTargetDomainData')
939
940    def create_target_domain(self, initiator_host_id, target_host_id):
941        """Create a target domain given a pair of hosts, target and initiator.
942
943        :param initiator_host_id: Id of the initiator host machine
944        :param target_host_id: Id of the target host server
945        :return: New target domain
946        """
947
948        return self._invoke_storage_services('CreateTargetDomain',
949                                             initiatorHostId=initiator_host_id,
950                                             targetHostId=target_host_id)
951
952    def delete_target_domain(self, target_domain_id):
953        """Delete a target domain.
954
955        :param target_domain_id: Target domain id
956        """
957
958        self._invoke_storage_services('DeleteTargetDomain',
959                                      targetDomainId=target_domain_id)
960
961    def get_target_devices(self):
962        """Get all the target devices in the configuration.
963
964        :return: A list of target devices
965        """
966
967        return self._get_list_data(
968            self._invoke_storage_services('GetTargetDevices'),
969            'VirtualTargetDeviceData')
970
971    def build_scsi_port_nexus_data(self, initiator_port_id, target_port_id):
972        """Create a ScsiPortNexusData object.
973
974        Nexus is a pair of ports that can communicate, one being the initiator,
975        the other the target
976
977        :param initiator_port_id: Id of the initiator port
978        :param target_port_id: Id of the target port
979        :return: ScsiPortNexusData object
980        """
981
982        scsi_port_nexus_data = (self._storage_services_client.factory
983                                .create(self.O_SCSI_PORT_NEXUS_DATA))
984        scsi_port_nexus_data.InitiatorPortId = initiator_port_id
985        scsi_port_nexus_data.TargetPortId = target_port_id
986
987        return scsi_port_nexus_data
988
989    def create_target_device(self, target_domain_id, nexus):
990        """Create a target device, given a target domain and a nexus.
991
992        :param target_domain_id: Target domain id
993        :param nexus: Nexus, or pair of ports
994        :return: New target device
995        """
996
997        return self._invoke_storage_services('CreateTargetDevice',
998                                             targetDomainId=target_domain_id,
999                                             nexus=nexus)
1000
1001    def delete_target_device(self, target_device_id):
1002        """Delete a target device.
1003
1004        :param target_device_id: Target device id
1005        """
1006
1007        self._invoke_storage_services('DeleteTargetDevice',
1008                                      targetDeviceId=target_device_id)
1009
1010    def get_next_free_lun(self, target_device_id):
1011        """Find the next unused LUN number for a specified target device.
1012
1013        :param target_device_id: Target device id
1014        :return: LUN number
1015        """
1016
1017        return self._invoke_storage_services('GetNextFreeLun',
1018                                             targetDeviceId=target_device_id)
1019
1020    def get_logical_units(self):
1021        """Get all the mappings configured in the system.
1022
1023        :return: A list of mappings
1024        """
1025
1026        return self._get_list_data(
1027            self._invoke_storage_services('GetLogicalUnits'),
1028            'VirtualLogicalUnitData')
1029
1030    def map_logical_disk(self, logical_disk_id, nexus, lun,
1031                         initiator_host_id, mapping_type):
1032        """Map a logical disk to a host.
1033
1034        :param logical_disk_id: Id of the logical disk
1035        :param nexus: Nexus, or pair of ports
1036        :param lun: Logical Unit Number
1037        :param initiator_host_id: Id of the initiator host machine
1038        :param mapping_type: Type of mapping
1039        :return: New mapping
1040        """
1041
1042        logical_unit_type = getattr(self._storage_services_client.factory
1043                                    .create(self.O_LOGICAL_UNIT_TYPE),
1044                                    mapping_type)
1045
1046        return self._invoke_storage_services('MapLogicalDisk',
1047                                             logicalDiskId=logical_disk_id,
1048                                             nexus=nexus,
1049                                             lun=lun,
1050                                             initiatorHostId=initiator_host_id,
1051                                             mappingType=logical_unit_type)
1052
1053    def unmap_logical_disk(self, logical_disk_id, nexus):
1054        """Unmap a logical disk mapped with a specified nexus.
1055
1056        :param logical_disk_id: Id of the logical disk
1057        :param nexus: Nexus, or pair of ports
1058        """
1059
1060        self._invoke_storage_services('UnmapLogicalDisk',
1061                                      logicalDiskId=logical_disk_id,
1062                                      nexusData=nexus)
1063