1# Copyright (c) 2017 Dell Inc. or its subsidiaries. 2# All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); you may 5# not use this file except in compliance with the License. You may obtain 6# a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13# License for the specific language governing permissions and limitations 14# under the License. 15 16import time 17 18from oslo_log import log as logging 19import six 20 21from cinder import coordination 22from cinder import exception 23from cinder.i18n import _ 24from cinder.volume.drivers.dell_emc.vmax import provision 25from cinder.volume.drivers.dell_emc.vmax import utils 26 27LOG = logging.getLogger(__name__) 28 29 30class VMAXMasking(object): 31 """Masking class for Dell EMC VMAX. 32 33 Masking code to dynamically create a masking view. 34 It supports VMAX arrays. 35 """ 36 def __init__(self, prtcl, rest): 37 self.protocol = prtcl 38 self.utils = utils.VMAXUtils() 39 self.rest = rest 40 self.provision = provision.VMAXProvision(self.rest) 41 42 def setup_masking_view( 43 self, serial_number, volume, masking_view_dict, extra_specs): 44 45 @coordination.synchronized("emc-mv-{maskingview_name}") 46 def do_get_or_create_masking_view_and_map_lun(maskingview_name): 47 return self.get_or_create_masking_view_and_map_lun( 48 serial_number, volume, maskingview_name, masking_view_dict, 49 extra_specs) 50 return do_get_or_create_masking_view_and_map_lun( 51 masking_view_dict[utils.MV_NAME]) 52 53 def get_or_create_masking_view_and_map_lun( 54 self, serial_number, volume, maskingview_name, masking_view_dict, 55 extra_specs): 56 """Get or Create a masking view and add a volume to the storage group. 57 58 Given a masking view dict either get or create a masking view and add 59 the volume to the associated storage group. 60 :param serial_number: the array serial number 61 :param volume: the volume object 62 :param maskingview_name: the masking view name 63 :param masking_view_dict: the masking view dict 64 :param extra_specs: the extra specifications 65 :returns: rollback_dict 66 :raises: VolumeBackendAPIException 67 """ 68 storagegroup_name = masking_view_dict[utils.SG_NAME] 69 volume_name = masking_view_dict[utils.VOL_NAME] 70 masking_view_dict[utils.EXTRA_SPECS] = extra_specs 71 device_id = masking_view_dict[utils.DEVICE_ID] 72 rep_mode = extra_specs.get(utils.REP_MODE, None) 73 default_sg_name = self.utils.get_default_storage_group_name( 74 masking_view_dict[utils.SRP], 75 masking_view_dict[utils.SLO], 76 masking_view_dict[utils.WORKLOAD], 77 masking_view_dict[utils.DISABLECOMPRESSION], 78 masking_view_dict[utils.IS_RE], rep_mode) 79 rollback_dict = masking_view_dict 80 81 try: 82 error_message = self._get_or_create_masking_view( 83 serial_number, masking_view_dict, 84 default_sg_name, extra_specs) 85 LOG.debug( 86 "The masking view in the attach operation is " 87 "%(masking_name)s. The storage group " 88 "in the masking view is %(storage_name)s.", 89 {'masking_name': maskingview_name, 90 'storage_name': storagegroup_name}) 91 rollback_dict['portgroup_name'] = ( 92 self.rest.get_element_from_masking_view( 93 serial_number, maskingview_name, portgroup=True)) 94 95 except Exception as e: 96 LOG.exception( 97 "Masking View creation or retrieval was not successful " 98 "for masking view %(maskingview_name)s. " 99 "Attempting rollback.", 100 {'maskingview_name': masking_view_dict[utils.MV_NAME]}) 101 error_message = six.text_type(e) 102 103 if 'source_nf_sg' in masking_view_dict: 104 default_sg_name = masking_view_dict['source_nf_sg'] 105 rollback_dict['default_sg_name'] = default_sg_name 106 107 if error_message: 108 # Rollback code if we cannot complete any of the steps above 109 # successfully then we must roll back by adding the volume back to 110 # the default storage group for that slo/workload combination. 111 112 if rollback_dict['slo'] is not None: 113 self.check_if_rollback_action_for_masking_required( 114 serial_number, volume, device_id, masking_view_dict) 115 116 else: 117 self._check_adding_volume_to_storage_group( 118 serial_number, device_id, rollback_dict['default_sg_name'], 119 masking_view_dict[utils.VOL_NAME], 120 masking_view_dict[utils.EXTRA_SPECS]) 121 122 exception_message = (_( 123 "Failed to get, create or add volume %(volumeName)s " 124 "to masking view %(maskingview_name)s. " 125 "The error message received was %(errorMessage)s.") 126 % {'maskingview_name': maskingview_name, 127 'volumeName': volume_name, 128 'errorMessage': error_message}) 129 LOG.error(exception_message) 130 raise exception.VolumeBackendAPIException(data=exception_message) 131 132 return rollback_dict 133 134 def _move_vol_from_default_sg( 135 self, serial_number, device_id, volume_name, 136 default_sg_name, dest_storagegroup, extra_specs): 137 """Get the default storage group and move the volume. 138 139 :param serial_number: the array serial number 140 :param device_id: the device id 141 :param volume_name: the volume name 142 :param default_sg_name: the name of the default sg 143 :param dest_storagegroup: the destination storage group 144 :param extra_specs: the extra specifications 145 :returns: msg 146 """ 147 msg = None 148 check_vol = self.rest.is_volume_in_storagegroup( 149 serial_number, device_id, default_sg_name) 150 if check_vol: 151 @coordination.synchronized("emc-sg-{sg_name}") 152 def do_move_vol_from_def_sg(sg_name): 153 num_vol_in_sg = self.rest.get_num_vols_in_sg( 154 serial_number, default_sg_name) 155 LOG.debug("There are %(num_vol)d volumes in the " 156 "storage group %(sg_name)s.", 157 {'num_vol': num_vol_in_sg, 158 'sg_name': default_sg_name}) 159 self.rest.move_volume_between_storage_groups( 160 serial_number, device_id, default_sg_name, 161 dest_storagegroup, extra_specs) 162 if num_vol_in_sg == 1: 163 # Last volume in the storage group - delete sg. 164 self.rest.delete_storage_group( 165 serial_number, default_sg_name) 166 167 try: 168 do_move_vol_from_def_sg(default_sg_name) 169 except Exception as e: 170 msg = ("Exception while moving volume from the default " 171 "storage group to %(sg)s. Exception received was " 172 "%(e)s") 173 LOG.error(msg, {'sg': dest_storagegroup, 'e': e}) 174 else: 175 LOG.warning( 176 "Volume: %(volume_name)s does not belong " 177 "to default storage group %(default_sg_name)s.", 178 {'volume_name': volume_name, 179 'default_sg_name': default_sg_name}) 180 msg = self._check_adding_volume_to_storage_group( 181 serial_number, device_id, dest_storagegroup, 182 volume_name, extra_specs) 183 184 return msg 185 186 def _get_or_create_masking_view(self, serial_number, masking_view_dict, 187 default_sg_name, extra_specs): 188 """Retrieve an existing masking view or create a new one. 189 190 :param serial_number: the array serial number 191 :param masking_view_dict: the masking view dict 192 :param default_sg_name: the name of the default sg 193 :param extra_specs: the extra specifications 194 :returns: error message 195 """ 196 maskingview_name = masking_view_dict[utils.MV_NAME] 197 198 masking_view_details = self.rest.get_masking_view( 199 serial_number, masking_view_name=maskingview_name) 200 if not masking_view_details: 201 error_message = self._create_new_masking_view( 202 serial_number, masking_view_dict, maskingview_name, 203 default_sg_name, extra_specs) 204 205 else: 206 storagegroup_name, error_message = ( 207 self._validate_existing_masking_view( 208 serial_number, masking_view_dict, maskingview_name, 209 default_sg_name, extra_specs)) 210 211 return error_message 212 213 def _create_new_masking_view( 214 self, serial_number, masking_view_dict, 215 maskingview_name, default_sg_name, extra_specs): 216 """Create a new masking view. 217 218 :param serial_number: the array serial number 219 :param masking_view_dict: the masking view dict 220 :param maskingview_name: the masking view name 221 :param default_sg_name: the name of the default sg 222 :param extra_specs: the extra specifications 223 :returns: error_message 224 """ 225 init_group_name = masking_view_dict[utils.IG_NAME] 226 parent_sg_name = masking_view_dict[utils.PARENT_SG_NAME] 227 storagegroup_name = masking_view_dict[utils.SG_NAME] 228 connector = masking_view_dict[utils.CONNECTOR] 229 port_group_name = masking_view_dict[utils.PORTGROUPNAME] 230 LOG.info("Port Group in masking view operation: %(port_group_name)s.", 231 {'port_group_name': port_group_name}) 232 233 # get or create parent sg 234 error_message = self._get_or_create_storage_group( 235 serial_number, masking_view_dict, parent_sg_name, extra_specs, 236 parent=True) 237 if error_message: 238 return error_message 239 240 # get or create child sg 241 error_message = self._get_or_create_storage_group( 242 serial_number, masking_view_dict, storagegroup_name, extra_specs) 243 if error_message: 244 return error_message 245 246 __, error_message = self._check_port_group( 247 serial_number, port_group_name) 248 if error_message: 249 return error_message 250 251 init_group_name, error_message = (self._get_or_create_initiator_group( 252 serial_number, init_group_name, connector, extra_specs)) 253 if error_message: 254 return error_message 255 256 # Only after the components of the MV have been validated, 257 # move the volume from the default storage group to the 258 # masking view storage group. This is necessary before 259 # creating a new masking view. 260 error_message = self._move_vol_from_default_sg( 261 serial_number, masking_view_dict[utils.DEVICE_ID], 262 masking_view_dict[utils.VOL_NAME], default_sg_name, 263 storagegroup_name, extra_specs) 264 if error_message: 265 return error_message 266 267 error_message = self._check_add_child_sg_to_parent_sg( 268 serial_number, storagegroup_name, parent_sg_name, 269 masking_view_dict[utils.EXTRA_SPECS]) 270 if error_message: 271 return error_message 272 273 error_message = (self.create_masking_view( 274 serial_number, maskingview_name, parent_sg_name, 275 port_group_name, init_group_name, extra_specs)) 276 277 return error_message 278 279 def _validate_existing_masking_view( 280 self, serial_number, masking_view_dict, 281 maskingview_name, default_sg_name, extra_specs): 282 """Validate the components of an existing masking view. 283 284 :param serial_number: the array serial number 285 :param masking_view_dict: the masking view dict 286 :param maskingview_name: the amsking view name 287 :param default_sg_name: the default sg name 288 :param extra_specs: the extra specifications 289 :returns: storage_group_name -- string, msg -- string 290 """ 291 storage_group_name, msg = self._check_existing_storage_group( 292 serial_number, maskingview_name, default_sg_name, 293 masking_view_dict) 294 if not msg: 295 portgroup_name = self.rest.get_element_from_masking_view( 296 serial_number, maskingview_name, portgroup=True) 297 __, msg = self._check_port_group( 298 serial_number, portgroup_name) 299 if not msg: 300 initiator_group, msg = self._check_existing_initiator_group( 301 serial_number, maskingview_name, masking_view_dict, 302 storage_group_name, portgroup_name, extra_specs) 303 304 return storage_group_name, msg 305 306 def _check_add_child_sg_to_parent_sg( 307 self, serial_number, child_sg_name, parent_sg_name, extra_specs): 308 """Check adding a child storage group to a parent storage group. 309 310 :param serial_number: the array serial number 311 :param child_sg_name: the name of the child storage group 312 :param parent_sg_name: the name of the aprent storage group 313 :param extra_specs: the extra specifications 314 :returns: error_message or None 315 """ 316 msg = None 317 if self.rest.is_child_sg_in_parent_sg( 318 serial_number, child_sg_name, parent_sg_name): 319 LOG.info("Child sg: %(child_sg)s is already part " 320 "of parent storage group %(parent_sg)s.", 321 {'child_sg': child_sg_name, 322 'parent_sg': parent_sg_name}) 323 else: 324 try: 325 self.add_child_sg_to_parent_sg( 326 serial_number, child_sg_name, parent_sg_name, extra_specs) 327 except Exception as e: 328 msg = ("Exception adding child sg %(child_sg)s to " 329 "%(parent_sg)s. Exception received was %(e)s" 330 % {'child_sg': child_sg_name, 331 'parent_sg': parent_sg_name, 332 'e': six.text_type(e)}) 333 LOG.error(msg) 334 return msg 335 336 def add_child_sg_to_parent_sg( 337 self, serial_number, child_sg_name, parent_sg_name, extra_specs, 338 default_version=True 339 ): 340 """Add a child storage group to a parent storage group. 341 342 :param default_version: the default uv4 version 343 :param serial_number: the array serial number 344 :param child_sg_name: the name of the child storage group 345 :param parent_sg_name: the name of the aprent storage group 346 :param extra_specs: the extra specifications 347 """ 348 start_time = time.time() 349 350 @coordination.synchronized("emc-sg-{child_sg}") 351 @coordination.synchronized("emc-sg-{parent_sg}") 352 def do_add_sg_to_sg(child_sg, parent_sg): 353 # Check if another process has added the child to the 354 # parent sg while this process was waiting for the lock 355 if self.rest.is_child_sg_in_parent_sg( 356 serial_number, child_sg_name, parent_sg_name): 357 pass 358 else: 359 if default_version: 360 self.rest.add_child_sg_to_parent_sg( 361 serial_number, child_sg, parent_sg, extra_specs) 362 else: 363 self.rest.add_empty_child_sg_to_parent_sg( 364 serial_number, child_sg, parent_sg, extra_specs) 365 366 do_add_sg_to_sg(child_sg_name, parent_sg_name) 367 368 LOG.debug("Add child to storagegroup took: %(delta)s H:MM:SS.", 369 {'delta': self.utils.get_time_delta(start_time, 370 time.time())}) 371 LOG.info("Added child sg: %(child_name)s to parent storage " 372 "group %(parent_name)s.", 373 {'child_name': child_sg_name, 'parent_name': parent_sg_name}) 374 375 def _get_or_create_storage_group( 376 self, serial_number, masking_view_dict, storagegroup_name, 377 extra_specs, parent=False): 378 """Get or create a storage group for a masking view. 379 380 :param serial_number: the array serial number 381 :param masking_view_dict: the masking view dict 382 :param storagegroup_name: the storage group name 383 :param extra_specs: the extra specifications 384 :param parent: flag to indicate if this a parent storage group 385 :returns: msg -- string or None 386 """ 387 msg = None 388 srp = extra_specs[utils.SRP] 389 workload = extra_specs[utils.WORKLOAD] 390 if parent: 391 slo = None 392 else: 393 slo = extra_specs[utils.SLO] 394 do_disable_compression = ( 395 masking_view_dict[utils.DISABLECOMPRESSION]) 396 storagegroup = self.rest.get_storage_group( 397 serial_number, storagegroup_name) 398 if storagegroup is None: 399 storagegroup = self.provision.create_storage_group( 400 serial_number, storagegroup_name, srp, slo, workload, 401 extra_specs, do_disable_compression) 402 403 if storagegroup is None: 404 msg = ("Cannot get or create a storage group: " 405 "%(storagegroup_name)s for volume %(volume_name)s." 406 % {'storagegroup_name': storagegroup_name, 407 'volume_name': masking_view_dict[utils.VOL_NAME]}) 408 LOG.error(msg) 409 410 # If qos exists, update storage group to reflect qos parameters 411 if 'qos' in extra_specs: 412 self.rest.update_storagegroup_qos( 413 serial_number, storagegroup_name, extra_specs) 414 415 return msg 416 417 def _check_existing_storage_group( 418 self, serial_number, maskingview_name, 419 default_sg_name, masking_view_dict): 420 """Check if the masking view has the child storage group. 421 422 Get the parent storage group associated with a masking view and check 423 if the required child storage group is already a member. If not, get 424 or create the child storage group. 425 :param serial_number: the array serial number 426 :param maskingview_name: the masking view name 427 :param default_sg_name: the default sg name 428 :param masking_view_dict: the masking view dict 429 :returns: storage group name, msg 430 """ 431 msg = None 432 child_sg_name = masking_view_dict[utils.SG_NAME] 433 434 sg_from_mv = self.rest.get_element_from_masking_view( 435 serial_number, maskingview_name, storagegroup=True) 436 437 storagegroup = self.rest.get_storage_group(serial_number, sg_from_mv) 438 439 if not storagegroup: 440 msg = ("Cannot get storage group: %(sg_from_mv)s " 441 "from masking view %(masking_view)s." 442 % {'sg_from_mv': sg_from_mv, 443 'masking_view': maskingview_name}) 444 LOG.error(msg) 445 else: 446 check_child = self.rest.is_child_sg_in_parent_sg( 447 serial_number, child_sg_name, sg_from_mv) 448 child_sg = self.rest.get_storage_group( 449 serial_number, child_sg_name) 450 # Ensure the child sg can be retrieved 451 if check_child and not child_sg: 452 msg = ("Cannot get child storage group: %(sg_name)s " 453 "but it is listed as child of %(parent_sg)s" 454 % {'sg_name': child_sg_name, 'parent_sg': sg_from_mv}) 455 LOG.error(msg) 456 elif check_child and child_sg: 457 LOG.info("Retrieved child sg %(sg_name)s from %(mv_name)s", 458 {'sg_name': child_sg_name, 459 'mv_name': maskingview_name}) 460 else: 461 msg = self._get_or_create_storage_group( 462 serial_number, masking_view_dict, child_sg_name, 463 masking_view_dict[utils.EXTRA_SPECS]) 464 if not msg: 465 msg = self._move_vol_from_default_sg( 466 serial_number, masking_view_dict[utils.DEVICE_ID], 467 masking_view_dict[utils.VOL_NAME], default_sg_name, 468 child_sg_name, masking_view_dict[utils.EXTRA_SPECS]) 469 if not msg and not check_child: 470 msg = self._check_add_child_sg_to_parent_sg( 471 serial_number, child_sg_name, sg_from_mv, 472 masking_view_dict[utils.EXTRA_SPECS]) 473 474 return child_sg_name, msg 475 476 @coordination.synchronized("emc-sg-{source_storagegroup_name}") 477 @coordination.synchronized("emc-sg-{target_storagegroup_name}") 478 def move_volume_between_storage_groups( 479 self, serial_number, device_id, source_storagegroup_name, 480 target_storagegroup_name, extra_specs): 481 """Move a volume between storage groups. 482 483 :param serial_number: the array serial number 484 :param device_id: the device id 485 :param source_storagegroup_name: the source sg 486 :param target_storagegroup_name: the target sg 487 :param extra_specs: the extra specifications 488 """ 489 self.rest.move_volume_between_storage_groups( 490 serial_number, device_id, source_storagegroup_name, 491 target_storagegroup_name, extra_specs) 492 493 def _check_port_group(self, serial_number, portgroup_name): 494 """Check that you can get a port group. 495 496 :param serial_number: the array serial number 497 :param portgroup_name: the port group name 498 :returns: string -- msg, the error message 499 """ 500 msg = None 501 portgroup = self.rest.get_portgroup(serial_number, portgroup_name) 502 if portgroup is None: 503 msg = ("Cannot get port group: %(portgroup)s from the array " 504 "%(array)s. Portgroups must be pre-configured - please " 505 "check the array." 506 % {'portgroup': portgroup_name, 'array': serial_number}) 507 LOG.error(msg) 508 return portgroup_name, msg 509 510 def _get_or_create_initiator_group( 511 self, serial_number, init_group_name, connector, extra_specs): 512 """Retrieve or create an initiator group. 513 514 :param serial_number: the array serial number 515 :param init_group_name: the name of the initiator group 516 :param connector: the connector object 517 :param extra_specs: the extra specifications 518 :returns: name of the initiator group -- string, msg 519 """ 520 msg = None 521 initiator_names = self.find_initiator_names(connector) 522 LOG.debug("The initiator name(s) are: %(initiatorNames)s.", 523 {'initiatorNames': initiator_names}) 524 525 found_init_group = self._find_initiator_group( 526 serial_number, initiator_names) 527 528 # If you cannot find an initiator group that matches the connector 529 # info, create a new initiator group. 530 if found_init_group is None: 531 found_init_group = self._create_initiator_group( 532 serial_number, init_group_name, initiator_names, extra_specs) 533 LOG.info("Created new initiator group name: %(init_group_name)s.", 534 {'init_group_name': init_group_name}) 535 else: 536 LOG.info("Using existing initiator group name: " 537 "%(init_group_name)s.", 538 {'init_group_name': found_init_group}) 539 540 if found_init_group is None: 541 msg = ("Cannot get or create initiator group: " 542 "%(init_group_name)s. " 543 % {'init_group_name': init_group_name}) 544 LOG.error(msg) 545 546 return found_init_group, msg 547 548 def _check_existing_initiator_group( 549 self, serial_number, maskingview_name, masking_view_dict, 550 storagegroup_name, portgroup_name, extra_specs): 551 """Checks an existing initiator group in the masking view. 552 553 Check if the initiators in the initiator group match those in the 554 system. 555 :param serial_number: the array serial number 556 :param maskingview_name: name of the masking view 557 :param masking_view_dict: masking view dict 558 :param storagegroup_name: the storage group name 559 :param portgroup_name: the port group name 560 :param extra_specs: the extra specifications 561 :returns: ig_from_mv, msg 562 """ 563 msg = None 564 ig_from_mv = self.rest.get_element_from_masking_view( 565 serial_number, maskingview_name, host=True) 566 check_ig = masking_view_dict[utils.INITIATOR_CHECK] 567 568 if check_ig: 569 # First verify that the initiator group matches the initiators. 570 check, found_ig = self._verify_initiator_group_from_masking_view( 571 serial_number, maskingview_name, masking_view_dict, ig_from_mv, 572 storagegroup_name, portgroup_name, extra_specs) 573 if not check: 574 msg = ("Unable to verify initiator group: %(ig_name)s " 575 "in masking view %(maskingview_name)s." 576 % {'ig_name': ig_from_mv, 577 'maskingview_name': maskingview_name}) 578 LOG.error(msg) 579 return ig_from_mv, msg 580 581 def _check_adding_volume_to_storage_group( 582 self, serial_number, device_id, storagegroup_name, 583 volume_name, extra_specs): 584 """Check if a volume is part of an sg and add it if not. 585 586 :param serial_number: the array serial number 587 :param device_id: the device id 588 :param storagegroup_name: the storage group name 589 :param volume_name: volume name 590 :param extra_specs: extra specifications 591 :returns: msg 592 """ 593 msg = None 594 if self.rest.is_volume_in_storagegroup( 595 serial_number, device_id, storagegroup_name): 596 LOG.info("Volume: %(volume_name)s is already part " 597 "of storage group %(sg_name)s.", 598 {'volume_name': volume_name, 599 'sg_name': storagegroup_name}) 600 else: 601 try: 602 self.add_volume_to_storage_group( 603 serial_number, device_id, storagegroup_name, 604 volume_name, extra_specs) 605 except Exception as e: 606 msg = ("Exception adding volume %(vol)s to %(sg)s. " 607 "Exception received was %(e)s." 608 % {'vol': volume_name, 'sg': storagegroup_name, 609 'e': six.text_type(e)}) 610 LOG.error(msg) 611 return msg 612 613 def add_volume_to_storage_group( 614 self, serial_number, device_id, storagegroup_name, 615 volume_name, extra_specs): 616 """Add a volume to a storage group. 617 618 :param serial_number: array serial number 619 :param device_id: volume device id 620 :param storagegroup_name: storage group name 621 :param volume_name: volume name 622 :param extra_specs: extra specifications 623 """ 624 start_time = time.time() 625 626 @coordination.synchronized("emc-sg-{sg_name}") 627 def do_add_volume_to_sg(sg_name): 628 # Check if another process has added the volume to the 629 # sg while this process was waiting for the lock 630 if self.rest.is_volume_in_storagegroup( 631 serial_number, device_id, storagegroup_name): 632 LOG.info("Volume: %(volume_name)s is already part " 633 "of storage group %(sg_name)s.", 634 {'volume_name': volume_name, 635 'sg_name': storagegroup_name}) 636 else: 637 self.rest.add_vol_to_sg(serial_number, sg_name, 638 device_id, extra_specs) 639 do_add_volume_to_sg(storagegroup_name) 640 641 LOG.debug("Add volume to storagegroup took: %(delta)s H:MM:SS.", 642 {'delta': self.utils.get_time_delta(start_time, 643 time.time())}) 644 LOG.info("Added volume: %(vol_name)s to storage group %(sg_name)s.", 645 {'vol_name': volume_name, 'sg_name': storagegroup_name}) 646 647 def add_volumes_to_storage_group( 648 self, serial_number, list_device_id, storagegroup_name, 649 extra_specs): 650 """Add a volume to a storage group. 651 652 :param serial_number: array serial number 653 :param list_device_id: list of volume device id 654 :param storagegroup_name: storage group name 655 :param extra_specs: extra specifications 656 """ 657 if not list_device_id: 658 LOG.info("add_volumes_to_storage_group: No volumes to add") 659 return 660 start_time = time.time() 661 temp_device_id_list = list_device_id 662 663 @coordination.synchronized("emc-sg-{sg_name}") 664 def do_add_volume_to_sg(sg_name): 665 # Check if another process has added any volume to the 666 # sg while this process was waiting for the lock 667 volume_list = self.rest.get_volumes_in_storage_group( 668 serial_number, storagegroup_name) 669 for volume in volume_list: 670 if volume in temp_device_id_list: 671 LOG.info("Volume: %(volume_name)s is already part " 672 "of storage group %(sg_name)s.", 673 {'volume_name': volume, 674 'sg_name': storagegroup_name}) 675 # Remove this device id from the list 676 temp_device_id_list.remove(volume) 677 self.rest.add_vol_to_sg(serial_number, storagegroup_name, 678 temp_device_id_list, extra_specs) 679 do_add_volume_to_sg(storagegroup_name) 680 681 LOG.debug("Add volumes to storagegroup took: %(delta)s H:MM:SS.", 682 {'delta': self.utils.get_time_delta(start_time, 683 time.time())}) 684 LOG.info("Added volumes to storage group %(sg_name)s.", 685 {'sg_name': storagegroup_name}) 686 687 def remove_vol_from_storage_group( 688 self, serial_number, device_id, storagegroup_name, 689 volume_name, extra_specs): 690 """Remove a volume from a storage group. 691 692 :param serial_number: the array serial number 693 :param device_id: the volume device id 694 :param storagegroup_name: the name of the storage group 695 :param volume_name: the volume name 696 :param extra_specs: the extra specifications 697 :raises: VolumeBackendAPIException 698 """ 699 start_time = time.time() 700 701 self.rest.remove_vol_from_sg( 702 serial_number, storagegroup_name, device_id, extra_specs) 703 704 LOG.debug("Remove volume from storagegroup took: %(delta)s H:MM:SS.", 705 {'delta': self.utils.get_time_delta(start_time, 706 time.time())}) 707 708 check_vol = (self.rest.is_volume_in_storagegroup( 709 serial_number, device_id, storagegroup_name)) 710 if check_vol: 711 exception_message = (_( 712 "Failed to remove volume %(vol)s from SG: %(sg_name)s.") 713 % {'vol': volume_name, 'sg_name': storagegroup_name}) 714 LOG.error(exception_message) 715 raise exception.VolumeBackendAPIException( 716 data=exception_message) 717 718 def remove_volumes_from_storage_group( 719 self, serial_number, list_of_device_ids, 720 storagegroup_name, extra_specs): 721 """Remove multiple volumes from a storage group. 722 723 :param serial_number: the array serial number 724 :param list_of_device_ids: list of device ids 725 :param storagegroup_name: the name of the storage group 726 :param extra_specs: the extra specifications 727 :raises: VolumeBackendAPIException 728 """ 729 start_time = time.time() 730 731 @coordination.synchronized("emc-sg-{sg_name}") 732 def do_remove_volumes_from_storage_group(sg_name): 733 self.rest.remove_vol_from_sg( 734 serial_number, storagegroup_name, 735 list_of_device_ids, extra_specs) 736 737 LOG.debug("Remove volumes from storagegroup " 738 "took: %(delta)s H:MM:SS.", 739 {'delta': self.utils.get_time_delta(start_time, 740 time.time())}) 741 volume_list = self.rest.get_volumes_in_storage_group( 742 serial_number, storagegroup_name) 743 744 for device_id in list_of_device_ids: 745 if device_id in volume_list: 746 exception_message = (_( 747 "Failed to remove device " 748 "with id %(dev_id)s from SG: %(sg_name)s.") 749 % {'dev_id': device_id, 'sg_name': storagegroup_name}) 750 LOG.error(exception_message) 751 raise exception.VolumeBackendAPIException( 752 data=exception_message) 753 return do_remove_volumes_from_storage_group(storagegroup_name) 754 755 def find_initiator_names(self, connector): 756 """Check the connector object for initiators(ISCSI) or wwpns(FC). 757 758 :param connector: the connector object 759 :returns: list -- list of found initiator names 760 :raises: VolumeBackendAPIException 761 """ 762 foundinitiatornames = [] 763 name = 'initiator name' 764 if self.protocol.lower() == utils.ISCSI and connector['initiator']: 765 foundinitiatornames.append(connector['initiator']) 766 elif self.protocol.lower() == utils.FC: 767 if 'wwpns' in connector and connector['wwpns']: 768 for wwn in connector['wwpns']: 769 foundinitiatornames.append(wwn) 770 name = 'world wide port names' 771 else: 772 msg = (_("FC is the protocol but wwpns are " 773 "not supplied by OpenStack.")) 774 LOG.error(msg) 775 raise exception.VolumeBackendAPIException(data=msg) 776 777 if not foundinitiatornames: 778 msg = (_("Error finding %(name)s.") % {'name': name}) 779 LOG.error(msg) 780 raise exception.VolumeBackendAPIException(data=msg) 781 782 LOG.debug("Found %(name)s: %(initiator)s.", 783 {'name': name, 784 'initiator': foundinitiatornames}) 785 786 return foundinitiatornames 787 788 def _find_initiator_group(self, serial_number, initiator_names): 789 """Check to see if an initiator group already exists. 790 791 NOTE: An initiator/wwn can only belong to one initiator group. 792 If we were to attempt to create one with an initiator/wwn that is 793 already belonging to another initiator group, it would fail. 794 :param serial_number: the array serial number 795 :param initiator_names: the list of initiator names 796 :returns: initiator group name -- string or None 797 """ 798 ig_name = None 799 for initiator in initiator_names: 800 params = {'initiator_hba': initiator.lower()} 801 found_init = self.rest.get_initiator_list(serial_number, params) 802 if found_init: 803 ig_name = self.rest.get_initiator_group_from_initiator( 804 serial_number, found_init[0]) 805 break 806 return ig_name 807 808 def create_masking_view( 809 self, serial_number, maskingview_name, storagegroup_name, 810 port_group_name, init_group_name, extra_specs): 811 """Create a new masking view. 812 813 :param serial_number: the array serial number 814 :param maskingview_name: the masking view name 815 :param storagegroup_name: the storage group name 816 :param port_group_name: the port group 817 :param init_group_name: the initiator group 818 :param extra_specs: extra specifications 819 :returns: error_message -- string or None 820 """ 821 error_message = None 822 try: 823 self.rest.create_masking_view( 824 serial_number, maskingview_name, storagegroup_name, 825 port_group_name, init_group_name, extra_specs) 826 827 except Exception as e: 828 error_message = ("Error creating new masking view. Exception " 829 "received: %(e)s" % {'e': six.text_type(e)}) 830 return error_message 831 832 def check_if_rollback_action_for_masking_required( 833 self, serial_number, volume, device_id, rollback_dict): 834 """Rollback action for volumes with an associated service level. 835 836 We need to be able to return the volume to the default storage group 837 if anything has gone wrong. The volume can also potentially belong to 838 a storage group that is not the default depending on where 839 the exception occurred. We also may need to clean up any unused 840 initiator groups. 841 :param serial_number: the array serial number 842 :param volume: the volume object 843 :param device_id: the device id 844 :param rollback_dict: the rollback dict 845 :returns: error message -- string, or None 846 :raises: VolumeBackendAPIException 847 """ 848 message = None 849 # Check if ig has been created. If so, check for other 850 # masking views associated with the ig. If none, delete the ig. 851 self._check_ig_rollback( 852 serial_number, rollback_dict['init_group_name'], 853 rollback_dict['connector']) 854 try: 855 found_sg_name_list = ( 856 self.rest.get_storage_groups_from_volume( 857 serial_number, rollback_dict['device_id'])) 858 # Volume is not associated with any storage group so add 859 # it back to the default. 860 if not found_sg_name_list: 861 error_message = self._check_adding_volume_to_storage_group( 862 serial_number, device_id, 863 rollback_dict['default_sg_name'], 864 rollback_dict[utils.VOL_NAME], 865 rollback_dict[utils.EXTRA_SPECS]) 866 if error_message: 867 LOG.error(error_message) 868 message = (_("Rollback")) 869 elif 'isLiveMigration' in rollback_dict and ( 870 rollback_dict['isLiveMigration'] is True): 871 # Live migration case. 872 # Remove from nonfast storage group to fast sg 873 self.failed_live_migration(rollback_dict, found_sg_name_list, 874 rollback_dict[utils.EXTRA_SPECS]) 875 else: 876 LOG.info("Volume %(vol_id)s is in %(list_size)d storage" 877 "groups. The storage groups are %(found_sg_list)s.", 878 {'vol_id': volume.id, 879 'list_size': len(found_sg_name_list), 880 'found_sg_list': found_sg_name_list}) 881 882 # Check the name, see if it is the default storage group 883 # or another. 884 sg_found = False 885 for found_sg_name in found_sg_name_list: 886 if found_sg_name == rollback_dict['default_sg_name']: 887 sg_found = True 888 if not sg_found: 889 # Remove it from its current storage group and return it 890 # to its default storage group if slo is defined. 891 self.remove_and_reset_members( 892 serial_number, volume, device_id, 893 rollback_dict['volume_name'], 894 rollback_dict['extra_specs'], True, 895 rollback_dict['connector']) 896 message = (_("Rollback - Volume in another storage " 897 "group besides default storage group.")) 898 except Exception as e: 899 error_message = (_( 900 "Rollback for Volume: %(volume_name)s has failed. " 901 "Please contact your system administrator to manually return " 902 "your volume to the default storage group for its slo. " 903 "Exception received: %(e)s") 904 % {'volume_name': rollback_dict['volume_name'], 905 'e': six.text_type(e)}) 906 LOG.exception(error_message) 907 raise exception.VolumeBackendAPIException(data=error_message) 908 return message 909 910 def _verify_initiator_group_from_masking_view( 911 self, serial_number, maskingview_name, maskingview_dict, 912 ig_from_mv, storagegroup_name, portgroup_name, extra_specs): 913 """Check that the initiator group contains the correct initiators. 914 915 If using an existing masking view check that the initiator group 916 contains the correct initiators. If it does not contain the correct 917 initiators then we delete the initiator group from the masking view, 918 re-create it with the correct initiators and add it to the masking view 919 NOTE: VMAX does not support ModifyMaskingView so we must first 920 delete the masking view and recreate it. 921 :param serial_number: the array serial number 922 :param maskingview_name: name of the masking view 923 :param maskingview_dict: the masking view dict 924 :param ig_from_mv: the initiator group name 925 :param storagegroup_name: the storage group 926 :param portgroup_name: the port group 927 :param extra_specs: extra specifications 928 :returns: bool, found_ig_from_connector 929 """ 930 connector = maskingview_dict['connector'] 931 initiator_names = self.find_initiator_names(connector) 932 found_ig_from_connector = self._find_initiator_group( 933 serial_number, initiator_names) 934 935 if found_ig_from_connector != ig_from_mv: 936 check_ig = self.rest.get_initiator_group( 937 serial_number, initiator_group=ig_from_mv) 938 if check_ig: 939 if found_ig_from_connector is None: 940 # If the name of the current initiator group from the 941 # masking view matches the igGroupName supplied for the 942 # new group, the existing ig needs to be deleted before 943 # the new one with the correct initiators can be created. 944 if maskingview_dict['init_group_name'] == ig_from_mv: 945 # Masking view needs to be deleted before IG 946 # can be deleted. 947 self.rest.delete_masking_view( 948 serial_number, maskingview_name) 949 self.rest.delete_initiator_group( 950 serial_number, ig_from_mv) 951 found_ig_from_connector = ( 952 self._create_initiator_group( 953 serial_number, ig_from_mv, initiator_names, 954 extra_specs)) 955 if (found_ig_from_connector is not None and 956 storagegroup_name is not None and 957 portgroup_name is not None): 958 # Existing masking view (if still on the array) needs 959 # to be deleted before a new one can be created. 960 try: 961 self.rest.delete_masking_view( 962 serial_number, maskingview_name) 963 except Exception: 964 pass 965 error_message = ( 966 self.create_masking_view( 967 serial_number, maskingview_name, storagegroup_name, 968 portgroup_name, 969 maskingview_dict['init_group_name'], 970 extra_specs)) 971 if not error_message: 972 LOG.debug( 973 "The old masking view has been replaced: " 974 "%(maskingview_name)s.", 975 {'maskingview_name': maskingview_name}) 976 else: 977 LOG.error( 978 "One of the components of the original masking view " 979 "%(maskingview_name)s cannot be retrieved so " 980 "please contact your system administrator to check " 981 "that the correct initiator(s) are part of masking.", 982 {'maskingview_name': maskingview_name}) 983 return False 984 return True, found_ig_from_connector 985 986 def _create_initiator_group( 987 self, serial_number, init_group_name, initiator_names, 988 extra_specs): 989 """Create a new initiator group. 990 991 Given a list of initiators, create a new initiator group. 992 :param serial_number: array serial number 993 :param init_group_name: the name for the initiator group 994 :param initiator_names: initaitor names 995 :param extra_specs: the extra specifications 996 :returns: the initiator group name 997 """ 998 self.rest.create_initiator_group( 999 serial_number, init_group_name, initiator_names, extra_specs) 1000 return init_group_name 1001 1002 def _check_ig_rollback( 1003 self, serial_number, init_group_name, connector, force=False): 1004 """Check if rollback action is required on an initiator group. 1005 1006 If anything goes wrong on a masking view creation, we need to check if 1007 the process created a now-stale initiator group before failing, i.e. 1008 an initiator group a) matching the name used in the mv process and 1009 b) not associated with any other masking views. 1010 If a stale ig exists, delete the ig. 1011 :param serial_number: the array serial number 1012 :param init_group_name: the initiator group name 1013 :param connector: the connector object 1014 :param force: force a delete even if no entry in login table 1015 """ 1016 initiator_names = self.find_initiator_names(connector) 1017 found_ig_name = self._find_initiator_group( 1018 serial_number, initiator_names) 1019 if found_ig_name: 1020 if found_ig_name == init_group_name: 1021 force = True 1022 if force: 1023 found_ig_name = init_group_name 1024 host = init_group_name.split("-")[1] 1025 LOG.debug("Searching for masking views associated with " 1026 "%(init_group_name)s", 1027 {'init_group_name': init_group_name}) 1028 self._last_volume_delete_initiator_group( 1029 serial_number, found_ig_name, host) 1030 1031 @coordination.synchronized("emc-vol-{device_id}") 1032 def remove_and_reset_members( 1033 self, serial_number, volume, device_id, volume_name, 1034 extra_specs, reset=True, connector=None, async_grp=None): 1035 """This is called on a delete, unmap device or rollback. 1036 1037 :param serial_number: the array serial number 1038 :param volume: the volume object 1039 :param device_id: the volume device id 1040 :param volume_name: the volume name 1041 :param extra_specs: additional info 1042 :param reset: reset, return to original SG (optional) 1043 :param connector: the connector object (optional) 1044 :param async_grp: the async rep group (optional) 1045 """ 1046 self._cleanup_deletion( 1047 serial_number, volume, device_id, volume_name, 1048 extra_specs, connector, reset, async_grp) 1049 1050 def _cleanup_deletion( 1051 self, serial_number, volume, device_id, volume_name, 1052 extra_specs, connector, reset, async_grp): 1053 """Prepare a volume for a delete operation. 1054 1055 :param serial_number: the array serial number 1056 :param volume: the volume object 1057 :param device_id: the volume device id 1058 :param volume_name: the volume name 1059 :param extra_specs: the extra specifications 1060 :param connector: the connector object 1061 :param async_grp: the async rep group 1062 """ 1063 move = False 1064 short_host_name = None 1065 storagegroup_names = (self.rest.get_storage_groups_from_volume( 1066 serial_number, device_id)) 1067 if storagegroup_names: 1068 if async_grp is not None: 1069 for index, sg in enumerate(storagegroup_names): 1070 if sg == async_grp: 1071 storagegroup_names.pop(index) 1072 if len(storagegroup_names) == 1 and reset is True: 1073 move = True 1074 elif connector is not None and reset is True: 1075 short_host_name = self.utils.get_host_short_name( 1076 connector['host']) 1077 move = True 1078 if short_host_name: 1079 for sg_name in storagegroup_names: 1080 if short_host_name in sg_name: 1081 self.remove_volume_from_sg( 1082 serial_number, device_id, volume_name, sg_name, 1083 extra_specs, connector, move) 1084 break 1085 else: 1086 for sg_name in storagegroup_names: 1087 self.remove_volume_from_sg( 1088 serial_number, device_id, volume_name, sg_name, 1089 extra_specs, connector, move) 1090 if reset is True and move is False: 1091 self.add_volume_to_default_storage_group( 1092 serial_number, device_id, volume_name, 1093 extra_specs, volume=volume) 1094 1095 def remove_volume_from_sg( 1096 self, serial_number, device_id, vol_name, storagegroup_name, 1097 extra_specs, connector=None, move=False): 1098 """Remove a volume from a storage group. 1099 1100 :param serial_number: the array serial number 1101 :param device_id: the volume device id 1102 :param vol_name: the volume name 1103 :param storagegroup_name: the storage group name 1104 :param extra_specs: the extra specifications 1105 :param connector: the connector object 1106 :param move: flag to indicate if move should be used instead of remove 1107 """ 1108 masking_list = self.rest.get_masking_views_from_storage_group( 1109 serial_number, storagegroup_name) 1110 if not masking_list: 1111 LOG.debug("No masking views associated with storage group " 1112 "%(sg_name)s", {'sg_name': storagegroup_name}) 1113 1114 @coordination.synchronized("emc-sg-{sg_name}") 1115 def do_remove_volume_from_sg(sg_name): 1116 # Make sure volume hasn't been recently removed from the sg 1117 if self.rest.is_volume_in_storagegroup( 1118 serial_number, device_id, sg_name): 1119 num_vol_in_sg = self.rest.get_num_vols_in_sg( 1120 serial_number, sg_name) 1121 LOG.debug("There are %(num_vol)d volumes in the " 1122 "storage group %(sg_name)s.", 1123 {'num_vol': num_vol_in_sg, 1124 'sg_name': sg_name}) 1125 1126 if num_vol_in_sg == 1: 1127 # Last volume in the storage group - delete sg. 1128 self._last_vol_in_sg( 1129 serial_number, device_id, vol_name, sg_name, 1130 extra_specs, move) 1131 else: 1132 # Not the last volume so remove it from storage group 1133 self._multiple_vols_in_sg( 1134 serial_number, device_id, sg_name, vol_name, 1135 extra_specs, move) 1136 else: 1137 LOG.info("Volume with device_id %(dev)s is no longer a " 1138 "member of %(sg)s.", 1139 {'dev': device_id, 'sg': sg_name}) 1140 1141 return do_remove_volume_from_sg(storagegroup_name) 1142 else: 1143 # Need to lock masking view when we are locking the storage 1144 # group to avoid possible deadlock situations from concurrent 1145 # processes 1146 masking_name = masking_list[0] 1147 parent_sg_name = self.rest.get_element_from_masking_view( 1148 serial_number, masking_name, storagegroup=True) 1149 1150 @coordination.synchronized("emc-mv-{parent_name}") 1151 @coordination.synchronized("emc-mv-{mv_name}") 1152 @coordination.synchronized("emc-sg-{sg_name}") 1153 def do_remove_volume_from_sg(mv_name, sg_name, parent_name): 1154 # Make sure volume hasn't been recently removed from the sg 1155 is_vol = self.rest.is_volume_in_storagegroup( 1156 serial_number, device_id, sg_name) 1157 if is_vol: 1158 num_vol_in_sg = self.rest.get_num_vols_in_sg( 1159 serial_number, sg_name) 1160 LOG.debug( 1161 "There are %(num_vol)d volumes in the storage group " 1162 "%(sg_name)s associated with %(mv_name)s. Parent " 1163 "storagegroup is %(parent)s.", 1164 {'num_vol': num_vol_in_sg, 'sg_name': sg_name, 1165 'mv_name': mv_name, 'parent': parent_name}) 1166 1167 if num_vol_in_sg == 1: 1168 # Last volume in the storage group - delete sg. 1169 self._last_vol_in_sg( 1170 serial_number, device_id, vol_name, sg_name, 1171 extra_specs, move, connector) 1172 else: 1173 # Not the last volume so remove it from storage group 1174 self._multiple_vols_in_sg( 1175 serial_number, device_id, sg_name, vol_name, 1176 extra_specs, move) 1177 else: 1178 LOG.info("Volume with device_id %(dev)s is no longer a " 1179 "member of %(sg)s", 1180 {'dev': device_id, 'sg': sg_name}) 1181 1182 return do_remove_volume_from_sg(masking_name, storagegroup_name, 1183 parent_sg_name) 1184 1185 def _last_vol_in_sg(self, serial_number, device_id, volume_name, 1186 storagegroup_name, extra_specs, move, connector=None): 1187 """Steps if the volume is the last in a storage group. 1188 1189 1. Check if the volume is in a masking view. 1190 2. If it is in a masking view, check if it is the last volume in the 1191 masking view or just this child storage group. 1192 3. If it is last in the masking view, delete the masking view, 1193 delete the initiator group if there are no other masking views 1194 associated with it, and delete the both the current storage group 1195 and its parent group. 1196 4. Otherwise, remove the volume and delete the child storage group. 1197 5. If it is not in a masking view, delete the storage group. 1198 :param serial_number: array serial number 1199 :param device_id: volume device id 1200 :param volume_name: volume name 1201 :param storagegroup_name: storage group name 1202 :param extra_specs: extra specifications 1203 :param move: flag to indicate a move instead of remove 1204 :param connector: the connector object 1205 :returns: status -- bool 1206 """ 1207 LOG.debug("Only one volume remains in storage group " 1208 "%(sgname)s. Driver will attempt cleanup.", 1209 {'sgname': storagegroup_name}) 1210 maskingview_list = self.rest.get_masking_views_from_storage_group( 1211 serial_number, storagegroup_name) 1212 if not bool(maskingview_list): 1213 status = self._last_vol_no_masking_views( 1214 serial_number, storagegroup_name, device_id, volume_name, 1215 extra_specs, move) 1216 else: 1217 status = self._last_vol_masking_views( 1218 serial_number, storagegroup_name, maskingview_list, 1219 device_id, volume_name, extra_specs, connector, move) 1220 return status 1221 1222 def _last_vol_no_masking_views(self, serial_number, storagegroup_name, 1223 device_id, volume_name, extra_specs, move): 1224 """Remove the last vol from an sg not associated with an mv. 1225 1226 Helper function for removing the last vol from a storage group 1227 which is not associated with a masking view. 1228 :param serial_number: the array serial number 1229 :param storagegroup_name: the storage group name 1230 :param device_id: the device id 1231 :param volume_name: the volume name 1232 :param extra_specs: the extra specifications 1233 :param move: flag to indicate a move instead of remove 1234 :returns: status -- bool 1235 """ 1236 # Check if storage group is a child sg: 1237 parent_sg = self.get_parent_sg_from_child( 1238 serial_number, storagegroup_name) 1239 if parent_sg is None: 1240 # Move the volume back to the default storage group, if required 1241 if move: 1242 self.add_volume_to_default_storage_group( 1243 serial_number, device_id, volume_name, 1244 extra_specs, src_sg=storagegroup_name) 1245 # Delete the storage group. 1246 self.rest.delete_storage_group(serial_number, storagegroup_name) 1247 status = True 1248 else: 1249 num_vols_parent = self.rest.get_num_vols_in_sg( 1250 serial_number, parent_sg) 1251 if num_vols_parent == 1: 1252 self._delete_cascaded_storage_groups( 1253 serial_number, storagegroup_name, parent_sg, 1254 extra_specs, device_id, move) 1255 else: 1256 self._remove_last_vol_and_delete_sg( 1257 serial_number, device_id, volume_name, 1258 storagegroup_name, extra_specs, parent_sg, move) 1259 status = True 1260 return status 1261 1262 def _last_vol_masking_views( 1263 self, serial_number, storagegroup_name, maskingview_list, 1264 device_id, volume_name, extra_specs, connector, move): 1265 """Remove the last vol from an sg associated with masking views. 1266 1267 Helper function for removing the last vol from a storage group 1268 which is associated with one or more masking views. 1269 :param serial_number: the array serial number 1270 :param storagegroup_name: the storage group name 1271 :param maskingview_list: the liast of masking views 1272 :param device_id: the device id 1273 :param volume_name: the volume name 1274 :param extra_specs: the extra specifications 1275 :param move: flag to indicate a move instead of remove 1276 :returns: status -- bool 1277 """ 1278 status = False 1279 for mv in maskingview_list: 1280 num_vols_in_mv, parent_sg_name = ( 1281 self._get_num_vols_from_mv(serial_number, mv)) 1282 # If the volume is the last in the masking view, full cleanup 1283 if num_vols_in_mv == 1: 1284 self._delete_mv_ig_and_sg( 1285 serial_number, device_id, mv, storagegroup_name, 1286 parent_sg_name, connector, move, extra_specs) 1287 else: 1288 self._remove_last_vol_and_delete_sg( 1289 serial_number, device_id, volume_name, 1290 storagegroup_name, extra_specs, parent_sg_name, move) 1291 status = True 1292 return status 1293 1294 def get_parent_sg_from_child(self, serial_number, storagegroup_name): 1295 """Given a storage group name, get its parent storage group, if any. 1296 1297 :param serial_number: the array serial number 1298 :param storagegroup_name: the name of the storage group 1299 :returns: the parent storage group name, or None 1300 """ 1301 parent_sg_name = None 1302 storagegroup = self.rest.get_storage_group( 1303 serial_number, storagegroup_name) 1304 if storagegroup and storagegroup.get('parent_storage_group'): 1305 parent_sg_name = storagegroup['parent_storage_group'][0] 1306 return parent_sg_name 1307 1308 def _get_num_vols_from_mv(self, serial_number, maskingview_name): 1309 """Get the total number of volumes associated with a masking view. 1310 1311 :param serial_number: the array serial number 1312 :param maskingview_name: the name of the masking view 1313 :returns: num_vols, parent_sg_name 1314 """ 1315 parent_sg_name = self.rest.get_element_from_masking_view( 1316 serial_number, maskingview_name, storagegroup=True) 1317 num_vols = self.rest.get_num_vols_in_sg(serial_number, parent_sg_name) 1318 return num_vols, parent_sg_name 1319 1320 def _multiple_vols_in_sg(self, serial_number, device_id, storagegroup_name, 1321 volume_name, extra_specs, move): 1322 """Remove the volume from the SG. 1323 1324 If the volume is not the last in the storage group, 1325 remove the volume from the SG and leave the sg on the array. 1326 :param serial_number: array serial number 1327 :param device_id: volume device id 1328 :param volume_name: volume name 1329 :param storagegroup_name: storage group name 1330 :param move: flag to indicate a move instead of remove 1331 :param extra_specs: extra specifications 1332 """ 1333 if move: 1334 self.add_volume_to_default_storage_group( 1335 serial_number, device_id, volume_name, 1336 extra_specs, src_sg=storagegroup_name) 1337 else: 1338 self.remove_vol_from_storage_group( 1339 serial_number, device_id, storagegroup_name, 1340 volume_name, extra_specs) 1341 1342 LOG.debug( 1343 "Volume %(volume_name)s successfully moved/ removed from " 1344 "storage group %(sg)s.", 1345 {'volume_name': volume_name, 'sg': storagegroup_name}) 1346 1347 num_vol_in_sg = self.rest.get_num_vols_in_sg( 1348 serial_number, storagegroup_name) 1349 LOG.debug("There are %(num_vol)d volumes remaining in the storage " 1350 "group %(sg_name)s.", 1351 {'num_vol': num_vol_in_sg, 1352 'sg_name': storagegroup_name}) 1353 1354 def _delete_cascaded_storage_groups(self, serial_number, child_sg_name, 1355 parent_sg_name, extra_specs, 1356 device_id, move): 1357 """Delete a child and parent storage groups. 1358 1359 :param serial_number: the array serial number 1360 :param child_sg_name: the child storage group name 1361 :param parent_sg_name: the parent storage group name 1362 :param extra_specs: the extra specifications 1363 :param device_id: the volume device id 1364 :param move: flag to indicate if volume should be moved to default sg 1365 """ 1366 if move: 1367 self.add_volume_to_default_storage_group( 1368 serial_number, device_id, "", 1369 extra_specs, src_sg=child_sg_name) 1370 if child_sg_name != parent_sg_name: 1371 self.rest.delete_storage_group(serial_number, parent_sg_name) 1372 LOG.debug("Storage Group %(storagegroup_name)s " 1373 "successfully deleted.", 1374 {'storagegroup_name': parent_sg_name}) 1375 self.rest.delete_storage_group(serial_number, child_sg_name) 1376 1377 LOG.debug("Storage Group %(storagegroup_name)s successfully deleted.", 1378 {'storagegroup_name': child_sg_name}) 1379 1380 def _delete_mv_ig_and_sg( 1381 self, serial_number, device_id, masking_view, storagegroup_name, 1382 parent_sg_name, connector, move, extra_specs): 1383 """Delete the masking view, storage groups and initiator group. 1384 1385 :param serial_number: array serial number 1386 :param device_id: the device id 1387 :param masking_view: masking view name 1388 :param storagegroup_name: storage group name 1389 :param parent_sg_name: the parent storage group name 1390 :param connector: the connector object 1391 :param move: flag to indicate if the volume should be moved 1392 :param extra_specs: the extra specifications 1393 """ 1394 host = (self.utils.get_host_short_name(connector['host']) 1395 if connector else None) 1396 1397 initiatorgroup = self.rest.get_element_from_masking_view( 1398 serial_number, masking_view, host=True) 1399 self._last_volume_delete_masking_view(serial_number, masking_view) 1400 self._last_volume_delete_initiator_group( 1401 serial_number, initiatorgroup, host) 1402 self._delete_cascaded_storage_groups( 1403 serial_number, storagegroup_name, parent_sg_name, 1404 extra_specs, device_id, move) 1405 1406 def _last_volume_delete_masking_view(self, serial_number, masking_view): 1407 """Delete the masking view. 1408 1409 Delete the masking view if the volume is the last one in the 1410 storage group. 1411 :param serial_number: the array serial number 1412 :param masking_view: masking view name 1413 """ 1414 LOG.debug("Last volume in the storage group, deleting masking view " 1415 "%(maskingview_name)s.", {'maskingview_name': masking_view}) 1416 self.rest.delete_masking_view(serial_number, masking_view) 1417 LOG.info("Masking view %(maskingview)s successfully deleted.", 1418 {'maskingview': masking_view}) 1419 1420 def add_volume_to_default_storage_group( 1421 self, serial_number, device_id, volume_name, 1422 extra_specs, src_sg=None, volume=None): 1423 """Return volume to its default storage group. 1424 1425 :param serial_number: the array serial number 1426 :param device_id: the volume device id 1427 :param volume_name: the volume name 1428 :param extra_specs: the extra specifications 1429 :param src_sg: the source storage group, if any 1430 :param volume: the volume object 1431 """ 1432 do_disable_compression = self.utils.is_compression_disabled( 1433 extra_specs) 1434 rep_enabled = self.utils.is_replication_enabled(extra_specs) 1435 rep_mode = extra_specs.get(utils.REP_MODE, None) 1436 if self.rest.is_next_gen_array(serial_number): 1437 extra_specs[utils.WORKLOAD] = 'NONE' 1438 storagegroup_name = self.get_or_create_default_storage_group( 1439 serial_number, extra_specs[utils.SRP], extra_specs[utils.SLO], 1440 extra_specs[utils.WORKLOAD], extra_specs, do_disable_compression, 1441 rep_enabled, rep_mode) 1442 if src_sg is not None: 1443 # Need to lock the default storage group 1444 @coordination.synchronized("emc-sg-{default_sg_name}") 1445 def _move_vol_to_default_sg(default_sg_name): 1446 self.rest.move_volume_between_storage_groups( 1447 serial_number, device_id, src_sg, 1448 default_sg_name, extra_specs, force=True) 1449 _move_vol_to_default_sg(storagegroup_name) 1450 else: 1451 self._check_adding_volume_to_storage_group( 1452 serial_number, device_id, storagegroup_name, volume_name, 1453 extra_specs) 1454 if volume: 1455 # Need to check if the volume needs to be returned to a 1456 # generic volume group. This may be necessary in a force-detach 1457 # situation. 1458 if volume.group_id is not None: 1459 vol_grp_name = self.provision.get_or_create_volume_group( 1460 serial_number, volume.group, extra_specs) 1461 self._check_adding_volume_to_storage_group( 1462 serial_number, device_id, 1463 vol_grp_name, volume_name, extra_specs) 1464 1465 def get_or_create_default_storage_group( 1466 self, serial_number, srp, slo, workload, extra_specs, 1467 do_disable_compression=False, is_re=False, rep_mode=None): 1468 """Get or create a default storage group. 1469 1470 :param serial_number: the array serial number 1471 :param srp: the SRP name 1472 :param slo: the SLO 1473 :param workload: the workload 1474 :param extra_specs: extra specifications 1475 :param do_disable_compression: flag for compression 1476 :param is_re: is replication enabled 1477 :param rep_mode: flag to indicate replication mode 1478 :returns: storagegroup_name 1479 :raises: VolumeBackendAPIException 1480 """ 1481 storagegroup, storagegroup_name = ( 1482 self.rest.get_vmax_default_storage_group( 1483 serial_number, srp, slo, workload, do_disable_compression, 1484 is_re, rep_mode)) 1485 if storagegroup is None: 1486 self.provision.create_storage_group( 1487 serial_number, storagegroup_name, srp, slo, workload, 1488 extra_specs, do_disable_compression) 1489 else: 1490 # Check that SG is not part of a masking view 1491 LOG.info("Using existing default storage group") 1492 masking_views = self.rest.get_masking_views_from_storage_group( 1493 serial_number, storagegroup_name) 1494 if masking_views: 1495 exception_message = (_( 1496 "Default storage group %(sg_name)s is part of masking " 1497 "views %(mvs)s. Please remove it from all masking views") 1498 % {'sg_name': storagegroup_name, 'mvs': masking_views}) 1499 LOG.error(exception_message) 1500 raise exception.VolumeBackendAPIException( 1501 data=exception_message) 1502 # If qos exists, update storage group to reflect qos parameters 1503 if 'qos' in extra_specs: 1504 self.rest.update_storagegroup_qos( 1505 serial_number, storagegroup_name, extra_specs) 1506 1507 return storagegroup_name 1508 1509 def _remove_last_vol_and_delete_sg( 1510 self, serial_number, device_id, volume_name, 1511 storagegroup_name, extra_specs, parent_sg_name=None, move=False): 1512 """Remove the last volume and delete the storage group. 1513 1514 If the storage group is a child of another storage group, 1515 it must be removed from the parent before deletion. 1516 :param serial_number: the array serial number 1517 :param device_id: the volume device id 1518 :param volume_name: the volume name 1519 :param storagegroup_name: the sg name 1520 :param extra_specs: extra specifications 1521 :param parent_sg_name: the parent sg name 1522 """ 1523 if move: 1524 self.add_volume_to_default_storage_group( 1525 serial_number, device_id, volume_name, 1526 extra_specs, src_sg=storagegroup_name) 1527 else: 1528 self.remove_vol_from_storage_group( 1529 serial_number, device_id, storagegroup_name, volume_name, 1530 extra_specs) 1531 1532 LOG.debug("Remove the last volume %(volumeName)s completed " 1533 "successfully.", {'volumeName': volume_name}) 1534 if parent_sg_name: 1535 self.rest.remove_child_sg_from_parent_sg( 1536 serial_number, storagegroup_name, parent_sg_name, 1537 extra_specs) 1538 1539 self.rest.delete_storage_group(serial_number, storagegroup_name) 1540 1541 def _last_volume_delete_initiator_group( 1542 self, serial_number, initiatorgroup_name, host): 1543 """Delete the initiator group. 1544 1545 Delete the Initiator group if it has been created by the VMAX driver, 1546 and if there are no masking views associated with it. 1547 :param serial_number: the array serial number 1548 :param initiatorgroup_name: initiator group name 1549 :param host: the short name of the host 1550 """ 1551 if host is not None: 1552 protocol = self.utils.get_short_protocol_type(self.protocol) 1553 default_ig_name = ("OS-%(shortHostName)s-%(protocol)s-IG" 1554 % {'shortHostName': host, 1555 'protocol': protocol}) 1556 1557 if initiatorgroup_name == default_ig_name: 1558 maskingview_names = ( 1559 self.rest.get_masking_views_by_initiator_group( 1560 serial_number, initiatorgroup_name)) 1561 if not maskingview_names: 1562 @coordination.synchronized("emc-ig-{ig_name}") 1563 def _delete_ig(ig_name): 1564 # Check initiator group hasn't been recently deleted 1565 ig_details = self.rest.get_initiator_group( 1566 serial_number, ig_name) 1567 if ig_details: 1568 LOG.debug( 1569 "Last volume associated with the initiator " 1570 "group - deleting the associated initiator " 1571 "group %(initiatorgroup_name)s.", 1572 {'initiatorgroup_name': initiatorgroup_name}) 1573 self.rest.delete_initiator_group( 1574 serial_number, initiatorgroup_name) 1575 _delete_ig(initiatorgroup_name) 1576 else: 1577 LOG.warning("Initiator group %(ig_name)s is associated " 1578 "with masking views and can't be deleted. " 1579 "Number of associated masking view is: " 1580 "%(nmv)d.", 1581 {'ig_name': initiatorgroup_name, 1582 'nmv': len(maskingview_names)}) 1583 else: 1584 LOG.warning("Initiator group %(ig_name)s was " 1585 "not created by the VMAX driver so will " 1586 "not be deleted by the VMAX driver.", 1587 {'ig_name': initiatorgroup_name}) 1588 else: 1589 LOG.warning("Cannot get host name from connector object - " 1590 "initiator group %(ig_name)s will not be deleted.", 1591 {'ig_name': initiatorgroup_name}) 1592 1593 def pre_live_migration(self, source_nf_sg, source_sg, source_parent_sg, 1594 is_source_nf_sg, device_info_dict, extra_specs): 1595 """Run before any live migration operation. 1596 1597 :param source_nf_sg: The non fast storage group 1598 :param source_sg: The source storage group 1599 :param source_parent_sg: The parent storage group 1600 :param is_source_nf_sg: if the non fast storage group already exists 1601 :param device_info_dict: the data dict 1602 :param extra_specs: extra specifications 1603 """ 1604 if is_source_nf_sg is False: 1605 storage_group = self.rest.get_storage_group( 1606 device_info_dict['array'], source_nf_sg) 1607 if storage_group is None: 1608 self.provision.create_storage_group( 1609 device_info_dict['array'], source_nf_sg, None, None, None, 1610 extra_specs) 1611 self.add_child_sg_to_parent_sg( 1612 device_info_dict['array'], source_nf_sg, source_parent_sg, 1613 extra_specs, default_version=False) 1614 self.move_volume_between_storage_groups( 1615 device_info_dict['array'], device_info_dict['device_id'], 1616 source_sg, source_nf_sg, extra_specs) 1617 1618 def post_live_migration(self, device_info_dict, extra_specs): 1619 """Run after every live migration operation. 1620 1621 :param device_info_dict: : the data dict 1622 :param extra_specs: extra specifications 1623 """ 1624 array = device_info_dict['array'] 1625 source_sg = device_info_dict['source_sg'] 1626 # Delete fast storage group 1627 num_vol_in_sg = self.rest.get_num_vols_in_sg( 1628 array, source_sg) 1629 if num_vol_in_sg == 0: 1630 self.rest.remove_child_sg_from_parent_sg( 1631 array, source_sg, device_info_dict['source_parent_sg'], 1632 extra_specs) 1633 self.rest.delete_storage_group(array, source_sg) 1634 1635 def failed_live_migration(self, device_info_dict, 1636 source_storage_group_list, extra_specs): 1637 """This is run in the event of a failed live migration operation. 1638 1639 :param device_info_dict: the data dict 1640 :param source_storage_group_list: list of storage groups associated 1641 with the device 1642 :param extra_specs: extra specifications 1643 """ 1644 array = device_info_dict['array'] 1645 source_nf_sg = device_info_dict['source_nf_sg'] 1646 source_sg = device_info_dict['source_sg'] 1647 source_parent_sg = device_info_dict['source_parent_sg'] 1648 device_id = device_info_dict['device_id'] 1649 for sg in source_storage_group_list: 1650 if sg not in [source_sg, source_nf_sg]: 1651 self.remove_volume_from_sg( 1652 array, device_id, device_info_dict['volume_name'], sg, 1653 extra_specs) 1654 if source_nf_sg in source_storage_group_list: 1655 self.move_volume_between_storage_groups( 1656 array, device_id, source_nf_sg, 1657 source_sg, extra_specs) 1658 is_descendant = self.rest.is_child_sg_in_parent_sg( 1659 array, source_nf_sg, source_parent_sg) 1660 if is_descendant: 1661 self.rest.remove_child_sg_from_parent_sg( 1662 array, source_nf_sg, source_parent_sg, extra_specs) 1663 # Delete non fast storage group 1664 self.rest.delete_storage_group(array, source_nf_sg) 1665 1666 def attempt_ig_cleanup(self, connector, protocol, serial_number, force): 1667 """Attempt to cleanup an orphan initiator group 1668 1669 :param connector: connector object 1670 :param protocol: iscsi or fc 1671 :param serial_number: extra the array serial number 1672 """ 1673 protocol = self.utils.get_short_protocol_type(protocol) 1674 host_name = connector['host'] 1675 short_host_name = self.utils.get_host_short_name(host_name) 1676 init_group = ( 1677 ("OS-%(shortHostName)s-%(protocol)s-IG" 1678 % {'shortHostName': short_host_name, 1679 'protocol': protocol})) 1680 self._check_ig_rollback( 1681 serial_number, init_group, connector, force) 1682