Feature 10920: Monitoring of NFVI-leve VNF metrics form Prometheus TSDB
[osm/osmclient.git] / osmclient / scripts / osm.py
index feb8c2f..3ce02df 100755 (executable)
@@ -94,6 +94,26 @@ def get_vim_name(vim_list, vim_id):
     return vim_name
 
 
+def create_config(config_file, json_string):
+    '''
+    Combines a YAML or JSON file with a JSON string into a Python3 structure
+    It loads the YAML or JSON file 'cfile' into a first dictionary.
+    It loads the JSON string into a second dictionary.
+    Then it updates the first dictionary with the info in the second dictionary.
+    If the field is present in both cfile and cdict, the field in cdict prevails.
+    If both cfile and cdict are None, it returns an empty dict (i.e. {})
+    '''
+    config = {}
+    if config_file:
+        with open(config_file, "r") as cf:
+            config = yaml.safe_load(cf.read())
+    if json_string:
+        cdict = yaml.safe_load(json_string)
+        for k, v in cdict.items():
+            config[k] = v
+    return config
+
+
 @click.group(
     context_settings=dict(help_option_names=["-h", "--help"], max_content_width=160)
 )
@@ -2330,6 +2350,7 @@ def nfpkg_create(
     help="do not return the control immediately, but keep it "
     "until the operation is completed, or timeout",
 )
+@click.option("--timeout", default=None, help="ns deployment timeout")
 @click.pass_context
 def ns_create(
     ctx,
@@ -2341,6 +2362,7 @@ def ns_create(
     config,
     config_file,
     wait,
+    timeout
 ):
     """creates a new NS instance"""
     logger.debug("")
@@ -2360,6 +2382,7 @@ def ns_create(
         ssh_keys=ssh_keys,
         account=vim_account,
         wait=wait,
+        timeout=timeout,
     )
     # except ClientException as e:
     #     print(str(e))
@@ -2564,13 +2587,15 @@ def nsi_create2(
 @click.option("--description", help="human readable description")
 @click.option(
     "--vim_account",
-    help="list of VIM accounts (in the same VIM) that can reach this PDU",
+    help="list of VIM accounts (in the same VIM) that can reach this PDU\n"
+    + "The format for multiple VIMs is --vim_account <vim_account_id_1> "
+    + "--vim_account <vim_account_id_2> ... --vim_account <vim_account_id_N>",
     multiple=True,
 )
 @click.option(
     "--descriptor_file",
     default=None,
-    help="PDU descriptor file (as an alternative to using the other arguments",
+    help="PDU descriptor file (as an alternative to using the other arguments)",
 )
 @click.pass_context
 def pdu_create(
@@ -2578,26 +2603,90 @@ def pdu_create(
 ):
     """creates a new Physical Deployment Unit (PDU)"""
     logger.debug("")
-    # try:
+
+    check_client_version(ctx.obj, ctx.command.name)
+
+    pdu = create_pdu_dictionary(
+        name, pdu_type, interface, description, vim_account, descriptor_file
+    )
+    ctx.obj.pdu.create(pdu)
+
+
+########################
+# UPDATE PDU operation #
+########################
+
+
+@cli_osm.command(
+    name="pdu-update", short_help="updates a Physical Deployment Unit to the catalog"
+)
+@click.argument("name")
+@click.option("--newname", help="New name for the Physical Deployment Unit")
+@click.option("--pdu_type", help="type of PDU (e.g. router, firewall, FW001)")
+@click.option(
+    "--interface",
+    help="interface(s) of the PDU: name=<NAME>,mgmt=<true|false>,ip-address=<IP_ADDRESS>"
+    + "[,type=<overlay|underlay>][,mac-address=<MAC_ADDRESS>][,vim-network-name=<VIM_NET_NAME>]",
+    multiple=True,
+)
+@click.option("--description", help="human readable description")
+@click.option(
+    "--vim_account",
+    help="list of VIM accounts (in the same VIM) that can reach this PDU\n"
+    + "The format for multiple VIMs is --vim_account <vim_account_id_1> "
+    + "--vim_account <vim_account_id_2> ... --vim_account <vim_account_id_N>",
+    multiple=True,
+)
+@click.option(
+    "--descriptor_file",
+    default=None,
+    help="PDU descriptor file (as an alternative to using the other arguments)",
+)
+@click.pass_context
+def pdu_update(
+    ctx, name, newname, pdu_type, interface, description, vim_account, descriptor_file
+):
+    """Updates a new Physical Deployment Unit (PDU)"""
+    logger.debug("")
+
     check_client_version(ctx.obj, ctx.command.name)
+
+    update = True
+
+    if not newname:
+        newname = name
+
+    pdu = create_pdu_dictionary(
+        newname, pdu_type, interface, description, vim_account, descriptor_file, update
+    )
+    ctx.obj.pdu.update(name, pdu)
+
+
+def create_pdu_dictionary(
+    name, pdu_type, interface, description, vim_account, descriptor_file, update=False
+):
+
+    logger.debug("")
     pdu = {}
+
     if not descriptor_file:
-        if not name:
-            raise ClientException(
-                'in absence of descriptor file, option "--name" is mandatory'
-            )
-        if not pdu_type:
-            raise ClientException(
-                'in absence of descriptor file, option "--pdu_type" is mandatory'
-            )
-        if not interface:
-            raise ClientException(
-                'in absence of descriptor file, option "--interface" is mandatory (at least once)'
-            )
-        if not vim_account:
-            raise ClientException(
-                'in absence of descriptor file, option "--vim_account" is mandatory (at least once)'
-            )
+        if not update:
+            if not name:
+                raise ClientException(
+                    'in absence of descriptor file, option "--name" is mandatory'
+                )
+            if not pdu_type:
+                raise ClientException(
+                    'in absence of descriptor file, option "--pdu_type" is mandatory'
+                )
+            if not interface:
+                raise ClientException(
+                    'in absence of descriptor file, option "--interface" is mandatory (at least once)'
+                )
+            if not vim_account:
+                raise ClientException(
+                    'in absence of descriptor file, option "--vim_account" is mandatory (at least once)'
+                )
     else:
         with open(descriptor_file, "r") as df:
             pdu = yaml.safe_load(df.read())
@@ -2616,10 +2705,7 @@ def pdu_create(
             new_iface["mgmt"] = new_iface.get("mgmt", "false").lower() == "true"
             ifaces_list.append(new_iface)
         pdu["interfaces"] = ifaces_list
-    ctx.obj.pdu.create(pdu)
-    # except ClientException as e:
-    #     print(str(e))
-    #     exit(1)
+    return pdu
 
 
 ####################
@@ -3039,18 +3125,13 @@ def pdu_delete(ctx, name, force):
 
 
 @cli_osm.command(name="vim-create", short_help="creates a new VIM account")
-@click.option("--name", prompt=True, help="Name to create datacenter")
-@click.option("--user", prompt=True, help="VIM username")
-@click.option(
-    "--password",
-    prompt=True,
-    hide_input=True,
-    confirmation_prompt=True,
-    help="VIM password",
-)
-@click.option("--auth_url", prompt=True, help="VIM url")
-@click.option("--tenant", prompt=True, help="VIM tenant name")
+@click.option("--name", required=True, help="Name to create datacenter")
+@click.option("--user", default=None, help="VIM username")
+@click.option("--password", default=None, help="VIM password")
+@click.option("--auth_url", default=None, help="VIM url")
+@click.option("--tenant", "--project", "tenant", default=None, help="VIM tenant/project name")
 @click.option("--config", default=None, help="VIM specific config parameters")
+@click.option("--config_file", default=None, help="VIM specific config parameters in YAML or JSON file")
 @click.option("--account_type", default="openstack", help="VIM type")
 @click.option("--description", default=None, help="human readable description")
 @click.option(
@@ -3072,6 +3153,8 @@ def pdu_delete(ctx, name, force):
     "until the operation is completed, or timeout",
 )
 @click.option("--vca", default=None, help="VCA to be used in this VIM account")
+@click.option("--creds", default=None, help="credentials file (only applycable for GCP VIM type)")
+@click.option("--prometheus_config_file", default=None, help="Prometheus configuration to get VIM data")
 @click.pass_context
 def vim_create(
     ctx,
@@ -3081,12 +3164,15 @@ def vim_create(
     auth_url,
     tenant,
     config,
+    config_file,
     account_type,
     description,
     sdn_controller,
     sdn_port_mapping,
     wait,
     vca,
+    creds,
+    prometheus_config_file
 ):
     """creates a new VIM account"""
     logger.debug("")
@@ -3096,19 +3182,24 @@ def vim_create(
     if sdn_port_mapping:
         check_client_version(ctx.obj, "--sdn_port_mapping")
     vim = {}
+    if prometheus_config_file:
+        with open(prometheus_config_file) as prometheus_file:
+            prometheus_config_dict = json.load(prometheus_file)
+        vim["prometheus-config"] = prometheus_config_dict
+
     vim["vim-username"] = user
     vim["vim-password"] = password
     vim["vim-url"] = auth_url
     vim["vim-tenant-name"] = tenant
     vim["vim-type"] = account_type
     vim["description"] = description
-    vim["config"] = config
     if vca:
         vim["vca"] = vca
-    if sdn_controller or sdn_port_mapping:
-        ctx.obj.vim.create(name, vim, sdn_controller, sdn_port_mapping, wait=wait)
-    else:
-        ctx.obj.vim.create(name, vim, wait=wait)
+    vim_config = create_config(config_file, config)
+    if creds:
+        with open(creds, "r") as cf:
+            vim_config["credentials"] = yaml.safe_load(cf.read())
+    ctx.obj.vim.create(name, vim, vim_config, sdn_controller, sdn_port_mapping, wait=wait)
     # except ClientException as e:
     #     print(str(e))
     #     exit(1)
@@ -3122,6 +3213,7 @@ def vim_create(
 @click.option("--auth_url", help="VIM url")
 @click.option("--tenant", help="VIM tenant name")
 @click.option("--config", help="VIM specific config parameters")
+@click.option("--config_file", default=None, help="VIM specific config parameters in YAML or JSON file")
 @click.option("--account_type", help="VIM type")
 @click.option("--description", help="human readable description")
 @click.option(
@@ -3143,6 +3235,8 @@ def vim_create(
     help="do not return the control immediately, but keep it "
     "until the operation is completed, or timeout",
 )
+@click.option("--creds", default=None, help="credentials file (only applycable for GCP VIM type)")
+@click.option("--prometheus_config_file", default=None, help="Prometheus configuration to get VIM data")
 @click.pass_context
 def vim_update(
     ctx,
@@ -3153,11 +3247,14 @@ def vim_update(
     auth_url,
     tenant,
     config,
+    config_file,
     account_type,
     description,
     sdn_controller,
     sdn_port_mapping,
     wait,
+    creds,
+    prometheus_config_file
 ):
     """updates a VIM account
 
@@ -3181,9 +3278,18 @@ def vim_update(
         vim["vim_type"] = account_type
     if description:
         vim["description"] = description
-    if config:
-        vim["config"] = config
-    ctx.obj.vim.update(name, vim, sdn_controller, sdn_port_mapping, wait=wait)
+    vim_config = None
+    if config or config_file:
+        vim_config = create_config(config_file, config)
+    if creds:
+        with open(creds, "r") as cf:
+            vim_config["credentials"] = yaml.safe_load(cf.read())
+    if prometheus_config_file:
+        with open(prometheus_config_file) as prometheus_file:
+            prometheus_config_dict = json.load(prometheus_file)
+        vim["prometheus-config"] = prometheus_config_dict
+    logger.info(f"VIM: {vim}, VIM config: {vim_config}")
+    ctx.obj.vim.update(name, vim, vim_config, sdn_controller, sdn_port_mapping, wait=wait)
     # except ClientException as e:
     #     print(str(e))
     #     exit(1)
@@ -3260,6 +3366,8 @@ def vim_list(ctx, filter, long):
         if long:
             if "vim_password" in vim:
                 vim["vim_password"] = "********"
+            if "config" in vim and "credentials" in vim["config"]:
+                vim["config"]["credentials"] = "********"
             logger.debug("VIM details: {}".format(yaml.safe_dump(vim)))
             vim_state = vim["_admin"].get("operationalState", "-")
             error_details = "N/A"
@@ -3292,8 +3400,9 @@ def vim_list(ctx, filter, long):
     multiple=True,
     help="restricts the information to the fields in the filter",
 )
+@click.option("--literal", is_flag=True, help="print literally, no pretty table")
 @click.pass_context
-def vim_show(ctx, name, filter):
+def vim_show(ctx, name, filter, literal):
     """shows the details of a VIM account
 
     NAME: name or ID of the VIM account
@@ -3303,10 +3412,15 @@ def vim_show(ctx, name, filter):
     resp = ctx.obj.vim.get(name)
     if "vim_password" in resp:
         resp["vim_password"] = "********"
+    if "config" in resp and "credentials" in resp["config"]:
+        resp["config"]["credentials"] = "********"
     # except ClientException as e:
     #     print(str(e))
     #     exit(1)
 
+    if literal:
+        print(yaml.safe_dump(resp, indent=4, default_flow_style=False))
+        return
     table = PrettyTable(["key", "attribute"])
     for k, v in list(resp.items()):
         if not filter or k in filter:
@@ -3770,6 +3884,14 @@ def sdnc_show(ctx, name):
     default="kube-system",
     help="namespace to be used for its operation, defaults to `kube-system`",
 )
+@click.option(
+    "--wait",
+    required=False,
+    default=False,
+    is_flag=True,
+    help="do not return the control immediately, but keep it "
+    "until the operation is completed, or timeout",
+)
 @click.option(
     "--cni",
     default=None,
@@ -3783,7 +3905,7 @@ def sdnc_show(ctx, name):
 #              help='do not return the control immediately, but keep it until the operation is completed, or timeout')
 @click.pass_context
 def k8scluster_add(
-    ctx, name, creds, version, vim, k8s_nets, description, namespace, cni
+    ctx, name, creds, version, vim, k8s_nets, description, namespace, wait, cni
 ):
     """adds a K8s cluster to OSM
 
@@ -3804,7 +3926,7 @@ def k8scluster_add(
         cluster["namespace"] = namespace
     if cni:
         cluster["cni"] = yaml.safe_load(cni)
-    ctx.obj.k8scluster.create(name, cluster)
+    ctx.obj.k8scluster.create(name, cluster, wait)
     # except ClientException as e:
     #     print(str(e))
     #     exit(1)
@@ -3826,12 +3948,20 @@ def k8scluster_add(
     "--namespace",
     help="namespace to be used for its operation, defaults to `kube-system`",
 )
+@click.option(
+    "--wait",
+    required=False,
+    default=False,
+    is_flag=True,
+    help="do not return the control immediately, but keep it "
+    "until the operation is completed, or timeout",
+)
 @click.option(
     "--cni", help="list of CNI plugins, in JSON inline format, used in the cluster"
 )
 @click.pass_context
 def k8scluster_update(
-    ctx, name, newname, creds, version, vim, k8s_nets, description, namespace, cni
+    ctx, name, newname, creds, version, vim, k8s_nets, description, namespace, wait, cni
 ):
     """updates a K8s cluster
 
@@ -3857,7 +3987,7 @@ def k8scluster_update(
         cluster["namespace"] = namespace
     if cni:
         cluster["cni"] = yaml.safe_load(cni)
-    ctx.obj.k8scluster.update(name, cluster)
+    ctx.obj.k8scluster.update(name, cluster, wait)
     # except ClientException as e:
     #     print(str(e))
     #     exit(1)
@@ -3868,18 +3998,23 @@ def k8scluster_update(
 @click.option(
     "--force", is_flag=True, help="forces the deletion from the DB (not recommended)"
 )
-# @click.option('--wait',
-#              is_flag=True,
-#              help='do not return the control immediately, but keep it until the operation is completed, or timeout')
+@click.option(
+    "--wait",
+    required=False,
+    default=False,
+    is_flag=True,
+    help="do not return the control immediately, but keep it "
+    "until the operation is completed, or timeout",
+)
 @click.pass_context
-def k8scluster_delete(ctx, name, force):
+def k8scluster_delete(ctx, name, force, wait):
     """deletes a K8s cluster
 
     NAME: name or ID of the K8s cluster to be deleted
     """
     # try:
     check_client_version(ctx.obj, ctx.command.name)
-    ctx.obj.k8scluster.delete(name, force=force)
+    ctx.obj.k8scluster.delete(name, force, wait)
     # except ClientException as e:
     #     print(str(e))
     #     exit(1)
@@ -4722,6 +4857,16 @@ def user_create(ctx, username, password, projects, project_role_mappings, domain
     multiple=True,
     help="remove role(s) in a project. Can be used several times: 'project,role1[,role2,...]'",
 )
+@click.option(
+    "--change_password",
+    "change_password",
+    help="user's current password"
+)
+@click.option(
+    "--new_password",
+    "new_password",
+    help="user's new password to update in expiry condition"
+)
 @click.pass_context
 def user_update(
     ctx,
@@ -4732,6 +4877,8 @@ def user_update(
     remove_project,
     add_project_role,
     remove_project_role,
+    change_password,
+    new_password,
 ):
     """Update a user information
 
@@ -4743,6 +4890,8 @@ def user_update(
     REMOVE_PROJECT: deleting mappings for project/role(s)
     ADD_PROJECT_ROLE: adding mappings for project/role(s)
     REMOVE_PROJECT_ROLE: removing mappings for project/role(s)
+    CHANGE_PASSWORD: user's current password to change
+    NEW_PASSWORD: user's new password to update in expiry condition
     """
     logger.debug("")
     user = {}
@@ -4752,10 +4901,15 @@ def user_update(
     user["remove-project"] = remove_project
     user["add-project-role"] = add_project_role
     user["remove-project-role"] = remove_project_role
+    user["change_password"] = change_password
+    user["new_password"] = new_password
 
     # try:
     check_client_version(ctx.obj, ctx.command.name)
-    ctx.obj.user.update(username, user)
+    if not user.get("change_password"):
+        ctx.obj.user.update(username, user)
+    else:
+        ctx.obj.user.update(username, user, pwd_change=True)
     # except ClientException as e:
     #     print(str(e))
     #     exit(1)
@@ -5410,6 +5564,53 @@ def vnf_scale(
     #     exit(1)
 
 
+@cli_osm.command(
+    name="ns-update", short_help="executes an update of a Network Service."
+)
+@click.argument("ns_name")
+@click.option(
+    "--updatetype", required=True, type=str, help="available types: CHANGE_VNFPKG"
+)
+@click.option(
+    "--config",
+    required=True,
+    type=str,
+    help="extra information for update operation as YAML/JSON inline string as --config"
+    " '{changeVnfPackageData:[{vnfInstanceId: xxx, vnfdId: yyy}]}'",
+)
+@click.option(
+    "--timeout", required=False, default=None, type=int, help="timeout in seconds"
+)
+@click.option(
+    "--wait",
+    required=False,
+    default=False,
+    is_flag=True,
+    help="do not return the control immediately, but keep it until the operation is completed, or timeout",
+)
+@click.pass_context
+def update(ctx, ns_name, updatetype, config, timeout, wait):
+    """Executes an update of a Network Service.
+
+    The update will check new revisions of the Network Functions that are part of the
+    Network Service, and it will update them if needed.
+    Sample update command: osm ns-update  ns_instance_id --updatetype CHANGE_VNFPKG
+    --config '{changeVnfPackageData: [{vnfInstanceId: id_x,vnfdId: id_y}]}' --timeout 300 --wait
+
+    NS_NAME: Network service instance name or ID.
+
+    """
+    op_data = {
+        "timeout": timeout,
+        "updateType": updatetype,
+    }
+    if config:
+        op_data["config"] = yaml.safe_load(config)
+
+    check_client_version(ctx.obj, ctx.command.name)
+    ctx.obj.ns.update(ns_name, op_data, wait=wait)
+
+
 @cli_osm.command(name="alarm-show", short_help="show alarm details")
 @click.argument("uuid")
 @click.pass_context
@@ -5695,6 +5896,12 @@ def role_show(ctx, name):
 @click.option(
     "--netslice-vlds", default=1, help="(NST) Number of netslice vlds. Default 1"
 )
+@click.option(
+    "--old",
+    default=False,
+    is_flag=True,
+    help="Flag to create a descriptor using the previous OSM format (pre SOL006, OSM<9)",
+)
 @click.pass_context
 def package_create(
     ctx,
@@ -5712,6 +5919,7 @@ def package_create(
     detailed,
     netslice_subnets,
     netslice_vlds,
+    old,
 ):
     """
     Creates an OSM NS, VNF, NST package
@@ -5744,6 +5952,7 @@ def package_create(
         detailed=detailed,
         netslice_subnets=netslice_subnets,
         netslice_vlds=netslice_vlds,
+        old=old,
     )
     print(resp)
     # except ClientException as inst: