import os
import os.path
import re
+import shlex
import ssl
+import subprocess
import sys
# import time
########################################################
to = ""
if machine_spec.keys():
- # TODO: This needs to be tested.
- # if all(k in machine_spec for k in ['hostname', 'username']):
- # # Enlist the existing machine in Juju
- # machine = await self.model.add_machine(spec='ssh:%@%'.format(
- # specs['host'],
- # specs['user'],
- # ))
- # to = machine.id
- pass
+ if all(k in machine_spec for k in ['host', 'user']):
+ # Enlist an existing machine as a Juju unit
+ machine = await model.add_machine(spec='ssh:{}@{}:{}'.format(
+ machine_spec['user'],
+ machine_spec['host'],
+ self.GetPrivateKeyPath(),
+ ))
+ to = machine.id
#######################################
# Get the initial charm configuration #
if 'rw_mgmt_ip' in params:
rw_mgmt_ip = params['rw_mgmt_ip']
- # initial_config = {}
- # self.log.debug(type(params))
- # self.log.debug("Params: {}".format(params))
if 'initial-config-primitive' not in params:
params['initial-config-primitive'] = {}
series='xenial',
# Apply the initial 'config' primitive during deployment
config=initial_config,
- # TBD: Where to deploy the charm to.
- to=None,
+ # Where to deploy the charm to.
+ to=to,
)
# #######################################
return results
+ # async def ProvisionMachine(self, model_name, hostname, username):
+ # """Provision machine for usage with Juju.
+ #
+ # Provisions a previously instantiated machine for use with Juju.
+ # """
+ # try:
+ # if not self.authenticated:
+ # await self.login()
+ #
+ # # FIXME: This is hard-coded until model-per-ns is added
+ # model_name = 'default'
+ #
+ # model = await self.get_model(model_name)
+ # model.add_machine(spec={})
+ #
+ # machine = await model.add_machine(spec='ssh:{}@{}:{}'.format(
+ # "ubuntu",
+ # host['address'],
+ # private_key_path,
+ # ))
+ # return machine.id
+ #
+ # except Exception as e:
+ # self.log.debug(
+ # "Caught exception while getting primitive status: {}".format(e)
+ # )
+ # raise N2VCPrimitiveExecutionFailed(e)
+
+ def GetPrivateKeyPath(self):
+ homedir = os.environ['HOME']
+ sshdir = "{}/.ssh".format(homedir)
+ private_key_path = "{}/id_n2vc_rsa".format(sshdir)
+ return private_key_path
+
+ async def GetPublicKey(self):
+ """Get the N2VC SSH public key.abs
+
+ Returns the SSH public key, to be injected into virtual machines to
+ be managed by the VCA.
+
+ The first time this is run, a ssh keypair will be created. The public
+ key is injected into a VM so that we can provision the machine with
+ Juju, after which Juju will communicate with the VM directly via the
+ juju agent.
+ """
+ public_key = ""
+
+ # Find the path to where we expect our key to live.
+ homedir = os.environ['HOME']
+ sshdir = "{}/.ssh".format(homedir)
+ if not os.path.exists(sshdir):
+ os.mkdir(sshdir)
+
+ private_key_path = "{}/id_n2vc_rsa".format(sshdir)
+ public_key_path = "{}.pub".format(private_key_path)
+
+ # If we don't have a key generated, generate it.
+ if not os.path.exists(private_key_path):
+ cmd = "ssh-keygen -t {} -b {} -N '' -f {}".format(
+ "rsa",
+ "4096",
+ private_key_path
+ )
+ subprocess.check_output(shlex.split(cmd))
+
+ # Read the public key
+ with open(public_key_path, "r") as f:
+ public_key = f.readline()
+
+ return public_key
+
async def ExecuteInitialPrimitives(self, model_name, application_name,
params, callback=None, *callback_args):
"""Execute multiple primitives.
return metrics
+ async def HasApplication(self, model_name, application_name):
+ model = await self.get_model(model_name)
+ app = await self.get_application(model, application_name)
+ if app:
+ return True
+ return False
+
# Non-public methods
async def add_relation(self, a, b, via=None):
"""
name,
)
- private_key_path, public_key_path = find_juju_ssh_keys()
+ private_key_path, public_key_path = find_n2vc_ssh_keys()
try:
# create profile w/cloud-init and juju ssh key
return (None, None)
+def find_n2vc_ssh_keys():
+ """Find the N2VC ssh keys."""
+
+ paths = []
+ paths.append(os.path.expanduser("~/.ssh/"))
+
+ for path in paths:
+ if os.path.exists(path):
+ private = os.path.expanduser("{}/id_n2vc_rsa".format(path))
+ public = os.path.expanduser("{}/id_n2vc_rsa.pub".format(path))
+ if os.path.exists(private) and os.path.exists(public):
+ return (private, public)
+ return (None, None)
+
+
def find_juju_ssh_keys():
"""Find the Juju ssh keys."""
"""
debug("Running teardown_class...")
try:
+
debug("Destroying LXD containers...")
for application in self.state:
if self.state[application]['container']:
# Logout of N2VC
if self.n2vc:
- debug("Logging out of N2VC...")
+ debug("teardown_class(): Logging out of N2VC...")
yield from self.n2vc.logout()
- debug("Logging out of N2VC...done.")
+ debug("teardown_class(): Logging out of N2VC...done.")
+
debug("Running teardown_class...done.")
except Exception as ex:
debug("Exception in teardown_class: {}".format(ex))
if not self.n2vc:
self.n2vc = get_n2vc(loop=loop)
- vnf_name = self.n2vc.FormatApplicationName(
+ application = self.n2vc.FormatApplicationName(
self.ns_name,
self.vnf_name,
str(vnf_index),
)
+
+ # Initialize the state of the application
+ self.state[application] = {
+ 'status': None, # Juju status
+ 'container': None, # lxd container, for proxy charms
+ 'actions': {}, # Actions we've executed
+ 'done': False, # Are we done testing this charm?
+ 'phase': "deploy", # What phase is this application in?
+ }
+
debug("Deploying charm at {}".format(self.artifacts[charm]))
+ # If this is a native charm, we need to provision the underlying
+ # machine ala an LXC container.
+ machine_spec = {}
+
+ if not self.isproxy(application):
+ debug("Creating container for native charm")
+ # args = ("default", application, None, None)
+ self.state[application]['container'] = create_lxd_container(
+ name=os.path.basename(__file__)
+ )
+
+ hostname = self.get_container_ip(
+ self.state[application]['container'],
+ )
+
+ machine_spec = {
+ 'host': hostname,
+ 'user': 'ubuntu',
+ }
+
await self.n2vc.DeployCharms(
self.ns_name,
- vnf_name,
+ application,
self.vnfd,
self.get_charm(charm),
params,
- {},
+ machine_spec,
self.n2vc_callback,
)
@classmethod
async def configure_proxy_charm(self, *args):
+ """Configure a container for use via ssh."""
(model, application, _, _) = args
try:
for application in self.charms:
try:
await self.n2vc.RemoveCharms(self.model, application)
+
+ while True:
+ # Wait for the application to be removed
+ await asyncio.sleep(10)
+ if not await self.n2vc.HasApplication(
+ self.model,
+ application,
+ ):
+ break
+
+ # Need to wait for the charm to finish, because native charms
if self.state[application]['container']:
debug("Deleting LXD container...")
destroy_lxd_container(
# Logout of N2VC
try:
- debug("Logging out of N2VC...")
+ debug("stop(): Logging out of N2VC...")
await self.n2vc.logout()
self.n2vc = None
- debug("Logging out of N2VC...Done.")
+ debug("stop(): Logging out of N2VC...Done.")
except Exception as ex:
debug(ex)
--- /dev/null
+"""
+Test N2VC's ssh key generation
+"""
+import os
+import pytest
+from . import base
+import tempfile
+
+
+@pytest.mark.asyncio
+async def test_ssh_keygen(monkeypatch):
+ with tempfile.TemporaryDirectory() as tmpdirname:
+ monkeypatch.setitem(os.environ, "HOME", tmpdirname)
+
+ client = base.get_n2vc()
+
+ public_key = await client.GetPublicKey()
+ assert len(public_key)