From 8a3ccd4c23c41b30126ae548a575233cba11cc4d Mon Sep 17 00:00:00 2001 From: Tim Van Steenburgh Date: Mon, 23 May 2016 16:36:01 -0400 Subject: [PATCH] connection test --- Makefile | 5 ++ juju/client/connection.py | 119 ++++++++++++++++++++++++++++++++------ setup.py | 5 +- tox.ini | 4 +- 4 files changed, 112 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index 88d58dc..0812d53 100644 --- a/Makefile +++ b/Makefile @@ -3,5 +3,10 @@ PY := .tox/py35/bin/python3.5 .tox: tox -r --notest +.phony: client client: $(PY) -m juju.client.facade -s juju/client/schemas.json -o juju/client/client.py + +.phony: test +test: + tox diff --git a/juju/client/connection.py b/juju/client/connection.py index 4d9ee1c..9a631ec 100644 --- a/juju/client/connection.py +++ b/juju/client/connection.py @@ -1,23 +1,38 @@ import asyncio +import io import json import logging +import os import random import ssl import string import websockets +import yaml log = logging.getLogger("websocket") + class Connection: """ - Usage: - client = await Connection.connect(info) + Usage:: + + # Connect to an arbitrary api server + client = await Connection.connect( + api_endpoint, model_uuid, username, password, cacert) + + # Connect using a controller/model name + client = await Connection.connect_model('local.local:default') + + # Connect to the currently active model + client = await Connection.connect_current() + """ def __init__(self): - self.__requestId__ = 0 + self.__request_id__ = 0 self.addr = None self.ws = None + self.facades = {} def _get_ssl(self, cert): return ssl.create_default_context( @@ -41,8 +56,8 @@ class Connection: return result async def rpc(self, msg): - self.__requestID += 1 - msg['RequestId'] = self.__requestID + self.__request_id__ += 1 + msg['RequestId'] = self.__request_id__ if'Params' not in msg: msg['Params'] = {} if "Version" not in msg: @@ -56,36 +71,102 @@ class Connection: return result @classmethod - async def connect(cls, info): - uuid = info.model_uuid - controller = info.controller - details = controller['details'] - endpoint = "wss://{}/model/{}/api".format( - details['api-endpoints'][0], - entity.model_uuid) + async def connect(cls, endpoint, uuid, username, password, cacert=None): + url = "wss://{}/model/{}/api".format(endpoint, uuid) client = cls() - await client.dial(endpoint, details['ca-cert']) - server_info = await client.login(info) + await client.open(url, cacert) + server_info = await client.login(username, password) client.build_facades(server_info['facades']) log.info("Driver connected to juju %s", endpoint) return client + @classmethod + async def connect_current(cls): + """Connect to the currently active model. + + """ + jujudata = JujuData() + controller_name = jujudata.current_controller() + controller = jujudata.controllers()[controller_name] + endpoint = controller['api-endpoints'][0] + cacert = controller.get('ca-cert') + accounts = jujudata.accounts()[controller_name] + username = accounts['current-account'] + password = accounts['accounts'][username]['password'] + models = jujudata.models()[controller_name]['accounts'][username] + model_name = models['current-model'] + model_uuid = models['models'][model_name]['uuid'] + + return await cls.connect( + endpoint, model_uuid, username, password, cacert) + + @classmethod + async def connect_model(cls, model): + """Connect to a model by name. + + :param str model: : + + """ + controller_name, model_name = model.split(':') + + jujudata = JujuData() + controller = jujudata.controllers()[controller_name] + endpoint = controller['api-endpoints'][0] + cacert = controller.get('ca-cert') + accounts = jujudata.accounts()[controller_name] + username = accounts['current-account'] + password = accounts['accounts'][username]['password'] + models = jujudata.models()[controller_name]['accounts'][username] + model_uuid = models['models'][model_name]['uuid'] + + return await cls.connect( + endpoint, model_uuid, username, password, cacert) + def build_facades(self, info): self.facades.clear() for facade in info: self.facades[facade['Name']] = facade['Versions'][-1] - async def login(self, info): - account = info.account - result = await client.rpc({ + async def login(self, username, password): + if not username.startswith('user-'): + username = 'user-{}'.format(username) + + result = await self.rpc({ "Type": "Admin", "Request": "Login", "Version": 3, "Params": { - "auth-tag": "user-{}".format(account['user']), - "credentials": account['password'], + "auth-tag": username, + "credentials": password, "Nonce": "".join(random.sample(string.printable, 12)), }}) return result['Response'] +class JujuData: + def __init__(self): + self.path = os.environ.get('JUJU_DATA') or '~/.local/share/juju' + self.path = os.path.abspath(os.path.expanduser(self.path)) + + def current_controller(self): + try: + filepath = os.path.join(self.path, 'current-controller') + with io.open(filepath, 'rt') as f: + return f.read().strip() + except OSError as e: + log.exception(e) + return None + + def controllers(self): + return self._load_yaml('controllers.yaml', 'controllers') + + def models(self): + return self._load_yaml('models.yaml', 'controllers') + + def accounts(self): + return self._load_yaml('accounts.yaml', 'controllers') + + def _load_yaml(self, filename, key): + filepath = os.path.join(self.path, filename) + with io.open(filepath, 'rt') as f: + return yaml.safe_load(f)[key] diff --git a/setup.py b/setup.py index adea67f..7a7e110 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,10 @@ setup( version="0.0.1", packages=find_packages( exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), - install_requires=[], + install_requires=[ + 'websockets', + 'pyyaml', + ], include_package_data=True, maintainer='Juju Ecosystem Engineering', maintainer_email='juju@lists.ubunut.com', diff --git a/tox.ini b/tox.ini index 0f55e6a..e57cea1 100644 --- a/tox.ini +++ b/tox.ini @@ -7,6 +7,8 @@ envlist = py35 [testenv] -commands = py.test +passenv = + HOME +commands = py.test -rsx -s deps = pytest -- 2.25.1