X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=osmclient%2Fscripts%2Fosm.py;h=3ce02df0d5ad89f0df166546d64db046c7090f7f;hb=0a3cee20d28824aeca5ef5afce2b83bdb31222d6;hp=4af0dad5f3bd3624823e73b67ca89f97cd6d3914;hpb=4335f9962fec0516628b5d0b8337fc977e55669c;p=osm%2Fosmclient.git diff --git a/osmclient/scripts/osm.py b/osmclient/scripts/osm.py index 4af0dad..3ce02df 100755 --- a/osmclient/scripts/osm.py +++ b/osmclient/scripts/osm.py @@ -31,6 +31,7 @@ import textwrap import pkg_resources import logging from datetime import datetime +from typing import Any, Dict def wrap_text(text, width): @@ -93,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) ) @@ -2329,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, @@ -2340,6 +2362,7 @@ def ns_create( config, config_file, wait, + timeout ): """creates a new NS instance""" logger.debug("") @@ -2359,6 +2382,7 @@ def ns_create( ssh_keys=ssh_keys, account=vim_account, wait=wait, + timeout=timeout, ) # except ClientException as e: # print(str(e)) @@ -2563,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 ... --vim_account ", 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( @@ -2577,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=,mgmt=,ip-address=" + + "[,type=][,mac-address=][,vim-network-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 ... --vim_account ", + 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()) @@ -2615,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 #################### @@ -3038,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( @@ -3070,6 +3152,9 @@ def pdu_delete(ctx, name, force): help="do not return the control immediately, but keep it " "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, @@ -3079,11 +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("") @@ -3093,17 +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 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) + if vca: + vim["vca"] = vca + 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) @@ -3117,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( @@ -3138,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, @@ -3148,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 @@ -3176,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) @@ -3255,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" @@ -3287,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 @@ -3298,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: @@ -3765,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, @@ -3778,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 @@ -3799,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) @@ -3821,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 @@ -3852,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) @@ -3863,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) @@ -3995,6 +4135,273 @@ def k8scluster_show(ctx, name, literal): # exit(1) +########################### +# VCA operations +########################### + + +@cli_osm.command(name="vca-add", short_help="adds a VCA (Juju controller) to OSM") +@click.argument("name") +@click.option( + "--endpoints", + prompt=True, + help="Comma-separated list of IP or hostnames of the Juju controller", +) +@click.option("--user", prompt=True, help="Username with admin priviledges") +@click.option("--secret", prompt=True, help="Password of the specified username") +@click.option("--cacert", prompt=True, help="CA certificate") +@click.option( + "--lxd-cloud", + prompt=True, + help="Name of the cloud that will be used for LXD containers (LXD proxy charms)", +) +@click.option( + "--lxd-credentials", + prompt=True, + help="Name of the cloud credentialsto be used for the LXD cloud", +) +@click.option( + "--k8s-cloud", + prompt=True, + help="Name of the cloud that will be used for K8s containers (K8s proxy charms)", +) +@click.option( + "--k8s-credentials", + prompt=True, + help="Name of the cloud credentialsto be used for the K8s cloud", +) +@click.option( + "--model-config", + default={}, + help="Configuration options for the models", +) +@click.option("--description", default=None, help="human readable description") +@click.pass_context +def vca_add( + ctx, + name, + endpoints, + user, + secret, + cacert, + lxd_cloud, + lxd_credentials, + k8s_cloud, + k8s_credentials, + model_config, + description, +): + """adds a VCA to OSM + + NAME: name of the VCA + """ + check_client_version(ctx.obj, ctx.command.name) + vca = {} + vca["name"] = name + vca["endpoints"] = endpoints.split(",") + vca["user"] = user + vca["secret"] = secret + vca["cacert"] = cacert + vca["lxd-cloud"] = lxd_cloud + vca["lxd-credentials"] = lxd_credentials + vca["k8s-cloud"] = k8s_cloud + vca["k8s-credentials"] = k8s_credentials + if description: + vca["description"] = description + if model_config: + model_config = load(model_config) + vca["model-config"] = model_config + ctx.obj.vca.create(name, vca) + + +def load(data: Any): + if os.path.isfile(data): + return load_file(data) + else: + try: + return json.loads(data) + except ValueError as e: + raise ClientException(e) + + +def load_file(file_path: str) -> Dict: + content = None + with open(file_path, "r") as f: + content = f.read() + try: + return yaml.safe_load(content) + except yaml.scanner.ScannerError: + pass + try: + return json.loads(content) + except ValueError: + pass + raise ClientException(f"{file_path} must be a valid yaml or json file") + + +@cli_osm.command(name="vca-update", short_help="updates a K8s cluster") +@click.argument("name") +@click.option( + "--endpoints", help="Comma-separated list of IP or hostnames of the Juju controller" +) +@click.option("--user", help="Username with admin priviledges") +@click.option("--secret", help="Password of the specified username") +@click.option("--cacert", help="CA certificate") +@click.option( + "--lxd-cloud", + help="Name of the cloud that will be used for LXD containers (LXD proxy charms)", +) +@click.option( + "--lxd-credentials", + help="Name of the cloud credentialsto be used for the LXD cloud", +) +@click.option( + "--k8s-cloud", + help="Name of the cloud that will be used for K8s containers (K8s proxy charms)", +) +@click.option( + "--k8s-credentials", + help="Name of the cloud credentialsto be used for the K8s cloud", +) +@click.option( + "--model-config", + help="Configuration options for the models", +) +@click.option("--description", default=None, help="human readable description") +@click.pass_context +def vca_update( + ctx, + name, + endpoints, + user, + secret, + cacert, + lxd_cloud, + lxd_credentials, + k8s_cloud, + k8s_credentials, + model_config, + description, +): + """updates a K8s cluster + + NAME: name or ID of the K8s cluster + """ + check_client_version(ctx.obj, ctx.command.name) + vca = {} + vca["name"] = name + if endpoints: + vca["endpoints"] = endpoints.split(",") + if user: + vca["user"] = user + if secret: + vca["secret"] = secret + if cacert: + vca["cacert"] = cacert + if lxd_cloud: + vca["lxd-cloud"] = lxd_cloud + if lxd_credentials: + vca["lxd-credentials"] = lxd_credentials + if k8s_cloud: + vca["k8s-cloud"] = k8s_cloud + if k8s_credentials: + vca["k8s-credentials"] = k8s_credentials + if description: + vca["description"] = description + if model_config: + model_config = load(model_config) + vca["model-config"] = model_config + ctx.obj.vca.update(name, vca) + + +@cli_osm.command(name="vca-delete", short_help="deletes a K8s cluster") +@click.argument("name") +@click.option( + "--force", is_flag=True, help="forces the deletion from the DB (not recommended)" +) +@click.pass_context +def vca_delete(ctx, name, force): + """deletes a K8s cluster + + NAME: name or ID of the K8s cluster to be deleted + """ + check_client_version(ctx.obj, ctx.command.name) + ctx.obj.vca.delete(name, force=force) + + +@cli_osm.command(name="vca-list") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the VCAs matching the filter", +) +@click.option("--literal", is_flag=True, help="print literally, no pretty table") +@click.option("--long", is_flag=True, help="get more details") +@click.pass_context +def vca_list(ctx, filter, literal, long): + """list VCAs""" + check_client_version(ctx.obj, ctx.command.name) + if filter: + filter = "&".join(filter) + resp = ctx.obj.vca.list(filter) + if literal: + print(yaml.safe_dump(resp, indent=4, default_flow_style=False)) + return + if long: + table = PrettyTable( + ["Name", "Id", "Project", "Operational State", "Detailed Status"] + ) + project_list = ctx.obj.project.list() + else: + table = PrettyTable(["Name", "Id", "Operational State"]) + for vca in resp: + logger.debug("VCA details: {}".format(yaml.safe_dump(vca))) + if long: + project_id, project_name = get_project(project_list, vca) + detailed_status = vca.get("_admin", {}).get("detailed-status", "-") + table.add_row( + [ + vca["name"], + vca["_id"], + project_name, + vca.get("_admin", {}).get("operationalState", "-"), + wrap_text(text=detailed_status, width=40), + ] + ) + else: + table.add_row( + [ + vca["name"], + vca["_id"], + vca.get("_admin", {}).get("operationalState", "-"), + ] + ) + table.align = "l" + print(table) + + +@cli_osm.command(name="vca-show", short_help="shows the details of a K8s cluster") +@click.argument("name") +@click.option("--literal", is_flag=True, help="print literally, no pretty table") +@click.pass_context +def vca_show(ctx, name, literal): + """shows the details of a K8s cluster + + NAME: name or ID of the K8s cluster + """ + # try: + resp = ctx.obj.vca.get(name) + 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()): + table.add_row([k, wrap_text(text=json.dumps(v, indent=2), width=100)]) + table.align = "l" + print(table) + + ########################### # Repo operations ########################### @@ -4450,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, @@ -4460,6 +4877,8 @@ def user_update( remove_project, add_project_role, remove_project_role, + change_password, + new_password, ): """Update a user information @@ -4471,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 = {} @@ -4480,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) @@ -4694,6 +5120,128 @@ def ns_metric_export(ctx, ns, vnf, vdu, metric, interval): # exit(1) +################# +# Subscription operations +################# + + +@cli_osm.command( + name="subscription-create", + short_help="creates a new subscription to a specific event", +) +@click.option( + "--event_type", + # type=click.Choice(['ns', 'nspkg', 'vnfpkg'], case_sensitive=False)) + type=click.Choice(["ns"], case_sensitive=False), + help="event type to be subscribed (for the moment, only ns is supported)", +) +@click.option("--event", default=None, help="specific yaml configuration for the event") +@click.option( + "--event_file", default=None, help="specific yaml configuration file for the event" +) +@click.pass_context +def subscription_create(ctx, event_type, event, event_file): + """creates a new subscription to a specific event""" + logger.debug("") + check_client_version(ctx.obj, ctx.command.name) + if event_file: + if event: + raise ClientException( + '"--event" option is incompatible with "--event_file" option' + ) + with open(event_file, "r") as cf: + event = cf.read() + ctx.obj.subscription.create(event_type, event) + + +@cli_osm.command(name="subscription-delete", short_help="deletes a subscription") +@click.option( + "--event_type", + # type=click.Choice(['ns', 'nspkg', 'vnfpkg'], case_sensitive=False)) + type=click.Choice(["ns"], case_sensitive=False), + help="event type to be subscribed (for the moment, only ns is supported)", +) +@click.argument("subscription_id") +@click.option( + "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" +) +@click.pass_context +def subscription_delete(ctx, event_type, subscription_id, force): + """deletes a subscription + + SUBSCRIPTION_ID: ID of the subscription to be deleted + """ + logger.debug("") + check_client_version(ctx.obj, ctx.command.name) + ctx.obj.subscription.delete(event_type, subscription_id, force) + + +@cli_osm.command(name="subscription-list", short_help="list all subscriptions") +@click.option( + "--event_type", + # type=click.Choice(['ns', 'nspkg', 'vnfpkg'], case_sensitive=False)) + type=click.Choice(["ns"], case_sensitive=False), + help="event type to be subscribed (for the moment, only ns is supported)", +) +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the subscriptions matching the filter", +) +@click.pass_context +def subscription_list(ctx, event_type, filter): + """list all subscriptions""" + logger.debug("") + check_client_version(ctx.obj, ctx.command.name) + if filter: + filter = "&".join(filter) + resp = ctx.obj.subscription.list(event_type, filter) + table = PrettyTable(["id", "filter", "CallbackUri"]) + for sub in resp: + table.add_row( + [ + sub["_id"], + wrap_text(text=json.dumps(sub["filter"], indent=2), width=70), + sub["CallbackUri"], + ] + ) + table.align = "l" + print(table) + + +@cli_osm.command( + name="subscription-show", short_help="shows the details of a subscription" +) +@click.argument("subscription_id") +@click.option( + "--event_type", + # type=click.Choice(['ns', 'nspkg', 'vnfpkg'], case_sensitive=False)) + type=click.Choice(["ns"], case_sensitive=False), + help="event type to be subscribed (for the moment, only ns is supported)", +) +@click.option( + "--filter", + multiple=True, + help="restricts the information to the fields in the filter", +) +@click.pass_context +def subscription_show(ctx, event_type, subscription_id, filter): + """shows the details of a subscription + + SUBSCRIPTION_ID: ID of the subscription + """ + logger.debug("") + # try: + resp = ctx.obj.subscription.get(subscription_id) + table = PrettyTable(["key", "attribute"]) + for k, v in list(resp.items()): + if not filter or k in filter: + table.add_row([k, wrap_text(text=json.dumps(v, indent=2), width=100)]) + table.align = "l" + print(table) + + #################### # Other operations #################### @@ -5016,6 +5564,154 @@ 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 +def alarm_show(ctx, uuid): + """Show alarm's detail information""" + + check_client_version(ctx.obj, ctx.command.name) + resp = ctx.obj.ns.get_alarm(uuid=uuid) + alarm_filter = [ + "uuid", + "name", + "metric", + "statistic", + "threshold", + "operation", + "ns-id", + "vnf-id", + "vdu_name", + "action", + "status", + ] + table = PrettyTable(["key", "attribute"]) + try: + # Arrange and return the response data + resp = resp.replace("ObjectId", "") + alarm = eval(resp) + for key in alarm_filter: + if key == "uuid": + value = alarm.get(key) + key = "alarm-id" + elif key == "name": + value = alarm.get(key) + key = "alarm-name" + elif key == "ns-id": + value = alarm["tags"].get("ns_id") + elif key == "vdu_name": + value = alarm["tags"].get("vdu_name") + elif key == "status": + value = alarm["alarm_status"] + else: + value = alarm[key] + table.add_row([key, wrap_text(text=json.dumps(value, indent=2), width=100)]) + table.align = "l" + print(table) + except Exception: + print(resp) + + +# List alarm +@cli_osm.command(name="alarm-list", short_help="list all alarms") +@click.option( + "--ns_id", default=None, required=False, help="List out alarm for given ns id" +) +@click.pass_context +def alarm_list(ctx, ns_id): + """list all alarm""" + + check_client_version(ctx.obj, ctx.command.name) + project_name = os.getenv("OSM_PROJECT", "admin") + resp = ctx.obj.ns.get_alarm(project_name=project_name, ns_id=ns_id) + + table = PrettyTable( + ["alarm-id", "metric", "threshold", "operation", "action", "status"] + ) + if resp: + # return the response data in a table + resp = resp.replace("ObjectId", "") + resp = eval(resp) + for alarm in resp: + table.add_row( + [ + wrap_text(text=str(alarm["uuid"]), width=38), + alarm["metric"], + alarm["threshold"], + alarm["operation"], + wrap_text(text=alarm["action"], width=25), + alarm["alarm_status"], + ] + ) + table.align = "l" + print(table) + + +# Update alarm +@cli_osm.command(name="alarm-update", short_help="Update a alarm") +@click.argument("uuid") +@click.option("--threshold", default=None, help="Alarm threshold") +@click.option("--is_enable", default=None, type=bool, help="enable or disable alarm") +@click.pass_context +def alarm_update(ctx, uuid, threshold, is_enable): + """ + Update alarm + + """ + if not threshold and is_enable is None: + raise ClientException( + "Please provide option to update i.e threshold or is_enable" + ) + ctx.obj.ns.update_alarm(uuid, threshold, is_enable) + + ############################## # Role Management Operations # ############################## @@ -5200,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, @@ -5217,6 +5919,7 @@ def package_create( detailed, netslice_subnets, netslice_vlds, + old, ): """ Creates an OSM NS, VNF, NST package @@ -5249,6 +5952,7 @@ def package_create( detailed=detailed, netslice_subnets=netslice_subnets, netslice_vlds=netslice_vlds, + old=old, ) print(resp) # except ClientException as inst: