diff --git a/CHANGELOG.md b/CHANGELOG.md index eac8fe11..fac39910 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 5.0.0 (Unrelased) + +#### Features supported with current release: +- Id pool + # 4.7.1 #### Bug fixes - [#364] (https://github.com/HewlettPackard/python-hpOneView/issues/364) Bug in index_resources.get_all() diff --git a/endpoints-support.md b/endpoints-support.md index d1e7df72..5bfc4c2b 100755 --- a/endpoints-support.md +++ b/endpoints-support.md @@ -137,14 +137,14 @@ |/rest/firmware-drivers/{id} | GET | :white_check_mark: | :white_check_mark: | :white_check_mark: | |/rest/firmware-drivers/{id} | DELETE | :white_check_mark: | :white_check_mark: | :white_check_mark: | | **ID Pools** | -|/rest/id-pools/{poolType} | GET | :white_check_mark: | :white_check_mark: | -|/rest/id-pools/{poolType} | PUT | :white_check_mark: | :white_check_mark: | -|/rest/id-pools/{poolType}/allocator | PUT | :white_check_mark: | :white_check_mark: | -|/rest/id-pools/{poolType}/checkrangeavailability | GET | :white_check_mark: | :white_check_mark: | -|/rest/id-pools/{poolType}/collector | PUT | :white_check_mark: | :white_check_mark: | -|/rest/id-pools/{poolType}/generate | GET | :white_check_mark: | :white_check_mark: | -|/rest/id-pools/{poolType}/validate | GET | :white_check_mark: | :white_check_mark: | -|/rest/id-pools/{poolType}/validate | PUT | :white_check_mark: | :white_check_mark: | +|/rest/id-pools/{poolType} | GET | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +|/rest/id-pools/{poolType} | PUT | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +|/rest/id-pools/{poolType}/allocator | PUT | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +|/rest/id-pools/{poolType}/checkrangeavailability | GET | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +|/rest/id-pools/{poolType}/collector | PUT | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +|/rest/id-pools/{poolType}/generate | GET | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +|/rest/id-pools/{poolType}/validate | GET | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +|/rest/id-pools/{poolType}/validate | PUT | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | **ID Pools IPv4 Ranges** | |/rest/id-pools/ipv4/ranges | POST | :white_check_mark: | :white_check_mark: | |/rest/id-pools/ipv4/ranges/{id} | GET | :white_check_mark: | :white_check_mark: | diff --git a/examples/enclosures.py b/examples/enclosures.py index 49c8611c..ab7f5ba9 100644 --- a/examples/enclosures.py +++ b/examples/enclosures.py @@ -22,18 +22,23 @@ ### from pprint import pprint + from hpOneView.oneview_client import OneViewClient -from hpOneView.exceptions import HPOneViewException +from hpOneView.exceptions import HPOneViewException, HPOneViewTaskError from config_loader import try_load_from_file # This example is compatible only for C7000 enclosures config = { - "ip": "", + "ip": "", "credentials": { - "userName": "", - "password": "" - } + "userName": "", + "password": "" + }, + "enclosure_group_uri": "", + "enclosure_hostname": "", + "enclosure_username": "", + "enclosure_password": "", } # Declare a CA signed certificate file path. @@ -44,6 +49,7 @@ # The hostname, enclosure group URI, username, and password must be set on the configuration file options = { + "name": "Encl1-Updated-Updated-Updated", "enclosureGroupUri": config['enclosure_group_uri'], "hostname": config['enclosure_hostname'], "username": config['enclosure_username'], @@ -51,46 +57,47 @@ "licensingIntent": "OneView" } +# Get OneView client oneview_client = OneViewClient(config) -# Add an Enclosure -enclosure = oneview_client.enclosures.add(options) -enclosure_uri = enclosure['uri'] -print("Added enclosure '{name}'.\n URI = '{uri}'".format(**enclosure)) - -# Perform a patch operation, replacing the name of the enclosure -enclosure_name = enclosure['name'] + "-Updated" +# Add an enclosure +try: + # This will return an enclosure object + enclosure = oneview_client.enclosures.add(options) + print("Added enclosure '{name}'.\n URI = '{uri}'".format(**enclosure.data)) +except HPOneViewTaskError, e: + print(e) + # Get the reosurce by name + enclosure = oneview_client.enclosures.get_by_name(options['name']) + +# Perform a patch operation on the enclosure, replacing name of the enclosure +enclosure_name = "Encl1-Updated" print("Updating the enclosure to have a name of " + enclosure_name) -enclosure = oneview_client.enclosures.patch(enclosure_uri, 'replace', '/name', enclosure_name) -print(" Done.\n URI = '{uri}', name = {name}".format(**enclosure)) - -# Find the recently added enclosure by name -print("Find an enclosure by name") -enclosure = oneview_client.enclosures.get_by('name', enclosure['name'])[0] -print(" URI = '{uri}'".format(**enclosure)) +enclosure.patch('replace', '/name', enclosure_name) +print(" Done.\n URI = '{uri}', name = {name}".format(**enclosure.data)) # Get by URI print("Find an enclosure by URI") -enclosure = oneview_client.enclosures.get(enclosure_uri) -pprint(enclosure) +enclosure = enclosure.get_by_uri(enclosure.data['uri']) +pprint(enclosure.data) # Get all enclosures print("Get all enclosures") -enclosures = oneview_client.enclosures.get_all() +enclosures = enclosure.get_all() for enc in enclosures: print(' {name}'.format(**enc)) # Update configuration print("Reapplying the appliance's configuration on the enclosure") try: - oneview_client.enclosures.update_configuration(enclosure_uri) + enclosure.update_configuration() print(" Done.") except HPOneViewException as e: print(e.msg) print("Retrieve the environmental configuration data for the enclosure") try: - environmental_configuration = oneview_client.enclosures.get_environmental_configuration(enclosure_uri) + environmental_configuration = enclosure.get_environmental_configuration() print(" Enclosure calibratedMaxPower = {calibratedMaxPower}".format(**environmental_configuration)) except HPOneViewException as e: print(e.msg) @@ -99,7 +106,7 @@ print("Refreshing the enclosure") try: refresh_state = {"refreshState": "RefreshPending"} - enclosure = oneview_client.enclosures.refresh_state(enclosure_uri, refresh_state) + enclosure.refresh_state(refresh_state) print(" Done") except HPOneViewException as e: print(e.msg) @@ -107,7 +114,7 @@ # Get the enclosure script print("Get the enclosure script") try: - script = oneview_client.enclosures.get_script(enclosure_uri) + script = enclosure.get_script() pprint(script) except HPOneViewException as e: print(e.msg) @@ -115,7 +122,7 @@ # Buid the SSO URL parameters print("Build the SSO (Single Sign-On) URL parameters for the enclosure") try: - sso_url_parameters = oneview_client.enclosures.get_sso(enclosure_uri, 'Active') + sso_url_parameters = enclosure.get_sso('Active') pprint(sso_url_parameters) except HPOneViewException as e: print(e.msg) @@ -123,10 +130,9 @@ # Get Statistics specifying parameters print("Get the enclosure statistics") try: - enclosure_statistics = oneview_client.enclosures.get_utilization(enclosure_uri, - fields='AveragePower', - filter='startDate=2016-06-30T03:29:42.000Z', - view='day') + enclosure_statistics = enclosure.get_utilization(fields='AveragePower', + filter='startDate=2016-06-30T03:29:42.000Z', + view='day') pprint(enclosure_statistics) except HPOneViewException as e: print(e.msg) @@ -143,14 +149,14 @@ "commonName": "" } try: - oneview_client.enclosures.generate_csr(csr_data, enclosure_uri, bay_number=bay_number) + enclosure.generate_csr(csr_data, bay_number=bay_number) print("Generated CSR for the enclosure.") except HPOneViewException as e: print(e.msg) # Get the certificate Signing Request (CSR) that was generated by previous POST. try: - csr = oneview_client.enclosures.get_csr(enclosure_uri, bay_number=bay_number) + csr = enclosure.get_csr(bay_number=bay_number) with open('enclosure.csr', 'w') as csr_file: csr_file.write(csr["base64Data"]) print("Saved CSR(generated by previous POST) to 'enclosure.csr' file") @@ -160,7 +166,7 @@ try: # Get Enclosure by scope_uris if oneview_client.api_version >= 600: - enclosures_by_scope_uris = oneview_client.enclosures.get_all(scope_uris="\"'/rest/scopes/3bb0c754-fd38-45af-be8a-4d4419de06e9'\"") + enclosures_by_scope_uris = enclosure.get_all(scope_uris="\"'/rest/scopes/3bb0c754-fd38-45af-be8a-4d4419de06e9'\"") if len(enclosures_by_scope_uris) > 0: print("Found %d Enclosures" % (len(enclosures_by_scope_uris))) i = 0 @@ -185,11 +191,11 @@ "base64Data": certificate } - oneview_client.enclosures.import_certificate(certificate_data, enclosure_uri, bay_number=bay_number) + enclosure.import_certificate(certificate_data, enclosure.data['uri'], bay_number=bay_number) print("Imported Signed Certificate to the enclosure.") except HPOneViewException as e: print(e.msg) # Remove the recently added enclosure -oneview_client.enclosures.remove(enclosure) +enclosure.remove() print("Enclosure removed successfully") diff --git a/examples/fc_networks.py b/examples/fc_networks.py index 49394ce8..c7fe6126 100644 --- a/examples/fc_networks.py +++ b/examples/fc_networks.py @@ -27,90 +27,85 @@ from config_loader import try_load_from_file config = { - "ip": "", + "ip": "", "credentials": { - "userName": "", - "password": "" - } + "userName": "", + "password": "" + }, } options = { - "name": "OneViewSDK Test FC Network", + "name": "fc_test", "connectionTemplateUri": None, "autoLoginRedistribution": True, "fabricType": "FabricAttach", "linkStabilityTime": 30, } -# FC Network ID to perform a get by ID -fc_network_id = "" - # Scope name to perform the patch operation -scope_name = "" +scope_name = "sample" # Try load config from a file (if there is a config file) config = try_load_from_file(config) oneview_client = OneViewClient(config) -# Create a FC Network -fc_network = oneview_client.fc_networks.create(options) -print("\nCreated fc-network '%s' successfully.\n uri = '%s'" % (fc_network['name'], fc_network['uri'])) +# Create a FcNetWork with the options provided +try: + fc_network = oneview_client.fc_networks.create(data=options) + print("\nCreated a fc-network with name: '%s'.\n uri = '%s'" % (fc_network.data['name'], fc_network.data['uri'])) +except HPOneViewException, e: + print(e[0]) # Find recently created network by name -fc_network = oneview_client.fc_networks.get_by('name', 'OneViewSDK Test FC Network')[0] -print("\nFound fc-network by name: '%s'.\n uri = '%s'" % (fc_network['name'], fc_network['uri'])) +fc_network = oneview_client.fc_networks.get_by_name(options['name']) +print("\nFound fc-network by name: '%s'.\n uri = '%s'" % (fc_network.data['name'], fc_network.data['uri'])) # Update autoLoginRedistribution from recently created network -fc_network['autoLoginRedistribution'] = False -fc_network = oneview_client.fc_networks.update(fc_network) -print("\nUpdated fc-network '%s' successfully.\n uri = '%s'" % (fc_network['name'], fc_network['uri'])) -print(" with attribute {'autoLoginRedistribution': %s}" % fc_network['autoLoginRedistribution']) +data_to_update = {'autoLoginRedistribution': False, + 'name': 'Updated FC'} +resource = fc_network.update(data=data_to_update) +print("\nUpdated fc-network '%s' successfully.\n uri = '%s'" % (resource.data['name'], resource.data['uri'])) +print(" with attribute {'autoLoginRedistribution': %s}" % resource.data['autoLoginRedistribution']) # Get all, with defaults print("\nGet all fc-networks") -fc_nets = oneview_client.fc_networks.get_all() +fc_nets = fc_network.get_all() pprint(fc_nets) # Filter by name print("\nGet all fc-networks filtering by name") -fc_nets_filtered = oneview_client.fc_networks.get_all(filter="\"'name'='OneViewSDK Test FC Network'\"") +fc_nets_filtered = fc_network.get_all(filter="\"'name'='Updated FC'\"") pprint(fc_nets_filtered) # Get all sorting by name descending print("\nGet all fc-networks sorting by name") -fc_nets_sorted = oneview_client.fc_networks.get_all(sort='name:descending') +fc_nets_sorted = fc_network.get_all(sort='name:descending') pprint(fc_nets_sorted) # Get the first 10 records print("\nGet the first ten fc-networks") -fc_nets_limited = oneview_client.fc_networks.get_all(0, 10) +fc_nets_limited = fc_network.get_all(0, 10) pprint(fc_nets_limited) -# Get by Id -if fc_network_id: - try: - print("\nGet a fc-network by id") - fc_nets_byid = oneview_client.fc_networks.get(fc_network_id) - pprint(fc_nets_byid) - except HPOneViewException as e: - print(e.msg) - -# Get by Uri +# Get by uri print("\nGet a fc-network by uri") -fc_nets_by_uri = oneview_client.fc_networks.get(fc_network['uri']) -pprint(fc_nets_by_uri) +fc_nets_by_uri = fc_network.get_by_uri(resource.data['uri']) +pprint(fc_nets_by_uri.data) # Adds ethernet to scope defined if scope_name: print("\nGet scope then add the network to it") - scope = oneview_client.scopes.get_by_name(scope_name) - fc_with_scope = oneview_client.fc_networks.patch(fc_network['uri'], - 'replace', - '/scopeUris', - [scope['uri']]) - pprint(fc_with_scope) + scope = oneview_client.scopes.get_by_name(scope_name) # TODO: This has to updated + try: + fc_with_scope = fc_network.patch(resource.data['uri'], + 'replace', + '/scopeUris', + [scope['uri']]) + pprint(fc_with_scope) + except HPOneViewException, e: + print(e) # Delete the created network -oneview_client.fc_networks.delete(fc_network) +fc_network.delete() print("\nSuccessfully deleted fc-network") diff --git a/examples/id_pools.py b/examples/id_pools.py index 7ab8f71a..ee47299a 100644 --- a/examples/id_pools.py +++ b/examples/id_pools.py @@ -45,56 +45,48 @@ pool_type_ipv4 = 'ipv4' print("\n Gets the Pool: " + pool_type_vsn) -id_pool = oneview_client.id_pools.get(pool_type_vsn) -pprint(id_pool) +id_pool_vsn = oneview_client.id_pools.get_by_name(pool_type_vsn) +pprint(id_pool_vsn.data) print("\n Gets the Pool: " + pool_type_vwwn) -id_pool = oneview_client.id_pools.get(pool_type_vwwn) -pprint(id_pool) +id_pool_vwwn = oneview_client.id_pools.get_by_name(pool_type_vwwn) +pprint(id_pool_vwwn.data) print("\n Gets the Pool: " + pool_type_vmac) -id_pool = oneview_client.id_pools.get(pool_type_vmac) -pprint(id_pool) +id_pool_vmac = oneview_client.id_pools.get_by_name(pool_type_vmac) +pprint(id_pool_vmac.data) print("\n Gets the Pool: " + pool_type_ipv4) -id_pool = oneview_client.id_pools.get(pool_type_ipv4) -pprint(id_pool) +id_pool_ipv4 = oneview_client.id_pools.get_by_name(pool_type_ipv4) +pprint(id_pool_ipv4.data) print("\n Enable the Id Pool") -id_pool = oneview_client.id_pools.enable({"type": "Pool", - "enabled": True}, - pool_type_vsn) +id_pool_vsn.enable({"type": "Pool", "enabled": True}) print(" Id Pool enabled") print("\n Generates a random range") -rnd_range = oneview_client.id_pools.generate(pool_type_vsn) +rnd_range = id_pool_vsn.generate() pprint(rnd_range) print("\n Allocates a set of IDs from a pool") -allocated_ids = oneview_client.id_pools.allocate({"count": 10 - }, pool_type_vsn) +allocated_ids = id_pool_vsn.allocate({"count": 10}) pprint(allocated_ids) print("\n Checks the range availability in the Id pool") -range_availability = oneview_client.id_pools.get_check_range_availability(pool_type_vsn, - ['VCGYOAF00P', - 'VCGYOAF002']) +range_availability = id_pool_vsn.get_check_range_availability(['VCGYOAF00P', 'VCGYOAF002']) pprint(range_availability) print("\n Validates a set of user specified IDs to reserve in the pool") -validated = oneview_client.id_pools.validate({'idList': ['VCGYOAA023', - 'VCGYOAA024']}, pool_type_vsn) +validated = id_pool_vsn.validate({'idList': ['VCGYOAA023', 'VCGYOAA024']}) pprint(validated) print("\n Validates an Id Pool") -get_validate = oneview_client.id_pools.validate_id_pool(pool_type_ipv4, - ['172.18.9.11']) +get_validate = id_pool_vsn.validate_id_pool(['172.18.9.11']) pprint(get_validate) print("\n Collect a set of IDs back to Id Pool") try: - collected_ids = oneview_client.id_pools.collect({"idList": allocated_ids['idList']}, - pool_type_vsn) + collected_ids = id_pool_vsn.collect({"idList": allocated_ids['idList']}) pprint(collected_ids) except HPOneViewException as e: print(e.msg) diff --git a/hpOneView/exceptions.py b/hpOneView/exceptions.py index 46101fab..5842475b 100644 --- a/hpOneView/exceptions.py +++ b/hpOneView/exceptions.py @@ -149,3 +149,25 @@ class HPOneViewResourceNotFound(HPOneViewException): msg (str): Exception message. """ pass + + +class HPOneViewUnavailableMethod(HPOneViewException): + """ + OneView Unavailable Method Exception. + The exception is raised when a method is not available for the resource class. + + Attributes: + msg (str): Exception message. + """ + pass + + +class HPOneViewMissingUniqueIdentifiers(HPOneViewException): + """ + OneView Missing Unique Identifiers Exception. + The exception is raised when unique identifiers are missing for the resource + + Attributes: + msg (str): Exception message. + """ + pass diff --git a/hpOneView/oneview_client.py b/hpOneView/oneview_client.py index 6c1a7bf5..21774e74 100755 --- a/hpOneView/oneview_client.py +++ b/hpOneView/oneview_client.py @@ -341,9 +341,7 @@ def fc_networks(self): Returns: FcNetworks: """ - if not self.__fc_networks: - self.__fc_networks = FcNetworks(self.__connection) - return self.__fc_networks + return FcNetworks(self.__connection) @property def fcoe_networks(self): @@ -522,9 +520,7 @@ def id_pools(self): Returns: IdPools: """ - if not self.__id_pools: - self.__id_pools = IdPools(self.__connection) - return self.__id_pools + return IdPools(self.__connection) @property def switches(self): @@ -618,9 +614,7 @@ def enclosures(self): Returns: Enclosures: """ - if not self.__enclosures: - self.__enclosures = Enclosures(self.__connection) - return self.__enclosures + return Enclosures(self.__connection) @property def logical_enclosures(self): diff --git a/hpOneView/resources/networking/fc_networks.py b/hpOneView/resources/networking/fc_networks.py index e78f39a8..02150725 100644 --- a/hpOneView/resources/networking/fc_networks.py +++ b/hpOneView/resources/networking/fc_networks.py @@ -30,10 +30,10 @@ standard_library.install_aliases() -from hpOneView.resources.resource import ResourceClient +from hpOneView.resources.resource import Resource -class FcNetworks(object): +class FcNetworks(Resource): """ Fibre Channel networks API client. @@ -48,130 +48,5 @@ class FcNetworks(object): '600': {"type": "fc-networkV4"} } - def __init__(self, con): - self._connection = con - self._client = ResourceClient(con, self.URI) - - def get_all(self, start=0, count=-1, filter='', sort=''): - """ - Gets a paginated collection of Fibre Channel networks. The collection is based on optional - sorting and filtering and is constrained by start and count parameters. - - Args: - start: - The first item to return, using 0-based indexing. - If not specified, the default is 0 - start with the first available item. - count: - The number of resources to return. A count of -1 requests all items. - - The actual number of items in the response might differ from the requested - count if the sum of start and count exceeds the total number of items. - filter (list or str): - A general filter/query string to narrow the list of items returned. The - default is no filter; all resources are returned. - sort: - The sort order of the returned data set. By default, the sort order is based - on create time with the oldest entry first. - - Returns: - list: A list of Fibre Channel networks. - """ - return self._client.get_all(start, count, filter=filter, sort=sort) - - def delete(self, resource, force=False, timeout=-1): - """ - Deletes a Fibre Channel network. - Any deployed connections that are using the network are placed in the 'Failed' state. - - Args: - resource: dict object to delete - force: - If set to true, the operation completes despite any problems with - network connectivity or errors on the resource itself. The default is false. - timeout: - Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation - in OneView; it just stops waiting for its completion. - - Returns: - bool: Indicates if the resource was successfully deleted. - - """ - return self._client.delete(resource, force=force, timeout=timeout) - - def get(self, id_or_uri): - """ - Gets the Fibre Channel network with the specified ID. - - Args: - id_or_uri: ID or URI of Fibre Channel network. - - Returns: - dict: The Fibre Channel network. - """ - return self._client.get(id_or_uri) - - def create(self, resource, timeout=-1): - """ - Creates a Fibre Channel network. - - Args: - resource (dict): Object to create. - timeout: - Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation - in OneView, just stop waiting for its completion. - - Returns: - dict: Created resource. - - """ - return self._client.create(resource, timeout=timeout, default_values=self.DEFAULT_VALUES) - - def update(self, resource, timeout=-1): - """ - Updates a Fibre Channel network. - - Args: - resource (dict): Object to update. - timeout: - Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation - in OneView, just stop waiting for its completion. - - Returns: - dict: Updated resource. - - """ - return self._client.update(resource, timeout=timeout, default_values=self.DEFAULT_VALUES) - - def get_by(self, field, value): - """ - Gets all Fibre Channel networks that match the filter. - - The search is case-insensitive. - - Args: - field: Field name to filter. - value: Value to filter. - - Returns: - list: A list of Fibre Channel networks. - """ - return self._client.get_by(field, value) - - def patch(self, id_or_uri, operation, path, value, timeout=-1): - """ - Uses the PATCH to update the given resource. - - Only one operation can be performed in each PATCH call. - - Args: - id_or_uri: Can be either the resource ID or the resource URI. - operation: Patch operation - path: Path - value: Value - timeout: Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation - in OneView; it just stops waiting for its completion. - - Returns: - dict: Updated resource. - """ - return self._client.patch(id_or_uri, operation, path, value, timeout=timeout) + def __init__(self, connection, options=None): + super(FcNetworks, self).__init__(connection, options) diff --git a/hpOneView/resources/resource.py b/hpOneView/resources/resource.py index 1d0fd441..19d9996a 100755 --- a/hpOneView/resources/resource.py +++ b/hpOneView/resources/resource.py @@ -33,57 +33,817 @@ import logging import os +from copy import deepcopy +from urllib.parse import quote +from functools import update_wrapper, partial + +from hpOneView.resources.task_monitor import TaskMonitor +from hpOneView import exceptions + +RESOURCE_CLIENT_RESOURCE_WAS_NOT_PROVIDED = 'Resource was not provided' +RESOURCE_CLIENT_INVALID_FIELD = 'Invalid field was provided' +RESOURCE_CLIENT_INVALID_ID = 'Invalid id was provided' +RESOURCE_CLIENT_UNKNOWN_OBJECT_TYPE = 'Unknown object type' +UNRECOGNIZED_URI = 'Unrecognized URI for this resource' +RESOURCE_CLIENT_TASK_EXPECTED = "Failed: Expected a TaskResponse." +RESOURCE_ID_OR_URI_REQUIRED = 'It is required to inform the Resource ID or URI.' +UNAVAILABLE_METHOD = "Method is not available for this resource" +MISSING_UNIQUE_IDENTIFIERS = "Missing unique identifiers(URI/Name) for the resource" +RESOURCE_DOES_NOT_EXISTS = "Resource does not exists with provided unique identifiers" + +logger = logging.getLogger(__name__) + + +class EnsureResourceClient(object): + """ + Decorator class to sync resource data with server + """ + def __init__(self, func): + update_wrapper(self, func) + self.func = func + + def __get__(self, obj, objtype): + return partial(self.__call__, obj) + + def __call__(self, obj, *args, **kwargs): + obj.load_resource() + return self.func(obj, *args, **kwargs) + + +# Decorator to ensure the resource client +ensure_resource_client = EnsureResourceClient + + +class Resource(object): + """ + Base class for OneView resources + """ + # Base URI for the rest calls + URI = '/rest' + + # Unique identifiers to query the resource + UNIQUE_IDENTIFIERS = ['uri', 'name'] + + # Add fields to be removed from the request body + EXCLUDE_FROM_REQUEST = [] + + # Default values required for the api versions + DEFAULT_VALUES = {} + + def __init__(self, connection, data=None): + # OneView connection object + self._connection = connection + + # Resource data + self.data = data if data else {} + + # Merge resoure data with the default values + self._merge_default_values() + + self._task_monitor = TaskMonitor(connection) + + def load_resource(self): + """ + Retrieve data from OneView and update resource data + """ + # Check for unique identifier in the resource data + if not any(key in self.data for key in self.UNIQUE_IDENTIFIERS): + raise exceptions.HPOneViewMissingUniqueIdentifiers(MISSING_UNIQUE_IDENTIFIERS) + + resource_data = None + + if 'uri' in self.UNIQUE_IDENTIFIERS and self.data.get('uri'): + uri = self.data['uri'] + resource_data = self.do_get(uri) + else: + for identifier in self.UNIQUE_IDENTIFIERS: + identifier_value = self.data.get(identifier) + + if identifier_value: + result = self.get_by(identifier, identifier_value) + if result and isinstance(result, list): + resource_data = result[0] + break + + if not resource_data: + raise exceptions.HPOneViewResourceNotFound(RESOURCE_DOES_NOT_EXISTS) + + self.data.update(resource_data) + + def build_query_uri(self, start=0, count=-1, filter='', query='', sort='', view='', fields='', uri=None, scope_uris=''): + """ + Builds the URI given the parameters. + + More than one request can be send to get the items, regardless the query parameter 'count', because the actual + number of items in the response might differ from the requested count. Some types of resource have a limited + number of items returned on each call. For those resources, additional calls are made to the API to retrieve + any other items matching the given filter. The actual number of items can also differ from the requested call + if the requested number of items would take too long. + + The use of optional parameters for OneView 2.0 is described at: + http://h17007.www1.hpe.com/docs/enterprise/servers/oneview2.0/cic-api/en/api-docs/current/index.html + + Note: + Single quote - "'" - inside a query parameter is not supported by OneView API. + + Args: + start: + The first item to return, using 0-based indexing. + If not specified, the default is 0 - start with the first available item. + count: + The number of resources to return. A count of -1 requests all items (default). + filter (list or str): + A general filter/query string to narrow the list of items returned. The default is no + filter; all resources are returned. + query: + A single query parameter can do what would take multiple parameters or multiple GET requests using + filter. Use query for more complex queries. NOTE: This parameter is experimental for OneView 2.0. + sort: + The sort order of the returned data set. By default, the sort order is based on create time with the + oldest entry first. + view: + Returns a specific subset of the attributes of the resource or collection by specifying the name of a + predefined view. The default view is expand (show all attributes of the resource and all elements of + the collections or resources). + fields: + Name of the fields. + uri: + A specific URI (optional) + scope_uris: + An expression to restrict the resources returned according to the scopes to + which they are assigned. + + Returns: + uri: The complete uri + """ + + if filter: + filter = self.__make_query_filter(filter) + + if query: + query = "&query=" + quote(query) + + if sort: + sort = "&sort=" + quote(sort) + + if view: + view = "&view=" + quote(view) + + if fields: + fields = "&fields=" + quote(fields) + + if scope_uris: + scope_uris = "&scopeUris=" + quote(scope_uris) + + path = uri if uri else self.URI + self.__validate_resource_uri(path) + + symbol = '?' if '?' not in path else '&' + + uri = "{0}{1}start={2}&count={3}{4}{5}{6}{7}{8}{9}".format(path, symbol, start, count, filter, query, sort, + view, fields, scope_uris) + return uri + + def get_all(self, start=0, count=-1, filter='', query='', sort='', view='', fields='', scope_uris=''): + """ + Gets all items according with the given arguments. + + Args: + start: + The first item to return, using 0-based indexing. + If not specified, the default is 0 - start with the first available item. + count: + The number of resources to return. A count of -1 requests all items (default). + filter (list or str): + A general filter/query string to narrow the list of items returned. The default is no + filter; all resources are returned. + query: + A single query parameter can do what would take multiple parameters or multiple GET requests using + filter. Use query for more complex queries. NOTE: This parameter is experimental for OneView 2.0. + sort: + The sort order of the returned data set. By default, the sort order is based on create time with the + oldest entry first. + view: + Returns a specific subset of the attributes of the resource or collection by specifying the name of a + predefined view. The default view is expand (show all attributes of the resource and all elements of + the collections or resources). + fields: + Name of the fields. + uri: + A specific URI (optional) + scope_uris: + An expression to restrict the resources returned according to the scopes to + which they are assigned. + + Returns: + list: A list of items matching the specified filter. + """ + + uri = self.build_query_uri(start=start, + count=count, + filter=filter, + query=query, + sort=sort, + view=view, + fields=fields, + uri=self.URI, + scope_uris=scope_uris) + + logger.debug('Getting all resources with uri: {0}'.format(uri)) + + result = self.__do_requests_to_getall(uri, count) + + return result + + def create_with_zero_body(self, timeout=-1, custom_headers=None): + """ + Makes a POST request to create a resource when no request body is required. + + Args: + timeout: + Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation + in OneView; it just stops waiting for its completion. + custom_headers: + Allows set specific HTTP headers. + + Returns: + Created resource. + """ + logger.debug('Create with zero body (uri = %s)' % self.URI) + + self.do_post(self.URI, {}, timeout, custom_headers) + + return self + + def create(self, data=None, timeout=-1, custom_headers=None): + """ + Makes a POST request to create a resource when a request body is required. + + Args: + data: + Additional fields can be passed to create the resource. + timeout: + Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation + in OneView; it just stops waiting for its completion. + custom_headers: + Allows set specific HTTP headers. + Returns: + Created resource. + """ + uri = self.URI + resource = deepcopy(self.data) + + if data: + resource.update(data) + + logger.debug('Create (uri = %s, resource = %s)' % (uri, str(resource))) + + self.data = self.do_post(uri, resource, timeout, custom_headers) + + return self + + def delete_all(self, filter, force=False, timeout=-1): + """ + Deletes all resources from the appliance that match the provided filter. + + Args: + filter: + A general filter/query string to narrow the list of items deleted. + force: + If set to true, the operation completes despite any problems with network connectivity or errors + on the resource itself. The default is false. + timeout: + Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation + in OneView; it just stops waiting for its completion. + + Returns: + bool: Indicates if the resources were successfully deleted. + """ + uri = "{}?filter={}&force={}".format(self.URI, quote(filter), force) + logger.debug("Delete all resources (uri = %s)" % uri) + + task, body = self._connection.delete(uri) + + if not task: + # 204 NO CONTENT + # Successful return from a synchronous delete operation. + return True + + return self._task_monitor.wait_for_task(task, timeout=timeout) + + @ensure_resource_client + def delete(self, force=False, timeout=-1, custom_headers=None): + """ + Deletes current resource + """ + if self.data and 'uri' in self.data and self.data['uri']: + uri = self.data['uri'] + else: + logger.exception(RESOURCE_CLIENT_RESOURCE_WAS_NOT_PROVIDED) + raise ValueError(RESOURCE_CLIENT_RESOURCE_WAS_NOT_PROVIDED) + + if force: + uri += '?force=True' + + logger.debug("Delete resource (uri = %s)" % (str(uri))) + + task, body = self._connection.delete(uri, custom_headers=custom_headers) + + if not task: + # 204 NO CONTENT + # Successful return from a synchronous delete operation. + return True + + task = self._task_monitor.wait_for_task(task, timeout=timeout) + + return task + + def get_schema(self): + logger.debug('Get schema (uri = %s, resource = %s)' % + (self.URI, self.URI)) + return self._connection.get(self.URI + '/schema') + + @ensure_resource_client + def get_collection(self, filter=''): + """ + Retrieves a collection of resources. + + Use this function when the 'start' and 'count' parameters are not allowed in the GET call. + Otherwise, use get_all instead. + + Optional filtering criteria may be specified. + + Args: + filter (list or str): General filter/query string. + + Returns: + Collection of the requested resource. + """ + if filter: + filter = self.__make_query_filter(filter) + filter = "?" + filter[1:] + + uri = "{uri}{filter}".format(uri=self.data['uri'], filter=filter) + logger.debug('Get resource collection (uri = %s)' % uri) + response = self._connection.get(uri) + + return self.__get_members(response) + + @ensure_resource_client + def update_with_zero_body(self, path=None, timeout=-1, custom_headers=None): + """ + Makes a PUT request to update a resource when no request body is required. + + Args: + timeout: + Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation + in OneView; it just stops waiting for its completion. + custom_headers: + Allows set specific HTTP headers. + + Returns: + Updated resource. + """ + if path: + uri = '{}/{}'.format(self.URI, path) + else: + uri = self.data['uri'] + + logger.debug('Update with zero length body (uri = %s)' % uri) + + return self.do_put(uri, None, timeout, custom_headers) + + @ensure_resource_client + def update(self, data=None, force=False, timeout=-1, custom_headers=None): + """ + Makes a PUT request to update a resource when a request body is required. + + Args: + data: + Data to update the resource. + force: + If set to true, the operation completes despite any problems with network connectivity or errors + on the resource itself. The default is false. + timeout: + Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation + in OneView; it just stops waiting for its completion. + custom_headers: + Allows set specific HTTP headers. + + Returns: + Updated resource. + """ + uri = self.data['uri'] + + resource = deepcopy(self.data) + resource.update(data) + + logger.debug('Update async (uri = %s, resource = %s)' % + (uri, str(resource))) + if force: + uri += '?force=True' + + self.data = self.do_put(uri, resource, timeout, custom_headers) + + return self + + def patch(self, operation, path, value, timeout=-1, custom_headers=None): + """ + Uses the PATCH to update a resource. + + Only one operation can be performed in each PATCH call. + + Args + operation: Patch operation + path: Path + value: Value + timeout: Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation + in OneView; it just stops waiting for its completion. + + Returns: + Updated resource. + """ + patch_request_body = [{'op': operation, 'path': path, 'value': value}] + + self.data = self._patch_request(body=patch_request_body, + timeout=timeout, + custom_headers=custom_headers) + return self + + @ensure_resource_client + def patch_request(self, body, timeout=-1, custom_headers=None): + """ + Uses the PATCH to update a resource. + + Only one operation can be performed in each PATCH call. + + Args: + body: Patch request body + timeout: Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation + in OneView; it just stops waiting for its completion. + + Returns: + Updated resource. + """ + uri = self.data['uri'] + logger.debug('Patch resource (uri = %s, data = %s)' % (uri, body)) + + custom_headers_copy = custom_headers.copy() if custom_headers else {} + if self._connection._apiVersion >= 300 and 'Content-Type' not in custom_headers_copy: + custom_headers_copy['Content-Type'] = 'application/json-patch+json' + + task, entity = self._connection.patch(uri, body, custom_headers=custom_headers_copy) + + if not task: + return entity + + return self._task_monitor.wait_for_task(task, timeout) + + def get_by(self, field, value): + """ + This function uses get_all passing a filter. + + The search is case-insensitive. + + Args: + field: Field name to filter. + value: Value to filter. + Returns: + dict + """ + if not field: + logger.exception(RESOURCE_CLIENT_INVALID_FIELD) + raise ValueError(RESOURCE_CLIENT_INVALID_FIELD) + + filter = "\"{0}='{1}'\"".format(field, value) + results = self.get_all(filter=filter) + + # Workaround when the OneView filter does not work, it will filter again + if "." not in field: + # This filter only work for the first level + results = [item for item in results if str(item.get(field, '')).lower() == value.lower()] + + return results + + def get_by_name(self, name): + """ + Retrieve a resource by its name. + + Args: + name: Resource name. + + Returns: + dict + """ + result = self.get_by('name', name) + if not result: + return None + else: + self.data = result[0] + return self + + def get_by_uri(self, uri): + """ + Retrieve a resource by its id + """ + self. __validate_resource_uri(uri) + self.data = self.do_get(uri) + + return self + + @ensure_resource_client + def get_utilization(self, fields=None, filter=None, refresh=False, view=None): + """ + Retrieves historical utilization data for the specified resource, metrics, and time span. + + Args: + fields: + Name of the supported metric(s) to be retrieved in the format METRIC[,METRIC]... + If unspecified, all metrics supported are returned. + + filter (list or str): + Filters should be in the format FILTER_NAME=VALUE[,FILTER_NAME=VALUE]... + E.g.: 'startDate=2016-05-30T11:20:44.541Z,endDate=2016-05-30T19:20:44.541Z' + + startDate + Start date of requested starting time range in ISO 8601 format. If omitted, the startDate is + determined by the endDate minus 24 hours. + endDate + End date of requested starting time range in ISO 8601 format. When omitted, the endDate includes + the latest data sample available. + + If an excessive number of samples would otherwise be returned, the results will be segmented. The + caller is responsible for comparing the returned sliceStartTime with the requested startTime in the + response. If the sliceStartTime is greater than the oldestSampleTime and the requested start time, + the caller is responsible for repeating the request with endTime set to sliceStartTime to obtain the + next segment. This process is repeated until the full data set is retrieved. + + If the resource has no data, the UtilizationData is still returned but will contain no samples and + sliceStartTime/sliceEndTime will be equal. oldestSampleTime/newestSampleTime will still be set + appropriately (null if no data is available). If the filter does not happen to overlap the data + that a resource has, then the metric history service will return null sample values for any + missing samples. + + refresh: + Specifies that if necessary, an additional request will be queued to obtain the most recent + utilization data from the iLO. The response will not include any refreshed data. To track the + availability of the newly collected data, monitor the TaskResource identified by the refreshTaskUri + property in the response. If null, no refresh was queued. + + view: + Specifies the resolution interval length of the samples to be retrieved. This is reflected in the + resolution in the returned response. Utilization data is automatically purged to stay within storage + space constraints. Supported views are listed below: + + native + Resolution of the samples returned will be one sample for each 5-minute time period. This is the + default view and matches the resolution of the data returned by the iLO. Samples at this resolution + are retained up to one year. + hour + Resolution of the samples returned will be one sample for each 60-minute time period. Samples are + calculated by averaging the available 5-minute data samples that occurred within the hour, except + for PeakPower which is calculated by reporting the peak observed 5-minute sample value data during + the hour. Samples at this resolution are retained up to three years. + day + Resolution of the samples returned will be one sample for each 24-hour time period. One day is a + 24-hour period that starts at midnight GMT regardless of the time zone in which the appliance or + client is located. Samples are calculated by averaging the available 5-minute data samples that + occurred during the day, except for PeakPower which is calculated by reporting the peak observed + 5-minute sample value data during the day. Samples at this resolution are retained up to three + years. -from urllib.parse import quote -from hpOneView.resources.task_monitor import TaskMonitor -from hpOneView.exceptions import HPOneViewUnknownType, HPOneViewException -from hpOneView.exceptions import HPOneViewValueError + Returns: + dict + """ -RESOURCE_CLIENT_RESOURCE_WAS_NOT_PROVIDED = 'Resource was not provided' -RESOURCE_CLIENT_INVALID_FIELD = 'Invalid field was provided' -RESOURCE_CLIENT_INVALID_ID = 'Invalid id was provided' -RESOURCE_CLIENT_UNKNOWN_OBJECT_TYPE = 'Unknown object type' -UNRECOGNIZED_URI = 'Unrecognized URI for this resource' -RESOURCE_CLIENT_TASK_EXPECTED = "Failed: Expected a TaskResponse." -RESOURCE_ID_OR_URI_REQUIRED = 'It is required to inform the Resource ID or URI.' + uri = self.data['uri'] + query = '' + if filter: + query += self.__make_query_filter(filter) -logger = logging.getLogger(__name__) + if fields: + query += "&fields=" + quote(fields) + if refresh: + query += "&refresh=true" -def merge_resources(resource1, resource2): - """ - Updates a copy of resource1 with resource2 values and returns the merged dictionary. + if view: + query += "&view=" + quote(view) - Args: - resource1: original resource - resource2: resource to update resource1 + if query: + query = "?" + query[1:] - Returns: - dict: merged resource - """ - merged = resource1.copy() - merged.update(resource2) - return merged + uri = "{0}/utilization{1}".format(self.build_uri(uri), query) + return self.do_get(uri) -def merge_default_values(resource_list, default_values): - """ - Generate a new list where each item of original resource_list will be merged with the default_values. + @ensure_resource_client + def create_report(self, timeout=-1): + """ + Creates a report and returns the output. - Args: - resource_list: list with items to be merged - default_values: properties to be merged with each item list. If the item already contains some property - the original value will be maintained. + Args: + timeout: + Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation + in OneView; it just stops waiting for its completion. + Returns: + list: + """ + uri = self.data['uri'] + logger.debug('Creating Report (uri = %s)'.format(uri)) - Returns: - list: list containing each item merged with default_values - """ + task, _ = self._connection.post(uri, {}) - def merge_item(resource): - return merge_resources(default_values, resource) + if not task: + raise exceptions.HPOneViewException(RESOURCE_CLIENT_TASK_EXPECTED) - return lmap(merge_item, resource_list) + task = self._task_monitor.get_completed_task(task, timeout) + + return task['taskOutput'] + + def build_uri(self, id_or_uri): + if not id_or_uri: + logger.exception(RESOURCE_CLIENT_INVALID_ID) + raise ValueError(RESOURCE_CLIENT_INVALID_ID) + + if "/" in id_or_uri: + self.__validate_resource_uri(id_or_uri) + return id_or_uri + else: + return self._uri + "/" + id_or_uri + + def build_subresource_uri(self, resource_id_or_uri=None, subresource_id_or_uri=None, subresource_path=''): + if subresource_id_or_uri and "/" in subresource_id_or_uri: + return subresource_id_or_uri + else: + if not resource_id_or_uri: + raise exceptions.HPOneViewValueError(RESOURCE_ID_OR_URI_REQUIRED) + + resource_uri = self.build_uri(resource_id_or_uri) + + uri = "{}/{}/{}".format(resource_uri, subresource_path, str(subresource_id_or_uri or '')) + uri = uri.replace("//", "/") + + if uri.endswith("/"): + uri = uri[:-1] + + return uri + + def do_get(self, uri): + """ + Method to support get requests of the resource + """ + self.__validate_resource_uri(uri) + return self._connection.get(uri) + + def do_post(self, uri, resource, timeout, custom_headers): + """ + Method to support post requests of the resource + """ + self.__validate_resource_uri(uri) + + for field in self.EXCLUDE_FROM_REQUEST: + resource.pop(field, None) + + task, entity = self._connection.post(uri, resource, custom_headers=custom_headers) + + if not task: + return entity + + return self._task_monitor.wait_for_task(task, timeout) + + def do_put(self, uri, resource, timeout, custom_headers=None): + """ + Method to support put requests of the resource + """ + self.__validate_resource_uri(uri) + + task, body = self._connection.put(uri, resource, custom_headers=custom_headers) + + if not task: + return body + + return self._task_monitor.wait_for_task(task, timeout) + + def merge_resources(self, resource_to_merge): + """ + Updates a copy of resource1 with resource2 values and returns the merged dictionary. + + Args: + resource_to_merge: data to update resource + + Returns: + dict: merged resource + """ + merged = deepcopy(self.data) + merged.update(resource_to_merge) + return merged + + def upload(self, file_path, uri=None, timeout=-1): + """ + Makes a multipart request. + + Args: + file_path: + File to upload. + uri: + A specific URI (optional). + timeout: + Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation + in OneView; it just stops waiting for its completion. + + Returns: + dict: Response body. + """ + if not uri: + uri = self.URI + + self.__validate_resource_uri(uri) + + upload_file_name = os.path.basename(file_path) + task, entity = self._connection.post_multipart_with_response_handling(uri, file_path, upload_file_name) + + if not task: + return entity + + return self._task_monitor.wait_for_task(task, timeout) + + def download(self, uri, file_path): + """ + Downloads the contents of the requested URI to a stream. + + Args: + uri: URI + file_path: File path destination + + Returns: + bool: Indicates if the file was successfully downloaded. + """ + self.__validate_resource_uri(uri) + + with open(file_path, 'wb') as file: + return self._connection.download_to_stream(file, uri) + + def __validate_resource_uri(self, path): + if self.URI not in path: + logger.exception('Get by uri : unrecognized uri: (%s)' % path) + raise exceptions.HPOneViewUnknownType(UNRECOGNIZED_URI) + + def __make_query_filter(self, filters): + if isinstance(filters, list): + formated_filter = "&filter=".join(quote(f) for f in filters) + else: + formated_filter = quote(filters) + + return "&filter=" + formated_filter + + def __get_members(self, mlist): + if mlist and 'members' in mlist and mlist['members']: + return mlist['members'] + else: + return [] + + def __do_requests_to_getall(self, uri, requested_count): + items = [] + + while uri: + logger.debug('Making HTTP request to get all resources. Uri: {0}'.format(uri)) + response = self._connection.get(uri) + members = self.__get_members(response) + items += members + + logger.debug("Response getAll: nextPageUri = {0}, members list length: {1}".format(uri, str(len(members)))) + uri = self.__get_next_page(response, items, requested_count) + + logger.debug('Total # of members found = {0}'.format(str(len(items)))) + return items + + def __get_next_page(self, response, items, requested_count): + next_page_is_empty = response.get('nextPageUri') is None + has_different_next_page = not response.get('uri') == response.get('nextPageUri') + has_next_page = not next_page_is_empty and has_different_next_page + + if len(items) >= requested_count and requested_count != -1: + return None + + return response.get('nextPageUri') if has_next_page else None + + def _merge_default_values(self): + """ + Pick the default values for the api version and append that with resource data + """ + if self.DEFAULT_VALUES: + api_version = str(self._connection._apiVersion) + values = self.DEFAULT_VALUES.get(api_version, {}).copy() + self.data.update(values) + + @staticmethod + def unavailable_method(): + """ + Raise exception if method is not available for the resource + """ + raise exceptions.HPOneViewUnavailableMethod(UNAVAILABLE_METHOD) class ResourceClient(object): @@ -254,7 +1014,7 @@ def delete(self, resource, force=False, timeout=-1, custom_headers=None): uri = resource['uri'] else: logger.exception(RESOURCE_CLIENT_UNKNOWN_OBJECT_TYPE) - raise HPOneViewUnknownType(RESOURCE_CLIENT_UNKNOWN_OBJECT_TYPE) + raise exceptions.HPOneViewUnknownType(RESOURCE_CLIENT_UNKNOWN_OBJECT_TYPE) else: uri = self.build_uri(resource) @@ -688,7 +1448,7 @@ def create_report(self, uri, timeout=-1): task, _ = self._connection.post(uri, {}) if not task: - raise HPOneViewException(RESOURCE_CLIENT_TASK_EXPECTED) + raise exceptions.HPOneViewException(RESOURCE_CLIENT_TASK_EXPECTED) task = self._task_monitor.get_completed_task(task, timeout) @@ -710,7 +1470,7 @@ def build_subresource_uri(self, resource_id_or_uri=None, subresource_id_or_uri=N return subresource_id_or_uri else: if not resource_id_or_uri: - raise HPOneViewValueError(RESOURCE_ID_OR_URI_REQUIRED) + raise exceptions.HPOneViewValueError(RESOURCE_ID_OR_URI_REQUIRED) resource_uri = self.build_uri(resource_id_or_uri) @@ -739,7 +1499,7 @@ def download(self, uri, file_path): def __validate_resource_uri(self, path): if self._uri not in path: logger.exception('Get by uri : unrecognized uri: (%s)' % path) - raise HPOneViewUnknownType(UNRECOGNIZED_URI) + raise exceptions.HPOneViewUnknownType(UNRECOGNIZED_URI) def __make_query_filter(self, filters): if isinstance(filters, list): @@ -810,6 +1570,41 @@ def merge_default_values(self, resource, default_values): return merged_resource or resource +def merge_resources(resource1, resource2): + """ + Updates a copy of resource1 with resource2 values and returns the merged dictionary. + + Args: + resource1: original resource + resource2: resource to update resource1 + + Returns: + dict: merged resource + """ + merged = resource1.copy() + merged.update(resource2) + return merged + + +def merge_default_values(resource_list, default_values): + """ + Generate a new list where each item of original resource_list will be merged with the default_values. + + Args: + resource_list: list with items to be merged + default_values: properties to be merged with each item list. If the item already contains some property + the original value will be maintained. + + Returns: + list: list containing each item merged with default_values + """ + + def merge_item(resource): + return merge_resources(default_values, resource) + + return lmap(merge_item, resource_list) + + def transform_list_to_dict(list): """ Transforms a list into a dictionary, putting values as keys diff --git a/hpOneView/resources/servers/enclosures.py b/hpOneView/resources/servers/enclosures.py index d2178d54..d80c3d5a 100644 --- a/hpOneView/resources/servers/enclosures.py +++ b/hpOneView/resources/servers/enclosures.py @@ -29,60 +29,18 @@ standard_library.install_aliases() +from hpOneView.resources.resource import Resource, ensure_resource_client -from hpOneView.resources.resource import ResourceClient - -class Enclosures(object): +class Enclosures(Resource): """ Enclosures API client. """ URI = '/rest/enclosures' - def __init__(self, con): - self._connection = con - self._client = ResourceClient(con, self.URI) - - def get_all(self, start=0, count=-1, filter='', sort='', scope_uris=''): - """ - Gets a paginated collection of Enclosures. The collection is based on optional sorting and filtering, and - constrained by start and count parameters. - - Args: - start: - The first item to return, using 0-based indexing. - If not specified, the default is 0 - start with the first available item. - count: - The number of resources to return. A count of -1 requests all items. - The actual number of items in the response might differ from the requested - count if the sum of start and count exceeds the total number of items. - filter (list or str): - A general filter/query string to narrow the list of items returned. The - default is no filter; all resources are returned. - sort: - The sort order of the returned data set. By default, the sort order is based - on create time with the oldest entry first. - - Returns: - list: A list of Enclosures. - """ - return self._client.get_all(start, count, filter=filter, sort=sort, scope_uris=scope_uris) - - def get_by(self, field, value): - """ - Gets all Enclosures that match the filter. - - The search is case-insensitive. - - Args: - field: Field name to filter. - value: Value to filter. - - Returns: - list: A list of Enclosures. - """ - return self._client.get_by(field, value) + def __init__(self, connection, options=None): + super(Enclosures, self).__init__(connection, options) def add(self, information, timeout=-1): """ @@ -104,28 +62,16 @@ def add(self, information, timeout=-1): dict: Enclosure. """ - return self._client.create(information, timeout=timeout) - - def get(self, id_or_uri): - """ - Returns the enclosure with the specified ID, if it exists. - - Args: - id_or_uri: ID or URI of the Enclosure. - - Returns: - dict: Enclosure. - """ - return self._client.get(id_or_uri) + self.EXCLUDE_FROM_REQUEST = ['name'] + return self.create(data=information, timeout=timeout) - def patch(self, id_or_uri, operation, path, value, timeout=-1): + def patch(self, operation, path, value, timeout=-1): """ - Uses the PATCH to update a resource for a given enclosure. + Uses the PATCH to update a resource. Only one operation can be performed in each PATCH call. - Args: - id_or_uri: Can be either the resource ID or the resource URI. + Args operation: Patch operation path: Path value: Value @@ -133,68 +79,56 @@ def patch(self, id_or_uri, operation, path, value, timeout=-1): in OneView; it just stops waiting for its completion. Returns: - dict: Updated resource. + Updated resource. """ + patch_request_body = [{'op': operation, 'path': path, 'value': value}] + headers = {'If-Match': '*'} - return self._client.patch(id_or_uri, operation, path, value, timeout=timeout, custom_headers=headers) + self.data = self.patch_request(body=patch_request_body, + timeout=timeout, + custom_headers=headers) + return self - def remove(self, resource, force=False, timeout=-1): + def remove(self, force=False): """ - Removes and unconfigures the specified enclosure from the appliance. All components of the enclosure (for - example: blades and interconnects) are unconfigured/removed as part of this process. - - If the force option is set to "true", then any errors encountered as part of unconfiguring the enclosure or its - components are ignored and the enclosure is removed regardless of any errors that occur. - - Args: - resource: Dict object to delete; - force: - If set to true, the operation completes despite any problems with - network connectivity or errors on the resource itself. The default is false. - timeout: Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation - in OneView; it just stops waiting for its completion. - - Returns: - bool: Indicates if the resource was successfully removed. + Remove enclosure """ - return self._client.delete(resource, force=force, timeout=timeout) + self.delete(force=force) - def update_configuration(self, id_or_uri, timeout=-1): + @ensure_resource_client + def update_configuration(self, timeout=-1): """ Reapplies the appliance's configuration on the enclosure. This includes running the same configure steps that were performed as part of the enclosure add. Args: - id_or_uri: Can be either the resource ID or the resource URI. timeout: Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation in OneView; it just stops waiting for its completion. Returns: Enclosure """ - uri = self._client.build_uri(id_or_uri) + "/configuration" - return self._client.update_with_zero_body(uri, timeout=timeout) + path = "/configuration" + return self.update_with_zero_body(path=path, timeout=timeout) - def get_environmental_configuration(self, id_or_uri): + @ensure_resource_client + def get_environmental_configuration(self): """ Gets the settings that describe the environmental configuration (supported feature set, calibrated minimum & maximum power, location & dimensions, ...) of the enclosure resource. - Args: - id_or_uri: Can be either the resource ID or the resource URI. - Returns: Settings that describe the environmental configuration. """ - uri = self._client.build_uri(id_or_uri) + '/environmentalConfiguration' - return self._client.get(uri) + uri = '{}/environmentalConfiguration'.format(self.data['uri']) + return self.do_get(uri) - def update_environmental_configuration(self, id_or_uri, configuration, timeout=-1): + @ensure_resource_client + def update_environmental_configuration(self, configuration, timeout=-1): """ Sets the calibrated max power of an unmanaged or unsupported enclosure. Args: - id_or_uri: Can be either the resource ID or the resource URI. configuration: Configuration timeout: Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation in OneView; it just stops waiting for its completion. @@ -202,10 +136,11 @@ def update_environmental_configuration(self, id_or_uri, configuration, timeout=- Returns: Settings that describe the environmental configuration. """ - uri = self._client.build_uri(id_or_uri) + '/environmentalConfiguration' - return self._client.update(configuration, uri=uri, timeout=timeout) + uri = '{}/environmentalConfiguration'.format(self.data['uri']) + return self.do_put(uri, configuration, timeout, None) - def refresh_state(self, id_or_uri, configuration, timeout=-1): + @ensure_resource_client + def refresh_state(self, configuration, timeout=-1): """ Refreshes the enclosure along with all of its components, including interconnects and servers. Any new hardware is added and any hardware that is no longer present within the enclosure is removed. The @@ -213,7 +148,6 @@ def refresh_state(self, id_or_uri, configuration, timeout=-1): provide information to re-claim the enclosure (for example: IP address, user name, password, etc.). Args: - id_or_uri: Can be either the resource ID or the resource URI. configuration: Configuration timeout: Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation in OneView; it just stops waiting for its completion. @@ -221,174 +155,92 @@ def refresh_state(self, id_or_uri, configuration, timeout=-1): Returns: Enclosure """ - uri = self._client.build_uri(id_or_uri) + "/refreshState" - return self._client.update(configuration, uri=uri, timeout=timeout) + uri = "{}/refreshState".format(self.data['uri']) + return self.do_put(uri, configuration, timeout, None) - def get_script(self, id_or_uri): + @ensure_resource_client + def get_script(self): """ Gets the script of the enclosure. - Args: - id_or_uri: Can be either the resource ID or the resource URI. - Returns: Enclosure script. """ - uri = self._client.build_uri(id_or_uri) + "/script" - return self._client.get(uri) + uri = "{}/script".format(self.data['uri']) + return self.do_get(uri) - def get_sso(self, id_or_uri, role): + @ensure_resource_client + def get_sso(self, role): """ Builds the SSO (Single Sign-On) URL parameters for the specified enclosure. This allows the user to log in to the enclosure without providing credentials. This API is currently only supported by C7000 enclosures. Args: - id_or_uri: Can be either the resource ID or the resource URI. role: Role Returns: SSO (Single Sign-On) URL parameters. """ - uri = self._client.build_uri(id_or_uri) + "/sso?role=%s" % role - return self._client.get(uri) + uri = "{}/sso?role={}".format(self.data['uri'], role) + return self.do_get(uri) - def get_utilization(self, id_or_uri, fields=None, filter=None, refresh=False, view=None): - """ - Retrieves historical utilization data for the specified enclosure, metrics, and time span. - - Args: - id_or_uri: Can be either the resource ID or the resource URI. - fields: Name of the metrics to be retrieved in the format METRIC[,METRIC]... - - If unspecified, all metrics supported are returned. - - Enclosures support the following utilization metrics: - AmbientTemperature - Inlet air temperature in degrees Celsius during this sample interval. - AveragePower - Average power consumption in Watts during this sample interval. - PeakPower - Peak power consumption in Watts during this sample interval. - PowerCap - Dynamic power cap setting on the server hardware in Watts during this sample interval. - DeratedCapacity - Enclosure dynamic power cap derated capacity setting in Watts during this sample interval. - RatedCapacity - Enclosure dynamic power cap rated capacity setting in Watts during this sample interval. - - filter (list or str): - Provides an expression of the requested time range of data. One condition (startDate/endDate) is - specified per filter specification as described below. The condition must be specified via the - equals (=) operator. - - startDate - Start date of requested starting time range in ISO 8601 format (2016-05-31T07:20:00.000Z). - If omitted, the startDate is determined by the endDate minus 24 hours. - endDate - End date of requested starting time range in ISO 8601 format. When omitted the endDate includes the - latest data sample available. - - If an excessive number of samples would otherwise be returned, the results will be segmented. The caller - is responsible for comparing the returned sliceStartTime with the requested startTime in the response. - If the sliceStartTime is greater than the oldestSampleTime and the requested start time, the caller is - responsible for repeating the request with endTime set to sliceStartTime to obtain the next segment. - This process is repeated until the full data set is retrieved. - - If the resource has no data, the UtilizationData is still returned, but will contain no samples and - sliceStartTime/sliceEndTime will be equal. oldestSampleTime/newestSampleTime will still be set - appropriately (null if no data is available). If the filter just does not happen to overlap the data - that a resource does have, then the metric history service will return null sample values for any - missing samples. - - refresh: - Specifies that if necessary an additional request will be queued to obtain the most recent utilization - data from the enclosure. The response will not include any refreshed data. To track the availability - of the newly collected data, monitor the TaskResource identified by the refreshTaskUri property in - the response. If null, no refresh was queued. - view: - Specifies the resolution interval length of the samples to be retrieved. This is reflected in the - resolution in the returned response. Utilization data is automatically purged to stay within storage - space constraints. Supported views are listed below. - - native (DEFAULT) - Resolution of the samples returned will be one sample for each 5-minute time period. This is the - default view and matches the resolution of the data returned by the enclosure. Samples at this - resolution are retained up to one year. - hour - Resolution of the samples returned will be one sample for each 60-minute time period. Samples are - calculated by averaging the available 5-minute data samples that occurred within the hour, except - for PeakPower, which is calculated by reporting the peak observed 5-minute sample value data during - the hour. Samples at this resolution are retained up to three years. - day - Resolution of the samples returned will be one sample for each 24-hour time period. One day is a - 24-hour period that starts at midnight GMT, regardless of the time zone in which the appliance or - client is located. Samples are calculated by averaging the available 5-minute data samples that - occurred during the day, except for PeakPower, which is calculated by reporting the peak observed - 5-minute sample value data during the day. Samples at this resolution are retained up to three - years. - - Returns: - dict - """ - return self._client.get_utilization(id_or_uri, fields=fields, filter=filter, refresh=refresh, view=view) - - def generate_csr(self, csr_data, id_or_uri, bay_number=None): + @ensure_resource_client + def generate_csr(self, csr_data, bay_number=None): """ Creates a Certificate Signing Request (CSR) for an enclosure. Args: csr_data: Dictionary with csr details. - id_or_uri: Can be either the resource ID or the resource URI. bay_number: OA from which the CSR should be generated. Returns: Enclosure. """ - - uri = self._client.build_uri(id_or_uri) + "/https/certificaterequest" + uri = "{}/https/certificaterequest".format(self.data['uri']) if bay_number: uri += "?bayNumber=%d" % (bay_number) headers = {'Content-Type': 'application/json'} - return self._client.create(csr_data, uri=uri, custom_headers=headers) - def get_csr(self, id_or_uri, bay_number=None): + return self.do_post(uri, csr_data, -1, headers) + + @ensure_resource_client + def get_csr(self, bay_number=None): """ Get an enclosure's Certificate Signing Request (CSR) that was generated by previous POST to the same URI. Args: - id_or_uri: Can be either the resource ID or the resource URI. bay_number: OA to retrieve the previously generated CSR. Returns: dict """ - uri = self._client.build_uri(id_or_uri) + "/https/certificaterequest" + uri = "{}/https/certificaterequest".format(self.data['uri']) if bay_number: uri += "?bayNumber=%d" % (bay_number) - return self._client.get(uri) + return self.do_get(uri) - def import_certificate(self, certificate_data, id_or_uri, bay_number=None): + @ensure_resource_client + def import_certificate(self, certificate_data, bay_number=None): """ Imports a signed server certificate into the enclosure. Args: certificate_data: Dictionary with Signed certificate and type. - id_or_uri: Can be either the resource ID or the resource URI. bay_number: OA to which the signed certificate will be imported. Returns: Enclosure. """ - uri = self._client.build_uri(id_or_uri) + "/https/certificaterequest" + uri = "{}/https/certificaterequest".format(self.data['uri']) if bay_number: uri += "?bayNumber=%d" % (bay_number) headers = {'Content-Type': 'application/json'} - return self._client.update(certificate_data, uri=uri, custom_headers=headers) + return self.do_put(uri, certificate_data, -1, headers) diff --git a/hpOneView/resources/servers/id_pools.py b/hpOneView/resources/servers/id_pools.py index 211f6e3c..5c3b2e1c 100644 --- a/hpOneView/resources/servers/id_pools.py +++ b/hpOneView/resources/servers/id_pools.py @@ -29,37 +29,39 @@ standard_library.install_aliases() -from hpOneView.resources.resource import ResourceClient +from hpOneView.resources.resource import Resource, ensure_resource_client -class IdPools(object): +class IdPools(Resource): """ Class for Id Pools API client. """ URI = '/rest/id-pools' - def __init__(self, con): - self._client = ResourceClient(con, self.URI) + def __init__(self, connection, options=None): + super(IdPools, self).__init__(connection, options) - def get(self, id_or_uri): + def get_by_name(self, name): """ - Gets a pool. - - Args: - id_or_uri: Can be either the range ID or URI. + Retrieve a id pool + """ + uri = '{}/{}'.format(self.URI, name) + self.data = self.do_get(uri) + return self - Returns: - dict: Pool resource. + def get_by_uri(self, uri): """ - return self._client.get(id_or_uri) + Retrieve a id pool + """ + self.data = self.do_get(uri) + return self - def enable(self, information, id_or_uri, timeout=-1): + def enable(self, information, timeout=-1): """ Enables or disables a pool. Args: information (dict): Information to update. - id_or_uri: ID or URI of range. timeout: Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation in OneView; it just stops waiting for its completion. @@ -67,26 +69,26 @@ def enable(self, information, id_or_uri, timeout=-1): dict: Updated resource. """ - uri = self._client.build_uri(id_or_uri) - return self._client.update(information, uri, timeout=timeout) + return self.update(information, timeout=timeout) - def validate_id_pool(self, id_or_uri, ids_pools): + @ensure_resource_client + def validate_id_pool(self, ids_pools): """ Validates an ID pool. Args: - id_or_uri: - ID or URI of range. ids_pools (list): List of Id Pools. Returns: dict: A dict containing a list with IDs. """ - uri = self._client.build_uri(id_or_uri) + "/validate?idList=" + "&idList=".join(ids_pools) - return self._client.get(uri) - def validate(self, information, id_or_uri, timeout=-1): + uri = self.data['uri'] + "/validate?idList=" + "&idList=".join(ids_pools) + return self.do_get(uri) + + @ensure_resource_client + def validate(self, information, timeout=-1): """ Validates a set of user specified IDs to reserve in the pool. @@ -95,8 +97,6 @@ def validate(self, information, id_or_uri, timeout=-1): Args: information (dict): Information to update. Can result in system specified IDs or the system reserving user-specified IDs. - id_or_uri: - ID or URI of vSN range. timeout: Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation in OneView; it just stops waiting for its completion. @@ -104,10 +104,12 @@ def validate(self, information, id_or_uri, timeout=-1): Returns: dict: A dict containing a list with IDs. """ - uri = self._client.build_uri(id_or_uri) + "/validate" - return self._client.update(information, uri, timeout=timeout) - def allocate(self, information, id_or_uri, timeout=-1): + uri = '{}/validate'.format(self.data['uri']) + return self.do_put(uri, information, timeout=timeout) + + @ensure_resource_client + def allocate(self, information, timeout=-1): """ Allocates a set of IDs from range. @@ -116,8 +118,6 @@ def allocate(self, information, id_or_uri, timeout=-1): Args: information (dict): Information to update. Can result in system specified IDs or the system reserving user-specified IDs. - id_or_uri: - ID or URI of vSN range. timeout: Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation in OneView; it just stops waiting for its completion. @@ -125,19 +125,18 @@ def allocate(self, information, id_or_uri, timeout=-1): Returns: dict: A dict containing a list with IDs. """ - uri = self._client.build_uri(id_or_uri) + "/allocator" - return self._client.update(information, uri, timeout=timeout) + uri = '{}/allocator'.format(self.data['uri']) + return self.do_put(uri, information, timeout=timeout) - def collect(self, information, id_or_uri, timeout=-1): + @ensure_resource_client + def collect(self, information, timeout=-1): """ Collects one or more IDs to be returned to a pool. Args: information (dict): The list of IDs to be collected - id_or_uri: - ID or URI of range timeout: Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation in OneView; it just stops waiting for its completion. @@ -145,17 +144,16 @@ def collect(self, information, id_or_uri, timeout=-1): Returns: dict: Collector containing list of collected IDs successfully collected. """ - uri = self._client.build_uri(id_or_uri) + "/collector" - return self._client.update(information, uri, timeout=timeout) + uri = '{}/collector'.format(self.data['uri']) + return self.do_put(uri, information, timeout=timeout) - def get_check_range_availability(self, id_or_uri, ids_pools): + @ensure_resource_client + def get_check_range_availability(self, ids_pools): """ Checks the range availability in the ID pool. Args: - id_or_uri: - ID or URI of range. ids_pools (list): List of Id Pools. @@ -163,19 +161,20 @@ def get_check_range_availability(self, id_or_uri, ids_pools): dict: A dict containing a list with IDs. """ - uri = self._client.build_uri(id_or_uri) + "/checkrangeavailability?idList=" + "&idList=".join(ids_pools) - return self._client.get(uri) + uri = self.data['uri'] + "/checkrangeavailability?idList=" + "&idList=".join(ids_pools) + return self.do_get(uri) - def generate(self, id_or_uri): + @ensure_resource_client + def generate(self): """ Generates and returns a random range. Args: - id_or_uri: - ID or URI of range. + None Returns: dict: A dict containing a list with IDs. """ - uri = self._client.build_uri(id_or_uri) + "/generate" - return self._client.get(uri) + + uri = '{}/generate'.format(self.data['uri']) + return self.do_get(uri) diff --git a/tests/unit/resources/networking/test_fc_networks.py b/tests/unit/resources/networking/test_fc_networks.py index 98499a6a..d51ba201 100644 --- a/tests/unit/resources/networking/test_fc_networks.py +++ b/tests/unit/resources/networking/test_fc_networks.py @@ -27,25 +27,25 @@ from hpOneView.connection import connection from hpOneView.resources.networking.fc_networks import FcNetworks -from hpOneView.resources.resource import ResourceClient +from hpOneView.resources.resource import Resource class FcNetworksTest(unittest.TestCase): + def setUp(self): self.host = '127.0.0.1' self.connection = connection(self.host) self._fc_networks = FcNetworks(self.connection) - @mock.patch.object(ResourceClient, 'get_all') + @mock.patch.object(Resource, 'get_all') def test_get_all_called_once(self, mock_get_all): filter = 'name=TestName' sort = 'name:ascending' self._fc_networks.get_all(2, 500, filter, sort) + mock_get_all.assert_called_once_with(2, 500, filter, sort) - mock_get_all.assert_called_once_with(2, 500, filter=filter, sort=sort) - - @mock.patch.object(ResourceClient, 'create') + @mock.patch.object(Resource, 'create') def test_create_should_use_given_values(self, mock_create): resource = { 'name': 'OneViewSDK Test FC Network', @@ -58,10 +58,9 @@ def test_create_should_use_given_values(self, mock_create): mock_create.return_value = {} self._fc_networks.create(resource, 30) - mock_create.assert_called_once_with(resource_rest_call, timeout=30, - default_values=self._fc_networks.DEFAULT_VALUES) + mock_create.assert_called_once_with(resource_rest_call, 30) - @mock.patch.object(ResourceClient, 'update') + @mock.patch.object(Resource, 'update') def test_update_should_use_given_values(self, mock_update): resource = { 'name': 'OneViewSDK Test FC Network', @@ -75,39 +74,29 @@ def test_update_should_use_given_values(self, mock_update): mock_update.return_value = {} self._fc_networks.update(resource, 60) - mock_update.assert_called_once_with(resource_rest_call, timeout=60, - default_values=self._fc_networks.DEFAULT_VALUES) + mock_update.assert_called_once_with(resource_rest_call, 60) - @mock.patch.object(ResourceClient, 'delete') + @mock.patch.object(Resource, 'delete') def test_delete_called_once(self, mock_delete): - id = 'ad28cf21-8b15-4f92-bdcf-51cb2042db32' - self._fc_networks.delete(id, force=False, timeout=-1) - - mock_delete.assert_called_once_with(id, force=False, timeout=-1) + self._fc_networks.delete(force=False, timeout=-1) + mock_delete.assert_called_once_with(force=False, timeout=-1) - @mock.patch.object(ResourceClient, 'get_by') + @mock.patch.object(Resource, 'get_by') def test_get_by_called_once(self, mock_get_by): self._fc_networks.get_by('name', 'OneViewSDK "Test FC Network') - mock_get_by.assert_called_once_with('name', 'OneViewSDK "Test FC Network') - @mock.patch.object(ResourceClient, 'get') - def test_get_called_once(self, mock_get): - self._fc_networks.get('3518be0e-17c1-4189-8f81-83f3724f6155') - - mock_get.assert_called_once_with('3518be0e-17c1-4189-8f81-83f3724f6155') - - @mock.patch.object(ResourceClient, 'get') + @mock.patch.object(Resource, 'get_by_uri') def test_get_with_uri_called_once(self, mock_get): uri = '/rest/fc-networks/3518be0e-17c1-4189-8f81-83f3724f6155' - self._fc_networks.get(uri) + self._fc_networks.get_by_uri(uri) mock_get.assert_called_once_with(uri) - @mock.patch.object(ResourceClient, 'patch') + @mock.patch.object(Resource, 'patch') def test_patch_should_use_user_defined_values(self, mock_patch): mock_patch.return_value = {} self._fc_networks.patch('/rest/fake/fc123', 'replace', '/scopeUris', ['/rest/fake/scope123'], 1) mock_patch.assert_called_once_with('/rest/fake/fc123', 'replace', '/scopeUris', - ['/rest/fake/scope123'], timeout=1) + ['/rest/fake/scope123'], 1) diff --git a/tests/unit/resources/servers/test_enclosures.py b/tests/unit/resources/servers/test_enclosures.py index 6359dea6..57a72886 100644 --- a/tests/unit/resources/servers/test_enclosures.py +++ b/tests/unit/resources/servers/test_enclosures.py @@ -27,7 +27,7 @@ from hpOneView.connection import connection from hpOneView.resources.servers.enclosures import Enclosures -from hpOneView.resources.resource import ResourceClient +from hpOneView.resources.resource import Resource class EnclosuresTest(TestCase): @@ -35,30 +35,28 @@ def setUp(self): self.host = '127.0.0.1' self.connection = connection(self.host) self._enclosures = Enclosures(self.connection) + self._enclosures.data = {'uri': '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32'} - @mock.patch.object(ResourceClient, 'get_all') + @mock.patch.object(Resource, 'get_all') def test_get_all_called_once(self, mock_get_all): filter = 'name=TestName' sort = 'name:ascending' scope_uris = 'rest/scopes/cd237b60-09e2-45c4-829e-082e318a6d2a' - self._enclosures.get_all(2, 500, filter, sort, scope_uris) + self._enclosures.get_all(2, 500, filter, sort, scope_uris=scope_uris) + mock_get_all.assert_called_once_with(2, 500, filter, sort, scope_uris=scope_uris) - mock_get_all.assert_called_once_with(2, 500, filter=filter, sort=sort, scope_uris=scope_uris) - - @mock.patch.object(ResourceClient, 'get_all') + @mock.patch.object(Resource, 'get_all') def test_get_all_called_once_with_default_values(self, mock_get_all): - self._enclosures.get_all() - - mock_get_all.assert_called_once_with(0, -1, filter='', sort='', scope_uris='') + self._enclosures.get_all(0, -1) + mock_get_all.assert_called_once_with(0, -1) - @mock.patch.object(ResourceClient, 'get_by') + @mock.patch.object(Resource, 'get_by') def test_get_by_called_once(self, mock_get_by): self._enclosures.get_by('name', 'OneViewSDK-Test-Enclosure') - mock_get_by.assert_called_once_with('name', 'OneViewSDK-Test-Enclosure') - @mock.patch.object(ResourceClient, 'create') + @mock.patch.object(Resource, 'create') def test_add_called_once(self, mock_create): information = { 'enclosureGroupUri': '/rest/enclosure-groups/id-enclosure-group' @@ -66,160 +64,93 @@ def test_add_called_once(self, mock_create): mock_create.return_value = {} self._enclosures.add(information) - mock_create.assert_called_once_with(information.copy(), timeout=-1) + mock_create.assert_called_once_with(data=information.copy(), timeout=-1) - @mock.patch.object(ResourceClient, 'get') - def test_get_called_once(self, mock_get): - self._enclosures.get('3518be0e-17c1-4189-8f81-83f3724f6155') - - mock_get.assert_called_once_with('3518be0e-17c1-4189-8f81-83f3724f6155') - - @mock.patch.object(ResourceClient, 'get') + @mock.patch.object(Resource, 'get_by_uri') def test_get_with_uri_called_once(self, mock_get): uri = '/rest/enclosures/3518be0e-17c1-4189-8f81-83f3724f6155' - self._enclosures.get(uri) + self._enclosures.get_by_uri(uri) mock_get.assert_called_once_with(uri) - @mock.patch.object(ResourceClient, 'patch') - def test_patch_should_use_user_defined_values(self, mock_patch): + @mock.patch.object(Resource, 'load_resource') + @mock.patch.object(Resource, 'patch_request') + def test_patch_should_use_user_defined_values(self, mock_patch, load_resource): mock_patch.return_value = {} - self._enclosures.patch('123a53cz', 'replace', '/name', 'new_name', 1) - mock_patch.assert_called_once_with('123a53cz', 'replace', '/name', 'new_name', - custom_headers={'If-Match': '*'}, timeout=1) + self._enclosures.patch('replace', '/name', 'new_name', 1) + mock_patch.assert_called_once_with(body=[{u'path': '/name', u'value': 'new_name', u'op': 'replace'}], + custom_headers={u'If-Match': u'*'}, timeout=1) - @mock.patch.object(ResourceClient, 'delete') + @mock.patch.object(Resource, 'delete') def test_remove_called_once(self, mock_delete): - id = 'ad28cf21-8b15-4f92-bdcf-51cb2042db32' - self._enclosures.remove(id, force=False) - - mock_delete.assert_called_once_with(id, force=False, timeout=-1) + self._enclosures.remove(force=False) + mock_delete.assert_called_once_with(force=False) - @mock.patch.object(ResourceClient, 'delete') + @mock.patch.object(Resource, 'delete') def test_remove_called_once_with_force(self, mock_delete): - id = 'ad28cf21-8b15-4f92-bdcf-51cb2042db32' - self._enclosures.remove(id, force=True) - - mock_delete.assert_called_once_with(id, force=True, timeout=-1) - - @mock.patch.object(ResourceClient, 'update_with_zero_body') - def test_update_configuration_by_uri(self, mock_update_with_zero_body): - uri_enclosure = '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32' - uri_rest_call = '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32/configuration' - - self._enclosures.update_configuration(uri_enclosure) - - mock_update_with_zero_body.assert_called_once_with(uri_rest_call, timeout=-1) - - @mock.patch.object(ResourceClient, 'update_with_zero_body') - def test_update_configuration_by_id(self, mock_update_with_zero_body): - id_enclosure = 'ad28cf21-8b15-4f92-bdcf-51cb2042db32' - uri_rest_call = '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32/configuration' - - self._enclosures.update_configuration(id_enclosure) - - mock_update_with_zero_body.assert_called_once_with(uri_rest_call, timeout=-1) - - @mock.patch.object(ResourceClient, 'get') - def test_get_environmental_configuration_by_uri(self, mock_get): - uri_enclosure = '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32' + self._enclosures.remove(force=True) + mock_delete.assert_called_once_with(force=True) + + @mock.patch.object(Resource, 'load_resource') + @mock.patch.object(Resource, 'update_with_zero_body') + def test_update_configuration_by_uri(self, mock_update_with_zero_body, load_resource): + self._enclosures.update_configuration() + mock_update_with_zero_body.assert_called_once_with(path='/configuration', timeout=-1) + + @mock.patch.object(Resource, 'load_resource') + @mock.patch.object(Resource, 'do_get') + def test_get_environmental_configuration_by_uri(self, mock_get, load_resource): uri_rest_call = '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32/environmentalConfiguration' - self._enclosures.get_environmental_configuration(uri_enclosure) - + self._enclosures.get_environmental_configuration() mock_get.assert_called_once_with(uri_rest_call) - @mock.patch.object(ResourceClient, 'get') - def test_get_environmental_configuration_by_id(self, mock_get): - id_enclosure = 'ad28cf21-8b15-4f92-bdcf-51cb2042db32' + @mock.patch.object(Resource, 'load_resource') + @mock.patch.object(Resource, 'do_get') + def test_get_environmental_configuration_by_id(self, mock_get, load_resource): uri_rest_call = '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32/environmentalConfiguration' - self._enclosures.get_environmental_configuration(id_enclosure) - + self._enclosures.get_environmental_configuration() mock_get.assert_called_once_with(uri_rest_call) - @mock.patch.object(ResourceClient, 'update') - def test_update_environmental_configuration_by_uri(self, mock_update): - uri_enclosure = '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32' - uri_rest_call = '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32/environmentalConfiguration' - configuration = {"calibratedMaxPower": 2500} - configuration_rest_call = configuration.copy() - - self._enclosures.update_environmental_configuration(uri_enclosure, configuration) - - mock_update.assert_called_once_with(configuration_rest_call, uri=uri_rest_call, timeout=-1) - - @mock.patch.object(ResourceClient, 'update') - def test_update_environmental_configuration_by_id(self, mock_update): - id_enclosure = 'ad28cf21-8b15-4f92-bdcf-51cb2042db32' + @mock.patch.object(Resource, 'load_resource') + @mock.patch.object(Resource, 'do_put') + def test_update_environmental_configuration_by_uri(self, mock_put, load_resource): uri_rest_call = '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32/environmentalConfiguration' configuration = {"calibratedMaxPower": 2500} configuration_rest_call = configuration.copy() - self._enclosures.update_environmental_configuration(id_enclosure, configuration) - - mock_update.assert_called_once_with(configuration_rest_call, uri=uri_rest_call, timeout=-1) - - @mock.patch.object(ResourceClient, 'update') - def test_refresh_state_by_uri(self, mock_update): - uri_enclosure = '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32' - uri_rest_call = '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32/refreshState' - configuration = {"refreshState": "RefreshPending"} - configuration_rest_call = configuration.copy() - - self._enclosures.refresh_state(uri_enclosure, configuration) - - mock_update.assert_called_once_with(configuration_rest_call, uri=uri_rest_call, timeout=-1) + self._enclosures.update_environmental_configuration(configuration, timeout=-1) + mock_put.assert_called_once_with(uri_rest_call, configuration_rest_call, -1, None) - @mock.patch.object(ResourceClient, 'update') - def test_refresh_state_by_id(self, mock_update): - id_enclosure = 'ad28cf21-8b15-4f92-bdcf-51cb2042db32' + @mock.patch.object(Resource, 'load_resource') + @mock.patch.object(Resource, 'do_put') + def test_refresh_state_by_uri(self, mock_put, load_resource): uri_rest_call = '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32/refreshState' configuration = {"refreshState": "RefreshPending"} configuration_rest_call = configuration.copy() - self._enclosures.refresh_state(id_enclosure, configuration) - - mock_update.assert_called_once_with(configuration_rest_call, uri=uri_rest_call, timeout=-1) + self._enclosures.refresh_state(configuration) + mock_put.assert_called_once_with(uri_rest_call, configuration_rest_call, -1, None) - @mock.patch.object(ResourceClient, 'get') - def test_get_script_by_uri(self, mock_get): - uri_enclosure = '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32' + @mock.patch.object(Resource, 'load_resource') + @mock.patch.object(Resource, 'do_get') + def test_get_script_by_uri(self, mock_get, load_resource): uri_rest_call = '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32/script' - self._enclosures.get_script(uri_enclosure) - - mock_get.assert_called_once_with(uri_rest_call) - - @mock.patch.object(ResourceClient, 'get') - def test_get_script_by_id(self, mock_get): - id_enclosure = 'ad28cf21-8b15-4f92-bdcf-51cb2042db32' - uri_rest_call = '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32/script' - - self._enclosures.get_script(id_enclosure) - + self._enclosures.get_script() mock_get.assert_called_once_with(uri_rest_call) - @mock.patch.object(ResourceClient, 'get') - def test_get_sso_by_uri(self, mock_get): - uri_enclosure = '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32' + @mock.patch.object(Resource, 'load_resource') + @mock.patch.object(Resource, 'do_get') + def test_get_sso_by_uri(self, mock_get, load_resource): uri_rest_call = '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32/sso?role=Active' - self._enclosures.get_sso(uri_enclosure, 'Active') - - mock_get.assert_called_once_with(uri_rest_call) - - @mock.patch.object(ResourceClient, 'get') - def test_get_sso_by_id(self, mock_get): - id_enclosure = 'ad28cf21-8b15-4f92-bdcf-51cb2042db32' - uri_rest_call = '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32/sso?role=Active' - - self._enclosures.get_sso(id_enclosure, 'Active') - + self._enclosures.get_sso('Active') mock_get.assert_called_once_with(uri_rest_call) - @mock.patch.object(ResourceClient, 'get_utilization') + @mock.patch.object(Resource, 'get_utilization') def test_get_utilization_with_all_args(self, mock_get_utilization): self._enclosures.get_utilization('09USE7335NW3', fields='AmbientTemperature,AveragePower,PeakPower', filter='startDate=2016-05-30T03:29:42.361Z', @@ -229,23 +160,15 @@ def test_get_utilization_with_all_args(self, mock_get_utilization): filter='startDate=2016-05-30T03:29:42.361Z', refresh=True, view='day') - @mock.patch.object(ResourceClient, 'get_utilization') - def test_get_utilization_by_id_with_defaults(self, mock_get): - self._enclosures.get_utilization('09USE7335NW3') - - mock_get.assert_called_once_with('09USE7335NW3', fields=None, filter=None, refresh=False, view=None) - - @mock.patch.object(ResourceClient, 'get_utilization') + @mock.patch.object(Resource, 'get_utilization') def test_get_utilization_by_uri_with_defaults(self, mock_get): self._enclosures.get_utilization('/rest/enclosures/09USE7335NW3') + mock_get.assert_called_once_with('/rest/enclosures/09USE7335NW3') - mock_get.assert_called_once_with('/rest/enclosures/09USE7335NW3', - fields=None, filter=None, refresh=False, view=None) - - @mock.patch.object(ResourceClient, 'create') - def test_generate_csr(self, mock_create): + @mock.patch.object(Resource, 'load_resource') + @mock.patch.object(Resource, 'do_post') + def test_generate_csr(self, mock_post, load_resource): bay_number = 1 - id_enclosure = 'ad28cf21-8b15-4f92-bdcf-51cb2042db32' uri_rest_call = '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32/https/certificaterequest?bayNumber=%d' % (bay_number) csr_data = { 'type': 'CertificateDtoV2', @@ -258,24 +181,22 @@ def test_generate_csr(self, mock_create): } headers = {'Content-Type': 'application/json'} - self._enclosures.generate_csr(csr_data, id_enclosure, bay_number=bay_number) + self._enclosures.generate_csr(csr_data, bay_number=bay_number) + mock_post.assert_called_once_with(uri_rest_call, csr_data, -1, headers) - mock_create.assert_called_once_with(csr_data, uri=uri_rest_call, custom_headers=headers) - - @mock.patch.object(ResourceClient, 'get') - def test_get_csr(self, mock_get): + @mock.patch.object(Resource, 'load_resource') + @mock.patch.object(Resource, 'do_get') + def test_get_csr(self, mock_get, load_resource): bay_number = 1 - id_enclosure = 'ad28cf21-8b15-4f92-bdcf-51cb2042db32' uri_rest_call = '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32/https/certificaterequest?bayNumber=%d' % (bay_number) - self._enclosures.get_csr(id_enclosure, bay_number=bay_number) - + self._enclosures.get_csr(bay_number=bay_number) mock_get.assert_called_once_with(uri_rest_call) - @mock.patch.object(ResourceClient, 'update') - def test_import_certificate(self, mock_update): + @mock.patch.object(Resource, 'load_resource') + @mock.patch.object(Resource, 'do_put') + def test_import_certificate(self, mock_put, load_resource): bay_number = 1 - id_enclosure = 'ad28cf21-8b15-4f92-bdcf-51cb2042db32' uri_rest_call = '/rest/enclosures/ad28cf21-8b15-4f92-bdcf-51cb2042db32/https/certificaterequest?bayNumber=%d' % (bay_number) certificate_data = { 'type': 'CertificateDataV2', @@ -283,6 +204,5 @@ def test_import_certificate(self, mock_update): } headers = {'Content-Type': 'application/json'} - self._enclosures.import_certificate(certificate_data, id_enclosure, bay_number=bay_number) - - mock_update.assert_called_once_with(certificate_data, uri=uri_rest_call, custom_headers=headers) + self._enclosures.import_certificate(certificate_data, bay_number=bay_number) + mock_put.assert_called_once_with(uri_rest_call, certificate_data, -1, headers) diff --git a/tests/unit/resources/servers/test_id_pools.py b/tests/unit/resources/servers/test_id_pools.py index 9dd86c7d..c9445c0e 100644 --- a/tests/unit/resources/servers/test_id_pools.py +++ b/tests/unit/resources/servers/test_id_pools.py @@ -24,7 +24,7 @@ import unittest from hpOneView.connection import connection -from hpOneView.resources.resource import ResourceClient +from hpOneView.resources.resource import Resource from hpOneView.resources.servers.id_pools import IdPools @@ -36,53 +36,58 @@ class TestIdPools(unittest.TestCase): def setUp(self): self.host = '127.0.0.1' self.connection = connection(self.host) - self.client = IdPools(self.connection) + self._id_pools = IdPools(self.connection) + self._id_pools.data = {'uri': '/rest/id-pools/ipv4'} - @mock.patch.object(ResourceClient, 'get') - def test_get_called_once_by_id(self, mock_get): - id_pools_range_id = "f0a0a113-ec97-41b4-83ce-d7c92b900e7c" - self.client.get(id_pools_range_id) - mock_get.assert_called_once_with(id_pools_range_id) + @mock.patch.object(Resource, 'do_get') + def test_get_called_once_by_name(self, mock_do_get): + id_pools_range_id = "ipv4" + self._id_pools.get_by_name(id_pools_range_id) + mock_do_get.assert_called_once_with(self.example_uri) - @mock.patch.object(ResourceClient, 'get') - def test_get_called_once_by_uri(self, mock_get): - self.client.get(self.example_uri) - mock_get.assert_called_once_with(self.example_uri) + @mock.patch.object(Resource, 'do_get') + def test_get_called_once_by_uri(self, mock_do_get): + self._id_pools.get_by_uri(self.example_uri) + mock_do_get.assert_called_once_with(self.example_uri) - @mock.patch.object(ResourceClient, 'get') - def test_generate_called_once(self, mock_get): - self.client.generate(self.example_uri) - mock_get.assert_called_once_with(self.example_uri + '/generate') + @mock.patch.object(Resource, 'load_resource') + @mock.patch.object(Resource, 'do_get') + def test_generate_called_once(self, mock_do_get, load_resource): + self._id_pools.generate() + mock_do_get.assert_called_once_with(self.example_uri + '/generate') - @mock.patch.object(ResourceClient, 'get') - def test_validate_id_pool_called_once(self, mock_get): - self.client.validate_id_pool(self.example_uri, ['VCGYOAA023', - 'VCGYOAA024']) - mock_get.assert_called_once_with(self.example_uri + "/validate?idList=VCGYOAA023&idList=VCGYOAA024") + @mock.patch.object(Resource, 'load_resource') + @mock.patch.object(Resource, 'do_get') + def test_validate_id_pool_called_once(self, mock_do_get, load_resource): + self._id_pools.validate_id_pool(['VCGYOAA023', 'VCGYOAA024']) + mock_do_get.assert_called_once_with(self.example_uri + "/validate?idList=VCGYOAA023&idList=VCGYOAA024") - @mock.patch.object(ResourceClient, 'update') - def test_validate_called_once(self, update): - self.client.validate(self.resource_info.copy(), self.example_uri) - update.assert_called_once_with(self.resource_info.copy(), self.example_uri + "/validate", timeout=-1) + @mock.patch.object(Resource, 'load_resource') + @mock.patch.object(Resource, 'do_put') + def test_validate_called_once(self, mock_do_put, load_resource): + self._id_pools.validate(self.resource_info.copy()) + mock_do_put.assert_called_once_with(self.example_uri + "/validate", self.resource_info.copy(), timeout=-1) - @mock.patch.object(ResourceClient, 'update') + @mock.patch.object(Resource, 'update') def test_enable_called_once(self, update): - self.client.enable(self.resource_info.copy(), self.example_uri) - update.assert_called_once_with(self.resource_info.copy(), self.example_uri, timeout=-1) + self._id_pools.enable(self.resource_info.copy()) + update.assert_called_once_with(self.resource_info.copy(), timeout=-1) - @mock.patch.object(ResourceClient, 'get') - def test_get_check_range_availability_called_once_with_defaults(self, mock_get): - self.client.get_check_range_availability(self.example_uri, ['VCGYOAA023', - 'VCGYOAA024']) - mock_get.assert_called_once_with( + @mock.patch.object(Resource, 'load_resource') + @mock.patch.object(Resource, 'do_get') + def test_get_check_range_availability_called_once_with_defaults(self, mock_do_get, load_resource): + self._id_pools.get_check_range_availability(['VCGYOAA023', 'VCGYOAA024']) + mock_do_get.assert_called_once_with( self.example_uri + "/checkrangeavailability?idList=VCGYOAA023&idList=VCGYOAA024") - @mock.patch.object(ResourceClient, 'update') - def test_allocate_called_once(self, mock_update): - self.client.allocate(self.resource_info.copy(), self.example_uri) - mock_update.assert_called_once_with(self.resource_info.copy(), self.example_uri + "/allocator", timeout=-1) + @mock.patch.object(Resource, 'load_resource') + @mock.patch.object(Resource, 'do_put') + def test_allocate_called_once(self, mock_do_put, load_resource): + self._id_pools.allocate(self.resource_info.copy()) + mock_do_put.assert_called_once_with(self.example_uri + "/allocator", self.resource_info.copy(), timeout=-1) - @mock.patch.object(ResourceClient, 'update') - def test_collect_called_once(self, update): - self.client.collect(self.resource_info.copy(), self.example_uri) - update.assert_called_once_with(self.resource_info.copy(), self.example_uri + "/collector", timeout=-1) + @mock.patch.object(Resource, 'load_resource') + @mock.patch.object(Resource, 'do_put') + def test_collect_called_once(self, mock_do_put, load_resource): + self._id_pools.collect(self.resource_info.copy()) + mock_do_put.assert_called_once_with(self.example_uri + "/collector", self.resource_info.copy(), timeout=-1) diff --git a/tests/unit/test_oneview_client.py b/tests/unit/test_oneview_client.py index f3ae5b7f..8fdb6ff0 100755 --- a/tests/unit/test_oneview_client.py +++ b/tests/unit/test_oneview_client.py @@ -387,10 +387,6 @@ def test_fc_networks_has_right_type(self): def test_fc_networks_has_value(self): self.assertIsNotNone(self._oneview.fc_networks) - def test_lazy_loading_fc_networks(self): - fcn = self._oneview.fc_networks - self.assertEqual(fcn, self._oneview.fc_networks) - def test_connection_type(self): self.assertIsInstance(self._oneview.connection, connection) @@ -438,10 +434,6 @@ def test_lazy_loading_metric_streaming(self): metric = self._oneview.metric_streaming self.assertEqual(metric, self._oneview.metric_streaming) - def test_lazy_loading_enclosures(self): - enclosures = self._oneview.enclosures - self.assertEqual(enclosures, self._oneview.enclosures) - def test_lazy_loading_switches(self): switches = self._oneview.switches self.assertEqual(switches, self._oneview.switches) @@ -546,10 +538,6 @@ def test_id_pools_ipv4_subnets_lazy_loading(self): def test_id_pools_has_right_type(self): self.assertIsInstance(self._oneview.id_pools, IdPools) - def test_id_pools_lazy_loading(self): - id_pools = self._oneview.id_pools - self.assertEqual(id_pools, self._oneview.id_pools) - def test_lazy_loading_logical_enclosures(self): logical_enclosures = self._oneview.logical_enclosures self.assertEqual(logical_enclosures, self._oneview.logical_enclosures)