Feature 10909: Heal operation for VDU
[osm/osmclient.git] / osmclient / scripts / osm.py
index dd89b9d..a196139 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))
@@ -2583,7 +2606,9 @@ def pdu_create(
 
     check_client_version(ctx.obj, ctx.command.name)
 
-    pdu = create_pdu_dictionary(name, pdu_type, interface, description, vim_account, descriptor_file)
+    pdu = create_pdu_dictionary(
+        name, pdu_type, interface, description, vim_account, descriptor_file
+    )
     ctx.obj.pdu.create(pdu)
 
 
@@ -2631,11 +2656,15 @@ def pdu_update(
     if not newname:
         newname = name
 
-    pdu = create_pdu_dictionary(newname, pdu_type, interface, description, vim_account, descriptor_file, update)
+    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):
+def create_pdu_dictionary(
+    name, pdu_type, interface, description, vim_account, descriptor_file, update=False
+):
 
     logger.debug("")
     pdu = {}
@@ -2678,6 +2707,7 @@ def create_pdu_dictionary(name, pdu_type, interface, description, vim_account, d
         pdu["interfaces"] = ifaces_list
     return pdu
 
+
 ####################
 # UPDATE operations
 ####################
@@ -3095,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(
@@ -3128,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,
@@ -3137,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("")
@@ -3152,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)
@@ -3178,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(
@@ -3199,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,
@@ -3209,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
 
@@ -3237,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)
@@ -3316,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"
@@ -3348,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
@@ -3359,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:
@@ -3820,6 +3878,24 @@ def sdnc_show(ctx, name):
     help='''list of VIM networks, in JSON inline format, where the cluster is
     accessible via L3 routing, e.g. "{(k8s_net1:vim_network1) [,(k8s_net2:vim_network2) ...]}"''',
 )
+@click.option(
+    "--init-helm2/--skip-helm2",
+    required=False,
+    default=True,
+    help="Initialize helm v2",
+)
+@click.option(
+    "--init-helm3/--skip-helm3",
+    required=False,
+    default=True,
+    help="Initialize helm v3",
+)
+@click.option(
+    "--init-jujubundle/--skip-jujubundle",
+    required=False,
+    default=True,
+    help="Initialize juju-bundle",
+)
 @click.option("--description", default=None, help="human readable description")
 @click.option(
     "--namespace",
@@ -3847,7 +3923,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, wait, cni
+    ctx, name, creds, version, vim, k8s_nets, init_helm2, init_helm3, init_jujubundle, description, namespace, wait, cni
 ):
     """adds a K8s cluster to OSM
 
@@ -3862,6 +3938,10 @@ def k8scluster_add(
     cluster["k8s_version"] = version
     cluster["vim_account"] = vim
     cluster["nets"] = yaml.safe_load(k8s_nets)
+    if not (init_helm2 and init_jujubundle and init_helm3):
+        cluster["deployment_methods"] = {"helm-chart": init_helm2,
+                                         "juju-bundle": init_jujubundle,
+                                         "helm-chart-v3": init_helm3}
     if description:
         cluster["description"] = description
     if namespace:
@@ -3991,6 +4071,7 @@ def k8scluster_list(ctx, filter, literal, long):
                 "Version",
                 "VIM",
                 "K8s-nets",
+                "Deployment methods",
                 "Operational State",
                 "Op. state (details)",
                 "Description",
@@ -4028,6 +4109,7 @@ def k8scluster_list(ctx, filter, literal, long):
                     cluster["k8s_version"],
                     vim_info,
                     json.dumps(cluster["nets"]),
+                    json.dumps(cluster["deployment_methods"]),
                     cluster["_admin"]["operationalState"],
                     op_state_details,
                     trunc_text(cluster.get("description") or "", 40),
@@ -4799,6 +4881,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,
@@ -4809,6 +4901,8 @@ def user_update(
     remove_project,
     add_project_role,
     remove_project_role,
+    change_password,
+    new_password,
 ):
     """Update a user information
 
@@ -4820,6 +4914,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 = {}
@@ -4829,10 +4925,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)
@@ -5487,6 +5588,271 @@ 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)
+
+
+def process_common_heal_params(ctx, param, value):
+    logger.debug("")
+    if not value:
+        return
+    logger.debug(f"Param name: {param.name}")
+    if not ctx.params.get("heal_params", {}).get("healVnfData"):
+        raise ClientException(f"Expected option --vnf before {param.name}")
+    if param.name == "cause":
+        param_dict = ctx.params["heal_params"]["healVnfData"][-1]["cause"] = value
+        return
+    # If not "vnf" and not "cause", then the param lies on "additionalParams"
+    if not ctx.params["heal_params"]["healVnfData"][-1].get("additionalParams"):
+        ctx.params["heal_params"]["healVnfData"][-1]["additionalParams"] = {}
+    if param.name == "vdu":
+        # Check VDU id ?
+        if not ctx.params["heal_params"]["healVnfData"][-1]["additionalParams"].get(
+            "vdu"
+        ):
+            ctx.params["heal_params"]["healVnfData"][-1]["additionalParams"][
+                "vdu"
+            ] = []
+        vdu = {"vdu-id": value}
+        ctx.params["heal_params"]["healVnfData"][-1]["additionalParams"][
+            "vdu"
+        ].append(vdu)
+        ctx.params["heal_params"]["current_item"] = "vdu"
+    else:
+        current_item = ctx.params["heal_params"]["current_item"]
+        if current_item == "vnf":
+            param_dict = ctx.params["heal_params"]["healVnfData"][-1][
+                "additionalParams"
+            ]
+        else:
+            # if current_item == "vdu":
+            param_dict = ctx.params["heal_params"]["healVnfData"][-1][
+                "additionalParams"
+            ]["vdu"][-1]
+        if param.name == "count_index":
+            param_name = "count-index"
+        elif param.name == "run_day1":
+            param_name = "run-day1"
+        else:
+            param_name = param.name
+        param_dict[param_name] = value
+    return
+
+
+def process_ns_heal_params(ctx, param, value):
+    logger.debug("")
+    if not ctx.params.get("heal_params"):
+        ctx.params["heal_params"] = {}
+        ctx.params["heal_params"]["healVnfData"] = []
+    if param.name == "vnf" and value:
+        # Check VNF id ?
+        logger.debug(f"Param name: {param.name}")
+        vnf = {"vnfInstanceId": value}
+        ctx.params["heal_params"]["healVnfData"].append(vnf)
+        ctx.params["heal_params"]["current_item"] = "vnf"
+    else:
+        process_common_heal_params(ctx, param, value)
+
+
+def process_vnf_heal_params(ctx, param, value):
+    logger.debug("")
+    if not ctx.params.get("heal_params"):
+        ctx.params["heal_params"] = {}
+        ctx.params["heal_params"]["healVnfData"] = []
+        vnf = {"vnfInstanceId": "id_to_be_substituted"}
+        ctx.params["heal_params"]["healVnfData"].append(vnf)
+        ctx.params["heal_params"]["current_item"] = "vnf"
+    else:
+        process_common_heal_params(ctx, param, value)
+
+
+@click.command(
+    name="ns-heal", short_help="heals (recreates) VNFs or VDUs of a NS instance"
+)
+@click.argument("ns_name")
+@click.option(
+    "--vnf",
+    required=True,
+    default=None,
+    callback=process_ns_heal_params,
+    help="vnf-id if the target is a vnf instead of a ns)",
+)
+@click.option(
+    "--cause",
+    default=None,
+    callback=process_ns_heal_params,
+    help="human readable cause of the healing",
+)
+@click.option(
+    "--run-day1",
+    is_flag=True,
+    default=False,
+    callback=process_ns_heal_params,
+    help="indicates whether or not to run day1 primitives for the VNF/VDU",
+)
+@click.option(
+    "--vdu",
+    default=None,
+    callback=process_ns_heal_params,
+    help="vdu-id",
+)
+@click.option(
+    "--count-index",
+    type=int,
+    default=None,
+    callback=process_ns_heal_params,
+    help="count-index",
+)
+@click.option(
+    "--timeout",
+    type=int,
+    default=None,
+    help="timeout in seconds"
+)
+@click.option(
+    "--wait",
+    is_flag=True,
+    default=False,
+    help="do not return the control immediately, but keep it until the operation is completed, or timeout",
+)
+@click.pass_context
+def ns_heal(
+    ctx,
+    ns_name,
+    heal_params,
+    cause,
+    vnf,
+    run_day1,
+    vdu,
+    count_index,
+    wait,
+    timeout,
+):
+    """heals (recreates) VNFs or VDUs of a NS instance
+
+    NS_NAME: name or ID of the NS instance
+    """
+    logger.debug("")
+    heal_dict = ctx.params["heal_params"]
+    heal_dict.pop("current_item")
+    if cause:
+        heal_dict["cause"] = cause
+    logger.debug(f"Heal dict: {heal_dict}")
+    check_client_version(ctx.obj, ctx.command.name)
+    ctx.obj.ns.heal(ns_name, heal_dict, wait, timeout)
+
+
+@click.command(
+    name="vnf-heal",
+    short_help="heals (recreates) a VNF instance or the VDUs of a VNF instance",
+)
+@click.argument("vnf_name")
+@click.option(
+    "--cause",
+    default=None,
+    callback=process_vnf_heal_params,
+    help="human readable cause of the healing",
+)
+@click.option(
+    "--run-day1",
+    is_flag=True,
+    default=False,
+    callback=process_vnf_heal_params,
+    help="indicates whether or not to run day1 primitives for the VNF/VDU",
+)
+@click.option(
+    "--vdu",
+    default=None,
+    callback=process_vnf_heal_params,
+    help="vdu-id",
+)
+@click.option(
+    "--count-index",
+    type=int,
+    default=None,
+    callback=process_vnf_heal_params,
+    help="count-index",
+)
+@click.option(
+    "--timeout",
+    type=int,
+    default=None,
+    help="timeout in seconds"
+)
+@click.option(
+    "--wait",
+    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 vnf_heal(
+    ctx,
+    vnf_name,
+    heal_params,
+    cause,
+    run_day1,
+    vdu,
+    count_index,
+    wait,
+    timeout,
+):
+    """heals (recreates) a VNF instance or the VDUs of a VNF instance
+
+    VNF_NAME: name or ID of the VNF instance
+    """
+    logger.debug("")
+    heal_dict = ctx.params["heal_params"]
+    heal_dict.pop("current_item")
+    heal_dict["healVnfData"][-1]["vnfInstanceId"] = vnf_name
+    logger.debug(f"Heal dict: {heal_dict}")
+    check_client_version(ctx.obj, ctx.command.name)
+    ctx.obj.vnf.heal(vnf_name, heal_dict, wait, timeout)
+
+
 @cli_osm.command(name="alarm-show", short_help="show alarm details")
 @click.argument("uuid")
 @click.pass_context
@@ -5511,8 +5877,7 @@ def alarm_show(ctx, uuid):
     table = PrettyTable(["key", "attribute"])
     try:
         # Arrange and return the response data
-        resp = resp.replace("ObjectId", "")
-        alarm = eval(resp)
+        alarm = resp.replace("ObjectId", "")
         for key in alarm_filter:
             if key == "uuid":
                 value = alarm.get(key)
@@ -5554,7 +5919,6 @@ def alarm_list(ctx, ns_id):
     if resp:
         # return the response data in a table
         resp = resp.replace("ObjectId", "")
-        resp = eval(resp)
         for alarm in resp:
             table.add_row(
                 [
@@ -5773,7 +6137,10 @@ def role_show(ctx, name):
     "--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)"
+    "--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(