From 12b478cb4a6b2dd74d61c4e7272fb0eda635c8b9 Mon Sep 17 00:00:00 2001 From: garciadeblas Date: Sun, 19 Jun 2022 00:49:47 +0200 Subject: [PATCH] Support of several VNF and VDU in ns-heal and vnf-heal commands Click does not allow advanced patterns for positional options. This makes impossible to request the healing of several VNF or VDU with different options like --count-index or --run-day1. This change introduces a processing of the args used in ns-heal and vnf-heal commands to allow those patterns. In addition, the change introduces the logic to use either the VNF instance ID or the identifier of a VNF inside a NS, known as "member-vnf-index-ref". Change-Id: I8b2f18aff6146ce579da33e67054f4a0f98c706a Signed-off-by: garciadeblas --- osmclient/scripts/osm.py | 336 +++++++++++++++++++++++---------------- 1 file changed, 195 insertions(+), 141 deletions(-) diff --git a/osmclient/scripts/osm.py b/osmclient/scripts/osm.py index aedf3c7..2d9a231 100755 --- a/osmclient/scripts/osm.py +++ b/osmclient/scripts/osm.py @@ -21,6 +21,7 @@ OSM shell/cli import click from osmclient import client from osmclient.common.exceptions import ClientException, NotFound +from osmclient.common.utils import validate_uuid4 from prettytable import PrettyTable import yaml import json @@ -5635,116 +5636,130 @@ def update(ctx, ns_name, updatetype, config, timeout, wait): ctx.obj.ns.update(ns_name, op_data, wait=wait) -def process_common_heal_params(ctx, param, value): +def iterator_split(iterator, separators): + """ + Splits a tuple or list into several lists whenever a separator is found + For instance, the following tuple will be separated with the separator "--vnf" as follows. + From: + ("--vnf", "A", "--cause", "cause_A", "--vdu", "vdu_A1", "--vnf", "B", "--cause", "cause_B", ... + "--vdu", "vdu_B1", "--count_index", "1", "--run-day1", "--vdu", "vdu_B1", "--count_index", "2") + To: + [ + ("--vnf", "A", "--cause", "cause_A", "--vdu", "vdu_A1"), + ("--vnf", "B", "--cause", "cause_B", "--vdu", "vdu_B1", "--count_index", "1", "--run-day1", ... + "--vdu", "vdu_B1", "--count_index", "2") + ] + + Returns as many lists as separators are found + """ 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" + if iterator[0] not in separators: + raise ClientException(f"Expected one of {separators}. Received: {iterator[0]}.") + list_of_lists = [] + first = 0 + for i in range(len(iterator)): + if iterator[i] in separators: + if i == first: + continue + if (i - first < 2): + raise ClientException(f"Expected at least one argument after separator (possible separators: {separators}).") + list_of_lists.append(list(iterator[first:i])) + first = i + if ((len(iterator) - first) < 2): + raise ClientException(f"Expected at least one argument after separator (possible separators: {separators}).") 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 + list_of_lists.append(list(iterator[first:len(iterator)])) + # logger.debug(f"List of lists: {list_of_lists}") + return list_of_lists + + +def process_common_heal_params(heal_vnf_dict, args): + logger.debug("") + current_item = "vnf" + i = 0 + while i < len(args): + if args[i] == "--cause": + if (i+1 >= len(args)) or args[i+1].startswith("--"): + raise ClientException("No cause was provided after --cause") + heal_vnf_dict["cause"] = args[i+1] + i = i + 2 + continue + if args[i] == "--run-day1": + if current_item == "vnf": + if "additionalParams" not in heal_vnf_dict: + heal_vnf_dict["additionalParams"] = {} + heal_vnf_dict["additionalParams"]["run-day1"] = True + else: + # if current_item == "vdu" + heal_vnf_dict["additionalParams"]["vdu"][-1]["run-day1"] = True + i = i + 1 + continue + if args[i] == "--vdu": + if "additionalParams" not in heal_vnf_dict: + heal_vnf_dict["additionalParams"] = {} + heal_vnf_dict["additionalParams"]["vdu"] = [] + if (i+1 >= len(args)) or args[i+1].startswith("--"): + raise ClientException("No VDU ID was provided after --vdu") + heal_vnf_dict["additionalParams"]["vdu"].append({"vdu-id": args[i+1]}) + current_item = "vdu" + i = i + 2 + continue + if args[i] == "--count-index": + if current_item == "vnf": + raise ClientException("Option --count-index only applies to VDU, not to VNF") + if (i+1 >= len(args)) or args[i+1].startswith("--"): + raise ClientException("No count index was provided after --count-index") + heal_vnf_dict["additionalParams"]["vdu"][-1]["count-index"] = int(args[i+1]) + i = i + 2 + continue + i = i + 1 return def process_ns_heal_params(ctx, param, value): + """ + Processes the params in the command ns-heal + Click does not allow advanced patterns for positional options like this: + --vnf volumes_vnf --cause "Heal several_volumes-VM of several_volumes_vnf" + --vdu several_volumes-VM + --vnf charm_vnf --cause "Heal two VMs of native_manual_scale_charm_vnf" + --vdu mgmtVM --count-index 1 --run-day1 + --vdu mgmtVM --count-index 2 + + It returns the dictionary with all the params stored in ctx.params["heal_params"] + """ 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) + # logger.debug(f"Args: {value}") + if param.name != "args": + raise ClientException(f"Unexpected param: {param.name}") + # Split the tuple "value" by "--vnf" + vnfs = iterator_split(value, ["--vnf"]) + logger.debug(f"VNFs: {vnfs}") + heal_dict = {} + heal_dict["healVnfData"] = [] + for vnf in vnfs: + # logger.debug(f"VNF: {vnf}") + heal_vnf = {} + if vnf[1].startswith("--"): + raise ClientException("Expected a VNF_ID after --vnf") + heal_vnf["vnfInstanceId"] = vnf[1] + process_common_heal_params(heal_vnf, vnf[2:]) + heal_dict["healVnfData"].append(heal_vnf) + ctx.params["heal_params"] = heal_dict + return @cli_osm.command( - name="ns-heal", short_help="heals (recreates) VNFs or VDUs of a NS instance" + name="ns-heal", + short_help="heals (recreates) VNFs or VDUs of a NS instance", + context_settings=dict(ignore_unknown_options=True,) ) @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, +@click.argument( + 'args', + nargs=-1, + type=click.UNPROCESSED, 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", @@ -5754,67 +5769,95 @@ def process_vnf_heal_params(ctx, param, value): ) @click.option( "--wait", - is_flag=True, 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 ns_heal( ctx, ns_name, + args, heal_params, - cause, - vnf, - run_day1, - vdu, - count_index, - wait, timeout, + wait ): """heals (recreates) VNFs or VDUs of a NS instance NS_NAME: name or ID of the NS instance + + \b + Options: + --vnf TEXT VNF instance ID or VNF id in the NS [required] + --cause TEXT human readable cause of the healing + --run-day1 indicates whether or not to run day1 primitives for the VNF/VDU + --vdu TEXT vdu-id + --count-index INTEGER count-index + + \b + Example: + osm ns-heal NS_NAME|NS_ID --vnf volumes_vnf --cause "Heal several_volumes-VM of several_volumes_vnf" + --vdu several_volumes-VM + --vnf charm_vnf --cause "Heal two VMs of native_manual_scale_charm_vnf" + --vdu mgmtVM --count-index 1 --run-day1 + --vdu mgmtVM --count-index 2 """ 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}") + logger.debug(f"Heal dict:\n{yaml.safe_dump(heal_dict)}") + # replace VNF id in the NS by the VNF instance ID + for vnf in heal_dict["healVnfData"]: + vnf_id = vnf["vnfInstanceId"] + if not validate_uuid4(vnf_id): + vnf_filter = f"member-vnf-index-ref={vnf_id}" + vnf_list = ctx.obj.vnf.list(ns=ns_name, filter=vnf_filter) + if len(vnf_list) == 0: + raise ClientException(f"No VNF found in NS {ns_name} with filter {vnf_filter}") + elif len(vnf_list) == 1: + vnf["vnfInstanceId"] = vnf_list[0]["_id"] + else: + raise ClientException(f"More than 1 VNF found in NS {ns_name} with filter {vnf_filter}") + logger.debug(f"Heal dict:\n{yaml.safe_dump(heal_dict)}") check_client_version(ctx.obj, ctx.command.name) ctx.obj.ns.heal(ns_name, heal_dict, wait, timeout) + exit(0) + + +def process_vnf_heal_params(ctx, param, value): + """ + Processes the params in the command vnf-heal + Click does not allow advanced patterns for positional options like this: + --vdu mgmtVM --count-index 1 --run-day1 --vdu mgmtVM --count-index 2 + + It returns the dictionary with all the params stored in ctx.params["heal_params"] + """ + logger.debug("") + # logger.debug(f"Args: {value}") + if param.name != "args": + raise ClientException(f"Unexpected param: {param.name}") + # Split the tuple "value" by "--vnf" + vnf = value + heal_dict = {} + heal_dict["healVnfData"] = [] + logger.debug(f"VNF: {vnf}") + heal_vnf = {"vnfInstanceId": "id_to_be_substituted"} + process_common_heal_params(heal_vnf, vnf) + heal_dict["healVnfData"].append(heal_vnf) + ctx.params["heal_params"] = heal_dict + return @cli_osm.command( name="vnf-heal", short_help="heals (recreates) a VNF instance or the VDUs of a VNF instance", + context_settings=dict(ignore_unknown_options=True,) ) @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, +@click.argument( + 'args', + nargs=-1, + type=click.UNPROCESSED, 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", @@ -5829,28 +5872,39 @@ def ns_heal( help="do not return the control immediately, but keep it until the operation is completed, or timeout", ) @click.pass_context -def vnf_heal( +def vnf_heal2( ctx, vnf_name, + args, heal_params, - cause, - run_day1, - vdu, - count_index, - wait, timeout, + wait, ): """heals (recreates) a VNF instance or the VDUs of a VNF instance VNF_NAME: name or ID of the VNF instance + + \b + Options: + --cause TEXT human readable cause of the healing of the VNF + --run-day1 indicates whether or not to run day1 primitives for the VNF/VDU + --vdu TEXT vdu-id + --count-index INTEGER count-index + + \b + Example: + osm vnf-heal VNF_INSTANCE_ID --vdu mgmtVM --count-index 1 --run-day1 + --vdu mgmtVM --count-index 2 """ 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}") + logger.debug(f"Heal dict:\n{yaml.safe_dump(heal_dict)}") check_client_version(ctx.obj, ctx.command.name) - ctx.obj.vnf.heal(vnf_name, heal_dict, wait, timeout) + vnfr = ctx.obj.vnf.get(vnf_name) + ns_id = vnfr["nsr-id-ref"] + ctx.obj.ns.heal(ns_id, heal_dict, wait, timeout) + exit(0) @cli_osm.command(name="alarm-show", short_help="show alarm details") -- 2.17.1