Revert "Remove v1 version of osmclient which was used with old NBI (pre-Release FOUR)" 45/13545/3
authorgarciadeblas <gerardo.garciadeblas@telefonica.com>
Mon, 19 Jun 2023 09:17:26 +0000 (11:17 +0200)
committergarciadeblas <gerardo.garciadeblas@telefonica.com>
Wed, 21 Jun 2023 14:42:00 +0000 (16:42 +0200)
This reverts commit ae90d6fd63a349680b9f8e9a975bf90ec3c598c4.

Change-Id: I1f67f548c99e04279d9095b51b11cbbeacdd9609
Signed-off-by: garciadeblas <gerardo.garciadeblas@telefonica.com>
21 files changed:
osmclient/client.py
osmclient/scripts/osm.py
osmclient/sol005/client.py
osmclient/v1/__init__.py [new file with mode: 0644]
osmclient/v1/client.py [new file with mode: 0644]
osmclient/v1/key.py [new file with mode: 0644]
osmclient/v1/ns.py [new file with mode: 0644]
osmclient/v1/nsd.py [new file with mode: 0644]
osmclient/v1/package.py [new file with mode: 0644]
osmclient/v1/tests/test_ns.py [new file with mode: 0644]
osmclient/v1/tests/test_nsd.py [new file with mode: 0644]
osmclient/v1/tests/test_package.py [new file with mode: 0644]
osmclient/v1/tests/test_vnf.py [new file with mode: 0644]
osmclient/v1/tests/test_vnfd.py [new file with mode: 0644]
osmclient/v1/utils.py [new file with mode: 0644]
osmclient/v1/vca.py [new file with mode: 0644]
osmclient/v1/vim.py [new file with mode: 0644]
osmclient/v1/vnf.py [new file with mode: 0644]
osmclient/v1/vnfd.py [new file with mode: 0644]
requirements.txt
tox.ini

index fe4869a..a076fc4 100644 (file)
@@ -19,6 +19,7 @@
 OSM client entry point
 """
 
+from osmclient.v1 import client as client
 from osmclient.sol005 import client as sol005client
 import logging
 import verboselogs
@@ -27,7 +28,7 @@ import verboselogs
 verboselogs.install()
 
 
-def Client(version=1, host=None, *args, **kwargs):
+def Client(version=1, host=None, sol005=True, *args, **kwargs):
     log_format_simple = "%(levelname)s %(message)s"
     log_format_complete = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(funcName)s(): %(message)s"
     log_formatter_simple = logging.Formatter(
@@ -52,7 +53,13 @@ def Client(version=1, host=None, *args, **kwargs):
             logger.setLevel(level=logging.VERBOSE)
         elif verbose > 2:
             logger.setLevel(level=logging.DEBUG)
-    if version == 1:
-        return sol005client.Client(host, *args, **kwargs)
+    if not sol005:
+        if version == 1:
+            return client.Client(host, *args, **kwargs)
+        else:
+            raise Exception("Unsupported client version")
     else:
-        raise Exception("Unsupported client version")
+        if version == 1:
+            return sol005client.Client(host, *args, **kwargs)
+        else:
+            raise Exception("Unsupported client version")
index 74bb84b..e5be118 100755 (executable)
@@ -41,8 +41,9 @@ from osmclient.cli_commands import (
     wim,
 )
 import yaml
+import pycurl
+import os
 import logging
-from requests import RequestException
 
 
 @click.group(
@@ -114,7 +115,8 @@ def cli_osm(ctx, **kwargs):
         exit(1)
     # Remove None values
     kwargs = {k: v for k, v in kwargs.items() if v is not None}
-    ctx.obj = client.Client(version=1, host=hostname, **kwargs)
+    sol005 = os.getenv("OSM_SOL005", True)
+    ctx.obj = client.Client(host=hostname, sol005=sol005, **kwargs)
     logger = logging.getLogger("osmclient")
 
 
@@ -290,7 +292,7 @@ def cli():
 
         cli_osm()
         exit(0)
-    except RequestException as exc:
+    except pycurl.error as exc:
         print(exc)
         print(
             'Maybe "--hostname" option or OSM_HOSTNAME environment variable needs to be specified'
index 70d5c13..b05dbcc 100644 (file)
@@ -18,6 +18,7 @@
 OSM SOL005 client API
 """
 
+# from osmclient.v1 import vca
 from osmclient.sol005 import vnfd
 from osmclient.sol005 import nsd
 from osmclient.sol005 import nst
diff --git a/osmclient/v1/__init__.py b/osmclient/v1/__init__.py
new file mode 100644 (file)
index 0000000..2bf7fed
--- /dev/null
@@ -0,0 +1,15 @@
+# Copyright 2017 Sandvine
+#
+# 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/v1/client.py b/osmclient/v1/client.py
new file mode 100644 (file)
index 0000000..7d01cfa
--- /dev/null
@@ -0,0 +1,121 @@
+# Copyright 2017 Sandvine
+#
+# 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.
+
+"""
+OSM v1 client API
+"""
+
+from osmclient.v1 import vnf
+from osmclient.v1 import vnfd
+from osmclient.v1 import ns
+from osmclient.v1 import nsd
+from osmclient.v1 import vim
+from osmclient.v1 import package
+from osmclient.v1 import utils
+from osmclient.common import http
+from osmclient.common import package_tool
+
+
+class Client(object):
+    def __init__(
+        self,
+        host=None,
+        so_port=8008,
+        so_project="default",
+        ro_host=None,
+        ro_port=9090,
+        upload_port=8443,
+        **kwargs
+    ):
+        self._user = "admin"
+        self._password = "admin"
+
+        if len(host.split(":")) > 1:
+            # backwards compatible, port provided as part of host
+            self._host = host.split(":")[0]
+            self._so_port = host.split(":")[1]
+        else:
+            self._host = host
+            self._so_port = so_port
+
+        self._so_project = so_project
+
+        http_client = http.Http("https://{}:{}/".format(self._host, self._so_port))
+        http_client.set_http_header(
+            ["Accept: application/vnd.yand.data+json", "Content-Type: application/json"]
+        )
+
+        self._so_version = self.get_so_version(http_client)
+
+        if ro_host is None:
+            ro_host = host
+        ro_http_client = http.Http("http://{}:{}/".format(ro_host, ro_port))
+        ro_http_client.set_http_header(
+            ["Accept: application/vnd.yand.data+json", "Content-Type: application/json"]
+        )
+
+        upload_client_url = "https://{}:{}/composer/upload?api_server={}{}".format(
+            self._host,
+            upload_port,
+            "https://localhost&upload_server=https://",
+            self._host,
+        )
+
+        if self._so_version == "v3":
+            upload_client_url = (
+                "https://{}:{}/composer/upload?api_server={}{}&project_name={}".format(
+                    self._host,
+                    upload_port,
+                    "https://localhost&upload_server=https://",
+                    self._host,
+                    self._so_project,
+                )
+            )
+
+        upload_client = http.Http(upload_client_url)
+
+        self.vnf = vnf.Vnf(http_client, client=self, **kwargs)
+        self.vnfd = vnfd.Vnfd(http_client, client=self, **kwargs)
+        self.ns = ns.Ns(http=http_client, client=self, **kwargs)
+        self.nsd = nsd.Nsd(http_client, client=self, **kwargs)
+        self.vim = vim.Vim(
+            http=http_client, ro_http=ro_http_client, client=self, **kwargs
+        )
+        self.package = package.Package(
+            http=http_client, upload_http=upload_client, client=self, **kwargs
+        )
+        self.utils = utils.Utils(http_client, **kwargs)
+        self.package_tool = package_tool.PackageTool(client=self)
+
+    @property
+    def so_rbac_project_path(self):
+        if self._so_version == "v3":
+            return "project/{}/".format(self._so_project)
+        else:
+            return ""
+
+    def get_so_version(self, http_client):
+        try:
+            resp = http_client.get_cmd("api/operational/version")
+            if not resp or "rw-base:version" not in resp:
+                return "v2"
+
+            if resp["rw-base:version"]["version"].split(".")[0] == "5":
+                # SO Version 5.x.x.x.x translates to OSM V3
+                return "v3"
+            return "v2"
+        except Exception:
+            return "v2"
diff --git a/osmclient/v1/key.py b/osmclient/v1/key.py
new file mode 100644 (file)
index 0000000..4e51b78
--- /dev/null
@@ -0,0 +1,40 @@
+# Copyright 2017 Sandvine
+#
+# 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.
+
+"""
+OSM ssh-key API handling
+"""
+
+import json
+import pycurl
+from io import BytesIO
+
+
+class Key(object):
+    def __init__(self, client=None):
+        self._client = client
+
+    def list(self):
+        data = BytesIO()
+        curl_cmd = self._client.get_curl_cmd("v1/api/config/key-pair?deep")
+        curl_cmd.setopt(pycurl.HTTPGET, 1)
+        curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
+        curl_cmd.perform()
+        curl_cmd.close()
+        resp = json.loads(data.getvalue().decode())
+        if "nsr:key-pair" in resp:
+            return resp["nsr:key-pair"]
+        return list()
diff --git a/osmclient/v1/ns.py b/osmclient/v1/ns.py
new file mode 100644 (file)
index 0000000..5006c1f
--- /dev/null
@@ -0,0 +1,230 @@
+# Copyright 2017 Sandvine
+#
+# 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.
+
+"""
+OSM ns API handling
+"""
+
+from osmclient.common import utils
+from osmclient.common.exceptions import ClientException
+from osmclient.common.exceptions import NotFound
+import uuid
+import yaml
+
+
+class Ns(object):
+    def __init__(self, http=None, client=None):
+        self._http = http
+        self._client = client
+
+    def list(self):
+        """Returns a list of ns's"""
+        resp = self._http.get_cmd(
+            "api/running/{}ns-instance-config".format(self._client.so_rbac_project_path)
+        )
+        if not resp or "nsr:ns-instance-config" not in resp:
+            return list()
+
+        if "nsr" not in resp["nsr:ns-instance-config"]:
+            return list()
+
+        return resp["nsr:ns-instance-config"]["nsr"]
+
+    def get(self, name):
+        """Returns an ns based on name"""
+        for ns in self.list():
+            if name == ns["name"]:
+                return ns
+        raise NotFound("ns {} not found".format(name))
+
+    def scale(self, ns_name, ns_scale_group, instance_index):
+        postdata = {}
+        postdata["instance"] = list()
+        instance = {}
+        instance["id"] = instance_index
+        postdata["instance"].append(instance)
+
+        ns = self.get(ns_name)
+        resp = self._http.post_cmd(
+            "v1/api/config/{}ns-instance-config/nsr/{}/scaling-group/{}/instance".format(
+                self._client.so_rbac_project_path, ns["id"], ns_scale_group
+            ),
+            postdata,
+        )
+        if "success" not in resp:
+            raise ClientException(
+                "failed to scale ns: {} result: {}".format(ns_name, resp)
+            )
+
+    def create(
+        self,
+        nsd_name,
+        nsr_name,
+        account,
+        config=None,
+        ssh_keys=None,
+        description="default description",
+        admin_status="ENABLED",
+    ):
+        postdata = {}
+        postdata["nsr"] = list()
+        nsr = {}
+        nsr["id"] = str(uuid.uuid1())
+
+        nsd = self._client.nsd.get(nsd_name)
+
+        if self._client._so_version == "v3":
+            datacenter, resource_orchestrator = self._client.vim.get_datacenter(account)
+            if datacenter is None or resource_orchestrator is None:
+                raise NotFound("cannot find datacenter account {}".format(account))
+            if "uuid" not in datacenter:
+                raise NotFound(
+                    "The RO Datacenter - {} is invalid. Please select another".format(
+                        account
+                    )
+                )
+        else:
+            # Backwards Compatiility
+            datacenter = self._client.vim.get_datacenter(account)
+            if datacenter is None:
+                raise NotFound("cannot find datacenter account {}".format(account))
+
+        nsr["nsd"] = nsd
+        nsr["name"] = nsr_name
+        nsr["short-name"] = nsr_name
+        nsr["description"] = description
+        nsr["admin-status"] = admin_status
+
+        if self._client._so_version == "v3":
+            # New format for V3
+            nsr["resource-orchestrator"] = resource_orchestrator
+            nsr["datacenter"] = datacenter["name"]
+        else:
+            # Backwards Compatiility
+            nsr["om-datacenter"] = datacenter["uuid"]
+
+        if ssh_keys is not None:
+            # ssh_keys is comma separate list
+            ssh_keys_format = []
+            for key in ssh_keys.split(","):
+                ssh_keys_format.append({"key-pair-ref": key})
+
+            nsr["ssh-authorized-key"] = ssh_keys_format
+
+        ns_config = {}
+
+        if config:
+            ns_config = yaml.safe_load(config)
+
+        if ns_config and "vim-network-name" in ns_config:
+            for network in ns_config["vim-network-name"]:
+                # now find this network
+                vld_name = network["name"]
+                # vim_vld_name = network['vim-network-name']
+
+                for index, vld in enumerate(nsr["nsd"]["vld"]):
+                    if vld["name"] == vld_name:
+                        nsr["nsd"]["vld"][index]["vim-network-name"] = network[
+                            "vim-network-name"
+                        ]
+
+        postdata["nsr"].append(nsr)
+
+        resp = self._http.post_cmd(
+            "api/config/{}ns-instance-config/nsr".format(
+                self._client.so_rbac_project_path
+            ),
+            postdata,
+        )
+
+        if "success" not in resp:
+            raise ClientException(
+                "failed to create ns: {} nsd: {} result: {}".format(
+                    nsr_name, nsd_name, resp
+                )
+            )
+
+    def get_opdata(self, id):
+        return self._http.get_cmd(
+            "api/operational/{}ns-instance-opdata/nsr/{}?deep".format(
+                self._client.so_rbac_project_path, id
+            )
+        )
+
+    def get_field(self, ns_name, field):
+        nsr = self.get(ns_name)
+        if nsr is None:
+            raise NotFound("failed to retrieve ns {}".format(ns_name))
+
+        if field in nsr:
+            return nsr[field]
+
+        nsopdata = self.get_opdata(nsr["id"])
+
+        if field in nsopdata["nsr:nsr"]:
+            return nsopdata["nsr:nsr"][field]
+
+        raise NotFound("failed to find {} in ns {}".format(field, ns_name))
+
+    def _terminate(self, ns_name):
+        ns = self.get(ns_name)
+        if ns is None:
+            raise NotFound("cannot find ns {}".format(ns_name))
+
+        return self._http.delete_cmd(
+            "api/config/{}ns-instance-config/nsr/{}".format(
+                self._client.so_rbac_project_path, ns["id"]
+            )
+        )
+
+    def delete(self, ns_name, wait=True):
+        vnfs = self.get_field(ns_name, "constituent-vnfr-ref")
+
+        resp = self._terminate(ns_name)
+        if "success" not in resp:
+            raise ClientException("failed to delete ns {}".format(ns_name))
+
+        # helper method to check if pkg exists
+        def check_not_exists(func):
+            try:
+                func()
+                return False
+            except NotFound:
+                return True
+
+        for vnf in vnfs:
+            if not utils.wait_for_value(
+                lambda: check_not_exists(lambda: self._client.vnf.get(vnf["vnfr-id"]))
+            ):
+                raise ClientException("vnf {} failed to delete".format(vnf["vnfr-id"]))
+        if not utils.wait_for_value(
+            lambda: check_not_exists(lambda: self.get(ns_name))
+        ):
+            raise ClientException("ns {} failed to delete".format(ns_name))
+
+    def get_monitoring(self, ns_name):
+        ns = self.get(ns_name)
+        mon_list = {}
+        if ns is None:
+            return mon_list
+
+        vnfs = self._client.vnf.list()
+        for vnf in vnfs:
+            if ns["id"] == vnf["nsr-id-ref"]:
+                if "monitoring-param" in vnf:
+                    mon_list[vnf["name"]] = vnf["monitoring-param"]
+
+        return mon_list
diff --git a/osmclient/v1/nsd.py b/osmclient/v1/nsd.py
new file mode 100644 (file)
index 0000000..527a45f
--- /dev/null
@@ -0,0 +1,62 @@
+# Copyright 2017 Sandvine
+#
+# 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.
+
+"""
+OSM nsd API handling
+"""
+
+from osmclient.common.exceptions import NotFound
+from osmclient.common.exceptions import ClientException
+
+
+class Nsd(object):
+    def __init__(self, http=None, client=None):
+        self._http = http
+        self._client = client
+
+    def list(self):
+        resp = self._http.get_cmd(
+            "api/running/{}nsd-catalog/nsd".format(self._client.so_rbac_project_path)
+        )
+
+        if self._client._so_version == "v3":
+            if resp and "project-nsd:nsd" in resp:
+                return resp["project-nsd:nsd"]
+        else:
+            # Backwards Compatibility
+            if resp and "nsd:nsd" in resp:
+                return resp["nsd:nsd"]
+
+        return list()
+
+    def get(self, name):
+        for nsd in self.list():
+            if name == nsd["name"]:
+                return nsd
+        raise NotFound("cannot find nsd {}".format(name))
+
+    def delete(self, nsd_name):
+        nsd = self.get(nsd_name)
+        if nsd is None:
+            raise NotFound("cannot find nsd {}".format(nsd_name))
+
+        resp = self._http.delete_cmd(
+            "api/running/{}nsd-catalog/nsd/{}".format(
+                self._client.so_rbac_project_path, nsd["id"]
+            )
+        )
+        if "success" not in resp:
+            raise ClientException("failed to delete nsd {}".format(nsd_name))
diff --git a/osmclient/v1/package.py b/osmclient/v1/package.py
new file mode 100644 (file)
index 0000000..f1a4bf4
--- /dev/null
@@ -0,0 +1,70 @@
+# Copyright 2017 Sandvine
+#
+# 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.
+
+"""
+OSM package API handling
+"""
+
+from osmclient.common.exceptions import ClientException
+from osmclient.common.exceptions import NotFound
+from osmclient.common import utils
+
+
+class Package(object):
+    def __init__(self, http=None, upload_http=None, client=None):
+        self._client = client
+        self._http = http
+        self._upload_http = upload_http
+
+    def _wait_for_package(self, pkg_type):
+        if "vnfd" in pkg_type["type"]:
+            get_method = self._client.vnfd.get
+        elif "nsd" in pkg_type["type"]:
+            get_method = self._client.nsd.get
+        else:
+            raise ClientException("no valid package type found")
+
+        # helper method to check if pkg exists
+        def check_exists(func):
+            try:
+                func()
+            except NotFound:
+                return False
+            return True
+
+        return utils.wait_for_value(
+            lambda: check_exists(lambda: get_method(pkg_type["name"]))
+        )
+
+    def get_key_val_from_pkg(self, descriptor_file):
+        return utils.get_key_val_from_pkg(descriptor_file)
+
+    def wait_for_upload(self, filename):
+        """wait(block) for an upload to succeed.
+        The filename passed is assumed to be a descriptor tarball.
+        """
+        pkg_type = utils.get_key_val_from_pkg(filename)
+
+        if pkg_type is None:
+            raise ClientException("Cannot determine package type")
+
+        if not self._wait_for_package(pkg_type):
+            raise ClientException("package {} failed to upload".format(filename))
+
+    def upload(self, filename):
+        resp = self._upload_http.post_cmd(formfile=("package", filename))
+        if not resp or "transaction_id" not in resp:
+            raise ClientException("failed to upload package")
diff --git a/osmclient/v1/tests/test_ns.py b/osmclient/v1/tests/test_ns.py
new file mode 100644 (file)
index 0000000..616b8a4
--- /dev/null
@@ -0,0 +1,51 @@
+# Copyright 2017 Sandvine
+#
+# 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 unittest
+from mock import Mock
+from osmclient.v1 import ns
+from osmclient.v1 import client
+from osmclient.common.exceptions import NotFound
+
+
+class TestNs(unittest.TestCase):
+    def test_list_empty(self):
+        mock = Mock()
+        mock.get_cmd.return_value = list()
+        assert len(ns.Ns(mock, client=client.Client(host="127.0.0.1")).list()) == 0
+
+    def test_get_notfound(self):
+        mock = Mock()
+        mock.get_cmd.return_value = "foo"
+        self.assertRaises(
+            NotFound, ns.Ns(mock, client=client.Client(host="127.0.0.1")).get, "bar"
+        )
+
+    def test_get_found(self):
+        mock = Mock()
+        mock.get_cmd.return_value = {
+            "nsr:ns-instance-config": {"nsr": [{"name": "foo"}]}
+        }
+        assert ns.Ns(mock, client=client.Client(host="127.0.0.1")).get("foo")
+
+    def test_get_monitoring_notfound(self):
+        mock = Mock()
+        mock.get_cmd.return_value = "foo"
+        self.assertRaises(
+            NotFound,
+            ns.Ns(mock, client=client.Client(host="127.0.0.1")).get_monitoring,
+            "bar",
+        )
diff --git a/osmclient/v1/tests/test_nsd.py b/osmclient/v1/tests/test_nsd.py
new file mode 100644 (file)
index 0000000..6f0255e
--- /dev/null
@@ -0,0 +1,44 @@
+# Copyright 2017 Sandvine
+#
+# 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 unittest
+from mock import Mock
+from osmclient.v1 import nsd
+from osmclient.v1 import client
+from osmclient.common.exceptions import NotFound
+
+
+class TestNsd(unittest.TestCase):
+    def test_list_empty(self):
+        mock = Mock()
+        mock.get_cmd.return_value = list()
+        assert len(nsd.Nsd(mock, client=client.Client(host="127.0.0.1")).list()) == 0
+
+    def test_get_notfound(self):
+        mock = Mock()
+        mock.get_cmd.return_value = "foo"
+        self.assertRaises(
+            NotFound, nsd.Nsd(mock, client=client.Client(host="127.0.0.1")).get, "bar"
+        )
+
+    def test_get_found(self):
+        mock = Mock()
+        if client.Client(host="127.0.0.1")._so_version == "v3":
+            mock.get_cmd.return_value = {"project-nsd:nsd": [{"name": "foo"}]}
+        else:
+            # Backwards Compatibility
+            mock.get_cmd.return_value = {"nsd:nsd": [{"name": "foo"}]}
+        assert nsd.Nsd(mock, client=client.Client(host="127.0.0.1")).get("foo")
diff --git a/osmclient/v1/tests/test_package.py b/osmclient/v1/tests/test_package.py
new file mode 100644 (file)
index 0000000..2f0362c
--- /dev/null
@@ -0,0 +1,41 @@
+# Copyright 2017 Sandvine
+#
+# 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 unittest
+from mock import Mock
+from osmclient.v1 import package
+from osmclient.common.exceptions import ClientException
+
+
+class TestPackage(unittest.TestCase):
+    def test_upload_fail(self):
+        mock = Mock()
+        mock.post_cmd.return_value = "foo"
+        self.assertRaises(
+            ClientException, package.Package(upload_http=mock).upload, "bar"
+        )
+
+        mock.post_cmd.return_value = None
+        self.assertRaises(
+            ClientException, package.Package(upload_http=mock).upload, "bar"
+        )
+
+    def test_wait_for_upload_bad_file(self):
+        mock = Mock()
+        mock.post_cmd.return_value = "foo"
+        self.assertRaises(
+            IOError, package.Package(upload_http=mock).wait_for_upload, "invalidfile"
+        )
diff --git a/osmclient/v1/tests/test_vnf.py b/osmclient/v1/tests/test_vnf.py
new file mode 100644 (file)
index 0000000..40b4648
--- /dev/null
@@ -0,0 +1,58 @@
+# Copyright 2017 Sandvine
+#
+# 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 unittest
+from mock import Mock
+from osmclient.v1 import vnf
+from osmclient.v1 import client
+from osmclient.common.exceptions import NotFound
+
+
+class TestVnf(unittest.TestCase):
+    def test_list_empty(self):
+        mock = Mock()
+        mock.get_cmd.return_value = list()
+        assert len(vnf.Vnf(mock, client=client.Client(host="127.0.0.1")).list()) == 0
+
+    def test_get_notfound(self):
+        mock = Mock()
+        mock.get_cmd.return_value = "foo"
+        self.assertRaises(
+            NotFound, vnf.Vnf(mock, client=client.Client(host="127.0.0.1")).get, "bar"
+        )
+
+    def test_get_found(self):
+        mock = Mock()
+        mock.get_cmd.return_value = {"vnfr:vnfr": [{"name": "foo"}]}
+        assert vnf.Vnf(mock, client=client.Client(host="127.0.0.1")).get("foo")
+
+    def test_get_monitoring_notfound(self):
+        mock = Mock()
+        mock.get_cmd.return_value = "foo"
+        self.assertRaises(
+            NotFound,
+            vnf.Vnf(mock, client=client.Client(host="127.0.0.1")).get_monitoring,
+            "bar",
+        )
+
+    def test_get_monitoring_found(self):
+        mock = Mock()
+        mock.get_cmd.return_value = {
+            "vnfr:vnfr": [{"name": "foo", "monitoring-param": True}]
+        }
+        assert vnf.Vnf(mock, client=client.Client(host="127.0.0.1")).get_monitoring(
+            "foo"
+        )
diff --git a/osmclient/v1/tests/test_vnfd.py b/osmclient/v1/tests/test_vnfd.py
new file mode 100644 (file)
index 0000000..f069742
--- /dev/null
@@ -0,0 +1,45 @@
+# Copyright 2017 Sandvine
+#
+# 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 unittest
+from mock import Mock
+from osmclient.v1 import vnfd
+from osmclient.v1 import client
+from osmclient.common.exceptions import NotFound
+
+
+class TestVnfd(unittest.TestCase):
+    def test_list_empty(self):
+        mock = Mock()
+        mock.get_cmd.return_value = list()
+        assert len(vnfd.Vnfd(mock, client=client.Client(host="127.0.0.1")).list()) == 0
+
+    def test_get_notfound(self):
+        mock = Mock()
+        mock.get_cmd.return_value = "foo"
+        self.assertRaises(
+            NotFound, vnfd.Vnfd(mock, client=client.Client(host="127.0.0.1")).get, "bar"
+        )
+
+    def test_get_found(self):
+        mock = Mock()
+        if client.Client(host="127.0.0.1")._so_version == "v3":
+            mock.get_cmd.return_value = {"project-vnfd:vnfd": [{"name": "foo"}]}
+        else:
+            # Backwards Compatibility
+            mock.get_cmd.return_value = {"vnfd:vnfd": [{"name": "foo"}]}
+
+        assert vnfd.Vnfd(mock, client=client.Client(host="127.0.0.1")).get("foo")
diff --git a/osmclient/v1/utils.py b/osmclient/v1/utils.py
new file mode 100644 (file)
index 0000000..092f193
--- /dev/null
@@ -0,0 +1,30 @@
+# Copyright 2017 Sandvine
+#
+# 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.
+
+"""
+OSM utils
+"""
+
+
+class Utils(object):
+    def __init__(self, http=None):
+        self._http = http
+
+    def get_vcs_info(self):
+        resp = self._http.get_cmd("api/operational/vcs/info")
+        if resp:
+            return resp["rw-base:info"]["components"]["component_info"]
+        return list()
diff --git a/osmclient/v1/vca.py b/osmclient/v1/vca.py
new file mode 100644 (file)
index 0000000..d2f17c6
--- /dev/null
@@ -0,0 +1,62 @@
+# Copyright 2017 Sandvine
+#
+# 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.
+
+"""
+OSM VCA API handling
+"""
+
+from osmclient.common.exceptions import ClientException
+
+
+class Vca(object):
+    def __init__(self, http=None, client=None):
+        self._http = http
+        self._client = client
+
+    def list(self):
+        resp = self._http.get_cmd(
+            "api/config/{}config-agent".format(self._client.so_rbac_project_path)
+        )
+        if resp and "rw-config-agent:config-agent" in resp:
+            return resp["rw-config-agent:config-agent"]["account"]
+        return list()
+
+    def delete(self, name):
+        if "success" not in self._http.delete_cmd(
+            "api/config/{}config-agent/account/{}".format(
+                self._client.so_rbac_project_path, name
+            )
+        ):
+            raise ClientException("failed to delete config agent {}".format(name))
+
+    def create(self, name, account_type, server, user, secret):
+        postdata = {}
+        postdata["account"] = list()
+
+        account = {}
+        account["name"] = name
+        account["account-type"] = account_type
+        account["juju"] = {}
+        account["juju"]["user"] = user
+        account["juju"]["secret"] = secret
+        account["juju"]["ip-address"] = server
+        postdata["account"].append(account)
+
+        if "success" not in self._http.post_cmd(
+            "api/config/{}config-agent".format(self._client.so_rbac_project_path),
+            postdata,
+        ):
+            raise ClientException("failed to create config agent {}".format(name))
diff --git a/osmclient/v1/vim.py b/osmclient/v1/vim.py
new file mode 100644 (file)
index 0000000..1e8b604
--- /dev/null
@@ -0,0 +1,299 @@
+# Copyright 2017 Sandvine
+#
+# 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.
+
+"""
+OSM vim API handling
+"""
+
+from osmclient.common.exceptions import ClientException
+from osmclient.common.exceptions import NotFound
+import yaml
+import time
+
+
+class Vim(object):
+    def __init__(self, http=None, ro_http=None, client=None):
+        self._client = client
+        self._ro_http = ro_http
+        self._http = http
+
+    def _attach(self, vim_name, vim_account):
+        tenant_name = "osm"
+        tenant = self._get_ro_tenant()
+        if tenant is None:
+            raise ClientException("tenant {} not found".format(tenant_name))
+
+        datacenter = self._get_ro_datacenter(vim_name)
+        if datacenter is None:
+            raise Exception("datacenter {} not found".format(vim_name))
+
+        return self._ro_http.post_cmd(
+            "openmano/{}/datacenters/{}".format(tenant["uuid"], datacenter["uuid"]),
+            vim_account,
+        )
+
+    def _detach(self, vim_name):
+        tenant_name = "osm"
+        tenant = self._get_ro_tenant()
+        if tenant is None:
+            raise ClientException("tenant {} not found".format(tenant_name))
+        return self._ro_http.delete_cmd(
+            "openmano/{}/datacenters/{}".format(tenant["uuid"], vim_name)
+        )
+
+    def create(self, name, vim_access):
+        vim_account = {}
+        vim_account["datacenter"] = {}
+
+        # currently assumes vim_acc
+        if "vim-type" not in vim_access:
+            # 'openstack' not in vim_access['vim-type']):
+            raise Exception("vim type not provided")
+
+        vim_account["datacenter"]["name"] = name
+        vim_account["datacenter"]["type"] = vim_access["vim-type"]
+
+        vim_config = {}
+        if "config" in vim_access and vim_access["config"] is not None:
+            vim_config = yaml.safe_load(vim_access["config"])
+
+        if vim_config:
+            vim_account["datacenter"]["config"] = vim_config
+
+        vim_account = self.update_vim_account_dict(vim_account, vim_access, vim_config)
+
+        resp = self._ro_http.post_cmd("openmano/datacenters", vim_account)
+        if resp and "error" in resp:
+            raise ClientException("failed to create vim")
+        else:
+            self._attach(name, vim_account)
+            self._update_ro_accounts()
+
+    def _update_ro_accounts(self):
+        get_ro_accounts = self._http.get_cmd(
+            "api/operational/{}ro-account".format(self._client.so_rbac_project_path)
+        )
+        if not get_ro_accounts or "rw-ro-account:ro-account" not in get_ro_accounts:
+            return
+        for account in get_ro_accounts["rw-ro-account:ro-account"]["account"]:
+            if account["ro-account-type"] == "openmano":
+                # Refresh the Account Status
+                refresh_body = {
+                    "input": {
+                        "ro-account": account["name"],
+                        "project-name": self._client._so_project,
+                    }
+                }
+                refresh_status = self._http.post_cmd(
+                    "api/operations/update-ro-account-status", refresh_body
+                )
+                if refresh_status and "error" in refresh_status:
+                    raise ClientException("Failed to refersh RO Account Status")
+
+    def update_vim_account_dict(self, vim_account, vim_access, vim_config):
+        if vim_access["vim-type"] == "vmware":
+            if "admin_username" in vim_config:
+                vim_account["datacenter"]["admin_username"] = vim_config[
+                    "admin_username"
+                ]
+            if "admin_password" in vim_config:
+                vim_account["datacenter"]["admin_password"] = vim_config[
+                    "admin_password"
+                ]
+            if "nsx_manager" in vim_config:
+                vim_account["datacenter"]["nsx_manager"] = vim_config["nsx_manager"]
+            if "nsx_user" in vim_config:
+                vim_account["datacenter"]["nsx_user"] = vim_config["nsx_user"]
+            if "nsx_password" in vim_config:
+                vim_account["datacenter"]["nsx_password"] = vim_config["nsx_password"]
+            if "orgname" in vim_config:
+                vim_account["datacenter"]["orgname"] = vim_config["orgname"]
+            if "vcenter_ip" in vim_config:
+                vim_account["datacenter"]["vcenter_ip"] = vim_config["vcenter_ip"]
+            if "vcenter_user" in vim_config:
+                vim_account["datacenter"]["vcenter_user"] = vim_config["vcenter_user"]
+            if "vcenter_password" in vim_config:
+                vim_account["datacenter"]["vcenter_password"] = vim_config[
+                    "vcenter_password"
+                ]
+            if "vcenter_port" in vim_config:
+                vim_account["datacenter"]["vcenter_port"] = vim_config["vcenter_port"]
+            vim_account["datacenter"]["vim_url"] = vim_access["vim-url"]
+            vim_account["datacenter"]["vim_url_admin"] = vim_access["vim-url"]
+            vim_account["datacenter"]["description"] = vim_access["description"]
+            vim_account["datacenter"]["vim_username"] = vim_access["vim-username"]
+            vim_account["datacenter"]["vim_password"] = vim_access["vim-password"]
+            vim_account["datacenter"]["vim_tenant_name"] = vim_access["vim-tenant-name"]
+        else:
+            vim_account["datacenter"]["vim_url"] = vim_access["vim-url"]
+            vim_account["datacenter"]["vim_url_admin"] = vim_access["vim-url"]
+            vim_account["datacenter"]["description"] = vim_access["description"]
+            vim_account["datacenter"]["vim_username"] = vim_access["vim-username"]
+            vim_account["datacenter"]["vim_password"] = vim_access["vim-password"]
+            vim_account["datacenter"]["vim_tenant_name"] = vim_access["vim-tenant-name"]
+        return vim_account
+
+    def delete(self, vim_name):
+        # first detach
+        self._detach(vim_name)
+        # detach.  continue if error,
+        # it could be the datacenter is left without attachment
+        resp = self._ro_http.delete_cmd("openmano/datacenters/{}".format(vim_name))
+        if "result" not in resp:
+            raise ClientException("failed to delete vim {} - {}".format(vim_name, resp))
+        self._update_ro_accounts()
+
+    def list(self, ro_update):
+        if ro_update:
+            self._update_ro_accounts()
+            # the ro_update needs to be made synchronous, for now this works around the issue
+            # and waits a resonable amount of time for the update to finish
+            time.sleep(2)
+
+        if self._client._so_version == "v3":
+            resp = self._http.get_cmd(
+                "v1/api/operational/{}ro-account-state".format(
+                    self._client.so_rbac_project_path
+                )
+            )
+            datacenters = []
+            if not resp or "rw-ro-account:ro-account-state" not in resp:
+                return list()
+
+            ro_accounts = resp["rw-ro-account:ro-account-state"]
+            for ro_account in ro_accounts["account"]:
+                if "datacenters" not in ro_account:
+                    continue
+                if "datacenters" not in ro_account["datacenters"]:
+                    continue
+                for datacenter in ro_account["datacenters"]["datacenters"]:
+                    datacenters.append(
+                        {
+                            "name": datacenter["name"],
+                            "uuid": datacenter["uuid"]
+                            if "uuid" in datacenter
+                            else None,
+                        }
+                    )
+
+            vim_accounts = datacenters
+            return vim_accounts
+        else:
+            # Backwards Compatibility
+            resp = self._http.get_cmd("v1/api/operational/datacenters")
+            if not resp or "rw-launchpad:datacenters" not in resp:
+                return list()
+
+            datacenters = resp["rw-launchpad:datacenters"]
+
+            vim_accounts = list()
+            if "ro-accounts" not in datacenters:
+                return vim_accounts
+
+            tenant = self._get_ro_tenant()
+            if tenant is None:
+                return vim_accounts
+
+            for roaccount in datacenters["ro-accounts"]:
+                if "datacenters" not in roaccount:
+                    continue
+                for datacenter in roaccount["datacenters"]:
+                    vim_accounts.append(
+                        self._get_ro_datacenter(datacenter["name"], tenant["uuid"])
+                    )
+            return vim_accounts
+
+    def _get_ro_tenant(self, name="osm"):
+        resp = self._ro_http.get_cmd("openmano/tenants/{}".format(name))
+
+        if not resp:
+            return None
+
+        if "tenant" in resp and "uuid" in resp["tenant"]:
+            return resp["tenant"]
+        else:
+            return None
+
+    def _get_ro_datacenter(self, name, tenant_uuid="any"):
+        resp = self._ro_http.get_cmd(
+            "openmano/{}/datacenters/{}".format(tenant_uuid, name)
+        )
+        if not resp:
+            raise NotFound("datacenter {} not found".format(name))
+
+        if "datacenter" in resp and "uuid" in resp["datacenter"]:
+            return resp["datacenter"]
+        else:
+            raise NotFound("datacenter {} not found".format(name))
+
+    def get(self, name):
+        tenant = self._get_ro_tenant()
+        if tenant is None:
+            raise NotFound("no ro tenant found")
+
+        return self._get_ro_datacenter(name, tenant["uuid"])
+
+    def get_datacenter(self, name):
+        if self._client._so_version == "v3":
+            resp = self._http.get_cmd(
+                "v1/api/operational/{}ro-account-state".format(
+                    self._client.so_rbac_project_path
+                )
+            )
+            if not resp:
+                return None, None
+
+            if not resp or "rw-ro-account:ro-account-state" not in resp:
+                return None, None
+
+            ro_accounts = resp["rw-ro-account:ro-account-state"]
+            for ro_account in ro_accounts["account"]:
+                if "datacenters" not in ro_account:
+                    continue
+                if "datacenters" not in ro_account["datacenters"]:
+                    continue
+                for datacenter in ro_account["datacenters"]["datacenters"]:
+                    if datacenter["name"] == name:
+                        return datacenter, ro_account["name"]
+            return None, None
+        else:
+            # Backwards Compatibility
+            resp = self._http.get_cmd("v1/api/operational/datacenters")
+            if not resp:
+                return None
+
+            if not resp or "rw-launchpad:datacenters" not in resp:
+                return None
+            if "ro-accounts" not in resp["rw-launchpad:datacenters"]:
+                return None
+            for roaccount in resp["rw-launchpad:datacenters"]["ro-accounts"]:
+                if "datacenters" not in roaccount:
+                    continue
+                for datacenter in roaccount["datacenters"]:
+                    if datacenter["name"] == name:
+                        return datacenter
+            return None
+
+    def get_resource_orchestrator(self):
+        resp = self._http.get_cmd(
+            "v1/api/operational/{}resource-orchestrator".format(
+                self._client.so_rbac_project_path
+            )
+        )
+
+        if not resp or "rw-launchpad:resource-orchestrator" not in resp:
+            return None
+        return resp["rw-launchpad:resource-orchestrator"]
diff --git a/osmclient/v1/vnf.py b/osmclient/v1/vnf.py
new file mode 100644 (file)
index 0000000..5db0240
--- /dev/null
@@ -0,0 +1,52 @@
+# Copyright 2017 Sandvine
+#
+# 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.
+
+"""
+OSM vnf API handling
+"""
+
+from osmclient.common.exceptions import NotFound
+
+
+class Vnf(object):
+    def __init__(self, http=None, client=None):
+        self._http = http
+        self._client = client
+
+    def list(self):
+        resp = self._http.get_cmd(
+            "v1/api/operational/{}vnfr-catalog/vnfr".format(
+                self._client.so_rbac_project_path
+            )
+        )
+        if resp and "vnfr:vnfr" in resp:
+            return resp["vnfr:vnfr"]
+        return list()
+
+    def get(self, vnf_name):
+        vnfs = self.list()
+        for vnf in vnfs:
+            if vnf_name == vnf["name"]:
+                return vnf
+            if vnf_name == vnf["id"]:
+                return vnf
+        raise NotFound("vnf {} not found".format(vnf_name))
+
+    def get_monitoring(self, vnf_name):
+        vnf = self.get(vnf_name)
+        if vnf and "monitoring-param" in vnf:
+            return vnf["monitoring-param"]
+        return None
diff --git a/osmclient/v1/vnfd.py b/osmclient/v1/vnfd.py
new file mode 100644 (file)
index 0000000..e43475d
--- /dev/null
@@ -0,0 +1,59 @@
+# Copyright 2017 Sandvine
+#
+# 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.
+
+"""
+OSM vnfd API handling
+"""
+
+from osmclient.common.exceptions import NotFound
+from osmclient.common.exceptions import ClientException
+
+
+class Vnfd(object):
+    def __init__(self, http=None, client=None):
+        self._http = http
+        self._client = client
+
+    def list(self):
+        resp = self._http.get_cmd(
+            "api/running/{}vnfd-catalog/vnfd".format(self._client.so_rbac_project_path)
+        )
+
+        if self._client._so_version == "v3":
+            if resp and "project-vnfd:vnfd" in resp:
+                return resp["project-vnfd:vnfd"]
+        else:
+            # Backwards Compatibility
+            if resp and "vnfd:vnfd" in resp:
+                return resp["vnfd:vnfd"]
+
+        return list()
+
+    def get(self, name):
+        for vnfd in self.list():
+            if name == vnfd["name"]:
+                return vnfd
+        raise NotFound("vnfd {} not found".format(name))
+
+    def delete(self, vnfd_name):
+        vnfd = self.get(vnfd_name)
+        resp = self._http.delete_cmd(
+            "api/running/{}vnfd-catalog/vnfd/{}".format(
+                self._client.so_rbac_project_path, vnfd["id"]
+            )
+        )
+        if "success" not in resp:
+            raise ClientException("failed to delete vnfd {}".format(vnfd_name))
index 500b7ad..421e66d 100644 (file)
@@ -30,6 +30,8 @@ packaging==23.1
     # via -r requirements.in
 prettytable==3.7.0
     # via -r requirements.in
+pycurl==7.45.2
+    # via -r requirements.in
 python-magic==0.4.27
     # via -r requirements.in
 pyyaml==5.4.1
diff --git a/tox.ini b/tox.ini
index e1560bb..78629d5 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -66,7 +66,7 @@ deps =  {[testenv]deps}
         -r{toxinidir}/requirements-test.txt
         pylint
 commands =
-        pylint -E osmclient/
+        pylint -E osmclient
 
 
 #######################################################################################