1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028
|
"""
SoftLayer.storage_utils
~~~~~~~~~~~~~~~~~~~~~~~
Utility functions used by File and Block Storage Managers
:license: MIT, see LICENSE for more details.
"""
from SoftLayer import exceptions
from SoftLayer import utils
# pylint: disable=too-many-lines
ENDURANCE_TIERS = {
0.25: 100,
2: 200,
4: 300,
10: 1000,
}
def populate_host_templates(hardware_ids=None,
virtual_guest_ids=None,
ip_address_ids=None,
subnet_ids=None):
"""Returns a populated array with the IDs provided
:param hardware_ids: A List of SoftLayer_Hardware ids
:param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids
:param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids
:param subnet_ids: A List of SoftLayer_Network_Subnet ids
:return: array of objects formatted for allowAccessFromHostList
"""
host_templates = []
if hardware_ids is not None:
for hardware_id in hardware_ids:
host_templates.append({
'objectType': 'SoftLayer_Hardware',
'id': hardware_id
})
if virtual_guest_ids is not None:
for virtual_guest_id in virtual_guest_ids:
host_templates.append({
'objectType': 'SoftLayer_Virtual_Guest',
'id': virtual_guest_id
})
if ip_address_ids is not None:
for ip_address_id in ip_address_ids:
host_templates.append({
'objectType': 'SoftLayer_Network_Subnet_IpAddress',
'id': ip_address_id
})
if subnet_ids is not None:
for subnet_id in subnet_ids:
host_templates.append({
'objectType': 'SoftLayer_Network_Subnet',
'id': subnet_id
})
return host_templates
def get_package(manager, category_code):
"""Returns a product package based on type of storage.
:param manager: The storage manager which calls this function.
:param category_code: Category code of product package.
:return: Returns a packaged based on type of storage.
"""
_filter = utils.NestedDict({})
_filter['categories']['categoryCode'] = (
utils.query_filter(category_code))
_filter['statusCode'] = utils.query_filter('ACTIVE')
packages = manager.client.call(
'Product_Package', 'getAllObjects',
filter=_filter.to_dict(),
mask='id,name,items[prices[categories],attributes]'
)
if len(packages) == 0:
raise ValueError('No packages were found for %s' % category_code)
if len(packages) > 1:
raise ValueError('More than one package was found for %s'
% category_code)
return packages[0]
def get_location_id(manager, location):
"""Returns location id
:param manager: The storage manager which calls this function.
:param location: Datacenter short name
:return: Returns location id
"""
loc_svc = manager.client['Location_Datacenter']
datacenters = loc_svc.getDatacenters(mask='mask[longName,id,name]')
for datacenter in datacenters:
if datacenter['name'] == location:
location = datacenter['id']
return location
raise ValueError('Invalid datacenter name specified.')
def find_price_by_category(package, price_category):
"""Find the price in the given package that has the specified category
:param package: The AsAService, Enterprise, or Performance product package
:param price_category: The price category code to search for
:return: Returns the price for the given category, or an error if not found
"""
for item in package['items']:
price_id = _find_price_id(item['prices'], price_category)
if price_id:
return price_id
raise ValueError("Could not find price with the category, %s" % price_category)
def find_ent_space_price(package, category, size, tier_level):
"""Find the space price for the given category, size, and tier
:param package: The Enterprise (Endurance) product package
:param category: The category of space (endurance, replication, snapshot)
:param size: The size for which a price is desired
:param tier_level: The endurance tier for which a price is desired
:return: Returns the matching price, or an error if not found
"""
if category == 'snapshot':
category_code = 'storage_snapshot_space'
elif category == 'replication':
category_code = 'performance_storage_replication'
else: # category == 'endurance'
category_code = 'performance_storage_space'
level = ENDURANCE_TIERS.get(tier_level)
for item in package['items']:
if int(item['capacity']) != size:
continue
price_id = _find_price_id(item['prices'], category_code, 'STORAGE_TIER_LEVEL', level)
if price_id:
return price_id
raise ValueError("Could not find price for %s storage space" % category)
def find_ent_endurance_tier_price(package, tier_level):
"""Find the price in the given package with the specified tier level
:param package: The Enterprise (Endurance) product package
:param tier_level: The endurance tier for which a price is desired
:return: Returns the price for the given tier, or an error if not found
"""
for item in package['items']:
for attribute in item.get('attributes', []):
if int(attribute['value']) == ENDURANCE_TIERS.get(tier_level):
break
else:
continue
price_id = _find_price_id(item['prices'], 'storage_tier_level')
if price_id:
return price_id
raise ValueError("Could not find price for endurance tier level")
def find_endurance_tier_iops_per_gb(volume):
"""Find the tier for the given endurance volume (IOPS per GB)
:param volume: The volume for which the tier level is desired
:return: Returns a float value indicating the IOPS per GB for the volume
"""
tier = volume['storageTierLevel']
iops_per_gb = 0.25
if tier == "LOW_INTENSITY_TIER":
iops_per_gb = 0.25
elif tier == "READHEAVY_TIER":
iops_per_gb = 2
elif tier == "WRITEHEAVY_TIER":
iops_per_gb = 4
elif tier == "10_IOPS_PER_GB":
iops_per_gb = 10
else:
raise ValueError("Could not find tier IOPS per GB for this volume")
return iops_per_gb
def find_perf_space_price(package, size):
"""Find the price in the given package with the specified size
:param package: The Performance product package
:param size: The storage space size for which a price is desired
:return: Returns the price for the given size, or an error if not found
"""
for item in package['items']:
if int(item['capacity']) != size:
continue
price_id = _find_price_id(item['prices'], 'performance_storage_space')
if price_id:
return price_id
raise ValueError("Could not find performance space price for this volume")
def find_perf_iops_price(package, size, iops):
"""Find the price in the given package with the specified size and iops
:param package: The Performance product package
:param size: The size of storage space for which an IOPS price is desired
:param iops: The number of IOPS for which a price is desired
:return: Returns the price for the size and IOPS, or an error if not found
"""
for item in package['items']:
if int(item['capacity']) != int(iops):
continue
price_id = _find_price_id(item['prices'], 'performance_storage_iops', 'STORAGE_SPACE', size)
if price_id:
return price_id
raise ValueError("Could not find price for iops for the given volume")
def find_saas_endurance_space_price(package, size, tier_level):
"""Find the SaaS endurance storage space price for the size and tier
:param package: The Storage As A Service product package
:param size: The volume size for which a price is desired
:param tier_level: The endurance tier for which a price is desired
:return: Returns the price for the size and tier, or an error if not found
"""
if tier_level != 0.25:
tier_level = int(tier_level)
key_name = f'STORAGE_SPACE_FOR_{tier_level}_IOPS_PER_GB'
key_name = key_name.replace(".", "_")
for item in package['items']:
if key_name not in item['keyName']:
continue
if 'capacityMinimum' not in item or 'capacityMaximum' not in item:
continue
capacity_minimum = int(item['capacityMinimum'])
capacity_maximum = int(item['capacityMaximum'])
if size < capacity_minimum or size > capacity_maximum:
continue
price_id = _find_price_id(item['prices'], 'performance_storage_space')
if price_id:
return price_id
raise ValueError("Could not find price for endurance storage space")
def find_saas_endurance_tier_price(package, tier_level):
"""Find the SaaS storage tier level price for the specified tier level
:param package: The Storage As A Service product package
:param tier_level: The endurance tier for which a price is desired
:return: Returns the price for the given tier, or an error if not found
"""
target_capacity = ENDURANCE_TIERS.get(tier_level)
for item in package['items']:
if 'itemCategory' not in item\
or 'categoryCode' not in item['itemCategory']\
or item['itemCategory']['categoryCode']\
!= 'storage_tier_level':
continue
if int(item['capacity']) != target_capacity:
continue
price_id = _find_price_id(item['prices'], 'storage_tier_level')
if price_id:
return price_id
raise ValueError("Could not find price for endurance tier level")
def find_saas_perform_space_price(package, size):
"""Find the SaaS performance storage space price for the given size
:param package: The Storage As A Service product package
:param size: The volume size for which a price is desired
:return: Returns the price for the size and tier, or an error if not found
"""
for item in package['items']:
if 'itemCategory' not in item\
or 'categoryCode' not in item['itemCategory']\
or item['itemCategory']['categoryCode']\
!= 'performance_storage_space':
continue
if 'capacityMinimum' not in item or 'capacityMaximum' not in item:
continue
capacity_minimum = int(item['capacityMinimum'])
capacity_maximum = int(item['capacityMaximum'])
if size < capacity_minimum or size > capacity_maximum:
continue
key_name = f'{capacity_minimum}_{capacity_maximum}_GBS'
if item['keyName'] != key_name:
continue
price_id = _find_price_id(item['prices'], 'performance_storage_space')
if price_id:
return price_id
raise ValueError("Could not find price for performance storage space")
def find_saas_perform_iops_price(package, size, iops):
"""Find the SaaS IOPS price for the specified size and iops
:param package: The Storage As A Service product package
:param size: The volume size for which a price is desired
:param iops: The number of IOPS for which a price is desired
:return: Returns the price for the size and IOPS, or an error if not found
"""
for item in package['items']:
if 'itemCategory' not in item\
or 'categoryCode' not in item['itemCategory']\
or item['itemCategory']['categoryCode']\
!= 'performance_storage_iops':
continue
if 'capacityMinimum' not in item or 'capacityMaximum' not in item:
continue
capacity_minimum = int(item['capacityMinimum'])
capacity_maximum = int(item['capacityMaximum'])
if iops < capacity_minimum or iops > capacity_maximum:
continue
price_id = _find_price_id(item['prices'], 'performance_storage_iops', 'STORAGE_SPACE', size)
if price_id:
return price_id
raise ValueError("Could not find price for iops for the given volume")
def find_saas_snapshot_space_price(package, size, tier=None, iops=None):
"""Find the price in the SaaS package for the desired snapshot space size
:param package: The product package of the endurance storage type
:param size: The snapshot space size for which a price is desired
:param tier: The tier of the volume for which space is being ordered
:param iops: The IOPS of the volume for which space is being ordered
:return: Returns the price for the given size, or an error if not found
"""
if tier is not None:
target_value = ENDURANCE_TIERS.get(tier)
target_restriction_type = 'STORAGE_TIER_LEVEL'
else:
target_value = iops
target_restriction_type = 'IOPS'
for item in package['items']:
if int(item['capacity']) != size:
continue
price_id = _find_price_id(item['prices'], 'storage_snapshot_space', target_restriction_type, target_value)
if price_id:
return price_id
raise ValueError("Could not find price for snapshot space")
def find_saas_replication_price(package, tier=None, iops=None):
"""Find the price in the given package for the desired replicant volume
:param package: The product package of the endurance storage type
:param tier: The tier of the primary storage volume
:param iops: The IOPS of the primary storage volume
:return: Returns the replication price, or an error if not found
"""
if tier is not None:
target_value = ENDURANCE_TIERS.get(tier)
target_item_keyname = 'REPLICATION_FOR_TIERBASED_PERFORMANCE'
target_restriction_type = 'STORAGE_TIER_LEVEL'
else:
target_value = iops
target_item_keyname = 'REPLICATION_FOR_IOPSBASED_PERFORMANCE'
target_restriction_type = 'IOPS'
for item in package['items']:
if item['keyName'] != target_item_keyname:
continue
price_id = _find_price_id(
item['prices'],
'performance_storage_replication',
target_restriction_type,
target_value
)
if price_id:
return price_id
raise ValueError("Could not find price for replicant volume")
def find_snapshot_schedule_id(volume, snapshot_schedule_keyname):
"""Find the snapshot schedule ID for the given volume and keyname
:param volume: The volume for which the snapshot ID is desired
:param snapshot_schedule_keyname: The keyname of the snapshot schedule
:return: Returns an int value indicating the volume's snapshot schedule ID
"""
for schedule in volume['schedules']:
if 'type' in schedule and 'keyname' in schedule['type']:
if schedule['type']['keyname'] == snapshot_schedule_keyname:
return schedule['id']
raise ValueError("The given snapshot schedule ID was not found for the given storage volume")
def prepare_snapshot_order_object(manager, volume, capacity, tier, upgrade, iops):
"""Prepare the snapshot space order object for the placeOrder() method
:param manager: The File or Block manager calling this function
:param integer volume: The volume for which snapshot space is ordered
:param integer capacity: The snapshot space size to order, in GB
:param float tier: The tier level of the volume, in IOPS per GB (optional)
:param boolean upgrade: Flag to indicate if this order is an upgrade
:return: Returns the order object for the
Product_Order service's placeOrder() method
"""
# Ensure the storage volume has not been cancelled
if 'billingItem' not in volume:
raise exceptions.SoftLayerError('This volume has been cancelled; unable to order snapshot space')
# Determine and validate the storage volume's billing item category
billing_item_category_code = volume['billingItem']['categoryCode']
if billing_item_category_code == 'storage_as_a_service':
order_type_is_saas = True
elif billing_item_category_code == 'storage_service_enterprise':
order_type_is_saas = False
else:
raise exceptions.SoftLayerError(
"Snapshot space cannot be ordered for a primary volume with a "
"billing item category code of '%s'" % billing_item_category_code)
# Use the volume's billing item category code to get the product package
package = get_package(manager, billing_item_category_code)
# Find prices based on the volume's type and billing item category
if order_type_is_saas: # 'storage_as_a_service' package
volume_storage_type = volume['storageType']['keyName']
if 'ENDURANCE' in volume_storage_type:
if tier is None:
tier = find_endurance_tier_iops_per_gb(volume)
prices = [find_saas_snapshot_space_price(
package, capacity, tier=tier)]
elif 'PERFORMANCE' in volume_storage_type:
if not _staas_version_is_v2_or_above(volume):
raise exceptions.SoftLayerError(
"Snapshot space cannot be ordered for this performance "
"volume since it does not support Encryption at Rest.")
prices = [find_saas_snapshot_space_price(
package, capacity, iops=iops)]
else:
raise exceptions.SoftLayerError(
"Storage volume does not have a valid storage type "
"(with an appropriate keyName to indicate the "
"volume is a PERFORMANCE or an ENDURANCE volume)")
else: # 'storage_service_enterprise' package
if tier is None:
tier = find_endurance_tier_iops_per_gb(volume)
prices = [find_ent_space_price(package, 'snapshot', capacity, tier)]
# Currently, these types are valid for snapshot space orders, whether
# the base volume's order container was Enterprise or AsAService
if upgrade:
complex_type = 'SoftLayer_Container_Product_Order_'\
'Network_Storage_Enterprise_SnapshotSpace_Upgrade'
else:
complex_type = 'SoftLayer_Container_Product_Order_'\
'Network_Storage_Enterprise_SnapshotSpace'
# Determine if hourly billing should be used
hourly_billing_flag = utils.lookup(volume, 'billingItem', 'hourlyFlag')
if hourly_billing_flag is None:
hourly_billing_flag = False
# Build and return the order object
snapshot_space_order = {
'complexType': complex_type,
'packageId': package['id'],
'prices': prices,
'quantity': 1,
'location': volume['billingItem']['location']['id'],
'volumeId': volume['id'],
'useHourlyPricing': hourly_billing_flag
}
return snapshot_space_order
def prepare_volume_order_object(manager, storage_type, location, size,
iops, tier, snapshot_size, service_offering,
volume_type, hourly_billing_flag=False):
"""Prepare the order object which is submitted to the placeOrder() method
:param manager: The File or Block manager calling this function
:param storage_type: "performance" or "endurance"
:param location: Requested datacenter location name for the ordered volume
:param size: Desired size of the volume, in GB
:param iops: Number of IOPs for a "Performance" volume order
:param tier: Tier level to use for an "Endurance" volume order
:param snapshot_size: The size of snapshot space for the volume (optional)
:param service_offering: Requested offering package to use for the order
:param volume_type: The type of the volume to order ('file' or 'block')
:param hourly_billing_flag: Billing type, monthly (False) or hourly (True)
:return: Returns the order object for the
Product_Order service's placeOrder() method
"""
# Ensure the volume storage type is valid
if storage_type != 'performance' and storage_type != 'endurance':
raise exceptions.SoftLayerError(
"Volume storage type must be either performance or endurance")
# Find the ID for the requested location
try:
location_id = get_location_id(manager, location)
except ValueError as ex:
message = "Invalid datacenter name specified. Please provide the lower case short name (e.g.: dal09)"
raise exceptions.SoftLayerError(message) from ex
# Determine the category code to use for the order (and product package)
order_type_is_saas, order_category_code = _get_order_type_and_category(
service_offering,
storage_type,
volume_type
)
# Get the product package for the given category code
package = get_package(manager, order_category_code)
# Based on the storage type and product package, build up the complex type
# and array of price codes to include in the order object
base_type_name = 'SoftLayer_Container_Product_Order_Network_'
if order_type_is_saas:
complex_type = base_type_name + 'Storage_AsAService'
if storage_type == 'performance':
prices = [
find_price_by_category(package, order_category_code),
find_price_by_category(package, 'storage_' + volume_type),
find_saas_perform_space_price(package, size),
find_saas_perform_iops_price(package, size, iops)
]
if snapshot_size is not None:
prices.append(find_saas_snapshot_space_price(
package, snapshot_size, iops=iops))
else: # storage_type == 'endurance'
prices = [
find_price_by_category(package, order_category_code),
find_price_by_category(package, 'storage_' + volume_type),
find_saas_endurance_space_price(package, size, tier),
find_saas_endurance_tier_price(package, tier)
]
if snapshot_size is not None:
prices.append(find_saas_snapshot_space_price(
package, snapshot_size, tier=tier))
else: # offering package is enterprise or performance
if storage_type == 'performance':
if volume_type == 'block':
complex_type = base_type_name + 'PerformanceStorage_Iscsi'
else:
complex_type = base_type_name + 'PerformanceStorage_Nfs'
prices = [
find_price_by_category(package, order_category_code),
find_perf_space_price(package, size),
find_perf_iops_price(package, size, iops),
]
else: # storage_type == 'endurance'
complex_type = base_type_name + 'Storage_Enterprise'
prices = [
find_price_by_category(package, order_category_code),
find_price_by_category(package, 'storage_' + volume_type),
find_ent_space_price(package, 'endurance', size, tier),
find_ent_endurance_tier_price(package, tier),
]
if snapshot_size is not None:
prices.append(find_ent_space_price(
package, 'snapshot', snapshot_size, tier))
# Build and return the order object
order = {
'complexType': complex_type,
'packageId': package['id'],
'prices': prices,
'quantity': 1,
'location': location_id,
'useHourlyPricing': hourly_billing_flag
}
if order_type_is_saas:
order['volumeSize'] = size
if storage_type == 'performance':
order['iops'] = iops
return order
def _get_order_type_and_category(service_offering, storage_type, volume_type):
if service_offering == 'storage_as_a_service':
order_type_is_saas = True
order_category_code = 'storage_as_a_service'
elif service_offering == 'enterprise':
order_type_is_saas = False
if storage_type == 'endurance':
order_category_code = 'storage_service_enterprise'
else:
raise exceptions.SoftLayerError(
"The requested offering package, '%s', is not available for "
"the '%s' storage type." % (service_offering, storage_type))
elif service_offering == 'performance':
order_type_is_saas = False
if storage_type == 'performance':
if volume_type == 'block':
order_category_code = 'performance_storage_iscsi'
else:
order_category_code = 'performance_storage_nfs'
else:
raise exceptions.SoftLayerError(
"The requested offering package, '%s', is not available for "
"the '%s' storage type." % (service_offering, storage_type))
else:
raise exceptions.SoftLayerError(
"The requested service offering package is not valid. "
"Please check the available options and try again.")
return order_type_is_saas, order_category_code
def prepare_replicant_order_object(manager, snapshot_schedule, location,
tier, volume, volume_type, iops):
"""Prepare the order object which is submitted to the placeOrder() method
:param manager: The File or Block manager calling this function
:param snapshot_schedule: The primary volume's snapshot
schedule to use for replication
:param location: The location for the ordered replicant volume
:param tier: The tier (IOPS per GB) of the primary volume
:param volume: The primary volume as a SoftLayer_Network_Storage object
:param volume_type: The type of the primary volume ('file' or 'block')
:return: Returns the order object for the
Product_Order service's placeOrder() method
"""
# Ensure the primary volume and snapshot space are not set for cancellation
if 'billingItem' not in volume\
or volume['billingItem'].get('cancellationDate'):
raise exceptions.SoftLayerError(
'This volume is set for cancellation; '
'unable to order replicant volume')
for child in volume['billingItem']['activeChildren']:
if child['categoryCode'] == 'storage_snapshot_space'\
and child.get('cancellationDate'):
raise exceptions.SoftLayerError(
'The snapshot space for this volume is set for '
'cancellation; unable to order replicant volume')
# Find the ID for the requested location
try:
location_id = get_location_id(manager, location)
except ValueError as ex:
message = "Invalid datacenter name specified. Please provide the lower case short name (e.g.: dal09)"
raise exceptions.SoftLayerError(message) from ex
# Get sizes and properties needed for the order
volume_size = int(volume['capacityGb'])
billing_item_category_code = volume['billingItem']['categoryCode']
if billing_item_category_code == 'storage_as_a_service':
order_type_is_saas = True
elif billing_item_category_code == 'storage_service_enterprise':
order_type_is_saas = False
else:
raise exceptions.SoftLayerError(
"A replicant volume cannot be ordered for a primary volume with a "
"billing item category code of '%s'" % billing_item_category_code)
if 'snapshotCapacityGb' in volume:
snapshot_size = int(volume['snapshotCapacityGb'])
else:
raise exceptions.SoftLayerError(
"Snapshot capacity not found for the given primary volume")
snapshot_schedule_id = find_snapshot_schedule_id(
volume,
'SNAPSHOT_' + snapshot_schedule
)
# Use the volume's billing item category code to get the product package
package = get_package(manager, billing_item_category_code)
# Find prices based on the primary volume's type and billing item category
if order_type_is_saas: # 'storage_as_a_service' package
complex_type = 'SoftLayer_Container_Product_Order_'\
'Network_Storage_AsAService'
volume_storage_type = volume['storageType']['keyName']
if 'ENDURANCE' in volume_storage_type:
volume_is_performance = False
if tier is None:
tier = find_endurance_tier_iops_per_gb(volume)
prices = [
find_price_by_category(package, billing_item_category_code),
find_price_by_category(package, 'storage_' + volume_type),
find_saas_endurance_space_price(package, volume_size, tier),
find_saas_endurance_tier_price(package, tier),
find_saas_snapshot_space_price(
package, snapshot_size, tier=tier),
find_saas_replication_price(package, tier=tier)
]
elif 'PERFORMANCE' in volume_storage_type:
if not _staas_version_is_v2_or_above(volume):
raise exceptions.SoftLayerError(
"A replica volume cannot be ordered for this performance "
"volume since it does not support Encryption at Rest.")
volume_is_performance = True
prices = [
find_price_by_category(package, billing_item_category_code),
find_price_by_category(package, 'storage_' + volume_type),
find_saas_perform_space_price(package, volume_size),
find_saas_perform_iops_price(package, volume_size, iops),
find_saas_snapshot_space_price(
package, snapshot_size, iops=iops),
find_saas_replication_price(package, iops=iops)
]
else:
raise exceptions.SoftLayerError(
"Storage volume does not have a valid storage type "
"(with an appropriate keyName to indicate the "
"volume is a PERFORMANCE or an ENDURANCE volume)")
else: # 'storage_service_enterprise' package
complex_type = 'SoftLayer_Container_Product_Order_'\
'Network_Storage_Enterprise'
volume_is_performance = False
if tier is None:
tier = find_endurance_tier_iops_per_gb(volume)
prices = [
find_price_by_category(package, billing_item_category_code),
find_price_by_category(package, 'storage_' + volume_type),
find_ent_space_price(package, 'endurance', volume_size, tier),
find_ent_endurance_tier_price(package, tier),
find_ent_space_price(package, 'snapshot', snapshot_size, tier),
find_ent_space_price(package, 'replication', volume_size, tier)
]
# Determine if hourly billing should be used
hourly_billing_flag = utils.lookup(volume, 'billingItem', 'hourlyFlag')
if hourly_billing_flag is None:
hourly_billing_flag = False
# Build and return the order object
replicant_order = {
'complexType': complex_type,
'packageId': package['id'],
'prices': prices,
'quantity': 1,
'location': location_id,
'originVolumeId': volume['id'],
'originVolumeScheduleId': snapshot_schedule_id,
'useHourlyPricing': hourly_billing_flag
}
if order_type_is_saas:
replicant_order['volumeSize'] = volume_size
if volume_is_performance:
replicant_order['iops'] = iops
return replicant_order
def prepare_duplicate_order_object(manager, origin_volume, iops, tier,
duplicate_size, duplicate_snapshot_size,
volume_type, hourly_billing_flag=False,
dependent_duplicate=False):
"""Prepare the duplicate order to submit to SoftLayer_Product::placeOrder()
:param manager: The File or Block manager calling this function
:param origin_volume: The origin volume which is being duplicated
:param iops: The IOPS for the duplicate volume (performance)
:param tier: The tier level for the duplicate volume (endurance)
:param duplicate_size: The requested size for the duplicate volume
:param duplicate_snapshot_size: The size for the duplicate snapshot space
:param volume_type: The type of the origin volume ('file' or 'block')
:param hourly_billing_flag: Billing type, monthly (False) or hourly (True)
:param dependent_duplicate: Duplicate type, normal (False) or dependent
duplicate (True)
:return: Returns the order object to be passed to the
placeOrder() method of the Product_Order service
"""
# Verify that the origin volume has not been cancelled
if 'billingItem' not in origin_volume:
raise exceptions.SoftLayerError(
"The origin volume has been cancelled; "
"unable to order duplicate volume")
# Verify that the origin volume has snapshot space (needed for duplication)
if isinstance(utils.lookup(origin_volume, 'snapshotCapacityGb'), str):
origin_snapshot_size = int(origin_volume['snapshotCapacityGb'])
else:
raise exceptions.SoftLayerError(
"Snapshot space not found for the origin volume. "
"Origin snapshot space is needed for duplication.")
# Obtain the datacenter location ID for the duplicate
if isinstance(utils.lookup(origin_volume, 'billingItem',
'location', 'id'), int):
location_id = origin_volume['billingItem']['location']['id']
else:
raise exceptions.SoftLayerError(
"Cannot find origin volume's location")
# Ensure the origin volume is STaaS v2 or higher
# and supports Encryption at Rest
if not _staas_version_is_v2_or_above(origin_volume):
raise exceptions.SoftLayerError(
"This volume cannot be duplicated since it "
"does not support Encryption at Rest.")
# If no specific snapshot space was requested for the duplicate,
# use the origin snapshot space size
if duplicate_snapshot_size is None:
duplicate_snapshot_size = origin_snapshot_size
# Use the origin volume size if no size was specified for the duplicate
if duplicate_size is None:
duplicate_size = origin_volume['capacityGb']
# Get the appropriate package for the order
# ('storage_as_a_service' is currently used for duplicate volumes)
package = get_package(manager, 'storage_as_a_service')
# Determine the IOPS or tier level for the duplicate volume, along with
# the type and prices for the order
origin_storage_type = origin_volume['storageType']['keyName']
if 'PERFORMANCE' in origin_storage_type:
volume_is_performance = True
if iops is None:
iops = int(origin_volume.get('provisionedIops', 0))
if iops <= 0:
raise exceptions.SoftLayerError("Cannot find origin volume's provisioned IOPS")
# Set up the price array for the order
prices = [
find_price_by_category(package, 'storage_as_a_service'),
find_price_by_category(package, 'storage_' + volume_type),
find_saas_perform_space_price(package, duplicate_size),
find_saas_perform_iops_price(package, duplicate_size, iops),
]
# Add the price code for snapshot space as well, unless 0 GB was given
if duplicate_snapshot_size > 0:
prices.append(find_saas_snapshot_space_price(
package, duplicate_snapshot_size, iops=iops))
elif 'ENDURANCE' in origin_storage_type:
volume_is_performance = False
if tier is None:
tier = find_endurance_tier_iops_per_gb(origin_volume)
# Set up the price array for the order
prices = [
find_price_by_category(package, 'storage_as_a_service'),
find_price_by_category(package, 'storage_' + volume_type),
find_saas_endurance_space_price(package, duplicate_size, tier),
find_saas_endurance_tier_price(package, tier),
]
# Add the price code for snapshot space as well, unless 0 GB was given
if duplicate_snapshot_size > 0:
prices.append(find_saas_snapshot_space_price(
package, duplicate_snapshot_size, tier=tier))
else:
raise exceptions.SoftLayerError(
"Origin volume does not have a valid storage type "
"(with an appropriate keyName to indicate the "
"volume is a PERFORMANCE or an ENDURANCE volume)")
duplicate_order = {
'complexType': 'SoftLayer_Container_Product_Order_'
'Network_Storage_AsAService',
'packageId': package['id'],
'prices': prices,
'volumeSize': duplicate_size,
'quantity': 1,
'location': location_id,
'duplicateOriginVolumeId': origin_volume['id'],
'useHourlyPricing': hourly_billing_flag
}
if volume_is_performance:
duplicate_order['iops'] = iops
if dependent_duplicate:
duplicate_order['isDependentDuplicateFlag'] = 1
return duplicate_order
def prepare_modify_order_object(manager, volume, new_iops, new_tier, new_size):
"""Prepare the modification order to submit to SoftLayer_Product::placeOrder()
:param manager: The File or Block manager calling this function
:param volume: The volume which is being modified
:param new_iops: The new IOPS for the volume (performance)
:param new_tier: The new tier level for the volume (endurance)
:param new_size: The requested new size for the volume
:return: Returns the order object to be passed to the placeOrder() method of the Product_Order service
"""
# Verify that the origin volume has not been cancelled
if 'billingItem' not in volume:
raise exceptions.SoftLayerError("The volume has been cancelled; unable to modify volume.")
# Ensure the origin volume is STaaS v2 or higher and supports Encryption at Rest
if not _staas_version_is_v2_or_above(volume):
raise exceptions.SoftLayerError("This volume cannot be modified since it does not support Encryption at Rest.")
# Get the appropriate package for the order ('storage_as_a_service' is currently used for modifying volumes)
package = get_package(manager, 'storage_as_a_service')
# Based on volume storage type, ensure at least one volume property is being modified,
# use current values if some are not specified, and lookup price codes for the order
volume_storage_type = volume['storageType']['keyName']
if 'PERFORMANCE' in volume_storage_type:
volume_is_performance = True
if new_size is None and new_iops is None:
raise exceptions.SoftLayerError("A size or IOPS value must be given to modify this performance volume.")
if new_size is None:
new_size = volume['capacityGb']
elif new_iops is None:
new_iops = int(volume.get('provisionedIops', 0))
if new_iops <= 0:
raise exceptions.SoftLayerError("Cannot find volume's provisioned IOPS.")
# Set up the prices array for the order
prices = [
find_price_by_category(package, 'storage_as_a_service'),
find_saas_perform_space_price(package, new_size),
find_saas_perform_iops_price(package, new_size, new_iops),
]
elif 'ENDURANCE' in volume_storage_type:
volume_is_performance = False
if new_size is None and new_tier is None:
raise exceptions.SoftLayerError("A size or tier value must be given to modify this endurance volume.")
if new_size is None:
new_size = volume['capacityGb']
elif new_tier is None:
new_tier = find_endurance_tier_iops_per_gb(volume)
# Set up the prices array for the order
prices = [
find_price_by_category(package, 'storage_as_a_service'),
find_saas_endurance_space_price(package, new_size, new_tier),
find_saas_endurance_tier_price(package, new_tier),
]
else:
raise exceptions.SoftLayerError("Volume does not have a valid storage type (with an appropriate "
"keyName to indicate the volume is a PERFORMANCE or an ENDURANCE volume).")
modify_order = {
'complexType': 'SoftLayer_Container_Product_Order_Network_Storage_AsAService_Upgrade',
'packageId': package['id'],
'prices': prices,
'volume': {'id': volume['id']},
'volumeSize': new_size
}
if volume_is_performance:
modify_order['iops'] = new_iops
return modify_order
def block_or_file(storage_type_keyname):
"""returns either 'block' or 'file'
:param storage_type_keyname: the Network_Storage['storageType']['keyName']
:returns: 'block' or 'file'
"""
return 'block' if 'BLOCK_STORAGE' in storage_type_keyname else 'file'
def _has_category(categories, category_code):
return any(
True
for category
in categories
if category['categoryCode'] == category_code
)
def _staas_version_is_v2_or_above(volume):
return int(volume['staasVersion']) > 1 and volume['hasEncryptionAtRest']
def _find_price_id(prices, category, restriction_type=None, restriction_value=None):
for price in prices:
# Only collect prices from valid location groups.
if price['locationGroupId']:
continue
if restriction_type is not None and restriction_value is not None:
if restriction_type != price['capacityRestrictionType']\
or restriction_value < int(price['capacityRestrictionMinimum'])\
or restriction_value > int(price['capacityRestrictionMaximum']):
continue
if not _has_category(price['categories'], category):
continue
return {'id': price['id']}
|