From 54a2a65cf3c784ceeb41e2351140ce216a21dd4d Mon Sep 17 00:00:00 2001 From: garciadeblas Date: Fri, 26 Feb 2021 12:06:00 +0000 Subject: [PATCH] Feature 10476: New client commands and library to manage subscriptions Change-Id: I55aa5e78375829f20fa4aa881058a6c3a0df622d Signed-off-by: garciadeblas --- osmclient/scripts/osm.py | 122 +++++++++++++++++++++++++++ osmclient/sol005/client.py | 2 + osmclient/sol005/subscription.py | 138 +++++++++++++++++++++++++++++++ 3 files changed, 262 insertions(+) create mode 100644 osmclient/sol005/subscription.py diff --git a/osmclient/scripts/osm.py b/osmclient/scripts/osm.py index 0ce20c4..feb8c2f 100755 --- a/osmclient/scripts/osm.py +++ b/osmclient/scripts/osm.py @@ -4966,6 +4966,128 @@ def ns_metric_export(ctx, ns, vnf, vdu, metric, interval): # exit(1) +################# +# Subscription operations +################# + + +@cli_osm.command( + name="subscription-create", + short_help="creates a new subscription to a specific event", +) +@click.option( + "--event_type", + # type=click.Choice(['ns', 'nspkg', 'vnfpkg'], case_sensitive=False)) + type=click.Choice(["ns"], case_sensitive=False), + help="event type to be subscribed (for the moment, only ns is supported)", +) +@click.option("--event", default=None, help="specific yaml configuration for the event") +@click.option( + "--event_file", default=None, help="specific yaml configuration file for the event" +) +@click.pass_context +def subscription_create(ctx, event_type, event, event_file): + """creates a new subscription to a specific event""" + logger.debug("") + check_client_version(ctx.obj, ctx.command.name) + if event_file: + if event: + raise ClientException( + '"--event" option is incompatible with "--event_file" option' + ) + with open(event_file, "r") as cf: + event = cf.read() + ctx.obj.subscription.create(event_type, event) + + +@cli_osm.command(name="subscription-delete", short_help="deletes a subscription") +@click.option( + "--event_type", + # type=click.Choice(['ns', 'nspkg', 'vnfpkg'], case_sensitive=False)) + type=click.Choice(["ns"], case_sensitive=False), + help="event type to be subscribed (for the moment, only ns is supported)", +) +@click.argument("subscription_id") +@click.option( + "--force", is_flag=True, help="forces the deletion bypassing pre-conditions" +) +@click.pass_context +def subscription_delete(ctx, event_type, subscription_id, force): + """deletes a subscription + + SUBSCRIPTION_ID: ID of the subscription to be deleted + """ + logger.debug("") + check_client_version(ctx.obj, ctx.command.name) + ctx.obj.subscription.delete(event_type, subscription_id, force) + + +@cli_osm.command(name="subscription-list", short_help="list all subscriptions") +@click.option( + "--event_type", + # type=click.Choice(['ns', 'nspkg', 'vnfpkg'], case_sensitive=False)) + type=click.Choice(["ns"], case_sensitive=False), + help="event type to be subscribed (for the moment, only ns is supported)", +) +@click.option( + "--filter", + default=None, + multiple=True, + help="restricts the list to the subscriptions matching the filter", +) +@click.pass_context +def subscription_list(ctx, event_type, filter): + """list all subscriptions""" + logger.debug("") + check_client_version(ctx.obj, ctx.command.name) + if filter: + filter = "&".join(filter) + resp = ctx.obj.subscription.list(event_type, filter) + table = PrettyTable(["id", "filter", "CallbackUri"]) + for sub in resp: + table.add_row( + [ + sub["_id"], + wrap_text(text=json.dumps(sub["filter"], indent=2), width=70), + sub["CallbackUri"], + ] + ) + table.align = "l" + print(table) + + +@cli_osm.command( + name="subscription-show", short_help="shows the details of a subscription" +) +@click.argument("subscription_id") +@click.option( + "--event_type", + # type=click.Choice(['ns', 'nspkg', 'vnfpkg'], case_sensitive=False)) + type=click.Choice(["ns"], case_sensitive=False), + help="event type to be subscribed (for the moment, only ns is supported)", +) +@click.option( + "--filter", + multiple=True, + help="restricts the information to the fields in the filter", +) +@click.pass_context +def subscription_show(ctx, event_type, subscription_id, filter): + """shows the details of a subscription + + SUBSCRIPTION_ID: ID of the subscription + """ + logger.debug("") + # try: + resp = ctx.obj.subscription.get(subscription_id) + table = PrettyTable(["key", "attribute"]) + for k, v in list(resp.items()): + if not filter or k in filter: + table.add_row([k, wrap_text(text=json.dumps(v, indent=2), width=100)]) + table.align = "l" + print(table) + + #################### # Other operations #################### diff --git a/osmclient/sol005/client.py b/osmclient/sol005/client.py index 57bc2b1..c7f043b 100644 --- a/osmclient/sol005/client.py +++ b/osmclient/sol005/client.py @@ -38,6 +38,7 @@ from osmclient.sol005 import k8scluster from osmclient.sol005 import vca from osmclient.sol005 import repo from osmclient.sol005 import osmrepo +from osmclient.sol005 import subscription from osmclient.common import package_tool import json import logging @@ -100,6 +101,7 @@ class Client(object): 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) + self.subscription = subscription.Subscription(self._http_client, client=self) """ self.vca = vca.Vca(http_client, client=self, **kwargs) self.utils = utils.Utils(http_client, **kwargs) diff --git a/osmclient/sol005/subscription.py b/osmclient/sol005/subscription.py new file mode 100644 index 0000000..d4860ac --- /dev/null +++ b/osmclient/sol005/subscription.py @@ -0,0 +1,138 @@ +# +# 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 Subscription API handling +""" + +from osmclient.common.exceptions import NotFound +from osmclient.common.exceptions import ClientException +import yaml +import json +import logging + + +class Subscription(object): + def __init__(self, http=None, client=None): + self._http = http + self._client = client + self._logger = logging.getLogger("osmclient") + self._apiName = "/nslcm" + self._apiVersion = "/v1" + self._apiResource = "/subscriptions" + self._apiBase = "{}{}{}".format( + self._apiName, self._apiVersion, self._apiResource + ) + + def _rebuild_apibase(self, event_type): + self._logger.debug("") + if event_type == "ns": + self._apiName = "/nslcm" + else: + raise ClientException("unsupported event_type {}".format(event_type)) + self._apiBase = "{}{}{}".format( + self._apiName, self._apiVersion, self._apiResource + ) + + def create(self, event_type, event): + """ + Creates a subscription + :param event_type: event type to be subscribed (only ns is supported) + :param event: yaml string defining the event to be subscribed + :return: None. Exception if fail + """ + self._logger.debug("") + self._client.get_token() + self._rebuild_apibase(event_type) + + subscription_event = yaml.safe_load(event) + + http_code, resp = self._http.post_cmd( + endpoint=self._apiBase, postfields_dict=subscription_event + ) + if resp: + resp = json.loads(resp) + if not resp or "id" not in resp: + raise ClientException("unexpected response from server - {}".format(resp)) + print(resp["id"]) + + def delete(self, event_type, subscription_id, force=False): + """ + Deletes a subscription + :param event_type: event type to be subscribed (only ns is supported) + :param subscription_id: identifier of the subscription + :param force: forces deletion + :return: None. Exception if fail + """ + self._logger.debug("") + self._client.get_token() + self._rebuild_apibase(event_type) + querystring = "" + if force: + querystring = "?FORCE=True" + http_code, resp = self._http.delete_cmd( + "{}/{}{}".format(self._apiBase, subscription_id, querystring) + ) + if http_code == 202: + print("Deletion in progress") + elif http_code == 204: + print("Deleted") + else: + msg = resp or "" + raise ClientException( + "failed to delete subscription {} - {}".format(subscription_id, msg) + ) + + def list(self, event_type, filter=None): + """ + Returns a list of subscriptions + :param event_type: event type to be subscribed (only ns is supported) + :param filter + :return: list of subscriptions + """ + self._logger.debug("") + self._client.get_token() + self._rebuild_apibase(event_type) + filter_string = "" + if filter: + filter_string = "?{}".format(filter) + _, resp = self._http.get2_cmd("{}{}".format(self._apiBase, filter_string)) + if resp: + return json.loads(resp) + return list() + + def get(self, event_type, subscription_id): + """ + Returns a subscription from a subscription id + :param event_type: event type to be subscribed (only ns is supported) + :param subscription_id: identifier of the subscription + :return: dict with the subscription + """ + self._logger.debug("") + self._client.get_token() + self._rebuild_apibase(event_type) + try: + _, resp = self._http.get2_cmd( + "{}/{}".format(self._apiBase, subscription_id) + ) + self._logger.debug(yaml.safe_dump(resp)) + if resp: + return json.loads(resp) + if not resp or "id" not in resp: + raise ClientException( + "failed to get subscription info: {}".format(resp) + ) + return resp + except NotFound: + raise NotFound("subscription '{}' not found".format(subscription_id)) -- 2.17.1