From: Patricia Reinoso Date: Wed, 26 Oct 2022 08:45:49 +0000 (+0000) Subject: Addition of PaaS X-Git-Url: https://osm.etsi.org/gitweb/?a=commitdiff_plain;h=refs%2Fchanges%2F17%2F12617%2F1;p=osm%2Fosmclient.git Addition of PaaS Add the following commands to the OSM Client: - osm paas-add - osm paas-delete - osm paas-update - osm paas-list - osm paas-show Change-Id: I5a0ba86c2b3a6a9239c2f336c33a0777cc72654b Signed-off-by: Patricia Reinoso --- diff --git a/osmclient/scripts/osm.py b/osmclient/scripts/osm.py index ab56a07..de121b1 100755 --- a/osmclient/scripts/osm.py +++ b/osmclient/scripts/osm.py @@ -3879,16 +3879,10 @@ def sdnc_show(ctx, name): 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", + "--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", + "--init-helm3/--skip-helm3", required=False, default=True, help="Initialize helm v3" ) @click.option( "--init-jujubundle/--skip-jujubundle", @@ -4208,11 +4202,7 @@ def k8scluster_show(ctx, name, literal): 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("--model-config", default={}, help="Configuration options for the models") @click.option("--description", default=None, help="human readable description") @click.pass_context def vca_add( @@ -4301,10 +4291,7 @@ def load_file(file_path: str) -> Dict: "--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("--model-config", help="Configuration options for the models") @click.option("--description", default=None, help="human readable description") @click.pass_context def vca_update( @@ -4440,6 +4427,193 @@ def vca_show(ctx, name, literal): 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 ########################### @@ -5634,10 +5808,7 @@ def update(ctx, ns_name, updatetype, config, timeout, wait): NS_NAME: Network service instance name or ID. """ - op_data = { - "timeout": timeout, - "updateType": updatetype, - } + op_data = {"timeout": timeout, "updateType": updatetype} if config: op_data["config"] = yaml.safe_load(config) @@ -5769,16 +5940,11 @@ def process_ns_heal_params(ctx, param, value): @cli_osm.command( name="ns-heal", short_help="heals (recreates) VNFs or VDUs of a NS instance", - context_settings=dict( - ignore_unknown_options=True, - ), + context_settings=dict(ignore_unknown_options=True), ) @click.argument("ns_name") @click.argument( - "args", - nargs=-1, - type=click.UNPROCESSED, - callback=process_ns_heal_params, + "args", nargs=-1, type=click.UNPROCESSED, callback=process_ns_heal_params ) @click.option("--timeout", type=int, default=None, help="timeout in seconds") @click.option( @@ -5861,16 +6027,11 @@ def process_vnf_heal_params(ctx, param, value): @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, - ), + context_settings=dict(ignore_unknown_options=True), ) @click.argument("vnf_name") @click.argument( - "args", - nargs=-1, - type=click.UNPROCESSED, - callback=process_vnf_heal_params, + "args", nargs=-1, type=click.UNPROCESSED, callback=process_vnf_heal_params ) @click.option("--timeout", type=int, default=None, help="timeout in seconds") @click.option( @@ -5880,14 +6041,7 @@ def process_vnf_heal_params(ctx, param, value): 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, -): +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 @@ -6426,7 +6580,7 @@ def cli(): print( 'Maybe "--hostname" option or OSM_HOSTNAME environment variable needs to be specified' ) - except ClientException as exc: + except (ClientException, NotFound) as exc: print("ERROR: {}".format(exc)) except (FileNotFoundError, PermissionError) as exc: print("Cannot open file: {}".format(exc)) diff --git a/osmclient/scripts/tests/test_paas_operations.py b/osmclient/scripts/tests/test_paas_operations.py new file mode 100644 index 0000000..9edd017 --- /dev/null +++ b/osmclient/scripts/tests/test_paas_operations.py @@ -0,0 +1,312 @@ +# 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/sol005/client.py b/osmclient/sol005/client.py index a69f3cc..5dd6f06 100644 --- a/osmclient/sol005/client.py +++ b/osmclient/sol005/client.py @@ -36,6 +36,7 @@ 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 @@ -99,6 +100,7 @@ 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/paas.py b/osmclient/sol005/paas.py new file mode 100644 index 0000000..3dcadf6 --- /dev/null +++ b/osmclient/sol005/paas.py @@ -0,0 +1,146 @@ +# 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 new file mode 100644 index 0000000..de7bcd3 --- /dev/null +++ b/osmclient/sol005/tests/test_paas.py @@ -0,0 +1,267 @@ +# 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()