summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/common/clientmanager.py107
-rw-r--r--openstackclient/compute/client.py32
-rw-r--r--openstackclient/compute/v2/server.py189
-rw-r--r--openstackclient/shell.py77
4 files changed, 299 insertions, 106 deletions
diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py
new file mode 100644
index 00000000..66d3ce76
--- /dev/null
+++ b/openstackclient/common/clientmanager.py
@@ -0,0 +1,107 @@
+"""Manage access to the clients, including authenticating when needed.
+"""
+
+import logging
+
+from openstackclient.common import exceptions as exc
+from openstackclient.compute import client as compute_client
+
+from keystoneclient.v2_0 import client as keystone_client
+
+LOG = logging.getLogger(__name__)
+
+
+class ClientCache(object):
+ """Descriptor class for caching created client handles.
+ """
+
+ def __init__(self, factory):
+ self.factory = factory
+ self._handle = None
+
+ def __get__(self, instance, owner):
+ # Tell the ClientManager to login to keystone
+ if self._handle is None:
+ instance.init_token()
+ self._handle = self.factory(instance)
+ return self._handle
+
+
+class ClientManager(object):
+ """Manages access to API clients, including authentication.
+ """
+
+ compute = ClientCache(compute_client.make_client)
+
+ def __init__(self, token=None, url=None,
+ auth_url=None,
+ tenant_name=None, tenant_id=None,
+ username=None, password=None,
+ region_name=None,
+ identity_api_version=None,
+ compute_api_version=None,
+ image_api_version=None,
+ ):
+ self._token = token
+ self._url = url
+ self._auth_url = auth_url
+ self._tenant_name = tenant_name
+ self._tenant_id = tenant_id
+ self._username = username
+ self._password = password
+ self._region_name = region_name
+ self._identity_api_version = identity_api_version
+ self._compute_api_version = compute_api_version
+ self._image_api_version = image_api_version
+
+ def init_token(self):
+ """Return the auth token and endpoint.
+ """
+ if self._token:
+ LOG.debug('using existing auth token')
+ return
+
+ LOG.debug('validating authentication options')
+ if not self._username:
+ raise exc.CommandError(
+ "You must provide a username via"
+ " either --os-username or env[OS_USERNAME]")
+
+ if not self._password:
+ raise exc.CommandError(
+ "You must provide a password via"
+ " either --os-password or env[OS_PASSWORD]")
+
+ if not (self._tenant_id or self._tenant_name):
+ raise exc.CommandError(
+ "You must provide a tenant_id via"
+ " either --os-tenant-id or via env[OS_TENANT_ID]")
+
+ if not self._auth_url:
+ raise exc.CommandError(
+ "You must provide an auth url via"
+ " either --os-auth-url or via env[OS_AUTH_URL]")
+
+ kwargs = {
+ 'username': self._username,
+ 'password': self._password,
+ 'tenant_id': self._tenant_id,
+ 'tenant_name': self._tenant_name,
+ 'auth_url': self._auth_url
+ }
+ self._auth_client = keystone_client.Client(**kwargs)
+ self._token = self._auth_client.auth_token
+ return
+
+ def get_endpoint_for_service_type(self, service_type):
+ """Return the endpoint URL for the service type.
+ """
+ # See if we are using password flow auth, i.e. we have a
+ # service catalog to select endpoints from
+ if self._auth_client and self._auth_client.service_catalog:
+ endpoint = self._auth_client.service_catalog.url_for(
+ service_type=service_type)
+ else:
+ # Hope we were given the correct URL.
+ endpoint = self._url
+ return endpoint
diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py
new file mode 100644
index 00000000..ef0ceb38
--- /dev/null
+++ b/openstackclient/compute/client.py
@@ -0,0 +1,32 @@
+import logging
+
+from novaclient import client as nova_client
+
+LOG = logging.getLogger(__name__)
+
+
+def make_client(instance):
+ """Returns a compute service client.
+ """
+ LOG.debug('instantiating compute client')
+ # FIXME(dhellmann): Where is the endpoint value used?
+ # url = instance.get_endpoint_for_service_type('compute')
+ client = nova_client.Client(
+ version=instance._compute_api_version,
+ username=instance._username,
+ api_key=instance._password,
+ project_id=instance._tenant_name,
+ auth_url=instance._auth_url,
+ # FIXME(dhellmann): add constructor argument for this
+ insecure=False,
+ region_name=instance._region_name,
+ # FIXME(dhellmann): get endpoint_type from option?
+ endpoint_type='publicURL',
+ # FIXME(dhellmann): add extension discovery
+ extensions=[],
+ service_type='compute',
+ # FIXME(dhellmann): what is service_name?
+ service_name='',
+ )
+ client.authenticate()
+ return client
diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py
index c7c6add0..69bfc7e8 100644
--- a/openstackclient/compute/v2/server.py
+++ b/openstackclient/compute/v2/server.py
@@ -20,45 +20,55 @@ Server action implementations
"""
import logging
+import os
+
+from cliff import lister
+from cliff import show
from openstackclient.common import command
from openstackclient.common import utils
-def _find_server(cs, server):
- """Get a server by name or ID."""
- return utils.find_resource(cs.servers, server)
-
-
-def _print_server(cs, server):
- # By default when searching via name we will do a
- # findall(name=blah) and due a REST /details which is not the same
- # as a .get() and doesn't get the information about flavors and
- # images. This fix it as we redo the call with the id which does a
- # .get() to get all informations.
- if not 'flavor' in server._info:
- server = _find_server(cs, server.id)
-
- networks = server.networks
- info = server._info.copy()
- for network_label, address_list in networks.items():
- info['%s network' % network_label] = ', '.join(address_list)
-
- flavor = info.get('flavor', {})
- flavor_id = flavor.get('id', '')
- info['flavor'] = _find_flavor(cs, flavor_id).name
-
- image = info.get('image', {})
- image_id = image.get('id', '')
- info['image'] = _find_image(cs, image_id).name
-
- info.pop('links', None)
- info.pop('addresses', None)
-
- utils.print_dict(info)
-
-
-class List_Server(command.OpenStackCommand):
+def _format_servers_list_networks(server):
+ """Return a string containing the networks a server is attached to.
+
+ :param server: a single Server resource
+ """
+ output = []
+ for (network, addresses) in server.networks.items():
+ if not addresses:
+ continue
+ addresses_csv = ', '.join(addresses)
+ group = "%s=%s" % (network, addresses_csv)
+ output.append(group)
+ return '; '.join(output)
+
+
+def get_server_properties(server, fields, formatters={}):
+ """Return a tuple containing the server properties.
+
+ :param server: a single Server resource
+ :param fields: tuple of strings with the desired field names
+ :param formatters: dictionary mapping field names to callables
+ to format the values
+ """
+ row = []
+ mixed_case_fields = ['serverId']
+
+ for field in fields:
+ if field in formatters:
+ row.append(formatters[field](server))
+ else:
+ if field in mixed_case_fields:
+ field_name = field.replace(' ', '_')
+ else:
+ field_name = field.lower().replace(' ', '_')
+ data = getattr(server, field_name, '')
+ row.append(data)
+ return tuple(row)
+
+
+class List_Server(command.OpenStackCommand, lister.Lister):
"List server command."
api = 'compute'
@@ -67,17 +77,79 @@ class List_Server(command.OpenStackCommand):
def get_parser(self, prog_name):
parser = super(List_Server, self).get_parser(prog_name)
parser.add_argument(
- '--long',
+ '--reservation-id',
+ help='only return instances that match the reservation',
+ )
+ parser.add_argument(
+ '--ip',
+ help='regular expression to match IP address',
+ )
+ parser.add_argument(
+ '--ip6',
+ help='regular expression to match IPv6 address',
+ )
+ parser.add_argument(
+ '--name',
+ help='regular expression to match name',
+ )
+ parser.add_argument(
+ '--instance-name',
+ help='regular expression to match instance name',
+ )
+ parser.add_argument(
+ '--status',
+ help='search by server status',
+ # FIXME(dhellmann): Add choices?
+ )
+ parser.add_argument(
+ '--flavor',
+ help='search by flavor ID',
+ )
+ parser.add_argument(
+ '--image',
+ help='search by image ID',
+ )
+ parser.add_argument(
+ '--host',
+ metavar='HOSTNAME',
+ help='search by hostname',
+ )
+ parser.add_argument(
+ '--all-tenants',
action='store_true',
- default=False,
- help='Additional fields are listed in output')
+ default=bool(int(os.environ.get("ALL_TENANTS", 0))),
+ help='display information from all tenants (admin only)',
+ )
return parser
- def run(self, parsed_args):
- self.log.info('v2.List_Server.run(%s)' % parsed_args)
-
-
-class Show_Server(command.OpenStackCommand):
+ def get_data(self, parsed_args):
+ self.log.debug('v2.List_Server.run(%s)' % parsed_args)
+ nova_client = self.app.client_manager.compute
+ search_opts = {
+ 'all_tenants': parsed_args.all_tenants,
+ 'reservation_id': parsed_args.reservation_id,
+ 'ip': parsed_args.ip,
+ 'ip6': parsed_args.ip6,
+ 'name': parsed_args.name,
+ 'image': parsed_args.image,
+ 'flavor': parsed_args.flavor,
+ 'status': parsed_args.status,
+ 'host': parsed_args.host,
+ 'instance_name': parsed_args.instance_name,
+ }
+ self.log.debug('search options: %s', search_opts)
+ # FIXME(dhellmann): Consider adding other columns
+ columns = ('ID', 'Name', 'Status', 'Networks')
+ data = nova_client.servers.list(search_opts=search_opts)
+ return (columns,
+ (get_server_properties(
+ s, columns,
+ formatters={'Networks': _format_servers_list_networks},
+ ) for s in data),
+ )
+
+
+class Show_Server(command.OpenStackCommand, show.ShowOne):
"Show server command."
api = 'compute'
@@ -91,7 +163,32 @@ class Show_Server(command.OpenStackCommand):
help='Name or ID of server to display')
return parser
- def run(self, parsed_args):
- self.log.info('v2.Show_Server.run(%s)' % parsed_args)
- #s = _find_server(cs, args.server)
- #_print_server(cs, s)
+ def get_data(self, parsed_args):
+ self.log.debug('v2.Show_Server.run(%s)' % parsed_args)
+ nova_client = self.app.client_manager.compute
+ server = utils.find_resource(nova_client.servers, parsed_args.server)
+
+ info = {}
+ info.update(server._info)
+
+ # Convert the flavor blob to a name
+ flavor_info = info.get('flavor', {})
+ flavor_id = flavor_info.get('id', '')
+ flavor = utils.find_resource(nova_client.flavors, flavor_id)
+ info['flavor'] = flavor.name
+
+ # Convert the image blob to a name
+ image_info = info.get('image', {})
+ image_id = image_info.get('id', '')
+ image = utils.find_resource(nova_client.images, image_id)
+ info['image'] = image.name
+
+ # Format addresses in a useful way
+ info['addresses'] = _format_servers_list_networks(server)
+
+ # Remove a couple of values that are long and not too useful
+ info.pop('links', None)
+
+ columns = sorted(info.keys())
+ values = [info[c] for c in columns]
+ return (columns, values)
diff --git a/openstackclient/shell.py b/openstackclient/shell.py
index c7e63b80..fb5d0727 100644
--- a/openstackclient/shell.py
+++ b/openstackclient/shell.py
@@ -19,7 +19,6 @@
Command-line interface to the OpenStack APIs
"""
-import argparse
import logging
import os
import sys
@@ -27,9 +26,7 @@ import sys
from cliff.app import App
from cliff.commandmanager import CommandManager
-from keystoneclient.v2_0 import client as ksclient
-
-from openstackclient.common import exceptions as exc
+from openstackclient.common import clientmanager
from openstackclient.common import utils
@@ -144,72 +141,32 @@ class OpenStackShell(App):
'image': self.options.os_image_api_version,
}
+ self.client_manager = clientmanager.ClientManager(
+ token=self.options.os_token,
+ url=self.options.os_url,
+ auth_url=self.options.os_auth_url,
+ tenant_name=self.options.os_tenant_name,
+ tenant_id=self.options.os_tenant_id,
+ username=self.options.os_username,
+ password=self.options.os_password,
+ region_name=self.options.os_region_name,
+ identity_api_version=self.options.os_identity_api_version,
+ compute_api_version=self.options.os_compute_api_version,
+ image_api_version=self.options.os_image_api_version,
+ )
+
self.log.debug("API: Identity=%s Compute=%s Image=%s" % (
self.api_version['identity'],
self.api_version['compute'],
self.api_version['image'])
)
- # do checking of os_username, etc here
- if (self.options.os_token and self.options.os_url):
- # do token auth
- self.endpoint = self.options.os_url
- self.token = self.options.os_token
- else:
- if not self.options.os_username:
- raise exc.CommandError("You must provide a username via"
- " either --os-username or env[OS_USERNAME]")
-
- if not self.options.os_password:
- raise exc.CommandError("You must provide a password via"
- " either --os-password or env[OS_PASSWORD]")
-
- if not (self.options.os_tenant_id or self.options.os_tenant_name):
- raise exc.CommandError("You must provide a tenant_id via"
- " either --os-tenant-id or via env[OS_TENANT_ID]")
-
- if not self.options.os_auth_url:
- raise exc.CommandError("You must provide an auth url via"
- " either --os-auth-url or via env[OS_AUTH_URL]")
- kwargs = {
- 'username': self.options.os_username,
- 'password': self.options.os_password,
- 'tenant_id': self.options.os_tenant_id,
- 'tenant_name': self.options.os_tenant_name,
- 'auth_url': self.options.os_auth_url
- }
- self.auth_client = ksclient.Client(
- username=kwargs.get('username'),
- password=kwargs.get('password'),
- tenant_id=kwargs.get('tenant_id'),
- tenant_name=kwargs.get('tenant_name'),
- auth_url=kwargs.get('auth_url'),
- )
- self.token = self.auth_client.auth_token
- # Since we don't know which command is being executed yet, defer
- # selection of a service API until later
- self.endpoint = None
-
- self.log.debug("token: %s" % self.token)
- self.log.debug("endpoint: %s" % self.endpoint)
-
def prepare_to_run_command(self, cmd):
"""Set up auth and API versions"""
self.log.debug('prepare_to_run_command %s', cmd.__class__.__name__)
- self.log.debug("api: %s" % cmd.api)
-
- # See if we are using password flow auth, i.e. we have a
- # service catalog to select endpoints from
- if self.auth_client and self.auth_client.service_catalog:
- self.endpoint = self.auth_client.service_catalog.url_for(
- service_type=cmd.api)
-
- # self.endpoint == None here is an error...
- if not self.endpoint:
- raise RuntimeError('no endpoint found')
-
- # get a client for the desired api here
+ self.log.debug("api: %s" % cmd.api if hasattr(cmd, 'api') else None)
+ return
def clean_up(self, cmd, result, err):
self.log.debug('clean_up %s', cmd.__class__.__name__)