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