Add PaaS service creation UTs
[osm/osmclient.git] / osmclient / sol005 / ns.py
index c8e2a77..992bf0f 100644 (file)
@@ -22,65 +22,67 @@ from osmclient.common import utils
 from osmclient.common import wait as WaitForStatus
 from osmclient.common.exceptions import ClientException
 from osmclient.common.exceptions import NotFound
 from osmclient.common import wait as WaitForStatus
 from osmclient.common.exceptions import ClientException
 from osmclient.common.exceptions import NotFound
-from osmclient.common.exceptions import OsmHttpException
 import yaml
 import json
 import logging
 
 
 class Ns(object):
 import yaml
 import json
 import logging
 
 
 class Ns(object):
-
     def __init__(self, http=None, client=None):
         self._http = http
         self._client = client
     def __init__(self, http=None, client=None):
         self._http = http
         self._client = client
-        self._logger = logging.getLogger('osmclient')
-        self._apiName = '/nslcm'
-        self._apiVersion = '/v1'
-        self._apiResource = '/ns_instances_content'
-        self._apiBase = '{}{}{}'.format(self._apiName,
-                                        self._apiVersion, self._apiResource)
+        self._logger = logging.getLogger("osmclient")
+        self._apiName = "/nslcm"
+        self._apiVersion = "/v1"
+        self._apiResource = "/ns_instances_content"
+        self._apiBase = "{}{}{}".format(
+            self._apiName, self._apiVersion, self._apiResource
+        )
 
     # NS '--wait' option
 
     # NS '--wait' option
-    def _wait(self, id, deleteFlag=False):
+    def _wait(self, op_id, wait_time, delete_flag=False):
         self._logger.debug("")
         # Endpoint to get operation status
         self._logger.debug("")
         # Endpoint to get operation status
-        apiUrlStatus = '{}{}{}'.format(self._apiName, self._apiVersion, '/ns_lcm_op_occs')
+        apiUrlStatus = "{}{}{}".format(
+            self._apiName, self._apiVersion, "/ns_lcm_op_occs"
+        )
         # Wait for status for NS instance creation/update/deletion
         # Wait for status for NS instance creation/update/deletion
+        if isinstance(wait_time, bool):
+            wait_time = WaitForStatus.TIMEOUT_NS_OPERATION
         WaitForStatus.wait_for_status(
         WaitForStatus.wait_for_status(
-            'NS',
-            str(id),
-            WaitForStatus.TIMEOUT_NS_OPERATION,
+            "NS",
+            str(op_id),
+            wait_time,
             apiUrlStatus,
             self._http.get2_cmd,
             apiUrlStatus,
             self._http.get2_cmd,
-            deleteFlag=deleteFlag)
+            deleteFlag=delete_flag,
+        )
 
     def list(self, filter=None):
 
     def list(self, filter=None):
-        """Returns a list of NS
-        """
+        """Returns a list of NS"""
         self._logger.debug("")
         self._client.get_token()
         self._logger.debug("")
         self._client.get_token()
-        filter_string = ''
+        filter_string = ""
         if filter:
         if filter:
-            filter_string = '?{}'.format(filter)
-        _, resp = self._http.get2_cmd('{}{}'.format(self._apiBase,filter_string))
+            filter_string = "?{}".format(filter)
+        _, resp = self._http.get2_cmd("{}{}".format(self._apiBase, filter_string))
         if resp:
             return json.loads(resp)
         return list()
 
     def get(self, name):
         if resp:
             return json.loads(resp)
         return list()
 
     def get(self, name):
-        """Returns an NS based on name or id
-        """
+        """Returns an NS based on name or id"""
         self._logger.debug("")
         self._client.get_token()
         if utils.validate_uuid4(name):
             for ns in self.list():
         self._logger.debug("")
         self._client.get_token()
         if utils.validate_uuid4(name):
             for ns in self.list():
-                if name == ns['_id']:
+                if name == ns["_id"]:
                     return ns
         else:
             for ns in self.list():
                     return ns
         else:
             for ns in self.list():
-                if name == ns['name']:
+                if name == ns["name"]:
                     return ns
                     return ns
-        raise NotFound("ns {} not found".format(name))
+        raise NotFound("ns '{}' not found".format(name))
 
     def get_individual(self, name):
         self._logger.debug("")
 
     def get_individual(self, name):
         self._logger.debug("")
@@ -88,272 +90,543 @@ class Ns(object):
         ns_id = name
         if not utils.validate_uuid4(name):
             for ns in self.list():
         ns_id = name
         if not utils.validate_uuid4(name):
             for ns in self.list():
-                if name == ns['name']:
-                    ns_id = ns['_id']
+                if name == ns["name"]:
+                    ns_id = ns["_id"]
                     break
                     break
-        _, resp = self._http.get2_cmd('{}/{}'.format(self._apiBase, ns_id))
-        #resp = self._http.get_cmd('{}/{}/nsd_content'.format(self._apiBase, ns_id))
-        #print(yaml.safe_dump(resp))
-        if resp:
-            return json.loads(resp)
-        raise NotFound("ns {} not found".format(name))
+        try:
+            _, resp = self._http.get2_cmd("{}/{}".format(self._apiBase, ns_id))
+            # resp = self._http.get_cmd('{}/{}/nsd_content'.format(self._apiBase, ns_id))
+            # print(yaml.safe_dump(resp))
+            if resp:
+                return json.loads(resp)
+        except NotFound:
+            raise NotFound("ns '{}' not found".format(name))
+        raise NotFound("ns '{}' not found".format(name))
 
 
-    def delete(self, name, force=False, wait=False):
+    def delete(self, name, force=False, config=None, wait=False):
+        """
+        Deletes a Network Service (NS)
+        :param name: name of network service
+        :param force: set force. Direct deletion without cleaning at VIM
+        :param config: parameters of deletion, as:
+             autoremove: Bool (default True)
+             timeout_ns_terminate: int
+             skip_terminate_primitives: Bool (default False) to not exec termination primitives
+        :param wait: Make synchronous. Wait until deletion is completed:
+            False to not wait (by default), True to wait a standard time, or int (time to wait)
+        :return: None. Exception if fail
+        """
         self._logger.debug("")
         ns = self.get(name)
         self._logger.debug("")
         ns = self.get(name)
-        querystring = ''
+        querystring_list = []
+        querystring = ""
+        if config:
+            ns_config = yaml.safe_load(config)
+            querystring_list += ["{}={}".format(k, v) for k, v in ns_config.items()]
         if force:
         if force:
-            querystring = '?FORCE=True'
-        http_code, resp = self._http.delete_cmd('{}/{}{}'.format(self._apiBase,
-                                                 ns['_id'], querystring))
+            querystring_list.append("FORCE=True")
+        if querystring_list:
+            querystring = "?" + "&".join(querystring_list)
+        http_code, resp = self._http.delete_cmd(
+            "{}/{}{}".format(self._apiBase, ns["_id"], querystring)
+        )
+        # TODO change to use a POST self._http.post_cmd('{}/{}/terminate{}'.format(_apiBase, ns['_id'], querystring),
+        #                                               postfields_dict=ns_config)
+        # seting autoremove as True by default
         # print('HTTP CODE: {}'.format(http_code))
         # print('RESP: {}'.format(resp))
         if http_code == 202:
             if wait and resp:
                 resp = json.loads(resp)
                 # For the 'delete' operation, '_id' is used
         # print('HTTP CODE: {}'.format(http_code))
         # print('RESP: {}'.format(resp))
         if http_code == 202:
             if wait and resp:
                 resp = json.loads(resp)
                 # For the 'delete' operation, '_id' is used
-                self._wait(resp.get('_id'), deleteFlag=True)
+                self._wait(resp.get("_id"), wait, delete_flag=True)
             else:
             else:
-                print('Deletion in progress')
+                print("Deletion in progress")
         elif http_code == 204:
         elif http_code == 204:
-            print('Deleted')
+            print("Deleted")
         else:
         else:
-            msg = ""
-            if resp:
-                try:
-                    msg = json.loads(resp)
-                except ValueError:
-                    msg = resp
-            raise OsmHttpException("failed to delete ns {} - {}".format(name, msg))
-
-    def create(self, nsd_name, nsr_name, account, config=None,
-               ssh_keys=None, description='default description',
-               admin_status='ENABLED', wait=False):
+            msg = resp or ""
+            raise ClientException("failed to delete ns {} - {}".format(name, msg))
+
+    def _get_vim_account_id(self, vim_account: str, vim_account_dict: dict) -> str:
+        """Get VIM account ID.
+        Args:
+            vim_account (str):          VIM account id as string
+            vim_account_dict  (dict):   A dictionary which includes vim account id
+
+        Returns:
+            vim_id (str):               VIM account id as string
+
+        Raises:
+            NotFound Exception
+        """
         self._logger.debug("")
         self._logger.debug("")
-        self._client.get_token()
-        nsd = self._client.nsd.get(nsd_name)
+        if vim_account_dict.get(vim_account):
+            return vim_account_dict[vim_account]
+        vim = self._client.vim.get(vim_account)
+        if vim is None:
+            raise NotFound("cannot find vim account '{}'".format(vim_account))
+        vim_account_dict[vim_account] = vim["_id"]
+        return vim["_id"]
+
+    def _get_wim_account_id(self, wim_account: str, wim_account_dict: dict) -> str:
+        """Get WIM account ID.
+        Args:
+            wim_account (str):          WIM account id as string
+            wim_account_dict  (dict):   A dictionary which includes wim account id
+
+        Returns:
+            wim_id (str):               WIM account id as string
+
+        Raises:
+            NotFound Exception
+        """
+        self._logger.debug("")
+        # wim_account can be False (boolean) to indicate not use wim account
+        if not isinstance(wim_account, str):
+            return wim_account
+        if wim_account_dict.get(wim_account):
+            return wim_account_dict[wim_account]
+        wim = self._client.wim.get(wim_account)
+        if wim is None:
+            raise NotFound("cannot find wim account '{}'".format(wim_account))
+        wim_account_dict[wim_account] = wim["_id"]
+        return wim["_id"]
+
+    def _get_paas_account_id(self, paas_account: str) -> str:
+        """Get PaaS account ID.
+        Args:
+            paas_account    (str):          PaaS account id as string
+
+        Returns:
+            paas_id (str):                  PaaS account id as string
+
+        Raises:
+            NotFound Exception
+        """
+        self._logger.debug("")
+        paas = self._client.paas.get(paas_account)
+        if paas is None:
+            raise NotFound("cannot find PaaS account '{}'".format(paas_account))
+        return paas["_id"]
+
+    def _update_vnf_in_ns_config(self, ns_config: dict, vim_account_dict: dict) -> dict:
+        """Update vnf field in ns_config.
+        Args:
+            ns_config   (dict):         NS config dictionary which includes additional params
+            vim_account_dict  (dict):   A dictionary which includes vim account id
+
+        Returns:
+            ns  (dict):                 NS dictionary
+        """
+        if "vnf" in ns_config:
+            for vnf in ns_config["vnf"]:
+                if vnf.get("vim_account"):
+                    vnf["vimAccountId"] = self._get_vim_account_id(
+                        vnf.pop("vim_account"), vim_account_dict
+                    )
+        return ns_config
+
+    def _update_wim_account_in_ns(
+        self, ns_config: dict, wim_account_dict: dict, ns: dict
+    ) -> dict:
+        """Update WIM_account in NS dictionary.
+        Args:
+            ns_config   (dict):         NS config dictionary which includes additional params
+            wim_account_dict  (dict):   A dictionary which includes wim account id
+            ns  (dict):                 NS dictionary which includes ns_id, ns_name, description etc.
+
+        Returns:
+            ns  (dict):                 NS dictionary
+        """
+        if "wim_account" in ns_config:
+            wim_account = ns_config.pop("wim_account")
+            if wim_account is not None:
+                ns["wimAccountId"] = self._get_wim_account_id(
+                    wim_account, wim_account_dict
+                )
+        return ns
+
+    def _update_vld_in_ns_config(
+        self, ns_config: dict, vim_account_dict: dict, wim_account_dict: dict
+    ) -> dict:
+        """Validating the additionalParamsForNs and additionalParamsForVnf in ns_config.
+
+        Args:
+            ns_config   (dict):     NS config dictionary which includes additional params
+            vim_account_dict  (dict): A dictionary which includes vim account id
+            wim_account_dict  (dict): A dictionary which includes wim account id
+
+        Returns:
+            ns_config   (dict):     NS config dictionary which includes additional params
+
+        Raises:
+            ClientException
+        """
+        if "vld" in ns_config:
+            if not isinstance(ns_config["vld"], list):
+                raise ClientException(
+                    "Error at --config 'vld' must be a list of dictionaries"
+                )
+            for vld in ns_config["vld"]:
+                if not isinstance(vld, dict):
+                    raise ClientException(
+                        "Error at --config 'vld' must be a list of dictionaries"
+                    )
+                if vld.get("vim-network-name"):
+                    if isinstance(vld["vim-network-name"], dict):
+                        vim_network_name_dict = {}
+                        for vim_account, vim_net in vld["vim-network-name"].items():
+                            vim_network_name_dict[
+                                self._get_vim_account_id(vim_account, vim_account_dict)
+                            ] = vim_net
+                        vld["vim-network-name"] = vim_network_name_dict
+                if "wim_account" in vld and vld["wim_account"] is not None:
+                    vld["wimAccountId"] = self._get_wim_account_id(
+                        vld.pop("wim_account"), wim_account_dict
+                    )
+        return ns_config
+
+    def _validate_additional_params_in_ns_config(self, ns_config: dict) -> None:
+        """Validating the additionalParamsForNs and additionalParamsForVnf in ns_config.
+        Args:
+            ns_config   (dict):     NS config dictionary which includes additional params
+
+        Raises:
+            ClientException
+        """
+        if "additionalParamsForNs" in ns_config:
+            if not isinstance(ns_config["additionalParamsForNs"], dict):
+                raise ClientException(
+                    "Error at --config 'additionalParamsForNs' must be a dictionary"
+                )
+        if "additionalParamsForVnf" in ns_config:
+            if not isinstance(ns_config["additionalParamsForVnf"], list):
+                raise ClientException(
+                    "Error at --config 'additionalParamsForVnf' must be a list"
+                )
+            for additional_param_vnf in ns_config["additionalParamsForVnf"]:
+                if not isinstance(additional_param_vnf, dict):
+                    raise ClientException(
+                        "Error at --config 'additionalParamsForVnf' items must be dictionaries"
+                    )
+                if not additional_param_vnf.get("member-vnf-index"):
+                    raise ClientException(
+                        "Error at --config 'additionalParamsForVnf' items must contain "
+                        "'member-vnf-index'"
+                    )
 
 
-        vim_account_id = {}
-        wim_account_id = {}
-
-        def get_vim_account_id(vim_account):
-            self._logger.debug("")
-            if vim_account_id.get(vim_account):
-                return vim_account_id[vim_account]
-
-            vim = self._client.vim.get(vim_account)
-            if vim is None:
-                raise NotFound("cannot find vim account '{}'".format(vim_account))
-            vim_account_id[vim_account] = vim['_id']
-            return vim['_id']
-
-        def get_wim_account_id(wim_account):
-            self._logger.debug("")
-            if not isinstance(wim_account, str):
-                return wim_account
-            if wim_account_id.get(wim_account):
-                return wim_account_id[wim_account]
-
-            wim = self._client.wim.get(wim_account)
-            if wim is None:
-                raise NotFound("cannot find wim account '{}'".format(wim_account))
-            wim_account_id[wim_account] = wim['_id']
-            return wim['_id']
-
-        ns = {}
-        ns['nsdId'] = nsd['_id']
-        ns['nsName'] = nsr_name
-        ns['nsDescription'] = description
-        ns['vimAccountId'] = get_vim_account_id(account)
-        #ns['userdata'] = {}
-        #ns['userdata']['key1']='value1'
-        #ns['userdata']['key2']='value2'
+    def process_ns_create_with_vim_account(
+        self,
+        vim_account: str,
+        nsd: dict,
+        nsr_name: str,
+        description: str,
+        config: dict = None,
+        ssh_keys: str = None,
+        timeout: int = None,
+    ) -> dict:
+        """Process NS create request which includes VIM Account.
+        Args:
+            vim_account (str):  VIM Account id as string
+            nsd (dict):         A dictionary which includes network service description
+            nsr_name    (str):  Network service record name
+            description (str):  Service description
+            config  (dict):     Placeholder for additional configuration
+            ssh_keys    (str):  ssh-key file
+            timeout (int):      Max time to wait (seconds)
+
+        Returns:
+            ns (dict):          Payload for ns create request
+
+        Raises:
+            ClientException
+        """
+        vim_account_dict = {}
+        wim_account_dict = {}
+        vim_id = self._get_vim_account_id(vim_account, vim_account_dict)
+        ns = {
+            "nsdId": nsd["_id"],
+            "nsName": nsr_name,
+            "nsDescription": description,
+            "vimAccountId": vim_id,
+        }
 
         if ssh_keys is not None:
 
         if ssh_keys is not None:
-            ns['ssh_keys'] = []
-            for pubkeyfile in ssh_keys.split(','):
-                with open(pubkeyfile, 'r') as f:
-                    ns['ssh_keys'].append(f.read())
+            ns["ssh_keys"] = []
+            for pubkeyfile in ssh_keys.split(","):
+                with open(pubkeyfile, "r") as f:
+                    ns["ssh_keys"].append(f.read())
+
+        if timeout:
+            ns["timeout_ns_deploy"] = timeout
+
         if config:
             ns_config = yaml.safe_load(config)
             if "vim-network-name" in ns_config:
                 ns_config["vld"] = ns_config.pop("vim-network-name")
         if config:
             ns_config = yaml.safe_load(config)
             if "vim-network-name" in ns_config:
                 ns_config["vld"] = ns_config.pop("vim-network-name")
-            if "vld" in ns_config:
-                for vld in ns_config["vld"]:
-                    if vld.get("vim-network-name"):
-                        if isinstance(vld["vim-network-name"], dict):
-                            vim_network_name_dict = {}
-                            for vim_account, vim_net in list(vld["vim-network-name"].items()):
-                                vim_network_name_dict[get_vim_account_id(vim_account)] = vim_net
-                            vld["vim-network-name"] = vim_network_name_dict
-                    if "wim_account" in vld and vld["wim_account"] is not None:
-                        vld["wimAccountId"] = get_wim_account_id(vld.pop("wim_account"))
-                ns["vld"] = ns_config["vld"]
-            if "vnf" in ns_config:
-                for vnf in ns_config["vnf"]:
-                    if vnf.get("vim_account"):
-                        vnf["vimAccountId"] = get_vim_account_id(vnf.pop("vim_account"))
-                ns["vnf"] = ns_config["vnf"]
-
-            if "additionalParamsForNs" in ns_config:
-                ns["additionalParamsForNs"] = ns_config.pop("additionalParamsForNs")
-                if not isinstance(ns["additionalParamsForNs"], dict):
-                    raise ValueError("Error at --config 'additionalParamsForNs' must be a dictionary")
-            if "additionalParamsForVnf" in ns_config:
-                ns["additionalParamsForVnf"] = ns_config.pop("additionalParamsForVnf")
-                if not isinstance(ns["additionalParamsForVnf"], list):
-                    raise ValueError("Error at --config 'additionalParamsForVnf' must be a list")
-                for additional_param_vnf in ns["additionalParamsForVnf"]:
-                    if not isinstance(additional_param_vnf, dict):
-                        raise ValueError("Error at --config 'additionalParamsForVnf' items must be dictionaries")
-                    if not additional_param_vnf.get("member-vnf-index"):
-                        raise ValueError("Error at --config 'additionalParamsForVnf' items must contain "
-                                         "'member-vnf-index'")
-            if "wim_account" in ns_config:
-                wim_account = ns_config.pop("wim_account")
-                if wim_account is not None:
-                    ns['wimAccountId'] = get_wim_account_id(wim_account)
-            if "timeout_ns_deploy" in ns_config:
-                ns["timeout_ns_deploy"] = ns_config.pop("timeout_ns_deploy")
-
-        # print(yaml.safe_dump(ns))
+
+            ns_config = self._update_vld_in_ns_config(
+                ns_config, vim_account_dict, wim_account_dict
+            )
+            ns_config = self._update_vnf_in_ns_config(ns_config, vim_account_dict)
+            self._validate_additional_params_in_ns_config(ns_config)
+            ns = self._update_wim_account_in_ns(ns_config, vim_account_dict, ns)
+            ns.update(ns_config)
+
+        return ns
+
+    def process_ns_create_with_paas_account(
+        self,
+        paas_account: str,
+        nsd: dict,
+        nsr_name: str,
+        description: str,
+        config: dict = None,
+        timeout: int = None,
+    ) -> dict:
+        """Process NS create request which includes PaaS Account.
+        Args:
+            paas_account (str): PaaS Account id as string
+            nsd (dict):         A dictionary which includes network service description
+            nsr_name    (str):  Network service record name
+            description (str):  Service description
+            config  (dict):     Placeholder for additional configuration
+            timeout (int):      Max time to wait (seconds)
+
+        Returns:
+            ns (dict):          Payload for ns create request
+
+        Raises:
+            ClientException
+        """
+        paas_id = self._get_paas_account_id(paas_account)
+        ns = {
+            "nsdId": nsd["_id"],
+            "nsName": nsr_name,
+            "nsDescription": description,
+            "paasAccountId": paas_id,
+        }
+
+        if timeout:
+            ns["timeout_ns_deploy"] = timeout
+
+        if config:
+            ns_config = yaml.safe_load(config)
+            self._validate_additional_params_in_ns_config(ns_config)
+            ns.update(ns_config)
+
+        return ns
+
+    def create(
+        self,
+        nsd_name: dict,
+        nsr_name: str,
+        vim_account: str = None,
+        paas_account: str = None,
+        config: dict = None,
+        ssh_keys: str = None,
+        description: str = "default description",
+        admin_status: str = "ENABLED",
+        wait: bool = False,
+        timeout: int = None,
+    ) -> str:
+        """NS create request which includes PaaS Account or VIM account.
+        Args:
+            nsd_name (dict):        A dictionary which includes network service description
+            nsr_name    (str):      Network service record name
+            vim_account (str):      VIM account ID as string
+            paas_account (str):     PaaS Account id as string
+            config  (dict):         Placeholder for additional configuration
+            ssh_keys    (str):      ssh-key file
+            description (str):      Service description
+            admin_status    (str):  Administration Status
+            wait    (Boolean):      True or False
+            timeout (int):          Max time to wait (seconds)
+
+        Returns:
+            response id (str):      Response ID
+
+        Raises:
+            ClientException
+        """
+        self._logger.debug("")
+
+        if not (vim_account or paas_account):
+            raise ClientException(
+                "Both of vim_account and paas_account options are empty."
+            )
+
+        if vim_account and paas_account:
+            raise ClientException(
+                "Both of vim_account and paas_account options are set."
+            )
+
+        self._client.get_token()
+        nsd = self._client.nsd.get(nsd_name)
+
+        if vim_account:
+            # VIM account is provided as input parameter.
+            ns = self.process_ns_create_with_vim_account(
+                vim_account,
+                nsd,
+                nsr_name,
+                description,
+                config=config,
+                ssh_keys=ssh_keys,
+                timeout=timeout,
+            )
+
+        elif paas_account:
+            # PaaS account is provided as input parameter.
+            ns = self.process_ns_create_with_paas_account(
+                paas_account, nsd, nsr_name, description, config=config, timeout=timeout
+            )
+
         try:
         try:
-            self._apiResource = '/ns_instances_content'
-            self._apiBase = '{}{}{}'.format(self._apiName,
-                                            self._apiVersion, self._apiResource)
+            self._apiResource = "/ns_instances_content"
+            self._apiBase = "{}{}{}".format(
+                self._apiName, self._apiVersion, self._apiResource
+            )
             headers = self._client._headers
             headers = self._client._headers
-            headers['Content-Type'] = 'application/yaml'
-            http_header = ['{}: {}'.format(key,val)
-                          for (key,val) in list(headers.items())]
+            headers["Content-Type"] = "application/yaml"
+            http_header = [
+                "{}: {}".format(key, val) for (key, val) in list(headers.items())
+            ]
             self._http.set_http_header(http_header)
             self._http.set_http_header(http_header)
-            http_code, resp = self._http.post_cmd(endpoint=self._apiBase,
-                                   postfields_dict=ns)
-            # print('HTTP CODE: {}'.format(http_code))
-            # print('RESP: {}'.format(resp))
-            #if http_code in (200, 201, 202, 204):
+            http_code, resp = self._http.post_cmd(
+                endpoint=self._apiBase, postfields_dict=ns
+            )
+
+            if not resp or "id" not in resp:
+                raise ClientException(
+                    "unexpected response from server - {} ".format(resp)
+                )
             if resp:
                 resp = json.loads(resp)
             if resp:
                 resp = json.loads(resp)
-            if not resp or 'id' not in resp:
-                raise ClientException('unexpected response from server - {} '.format(
-                                      resp))
+                print(str(resp["id"]))
             if wait:
                 # Wait for status for NS instance creation
             if wait:
                 # Wait for status for NS instance creation
-                self._wait(resp.get('nslcmop_id'))
-            return resp['id']
-            #else:
-            #    msg = ""
-            #    if resp:
-            #        try:
-            #            msg = json.loads(resp)
-            #        except ValueError:
-            #            msg = resp
-            #    raise ClientException(msg)
+                self._wait(resp.get("nslcmop_id"), wait)
+
+            return resp["id"]
+
         except ClientException as exc:
         except ClientException as exc:
-            message="failed to create ns: {} nsd: {}\nerror:\n{}".format(
-                    nsr_name,
-                    nsd_name,
-                    str(exc))
-            raise OsmHttpException(message)
+            message = "failed to create ns: {} nsd: {}\nerror:\n{}".format(
+                nsr_name, nsd_name, str(exc)
+            )
+            raise ClientException(message)
 
     def list_op(self, name, filter=None):
 
     def list_op(self, name, filter=None):
-        """Returns the list of operations of a NS
-        """
+        """Returns the list of operations of a NS"""
         self._logger.debug("")
         ns = self.get(name)
         try:
         self._logger.debug("")
         ns = self.get(name)
         try:
-            self._apiResource = '/ns_lcm_op_occs'
-            self._apiBase = '{}{}{}'.format(self._apiName,
-                                      self._apiVersion, self._apiResource)
-            filter_string = ''
+            self._apiResource = "/ns_lcm_op_occs"
+            self._apiBase = "{}{}{}".format(
+                self._apiName, self._apiVersion, self._apiResource
+            )
+            filter_string = ""
             if filter:
             if filter:
-                 filter_string = '&{}'.format(filter)
-            http_code, resp = self._http.get2_cmd('{}?nsInstanceId={}'.format(
-                                                       self._apiBase, ns['_id'],
-                                                       filter_string) )
-            #print('HTTP CODE: {}'.format(http_code))
-            #print('RESP: {}'.format(resp))
-            #if http_code == 200:
-            if resp:
-                resp = json.loads(resp)
-                return resp
+                filter_string = "&{}".format(filter)
+            http_code, resp = self._http.get2_cmd(
+                "{}?nsInstanceId={}{}".format(self._apiBase, ns["_id"], filter_string)
+            )
+            # print('HTTP CODE: {}'.format(http_code))
+            # print('RESP: {}'.format(resp))
+            if http_code == 200:
+                if resp:
+                    resp = json.loads(resp)
+                    return resp
+                else:
+                    raise ClientException("unexpected response from server")
             else:
             else:
-                raise ClientException('unexpected response from server')
-            #else:
-            #    msg = ""
-            #    if resp:
-            #        try:
-            #            resp = json.loads(resp)
-            #            msg = resp['detail']
-            #        except ValueError:
-            #            msg = resp
-            #    raise ClientException(msg)
+                msg = resp or ""
+                #    if resp:
+                #        try:
+                #            resp = json.loads(resp)
+                #            msg = resp['detail']
+                #        except ValueError:
+                #            msg = resp
+                raise ClientException(msg)
         except ClientException as exc:
         except ClientException as exc:
-            message="failed to get operation list of NS {}:\nerror:\n{}".format(
-                    name,
-                    str(exc))
-            raise OsmHttpException(message)
+            message = "failed to get operation list of NS {}:\nerror:\n{}".format(
+                name, str(exc)
+            )
+            raise ClientException(message)
 
     def get_op(self, operationId):
 
     def get_op(self, operationId):
-        """Returns the status of an operation
-        """
+        """Returns the status of an operation"""
         self._logger.debug("")
         self._client.get_token()
         try:
         self._logger.debug("")
         self._client.get_token()
         try:
-            self._apiResource = '/ns_lcm_op_occs'
-            self._apiBase = '{}{}{}'.format(self._apiName,
-                                      self._apiVersion, self._apiResource)
-            http_code, resp = self._http.get2_cmd('{}/{}'.format(self._apiBase, operationId))
-            #print('HTTP CODE: {}'.format(http_code))
-            #print('RESP: {}'.format(resp))
-            #if http_code == 200:
-            if resp:
-                resp = json.loads(resp)
-                return resp
+            self._apiResource = "/ns_lcm_op_occs"
+            self._apiBase = "{}{}{}".format(
+                self._apiName, self._apiVersion, self._apiResource
+            )
+            http_code, resp = self._http.get2_cmd(
+                "{}/{}".format(self._apiBase, operationId)
+            )
+            # print('HTTP CODE: {}'.format(http_code))
+            # print('RESP: {}'.format(resp))
+            if http_code == 200:
+                if resp:
+                    resp = json.loads(resp)
+                    return resp
+                else:
+                    raise ClientException("unexpected response from server")
             else:
             else:
-                raise ClientException('unexpected response from server')
-            #else:
-            #    msg = ""
-            #    if resp:
-            #        try:
-            #            resp = json.loads(resp)
-            #            msg = resp['detail']
-            #        except ValueError:
-            #            msg = resp
-            #    raise ClientException(msg)
+                msg = resp or ""
+                #    if resp:
+                #        try:
+                #            resp = json.loads(resp)
+                #            msg = resp['detail']
+                #        except ValueError:
+                #            msg = resp
+                raise ClientException(msg)
         except ClientException as exc:
         except ClientException as exc:
-            message="failed to get status of operation {}:\nerror:\n{}".format(
-                    operationId,
-                    str(exc))
-            raise OsmHttpException(message)
+            message = "failed to get status of operation {}:\nerror:\n{}".format(
+                operationId, str(exc)
+            )
+            raise ClientException(message)
 
 
-    def exec_op(self, name, op_name, op_data=None, wait=False, ):
-        """Executes an operation on a NS
-        """
+    def exec_op(
+        self,
+        name,
+        op_name,
+        op_data=None,
+        wait=False,
+    ):
+        """Executes an operation on a NS"""
         self._logger.debug("")
         ns = self.get(name)
         try:
             ns = self.get(name)
         self._logger.debug("")
         ns = self.get(name)
         try:
             ns = self.get(name)
-            self._apiResource = '/ns_instances'
-            self._apiBase = '{}{}{}'.format(self._apiName,
-                                            self._apiVersion, self._apiResource)
-            endpoint = '{}/{}/{}'.format(self._apiBase, ns['_id'], op_name)
-            #print('OP_NAME: {}'.format(op_name))
-            #print('OP_DATA: {}'.format(json.dumps(op_data)))
-            http_code, resp = self._http.post_cmd(endpoint=endpoint, postfields_dict=op_data)
-            #print('HTTP CODE: {}'.format(http_code))
-            #print('RESP: {}'.format(resp))
-            #if http_code in (200, 201, 202, 204):
+            self._apiResource = "/ns_instances"
+            self._apiBase = "{}{}{}".format(
+                self._apiName, self._apiVersion, self._apiResource
+            )
+            endpoint = "{}/{}/{}".format(self._apiBase, ns["_id"], op_name)
+            # print('OP_NAME: {}'.format(op_name))
+            # print('OP_DATA: {}'.format(json.dumps(op_data)))
+            http_code, resp = self._http.post_cmd(
+                endpoint=endpoint, postfields_dict=op_data
+            )
+            # print('HTTP CODE: {}'.format(http_code))
+            # print('RESP: {}'.format(resp))
+            # if http_code in (200, 201, 202, 204):
             if resp:
                 resp = json.loads(resp)
             if resp:
                 resp = json.loads(resp)
-            if not resp or 'id' not in resp:
-                raise ClientException('unexpected response from server - {}'.format(
-                                  resp))
+            if not resp or "id" not in resp:
+                raise ClientException(
+                    "unexpected response from server - {}".format(resp)
+                )
             if wait:
                 # Wait for status for NS instance action
                 # For the 'action' operation, 'id' is used
             if wait:
                 # Wait for status for NS instance action
                 # For the 'action' operation, 'id' is used
-                self._wait(resp.get('id'))
-            return resp['id']
-            #else:
+                self._wait(resp.get("id"), wait)
+            return resp["id"]
+            # else:
             #    msg = ""
             #    if resp:
             #        try:
             #    msg = ""
             #    if resp:
             #        try:
@@ -362,33 +635,92 @@ class Ns(object):
             #            msg = resp
             #    raise ClientException(msg)
         except ClientException as exc:
             #            msg = resp
             #    raise ClientException(msg)
         except ClientException as exc:
-            message="failed to exec operation {}:\nerror:\n{}".format(
-                    name,
-                    str(exc))
-            raise OsmHttpException(message)
+            message = "failed to exec operation {}:\nerror:\n{}".format(name, str(exc))
+            raise ClientException(message)
 
 
-    def scale_vnf(self, ns_name, vnf_name, scaling_group, scale_in, scale_out, wait=False):
-        """Scales a VNF by adding/removing VDUs
-        """
+    def scale_vnf(
+        self,
+        ns_name,
+        vnf_name,
+        scaling_group,
+        scale_in,
+        scale_out,
+        wait=False,
+        timeout=None,
+    ):
+        """Scales a VNF by adding/removing VDUs"""
         self._logger.debug("")
         self._client.get_token()
         try:
         self._logger.debug("")
         self._client.get_token()
         try:
-            op_data={}
+            op_data = {}
             op_data["scaleType"] = "SCALE_VNF"
             op_data["scaleVnfData"] = {}
             op_data["scaleType"] = "SCALE_VNF"
             op_data["scaleVnfData"] = {}
-            if scale_in:
+            if scale_in and not scale_out:
                 op_data["scaleVnfData"]["scaleVnfType"] = "SCALE_IN"
                 op_data["scaleVnfData"]["scaleVnfType"] = "SCALE_IN"
-            else:
+            elif not scale_in and scale_out:
                 op_data["scaleVnfData"]["scaleVnfType"] = "SCALE_OUT"
                 op_data["scaleVnfData"]["scaleVnfType"] = "SCALE_OUT"
+            else:
+                raise ClientException("you must set either 'scale_in' or 'scale_out'")
             op_data["scaleVnfData"]["scaleByStepData"] = {
                 "member-vnf-index": vnf_name,
                 "scaling-group-descriptor": scaling_group,
             }
             op_data["scaleVnfData"]["scaleByStepData"] = {
                 "member-vnf-index": vnf_name,
                 "scaling-group-descriptor": scaling_group,
             }
-            op_id = self.exec_op(ns_name, op_name='scale', op_data=op_data, wait=wait)
+            if timeout:
+                op_data["timeout_ns_scale"] = timeout
+            op_id = self.exec_op(ns_name, op_name="scale", op_data=op_data, wait=wait)
             print(str(op_id))
         except ClientException as exc:
             print(str(op_id))
         except ClientException as exc:
-            message="failed to scale vnf {} of ns {}:\nerror:\n{}".format(
-                    vnf_name, ns_name, str(exc))
+            message = "failed to scale vnf {} of ns {}:\nerror:\n{}".format(
+                vnf_name, ns_name, str(exc)
+            )
+            raise ClientException(message)
+
+    def update(self, ns_name, data, wait=False):
+        """Update NS instance.
+
+        This function calls the NBI in order to perform an update operation
+        on a Network Service instance.
+
+        Args:
+            ns_name: (str)
+            data: (dict)
+            wait: (boolean)
+
+        Returns:
+            None
+
+        """
+        self._logger.debug("")
+        self._client.get_token()
+        try:
+            op_data = {"updateType": data.pop("updateType")}
+
+            # Check update parameters availability according to update type
+            if op_data["updateType"] == "CHANGE_VNFPKG":
+                if not (
+                    data["config"]["changeVnfPackageData"][0].get("vnfInstanceId")
+                    and data["config"]["changeVnfPackageData"][0].get("vnfdId")
+                ):
+                    raise ClientException("you must set both vnfInstanceId and vnfdId")
+
+            # Fill up op_data
+            op_data["changeVnfPackageData"] = {}
+            op_data["changeVnfPackageData"]["vnfInstanceId"] = data["config"][
+                "changeVnfPackageData"
+            ][0].get("vnfInstanceId")
+
+            op_data["changeVnfPackageData"]["vnfdId"] = data["config"][
+                "changeVnfPackageData"
+            ][0].get("vnfdId")
+
+            if data.get("timeout"):
+                op_data["timeout_ns_update"] = data["timeout"]
+
+            op_id = self.exec_op(ns_name, op_name="update", op_data=op_data, wait=wait)
+            print(str(op_id))
+
+        except ClientException as exc:
+            message = "failed to update ns {}:\nerror:\n{}".format(ns_name, str(exc))
             raise ClientException(message)
 
     def create_alarm(self, alarm):
             raise ClientException(message)
 
     def create_alarm(self, alarm):
@@ -397,15 +729,16 @@ class Ns(object):
         data = {}
         data["create_alarm_request"] = {}
         data["create_alarm_request"]["alarm_create_request"] = alarm
         data = {}
         data["create_alarm_request"] = {}
         data["create_alarm_request"]["alarm_create_request"] = alarm
-        #try:
-        http_code, resp = self._http.post_cmd(endpoint='/test/message/alarm_request',
-                                   postfields_dict=data)
-            #print('HTTP CODE: {}'.format(http_code))
-            #print('RESP: {}'.format(resp))
-            #if http_code in (200, 201, 202, 204):
-            #resp = json.loads(resp)
-        print('Alarm created')
-            #else:
+        try:
+            http_code, resp = self._http.post_cmd(
+                endpoint="/test/message/alarm_request", postfields_dict=data
+            )
+            # print('HTTP CODE: {}'.format(http_code))
+            # print('RESP: {}'.format(resp))
+            # if http_code in (200, 201, 202, 204):
+            #     resp = json.loads(resp)
+            print("Alarm created")
+            # else:
             #    msg = ""
             #    if resp:
             #        try:
             #    msg = ""
             #    if resp:
             #        try:
@@ -414,11 +747,9 @@ class Ns(object):
             #            msg = resp
             #    raise ClientException('error: code: {}, resp: {}'.format(
             #                          http_code, msg))
             #            msg = resp
             #    raise ClientException('error: code: {}, resp: {}'.format(
             #                          http_code, msg))
-        #except ClientException as exc:
-        #    message="failed to create alarm: alarm {}\n{}".format(
-        #            alarm,
-        #            str(exc))
-        #    raise ClientException(message)
+        except ClientException as exc:
+            message = "failed to create alarm: alarm {}\n{}".format(alarm, str(exc))
+            raise ClientException(message)
 
     def delete_alarm(self, name):
         self._logger.debug("")
 
     def delete_alarm(self, name):
         self._logger.debug("")
@@ -427,15 +758,16 @@ class Ns(object):
         data["delete_alarm_request"] = {}
         data["delete_alarm_request"]["alarm_delete_request"] = {}
         data["delete_alarm_request"]["alarm_delete_request"]["alarm_uuid"] = name
         data["delete_alarm_request"] = {}
         data["delete_alarm_request"]["alarm_delete_request"] = {}
         data["delete_alarm_request"]["alarm_delete_request"]["alarm_uuid"] = name
-        #try:
-        http_code, resp = self._http.post_cmd(endpoint='/test/message/alarm_request',
-                                   postfields_dict=data)
-            #print('HTTP CODE: {}'.format(http_code))
-            #print('RESP: {}'.format(resp))
-            #if http_code in (200, 201, 202, 204):
-            #resp = json.loads(resp)
-        print('Alarm deleted')
-            #else:
+        try:
+            http_code, resp = self._http.post_cmd(
+                endpoint="/test/message/alarm_request", postfields_dict=data
+            )
+            # print('HTTP CODE: {}'.format(http_code))
+            # print('RESP: {}'.format(resp))
+            # if http_code in (200, 201, 202, 204):
+            #    resp = json.loads(resp)
+            print("Alarm deleted")
+            # else:
             #    msg = ""
             #    if resp:
             #        try:
             #    msg = ""
             #    if resp:
             #        try:
@@ -444,26 +776,78 @@ class Ns(object):
             #            msg = resp
             #    raise ClientException('error: code: {}, resp: {}'.format(
             #                          http_code, msg))
             #            msg = resp
             #    raise ClientException('error: code: {}, resp: {}'.format(
             #                          http_code, msg))
-        #except ClientException as exc:
-        #    message="failed to delete alarm: alarm {}\n{}".format(
-        #            name,
-        #            str(exc))
-        #    raise ClientException(message)
+        except ClientException as exc:
+            message = "failed to delete alarm: alarm {}\n{}".format(name, str(exc))
+            raise ClientException(message)
+
+    def get_alarm(self, project_name=None, ns_id=None, uuid=None):
+        self._client.get_token()
+        try:
+            self._apiName = "/nsfm"
+            self._apiResource = "/alarms"
+            self._apiBase = "{}{}{}".format(
+                self._apiName, self._apiVersion, self._apiResource
+            )
+            if uuid:
+                # if request is for any uuid
+                http_code, resp = self._http.get2_cmd(
+                    "{}/{}".format(self._apiBase, uuid)
+                )
+            if not uuid:
+                http_code, resp = self._http.get2_cmd(
+                    "{}/{}/{}/{}".format(self._apiBase, uuid, project_name, ns_id)
+                )
+            if http_code == 200:
+                if resp:
+                    resp = json.loads(resp)
+                    return resp
+                else:
+                    raise ClientException("unexpected response from server")
+            else:
+                msg = resp or ""
+                raise ClientException(msg)
+        except ClientException as exc:
+            message = "failed to get alarm :\nerror:\n{}".format(str(exc))
+            raise ClientException(message)
+
+    def update_alarm(self, uuid, threshold=None, is_enable=None, wait=None):
+        self._client.get_token()
+        try:
+            op_data = {}
+            op_data["uuid"] = uuid
+            op_data["threshold"] = threshold
+            op_data["is_enable"] = is_enable
+            self._apiName = "/nsfm"
+            self._apiResource = "/alarms"
+            self._apiBase = "{}{}{}".format(
+                self._apiName, self._apiVersion, self._apiResource
+            )
+            http_code, resp = self._http.patch_cmd(
+                endpoint="{}".format(self._apiBase), postfields_dict=op_data
+            )
+            if resp:
+                resp = json.loads(resp)
+            print(resp)
+            return resp
+        except ClientException as exc:
+            message = "failed to update alarm :\nerror:\n{}".format(str(exc))
+            raise ClientException(message)
 
     def export_metric(self, metric):
         self._logger.debug("")
         self._client.get_token()
         data = {}
         data["read_metric_data_request"] = metric
 
     def export_metric(self, metric):
         self._logger.debug("")
         self._client.get_token()
         data = {}
         data["read_metric_data_request"] = metric
-        #try:
-        http_code, resp = self._http.post_cmd(endpoint='/test/message/metric_request',
-                                   postfields_dict=data)
-            #print('HTTP CODE: {}'.format(http_code))
-            #print('RESP: {}'.format(resp))
-            #if http_code in (200, 201, 202, 204):
-            #resp = json.loads(resp)
-        return 'Metric exported'
-            #else:
+        try:
+            http_code, resp = self._http.post_cmd(
+                endpoint="/test/message/metric_request", postfields_dict=data
+            )
+            # print('HTTP CODE: {}'.format(http_code))
+            # print('RESP: {}'.format(resp))
+            # if http_code in (200, 201, 202, 204):
+            #    resp = json.loads(resp)
+            return "Metric exported"
+            # else:
             #    msg = ""
             #    if resp:
             #        try:
             #    msg = ""
             #    if resp:
             #        try:
@@ -472,11 +856,9 @@ class Ns(object):
             #            msg = resp
             #    raise ClientException('error: code: {}, resp: {}'.format(
             #                          http_code, msg))
             #            msg = resp
             #    raise ClientException('error: code: {}, resp: {}'.format(
             #                          http_code, msg))
-        #except ClientException as exc:
-        #    message="failed to export metric: metric {}\n{}".format(
-        #            metric,
-        #            str(exc))
-        #    raise ClientException(message)
+        except ClientException as exc:
+            message = "failed to export metric: metric {}\n{}".format(metric, str(exc))
+            raise ClientException(message)
 
     def get_field(self, ns_name, field):
         self._logger.debug("")
 
     def get_field(self, ns_name, field):
         self._logger.debug("")
@@ -490,3 +872,22 @@ class Ns(object):
 
         raise NotFound("failed to find {} in ns {}".format(field, ns_name))
 
 
         raise NotFound("failed to find {} in ns {}".format(field, ns_name))
 
+    def heal(
+        self,
+        ns_name,
+        heal_dict,
+        wait=False,
+        timeout=None,
+    ):
+        """Heals a NS"""
+        self._logger.debug("")
+        self._client.get_token()
+        try:
+            op_data = heal_dict
+            if timeout:
+                op_data["timeout_ns_heal"] = timeout
+            op_id = self.exec_op(ns_name, op_name="heal", op_data=op_data, wait=wait)
+            print(str(op_id))
+        except ClientException as exc:
+            message = "failed to heal ns {}:\nerror:\n{}".format(ns_name, str(exc))
+            raise ClientException(message)