Commit c86c360d authored by Ilias Tsitsimpis's avatar Ilias Tsitsimpis
Browse files

Merge pull request #7 from saxtouri/feature-service-endpoint

Introduce the 'get_endpoint_url' method (identity client wrapper) and use it in client initialization.
parents 2fc3da09 1f969912
......@@ -23,4 +23,5 @@ Features:
- Datetime support in DateArgument
- Resources can be reassigned to projects
- Update account API commands to reflect changes in synnefo 0.16
- Implement a get_endpoint_url method and use it
......@@ -2,8 +2,8 @@ Creating applications with kamaki API
=====================================
Kamaki features a clients API for building third-party client applications that
communicate with Synnefo and (in most cases) OpenStack cloud services. The package is
called *kamaki.clients* and serves as a library.
communicate with Synnefo and (in most cases) OpenStack cloud services. The
package is called *kamaki.clients* and serves as a library.
A showcase of an application built on *kamaki.clients* is *kamaki.cli*, the
command line interface of kamaki.
......@@ -46,32 +46,28 @@ respectively.
Using endpoints to get the authentication url
---------------------------------------------
In OpenStack, each service (e.g., `compute`, `object-store`, etc.) has a number
of `endpoints`. These `endpoints` are URIs which are used by kamaki as prefixes
to form the corresponding API calls. Client applications need just one of these
`endpoints`, namely the `publicURL` (also referred to as `publicURL` in the
internals of kamaki client libraries).
Each client is initialized with a URL called ``the endpoint URL``. To get one,
an AstakosClient should be initialized first. The AstakosClient is an Astakos
client class. Astakos implements the OpenStack Identity API and the Synnefo
Account API. The astakos library features a simple call (``get_endpoint_url``)
which returns the initialization URL for a given service type (e.g., 'compute',
'object-store', 'network', 'image')
Here are instructions for getting the publicURL for a service::
Kamaki features a library of clients. Each client abides to a service type,
stored in ``service_type`` attribute in the client class.
1. From the deployment UI get the AUTHENTICATION_URL and TOKEN
(Example 1.2)
2. Use them to instantiate an AstakosClient
(Example 1.2)
3. Use AstakosClient instance to get endpoints for the service of interest
(Example 1.3)
4. The 'publicURL' endpoint is the URL we are looking for
(Example 1.3)
The values of ``service_type`` for each client are shown bellow::
The AstakosClient is a client for the Synnefo/Astakos server. Synnefo/Astakos
is an identity server that implements the OpenStack identity API and it
can be used to get the URLs needed for API calls URL construction. The astakos
kamaki client library simplifies this process.
storage.StorageClient, pithos.PithosClient --> object-store
compute.ComputeClient, cyclades.CycladesClient --> compute
network.NetworkClient, cyclades.CycladesNetworkClient --> network
image.ImageClient --> image
astakos.AstakosClient --> identity
Let's review with a few examples.
First, an astakos client must be initialized (Example 1.2). An
AUTHENTICATION_URL and a TOKEN can be acquired from the Synnefo deployment UI.
AUTHENTICATION_URL and a TOKEN can be acquired from the service web UI.
.. code-block:: python
:emphasize-lines: 1
......@@ -82,32 +78,19 @@ AUTHENTICATION_URL and a TOKEN can be acquired from the Synnefo deployment UI.
astakos = AstakosClient(AUTHENTICATION_URL, TOKEN)
Next, the astakos client can be used to retrieve the `publicURL` values for the
services of interest. In this case (Example 1.3) they are *cyclades* (compute)
and *pithos* (object-store). A number of endpoints is related to each service,
but kamaki clients only need the ones labeled ``publicURL``.
Next, the astakos client can be used to retrieve the URL for each service. In
this case (Example 1.3) we need a *compute* and an *object-store* URL. They
will be used to initialize a *cyclades* and a *pithos* client respectively.
.. code-block:: python
:emphasize-lines: 1
Example 1.3: Retrieve cyclades and pithos publicURL values
cyclades_endpoints = astakos.get_service_endpoints('compute')
cyclades_URL = cyclades_endpoints['publicURL']
pithos_endpoints = astakos.get_service_endpoints('object-store')
pithos_URL = pithos_endpoints['publicURL']
Example 1.3: Retrieve cyclades and pithos URLs
The ``get_service_endpoints`` method is called with the service name as an
argument. Here are the service names for the kamaki clients::
storage.StorageClient, pithos.PithosClient --> object-store
compute.ComputeClient, cyclades.CycladesClient --> compute
network.NetworkClient, cyclades.CycladesNetworkClient --> network
image.ImageClient --> image
astakos.AstakosClient --> identity, account
cyclades_URL = astakos.get_endpoint_url(CycladesClient.service_type)
pithos_URL = astakos.get_endpoint_url(PithosClent.service_type)
For example
It's time to initialize both clients.
.. code-block:: python
:emphasize-lines: 1
......@@ -199,8 +182,7 @@ The following example concatenates examples 1.1 to 1.4 plus error handling
raise
try:
cyclades_endpoints = astakos.get_service_endpoints('compute')
CYCLADES_URL = cyclades_endpoints['publicURL']
CYCLADES_URL = astakos.get_endpoint_url(CycladesClient.service_type)
except ClientError:
print('Failed to get endpoints for cyclades')
......@@ -210,8 +192,7 @@ The following example concatenates examples 1.1 to 1.4 plus error handling
print('Failed to initialize Cyclades client')
try:
pithos_endpoints = astakos.get_service_endpoints('object-store')
PITHOS_URL = pithos_endpoints['publicURL']
PITHOS_URL = astakos.get_endpoint_url(PithosClient.service_type)
except ClientError:
print('Failed to get endpoints for pithos')
......@@ -257,7 +238,7 @@ Batch-create servers
astakos = AstakosClient(AUTHENTICATION_URL, TOKEN)
CYCLADES_URL = astakos.get_service_endpoints('compute')['publicURL']
CYCLADES_URL = astakos.get_endpoint_url(CycladesClient.service_type)
cyclades = CycladesClient(CYCLADES_URL, TOKEN)
# (name, flavor-id, image-id)
......@@ -300,11 +281,11 @@ Register a banch of pre-uploaded images
astakos = AstakosClient(AUTHENTICATION_URL, TOKEN)
USER_UUID = astakos.user_info['id']
PITHOS_URL = astakos.get_service_endpoints('object-store')['publicURL']
PITHOS_URL = astakos.get_endpoint_url(PithosClient.service_type)
pithos = PithosClient(
PITHOS_URL, TOKEN, account=USER_UUID, container=IMAGE_CONTAINER)
IMAGE_URL = astakos.get_service_endpoints('image')['publicURL']
IMAGE_URL = astakos.get_endpoint_url(ImageClient.service_type)
plankton = ImageClient(IMAGE_URL, TOKEN)
for img in pithos.list_objects():
......@@ -336,12 +317,12 @@ Two servers and a private network
astakos = AstakosClient(AUTHENTICATION_URL, TOKEN)
NETWORK_URL = astakos.get_service_endpoints('network')['publicURL']
NETWORK_URL = astakos.get_endpoint_url(CycladesNetworkClient.service_type)
network = CycladesNetworkClient(NETWORK_URL, TOKEN)
net = network.create_network(type='MAC_FILTERED', name='My private network')
CYCLADES_URL = astakos.get_service_endpoints('compute')['publicURL']
CYCLADES_URL = astakos.get_endpoint_url(CycladesClient.service_type)
cyclades = CycladesClient(CYCLADES_URL, TOKEN)
FLAVOR_ID = 'put your flavor id here'
......
......@@ -82,18 +82,14 @@ This is the plan:
AUTH_URL, AUTH_TOKEN))
raise
# 3. Get the endpoints
# Identity, Account --> astakos
# Compute, Network --> cyclades
# Object-store --> pithos
# Image --> plankton
# 3. Get the endpoint URLs
try:
endpoints = dict(
astakos=AUTH_URL,
cyclades=auth.get_service_endpoints('compute')['publicURL'],
network=auth.get_service_endpoints('network')['publicURL'],
pithos=auth.get_service_endpoints('object-store')['publicURL'],
plankton=auth.get_service_endpoints('image')['publicURL']
cyclades=auth.get_endpoint_url(CycladesClient.service_type),
network=auth.get_endpoint_url(CycladesNetworkClient.service_type),
pithos=auth.get_endpoint_url(PithosClient.service_type),
plankton=auth.get_endpoint_url(ImageClient.service_type)
)
user_id = auth.user_info['id']
except ClientError:
......@@ -641,11 +637,13 @@ logging more. We also added some command line interaction candy.
print(' Get the endpoints')
try:
endpoints = dict(
astakos=auth.get_service_endpoints('identity')['publicURL'],
cyclades=auth.get_service_endpoints('compute')['publicURL'],
network=auth.get_service_endpoints('network')['publicURL'],
pithos=auth.get_service_endpoints('object-store')['publicURL'],
plankton=auth.get_service_endpoints('image')['publicURL']
# Astakos implements identity and account APIs - The endpoint
# URL is the same for both services
astakos=auth.get_endpoint_url('identity'),
cyclades=auth.get_endpoint_url(CycladesClient.service_type),
network=auth.get_endpoint_url(CycladesNetworkClient.service_type),
pithos=auth.get_endpoint_url(PithosClient.service_type),
plankton=auth.get_endpoint_url(ImageClient.service_type)
)
user_id = auth.user_info['id']
except ClientError:
......
......@@ -36,7 +36,7 @@ from kamaki.cli.utils import (
print_list, print_dict, print_json, print_items, ask_user,
filter_dicts_by_dict, DontRaiseUnicodeError, pref_enc)
from kamaki.cli.argument import FlagArgument, ValueArgument
from kamaki.cli.errors import CLIInvalidArgument
from kamaki.cli.errors import CLIInvalidArgument, CLIBaseUrlError
from sys import stdin, stdout, stderr
import codecs
......@@ -114,6 +114,20 @@ class _command_init(object):
self.auth_base = auth_base or getattr(self, 'auth_base', None)
self.cloud = cloud or getattr(self, 'cloud', None)
def get_client(self, cls, service):
self.cloud = getattr(self, 'cloud', 'default')
URL, TOKEN = self._custom_url(service), self._custom_token(service)
if not all([URL, TOKEN]):
astakos = getattr(self, 'auth_base', None)
if astakos:
URL = URL or astakos.get_endpoint_url(
self._custom_type(service) or cls.service_type,
self._custom_version(service))
TOKEN = TOKEN or astakos.token
else:
raise CLIBaseUrlError(service=service)
return cls(URL, TOKEN)
@DontRaiseUnicodeError
def write(self, s):
self._out.write(s)
......
......@@ -36,7 +36,6 @@ from base64 import b64encode
from os.path import exists, expanduser
from io import StringIO
from pydoc import pager
from json import dumps
from kamaki.cli import command
from kamaki.cli.command_tree import CommandTree
......@@ -116,26 +115,8 @@ class _server_wait(_service_wait):
class _init_cyclades(_command_init):
@errors.generic.all
@addLogSettings
def _run(self, service='compute'):
if getattr(self, 'cloud', None):
base_url = self._custom_url(service) or self._custom_url(
'cyclades')
if base_url:
token = self._custom_token(service) or self._custom_token(
'cyclades') or self.config.get_cloud('token')
self.client = CycladesClient(base_url=base_url, token=token)
return
else:
self.cloud = 'default'
if getattr(self, 'auth_base', False):
cyclades_endpoints = self.auth_base.get_service_endpoints(
self._custom_type('cyclades') or 'compute',
self._custom_version('cyclades') or '')
base_url = cyclades_endpoints['publicURL']
token = self.auth_base.token
self.client = CycladesClient(base_url=base_url, token=token)
else:
raise CLIBaseUrlError(service='cyclades')
def _run(self):
self.client = self.get_client(CycladesClient, 'cyclades')
@dataModification
def _restruct_server_info(self, vm):
......
......@@ -80,24 +80,7 @@ class _init_image(_command_init):
@errors.generic.all
@addLogSettings
def _run(self):
if getattr(self, 'cloud', None):
img_url = self._custom_url('image') or self._custom_url('plankton')
if img_url:
token = self._custom_token('image') or self._custom_token(
'plankton') or self.config.get_cloud(self.cloud, 'token')
self.client = ImageClient(base_url=img_url, token=token)
return
if getattr(self, 'auth_base', False):
plankton_endpoints = self.auth_base.get_service_endpoints(
self._custom_type('image') or self._custom_type(
'plankton') or 'image',
self._custom_version('image') or self._custom_version(
'plankton') or '')
base_url = plankton_endpoints['publicURL']
token = self.auth_base.token
else:
raise CLIBaseUrlError(service='plankton')
self.client = ImageClient(base_url=base_url, token=token)
self.client = self.get_client(ImageClient, 'plankton')
def main(self):
self._run()
......@@ -477,16 +460,9 @@ class image_register(_init_image, _optional_json):
required = ('name', 'pithos_location')
def _get_pithos_client(self, locator):
ptoken = self.client.token
if getattr(self, 'auth_base', False):
pithos_endpoints = self.auth_base.get_service_endpoints(
'object-store')
purl = pithos_endpoints['publicURL']
else:
purl = self.config.get_cloud('pithos', 'url')
if not purl:
raise CLIBaseUrlError(service='pithos')
return PithosClient(purl, ptoken, locator.uuid, locator.container)
pithos = self.get_client(PithosClient, 'pithos')
pithos.account, pithos.container = locator.uuid, locator.container
return pithos
def _load_params_from_file(self, location):
params, properties = dict(), dict()
......
......@@ -38,7 +38,8 @@ from kamaki.cli import command
from kamaki.cli.command_tree import CommandTree
from kamaki.cli.errors import (
CLIBaseUrlError, CLIInvalidArgument, raiseCLIError)
from kamaki.clients.cyclades import CycladesNetworkClient, ClientError
from kamaki.clients.cyclades import (
CycladesNetworkClient, ClientError, CycladesClient)
from kamaki.cli.argument import (
FlagArgument, ValueArgument, RepeatableArgument, IntArgument,
StatusArgument)
......@@ -74,27 +75,8 @@ class _port_wait(_service_wait):
class _init_network(_command_init):
@errors.generic.all
@addLogSettings
def _run(self, service='network'):
if getattr(self, 'cloud', None):
base_url = self._custom_url(service) or self._custom_url(
'network')
if base_url:
token = self._custom_token(service) or self._custom_token(
'network') or self.config.get_cloud('token')
self.client = CycladesNetworkClient(
base_url=base_url, token=token)
return
else:
self.cloud = 'default'
if getattr(self, 'auth_base', False):
network_endpoints = self.auth_base.get_service_endpoints(
self._custom_type('network') or 'network',
self._custom_version('network') or '')
base_url = network_endpoints['publicURL']
token = self.auth_base.token
self.client = CycladesNetworkClient(base_url=base_url, token=token)
else:
raise CLIBaseUrlError(service='network')
def _run(self):
self.client = self.get_client(CycladesNetworkClient, 'network')
def _filter_by_user_id(self, nets):
return [net for net in nets if net['user_id'] == self['user_id']] if (
......@@ -376,21 +358,6 @@ class subnet_create(_init_network, _optional_json):
self._run(network_id=self['network_id'], cidr=self['cidr'])
# @command(subnet_cmds)
# class subnet_delete(_init_network, _optional_output_cmd):
# """Delete a subnet"""
# @errors.generic.all
# @errors.cyclades.connection
# def _run(self, subnet_id):
# r = self.client.delete_subnet(subnet_id)
# self._optional_output(r)
# def main(self, subnet_id):
# super(self.__class__, self)._run()
# self._run(subnet_id=subnet_id)
@command(subnet_cmds)
class subnet_modify(_init_network, _optional_json):
"""Modify the attributes of a subnet"""
......@@ -802,11 +769,7 @@ class network_disconnect(_init_network, _port_wait, _optional_json):
"""Disconnect a network from a device"""
def _cyclades_client(self):
auth = getattr(self, 'auth_base')
endpoints = auth.get_service_endpoints('compute')
URL = endpoints['publicURL']
from kamaki.clients.cyclades import CycladesClient
return CycladesClient(URL, self.client.token)
return self.get_client(CycladesClient, 'cyclades')
arguments = dict(
wait=FlagArgument('Wait network to disconnect', ('-w', '--wait')),
......
......@@ -88,28 +88,12 @@ class _pithos_init(_command_init):
@errors.generic.all
@addLogSettings
def _run(self):
cloud = getattr(self, 'cloud', None)
if cloud:
self.base_url = self._custom_url('pithos')
else:
self.cloud = 'default'
self.token = self._custom_token('pithos')
self.container = self._custom_container() or 'pithos'
astakos = getattr(self, 'auth_base', None)
if astakos:
self.token = self.token or astakos.token
if not self.base_url:
pithos_endpoints = astakos.get_service_endpoints(
self._custom_type('pithos') or 'object-store',
self._custom_version('pithos') or '')
self.base_url = pithos_endpoints['publicURL']
else:
raise CLIBaseUrlError(service='astakos')
self.client = self.get_client(PithosClient, 'pithos')
self.base_url, self.token = self.client.base_url, self.client.token
self._set_account()
self.client = PithosClient(
self.base_url, self.token, self.account, self.container)
self.client.account = self.account
self.container = self._custom_container() or 'pithos'
self.client.container = self.container
def main(self):
self._run()
......
......@@ -72,16 +72,22 @@ class CLIUnimplemented(CLIError):
class CLIBaseUrlError(CLIError):
def __init__(self, message='', details=[], importance=2, service=None):
def __init__(
self,
message='', details=[], importance=2, service=None):
service = '%s' % (service or '')
message = message or 'No URL for %s' % service.lower()
details = details or [
'Two ways to resolve this:',
'(Use the correct cloud name, instead of "default")',
'A. (recommended) Let kamaki discover endpoint URLs for all',
'services by setting a single Authentication URL and token:',
' /config set cloud.default.url <AUTH_URL>',
' /config set cloud.default.token <t0k3n>']
'To resolve this:',
'Set the authentication URL and TOKEN:',
' kamaki config set cloud.<CLOUD NAME>.url <AUTH_URL>',
' kamaki config set cloud.<CLOUD NAME>.token <t0k3n>',
'OR',
'set a service-specific URL and/or TOKEN',
' kamaki config set '
'cloud.<CLOUD NAME>.%s_url <URL>' % (service or '<SERVICE>'),
' kamaki config set '
'cloud.<CLOUD NAME>.%s_token <TOKEN>' % (service or '<SERVICE>')]
super(CLIBaseUrlError, self).__init__(message, details, importance)
......
......@@ -338,7 +338,7 @@ class SilentEvent(Thread):
class Client(Logged):
service_type = ''
MAX_THREADS = 1
DATE_FORMATS = ['%a %b %d %H:%M:%S %Y', ]
CONNECTION_RETRY_LIMIT = 0
......
......@@ -68,7 +68,10 @@ class AstakosClient(OriginalAstakosClient):
def get_service_endpoints(self, service_type, version=None):
services = parse_endpoints(
self.get_endpoints(), ep_type=service_type, ep_version_id=version)
return services[0]['endpoints'][0] if services else []
return services[0]['endpoints'][0] if services else {}
def get_endpoint_url(self, service_type, version=None):
return self.get_service_endpoints(service_type, version)['publicURL']
@property
def user_info(self):
......@@ -134,6 +137,7 @@ class LoggedAstakosClient(AstakosClient):
class CachedAstakosClient(Client):
"""Synnefo Astakos cached client wraper"""
service_type = 'identity'
@_astakos_error
def __init__(self, base_url, token=None):
......@@ -251,6 +255,10 @@ class CachedAstakosClient(Client):
601)
return matches[0]
def get_endpoint_url(self, service_type, version=None, token=None):
r = self.get_service_endpoints(service_type, version, token)
return r['publicURL']
def list_users(self):
"""list cached users information"""
if not self._cache:
......
......@@ -37,6 +37,7 @@ import json
class ComputeRestClient(Client):
service_type = 'compute'
# NON-cyclades
def limits_get(self, success=200, **kwargs):
......
......@@ -53,6 +53,7 @@ def _format_image_headers(headers):
class ImageClient(Client):
"""Synnefo Plankton API client"""
service_type = 'image'
def __init__(self, base_url, token):
super(ImageClient, self).__init__(base_url, token)
......
......@@ -36,6 +36,7 @@ from kamaki.clients.utils import path4url
class NetworkRestClient(Client):
service_type = 'network'
def networks_get(self, network_id=None, **kwargs):
if network_id:
......
......@@ -36,6 +36,7 @@ from kamaki.clients.utils import path4url
class PithosRestClient(StorageClient):
service_type = 'object-store'
def account_head(
self,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment