OSM client entry point
"""
+from osmclient.v1 import client as client
from osmclient.sol005 import client as sol005client
import logging
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(
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")
wim,
)
import yaml
+import pycurl
+import os
import logging
-from requests import RequestException
@click.group(
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")
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'
OSM SOL005 client API
"""
+# from osmclient.v1 import vca
from osmclient.sol005 import vnfd
from osmclient.sol005 import nsd
from osmclient.sol005 import nst
--- /dev/null
+# 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.
--- /dev/null
+# 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"
--- /dev/null
+# 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()
--- /dev/null
+# 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
--- /dev/null
+# 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))
--- /dev/null
+# 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")
--- /dev/null
+# 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",
+ )
--- /dev/null
+# 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")
--- /dev/null
+# 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"
+ )
--- /dev/null
+# 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"
+ )
--- /dev/null
+# 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")
--- /dev/null
+# 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()
--- /dev/null
+# 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))
--- /dev/null
+# 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"]
--- /dev/null
+# 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
--- /dev/null
+# 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))
# 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
-r{toxinidir}/requirements-test.txt
pylint
commands =
- pylint -E osmclient/
+ pylint -E osmclient
#######################################################################################