def test__deep_load_response_OK(mock_send, mock_created): mock_created.return_value = True vf = Vf() vf.identifier = "5689" vf._version = "4567" vf._status = const.CHECKED_IN mock_send.return_value = { 'resources': [{ 'uuid': '5689', 'name': 'test', 'uniqueId': '71011', 'categories': [{ 'name': 'test', 'subcategories': [{ 'name': 'test_subcategory' }] }] }] } vf.deep_load() assert vf.unique_identifier == "71011" assert vf._category_name == "test" assert vf._subcategory_name == "test_subcategory" mock_send.assert_called_once_with( 'GET', 'Deep Load Vf', "{}/sdc1/feProxy/rest/v1/screen?excludeTypes=VFCMT&excludeTypes=Configuration" .format(vf.base_front_url), headers=headers_sdc_creator(vf.headers))
def deep_load(self) -> None: """Deep load Object informations from SDC.""" url = (f"{self.base_front_url}/sdc1/feProxy/rest/v1/" "screen?excludeTypes=VFCMT&excludeTypes=Configuration") headers = headers_sdc_creator(SdcResource.headers) if self.status == const.UNDER_CERTIFICATION: headers = headers_sdc_tester(SdcResource.headers) response = self.send_message_json("GET", "Deep Load {}".format( type(self).__name__), url, headers=headers) for resource in response[self._sdc_path()]: if resource["invariantUUID"] == self.unique_uuid: if resource["uuid"] == self.identifier: self._logger.debug("Resource %s found in %s list", resource["name"], self._sdc_path()) self.unique_identifier = resource["uniqueId"] self._category_name = resource["categories"][0]["name"] subcategories = resource["categories"][0].get( "subcategories", [{}]) self._subcategory_name = None if subcategories is None else \ subcategories[0].get("name") return if self._sdc_path() == "services": for dependency in self.send_message_json( "GET", "Get service dependecies", f"{self._base_create_url()}/services/" f"{resource['uniqueId']}/" "dependencies"): if dependency["version"] == self.version: self.unique_identifier = dependency["uniqueId"] return
def _check_distributed(self) -> bool: """Check if service is distributed and update status accordingly.""" url = "{}/services/distribution/{}".format(self._base_create_url(), self.distribution_id) headers = headers_sdc_creator(SdcResource.headers) status = {} for component in components_needing_distribution(): status[component] = False try: result = self.send_message_json("GET", "Check distribution for {}".format( self.name), url, headers=headers) except ResourceNotFound: msg = f"No distributions found for {self.name} of {self.__class__.__name__}." self._logger.debug(msg) else: status = self._update_components_status(status, result) for state in status.values(): if not state: self._distributed = False return self._distributed = True
class Vf(SdcResource): """ ONAP Vf Object used for SDC operations. Attributes: name (str): the name of the vendor. Defaults to "Generic-Vendor". identifier (str): the unique ID of the vendor from SDC. status (str): the status of the vendor from SDC. version (str): the version ID of the vendor from SDC. vsp (Vsp): the Vsp used for VF creation uuid (str): the UUID of the VF (which is different from identifier, don't ask why...) unique_identifier (str): Yet Another ID, just to puzzle us... """ headers = headers_sdc_creator(SdcResource.headers) def __init__(self, name: str = None, sdc_values: Dict[str, str] = None, vsp: Vsp = None): """ Initialize vendor object. Args: name (optional): the name of the vendor """ super().__init__(sdc_values=sdc_values) self.name: str = name or "ONAP-test-VF" self.vsp: Vsp = vsp or None self._time_wait: int = 10 def onboard(self) -> None: """Onboard the VF in SDC.""" if not self.status: if not self.vsp: raise ValueError("No Vsp was given") self.create() time.sleep(self._time_wait) self.onboard() elif self.status == const.DRAFT: self.submit() time.sleep(self._time_wait) self.onboard() elif self.status == const.CERTIFIED: self.load() def create(self) -> None: """Create the Vf in SDC if not already existing.""" if self.vsp: self._create("vf_create.json.j2", name=self.name, vsp=self.vsp) def _really_submit(self) -> None: """Really submit the SDC Vf in order to enable it.""" result = self._action_to_sdc(const.CERTIFY, "lifecycleState") if result: self.load()
def test_distribute(mock_verify): svc = Service() svc.distribute() mock_verify.assert_called_once_with(const.CERTIFIED, const.DISTRIBUTE, 'distribution', headers=headers_sdc_creator( svc.headers))
def test_certify(mock_verify): svc = Service() svc.certify() mock_verify.assert_called_once_with(const.CHECKED_IN, const.CERTIFY, 'lifecycleState', headers=headers_sdc_creator( svc.headers))
def test_load_metadata_bad_json(mock_send): mock_send.return_value = {'yolo': 'in the wood'} svc = Service() svc.identifier = "1" svc.load_metadata() assert svc._distribution_id is None mock_send.assert_called_once_with( 'GET', 'Get Metadata for ONAP-test-Service', 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/services/1/distribution', headers=headers_sdc_creator(svc.headers))
def test_get_tosca_no_result(mock_send): if path.exists('/tmp/tosca_files'): shutil.rmtree('/tmp/tosca_files') mock_send.return_value = {} svc = Service() svc.identifier = "12" svc.get_tosca() headers = headers_sdc_creator(svc.headers) headers['Accept'] = 'application/octet-stream' mock_send.assert_called_once_with( 'GET', 'Download Tosca Model for ONAP-test-Service', 'https://sdc.api.be.simpledemo.onap.org:30204/sdc/v1/catalog/services/12/toscaModel', headers=headers) assert not path.exists('/tmp/tosca_files')
def test__deep_load_no_response(mock_send, mock_created): mock_created.return_value = True vf = Vf() vf.identifier = "1234" vf._version = "4567" vf._status = const.CHECKED_IN mock_send.return_value = {} vf.deep_load() assert vf._unique_identifier is None mock_send.assert_called_once_with( 'GET', 'Deep Load Vf', "{}/sdc1/feProxy/rest/v1/screen?excludeTypes=VFCMT&excludeTypes=Configuration" .format(vf.base_front_url), headers=headers_sdc_creator(vf.headers))
def load_metadata(self) -> None: """Load Metada of Service and retrieve informations.""" url = "{}/services/{}/distribution".format(self._base_create_url(), self.identifier) headers = headers_sdc_creator(SdcResource.headers) result = self.send_message_json("GET", "Get Metadata for {}".format( self.name), url, headers=headers) if (result and 'distributionStatusOfServiceList' in result and len(result['distributionStatusOfServiceList']) > 0): # API changed and the latest distribution is not added to the end # of distributions list but inserted as the first one. dist_status = result['distributionStatusOfServiceList'][0] self._distribution_id = dist_status['distributionID']
def test_load_metadata_OK(mock_send): mock_send.return_value = { 'distributionStatusOfServiceList': [{ 'distributionID': "11" }, { 'distributionID': "12" }] } svc = Service() svc.identifier = "1" svc.load_metadata() assert svc._distribution_id == "11" mock_send.assert_called_once_with( 'GET', 'Get Metadata for ONAP-test-Service', 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/services/1/distribution', headers=headers_sdc_creator(svc.headers))
def test_distributed_not_distributed(mock_send): mock_send.return_value = { 'distributionStatusList': [{ 'omfComponentID': "SO", 'status': "DOWNLOAD_OK" }, { 'omfComponentID': "aai", 'status': "DOWNLOAD_OK" }] } svc = Service() svc.distribution_id = "12" assert not svc.distributed mock_send.assert_called_once_with( 'GET', 'Check distribution for ONAP-test-Service', 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/services/distribution/12', headers=headers_sdc_creator(svc.headers))
def deep_load(self) -> None: """Deep load Object informations from SDC.""" url = ( f"{self.base_front_url}/sdc1/feProxy/rest/v1/" "screen?excludeTypes=VFCMT&excludeTypes=Configuration" ) headers = headers_sdc_creator(SdcResource.headers) if self.status == const.UNDER_CERTIFICATION: headers = headers_sdc_tester(SdcResource.headers) response = self.send_message_json("GET", "Deep Load {}".format( type(self).__name__), url, headers=headers) if response: for resource in response[self._sdc_path()]: if resource["uuid"] == self.identifier: self._logger.debug("Resource %s found in %s list", resource["name"], self._sdc_path()) self.unique_identifier = resource["uniqueId"]
def _check_distributed(self) -> bool: """Check if service is distributed and update status accordingly.""" url = "{}/services/distribution/{}".format(self._base_create_url(), self.distribution_id) headers = headers_sdc_creator(SdcResource.headers) result = self.send_message_json("GET", "Check distribution for {}".format( self.name), url, headers=headers) status = {} for component in components_needing_distribution(): status[component] = False if result: status = self._update_components_status(status, result) for state in status.values(): if not state: self._distributed = False return self._distributed = True
class Vsp(SdcElement): # pylint: disable=too-many-instance-attributes """ ONAP VSP Object used for SDC operations. Attributes: name (str): the name of the vsp. Defaults to "ONAP-test-VSP". identifier (str): the unique ID of the VSP from SDC. status (str): the status of the VSP from SDC. version (str): the version ID of the VSP from SDC. csar_uuid (str): the CSAR ID of the VSP from SDC. vendor (Vendor): The VSP Vendor """ VSP_PATH = "vendor-software-products" headers = headers_sdc_creator(SdcElement.headers) def __init__(self, name: str = None, package: BinaryIO = None, vendor: Vendor = None): """ Initialize vsp object. Args: name (optional): the name of the vsp """ super().__init__() self._csar_uuid: str = None self._vendor: Vendor = vendor or None self.name: str = name or "ONAP-test-VSP" self.package = package or None @property def status(self): """Return and load the status.""" self.load_status() return self._status def onboard(self) -> None: """Onboard the VSP in SDC.""" if not self.status: if not self.vendor: raise ValueError("No Vendor was given") self.create() self.onboard() elif self.status == const.DRAFT: if not self.package: raise ValueError("No file were given for upload") self.upload_package(self.package) self.onboard() elif self.status == const.UPLOADED: self.validate() self.onboard() elif self.status == const.VALIDATED: self.commit() self.onboard() elif self.status == const.COMMITED: self.submit() self.onboard() elif self.status == const.CERTIFIED: self.create_csar() def create(self) -> None: """Create the Vsp in SDC if not already existing.""" if self.vendor: self._create("vsp_create.json.j2", name=self.name, vendor=self.vendor) def upload_package(self, package_to_upload: BinaryIO) -> None: """ Upload given zip file into SDC as artifacts for this Vsp. Args: package_to_upload (file): the zip file to upload """ self._action("upload package", const.DRAFT, self._upload_action, package_to_upload=package_to_upload) def validate(self) -> None: """Validate the artifacts uploaded.""" self._action("validate", const.UPLOADED, self._validate_action) def commit(self) -> None: """Commit the SDC Vsp.""" self._action("commit", const.VALIDATED, self._generic_action, action=const.COMMIT) def submit(self) -> None: """Submit the SDC Vsp in order to enable it.""" self._action("certify/sumbit", const.COMMITED, self._generic_action, action=const.SUBMIT) def create_csar(self) -> None: """Create the CSAR package in the SDC Vsp.""" self._action("create CSAR package", const.CERTIFIED, self._create_csar_action) @property def vendor(self) -> Vendor: """Return and lazy load the vendor.""" if self.created() and not self._vendor: details = self._get_vsp_details() if details: self._vendor = Vendor(name=details['vendorName']) return self._vendor @vendor.setter def vendor(self, vendor: Vendor) -> None: """Set value for Vendor.""" self._vendor = vendor @property def csar_uuid(self) -> str: """Return and lazy load the CSAR UUID.""" if self.created() and not self._csar_uuid: self.create_csar() return self._csar_uuid @csar_uuid.setter def csar_uuid(self, csar_uuid: str) -> None: """Set value for csar uuid.""" self._csar_uuid = csar_uuid def _upload_action(self, package_to_upload: BinaryIO = None): """Do upload for real.""" if package_to_upload: url = "{}/{}/{}/orchestration-template-candidate".format( self._base_url(), Vsp._sdc_path(), self._version_path()) headers = self.headers.copy() headers.pop("Content-Type") headers["Accept-Encoding"] = "gzip, deflate" data = {'upload': package_to_upload} upload_result = self.send_message('POST', 'upload ZIP for Vsp', url, headers=headers, files=data) if upload_result: self._logger.info("Files for Vsp %s have been uploaded", self.name) else: self._logger.error( "an error occured during file upload for Vsp %s", self.name) def _validate_action(self): """Do validate for real.""" url = "{}/{}/{}/orchestration-template-candidate/process".format( self._base_url(), Vsp._sdc_path(), self._version_path()) validate_result = self.send_message_json('PUT', 'Validate artifacts for Vsp', url) if validate_result and validate_result['status'] == 'Success': self._logger.info("Artifacts for Vsp %s have been validated", self.name) else: self._logger.error( "an error occured during artifacts validation for Vsp %s", self.name) def _generic_action(self, action=None): """Do a generic action for real.""" if action: self._action_to_sdc(action, action_type="lifecycleState") def _create_csar_action(self): """Create CSAR package for real.""" result = self._action_to_sdc(const.CREATE_PACKAGE, action_type="lifecycleState") if result: self._logger.info("result: %s", result.text) data = result.json() self.csar_uuid = data['packageId'] def _action(self, action_name: str, right_status: str, action_function: Callable[['Vsp'], None], **kwargs) -> None: """ Generate an action on the instance in order to send it to SDC. Args: action_name (str): The name of the action (for the logs) right_status (str): The status that the object must be action_function (function): the function to perform if OK """ self._logger.info("attempting to %s for %s in SDC", action_name, self.name) if self.status == right_status: action_function(**kwargs) else: self._logger.warning( "vsp %s in SDC is not created or not in %s status", self.name, right_status) # VSP: DRAFT --> UPLOADED --> VALIDATED --> COMMITED --> CERTIFIED def load_status(self) -> None: """ Load Vsp status from SDC. rules are following: * DRAFT: status == DRAFT and networkPackageName not present * UPLOADED: status == DRAFT and networkPackageName present and validationData not present * VALIDATED: status == DRAFT and networkPackageName present and validationData present and state.dirty = true * COMMITED: status == DRAFT and networkPackageName present and validationData present and state.dirty = false * CERTIFIED: status == CERTIFIED status is found in sdc items state is found in sdc version from items networkPackageName and validationData is found in SDC vsp show """ item_details = self._get_item_details() if (item_details and item_details['results'][-1]['status'] == const.CERTIFIED): self._status = const.CERTIFIED else: self._check_status_not_certified() def _check_status_not_certified(self) -> None: """Check a status when it's not certified.""" vsp_version_details = self._get_item_version_details() vsp_details = self._get_vsp_details() if (vsp_version_details and 'state' in vsp_version_details and not vsp_version_details['state']['dirty'] and vsp_details and 'validationData' in vsp_details): self._status = const.COMMITED else: self._check_status_not_commited() def _check_status_not_commited(self) -> None: """Check a status when it's not certified or commited.""" vsp_details = self._get_vsp_details() if (vsp_details and 'validationData' in vsp_details): self._status = const.VALIDATED elif (vsp_details and 'validationData' not in vsp_details and 'networkPackageName' in vsp_details): self._status = const.UPLOADED elif vsp_details: self._status = const.DRAFT def _get_vsp_details(self) -> Dict[Any, Any]: """Get vsp details.""" if self.created() and self.version: url = "{}/vendor-software-products/{}/versions/{}".format( self._base_url(), self.identifier, self.version) return self.send_message_json('GET', 'get vsp version', url) return {} @classmethod def import_from_sdc(cls, values: Dict[str, Any]) -> 'Vsp': """ Import Vsp from SDC. Args: values (Dict[str, Any]): dict to parse returned from SDC. Returns: a Vsp instance with right values """ cls._logger.debug("importing VSP %s from SDC", values['name']) vsp = Vsp(values['name']) vsp.identifier = values['id'] vsp.vendor = Vendor(name=values['vendorName']) vsp.load_status() cls._logger.info("status of VSP %s: %s", vsp.name, vsp.status) return vsp def _really_submit(self) -> None: """Really submit the SDC Vf in order to enable it.""" raise NotImplementedError("VSP don't need _really_submit") @classmethod def _sdc_path(cls) -> None: """Give back the end of SDC path.""" return cls.VSP_PATH
class Service(SdcResource): # pylint: disable=too-many-instance-attributes """ ONAP Service Object used for SDC operations. Attributes: name (str): the name of the service. Defaults to "ONAP-test-Service". identifier (str): the unique ID of the service from SDC. status (str): the status of the service from SDC. version (str): the version ID of the service from SDC. uuid (str): the UUID of the Service (which is different from identifier, don't ask why...) distribution_status (str): the status of distribution in the different ONAP parts. distribution_id (str): the ID of the distribution when service is distributed. distributed (bool): True if the service is distributed unique_identifier (str): Yet Another ID, just to puzzle us... """ SERVICE_PATH = "services" headers = headers_sdc_creator(SdcResource.headers) def __init__(self, name: str = None, sdc_values: Dict[str, str] = None, resources: List[SdcResource] = None): """ Initialize service object. Args: name (str, optional): the name of the service sdc_values (Dict[str, str], optional): dictionary of values returned by SDC resources (List[SdcResource], optional): list of SDC resources """ super().__init__(sdc_values=sdc_values) self.name: str = name or "ONAP-test-Service" self.distribution_status = None if sdc_values: self.distribution_status = sdc_values['distributionStatus'] self.resources = resources or [] self._distribution_id: str = None self._distributed: bool = False self._resource_type: str = "services" self._tosca_model: bytes = None self._tosca_template: str = None self._vnfs: list = None self._networks: list = None self._vf_modules: list = None self._time_wait: int = 10 def onboard(self) -> None: """Onboard the Service in SDC.""" # first Lines are equivalent for all onboard functions but it's more # readable if not self.status: self.create() time.sleep(self._time_wait) self.onboard() elif self.status == const.DRAFT: if not self.resources: raise ValueError("No resources were given") for resource in self.resources: self.add_resource(resource) self.checkin() time.sleep(self._time_wait) self.onboard() elif self.status == const.CHECKED_IN: self.certify() time.sleep(self._time_wait) self.onboard() time.sleep(self._time_wait) elif self.status == const.CERTIFIED: self.distribute() elif self.status == const.DISTRIBUTED: self._logger.info("Service %s onboarded", self.name) return self._logger.error("Service has invalid status") @property def distribution_id(self) -> str: """Return and lazy load the distribution_id.""" if not self._distribution_id: self.load_metadata() return self._distribution_id @distribution_id.setter def distribution_id(self, value: str) -> None: """Set value for distribution_id.""" self._distribution_id = value @property def distributed(self) -> bool: """Return and lazy load the distributed state.""" if not self._distributed: self._check_distributed() return self._distributed @property def tosca_template(self) -> str: """Service tosca template file. Get tosca template from service tosca model bytes. Raises: AttributeError: Tosca model can't be downloaded using HTTP API Returns: str: Tosca template file """ if not self._tosca_template and self.tosca_model: self._unzip_csar_file(BytesIO(self.tosca_model), self._load_tosca_template) return self._tosca_template @property def tosca_model(self) -> bytes: """Service's tosca model file. Send request to get service TOSCA model, Raises: AttributeError: Tosca model can't be downloaded using HTTP API Returns: bytes: TOSCA model file bytes """ if not self._tosca_model: url = "{}/services/{}/toscaModel".format(self._base_url(), self.identifier) headers = self.headers.copy() headers["Accept"] = "application/octet-stream" self._tosca_model = self.send_message( "GET", "Download Tosca Model for {}".format(self.name), url, headers=headers, exception=AttributeError).content return self._tosca_model @property def vnfs(self) -> List[Vnf]: """Service Vnfs. Load VNFs from service's tosca file Raises: AttributeError: Service has no TOSCA template Returns: List[Vnf]: Vnf objects list """ if not self.tosca_template: raise AttributeError("Service has no TOSCA template") if self._vnfs is None: self._vnfs = [] for node_template_name, values in \ self.tosca_template.get("topology_template", {}).get( "node_templates", {}).items(): if re.match("org.openecomp.resource.vf.*", values["type"]): vnf: Vnf = Vnf(name=node_template_name, node_template_type=values["type"], metadata=values["metadata"], properties=values["properties"], capabilities=values.get("capabilities", {})) vnf.associate_vf_module(self.vf_modules) self._vnfs.append(vnf) return self._vnfs @property def networks(self) -> List[Network]: """Service networks. Load networks from service's tosca file Raises: AttributeError: Service has no TOSCA template Returns: List[Network]: Network objects list """ if not self.tosca_template: raise AttributeError("Service has no TOSCA template") if self._networks is None: self._networks = [] for node_template_name, values in \ self.tosca_template.get("topology_template", {}).get( "node_templates", {}).items(): if re.match("org.openecomp.resource.vl.*", values["type"]): self._networks.append( Network(name=node_template_name, node_template_type=values["type"], metadata=values["metadata"], properties=values["properties"], capabilities=values["capabilities"])) return self._networks @property def vf_modules(self) -> List[VfModule]: """Service VF modules. Load VF modules from service's tosca file Returns: List[VfModule]: VfModule objects list """ if self._vf_modules is None: self._vf_modules = [] groups: dict = self.tosca_template.get("topology_template", {}).get("groups", {}) for group_name, values in groups.items(): self._vf_modules.append( VfModule(name=group_name, group_type=values["type"], metadata=values["metadata"], properties=values["properties"])) return self._vf_modules def create(self) -> None: """Create the Service in SDC if not already existing.""" self._create("service_create.json.j2", name=self.name) def add_resource(self, resource: SdcResource) -> None: """ Add a Resource to the service. Args: resource (SdcResource): the resource to add """ if self.status == const.DRAFT: url = "{}/{}/{}/resourceInstance".format(self._base_create_url(), self._sdc_path(), self.unique_identifier) template = jinja_env().get_template( "add_resource_to_service.json.j2") data = template.render(resource=resource, resource_type=type(resource).__name__) result = self.send_message("POST", "Add {} to service".format( type(resource).__name__), url, data=data) if result: self._logger.info("Resource %s %s has been added on serice %s", type(resource).__name__, resource.name, self.name) return result self._logger.error(("an error occured during adding resource %s %s" " on service %s in SDC"), type(resource).__name__, resource.name, self.name) return None self._logger.error("Service is not in Draft mode") return None def checkin(self) -> None: """Checkin Service.""" self._verify_lcm_to_sdc(const.DRAFT, const.CHECKIN) def submit(self) -> None: """Really submit the SDC Service.""" self._verify_lcm_to_sdc(const.CHECKED_IN, const.SUBMIT_FOR_TESTING) def start_certification(self) -> None: """Start Certification on Service.""" headers = headers_sdc_creator(SdcResource.headers) self._verify_lcm_to_sdc(const.CHECKED_IN, const.START_CERTIFICATION, headers=headers) def certify(self) -> None: """Certify Service in SDC.""" headers = headers_sdc_creator(SdcResource.headers) self._verify_lcm_to_sdc(const.CHECKED_IN, const.CERTIFY, headers=headers) def approve(self) -> None: """Approve Service in SDC.""" headers = headers_sdc_creator(SdcResource.headers) self._verify_approve_to_sdc(const.CERTIFIED, const.APPROVE, headers=headers) def distribute(self) -> None: """Apptove Service in SDC.""" headers = headers_sdc_creator(SdcResource.headers) self._verify_distribute_to_sdc(const.CERTIFIED, const.DISTRIBUTE, headers=headers) def get_tosca(self) -> None: """Get Service tosca files and save it.""" url = "{}/services/{}/toscaModel".format(self._base_url(), self.identifier) headers = self.headers.copy() headers["Accept"] = "application/octet-stream" result = self.send_message("GET", "Download Tosca Model for {}".format( self.name), url, headers=headers) if result: self._create_tosca_file(result) def _create_tosca_file(self, result: Response) -> None: """Create Service Tosca files from HTTP response.""" csar_filename = "service-{}-csar.csar".format(self.name) makedirs(tosca_path(), exist_ok=True) with open((tosca_path() + csar_filename), 'wb') as csar_file: for chunk in result.iter_content(chunk_size=128): csar_file.write(chunk) try: self._unzip_csar_file(tosca_path() + csar_filename, self._write_csar_file) except BadZipFile as exc: self._logger.exception(exc) def _check_distributed(self) -> bool: """Check if service is distributed and update status accordingly.""" url = "{}/services/distribution/{}".format(self._base_create_url(), self.distribution_id) headers = headers_sdc_creator(SdcResource.headers) result = self.send_message_json("GET", "Check distribution for {}".format( self.name), url, headers=headers) status = {} for component in components_needing_distribution(): status[component] = False if result: status = self._update_components_status(status, result) for state in status.values(): if not state: self._distributed = False return self._distributed = True def _update_components_status(self, status: Dict[str, bool], result: Response) -> Dict[str, bool]: """Update components distribution status.""" distrib_list = result['distributionStatusList'] self._logger.debug("[SDC][Get Distribution] distrib_list = %s", distrib_list) for elt in distrib_list: status = self._parse_components_status(status, elt) return status def _parse_components_status(self, status: Dict[str, bool], element: Dict[str, Any]) -> Dict[str, bool]: """Parse components distribution status.""" for key in status: if ((key in element['omfComponentID']) and (const.DOWNLOAD_OK in element['status'])): status[key] = True self._logger.info(("[SDC][Get Distribution] Service " "distributed in %s"), key) return status def load_metadata(self) -> None: """Load Metada of Service and retrieve informations.""" url = "{}/services/{}/distribution".format(self._base_create_url(), self.identifier) headers = headers_sdc_creator(SdcResource.headers) result = self.send_message_json("GET", "Get Metadata for {}".format( self.name), url, headers=headers) if (result and 'distributionStatusOfServiceList' in result and len(result['distributionStatusOfServiceList']) > 0): # API changed and the latest distribution is not added to the end # of distributions list but inserted as the first one. dist_status = result['distributionStatusOfServiceList'][0] self._distribution_id = dist_status['distributionID'] @classmethod def _get_all_url(cls) -> str: """ Get URL for all elements in SDC. Returns: str: the url """ return "{}/{}".format(cls._base_url(), cls._sdc_path()) def _really_submit(self) -> None: """Really submit the SDC Service in order to enable it.""" result = self._action_to_sdc(const.CERTIFY, action_type="lifecycleState") if result: self.load() def _specific_copy(self, obj: 'Service') -> None: """ Copy specific properties from object. Args: obj (Service): the object to "copy" """ def _verify_distribute_to_sdc(self, desired_status: str, desired_action: str, **kwargs) -> None: self._verify_action_to_sdc(desired_status, desired_action, "distribution", **kwargs) def _verify_approve_to_sdc(self, desired_status: str, desired_action: str, **kwargs) -> None: self._verify_action_to_sdc(desired_status, desired_action, "distribution-state", **kwargs) def _verify_lcm_to_sdc(self, desired_status: str, desired_action: str, **kwargs) -> None: self._verify_action_to_sdc(desired_status, desired_action, "lifecycleState", **kwargs) def _verify_action_to_sdc(self, desired_status: str, desired_action: str, action_type: str, **kwargs) -> None: """ Verify action to SDC. Verify that object is in right state before launching the action on SDC. Args: desired_status (str): the status the object should be desired_action (str): the action we want to perform action_type (str): the type of action ('distribution-state' or 'lifecycleState') **kwargs: any specific stuff to give to requests """ self._logger.info("attempting to %s Service %s in SDC", desired_action, self.name) if self.status == desired_status and self.created(): result = self._action_to_sdc(desired_action, action_type=action_type, **kwargs) if result: self.load() elif not self.created(): self._logger.warning("Service %s in SDC is not created", self.name) elif self.status != desired_status: self._logger.warning(("Service %s in SDC is in status %s and it " "should be in status %s"), self.name, self.status, desired_status) @staticmethod def _unzip_csar_file( zip_file: Union[str, BytesIO], function: Callable[[str, TextIOWrapper], None]) -> None: """ Unzip Csar File and perform an action on the file. Raises: AttributeError: CSAR file has no service template """ with ZipFile(zip_file) as myzip: service_template = None for name in myzip.namelist(): if (name[-13:] == "-template.yml" and name[:20] == "Definitions/service-"): service_template = name if not service_template: raise AttributeError("CSAR file has no service template") with myzip.open(service_template) as template_file: function(service_template, template_file) @staticmethod def _write_csar_file(service_template: str, template_file: TextIOWrapper) -> None: """Write service temple into a file.""" with open(tosca_path() + service_template[12:], 'wb') as file: file.write(template_file.read()) # _service_template is not used but function generation is generic # pylint: disable-unused-argument def _load_tosca_template(self, _service_template: str, template_file: TextIOWrapper) -> None: """Load Tosca template.""" self._tosca_template = yaml.safe_load(template_file.read()) @classmethod def _sdc_path(cls) -> None: """Give back the end of SDC path.""" return cls.SERVICE_PATH
def distribute(self) -> None: """Apptove Service in SDC.""" headers = headers_sdc_creator(SdcResource.headers) self._verify_distribute_to_sdc(const.CERTIFIED, const.DISTRIBUTE, headers=headers)
def approve(self) -> None: """Approve Service in SDC.""" headers = headers_sdc_creator(SdcResource.headers) self._verify_approve_to_sdc(const.CERTIFIED, const.APPROVE, headers=headers)
def certify(self) -> None: """Certify Service in SDC.""" headers = headers_sdc_creator(SdcResource.headers) self._verify_lcm_to_sdc(const.CHECKED_IN, const.CERTIFY, headers=headers)
def test_headers_sdc_creator(): base_header = {} sdc_headers_creator = headers_sdc_creator(base_header) assert base_header != sdc_headers_creator assert sdc_headers_creator["USER_ID"] == "cs0008" assert sdc_headers_creator["Authorization"]
class Vendor(SdcElement): """ ONAP Vendor Object used for SDC operations. Attributes: name (str): the name of the vendor. Defaults to "Generic-Vendor". identifier (str): the unique ID of the vendor from SDC. status (str): the status of the vendor from SDC. version (str): the version ID of the vendor from SDC. """ VENDOR_PATH = "vendor-license-models" headers = headers_sdc_creator(SdcElement.headers) def __init__(self, name: str = None): """ Initialize vendor object. Args: name (optional): the name of the vendor """ super().__init__() self.name: str = name or "Generic-Vendor" def onboard(self) -> None: """Onboard the vendor in SDC.""" if not self.status: self.create() self.onboard() elif self.status == const.DRAFT: self.submit() def create(self) -> None: """Create the vendor in SDC if not already existing.""" self._create("vendor_create.json.j2", name=self.name) def submit(self) -> None: """Submit the SDC vendor in order to enable it.""" self._logger.info("attempting to certify/sumbit vendor %s in SDC", self.name) if self.status != const.CERTIFIED and self.created(): self._really_submit() elif self.status == const.CERTIFIED: self._logger.warning( "vendor %s in SDC is already submitted/certified", self.name) elif not self.created(): self._logger.warning("vendor %s in SDC is not created", self.name) def update_informations_from_sdc(self, details: Dict[str, Any]) -> None: """ Update instance with details from SDC. Args: details (Dict[str, Any]): dict from SDC """ self._status = details['results'][-1]['status'] @classmethod def import_from_sdc(cls, values: Dict[str, Any]) -> 'Vendor': """ Import Vendor from SDC. Args: values (Dict[str, Any]): dict to parse returned from SDC. Returns: a Vsp instance with right values """ vendor = Vendor(values['name']) vendor.identifier = values['id'] return vendor def _really_submit(self) -> None: """Really submit the SDC Vf in order to enable it.""" result = self._action_to_sdc(const.SUBMIT) if result: self._status = const.CERTIFIED @classmethod def _sdc_path(cls) -> None: """Give back the end of SDC path.""" return cls.VENDOR_PATH
class SdcResource(SdcOnboardable, ABC): # pylint: disable=too-many-instance-attributes, too-many-public-methods """Mother Class of all SDC resources.""" RESOURCE_PATH = 'resources' ACTION_TEMPLATE = 'sdc_resource_action.json.j2' ACTION_METHOD = 'POST' headers = headers_sdc_creator(SdcOnboardable.headers) def __init__( self, name: str = None, version: str = None, # pylint: disable=too-many-arguments sdc_values: Dict[str, str] = None, properties: List[Property] = None, inputs: Union[Property, NestedInput] = None, category: str = None, subcategory: str = None): """Initialize the object.""" super().__init__(name) self.version_filter: str = version self._unique_uuid: str = None self._unique_identifier: str = None self._resource_type: str = "resources" self._properties_to_add: List[Property] = properties or [] self._inputs_to_add: Union[Property, NestedInput] = inputs or [] self._time_wait: int = 10 self._category_name: str = category self._subcategory_name: str = subcategory if sdc_values: self._logger.debug("SDC values given, using them") self.identifier = sdc_values['uuid'] self.version = sdc_values['version'] self.unique_uuid = sdc_values['invariantUUID'] distribitution_state = None if 'distributionStatus' in sdc_values: distribitution_state = sdc_values['distributionStatus'] self.status = self._parse_sdc_status(sdc_values['lifecycleState'], distribitution_state, self._logger) self._logger.debug("SDC resource %s status: %s", self.name, self.status) def __repr__(self) -> str: """SDC resource description. Returns: str: SDC resource object description """ return f"{self.__class__.__name__.upper()}(name={self.name})" @property def unique_uuid(self) -> str: """Return and lazy load the unique_uuid.""" if not self._unique_uuid: self.load() return self._unique_uuid @property def unique_identifier(self) -> str: """Return and lazy load the unique_identifier.""" if not self._unique_identifier: self.deep_load() return self._unique_identifier @unique_uuid.setter def unique_uuid(self, value: str) -> None: """Set value for unique_uuid.""" self._unique_uuid = value @unique_identifier.setter def unique_identifier(self, value: str) -> None: """Set value for unique_identifier.""" self._unique_identifier = value def load(self) -> None: """Load Object information from SDC.""" self.exists() def deep_load(self) -> None: """Deep load Object informations from SDC.""" url = (f"{self.base_front_url}/sdc1/feProxy/rest/v1/" "screen?excludeTypes=VFCMT&excludeTypes=Configuration") headers = headers_sdc_creator(SdcResource.headers) if self.status == const.UNDER_CERTIFICATION: headers = headers_sdc_tester(SdcResource.headers) response = self.send_message_json("GET", "Deep Load {}".format( type(self).__name__), url, headers=headers) if response: for resource in response[self._sdc_path()]: if resource["uuid"] == self.identifier: self._logger.debug("Resource %s found in %s list", resource["name"], self._sdc_path()) self.unique_identifier = resource["uniqueId"] self._category_name = resource["categories"][0]["name"] subcategories = resource["categories"][0].get( "subcategories", [{}]) self._subcategory_name = None if subcategories is None else \ subcategories[0].get("name") def _generate_action_subpath(self, action: str) -> str: """ Generate subpath part of SDC action url. Args: action (str): the action that will be done Returns: str: the subpath part """ return action def _version_path(self) -> str: """ Give the end of the path for a version. Returns: str: the end of the path """ return self.unique_identifier def _action_url(self, base: str, subpath: str, version_path: str, action_type: str = None) -> str: """ Generate action URL for SDC. Args: base (str): base part of url subpath (str): subpath of url version_path (str): version path of the url action_type (str, optional): the type of action ('distribution', 'distribution-state' or 'lifecycleState'). Default to 'lifecycleState'). Returns: str: the URL to use """ if not action_type: action_type = "lifecycleState" return "{}/{}/{}/{}/{}".format(base, self._resource_type, version_path, action_type, subpath) @classmethod def _base_create_url(cls) -> str: """ Give back the base url of Sdc. Returns: str: the base url """ return "{}/sdc1/feProxy/rest/v1/catalog".format(cls.base_front_url) @classmethod def _base_url(cls) -> str: """ Give back the base url of Sdc. Returns: str: the base url """ return "{}/sdc/v1/catalog".format(cls.base_back_url) @classmethod def _get_all_url(cls) -> str: """ Get URL for all elements in SDC. Returns: str: the url """ return "{}/{}?resourceType={}".format(cls._base_url(), cls._sdc_path(), cls.__name__.upper()) @classmethod def _get_objects_list( cls, result: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """ Import objects created in SDC. Args: result (Dict[str, Any]): the result returned by SDC in a Dict Return: List[Dict[str, Any]]: the list of objects """ return result def _get_version_from_sdc(self, sdc_infos: Dict[str, Any]) -> str: """ Get version from SDC results. Args: sdc_infos (Dict[str, Any]): the result dict from SDC Returns: str: the version """ return sdc_infos['version'] def _get_identifier_from_sdc(self, sdc_infos: Dict[str, Any]) -> str: """ Get identifier from SDC results. Args: sdc_infos (Dict[str, Any]): the result dict from SDC Returns: str: the identifier """ return sdc_infos['uuid'] @classmethod def import_from_sdc(cls, values: Dict[str, Any]) -> 'SdcResource': """ Import SdcResource from SDC. Args: values (Dict[str, Any]): dict to parse returned from SDC. Return: SdcResource: the created resource """ cls._logger.debug("importing SDC Resource %s from SDC", values['name']) return cls(name=values['name'], sdc_values=values) def _copy_object(self, obj: 'SdcResource') -> None: """ Copy relevant properties from object. Args: obj (SdcResource): the object to "copy" """ self.identifier = obj.identifier self.unique_uuid = obj.unique_uuid self.status = obj.status self.version = obj.version self.unique_identifier = obj.unique_identifier self._specific_copy(obj) def _specific_copy(self, obj: 'SdcResource') -> None: """ Copy specific properties from object. Args: obj (SdcResource): the object to "copy" """ def update_informations_from_sdc(self, details: Dict[str, Any]) -> None: """ Update instance with details from SDC. Args: details ([type]): [description] """ def update_informations_from_sdc_creation(self, details: Dict[str, Any]) -> None: """ Update instance with details from SDC after creation. Args: details ([type]): the details from SDC """ self.unique_uuid = details['invariantUUID'] distribution_state = None if 'distributionStatus' in details: distribution_state = details['distributionStatus'] self.status = self._parse_sdc_status(details['lifecycleState'], distribution_state, self._logger) self.version = details['version'] self.unique_identifier = details['uniqueId'] # Not my fault if SDC has so many states... # pylint: disable=too-many-return-statements @staticmethod def _parse_sdc_status(sdc_status: str, distribution_state: str, logger: logging.Logger) -> str: """ Parse SDC status in order to normalize it. Args: sdc_status (str): the status found in SDC distribution_state (str): the distribution status found in SDC. Can be None. Returns: str: the normalized status """ logger.debug("Parse status for SDC Resource") if sdc_status.capitalize() == const.CERTIFIED: if distribution_state and distribution_state == const.SDC_DISTRIBUTED: return const.DISTRIBUTED return const.CERTIFIED if sdc_status == const.NOT_CERTIFIED_CHECKOUT: return const.DRAFT if sdc_status == const.NOT_CERTIFIED_CHECKIN: return const.CHECKED_IN if sdc_status == const.READY_FOR_CERTIFICATION: return const.SUBMITTED if sdc_status == const.CERTIFICATION_IN_PROGRESS: return const.UNDER_CERTIFICATION if sdc_status != "": return sdc_status return None def _really_submit(self) -> None: """Really submit the SDC Vf in order to enable it.""" raise NotImplementedError("SDC is an abstract class") def onboard(self) -> None: """Onboard resource in SDC.""" if not self.status: self.create() time.sleep(self._time_wait) self.onboard() elif self.status == const.DRAFT: for property_to_add in self._properties_to_add: self.add_property(property_to_add) for input_to_add in self._inputs_to_add: self.declare_input(input_to_add) self.submit() time.sleep(self._time_wait) self.onboard() elif self.status == const.CERTIFIED: self.load() @classmethod def _sdc_path(cls) -> None: """Give back the end of SDC path.""" return cls.RESOURCE_PATH @property def deployment_artifacts_url(self) -> str: """Deployment artifacts url. Returns: str: SdcResource Deployment artifacts url """ return ( f"{self._base_create_url()}/resources/" f"{self.unique_identifier}/filteredDataByParams?include=deploymentArtifacts" ) @property def add_deployment_artifacts_url(self) -> str: """Add deployment artifacts url. Returns: str: Url used to add deployment artifacts """ return (f"{self._base_create_url()}/resources/" f"{self.unique_identifier}/artifacts") @property def properties_url(self) -> str: """Properties url. Returns: str: SdcResource properties url """ return ( f"{self._base_create_url()}/resources/" f"{self.unique_identifier}/filteredDataByParams?include=properties" ) @property def add_property_url(self) -> str: """Add property url. Returns: str: Url used to add property """ return (f"{self._base_create_url()}/services/" f"{self.unique_identifier}/properties") @property def set_input_default_value_url(self) -> str: """Url to set input default value. Returns: str: SDC API url used to set input default value """ return (f"{self._base_create_url()}/resources/" f"{self.unique_identifier}/update/inputs") @property def origin_type(self) -> str: """Resource origin type. Value needed for composition. It's used for adding SDC resource as an another SDC resource component. Returns: str: SDC resource origin type """ return type(self).__name__ @property def properties(self) -> Iterator[Property]: """SDC resource properties. Iterate resource properties. Yields: Property: Resource property """ for property_data in self.send_message_json(\ "GET", f"Get {self.name} resource properties", self.properties_url, exception=AttributeError).get("properties", []): yield Property( sdc_resource=self, unique_id=property_data["uniqueId"], name=property_data["name"], property_type=property_data["type"], parent_unique_id=property_data["parentUniqueId"], value=property_data.get("value"), description=property_data.get("description"), get_input_values=property_data.get("getInputValues"), ) def get_property(self, property_name: str) -> Property: """Get resource property by it's name. Args: property_name (str): property name Raises: AttributeError: Resource has no property with given name Returns: Property: Resource's property object """ for property_obj in self.properties: if property_obj.name == property_name: return property_obj raise AttributeError("Resource has no property with %s name" % property_name) @property def resource_inputs_url(self) -> str: """Resource inputs url. Method which returns url which point to resource inputs. Returns: str: Resource inputs url """ return (f"{self._base_create_url()}/resources/" f"{self.unique_identifier}") def create(self) -> None: """Create resource. Abstract method which should be implemented by subclasses and creates resource in SDC. Raises: NotImplementedError: Method not implemented by subclasses. """ raise NotImplementedError @property def inputs(self) -> Iterator[Input]: """SDC resource inputs. Iterate resource inputs. Yields: Iterator[Input]: Resource input """ for input_data in self.send_message_json(\ "GET", f"Get {self.name} resource inputs", f"{self.resource_inputs_url}/filteredDataByParams?include=inputs", exception=AttributeError).get("inputs", []): yield Input(unique_id=input_data["uniqueId"], input_type=input_data["type"], name=input_data["name"], sdc_resource=self, _default_value=input_data.get("defaultValue")) def get_input(self, input_name: str) -> Input: """Get input by it's name. Args: input_name (str): Input name Raises: AttributeError: Resource doesn't have input with given name Returns: Input: Found input object """ for input_obj in self.inputs: if input_obj.name == input_name: return input_obj raise AttributeError("SDC resource has no %s input" % input_name) def add_deployment_artifact(self, artifact_type: str, artifact_label: str, artifact_name: str, artifact: str): """ Add deployment artifact to resource. Add deployment artifact to resource using payload data. Args: artifact_type (str): all SDC artifact types are supported (DCAE_*, HEAT_*, ...) artifact_name (str): the artifact file name including its extension artifact (str): artifact file to upload artifact_label (str): Unique Identifier of the artifact within the VF / Service. Raises: AttributeError: Resource has not DRAFT status """ data = open(artifact, 'rb').read() artifact_string = base64.b64encode(data).decode('utf-8') if self.status != const.DRAFT: raise AttributeError( "Can't add artifact to resource which is not in DRAFT status") self._logger.debug("Add deployment artifact to sdc resource") my_data = jinja_env().get_template( "sdc_resource_add_deployment_artifact.json.j2").\ render(artifact_name=artifact_name, artifact_label=artifact_label, artifact_type=artifact_type, b64_artifact=artifact_string) my_header = headers_sdc_artifact_upload(base_header=self.headers, data=my_data) self.send_message_json( "POST", f"Add deployment artifact for {self.name} sdc resource", self.add_deployment_artifacts_url, data=my_data, headers=my_header, exception=ValueError) @property def components(self) -> Iterator[Component]: """Resource components. Iterate resource components. Yields: Component: Resource component object """ for component_instance in self.send_message_json(\ "GET", f"Get {self.name} resource inputs", f"{self.resource_inputs_url}/filteredDataByParams?include=componentInstances", exception=AttributeError).get("componentInstances", []): sdc_resource: "SdcResource" = SdcResource.import_from_sdc(self.send_message_json(\ "GET", f"Get {self.name} component's SDC resource metadata", (f"{self.base_front_url}/sdc1/feProxy/rest/v1/catalog/resources/" f"{component_instance['actualComponentUid']}/" "filteredDataByParams?include=metadata"), exception=AttributeError)["metadata"]) yield Component.create_from_api_response( api_response=component_instance, sdc_resource=sdc_resource, parent_sdc_resource=self) @property def category(self) -> Union[ResourceCategory, ServiceCategory]: """Sdc resource category. Depends on the resource type returns ResourceCategory or ServiceCategory. Returns: Uniton[ResourceCategory, ServiceCategory]: resource category """ if self.created(): if not any([self._category_name, self._subcategory_name]): self.deep_load() if all([self._category_name, self._subcategory_name]): return ResourceCategory.get(name=self._category_name, subcategory=self._subcategory_name) return ServiceCategory.get(name=self._category_name) return self.get_category_for_new_resource() def get_category_for_new_resource(self) -> ResourceCategory: """Get category for resource not created in SDC yet. If no category values are provided default category is going to be used. Returns: ResourceCategory: Category of the new resource """ if not all([self._category_name, self._subcategory_name]): return ResourceCategory.get(name="Generic", subcategory="Abstract") return ResourceCategory.get(name=self._category_name, subcategory=self._subcategory_name) def get_component_properties_url(self, component: "Component") -> str: """Url to get component's properties. This method is here because component can have different url when it's a component of another SDC resource type, eg. for service and for VF components have different urls. Args: component (Component): Component object to prepare url for Returns: str: Component's properties url """ return (f"{self.resource_inputs_url}/" f"componentInstances/{component.unique_id}/properties") def get_component_properties_value_set_url(self, component: "Component") -> str: """Url to set component property value. This method is here because component can have different url when it's a component of another SDC resource type, eg. for service and for VF components have different urls. Args: component (Component): Component object to prepare url for Returns: str: Component's properties url """ return (f"{self.resource_inputs_url}/" f"resourceInstance/{component.unique_id}/properties") def is_own_property(self, property_to_check: Property) -> bool: """Check if given property is one of the resource's properties. Args: property_to_check (Property): Property to check Returns: bool: True if resource has given property, False otherwise """ return any((prop == property_to_check for prop in self.properties)) def get_component(self, sdc_resource: "SdcResource") -> Component: """Get resource's component. Get component by SdcResource object. Args: sdc_resource (SdcResource): Component's SdcResource Raises: AttributeError: Component with given SdcResource does not exist Returns: Component: Component object """ for component in self.components: if component.sdc_resource.name == sdc_resource.name: return component raise AttributeError("SDC resource %s is not a component" % sdc_resource.name) def declare_input_for_own_property(self, property_obj: Property) -> None: """Declare input for resource's property. For each property input can be declared. Args: property_obj (Property): Property to declare input """ self._logger.debug("Declare input for SDC resource property") self.send_message_json("POST", f"Declare new input for {property_obj.name} property", f"{self.resource_inputs_url}/create/inputs", data=jinja_env().get_template(\ "sdc_resource_add_input.json.j2").\ render(\ sdc_resource=self, property=property_obj), exception=ValueError) def declare_nested_input(self, nested_input: NestedInput) -> None: """Declare nested input for SDC resource. Nested input is an input of one of the components. Args: nested_input (NestedInput): Nested input object """ self._logger.debug( "Declare input for SDC resource's component property") component: Component = self.get_component(nested_input.sdc_resource) self.send_message_json("POST", f"Declare new input for {nested_input.input_obj.name} input", f"{self.resource_inputs_url}/create/inputs", data=jinja_env().get_template(\ "sdc_resource_add_nested_input.json.j2").\ render(\ sdc_resource=self, component=component, input=nested_input.input_obj), exception=ValueError) def declare_input(self, input_to_declare: Union[Property, NestedInput]) -> None: """Declare input for given property or nested input object. Call SDC FE API to declare input for given property. Args: input_declaration (Union[Property, NestedInput]): Property to declare input or NestedInput object """ self._logger.debug("Declare input") if isinstance(input_to_declare, Property): if self.is_own_property(input_to_declare): self.declare_input_for_own_property(input_to_declare) else: raise ValueError("Given property is not SDC resource property") else: self.declare_nested_input(input_to_declare) def add_property(self, property_to_add: Property) -> None: """Add property to resource. Call SDC FE API to add property to resource. Args: property_to_add (Property): Property object to add to resource. Raises: AttributeError: Resource has not DRAFT status """ if self.status != const.DRAFT: raise AttributeError( "Can't add property to resource which is not in DRAFT status") self._logger.debug("Add property to sdc resource") self.send_message_json("POST", f"Declare new property for {self.name} sdc resource", self.add_property_url, data=jinja_env().get_template( "sdc_resource_add_property.json.j2").\ render( property=property_to_add ), exception=ValueError) def set_property_value(self, property_obj: Property, value: Any) -> None: """Set property value. Set given value to resource property Args: property_obj (Property): Property object value (Any): Property value to set """ if not self.is_own_property(property_obj): raise ValueError("Given property is not a resource's property") self._logger.debug("Set %s property value", property_obj.name) self.send_message_json("PUT", f"Set {property_obj.name} value to {value}", self.add_property_url, data=jinja_env().get_template( "sdc_resource_set_property_value.json.j2").\ render( sdc_resource=self, property=property_obj, value=value ), exception=ValueError ) def set_input_default_value(self, input_obj: Input, default_value: Any) -> None: """Set input default value. Set given value as input default value Args: input_obj (Input): Input object value (Any): Default value to set """ self._logger.debug("Set %s input default value", input_obj.name) self.send_message_json("POST", f"Set {input_obj.name} default value to {default_value}", self.set_input_default_value_url, data=jinja_env().get_template( "sdc_resource_set_input_default_value.json.j2").\ render( sdc_resource=self, input=input_obj, default_value=default_value ), exception=ValueError )
def start_certification(self) -> None: """Start Certification on Service.""" headers = headers_sdc_creator(SdcResource.headers) self._verify_lcm_to_sdc(const.CHECKED_IN, const.START_CERTIFICATION, headers=headers)