From: Mark Beierl Date: Fri, 6 Jan 2023 18:03:17 +0000 (-0500) Subject: Reset branch to master X-Git-Url: https://osm.etsi.org/gitweb/?a=commitdiff_plain;h=a448805c3b22be921f1076be86168591e60552d3;p=osm%2Fosmclient.git Reset branch to master Change-Id: I89942d1e14403d404ddf71f4b8daf62f09e32eab Signed-off-by: Mark Beierl --- diff --git a/README.md b/README.md index 4b865c7..c53049e 100644 --- a/README.md +++ b/README.md @@ -178,4 +178,3 @@ Then you can add the following to your $HOME/.bashrc file: - See how to deprecate commands and options: - Evaluate the possibility to re-structure code to uniform all commands: `check`, `table_headers`, `run`, `output` - diff --git a/osmclient/cli_commands/__init__.py b/osmclient/cli_commands/__init__.py new file mode 100644 index 0000000..0658c88 --- /dev/null +++ b/osmclient/cli_commands/__init__.py @@ -0,0 +1,14 @@ +# +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/osmclient/cli_commands/alarms.py b/osmclient/cli_commands/alarms.py new file mode 100755 index 0000000..bc6d52e --- /dev/null +++ b/osmclient/cli_commands/alarms.py @@ -0,0 +1,188 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.common.exceptions import ClientException +from osmclient.cli_commands import utils +from prettytable import PrettyTable +import json +import os +import logging + +logger = logging.getLogger("osmclient") + + +@click.command(name="ns-alarm-create") +@click.argument("name") +@click.option("--ns", prompt=True, help="NS instance id or name") +@click.option( + "--vnf", prompt=True, help="VNF name (VNF member index as declared in the NSD)" +) +@click.option("--vdu", prompt=True, help="VDU name (VDU name as declared in the VNFD)") +@click.option("--metric", prompt=True, help="Name of the metric (e.g. cpu_utilization)") +@click.option( + "--severity", + default="WARNING", + help="severity of the alarm (WARNING, MINOR, MAJOR, CRITICAL, INDETERMINATE)", +) +@click.option( + "--threshold_value", + prompt=True, + help="threshold value that, when crossed, an alarm is triggered", +) +@click.option( + "--threshold_operator", + prompt=True, + help="threshold operator describing the comparison (GE, LE, GT, LT, EQ)", +) +@click.option( + "--statistic", + default="AVERAGE", + help="statistic (AVERAGE, MINIMUM, MAXIMUM, COUNT, SUM)", +) +@click.pass_context +def ns_alarm_create( + ctx, + name, + ns, + vnf, + vdu, + metric, + severity, + threshold_value, + threshold_operator, + statistic, +): + """creates a new alarm for a NS instance""" + # TODO: Check how to validate threshold_value. + # Should it be an integer (1-100), percentage, or decimal (0.01-1.00)? + logger.debug("") + ns_instance = ctx.obj.ns.get(ns) + alarm = {} + alarm["alarm_name"] = name + alarm["ns_id"] = ns_instance["_id"] + alarm["correlation_id"] = ns_instance["_id"] + alarm["vnf_member_index"] = vnf + alarm["vdu_name"] = vdu + alarm["metric_name"] = metric + alarm["severity"] = severity + alarm["threshold_value"] = int(threshold_value) + alarm["operation"] = threshold_operator + alarm["statistic"] = statistic + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.ns.create_alarm(alarm) + + +@click.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""" + + utils.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 + alarm = resp.replace("ObjectId", "") + 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, utils.wrap_text(text=json.dumps(value, indent=2), width=100)] + ) + table.align = "l" + print(table) + except Exception: + print(resp) + # TODO: check the reason for the try-except + + +# List alarm +@click.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 alarms""" + + utils.check_client_version(ctx.obj, ctx.command.name) + project_name = os.getenv("OSM_PROJECT", "admin") + # TODO: check the reason for this^ + 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", "") + for alarm in resp: + table.add_row( + [ + utils.wrap_text(text=str(alarm["uuid"]), width=38), + alarm["metric"], + alarm["threshold"], + alarm["operation"], + utils.wrap_text(text=alarm["action"], width=25), + alarm["alarm_status"], + ] + ) + table.align = "l" + print(table) + + +# Update alarm +@click.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) diff --git a/osmclient/cli_commands/k8scluster.py b/osmclient/cli_commands/k8scluster.py new file mode 100755 index 0000000..9c1bcce --- /dev/null +++ b/osmclient/cli_commands/k8scluster.py @@ -0,0 +1,308 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.cli_commands import utils +from prettytable import PrettyTable +import yaml +import json +import logging + +logger = logging.getLogger("osmclient") + + +@click.command(name="k8scluster-add", short_help="adds a K8s cluster to OSM") +@click.argument("name") +@click.option( + "--creds", prompt=True, help="credentials file, i.e. a valid `.kube/config` file" +) +@click.option("--version", prompt=True, help="Kubernetes version") +@click.option( + "--vim", prompt=True, help="VIM target, the VIM where the cluster resides" +) +@click.option( + "--k8s-nets", + prompt=True, + 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", + 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, + help="list of CNI plugins, in JSON inline format, used in the cluster", +) +# @click.option('--skip-init', +# is_flag=True, +# help='If set, K8s cluster is assumed to be ready for its use with OSM') +@click.pass_context +def k8scluster_add( + ctx, + name, + creds, + version, + vim, + k8s_nets, + init_helm2, + init_helm3, + init_jujubundle, + description, + namespace, + wait, + cni, +): + """adds a K8s cluster to OSM + + NAME: name of the K8s cluster + """ + utils.check_client_version(ctx.obj, ctx.command.name) + cluster = {} + cluster["name"] = name + with open(creds, "r") as cf: + cluster["credentials"] = yaml.safe_load(cf.read()) + 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: + cluster["namespace"] = namespace + if cni: + cluster["cni"] = yaml.safe_load(cni) + ctx.obj.k8scluster.create(name, cluster, wait) + + +@click.command(name="k8scluster-update", short_help="updates a K8s cluster") +@click.argument("name") +@click.option("--newname", help="New name for the K8s cluster") +@click.option("--creds", help="credentials file, i.e. a valid `.kube/config` file") +@click.option("--version", help="Kubernetes version") +@click.option("--vim", help="VIM target, the VIM where the cluster resides") +@click.option( + "--k8s-nets", + 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("--description", help="human readable description") +@click.option( + "--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, wait, cni +): + """updates a K8s cluster + + NAME: name or ID of the K8s cluster + """ + utils.check_client_version(ctx.obj, ctx.command.name) + cluster = {} + if newname: + cluster["name"] = newname + if creds: + with open(creds, "r") as cf: + cluster["credentials"] = yaml.safe_load(cf.read()) + if version: + cluster["k8s_version"] = version + if vim: + cluster["vim_account"] = vim + if k8s_nets: + cluster["nets"] = yaml.safe_load(k8s_nets) + if description: + cluster["description"] = description + if namespace: + cluster["namespace"] = namespace + if cni: + cluster["cni"] = yaml.safe_load(cni) + ctx.obj.k8scluster.update(name, cluster, wait) + + +@click.command(name="k8scluster-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.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, wait): + """deletes a K8s cluster + + NAME: name or ID of the K8s cluster to be deleted + """ + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.k8scluster.delete(name, force, wait) + + +@click.command(name="k8scluster-list") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the K8s clusters 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 k8scluster_list(ctx, filter, literal, long): + """list all K8s clusters""" + utils.check_client_version(ctx.obj, ctx.command.name) + if filter: + filter = "&".join(filter) + resp = ctx.obj.k8scluster.list(filter) + if literal: + print(yaml.safe_dump(resp, indent=4, default_flow_style=False)) + return + if long: + table = PrettyTable( + [ + "Name", + "Id", + "Project", + "Version", + "VIM", + "K8s-nets", + "Deployment methods", + "Operational State", + "Op. state (details)", + "Description", + "Detailed status", + ] + ) + project_list = ctx.obj.project.list() + else: + table = PrettyTable( + ["Name", "Id", "VIM", "Operational State", "Op. state details"] + ) + try: + vim_list = ctx.obj.vim.list() + except Exception: + vim_list = [] + for cluster in resp: + logger.debug("Cluster details: {}".format(yaml.safe_dump(cluster))) + vim_name = utils.get_vim_name(vim_list, cluster["vim_account"]) + # vim_info = '{} ({})'.format(vim_name,cluster['vim_account']) + vim_info = vim_name + op_state_details = "Helm: {}\nJuju: {}".format( + cluster["_admin"].get("helm-chart", {}).get("operationalState", "-"), + cluster["_admin"].get("juju-bundle", {}).get("operationalState", "-"), + ) + if long: + project_id, project_name = utils.get_project(project_list, cluster) + # project_info = '{} ({})'.format(project_name, project_id) + project_info = project_name + detailed_status = cluster["_admin"].get("detailed-status", "-") + table.add_row( + [ + cluster["name"], + cluster["_id"], + project_info, + cluster["k8s_version"], + vim_info, + json.dumps(cluster["nets"]), + json.dumps(cluster["deployment_methods"]), + cluster["_admin"]["operationalState"], + op_state_details, + utils.trunc_text(cluster.get("description") or "", 40), + utils.wrap_text(text=detailed_status, width=40), + ] + ) + else: + table.add_row( + [ + cluster["name"], + cluster["_id"], + vim_info, + cluster["_admin"]["operationalState"], + op_state_details, + ] + ) + table.align = "l" + print(table) + + +@click.command(name="k8scluster-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 k8scluster_show(ctx, name, literal): + """shows the details of a K8s cluster + + NAME: name or ID of the K8s cluster + """ + resp = ctx.obj.k8scluster.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, utils.wrap_text(text=json.dumps(v, indent=2), width=100)]) + table.align = "l" + print(table) diff --git a/osmclient/cli_commands/metrics.py b/osmclient/cli_commands/metrics.py new file mode 100755 index 0000000..fc62595 --- /dev/null +++ b/osmclient/cli_commands/metrics.py @@ -0,0 +1,62 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.cli_commands import utils +import time +import logging + +logger = logging.getLogger("osmclient") + + +@click.command( + name="ns-metric-export", + short_help="exports a metric to the internal OSM bus, which can be read by other apps", +) +@click.option("--ns", prompt=True, help="NS instance id or name") +@click.option( + "--vnf", prompt=True, help="VNF name (VNF member index as declared in the NSD)" +) +@click.option("--vdu", prompt=True, help="VDU name (VDU name as declared in the VNFD)") +@click.option("--metric", prompt=True, help="name of the metric (e.g. cpu_utilization)") +# @click.option('--period', default='1w', +# help='metric collection period (e.g. 20s, 30m, 2h, 3d, 1w)') +@click.option( + "--interval", help="periodic interval (seconds) to export metrics continuously" +) +@click.pass_context +def ns_metric_export(ctx, ns, vnf, vdu, metric, interval): + """exports a metric to the internal OSM bus, which can be read by other apps""" + # TODO: Check how to validate interval. + # Should it be an integer (seconds), or should a suffix (s,m,h,d,w) also be permitted? + logger.debug("") + ns_instance = ctx.obj.ns.get(ns) + metric_data = {} + metric_data["ns_id"] = ns_instance["_id"] + metric_data["correlation_id"] = ns_instance["_id"] + metric_data["vnf_member_index"] = vnf + metric_data["vdu_name"] = vdu + metric_data["metric_name"] = metric + metric_data["collection_unit"] = "WEEK" + metric_data["collection_period"] = 1 + utils.check_client_version(ctx.obj, ctx.command.name) + if not interval: + print("{}".format(ctx.obj.ns.export_metric(metric_data))) + else: + i = 1 + while True: + print("{} {}".format(ctx.obj.ns.export_metric(metric_data), i)) + time.sleep(int(interval)) + i += 1 diff --git a/osmclient/cli_commands/netslice_instance.py b/osmclient/cli_commands/netslice_instance.py new file mode 100755 index 0000000..3799f47 --- /dev/null +++ b/osmclient/cli_commands/netslice_instance.py @@ -0,0 +1,308 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.common.exceptions import ClientException +from osmclient.cli_commands import utils +from prettytable import PrettyTable +import yaml +import json +import logging + +logger = logging.getLogger("osmclient") + + +def nsi_list(ctx, filter): + """list all Network Slice Instances""" + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + if filter: + filter = "&".join(filter) + resp = ctx.obj.nsi.list(filter) + table = PrettyTable( + [ + "netslice instance name", + "id", + "operational status", + "config status", + "detailed status", + ] + ) + for nsi in resp: + nsi_name = nsi["name"] + nsi_id = nsi["_id"] + opstatus = ( + nsi["operational-status"] if "operational-status" in nsi else "Not found" + ) + configstatus = nsi["config-status"] if "config-status" in nsi else "Not found" + detailed_status = ( + nsi["detailed-status"] if "detailed-status" in nsi else "Not found" + ) + if configstatus == "config_not_needed": + configstatus = "configured (no charms)" + table.add_row([nsi_name, nsi_id, opstatus, configstatus, detailed_status]) + table.align = "l" + print(table) + + +@click.command(name="nsi-list", short_help="list all Network Slice Instances (NSI)") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the Network Slice Instances matching the filter", +) +@click.pass_context +def nsi_list1(ctx, filter): + """list all Network Slice Instances (NSI)""" + logger.debug("") + nsi_list(ctx, filter) + + +@click.command( + name="netslice-instance-list", short_help="list all Network Slice Instances (NSI)" +) +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the Network Slice Instances matching the filter", +) +@click.pass_context +def nsi_list2(ctx, filter): + """list all Network Slice Instances (NSI)""" + logger.debug("") + nsi_list(ctx, filter) + + +def nsi_show(ctx, name, literal, filter): + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + nsi = ctx.obj.nsi.get(name) + + if literal: + print(yaml.safe_dump(nsi, indent=4, default_flow_style=False)) + return + + table = PrettyTable(["field", "value"]) + + for k, v in list(nsi.items()): + if not filter or k in filter: + table.add_row([k, json.dumps(v, indent=2)]) + + table.align = "l" + print(table) + + +@click.command( + name="nsi-show", short_help="shows the content of a Network Slice Instance (NSI)" +) +@click.argument("name") +@click.option("--literal", is_flag=True, help="print literally, no pretty table") +@click.option( + "--filter", + multiple=True, + help="restricts the information to the fields in the filter", +) +@click.pass_context +def nsi_show1(ctx, name, literal, filter): + """shows the content of a Network Slice Instance (NSI) + + NAME: name or ID of the Network Slice Instance + """ + logger.debug("") + nsi_show(ctx, name, literal, filter) + + +@click.command( + name="netslice-instance-show", + short_help="shows the content of a Network Slice Instance (NSI)", +) +@click.argument("name") +@click.option("--literal", is_flag=True, help="print literally, no pretty table") +@click.option( + "--filter", + multiple=True, + help="restricts the information to the fields in the filter", +) +@click.pass_context +def nsi_show2(ctx, name, literal, filter): + """shows the content of a Network Slice Instance (NSI) + + NAME: name or ID of the Network Slice Instance + """ + logger.debug("") + nsi_show(ctx, name, literal, filter) + + +def nsi_create( + ctx, nst_name, nsi_name, vim_account, ssh_keys, config, config_file, wait +): + """creates a new Network Slice Instance (NSI)""" + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + if config_file: + if config: + raise ClientException( + '"--config" option is incompatible with "--config_file" option' + ) + with open(config_file, "r") as cf: + config = cf.read() + ctx.obj.nsi.create( + nst_name, + nsi_name, + config=config, + ssh_keys=ssh_keys, + account=vim_account, + wait=wait, + ) + + +@click.command(name="nsi-create", short_help="creates a new Network Slice Instance") +@click.option("--nsi_name", prompt=True, help="name of the Network Slice Instance") +@click.option("--nst_name", prompt=True, help="name of the Network Slice Template") +@click.option( + "--vim_account", + prompt=True, + help="default VIM account id or name for the deployment", +) +@click.option( + "--ssh_keys", default=None, help="comma separated list of keys to inject to vnfs" +) +@click.option( + "--config", + default=None, + help="Netslice specific yaml configuration:\n" + "netslice_subnet: [\n" + "id: TEXT, vim_account: TEXT,\n" + "vnf: [member-vnf-index: TEXT, vim_account: TEXT]\n" + "vld: [name: TEXT, vim-network-name: TEXT or DICT with vim_account, vim_net entries]\n" + "additionalParamsForNsi: {param: value, ...}\n" + "additionalParamsForsubnet: [{id: SUBNET_ID, additionalParamsForNs: {}, additionalParamsForVnf: {}}]\n" + "],\n" + "netslice-vld: [name: TEXT, vim-network-name: TEXT or DICT with vim_account, vim_net entries]", +) +@click.option( + "--config_file", default=None, help="nsi specific yaml configuration file" +) +@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 nsi_create1( + ctx, nst_name, nsi_name, vim_account, ssh_keys, config, config_file, wait +): + """creates a new Network Slice Instance (NSI)""" + logger.debug("") + nsi_create( + ctx, nst_name, nsi_name, vim_account, ssh_keys, config, config_file, wait=wait + ) + + +@click.command( + name="netslice-instance-create", short_help="creates a new Network Slice Instance" +) +@click.option("--nsi_name", prompt=True, help="name of the Network Slice Instance") +@click.option("--nst_name", prompt=True, help="name of the Network Slice Template") +@click.option( + "--vim_account", + prompt=True, + help="default VIM account id or name for the deployment", +) +@click.option( + "--ssh_keys", default=None, help="comma separated list of keys to inject to vnfs" +) +@click.option( + "--config", + default=None, + help="Netslice specific yaml configuration:\n" + "netslice_subnet: [\n" + "id: TEXT, vim_account: TEXT,\n" + "vnf: [member-vnf-index: TEXT, vim_account: TEXT]\n" + "vld: [name: TEXT, vim-network-name: TEXT or DICT with vim_account, vim_net entries]" + "],\n" + "netslice-vld: [name: TEXT, vim-network-name: TEXT or DICT with vim_account, vim_net entries]", +) +@click.option( + "--config_file", default=None, help="nsi specific yaml configuration file" +) +@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 nsi_create2( + ctx, nst_name, nsi_name, vim_account, ssh_keys, config, config_file, wait +): + """creates a new Network Slice Instance (NSI)""" + logger.debug("") + nsi_create( + ctx, nst_name, nsi_name, vim_account, ssh_keys, config, config_file, wait=wait + ) + + +def nsi_delete(ctx, name, force, wait): + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.nsi.delete(name, force, wait=wait) + + +@click.command(name="nsi-delete", short_help="deletes a Network Slice Instance (NSI)") +@click.argument("name") +@click.option( + "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" +) +@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 nsi_delete1(ctx, name, force, wait): + """deletes a Network Slice Instance (NSI) + + NAME: name or ID of the Network Slice instance to be deleted + """ + logger.debug("") + nsi_delete(ctx, name, force, wait=wait) + + +@click.command( + name="netslice-instance-delete", short_help="deletes a Network Slice Instance (NSI)" +) +@click.argument("name") +@click.option( + "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" +) +@click.pass_context +def nsi_delete2(ctx, name, force, wait): + """deletes a Network Slice Instance (NSI) + + NAME: name or ID of the Network Slice instance to be deleted + """ + logger.debug("") + nsi_delete(ctx, name, force, wait=wait) diff --git a/osmclient/cli_commands/netslice_ops.py b/osmclient/cli_commands/netslice_ops.py new file mode 100755 index 0000000..d891f45 --- /dev/null +++ b/osmclient/cli_commands/netslice_ops.py @@ -0,0 +1,116 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.cli_commands import utils +from prettytable import PrettyTable +import json +import logging + +logger = logging.getLogger("osmclient") + + +def nsi_op_list(ctx, name): + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + resp = ctx.obj.nsi.list_op(name) + table = PrettyTable(["id", "operation", "status"]) + for op in resp: + table.add_row([op["id"], op["lcmOperationType"], op["operationState"]]) + table.align = "l" + print(table) + + +@click.command( + name="nsi-op-list", + short_help="shows the history of operations over a Network Slice Instance (NSI)", +) +@click.argument("name") +@click.pass_context +def nsi_op_list1(ctx, name): + """shows the history of operations over a Network Slice Instance (NSI) + + NAME: name or ID of the Network Slice Instance + """ + logger.debug("") + nsi_op_list(ctx, name) + + +@click.command( + name="netslice-instance-op-list", + short_help="shows the history of operations over a Network Slice Instance (NSI)", +) +@click.argument("name") +@click.pass_context +def nsi_op_list2(ctx, name): + """shows the history of operations over a Network Slice Instance (NSI) + + NAME: name or ID of the Network Slice Instance + """ + logger.debug("") + nsi_op_list(ctx, name) + + +def nsi_op_show(ctx, id, filter): + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + op_info = ctx.obj.nsi.get_op(id) + + table = PrettyTable(["field", "value"]) + for k, v in list(op_info.items()): + if not filter or k in filter: + table.add_row([k, json.dumps(v, indent=2)]) + table.align = "l" + print(table) + + +@click.command( + name="nsi-op-show", + short_help="shows the info of an operation over a Network Slice Instance(NSI)", +) +@click.argument("id") +@click.option( + "--filter", + multiple=True, + help="restricts the information to the fields in the filter", +) +@click.pass_context +def nsi_op_show1(ctx, id, filter): + """shows the info of an operation over a Network Slice Instance(NSI) + + ID: operation identifier + """ + logger.debug("") + nsi_op_show(ctx, id, filter) + + +@click.command( + name="netslice-instance-op-show", + short_help="shows the info of an operation over a Network Slice Instance(NSI)", +) +@click.argument("id") +@click.option( + "--filter", + multiple=True, + help="restricts the information to the fields in the filter", +) +@click.pass_context +def nsi_op_show2(ctx, id, filter): + """shows the info of an operation over a Network Slice Instance(NSI) + + ID: operation identifier + """ + logger.debug("") + nsi_op_show(ctx, id, filter) diff --git a/osmclient/cli_commands/netslice_template.py b/osmclient/cli_commands/netslice_template.py new file mode 100755 index 0000000..8ae780c --- /dev/null +++ b/osmclient/cli_commands/netslice_template.py @@ -0,0 +1,256 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.cli_commands import utils +from prettytable import PrettyTable +import yaml +import json +import logging + +logger = logging.getLogger("osmclient") + + +def nst_list(ctx, filter): + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + if filter: + filter = "&".join(filter) + resp = ctx.obj.nst.list(filter) + table = PrettyTable(["nst name", "id"]) + for nst in resp: + name = nst["name"] if "name" in nst else "-" + table.add_row([name, nst["_id"]]) + table.align = "l" + print(table) + + +@click.command(name="nst-list", short_help="list all Network Slice Templates (NST)") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the NST matching the filter", +) +@click.pass_context +def nst_list1(ctx, filter): + """list all Network Slice Templates (NST) in the system""" + logger.debug("") + nst_list(ctx, filter) + + +@click.command( + name="netslice-template-list", short_help="list all Network Slice Templates (NST)" +) +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the NST matching the filter", +) +@click.pass_context +def nst_list2(ctx, filter): + """list all Network Slice Templates (NST) in the system""" + logger.debug("") + nst_list(ctx, filter) + + +def nst_show(ctx, name, literal): + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + resp = ctx.obj.nst.get(name) + # resp = ctx.obj.nst.get_individual(name) + + if literal: + print(yaml.safe_dump(resp, indent=4, default_flow_style=False)) + return + + table = PrettyTable(["field", "value"]) + for k, v in list(resp.items()): + table.add_row([k, utils.wrap_text(json.dumps(v, indent=2), 100)]) + table.align = "l" + print(table) + + +@click.command( + name="nst-show", short_help="shows the content of a Network Slice Template (NST)" +) +@click.option("--literal", is_flag=True, help="print literally, no pretty table") +@click.argument("name") +@click.pass_context +def nst_show1(ctx, name, literal): + """shows the content of a Network Slice Template (NST) + + NAME: name or ID of the NST + """ + logger.debug("") + nst_show(ctx, name, literal) + + +@click.command( + name="netslice-template-show", + short_help="shows the content of a Network Slice Template (NST)", +) +@click.option("--literal", is_flag=True, help="print literally, no pretty table") +@click.argument("name") +@click.pass_context +def nst_show2(ctx, name, literal): + """shows the content of a Network Slice Template (NST) + + NAME: name or ID of the NST + """ + logger.debug("") + nst_show(ctx, name, literal) + + +def nst_create(ctx, filename, overwrite): + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.nst.create(filename, overwrite) + + +@click.command( + name="nst-create", short_help="creates a new Network Slice Template (NST)" +) +@click.argument("filename") +@click.option( + "--overwrite", + "overwrite", + default=None, # hidden=True, + help="Deprecated. Use override", +) +@click.option( + "--override", + "overwrite", + default=None, + help="overrides fields in descriptor, format: " + '"key1.key2...=value[;key3...=value;...]"', +) +@click.pass_context +def nst_create1(ctx, filename, overwrite): + """creates a new Network Slice Template (NST) + + FILENAME: NST package folder, NST yaml file or NSTpkg tar.gz file + """ + logger.debug("") + nst_create(ctx, filename, overwrite) + + +@click.command( + name="netslice-template-create", + short_help="creates a new Network Slice Template (NST)", +) +@click.argument("filename") +@click.option( + "--overwrite", + "overwrite", + default=None, # hidden=True, + help="Deprecated. Use override", +) +@click.option( + "--override", + "overwrite", + default=None, + help="overrides fields in descriptor, format: " + '"key1.key2...=value[;key3...=value;...]"', +) +@click.pass_context +def nst_create2(ctx, filename, overwrite): + """creates a new Network Slice Template (NST) + + FILENAME: NST yaml file or NSTpkg tar.gz file + """ + logger.debug("") + nst_create(ctx, filename, overwrite) + + +def nst_update(ctx, name, content): + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.nst.update(name, content) + + +@click.command(name="nst-update", short_help="updates a Network Slice Template (NST)") +@click.argument("name") +@click.option( + "--content", + default=None, + help="filename with the NST/NSTpkg replacing the current one", +) +@click.pass_context +def nst_update1(ctx, name, content): + """updates a Network Slice Template (NST) + + NAME: name or ID of the NSD/NSpkg + """ + logger.debug("") + nst_update(ctx, name, content) + + +@click.command( + name="netslice-template-update", short_help="updates a Network Slice Template (NST)" +) +@click.argument("name") +@click.option( + "--content", + default=None, + help="filename with the NST/NSTpkg replacing the current one", +) +@click.pass_context +def nst_update2(ctx, name, content): + """updates a Network Slice Template (NST) + + NAME: name or ID of the NSD/NSpkg + """ + logger.debug("") + nst_update(ctx, name, content) + + +def nst_delete(ctx, name, force): + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.nst.delete(name, force) + + +@click.command(name="nst-delete", short_help="deletes a Network Slice Template (NST)") +@click.argument("name") +@click.option( + "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" +) +@click.pass_context +def nst_delete1(ctx, name, force): + """deletes a Network Slice Template (NST) + + NAME: name or ID of the NST/NSTpkg to be deleted + """ + logger.debug("") + nst_delete(ctx, name, force) + + +@click.command( + name="netslice-template-delete", short_help="deletes a Network Slice Template (NST)" +) +@click.argument("name") +@click.option( + "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" +) +@click.pass_context +def nst_delete2(ctx, name, force): + """deletes a Network Slice Template (NST) + + NAME: name or ID of the NST/NSTpkg to be deleted + """ + logger.debug("") + nst_delete(ctx, name, force) diff --git a/osmclient/cli_commands/nfpkg.py b/osmclient/cli_commands/nfpkg.py new file mode 100755 index 0000000..c69be62 --- /dev/null +++ b/osmclient/cli_commands/nfpkg.py @@ -0,0 +1,597 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.common.exceptions import ClientException +from osmclient.cli_commands import utils +from prettytable import PrettyTable +import yaml +import json +from datetime import datetime +import logging + +logger = logging.getLogger("osmclient") + + +def vnfd_list(ctx, nf_type, filter, long): + logger.debug("") + if nf_type: + utils.check_client_version(ctx.obj, "--nf_type") + elif filter: + utils.check_client_version(ctx.obj, "--filter") + if filter: + filter = "&".join(filter) + if nf_type: + if nf_type == "vnf": + nf_filter = "_admin.type=vnfd" + elif nf_type == "pnf": + nf_filter = "_admin.type=pnfd" + elif nf_type == "hnf": + nf_filter = "_admin.type=hnfd" + else: + raise ClientException( + 'wrong value for "--nf_type" option, allowed values: vnf, pnf, hnf' + ) + if filter: + filter = "{}&{}".format(nf_filter, filter) + else: + filter = nf_filter + if filter: + resp = ctx.obj.vnfd.list(filter) + else: + resp = ctx.obj.vnfd.list() + # print(yaml.safe_dump(resp)) + fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__ + if fullclassname == "osmclient.sol005.client.Client": + if long: + table = PrettyTable( + [ + "nfpkg name", + "id", + "desc type", + "vendor", + "version", + "onboarding state", + "operational state", + "usage state", + "date", + "last update", + ] + ) + else: + table = PrettyTable(["nfpkg name", "id", "desc type"]) + for vnfd in resp: + name = vnfd.get("id", vnfd.get("name", "-")) + descriptor_type = "sol006" if "product-name" in vnfd else "rel8" + if long: + onb_state = vnfd["_admin"].get("onboardingState", "-") + op_state = vnfd["_admin"].get("operationalState", "-") + vendor = vnfd.get("provider", vnfd.get("vendor")) + version = vnfd.get("version") + usage_state = vnfd["_admin"].get("usageState", "-") + date = datetime.fromtimestamp(vnfd["_admin"]["created"]).strftime( + "%Y-%m-%dT%H:%M:%S" + ) + last_update = datetime.fromtimestamp( + vnfd["_admin"]["modified"] + ).strftime("%Y-%m-%dT%H:%M:%S") + table.add_row( + [ + name, + vnfd["_id"], + descriptor_type, + vendor, + version, + onb_state, + op_state, + usage_state, + date, + last_update, + ] + ) + else: + table.add_row([name, vnfd["_id"], descriptor_type]) + else: + table = PrettyTable(["nfpkg name", "id"]) + for vnfd in resp: + table.add_row([vnfd["name"], vnfd["id"]]) + table.align = "l" + print(table) + + +@click.command(name="vnfd-list", short_help="list all xNF packages (VNF, HNF, PNF)") +@click.option("--nf_type", help="type of NF (vnf, pnf, hnf)") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the NF pkg matching the filter", +) +@click.option("--long", is_flag=True, help="get more details") +@click.pass_context +def vnfd_list1(ctx, nf_type, filter, long): + """list all xNF packages (VNF, HNF, PNF)""" + logger.debug("") + vnfd_list(ctx, nf_type, filter, long) + + +@click.command(name="vnfpkg-list", short_help="list all xNF packages (VNF, HNF, PNF)") +@click.option("--nf_type", help="type of NF (vnf, pnf, hnf)") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the NFpkg matching the filter", +) +@click.option("--long", is_flag=True, help="get more details") +@click.pass_context +def vnfd_list2(ctx, nf_type, filter, long): + """list all xNF packages (VNF, HNF, PNF)""" + logger.debug("") + vnfd_list(ctx, nf_type, filter, long) + + +@click.command(name="nfpkg-list", short_help="list all xNF packages (VNF, HNF, PNF)") +@click.option("--nf_type", help="type of NF (vnf, pnf, hnf)") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the NFpkg matching the filter", +) +@click.option("--long", is_flag=True, help="get more details") +@click.pass_context +def nfpkg_list(ctx, nf_type, filter, long): + """list all xNF packages (VNF, HNF, PNF)""" + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + vnfd_list(ctx, nf_type, filter, long) + + +def vnfd_show(ctx, name, literal): + logger.debug("") + resp = ctx.obj.vnfd.get(name) + # resp = ctx.obj.vnfd.get_individual(name) + + if literal: + print(yaml.safe_dump(resp, indent=4, default_flow_style=False)) + return + + table = PrettyTable(["field", "value"]) + for k, v in list(resp.items()): + table.add_row([k, utils.wrap_text(text=json.dumps(v, indent=2), width=100)]) + table.align = "l" + print(table) + + +@click.command(name="vnfd-show", short_help="shows the details of a NF package") +@click.option("--literal", is_flag=True, help="print literally, no pretty table") +@click.argument("name") +@click.pass_context +def vnfd_show1(ctx, name, literal): + """shows the content of a VNFD + + NAME: name or ID of the VNFD/VNFpkg + """ + logger.debug("") + vnfd_show(ctx, name, literal) + + +@click.command(name="vnfpkg-show", short_help="shows the details of a NF package") +@click.option("--literal", is_flag=True, help="print literally, no pretty table") +@click.argument("name") +@click.pass_context +def vnfd_show2(ctx, name, literal): + """shows the content of a VNFD + + NAME: name or ID of the VNFD/VNFpkg + """ + logger.debug("") + vnfd_show(ctx, name, literal) + + +@click.command(name="nfpkg-show", short_help="shows the details of a NF package") +@click.option("--literal", is_flag=True, help="print literally, no pretty table") +@click.argument("name") +@click.pass_context +def nfpkg_show(ctx, name, literal): + """shows the content of a NF Descriptor + + NAME: name or ID of the NFpkg + """ + logger.debug("") + vnfd_show(ctx, name, literal) + + +def vnfd_create( + ctx, + filename, + overwrite, + skip_charm_build, + override_epa, + override_nonepa, + override_paravirt, + repo, + vendor, + version, +): + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + if repo: + filename = ctx.obj.osmrepo.get_pkg("vnf", filename, repo, vendor, version) + ctx.obj.vnfd.create( + filename, + overwrite=overwrite, + skip_charm_build=skip_charm_build, + override_epa=override_epa, + override_nonepa=override_nonepa, + override_paravirt=override_paravirt, + ) + + +@click.command(name="vnfd-create", short_help="creates a new VNFD/VNFpkg") +@click.argument("filename") +@click.option( + "--overwrite", "overwrite", default=None, help="overwrite deprecated, use override" +) +@click.option( + "--override", + "overwrite", + default=None, + help="overrides fields in descriptor, format: " + '"key1.key2...=value[;key3...=value;...]"', +) +@click.option( + "--skip-charm-build", + default=False, + is_flag=True, + help="The charm will not be compiled, it is assumed to already exist", +) +@click.option( + "--override-epa", + required=False, + default=False, + is_flag=True, + help="adds guest-epa parameters to all VDU", +) +@click.option( + "--override-nonepa", + required=False, + default=False, + is_flag=True, + help="removes all guest-epa parameters from all VDU", +) +@click.option( + "--override-paravirt", + required=False, + default=False, + is_flag=True, + help="overrides all VDU interfaces to PARAVIRT", +) +@click.option("--repo", default=None, help="[repository]: Repository name") +@click.option("--vendor", default=None, help="[repository]: filter by vendor]") +@click.option( + "--version", + default="latest", + help="[repository]: filter by version. Default: latest", +) +@click.pass_context +def vnfd_create1( + ctx, + filename, + overwrite, + skip_charm_build, + override_epa, + override_nonepa, + override_paravirt, + repo, + vendor, + version, +): + """creates a new VNFD/VNFpkg + \b + FILENAME: NF Package tar.gz file, NF Descriptor YAML file or NF Package folder + If FILENAME is a file (NF Package tar.gz or NF Descriptor YAML), it is onboarded. + If FILENAME is an NF Package folder, it is built and then onboarded. + """ + logger.debug("") + vnfd_create( + ctx, + filename, + overwrite=overwrite, + skip_charm_build=skip_charm_build, + override_epa=override_epa, + override_nonepa=override_nonepa, + override_paravirt=override_paravirt, + repo=repo, + vendor=vendor, + version=version, + ) + + +@click.command(name="vnfpkg-create", short_help="creates a new VNFD/VNFpkg") +@click.argument("filename") +@click.option( + "--overwrite", + "overwrite", + default=None, # hidden=True, + help="Deprecated. Use override", +) +@click.option( + "--override", + "overwrite", + default=None, + help="overrides fields in descriptor, format: " + '"key1.key2...=value[;key3...=value;...]"', +) +@click.option( + "--skip-charm-build", + default=False, + is_flag=True, + help="The charm will not be compiled, it is assumed to already exist", +) +@click.option( + "--override-epa", + required=False, + default=False, + is_flag=True, + help="adds guest-epa parameters to all VDU", +) +@click.option( + "--override-nonepa", + required=False, + default=False, + is_flag=True, + help="removes all guest-epa parameters from all VDU", +) +@click.option( + "--override-paravirt", + required=False, + default=False, + is_flag=True, + help="overrides all VDU interfaces to PARAVIRT", +) +@click.option("--repo", default=None, help="[repository]: Repository name") +@click.option("--vendor", default=None, help="[repository]: filter by vendor]") +@click.option( + "--version", + default="latest", + help="[repository]: filter by version. Default: latest", +) +@click.pass_context +def vnfd_create2( + ctx, + filename, + overwrite, + skip_charm_build, + override_epa, + override_nonepa, + override_paravirt, + repo, + vendor, + version, +): + """creates a new VNFD/VNFpkg + \b + FILENAME: NF Package tar.gz file, NF Descriptor YAML file or NF Package folder + If FILENAME is a file (NF Package tar.gz or NF Descriptor YAML), it is onboarded. + If FILENAME is an NF Package folder, it is built and then onboarded. + """ + logger.debug("") + vnfd_create( + ctx, + filename, + overwrite=overwrite, + skip_charm_build=skip_charm_build, + override_epa=override_epa, + override_nonepa=override_nonepa, + override_paravirt=override_paravirt, + repo=repo, + vendor=vendor, + version=version, + ) + + +@click.command(name="nfpkg-create", short_help="creates a new NFpkg") +@click.argument("filename") +@click.option( + "--overwrite", + "overwrite", + default=None, # hidden=True, + help="Deprecated. Use override", +) +@click.option( + "--override", + "overwrite", + default=None, + help="overrides fields in descriptor, format: " + '"key1.key2...=value[;key3...=value;...]"', +) +@click.option( + "--skip-charm-build", + default=False, + is_flag=True, + help="The charm will not be compiled, it is assumed to already exist", +) +@click.option( + "--override-epa", + required=False, + default=False, + is_flag=True, + help="adds guest-epa parameters to all VDU", +) +@click.option( + "--override-nonepa", + required=False, + default=False, + is_flag=True, + help="removes all guest-epa parameters from all VDU", +) +@click.option( + "--override-paravirt", + required=False, + default=False, + is_flag=True, + help="overrides all VDU interfaces to PARAVIRT", +) +@click.option("--repo", default=None, help="[repository]: Repository name") +@click.option("--vendor", default=None, help="[repository]: filter by vendor]") +@click.option( + "--version", + default="latest", + help="[repository]: filter by version. Default: latest", +) +@click.pass_context +def nfpkg_create( + ctx, + filename, + overwrite, + skip_charm_build, + override_epa, + override_nonepa, + override_paravirt, + repo, + vendor, + version, +): + """creates a new NFpkg + + \b + FILENAME: NF Package tar.gz file, NF Descriptor YAML file or NF Package folder + If FILENAME is a file (NF Package tar.gz or NF Descriptor YAML), it is onboarded. + If FILENAME is an NF Package folder, it is built and then onboarded. + """ + logger.debug("") + vnfd_create( + ctx, + filename, + overwrite=overwrite, + skip_charm_build=skip_charm_build, + override_epa=override_epa, + override_nonepa=override_nonepa, + override_paravirt=override_paravirt, + repo=repo, + vendor=vendor, + version=version, + ) + + +def vnfd_update(ctx, name, content): + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.vnfd.update(name, content) + + +@click.command(name="vnfd-update", short_help="updates a new VNFD/VNFpkg") +@click.argument("name") +@click.option( + "--content", + default=None, + help="filename with the VNFD/VNFpkg replacing the current one", +) +@click.pass_context +def vnfd_update1(ctx, name, content): + """updates a VNFD/VNFpkg + + NAME: name or ID of the VNFD/VNFpkg + """ + logger.debug("") + vnfd_update(ctx, name, content) + + +@click.command(name="vnfpkg-update", short_help="updates a VNFD/VNFpkg") +@click.argument("name") +@click.option( + "--content", + default=None, + help="filename with the VNFD/VNFpkg replacing the current one", +) +@click.pass_context +def vnfd_update2(ctx, name, content): + """updates a VNFD/VNFpkg + + NAME: VNFD yaml file or VNFpkg tar.gz file + """ + logger.debug("") + vnfd_update(ctx, name, content) + + +@click.command(name="nfpkg-update", short_help="updates a NFpkg") +@click.argument("name") +@click.option( + "--content", default=None, help="filename with the NFpkg replacing the current one" +) +@click.pass_context +def nfpkg_update(ctx, name, content): + """updates a NFpkg + + NAME: NF Descriptor yaml file or NFpkg tar.gz file + """ + logger.debug("") + vnfd_update(ctx, name, content) + + +def vnfd_delete(ctx, name, force): + logger.debug("") + if not force: + ctx.obj.vnfd.delete(name) + else: + utils.check_client_version(ctx.obj, "--force") + ctx.obj.vnfd.delete(name, force) + + +@click.command(name="vnfd-delete", short_help="deletes a VNFD/VNFpkg") +@click.argument("name") +@click.option( + "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" +) +@click.pass_context +def vnfd_delete1(ctx, name, force): + """deletes a VNFD/VNFpkg + + NAME: name or ID of the VNFD/VNFpkg to be deleted + """ + logger.debug("") + vnfd_delete(ctx, name, force) + + +@click.command(name="vnfpkg-delete", short_help="deletes a VNFD/VNFpkg") +@click.argument("name") +@click.option( + "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" +) +@click.pass_context +def vnfd_delete2(ctx, name, force): + """deletes a VNFD/VNFpkg + + NAME: name or ID of the VNFD/VNFpkg to be deleted + """ + logger.debug("") + vnfd_delete(ctx, name, force) + + +@click.command(name="nfpkg-delete", short_help="deletes a NFpkg") +@click.argument("name") +@click.option( + "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" +) +@click.pass_context +def nfpkg_delete(ctx, name, force): + """deletes a NFpkg + + NAME: name or ID of the NFpkg to be deleted + """ + logger.debug("") + vnfd_delete(ctx, name, force) diff --git a/osmclient/cli_commands/ns.py b/osmclient/cli_commands/ns.py new file mode 100755 index 0000000..f598e35 --- /dev/null +++ b/osmclient/cli_commands/ns.py @@ -0,0 +1,432 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.common.exceptions import ClientException +from osmclient.cli_commands import utils +from prettytable import PrettyTable +import yaml +import json +from datetime import datetime +import logging + +logger = logging.getLogger("osmclient") + + +@click.command(name="ns-list", short_help="list all NS instances") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the NS instances matching the filter.", +) +@click.option( + "--long", + is_flag=True, + help="get more details of the NS (project, vim, deployment status, configuration status.", +) +@click.pass_context +def ns_list(ctx, filter, long): + """list all NS instances + + \b + Options: + --filter filterExpr Restricts the list to the NS instances matching the filter + + \b + filterExpr consists of one or more strings formatted according to "simpleFilterExpr", + concatenated using the "&" character: + + \b + filterExpr := ["&"]* + simpleFilterExpr := ["."]*["."]"="[","]* + op := "eq" | "neq" | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont" + attrName := string + value := scalar value + + \b + where: + * zero or more occurrences + ? zero or one occurrence + [] grouping of expressions to be used with ? and * + "" quotation marks for marking string constants + <> name separator + + \b + "AttrName" is the name of one attribute in the data type that defines the representation + of the resource. The dot (".") character in "simpleFilterExpr" allows concatenation of + entries to filter by attributes deeper in the hierarchy of a structured document. + "Op" stands for the comparison operator. If the expression has concatenated + entries, it means that the operator "op" is applied to the attribute addressed by the last + entry included in the concatenation. All simple filter expressions are combined + by the "AND" logical operator. In a concatenation of entries in a , + the rightmost "attrName" entry in a "simpleFilterExpr" is called "leaf attribute". The + concatenation of all "attrName" entries except the leaf attribute is called the "attribute + prefix". If an attribute referenced in an expression is an array, an object that contains a + corresponding array shall be considered to match the expression if any of the elements in the + array matches all expressions that have the same attribute prefix. + + \b + Filter examples: + --filter admin-status=ENABLED + --filter nsd-ref= + --filter nsd.vendor= + --filter nsd.vendor=&nsd-ref= + --filter nsd.constituent-vnfd.vnfd-id-ref= + """ + + def summarize_deployment_status(status_dict): + # Nets + summary = "" + if not status_dict: + return summary + n_nets = 0 + status_nets = {} + net_list = status_dict.get("nets", []) + for net in net_list: + n_nets += 1 + if net["status"] not in status_nets: + status_nets[net["status"]] = 1 + else: + status_nets[net["status"]] += 1 + message = "Nets: " + for k, v in status_nets.items(): + message += "{}:{},".format(k, v) + message += "TOTAL:{}".format(n_nets) + summary += "{}".format(message) + # VMs and VNFs + n_vms = 0 + status_vms = {} + status_vnfs = {} + vnf_list = status_dict["vnfs"] + for vnf in vnf_list: + member_vnf_index = vnf["member_vnf_index"] + if member_vnf_index not in status_vnfs: + status_vnfs[member_vnf_index] = {} + for vm in vnf["vms"]: + n_vms += 1 + if vm["status"] not in status_vms: + status_vms[vm["status"]] = 1 + else: + status_vms[vm["status"]] += 1 + if vm["status"] not in status_vnfs[member_vnf_index]: + status_vnfs[member_vnf_index][vm["status"]] = 1 + else: + status_vnfs[member_vnf_index][vm["status"]] += 1 + message = "VMs: " + for k, v in status_vms.items(): + message += "{}:{},".format(k, v) + message += "TOTAL:{}".format(n_vms) + summary += "\n{}".format(message) + summary += "\nNFs:" + for k, v in status_vnfs.items(): + total = 0 + message = "\n {} VMs: ".format(k) + for k2, v2 in v.items(): + message += "{}:{},".format(k2, v2) + total += v2 + message += "TOTAL:{}".format(total) + summary += message + return summary + + def summarize_config_status(ee_list): + summary = "" + if not ee_list: + return summary + n_ee = 0 + status_ee = {} + for ee in ee_list: + n_ee += 1 + if ee["elementType"] not in status_ee: + status_ee[ee["elementType"]] = {} + status_ee[ee["elementType"]][ee["status"]] = 1 + continue + if ee["status"] in status_ee[ee["elementType"]]: + status_ee[ee["elementType"]][ee["status"]] += 1 + else: + status_ee[ee["elementType"]][ee["status"]] = 1 + for elementType in ["KDU", "VDU", "PDU", "VNF", "NS"]: + if elementType in status_ee: + message = "" + total = 0 + for k, v in status_ee[elementType].items(): + message += "{}:{},".format(k, v) + total += v + message += "TOTAL:{}\n".format(total) + summary += "{}: {}".format(elementType, message) + summary += "TOTAL Exec. Env.: {}".format(n_ee) + return summary + + logger.debug("") + if filter: + utils.check_client_version(ctx.obj, "--filter") + filter = "&".join(filter) + resp = ctx.obj.ns.list(filter) + else: + resp = ctx.obj.ns.list() + if long: + table = PrettyTable( + [ + "ns instance name", + "id", + "date", + "ns state", + "current operation", + "error details", + "project", + "vim (inst param)", + "deployment status", + "configuration status", + ] + ) + project_list = ctx.obj.project.list() + try: + vim_list = ctx.obj.vim.list() + except Exception: + vim_list = [] + else: + table = PrettyTable( + [ + "ns instance name", + "id", + "date", + "ns state", + "current operation", + "error details", + ] + ) + for ns in resp: + fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__ + if fullclassname == "osmclient.sol005.client.Client": + nsr = ns + logger.debug("NS info: {}".format(nsr)) + nsr_name = nsr["name"] + nsr_id = nsr["_id"] + date = datetime.fromtimestamp(nsr["create-time"]).strftime( + "%Y-%m-%dT%H:%M:%S" + ) + ns_state = nsr.get("nsState", nsr["_admin"]["nsState"]) + if long: + deployment_status = summarize_deployment_status( + nsr.get("deploymentStatus") + ) + config_status = summarize_config_status(nsr.get("configurationStatus")) + project_id, project_name = utils.get_project(project_list, nsr) + # project = '{} ({})'.format(project_name, project_id) + project = project_name + vim_id = nsr.get("datacenter") + vim_name = utils.get_vim_name(vim_list, vim_id) + + # vim = '{} ({})'.format(vim_name, vim_id) + vim = vim_name + if "currentOperation" in nsr: + current_operation = "{} ({})".format( + nsr["currentOperation"], nsr["currentOperationID"] + ) + else: + current_operation = "{} ({})".format( + nsr["_admin"].get("current-operation", "-"), + nsr["_admin"]["nslcmop"], + ) + error_details = "N/A" + if ( + ns_state == "BROKEN" + or ns_state == "DEGRADED" + or ("currentOperation" not in nsr and nsr.get("errorDescription")) + ): + error_details = "{}\nDetail: {}".format( + nsr["errorDescription"], nsr["errorDetail"] + ) + else: + nsopdata = ctx.obj.ns.get_opdata(ns["id"]) + nsr = nsopdata["nsr:nsr"] + nsr_name = nsr["name-ref"] + nsr_id = nsr["ns-instance-config-ref"] + date = "-" + project = "-" + deployment_status = ( + nsr["operational-status"] + if "operational-status" in nsr + else "Not found" + ) + ns_state = deployment_status + config_status = nsr.get("config-status", "Not found") + current_operation = "Unknown" + error_details = nsr.get("detailed-status", "Not found") + if config_status == "config_not_needed": + config_status = "configured (no charms)" + + if long: + table.add_row( + [ + nsr_name, + nsr_id, + date, + ns_state, + current_operation, + utils.wrap_text(text=error_details, width=40), + project, + vim, + deployment_status, + config_status, + ] + ) + else: + table.add_row( + [ + nsr_name, + nsr_id, + date, + ns_state, + current_operation, + utils.wrap_text(text=error_details, width=40), + ] + ) + table.align = "l" + print(table) + print('To get the history of all operations over a NS, run "osm ns-op-list NS_ID"') + print( + 'For more details on the current operation, run "osm ns-op-show OPERATION_ID"' + ) + + +@click.command(name="ns-show", short_help="shows the info of a NS instance") +@click.argument("name") +@click.option("--literal", is_flag=True, help="print literally, no pretty table") +@click.option( + "--filter", + multiple=True, + help="restricts the information to the fields in the filter", +) +@click.pass_context +def ns_show(ctx, name, literal, filter): + """shows the info of a NS instance + + NAME: name or ID of the NS instance + """ + logger.debug("") + ns = ctx.obj.ns.get(name) + + if literal: + print(yaml.safe_dump(ns, indent=4, default_flow_style=False)) + return + + table = PrettyTable(["field", "value"]) + + for k, v in list(ns.items()): + if not filter or k in filter: + table.add_row([k, utils.wrap_text(text=json.dumps(v, indent=2), width=100)]) + + fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__ + if fullclassname != "osmclient.sol005.client.Client": + nsopdata = ctx.obj.ns.get_opdata(ns["id"]) + nsr_optdata = nsopdata["nsr:nsr"] + for k, v in list(nsr_optdata.items()): + if not filter or k in filter: + table.add_row([k, utils.wrap_text(json.dumps(v, indent=2), width=100)]) + table.align = "l" + print(table) + + +@click.command(name="ns-create", short_help="creates a new Network Service instance") +@click.option("--ns_name", prompt=True, help="name of the NS instance") +@click.option("--nsd_name", prompt=True, help="name of the NS descriptor") +@click.option( + "--vim_account", + prompt=True, + help="default VIM account id or name for the deployment", +) +@click.option("--admin_status", default="ENABLED", help="administration status") +@click.option( + "--ssh_keys", + default=None, + help="comma separated list of public key files to inject to vnfs", +) +@click.option("--config", default=None, help="ns specific yaml configuration") +@click.option("--config_file", default=None, help="ns specific yaml configuration file") +@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("--timeout", default=None, help="ns deployment timeout") +@click.pass_context +def ns_create( + ctx, + nsd_name, + ns_name, + vim_account, + admin_status, + ssh_keys, + config, + config_file, + wait, + timeout, +): + """creates a new NS instance""" + logger.debug("") + if config_file: + utils.check_client_version(ctx.obj, "--config_file") + if config: + raise ClientException( + '"--config" option is incompatible with "--config_file" option' + ) + with open(config_file, "r") as cf: + config = cf.read() + ctx.obj.ns.create( + nsd_name, + ns_name, + config=config, + ssh_keys=ssh_keys, + account=vim_account, + wait=wait, + timeout=timeout, + ) + + +@click.command(name="ns-delete", short_help="deletes a NS instance") +@click.argument("name") +@click.option( + "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" +) +@click.option( + "--config", + default=None, + help="specific yaml configuration for the termination, e.g. '{autoremove: False, timeout_ns_terminate: " + "600, skip_terminate_primitives: True}'", +) +@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 ns_delete(ctx, name, force, config, wait): + """deletes a NS instance + + NAME: name or ID of the NS instance to be deleted + """ + logger.debug("") + if not force: + ctx.obj.ns.delete(name, config=config, wait=wait) + else: + utils.check_client_version(ctx.obj, "--force") + ctx.obj.ns.delete(name, force, config=config, wait=wait) diff --git a/osmclient/cli_commands/nslcm.py b/osmclient/cli_commands/nslcm.py new file mode 100755 index 0000000..355422c --- /dev/null +++ b/osmclient/cli_commands/nslcm.py @@ -0,0 +1,452 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.common.exceptions import ClientException +from osmclient.common.utils import validate_uuid4 +from osmclient.cli_commands import utils +import yaml +import logging + +logger = logging.getLogger("osmclient") + + +@click.command( + name="ns-action", short_help="executes an action/primitive over a NS instance" +) +@click.argument("ns_name") +@click.option( + "--vnf_name", + default=None, + help="member-vnf-index if the target is a vnf instead of a ns)", +) +@click.option("--kdu_name", default=None, help="kdu-name if the target is a kdu)") +@click.option("--vdu_id", default=None, help="vdu-id if the target is a vdu") +@click.option( + "--vdu_count", default=None, type=int, help="number of vdu instance of this vdu_id" +) +@click.option("--action_name", prompt=True, help="action name") +@click.option("--params", default=None, help="action params in YAML/JSON inline string") +@click.option("--params_file", default=None, help="YAML/JSON file with action params") +@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 ns_action( + ctx, + ns_name, + vnf_name, + kdu_name, + vdu_id, + vdu_count, + action_name, + params, + params_file, + timeout, + wait, +): + """executes an action/primitive over a NS instance + + NS_NAME: name or ID of the NS instance + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + op_data = {} + if vnf_name: + op_data["member_vnf_index"] = vnf_name + if kdu_name: + op_data["kdu_name"] = kdu_name + if vdu_id: + op_data["vdu_id"] = vdu_id + if vdu_count is not None: + op_data["vdu_count_index"] = vdu_count + if timeout: + op_data["timeout_ns_action"] = timeout + op_data["primitive"] = action_name + if params_file: + with open(params_file, "r") as pf: + params = pf.read() + if params: + op_data["primitive_params"] = yaml.safe_load(params) + else: + op_data["primitive_params"] = {} + print(ctx.obj.ns.exec_op(ns_name, op_name="action", op_data=op_data, wait=wait)) + + +@click.command( + name="vnf-scale", short_help="executes a VNF scale (adding/removing VDUs)" +) +@click.argument("ns_name") +@click.argument("vnf_name") +@click.option( + "--scaling-group", prompt=True, help="scaling-group-descriptor name to use" +) +@click.option( + "--scale-in", default=False, is_flag=True, help="performs a scale in operation" +) +@click.option( + "--scale-out", + default=False, + is_flag=True, + help="performs a scale out operation (by default)", +) +@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 vnf_scale( + ctx, ns_name, vnf_name, scaling_group, scale_in, scale_out, timeout, wait +): + """ + Executes a VNF scale (adding/removing VDUs) + + \b + NS_NAME: name or ID of the NS instance. + VNF_NAME: member-vnf-index in the NS to be scaled. + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + if not scale_in and not scale_out: + scale_out = True + ctx.obj.ns.scale_vnf( + ns_name, vnf_name, scaling_group, scale_in, scale_out, wait, timeout + ) + + +@click.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 ns_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) + + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.ns.update(ns_name, op_data, wait=wait) + + +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 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: + 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("") + # 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 + + +@click.command( + 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.argument( + "args", + nargs=-1, + type=click.UNPROCESSED, + callback=process_ns_heal_params, +) +@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 ns_heal(ctx, ns_name, args, heal_params, 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"] + 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)}") + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.ns.heal(ns_name, heal_dict, wait, timeout) + + +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 + + +@click.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.argument( + "args", + nargs=-1, + type=click.UNPROCESSED, + callback=process_vnf_heal_params, +) +@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, + args, + heal_params, + 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["healVnfData"][-1]["vnfInstanceId"] = vnf_name + logger.debug(f"Heal dict:\n{yaml.safe_dump(heal_dict)}") + utils.check_client_version(ctx.obj, ctx.command.name) + vnfr = ctx.obj.vnf.get(vnf_name) + ns_id = vnfr["nsr-id-ref"] + ctx.obj.ns.heal(ns_id, heal_dict, wait, timeout) diff --git a/osmclient/cli_commands/nslcm_ops.py b/osmclient/cli_commands/nslcm_ops.py new file mode 100755 index 0000000..46197d0 --- /dev/null +++ b/osmclient/cli_commands/nslcm_ops.py @@ -0,0 +1,151 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.cli_commands import utils +from prettytable import PrettyTable +import yaml +import json +from datetime import datetime +import logging + +logger = logging.getLogger("osmclient") + + +@click.command( + name="ns-op-list", short_help="shows the history of operations over a NS instance" +) +@click.argument("name") +@click.option( + "--long", is_flag=True, help="get more details of the NS operation (date, )." +) +@click.pass_context +def ns_op_list(ctx, name, long): + """shows the history of operations over a NS instance + + NAME: name or ID of the NS instance + """ + + def formatParams(params): + if params["lcmOperationType"] == "instantiate": + params.pop("nsDescription") + params.pop("nsName") + params.pop("nsdId") + params.pop("nsr_id") + elif params["lcmOperationType"] == "action": + params.pop("primitive") + params.pop("lcmOperationType") + params.pop("nsInstanceId") + return params + + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + resp = ctx.obj.ns.list_op(name) + + if long: + table = PrettyTable( + [ + "id", + "operation", + "action_name", + "operation_params", + "status", + "date", + "last update", + "detail", + ] + ) + else: + table = PrettyTable( + ["id", "operation", "action_name", "status", "date", "detail"] + ) + + # print(yaml.safe_dump(resp)) + for op in resp: + action_name = "N/A" + if op["lcmOperationType"] == "action": + action_name = op["operationParams"]["primitive"] + detail = "-" + if op["operationState"] == "PROCESSING": + if op.get("queuePosition") is not None and op.get("queuePosition") > 0: + detail = "In queue. Current position: {}".format(op["queuePosition"]) + elif op["lcmOperationType"] in ("instantiate", "terminate"): + if op["stage"]: + detail = op["stage"] + elif op["operationState"] in ("FAILED", "FAILED_TEMP"): + detail = op.get("errorMessage", "-") + date = datetime.fromtimestamp(op["startTime"]).strftime("%Y-%m-%dT%H:%M:%S") + last_update = datetime.fromtimestamp(op["statusEnteredTime"]).strftime( + "%Y-%m-%dT%H:%M:%S" + ) + if long: + table.add_row( + [ + op["id"], + op["lcmOperationType"], + action_name, + utils.wrap_text( + text=json.dumps(formatParams(op["operationParams"]), indent=2), + width=50, + ), + op["operationState"], + date, + last_update, + utils.wrap_text(text=detail, width=50), + ] + ) + else: + table.add_row( + [ + op["id"], + op["lcmOperationType"], + action_name, + op["operationState"], + date, + utils.wrap_text(text=detail or "", width=50), + ] + ) + table.align = "l" + print(table) + + +@click.command(name="ns-op-show", short_help="shows the info of a NS operation") +@click.argument("id") +@click.option( + "--filter", + 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 ns_op_show(ctx, id, filter, literal): + """shows the detailed info of a NS operation + + ID: operation identifier + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + op_info = ctx.obj.ns.get_op(id) + + if literal: + print(yaml.safe_dump(op_info, indent=4, default_flow_style=False)) + return + + table = PrettyTable(["field", "value"]) + for k, v in list(op_info.items()): + if not filter or k in filter: + table.add_row([k, utils.wrap_text(json.dumps(v, indent=2), 100)]) + table.align = "l" + print(table) diff --git a/osmclient/cli_commands/nspkg.py b/osmclient/cli_commands/nspkg.py new file mode 100755 index 0000000..44f0a4f --- /dev/null +++ b/osmclient/cli_commands/nspkg.py @@ -0,0 +1,338 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.cli_commands import utils +from prettytable import PrettyTable +import yaml +import json +from datetime import datetime +import logging + +logger = logging.getLogger("osmclient") + + +def nsd_list(ctx, filter, long): + logger.debug("") + if filter: + utils.check_client_version(ctx.obj, "--filter") + filter = "&".join(filter) + resp = ctx.obj.nsd.list(filter) + else: + resp = ctx.obj.nsd.list() + # print(yaml.safe_dump(resp)) + fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__ + if fullclassname == "osmclient.sol005.client.Client": + if long: + table = PrettyTable( + [ + "nsd name", + "id", + "onboarding state", + "operational state", + "usage state", + "date", + "last update", + ] + ) + else: + table = PrettyTable(["nsd name", "id"]) + for nsd in resp: + name = nsd.get("id", "-") + if long: + onb_state = nsd["_admin"].get("onboardingState", "-") + op_state = nsd["_admin"].get("operationalState", "-") + usage_state = nsd["_admin"].get("usageState", "-") + date = datetime.fromtimestamp(nsd["_admin"]["created"]).strftime( + "%Y-%m-%dT%H:%M:%S" + ) + last_update = datetime.fromtimestamp( + nsd["_admin"]["modified"] + ).strftime("%Y-%m-%dT%H:%M:%S") + table.add_row( + [ + name, + nsd["_id"], + onb_state, + op_state, + usage_state, + date, + last_update, + ] + ) + else: + table.add_row([name, nsd["_id"]]) + else: + table = PrettyTable(["nsd name", "id"]) + for nsd in resp: + table.add_row([nsd["name"], nsd["id"]]) + table.align = "l" + print(table) + + +@click.command(name="nsd-list", short_help="list all NS packages") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the NSD/NSpkg matching the filter", +) +@click.option("--long", is_flag=True, help="get more details") +@click.pass_context +def nsd_list1(ctx, filter, long): + """list all NSD/NS pkg in the system""" + logger.debug("") + nsd_list(ctx, filter, long) + + +@click.command(name="nspkg-list", short_help="list all NS packages") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the NSD/NSpkg matching the filter", +) +@click.option("--long", is_flag=True, help="get more details") +@click.pass_context +def nsd_list2(ctx, filter, long): + """list all NS packages""" + logger.debug("") + nsd_list(ctx, filter, long) + + +def nsd_show(ctx, name, literal): + logger.debug("") + resp = ctx.obj.nsd.get(name) + # resp = ctx.obj.nsd.get_individual(name) + + if literal: + print(yaml.safe_dump(resp, indent=4, default_flow_style=False)) + return + + table = PrettyTable(["field", "value"]) + for k, v in list(resp.items()): + table.add_row([k, utils.wrap_text(text=json.dumps(v, indent=2), width=100)]) + table.align = "l" + print(table) + + +@click.command(name="nsd-show", short_help="shows the details of a NS package") +@click.option("--literal", is_flag=True, help="print literally, no pretty table") +@click.argument("name") +@click.pass_context +def nsd_show1(ctx, name, literal): + """shows the content of a NSD + + NAME: name or ID of the NSD/NSpkg + """ + logger.debug("") + nsd_show(ctx, name, literal) + + +@click.command(name="nspkg-show", short_help="shows the details of a NS package") +@click.option("--literal", is_flag=True, help="print literally, no pretty table") +@click.argument("name") +@click.pass_context +def nsd_show2(ctx, name, literal): + """shows the content of a NSD + + NAME: name or ID of the NSD/NSpkg + """ + logger.debug("") + nsd_show(ctx, name, literal) + + +def nsd_create(ctx, filename, overwrite, skip_charm_build, repo, vendor, version): + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + if repo: + filename = ctx.obj.osmrepo.get_pkg("ns", filename, repo, vendor, version) + ctx.obj.nsd.create(filename, overwrite=overwrite, skip_charm_build=skip_charm_build) + + +@click.command(name="nsd-create", short_help="creates a new NSD/NSpkg") +@click.argument("filename") +@click.option( + "--overwrite", + "overwrite", + default=None, # hidden=True, + help="Deprecated. Use override", +) +@click.option( + "--override", + "overwrite", + default=None, + help="overrides fields in descriptor, format: " + '"key1.key2...=value[;key3...=value;...]"', +) +@click.option( + "--skip-charm-build", + default=False, + is_flag=True, + help="The charm will not be compiled, it is assumed to already exist", +) +@click.option("--repo", default=None, help="[repository]: Repository name") +@click.option("--vendor", default=None, help="[repository]: filter by vendor]") +@click.option( + "--version", + default="latest", + help="[repository]: filter by version. Default: latest", +) +@click.pass_context +def nsd_create1(ctx, filename, overwrite, skip_charm_build, repo, vendor, version): + """onboards a new NSpkg (alias of nspkg-create) (TO BE DEPRECATED) + + \b + FILENAME: NF Package tar.gz file, NF Descriptor YAML file or NF Package folder + If FILENAME is a file (NF Package tar.gz or NF Descriptor YAML), it is onboarded. + If FILENAME is an NF Package folder, it is built and then onboarded. + """ + logger.debug("") + nsd_create( + ctx, + filename, + overwrite=overwrite, + skip_charm_build=skip_charm_build, + repo=repo, + vendor=vendor, + version=version, + ) + + +@click.command(name="nspkg-create", short_help="creates a new NSD/NSpkg") +@click.argument("filename") +@click.option( + "--overwrite", + "overwrite", + default=None, # hidden=True, + help="Deprecated. Use override", +) +@click.option( + "--override", + "overwrite", + default=None, + help="overrides fields in descriptor, format: " + '"key1.key2...=value[;key3...=value;...]"', +) +@click.option( + "--skip-charm-build", + default=False, + is_flag=True, + help="The charm will not be compiled, it is assumed to already exist", +) +@click.option("--repo", default=None, help="[repository]: Repository name") +@click.option("--vendor", default=None, help="[repository]: filter by vendor]") +@click.option( + "--version", + default="latest", + help="[repository]: filter by version. Default: latest", +) +@click.pass_context +def nsd_create2(ctx, filename, overwrite, skip_charm_build, repo, vendor, version): + """onboards a new NSpkg + \b + FILENAME: NF Package tar.gz file, NF Descriptor YAML file or NF Package folder + If FILENAME is a file (NF Package tar.gz or NF Descriptor YAML), it is onboarded. + If FILENAME is an NF Package folder, it is built and then onboarded. + """ + logger.debug("") + nsd_create( + ctx, + filename, + overwrite=overwrite, + skip_charm_build=skip_charm_build, + repo=repo, + vendor=vendor, + version=version, + ) + + +def nsd_update(ctx, name, content): + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.nsd.update(name, content) + + +@click.command(name="nsd-update", short_help="updates a NSD/NSpkg") +@click.argument("name") +@click.option( + "--content", + default=None, + help="filename with the NSD/NSpkg replacing the current one", +) +@click.pass_context +def nsd_update1(ctx, name, content): + """updates a NSD/NSpkg + + NAME: name or ID of the NSD/NSpkg + """ + logger.debug("") + nsd_update(ctx, name, content) + + +@click.command(name="nspkg-update", short_help="updates a NSD/NSpkg") +@click.argument("name") +@click.option( + "--content", + default=None, + help="filename with the NSD/NSpkg replacing the current one", +) +@click.pass_context +def nsd_update2(ctx, name, content): + """updates a NSD/NSpkg + + NAME: name or ID of the NSD/NSpkg + """ + logger.debug("") + nsd_update(ctx, name, content) + + +def nsd_delete(ctx, name, force): + logger.debug("") + if not force: + ctx.obj.nsd.delete(name) + else: + utils.check_client_version(ctx.obj, "--force") + ctx.obj.nsd.delete(name, force) + + +@click.command(name="nsd-delete", short_help="deletes a NSD/NSpkg") +@click.argument("name") +@click.option( + "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" +) +@click.pass_context +def nsd_delete1(ctx, name, force): + """deletes a NSD/NSpkg + + NAME: name or ID of the NSD/NSpkg to be deleted + """ + logger.debug("") + nsd_delete(ctx, name, force) + + +@click.command(name="nspkg-delete", short_help="deletes a NSD/NSpkg") +@click.argument("name") +@click.option( + "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" +) +@click.pass_context +def nsd_delete2(ctx, name, force): + """deletes a NSD/NSpkg + + NAME: name or ID of the NSD/NSpkg to be deleted + """ + logger.debug("") + nsd_delete(ctx, name, force) diff --git a/osmclient/cli_commands/other.py b/osmclient/cli_commands/other.py new file mode 100755 index 0000000..b51a868 --- /dev/null +++ b/osmclient/cli_commands/other.py @@ -0,0 +1,32 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.cli_commands import utils +import pkg_resources +import logging + +logger = logging.getLogger("osmclient") + + +@click.command(name="version", short_help="shows client and server versions") +@click.pass_context +def get_version(ctx): + """shows client and server versions""" + utils.check_client_version(ctx.obj, "version") + print("Server version: {}".format(ctx.obj.get_version())) + print( + "Client version: {}".format(pkg_resources.get_distribution("osmclient").version) + ) diff --git a/osmclient/cli_commands/packages.py b/osmclient/cli_commands/packages.py new file mode 100755 index 0000000..4b62743 --- /dev/null +++ b/osmclient/cli_commands/packages.py @@ -0,0 +1,302 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.cli_commands import utils +from prettytable import PrettyTable +import logging + +logger = logging.getLogger("osmclient") + + +@click.command( + name="package-create", short_help="Create empty VNF or NS package structure" +) +@click.argument("package-type") +@click.argument("package-name") +@click.option( + "--base-directory", + default=".", + help=('(NS/VNF/NST) Set the location for package creation. Default: "."'), +) +@click.option( + "--image", + default="image-name", + help='(VNF) Set the name of the vdu image. Default "image-name"', +) +@click.option( + "--vdus", default=1, help="(VNF) Set the number of vdus in a VNF. Default 1" +) +@click.option( + "--vcpu", default=1, help="(VNF) Set the number of virtual CPUs in a vdu. Default 1" +) +@click.option( + "--memory", + default=1024, + help="(VNF) Set the memory size (MB) of the vdu. Default 1024", +) +@click.option( + "--storage", default=10, help="(VNF) Set the disk size (GB) of the vdu. Default 10" +) +@click.option( + "--interfaces", + default=0, + help="(VNF) Set the number of additional interfaces apart from the management interface. Default 0", +) +@click.option( + "--vendor", default="OSM", help='(NS/VNF) Set the descriptor vendor. Default "OSM"' +) +@click.option( + "--override", + default=False, + is_flag=True, + help="(NS/VNF/NST) Flag for overriding the package if exists.", +) +@click.option( + "--detailed", + is_flag=True, + default=False, + help="(NS/VNF/NST) Flag for generating descriptor .yaml with all possible commented options", +) +@click.option( + "--netslice-subnets", default=1, help="(NST) Number of netslice subnets. Default 1" +) +@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, + package_type, + base_directory, + package_name, + override, + image, + vdus, + vcpu, + memory, + storage, + interfaces, + vendor, + detailed, + netslice_subnets, + netslice_vlds, + old, +): + """ + Creates an OSM NS, VNF, NST package + + \b + PACKAGE_TYPE: Package to be created: NS, VNF or NST. + PACKAGE_NAME: Name of the package to create the folder with the content. + """ + + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + print( + "Creating the {} structure: {}/{}".format( + package_type.upper(), base_directory, package_name + ) + ) + resp = ctx.obj.package_tool.create( + package_type, + base_directory, + package_name, + override=override, + image=image, + vdus=vdus, + vcpu=vcpu, + memory=memory, + storage=storage, + interfaces=interfaces, + vendor=vendor, + detailed=detailed, + netslice_subnets=netslice_subnets, + netslice_vlds=netslice_vlds, + old=old, + ) + print(resp) + + +@click.command( + name="package-validate", short_help="Validate descriptors given a base directory" +) +@click.argument("base-directory", default=".", required=False) +@click.option( + "--recursive/--no-recursive", + default=True, + help="The activated recursive option will validate the yaml files" + " within the indicated directory and in its subdirectories", +) +@click.option( + "--old", + is_flag=True, + default=False, + help="Validates also the descriptors using the previous OSM format (pre SOL006)", +) +@click.pass_context +def package_validate(ctx, base_directory, recursive, old): + """ + Validate descriptors given a base directory. + + \b + BASE_DIRECTORY: Base folder for NS, VNF or NST package. + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + results = ctx.obj.package_tool.validate(base_directory, recursive, old) + table = PrettyTable() + table.field_names = ["TYPE", "PATH", "VALID", "ERROR"] + # Print the dictionary generated by the validation function + for result in results: + table.add_row( + [result["type"], result["path"], result["valid"], result["error"]] + ) + table.sortby = "VALID" + table.align["PATH"] = "l" + table.align["TYPE"] = "l" + table.align["ERROR"] = "l" + print(table) + + +@click.command( + name="package-translate", short_help="Translate descriptors given a base directory" +) +@click.argument("base-directory", default=".", required=False) +@click.option( + "--recursive/--no-recursive", + default=True, + help="The activated recursive option will translate the yaml files" + " within the indicated directory and in its subdirectories", +) +@click.option( + "--dryrun", + is_flag=True, + default=False, + help="Do not translate yet, only make a dry-run to test translation", +) +@click.pass_context +def package_translate(ctx, base_directory, recursive, dryrun): + """ + Translate descriptors given a base directory. + + \b + BASE_DIRECTORY: Stub folder for NS, VNF or NST package. + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + results = ctx.obj.package_tool.translate(base_directory, recursive, dryrun) + table = PrettyTable() + table.field_names = [ + "CURRENT TYPE", + "NEW TYPE", + "PATH", + "VALID", + "TRANSLATED", + "ERROR", + ] + # Print the dictionary generated by the validation function + for result in results: + table.add_row( + [ + result["current type"], + result["new type"], + result["path"], + result["valid"], + result["translated"], + result["error"], + ] + ) + table.sortby = "TRANSLATED" + table.align["PATH"] = "l" + table.align["TYPE"] = "l" + table.align["ERROR"] = "l" + print(table) + + +@click.command(name="package-build", short_help="Build the tar.gz of the package") +@click.argument("package-folder") +@click.option( + "--skip-validation", default=False, is_flag=True, help="skip package validation" +) +@click.option( + "--skip-charm-build", + default=False, + is_flag=True, + help="the charm will not be compiled, it is assumed to already exist", +) +@click.pass_context +def package_build(ctx, package_folder, skip_validation, skip_charm_build): + """ + Build the package NS, VNF given the package_folder. + + \b + PACKAGE_FOLDER: Folder of the NS, VNF or NST to be packaged + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + results = ctx.obj.package_tool.build( + package_folder, + skip_validation=skip_validation, + skip_charm_build=skip_charm_build, + ) + print(results) + + +@click.command( + name="descriptor-translate", + short_help="Translate input descriptor file from Rel EIGHT OSM descriptors to SOL006 and prints in standard output", +) +@click.argument("descriptor-file", required=True) +@click.pass_context +def descriptor_translate(ctx, descriptor_file): + """ + Translate input descriptor. + + \b + DESCRIPTOR_FILE: Descriptor file for NS, VNF or Network Slice. + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + result = ctx.obj.package_tool.descriptor_translate(descriptor_file) + print(result) + + +# TODO: check if this command should be here. It is more related to nspkg and nfpkg +@click.command(name="upload-package", short_help="uploads a VNF package or NS package") +@click.argument("filename") +@click.option( + "--skip-charm-build", + default=False, + is_flag=True, + help="the charm will not be compiled, it is assumed to already exist", +) +@click.pass_context +def upload_package(ctx, filename, skip_charm_build): + """uploads a vnf package or ns package + + filename: vnf or ns package folder, or vnf or ns package file (tar.gz) + """ + logger.debug("") + ctx.obj.package.upload(filename, skip_charm_build=skip_charm_build) + fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__ + if fullclassname != "osmclient.sol005.client.Client": + ctx.obj.package.wait_for_upload(filename) diff --git a/osmclient/cli_commands/pdus.py b/osmclient/cli_commands/pdus.py new file mode 100755 index 0000000..3ce5572 --- /dev/null +++ b/osmclient/cli_commands/pdus.py @@ -0,0 +1,234 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.common.exceptions import ClientException +from osmclient.cli_commands import utils +from prettytable import PrettyTable +import yaml +import json +import logging + +logger = logging.getLogger("osmclient") + + +@click.command(name="pdu-list", short_help="list all Physical Deployment Units (PDU)") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the Physical Deployment Units matching the filter", +) +@click.pass_context +def pdu_list(ctx, filter): + """list all Physical Deployment Units (PDU)""" + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + if filter: + filter = "&".join(filter) + resp = ctx.obj.pdu.list(filter) + table = PrettyTable(["pdu name", "id", "type", "mgmt ip address"]) + for pdu in resp: + pdu_name = pdu["name"] + pdu_id = pdu["_id"] + pdu_type = pdu["type"] + pdu_ipaddress = "None" + for iface in pdu["interfaces"]: + if iface["mgmt"]: + pdu_ipaddress = iface["ip-address"] + break + table.add_row([pdu_name, pdu_id, pdu_type, pdu_ipaddress]) + table.align = "l" + print(table) + + +@click.command( + name="pdu-show", short_help="shows the content of a Physical Deployment Unit (PDU)" +) +@click.argument("name") +@click.option("--literal", is_flag=True, help="print literally, no pretty table") +@click.option( + "--filter", + multiple=True, + help="restricts the information to the fields in the filter", +) +@click.pass_context +def pdu_show(ctx, name, literal, filter): + """shows the content of a Physical Deployment Unit (PDU) + + NAME: name or ID of the PDU + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + pdu = ctx.obj.pdu.get(name) + + if literal: + print(yaml.safe_dump(pdu, indent=4, default_flow_style=False)) + return + + table = PrettyTable(["field", "value"]) + + for k, v in list(pdu.items()): + if not filter or k in filter: + table.add_row([k, json.dumps(v, indent=2)]) + + table.align = "l" + print(table) + + +@click.command( + name="pdu-create", short_help="adds a new Physical Deployment Unit to the catalog" +) +@click.option("--name", help="name of 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_create( + ctx, name, pdu_type, interface, description, vim_account, descriptor_file +): + """creates a new Physical Deployment Unit (PDU)""" + logger.debug("") + + utils.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) + + +@click.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("") + + utils.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 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()) + if name: + pdu["name"] = name + if pdu_type: + pdu["type"] = pdu_type + if description: + pdu["description"] = description + if vim_account: + pdu["vim_accounts"] = vim_account + if interface: + ifaces_list = [] + for iface in interface: + new_iface = {k: v for k, v in [i.split("=") for i in iface.split(",")]} + new_iface["mgmt"] = new_iface.get("mgmt", "false").lower() == "true" + ifaces_list.append(new_iface) + pdu["interfaces"] = ifaces_list + return pdu + + +@click.command(name="pdu-delete", short_help="deletes a Physical Deployment Unit (PDU)") +@click.argument("name") +@click.option( + "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" +) +@click.pass_context +def pdu_delete(ctx, name, force): + """deletes a Physical Deployment Unit (PDU) + + NAME: name or ID of the PDU to be deleted + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.pdu.delete(name, force) diff --git a/osmclient/cli_commands/rbac.py b/osmclient/cli_commands/rbac.py new file mode 100755 index 0000000..57809bf --- /dev/null +++ b/osmclient/cli_commands/rbac.py @@ -0,0 +1,476 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.common.exceptions import ClientException +from osmclient.cli_commands import utils +from prettytable import PrettyTable +import json +import logging + +logger = logging.getLogger("osmclient") + + +############################## +# Role Management Operations # +############################## + + +@click.command(name="role-create", short_help="creates a new role") +@click.argument("name") +@click.option("--permissions", default=None, help="role permissions using a dictionary") +@click.pass_context +def role_create(ctx, name, permissions): + """ + Creates a new role. + + \b + NAME: Name or ID of the role. + DEFINITION: Definition of grant/denial of access to resources. + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.role.create(name, permissions) + + +@click.command(name="role-update", short_help="updates a role") +@click.argument("name") +@click.option("--set-name", default=None, help="change name of rle") +@click.option( + "--add", + default=None, + help="yaml format dictionary with permission: True/False to access grant/denial", +) +@click.option("--remove", default=None, help="yaml format list to remove a permission") +@click.pass_context +def role_update(ctx, name, set_name, add, remove): + """ + Updates a role. + + \b + NAME: Name or ID of the role. + DEFINITION: Definition overwrites the old definition. + ADD: Grant/denial of access to resource to add. + REMOVE: Grant/denial of access to resource to remove. + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.role.update(name, set_name, None, add, remove) + + +@click.command(name="role-delete", short_help="deletes a role") +@click.argument("name") +@click.pass_context +def role_delete(ctx, name): + """ + Deletes a role. + + \b + NAME: Name or ID of the role. + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.role.delete(name) + + +@click.command(name="role-list", short_help="list all roles") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the projects matching the filter", +) +@click.pass_context +def role_list(ctx, filter): + """ + List all roles. + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + if filter: + filter = "&".join(filter) + resp = ctx.obj.role.list(filter) + table = PrettyTable(["name", "id"]) + for role in resp: + table.add_row([role["name"], role["_id"]]) + table.align = "l" + print(table) + + +@click.command(name="role-show", short_help="show specific role") +@click.argument("name") +@click.pass_context +def role_show(ctx, name): + """ + Shows the details of a role. + + \b + NAME: Name or ID of the role. + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + resp = ctx.obj.role.get(name) + + table = PrettyTable(["key", "attribute"]) + for k, v in resp.items(): + table.add_row([k, json.dumps(v, indent=2)]) + table.align = "l" + print(table) + + +#################### +# Project mgmt operations +#################### + + +@click.command(name="project-create", short_help="creates a new project") +@click.argument("name") +# @click.option('--description', +# default='no description', +# help='human readable description') +@click.option("--domain-name", "domain_name", default=None, help="assign to a domain") +@click.option( + "--quotas", + "quotas", + multiple=True, + default=None, + help="provide quotas. Can be used several times: 'quota1=number[,quota2=number,...]'. Quotas can be one " + "of vnfds, nsds, nsts, pdus, nsrs, nsis, vim_accounts, wim_accounts, sdns, k8sclusters, k8srepos", +) +@click.pass_context +def project_create(ctx, name, domain_name, quotas): + """Creates a new project + + NAME: name of the project + DOMAIN_NAME: optional domain name for the project when keystone authentication is used + QUOTAS: set quotas for the project + """ + logger.debug("") + project = {"name": name} + if domain_name: + project["domain_name"] = domain_name + quotas_dict = _process_project_quotas(quotas) + if quotas_dict: + project["quotas"] = quotas_dict + + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.project.create(name, project) + + +def _process_project_quotas(quota_list): + quotas_dict = {} + if not quota_list: + return quotas_dict + try: + for quota in quota_list: + for single_quota in quota.split(","): + k, v = single_quota.split("=") + quotas_dict[k] = None if v in ("None", "null", "") else int(v) + except (ValueError, TypeError): + raise ClientException( + "invalid format for 'quotas'. Use 'k1=v1,v1=v2'. v must be a integer or null" + ) + return quotas_dict + + +@click.command(name="project-delete", short_help="deletes a project") +@click.argument("name") +@click.pass_context +def project_delete(ctx, name): + """deletes a project + + NAME: name or ID of the project to be deleted + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.project.delete(name) + + +@click.command(name="project-list", short_help="list all projects") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the projects matching the filter", +) +@click.pass_context +def project_list(ctx, filter): + """list all projects""" + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + if filter: + filter = "&".join(filter) + resp = ctx.obj.project.list(filter) + table = PrettyTable(["name", "id"]) + for proj in resp: + table.add_row([proj["name"], proj["_id"]]) + table.align = "l" + print(table) + + +@click.command(name="project-show", short_help="shows the details of a project") +@click.argument("name") +@click.pass_context +def project_show(ctx, name): + """shows the details of a project + + NAME: name or ID of the project + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + resp = ctx.obj.project.get(name) + + table = PrettyTable(["key", "attribute"]) + for k, v in resp.items(): + table.add_row([k, json.dumps(v, indent=2)]) + table.align = "l" + print(table) + + +@click.command( + name="project-update", short_help="updates a project (only the name can be updated)" +) +@click.argument("project") +@click.option("--name", default=None, help="new name for the project") +@click.option( + "--quotas", + "quotas", + multiple=True, + default=None, + help="change quotas. Can be used several times: 'quota1=number|empty[,quota2=...]' " + "(use empty to reset quota to default", +) +@click.pass_context +def project_update(ctx, project, name, quotas): + """ + Update a project name + + :param ctx: + :param project: id or name of the project to modify + :param name: new name for the project + :param quotas: change quotas of the project + :return: + """ + logger.debug("") + project_changes = {} + if name: + project_changes["name"] = name + quotas_dict = _process_project_quotas(quotas) + if quotas_dict: + project_changes["quotas"] = quotas_dict + + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.project.update(project, project_changes) + + +#################### +# User mgmt operations +#################### + + +@click.command(name="user-create", short_help="creates a new user") +@click.argument("username") +@click.option( + "--password", + prompt=True, + hide_input=True, + confirmation_prompt=True, + help="user password", +) +@click.option( + "--projects", + # prompt="Comma separate list of projects", + multiple=True, + callback=lambda ctx, param, value: "".join(value).split(",") + if all(len(x) == 1 for x in value) + else value, + help="list of project ids that the user belongs to", +) +@click.option( + "--project-role-mappings", + "project_role_mappings", + default=None, + multiple=True, + help="assign role(s) in a project. Can be used several times: 'project,role1[,role2,...]'", +) +@click.option("--domain-name", "domain_name", default=None, help="assign to a domain") +@click.pass_context +def user_create(ctx, username, password, projects, project_role_mappings, domain_name): + """Creates a new user + + \b + USERNAME: name of the user + PASSWORD: password of the user + PROJECTS: projects assigned to user (internal only) + PROJECT_ROLE_MAPPING: roles in projects assigned to user (keystone) + DOMAIN_NAME: optional domain name for the user when keystone authentication is used + """ + logger.debug("") + user = {} + user["username"] = username + user["password"] = password + user["projects"] = projects + user["project_role_mappings"] = project_role_mappings + if domain_name: + user["domain_name"] = domain_name + + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.user.create(username, user) + + +@click.command(name="user-update", short_help="updates user information") +@click.argument("username") +@click.option( + "--password", + # prompt=True, + # hide_input=True, + # confirmation_prompt=True, + help="user password", +) +@click.option("--set-username", "set_username", default=None, help="change username") +@click.option( + "--set-project", + "set_project", + default=None, + multiple=True, + help="create/replace the roles for this project: 'project,role1[,role2,...]'", +) +@click.option( + "--remove-project", + "remove_project", + default=None, + multiple=True, + help="removes project from user: 'project'", +) +@click.option( + "--add-project-role", + "add_project_role", + default=None, + multiple=True, + help="assign role(s) in a project. Can be used several times: 'project,role1[,role2,...]'", +) +@click.option( + "--remove-project-role", + "remove_project_role", + default=None, + 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, + username, + password, + set_username, + set_project, + remove_project, + add_project_role, + remove_project_role, + change_password, + new_password, +): + """Update a user information + + \b + USERNAME: name of the user + PASSWORD: new password + SET_USERNAME: new username + SET_PROJECT: creating mappings for project/role(s) + 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 = {} + user["password"] = password + user["username"] = set_username + user["set-project"] = set_project + 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 + + utils.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) + + +@click.command(name="user-delete", short_help="deletes a user") +@click.argument("name") +# @click.option('--force', is_flag=True, help='forces the deletion bypassing pre-conditions') +@click.pass_context +def user_delete(ctx, name): + """deletes a user + + \b + NAME: name or ID of the user to be deleted + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.user.delete(name) + + +@click.command(name="user-list", short_help="list all users") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the users matching the filter", +) +@click.pass_context +def user_list(ctx, filter): + """list all users""" + utils.check_client_version(ctx.obj, ctx.command.name) + if filter: + filter = "&".join(filter) + resp = ctx.obj.user.list(filter) + table = PrettyTable(["name", "id"]) + for user in resp: + table.add_row([user["username"], user["_id"]]) + table.align = "l" + print(table) + + +@click.command(name="user-show", short_help="shows the details of a user") +@click.argument("name") +@click.pass_context +def user_show(ctx, name): + """shows the details of a user + + NAME: name or ID of the user + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + resp = ctx.obj.user.get(name) + if "password" in resp: + resp["password"] = "********" + + table = PrettyTable(["key", "attribute"]) + for k, v in resp.items(): + table.add_row([k, json.dumps(v, indent=2)]) + table.align = "l" + print(table) diff --git a/osmclient/cli_commands/repo.py b/osmclient/cli_commands/repo.py new file mode 100755 index 0000000..2e5b860 --- /dev/null +++ b/osmclient/cli_commands/repo.py @@ -0,0 +1,383 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.common.exceptions import NotFound +from osmclient.cli_commands import utils +from prettytable import PrettyTable +import yaml +import json +import logging + +logger = logging.getLogger("osmclient") + + +@click.command(name="repo-add", short_help="adds a repo to OSM") +@click.argument("name") +@click.argument("uri") +@click.option( + "--type", + type=click.Choice(["helm-chart", "juju-bundle", "osm"]), + default="osm", + help="type of repo (helm-chart for Helm Charts, juju-bundle for Juju Bundles, osm for OSM Repositories)", +) +@click.option("--description", default=None, help="human readable description") +@click.option( + "--user", default=None, help="OSM repository: The username of the OSM repository" +) +@click.option( + "--password", + default=None, + help="OSM repository: The password of the OSM repository", +) +@click.pass_context +def repo_add(ctx, **kwargs): + """adds a repo to OSM + + NAME: name of the repo + URI: URI of the repo + """ + kwargs = {k: v for k, v in kwargs.items() if v is not None} + repo = kwargs + repo["url"] = repo.pop("uri") + if repo["type"] in ["helm-chart", "juju-bundle"]: + ctx.obj.repo.create(repo["name"], repo) + else: + ctx.obj.osmrepo.create(repo["name"], repo) + + +@click.command(name="repo-update", short_help="updates a repo in OSM") +@click.argument("name") +@click.option("--newname", help="New name for the repo") +@click.option("--uri", help="URI of the repo") +@click.option("--description", help="human readable description") +@click.pass_context +def repo_update(ctx, name, newname, uri, description): + """updates a repo in OSM + + NAME: name of the repo + """ + utils.check_client_version(ctx.obj, ctx.command.name) + repo = {} + if newname: + repo["name"] = newname + if uri: + repo["uri"] = uri + if description: + repo["description"] = description + try: + ctx.obj.repo.update(name, repo) + except NotFound: + ctx.obj.osmrepo.update(name, repo) + + +@click.command( + name="repo-index", short_help="Index a repository from a folder with artifacts" +) +@click.option( + "--origin", default=".", help="origin path where the artifacts are located" +) +@click.option( + "--destination", default=".", help="destination path where the index is deployed" +) +@click.pass_context +def repo_index(ctx, origin, destination): + """Index a repository + + NAME: name or ID of the repo to be deleted + """ + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.osmrepo.repo_index(origin, destination) + + +@click.command(name="repo-delete", short_help="deletes a repo") +@click.argument("name") +@click.option( + "--force", is_flag=True, help="forces the deletion from the DB (not recommended)" +) +@click.pass_context +def repo_delete(ctx, name, force): + """deletes a repo + + NAME: name or ID of the repo to be deleted + """ + logger.debug("") + try: + ctx.obj.repo.delete(name, force=force) + except NotFound: + ctx.obj.osmrepo.delete(name, force=force) + + +@click.command(name="repo-list") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the repos matching the filter", +) +@click.option("--literal", is_flag=True, help="print literally, no pretty table") +@click.pass_context +def repo_list(ctx, filter, literal): + """list all repos""" + # K8s Repositories + utils.check_client_version(ctx.obj, ctx.command.name) + if filter: + filter = "&".join(filter) + resp = ctx.obj.repo.list(filter) + resp += ctx.obj.osmrepo.list(filter) + if literal: + print(yaml.safe_dump(resp, indent=4, default_flow_style=False)) + return + table = PrettyTable(["Name", "Id", "Type", "URI", "Description"]) + for repo in resp: + # cluster['k8s-nets'] = json.dumps(yaml.safe_load(cluster['k8s-nets'])) + table.add_row( + [ + repo["name"], + repo["_id"], + repo["type"], + repo["url"], + utils.trunc_text(repo.get("description") or "", 40), + ] + ) + table.align = "l" + print(table) + + +@click.command(name="repo-show", short_help="shows the details of a repo") +@click.argument("name") +@click.option("--literal", is_flag=True, help="print literally, no pretty table") +@click.pass_context +def repo_show(ctx, name, literal): + """shows the details of a repo + + NAME: name or ID of the repo + """ + try: + resp = ctx.obj.repo.get(name) + except NotFound: + resp = ctx.obj.osmrepo.get(name) + + if literal: + if resp: + print(yaml.safe_dump(resp, indent=4, default_flow_style=False)) + return + table = PrettyTable(["key", "attribute"]) + if resp: + for k, v in list(resp.items()): + table.add_row([k, json.dumps(v, indent=2)]) + + table.align = "l" + print(table) + + +######################## +# Catalogue commands +######################## + + +def pkg_repo_list(ctx, pkgtype, filter, repo, long): + resp = ctx.obj.osmrepo.pkg_list(pkgtype, filter, repo) + if long: + table = PrettyTable( + ["nfpkg name", "vendor", "version", "latest", "description", "repository"] + ) + else: + table = PrettyTable(["nfpkg name", "repository"]) + for vnfd in resp: + name = vnfd.get("id", vnfd.get("name", "-")) + repository = vnfd.get("repository") + if long: + vendor = vnfd.get("provider", vnfd.get("vendor")) + version = vnfd.get("version") + description = vnfd.get("description") + latest = vnfd.get("latest") + table.add_row([name, vendor, version, latest, description, repository]) + else: + table.add_row([name, repository]) + table.align = "l" + print(table) + + +def pkg_repo_show(ctx, pkgtype, name, repo, version, filter, literal): + logger.debug("") + if filter: + filter = "&".join(filter) + resp = ctx.obj.osmrepo.pkg_get(pkgtype, name, repo, version, filter) + + if literal: + print(yaml.safe_dump(resp, indent=4, default_flow_style=False)) + return + pkgtype += "d" + catalog = pkgtype + "-catalog" + full_catalog = pkgtype + ":" + catalog + if resp.get(catalog): + resp = resp.pop(catalog)[pkgtype][0] + elif resp.get(full_catalog): + resp = resp.pop(full_catalog)[pkgtype][0] + + table = PrettyTable(["field", "value"]) + for k, v in list(resp.items()): + table.add_row([k, utils.wrap_text(text=json.dumps(v, indent=2), width=100)]) + table.align = "l" + print(table) + + +@click.command(name="vnfpkg-repo-list", short_help="list all xNF from OSM repositories") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the NFpkg matching the filter", +) +@click.option( + "--repo", default=None, help="restricts the list to a particular OSM repository" +) +@click.option("--long", is_flag=True, help="get more details") +@click.pass_context +def nfpkg_repo_list1(ctx, filter, repo, long): + """list xNF packages from OSM repositories""" + pkgtype = "vnf" + pkg_repo_list(ctx, pkgtype, filter, repo, long) + + +@click.command(name="nfpkg-repo-list", short_help="list all xNF from OSM repositories") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the NFpkg matching the filter", +) +@click.option( + "--repo", default=None, help="restricts the list to a particular OSM repository" +) +@click.option("--long", is_flag=True, help="get more details") +@click.pass_context +def nfpkg_repo_list2(ctx, filter, repo, long): + """list xNF packages from OSM repositories""" + pkgtype = "vnf" + pkg_repo_list(ctx, pkgtype, filter, repo, long) + + +@click.command(name="nsd-repo-list", short_help="list all NS from OSM repositories") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the NS matching the filter", +) +@click.option( + "--repo", default=None, help="restricts the list to a particular OSM repository" +) +@click.option("--long", is_flag=True, help="get more details") +@click.pass_context +def nspkg_repo_list(ctx, filter, repo, long): + """list xNF packages from OSM repositories""" + pkgtype = "ns" + pkg_repo_list(ctx, pkgtype, filter, repo, long) + + +@click.command(name="nspkg-repo-list", short_help="list all NS from OSM repositories") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the NS matching the filter", +) +@click.option( + "--repo", default=None, help="restricts the list to a particular OSM repository" +) +@click.option("--long", is_flag=True, help="get more details") +@click.pass_context +def nspkg_repo_list2(ctx, filter, repo, long): + """list xNF packages from OSM repositories""" + pkgtype = "ns" + pkg_repo_list(ctx, pkgtype, filter, repo, long) + + +@click.command( + name="vnfpkg-repo-show", + short_help="shows the details of a NF package in an OSM repository", +) +@click.option("--literal", is_flag=True, help="print literally, no pretty table") +@click.option("--repo", required=True, help="Repository name") +@click.argument("name") +@click.option("--filter", default=None, multiple=True, help="filter by fields") +@click.option("--version", default="latest", help="package version") +@click.pass_context +def vnfd_show1(ctx, name, repo, version, literal=None, filter=None): + """shows the content of a VNFD in a repository + + NAME: name or ID of the VNFD/VNFpkg + """ + pkgtype = "vnf" + pkg_repo_show(ctx, pkgtype, name, repo, version, filter, literal) + + +@click.command( + name="nsd-repo-show", + short_help="shows the details of a NS package in an OSM repository", +) +@click.option("--literal", is_flag=True, help="print literally, no pretty table") +@click.option("--repo", required=True, help="Repository name") +@click.argument("name") +@click.option("--filter", default=None, multiple=True, help="filter by fields") +@click.option("--version", default="latest", help="package version") +@click.pass_context +def nsd_repo_show(ctx, name, repo, version, literal=None, filter=None): + """shows the content of a VNFD in a repository + + NAME: name or ID of the VNFD/VNFpkg + """ + pkgtype = "ns" + pkg_repo_show(ctx, pkgtype, name, repo, version, filter, literal) + + +@click.command( + name="nspkg-repo-show", + short_help="shows the details of a NS package in an OSM repository", +) +@click.option("--literal", is_flag=True, help="print literally, no pretty table") +@click.option("--repo", required=True, help="Repository name") +@click.argument("name") +@click.option("--filter", default=None, multiple=True, help="filter by fields") +@click.option("--version", default="latest", help="package version") +@click.pass_context +def nsd_repo_show2(ctx, name, repo, version, literal=None, filter=None): + """shows the content of a VNFD in a repository + + NAME: name or ID of the VNFD/VNFpkg + """ + pkgtype = "ns" + pkg_repo_show(ctx, pkgtype, name, repo, version, filter, literal) + + +@click.command( + name="nfpkg-repo-show", + short_help="shows the details of a NF package in an OSM repository", +) +@click.option("--literal", is_flag=True, help="print literally, no pretty table") +@click.option("--repo", required=True, help="Repository name") +@click.argument("name") +@click.option("--filter", default=None, multiple=True, help="filter by fields") +@click.option("--version", default="latest", help="package version") +@click.pass_context +def vnfd_show2(ctx, name, repo, version, literal=None, filter=None): + """shows the content of a VNFD in a repository + + NAME: name or ID of the VNFD/VNFpkg + """ + pkgtype = "vnf" + pkg_repo_show(ctx, pkgtype, name, repo, version, filter, literal) diff --git a/osmclient/cli_commands/sdnc.py b/osmclient/cli_commands/sdnc.py new file mode 100755 index 0000000..2bcd770 --- /dev/null +++ b/osmclient/cli_commands/sdnc.py @@ -0,0 +1,213 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.cli_commands import utils +from prettytable import PrettyTable +import json +import logging + +logger = logging.getLogger("osmclient") + + +@click.command(name="sdnc-create", short_help="creates a new SDN controller") +@click.option("--name", prompt=True, help="Name to create sdn controller") +@click.option("--type", prompt=True, help="SDN controller type") +@click.option( + "--sdn_controller_version", # hidden=True, + help="Deprecated. Use --config {version: sdn_controller_version}", +) +@click.option("--url", help="URL in format http[s]://HOST:IP/") +@click.option("--ip_address", help="Deprecated. Use --url") # hidden=True, +@click.option("--port", help="Deprecated. Use --url") # hidden=True, +@click.option( + "--switch_dpid", help="Deprecated. Use --config {switch_id: DPID}" # hidden=True, +) +@click.option( + "--config", + help="Extra information for SDN in yaml format, as {switch_id: identity used for the plugin (e.g. DPID: " + "Openflow Datapath ID), version: version}", +) +@click.option("--user", help="SDN controller username") +@click.option( + "--password", + hide_input=True, + confirmation_prompt=True, + help="SDN controller password", +) +@click.option("--description", default=None, help="human readable description") +@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 sdnc_create(ctx, **kwargs): + """creates a new SDN controller""" + logger.debug("") + sdncontroller = { + x: kwargs[x] + for x in kwargs + if kwargs[x] and x not in ("wait", "ip_address", "port", "switch_dpid") + } + if kwargs.get("port"): + print("option '--port' is deprecated, use '--url' instead") + sdncontroller["port"] = int(kwargs["port"]) + if kwargs.get("ip_address"): + print("option '--ip_address' is deprecated, use '--url' instead") + sdncontroller["ip"] = kwargs["ip_address"] + if kwargs.get("switch_dpid"): + print( + "option '--switch_dpid' is deprecated, use '--config={switch_id: id|DPID}' instead" + ) + sdncontroller["dpid"] = kwargs["switch_dpid"] + if kwargs.get("sdn_controller_version"): + print( + "option '--sdn_controller_version' is deprecated, use '--config={version: SDN_CONTROLLER_VERSION}'" + " instead" + ) + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.sdnc.create(kwargs["name"], sdncontroller, wait=kwargs["wait"]) + + +@click.command(name="sdnc-update", short_help="updates an SDN controller") +@click.argument("name") +@click.option("--newname", help="New name for the SDN controller") +@click.option("--description", default=None, help="human readable description") +@click.option("--type", help="SDN controller type") +@click.option("--url", help="URL in format http[s]://HOST:IP/") +@click.option( + "--config", + help="Extra information for SDN in yaml format, as " + "{switch_id: identity used for the plugin (e.g. DPID: " + "Openflow Datapath ID), version: version}", +) +@click.option("--user", help="SDN controller username") +@click.option("--password", help="SDN controller password") +@click.option("--ip_address", help="Deprecated. Use --url") # hidden=True +@click.option("--port", help="Deprecated. Use --url") # hidden=True +@click.option( + "--switch_dpid", help="Deprecated. Use --config {switch_dpid: DPID}" +) # hidden=True +@click.option( + "--sdn_controller_version", help="Deprecated. Use --config {version: VERSION}" +) # hidden=True +@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 sdnc_update(ctx, **kwargs): + """updates an SDN controller + + NAME: name or ID of the SDN controller + """ + logger.debug("") + sdncontroller = { + x: kwargs[x] + for x in kwargs + if kwargs[x] + and x not in ("wait", "ip_address", "port", "switch_dpid", "new_name") + } + if kwargs.get("newname"): + sdncontroller["name"] = kwargs["newname"] + if kwargs.get("port"): + print("option '--port' is deprecated, use '--url' instead") + sdncontroller["port"] = int(kwargs["port"]) + if kwargs.get("ip_address"): + print("option '--ip_address' is deprecated, use '--url' instead") + sdncontroller["ip"] = kwargs["ip_address"] + if kwargs.get("switch_dpid"): + print( + "option '--switch_dpid' is deprecated, use '--config={switch_id: id|DPID}' instead" + ) + sdncontroller["dpid"] = kwargs["switch_dpid"] + if kwargs.get("sdn_controller_version"): + print( + "option '--sdn_controller_version' is deprecated, use '---config={version: SDN_CONTROLLER_VERSION}'" + " instead" + ) + + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.sdnc.update(kwargs["name"], sdncontroller, wait=kwargs["wait"]) + + +@click.command(name="sdnc-delete", short_help="deletes an SDN controller") +@click.argument("name") +@click.option( + "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" +) +@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 sdnc_delete(ctx, name, force, wait): + """deletes an SDN controller + + NAME: name or ID of the SDN controller to be deleted + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.sdnc.delete(name, force, wait=wait) + + +@click.command(name="sdnc-list", short_help="list all SDN controllers") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the SDN controllers matching the filter with format: 'k[.k..]=v[&k[.k]=v2]'", +) +@click.pass_context +def sdnc_list(ctx, filter): + """list all SDN controllers""" + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + if filter: + filter = "&".join(filter) + resp = ctx.obj.sdnc.list(filter) + table = PrettyTable(["sdnc name", "id"]) + for sdnc in resp: + table.add_row([sdnc["name"], sdnc["_id"]]) + table.align = "l" + print(table) + + +@click.command(name="sdnc-show", short_help="shows the details of an SDN controller") +@click.argument("name") +@click.pass_context +def sdnc_show(ctx, name): + """shows the details of an SDN controller + + NAME: name or ID of the SDN controller + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + resp = ctx.obj.sdnc.get(name) + + table = PrettyTable(["key", "attribute"]) + for k, v in list(resp.items()): + table.add_row([k, json.dumps(v, indent=2)]) + table.align = "l" + print(table) diff --git a/osmclient/cli_commands/subscriptions.py b/osmclient/cli_commands/subscriptions.py new file mode 100755 index 0000000..3dc3c38 --- /dev/null +++ b/osmclient/cli_commands/subscriptions.py @@ -0,0 +1,139 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.common.exceptions import ClientException +from osmclient.cli_commands import utils +from prettytable import PrettyTable +import json +import logging + +logger = logging.getLogger("osmclient") + + +@click.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("") + utils.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) + + +@click.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("") + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.subscription.delete(event_type, subscription_id, force) + + +@click.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("") + utils.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"], + utils.wrap_text(text=json.dumps(sub["filter"], indent=2), width=70), + sub["CallbackUri"], + ] + ) + table.align = "l" + print(table) + + +@click.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("") + 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, utils.wrap_text(text=json.dumps(v, indent=2), width=100)]) + table.align = "l" + print(table) diff --git a/osmclient/cli_commands/utils.py b/osmclient/cli_commands/utils.py new file mode 100755 index 0000000..5ac9931 --- /dev/null +++ b/osmclient/cli_commands/utils.py @@ -0,0 +1,101 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import textwrap +import logging +import yaml +from osmclient.common.exceptions import ClientException + +logger = logging.getLogger("osmclient") + + +def wrap_text(text, width): + wrapper = textwrap.TextWrapper(width=width) + lines = text.splitlines() + return "\n".join(map(wrapper.fill, lines)) + + +def trunc_text(text, length): + if len(text) > length: + return text[: (length - 3)] + "..." + else: + return text + + +def check_client_version(obj, what, version="sol005"): + """ + Checks the version of the client object and raises error if it not the expected. + + :param obj: the client object + :what: the function or command under evaluation (used when an error is raised) + :return: - + :raises ClientError: if the specified version does not match the client version + """ + logger.debug("") + fullclassname = obj.__module__ + "." + obj.__class__.__name__ + message = 'The following commands or options are only supported with the option "--sol005": {}'.format( + what + ) + if version == "v1": + message = 'The following commands or options are not supported when using option "--sol005": {}'.format( + what + ) + if fullclassname != "osmclient.{}.client.Client".format(version): + raise ClientException(message) + return + + +def get_project(project_list, item): + # project_list = ctx.obj.project.list() + item_project_list = item.get("_admin", {}).get("projects_read") + project_id = "None" + project_name = "None" + if item_project_list: + for p1 in item_project_list: + project_id = p1 + for p2 in project_list: + if p2["_id"] == project_id: + project_name = p2["name"] + return project_id, project_name + return project_id, project_name + + +def get_vim_name(vim_list, vim_id): + vim_name = "-" + for v in vim_list: + if v["uuid"] == vim_id: + vim_name = v["name"] + break + 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 diff --git a/osmclient/cli_commands/vca.py b/osmclient/cli_commands/vca.py new file mode 100755 index 0000000..f60f98f --- /dev/null +++ b/osmclient/cli_commands/vca.py @@ -0,0 +1,294 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.common.exceptions import ClientException +from osmclient.cli_commands import utils +from prettytable import PrettyTable +import yaml +import json +import os +from typing import Any, Dict +import logging + +logger = logging.getLogger("osmclient") + + +@click.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 + """ + logger.debug("") + utils.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): + logger.debug("") + 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: + logger.debug("") + 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") + + +@click.command(name="vca-update", short_help="updates a VCA") +@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 VCA + + NAME: name or ID of the VCA + """ + logger.debug("") + utils.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) + + +@click.command(name="vca-delete", short_help="deletes a VCA") +@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 VCA + + NAME: name or ID of the VCA to be deleted + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.vca.delete(name, force=force) + + +@click.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""" + logger.debug("") + utils.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 = utils.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", "-"), + utils.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) + + +@click.command(name="vca-show", short_help="shows the details of a VCA") +@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 VCA + + NAME: name or ID of the VCA + """ + logger.debug("") + 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, utils.wrap_text(text=json.dumps(v, indent=2), width=100)]) + table.align = "l" + print(table) diff --git a/osmclient/cli_commands/vim.py b/osmclient/cli_commands/vim.py new file mode 100755 index 0000000..ff1bcc6 --- /dev/null +++ b/osmclient/cli_commands/vim.py @@ -0,0 +1,383 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.cli_commands import utils +from prettytable import PrettyTable +import yaml +import json +import logging + +logger = logging.getLogger("osmclient") + + +def _check_ca_cert(vim_config: dict) -> None: + """ + Checks if the VIM has a CA certificate. + In that case, reads the content and add it to the config + : param vim_config: configuration provided with the VIM creation + : return: None + """ + + if vim_config.get("ca_cert"): + with open(vim_config["ca_cert"], "r") as cert_f: + vim_config["ca_cert_content"] = str(cert_f.read()) + del vim_config["ca_cert"] + + +@click.command(name="vim-create", short_help="creates a new VIM account") +@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( + "--sdn_controller", + default=None, + help="Name or id of the SDN controller associated to this VIM account", +) +@click.option( + "--sdn_port_mapping", + default=None, + help="File describing the port mapping between compute nodes' ports and switch ports", +) +@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("--vca", default=None, help="VCA to be used in this VIM account") +@click.option( + "--creds", default=None, help="credentials file (only applicable for GCP VIM type)" +) +@click.option( + "--prometheus_url", + default=None, + help="PrometheusTSBD URL to get VIM data", +) +@click.option( + "--prometheus_map", + default=None, + help="PrometheusTSBD metrics mapping for VIM data", +) +@click.option( + "--prometheus_config_file", + default=None, + help="Prometheus configuration to get VIM data", +) +@click.pass_context +def vim_create( + ctx, + name, + user, + password, + auth_url, + tenant, + config, + config_file, + account_type, + description, + sdn_controller, + sdn_port_mapping, + wait, + vca, + creds, + prometheus_url, + prometheus_map, + prometheus_config_file, +): + """creates a new VIM account""" + logger.debug("") + if sdn_controller: + utils.check_client_version(ctx.obj, "--sdn_controller") + if sdn_port_mapping: + utils.check_client_version(ctx.obj, "--sdn_port_mapping") + vim = {} + prometheus_config = {} + if prometheus_url: + prometheus_config["prometheus-url"] = prometheus_url + if prometheus_map: + prometheus_config["prometheus-map"] = prometheus_map + if prometheus_config_file: + with open(prometheus_config_file) as prometheus_file: + prometheus_config_dict = json.load(prometheus_file) + prometheus_config.update(prometheus_config_dict) + if prometheus_config: + vim["prometheus-config"] = prometheus_config + 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 + if vca: + vim["vca"] = vca + vim_config = utils.create_config(config_file, config) + _check_ca_cert(vim_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 + ) + + +@click.command(name="vim-update", short_help="updates a VIM account") +@click.argument("name") +@click.option("--newname", help="New name for the VIM account") +@click.option("--user", help="VIM username") +@click.option("--password", help="VIM password") +@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( + "--sdn_controller", + default=None, + help="Name or id of the SDN controller to be associated with this VIM" + "account. Use empty string to disassociate", +) +@click.option( + "--sdn_port_mapping", + default=None, + help="File describing the port mapping between compute nodes' ports and switch ports", +) +@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( + "--creds", default=None, help="credentials file (only applicable for GCP VIM type)" +) +@click.option( + "--prometheus_url", + default=None, + help="PrometheusTSBD URL to get VIM data", +) +@click.option( + "--prometheus_map", + default=None, + help="PrometheusTSBD metrics mapping for VIM data", +) +@click.option( + "--prometheus_config_file", + default=None, + help="Prometheus configuration to get VIM data", +) +@click.pass_context +def vim_update( + ctx, + name, + newname, + user, + password, + auth_url, + tenant, + config, + config_file, + account_type, + description, + sdn_controller, + sdn_port_mapping, + wait, + creds, + prometheus_url, + prometheus_map, + prometheus_config_file, +): + """updates a VIM account + + NAME: name or ID of the VIM account + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + vim = {} + if newname: + vim["name"] = newname + if user: + vim["vim_user"] = user + if password: + vim["vim_password"] = password + if auth_url: + vim["vim_url"] = auth_url + if tenant: + vim["vim-tenant-name"] = tenant + if account_type: + vim["vim_type"] = account_type + if description: + vim["description"] = description + vim_config = None + if config or config_file: + vim_config = utils.create_config(config_file, config) + _check_ca_cert(vim_config) + if creds: + with open(creds, "r") as cf: + vim_config["credentials"] = yaml.safe_load(cf.read()) + prometheus_config = {} + if prometheus_url: + prometheus_config["prometheus-url"] = prometheus_url + if prometheus_map: + prometheus_config["prometheus-map"] = prometheus_map + if prometheus_config_file: + with open(prometheus_config_file) as prometheus_file: + prometheus_config_dict = json.load(prometheus_file) + prometheus_config.update(prometheus_config_dict) + if prometheus_config: + vim["prometheus-config"] = prometheus_config + ctx.obj.vim.update( + name, vim, vim_config, sdn_controller, sdn_port_mapping, wait=wait + ) + + +@click.command(name="vim-delete", short_help="deletes a VIM account") +@click.argument("name") +@click.option( + "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" +) +@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 vim_delete(ctx, name, force, wait): + """deletes a VIM account + + NAME: name or ID of the VIM account to be deleted + """ + logger.debug("") + if not force: + ctx.obj.vim.delete(name, wait=wait) + else: + utils.check_client_version(ctx.obj, "--force") + ctx.obj.vim.delete(name, force, wait=wait) + + +@click.command(name="vim-list", short_help="list all VIM accounts") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the VIM accounts matching the filter", +) +@click.option( + "--long", + is_flag=True, + help="get more details of the NS (project, vim, deployment status, configuration status.", +) +@click.pass_context +def vim_list(ctx, filter, long): + """list all VIM accounts""" + logger.debug("") + if filter: + filter = "&".join(filter) + utils.check_client_version(ctx.obj, "--filter") + fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__ + if fullclassname == "osmclient.sol005.client.Client": + resp = ctx.obj.vim.list(filter) + if long: + table = PrettyTable( + ["vim name", "uuid", "project", "operational state", "error details"] + ) + project_list = ctx.obj.project.list() + else: + table = PrettyTable(["vim name", "uuid", "operational state"]) + for vim in resp: + 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" + if vim_state == "ERROR": + error_details = vim["_admin"].get("detailed-status", "Not found") + project_id, project_name = utils.get_project(project_list, vim) + # project_info = '{} ({})'.format(project_name, project_id) + project_info = project_name + table.add_row( + [ + vim["name"], + vim["uuid"], + project_info, + vim_state, + utils.wrap_text(text=error_details, width=80), + ] + ) + else: + table.add_row( + [vim["name"], vim["uuid"], vim["_admin"].get("operationalState", "-")] + ) + table.align = "l" + print(table) + + +@click.command(name="vim-show", short_help="shows the details of a VIM account") +@click.argument("name") +@click.option( + "--filter", + 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, literal): + """shows the details of a VIM account + + NAME: name or ID of the VIM account + """ + logger.debug("") + 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"] = "********" + + 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: + table.add_row([k, utils.wrap_text(text=json.dumps(v, indent=2), width=100)]) + table.align = "l" + print(table) diff --git a/osmclient/cli_commands/vnf.py b/osmclient/cli_commands/vnf.py new file mode 100755 index 0000000..6e10834 --- /dev/null +++ b/osmclient/cli_commands/vnf.py @@ -0,0 +1,275 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.common.exceptions import ClientException +from osmclient.cli_commands import utils +from prettytable import PrettyTable +import yaml +import json +import time +from datetime import datetime +import logging + +logger = logging.getLogger("osmclient") + + +def vnf_list(ctx, ns, filter, long): + logger.debug("") + if ns or filter: + if ns: + utils.check_client_version(ctx.obj, "--ns") + if filter: + filter = "&".join(filter) + utils.check_client_version(ctx.obj, "--filter") + resp = ctx.obj.vnf.list(ns, filter) + else: + resp = ctx.obj.vnf.list() + fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__ + if fullclassname == "osmclient.sol005.client.Client": + field_names = [ + "vnf id", + "name", + "ns id", + "vnf member index", + "vnfd name", + "vim account id", + "ip address", + ] + if long: + field_names = [ + "vnf id", + "name", + "ns id", + "vnf member index", + "vnfd name", + "vim account id", + "ip address", + "date", + "last update", + ] + table = PrettyTable(field_names) + for vnfr in resp: + name = vnfr["name"] if "name" in vnfr else "-" + new_row = [ + vnfr["_id"], + name, + vnfr["nsr-id-ref"], + vnfr["member-vnf-index-ref"], + vnfr["vnfd-ref"], + vnfr["vim-account-id"], + vnfr["ip-address"], + ] + if long: + date = datetime.fromtimestamp(vnfr["_admin"]["created"]).strftime( + "%Y-%m-%dT%H:%M:%S" + ) + last_update = datetime.fromtimestamp( + vnfr["_admin"]["modified"] + ).strftime("%Y-%m-%dT%H:%M:%S") + new_row.extend([date, last_update]) + table.add_row(new_row) + else: + table = PrettyTable(["vnf name", "id", "operational status", "config status"]) + for vnfr in resp: + if "mgmt-interface" not in vnfr: + vnfr["mgmt-interface"] = {} + vnfr["mgmt-interface"]["ip-address"] = None + table.add_row( + [ + vnfr["name"], + vnfr["id"], + vnfr["operational-status"], + vnfr["config-status"], + ] + ) + table.align = "l" + print(table) + + +@click.command(name="vnf-list", short_help="list all NF instances") +@click.option( + "--ns", default=None, help="NS instance id or name to restrict the NF list" +) +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the NF instances matching the filter.", +) +@click.option("--long", is_flag=True, help="get more details") +@click.pass_context +def vnf_list1(ctx, ns, filter, long): + """list all NF instances""" + logger.debug("") + vnf_list(ctx, ns, filter, long) + + +@click.command(name="nf-list", short_help="list all NF instances") +@click.option( + "--ns", default=None, help="NS instance id or name to restrict the NF list" +) +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the NF instances matching the filter.", +) +@click.option("--long", is_flag=True, help="get more details") +@click.pass_context +def nf_list(ctx, ns, filter, long): + """list all NF instances + + \b + Options: + --ns TEXT NS instance id or name to restrict the VNF list + --filter filterExpr Restricts the list to the VNF instances matching the filter + + \b + filterExpr consists of one or more strings formatted according to "simpleFilterExpr", + concatenated using the "&" character: + + \b + filterExpr := ["&"]* + simpleFilterExpr := ["."]*["."]"="[","]* + op := "eq" | "neq" | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont" + attrName := string + value := scalar value + + \b + where: + * zero or more occurrences + ? zero or one occurrence + [] grouping of expressions to be used with ? and * + "" quotation marks for marking string constants + <> name separator + + \b + "AttrName" is the name of one attribute in the data type that defines the representation + of the resource. The dot (".") character in "simpleFilterExpr" allows concatenation of + entries to filter by attributes deeper in the hierarchy of a structured document. + "Op" stands for the comparison operator. If the expression has concatenated + entries, it means that the operator "op" is applied to the attribute addressed by the last + entry included in the concatenation. All simple filter expressions are combined + by the "AND" logical operator. In a concatenation of entries in a , + the rightmost "attrName" entry in a "simpleFilterExpr" is called "leaf attribute". The + concatenation of all "attrName" entries except the leaf attribute is called the "attribute + prefix". If an attribute referenced in an expression is an array, an object that contains a + corresponding array shall be considered to match the expression if any of the elements in the + array matches all expressions that have the same attribute prefix. + + \b + Filter examples: + --filter vim-account-id= + --filter vnfd-ref= + --filter vdur.ip-address= + --filter vnfd-ref=,vdur.ip-address= + """ + logger.debug("") + vnf_list(ctx, ns, filter, long) + + +@click.command(name="vnf-show", short_help="shows the info of a VNF instance") +@click.argument("name") +@click.option("--literal", is_flag=True, help="print literally, no pretty table") +@click.option( + "--filter", + multiple=True, + help="restricts the information to the fields in the filter", +) +@click.option("--kdu", default=None, help="KDU name (whose status will be shown)") +@click.pass_context +def vnf_show(ctx, name, literal, filter, kdu): + """shows the info of a VNF instance + + NAME: name or ID of the VNF instance + """ + + def print_kdu_status(op_info_status): + """print KDU status properly formatted""" + try: + op_status = yaml.safe_load(op_info_status) + if ( + "namespace" in op_status + and "info" in op_status + and "last_deployed" in op_status["info"] + and "status" in op_status["info"] + and "code" in op_status["info"]["status"] + and "resources" in op_status["info"]["status"] + and "seconds" in op_status["info"]["last_deployed"] + ): + last_deployed_time = datetime.fromtimestamp( + op_status["info"]["last_deployed"]["seconds"] + ).strftime("%a %b %d %I:%M:%S %Y") + print("LAST DEPLOYED: {}".format(last_deployed_time)) + print("NAMESPACE: {}".format(op_status["namespace"])) + status_code = "UNKNOWN" + if op_status["info"]["status"]["code"] == 1: + status_code = "DEPLOYED" + print("STATUS: {}".format(status_code)) + print() + print("RESOURCES:") + print(op_status["info"]["status"]["resources"]) + if "notes" in op_status["info"]["status"]: + print("NOTES:") + print(op_status["info"]["status"]["notes"]) + else: + print(op_info_status) + except Exception: + print(op_info_status) + + logger.debug("") + if kdu: + if literal: + raise ClientException( + '"--literal" option is incompatible with "--kdu" option' + ) + if filter: + raise ClientException( + '"--filter" option is incompatible with "--kdu" option' + ) + + utils.check_client_version(ctx.obj, ctx.command.name) + resp = ctx.obj.vnf.get(name) + + if kdu: + ns_id = resp["nsr-id-ref"] + op_data = {} + op_data["member_vnf_index"] = resp["member-vnf-index-ref"] + op_data["kdu_name"] = kdu + op_data["primitive"] = "status" + op_data["primitive_params"] = {} + op_id = ctx.obj.ns.exec_op(ns_id, op_name="action", op_data=op_data, wait=False) + t = 0 + while t < 30: + op_info = ctx.obj.ns.get_op(op_id) + if op_info["operationState"] == "COMPLETED": + print_kdu_status(op_info["detailed-status"]) + return + time.sleep(5) + t += 5 + print("Could not determine KDU status") + return + + if literal: + print(yaml.safe_dump(resp, indent=4, default_flow_style=False)) + return + + table = PrettyTable(["field", "value"]) + for k, v in list(resp.items()): + if not filter or k in filter: + table.add_row([k, utils.wrap_text(text=json.dumps(v, indent=2), width=100)]) + table.align = "l" + print(table) diff --git a/osmclient/cli_commands/wim.py b/osmclient/cli_commands/wim.py new file mode 100755 index 0000000..2e24d84 --- /dev/null +++ b/osmclient/cli_commands/wim.py @@ -0,0 +1,201 @@ +# Copyright ETSI Contributors and Others. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import click +from osmclient.cli_commands import utils +from prettytable import PrettyTable +import json +import logging + +logger = logging.getLogger("osmclient") + + +@click.command(name="wim-create", short_help="creates a new WIM account") +@click.option("--name", prompt=True, help="Name for the WIM account") +@click.option("--user", help="WIM username") +@click.option("--password", help="WIM password") +@click.option("--url", prompt=True, help="WIM url") +@click.option("--config", default=None, help="WIM specific config parameters") +@click.option("--wim_type", help="WIM type") +@click.option("--description", default=None, help="human readable description") +@click.option( + "--wim_port_mapping", + default=None, + help="File describing the port mapping between DC edge (datacenters, switches, ports) and WAN edge " + "(WAN service endpoint id and info)", +) +@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 wim_create( + ctx, + name, + user, + password, + url, + config, + wim_type, + description, + wim_port_mapping, + wait, +): + """creates a new WIM account""" + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + wim = {} + if user: + wim["user"] = user + if password: + wim["password"] = password + if url: + wim["wim_url"] = url + wim["wim_type"] = wim_type + if description: + wim["description"] = description + if config: + wim["config"] = config + ctx.obj.wim.create(name, wim, wim_port_mapping, wait=wait) + + +@click.command(name="wim-update", short_help="updates a WIM account") +@click.argument("name") +@click.option("--newname", help="New name for the WIM account") +@click.option("--user", help="WIM username") +@click.option("--password", help="WIM password") +@click.option("--url", help="WIM url") +@click.option("--config", help="WIM specific config parameters") +@click.option("--wim_type", help="WIM type") +@click.option("--description", help="human readable description") +@click.option( + "--wim_port_mapping", + default=None, + help="File describing the port mapping between DC edge (datacenters, switches, ports) and WAN edge " + "(WAN service endpoint id and info)", +) +@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 wim_update( + ctx, + name, + newname, + user, + password, + url, + config, + wim_type, + description, + wim_port_mapping, + wait, +): + """updates a WIM account + + NAME: name or ID of the WIM account + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + wim = {} + if newname: + wim["name"] = newname + if user: + wim["user"] = user + if password: + wim["password"] = password + if url: + wim["url"] = url + if wim_type: + wim["wim_type"] = wim_type + if description: + wim["description"] = description + if config: + wim["config"] = config + ctx.obj.wim.update(name, wim, wim_port_mapping, wait=wait) + + +@click.command(name="wim-delete", short_help="deletes a WIM account") +@click.argument("name") +@click.option( + "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" +) +@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 wim_delete(ctx, name, force, wait): + """deletes a WIM account + + NAME: name or ID of the WIM account to be deleted + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + ctx.obj.wim.delete(name, force, wait=wait) + + +@click.command(name="wim-list", short_help="list all WIM accounts") +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the WIM accounts matching the filter", +) +@click.pass_context +def wim_list(ctx, filter): + """list all WIM accounts""" + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + if filter: + filter = "&".join(filter) + resp = ctx.obj.wim.list(filter) + table = PrettyTable(["wim name", "uuid"]) + for wim in resp: + table.add_row([wim["name"], wim["uuid"]]) + table.align = "l" + print(table) + + +@click.command(name="wim-show", short_help="shows the details of a WIM account") +@click.argument("name") +@click.pass_context +def wim_show(ctx, name): + """shows the details of a WIM account + + NAME: name or ID of the WIM account + """ + logger.debug("") + utils.check_client_version(ctx.obj, ctx.command.name) + resp = ctx.obj.wim.get(name) + if "password" in resp: + resp["password"] = "********" + + table = PrettyTable(["key", "attribute"]) + for k, v in list(resp.items()): + table.add_row([k, json.dumps(v, indent=2)]) + table.align = "l" + print(table) diff --git a/osmclient/scripts/osm.py b/osmclient/scripts/osm.py index fac267d..e5be118 100755 --- a/osmclient/scripts/osm.py +++ b/osmclient/scripts/osm.py @@ -1,6 +1,4 @@ -# Copyright 2017-2018 Sandvine -# Copyright 2018 Telefonica -# +# Copyright ETSI Contributors and Others. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -14,105 +12,38 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -""" -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 +from osmclient.common.exceptions import ClientException +from osmclient.cli_commands import ( + alarms, + k8scluster, + metrics, + netslice_instance, + netslice_ops, + netslice_template, + nfpkg, + ns, + nslcm_ops, + nslcm, + nspkg, + other, + packages, + pdus, + rbac, + repo, + sdnc, + subscriptions, + vca, + vim, + vnf, + wim, +) import yaml -import json -import time import pycurl import os -import textwrap -import pkg_resources import logging -from datetime import datetime -from typing import Any, Dict - - -def wrap_text(text, width): - wrapper = textwrap.TextWrapper(width=width) - lines = text.splitlines() - return "\n".join(map(wrapper.fill, lines)) - - -def trunc_text(text, length): - if len(text) > length: - return text[: (length - 3)] + "..." - else: - return text - - -def check_client_version(obj, what, version="sol005"): - """ - Checks the version of the client object and raises error if it not the expected. - - :param obj: the client object - :what: the function or command under evaluation (used when an error is raised) - :return: - - :raises ClientError: if the specified version does not match the client version - """ - logger.debug("") - fullclassname = obj.__module__ + "." + obj.__class__.__name__ - message = 'The following commands or options are only supported with the option "--sol005": {}'.format( - what - ) - if version == "v1": - message = 'The following commands or options are not supported when using option "--sol005": {}'.format( - what - ) - if fullclassname != "osmclient.{}.client.Client".format(version): - raise ClientException(message) - return - - -def get_project(project_list, item): - # project_list = ctx.obj.project.list() - item_project_list = item.get("_admin", {}).get("projects_read") - project_id = "None" - project_name = "None" - if item_project_list: - for p1 in item_project_list: - project_id = p1 - for p2 in project_list: - if p2["_id"] == project_id: - project_name = p2["name"] - return project_id, project_name - return project_id, project_name - - -def get_vim_name(vim_list, vim_id): - vim_name = "-" - for v in vim_list: - if v["uuid"] == vim_id: - vim_name = v["name"] - break - 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( @@ -189,6411 +120,184 @@ def cli_osm(ctx, **kwargs): logger = logging.getLogger("osmclient") -#################### -# LIST operations -#################### - - -@cli_osm.command(name="ns-list", short_help="list all NS instances") -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the NS instances matching the filter.", -) -@click.option( - "--long", - is_flag=True, - help="get more details of the NS (project, vim, deployment status, configuration status.", -) -@click.pass_context -def ns_list(ctx, filter, long): - """list all NS instances - - \b - Options: - --filter filterExpr Restricts the list to the NS instances matching the filter - - \b - filterExpr consists of one or more strings formatted according to "simpleFilterExpr", - concatenated using the "&" character: - - \b - filterExpr := ["&"]* - simpleFilterExpr := ["."]*["."]"="[","]* - op := "eq" | "neq" | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont" - attrName := string - value := scalar value - - \b - where: - * zero or more occurrences - ? zero or one occurrence - [] grouping of expressions to be used with ? and * - "" quotation marks for marking string constants - <> name separator - - \b - "AttrName" is the name of one attribute in the data type that defines the representation - of the resource. The dot (".") character in "simpleFilterExpr" allows concatenation of - entries to filter by attributes deeper in the hierarchy of a structured document. - "Op" stands for the comparison operator. If the expression has concatenated - entries, it means that the operator "op" is applied to the attribute addressed by the last - entry included in the concatenation. All simple filter expressions are combined - by the "AND" logical operator. In a concatenation of entries in a , - the rightmost "attrName" entry in a "simpleFilterExpr" is called "leaf attribute". The - concatenation of all "attrName" entries except the leaf attribute is called the "attribute - prefix". If an attribute referenced in an expression is an array, an object that contains a - corresponding array shall be considered to match the expression if any of the elements in the - array matches all expressions that have the same attribute prefix. - - \b - Filter examples: - --filter admin-status=ENABLED - --filter nsd-ref= - --filter nsd.vendor= - --filter nsd.vendor=&nsd-ref= - --filter nsd.constituent-vnfd.vnfd-id-ref= - """ - - def summarize_deployment_status(status_dict): - # Nets - summary = "" - if not status_dict: - return summary - n_nets = 0 - status_nets = {} - net_list = status_dict.get("nets", []) - for net in net_list: - n_nets += 1 - if net["status"] not in status_nets: - status_nets[net["status"]] = 1 - else: - status_nets[net["status"]] += 1 - message = "Nets: " - for k, v in status_nets.items(): - message += "{}:{},".format(k, v) - message += "TOTAL:{}".format(n_nets) - summary += "{}".format(message) - # VMs and VNFs - n_vms = 0 - status_vms = {} - status_vnfs = {} - vnf_list = status_dict["vnfs"] - for vnf in vnf_list: - member_vnf_index = vnf["member_vnf_index"] - if member_vnf_index not in status_vnfs: - status_vnfs[member_vnf_index] = {} - for vm in vnf["vms"]: - n_vms += 1 - if vm["status"] not in status_vms: - status_vms[vm["status"]] = 1 - else: - status_vms[vm["status"]] += 1 - if vm["status"] not in status_vnfs[member_vnf_index]: - status_vnfs[member_vnf_index][vm["status"]] = 1 - else: - status_vnfs[member_vnf_index][vm["status"]] += 1 - message = "VMs: " - for k, v in status_vms.items(): - message += "{}:{},".format(k, v) - message += "TOTAL:{}".format(n_vms) - summary += "\n{}".format(message) - summary += "\nNFs:" - for k, v in status_vnfs.items(): - total = 0 - message = "\n {} VMs: ".format(k) - for k2, v2 in v.items(): - message += "{}:{},".format(k2, v2) - total += v2 - message += "TOTAL:{}".format(total) - summary += message - return summary - - def summarize_config_status(ee_list): - summary = "" - if not ee_list: - return summary - n_ee = 0 - status_ee = {} - for ee in ee_list: - n_ee += 1 - if ee["elementType"] not in status_ee: - status_ee[ee["elementType"]] = {} - status_ee[ee["elementType"]][ee["status"]] = 1 - continue - if ee["status"] in status_ee[ee["elementType"]]: - status_ee[ee["elementType"]][ee["status"]] += 1 - else: - status_ee[ee["elementType"]][ee["status"]] = 1 - for elementType in ["KDU", "VDU", "PDU", "VNF", "NS"]: - if elementType in status_ee: - message = "" - total = 0 - for k, v in status_ee[elementType].items(): - message += "{}:{},".format(k, v) - total += v - message += "TOTAL:{}\n".format(total) - summary += "{}: {}".format(elementType, message) - summary += "TOTAL Exec. Env.: {}".format(n_ee) - return summary - - logger.debug("") - if filter: - check_client_version(ctx.obj, "--filter") - filter = "&".join(filter) - resp = ctx.obj.ns.list(filter) - else: - resp = ctx.obj.ns.list() - if long: - table = PrettyTable( - [ - "ns instance name", - "id", - "date", - "ns state", - "current operation", - "error details", - "project", - "vim (inst param)", - "deployment status", - "configuration status", - ] - ) - project_list = ctx.obj.project.list() - try: - vim_list = ctx.obj.vim.list() - except Exception: - vim_list = [] - else: - table = PrettyTable( - [ - "ns instance name", - "id", - "date", - "ns state", - "current operation", - "error details", - ] - ) - for ns in resp: - fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__ - if fullclassname == "osmclient.sol005.client.Client": - nsr = ns - logger.debug("NS info: {}".format(nsr)) - nsr_name = nsr["name"] - nsr_id = nsr["_id"] - date = datetime.fromtimestamp(nsr["create-time"]).strftime( - "%Y-%m-%dT%H:%M:%S" - ) - ns_state = nsr.get("nsState", nsr["_admin"]["nsState"]) - if long: - deployment_status = summarize_deployment_status( - nsr.get("deploymentStatus") - ) - config_status = summarize_config_status(nsr.get("configurationStatus")) - project_id, project_name = get_project(project_list, nsr) - # project = '{} ({})'.format(project_name, project_id) - project = project_name - vim_id = nsr.get("datacenter") - vim_name = get_vim_name(vim_list, vim_id) - - # vim = '{} ({})'.format(vim_name, vim_id) - vim = vim_name - if "currentOperation" in nsr: - current_operation = "{} ({})".format( - nsr["currentOperation"], nsr["currentOperationID"] - ) - else: - current_operation = "{} ({})".format( - nsr["_admin"].get("current-operation", "-"), - nsr["_admin"]["nslcmop"], - ) - error_details = "N/A" - if ( - ns_state == "BROKEN" - or ns_state == "DEGRADED" - or ("currentOperation" not in nsr and nsr.get("errorDescription")) - ): - error_details = "{}\nDetail: {}".format( - nsr["errorDescription"], nsr["errorDetail"] - ) - else: - nsopdata = ctx.obj.ns.get_opdata(ns["id"]) - nsr = nsopdata["nsr:nsr"] - nsr_name = nsr["name-ref"] - nsr_id = nsr["ns-instance-config-ref"] - date = "-" - project = "-" - deployment_status = ( - nsr["operational-status"] - if "operational-status" in nsr - else "Not found" - ) - ns_state = deployment_status - config_status = nsr.get("config-status", "Not found") - current_operation = "Unknown" - error_details = nsr.get("detailed-status", "Not found") - if config_status == "config_not_needed": - config_status = "configured (no charms)" - - if long: - table.add_row( - [ - nsr_name, - nsr_id, - date, - ns_state, - current_operation, - wrap_text(text=error_details, width=40), - project, - vim, - deployment_status, - config_status, - ] - ) - else: - table.add_row( - [ - nsr_name, - nsr_id, - date, - ns_state, - current_operation, - wrap_text(text=error_details, width=40), - ] - ) - table.align = "l" - print(table) - print('To get the history of all operations over a NS, run "osm ns-op-list NS_ID"') - print( - 'For more details on the current operation, run "osm ns-op-show OPERATION_ID"' - ) - - -def nsd_list(ctx, filter, long): - logger.debug("") - if filter: - check_client_version(ctx.obj, "--filter") - filter = "&".join(filter) - resp = ctx.obj.nsd.list(filter) - else: - resp = ctx.obj.nsd.list() - # print(yaml.safe_dump(resp)) - fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__ - if fullclassname == "osmclient.sol005.client.Client": - if long: - table = PrettyTable( - [ - "nsd name", - "id", - "onboarding state", - "operational state", - "usage state", - "date", - "last update", - ] - ) - else: - table = PrettyTable(["nsd name", "id"]) - for nsd in resp: - name = nsd.get("id", "-") - if long: - onb_state = nsd["_admin"].get("onboardingState", "-") - op_state = nsd["_admin"].get("operationalState", "-") - usage_state = nsd["_admin"].get("usageState", "-") - date = datetime.fromtimestamp(nsd["_admin"]["created"]).strftime( - "%Y-%m-%dT%H:%M:%S" - ) - last_update = datetime.fromtimestamp( - nsd["_admin"]["modified"] - ).strftime("%Y-%m-%dT%H:%M:%S") - table.add_row( - [ - name, - nsd["_id"], - onb_state, - op_state, - usage_state, - date, - last_update, - ] - ) - else: - table.add_row([name, nsd["_id"]]) - else: - table = PrettyTable(["nsd name", "id"]) - for nsd in resp: - table.add_row([nsd["name"], nsd["id"]]) - table.align = "l" - print(table) - - -@cli_osm.command(name="nsd-list", short_help="list all NS packages") -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the NSD/NSpkg matching the filter", -) -@click.option("--long", is_flag=True, help="get more details") -@click.pass_context -def nsd_list1(ctx, filter, long): - """list all NSD/NS pkg in the system""" - logger.debug("") - nsd_list(ctx, filter, long) - - -@cli_osm.command(name="nspkg-list", short_help="list all NS packages") -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the NSD/NSpkg matching the filter", -) -@click.option("--long", is_flag=True, help="get more details") -@click.pass_context -def nsd_list2(ctx, filter, long): - """list all NS packages""" - logger.debug("") - nsd_list(ctx, filter, long) - - -def pkg_repo_list(ctx, pkgtype, filter, repo, long): - resp = ctx.obj.osmrepo.pkg_list(pkgtype, filter, repo) - if long: - table = PrettyTable( - ["nfpkg name", "vendor", "version", "latest", "description", "repository"] - ) - else: - table = PrettyTable(["nfpkg name", "repository"]) - for vnfd in resp: - name = vnfd.get("id", vnfd.get("name", "-")) - repository = vnfd.get("repository") - if long: - vendor = vnfd.get("provider", vnfd.get("vendor")) - version = vnfd.get("version") - description = vnfd.get("description") - latest = vnfd.get("latest") - table.add_row([name, vendor, version, latest, description, repository]) - else: - table.add_row([name, repository]) - table.align = "l" - print(table) - - -def vnfd_list(ctx, nf_type, filter, long): - logger.debug("") - if nf_type: - check_client_version(ctx.obj, "--nf_type") - elif filter: - check_client_version(ctx.obj, "--filter") - if filter: - filter = "&".join(filter) - if nf_type: - if nf_type == "vnf": - nf_filter = "_admin.type=vnfd" - elif nf_type == "pnf": - nf_filter = "_admin.type=pnfd" - elif nf_type == "hnf": - nf_filter = "_admin.type=hnfd" - else: - raise ClientException( - 'wrong value for "--nf_type" option, allowed values: vnf, pnf, hnf' - ) - if filter: - filter = "{}&{}".format(nf_filter, filter) - else: - filter = nf_filter - if filter: - resp = ctx.obj.vnfd.list(filter) - else: - resp = ctx.obj.vnfd.list() - # print(yaml.safe_dump(resp)) - fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__ - if fullclassname == "osmclient.sol005.client.Client": - if long: - table = PrettyTable( - [ - "nfpkg name", - "id", - "desc type", - "vendor", - "version", - "onboarding state", - "operational state", - "usage state", - "date", - "last update", - ] - ) - else: - table = PrettyTable(["nfpkg name", "id", "desc type"]) - for vnfd in resp: - name = vnfd.get("id", vnfd.get("name", "-")) - descriptor_type = "sol006" if "product-name" in vnfd else "rel8" - if long: - onb_state = vnfd["_admin"].get("onboardingState", "-") - op_state = vnfd["_admin"].get("operationalState", "-") - vendor = vnfd.get("provider", vnfd.get("vendor")) - version = vnfd.get("version") - usage_state = vnfd["_admin"].get("usageState", "-") - date = datetime.fromtimestamp(vnfd["_admin"]["created"]).strftime( - "%Y-%m-%dT%H:%M:%S" - ) - last_update = datetime.fromtimestamp( - vnfd["_admin"]["modified"] - ).strftime("%Y-%m-%dT%H:%M:%S") - table.add_row( - [ - name, - vnfd["_id"], - descriptor_type, - vendor, - version, - onb_state, - op_state, - usage_state, - date, - last_update, - ] - ) - else: - table.add_row([name, vnfd["_id"], descriptor_type]) - else: - table = PrettyTable(["nfpkg name", "id"]) - for vnfd in resp: - table.add_row([vnfd["name"], vnfd["id"]]) - table.align = "l" - print(table) - - -@cli_osm.command(name="vnfd-list", short_help="list all xNF packages (VNF, HNF, PNF)") -@click.option("--nf_type", help="type of NF (vnf, pnf, hnf)") -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the NF pkg matching the filter", -) -@click.option("--long", is_flag=True, help="get more details") -@click.pass_context -def vnfd_list1(ctx, nf_type, filter, long): - """list all xNF packages (VNF, HNF, PNF)""" - logger.debug("") - vnfd_list(ctx, nf_type, filter, long) - - -@cli_osm.command(name="vnfpkg-list", short_help="list all xNF packages (VNF, HNF, PNF)") -@click.option("--nf_type", help="type of NF (vnf, pnf, hnf)") -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the NFpkg matching the filter", -) -@click.option("--long", is_flag=True, help="get more details") -@click.pass_context -def vnfd_list2(ctx, nf_type, filter, long): - """list all xNF packages (VNF, HNF, PNF)""" - logger.debug("") - vnfd_list(ctx, nf_type, filter, long) - - -@cli_osm.command(name="nfpkg-list", short_help="list all xNF packages (VNF, HNF, PNF)") -@click.option("--nf_type", help="type of NF (vnf, pnf, hnf)") -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the NFpkg matching the filter", -) -@click.option("--long", is_flag=True, help="get more details") -@click.pass_context -def nfpkg_list(ctx, nf_type, filter, long): - """list all xNF packages (VNF, HNF, PNF)""" - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - vnfd_list(ctx, nf_type, filter, long) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command( - name="vnfpkg-repo-list", short_help="list all xNF from OSM repositories" -) -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the NFpkg matching the filter", -) -@click.option( - "--repo", default=None, help="restricts the list to a particular OSM repository" -) -@click.option("--long", is_flag=True, help="get more details") -@click.pass_context -def nfpkg_repo_list1(ctx, filter, repo, long): - """list xNF packages from OSM repositories""" - pkgtype = "vnf" - pkg_repo_list(ctx, pkgtype, filter, repo, long) - - -@cli_osm.command( - name="nfpkg-repo-list", short_help="list all xNF from OSM repositories" -) -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the NFpkg matching the filter", -) -@click.option( - "--repo", default=None, help="restricts the list to a particular OSM repository" -) -@click.option("--long", is_flag=True, help="get more details") -@click.pass_context -def nfpkg_repo_list2(ctx, filter, repo, long): - """list xNF packages from OSM repositories""" - pkgtype = "vnf" - pkg_repo_list(ctx, pkgtype, filter, repo, long) - - -def vnf_list(ctx, ns, filter, long): - # try: - if ns or filter: - if ns: - check_client_version(ctx.obj, "--ns") - if filter: - filter = "&".join(filter) - check_client_version(ctx.obj, "--filter") - resp = ctx.obj.vnf.list(ns, filter) - else: - resp = ctx.obj.vnf.list() - # except ClientException as e: - # print(str(e)) - # exit(1) - fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__ - if fullclassname == "osmclient.sol005.client.Client": - field_names = [ - "vnf id", - "name", - "ns id", - "vnf member index", - "vnfd name", - "vim account id", - "ip address", - ] - if long: - field_names = [ - "vnf id", - "name", - "ns id", - "vnf member index", - "vnfd name", - "vim account id", - "ip address", - "date", - "last update", - ] - table = PrettyTable(field_names) - for vnfr in resp: - name = vnfr["name"] if "name" in vnfr else "-" - new_row = [ - vnfr["_id"], - name, - vnfr["nsr-id-ref"], - vnfr["member-vnf-index-ref"], - vnfr["vnfd-ref"], - vnfr["vim-account-id"], - vnfr["ip-address"], - ] - if long: - date = datetime.fromtimestamp(vnfr["_admin"]["created"]).strftime( - "%Y-%m-%dT%H:%M:%S" - ) - last_update = datetime.fromtimestamp( - vnfr["_admin"]["modified"] - ).strftime("%Y-%m-%dT%H:%M:%S") - new_row.extend([date, last_update]) - table.add_row(new_row) - else: - table = PrettyTable(["vnf name", "id", "operational status", "config status"]) - for vnfr in resp: - if "mgmt-interface" not in vnfr: - vnfr["mgmt-interface"] = {} - vnfr["mgmt-interface"]["ip-address"] = None - table.add_row( - [ - vnfr["name"], - vnfr["id"], - vnfr["operational-status"], - vnfr["config-status"], - ] - ) - table.align = "l" - print(table) - - -@cli_osm.command(name="vnf-list", short_help="list all NF instances") -@click.option( - "--ns", default=None, help="NS instance id or name to restrict the NF list" -) -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the NF instances matching the filter.", -) -@click.option("--long", is_flag=True, help="get more details") -@click.pass_context -def vnf_list1(ctx, ns, filter, long): - """list all NF instances""" - logger.debug("") - vnf_list(ctx, ns, filter, long) - - -@cli_osm.command(name="nsd-repo-list", short_help="list all NS from OSM repositories") -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the NS matching the filter", -) -@click.option( - "--repo", default=None, help="restricts the list to a particular OSM repository" -) -@click.option("--long", is_flag=True, help="get more details") -@click.pass_context -def nspkg_repo_list(ctx, filter, repo, long): - """list xNF packages from OSM repositories""" - pkgtype = "ns" - pkg_repo_list(ctx, pkgtype, filter, repo, long) - - -@cli_osm.command(name="nspkg-repo-list", short_help="list all NS from OSM repositories") -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the NS matching the filter", -) -@click.option( - "--repo", default=None, help="restricts the list to a particular OSM repository" -) -@click.option("--long", is_flag=True, help="get more details") -@click.pass_context -def nspkg_repo_list2(ctx, filter, repo, long): - """list xNF packages from OSM repositories""" - pkgtype = "ns" - pkg_repo_list(ctx, pkgtype, filter, repo, long) - - -@cli_osm.command(name="nf-list", short_help="list all NF instances") -@click.option( - "--ns", default=None, help="NS instance id or name to restrict the NF list" -) -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the NF instances matching the filter.", -) -@click.option("--long", is_flag=True, help="get more details") -@click.pass_context -def nf_list(ctx, ns, filter, long): - """list all NF instances - - \b - Options: - --ns TEXT NS instance id or name to restrict the VNF list - --filter filterExpr Restricts the list to the VNF instances matching the filter - - \b - filterExpr consists of one or more strings formatted according to "simpleFilterExpr", - concatenated using the "&" character: - - \b - filterExpr := ["&"]* - simpleFilterExpr := ["."]*["."]"="[","]* - op := "eq" | "neq" | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont" - attrName := string - value := scalar value - - \b - where: - * zero or more occurrences - ? zero or one occurrence - [] grouping of expressions to be used with ? and * - "" quotation marks for marking string constants - <> name separator - - \b - "AttrName" is the name of one attribute in the data type that defines the representation - of the resource. The dot (".") character in "simpleFilterExpr" allows concatenation of - entries to filter by attributes deeper in the hierarchy of a structured document. - "Op" stands for the comparison operator. If the expression has concatenated - entries, it means that the operator "op" is applied to the attribute addressed by the last - entry included in the concatenation. All simple filter expressions are combined - by the "AND" logical operator. In a concatenation of entries in a , - the rightmost "attrName" entry in a "simpleFilterExpr" is called "leaf attribute". The - concatenation of all "attrName" entries except the leaf attribute is called the "attribute - prefix". If an attribute referenced in an expression is an array, an object that contains a - corresponding array shall be considered to match the expression if any of the elements in the - array matches all expressions that have the same attribute prefix. - - \b - Filter examples: - --filter vim-account-id= - --filter vnfd-ref= - --filter vdur.ip-address= - --filter vnfd-ref=,vdur.ip-address= - """ - logger.debug("") - vnf_list(ctx, ns, filter, long) - - -@cli_osm.command( - name="ns-op-list", short_help="shows the history of operations over a NS instance" -) -@click.argument("name") -@click.option( - "--long", is_flag=True, help="get more details of the NS operation (date, )." -) -@click.pass_context -def ns_op_list(ctx, name, long): - """shows the history of operations over a NS instance - - NAME: name or ID of the NS instance - """ - - def formatParams(params): - if params["lcmOperationType"] == "instantiate": - params.pop("nsDescription") - params.pop("nsName") - params.pop("nsdId") - params.pop("nsr_id") - elif params["lcmOperationType"] == "action": - params.pop("primitive") - params.pop("lcmOperationType") - params.pop("nsInstanceId") - return params - - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - resp = ctx.obj.ns.list_op(name) - # except ClientException as e: - # print(str(e)) - # exit(1) - - if long: - table = PrettyTable( - [ - "id", - "operation", - "action_name", - "operation_params", - "status", - "date", - "last update", - "detail", - ] - ) - else: - table = PrettyTable( - ["id", "operation", "action_name", "status", "date", "detail"] - ) - - # print(yaml.safe_dump(resp)) - for op in resp: - action_name = "N/A" - if op["lcmOperationType"] == "action": - action_name = op["operationParams"]["primitive"] - detail = "-" - if op["operationState"] == "PROCESSING": - if op.get("queuePosition") is not None and op.get("queuePosition") > 0: - detail = "In queue. Current position: {}".format(op["queuePosition"]) - elif op["lcmOperationType"] in ("instantiate", "terminate"): - if op["stage"]: - detail = op["stage"] - elif op["operationState"] in ("FAILED", "FAILED_TEMP"): - detail = op.get("errorMessage", "-") - date = datetime.fromtimestamp(op["startTime"]).strftime("%Y-%m-%dT%H:%M:%S") - last_update = datetime.fromtimestamp(op["statusEnteredTime"]).strftime( - "%Y-%m-%dT%H:%M:%S" - ) - if long: - table.add_row( - [ - op["id"], - op["lcmOperationType"], - action_name, - wrap_text( - text=json.dumps(formatParams(op["operationParams"]), indent=2), - width=50, - ), - op["operationState"], - date, - last_update, - wrap_text(text=detail, width=50), - ] - ) - else: - table.add_row( - [ - op["id"], - op["lcmOperationType"], - action_name, - op["operationState"], - date, - wrap_text(text=detail or "", width=50), - ] - ) - table.align = "l" - print(table) - - -def nsi_list(ctx, filter): - """list all Network Slice Instances""" - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - if filter: - filter = "&".join(filter) - resp = ctx.obj.nsi.list(filter) - # except ClientException as e: - # print(str(e)) - # exit(1) - table = PrettyTable( - [ - "netslice instance name", - "id", - "operational status", - "config status", - "detailed status", - ] - ) - for nsi in resp: - nsi_name = nsi["name"] - nsi_id = nsi["_id"] - opstatus = ( - nsi["operational-status"] if "operational-status" in nsi else "Not found" - ) - configstatus = nsi["config-status"] if "config-status" in nsi else "Not found" - detailed_status = ( - nsi["detailed-status"] if "detailed-status" in nsi else "Not found" - ) - if configstatus == "config_not_needed": - configstatus = "configured (no charms)" - table.add_row([nsi_name, nsi_id, opstatus, configstatus, detailed_status]) - table.align = "l" - print(table) - - -@cli_osm.command(name="nsi-list", short_help="list all Network Slice Instances (NSI)") -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the Network Slice Instances matching the filter", -) -@click.pass_context -def nsi_list1(ctx, filter): - """list all Network Slice Instances (NSI)""" - logger.debug("") - nsi_list(ctx, filter) - - -@cli_osm.command( - name="netslice-instance-list", short_help="list all Network Slice Instances (NSI)" -) -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the Network Slice Instances matching the filter", -) -@click.pass_context -def nsi_list2(ctx, filter): - """list all Network Slice Instances (NSI)""" - logger.debug("") - nsi_list(ctx, filter) - - -def nst_list(ctx, filter): - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - if filter: - filter = "&".join(filter) - resp = ctx.obj.nst.list(filter) - # except ClientException as e: - # print(str(e)) - # exit(1) - # print(yaml.safe_dump(resp)) - table = PrettyTable(["nst name", "id"]) - for nst in resp: - name = nst["name"] if "name" in nst else "-" - table.add_row([name, nst["_id"]]) - table.align = "l" - print(table) - - -@cli_osm.command(name="nst-list", short_help="list all Network Slice Templates (NST)") -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the NST matching the filter", -) -@click.pass_context -def nst_list1(ctx, filter): - """list all Network Slice Templates (NST) in the system""" - logger.debug("") - nst_list(ctx, filter) - - -@cli_osm.command( - name="netslice-template-list", short_help="list all Network Slice Templates (NST)" -) -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the NST matching the filter", -) -@click.pass_context -def nst_list2(ctx, filter): - """list all Network Slice Templates (NST) in the system""" - logger.debug("") - nst_list(ctx, filter) - - -def nsi_op_list(ctx, name): - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - resp = ctx.obj.nsi.list_op(name) - # except ClientException as e: - # print(str(e)) - # exit(1) - table = PrettyTable(["id", "operation", "status"]) - for op in resp: - table.add_row([op["id"], op["lcmOperationType"], op["operationState"]]) - table.align = "l" - print(table) - - -@cli_osm.command( - name="nsi-op-list", - short_help="shows the history of operations over a Network Slice Instance (NSI)", -) -@click.argument("name") -@click.pass_context -def nsi_op_list1(ctx, name): - """shows the history of operations over a Network Slice Instance (NSI) - - NAME: name or ID of the Network Slice Instance - """ - logger.debug("") - nsi_op_list(ctx, name) - - -@cli_osm.command( - name="netslice-instance-op-list", - short_help="shows the history of operations over a Network Slice Instance (NSI)", -) -@click.argument("name") -@click.pass_context -def nsi_op_list2(ctx, name): - """shows the history of operations over a Network Slice Instance (NSI) - - NAME: name or ID of the Network Slice Instance - """ - logger.debug("") - nsi_op_list(ctx, name) - - -@cli_osm.command(name="pdu-list", short_help="list all Physical Deployment Units (PDU)") -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the Physical Deployment Units matching the filter", -) -@click.pass_context -def pdu_list(ctx, filter): - """list all Physical Deployment Units (PDU)""" - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - if filter: - filter = "&".join(filter) - resp = ctx.obj.pdu.list(filter) - # except ClientException as e: - # print(str(e)) - # exit(1) - table = PrettyTable(["pdu name", "id", "type", "mgmt ip address"]) - for pdu in resp: - pdu_name = pdu["name"] - pdu_id = pdu["_id"] - pdu_type = pdu["type"] - pdu_ipaddress = "None" - for iface in pdu["interfaces"]: - if iface["mgmt"]: - pdu_ipaddress = iface["ip-address"] - break - table.add_row([pdu_name, pdu_id, pdu_type, pdu_ipaddress]) - table.align = "l" - print(table) - - -#################### -# SHOW operations -#################### - - -def nsd_show(ctx, name, literal): - logger.debug("") - # try: - resp = ctx.obj.nsd.get(name) - # resp = ctx.obj.nsd.get_individual(name) - # 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(["field", "value"]) - 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) - - -@cli_osm.command(name="nsd-show", short_help="shows the details of a NS package") -@click.option("--literal", is_flag=True, help="print literally, no pretty table") -@click.argument("name") -@click.pass_context -def nsd_show1(ctx, name, literal): - """shows the content of a NSD - - NAME: name or ID of the NSD/NSpkg - """ - logger.debug("") - nsd_show(ctx, name, literal) - - -@cli_osm.command(name="nspkg-show", short_help="shows the details of a NS package") -@click.option("--literal", is_flag=True, help="print literally, no pretty table") -@click.argument("name") -@click.pass_context -def nsd_show2(ctx, name, literal): - """shows the content of a NSD - - NAME: name or ID of the NSD/NSpkg - """ - logger.debug("") - nsd_show(ctx, name, literal) - - -def vnfd_show(ctx, name, literal): - logger.debug("") - # try: - resp = ctx.obj.vnfd.get(name) - # resp = ctx.obj.vnfd.get_individual(name) - # 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(["field", "value"]) - 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) - - -def pkg_repo_show(ctx, pkgtype, name, repo, version, filter, literal): - logger.debug("") - if filter: - filter = "&".join(filter) - # try: - resp = ctx.obj.osmrepo.pkg_get(pkgtype, name, repo, version, filter) - - if literal: - print(yaml.safe_dump(resp, indent=4, default_flow_style=False)) - return - pkgtype += "d" - catalog = pkgtype + "-catalog" - full_catalog = pkgtype + ":" + catalog - if resp.get(catalog): - resp = resp.pop(catalog)[pkgtype][0] - elif resp.get(full_catalog): - resp = resp.pop(full_catalog)[pkgtype][0] - - table = PrettyTable(["field", "value"]) - 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) - - -@cli_osm.command(name="vnfd-show", short_help="shows the details of a NF package") -@click.option("--literal", is_flag=True, help="print literally, no pretty table") -@click.argument("name") -@click.pass_context -def vnfd_show1(ctx, name, literal): - """shows the content of a VNFD - - NAME: name or ID of the VNFD/VNFpkg - """ - logger.debug("") - vnfd_show(ctx, name, literal) - - -@cli_osm.command(name="vnfpkg-show", short_help="shows the details of a NF package") -@click.option("--literal", is_flag=True, help="print literally, no pretty table") -@click.argument("name") -@click.pass_context -def vnfd_show2(ctx, name, literal): - """shows the content of a VNFD - - NAME: name or ID of the VNFD/VNFpkg - """ - logger.debug("") - vnfd_show(ctx, name, literal) - - -@cli_osm.command( - name="vnfpkg-repo-show", - short_help="shows the details of a NF package in an OSM repository", -) -@click.option("--literal", is_flag=True, help="print literally, no pretty table") -@click.option("--repo", required=True, help="Repository name") -@click.argument("name") -@click.option("--filter", default=None, multiple=True, help="filter by fields") -@click.option("--version", default="latest", help="package version") -@click.pass_context -def vnfd_show3(ctx, name, repo, version, literal=None, filter=None): - """shows the content of a VNFD in a repository - - NAME: name or ID of the VNFD/VNFpkg - """ - pkgtype = "vnf" - pkg_repo_show(ctx, pkgtype, name, repo, version, filter, literal) - - -@cli_osm.command( - name="nsd-repo-show", - short_help="shows the details of a NS package in an OSM repository", -) -@click.option("--literal", is_flag=True, help="print literally, no pretty table") -@click.option("--repo", required=True, help="Repository name") -@click.argument("name") -@click.option("--filter", default=None, multiple=True, help="filter by fields") -@click.option("--version", default="latest", help="package version") -@click.pass_context -def nsd_repo_show(ctx, name, repo, version, literal=None, filter=None): - """shows the content of a VNFD in a repository - - NAME: name or ID of the VNFD/VNFpkg - """ - pkgtype = "ns" - pkg_repo_show(ctx, pkgtype, name, repo, version, filter, literal) - - -@cli_osm.command( - name="nspkg-repo-show", - short_help="shows the details of a NS package in an OSM repository", -) -@click.option("--literal", is_flag=True, help="print literally, no pretty table") -@click.option("--repo", required=True, help="Repository name") -@click.argument("name") -@click.option("--filter", default=None, multiple=True, help="filter by fields") -@click.option("--version", default="latest", help="package version") -@click.pass_context -def nsd_repo_show2(ctx, name, repo, version, literal=None, filter=None): - """shows the content of a VNFD in a repository - - NAME: name or ID of the VNFD/VNFpkg - """ - pkgtype = "ns" - pkg_repo_show(ctx, pkgtype, name, repo, version, filter, literal) - - -@cli_osm.command(name="nfpkg-show", short_help="shows the details of a NF package") -@click.option("--literal", is_flag=True, help="print literally, no pretty table") -@click.argument("name") -@click.pass_context -def nfpkg_show(ctx, name, literal): - """shows the content of a NF Descriptor - - NAME: name or ID of the NFpkg - """ - logger.debug("") - vnfd_show(ctx, name, literal) - - -@cli_osm.command( - name="nfpkg-repo-show", - short_help="shows the details of a NF package in an OSM repository", -) -@click.option("--literal", is_flag=True, help="print literally, no pretty table") -@click.option("--repo", required=True, help="Repository name") -@click.argument("name") -@click.option("--filter", default=None, multiple=True, help="filter by fields") -@click.option("--version", default="latest", help="package version") -@click.pass_context -def vnfd_show4(ctx, name, repo, version, literal=None, filter=None): - """shows the content of a VNFD in a repository - - NAME: name or ID of the VNFD/VNFpkg - """ - pkgtype = "vnf" - pkg_repo_show(ctx, pkgtype, name, repo, version, filter, literal) - - -@cli_osm.command(name="ns-show", short_help="shows the info of a NS instance") -@click.argument("name") -@click.option("--literal", is_flag=True, help="print literally, no pretty table") -@click.option( - "--filter", - multiple=True, - help="restricts the information to the fields in the filter", -) -@click.pass_context -def ns_show(ctx, name, literal, filter): - """shows the info of a NS instance - - NAME: name or ID of the NS instance - """ - logger.debug("") - # try: - ns = ctx.obj.ns.get(name) - # except ClientException as e: - # print(str(e)) - # exit(1) - - if literal: - print(yaml.safe_dump(ns, indent=4, default_flow_style=False)) - return - - table = PrettyTable(["field", "value"]) - - for k, v in list(ns.items()): - if not filter or k in filter: - table.add_row([k, wrap_text(text=json.dumps(v, indent=2), width=100)]) - - fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__ - if fullclassname != "osmclient.sol005.client.Client": - nsopdata = ctx.obj.ns.get_opdata(ns["id"]) - nsr_optdata = nsopdata["nsr:nsr"] - for k, v in list(nsr_optdata.items()): - if not filter or k in filter: - table.add_row([k, wrap_text(json.dumps(v, indent=2), width=100)]) - table.align = "l" - print(table) - - -@cli_osm.command(name="vnf-show", short_help="shows the info of a VNF instance") -@click.argument("name") -@click.option("--literal", is_flag=True, help="print literally, no pretty table") -@click.option( - "--filter", - multiple=True, - help="restricts the information to the fields in the filter", -) -@click.option("--kdu", default=None, help="KDU name (whose status will be shown)") -@click.pass_context -def vnf_show(ctx, name, literal, filter, kdu): - """shows the info of a VNF instance - - NAME: name or ID of the VNF instance - """ - - def print_kdu_status(op_info_status): - """print KDU status properly formatted""" - try: - op_status = yaml.safe_load(op_info_status) - if ( - "namespace" in op_status - and "info" in op_status - and "last_deployed" in op_status["info"] - and "status" in op_status["info"] - and "code" in op_status["info"]["status"] - and "resources" in op_status["info"]["status"] - and "seconds" in op_status["info"]["last_deployed"] - ): - last_deployed_time = datetime.fromtimestamp( - op_status["info"]["last_deployed"]["seconds"] - ).strftime("%a %b %d %I:%M:%S %Y") - print("LAST DEPLOYED: {}".format(last_deployed_time)) - print("NAMESPACE: {}".format(op_status["namespace"])) - status_code = "UNKNOWN" - if op_status["info"]["status"]["code"] == 1: - status_code = "DEPLOYED" - print("STATUS: {}".format(status_code)) - print() - print("RESOURCES:") - print(op_status["info"]["status"]["resources"]) - if "notes" in op_status["info"]["status"]: - print("NOTES:") - print(op_status["info"]["status"]["notes"]) - else: - print(op_info_status) - except Exception: - print(op_info_status) - - logger.debug("") - if kdu: - if literal: - raise ClientException( - '"--literal" option is incompatible with "--kdu" option' - ) - if filter: - raise ClientException( - '"--filter" option is incompatible with "--kdu" option' - ) - - # try: - check_client_version(ctx.obj, ctx.command.name) - resp = ctx.obj.vnf.get(name) - - if kdu: - ns_id = resp["nsr-id-ref"] - op_data = {} - op_data["member_vnf_index"] = resp["member-vnf-index-ref"] - op_data["kdu_name"] = kdu - op_data["primitive"] = "status" - op_data["primitive_params"] = {} - op_id = ctx.obj.ns.exec_op(ns_id, op_name="action", op_data=op_data, wait=False) - t = 0 - while t < 30: - op_info = ctx.obj.ns.get_op(op_id) - if op_info["operationState"] == "COMPLETED": - print_kdu_status(op_info["detailed-status"]) - return - time.sleep(5) - t += 5 - print("Could not determine KDU status") - return - - if literal: - print(yaml.safe_dump(resp, indent=4, default_flow_style=False)) - return - - table = PrettyTable(["field", "value"]) - 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) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -# @cli_osm.command(name='vnf-monitoring-show') -# @click.argument('vnf_name') -# @click.pass_context -# def vnf_monitoring_show(ctx, vnf_name): -# try: -# check_client_version(ctx.obj, ctx.command.name, 'v1') -# resp = ctx.obj.vnf.get_monitoring(vnf_name) -# except ClientException as e: -# print(str(e)) -# exit(1) -# -# table = PrettyTable(['vnf name', 'monitoring name', 'value', 'units']) -# if resp is not None: -# for monitor in resp: -# table.add_row( -# [vnf_name, -# monitor['name'], -# monitor['value-integer'], -# monitor['units']]) -# table.align = 'l' -# print(table) - - -# @cli_osm.command(name='ns-monitoring-show') -# @click.argument('ns_name') -# @click.pass_context -# def ns_monitoring_show(ctx, ns_name): -# try: -# check_client_version(ctx.obj, ctx.command.name, 'v1') -# resp = ctx.obj.ns.get_monitoring(ns_name) -# except ClientException as e: -# print(str(e)) -# exit(1) -# -# table = PrettyTable(['vnf name', 'monitoring name', 'value', 'units']) -# for key, val in list(resp.items()): -# for monitor in val: -# table.add_row( -# [key, -# monitor['name'], -# monitor['value-integer'], -# monitor['units']]) -# table.align = 'l' -# print(table) - - -@cli_osm.command(name="ns-op-show", short_help="shows the info of a NS operation") -@click.argument("id") -@click.option( - "--filter", - 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 ns_op_show(ctx, id, filter, literal): - """shows the detailed info of a NS operation - - ID: operation identifier - """ - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - op_info = ctx.obj.ns.get_op(id) - # except ClientException as e: - # print(str(e)) - # exit(1) - - if literal: - print(yaml.safe_dump(op_info, indent=4, default_flow_style=False)) - return - - table = PrettyTable(["field", "value"]) - for k, v in list(op_info.items()): - if not filter or k in filter: - table.add_row([k, wrap_text(json.dumps(v, indent=2), 100)]) - table.align = "l" - print(table) - - -def nst_show(ctx, name, literal): - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - resp = ctx.obj.nst.get(name) - # resp = ctx.obj.nst.get_individual(name) - # 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(["field", "value"]) - for k, v in list(resp.items()): - table.add_row([k, wrap_text(json.dumps(v, indent=2), 100)]) - table.align = "l" - print(table) - - -@cli_osm.command( - name="nst-show", short_help="shows the content of a Network Slice Template (NST)" -) -@click.option("--literal", is_flag=True, help="print literally, no pretty table") -@click.argument("name") -@click.pass_context -def nst_show1(ctx, name, literal): - """shows the content of a Network Slice Template (NST) - - NAME: name or ID of the NST - """ - logger.debug("") - nst_show(ctx, name, literal) - - -@cli_osm.command( - name="netslice-template-show", - short_help="shows the content of a Network Slice Template (NST)", -) -@click.option("--literal", is_flag=True, help="print literally, no pretty table") -@click.argument("name") -@click.pass_context -def nst_show2(ctx, name, literal): - """shows the content of a Network Slice Template (NST) - - NAME: name or ID of the NST - """ - logger.debug("") - nst_show(ctx, name, literal) - - -def nsi_show(ctx, name, literal, filter): - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - nsi = ctx.obj.nsi.get(name) - # except ClientException as e: - # print(str(e)) - # exit(1) - - if literal: - print(yaml.safe_dump(nsi, indent=4, default_flow_style=False)) - return - - table = PrettyTable(["field", "value"]) - - for k, v in list(nsi.items()): - if not filter or k in filter: - table.add_row([k, json.dumps(v, indent=2)]) - - table.align = "l" - print(table) - - -@cli_osm.command( - name="nsi-show", short_help="shows the content of a Network Slice Instance (NSI)" -) -@click.argument("name") -@click.option("--literal", is_flag=True, help="print literally, no pretty table") -@click.option( - "--filter", - multiple=True, - help="restricts the information to the fields in the filter", -) -@click.pass_context -def nsi_show1(ctx, name, literal, filter): - """shows the content of a Network Slice Instance (NSI) - - NAME: name or ID of the Network Slice Instance - """ - logger.debug("") - nsi_show(ctx, name, literal, filter) - - -@cli_osm.command( - name="netslice-instance-show", - short_help="shows the content of a Network Slice Instance (NSI)", -) -@click.argument("name") -@click.option("--literal", is_flag=True, help="print literally, no pretty table") -@click.option( - "--filter", - multiple=True, - help="restricts the information to the fields in the filter", -) -@click.pass_context -def nsi_show2(ctx, name, literal, filter): - """shows the content of a Network Slice Instance (NSI) - - NAME: name or ID of the Network Slice Instance - """ - logger.debug("") - nsi_show(ctx, name, literal, filter) - - -def nsi_op_show(ctx, id, filter): - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - op_info = ctx.obj.nsi.get_op(id) - # except ClientException as e: - # print(str(e)) - # exit(1) - - table = PrettyTable(["field", "value"]) - for k, v in list(op_info.items()): - if not filter or k in filter: - table.add_row([k, json.dumps(v, indent=2)]) - table.align = "l" - print(table) - - -@cli_osm.command( - name="nsi-op-show", - short_help="shows the info of an operation over a Network Slice Instance(NSI)", -) -@click.argument("id") -@click.option( - "--filter", - multiple=True, - help="restricts the information to the fields in the filter", -) -@click.pass_context -def nsi_op_show1(ctx, id, filter): - """shows the info of an operation over a Network Slice Instance(NSI) - - ID: operation identifier - """ - logger.debug("") - nsi_op_show(ctx, id, filter) - - -@cli_osm.command( - name="netslice-instance-op-show", - short_help="shows the info of an operation over a Network Slice Instance(NSI)", -) -@click.argument("id") -@click.option( - "--filter", - multiple=True, - help="restricts the information to the fields in the filter", -) -@click.pass_context -def nsi_op_show2(ctx, id, filter): - """shows the info of an operation over a Network Slice Instance(NSI) - - ID: operation identifier - """ - logger.debug("") - nsi_op_show(ctx, id, filter) - - -@cli_osm.command( - name="pdu-show", short_help="shows the content of a Physical Deployment Unit (PDU)" -) -@click.argument("name") -@click.option("--literal", is_flag=True, help="print literally, no pretty table") -@click.option( - "--filter", - multiple=True, - help="restricts the information to the fields in the filter", -) -@click.pass_context -def pdu_show(ctx, name, literal, filter): - """shows the content of a Physical Deployment Unit (PDU) - - NAME: name or ID of the PDU - """ - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - pdu = ctx.obj.pdu.get(name) - # except ClientException as e: - # print(str(e)) - # exit(1) - - if literal: - print(yaml.safe_dump(pdu, indent=4, default_flow_style=False)) - return - - table = PrettyTable(["field", "value"]) - - for k, v in list(pdu.items()): - if not filter or k in filter: - table.add_row([k, json.dumps(v, indent=2)]) - - table.align = "l" - print(table) - - -#################### -# CREATE operations -#################### - - -def nsd_create(ctx, filename, overwrite, skip_charm_build, repo, vendor, version): - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - if repo: - filename = ctx.obj.osmrepo.get_pkg("ns", filename, repo, vendor, version) - ctx.obj.nsd.create(filename, overwrite=overwrite, skip_charm_build=skip_charm_build) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="nsd-create", short_help="creates a new NSD/NSpkg") -@click.argument("filename") -@click.option( - "--overwrite", - "overwrite", - default=None, # hidden=True, - help="Deprecated. Use override", -) -@click.option( - "--override", - "overwrite", - default=None, - help="overrides fields in descriptor, format: " - '"key1.key2...=value[;key3...=value;...]"', -) -@click.option( - "--skip-charm-build", - default=False, - is_flag=True, - help="The charm will not be compiled, it is assumed to already exist", -) -@click.option("--repo", default=None, help="[repository]: Repository name") -@click.option("--vendor", default=None, help="[repository]: filter by vendor]") -@click.option( - "--version", - default="latest", - help="[repository]: filter by version. Default: latest", -) -@click.pass_context -def nsd_create1(ctx, filename, overwrite, skip_charm_build, repo, vendor, version): - """onboards a new NSpkg (alias of nspkg-create) (TO BE DEPRECATED) - - \b - FILENAME: NF Package tar.gz file, NF Descriptor YAML file or NF Package folder - If FILENAME is a file (NF Package tar.gz or NF Descriptor YAML), it is onboarded. - If FILENAME is an NF Package folder, it is built and then onboarded. - """ - logger.debug("") - nsd_create( - ctx, - filename, - overwrite=overwrite, - skip_charm_build=skip_charm_build, - repo=repo, - vendor=vendor, - version=version, - ) - - -@cli_osm.command(name="nspkg-create", short_help="creates a new NSD/NSpkg") -@click.argument("filename") -@click.option( - "--overwrite", - "overwrite", - default=None, # hidden=True, - help="Deprecated. Use override", -) -@click.option( - "--override", - "overwrite", - default=None, - help="overrides fields in descriptor, format: " - '"key1.key2...=value[;key3...=value;...]"', -) -@click.option( - "--skip-charm-build", - default=False, - is_flag=True, - help="The charm will not be compiled, it is assumed to already exist", -) -@click.option("--repo", default=None, help="[repository]: Repository name") -@click.option("--vendor", default=None, help="[repository]: filter by vendor]") -@click.option( - "--version", - default="latest", - help="[repository]: filter by version. Default: latest", -) -@click.pass_context -def nsd_pkg_create(ctx, filename, overwrite, skip_charm_build, repo, vendor, version): - """onboards a new NSpkg - \b - FILENAME: NF Package tar.gz file, NF Descriptor YAML file or NF Package folder - If FILENAME is a file (NF Package tar.gz or NF Descriptor YAML), it is onboarded. - If FILENAME is an NF Package folder, it is built and then onboarded. - """ - logger.debug("") - nsd_create( - ctx, - filename, - overwrite=overwrite, - skip_charm_build=skip_charm_build, - repo=repo, - vendor=vendor, - version=version, - ) - - -def vnfd_create( - ctx, - filename, - overwrite, - skip_charm_build, - override_epa, - override_nonepa, - override_paravirt, - repo, - vendor, - version, -): - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - if repo: - filename = ctx.obj.osmrepo.get_pkg("vnf", filename, repo, vendor, version) - ctx.obj.vnfd.create( - filename, - overwrite=overwrite, - skip_charm_build=skip_charm_build, - override_epa=override_epa, - override_nonepa=override_nonepa, - override_paravirt=override_paravirt, - ) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="vnfd-create", short_help="creates a new VNFD/VNFpkg") -@click.argument("filename") -@click.option( - "--overwrite", "overwrite", default=None, help="overwrite deprecated, use override" -) -@click.option( - "--override", - "overwrite", - default=None, - help="overrides fields in descriptor, format: " - '"key1.key2...=value[;key3...=value;...]"', -) -@click.option( - "--skip-charm-build", - default=False, - is_flag=True, - help="The charm will not be compiled, it is assumed to already exist", -) -@click.option( - "--override-epa", - required=False, - default=False, - is_flag=True, - help="adds guest-epa parameters to all VDU", -) -@click.option( - "--override-nonepa", - required=False, - default=False, - is_flag=True, - help="removes all guest-epa parameters from all VDU", -) -@click.option( - "--override-paravirt", - required=False, - default=False, - is_flag=True, - help="overrides all VDU interfaces to PARAVIRT", -) -@click.option("--repo", default=None, help="[repository]: Repository name") -@click.option("--vendor", default=None, help="[repository]: filter by vendor]") -@click.option( - "--version", - default="latest", - help="[repository]: filter by version. Default: latest", -) -@click.pass_context -def vnfd_create1( - ctx, - filename, - overwrite, - skip_charm_build, - override_epa, - override_nonepa, - override_paravirt, - repo, - vendor, - version, -): - """creates a new VNFD/VNFpkg - \b - FILENAME: NF Package tar.gz file, NF Descriptor YAML file or NF Package folder - If FILENAME is a file (NF Package tar.gz or NF Descriptor YAML), it is onboarded. - If FILENAME is an NF Package folder, it is built and then onboarded. - """ - logger.debug("") - vnfd_create( - ctx, - filename, - overwrite=overwrite, - skip_charm_build=skip_charm_build, - override_epa=override_epa, - override_nonepa=override_nonepa, - override_paravirt=override_paravirt, - repo=repo, - vendor=vendor, - version=version, - ) - - -@cli_osm.command(name="vnfpkg-create", short_help="creates a new VNFD/VNFpkg") -@click.argument("filename") -@click.option( - "--overwrite", - "overwrite", - default=None, # hidden=True, - help="Deprecated. Use override", -) -@click.option( - "--override", - "overwrite", - default=None, - help="overrides fields in descriptor, format: " - '"key1.key2...=value[;key3...=value;...]"', -) -@click.option( - "--skip-charm-build", - default=False, - is_flag=True, - help="The charm will not be compiled, it is assumed to already exist", -) -@click.option( - "--override-epa", - required=False, - default=False, - is_flag=True, - help="adds guest-epa parameters to all VDU", -) -@click.option( - "--override-nonepa", - required=False, - default=False, - is_flag=True, - help="removes all guest-epa parameters from all VDU", -) -@click.option( - "--override-paravirt", - required=False, - default=False, - is_flag=True, - help="overrides all VDU interfaces to PARAVIRT", -) -@click.option("--repo", default=None, help="[repository]: Repository name") -@click.option("--vendor", default=None, help="[repository]: filter by vendor]") -@click.option( - "--version", - default="latest", - help="[repository]: filter by version. Default: latest", -) -@click.pass_context -def vnfd_create2( - ctx, - filename, - overwrite, - skip_charm_build, - override_epa, - override_nonepa, - override_paravirt, - repo, - vendor, - version, -): - """creates a new VNFD/VNFpkg - \b - FILENAME: NF Package tar.gz file, NF Descriptor YAML file or NF Package folder - If FILENAME is a file (NF Package tar.gz or NF Descriptor YAML), it is onboarded. - If FILENAME is an NF Package folder, it is built and then onboarded. - """ - logger.debug("") - vnfd_create( - ctx, - filename, - overwrite=overwrite, - skip_charm_build=skip_charm_build, - override_epa=override_epa, - override_nonepa=override_nonepa, - override_paravirt=override_paravirt, - repo=repo, - vendor=vendor, - version=version, - ) - - -@cli_osm.command(name="nfpkg-create", short_help="creates a new NFpkg") -@click.argument("filename") -@click.option( - "--overwrite", - "overwrite", - default=None, # hidden=True, - help="Deprecated. Use override", -) -@click.option( - "--override", - "overwrite", - default=None, - help="overrides fields in descriptor, format: " - '"key1.key2...=value[;key3...=value;...]"', -) -@click.option( - "--skip-charm-build", - default=False, - is_flag=True, - help="The charm will not be compiled, it is assumed to already exist", -) -@click.option( - "--override-epa", - required=False, - default=False, - is_flag=True, - help="adds guest-epa parameters to all VDU", -) -@click.option( - "--override-nonepa", - required=False, - default=False, - is_flag=True, - help="removes all guest-epa parameters from all VDU", -) -@click.option( - "--override-paravirt", - required=False, - default=False, - is_flag=True, - help="overrides all VDU interfaces to PARAVIRT", -) -@click.option("--repo", default=None, help="[repository]: Repository name") -@click.option("--vendor", default=None, help="[repository]: filter by vendor]") -@click.option( - "--version", - default="latest", - help="[repository]: filter by version. Default: latest", -) -@click.pass_context -def nfpkg_create( - ctx, - filename, - overwrite, - skip_charm_build, - override_epa, - override_nonepa, - override_paravirt, - repo, - vendor, - version, -): - """creates a new NFpkg - - \b - FILENAME: NF Package tar.gz file, NF Descriptor YAML file or NF Package folder - If FILENAME is a file (NF Package tar.gz or NF Descriptor YAML), it is onboarded. - If FILENAME is an NF Package folder, it is built and then onboarded. - """ - logger.debug("") - vnfd_create( - ctx, - filename, - overwrite=overwrite, - skip_charm_build=skip_charm_build, - override_epa=override_epa, - override_nonepa=override_nonepa, - override_paravirt=override_paravirt, - repo=repo, - vendor=vendor, - version=version, - ) - - -@cli_osm.command(name="ns-create", short_help="creates a new Network Service instance") -@click.option("--ns_name", prompt=True, help="name of the NS instance") -@click.option("--nsd_name", prompt=True, help="name of the NS descriptor") -@click.option( - "--vim_account", - default=None, - help="default VIM account id or name for the deployment", -) -@click.option( - "--paas_account", - default=None, - help="default PaaS account id or name for the deployment", -) -@click.option("--admin_status", default="ENABLED", help="administration status") -@click.option( - "--ssh_keys", - default=None, - help="comma separated list of public key files to inject to vnfs", -) -@click.option("--config", default=None, help="ns specific yaml configuration") -@click.option("--config_file", default=None, help="ns specific yaml configuration file") -@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("--timeout", default=None, help="ns deployment timeout") -@click.pass_context -def ns_create( - ctx, - nsd_name, - ns_name, - vim_account, - paas_account, - admin_status, - ssh_keys, - config, - config_file, - wait, - timeout, -): - """creates a new NS instance""" - logger.debug("") - # try: - if config_file: - check_client_version(ctx.obj, "--config_file") - if config: - raise ClientException( - '"--config" option is incompatible with "--config_file" option' - ) - with open(config_file, "r") as cf: - config = cf.read() - if not (vim_account or paas_account): - raise ClientException( - 'specify "vim_account" or "paas_account", both options can not be empty' - ) - if vim_account and paas_account: - raise ClientException( - '"vim_account" and "paas_account" can not be used together, use only one of them' - ) - ctx.obj.ns.create( - nsd_name, - ns_name, - config=config, - ssh_keys=ssh_keys, - vim_account=vim_account, - paas_account=paas_account, - admin_status=admin_status, - wait=wait, - timeout=timeout, - ) - - -def nst_create(ctx, filename, overwrite): - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.nst.create(filename, overwrite) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command( - name="nst-create", short_help="creates a new Network Slice Template (NST)" -) -@click.argument("filename") -@click.option( - "--overwrite", - "overwrite", - default=None, # hidden=True, - help="Deprecated. Use override", -) -@click.option( - "--override", - "overwrite", - default=None, - help="overrides fields in descriptor, format: " - '"key1.key2...=value[;key3...=value;...]"', -) -@click.pass_context -def nst_create1(ctx, filename, overwrite): - """creates a new Network Slice Template (NST) - - FILENAME: NST package folder, NST yaml file or NSTpkg tar.gz file - """ - logger.debug("") - nst_create(ctx, filename, overwrite) - - -@cli_osm.command( - name="netslice-template-create", - short_help="creates a new Network Slice Template (NST)", -) -@click.argument("filename") -@click.option( - "--overwrite", - "overwrite", - default=None, # hidden=True, - help="Deprecated. Use override", -) -@click.option( - "--override", - "overwrite", - default=None, - help="overrides fields in descriptor, format: " - '"key1.key2...=value[;key3...=value;...]"', -) -@click.pass_context -def nst_create2(ctx, filename, overwrite): - """creates a new Network Slice Template (NST) - - FILENAME: NST yaml file or NSTpkg tar.gz file - """ - logger.debug("") - nst_create(ctx, filename, overwrite) - - -def nsi_create( - ctx, nst_name, nsi_name, vim_account, ssh_keys, config, config_file, wait -): - """creates a new Network Slice Instance (NSI)""" - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - if config_file: - if config: - raise ClientException( - '"--config" option is incompatible with "--config_file" option' - ) - with open(config_file, "r") as cf: - config = cf.read() - ctx.obj.nsi.create( - nst_name, - nsi_name, - config=config, - ssh_keys=ssh_keys, - account=vim_account, - wait=wait, - ) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="nsi-create", short_help="creates a new Network Slice Instance") -@click.option("--nsi_name", prompt=True, help="name of the Network Slice Instance") -@click.option("--nst_name", prompt=True, help="name of the Network Slice Template") -@click.option( - "--vim_account", - prompt=True, - help="default VIM account id or name for the deployment", -) -@click.option( - "--ssh_keys", default=None, help="comma separated list of keys to inject to vnfs" -) -@click.option( - "--config", - default=None, - help="Netslice specific yaml configuration:\n" - "netslice_subnet: [\n" - "id: TEXT, vim_account: TEXT,\n" - "vnf: [member-vnf-index: TEXT, vim_account: TEXT]\n" - "vld: [name: TEXT, vim-network-name: TEXT or DICT with vim_account, vim_net entries]\n" - "additionalParamsForNsi: {param: value, ...}\n" - "additionalParamsForsubnet: [{id: SUBNET_ID, additionalParamsForNs: {}, additionalParamsForVnf: {}}]\n" - "],\n" - "netslice-vld: [name: TEXT, vim-network-name: TEXT or DICT with vim_account, vim_net entries]", -) -@click.option( - "--config_file", default=None, help="nsi specific yaml configuration file" -) -@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 nsi_create1( - ctx, nst_name, nsi_name, vim_account, ssh_keys, config, config_file, wait -): - """creates a new Network Slice Instance (NSI)""" - logger.debug("") - nsi_create( - ctx, nst_name, nsi_name, vim_account, ssh_keys, config, config_file, wait=wait - ) - - -@cli_osm.command( - name="netslice-instance-create", short_help="creates a new Network Slice Instance" -) -@click.option("--nsi_name", prompt=True, help="name of the Network Slice Instance") -@click.option("--nst_name", prompt=True, help="name of the Network Slice Template") -@click.option( - "--vim_account", - prompt=True, - help="default VIM account id or name for the deployment", -) -@click.option( - "--ssh_keys", default=None, help="comma separated list of keys to inject to vnfs" -) -@click.option( - "--config", - default=None, - help="Netslice specific yaml configuration:\n" - "netslice_subnet: [\n" - "id: TEXT, vim_account: TEXT,\n" - "vnf: [member-vnf-index: TEXT, vim_account: TEXT]\n" - "vld: [name: TEXT, vim-network-name: TEXT or DICT with vim_account, vim_net entries]" - "],\n" - "netslice-vld: [name: TEXT, vim-network-name: TEXT or DICT with vim_account, vim_net entries]", -) -@click.option( - "--config_file", default=None, help="nsi specific yaml configuration file" -) -@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 nsi_create2( - ctx, nst_name, nsi_name, vim_account, ssh_keys, config, config_file, wait -): - """creates a new Network Slice Instance (NSI)""" - logger.debug("") - nsi_create( - ctx, nst_name, nsi_name, vim_account, ssh_keys, config, config_file, wait=wait - ) - - -@cli_osm.command( - name="pdu-create", short_help="adds a new Physical Deployment Unit to the catalog" -) -@click.option("--name", help="name of 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_create( - ctx, name, pdu_type, interface, description, vim_account, descriptor_file -): - """creates a new Physical Deployment Unit (PDU)""" - logger.debug("") - - 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 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()) - if name: - pdu["name"] = name - if pdu_type: - pdu["type"] = pdu_type - if description: - pdu["description"] = description - if vim_account: - pdu["vim_accounts"] = vim_account - if interface: - ifaces_list = [] - for iface in interface: - new_iface = {k: v for k, v in [i.split("=") for i in iface.split(",")]} - new_iface["mgmt"] = new_iface.get("mgmt", "false").lower() == "true" - ifaces_list.append(new_iface) - pdu["interfaces"] = ifaces_list - return pdu - - -#################### -# UPDATE operations -#################### - - -def nsd_update(ctx, name, content): - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.nsd.update(name, content) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="nsd-update", short_help="updates a NSD/NSpkg") -@click.argument("name") -@click.option( - "--content", - default=None, - help="filename with the NSD/NSpkg replacing the current one", -) -@click.pass_context -def nsd_update1(ctx, name, content): - """updates a NSD/NSpkg - - NAME: name or ID of the NSD/NSpkg - """ - logger.debug("") - nsd_update(ctx, name, content) - - -@cli_osm.command(name="nspkg-update", short_help="updates a NSD/NSpkg") -@click.argument("name") -@click.option( - "--content", - default=None, - help="filename with the NSD/NSpkg replacing the current one", -) -@click.pass_context -def nsd_update2(ctx, name, content): - """updates a NSD/NSpkg - - NAME: name or ID of the NSD/NSpkg - """ - logger.debug("") - nsd_update(ctx, name, content) - - -def vnfd_update(ctx, name, content): - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.vnfd.update(name, content) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="vnfd-update", short_help="updates a new VNFD/VNFpkg") -@click.argument("name") -@click.option( - "--content", - default=None, - help="filename with the VNFD/VNFpkg replacing the current one", -) -@click.pass_context -def vnfd_update1(ctx, name, content): - """updates a VNFD/VNFpkg - - NAME: name or ID of the VNFD/VNFpkg - """ - logger.debug("") - vnfd_update(ctx, name, content) - - -@cli_osm.command(name="vnfpkg-update", short_help="updates a VNFD/VNFpkg") -@click.argument("name") -@click.option( - "--content", - default=None, - help="filename with the VNFD/VNFpkg replacing the current one", -) -@click.pass_context -def vnfd_update2(ctx, name, content): - """updates a VNFD/VNFpkg - - NAME: VNFD yaml file or VNFpkg tar.gz file - """ - logger.debug("") - vnfd_update(ctx, name, content) - - -@cli_osm.command(name="nfpkg-update", short_help="updates a NFpkg") -@click.argument("name") -@click.option( - "--content", default=None, help="filename with the NFpkg replacing the current one" -) -@click.pass_context -def nfpkg_update(ctx, name, content): - """updates a NFpkg - - NAME: NF Descriptor yaml file or NFpkg tar.gz file - """ - logger.debug("") - vnfd_update(ctx, name, content) - - -def nst_update(ctx, name, content): - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.nst.update(name, content) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="nst-update", short_help="updates a Network Slice Template (NST)") -@click.argument("name") -@click.option( - "--content", - default=None, - help="filename with the NST/NSTpkg replacing the current one", -) -@click.pass_context -def nst_update1(ctx, name, content): - """updates a Network Slice Template (NST) - - NAME: name or ID of the NSD/NSpkg - """ - logger.debug("") - nst_update(ctx, name, content) - - -@cli_osm.command( - name="netslice-template-update", short_help="updates a Network Slice Template (NST)" -) -@click.argument("name") -@click.option( - "--content", - default=None, - help="filename with the NST/NSTpkg replacing the current one", -) -@click.pass_context -def nst_update2(ctx, name, content): - """updates a Network Slice Template (NST) - - NAME: name or ID of the NSD/NSpkg - """ - logger.debug("") - nst_update(ctx, name, content) - - -#################### -# DELETE operations -#################### - - -def nsd_delete(ctx, name, force): - logger.debug("") - # try: - if not force: - ctx.obj.nsd.delete(name) - else: - check_client_version(ctx.obj, "--force") - ctx.obj.nsd.delete(name, force) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="nsd-delete", short_help="deletes a NSD/NSpkg") -@click.argument("name") -@click.option( - "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" -) -@click.pass_context -def nsd_delete1(ctx, name, force): - """deletes a NSD/NSpkg - - NAME: name or ID of the NSD/NSpkg to be deleted - """ - logger.debug("") - nsd_delete(ctx, name, force) - - -@cli_osm.command(name="nspkg-delete", short_help="deletes a NSD/NSpkg") -@click.argument("name") -@click.option( - "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" -) -@click.pass_context -def nsd_delete2(ctx, name, force): - """deletes a NSD/NSpkg - - NAME: name or ID of the NSD/NSpkg to be deleted - """ - logger.debug("") - nsd_delete(ctx, name, force) - - -def vnfd_delete(ctx, name, force): - logger.debug("") - # try: - if not force: - ctx.obj.vnfd.delete(name) - else: - check_client_version(ctx.obj, "--force") - ctx.obj.vnfd.delete(name, force) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="vnfd-delete", short_help="deletes a VNFD/VNFpkg") -@click.argument("name") -@click.option( - "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" -) -@click.pass_context -def vnfd_delete1(ctx, name, force): - """deletes a VNFD/VNFpkg - - NAME: name or ID of the VNFD/VNFpkg to be deleted - """ - logger.debug("") - vnfd_delete(ctx, name, force) - - -@cli_osm.command(name="vnfpkg-delete", short_help="deletes a VNFD/VNFpkg") -@click.argument("name") -@click.option( - "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" -) -@click.pass_context -def vnfd_delete2(ctx, name, force): - """deletes a VNFD/VNFpkg - - NAME: name or ID of the VNFD/VNFpkg to be deleted - """ - logger.debug("") - vnfd_delete(ctx, name, force) - - -@cli_osm.command(name="nfpkg-delete", short_help="deletes a NFpkg") -@click.argument("name") -@click.option( - "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" -) -@click.pass_context -def nfpkg_delete(ctx, name, force): - """deletes a NFpkg - - NAME: name or ID of the NFpkg to be deleted - """ - logger.debug("") - vnfd_delete(ctx, name, force) - - -@cli_osm.command(name="ns-delete", short_help="deletes a NS instance") -@click.argument("name") -@click.option( - "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" -) -@click.option( - "--config", - default=None, - help="specific yaml configuration for the termination, e.g. '{autoremove: False, timeout_ns_terminate: " - "600, skip_terminate_primitives: True}'", -) -@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 ns_delete(ctx, name, force, config, wait): - """deletes a NS instance - - NAME: name or ID of the NS instance to be deleted - """ - logger.debug("") - # try: - if not force: - ctx.obj.ns.delete(name, config=config, wait=wait) - else: - check_client_version(ctx.obj, "--force") - ctx.obj.ns.delete(name, force, config=config, wait=wait) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -def nst_delete(ctx, name, force): - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.nst.delete(name, force) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="nst-delete", short_help="deletes a Network Slice Template (NST)") -@click.argument("name") -@click.option( - "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" -) -@click.pass_context -def nst_delete1(ctx, name, force): - """deletes a Network Slice Template (NST) - - NAME: name or ID of the NST/NSTpkg to be deleted - """ - logger.debug("") - nst_delete(ctx, name, force) - - -@cli_osm.command( - name="netslice-template-delete", short_help="deletes a Network Slice Template (NST)" -) -@click.argument("name") -@click.option( - "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" -) -@click.pass_context -def nst_delete2(ctx, name, force): - """deletes a Network Slice Template (NST) - - NAME: name or ID of the NST/NSTpkg to be deleted - """ - logger.debug("") - nst_delete(ctx, name, force) - - -def nsi_delete(ctx, name, force, wait): - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.nsi.delete(name, force, wait=wait) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="nsi-delete", short_help="deletes a Network Slice Instance (NSI)") -@click.argument("name") -@click.option( - "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" -) -@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 nsi_delete1(ctx, name, force, wait): - """deletes a Network Slice Instance (NSI) - - NAME: name or ID of the Network Slice instance to be deleted - """ - logger.debug("") - nsi_delete(ctx, name, force, wait=wait) - - -@cli_osm.command( - name="netslice-instance-delete", short_help="deletes a Network Slice Instance (NSI)" -) -@click.argument("name") -@click.option( - "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" -) -@click.pass_context -def nsi_delete2(ctx, name, force, wait): - """deletes a Network Slice Instance (NSI) - - NAME: name or ID of the Network Slice instance to be deleted - """ - logger.debug("") - nsi_delete(ctx, name, force, wait=wait) - - -@cli_osm.command( - name="pdu-delete", short_help="deletes a Physical Deployment Unit (PDU)" -) -@click.argument("name") -@click.option( - "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" -) -@click.pass_context -def pdu_delete(ctx, name, force): - """deletes a Physical Deployment Unit (PDU) - - NAME: name or ID of the PDU to be deleted - """ - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.pdu.delete(name, force) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -################# -# VIM operations -################# - - -def _check_ca_cert(vim_config: dict) -> None: - """ - Checks if the VIM has a CA certificate. - In that case, reads the content and add it to the config - : param vim_config: configuration provided with the VIM creation - : return: None - """ - - if vim_config.get("ca_cert"): - with open(vim_config["ca_cert"], "r") as cert_f: - vim_config["ca_cert_content"] = str(cert_f.read()) - del vim_config["ca_cert"] - - -@cli_osm.command(name="vim-create", short_help="creates a new VIM account") -@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( - "--sdn_controller", - default=None, - help="Name or id of the SDN controller associated to this VIM account", -) -@click.option( - "--sdn_port_mapping", - default=None, - help="File describing the port mapping between compute nodes' ports and switch ports", -) -@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("--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, - name, - user, - password, - 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("") - # try: - if sdn_controller: - check_client_version(ctx.obj, "--sdn_controller") - 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 - if vca: - vim["vca"] = vca - vim_config = create_config(config_file, config) - _check_ca_cert(vim_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) - - -@cli_osm.command(name="vim-update", short_help="updates a VIM account") -@click.argument("name") -@click.option("--newname", help="New name for the VIM account") -@click.option("--user", help="VIM username") -@click.option("--password", help="VIM password") -@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( - "--sdn_controller", - default=None, - help="Name or id of the SDN controller to be associated with this VIM" - "account. Use empty string to disassociate", -) -@click.option( - "--sdn_port_mapping", - default=None, - help="File describing the port mapping between compute nodes' ports and switch ports", -) -@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( - "--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, - name, - newname, - user, - password, - auth_url, - tenant, - config, - config_file, - account_type, - description, - sdn_controller, - sdn_port_mapping, - wait, - creds, - prometheus_config_file, -): - """updates a VIM account - - NAME: name or ID of the VIM account - """ - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - vim = {} - if newname: - vim["name"] = newname - if user: - vim["vim_user"] = user - if password: - vim["vim_password"] = password - if auth_url: - vim["vim_url"] = auth_url - if tenant: - vim["vim-tenant-name"] = tenant - if account_type: - vim["vim_type"] = account_type - if description: - vim["description"] = description - vim_config = None - if config or config_file: - vim_config = create_config(config_file, config) - _check_ca_cert(vim_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) - - -@cli_osm.command(name="vim-delete", short_help="deletes a VIM account") -@click.argument("name") -@click.option( - "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" -) -@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 vim_delete(ctx, name, force, wait): - """deletes a VIM account - - NAME: name or ID of the VIM account to be deleted - """ - logger.debug("") - # try: - if not force: - ctx.obj.vim.delete(name, wait=wait) - else: - check_client_version(ctx.obj, "--force") - ctx.obj.vim.delete(name, force, wait=wait) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="vim-list", short_help="list all VIM accounts") -# @click.option('--ro_update/--no_ro_update', -# default=False, -# help='update list from RO') -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the VIM accounts matching the filter", -) -@click.option( - "--long", - is_flag=True, - help="get more details of the NS (project, vim, deployment status, configuration status.", -) -@click.pass_context -def vim_list(ctx, filter, long): - """list all VIM accounts""" - logger.debug("") - if filter: - filter = "&".join(filter) - check_client_version(ctx.obj, "--filter") - # if ro_update: - # check_client_version(ctx.obj, '--ro_update', 'v1') - fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__ - if fullclassname == "osmclient.sol005.client.Client": - resp = ctx.obj.vim.list(filter) - # else: - # resp = ctx.obj.vim.list(ro_update) - if long: - table = PrettyTable( - ["vim name", "uuid", "project", "operational state", "error details"] - ) - project_list = ctx.obj.project.list() - else: - table = PrettyTable(["vim name", "uuid", "operational state"]) - for vim in resp: - 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" - if vim_state == "ERROR": - error_details = vim["_admin"].get("detailed-status", "Not found") - project_id, project_name = get_project(project_list, vim) - # project_info = '{} ({})'.format(project_name, project_id) - project_info = project_name - table.add_row( - [ - vim["name"], - vim["uuid"], - project_info, - vim_state, - wrap_text(text=error_details, width=80), - ] - ) - else: - table.add_row( - [vim["name"], vim["uuid"], vim["_admin"].get("operationalState", "-")] - ) - table.align = "l" - print(table) - - -@cli_osm.command(name="vim-show", short_help="shows the details of a VIM account") -@click.argument("name") -@click.option( - "--filter", - 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, literal): - """shows the details of a VIM account - - NAME: name or ID of the VIM account - """ - logger.debug("") - # try: - 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: - table.add_row([k, wrap_text(text=json.dumps(v, indent=2), width=100)]) - table.align = "l" - print(table) - - -#################### -# WIM operations -#################### - - -@cli_osm.command(name="wim-create", short_help="creates a new WIM account") -@click.option("--name", prompt=True, help="Name for the WIM account") -@click.option("--user", help="WIM username") -@click.option("--password", help="WIM password") -@click.option("--url", prompt=True, help="WIM url") -# @click.option('--tenant', -# help='wIM tenant name') -@click.option("--config", default=None, help="WIM specific config parameters") -@click.option("--wim_type", help="WIM type") -@click.option("--description", default=None, help="human readable description") -@click.option( - "--wim_port_mapping", - default=None, - help="File describing the port mapping between DC edge (datacenters, switches, ports) and WAN edge " - "(WAN service endpoint id and info)", -) -@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 wim_create( - ctx, - name, - user, - password, - url, - # tenant, - config, - wim_type, - description, - wim_port_mapping, - wait, -): - """creates a new WIM account""" - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - # if sdn_controller: - # check_client_version(ctx.obj, '--sdn_controller') - # if sdn_port_mapping: - # check_client_version(ctx.obj, '--sdn_port_mapping') - wim = {} - if user: - wim["user"] = user - if password: - wim["password"] = password - if url: - wim["wim_url"] = url - # if tenant: wim['tenant'] = tenant - wim["wim_type"] = wim_type - if description: - wim["description"] = description - if config: - wim["config"] = config - ctx.obj.wim.create(name, wim, wim_port_mapping, wait=wait) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="wim-update", short_help="updates a WIM account") -@click.argument("name") -@click.option("--newname", help="New name for the WIM account") -@click.option("--user", help="WIM username") -@click.option("--password", help="WIM password") -@click.option("--url", help="WIM url") -@click.option("--config", help="WIM specific config parameters") -@click.option("--wim_type", help="WIM type") -@click.option("--description", help="human readable description") -@click.option( - "--wim_port_mapping", - default=None, - help="File describing the port mapping between DC edge (datacenters, switches, ports) and WAN edge " - "(WAN service endpoint id and info)", -) -@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 wim_update( - ctx, - name, - newname, - user, - password, - url, - config, - wim_type, - description, - wim_port_mapping, - wait, -): - """updates a WIM account - - NAME: name or ID of the WIM account - """ - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - wim = {} - if newname: - wim["name"] = newname - if user: - wim["user"] = user - if password: - wim["password"] = password - if url: - wim["url"] = url - # if tenant: wim['tenant'] = tenant - if wim_type: - wim["wim_type"] = wim_type - if description: - wim["description"] = description - if config: - wim["config"] = config - ctx.obj.wim.update(name, wim, wim_port_mapping, wait=wait) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="wim-delete", short_help="deletes a WIM account") -@click.argument("name") -@click.option( - "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" -) -@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 wim_delete(ctx, name, force, wait): - """deletes a WIM account - - NAME: name or ID of the WIM account to be deleted - """ - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.wim.delete(name, force, wait=wait) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="wim-list", short_help="list all WIM accounts") -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the WIM accounts matching the filter", -) -@click.pass_context -def wim_list(ctx, filter): - """list all WIM accounts""" - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - if filter: - filter = "&".join(filter) - resp = ctx.obj.wim.list(filter) - table = PrettyTable(["wim name", "uuid"]) - for wim in resp: - table.add_row([wim["name"], wim["uuid"]]) - table.align = "l" - print(table) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="wim-show", short_help="shows the details of a WIM account") -@click.argument("name") -@click.pass_context -def wim_show(ctx, name): - """shows the details of a WIM account - - NAME: name or ID of the WIM account - """ - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - resp = ctx.obj.wim.get(name) - if "password" in resp: - resp["password"] = "********" - # except ClientException as e: - # print(str(e)) - # exit(1) - - table = PrettyTable(["key", "attribute"]) - for k, v in list(resp.items()): - table.add_row([k, json.dumps(v, indent=2)]) - table.align = "l" - print(table) - - -#################### -# SDN controller operations -#################### - - -@cli_osm.command(name="sdnc-create", short_help="creates a new SDN controller") -@click.option("--name", prompt=True, help="Name to create sdn controller") -@click.option("--type", prompt=True, help="SDN controller type") -@click.option( - "--sdn_controller_version", # hidden=True, - help="Deprecated. Use --config {version: sdn_controller_version}", -) -@click.option("--url", help="URL in format http[s]://HOST:IP/") -@click.option("--ip_address", help="Deprecated. Use --url") # hidden=True, -@click.option("--port", help="Deprecated. Use --url") # hidden=True, -@click.option( - "--switch_dpid", help="Deprecated. Use --config {switch_id: DPID}" # hidden=True, -) -@click.option( - "--config", - help="Extra information for SDN in yaml format, as {switch_id: identity used for the plugin (e.g. DPID: " - "Openflow Datapath ID), version: version}", -) -@click.option("--user", help="SDN controller username") -@click.option( - "--password", - hide_input=True, - confirmation_prompt=True, - help="SDN controller password", -) -@click.option("--description", default=None, help="human readable description") -@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 sdnc_create(ctx, **kwargs): - """creates a new SDN controller""" - logger.debug("") - sdncontroller = { - x: kwargs[x] - for x in kwargs - if kwargs[x] and x not in ("wait", "ip_address", "port", "switch_dpid") - } - if kwargs.get("port"): - print("option '--port' is deprecated, use '--url' instead") - sdncontroller["port"] = int(kwargs["port"]) - if kwargs.get("ip_address"): - print("option '--ip_address' is deprecated, use '--url' instead") - sdncontroller["ip"] = kwargs["ip_address"] - if kwargs.get("switch_dpid"): - print( - "option '--switch_dpid' is deprecated, use '--config={switch_id: id|DPID}' instead" - ) - sdncontroller["dpid"] = kwargs["switch_dpid"] - if kwargs.get("sdn_controller_version"): - print( - "option '--sdn_controller_version' is deprecated, use '--config={version: SDN_CONTROLLER_VERSION}'" - " instead" - ) - # try: - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.sdnc.create(kwargs["name"], sdncontroller, wait=kwargs["wait"]) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="sdnc-update", short_help="updates an SDN controller") -@click.argument("name") -@click.option("--newname", help="New name for the SDN controller") -@click.option("--description", default=None, help="human readable description") -@click.option("--type", help="SDN controller type") -@click.option("--url", help="URL in format http[s]://HOST:IP/") -@click.option( - "--config", - help="Extra information for SDN in yaml format, as " - "{switch_id: identity used for the plugin (e.g. DPID: " - "Openflow Datapath ID), version: version}", -) -@click.option("--user", help="SDN controller username") -@click.option("--password", help="SDN controller password") -@click.option("--ip_address", help="Deprecated. Use --url") # hidden=True -@click.option("--port", help="Deprecated. Use --url") # hidden=True -@click.option( - "--switch_dpid", help="Deprecated. Use --config {switch_dpid: DPID}" -) # hidden=True -@click.option( - "--sdn_controller_version", help="Deprecated. Use --config {version: VERSION}" -) # hidden=True -@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 sdnc_update(ctx, **kwargs): - """updates an SDN controller - - NAME: name or ID of the SDN controller - """ - logger.debug("") - sdncontroller = { - x: kwargs[x] - for x in kwargs - if kwargs[x] - and x not in ("wait", "ip_address", "port", "switch_dpid", "new_name") - } - if kwargs.get("newname"): - sdncontroller["name"] = kwargs["newname"] - if kwargs.get("port"): - print("option '--port' is deprecated, use '--url' instead") - sdncontroller["port"] = int(kwargs["port"]) - if kwargs.get("ip_address"): - print("option '--ip_address' is deprecated, use '--url' instead") - sdncontroller["ip"] = kwargs["ip_address"] - if kwargs.get("switch_dpid"): - print( - "option '--switch_dpid' is deprecated, use '--config={switch_id: id|DPID}' instead" - ) - sdncontroller["dpid"] = kwargs["switch_dpid"] - if kwargs.get("sdn_controller_version"): - print( - "option '--sdn_controller_version' is deprecated, use '---config={version: SDN_CONTROLLER_VERSION}'" - " instead" - ) - - # try: - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.sdnc.update(kwargs["name"], sdncontroller, wait=kwargs["wait"]) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="sdnc-delete", short_help="deletes an SDN controller") -@click.argument("name") -@click.option( - "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" -) -@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 sdnc_delete(ctx, name, force, wait): - """deletes an SDN controller - - NAME: name or ID of the SDN controller to be deleted - """ - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.sdnc.delete(name, force, wait=wait) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="sdnc-list", short_help="list all SDN controllers") -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the SDN controllers matching the filter with format: 'k[.k..]=v[&k[.k]=v2]'", -) -@click.pass_context -def sdnc_list(ctx, filter): - """list all SDN controllers""" - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - if filter: - filter = "&".join(filter) - resp = ctx.obj.sdnc.list(filter) - # except ClientException as e: - # print(str(e)) - # exit(1) - table = PrettyTable(["sdnc name", "id"]) - for sdnc in resp: - table.add_row([sdnc["name"], sdnc["_id"]]) - table.align = "l" - print(table) - - -@cli_osm.command(name="sdnc-show", short_help="shows the details of an SDN controller") -@click.argument("name") -@click.pass_context -def sdnc_show(ctx, name): - """shows the details of an SDN controller - - NAME: name or ID of the SDN controller - """ - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - resp = ctx.obj.sdnc.get(name) - # except ClientException as e: - # print(str(e)) - # exit(1) - - table = PrettyTable(["key", "attribute"]) - for k, v in list(resp.items()): - table.add_row([k, json.dumps(v, indent=2)]) - table.align = "l" - print(table) - - -########################### -# K8s cluster operations -########################### - - -@cli_osm.command(name="k8scluster-add", short_help="adds a K8s cluster to OSM") -@click.argument("name") -@click.option( - "--creds", prompt=True, help="credentials file, i.e. a valid `.kube/config` file" -) -@click.option("--version", prompt=True, help="Kubernetes version") -@click.option( - "--vim", prompt=True, help="VIM target, the VIM where the cluster resides" -) -@click.option( - "--k8s-nets", - prompt=True, - 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", - 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, - help="list of CNI plugins, in JSON inline format, used in the cluster", -) -# @click.option('--skip-init', -# is_flag=True, -# help='If set, K8s cluster is assumed to be ready for its use with OSM') -# @click.option('--wait', -# 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_add( - ctx, - name, - creds, - version, - vim, - k8s_nets, - init_helm2, - init_helm3, - init_jujubundle, - description, - namespace, - wait, - cni, -): - """adds a K8s cluster to OSM - - NAME: name of the K8s cluster - """ - # try: - check_client_version(ctx.obj, ctx.command.name) - cluster = {} - cluster["name"] = name - with open(creds, "r") as cf: - cluster["credentials"] = yaml.safe_load(cf.read()) - 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: - cluster["namespace"] = namespace - if cni: - cluster["cni"] = yaml.safe_load(cni) - ctx.obj.k8scluster.create(name, cluster, wait) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="k8scluster-update", short_help="updates a K8s cluster") -@click.argument("name") -@click.option("--newname", help="New name for the K8s cluster") -@click.option("--creds", help="credentials file, i.e. a valid `.kube/config` file") -@click.option("--version", help="Kubernetes version") -@click.option("--vim", help="VIM target, the VIM where the cluster resides") -@click.option( - "--k8s-nets", - 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("--description", help="human readable description") -@click.option( - "--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, wait, cni -): - """updates a K8s cluster - - NAME: name or ID of the K8s cluster - """ - # try: - check_client_version(ctx.obj, ctx.command.name) - cluster = {} - if newname: - cluster["name"] = newname - if creds: - with open(creds, "r") as cf: - cluster["credentials"] = yaml.safe_load(cf.read()) - if version: - cluster["k8s_version"] = version - if vim: - cluster["vim_account"] = vim - if k8s_nets: - cluster["nets"] = yaml.safe_load(k8s_nets) - if description: - cluster["description"] = description - if namespace: - cluster["namespace"] = namespace - if cni: - cluster["cni"] = yaml.safe_load(cni) - ctx.obj.k8scluster.update(name, cluster, wait) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="k8scluster-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.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, 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, wait) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="k8scluster-list") -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the K8s clusters 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 k8scluster_list(ctx, filter, literal, long): - """list all K8s clusters""" - # try: - check_client_version(ctx.obj, ctx.command.name) - if filter: - filter = "&".join(filter) - resp = ctx.obj.k8scluster.list(filter) - if literal: - print(yaml.safe_dump(resp, indent=4, default_flow_style=False)) - return - if long: - table = PrettyTable( - [ - "Name", - "Id", - "Project", - "Version", - "VIM", - "K8s-nets", - "Deployment methods", - "Operational State", - "Op. state (details)", - "Description", - "Detailed status", - ] - ) - project_list = ctx.obj.project.list() - else: - table = PrettyTable( - ["Name", "Id", "VIM", "Operational State", "Op. state details"] - ) - try: - vim_list = ctx.obj.vim.list() - except Exception: - vim_list = [] - for cluster in resp: - logger.debug("Cluster details: {}".format(yaml.safe_dump(cluster))) - vim_name = get_vim_name(vim_list, cluster["vim_account"]) - # vim_info = '{} ({})'.format(vim_name,cluster['vim_account']) - vim_info = vim_name - op_state_details = "Helm: {}\nJuju: {}".format( - cluster["_admin"].get("helm-chart", {}).get("operationalState", "-"), - cluster["_admin"].get("juju-bundle", {}).get("operationalState", "-"), - ) - if long: - project_id, project_name = get_project(project_list, cluster) - # project_info = '{} ({})'.format(project_name, project_id) - project_info = project_name - detailed_status = cluster["_admin"].get("detailed-status", "-") - table.add_row( - [ - cluster["name"], - cluster["_id"], - project_info, - 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), - wrap_text(text=detailed_status, width=40), - ] - ) - else: - table.add_row( - [ - cluster["name"], - cluster["_id"], - vim_info, - cluster["_admin"]["operationalState"], - op_state_details, - ] - ) - table.align = "l" - print(table) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command( - name="k8scluster-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 k8scluster_show(ctx, name, literal): - """shows the details of a K8s cluster - - NAME: name or ID of the K8s cluster - """ - # try: - resp = ctx.obj.k8scluster.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) - # except ClientException as e: - # print(str(e)) - # 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) - - -########################### -# PaaS operations -########################### - - -@cli_osm.command(name="paas-add", short_help="adds a PaaS to OSM.") -@click.argument("name") -@click.option( - "--paas_type", - type=click.Choice(["juju"]), - default="juju", - prompt=True, - help="Type of PaaS that can be used. (For the moment, only juju is supported).", -) -@click.option( - "--endpoints", - prompt=True, - help="Comma-separated list of IP or hostnames of the PaaS.", -) -@click.option("--user", prompt=True, help="Username with admin priviledges.") -@click.option("--secret", prompt=True, help="Password of the specified username.") -@click.option( - "--config", default={}, help="Extra configuration needed by PaaS service." -) -@click.option("--description", default=None, help="Human readable description.") -@click.pass_context -def paas_add(ctx, name, paas_type, endpoints, user, secret, config, description): - """adds a PaaS to OSM. - Args: - name (str): Name of the new PaaS. - """ - check_client_version(ctx.obj, ctx.command.name) - paas = { - "name": name, - "paas_type": paas_type, - "endpoints": endpoints.split(","), - "user": user, - "secret": secret, - } - if description: - paas["description"] = description - if config: - config = load(config) - paas["config"] = config - ctx.obj.paas.create(paas) - - -@cli_osm.command(name="paas-update", short_help="updates a PaaS") -@click.argument("name") -@click.option("--newname", help="New name for the PaaS") -@click.option( - "--paas_type", - type=click.Choice(["juju"]), - help="Type of PaaS that can be used. (For the moment, only juju is supported)", -) -@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("--config", help="Extra configuration needed by PaaS service") -@click.option("--description", default=None, help="Human readable description") -@click.pass_context -def paas_update( - ctx, name, newname, paas_type, endpoints, user, secret, config, description -): - """updates a PaaS. - Args: - name (str): Name or ID of the PaaS to update. - """ - check_client_version(ctx.obj, ctx.command.name) - paas = {} - if newname: - paas["name"] = newname - if paas_type: - paas["paas_type"] = paas_type - if endpoints: - paas["endpoints"] = endpoints.split(",") - if user: - paas["user"] = user - if secret: - paas["secret"] = secret - if description: - paas["description"] = description - if config: - config = load(config) - paas["config"] = config - ctx.obj.paas.update(name, paas) - - -@cli_osm.command(name="paas-delete", short_help="deletes a PaaS") -@click.argument("name") -@click.option( - "--force", is_flag=True, help="forces the deletion from the DB (not recommended)" -) -@click.pass_context -def paas_delete(ctx, name, force): - """deletes a PaaS. - - Args: - name (str): Name or ID of the PaaS to delete. - """ - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.paas.delete(name, force=force) - - -@cli_osm.command(name="paas-list") -@click.option( - "--filter", - default=None, - multiple=True, - help="Restricts the list to the PaaS 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 paas_list(ctx, filter, literal, long): - """List PaaSs""" - check_client_version(ctx.obj, ctx.command.name) - if filter: - filter = "&".join(filter) - resp = ctx.obj.paas.list(filter) - if literal: - print(yaml.safe_dump(resp, indent=4, default_flow_style=False)) - return - - table = _get_paas_table_header(long) - project_list = ctx.obj.project.list() - for paas in resp: - logger.debug("PaaS details: {}".format(yaml.safe_dump(paas))) - if long: - _add_paas_long_row(table, paas, project_list) - else: - _add_paas_row(table, paas) - table.align = "l" - print(table) - - -def _get_paas_table_header(long): - if long: - return PrettyTable( - ["Name", "Id", "Project", "Operational State", "Detailed Status"] - ) - return PrettyTable(["Name", "Id", "Operational State"]) - - -def _add_paas_long_row(table, paas, project_list): - _, project_name = get_project(project_list, paas) - detailed_status = paas.get("_admin", {}).get("detailed-status", "-") - table.add_row( - [ - paas["name"], - paas["_id"], - project_name, - paas.get("_admin", {}).get("operationalState", "-"), - wrap_text(text=detailed_status, width=40), - ] - ) - - -def _add_paas_row(table, paas): - table.add_row( - [paas["name"], paas["_id"], paas.get("_admin", {}).get("operationalState", "-")] - ) - - -@cli_osm.command(name="paas-show", short_help="Shows the details of a PaaS") -@click.argument("name") -@click.option("--literal", is_flag=True, help="Print literally, no pretty table") -@click.pass_context -def paas_show(ctx, name, literal): - """Shows the details of a PaaS. - - Args: - name (str): Name or ID of the PaaS to show. - """ - resp = ctx.obj.paas.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 -########################### - - -@cli_osm.command(name="repo-add", short_help="adds a repo to OSM") -@click.argument("name") -@click.argument("uri") -@click.option( - "--type", - type=click.Choice(["helm-chart", "juju-bundle", "osm"]), - default="osm", - help="type of repo (helm-chart for Helm Charts, juju-bundle for Juju Bundles, osm for OSM Repositories)", -) -@click.option("--description", default=None, help="human readable description") -@click.option( - "--user", default=None, help="OSM repository: The username of the OSM repository" -) -@click.option( - "--password", - default=None, - help="OSM repository: The password of the OSM repository", -) -# @click.option('--wait', -# is_flag=True, -# help='do not return the control immediately, but keep it until the operation is completed, or timeout') -@click.pass_context -def repo_add(ctx, **kwargs): - """adds a repo to OSM - - NAME: name of the repo - URI: URI of the repo - """ - # try: - kwargs = {k: v for k, v in kwargs.items() if v is not None} - repo = kwargs - repo["url"] = repo.pop("uri") - if repo["type"] in ["helm-chart", "juju-bundle"]: - ctx.obj.repo.create(repo["name"], repo) - else: - ctx.obj.osmrepo.create(repo["name"], repo) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="repo-update", short_help="updates a repo in OSM") -@click.argument("name") -@click.option("--newname", help="New name for the repo") -@click.option("--uri", help="URI of the repo") -@click.option("--description", help="human readable description") -# @click.option('--wait', -# is_flag=True, -# help='do not return the control immediately, but keep it until the operation is completed, or timeout') -@click.pass_context -def repo_update(ctx, name, newname, uri, description): - """updates a repo in OSM - - NAME: name of the repo - """ - # try: - check_client_version(ctx.obj, ctx.command.name) - repo = {} - if newname: - repo["name"] = newname - if uri: - repo["uri"] = uri - if description: - repo["description"] = description - try: - ctx.obj.repo.update(name, repo) - except NotFound: - ctx.obj.osmrepo.update(name, repo) - - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command( - name="repo-index", short_help="Index a repository from a folder with artifacts" -) -@click.option( - "--origin", default=".", help="origin path where the artifacts are located" -) -@click.option( - "--destination", default=".", help="destination path where the index is deployed" -) -@click.pass_context -def repo_index(ctx, origin, destination): - """Index a repository - - NAME: name or ID of the repo to be deleted - """ - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.osmrepo.repo_index(origin, destination) - - -@cli_osm.command(name="repo-delete", short_help="deletes a repo") -@click.argument("name") -@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.pass_context -def repo_delete(ctx, name, force): - """deletes a repo - - NAME: name or ID of the repo to be deleted - """ - logger.debug("") - try: - ctx.obj.repo.delete(name, force=force) - except NotFound: - ctx.obj.osmrepo.delete(name, force=force) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="repo-list") -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the repos matching the filter", -) -@click.option("--literal", is_flag=True, help="print literally, no pretty table") -@click.pass_context -def repo_list(ctx, filter, literal): - """list all repos""" - # try: - # K8s Repositories - check_client_version(ctx.obj, ctx.command.name) - if filter: - filter = "&".join(filter) - resp = ctx.obj.repo.list(filter) - resp += ctx.obj.osmrepo.list(filter) - if literal: - print(yaml.safe_dump(resp, indent=4, default_flow_style=False)) - return - table = PrettyTable(["Name", "Id", "Type", "URI", "Description"]) - for repo in resp: - # cluster['k8s-nets'] = json.dumps(yaml.safe_load(cluster['k8s-nets'])) - table.add_row( - [ - repo["name"], - repo["_id"], - repo["type"], - repo["url"], - trunc_text(repo.get("description") or "", 40), - ] - ) - table.align = "l" - print(table) - - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="repo-show", short_help="shows the details of a repo") -@click.argument("name") -@click.option("--literal", is_flag=True, help="print literally, no pretty table") -@click.pass_context -def repo_show(ctx, name, literal): - """shows the details of a repo - - NAME: name or ID of the repo - """ - try: - resp = ctx.obj.repo.get(name) - except NotFound: - resp = ctx.obj.osmrepo.get(name) - - if literal: - if resp: - print(yaml.safe_dump(resp, indent=4, default_flow_style=False)) - return - table = PrettyTable(["key", "attribute"]) - if resp: - for k, v in list(resp.items()): - table.add_row([k, json.dumps(v, indent=2)]) - - table.align = "l" - print(table) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -#################### -# Project mgmt operations -#################### - - -@cli_osm.command(name="project-create", short_help="creates a new project") -@click.argument("name") -# @click.option('--description', -# default='no description', -# help='human readable description') -@click.option("--domain-name", "domain_name", default=None, help="assign to a domain") -@click.option( - "--quotas", - "quotas", - multiple=True, - default=None, - help="provide quotas. Can be used several times: 'quota1=number[,quota2=number,...]'. Quotas can be one " - "of vnfds, nsds, nsts, pdus, nsrs, nsis, vim_accounts, wim_accounts, sdns, k8sclusters, k8srepos", -) -@click.pass_context -def project_create(ctx, name, domain_name, quotas): - """Creates a new project - - NAME: name of the project - DOMAIN_NAME: optional domain name for the project when keystone authentication is used - QUOTAS: set quotas for the project - """ - logger.debug("") - project = {"name": name} - if domain_name: - project["domain_name"] = domain_name - quotas_dict = _process_project_quotas(quotas) - if quotas_dict: - project["quotas"] = quotas_dict - - # try: - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.project.create(name, project) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -def _process_project_quotas(quota_list): - quotas_dict = {} - if not quota_list: - return quotas_dict - try: - for quota in quota_list: - for single_quota in quota.split(","): - k, v = single_quota.split("=") - quotas_dict[k] = None if v in ("None", "null", "") else int(v) - except (ValueError, TypeError): - raise ClientException( - "invalid format for 'quotas'. Use 'k1=v1,v1=v2'. v must be a integer or null" - ) - return quotas_dict - - -@cli_osm.command(name="project-delete", short_help="deletes a project") -@click.argument("name") -# @click.option('--force', is_flag=True, help='forces the deletion bypassing pre-conditions') -@click.pass_context -def project_delete(ctx, name): - """deletes a project - - NAME: name or ID of the project to be deleted - """ - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.project.delete(name) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="project-list", short_help="list all projects") -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the projects matching the filter", -) -@click.pass_context -def project_list(ctx, filter): - """list all projects""" - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - if filter: - filter = "&".join(filter) - resp = ctx.obj.project.list(filter) - # except ClientException as e: - # print(str(e)) - # exit(1) - table = PrettyTable(["name", "id"]) - for proj in resp: - table.add_row([proj["name"], proj["_id"]]) - table.align = "l" - print(table) - - -@cli_osm.command(name="project-show", short_help="shows the details of a project") -@click.argument("name") -@click.pass_context -def project_show(ctx, name): - """shows the details of a project - - NAME: name or ID of the project - """ - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - resp = ctx.obj.project.get(name) - # except ClientException as e: - # print(str(e)) - # exit(1) - - table = PrettyTable(["key", "attribute"]) - for k, v in resp.items(): - table.add_row([k, json.dumps(v, indent=2)]) - table.align = "l" - print(table) - - -@cli_osm.command( - name="project-update", short_help="updates a project (only the name can be updated)" -) -@click.argument("project") -@click.option("--name", default=None, help="new name for the project") -@click.option( - "--quotas", - "quotas", - multiple=True, - default=None, - help="change quotas. Can be used several times: 'quota1=number|empty[,quota2=...]' " - "(use empty to reset quota to default", -) -@click.pass_context -def project_update(ctx, project, name, quotas): - """ - Update a project name - - :param ctx: - :param project: id or name of the project to modify - :param name: new name for the project - :param quotas: change quotas of the project - :return: - """ - logger.debug("") - project_changes = {} - if name: - project_changes["name"] = name - quotas_dict = _process_project_quotas(quotas) - if quotas_dict: - project_changes["quotas"] = quotas_dict - - # try: - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.project.update(project, project_changes) - # except ClientException as e: - # print(str(e)) - - -#################### -# User mgmt operations -#################### - - -@cli_osm.command(name="user-create", short_help="creates a new user") -@click.argument("username") -@click.option( - "--password", - prompt=True, - hide_input=True, - confirmation_prompt=True, - help="user password", -) -@click.option( - "--projects", - # prompt="Comma separate list of projects", - multiple=True, - callback=lambda ctx, param, value: "".join(value).split(",") - if all(len(x) == 1 for x in value) - else value, - help="list of project ids that the user belongs to", -) -@click.option( - "--project-role-mappings", - "project_role_mappings", - default=None, - multiple=True, - help="assign role(s) in a project. Can be used several times: 'project,role1[,role2,...]'", -) -@click.option("--domain-name", "domain_name", default=None, help="assign to a domain") -@click.pass_context -def user_create(ctx, username, password, projects, project_role_mappings, domain_name): - """Creates a new user - - \b - USERNAME: name of the user - PASSWORD: password of the user - PROJECTS: projects assigned to user (internal only) - PROJECT_ROLE_MAPPING: roles in projects assigned to user (keystone) - DOMAIN_NAME: optional domain name for the user when keystone authentication is used - """ - logger.debug("") - user = {} - user["username"] = username - user["password"] = password - user["projects"] = projects - user["project_role_mappings"] = project_role_mappings - if domain_name: - user["domain_name"] = domain_name - - # try: - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.user.create(username, user) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="user-update", short_help="updates user information") -@click.argument("username") -@click.option( - "--password", - # prompt=True, - # hide_input=True, - # confirmation_prompt=True, - help="user password", -) -@click.option("--set-username", "set_username", default=None, help="change username") -@click.option( - "--set-project", - "set_project", - default=None, - multiple=True, - help="create/replace the roles for this project: 'project,role1[,role2,...]'", -) -@click.option( - "--remove-project", - "remove_project", - default=None, - multiple=True, - help="removes project from user: 'project'", -) -@click.option( - "--add-project-role", - "add_project_role", - default=None, - multiple=True, - help="assign role(s) in a project. Can be used several times: 'project,role1[,role2,...]'", -) -@click.option( - "--remove-project-role", - "remove_project_role", - default=None, - 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, - username, - password, - set_username, - set_project, - remove_project, - add_project_role, - remove_project_role, - change_password, - new_password, -): - """Update a user information - - \b - USERNAME: name of the user - PASSWORD: new password - SET_USERNAME: new username - SET_PROJECT: creating mappings for project/role(s) - 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 = {} - user["password"] = password - user["username"] = set_username - user["set-project"] = set_project - 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) - 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) - - -@cli_osm.command(name="user-delete", short_help="deletes a user") -@click.argument("name") -# @click.option('--force', is_flag=True, help='forces the deletion bypassing pre-conditions') -@click.pass_context -def user_delete(ctx, name): - """deletes a user - - \b - NAME: name or ID of the user to be deleted - """ - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.user.delete(name) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="user-list", short_help="list all users") -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the users matching the filter", -) -@click.pass_context -def user_list(ctx, filter): - """list all users""" - # try: - check_client_version(ctx.obj, ctx.command.name) - if filter: - filter = "&".join(filter) - resp = ctx.obj.user.list(filter) - # except ClientException as e: - # print(str(e)) - # exit(1) - table = PrettyTable(["name", "id"]) - for user in resp: - table.add_row([user["username"], user["_id"]]) - table.align = "l" - print(table) - - -@cli_osm.command(name="user-show", short_help="shows the details of a user") -@click.argument("name") -@click.pass_context -def user_show(ctx, name): - """shows the details of a user - - NAME: name or ID of the user - """ - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - resp = ctx.obj.user.get(name) - if "password" in resp: - resp["password"] = "********" - # except ClientException as e: - # print(str(e)) - # exit(1) - - table = PrettyTable(["key", "attribute"]) - for k, v in resp.items(): - table.add_row([k, json.dumps(v, indent=2)]) - table.align = "l" - print(table) - - -#################### -# Fault Management operations -#################### - - -@cli_osm.command(name="ns-alarm-create") -@click.argument("name") -@click.option("--ns", prompt=True, help="NS instance id or name") -@click.option( - "--vnf", prompt=True, help="VNF name (VNF member index as declared in the NSD)" -) -@click.option("--vdu", prompt=True, help="VDU name (VDU name as declared in the VNFD)") -@click.option("--metric", prompt=True, help="Name of the metric (e.g. cpu_utilization)") -@click.option( - "--severity", - default="WARNING", - help="severity of the alarm (WARNING, MINOR, MAJOR, CRITICAL, INDETERMINATE)", -) -@click.option( - "--threshold_value", - prompt=True, - help="threshold value that, when crossed, an alarm is triggered", -) -@click.option( - "--threshold_operator", - prompt=True, - help="threshold operator describing the comparison (GE, LE, GT, LT, EQ)", -) -@click.option( - "--statistic", - default="AVERAGE", - help="statistic (AVERAGE, MINIMUM, MAXIMUM, COUNT, SUM)", -) -@click.pass_context -def ns_alarm_create( - ctx, - name, - ns, - vnf, - vdu, - metric, - severity, - threshold_value, - threshold_operator, - statistic, -): - """creates a new alarm for a NS instance""" - # TODO: Check how to validate threshold_value. - # Should it be an integer (1-100), percentage, or decimal (0.01-1.00)? - logger.debug("") - # try: - ns_instance = ctx.obj.ns.get(ns) - alarm = {} - alarm["alarm_name"] = name - alarm["ns_id"] = ns_instance["_id"] - alarm["correlation_id"] = ns_instance["_id"] - alarm["vnf_member_index"] = vnf - alarm["vdu_name"] = vdu - alarm["metric_name"] = metric - alarm["severity"] = severity - alarm["threshold_value"] = int(threshold_value) - alarm["operation"] = threshold_operator - alarm["statistic"] = statistic - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.ns.create_alarm(alarm) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -# @cli_osm.command(name='ns-alarm-delete') -# @click.argument('name') -# @click.pass_context -# def ns_alarm_delete(ctx, name): -# """deletes an alarm -# -# NAME: name of the alarm to be deleted -# """ -# try: -# check_client_version(ctx.obj, ctx.command.name) -# ctx.obj.ns.delete_alarm(name) -# except ClientException as e: -# print(str(e)) -# exit(1) - - -#################### -# Performance Management operations -#################### - - -@cli_osm.command( - name="ns-metric-export", - short_help="exports a metric to the internal OSM bus, which can be read by other apps", -) -@click.option("--ns", prompt=True, help="NS instance id or name") -@click.option( - "--vnf", prompt=True, help="VNF name (VNF member index as declared in the NSD)" -) -@click.option("--vdu", prompt=True, help="VDU name (VDU name as declared in the VNFD)") -@click.option("--metric", prompt=True, help="name of the metric (e.g. cpu_utilization)") -# @click.option('--period', default='1w', -# help='metric collection period (e.g. 20s, 30m, 2h, 3d, 1w)') -@click.option( - "--interval", help="periodic interval (seconds) to export metrics continuously" -) -@click.pass_context -def ns_metric_export(ctx, ns, vnf, vdu, metric, interval): - """exports a metric to the internal OSM bus, which can be read by other apps""" - # TODO: Check how to validate interval. - # Should it be an integer (seconds), or should a suffix (s,m,h,d,w) also be permitted? - logger.debug("") - # try: - ns_instance = ctx.obj.ns.get(ns) - metric_data = {} - metric_data["ns_id"] = ns_instance["_id"] - metric_data["correlation_id"] = ns_instance["_id"] - metric_data["vnf_member_index"] = vnf - metric_data["vdu_name"] = vdu - metric_data["metric_name"] = metric - metric_data["collection_unit"] = "WEEK" - metric_data["collection_period"] = 1 - check_client_version(ctx.obj, ctx.command.name) - if not interval: - print("{}".format(ctx.obj.ns.export_metric(metric_data))) - else: - i = 1 - while True: - print("{} {}".format(ctx.obj.ns.export_metric(metric_data), i)) - time.sleep(int(interval)) - i += 1 - # except ClientException as e: - # print(str(e)) - # 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 -#################### - - -@cli_osm.command(name="version", short_help="shows client and server versions") -@click.pass_context -def get_version(ctx): - """shows client and server versions""" - # try: - check_client_version(ctx.obj, "version") - print("Server version: {}".format(ctx.obj.get_version())) - print( - "Client version: {}".format(pkg_resources.get_distribution("osmclient").version) - ) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command( - name="upload-package", short_help="uploads a VNF package or NS package" -) -@click.argument("filename") -@click.option( - "--skip-charm-build", - default=False, - is_flag=True, - help="the charm will not be compiled, it is assumed to already exist", -) -@click.pass_context -def upload_package(ctx, filename, skip_charm_build): - """uploads a vnf package or ns package - - filename: vnf or ns package folder, or vnf or ns package file (tar.gz) - """ - logger.debug("") - # try: - ctx.obj.package.upload(filename, skip_charm_build=skip_charm_build) - fullclassname = ctx.obj.__module__ + "." + ctx.obj.__class__.__name__ - if fullclassname != "osmclient.sol005.client.Client": - ctx.obj.package.wait_for_upload(filename) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -# @cli_osm.command(name='ns-scaling-show') -# @click.argument('ns_name') -# @click.pass_context -# def show_ns_scaling(ctx, ns_name): -# """shows the status of a NS scaling operation -# -# NS_NAME: name of the NS instance being scaled -# """ -# try: -# check_client_version(ctx.obj, ctx.command.name, 'v1') -# resp = ctx.obj.ns.list() -# except ClientException as e: -# print(str(e)) -# exit(1) -# -# table = PrettyTable( -# ['group-name', -# 'instance-id', -# 'operational status', -# 'create-time', -# 'vnfr ids']) -# -# for ns in resp: -# if ns_name == ns['name']: -# nsopdata = ctx.obj.ns.get_opdata(ns['id']) -# scaling_records = nsopdata['nsr:nsr']['scaling-group-record'] -# for record in scaling_records: -# if 'instance' in record: -# instances = record['instance'] -# for inst in instances: -# table.add_row( -# [record['scaling-group-name-ref'], -# inst['instance-id'], -# inst['op-status'], -# time.strftime('%Y-%m-%d %H:%M:%S', -# time.localtime( -# inst['create-time'])), -# inst['vnfrs']]) -# table.align = 'l' -# print(table) - - -# @cli_osm.command(name='ns-scale') -# @click.argument('ns_name') -# @click.option('--ns_scale_group', prompt=True) -# @click.option('--index', prompt=True) -# @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 ns_scale(ctx, ns_name, ns_scale_group, index, wait): -# """scales NS -# -# NS_NAME: name of the NS instance to be scaled -# """ -# try: -# check_client_version(ctx.obj, ctx.command.name, 'v1') -# ctx.obj.ns.scale(ns_name, ns_scale_group, index, wait=wait) -# except ClientException as e: -# print(str(e)) -# exit(1) - - -# @cli_osm.command(name='config-agent-list') -# @click.pass_context -# def config_agent_list(ctx): -# """list config agents""" -# try: -# check_client_version(ctx.obj, ctx.command.name, 'v1') -# except ClientException as e: -# print(str(e)) -# exit(1) -# table = PrettyTable(['name', 'account-type', 'details']) -# for account in ctx.obj.vca.list(): -# table.add_row( -# [account['name'], -# account['account-type'], -# account['juju']]) -# table.align = 'l' -# print(table) - - -# @cli_osm.command(name='config-agent-delete') -# @click.argument('name') -# @click.pass_context -# def config_agent_delete(ctx, name): -# """deletes a config agent -# -# NAME: name of the config agent to be deleted -# """ -# try: -# check_client_version(ctx.obj, ctx.command.name, 'v1') -# ctx.obj.vca.delete(name) -# except ClientException as e: -# print(str(e)) -# exit(1) - - -# @cli_osm.command(name='config-agent-add') -# @click.option('--name', -# prompt=True) -# @click.option('--account_type', -# prompt=True) -# @click.option('--server', -# prompt=True) -# @click.option('--user', -# prompt=True) -# @click.option('--secret', -# prompt=True, -# hide_input=True, -# confirmation_prompt=True) -# @click.pass_context -# def config_agent_add(ctx, name, account_type, server, user, secret): -# """adds a config agent""" -# try: -# check_client_version(ctx.obj, ctx.command.name, 'v1') -# ctx.obj.vca.create(name, account_type, server, user, secret) -# except ClientException as e: -# print(str(e)) -# exit(1) - - -# @cli_osm.command(name='ro-dump') -# @click.pass_context -# def ro_dump(ctx): -# """shows RO agent information""" -# check_client_version(ctx.obj, ctx.command.name, 'v1') -# resp = ctx.obj.vim.get_resource_orchestrator() -# table = PrettyTable(['key', 'attribute']) -# for k, v in list(resp.items()): -# table.add_row([k, json.dumps(v, indent=2)]) -# table.align = 'l' -# print(table) - - -# @cli_osm.command(name='vcs-list') -# @click.pass_context -# def vcs_list(ctx): -# check_client_version(ctx.obj, ctx.command.name, 'v1') -# resp = ctx.obj.utils.get_vcs_info() -# table = PrettyTable(['component name', 'state']) -# for component in resp: -# table.add_row([component['component_name'], component['state']]) -# table.align = 'l' -# print(table) - - -@cli_osm.command( - name="ns-action", short_help="executes an action/primitive over a NS instance" -) -@click.argument("ns_name") -@click.option( - "--vnf_name", - default=None, - help="member-vnf-index if the target is a vnf instead of a ns)", -) -@click.option("--kdu_name", default=None, help="kdu-name if the target is a kdu)") -@click.option("--vdu_id", default=None, help="vdu-id if the target is a vdu") -@click.option( - "--vdu_count", default=None, type=int, help="number of vdu instance of this vdu_id" -) -@click.option("--action_name", prompt=True, help="action name") -@click.option("--params", default=None, help="action params in YAML/JSON inline string") -@click.option("--params_file", default=None, help="YAML/JSON file with action params") -@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 ns_action( - ctx, - ns_name, - vnf_name, - kdu_name, - vdu_id, - vdu_count, - action_name, - params, - params_file, - timeout, - wait, -): - """executes an action/primitive over a NS instance - - NS_NAME: name or ID of the NS instance - """ - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - op_data = {} - if vnf_name: - op_data["member_vnf_index"] = vnf_name - if kdu_name: - op_data["kdu_name"] = kdu_name - if vdu_id: - op_data["vdu_id"] = vdu_id - if vdu_count is not None: - op_data["vdu_count_index"] = vdu_count - if timeout: - op_data["timeout_ns_action"] = timeout - op_data["primitive"] = action_name - if params_file: - with open(params_file, "r") as pf: - params = pf.read() - if params: - op_data["primitive_params"] = yaml.safe_load(params) - else: - op_data["primitive_params"] = {} - print(ctx.obj.ns.exec_op(ns_name, op_name="action", op_data=op_data, wait=wait)) - - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command( - name="vnf-scale", short_help="executes a VNF scale (adding/removing VDUs)" -) -@click.argument("ns_name") -@click.argument("vnf_name") -@click.option( - "--scaling-group", prompt=True, help="scaling-group-descriptor name to use" -) -@click.option( - "--scale-in", default=False, is_flag=True, help="performs a scale in operation" -) -@click.option( - "--scale-out", - default=False, - is_flag=True, - help="performs a scale out operation (by default)", -) -@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 vnf_scale( - ctx, ns_name, vnf_name, scaling_group, scale_in, scale_out, timeout, wait -): - """ - Executes a VNF scale (adding/removing VDUs) - - \b - NS_NAME: name or ID of the NS instance. - VNF_NAME: member-vnf-index in the NS to be scaled. - """ - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - if not scale_in and not scale_out: - scale_out = True - ctx.obj.ns.scale_vnf( - ns_name, vnf_name, scaling_group, scale_in, scale_out, wait, timeout - ) - # except ClientException as e: - # print(str(e)) - # 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 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 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: - 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("") - # 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", - context_settings=dict(ignore_unknown_options=True), -) -@click.argument("ns_name") -@click.argument( - "args", nargs=-1, type=click.UNPROCESSED, callback=process_ns_heal_params -) -@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 ns_heal(ctx, ns_name, args, heal_params, 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"] - 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.argument( - "args", nargs=-1, type=click.UNPROCESSED, callback=process_vnf_heal_params -) -@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_heal2(ctx, vnf_name, args, heal_params, 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["healVnfData"][-1]["vnfInstanceId"] = vnf_name - logger.debug(f"Heal dict:\n{yaml.safe_dump(heal_dict)}") - check_client_version(ctx.obj, ctx.command.name) - 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") -@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 - alarm = resp.replace("ObjectId", "") - 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", "") - 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 # -############################## - - -@cli_osm.command(name="role-create", short_help="creates a new role") -@click.argument("name") -@click.option("--permissions", default=None, help="role permissions using a dictionary") -@click.pass_context -def role_create(ctx, name, permissions): - """ - Creates a new role. - - \b - NAME: Name or ID of the role. - DEFINITION: Definition of grant/denial of access to resources. - """ - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.role.create(name, permissions) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="role-update", short_help="updates a role") -@click.argument("name") -@click.option("--set-name", default=None, help="change name of rle") -# @click.option('--permissions', -# default=None, -# help='provide a yaml format dictionary with incremental changes. Values can be bool or None to delete') -@click.option( - "--add", - default=None, - help="yaml format dictionary with permission: True/False to access grant/denial", -) -@click.option("--remove", default=None, help="yaml format list to remove a permission") -@click.pass_context -def role_update(ctx, name, set_name, add, remove): - """ - Updates a role. - - \b - NAME: Name or ID of the role. - DEFINITION: Definition overwrites the old definition. - ADD: Grant/denial of access to resource to add. - REMOVE: Grant/denial of access to resource to remove. - """ - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.role.update(name, set_name, None, add, remove) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="role-delete", short_help="deletes a role") -@click.argument("name") -# @click.option('--force', is_flag=True, help='forces the deletion bypassing pre-conditions') -@click.pass_context -def role_delete(ctx, name): - """ - Deletes a role. - - \b - NAME: Name or ID of the role. - """ - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - ctx.obj.role.delete(name) - # except ClientException as e: - # print(str(e)) - # exit(1) - - -@cli_osm.command(name="role-list", short_help="list all roles") -@click.option( - "--filter", - default=None, - multiple=True, - help="restricts the list to the projects matching the filter", -) -@click.pass_context -def role_list(ctx, filter): - """ - List all roles. - """ - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - if filter: - filter = "&".join(filter) - resp = ctx.obj.role.list(filter) - # except ClientException as e: - # print(str(e)) - # exit(1) - table = PrettyTable(["name", "id"]) - for role in resp: - table.add_row([role["name"], role["_id"]]) - table.align = "l" - print(table) - - -@cli_osm.command(name="role-show", short_help="show specific role") -@click.argument("name") -@click.pass_context -def role_show(ctx, name): - """ - Shows the details of a role. - - \b - NAME: Name or ID of the role. - """ - logger.debug("") - # try: - check_client_version(ctx.obj, ctx.command.name) - resp = ctx.obj.role.get(name) - # except ClientException as e: - # print(str(e)) - # exit(1) - - table = PrettyTable(["key", "attribute"]) - for k, v in resp.items(): - table.add_row([k, json.dumps(v, indent=2)]) - table.align = "l" - print(table) - - -@cli_osm.command(name="package-create", short_help="Create empty NS package structure") -@click.argument("package-type") -@click.argument("package-name") -@click.option( - "--base-directory", - default=".", - help=('(NS/VNF/NST) Set the location for package creation. Default: "."'), -) -@click.option( - "--image", - default="image-name", - help='(VNF) Set the name of the vdu image. Default "image-name"', -) -@click.option( - "--vdus", default=1, help="(VNF) Set the number of vdus in a VNF. Default 1" -) -@click.option( - "--vcpu", default=1, help="(VNF) Set the number of virtual CPUs in a vdu. Default 1" -) -@click.option( - "--memory", - default=1024, - help="(VNF) Set the memory size (MB) of the vdu. Default 1024", -) -@click.option( - "--storage", default=10, help="(VNF) Set the disk size (GB) of the vdu. Default 10" -) -@click.option( - "--interfaces", - default=0, - help="(VNF) Set the number of additional interfaces apart from the management interface. Default 0", -) -@click.option( - "--vendor", default="OSM", help='(NS/VNF) Set the descriptor vendor. Default "OSM"' -) -@click.option( - "--override", - default=False, - is_flag=True, - help="(NS/VNF/NST) Flag for overriding the package if exists.", -) -@click.option( - "--detailed", - is_flag=True, - default=False, - help="(NS/VNF/NST) Flag for generating descriptor .yaml with all possible commented options", -) -@click.option( - "--netslice-subnets", default=1, help="(NST) Number of netslice subnets. Default 1" -) -@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, - package_type, - base_directory, - package_name, - override, - image, - vdus, - vcpu, - memory, - storage, - interfaces, - vendor, - detailed, - netslice_subnets, - netslice_vlds, - old, -): - """ - Creates an OSM NS, VNF, NST package - - \b - PACKAGE_TYPE: Package to be created: NS, VNF or NST. - PACKAGE_NAME: Name of the package to create the folder with the content. - """ - - # try: - logger.debug("") - check_client_version(ctx.obj, ctx.command.name) - print( - "Creating the {} structure: {}/{}".format( - package_type.upper(), base_directory, package_name - ) - ) - resp = ctx.obj.package_tool.create( - package_type, - base_directory, - package_name, - override=override, - image=image, - vdus=vdus, - vcpu=vcpu, - memory=memory, - storage=storage, - interfaces=interfaces, - vendor=vendor, - detailed=detailed, - netslice_subnets=netslice_subnets, - netslice_vlds=netslice_vlds, - old=old, - ) - print(resp) - # except ClientException as inst: - # print("ERROR: {}".format(inst)) - # exit(1) - - -@cli_osm.command( - name="package-validate", short_help="Validate descriptors given a base directory" -) -@click.argument("base-directory", default=".", required=False) -@click.option( - "--recursive/--no-recursive", - default=True, - help="The activated recursive option will validate the yaml files" - " within the indicated directory and in its subdirectories", -) -@click.option( - "--old", - is_flag=True, - default=False, - help="Validates also the descriptors using the previous OSM format (pre SOL006)", -) -@click.pass_context -def package_validate(ctx, base_directory, recursive, old): - """ - Validate descriptors given a base directory. - - \b - BASE_DIRECTORY: Base folder for NS, VNF or NST package. - """ - # try: - logger.debug("") - check_client_version(ctx.obj, ctx.command.name) - results = ctx.obj.package_tool.validate(base_directory, recursive, old) - table = PrettyTable() - table.field_names = ["TYPE", "PATH", "VALID", "ERROR"] - # Print the dictionary generated by the validation function - for result in results: - table.add_row( - [result["type"], result["path"], result["valid"], result["error"]] - ) - table.sortby = "VALID" - table.align["PATH"] = "l" - table.align["TYPE"] = "l" - table.align["ERROR"] = "l" - print(table) - # except ClientException as inst: - # print("ERROR: {}".format(inst)) - # exit(1) - - -@cli_osm.command( - name="package-translate", short_help="Translate descriptors given a base directory" -) -@click.argument("base-directory", default=".", required=False) -@click.option( - "--recursive/--no-recursive", - default=True, - help="The activated recursive option will translate the yaml files" - " within the indicated directory and in its subdirectories", -) -@click.option( - "--dryrun", - is_flag=True, - default=False, - help="Do not translate yet, only make a dry-run to test translation", -) -@click.pass_context -def package_translate(ctx, base_directory, recursive, dryrun): - """ - Translate descriptors given a base directory. - - \b - BASE_DIRECTORY: Stub folder for NS, VNF or NST package. - """ - logger.debug("") - check_client_version(ctx.obj, ctx.command.name) - results = ctx.obj.package_tool.translate(base_directory, recursive, dryrun) - table = PrettyTable() - table.field_names = [ - "CURRENT TYPE", - "NEW TYPE", - "PATH", - "VALID", - "TRANSLATED", - "ERROR", - ] - # Print the dictionary generated by the validation function - for result in results: - table.add_row( - [ - result["current type"], - result["new type"], - result["path"], - result["valid"], - result["translated"], - result["error"], - ] - ) - table.sortby = "TRANSLATED" - table.align["PATH"] = "l" - table.align["TYPE"] = "l" - table.align["ERROR"] = "l" - print(table) - # except ClientException as inst: - # print("ERROR: {}".format(inst)) - # exit(1) - - -@cli_osm.command(name="package-build", short_help="Build the tar.gz of the package") -@click.argument("package-folder") -@click.option( - "--skip-validation", default=False, is_flag=True, help="skip package validation" -) -@click.option( - "--skip-charm-build", - default=False, - is_flag=True, - help="the charm will not be compiled, it is assumed to already exist", -) -@click.pass_context -def package_build(ctx, package_folder, skip_validation, skip_charm_build): - """ - Build the package NS, VNF given the package_folder. - - \b - PACKAGE_FOLDER: Folder of the NS, VNF or NST to be packaged - """ - # try: - logger.debug("") - check_client_version(ctx.obj, ctx.command.name) - results = ctx.obj.package_tool.build( - package_folder, - skip_validation=skip_validation, - skip_charm_build=skip_charm_build, - ) - print(results) - # except ClientException as inst: - # print("ERROR: {}".format(inst)) - # exit(1) - - -@cli_osm.command( - name="descriptor-translate", - short_help="Translate input descriptor file from Rel EIGHT OSM descriptors to SOL006 and prints in standard output", -) -@click.argument("descriptor-file", required=True) -@click.pass_context -def descriptor_translate(ctx, descriptor_file): - """ - Translate input descriptor. - - \b - DESCRIPTOR_FILE: Descriptor file for NS, VNF or Network Slice. - """ - logger.debug("") - check_client_version(ctx.obj, ctx.command.name) - result = ctx.obj.package_tool.descriptor_translate(descriptor_file) - print(result) - - +# pylint: disable=no-value-for-parameter def cli(): try: - cli_osm() # pylint: disable=no-value-for-parameter + cli_osm.add_command(alarms.alarm_list) + cli_osm.add_command(alarms.alarm_show) + cli_osm.add_command(alarms.alarm_update) + cli_osm.add_command(alarms.ns_alarm_create) + + cli_osm.add_command(k8scluster.k8scluster_add) + cli_osm.add_command(k8scluster.k8scluster_delete) + cli_osm.add_command(k8scluster.k8scluster_list) + cli_osm.add_command(k8scluster.k8scluster_show) + cli_osm.add_command(k8scluster.k8scluster_update) + + cli_osm.add_command(metrics.ns_metric_export) + + cli_osm.add_command(k8scluster.k8scluster_delete) + cli_osm.add_command(k8scluster.k8scluster_list) + cli_osm.add_command(k8scluster.k8scluster_show) + cli_osm.add_command(k8scluster.k8scluster_update) + + cli_osm.add_command(netslice_instance.nsi_create1) + cli_osm.add_command(netslice_instance.nsi_create2) + cli_osm.add_command(netslice_instance.nsi_delete1) + cli_osm.add_command(netslice_instance.nsi_delete2) + cli_osm.add_command(netslice_instance.nsi_list1) + cli_osm.add_command(netslice_instance.nsi_list2) + cli_osm.add_command(netslice_instance.nsi_show1) + cli_osm.add_command(netslice_instance.nsi_show2) + + cli_osm.add_command(netslice_ops.nsi_op_list1) + cli_osm.add_command(netslice_ops.nsi_op_list2) + cli_osm.add_command(netslice_ops.nsi_op_show1) + cli_osm.add_command(netslice_ops.nsi_op_show2) + + cli_osm.add_command(netslice_template.nst_create1) + cli_osm.add_command(netslice_template.nst_create2) + cli_osm.add_command(netslice_template.nst_delete1) + cli_osm.add_command(netslice_template.nst_delete2) + cli_osm.add_command(netslice_template.nst_list1) + cli_osm.add_command(netslice_template.nst_list2) + cli_osm.add_command(netslice_template.nst_show1) + cli_osm.add_command(netslice_template.nst_show2) + cli_osm.add_command(netslice_template.nst_update1) + cli_osm.add_command(netslice_template.nst_update2) + + cli_osm.add_command(nfpkg.nfpkg_create) + cli_osm.add_command(nfpkg.nfpkg_delete) + cli_osm.add_command(nfpkg.nfpkg_list) + cli_osm.add_command(nfpkg.nfpkg_show) + cli_osm.add_command(nfpkg.nfpkg_update) + cli_osm.add_command(nfpkg.vnfd_create1) + cli_osm.add_command(nfpkg.vnfd_create2) + cli_osm.add_command(nfpkg.vnfd_delete1) + cli_osm.add_command(nfpkg.vnfd_delete2) + cli_osm.add_command(nfpkg.vnfd_list1) + cli_osm.add_command(nfpkg.vnfd_list2) + cli_osm.add_command(nfpkg.vnfd_show1) + cli_osm.add_command(nfpkg.vnfd_show2) + cli_osm.add_command(nfpkg.vnfd_update1) + cli_osm.add_command(nfpkg.vnfd_update2) + + cli_osm.add_command(ns.ns_create) + cli_osm.add_command(ns.ns_delete) + cli_osm.add_command(ns.ns_list) + cli_osm.add_command(ns.ns_show) + + cli_osm.add_command(nslcm_ops.ns_op_list) + cli_osm.add_command(nslcm_ops.ns_op_show) + + cli_osm.add_command(nslcm.ns_action) + cli_osm.add_command(nslcm.vnf_scale) + cli_osm.add_command(nslcm.ns_update) + cli_osm.add_command(nslcm.ns_heal) + cli_osm.add_command(nslcm.vnf_heal) + + cli_osm.add_command(nspkg.nsd_create1) + cli_osm.add_command(nspkg.nsd_create2) + cli_osm.add_command(nspkg.nsd_delete1) + cli_osm.add_command(nspkg.nsd_delete2) + cli_osm.add_command(nspkg.nsd_list1) + cli_osm.add_command(nspkg.nsd_list2) + cli_osm.add_command(nspkg.nsd_show1) + cli_osm.add_command(nspkg.nsd_show2) + cli_osm.add_command(nspkg.nsd_update1) + cli_osm.add_command(nspkg.nsd_update2) + + cli_osm.add_command(other.get_version) + + cli_osm.add_command(packages.descriptor_translate) + cli_osm.add_command(packages.package_build) + cli_osm.add_command(packages.package_create) + cli_osm.add_command(packages.package_translate) + cli_osm.add_command(packages.package_validate) + cli_osm.add_command(packages.upload_package) + + cli_osm.add_command(pdus.pdu_create) + cli_osm.add_command(pdus.pdu_delete) + cli_osm.add_command(pdus.pdu_list) + cli_osm.add_command(pdus.pdu_show) + cli_osm.add_command(pdus.pdu_update) + + cli_osm.add_command(rbac.project_create) + cli_osm.add_command(rbac.project_delete) + cli_osm.add_command(rbac.project_list) + cli_osm.add_command(rbac.project_show) + cli_osm.add_command(rbac.project_update) + + cli_osm.add_command(rbac.role_create) + cli_osm.add_command(rbac.role_delete) + cli_osm.add_command(rbac.role_list) + cli_osm.add_command(rbac.role_show) + cli_osm.add_command(rbac.role_update) + + cli_osm.add_command(rbac.user_create) + cli_osm.add_command(rbac.user_delete) + cli_osm.add_command(rbac.user_list) + cli_osm.add_command(rbac.user_show) + cli_osm.add_command(rbac.user_update) + + cli_osm.add_command(repo.repo_add) + cli_osm.add_command(repo.repo_delete) + cli_osm.add_command(repo.repo_list) + cli_osm.add_command(repo.repo_show) + cli_osm.add_command(repo.repo_update) + + cli_osm.add_command(repo.repo_index) + cli_osm.add_command(repo.nfpkg_repo_list1) + cli_osm.add_command(repo.nfpkg_repo_list2) + cli_osm.add_command(repo.nfpkg_repo_list2) + cli_osm.add_command(repo.nspkg_repo_list) + cli_osm.add_command(repo.nspkg_repo_list2) + cli_osm.add_command(repo.nsd_repo_show) + cli_osm.add_command(repo.nsd_repo_show2) + cli_osm.add_command(repo.vnfd_show1) + cli_osm.add_command(repo.vnfd_show2) + + cli_osm.add_command(sdnc.sdnc_create) + cli_osm.add_command(sdnc.sdnc_delete) + cli_osm.add_command(sdnc.sdnc_list) + cli_osm.add_command(sdnc.sdnc_show) + cli_osm.add_command(sdnc.sdnc_update) + + cli_osm.add_command(subscriptions.subscription_create) + cli_osm.add_command(subscriptions.subscription_delete) + cli_osm.add_command(subscriptions.subscription_list) + cli_osm.add_command(subscriptions.subscription_show) + + cli_osm.add_command(vca.vca_add) + cli_osm.add_command(vca.vca_delete) + cli_osm.add_command(vca.vca_list) + cli_osm.add_command(vca.vca_show) + cli_osm.add_command(vca.vca_update) + + cli_osm.add_command(vim.vim_create) + cli_osm.add_command(vim.vim_delete) + cli_osm.add_command(vim.vim_list) + cli_osm.add_command(vim.vim_show) + cli_osm.add_command(vim.vim_update) + + cli_osm.add_command(vnf.nf_list) + cli_osm.add_command(vnf.vnf_list1) + cli_osm.add_command(vnf.vnf_show) + + cli_osm.add_command(wim.wim_create) + cli_osm.add_command(wim.wim_delete) + cli_osm.add_command(wim.wim_list) + cli_osm.add_command(wim.wim_show) + cli_osm.add_command(wim.wim_update) + + cli_osm() exit(0) except pycurl.error as exc: print(exc) print( 'Maybe "--hostname" option or OSM_HOSTNAME environment variable needs to be specified' ) - except (ClientException, NotFound) as exc: + except ClientException as exc: print("ERROR: {}".format(exc)) except (FileNotFoundError, PermissionError) as exc: print("Cannot open file: {}".format(exc)) diff --git a/osmclient/scripts/tests/test_ns_operations.py b/osmclient/scripts/tests/test_ns_operations.py deleted file mode 100644 index cc6b2ca..0000000 --- a/osmclient/scripts/tests/test_ns_operations.py +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright 2022 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import unittest -from unittest.mock import Mock, patch -from click.testing import CliRunner -from osmclient.common.exceptions import ClientException -from osmclient.scripts import osm - - -@patch("osmclient.scripts.osm.client.Client") -class TestNS(unittest.TestCase): - def setUp(self): - self.runner = CliRunner() - self.ctx_obj = Mock() - - def test_ns_create_with_vim_account(self, mock_client): - mock_client.return_value = self.ctx_obj - result = self.runner.invoke( - osm.cli_osm, - [ - "ns-create", - "--ns_name", - "ns_name", - "--nsd_name", - "nsd_name", - "--vim_account", - "vim_account", - ], - ) - self.ctx_obj.ns.create.assert_called_with( - "nsd_name", - "ns_name", - config=None, - ssh_keys=None, - vim_account="vim_account", - paas_account=None, - admin_status="ENABLED", - wait=False, - timeout=None, - ) - assert not result.exception - - def test_ns_create_with_paas_account(self, mock_client): - mock_client.return_value = self.ctx_obj - result = self.runner.invoke( - osm.cli_osm, - [ - "ns-create", - "--ns_name", - "ns_name", - "--nsd_name", - "nsd_name", - "--paas_account", - "paas_account", - ], - ) - self.ctx_obj.ns.create.assert_called_with( - "nsd_name", - "ns_name", - config=None, - ssh_keys=None, - vim_account=None, - paas_account="paas_account", - admin_status="ENABLED", - wait=False, - timeout=None, - ) - assert not result.exception - - def test_ns_create_with_paas_and_vim_account_raises_exception(self, mock_client): - mock_client.return_value = self.ctx_obj - result = self.runner.invoke( - osm.cli_osm, - [ - "ns-create", - "--ns_name", - "ns_name", - "--nsd_name", - "nsd_name", - "--vim_account", - "vim_account", - "--paas_account", - "paas_account", - ], - ) - self.ctx_obj.ns.create.assert_not_called() - expected_msg = '"vim_account" and "paas_account" can not be used together, use only one of them' - assert result.exception - assert isinstance(result.exception, ClientException) - assert expected_msg in str(result.exception) - - def test_ns_creat_without_paas_or_vim_account_raises_exception(self, mock_client): - mock_client.return_value = self.ctx_obj - result = self.runner.invoke( - osm.cli_osm, ["ns-create", "--ns_name", "ns_name", "--nsd_name", "nsd_name"] - ) - self.ctx_obj.ns.create.assert_not_called() - expected_msg = ( - 'specify "vim_account" or "paas_account", both options can not be empty' - ) - assert result.exception - assert isinstance(result.exception, ClientException) - assert expected_msg in str(result.exception) diff --git a/osmclient/scripts/tests/test_paas_operations.py b/osmclient/scripts/tests/test_paas_operations.py deleted file mode 100644 index 9edd017..0000000 --- a/osmclient/scripts/tests/test_paas_operations.py +++ /dev/null @@ -1,312 +0,0 @@ -# Copyright 2021 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import json -import unittest -from unittest.mock import Mock, patch -import yaml - -from click.testing import CliRunner -from osmclient.common.exceptions import NotFound -from osmclient.scripts import osm - - -@patch("builtins.print") -@patch("osmclient.scripts.osm.PrettyTable") -@patch("osmclient.scripts.osm.client.Client") -@patch("osmclient.scripts.osm.check_client_version") -@patch("osmclient.scripts.osm.get_project") -class TestPaaS(unittest.TestCase): - def setUp(self): - self.runner = CliRunner() - self.ctx_obj = Mock() - self.table = Mock() - self.paas_data = { - "name": "name", - "_id": "1234", - "_admin": {"detailed-status": "status", "operationalState": "state"}, - } - - def test_paas_add( - self, - mock_get_project, - mock_check_client_version, - mock_client, - mock_pretty_table, - mock_print, - ): - mock_client.return_value = self.ctx_obj - self.runner.invoke( - osm.cli_osm, - [ - "paas-add", - "name", - "--paas_type", - "juju", - "--endpoints", - "1.2.3.4:17070", - "--user", - "user", - "--secret", - "secret", - "--description", - "description", - "--config", - json.dumps({"juju-https-proxy": "http://squid:3128"}), - ], - ) - mock_check_client_version.assert_called() - self.ctx_obj.paas.create.assert_called_with( - { - "name": "name", - "paas_type": "juju", - "endpoints": ["1.2.3.4:17070"], - "user": "user", - "secret": "secret", - "description": "description", - "config": {"juju-https-proxy": "http://squid:3128"}, - } - ) - mock_pretty_table.assert_not_called() - self.table.add_row.assert_not_called() - mock_print.assert_not_called() - - def test_paas_update( - self, - mock_get_project, - mock_check_client_version, - mock_client, - mock_pretty_table, - mock_print, - ): - mock_client.return_value = self.ctx_obj - self.runner.invoke(osm.cli_osm, ["paas-update", "name"]) - mock_check_client_version.assert_called() - self.ctx_obj.paas.update.assert_called_with("name", {}) - mock_pretty_table.assert_not_called() - self.table.add_row.assert_not_called() - mock_print.assert_not_called() - - def test_paas_update_with_args( - self, - mock_get_project, - mock_check_client_version, - mock_client, - mock_pretty_table, - mock_print, - ): - mock_client.return_value = self.ctx_obj - self.runner.invoke( - osm.cli_osm, - [ - "paas-update", - "name", - "--newname", - "paas_new_name", - "--paas_type", - "juju", - "--endpoints", - "1.2.3.4:17070", - "--user", - "user", - "--secret", - "secret", - "--description", - "description", - "--config", - json.dumps({"juju-https-proxy": "http://squid:3128"}), - ], - ) - mock_check_client_version.assert_called() - self.ctx_obj.paas.update.assert_called_with( - "name", - { - "name": "paas_new_name", - "paas_type": "juju", - "endpoints": ["1.2.3.4:17070"], - "user": "user", - "secret": "secret", - "description": "description", - "config": {"juju-https-proxy": "http://squid:3128"}, - }, - ) - mock_pretty_table.assert_not_called() - self.table.add_row.assert_not_called() - mock_print.assert_not_called() - - def test_paas_delete( - self, - mock_get_project, - mock_check_client_version, - mock_client, - mock_pretty_table, - mock_print, - ): - mock_client.return_value = self.ctx_obj - self.runner.invoke(osm.cli_osm, ["paas-delete", "name"]) - mock_check_client_version.assert_called() - self.ctx_obj.paas.delete.assert_called_with("name", force=False) - mock_pretty_table.assert_not_called() - self.table.add_row.assert_not_called() - mock_print.assert_not_called() - - def test_paas_delete_force( - self, - mock_get_project, - mock_check_client_version, - mock_client, - mock_pretty_table, - mock_print, - ): - mock_client.return_value = self.ctx_obj - self.runner.invoke(osm.cli_osm, ["paas-delete", "name", "--force"]) - mock_check_client_version.assert_called() - self.ctx_obj.paas.delete.assert_called_with("name", force=True) - mock_pretty_table.assert_not_called() - self.table.add_row.assert_not_called() - mock_print.assert_not_called() - - def test_paas_list( - self, - mock_get_project, - mock_check_client_version, - mock_client, - mock_pretty_table, - mock_print, - ): - mock_client.return_value = self.ctx_obj - mock_pretty_table.return_value = self.table - - self.ctx_obj.paas.list.return_value = [self.paas_data] - self.runner.invoke(osm.cli_osm, ["paas-list", "--filter", "somefilter"]) - mock_check_client_version.assert_called() - self.ctx_obj.paas.list.assert_called_with("somefilter") - mock_pretty_table.assert_called_with(["Name", "Id", "Operational State"]) - mock_get_project.assert_not_called() - self.table.add_row.assert_called_with(["name", "1234", "state"]) - mock_print.assert_called_with(self.table) - - def test_paas_list_long( - self, - mock_get_project, - mock_check_client_version, - mock_client, - mock_pretty_table, - mock_print, - ): - mock_client.return_value = self.ctx_obj - mock_pretty_table.return_value = self.table - mock_get_project.return_value = ("5678", "project_name") - self.ctx_obj.paas.list.return_value = [self.paas_data] - self.runner.invoke( - osm.cli_osm, ["paas-list", "--filter", "somefilter", "--long"] - ) - mock_check_client_version.assert_called() - self.ctx_obj.paas.list.assert_called_with("somefilter") - mock_pretty_table.assert_called_with( - ["Name", "Id", "Project", "Operational State", "Detailed Status"] - ) - self.table.add_row.assert_called_with( - ["name", "1234", "project_name", "state", "status"] - ) - mock_print.assert_called_with(self.table) - - def test_paas_list_literal( - self, - mock_get_project, - mock_check_client_version, - mock_client, - mock_pretty_table, - mock_print, - ): - mock_client.return_value = self.ctx_obj - self.ctx_obj.paas.list.return_value = [self.paas_data] - self.runner.invoke(osm.cli_osm, ["paas-list", "--literal"]) - mock_check_client_version.assert_called() - self.ctx_obj.paas.list.assert_called() - mock_pretty_table.assert_not_called() - self.table.add_row.assert_not_called() - mock_print.assert_called_with( - yaml.safe_dump([self.paas_data], indent=4, default_flow_style=False) - ) - - def test_paas_list_empty( - self, - mock_get_project, - mock_check_client_version, - mock_client, - mock_pretty_table, - mock_print, - ): - mock_client.return_value = self.ctx_obj - mock_pretty_table.return_value = self.table - self.ctx_obj.paas.list.return_value = [] - self.runner.invoke(osm.cli_osm, ["paas-list", "--filter", "somefilter"]) - mock_check_client_version.assert_called() - self.ctx_obj.paas.list.assert_called_with("somefilter") - mock_get_project.assert_not_called() - mock_pretty_table.assert_called_with(["Name", "Id", "Operational State"]) - mock_print.assert_called_with(self.table) - - def test_paas_show( - self, - mock_get_project, - mock_check_client_version, - mock_client, - mock_pretty_table, - mock_print, - ): - mock_client.return_value = self.ctx_obj - mock_pretty_table.return_value = self.table - self.ctx_obj.paas.get.return_value = self.paas_data - self.runner.invoke(osm.cli_osm, ["paas-show", "name"]) - self.ctx_obj.paas.get.assert_called_with("name") - mock_pretty_table.assert_called_with(["key", "attribute"]) - self.assertEqual(self.table.add_row.call_count, len(self.paas_data)) - mock_print.assert_called_with(self.table) - - def test_paas_show_literal( - self, - mock_get_project, - mock_check_client_version, - mock_client, - mock_pretty_table, - mock_print, - ): - mock_client.return_value = self.ctx_obj - self.ctx_obj.paas.get.return_value = self.paas_data - self.runner.invoke(osm.cli_osm, ["paas-show", "name", "--literal"]) - self.ctx_obj.paas.get.assert_called_with("name") - mock_pretty_table.assert_not_called() - self.table.add_row.assert_not_called() - mock_print.assert_called_with( - yaml.safe_dump(self.paas_data, indent=4, default_flow_style=False) - ) - - def test_paas_show_literal_throws_exception( - self, - mock_get_project, - mock_check_client_version, - mock_client, - mock_pretty_table, - mock_print, - ): - mock_client.return_value = self.ctx_obj - self.ctx_obj.paas.get.side_effect = NotFound() - self.runner.invoke(osm.cli_osm, ["paas-show", "name", "--literal"]) - self.ctx_obj.paas.get.assert_called_with("name") - mock_pretty_table.assert_not_called() - self.table.add_row.assert_not_called() - mock_print.assert_not_called() diff --git a/osmclient/scripts/tests/tests_vca.py b/osmclient/scripts/tests/tests_vca.py index 030a51f..f1ebb5f 100644 --- a/osmclient/scripts/tests/tests_vca.py +++ b/osmclient/scripts/tests/tests_vca.py @@ -13,21 +13,20 @@ # limitations under the License. +from click.testing import CliRunner import json import unittest from unittest.mock import Mock, patch import yaml - -from click.testing import CliRunner -from osmclient.scripts import osm +from osmclient.cli_commands import vca @patch("builtins.print") -@patch("osmclient.scripts.osm.PrettyTable") +@patch("osmclient.cli_commands.vca.PrettyTable") @patch("osmclient.scripts.osm.client.Client") -@patch("osmclient.scripts.osm.check_client_version") -@patch("osmclient.scripts.osm.get_project") +@patch("osmclient.cli_commands.utils.check_client_version") +@patch("osmclient.cli_commands.vca.utils.get_project") class TestVca(unittest.TestCase): def setUp(self): self.runner = CliRunner() @@ -55,8 +54,9 @@ class TestVca(unittest.TestCase): mock_get_project.return_value = ("5678", "project") self.ctx_obj.vca.list.return_value = [self.vca_data] self.runner.invoke( - osm.cli_osm, - ["vca-list", "--filter", "somefilter"], + vca.vca_list, + obj=self.ctx_obj, + args=["--filter", "somefilter"], ) mock_check_client_version.assert_called() self.ctx_obj.vca.list.assert_called_with("somefilter") @@ -77,8 +77,9 @@ class TestVca(unittest.TestCase): mock_get_project.return_value = ("5678", "project") self.ctx_obj.vca.list.return_value = [self.vca_data] self.runner.invoke( - osm.cli_osm, - ["vca-list", "--filter", "somefilter", "--long"], + vca.vca_list, + obj=self.ctx_obj, + args=["--filter", "somefilter", "--long"], ) mock_check_client_version.assert_called() self.ctx_obj.vca.list.assert_called_with("somefilter") @@ -101,8 +102,9 @@ class TestVca(unittest.TestCase): mock_client.return_value = self.ctx_obj self.ctx_obj.vca.list.return_value = [self.vca_data] self.runner.invoke( - osm.cli_osm, - ["vca-list", "--literal"], + vca.vca_list, + obj=self.ctx_obj, + args=["--literal"], ) mock_check_client_version.assert_called() self.ctx_obj.vca.list.assert_called() @@ -124,8 +126,9 @@ class TestVca(unittest.TestCase): mock_pretty_table.return_value = self.table self.ctx_obj.vca.get.return_value = self.vca_data self.runner.invoke( - osm.cli_osm, - ["vca-show", "name"], + vca.vca_show, + obj=self.ctx_obj, + args=["name"], ) self.ctx_obj.vca.get.assert_called_with("name") mock_pretty_table.assert_called_with(["key", "attribute"]) @@ -143,8 +146,9 @@ class TestVca(unittest.TestCase): mock_client.return_value = self.ctx_obj self.ctx_obj.vca.get.return_value = self.vca_data self.runner.invoke( - osm.cli_osm, - ["vca-show", "name", "--literal"], + vca.vca_show, + obj=self.ctx_obj, + args=["name", "--literal"], ) self.ctx_obj.vca.get.assert_called_with("name") mock_pretty_table.assert_not_called() @@ -163,8 +167,9 @@ class TestVca(unittest.TestCase): ): mock_client.return_value = self.ctx_obj self.runner.invoke( - osm.cli_osm, - ["vca-update", "name"], + vca.vca_update, + obj=self.ctx_obj, + args=["name"], ) mock_check_client_version.assert_called() self.ctx_obj.vca.update.assert_called_with("name", {"name": "name"}) @@ -182,9 +187,9 @@ class TestVca(unittest.TestCase): ): mock_client.return_value = self.ctx_obj self.runner.invoke( - osm.cli_osm, - [ - "vca-update", + vca.vca_update, + obj=self.ctx_obj, + args=[ "name", "--endpoints", "1.2.3.4:17070", @@ -239,9 +244,9 @@ class TestVca(unittest.TestCase): ): mock_client.return_value = self.ctx_obj self.runner.invoke( - osm.cli_osm, - [ - "vca-add", + vca.vca_add, + obj=self.ctx_obj, + args=[ "name", "--endpoints", "1.2.3.4:17070", @@ -296,8 +301,9 @@ class TestVca(unittest.TestCase): ): mock_client.return_value = self.ctx_obj self.runner.invoke( - osm.cli_osm, - ["vca-delete", "name"], + vca.vca_delete, + obj=self.ctx_obj, + args=["name"], ) mock_check_client_version.assert_called() self.ctx_obj.vca.delete.assert_called_with("name", force=False) @@ -313,5 +319,5 @@ class TestVca(unittest.TestCase): mock_pretty_table, mock_print, ): - data = osm.load(json.dumps({"juju-https-proxy": "http://squid:3128"})) + data = vca.load(json.dumps({"juju-https-proxy": "http://squid:3128"})) self.assertEqual(data, {"juju-https-proxy": "http://squid:3128"}) diff --git a/osmclient/scripts/tests/tests_vim.py b/osmclient/scripts/tests/tests_vim.py index 32b229e..903fca7 100644 --- a/osmclient/scripts/tests/tests_vim.py +++ b/osmclient/scripts/tests/tests_vim.py @@ -13,16 +13,16 @@ # limitations under the License. -import unittest import json +import unittest from unittest.mock import Mock, patch, mock_open from click.testing import CliRunner -from osmclient.scripts import osm +from osmclient.cli_commands import vim -@patch("osmclient.scripts.osm.check_client_version") +@patch("osmclient.cli_commands.utils.check_client_version") @patch("osmclient.scripts.osm.client.Client") -@patch("osmclient.scripts.osm.create_config") +@patch("osmclient.cli_commands.utils.create_config") class TestVim(unittest.TestCase): def setUp(self): self.runner = CliRunner() @@ -38,11 +38,10 @@ class TestVim(unittest.TestCase): mock_create_config.return_value = {"ca_cert": "/home/ubuntu/.ssh/id_rsa.pub"} vim_config = mock_create_config.return_value with patch("builtins.open", mock_open(read_data="test")): - self.runner.invoke( - osm.cli_osm, - [ - "vim-create", + vim.vim_create, + obj=self.ctx_obj, + args=[ "--name", "vim1", "--user", @@ -89,11 +88,10 @@ class TestVim(unittest.TestCase): mock_create_config.return_value = {} with patch("builtins.open", mock_open(read_data="test")): - self.runner.invoke( - osm.cli_osm, - [ - "vim-create", + vim.vim_create, + obj=self.ctx_obj, + args=[ "--name", "vim1", "--user", @@ -137,11 +135,10 @@ class TestVim(unittest.TestCase): mock_create_config.return_value = {} with patch("builtins.open", mock_open(read_data="test")): - self.runner.invoke( - osm.cli_osm, - [ - "vim-create", + vim.vim_create, + obj=self.ctx_obj, + args=[ "--name", "vim1", "--user", @@ -189,11 +186,10 @@ class TestVim(unittest.TestCase): mock_create_config.return_value = {"ca_cert": "/home/ubuntu/.ssh/id_rsa.pub"} vim_config = mock_create_config.return_value with patch("builtins.open", mock_open(read_data="test")): - self.runner.invoke( - osm.cli_osm, - [ - "vim-update", + vim.vim_update, + obj=self.ctx_obj, + args=[ "vim1", "--config", json.dumps({"ca_cert": "/home/ubuntu/.ssh/id_rsa.pub"}), @@ -222,11 +218,10 @@ class TestVim(unittest.TestCase): mock_client.return_value = self.ctx_obj with patch("builtins.open", mock_open(read_data="test")): - self.runner.invoke( - osm.cli_osm, - [ - "vim-update", + vim.vim_update, + obj=self.ctx_obj, + args=[ "vim1", "--password", "passwd", @@ -256,11 +251,10 @@ class TestVim(unittest.TestCase): mock_create_config.return_value = {} with patch("builtins.open", mock_open(read_data="test")): - self.runner.invoke( - osm.cli_osm, - [ - "vim-update", + vim.vim_update, + obj=self.ctx_obj, + args=[ "vim1", "--sdn_controller", "controller", diff --git a/osmclient/sol005/client.py b/osmclient/sol005/client.py index 5dd6f06..a69f3cc 100644 --- a/osmclient/sol005/client.py +++ b/osmclient/sol005/client.py @@ -36,7 +36,6 @@ from osmclient.sol005 import role from osmclient.sol005 import pdud from osmclient.sol005 import k8scluster from osmclient.sol005 import vca -from osmclient.sol005 import paas from osmclient.sol005 import repo from osmclient.sol005 import osmrepo from osmclient.sol005 import subscription @@ -100,7 +99,6 @@ class Client(object): self.pdu = pdud.Pdu(self._http_client, client=self) self.k8scluster = k8scluster.K8scluster(self._http_client, client=self) self.vca = vca.VCA(self._http_client, client=self) - self.paas = paas.PAAS(self._http_client, client=self) self.repo = repo.Repo(self._http_client, client=self) self.osmrepo = osmrepo.OSMRepo(self._http_client, client=self) self.package_tool = package_tool.PackageTool(client=self) diff --git a/osmclient/sol005/ns.py b/osmclient/sol005/ns.py index 992bf0f..00d68d6 100644 --- a/osmclient/sol005/ns.py +++ b/osmclient/sol005/ns.py @@ -40,7 +40,7 @@ class Ns(object): ) # NS '--wait' option - def _wait(self, op_id, wait_time, delete_flag=False): + def _wait(self, id, wait_time, deleteFlag=False): self._logger.debug("") # Endpoint to get operation status apiUrlStatus = "{}{}{}".format( @@ -51,11 +51,11 @@ class Ns(object): wait_time = WaitForStatus.TIMEOUT_NS_OPERATION WaitForStatus.wait_for_status( "NS", - str(op_id), + str(id), wait_time, apiUrlStatus, self._http.get2_cmd, - deleteFlag=delete_flag, + deleteFlag=deleteFlag, ) def list(self, filter=None): @@ -111,7 +111,7 @@ class Ns(object): :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 + skip_terminate_primitives: Bool (default False) to not exec the terminate 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 @@ -139,350 +139,138 @@ class Ns(object): if wait and resp: resp = json.loads(resp) # For the 'delete' operation, '_id' is used - self._wait(resp.get("_id"), wait, delete_flag=True) + self._wait(resp.get("_id"), wait, deleteFlag=True) else: print("Deletion in progress") elif http_code == 204: print("Deleted") else: msg = resp or "" + # if resp: + # try: + # msg = json.loads(resp) + # except ValueError: + # msg = resp 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("") - 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'" - ) - - def process_ns_create_with_vim_account( + def create( 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 + nsd_name, + nsr_name, + account, + config=None, + ssh_keys=None, + description="default description", + admin_status="ENABLED", + wait=False, + timeout=None, + ): + self._logger.debug("") + self._client.get_token() + nsd = self._client.nsd.get(nsd_name) - 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, - } + 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("") + # wim_account can be False (boolean) to indicate not use wim account + 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"] + + vim_id = get_vim_account_id(account) + ns = {} + ns["nsdId"] = nsd["_id"] + ns["nsName"] = nsr_name + ns["nsDescription"] = description + ns["vimAccountId"] = vim_id + # ns['userdata'] = {} + # ns['userdata']['key1']='value1' + # ns['userdata']['key2']='value2' 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()) - 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") - - 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) + 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[ + 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")) + 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")) + + 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'" + ) + 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) + # rest of parameters without any transformation or checking + # "timeout_ns_deploy" + # "placement-engine" 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 - ) - + # print(yaml.safe_dump(ns)) try: self._apiResource = "/ns_instances_content" self._apiBase = "{}{}{}".format( @@ -497,20 +285,28 @@ class Ns(object): 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): + if resp: + resp = json.loads(resp) if not resp or "id" not in resp: raise ClientException( "unexpected response from server - {} ".format(resp) ) - if resp: - resp = json.loads(resp) - print(str(resp["id"])) if wait: # Wait for status for NS instance creation self._wait(resp.get("nslcmop_id"), wait) - + print(resp["id"]) return resp["id"] - + # else: + # msg = "" + # if resp: + # try: + # msg = json.loads(resp) + # except ValueError: + # msg = resp + # raise ClientException(msg) except ClientException as exc: message = "failed to create ns: {} nsd: {}\nerror:\n{}".format( nsr_name, nsd_name, str(exc) diff --git a/osmclient/sol005/paas.py b/osmclient/sol005/paas.py deleted file mode 100644 index 3dcadf6..0000000 --- a/osmclient/sol005/paas.py +++ /dev/null @@ -1,146 +0,0 @@ -# Copyright 2021 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -OSM PaaS API handling -""" - -from osmclient.common import utils -from osmclient.common.exceptions import ClientException, NotFound -import json - - -class PAAS(object): - def __init__(self, http=None, client=None): - self._http = http - self._client = client - self._apiName = "/admin" - self._apiVersion = "/v1" - self._apiResource = "/paas" - self._apiBase = "{}{}{}".format( - self._apiName, self._apiVersion, self._apiResource - ) - - def _is_id(self, name): - return utils.validate_uuid4(name) - - def create(self, paas): - """Create PaaS. - Args: - paas (dict): includes the PaaS information. - - Raises: - ClientException - """ - self._client.get_token() - http_code, resp = self._http.post_cmd( - endpoint=self._apiBase, postfields_dict=paas - ) - resp = json.loads(resp) if resp else {} - if "id" not in resp: - raise ClientException("unexpected response from server - {}".format(resp)) - print("PaaS {} created with ID {}".format(paas["name"], resp["id"])) - - def update(self, name, paas): - """Updates a PaaS based on name or ID. - Args: - name (str): PaaS name or ID. - paas (dict): includes the new PaaS information to update. - - Raises: - NotFound: if PaaS does not exists in DB. - """ - paas_id = name - self._client.get_token() - try: - if not self._is_id(name): - paas_id = self.get(name)["_id"] - self._http.patch_cmd( - endpoint="{}/{}".format(self._apiBase, paas_id), postfields_dict=paas - ) - except NotFound: - raise NotFound("PaaS {} not found".format(name)) - - def get_id(self, name): - """Returns a PaaS ID from a PaaS name. - Args: - name (str): PaaS name. - Raises: - NotFound: if PaaS does not exists in DB. - """ - for paas in self.list(): - if name == paas["name"]: - return paas["_id"] - raise NotFound("PaaS {} not found".format(name)) - - def delete(self, name, force=False): - """Deletes a PaaS based on name or ID. - Args: - name (str): PaaS name or ID. - force (bool): if True, PaaS is deleted without any check. - Raises: - NotFound: if PaaS does not exists in DB. - ClientException: if delete fails. - """ - self._client.get_token() - paas_id = name - querystring = "?FORCE=True" if force else "" - try: - if not self._is_id(name): - paas_id = self.get_id(name) - http_code, resp = self._http.delete_cmd( - "{}/{}{}".format(self._apiBase, paas_id, querystring) - ) - except NotFound: - raise NotFound("PaaS {} not found".format(name)) - if http_code == 202: - print("Deletion in progress") - elif http_code == 204: - print("Deleted") - else: - msg = resp or "" - raise ClientException("Failed to delete PaaS {} - {}".format(name, msg)) - - def list(self, cmd_filter=None): - """Returns a list of PaaS""" - self._client.get_token() - filter_string = "" - if cmd_filter: - filter_string = "?{}".format(cmd_filter) - _, resp = self._http.get2_cmd("{}{}".format(self._apiBase, filter_string)) - if resp: - return json.loads(resp) - return list() - - def get(self, name): - """Returns a PaaS based on name or id. - Args: - name (str): PaaS name or ID. - - Raises: - NotFound: if PaaS does not exists in DB. - ClientException: if get fails. - """ - self._client.get_token() - paas_id = name - try: - if not self._is_id(name): - paas_id = self.get_id(name) - _, resp = self._http.get2_cmd("{}/{}".format(self._apiBase, paas_id)) - resp = json.loads(resp) if resp else {} - if "_id" not in resp: - raise ClientException("Failed to get PaaS info: {}".format(resp)) - return resp - except NotFound: - raise NotFound("PaaS {} not found".format(name)) diff --git a/osmclient/sol005/tests/test_paas.py b/osmclient/sol005/tests/test_paas.py deleted file mode 100644 index de7bcd3..0000000 --- a/osmclient/sol005/tests/test_paas.py +++ /dev/null @@ -1,267 +0,0 @@ -# Copyright 2021 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import json -import unittest -from unittest.mock import Mock, patch - -from osmclient.common.exceptions import ClientException, NotFound -from osmclient.sol005.paas import PAAS - - -class TestPaaS(unittest.TestCase): - def setUp(self): - self.paas = PAAS(Mock(), Mock()) - self.paas_data = { - "name": "name", - "type": "juju", - "endpoints": ["127.0.0.1:17070"], - "user": "user", - "secret": "secret", - "description": "description", - "config": {}, - } - - @patch("builtins.print") - def test_create_success(self, mock_print): - self.paas._http.post_cmd.return_value = (200, '{"id": "1234"}') - self.paas.create(self.paas_data) - self.paas._client.get_token.assert_called() - self.paas._http.post_cmd.assert_called_with( - endpoint="/admin/v1/paas", postfields_dict=self.paas_data - ) - mock_print.assert_called_with("PaaS name created with ID 1234") - - @patch("builtins.print") - def test_create_raise_exception(self, mock_print): - self.paas._http.post_cmd.return_value = (404, None) - with self.assertRaises(ClientException): - self.paas.create(self.paas_data) - self.paas._client.get_token.assert_called() - self.paas._http.post_cmd.assert_called_with( - endpoint="/admin/v1/paas", postfields_dict=self.paas_data - ) - mock_print.assert_not_called() - - @patch("osmclient.sol005.paas.utils") - def test_update_success(self, mock_utils): - mock_utils.validate_uuid4.return_value = False - self.paas.get = Mock() - self.paas.get.return_value = {"_id": "1234"} - self.paas.update("paas_name", self.paas_data) - self.paas._http.patch_cmd.assert_called_with( - endpoint="/admin/v1/paas/1234", postfields_dict=self.paas_data - ) - - @patch("osmclient.sol005.paas.utils") - def test_update_fail_not_found_exception(self, mock_utils): - mock_utils.validate_uuid4.return_value = False - self.paas.get = Mock() - self.paas.get.side_effect = NotFound() - with self.assertRaises(NotFound): - self.paas.update("paas_name", self.paas_data) - self.paas._http.patch_cmd.assert_not_called() - - @patch("osmclient.sol005.paas.utils") - def test_update_fail_client_exception(self, mock_utils): - mock_utils.validate_uuid4.return_value = False - self.paas.get = Mock() - self.paas.get.side_effect = ClientException() - with self.assertRaises(ClientException): - self.paas.update("paas_name", self.paas_data) - self.paas._http.patch_cmd.assert_not_called() - - @patch("osmclient.sol005.paas.utils") - def test_update_using_id(self, mock_utils): - mock_utils.validate_uuid4.return_value = True - self.paas.get = Mock() - self.paas.update("1234", self.paas_data) - self.paas._http.patch_cmd.assert_called_with( - endpoint="/admin/v1/paas/1234", postfields_dict=self.paas_data - ) - self.paas.get.assert_not_called() - - def test_get_id_sucess(self): - self.paas_data.update({"_id": "1234"}) - self.paas.list = Mock() - self.paas.list.return_value = [self.paas_data] - paas_id = self.paas.get_id("name") - self.assertEqual(paas_id, "1234") - - def test_get_id_not_found(self): - self.paas.list = Mock() - self.paas.list.return_value = [] - with self.assertRaises(NotFound): - self.paas.get_id("name") - - @patch("osmclient.sol005.paas.utils") - @patch("builtins.print") - def test_delete_success_202(self, mock_print, mock_utils): - mock_utils.validate_uuid4.return_value = False - self.paas.get_id = Mock() - self.paas.get_id.return_value = "1234" - self.paas._http.delete_cmd.return_value = (202, None) - self.paas.delete("paas_name") - self.paas._client.get_token.assert_called() - self.paas._http.delete_cmd.assert_called_with("/admin/v1/paas/1234") - mock_print.assert_called_with("Deletion in progress") - - @patch("osmclient.sol005.paas.utils") - @patch("builtins.print") - def test_delete_success_204(self, mock_print, mock_utils): - mock_utils.validate_uuid4.return_value = False - self.paas.get_id = Mock() - self.paas.get_id.return_value = "1234" - self.paas._http.delete_cmd.return_value = (204, None) - self.paas.delete("paas_name", force=True) - self.paas._client.get_token.assert_called() - self.paas._http.delete_cmd.assert_called_with("/admin/v1/paas/1234?FORCE=True") - mock_print.assert_called_with("Deleted") - - @patch("osmclient.sol005.paas.utils") - @patch("builtins.print") - def test_delete_fail_404(self, mock_print, mock_utils): - mock_utils.validate_uuid4.return_value = False - self.paas.get_id = Mock() - self.paas.get_id.return_value = "1234" - self.paas._http.delete_cmd.return_value = (404, "Not found") - with self.assertRaises(ClientException): - self.paas.delete("paas_name") - self.paas._client.get_token.assert_called() - self.paas._http.delete_cmd.assert_called_with("/admin/v1/paas/1234") - mock_print.assert_not_called() - - @patch("osmclient.sol005.paas.utils") - @patch("builtins.print") - def test_delete_failed_id_not_found(self, mock_print, mock_utils): - mock_utils.validate_uuid4.return_value = False - self.paas.get_id = Mock() - self.paas.get_id.side_effect = NotFound() - self.paas._http.delete_cmd.return_value = (204, None) - with self.assertRaises(NotFound): - self.paas.delete("paas_name", force=True) - self.paas._client.get_token.assert_called() - self.paas._http.delete_cmd.assert_not_called() - mock_print.assert_not_called() - - @patch("osmclient.sol005.paas.utils") - @patch("builtins.print") - def test_delete_using_id(self, mock_print, mock_utils): - mock_utils.validate_uuid4.return_value = True - self.paas.get_id = Mock() - self.paas._http.delete_cmd.return_value = (202, None) - paas_id = "1234" - self.paas.delete(paas_id) - self.paas.get_id.assert_not_called() - self.paas._client.get_token.assert_called() - self.paas._http.delete_cmd.assert_called_with("/admin/v1/paas/1234") - mock_print.assert_called_with("Deletion in progress") - - @patch("osmclient.sol005.paas.utils") - @patch("builtins.print") - def test_delete_using_id_client_exception(self, mock_print, mock_utils): - mock_utils.validate_uuid4.return_value = True - self.paas.get_id = Mock() - self.paas._http.delete_cmd.return_value = (5, None) - paas_id = "1234" - with self.assertRaises(ClientException): - self.paas.delete(paas_id, force=True) - self.paas._client.get_token.assert_called() - self.paas.get_id.assert_not_called() - self.paas._http.delete_cmd.assert_called_with("/admin/v1/paas/1234?FORCE=True") - mock_print.assert_not_called() - - @patch("osmclient.sol005.paas.utils") - @patch("builtins.print") - def test_delete_using_id_not_found(self, mock_print, mock_utils): - mock_utils.validate_uuid4.return_value = True - self.paas.get_id = Mock() - self.paas._http.delete_cmd.side_effect = NotFound() - paas_id = "1234" - with self.assertRaises(NotFound): - self.paas.delete(paas_id, force=True) - self.paas._client.get_token.assert_called() - self.paas.get_id.assert_not_called() - self.paas._http.delete_cmd.assert_called_with("/admin/v1/paas/1234?FORCE=True") - mock_print.assert_not_called() - - def test_list_success(self): - self.paas._http.get2_cmd.return_value = (None, '[{"_id": "1234"}]') - paas_list = self.paas.list("my_filter") - self.paas._client.get_token.assert_called() - self.paas._http.get2_cmd.assert_called_with("/admin/v1/paas?my_filter") - self.assertEqual(paas_list, [{"_id": "1234"}]) - - def test_list_no_response(self): - self.paas._http.get2_cmd.return_value = (None, None) - paas_list = self.paas.list() - self.paas._client.get_token.assert_called() - self.paas._http.get2_cmd.assert_called_with("/admin/v1/paas") - self.assertEqual(paas_list, []) - - @patch("osmclient.sol005.paas.utils") - def test_get_success(self, mock_utils): - self.paas_data.update({"_id": "1234"}) - mock_utils.validate_uuid4.return_value = False - self.paas.get_id = Mock() - self.paas.get_id.return_value = "1234" - self.paas._http.get2_cmd.return_value = (0, json.dumps(self.paas_data)) - paas = self.paas.get("paas_name") - self.paas._client.get_token.assert_called() - self.paas._http.get2_cmd.assert_called_with("/admin/v1/paas/1234") - self.assertEqual(paas, self.paas_data) - - @patch("osmclient.sol005.paas.utils") - def test_get_client_exception(self, mock_utils): - mock_utils.validate_uuid4.return_value = False - self.paas.get_id = Mock() - self.paas.get_id.return_value = "1234" - self.paas._http.get2_cmd.return_value = (404, json.dumps({})) - with self.assertRaises(ClientException): - self.paas.get("paas_name") - self.paas._client.get_token.assert_called() - self.paas._http.get2_cmd.assert_called_with("/admin/v1/paas/1234") - - @patch("osmclient.sol005.paas.utils") - def test_get_not_found_exception(self, mock_utils): - mock_utils.validate_uuid4.return_value = False - self.paas.get_id = Mock() - self.paas.get_id.return_value = "1234" - self.paas._http.get2_cmd.side_effect = NotFound() - with self.assertRaises(NotFound): - self.paas.get("paas_name") - self.paas._client.get_token.assert_called() - self.paas._http.get2_cmd.assert_called_with("/admin/v1/paas/1234") - - @patch("osmclient.sol005.paas.utils") - def test_get_success_use_id(self, mock_utils): - self.paas_data.update({"_id": "1234"}) - mock_utils.validate_uuid4.return_value = True - self.paas._http.get2_cmd.return_value = (0, json.dumps(self.paas_data)) - paas = self.paas.get("1234") - self.paas._client.get_token.assert_called() - self.paas._http.get2_cmd.assert_called_with("/admin/v1/paas/1234") - self.assertEqual(paas, self.paas_data) - - @patch("osmclient.sol005.paas.utils") - def test_get_with_get_id_exception(self, mock_utils): - self.paas_data.update({"_id": "1234"}) - mock_utils.validate_uuid4.return_value = False - self.paas.get_id = Mock() - self.paas.get_id.side_effect = NotFound() - self.paas._http.get2_cmd.return_value = (404, json.dumps(self.paas_data)) - with self.assertRaises(NotFound): - self.paas.get("paas_name") - self.paas._client.get_token.assert_called() - self.paas._http.get2_cmd.assert_not_called() diff --git a/osmclient/sol005/tests/test_sol005_ns.py b/osmclient/sol005/tests/test_sol005_ns.py deleted file mode 100644 index 77bdf70..0000000 --- a/osmclient/sol005/tests/test_sol005_ns.py +++ /dev/null @@ -1,261 +0,0 @@ -# Copyright 2022 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import yaml -import unittest -from unittest.mock import Mock, MagicMock - -from osmclient.common.exceptions import ClientException, NotFound -from osmclient.sol005.ns import Ns - - -class TestNs(unittest.TestCase): - def setUp(self): - self.ns = Ns(Mock(), Mock()) - self.set_http_header() - self.endpoint = "/nslcm/v1/ns_instances_content" - self.nsd_id = "nsd_id" - self.nsd_name = {"name": "nsd_name"} - self.nsr_name = {"name": "network service name"} - self.ns._client.nsd.get.return_value = {"_id": self.nsd_id} - - def set_http_header(self): - headers = {"key_1": "value"} - self.ns._client._headers = MagicMock() - self.ns._client._headers.__getitem__.side_effect = headers.__getitem__ - self.ns._client._headers.items = headers.items - - def test_create_ns_vim_account(self): - vim_account = "my_vim_id" - timeout = 15 - description = "description_for_my_NS" - - self.ns._client.vim.get.return_value = {"_id": vim_account} - self.ns._http.post_cmd.return_value = (200, '{"id": "1234"}') - - ns = { - "nsdId": self.nsd_id, - "nsName": self.nsr_name, - "nsDescription": description, - "vimAccountId": vim_account, - "timeout_ns_deploy": timeout, - } - - resp_id = self.ns.create( - nsd_name=self.nsd_name, - nsr_name=self.nsr_name, - vim_account=vim_account, - paas_account=None, - description=description, - timeout=timeout, - ) - - self.ns._client.get_token.assert_called() - self.ns._client.nsd.get.assert_called_with(self.nsd_name) - self.ns._http.post_cmd.assert_called_with( - endpoint=self.endpoint, postfields_dict=ns - ) - - self.assertEqual(resp_id, "1234") - - def test_create_ns_paas_account(self): - paas_account = "my_paas_id" - timeout = 10 - description = "description_for_my_NS" - config = { - "additionalParamsForNs": {}, - "additionalParamsForVnf": [ - {"additional_param0": "val", "member-vnf-index": "index"} - ], - } - - ns = { - "nsdId": self.nsd_id, - "nsName": self.nsr_name, - "nsDescription": description, - "paasAccountId": paas_account, - "timeout_ns_deploy": timeout, - "additionalParamsForNs": config["additionalParamsForNs"], - "additionalParamsForVnf": config["additionalParamsForVnf"], - } - - self.ns._client.paas.get.return_value = {"_id": paas_account} - self.ns._http.post_cmd.return_value = (200, '{"id": "1234"}') - - resp_id = self.ns.create( - nsd_name=self.nsd_name, - nsr_name=self.nsr_name, - vim_account=None, - paas_account=paas_account, - description=description, - timeout=timeout, - config=yaml.dump(config), - ) - - self.ns._client.get_token.assert_called() - self.ns._client.nsd.get.assert_called_with(self.nsd_name) - self.ns._http.post_cmd.assert_called_with( - endpoint=self.endpoint, postfields_dict=ns - ) - - self.assertEqual(resp_id, "1234") - - def test_create_ns_paas_account_does_not_exist(self): - paas_account = "my_paas_id" - - self.ns._client.paas.get.side_effect = NotFound() - - with self.assertRaises(NotFound): - self.ns.create( - nsd_name=self.nsd_name, - nsr_name=self.nsr_name, - vim_account=None, - paas_account=paas_account, - ) - - self.ns._client.get_token.assert_called() - self.ns._client.nsd.get.assert_called_with(self.nsd_name) - self.ns._http.set_http_header.assert_not_called() - self.ns._http.post_cmd.assert_not_called() - - def test_create_ns_paas_account_post_raises_exception(self): - paas_account = "my_paas_id" - - ns = { - "nsdId": self.nsd_id, - "nsName": self.nsr_name, - "nsDescription": "default description", - "paasAccountId": paas_account, - } - - self.ns._client.paas.get.return_value = {"_id": paas_account} - self.ns._http.post_cmd.side_effect = ClientException() - - with self.assertRaises(ClientException): - self.ns.create( - nsd_name=self.nsd_name, - nsr_name=self.nsr_name, - vim_account=None, - paas_account=paas_account, - ) - - self.ns._client.get_token.assert_called() - self.ns._client.nsd.get.assert_called_with(self.nsd_name) - self.ns._http.post_cmd.assert_called_with( - endpoint=self.endpoint, postfields_dict=ns - ) - - def test_create_ns_paas_account_id_is_not_in_response(self): - paas_account = "my_paas_id" - - ns = { - "nsdId": self.nsd_id, - "nsName": self.nsr_name, - "nsDescription": "default description", - "paasAccountId": paas_account, - } - - self.ns._client.paas.get.return_value = {"_id": paas_account} - self.ns._http.post_cmd.return_value = (200, "{}") - - with self.assertRaises(ClientException): - self.ns.create( - nsd_name=self.nsd_name, - nsr_name=self.nsr_name, - vim_account=None, - paas_account=paas_account, - ) - - self.ns._client.get_token.assert_called() - self.ns._client.nsd.get.assert_called_with(self.nsd_name) - self.ns._http.post_cmd.assert_called_with( - endpoint=self.endpoint, postfields_dict=ns - ) - - def invalid_config_test(self, config, exception): - paas_account = "my_paas_id" - - self.ns._client.paas.get.return_value = {"_id": paas_account} - - with self.assertRaises(ClientException) as e: - self.ns.create( - nsd_name=self.nsd_name, - nsr_name=self.nsr_name, - vim_account=None, - paas_account=paas_account, - config=yaml.dump(config), - ) - assert str(e.value) == exception - - self.ns._client.get_token.assert_called() - self.ns._client.nsd.get.assert_called_with(self.nsd_name) - self.ns._http.set_http_header.assert_not_called() - self.ns._http.post_cmd.assert_not_called() - - def test_create_ns_paas_invalid_additional_params_ns(self): - config = {"additionalParamsForNs": [], "additionalParamsForVnf": {}} - exception = "Error at --config 'additionalParamsForNs' must be a dictionary" - self.invalid_config_test(config, exception) - - def test_create_ns_paas_invalid_additional_params_vnf(self): - config = {"additionalParamsForNs": {}, "additionalParamsForVnf": {}} - exception = "Error at --config 'additionalParamsForVnf' must be a list" - self.invalid_config_test(config, exception) - - def test_create_ns_paas_invalid_additional_param_vnf(self): - config = {"additionalParamsForNs": {}, "additionalParamsForVnf": [[]]} - exception = ( - "Error at --config 'additionalParamsForVnf' items must be dictionaries" - ) - self.invalid_config_test(config, exception) - - def test_create_ns_paas_invalid_config_member_vnf_index_missing(self): - config = { - "additionalParamsForNs": {}, - "additionalParamsForVnf": [{"additional_param0": "val"}], - } - exception = "Error at --config 'additionalParamsForVnf' items must contain 'member-vnf-index'" - self.invalid_config_test(config, exception) - - def test_create_ns_without_paas_or_vim_account_raises_exception(self): - with self.assertRaises(ClientException) as e: - self.ns.create( - nsd_name=self.nsd_name, - nsr_name=self.nsr_name, - vim_account=None, - paas_account=None, - ) - error_msg = "Both of vim_account and paas_account options are empty." - assert str(e.value) == error_msg - - self.ns._client.get_token.assert_not_called() - self.ns._client.nsd.get.assert_not_called() - self.ns._http.set_http_header.assert_not_called() - self.ns._http.post_cmd.assert_not_called() - - def test_create_ns_with_paas_and_vim_account_raises_exception(self): - with self.assertRaises(ClientException) as e: - self.ns.create( - nsd_name=self.nsd_name, - nsr_name=self.nsr_name, - vim_account="vim_account", - paas_account="paas_account", - ) - error_msg = "Both of vim_account and paas_account options are set." - assert str(e.value) == error_msg - - self.ns._client.get_token.assert_not_called() - self.ns._client.nsd.get.assert_not_called() - self.ns._http.set_http_header.assert_not_called() - self.ns._http.post_cmd.assert_not_called() diff --git a/osmclient/sol005/vca.py b/osmclient/sol005/vca.py index 763739c..211fe0f 100644 --- a/osmclient/sol005/vca.py +++ b/osmclient/sol005/vca.py @@ -20,12 +20,14 @@ from osmclient.common import utils from osmclient.common.exceptions import NotFound from osmclient.common.exceptions import ClientException import json +import logging class VCA(object): def __init__(self, http=None, client=None): self._http = http self._client = client + self._logger = logging.getLogger("osmclient") self._apiName = "/admin" self._apiVersion = "/v1" self._apiResource = "/vca" @@ -34,6 +36,7 @@ class VCA(object): ) def create(self, name, vca): + self._logger.debug("") self._client.get_token() http_code, resp = self._http.post_cmd( endpoint=self._apiBase, postfields_dict=vca @@ -44,6 +47,7 @@ class VCA(object): print(resp["id"]) def update(self, name, vca): + self._logger.debug("") self._client.get_token() vca_id = self.get(name)["_id"] self._http.patch_cmd( @@ -52,6 +56,7 @@ class VCA(object): ) def get_id(self, name): + self._logger.debug("") """Returns a VCA id from a VCA name""" for vca in self.list(): if name == vca["name"]: @@ -59,6 +64,7 @@ class VCA(object): raise NotFound("VCA {} not found".format(name)) def delete(self, name, force=False): + self._logger.debug("") self._client.get_token() vca_id = name if not utils.validate_uuid4(name): @@ -77,6 +83,7 @@ class VCA(object): def list(self, cmd_filter=None): """Returns a list of K8s clusters""" + self._logger.debug("") self._client.get_token() filter_string = "" if cmd_filter: @@ -88,6 +95,7 @@ class VCA(object): def get(self, name): """Returns a VCA based on name or id""" + self._logger.debug("") self._client.get_token() vca_id = name if not utils.validate_uuid4(name): diff --git a/osmclient/sol005/vim.py b/osmclient/sol005/vim.py index 72a0822..4962a91 100644 --- a/osmclient/sol005/vim.py +++ b/osmclient/sol005/vim.py @@ -197,6 +197,8 @@ class Vim(object): vim_account["vim_user"] = vim_access["vim-username"] or "null" vim_account["vim_password"] = vim_access["vim-password"] or "null" vim_account["vim_tenant_name"] = vim_access["vim-tenant-name"] or "null" + if "prometheus-config" in vim_access: + vim_account["prometheus-config"] = vim_access["prometheus-config"] return vim_account def get_id(self, name):