class AuthenticationFailed(Exception):
"""The authentication for the specified user failed."""
+
+class InvalidCACertificate(Exception):
+ """The CA Certificate is not valid."""
\ No newline at end of file
# limitations under the License.
import asyncio
+import base64
+import binascii
import logging
import os
import os.path
import subprocess
import sys
# import time
+import n2vc.exceptions
from n2vc.provisioner import SSHProvisioner
# FIXME: this should load the juju inside or modules without having to
# explicitly install it. Check why it's not working.
# Load our subtree of the juju library
-path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
-path = os.path.join(path, "modules/libjuju/")
-if path not in sys.path:
- sys.path.insert(1, path)
+# path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
+# path = os.path.join(path, "modules/libjuju/")
+# if path not in sys.path:
+# sys.path.insert(1, path)
from juju.client import client
from juju.controller import Controller
self.authenticated = False
self.api_proxy = api_proxy
+ if log:
+ self.log = log
+ else:
+ self.log = logging.getLogger(__name__)
+
# For debugging
self.refcount = {
'controller': 0,
self.port = 17070
self.username = ""
self.secret = ""
-
+
self.juju_public_key = juju_public_key
if juju_public_key:
self._create_juju_public_key(juju_public_key)
+ else:
+ self.juju_public_key = ''
# TODO: Verify ca_cert is valid before using. VCA will crash
# if the ca_cert isn't formatted correctly.
- # self.ca_cert = ca_cert
- self.ca_cert = None
+ def base64_to_cacert(b64string):
+ """Convert the base64-encoded string containing the VCA CACERT.
+
+ The input string....
+
+ """
+ try:
+ cacert = base64.b64decode(b64string).decode("utf-8")
+
+ cacert = re.sub(
+ r'\\n',
+ r'\n',
+ cacert,
+ )
+ except binascii.Error as e:
+ self.log.debug("Caught binascii.Error: {}".format(e))
+ raise n2vc.exceptions.InvalidCACertificate("Invalid CA Certificate")
+
+ return cacert
+
+ self.ca_cert = base64_to_cacert(ca_cert)
+ # self.ca_cert = None
- if log:
- self.log = log
- else:
- self.log = logging.getLogger(__name__)
# Quiet websocket traffic
logging.getLogger('websockets.protocol').setLevel(logging.INFO)
models = await self.controller.list_models()
if ns_uuid not in models:
- try:
- self.models[ns_uuid] = await self.controller.add_model(
- ns_uuid
- )
- except JujuError as e:
- if "already exists" not in e.message:
- raise e
-
- # Create an observer for this model
- await self.create_model_monitor(ns_uuid)
+ # Get the new model
+ await self.get_model(ns_uuid)
return True
if model_name not in models:
try:
self.models[model_name] = await self.controller.add_model(
- model_name
+ model_name,
+ config={'authorized-keys': self.juju_public_key}
+
)
except JujuError as e:
if "already exists" not in e.message:
if self.secret:
self.log.debug(
- "Connecting to controller... ws://{}:{} as {}/{}".format(
+ "Connecting to controller... ws://{} as {}/{}".format(
self.endpoint,
- self.port,
self.user,
self.secret,
)
)
- await self.controller.connect(
- endpoint=self.endpoint,
- username=self.user,
- password=self.secret,
- cacert=self.ca_cert,
- )
- self.refcount['controller'] += 1
+ try:
+ await self.controller.connect(
+ endpoint=self.endpoint,
+ username=self.user,
+ password=self.secret,
+ cacert=self.ca_cert,
+ )
+ self.refcount['controller'] += 1
+ self.authenticated = True
+ self.log.debug("JujuApi: Logged into controller")
+ except Exception as ex:
+ self.log.debug("Caught exception: {}".format(ex))
else:
# current_controller no longer exists
# self.log.debug("Connecting to current controller...")
# cacert=cacert,
# )
self.log.fatal("VCA credentials not configured.")
+ self.authenticated = False
- self.authenticated = True
- self.log.debug("JujuApi: Logged into controller")
async def logout(self):
"""Logout of the Juju controller."""
packages=find_packages(
exclude=["*.tests", "*.tests.*", "tests.*", "tests"]),
install_requires=[
- 'macaroonbakery>=1.1,<2.0',
- 'pyRFC3339>=1.0,<2.0',
- 'pyyaml>=3.0,<4.0',
- 'theblues>=0.3.8,<1.0',
- 'websockets>=7.0,<8.0',
+ 'juju',
'paramiko',
'pyasn1>=0.4.4',
],
+<!--
+ Copyright 2019 Canonical Ltd.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
# N2VC Testing
export VCA_HOST=1.2.3.4
export VCA_USER=admin
export VCA_SECRET=admin
-
+export VCA_CACERT=$(juju controllers --format json | jq -r '.controllers["osm"]["ca-cert"]'| base64 | tr -d \\n)
+export VCA_PUBLIC_KEY=$(cat ~/.local/share/juju/ssh/juju_id_rsa.pub)
# Running tests
#!/usr/bin/env python3
+# Copyright 2019 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
import asyncio
import datetime
import logging
vca_user = os.getenv('VCA_USER', 'admin')
vca_charms = os.getenv('VCA_CHARMS', None)
vca_secret = os.getenv('VCA_SECRET', None)
+ vca_cacert = os.getenv('VCA_CACERT', None)
# Get the Juju Public key
juju_public_key = get_juju_public_key()
artifacts=vca_charms,
loop=loop,
juju_public_key=juju_public_key,
+ ca_cert=vca_cacert,
)
return client
-# A simple test to verify we're using the right libjuju module
+# Copyright 2019 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import base64
+import juju
+import logging
+import n2vc.exceptions
from n2vc.vnf import N2VC # noqa: F401
+import os
+import pytest
+import re
+import ssl
import sys
+MODEL_NAME = '5e4e7cb0-5678-4b82-97da-9e4a1b51f5d5'
+
+class TestN2VC(object):
+
+ @classmethod
+ def setup_class(self):
+ """ setup any state specific to the execution of the given class (which
+ usually contains tests).
+ """
+ # Initialize instance variable(s)
+ self.log = logging.getLogger()
+ self.log.level = logging.DEBUG
+
+ @classmethod
+ def teardown_class(self):
+ """ teardown any state that was previously setup with a call to
+ setup_class.
+ """
+ pass
+
+ """Utility functions"""
+ def get_n2vc(self, params={}):
+ """Return an instance of N2VC.VNF."""
+
+
+ # Extract parameters from the environment in order to run our test
+ vca_host = params['VCA_HOST']
+ vca_port = params['VCA_PORT']
+ vca_user = params['VCA_USER']
+ vca_charms = params['VCA_CHARMS']
+ vca_secret = params['VCA_SECRET']
+ vca_cacert = params['VCA_CACERT']
+ vca_public_key = params['VCA_PUBLIC_KEY']
+
+ client = n2vc.vnf.N2VC(
+ log=self.log,
+ server=vca_host,
+ port=vca_port,
+ user=vca_user,
+ secret=vca_secret,
+ artifacts=vca_charms,
+ juju_public_key=vca_public_key,
+ ca_cert=vca_cacert,
+ )
+ return client
+
+ """Tests"""
+
+ def test_vendored_libjuju(self):
+ """Test the module import for our vendored version of libjuju.
+
+ Test and verify that the version of libjuju being imported by N2VC is our
+ vendored version, not one installed externally.
+ """
+ for name in sys.modules:
+ if name.startswith("juju"):
+ module = sys.modules[name]
+ if getattr(module, "__file__"):
+ print(getattr(module, "__file__"))
+ assert re.search('n2vc', module.__file__, re.IGNORECASE)
+
+ # assert module.__file__.find("N2VC")
+ # assert False
+ return
+
+ @pytest.mark.asyncio
+ async def test_connect_invalid_cacert(self):
+ params = {
+ 'VCA_HOST': os.getenv('VCA_HOST', '127.0.0.1'),
+ 'VCA_PORT': os.getenv('VCA_PORT', 17070),
+ 'VCA_USER': os.getenv('VCA_USER', 'admin'),
+ 'VCA_SECRET': os.getenv('VCA_SECRET', 'admin'),
+ 'VCA_CHARMS': os.getenv('VCA_CHARMS', None),
+ 'VCA_PUBLIC_KEY': os.getenv('VCA_PUBLIC_KEY', None),
+ 'VCA_CACERT': 'invalidcacert',
+ }
+ with pytest.raises(n2vc.exceptions.InvalidCACertificate):
+ client = self.get_n2vc(params)
+
+
+ @pytest.mark.asyncio
+ async def test_login(self):
+ """Test connecting to libjuju."""
+ params = {
+ 'VCA_HOST': os.getenv('VCA_HOST', '127.0.0.1'),
+ 'VCA_PORT': os.getenv('VCA_PORT', 17070),
+ 'VCA_USER': os.getenv('VCA_USER', 'admin'),
+ 'VCA_SECRET': os.getenv('VCA_SECRET', 'admin'),
+ 'VCA_CHARMS': os.getenv('VCA_CHARMS', None),
+ 'VCA_PUBLIC_KEY': os.getenv('VCA_PUBLIC_KEY', None),
+ 'VCA_CACERT': os.getenv('VCA_CACERT', "invalidcacert"),
+ }
+
+ client = self.get_n2vc(params)
+
+ await client.login()
+ assert client.authenticated
+
+ await client.logout()
+ assert client.authenticated is False
+
+ @pytest.mark.asyncio
+ async def test_model(self):
+ """Test models."""
+ params = {
+ 'VCA_HOST': os.getenv('VCA_HOST', '127.0.0.1'),
+ 'VCA_PORT': os.getenv('VCA_PORT', 17070),
+ 'VCA_USER': os.getenv('VCA_USER', 'admin'),
+ 'VCA_SECRET': os.getenv('VCA_SECRET', 'admin'),
+ 'VCA_CHARMS': os.getenv('VCA_CHARMS', None),
+ 'VCA_PUBLIC_KEY': os.getenv('VCA_PUBLIC_KEY', None),
+ 'VCA_CACERT': os.getenv('VCA_CACERT', "invalidcacert"),
+ }
+
+ client = self.get_n2vc(params)
+
+ await client.login()
+ assert client.authenticated
+
+ self.log.debug("Creating model {}".format(MODEL_NAME))
+ await client.CreateNetworkService(MODEL_NAME)
+
+ # assert that model exists
+ model = await client.controller.get_model(MODEL_NAME)
+ assert model
+
+ await client.DestroyNetworkService(MODEL_NAME)
-def test_libjuju():
- """Test the module import for our vendored version of libjuju.
+ # Wait for model to be destroyed
+ import time
+ time.sleep(5)
- Test and verify that the version of libjuju being imported by N2VC is our
- vendored version, not one installed externally.
- """
- for name in sys.modules:
- if name.startswith("juju"):
- module = sys.modules[name]
- if getattr(module, "__file__"):
- assert module.__file__.find("N2VC/modules/libjuju/juju")
+ with pytest.raises(juju.errors.JujuAPIError):
+ model = await client.controller.get_model(MODEL_NAME)
- return
+ await client.logout()
+ assert client.authenticated is False
+# Copyright 2019 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
# Tox (http://tox.testrun.org/) is a tool for running tests
# in multiple virtualenvs. This configuration file will run the
# test suite on all supported python versions. To use it, "pip install tox"
VCA_SECRET
LXD_HOST
LXD_SECRET
+ VCA_CACERT
# These are needed so executing `charm build` succeeds
TERM
TERMINFO
[testenv:py3]
# default tox env, excludes integration and serial tests
commands =
- pytest --ignore modules/ --ignore tests/charms/ --tb native -ra -v -s -n auto -k 'not integration' -m 'not serial' {posargs}
+ pytest --ignore modules/ --ignore tests/charms/ --tb native -ra -v -n auto -k 'not integration' -m 'not serial' {posargs}
[testenv:lint]
envdir = {toxworkdir}/py3