aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorstark, steven <steven.stark@att.com>2020-04-07 08:36:44 -0700
committerstark, steven <steven.stark@att.com>2020-04-07 08:36:44 -0700
commit298508191ae545ecf6eb2b7a56fd1d0828f20f0e (patch)
tree9685dc85589fa2a395fc03f733299e63a3b77fd6
parent07c28e0a81383f76f5d563f265db27519d4922f1 (diff)
[VVP] Adding support for update to onap-client
Issue-ID: VVP-399 Signed-off-by: stark, steven <steven.stark@att.com> Change-Id: I3f7d1694fd23295a274c0c0e3801a33ab23dce7f
-rw-r--r--onap-client/etc/payloads/add_resource_relationship.jinja17
-rw-r--r--onap-client/etc/payloads/generic_payload.jinja1
-rw-r--r--onap-client/etc/payloads/software_product_update.jinja1
-rw-r--r--onap-client/etc/payloads/update_resource_instance.jinja1
-rw-r--r--onap-client/onap_client/client/request.py2
-rw-r--r--onap-client/onap_client/engine.py10
-rw-r--r--onap-client/onap_client/sdc/catalog/service_catalog.py52
-rw-r--r--onap-client/onap_client/sdc/catalog/vnf_catalog.py133
-rw-r--r--onap-client/onap_client/sdc/catalog/vsp_catalog.py26
-rw-r--r--onap-client/onap_client/sdc/service.py75
-rw-r--r--onap-client/onap_client/sdc/vnf.py354
-rw-r--r--onap-client/onap_client/sdc/vsp.py36
-rw-r--r--onap-client/setup.py2
13 files changed, 568 insertions, 142 deletions
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):
@@ -102,6 +102,10 @@ def spec_cli(args):
)
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",
required=False,
@@ -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",