From 298508191ae545ecf6eb2b7a56fd1d0828f20f0e Mon Sep 17 00:00:00 2001 From: "stark, steven" Date: Tue, 7 Apr 2020 08:36:44 -0700 Subject: [VVP] Adding support for update to onap-client Issue-ID: VVP-399 Signed-off-by: stark, steven Change-Id: I3f7d1694fd23295a274c0c0e3801a33ab23dce7f --- .../etc/payloads/add_resource_relationship.jinja | 17 + onap-client/etc/payloads/generic_payload.jinja | 1 + .../etc/payloads/software_product_update.jinja | 1 + .../etc/payloads/update_resource_instance.jinja | 1 + onap-client/onap_client/client/request.py | 2 + onap-client/onap_client/engine.py | 10 +- .../onap_client/sdc/catalog/service_catalog.py | 52 ++- onap-client/onap_client/sdc/catalog/vnf_catalog.py | 133 ++++++++ onap-client/onap_client/sdc/catalog/vsp_catalog.py | 26 ++ onap-client/onap_client/sdc/service.py | 75 ++++- onap-client/onap_client/sdc/vnf.py | 354 ++++++++++++++------- onap-client/onap_client/sdc/vsp.py | 36 ++- onap-client/setup.py | 2 +- 13 files changed, 568 insertions(+), 142 deletions(-) create mode 100644 onap-client/etc/payloads/add_resource_relationship.jinja create mode 100644 onap-client/etc/payloads/generic_payload.jinja create mode 100644 onap-client/etc/payloads/software_product_update.jinja create mode 100644 onap-client/etc/payloads/update_resource_instance.jinja diff --git a/onap-client/etc/payloads/add_resource_relationship.jinja b/onap-client/etc/payloads/add_resource_relationship.jinja new file mode 100644 index 0000000..38c8bf5 --- /dev/null +++ b/onap-client/etc/payloads/add_resource_relationship.jinja @@ -0,0 +1,17 @@ +{ + "fromNode": "{{from_node_resource_id}}", + "toNode": "{{to_node_resource_id}}", + "relationships": [{ + "relation": { + "relationship": { + "type": "{{relationship_type}}" + }, + "capability": "{{capability_name}}", + "capabilityOwnerId": "{{capability_owner_id}}", + "capabilityUid": "{{capability_id}}", + "requirement": "{{requirement_name}}", + "requirementOwnerId": "{{from_node_resource_id}}", + "requirementUid": "{{requirement_id}}" + } + }] +} \ No newline at end of file diff --git a/onap-client/etc/payloads/generic_payload.jinja b/onap-client/etc/payloads/generic_payload.jinja new file mode 100644 index 0000000..4c6075f --- /dev/null +++ b/onap-client/etc/payloads/generic_payload.jinja @@ -0,0 +1 @@ +{{payload_data}} \ No newline at end of file diff --git a/onap-client/etc/payloads/software_product_update.jinja b/onap-client/etc/payloads/software_product_update.jinja new file mode 100644 index 0000000..50e97ff --- /dev/null +++ b/onap-client/etc/payloads/software_product_update.jinja @@ -0,0 +1 @@ +{"description":"{{description}}","creationMethod":"major"} \ No newline at end of file diff --git a/onap-client/etc/payloads/update_resource_instance.jinja b/onap-client/etc/payloads/update_resource_instance.jinja new file mode 100644 index 0000000..d19c33d --- /dev/null +++ b/onap-client/etc/payloads/update_resource_instance.jinja @@ -0,0 +1 @@ +{"componentUid":"{{component_id}}"} \ No newline at end of file diff --git a/onap-client/onap_client/client/request.py b/onap-client/onap_client/client/request.py index 0e40591..0e1ce5c 100644 --- a/onap-client/onap_client/client/request.py +++ b/onap-client/onap_client/client/request.py @@ -96,6 +96,7 @@ class Request: self.kwargs["headers"] = request_object.headers if request_object.payload: + logger.info(self.kwargs.get("data")) self.kwargs["data"] = request_object.payload if request_object.files: @@ -107,6 +108,7 @@ class Request: try: logger.info(json.dumps(debug_request, indent=4)) + # logger.info(debug_request) except TypeError: logger.info(debug_request) diff --git a/onap-client/onap_client/engine.py b/onap-client/onap_client/engine.py index 7543783..a352163 100644 --- a/onap-client/onap_client/engine.py +++ b/onap-client/onap_client/engine.py @@ -70,7 +70,7 @@ def show_resource_spec(resource_name): list_spec_resources() -def load_spec(input_spec, validate_only=False): +def load_spec(input_spec, validate_only=False, submit=True): try: with open(input_spec, "r") as f: jdata = json.loads(f.read()) @@ -79,7 +79,7 @@ def load_spec(input_spec, validate_only=False): raise engine = SpecEngine() - return engine.load_spec(jdata, validate_only=validate_only) + return engine.load_spec(jdata, validate_only=validate_only, distribute=submit) def spec_cli(args): @@ -101,6 +101,10 @@ def spec_cli(args): "--show-resource-spec", required=False, help="Show spec for a given resource." ) + parser.add_argument( + "--no-submit", action="store_false", required=False, default=True, help="Dont execute submit() for each resource in spec." + ) + parser.add_argument( "--list-spec-resources", action="store_true", @@ -117,7 +121,7 @@ def spec_cli(args): elif arguments.validate_spec: print(json.dumps(load_spec(arguments.validate_spec, validate_only=True), indent=4)) elif arguments.load_spec: - load_spec(arguments.load_spec) + load_spec(arguments.load_spec, submit=arguments.no_submit) class SpecEngine: diff --git a/onap-client/onap_client/sdc/catalog/service_catalog.py b/onap-client/onap_client/sdc/catalog/service_catalog.py index 1d30725..eff2784 100644 --- a/onap-client/onap_client/sdc/catalog/service_catalog.py +++ b/onap-client/onap_client/sdc/catalog/service_catalog.py @@ -98,6 +98,29 @@ CATALOG_RESOURCES = { sdc_properties.GLOBAL_SDC_PASSWORD, ), }, + "CHECKOUT_CATALOG_SERVICE": { + "verb": "POST", + "description": "Creates a new version of a Service in the SDC catalog", + "uri": partial( + "{endpoint}{service_path}/{catalog_service_id}/lifecycleState/CHECKOUT".format, + endpoint=sdc_properties.SDC_BE_ENDPOINT, + service_path=sdc_properties.SDC_CATALOG_SERVICES_PATH, + ), + "uri-parameters": ["catalog_service_id"], + "success_code": 200, + "headers": { + "Accept": "application/json", + "Content-Type": "application/json", + "USER_ID": sdc_properties.SDC_DESIGNER_USER_ID, + "X-TransactionId": str(uuid.uuid4()), + "X-FromAppId": application_id, + }, + "return_data": {"catalog_service_id": ("uniqueId",)}, + "auth": ( + sdc_properties.GLOBAL_SDC_USERNAME, + sdc_properties.GLOBAL_SDC_PASSWORD, + ), + }, "ADD_RESOURCE_INSTANCE": { "verb": "POST", "description": "Attaches a Resource to a Service", @@ -130,6 +153,32 @@ CATALOG_RESOURCES = { sdc_properties.GLOBAL_SDC_PASSWORD, ), }, + "UPDATE_RESOURCE_VERSION": { + "verb": "POST", + "description": "Updates a component version in a service", + "uri": partial( + "{endpoint}{service_path}/{catalog_service_id}/resourceInstance/{component_name}/changeVersion".format, + endpoint=sdc_properties.SDC_BE_ENDPOINT, + service_path=sdc_properties.SDC_CATALOG_SERVICES_PATH, + ), + "uri-parameters": ["catalog_service_id", "component_name"], + "payload": "{}/update_resource_instance.jinja".format(PAYLOADS_DIR), + "payload-parameters": [ + "component_id", + ], + "success_code": 200, + "headers": { + "Accept": "application/json", + "Content-Type": "application/json", + "USER_ID": sdc_properties.SDC_DESIGNER_USER_ID, + "X-TransactionId": str(uuid.uuid4()), + "X-FromAppId": application_id, + }, + "auth": ( + sdc_properties.GLOBAL_SDC_USERNAME, + sdc_properties.GLOBAL_SDC_PASSWORD, + ), + }, "CHECKIN_SERVICE": { "verb": "POST", "description": "Checks a service into the SDC Catalog", @@ -261,11 +310,12 @@ CATALOG_RESOURCES = { ), "uri-parameters": ["catalog_service_id"], "success_code": 200, + "header-parameters": ["X-TransactionId"], "headers": { "Accept": "application/json", "Content-Type": "application/json", "USER_ID": sdc_properties.SDC_OPS_USER_ID, - "X-TransactionId": str(uuid.uuid4()), + # "X-TransactionId": str(uuid.uuid4()), "X-FromAppId": application_id, }, "auth": ( diff --git a/onap-client/onap_client/sdc/catalog/vnf_catalog.py b/onap-client/onap_client/sdc/catalog/vnf_catalog.py index c4fd3da..cd08dad 100644 --- a/onap-client/onap_client/sdc/catalog/vnf_catalog.py +++ b/onap-client/onap_client/sdc/catalog/vnf_catalog.py @@ -142,6 +142,52 @@ CATALOG_RESOURCES = { sdc_properties.GLOBAL_SDC_PASSWORD, ), }, + "UPDATE_CATALOG_RESOURCE": { + "verb": "PUT", + "description": "Creates a new version of a VF resource", + "uri": partial( + "{endpoint}{service_path}/{catalog_resource_id}".format, + endpoint=sdc_properties.SDC_BE_ENDPOINT, + service_path=sdc_properties.SDC_CATALOG_RESOURCES_PATH, + ), + "uri-parameters": ["catalog_resource_id"], + "success_code": 200, + "payload": "{}/generic_payload.jinja".format(PAYLOADS_DIR), + "payload-parameters": ["payload_data"], + "headers": { + "Accept": "application/json", + "Content-Type": "application/json", + "USER_ID": sdc_properties.SDC_DESIGNER_USER_ID, + "X-TransactionId": str(uuid.uuid4()), + "X-FromAppId": application_id, + }, + "auth": ( + sdc_properties.GLOBAL_SDC_USERNAME, + sdc_properties.GLOBAL_SDC_PASSWORD, + ), + }, + "CHECKOUT_CATALOG_RESOURCE": { + "verb": "POST", + "description": "Checks out a VF from the catalog", + "uri": partial( + "{endpoint}{service_path}/{catalog_resource_id}/lifecycleState/CHECKOUT".format, + endpoint=sdc_properties.SDC_BE_ENDPOINT, + service_path=sdc_properties.SDC_CATALOG_RESOURCES_PATH, + ), + "uri-parameters": ["catalog_resource_id"], + "success_code": 200, + "headers": { + "Accept": "application/json", + "Content-Type": "application/json", + "USER_ID": sdc_properties.SDC_DESIGNER_USER_ID, + "X-TransactionId": str(uuid.uuid4()), + "X-FromAppId": application_id, + }, + "auth": ( + sdc_properties.GLOBAL_SDC_USERNAME, + sdc_properties.GLOBAL_SDC_PASSWORD, + ), + }, "ADD_CATALOG_RESOURCE_PROPERTY": { "verb": "POST", "description": "Adds an property value for a VNF", @@ -174,6 +220,38 @@ CATALOG_RESOURCES = { sdc_properties.GLOBAL_SDC_PASSWORD, ), }, + "ADD_CATALOG_RESOURCE_PROPERTY_NON_VF": { + "verb": "POST", + "description": "Adds an property value for a VNF", + "uri": partial( + "{endpoint}{service_path}/{catalog_resource_id}/resourceInstance/{catalog_resource_instance_id}/properties".format, + endpoint=sdc_properties.SDC_BE_ENDPOINT, + service_path=sdc_properties.SDC_CATALOG_RESOURCES_PATH, + ), + "payload": "{}/catalog_vnf_property.jinja".format(PAYLOADS_DIR), + "payload-parameters": [ + "unique_id", + "parent_unique_id", + "owner_id", + "property_name", + "property_default_value", + "schema_type", + "property_type", + ], + "uri-parameters": ["catalog_resource_id", "catalog_resource_instance_id"], + "success_code": 200, + "headers": { + "Accept": "application/json", + "Content-Type": "application/json", + "USER_ID": sdc_properties.SDC_DESIGNER_USER_ID, + "X-TransactionId": str(uuid.uuid4()), + "X-FromAppId": application_id, + }, + "auth": ( + sdc_properties.GLOBAL_SDC_USERNAME, + sdc_properties.GLOBAL_SDC_PASSWORD, + ), + }, "ADD_CATALOG_RESOURCE_POLICY": { "verb": "POST", "description": "Adds an policy resource to a VNF", @@ -362,6 +440,39 @@ CATALOG_RESOURCES = { sdc_properties.GLOBAL_SDC_PASSWORD, ), }, + "ADD_RESOURCE_RELATIONSHIP": { + "verb": "POST", + "description": "Creates a relationship between two resources in a VF", + "uri": partial( + "{endpoint}{service_path}/{catalog_resource_id}/resourceInstance/associate".format, + endpoint=sdc_properties.SDC_BE_ENDPOINT, + service_path=sdc_properties.SDC_CATALOG_RESOURCES_PATH, + ), + "uri-parameters": ["catalog_resource_id"], + "payload": "{}/add_resource_relationship.jinja".format(PAYLOADS_DIR), + "payload-parameters": [ + "from_node_resource_id", + "to_node_resource_id", + "relationship_type", + "capability_name", + "capability_owner_id", + "capability_id", + "requirement_name", + "requirement_id", + ], + "success_code": 200, + "headers": { + "Accept": "application/json", + "Content-Type": "application/json", + "USER_ID": sdc_properties.SDC_DESIGNER_USER_ID, + "X-TransactionId": str(uuid.uuid4()), + "X-FromAppId": application_id, + }, + "auth": ( + sdc_properties.GLOBAL_SDC_USERNAME, + sdc_properties.GLOBAL_SDC_PASSWORD, + ), + }, "GET_CATALOG_RESOURCE": { "verb": "GET", "description": "Gets a VNF in the SDC catalog", @@ -385,6 +496,28 @@ CATALOG_RESOURCES = { sdc_properties.GLOBAL_SDC_PASSWORD, ), }, + "GET_CATALOG_RESOURCE_METADATA": { + "verb": "GET", + "description": "Gets metadata for a VNF in the SDC catalog", + "uri": partial( + "{endpoint}{service_path}/{catalog_resource_id}/filteredDataByParams?include=metadata".format, + endpoint=sdc_properties.SDC_BE_ENDPOINT, + service_path=sdc_properties.SDC_CATALOG_RESOURCES_PATH, + ), + "uri-parameters": ["catalog_resource_id"], + "success_code": 200, + "headers": { + "Accept": "application/json", + "Content-Type": "application/json", + "USER_ID": sdc_properties.SDC_DESIGNER_USER_ID, + "X-TransactionId": str(uuid.uuid4()), + "X-FromAppId": application_id, + }, + "auth": ( + sdc_properties.GLOBAL_SDC_USERNAME, + sdc_properties.GLOBAL_SDC_PASSWORD, + ), + }, "GET_RESOURCES": { "verb": "GET", "description": "Get all resources in the SDC catalog", diff --git a/onap-client/onap_client/sdc/catalog/vsp_catalog.py b/onap-client/onap_client/sdc/catalog/vsp_catalog.py index 65781a6..574149e 100644 --- a/onap-client/onap_client/sdc/catalog/vsp_catalog.py +++ b/onap-client/onap_client/sdc/catalog/vsp_catalog.py @@ -95,6 +95,32 @@ CATALOG_RESOURCES = { sdc_properties.GLOBAL_SDC_PASSWORD, ), }, + "UPDATE_SOFTWARE_PRODUCT": { + "verb": "POST", + "description": "Updates a VSP to a new version", + "uri": partial( + "{endpoint}{service_path}/{software_product_id}/versions/{software_product_version_id}".format, + endpoint=sdc_properties.SDC_BE_ONBOARD_ENDPOINT, + service_path=sdc_properties.SDC_VENDOR_ITEMS_PATH, + ), + "payload": "{}/software_product_update.jinja".format(PAYLOADS_DIR), + "payload-parameters": [ + "description", + ], + "uri-parameters": ["software_product_id", "software_product_version_id"], + "success_code": 200, + "headers": { + "Accept": "application/json", + "Content-Type": "application/json", + "USER_ID": sdc_properties.SDC_DESIGNER_USER_ID, + "X-TransactionId": str(uuid.uuid4()), + "X-FromAppId": application_id, + }, + "auth": ( + sdc_properties.GLOBAL_SDC_USERNAME, + sdc_properties.GLOBAL_SDC_PASSWORD, + ), + }, "UPLOAD_HEAT_PACKAGE": { "verb": "POST", "description": "Uploads a heat zip to a VSP", diff --git a/onap-client/onap_client/sdc/service.py b/onap-client/onap_client/sdc/service.py index 4e3dd03..57ef646 100644 --- a/onap-client/onap_client/sdc/service.py +++ b/onap-client/onap_client/sdc/service.py @@ -47,6 +47,7 @@ from onap_client.util import utility import time import json import random +import uuid service_client = SDCClient().sdc.service sdc_properties = sdc.SDC_PROPERTIES @@ -115,6 +116,7 @@ class Service(Resource): "properties": {"type": dict, "required": False, "default": {}}, }, }, + "allow_update": {"type": bool, "required": False, "default": False}, "wait_for_distribution": {"type": bool, "required": False, "default": False}, } @@ -134,6 +136,7 @@ class Service(Resource): naming_policy, resources=[], wait_for_distribution=False, + allow_update=False, ): service_input = {} @@ -158,6 +161,7 @@ class Service(Resource): service_input["naming_policy"] = naming_policy service_input["resources"] = resources service_input["wait_for_distribution"] = wait_for_distribution + service_input["allow_update"] = allow_update super().__init__(service_input) @@ -165,8 +169,11 @@ class Service(Resource): """Creates a service object in SDC""" service = None - if get_service_id(service_input.get("service_name")) is None: + existing = get_service_id(service_input.get("service_name")) + if existing is None: service = create_service(service_input) + elif service_input.get("allow_update"): + service = update_service(existing, service_input) else: raise exceptions.ResourceAlreadyExistsException( "Service resource {} already exists".format( @@ -237,8 +244,8 @@ class Service(Resource): service_client.approve_service_certification( **self.attributes, user_remarks="approved" ) - - service_client.distribute_sdc_service(**self.attributes) + headers = {"X-TransactionId": str(uuid.uuid4())} + service_client.distribute_sdc_service(**self.attributes, **headers) if self.wait_for_distribution: poll_distribution(self.service_name) @@ -256,20 +263,29 @@ class Service(Resource): """ milli_timestamp = int(time.time() * 1000) - - resource_instance = service_client.add_resource_instance( - **self.attributes, - posX=random.randrange(150, 550), # nosec - posY=random.randrange(150, 450), # nosec - milli_timestamp=milli_timestamp, - catalog_resource_id=catalog_resource_id, - catalog_resource_name=catalog_resource_name, - originType=origin_type, - ) + component_instances = self.tosca.get("componentInstances", []) + existing = False + if component_instances: + for component in component_instances: + if component.get("componentName") == catalog_resource_name: + existing = True + resource_instance = self.update_resource_instance_version(component) + break + + if not existing: + resource_instance = service_client.add_resource_instance( + **self.attributes, + posX=random.randrange(150, 550), # nosec + posY=random.randrange(150, 450), # nosec + milli_timestamp=milli_timestamp, + catalog_resource_id=catalog_resource_id, + catalog_resource_name=catalog_resource_name, + originType=origin_type, + ).response_data response = { - "id": resource_instance.catalog_resource_instance_id, - "tosca": resource_instance.response_data, + "id": resource_instance.get("uniqueId"), + "tosca": resource_instance, } self.attributes[catalog_resource_name] = response @@ -325,6 +341,35 @@ class Service(Resource): catalog_service_id=self.catalog_service_id ).response_data + def update_resource_instance_version(self, component): + resource_name = component.get("componentName") + resource_unique_id = component.get("uniqueId") + resource_id = component.get("componentUid") + + vf_id = get_vnf_id(resource_name) + + if vf_id != resource_id: + return service_client.update_resource_version( + catalog_service_id=self.catalog_service_id, + component_name=resource_unique_id, + component_id=vf_id + ).response_data + else: + return component + + +def update_service(existing_service_id, service_input): + kwargs = service_input + + service = service_client.checkout_catalog_service(catalog_service_id=existing_service_id).response_data + + new_service_id = service.get("uniqueId") + + kwargs["catalog_service_id"] = new_service_id + kwargs["tosca"] = service_client.get_sdc_service(catalog_service_id=new_service_id).response_data + + return kwargs + def create_service(service_input): """Creates a service object in SDC diff --git a/onap-client/onap_client/sdc/vnf.py b/onap-client/onap_client/sdc/vnf.py index da8f213..5fa401c 100644 --- a/onap-client/onap_client/sdc/vnf.py +++ b/onap-client/onap_client/sdc/vnf.py @@ -43,6 +43,8 @@ from onap_client.sdc import vsp from onap_client.util import utility import time +import random +import json vnf_client = SDCClient().sdc.vnf @@ -66,6 +68,30 @@ class VNF(Resource): "nested": { "vm_type": {"type": str, "required": True}, "properties": {"type": dict, "required": True, "default": {}}, + "resources": { + "type": list, + "list_item": dict, + "required": False, + "default": [], + "nested": { + "resource_name": {"type": str, "required": True}, + "resource_id": {"type": str, "required": False}, + "catalog_resource_name": {"type": str, "required": False}, + "origin_type": {"type": str, "required": False, "default": "VF"}, + "properties": {"type": dict, "required": False, "default": {}}, + "relationship": { + "type": dict, + "required": False, + "default": {}, + "nested": { + "relationship_type": {"type": str, "required": True}, + "requirement": {"type": str, "required": True}, + "requirement_id": {"type": str, "required": True}, + "properties": {"type": dict, "required": False, "default": {}}, + } + }, + }, + }, }, }, "network_roles": { @@ -94,6 +120,7 @@ class VNF(Resource): "properties": {"type": dict, "required": False, "default": {}}, }, }, + "allow_update": {"type": bool, "required": False, "default": False}, } def __init__( @@ -105,6 +132,7 @@ class VNF(Resource): vm_types=[], network_roles=[], policies=[], + allow_update=False, ): vnf_input = {} @@ -122,6 +150,7 @@ class VNF(Resource): vnf_input["vm_types"] = vm_types vnf_input["network_roles"] = network_roles vnf_input["policies"] = policies + vnf_input["allow_update"] = allow_update super().__init__(vnf_input) @@ -129,8 +158,11 @@ class VNF(Resource): """Creates a vnf object in SDC""" vnf = None - if get_vnf_id(vnf_input.get("vnf_name")) is None: + existing = get_vnf_id(vnf_input.get("vnf_name")) + if not existing: vnf = create_vnf(vnf_input) + elif vnf_input.get("allow_update"): + vnf = update_vnf(existing, vnf_input) else: raise exceptions.ResourceAlreadyExistsException( "VNF resource {} already exists".format(vnf_input.get("vnf_name")) @@ -149,39 +181,18 @@ class VNF(Resource): for vm_type in vm_types: vm_type_tag = vm_type.get("vm_type") properties = vm_type.get("properties") + resources = vm_type.get("resources", []) instance_ids = instance_ids_for_property(model, "vm_type_tag", vm_type_tag) for instance_id in instance_ids: - for k, v in properties.items(): - # updating vm_type properties - self.add_instance_property(instance_id, k, v) - vm_type_instances.append(instance_id) - for network_role in network_roles: - # checking if abstract node has matching network role, - # and updating if found - nrt = network_role.get("network_role_tag") - nr = network_role.get("network_role") - related_networks = network_role.get("related_networks") - instance_property = network_role_property_for_instance( - nrt, model, instance_id - ) - if instance_property: - self.add_instance_property(instance_id, instance_property, nr) - if related_networks: - property_val = [ - {"related_network_role": related_network_role} - for related_network_role in related_networks - ] - rnr_instance_property = instance_property.replace( - "_network_role", "_related_networks" - ) - self.add_instance_property( - instance_id, - rnr_instance_property, - str(property_val).replace("'", '\\"'), - ) + vm_type_instances.append(instance_id) + self._add_instance_properties(instance_id, properties) + self._add_resources(instance_id, resources) + self._add_vm_type_network_role(instance_id, network_roles) for policy in policies: policy_name = policy.get("policy_name") + if self.policy_exists(policy_name): + continue policy_model = self.add_policy_resource(policy_name) self.associate_policy(policy_model.catalog_resource_id, vm_type_instances) for k, v in policy.get("properties", {}).items(): @@ -190,6 +201,115 @@ class VNF(Resource): for k, v in inputs.items(): self.add_input_value(k, v) + def _add_instance_properties(self, instance_id, properties_dict): + for k, v in properties_dict.items(): + # updating vm_type properties + self.add_instance_property(instance_id, k, v) + + def _add_resources(self, instance_id, resources_dict): + for resource in resources_dict: + resource_name = resource.get("resource_name") + + if self.resource_exists(resource_name): + continue + + catalog_resource_name = resource.get("catalog_resource_name") + resource_id = resource.get("resource_id") + resource_origin = resource.get("origin_type") + resource_relationship = resource.get("relationship", {}) + + if not resource_id: + resource_id = get_vnf_id(catalog_resource_name) + if not resource_id: + raise exceptions.ResourceIDNotFoundException( + "resource ID was not passed, and resource lookup by name was not found {}".format( + resource_name + ) + ) + new_resource = add_resource(self.catalog_resource_id, resource_id, resource_name, origin_type=resource_origin) + new_resource_id = new_resource["id"] + if resource_relationship: + relationship_type = resource_relationship.get("relationship_type") + relationship_requirement = resource_relationship.get("requirement") + relationship_requirement_id = resource_relationship.get("requirement_id") + self.add_resource_relationship(new_resource_id, instance_id, relationship_type, relationship_requirement, relationship_requirement_id) + for k, v in resource_relationship.get("properties", {}).items(): + self.add_instance_property_non_vf(new_resource_id, k, v, origin_section="componentInstancesProperties") + + def add_resource_relationship(self, from_node, to_node, relationship_type, relationship_requirement, relationship_requirement_id): + components = self.tosca.get("componentInstances", []) + for component in components: + if component.get("uniqueId") == to_node: + capabilities = component.get("capabilities", {}).get(relationship_type, []) + for capability in capabilities: + capability_owner_id = capability.get("ownerId") + capability_name = capability.get("name") + capability_uid = capability.get("uniqueId") + + return vnf_client.add_resource_relationship( + **self.attributes, + from_node_resource_id=from_node, + to_node_resource_id=to_node, + relationship_type=relationship_type, + capability_name=capability_name, + capability_owner_id=capability_owner_id, + capability_id=capability_uid, + requirement_name=relationship_requirement, + requirement_id=relationship_requirement_id, + ) + + def _add_vm_type_network_role(self, instance_id, network_roles_dict): + model = self.tosca + for network_role in network_roles_dict: + # checking if abstract node has matching network role, + # and updating if found + nrt = network_role.get("network_role_tag") + nr = network_role.get("network_role") + related_networks = network_role.get("related_networks") + instance_property = network_role_property_for_instance( + nrt, model, instance_id + ) + if instance_property: + self.add_instance_property(instance_id, instance_property, nr) + if related_networks: + property_val = [ + {"related_network_role": related_network_role} + for related_network_role in related_networks + ] + rnr_instance_property = instance_property.replace( + "_network_role", "_related_networks" + ) + self.add_instance_property( + instance_id, + rnr_instance_property, + str(property_val).replace("'", '\\"'), + ) + + def resource_exists(self, resource_name): + """Checking the tosca model for a VF to see if a resource + has already been added""" + + component_instances = self.tosca.get("componentInstances", []) + + for component in component_instances: + if component.get("name") == resource_name: + return True + + return False + + def policy_exists(self, policy_name): + """Checking the tosca model for a VF to see if a resource + has already been added""" + + policies = self.tosca.get("policies", {}) + + for p_name, policy in policies.items(): + tosca_policy_name = policy.get("name").lower() + if tosca_policy_name.find("{}..{}".format(self.vnf_name.lower(), policy_name.lower())) != -1: + return True + + return False + def _submit(self): """Submits the vnf in SDC""" certification = vnf_client.certify_catalog_resource( @@ -202,34 +322,6 @@ class VNF(Resource): self.attributes["catalog_resource_name"] = vnf.catalog_resource_name self.attributes["tosca"] = vnf.response_data - def add_resource( - self, catalog_resource_id, catalog_resource_name, origin_type="VF" - ): - """Attaches a resource to a VNF in SDC - - :catalog_resource_id: ID of a resource in the SDC catalog - :catalog_resource_name: name to give to the resource when attaching to vnf - :origin_type: specifies the origin of the attached resource - - """ - milli_timestamp = int(time.time() * 1000) - - resource_instance = vnf_client.add_resource_instance( - **self.attributes, - posX=306, - posY=248, - milli_timestamp=milli_timestamp, - new_catalog_resource_id=catalog_resource_id, - new_catalog_resource_name=catalog_resource_name, - originType=origin_type, - ) - - response = { - "id": resource_instance.catalog_resource_instance_id, - "tosca": resource_instance.response_data, - } - self.attributes[catalog_resource_name] = response - def add_input_value(self, input_name, input_default_value): """Updates an input value on a VNF @@ -262,7 +354,7 @@ class VNF(Resource): # instance, policy, and group properties can probably be merged # rn there is a lot of dup - def add_instance_property(self, instance_id, property_name, property_value): + def add_instance_property(self, instance_id, property_name, property_value, origin_section="componentInstancesInputs"): """Updates an instance property on a abstract instance attached to a VNF :instance_id: ID of a instance attached to a VNF @@ -272,7 +364,7 @@ class VNF(Resource): """ self._refresh() - instance_inputs = self.tosca.get("componentInstancesInputs", {}).get( + instance_inputs = self.tosca.get(origin_section, {}).get( instance_id, {} ) @@ -301,97 +393,82 @@ class VNF(Resource): ) ) - def add_policy_property(self, policy_id, property_name, property_value): - """Updates a policy property on a polic attached to a VNF + def add_instance_property_non_vf(self, instance_id, property_name, property_value, origin_section="componentInstancesProperties"): + """Updates an instance property on a abstract instance attached to a VNF - :policy_id: ID of a policy attached to a VNF + :instance_id: ID of a instance attached to a VNF :property_name: property name to update :property_value: value to update property with """ self._refresh() - policies = ( - self.tosca.get("policies", {}).get(policy_id, {}).get("properties", {}) + instance_inputs = self.tosca.get(origin_section, {}).get( + instance_id, {} ) - for prop in policies: + for prop in instance_inputs: if prop.get("name") == property_name: unique_id = prop.get("uniqueId") + parent_unique_id = prop.get("parentUniqueId") + owner_id = prop.get("ownerId") + schemaType = prop.get("schemaType", "") property_type = prop.get("type") - description = prop.get("description") - return vnf_client.add_catalog_policy_property( + return vnf_client.add_catalog_resource_property_non_vf( **self.attributes, unique_id=unique_id, - catalog_policy_id=policy_id, + parent_unique_id=parent_unique_id, + owner_id=owner_id, + catalog_resource_instance_id=instance_id, property_name=property_name, property_default_value=property_value, - description=description, + schema_type=schemaType, property_type=property_type, ) raise exceptions.PropertyNotFoundException( - "Property {} was not found in policy {}".format(property_name, policy_id) + "Property {} was not found in Instance {}".format( + property_name, instance_id + ) ) - def add_group_property(self, group_id, property_name, property_value): - """Updates a group property on a group attached to a VNF + def add_policy_property(self, policy_id, property_name, property_value): + """Updates a policy property on a polic attached to a VNF - :group_id: ID of a group attached to a VNF + :policy_id: ID of a policy attached to a VNF :property_name: property name to update :property_value: value to update property with """ self._refresh() - groups = self.tosca.get("groups", []) - - for group in groups: - if group.get("uniqueId") == group_id: - properties = group.get("properties", []) - for prop in properties: - unique_id = prop.get("uniqueId") - property_type = prop.get("type") - description = prop.get("description") - parent_unique_id = prop.get("parentUniqueId") - owner_id = prop.get("ownerId") - return vnf_client.add_catalog_group_property( - **self.attributes, - unique_id=unique_id, - catalog_group_id=group_id, - property_name=property_name, - property_default_value=property_value, - description=description, - property_type=property_type, - parent_unique_id=parent_unique_id, - owner_id=owner_id, - ) - - raise exceptions.PropertyNotFoundException( - "Property {} was not found in group {}".format(property_name, group_id) + policies = ( + self.tosca.get("policies", {}).get(policy_id, {}).get("properties", {}) ) - def add_group_resource(self, group_name): - """Adds an SDC group resource to a VNF - - :group_name: name of the group, matching onap-client.conf - - """ - sdc_properties = sdc.SDC_PROPERTIES - group = sdc_properties.GROUPS.get(group_name) - if not group: - raise exceptions.UnknownGroupException( - "Group {} was not found in configuration file".format(group_name) - ) + for prop in policies: + if prop.get("name") == property_name: + unique_id = prop.get("uniqueId") + property_type = prop.get("type") + description = prop.get("description") + return vnf_client.add_catalog_policy_property( + **self.attributes, + unique_id=unique_id, + catalog_policy_id=policy_id, + property_name=property_name, + property_default_value=property_value, + description=description, + property_type=property_type, + ) - return vnf_client.add_catalog_resource_group( - **self.attributes, catalog_group_name=group + raise exceptions.PropertyNotFoundException( + "Property {} was not found in policy {}".format(property_name, policy_id) ) def add_policy_resource(self, policy_name): """Adds an SDC policy resource to a VNF - :group_name: name of the policy, matching onap-client.conf + :policy_name: name of the policy, matching onap-client.conf """ sdc_properties = sdc.SDC_PROPERTIES @@ -417,18 +494,30 @@ class VNF(Resource): **self.attributes, catalog_policy_id=policy_id, instance_ids=instance_ids ) - def associate_group(self, group_id, instance_id): - """associates an SDC group resource to an VNF instance resource""" - return vnf_client.add_group_to_instance( - **self.attributes, catalog_group_id=group_id, instance_id=instance_id - ) - def _refresh(self): """GETs the VNF model from SDC and updates the VNF object""" vnf = vnf_client.get_catalog_resource(**self.attributes) self.attributes["tosca"] = vnf.response_data +def update_vnf(catalog_resource_id, vnf_input): + vnf = vnf_client.checkout_catalog_resource(catalog_resource_id=catalog_resource_id).response_data + + new_vnf_metadata = vnf_client.get_catalog_resource_metadata(catalog_resource_id=vnf.get("uniqueId")).response_data.get("metadata", {}) + + csar_version = vsp.get_vsp_version_id(vnf.get("csarUUID"), search_key="name") + + vnf["csarVersion"] = csar_version + vnf["componentMetadata"] = new_vnf_metadata + + updated_vnf = vnf_client.update_catalog_resource(catalog_resource_id=vnf.get("uniqueId"), payload_data=json.dumps(vnf)).response_data + + vnf_input["catalog_resource_id"] = updated_vnf.get("uniqueId") + vnf_input["tosca"] = updated_vnf + + return vnf_input + + def create_vnf(vnf_input): """Creates a vnf object in SDC @@ -490,6 +579,33 @@ def network_role_property_for_instance(network_role_tag, vnf_model, instance_id) return None +def add_resource(parent_resource_id, catalog_resource_id, catalog_resource_name, origin_type="VF"): + """Attaches a resource to a VNF in SDC + + :catalog_resource_id: ID of a resource in the SDC catalog + :catalog_resource_name: name to give to the resource when attaching to vnf + :origin_type: specifies the origin of the attached resource + + """ + milli_timestamp = int(time.time() * 1000) + + resource_instance = vnf_client.add_resource_instance( + catalog_resource_id=parent_resource_id, + posX=random.randrange(150, 550), # nosec + posY=random.randrange(150, 450), # nosec + milli_timestamp=milli_timestamp, + new_catalog_resource_id=catalog_resource_id, + new_catalog_resource_name=catalog_resource_name, + originType=origin_type, + ) + + response = { + "id": resource_instance.catalog_resource_instance_id, + "tosca": resource_instance.response_data, + } + return response + + @utility def get_vnf(vnf_name): """Queries SDC for the TOSCA model for a VNF""" diff --git a/onap-client/onap_client/sdc/vsp.py b/onap-client/onap_client/sdc/vsp.py index fc9258b..7e99ece 100644 --- a/onap-client/onap_client/sdc/vsp.py +++ b/onap-client/onap_client/sdc/vsp.py @@ -70,6 +70,7 @@ class VSP(Resource): "required": False, "default": [], }, + "allow_update": {"type": bool, "required": False, "default": False}, } def __init__( @@ -83,6 +84,7 @@ class VSP(Resource): category, sub_category, contributers=[], + allow_update=False, ): vsp_input = {} @@ -110,6 +112,7 @@ class VSP(Resource): vsp_input["category"] = category.lower() vsp_input["sub_category"] = sub_category.lower() vsp_input["contributers"] = contributers + vsp_input["allow_update"] = allow_update super().__init__(vsp_input) @@ -117,8 +120,11 @@ class VSP(Resource): """Creates a vsp object in SDC""" vsp = None - if get_vsp_id(kwargs.get("software_product_name")) is None: + existing = get_vsp(kwargs.get("software_product_name")) + if not existing: vsp = create_vsp(kwargs) + elif kwargs.get("allow_update"): + vsp = update_vsp(existing, kwargs) else: raise ResourceAlreadyExistsException( "VSP resource {} already exists".format( @@ -143,6 +149,28 @@ class VSP(Resource): self.attributes["tosca"] = vsp.response_data +def update_vsp(existing_vsp, vsp_input): + existing_vsp_id = existing_vsp.get("id") + existing_vsp_version_id = existing_vsp.get("version") + + vsp_client.update_software_product( + software_product_id=existing_vsp_id, + software_product_version_id=existing_vsp_version_id, + description=vsp_input.get("description", "New VSP Version") + ).response_data + + vsp_input["software_product_id"] = existing_vsp_id + vsp_input["software_product_version_id"] = get_vsp_version_id(existing_vsp_id) + + vsp_client.upload_heat_package(**vsp_input) + vsp_client.validate_software_product(**vsp_input) + + vsp = vsp_client.get_software_product(**vsp_input) + vsp_input["tosca"] = vsp.response_data + + return vsp_input + + def create_vsp(vsp_input): """Creates a VSP object in SDC @@ -180,7 +208,7 @@ def get_vsp_id(vsp_name): return None -def get_vsp_version_id(vsp_id): +def get_vsp_version_id(vsp_id, search_key="id"): """GETs vsp model version UUID from SDC :vsp_id: uuid of vsp model in SDC @@ -194,7 +222,7 @@ def get_vsp_version_id(vsp_id): for version in results: if version.get("creationTime", 0) > creation_time: creation_time = version.get("creationTime") - vsp_version_id = version.get("id") + vsp_version_id = version.get(search_key) return vsp_version_id @@ -209,5 +237,7 @@ def get_vsp_model(vsp_id, vsp_version_id): def get_vsp(vsp_name): """Queries SDC for the tosca model for a VSP""" vsp_id = get_vsp_id(vsp_name) + if vsp_id is None: + return None vsp_version_id = get_vsp_version_id(vsp_id) return get_vsp_model(vsp_id, vsp_version_id) diff --git a/onap-client/setup.py b/onap-client/setup.py index 7806e3b..2624f9b 100644 --- a/onap-client/setup.py +++ b/onap-client/setup.py @@ -47,7 +47,7 @@ for file in os.listdir("etc/payloads"): setuptools.setup( name="onap-client", - version="0.3.0", + version="0.4.0", author="Steven Stark", author_email="steven.stark@att.com", description="Python API wrapper for ONAP applications", -- cgit 1.2.3-korg