# Copyright 2013 Red Hat, Inc. # All Rights Reserved. # # 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 argparse import datetime import inspect import json from savannaclient.nova import utils from savannaclient.openstack.common.apiclient import exceptions import sys def _print_list_field(field): return lambda obj: ', '.join(getattr(obj, field)) def _print_node_group_field(cluster): return ', '.join(map(lambda x: ': '.join(x), [(node_group['name'], str(node_group['count'])) for node_group in cluster.node_groups])) def _show_node_group_template(template): template._info['node_processes'] = ( ', '.join(template._info['node_processes']) ) utils.print_dict(template._info) def _show_cluster_template(template): template._info['node_groups'] = _print_node_group_field(template) utils.print_dict(template._info) def _show_cluster(cluster): # TODO(mattf): Make this pretty, e.g format node_groups and info urls # Forcing wrap=47 allows for clean display on a terminal of width 80 utils.print_dict(cluster._info, wrap=47) def _show_job_binary_data(data): columns = ('id', 'name') utils.print_list(data, columns) def _show_data_source(source): # TODO(mattf): why are we passing credentials around like this? if 'credentials' in source._info: del source._info['credentials'] utils.print_dict(source._info) def _show_job_binary(binary): # TODO(mattf): why are we passing credentials around like this? if 'extra' in binary._info: del binary._info['extra'] utils.print_dict(binary._info) def _show_job_template(template): # TODO(mattf): Make "mains" property pretty # TODO(mattf): handle/remove "extra" creds utils.print_dict(template._info) def _show_job(job): # TODO(mattf): make display of info pretty, until then # extract the important status information job._info['status'] = job._info['info']['status'] del job._info['info'] utils.print_dict(job._info) def _get_by_id_or_name(manager, id=None, name=None): if not (name or id): raise exceptions.CommandError("either NAME or ID is required") if id: return manager.get(id) ls = manager.find(name=name) if len(ls) == 0: raise exceptions.CommandError("%s '%s' not found" % (manager.resource_class.resource_name, name)) elif len(ls) > 1: raise exceptions.CommandError("%s '%s' not unique, try by ID" % (manager.resource_class.resource_name, name)) return ls[0] # # Plugins # ~~~~~~~ # plugin-list # # plugin-show --name [--version ] # def do_plugin_list(cs, args): """Print a list of available plugins.""" plugins = cs.plugins.list() columns = ('name', 'versions', 'title') utils.print_list(plugins, columns, {'versions': _print_list_field('versions')}) @utils.arg('--name', metavar='', required=True, help='Name of the plugin.') # TODO(mattf) - savannaclient does not support query w/ version #@utils.arg('--version', # metavar='', # help='Optional version') def do_plugin_show(cs, args): """Show details of a plugin.""" plugin = cs.plugins.get(args.name) plugin._info['versions'] = ', '.join(plugin._info['versions']) utils.print_dict(plugin._info) # # Image Registry # ~~~~~~~~~~~~~~ # image-list [--tag ]* # # image-show --name |--id # # image-register --name |--id # [--username ] [--description ] # # image-unregister --name |--id # # image-add-tag --name |--id --tag + # # image-remove-tag --name |--id --tag + # # TODO(mattf): [--tag ]* def do_image_list(cs, args): """Print a list of available images.""" images = cs.images.list() columns = ('name', 'id', 'username', 'tags', 'description') utils.print_list(images, columns, {'tags': _print_list_field('tags')}) @utils.arg('--name', help='Name of the image.') @utils.arg('--id', metavar='', help='ID of the image.') def do_image_show(cs, args): """Show details of an image.""" image = _get_by_id_or_name(cs.images, args.id, args.name) del image._info['metadata'] image._info['tags'] = ', '.join(image._info['tags']) utils.print_dict(image._info) # TODO(mattf): Add --name, must lookup in glance index @utils.arg('--id', metavar='', required=True, help='ID of image, run "glance image-list" to see all IDs.') @utils.arg('--username', default='root', metavar='', help='Username of privileged user in the image.') @utils.arg('--description', default='', metavar='', help='Description of the image.') def do_image_register(cs, args): """Register an image from the Image index.""" # TODO(mattf): image register should not be through update cs.images.update_image(args.id, args.username, args.description) # TODO(mattf): No indication of result, expect image details @utils.arg('--name', help='Name of the image.') @utils.arg('--id', metavar='', help='ID of image to unregister.') def do_image_unregister(cs, args): """Unregister an image.""" cs.images.unregister_image( args.id or _get_by_id_or_name(cs.images, name=args.name).id ) # TODO(mattf): No indication of result, expect result to display @utils.arg('--name', help='Name of the image.') @utils.arg('--id', metavar='', help='ID of image to tag.') # TODO(mattf): Change --tag to --tag+ @utils.arg('--tag', metavar='', required=True, help='Tag to add.') def do_image_add_tag(cs, args): """Add a tag to an image.""" # TODO(mattf): Need proper add_tag API call id = args.id or _get_by_id_or_name(cs.images, name=args.name).id cs.images.update_tags(id, cs.images.get(id).tags + [args.tag, ]) # TODO(mattf): No indication of result, expect image details @utils.arg('--name', help='Name of the image.') @utils.arg('--id', metavar='', help='Image to tag.') # TODO(mattf): Change --tag to --tag+ @utils.arg('--tag', metavar='', required=True, help='Tag to remove.') def do_image_remove_tag(cs, args): """Remove a tag from an image.""" # TODO(mattf): Need proper remove_tag API call id = args.id or _get_by_id_or_name(cs.images, name=args.name).id cs.images.update_tags(id, filter(lambda x: x != args.tag, cs.images.get(id).tags)) # TODO(mattf): No indication of result, expect image details # # Clusters # ~~~~~~~~ # cluster-list # # cluster-show --name |--id [--json] # # cluster-create [--json ] # # TODO(mattf): cluster-scale # # cluster-delete --name |--id # def do_cluster_list(cs, args): """Print a list of available clusters.""" clusters = cs.clusters.list() for cluster in clusters: cluster.node_count = sum(map(lambda g: g['count'], cluster.node_groups)) columns = ('name', 'id', 'status', 'node_count') utils.print_list(clusters, columns) @utils.arg('--name', help='Name of the cluster.') @utils.arg('--id', metavar='', help='ID of the cluster to show.') @utils.arg('--json', action='store_true', default=False, help='Print JSON representation of the cluster.') def do_cluster_show(cs, args): """Show details of a cluster.""" cluster = _get_by_id_or_name(cs.clusters, args.id, args.name) if args.json: print(json.dumps(cluster._info)) else: _show_cluster(cluster) @utils.arg('--json', default=sys.stdin, type=argparse.FileType('r'), help='JSON representation of cluster.') def do_cluster_create(cs, args): """Create a cluster.""" # TODO(mattf): improve template validation, e.g. template w/o name key template = json.loads(args.json.read()) # The neutron_management_network parameter to clusters.create is # called net_id. Therefore, we must translate before invoking # create w/ **template. It may be desirable to simple change # clusters.create in the future. template['net_id'] = template.get('neutron_management_network', None) valid_args = inspect.getargspec(cs.clusters.create).args for name in template.keys(): if name not in valid_args: # TODO(mattf): make this verbose - bug/1271147 del template[name] _show_cluster(cs.clusters.create(**template)) @utils.arg('--name', help='Name of the cluster.') @utils.arg('--id', metavar='', help='ID of the cluster to delete.') def do_cluster_delete(cs, args): """Delete a cluster.""" cs.clusters.delete( args.id or _get_by_id_or_name(cs.clusters, name=args.name).id ) # TODO(mattf): No indication of result # # Node Group Templates # ~~~~~~~~~~~~~~~~~~~~ # node-group-template-list # # node-group-template-show --name