From 61beeb7e203eda719e167d1fd2740f0b3225e11f Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 22 Jul 2013 10:23:51 -0500 Subject: [PATCH 0001/3586] Fix --password in server rebuild Use correct attribute to get password in server rebuild command. Fixes bug 1190722 Change-Id: Ibe2ccb8840a385319781885b8aadca6e1ba4cc43 --- openstackclient/compute/v2/server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index e78144b084..08742f37d8 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -521,8 +521,8 @@ def take_action(self, parsed_args): compute_client.servers, parsed_args.server) _password = None - if parsed_args.rebuild_password is not False: - _password = parsed_args.rebuild_password + if parsed_args.password is not False: + _password = parsed_args.password kwargs = {} server = server.rebuild(image, _password, **kwargs) From 3789cdfebe2d84fd77a4549dbdb08346f5d8e280 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sun, 21 Jul 2013 03:28:27 -0500 Subject: [PATCH 0002/3586] Add server diagnose for compute api Add server diagnose for compute api as per blueprint: nova-client Change-Id: I0a2c13e36e1e13f61ef4ba00ec146634f9644648 --- openstackclient/compute/v2/server.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index a8c86a2e66..5922f759c7 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -17,6 +17,7 @@ import logging import os +import sys import time from cliff import command @@ -599,7 +600,6 @@ def take_action(self, parsed_args): class ShowServer(show.ShowOne): """Show server command""" - api = 'compute' log = logging.getLogger(__name__ + '.ShowServer') def get_parser(self, prog_name): @@ -608,6 +608,11 @@ def get_parser(self, prog_name): 'server', metavar='', help='Name or ID of server to display') + parser.add_argument( + '--diagnostics', + action='store_true', + default=False, + help='Display diagnostics information for a given server') return parser def take_action(self, parsed_args): @@ -616,8 +621,15 @@ def take_action(self, parsed_args): server = utils.find_resource(compute_client.servers, parsed_args.server) - details = _prep_server_detail(compute_client, server) - return zip(*sorted(details.iteritems())) + if parsed_args.diagnostics: + (resp, data) = server.diagnostics() + if not resp.status_code == 200: + sys.stderr.write("Error retrieving diagnostics data") + return ({}, {}) + else: + data = _prep_server_detail(compute_client, server) + + return zip(*sorted(data.iteritems())) class SuspendServer(command.Command): From fcc34652ecad4029998e45c32016af1b03256fc5 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sun, 21 Jul 2013 02:52:25 -0500 Subject: [PATCH 0003/3586] Add usage command for compute api As per the blueprint: nova-client, adding usage command for compute Change-Id: Ib694b0b1ebf56b2a62b6f09c67ffaa6959911605 --- openstackclient/compute/v2/usage.py | 91 +++++++++++++++++++++++++++++ setup.cfg | 2 + 2 files changed, 93 insertions(+) create mode 100644 openstackclient/compute/v2/usage.py diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py new file mode 100644 index 0000000000..0bfbc9b84d --- /dev/null +++ b/openstackclient/compute/v2/usage.py @@ -0,0 +1,91 @@ +# Copyright 2013 OpenStack Foundation. +# +# 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. +# + +"""Usage action implementations""" + +import datetime +import logging + +from cliff import lister + +from openstackclient.common import utils + + +class ListUsage(lister.Lister): + """List resource usage per project. """ + + log = logging.getLogger(__name__ + ".ListUsage") + + def get_parser(self, prog_name): + parser = super(ListUsage, self).get_parser(prog_name) + parser.add_argument( + "--start", + metavar="", + default=None, + help="Usage range start date ex 2012-01-20" + " (default: 4 weeks ago)." + ) + parser.add_argument( + "--end", + metavar="", + default=None, + help="Usage range end date, ex 2012-01-20 (default: tomorrow)" + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + compute_client = self.app.client_manager.compute + columns = ( + "tenant_id", + "total_memory_mb_usage", + "total_vcpus_usage", + "total_local_gb_usage" + ) + column_headers = ( + "Project ID", + "RAM MB-Hours", + "CPU Hours", + "Disk GB-Hours" + ) + + dateformat = "%Y-%m-%d" + now = datetime.datetime.utcnow() + + if parsed_args.start: + start = datetime.datetime.strptime(parsed_args.start, dateformat) + else: + start = now - datetime.timedelta(weeks=4) + + if parsed_args.end: + end = datetime.datetime.strptime(parsed_args.end, dateformat) + else: + end = now + datetime.timedelta(days=1) + + usage_list = compute_client.usage.list(start, end) + + if len(usage_list) > 0: + print("Usage from %s to %s:" % (start.strftime(dateformat), + end.strftime(dateformat))) + + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters={ + 'total_memory_mb_usage': lambda x: float("%.2f" % x), + 'total_vcpus_usage': lambda x: float("%.2f" % x), + 'total_local_gb_usage': lambda x: float("%.2f" % x), + }, + ) for s in usage_list)) diff --git a/setup.cfg b/setup.cfg index b3a6657bf7..60fa17f897 100644 --- a/setup.cfg +++ b/setup.cfg @@ -208,6 +208,8 @@ openstack.compute.v2 = keypair_list = openstackclient.compute.v2.keypair:ListKeypair keypair_show = openstackclient.compute.v2.keypair:ShowKeypair + project_usage_list = openstackclient.compute.v2.usage:ListUsage + server_create = openstackclient.compute.v2.server:CreateServer server_delete = openstackclient.compute.v2.server:DeleteServer server_list = openstackclient.compute.v2.server:ListServer From 3ff6378c23ab103647310d5d64b1e51bf3cdcf04 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 25 Jul 2013 12:17:25 -0500 Subject: [PATCH 0004/3586] Add server commands: (un)lock, (un)rescue, (un)set, add/remove volume * server lock/unlock, rescue/unrescue, set/unset * add/remove volume Blueprint: nova-client Change-Id: I3709ecdb297ab15ad44df09d89af840164271a66 --- openstackclient/compute/v2/server.py | 343 +++++++++++++++++++++++++-- setup.cfg | 8 + 2 files changed, 325 insertions(+), 26 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index ce2390d7d3..009b49799e 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -15,8 +15,10 @@ """Compute v2 Server action implementations""" +import getpass import logging import os +import six import sys import time @@ -122,6 +124,52 @@ def _wait_for_status(poll_fn, obj_id, final_ok_states, poll_period=5, return retval +class AddServerVolume(command.Command): + """Add volume to server""" + + log = logging.getLogger(__name__ + '.AddServerVolume') + + def get_parser(self, prog_name): + parser = super(AddServerVolume, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Server (name or ID)', + ) + parser.add_argument( + 'volume', + metavar='', + help='Volume to add (name or ID)', + ) + parser.add_argument( + '--device', + metavar='', + help='Server internal device name for volume', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + volume_client = self.app.client_manager.volume + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + volume = utils.find_resource( + volume_client.volumes, + parsed_args.volume, + ) + + compute_client.volumes.create_server_volume( + server.id, + volume.id, + parsed_args.device, + ) + + class CreateServer(show.ShowOne): """Create a new server""" @@ -452,8 +500,32 @@ def take_action(self, parsed_args): ) for s in data)) +class LockServer(command.Command): + """Lock server""" + + log = logging.getLogger(__name__ + '.LockServer') + + def get_parser(self, prog_name): + parser = super(LockServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Server (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + utils.find_resource( + compute_client.servers, + parsed_args.server, + ).lock() + + class PauseServer(command.Command): - """Pause server command""" + """Pause server""" log = logging.getLogger(__name__ + '.PauseServer') @@ -462,16 +534,18 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Name or ID of server to pause') + help='Server (name or ID)', + ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) + compute_client = self.app.client_manager.compute - server = utils.find_resource( - compute_client.servers, parsed_args.server) - server.pause() - return + utils.find_resource( + compute_client.servers, + parsed_args.server, + ).pause() class RebootServer(command.Command): @@ -575,8 +649,73 @@ def take_action(self, parsed_args): return zip(*sorted(details.iteritems())) +class RemoveServerVolume(command.Command): + """Remove volume from server""" + + log = logging.getLogger(__name__ + '.RemoveServerVolume') + + def get_parser(self, prog_name): + parser = super(RemoveServerVolume, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Server (name or ID)', + ) + parser.add_argument( + 'volume', + metavar='', + help='Volume to remove (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + volume_client = self.app.client_manager.volume + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + volume = utils.find_resource( + volume_client.volumes, + parsed_args.volume, + ) + + compute_client.volumes.delete_server_volume( + server.id, + volume.id, + ) + + +class RescueServer(show.ShowOne): + """Put server in rescue mode""" + + log = logging.getLogger(__name__ + '.RescueServer') + + def get_parser(self, prog_name): + parser = super(RescueServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Server (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ).rescue() + return zip(*sorted(six.iteritems(server._info))) + + class ResumeServer(command.Command): - """Resume server command""" + """Resume server""" log = logging.getLogger(__name__ + '.ResumeServer') @@ -585,20 +724,81 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Name or ID of server to resume') + help='Server (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + utils.find_resource( + compute_client.servers, + parsed_args.server, + ) .resume() + + +class SetServer(command.Command): + """Set server properties""" + + log = logging.getLogger(__name__ + '.SetServer') + + def get_parser(self, prog_name): + parser = super(SetServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Server (name or ID)', + ) + parser.add_argument( + '--name', + metavar='', + help='New server name', + ) + parser.add_argument( + '--root-password', + action="store_true", + help='Set new root password (interactive only)', + ) + parser.add_argument( + "--property", + metavar="", + action=parseractions.KeyValueAction, + help='Property to add/change for this server ' + '(repeat option to set multiple properties)', + ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) + compute_client = self.app.client_manager.compute server = utils.find_resource( - compute_client.servers, parsed_args.server) - server.resume() - return + compute_client.servers, + parsed_args.server, + ) + + if parsed_args.name: + server.update(name=parsed_args.name) + + if parsed_args.property: + compute_client.servers.set_meta( + server, + parsed_args.property, + ) + + if parsed_args.root_password: + p1 = getpass.getpass('New password: ') + p2 = getpass.getpass('Retype new password: ') + if p1 == p2: + server.change_password(p1) + else: + raise exceptions.CommandError( + "Passwords do not match, password unchanged") class ShowServer(show.ShowOne): - """Show server command""" + """Show server details""" log = logging.getLogger(__name__ + '.ShowServer') @@ -607,12 +807,14 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Name or ID of server to display') + help='Server to show (name or ID)', + ) parser.add_argument( '--diagnostics', action='store_true', default=False, - help='Display diagnostics information for a given server') + help='Display diagnostics information for a given server', + ) return parser def take_action(self, parsed_args): @@ -633,7 +835,7 @@ def take_action(self, parsed_args): class SuspendServer(command.Command): - """Suspend server command""" + """Suspend server""" log = logging.getLogger(__name__ + '.SuspendServer') @@ -642,20 +844,46 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Name or ID of server to suspend') + help='Server (name or ID)', + ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) + compute_client = self.app.client_manager.compute - server = utils.find_resource(compute_client.servers, - parsed_args.server) - server.suspend() - return + utils.find_resource( + compute_client.servers, + parsed_args.server, + ).suspend() + + +class UnlockServer(command.Command): + """Unlock server""" + + log = logging.getLogger(__name__ + '.UnlockServer') + + def get_parser(self, prog_name): + parser = super(UnlockServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Server (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + utils.find_resource( + compute_client.servers, + parsed_args.server, + ).unlock() class UnpauseServer(command.Command): - """Unpause server command""" + """Unpause server""" log = logging.getLogger(__name__ + '.UnpauseServer') @@ -664,13 +892,76 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Name or ID of server to unpause') + help='Server (name or ID)', + ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) + compute_client = self.app.client_manager.compute - server = utils.find_resource(compute_client.servers, - parsed_args.server) - server.unpause() - return + utils.find_resource( + compute_client.servers, + parsed_args.server, + ).unpause() + + +class UnrescueServer(command.Command): + """Restore server from rescue mode""" + + log = logging.getLogger(__name__ + '.UnrescueServer') + + def get_parser(self, prog_name): + parser = super(UnrescueServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Server (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + utils.find_resource( + compute_client.servers, + parsed_args.server, + ).unrescue() + + +class UnsetServer(command.Command): + """Unset server properties""" + + log = logging.getLogger(__name__ + '.UnsetServer') + + def get_parser(self, prog_name): + parser = super(UnsetServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Server (name or ID)', + ) + parser.add_argument( + '--property', + metavar='', + action='append', + default=[], + help='Property key to remove from server ' + '(repeat to set multiple values)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + compute_client = self.app.client_manager.compute + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + + if parsed_args.property: + compute_client.servers.delete_meta( + server, + parsed_args.property, + ) diff --git a/setup.cfg b/setup.cfg index 60fa17f897..2fc34f37ea 100644 --- a/setup.cfg +++ b/setup.cfg @@ -210,16 +210,24 @@ openstack.compute.v2 = project_usage_list = openstackclient.compute.v2.usage:ListUsage + server_add_volume = openstackclient.compute.v2.server:AddServerVolume server_create = openstackclient.compute.v2.server:CreateServer server_delete = openstackclient.compute.v2.server:DeleteServer server_list = openstackclient.compute.v2.server:ListServer + server_lock = openstackclient.compute.v2.server:LockServer server_pause = openstackclient.compute.v2.server:PauseServer server_reboot = openstackclient.compute.v2.server:RebootServer server_rebuild = openstackclient.compute.v2.server:RebuildServer + server_remove_volume = openstackclient.compute.v2.server:RemoveServerVolume + server_rescue = openstackclient.compute.v2.server:RescueServer server_resume = openstackclient.compute.v2.server:ResumeServer + server_set = openstackclient.compute.v2.server:SetServer server_show = openstackclient.compute.v2.server:ShowServer server_suspend = openstackclient.compute.v2.server:SuspendServer + server_unlock = openstackclient.compute.v2.server:UnlockServer server_unpause = openstackclient.compute.v2.server:UnpauseServer + server_unrescue = openstackclient.compute.v2.server:UnrescueServer + server_unset = openstackclient.compute.v2.server:UnsetServer openstack.volume.v1 = snapshot_create = openstackclient.volume.v1.snapshot:CreateSnapshot From 3cc313a60db6d1a5a89c572ed10bca11402912d4 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 26 Jul 2013 15:00:21 -0500 Subject: [PATCH 0005/3586] Add server migrate command Blueprint: nova-client Note: I've tested that the API calls are made correctly but do not have an environment with migration proerly enabled to watch it complete... Change-Id: Ideaf0985d43aa2be22390cf0d2850124c549632d --- openstackclient/compute/v2/server.py | 92 ++++++++++++++++++++++++++++ setup.cfg | 1 + 2 files changed, 93 insertions(+) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 009b49799e..8c31e5d9e7 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -524,6 +524,98 @@ def take_action(self, parsed_args): ).lock() +# FIXME(dtroyer): Here is what I want, how with argparse/cliff? +# server migrate [--wait] \ +# [--live +# [--shared-migration | --block-migration] +# [--disk-overcommit | --no-disk-overcommit]] +# +# +# live_parser = parser.add_argument_group(title='Live migration options') +# then adding the groups doesn't seem to work + +class MigrateServer(command.Command): + """Migrate server to different host""" + + log = logging.getLogger(__name__ + '.MigrateServer') + + def get_parser(self, prog_name): + parser = super(MigrateServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Server to migrate (name or ID)', + ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for resize to complete', + ) + parser.add_argument( + '--live', + metavar='', + help='Target hostname', + ) + migration_group = parser.add_mutually_exclusive_group() + migration_group.add_argument( + '--shared-migration', + dest='shared_migration', + action='store_true', + default=True, + help='Perform a shared live migration (default)', + ) + migration_group.add_argument( + '--block-migration', + dest='shared_migration', + action='store_false', + help='Perform a block live migration', + ) + disk_group = parser.add_mutually_exclusive_group() + disk_group.add_argument( + '--no-disk-overcommit', + dest='disk_overcommit', + action='store_false', + default=False, + help='Do not over-commit disk on the destination host (default)', + ) + disk_group.add_argument( + '--disk-overcommit', + action='store_true', + default=False, + help='Allow disk over-commit on the destination host', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + if parsed_args.live: + server.live_migrate( + parsed_args.live, + parsed_args.shared_migration, + parsed_args.disk_overcommit, + ) + else: + server.migrate() + + if parsed_args.wait: + if utils.wait_for_status( + compute_client.servers.get, + server.id, + #callback=_show_progress, + ): + sys.stdout.write('Complete\n') + else: + sys.stdout.write('\nError migrating server') + raise SystemExit + + class PauseServer(command.Command): """Pause server""" diff --git a/setup.cfg b/setup.cfg index 2fc34f37ea..c2a744f3d6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -215,6 +215,7 @@ openstack.compute.v2 = server_delete = openstackclient.compute.v2.server:DeleteServer server_list = openstackclient.compute.v2.server:ListServer server_lock = openstackclient.compute.v2.server:LockServer + server_migrate = openstackclient.compute.v2.server:MigrateServer server_pause = openstackclient.compute.v2.server:PauseServer server_reboot = openstackclient.compute.v2.server:RebootServer server_rebuild = openstackclient.compute.v2.server:RebuildServer From 65d2a14e3e834ce0c57c879ec7d42715058254bf Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 25 Jul 2013 18:00:33 -0500 Subject: [PATCH 0006/3586] Add server resize command * add server resize * update --wait handling for server create, reboot, rebuild * move _wait_for_status to utils Blueprint: nova-client Rebased after https://review.openstack.org/38162 was committed Change-Id: I7a43b996feecadc7628fcfe20cd5b17333762739 --- openstackclient/common/utils.py | 33 +++++ openstackclient/compute/v2/server.py | 179 +++++++++++++++++---------- setup.cfg | 1 + 3 files changed, 149 insertions(+), 64 deletions(-) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 2f2f5718f5..fd504ea17d 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -17,6 +17,7 @@ import os import sys +import time import uuid from openstackclient.common import exceptions @@ -155,3 +156,35 @@ def get_client_class(api_name, version, version_map): raise exceptions.UnsupportedVersion(msg) return import_class(client_path) + + +def wait_for_status(status_f, + res_id, + status_field='status', + success_status=['active'], + sleep_time=5, + callback=None): + """Wait for status change on a resource during a long-running operation + + :param status_f: a status function that takes a single id argument + :param res_id: the resource id to watch + :param success_status: a list of status strings for successful completion + :param status_field: the status attribute in the returned resource object + :param sleep_time: wait this long (seconds) + :param callback: called per sleep cycle, useful to display progress + :rtype: True on success + """ + while True: + res = status_f(res_id) + status = getattr(res, status_field, '').lower() + if status in success_status: + retval = True + break + elif status == 'error': + retval = False + break + if callback: + progress = getattr(res, 'progress', None) or 0 + callback(progress) + time.sleep(sleep_time) + return retval diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 8c31e5d9e7..8f81dfdb2f 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -20,7 +20,6 @@ import os import six import sys -import time from cliff import command from cliff import lister @@ -90,38 +89,10 @@ def _prep_server_detail(compute_client, server): return info -def _wait_for_status(poll_fn, obj_id, final_ok_states, poll_period=5, - status_field="status"): - """Block while an action is being performed - - :param poll_fn: a function to retrieve the state of the object - :param obj_id: the id of the object - :param final_ok_states: a tuple of the states of the object that end the - wait as success, ex ['active'] - :param poll_period: the wait time between checks of object status - :param status_field: field name containing the status to be checked - """ - log = logging.getLogger(__name__ + '._wait_for_status') - while True: - obj = poll_fn(obj_id) - - status = getattr(obj, status_field) - - if status: - status = status.lower() - - if status in final_ok_states: - log.debug('Wait terminated with success') - retval = True - break - elif status == "error": - log.error('Wait terminated with an error') - retval = False - break - - time.sleep(poll_period) - - return retval +def _show_progress(progress): + if progress: + sys.stdout.write('\rProgress: %s' % progress) + sys.stdout.flush() class AddServerVolume(command.Command): @@ -263,9 +234,9 @@ def get_parser(self, prog_name): help='Maximum number of servers to launch (default=1)') parser.add_argument( '--wait', - dest='wait', action='store_true', - help='Wait for servers to become active') + help='Wait for build to complete', + ) return parser def take_action(self, parsed_args): @@ -359,8 +330,17 @@ def take_action(self, parsed_args): server = compute_client.servers.create(*boot_args, **boot_kwargs) if parsed_args.wait: - _wait_for_status(compute_client.servers.get, server._info['id'], - ['active']) + if utils.wait_for_status( + compute_client.servers.get, + server.id, + callback=_show_progress, + ): + sys.stdout.write('\n') + else: + self.log.error('Error creating server: %s' % + parsed_args.server_name) + sys.stdout.write('\nError creating server') + raise SystemExit details = _prep_server_detail(compute_client, server) return zip(*sorted(details.iteritems())) @@ -641,7 +621,7 @@ def take_action(self, parsed_args): class RebootServer(command.Command): - """Reboot server command""" + """Perform a hard or soft server reboot""" log = logging.getLogger(__name__ + '.RebootServer') @@ -650,7 +630,8 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Name or ID of server to reboot') + help='Server (name or ID)', + ) group = parser.add_mutually_exclusive_group() group.add_argument( '--hard', @@ -658,19 +639,21 @@ def get_parser(self, prog_name): action='store_const', const=servers.REBOOT_HARD, default=servers.REBOOT_SOFT, - help='Perform a hard reboot') + help='Perform a hard reboot', + ) group.add_argument( '--soft', dest='reboot_type', action='store_const', const=servers.REBOOT_SOFT, default=servers.REBOOT_SOFT, - help='Perform a soft reboot') + help='Perform a soft reboot', + ) parser.add_argument( '--wait', - dest='wait', action='store_true', - help='Wait for server to become active to return') + help='Wait for reboot to complete', + ) return parser def take_action(self, parsed_args): @@ -681,14 +664,19 @@ def take_action(self, parsed_args): server.reboot(parsed_args.reboot_type) if parsed_args.wait: - _wait_for_status(compute_client.servers.get, server.id, - ['active']) - - return + if utils.wait_for_status( + compute_client.servers.get, + server.id, + callback=_show_progress, + ): + sys.stdout.write('\nReboot complete\n') + else: + sys.stdout.write('\nError rebooting server\n') + raise SystemExit class RebuildServer(show.ShowOne): - """Rebuild server command""" + """Rebuild server""" log = logging.getLogger(__name__ + '.RebuildServer') @@ -697,22 +685,24 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Server name or ID') + help='Server (name or ID)', + ) parser.add_argument( '--image', metavar='', required=True, - help='Recreate server from this image') + help='Recreate server from this image', + ) parser.add_argument( '--password', metavar='', - default=False, - help="Set the provided password on the rebuild instance") + help="Set the password on the rebuilt instance", + ) parser.add_argument( '--wait', - dest='wait', action='store_true', - help='Wait for server to become active to return') + help='Wait for rebuild to complete', + ) return parser def take_action(self, parsed_args): @@ -725,17 +715,17 @@ def take_action(self, parsed_args): server = utils.find_resource( compute_client.servers, parsed_args.server) - _password = None - if parsed_args.password is not False: - _password = parsed_args.password - - kwargs = {} - server = server.rebuild(image, _password, **kwargs) - - # TODO(dtroyer): force silent=True if output filter != table + server = server.rebuild(image, parsed_args.password) if parsed_args.wait: - _wait_for_status(compute_client.servers.get, server._info['id'], - ['active']) + if utils.wait_for_status( + compute_client.servers.get, + server.id, + callback=_show_progress, + ): + sys.stdout.write('\nComplete\n') + else: + sys.stdout.write('\nError rebuilding server') + raise SystemExit details = _prep_server_detail(compute_client, server) return zip(*sorted(details.iteritems())) @@ -806,6 +796,67 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(server._info))) +class ResizeServer(command.Command): + """Convert server to a new flavor""" + + log = logging.getLogger(__name__ + '.ResizeServer') + + def get_parser(self, prog_name): + parser = super(ResizeServer, self).get_parser(prog_name) + phase_group = parser.add_mutually_exclusive_group() + phase_group.add_argument( + '--flavor', + metavar='', + help='Resize server to this flavor', + ) + phase_group.add_argument( + '--verify', + action="store_true", + help='Verify previous server resize', + ) + phase_group.add_argument( + '--revert', + action="store_true", + help='Restore server before resize', + ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for resize to complete', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + if parsed_args.flavor: + flavor = utils.find_resource( + compute_client.flavors, + parsed_args.flavor, + ) + server.resize(flavor) + if parsed_args.wait: + if utils.wait_for_status( + compute_client.servers.get, + server.id, + success_status=['active', 'verify_resize'], + callback=_show_progress, + ): + sys.stdout.write('Complete\n') + else: + sys.stdout.write('\nError resizing server') + raise SystemExit + elif parsed_args.verify: + server.confirm_resize() + elif parsed_args.revert: + server.revert_resize() + + class ResumeServer(command.Command): """Resume server""" diff --git a/setup.cfg b/setup.cfg index c2a744f3d6..7fd06a2ee6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -221,6 +221,7 @@ openstack.compute.v2 = server_rebuild = openstackclient.compute.v2.server:RebuildServer server_remove_volume = openstackclient.compute.v2.server:RemoveServerVolume server_rescue = openstackclient.compute.v2.server:RescueServer + server_resize = openstackclient.compute.v2.server:ResizeServer server_resume = openstackclient.compute.v2.server:ResumeServer server_set = openstackclient.compute.v2.server:SetServer server_show = openstackclient.compute.v2.server:ShowServer From c94e262df8d2d37e6c2043a3c3d0bc1cb78348a5 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 12 Jul 2013 15:49:03 -0500 Subject: [PATCH 0007/3586] Add security group commands * Add security group: create, delete, list, set, show * Add server: add secgroup, remove secgroup * Add security group rule: create, delete, list * Add Oslo's strutils and gettextutils * Adds parseractions.RangeAction() to handle option arguments of either a single number or a range of numbers: '--port 25' or '--port 1024:65535' Blueprint: nova-client Change-Id: Iad2de1b273ba29197709fc4c6a1036b4ae99725f --- openstack-common.conf | 1 + openstackclient/common/parseractions.py | 24 ++ openstackclient/common/utils.py | 5 +- openstackclient/compute/v2/security_group.py | 394 ++++++++++++++++++ openstackclient/compute/v2/server.py | 73 ++++ openstackclient/identity/client.py | 13 +- .../openstack/common/gettextutils.py | 259 ++++++++++++ openstackclient/openstack/common/strutils.py | 218 ++++++++++ setup.cfg | 11 + 9 files changed, 996 insertions(+), 2 deletions(-) create mode 100644 openstackclient/compute/v2/security_group.py create mode 100644 openstackclient/openstack/common/gettextutils.py create mode 100644 openstackclient/openstack/common/strutils.py diff --git a/openstack-common.conf b/openstack-common.conf index 867e204af7..5e55d5867c 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -5,6 +5,7 @@ module=cfg module=iniparser module=install_venv_common module=openstackkeyring +module=strutils # The base module to hold the copy of openstack.common base=openstackclient diff --git a/openstackclient/common/parseractions.py b/openstackclient/common/parseractions.py index f111c26427..644472d88e 100644 --- a/openstackclient/common/parseractions.py +++ b/openstackclient/common/parseractions.py @@ -32,3 +32,27 @@ def __call__(self, parser, namespace, values, option_string=None): getattr(namespace, self.dest, {}).update([values.split('=', 1)]) else: getattr(namespace, self.dest, {}).pop(values, None) + + +class RangeAction(argparse.Action): + """A custom action to parse a single value or a range of values.""" + def __call__(self, parser, namespace, values, option_string=None): + range = values.split(':') + if len(range) == 0: + # Nothing passed, return a zero default + setattr(namespace, self.dest, (0, 0)) + elif len(range) == 1: + # Only a single value is present + setattr(namespace, self.dest, (int(range[0]), int(range[0]))) + elif len(range) == 2: + # Range of two values + if int(range[0]) <= int(range[1]): + setattr(namespace, self.dest, (int(range[0]), int(range[1]))) + else: + msg = "Invalid range, %s is not less than %s" % \ + (range[0], range[1]) + raise argparse.ArgumentError(self, msg) + else: + # Too many values + msg = "Invalid range, too many values" + raise argparse.ArgumentError(self, msg) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index fd504ea17d..4d2afd15b3 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -16,11 +16,13 @@ """Common client utilities""" import os +import six import sys import time import uuid from openstackclient.common import exceptions +from openstackclient.openstack.common import strutils def find_resource(manager, name_or_id): @@ -84,7 +86,8 @@ def format_dict(data): output = "" for s in data: - output = output + s + "='" + data[s] + "', " + output = output + s + "='" + \ + strutils.safe_encode(six.text_type(data[s])) + "', " return output[:-2] diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py new file mode 100644 index 0000000000..a1dc786d71 --- /dev/null +++ b/openstackclient/compute/v2/security_group.py @@ -0,0 +1,394 @@ +# Copyright 2012 OpenStack Foundation +# Copyright 2013 Nebula Inc +# +# 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. +# + +"""Compute v2 Security Group action implementations""" + +import logging +import six + +from cliff import command +from cliff import lister +from cliff import show + +from novaclient.v1_1 import security_group_rules +from openstackclient.common import parseractions +from openstackclient.common import utils + + +def _xform_security_group_rule(sgroup): + info = {} + info.update(sgroup) + info.update( + {'port_range': "%u:%u" % ( + info.pop('from_port'), + info.pop('to_port'), + )} + ) + info['ip_range'] = info['ip_range']['cidr'] + if info['ip_protocol'] == 'icmp': + info['port_range'] = '' + return info + + +class CreateSecurityGroup(show.ShowOne): + """Create a new security group""" + + log = logging.getLogger(__name__ + ".CreateSecurityGroup") + + def get_parser(self, prog_name): + parser = super(CreateSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + "name", + metavar="", + help="New security group name", + ) + parser.add_argument( + "--description", + metavar="", + help="Security group description", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + + data = compute_client.security_groups.create( + parsed_args.name, + parsed_args.description, + ) + + info = {} + info.update(data._info) + return zip(*sorted(six.iteritems(info))) + + +class DeleteSecurityGroup(command.Command): + """Delete a security group""" + + log = logging.getLogger(__name__ + '.DeleteSecurityGroup') + + def get_parser(self, prog_name): + parser = super(DeleteSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help='Name or ID of security group to delete', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + data = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + compute_client.security_groups.delete(data.id) + return + + +class ListSecurityGroup(lister.Lister): + """List all security groups""" + + log = logging.getLogger(__name__ + ".ListSecurityGroup") + + def get_parser(self, prog_name): + parser = super(ListSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help='Display information from all projects (admin only)', + ) + return parser + + def take_action(self, parsed_args): + + def _get_project(project_id): + try: + return getattr(project_hash[project_id], 'name', project_id) + except KeyError: + return project_id + + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + columns = ( + "ID", + "Name", + "Description", + ) + column_headers = columns + if parsed_args.all_projects: + # TODO(dtroyer): Translate Project_ID to Project (name) + columns = columns + ('Tenant ID',) + column_headers = column_headers + ('Project',) + search = {'all_tenants': parsed_args.all_projects} + data = compute_client.security_groups.list(search_opts=search) + + projects = self.app.client_manager.identity.projects.list() + project_hash = {} + for project in projects: + project_hash[project.id] = project + + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters={'Tenant ID': _get_project}, + ) for s in data)) + + +class SetSecurityGroup(show.ShowOne): + """Set security group properties""" + + log = logging.getLogger(__name__ + '.SetSecurityGroup') + + def get_parser(self, prog_name): + parser = super(SetSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help='Name or ID of security group to change', + ) + parser.add_argument( + '--name', + metavar='', + help='New security group name', + ) + parser.add_argument( + "--description", + metavar="", + help="New security group name", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + data = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + + if parsed_args.name: + data.name = parsed_args.name + if parsed_args.description: + data.description = parsed_args.description + + info = {} + info.update(compute_client.security_groups.update( + data, + data.name, + data.description, + )._info) + + if info: + return zip(*sorted(six.iteritems(info))) + else: + return ({}, {}) + + +class ShowSecurityGroup(show.ShowOne): + """Show a specific security group""" + + log = logging.getLogger(__name__ + '.ShowSecurityGroup') + + def get_parser(self, prog_name): + parser = super(ShowSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help='Name or ID of security group to change', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + info = {} + info.update(utils.find_resource( + compute_client.security_groups, + parsed_args.group, + )._info) + rules = [] + for r in info['rules']: + rules.append(utils.format_dict(_xform_security_group_rule(r))) + + # Format rules into a list of strings + info.update( + {'rules': rules} + ) + # Map 'tenant_id' column to 'project_id' + info.update( + {'project_id': info.pop('tenant_id')} + ) + + return zip(*sorted(six.iteritems(info))) + + +class CreateSecurityGroupRule(show.ShowOne): + """Create a new security group rule""" + + log = logging.getLogger(__name__ + ".CreateSecurityGroupRule") + + def get_parser(self, prog_name): + parser = super(CreateSecurityGroupRule, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help='Create rule in this security group', + ) + parser.add_argument( + "--proto", + metavar="", + default="tcp", + help="IP protocol (icmp, tcp, udp; default: tcp)", + ) + parser.add_argument( + "--src-ip", + metavar="", + default="0.0.0.0/0", + help="Source IP (may use CIDR notation; default: 0.0.0.0/0)", + ) + parser.add_argument( + "--dst-port", + metavar="", + action=parseractions.RangeAction, + help="Destination port, may be a range: 137:139 (default: 0; " + "only required for proto tcp and udp)", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + group = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + from_port, to_port = parsed_args.dst_port + data = compute_client.security_group_rules.create( + group.id, + parsed_args.proto, + from_port, + to_port, + parsed_args.src_ip, + ) + + info = _xform_security_group_rule(data._info) + return zip(*sorted(six.iteritems(info))) + + +class DeleteSecurityGroupRule(command.Command): + """Delete a security group rule""" + + log = logging.getLogger(__name__ + '.DeleteSecurityGroupRule') + + def get_parser(self, prog_name): + parser = super(DeleteSecurityGroupRule, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help='Create rule in this security group', + ) + parser.add_argument( + "--proto", + metavar="", + default="tcp", + help="IP protocol (icmp, tcp, udp; default: tcp)", + ) + parser.add_argument( + "--src-ip", + metavar="", + default="0.0.0.0/0", + help="Source IP (may use CIDR notation; default: 0.0.0.0/0)", + ) + parser.add_argument( + "--dst-port", + metavar="", + action=parseractions.RangeAction, + help="Destination port, may be a range: 137:139 (default: 0; " + "only required for proto tcp and udp)", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + group = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + from_port, to_port = parsed_args.dst_port + # sigh...delete by ID? + compute_client.security_group_rules.delete( + group.id, + parsed_args.proto, + from_port, + to_port, + parsed_args.src_ip, + ) + return + + +class ListSecurityGroupRule(lister.Lister): + """List all security group rules""" + + log = logging.getLogger(__name__ + ".ListSecurityGroupRule") + + def get_parser(self, prog_name): + parser = super(ListSecurityGroupRule, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help='Create rule in this security group', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + group = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + + # Argh, the rules are not Resources... + rules = [] + for rule in group.rules: + rules.append(security_group_rules.SecurityGroupRule( + compute_client.security_group_rules, + _xform_security_group_rule(rule), + )) + + columns = column_headers = ( + "ID", + "IP Protocol", + "IP Range", + "Port Range", + ) + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in rules)) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 8f81dfdb2f..6be97981a9 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -141,6 +141,43 @@ def take_action(self, parsed_args): ) +class AddServerSecurityGroup(command.Command): + """Add security group to server""" + + log = logging.getLogger(__name__ + '.AddServerSecurityGroup') + + def get_parser(self, prog_name): + parser = super(AddServerSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Name or ID of server to use', + ) + parser.add_argument( + 'group', + metavar='', + help='Name or ID of security group to add to server', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + security_group = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + + server.add_security_group(security_group) + return + + class CreateServer(show.ShowOne): """Create a new server""" @@ -731,6 +768,42 @@ def take_action(self, parsed_args): return zip(*sorted(details.iteritems())) +class RemoveServerSecurityGroup(command.Command): + """Remove security group from server""" + + log = logging.getLogger(__name__ + '.RemoveServerSecurityGroup') + + def get_parser(self, prog_name): + parser = super(RemoveServerSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Name or ID of server to use', + ) + parser.add_argument( + 'group', + metavar='', + help='Name or ID of security group to remove from server', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + security_group = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + + server.remove_security_group(security_group) + + class RemoveServerVolume(command.Command): """Remove volume from server""" diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index 748d166683..0f8fbb815d 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -15,6 +15,7 @@ import logging +from keystoneclient.v2_0 import client as identity_client_v2_0 from openstackclient.common import utils @@ -22,7 +23,7 @@ API_NAME = 'identity' API_VERSIONS = { - '2.0': 'keystoneclient.v2_0.client.Client', + '2.0': 'openstackclient.identity.client.IdentityClientv2_0', '3': 'keystoneclient.v3.client.Client', } @@ -48,3 +49,13 @@ def make_client(instance): auth_url=instance._auth_url, region_name=instance._region_name) return client + + +class IdentityClientv2_0(identity_client_v2_0.Client): + """Tweak the earlier client class to deal with some changes""" + def __getattr__(self, name): + # Map v3 'projects' back to v2 'tenants' + if name == "projects": + return self.tenants + else: + raise AttributeError, name diff --git a/openstackclient/openstack/common/gettextutils.py b/openstackclient/openstack/common/gettextutils.py new file mode 100644 index 0000000000..2dd5449e6c --- /dev/null +++ b/openstackclient/openstack/common/gettextutils.py @@ -0,0 +1,259 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Red Hat, Inc. +# All Rights Reserved. +# Copyright 2013 IBM Corp. +# +# 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. + +""" +gettext for openstack-common modules. + +Usual usage in an openstack.common module: + + from openstackclient.openstack.common.gettextutils import _ +""" + +import copy +import gettext +import logging.handlers +import os +import re +import UserString + +import six + +_localedir = os.environ.get('openstackclient'.upper() + '_LOCALEDIR') +_t = gettext.translation('openstackclient', localedir=_localedir, fallback=True) + + +def _(msg): + return _t.ugettext(msg) + + +def install(domain): + """Install a _() function using the given translation domain. + + Given a translation domain, install a _() function using gettext's + install() function. + + The main difference from gettext.install() is that we allow + overriding the default localedir (e.g. /usr/share/locale) using + a translation-domain-specific environment variable (e.g. + NOVA_LOCALEDIR). + """ + gettext.install(domain, + localedir=os.environ.get(domain.upper() + '_LOCALEDIR'), + unicode=True) + + +""" +Lazy gettext functionality. + +The following is an attempt to introduce a deferred way +to do translations on messages in OpenStack. We attempt to +override the standard _() function and % (format string) operation +to build Message objects that can later be translated when we have +more information. Also included is an example LogHandler that +translates Messages to an associated locale, effectively allowing +many logs, each with their own locale. +""" + + +def get_lazy_gettext(domain): + """Assemble and return a lazy gettext function for a given domain. + + Factory method for a project/module to get a lazy gettext function + for its own translation domain (i.e. nova, glance, cinder, etc.) + """ + + def _lazy_gettext(msg): + """Create and return a Message object. + + Message encapsulates a string so that we can translate it later when + needed. + """ + return Message(msg, domain) + + return _lazy_gettext + + +class Message(UserString.UserString, object): + """Class used to encapsulate translatable messages.""" + def __init__(self, msg, domain): + # _msg is the gettext msgid and should never change + self._msg = msg + self._left_extra_msg = '' + self._right_extra_msg = '' + self.params = None + self.locale = None + self.domain = domain + + @property + def data(self): + # NOTE(mrodden): this should always resolve to a unicode string + # that best represents the state of the message currently + + localedir = os.environ.get(self.domain.upper() + '_LOCALEDIR') + if self.locale: + lang = gettext.translation(self.domain, + localedir=localedir, + languages=[self.locale], + fallback=True) + else: + # use system locale for translations + lang = gettext.translation(self.domain, + localedir=localedir, + fallback=True) + + full_msg = (self._left_extra_msg + + lang.ugettext(self._msg) + + self._right_extra_msg) + + if self.params is not None: + full_msg = full_msg % self.params + + return six.text_type(full_msg) + + def _save_dictionary_parameter(self, dict_param): + full_msg = self.data + # look for %(blah) fields in string; + # ignore %% and deal with the + # case where % is first character on the line + keys = re.findall('(?:[^%]|^)%\((\w*)\)[a-z]', full_msg) + + # if we don't find any %(blah) blocks but have a %s + if not keys and re.findall('(?:[^%]|^)%[a-z]', full_msg): + # apparently the full dictionary is the parameter + params = copy.deepcopy(dict_param) + else: + params = {} + for key in keys: + try: + params[key] = copy.deepcopy(dict_param[key]) + except TypeError: + # cast uncopyable thing to unicode string + params[key] = unicode(dict_param[key]) + + return params + + def _save_parameters(self, other): + # we check for None later to see if + # we actually have parameters to inject, + # so encapsulate if our parameter is actually None + if other is None: + self.params = (other, ) + elif isinstance(other, dict): + self.params = self._save_dictionary_parameter(other) + else: + # fallback to casting to unicode, + # this will handle the problematic python code-like + # objects that cannot be deep-copied + try: + self.params = copy.deepcopy(other) + except TypeError: + self.params = unicode(other) + + return self + + # overrides to be more string-like + def __unicode__(self): + return self.data + + def __str__(self): + return self.data.encode('utf-8') + + def __getstate__(self): + to_copy = ['_msg', '_right_extra_msg', '_left_extra_msg', + 'domain', 'params', 'locale'] + new_dict = self.__dict__.fromkeys(to_copy) + for attr in to_copy: + new_dict[attr] = copy.deepcopy(self.__dict__[attr]) + + return new_dict + + def __setstate__(self, state): + for (k, v) in state.items(): + setattr(self, k, v) + + # operator overloads + def __add__(self, other): + copied = copy.deepcopy(self) + copied._right_extra_msg += other.__str__() + return copied + + def __radd__(self, other): + copied = copy.deepcopy(self) + copied._left_extra_msg += other.__str__() + return copied + + def __mod__(self, other): + # do a format string to catch and raise + # any possible KeyErrors from missing parameters + self.data % other + copied = copy.deepcopy(self) + return copied._save_parameters(other) + + def __mul__(self, other): + return self.data * other + + def __rmul__(self, other): + return other * self.data + + def __getitem__(self, key): + return self.data[key] + + def __getslice__(self, start, end): + return self.data.__getslice__(start, end) + + def __getattribute__(self, name): + # NOTE(mrodden): handle lossy operations that we can't deal with yet + # These override the UserString implementation, since UserString + # uses our __class__ attribute to try and build a new message + # after running the inner data string through the operation. + # At that point, we have lost the gettext message id and can just + # safely resolve to a string instead. + ops = ['capitalize', 'center', 'decode', 'encode', + 'expandtabs', 'ljust', 'lstrip', 'replace', 'rjust', 'rstrip', + 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] + if name in ops: + return getattr(self.data, name) + else: + return UserString.UserString.__getattribute__(self, name) + + +class LocaleHandler(logging.Handler): + """Handler that can have a locale associated to translate Messages. + + A quick example of how to utilize the Message class above. + LocaleHandler takes a locale and a target logging.Handler object + to forward LogRecord objects to after translating the internal Message. + """ + + def __init__(self, locale, target): + """Initialize a LocaleHandler + + :param locale: locale to use for translating messages + :param target: logging.Handler object to forward + LogRecord objects to after translation + """ + logging.Handler.__init__(self) + self.locale = locale + self.target = target + + def emit(self, record): + if isinstance(record.msg, Message): + # set the locale and resolve to a string + record.msg.locale = self.locale + + self.target.emit(record) diff --git a/openstackclient/openstack/common/strutils.py b/openstackclient/openstack/common/strutils.py new file mode 100644 index 0000000000..e3f26a7876 --- /dev/null +++ b/openstackclient/openstack/common/strutils.py @@ -0,0 +1,218 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack Foundation. +# 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. + +""" +System-level utilities and helper functions. +""" + +import re +import sys +import unicodedata + +import six + +from openstackclient.openstack.common.gettextutils import _ # noqa + + +# Used for looking up extensions of text +# to their 'multiplied' byte amount +BYTE_MULTIPLIERS = { + '': 1, + 't': 1024 ** 4, + 'g': 1024 ** 3, + 'm': 1024 ** 2, + 'k': 1024, +} +BYTE_REGEX = re.compile(r'(^-?\d+)(\D*)') + +TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes') +FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no') + +SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]") +SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+") + + +def int_from_bool_as_string(subject): + """Interpret a string as a boolean and return either 1 or 0. + + Any string value in: + + ('True', 'true', 'On', 'on', '1') + + is interpreted as a boolean True. + + Useful for JSON-decoded stuff and config file parsing + """ + return bool_from_string(subject) and 1 or 0 + + +def bool_from_string(subject, strict=False): + """Interpret a string as a boolean. + + A case-insensitive match is performed such that strings matching 't', + 'true', 'on', 'y', 'yes', or '1' are considered True and, when + `strict=False`, anything else is considered False. + + Useful for JSON-decoded stuff and config file parsing. + + If `strict=True`, unrecognized values, including None, will raise a + ValueError which is useful when parsing values passed in from an API call. + Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'. + """ + if not isinstance(subject, six.string_types): + subject = str(subject) + + lowered = subject.strip().lower() + + if lowered in TRUE_STRINGS: + return True + elif lowered in FALSE_STRINGS: + return False + elif strict: + acceptable = ', '.join( + "'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS)) + msg = _("Unrecognized value '%(val)s', acceptable values are:" + " %(acceptable)s") % {'val': subject, + 'acceptable': acceptable} + raise ValueError(msg) + else: + return False + + +def safe_decode(text, incoming=None, errors='strict'): + """Decodes incoming str using `incoming` if they're not already unicode. + + :param incoming: Text's current encoding + :param errors: Errors handling policy. See here for valid + values http://docs.python.org/2/library/codecs.html + :returns: text or a unicode `incoming` encoded + representation of it. + :raises TypeError: If text is not an isntance of str + """ + if not isinstance(text, six.string_types): + raise TypeError("%s can't be decoded" % type(text)) + + if isinstance(text, six.text_type): + return text + + if not incoming: + incoming = (sys.stdin.encoding or + sys.getdefaultencoding()) + + try: + return text.decode(incoming, errors) + except UnicodeDecodeError: + # Note(flaper87) If we get here, it means that + # sys.stdin.encoding / sys.getdefaultencoding + # didn't return a suitable encoding to decode + # text. This happens mostly when global LANG + # var is not set correctly and there's no + # default encoding. In this case, most likely + # python will use ASCII or ANSI encoders as + # default encodings but they won't be capable + # of decoding non-ASCII characters. + # + # Also, UTF-8 is being used since it's an ASCII + # extension. + return text.decode('utf-8', errors) + + +def safe_encode(text, incoming=None, + encoding='utf-8', errors='strict'): + """Encodes incoming str/unicode using `encoding`. + + If incoming is not specified, text is expected to be encoded with + current python's default encoding. (`sys.getdefaultencoding`) + + :param incoming: Text's current encoding + :param encoding: Expected encoding for text (Default UTF-8) + :param errors: Errors handling policy. See here for valid + values http://docs.python.org/2/library/codecs.html + :returns: text or a bytestring `encoding` encoded + representation of it. + :raises TypeError: If text is not an isntance of str + """ + if not isinstance(text, six.string_types): + raise TypeError("%s can't be encoded" % type(text)) + + if not incoming: + incoming = (sys.stdin.encoding or + sys.getdefaultencoding()) + + if isinstance(text, six.text_type): + return text.encode(encoding, errors) + elif text and encoding != incoming: + # Decode text before encoding it with `encoding` + text = safe_decode(text, incoming, errors) + return text.encode(encoding, errors) + + return text + + +def to_bytes(text, default=0): + """Converts a string into an integer of bytes. + + Looks at the last characters of the text to determine + what conversion is needed to turn the input text into a byte number. + Supports "B, K(B), M(B), G(B), and T(B)". (case insensitive) + + :param text: String input for bytes size conversion. + :param default: Default return value when text is blank. + + """ + match = BYTE_REGEX.search(text) + if match: + magnitude = int(match.group(1)) + mult_key_org = match.group(2) + if not mult_key_org: + return magnitude + elif text: + msg = _('Invalid string format: %s') % text + raise TypeError(msg) + else: + return default + mult_key = mult_key_org.lower().replace('b', '', 1) + multiplier = BYTE_MULTIPLIERS.get(mult_key) + if multiplier is None: + msg = _('Unknown byte multiplier: %s') % mult_key_org + raise TypeError(msg) + return magnitude * multiplier + + +def to_slug(value, incoming=None, errors="strict"): + """Normalize string. + + Convert to lowercase, remove non-word characters, and convert spaces + to hyphens. + + Inspired by Django's `slugify` filter. + + :param value: Text to slugify + :param incoming: Text's current encoding + :param errors: Errors handling policy. See here for valid + values http://docs.python.org/2/library/codecs.html + :returns: slugified unicode representation of `value` + :raises TypeError: If text is not an instance of str + """ + value = safe_decode(value, incoming, errors) + # NOTE(aababilov): no need to use safe_(encode|decode) here: + # encodings are always "ascii", error handling is always "ignore" + # and types are always known (first: unicode; second: str) + value = unicodedata.normalize("NFKD", value).encode( + "ascii", "ignore").decode("ascii") + value = SLUGIFY_STRIP_RE.sub("", value).strip().lower() + return SLUGIFY_HYPHENATE_RE.sub("-", value) diff --git a/setup.cfg b/setup.cfg index 7fd06a2ee6..0fa59c4020 100644 --- a/setup.cfg +++ b/setup.cfg @@ -210,6 +210,16 @@ openstack.compute.v2 = project_usage_list = openstackclient.compute.v2.usage:ListUsage + security_group_create = openstackclient.compute.v2.secgroup:CreateSecurityGroup + security_group_delete = openstackclient.compute.v2.secgroup:DeleteSecurityGroup + security_group_list = openstackclient.compute.v2.secgroup:ListSecurityGroup + security_group_set = openstackclient.compute.v2.secgroup:SetSecurityGroup + security_group_show = openstackclient.compute.v2.secgroup:ShowSecurityGroup + security_group_rule_create = openstackclient.compute.v2.secgroup:CreateSecurityGroupRule + security_group_rule_delete = openstackclient.compute.v2.secgroup:DeleteSecurityGroupRule + security_group_rule_list = openstackclient.compute.v2.secgroup:ListSecurityGroupRule + + server_add_security_group = openstackclient.compute.v2.server:AddServerSecurityGroup server_add_volume = openstackclient.compute.v2.server:AddServerVolume server_create = openstackclient.compute.v2.server:CreateServer server_delete = openstackclient.compute.v2.server:DeleteServer @@ -219,6 +229,7 @@ openstack.compute.v2 = server_pause = openstackclient.compute.v2.server:PauseServer server_reboot = openstackclient.compute.v2.server:RebootServer server_rebuild = openstackclient.compute.v2.server:RebuildServer + server_remove_security_group = openstackclient.compute.v2.server:RemoveServerSecurityGroup server_remove_volume = openstackclient.compute.v2.server:RemoveServerVolume server_rescue = openstackclient.compute.v2.server:RescueServer server_resize = openstackclient.compute.v2.server:ResizeServer From dfb0e3e3c1b5b5563279bebfe222ed4762f79494 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 3 Jul 2013 18:12:58 -0500 Subject: [PATCH 0008/3586] Begin Python 3 compatability * use six.iteritems() * replace basestring with six.string_types * convert print statements to functions (they're all debugging and should be removed eventually anyway) * clean up OpenStack copyright: LLC -> Foundation Change-Id: Icb14212bcb408e63816bfec3922a697bc1a6c946 --- openstackclient/compute/v2/agent.py | 7 ++++--- openstackclient/compute/v2/console.py | 7 +++---- openstackclient/compute/v2/flavor.py | 7 ++++--- openstackclient/compute/v2/floatingip.py | 3 ++- openstackclient/compute/v2/hypervisor.py | 3 ++- openstackclient/compute/v2/keypair.py | 5 +++-- openstackclient/compute/v2/server.py | 8 ++++---- openstackclient/identity/v2_0/ec2creds.py | 8 +++++--- openstackclient/identity/v2_0/endpoint.py | 9 +++++---- openstackclient/identity/v2_0/role.py | 9 +++++---- openstackclient/identity/v2_0/service.py | 9 +++++---- openstackclient/identity/v2_0/tenant.py | 7 ++++--- openstackclient/identity/v2_0/user.py | 7 ++++--- openstackclient/identity/v3/credential.py | 7 ++++--- openstackclient/identity/v3/domain.py | 7 ++++--- openstackclient/identity/v3/endpoint.py | 7 ++++--- openstackclient/identity/v3/group.py | 7 ++++--- openstackclient/identity/v3/oauth.py | 17 +++++++++-------- openstackclient/identity/v3/policy.py | 7 ++++--- openstackclient/identity/v3/project.py | 7 ++++--- openstackclient/identity/v3/role.py | 7 ++++--- openstackclient/identity/v3/service.py | 7 ++++--- openstackclient/identity/v3/user.py | 7 ++++--- openstackclient/volume/v1/backup.py | 7 ++++--- openstackclient/volume/v1/snapshot.py | 5 +++-- openstackclient/volume/v1/type.py | 3 ++- openstackclient/volume/v1/volume.py | 5 +++-- 27 files changed, 107 insertions(+), 82 deletions(-) diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index aac69d8a0b..b79ebbe761 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -1,4 +1,4 @@ -# Copyright 2013 OpenStack, LLC. +# Copyright 2013 OpenStack Foundation # # 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 @@ -16,6 +16,7 @@ """Agent action implementations""" import logging +import six from cliff import command from cliff import lister @@ -70,7 +71,7 @@ def take_action(self, parsed_args): parsed_args.hypervisor ) agent = compute_client.agents.create(*args)._info.copy() - return zip(*sorted(agent.iteritems())) + return zip(*sorted(six.iteritems(agent))) class DeleteAgent(command.Command): @@ -160,4 +161,4 @@ def take_action(self, parsed_args): parsed_args.md5hash ) agent = compute_client.agents.update(*args)._info.copy() - return zip(*sorted(agent.iteritems())) + return zip(*sorted(six.iteritems(agent))) diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index a67b004c05..8f49c5134a 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -13,9 +13,10 @@ # under the License. # -"""Console action implementations""" +"""Compute v2 Console action implementations""" import logging +import six import sys from cliff import command @@ -106,7 +107,6 @@ def take_action(self, parsed_args): parsed_args.server, ) - print "type: %s" % parsed_args.url_type if parsed_args.url_type in ['novnc', 'xvpvnc']: data = server.get_vnc_console(parsed_args.url_type) if parsed_args.url_type in ['spice']: @@ -114,8 +114,7 @@ def take_action(self, parsed_args): if not data: return ({}, {}) - print "data: %s" % data['console'] info = {} info.update(data['console']) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 4d53a4122e..d1d08d8df0 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -1,4 +1,4 @@ -# Copyright 2013 OpenStack, LLC. +# Copyright 2013 OpenStack Foundation # # 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 @@ -16,6 +16,7 @@ """Flavor action implementations""" import logging +import six from cliff import command from cliff import lister @@ -110,7 +111,7 @@ def take_action(self, parsed_args): flavor = compute_client.flavors.create(*args)._info.copy() flavor.pop("links") - return zip(*sorted(flavor.iteritems())) + return zip(*sorted(six.iteritems(flavor))) class DeleteFlavor(command.Command): @@ -182,4 +183,4 @@ def take_action(self, parsed_args): parsed_args.flavor)._info.copy() flavor.pop("links") - return zip(*sorted(flavor.iteritems())) + return zip(*sorted(six.iteritems(flavor))) diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py index 1b07beb3d3..7ed847f5f4 100644 --- a/openstackclient/compute/v2/floatingip.py +++ b/openstackclient/compute/v2/floatingip.py @@ -16,6 +16,7 @@ """Floating IP action implementations""" import logging +import six from cliff import command from cliff import lister @@ -75,7 +76,7 @@ def take_action(self, parsed_args): info = {} info.update(floating_ip._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteFloatingIP(command.Command): diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index ad69d3285a..535062e8da 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -16,6 +16,7 @@ """Hypervisor action implementations""" import logging +import six from cliff import lister from cliff import show @@ -79,4 +80,4 @@ def take_action(self, parsed_args): hypervisor["service_host"] = hypervisor["service"]["host"] del hypervisor["service"] - return zip(*sorted(hypervisor.iteritems())) + return zip(*sorted(six.iteritems(hypervisor))) diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 65f3679b71..d68dae0643 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -17,6 +17,7 @@ import logging import os +import six import sys from cliff import command @@ -71,7 +72,7 @@ def take_action(self, parsed_args): if public_key: info.update(keypair._info) del info['public_key'] - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) else: sys.stdout.write(keypair.private_key) return ({}, {}) @@ -148,7 +149,7 @@ def take_action(self, parsed_args): info.update(keypair._info['keypair']) if not parsed_args.public_key: del info['public_key'] - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) else: # NOTE(dtroyer): a way to get the public key in a similar form # as the private key in the create command diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 8f81dfdb2f..42529af586 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -293,7 +293,7 @@ def take_action(self, parsed_args): # NOTE(vish): multiple copies of the same hint will # result in a list of values if key in hints: - if isinstance(hints[key], basestring): + if isinstance(hints[key], six.string_types): hints[key] = [hints[key]] hints[key] += [value] else: @@ -343,7 +343,7 @@ def take_action(self, parsed_args): raise SystemExit details = _prep_server_detail(compute_client, server) - return zip(*sorted(details.iteritems())) + return zip(*sorted(six.iteritems(details))) class DeleteServer(command.Command): @@ -728,7 +728,7 @@ def take_action(self, parsed_args): raise SystemExit details = _prep_server_detail(compute_client, server) - return zip(*sorted(details.iteritems())) + return zip(*sorted(six.iteritems(details))) class RemoveServerVolume(command.Command): @@ -974,7 +974,7 @@ def take_action(self, parsed_args): else: data = _prep_server_detail(compute_client, server) - return zip(*sorted(data.iteritems())) + return zip(*sorted(six.iteritems(data))) class SuspendServer(command.Command): diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py index 953a3de6ad..cb3a51658f 100644 --- a/openstackclient/identity/v2_0/ec2creds.py +++ b/openstackclient/identity/v2_0/ec2creds.py @@ -1,3 +1,4 @@ +# Copyright 2012 OpenStack Foundation # Copyright 2013 Nebula Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -13,9 +14,10 @@ # under the License. # -"""EC2 Credentials action implementations""" +"""Identity v2 EC2 Credentials action implementations""" import logging +import six from cliff import command from cliff import lister @@ -68,7 +70,7 @@ def take_action(self, parsed_args): info = {} info.update(creds._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteEC2Creds(command.Command): @@ -178,4 +180,4 @@ def take_action(self, parsed_args): info = {} info.update(creds._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index 680465ae47..5a050fa190 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 @@ -16,6 +16,7 @@ """Endpoint action implementations""" import logging +import six from cliff import command from cliff import lister @@ -71,7 +72,7 @@ def take_action(self, parsed_args): info.update(endpoint._info) info['service_name'] = service.name info['service_type'] = service.type - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteEndpoint(command.Command): @@ -183,7 +184,7 @@ def take_action(self, parsed_args): url = identity_client.service_catalog.url_for(**kwargs) info = {'%s.%s' % (parsed_args.service, parsed_args.type): url} - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) else: # The Identity 2.0 API doesn't support retrieving a single # endpoint so we have to do this ourselves @@ -211,4 +212,4 @@ def take_action(self, parsed_args): ep.service_id) info['service_name'] = service.name info['service_type'] = service.type - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 867230b6b2..61a83af575 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 @@ -16,6 +16,7 @@ """Role action implementations""" import logging +import six from cliff import command from cliff import lister @@ -61,7 +62,7 @@ def take_action(self, parsed_args): info = {} info.update(role._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class CreateRole(show.ShowOne): @@ -84,7 +85,7 @@ def take_action(self, parsed_args): info = {} info.update(role._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteRole(command.Command): @@ -229,4 +230,4 @@ def take_action(self, parsed_args): info = {} info.update(role._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 629475df00..2e81805bb2 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -16,6 +16,7 @@ """Service action implementations""" import logging +import six from cliff import command from cliff import lister @@ -58,7 +59,7 @@ def take_action(self, parsed_args): info = {} info.update(service._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteService(command.Command): @@ -136,11 +137,11 @@ def take_action(self, parsed_args): if parsed_args.catalog: endpoints = identity_client.service_catalog.get_endpoints( service_type=parsed_args.service) - for (service, service_endpoints) in endpoints.iteritems(): + for (service, service_endpoints) in six.iteritems(endpoints): if service_endpoints: info = {"type": service} info.update(service_endpoints[0]) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) msg = ("No service catalog with a type, name or ID of '%s' " "exists." % (parsed_args.service)) @@ -166,4 +167,4 @@ def take_action(self, parsed_args): info = {} info.update(service._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v2_0/tenant.py b/openstackclient/identity/v2_0/tenant.py index c9a423c5f1..d535716cac 100644 --- a/openstackclient/identity/v2_0/tenant.py +++ b/openstackclient/identity/v2_0/tenant.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 @@ -16,6 +16,7 @@ """Tenant action implementations""" import logging +import six import sys from cliff import command @@ -64,7 +65,7 @@ def take_action(self, parsed_args): info = {} info.update(tenant._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteTenant(command.Command): @@ -191,4 +192,4 @@ def take_action(self, parsed_args): info = {} info.update(tenant._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 39be81af82..8c8e2622db 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 @@ -16,6 +16,7 @@ """Identity v2.0 User action implementations""" import logging +import six import sys from cliff import command @@ -79,7 +80,7 @@ def take_action(self, parsed_args): info = {} info.update(user._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteUser(command.Command): @@ -219,4 +220,4 @@ def take_action(self, parsed_args): info = {} info.update(user._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index a2fb43a256..b82825f00b 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 @@ -16,6 +16,7 @@ """Identity v3 Credential action implementations""" import logging +import six import sys from cliff import command @@ -72,7 +73,7 @@ def take_action(self, parsed_args): parsed_args.data, project=project) - return zip(*sorted(credential._info.iteritems())) + return zip(*sorted(six.iteritems(credential._info))) class DeleteCredential(command.Command): @@ -191,4 +192,4 @@ def take_action(self, parsed_args): credential = utils.find_resource(identity_client.credentials, parsed_args.credential) - return zip(*sorted(credential._info.iteritems())) + return zip(*sorted(six.iteritems(credential._info))) diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index f6064a589c..1e9a4a2a0b 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 @@ -16,6 +16,7 @@ """Identity v3 Domain action implementations""" import logging +import six import sys from cliff import command @@ -65,7 +66,7 @@ def take_action(self, parsed_args): enabled=parsed_args.enabled, ) - return zip(*sorted(domain._info.iteritems())) + return zip(*sorted(six.iteritems(domain._info))) class DeleteDomain(command.Command): @@ -185,4 +186,4 @@ def take_action(self, parsed_args): domain = utils.find_resource(identity_client.domains, parsed_args.domain) - return zip(*sorted(domain._info.iteritems())) + return zip(*sorted(six.iteritems(domain._info))) diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index c5f3ebadff..43da07aaad 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 @@ -16,6 +16,7 @@ """Identity v3 Endpoint action implementations""" import logging +import six import sys from cliff import command @@ -83,7 +84,7 @@ def take_action(self, parsed_args): info.update(endpoint._info) info['service_name'] = service.name info['service_type'] = service.type - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteEndpoint(command.Command): @@ -239,4 +240,4 @@ def take_action(self, parsed_args): info.update(endpoint._info) info['service_name'] = service.name info['service_type'] = service.type - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index ca0493ebbb..b5d24ef5f8 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 @@ -16,6 +16,7 @@ """Group action implementations""" import logging +import six import sys from cliff import command @@ -138,7 +139,7 @@ def take_action(self, parsed_args): info = {} info.update(group._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteGroup(command.Command): @@ -375,4 +376,4 @@ def take_action(self, parsed_args): info = {} info.update(group._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v3/oauth.py b/openstackclient/identity/v3/oauth.py index bcbbdf7e09..1672cd24f3 100644 --- a/openstackclient/identity/v3/oauth.py +++ b/openstackclient/identity/v3/oauth.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 @@ -16,6 +16,7 @@ """Identity v3 OAuth action implementations""" import logging +import six import sys from cliff import command @@ -65,7 +66,7 @@ def take_action(self, parsed_args): keystone_token = oauth_client.authenticate( parsed_args.consumer_key, parsed_args.consumer_secret, parsed_args.access_key, parsed_args.access_secret) - return zip(*sorted(keystone_token.iteritems())) + return zip(*sorted(six.iteritems(keystone_token))) class AuthorizeRequestToken(show.ShowOne): @@ -97,7 +98,7 @@ def take_action(self, parsed_args): parsed_args.request_key, parsed_args.roles) info = {} info.update(verifier_pin._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class CreateAccessToken(show.ShowOne): @@ -146,7 +147,7 @@ def take_action(self, parsed_args): parsed_args.consumer_key, parsed_args.consumer_secret, parsed_args.request_key, parsed_args.request_secret, parsed_args.verifier) - return zip(*sorted(access_token.iteritems())) + return zip(*sorted(six.iteritems(access_token))) class CreateConsumer(show.ShowOne): @@ -171,7 +172,7 @@ def take_action(self, parsed_args): ) info = {} info.update(consumer._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class CreateRequestToken(show.ShowOne): @@ -207,7 +208,7 @@ def take_action(self, parsed_args): parsed_args.consumer_key, parsed_args.consumer_secret, parsed_args.roles) - return zip(*sorted(request_token.iteritems())) + return zip(*sorted(six.iteritems(request_token))) class DeleteConsumer(command.Command): @@ -366,7 +367,7 @@ def take_action(self, parsed_args): parsed_args.request_id) info = {} info.update(data._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class ShowConsumer(show.ShowOne): @@ -391,4 +392,4 @@ def take_action(self, parsed_args): info = {} info.update(consumer._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index ac14cc464d..cdbb1cf299 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 @@ -16,6 +16,7 @@ """Identity v3 Policy action implementations""" import logging +import six import sys from cliff import command @@ -54,7 +55,7 @@ def take_action(self, parsed_args): blob, type=parsed_args.type ) - return zip(*sorted(policy._info.iteritems())) + return zip(*sorted(six.iteritems(policy._info))) class DeletePolicy(command.Command): @@ -172,7 +173,7 @@ def take_action(self, parsed_args): policy = utils.find_resource(identity_client.policies, parsed_args.policy) - return zip(*sorted(policy._info.iteritems())) + return zip(*sorted(six.iteritems(policy._info))) def _read_blob_file_contents(blob_file): diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 9d1e360bac..05722b54d1 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 @@ -16,6 +16,7 @@ """Project action implementations""" import logging +import six import sys from cliff import command @@ -78,7 +79,7 @@ def take_action(self, parsed_args): info = {} info.update(project._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteProject(command.Command): @@ -213,4 +214,4 @@ def take_action(self, parsed_args): info = {} info.update(project._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 9be3b78410..5403d4cb8c 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 @@ -16,6 +16,7 @@ """Identity v3 Role action implementations""" import logging +import six import sys from cliff import command @@ -123,7 +124,7 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity role = identity_client.roles.create(parsed_args.name) - return zip(*sorted(role._info.iteritems())) + return zip(*sorted(six.iteritems(role._info))) class DeleteRole(command.Command): @@ -296,4 +297,4 @@ def take_action(self, parsed_args): role = utils.find_resource(identity_client.roles, parsed_args.role) - return zip(*sorted(role._info.iteritems())) + return zip(*sorted(six.iteritems(role._info))) diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index 5c82284c52..77efbeadfc 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 @@ -16,6 +16,7 @@ """Identity v3 Service action implementations""" import logging +import six import sys from cliff import command @@ -62,7 +63,7 @@ def take_action(self, parsed_args): parsed_args.type, parsed_args.enabled) - return zip(*sorted(service._info.iteritems())) + return zip(*sorted(six.iteritems(service._info))) class DeleteService(command.Command): @@ -178,4 +179,4 @@ def take_action(self, parsed_args): service = utils.find_resource(identity_client.services, parsed_args.service) - return zip(*sorted(service._info.iteritems())) + return zip(*sorted(six.iteritems(service._info))) diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 8ee535dc33..b90527a3f5 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 @@ -16,6 +16,7 @@ """Identity v3 User action implementations""" import logging +import six import sys from cliff import command @@ -106,7 +107,7 @@ def take_action(self, parsed_args): info = {} info.update(user._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteUser(command.Command): @@ -355,4 +356,4 @@ def take_action(self, parsed_args): info = {} info.update(user._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index 8ef666c1fa..dbf6eb7370 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # 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 @@ -16,6 +16,7 @@ """Volume v1 Backup action implementations""" import logging +import six from cliff import command from cliff import lister @@ -68,7 +69,7 @@ def take_action(self, parsed_args): ) backup._info.pop('links') - return zip(*sorted(backup._info.iteritems())) + return zip(*sorted(six.iteritems(backup._info))) class DeleteBackup(command.Command): @@ -163,4 +164,4 @@ def take_action(self, parsed_args): backup = utils.find_resource(volume_client.backups, parsed_args.backup) backup._info.pop('links') - return zip(*sorted(backup._info.iteritems())) + return zip(*sorted(six.iteritems(backup._info))) diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index 6055a4d8af..d3a56b759e 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -16,6 +16,7 @@ """Volume v1 Snapshot action implementations""" import logging +import six import sys from cliff import command @@ -69,7 +70,7 @@ def take_action(self, parsed_args): parsed_args.description ) - return zip(*sorted(snapshot._info.iteritems())) + return zip(*sorted(six.iteritems(snapshot._info))) class DeleteSnapshot(command.Command): @@ -175,4 +176,4 @@ def take_action(self, parsed_args): snapshot = utils.find_resource(volume_client.volume_snapshots, parsed_args.snapshot) - return zip(*sorted(snapshot._info.iteritems())) + return zip(*sorted(six.iteritems(snapshot._info))) diff --git a/openstackclient/volume/v1/type.py b/openstackclient/volume/v1/type.py index 895f237776..ecf22781b4 100644 --- a/openstackclient/volume/v1/type.py +++ b/openstackclient/volume/v1/type.py @@ -16,6 +16,7 @@ """Volume v1 Type action implementations""" import logging +import six from cliff import command from cliff import lister @@ -62,7 +63,7 @@ def take_action(self, parsed_args): info = {} info.update(volume_type._info) - return zip(*sorted(info.iteritems())) + return zip(*sorted(six.iteritems(info))) class DeleteVolumeType(command.Command): diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index b74fe45249..97f8d31d0a 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -16,6 +16,7 @@ """Volume v1 Volume action implementations""" import logging +import six from cliff import command from cliff import lister @@ -124,7 +125,7 @@ def take_action(self, parsed_args): {'properties': utils.format_dict(volume._info.pop('metadata'))} ) - return zip(*sorted(volume._info.iteritems())) + return zip(*sorted(six.iteritems(volume._info))) class DeleteVolume(command.Command): @@ -322,7 +323,7 @@ def take_action(self, parsed_args): {'properties': utils.format_dict(volume._info.pop('metadata'))} ) - return zip(*sorted(volume._info.iteritems())) + return zip(*sorted(six.iteritems(volume._info))) class UnsetVolume(command.Command): From 978c2e7dec712c1dd355cf9f5eb6a7270fa2c385 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 26 Jul 2013 16:26:50 -0500 Subject: [PATCH 0009/3586] Add server ssh command Change-Id: I9317ad6a47818d5479a046b4be8c5adbbce613ef --- openstackclient/compute/v2/server.py | 159 +++++++++++++++++++++++++++ setup.cfg | 1 + 2 files changed, 160 insertions(+) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 8f81dfdb2f..eb4ccaba23 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -15,6 +15,7 @@ """Compute v2 Server action implementations""" +import argparse import getpass import logging import os @@ -977,6 +978,164 @@ def take_action(self, parsed_args): return zip(*sorted(data.iteritems())) +class SshServer(command.Command): + """Ssh to server""" + + log = logging.getLogger(__name__ + '.SshServer') + + def get_parser(self, prog_name): + parser = super(SshServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help='Server (name or ID)', + ) + parser.add_argument( + '--login', + metavar='', + help='Login name (ssh -l option)', + ) + parser.add_argument( + '-l', + metavar='', + help=argparse.SUPPRESS, + ) + parser.add_argument( + '--port', + metavar='', + type=int, + help='Destination port (ssh -p option)', + ) + parser.add_argument( + '-p', + metavar='', + dest='port', + type=int, + help=argparse.SUPPRESS, + ) + parser.add_argument( + '--identity', + metavar='', + help='Private key file (ssh -i option)', + ) + parser.add_argument( + '-i', + metavar='', + dest='identity', + help=argparse.SUPPRESS, + ) + parser.add_argument( + '--option', + metavar='', + help='Options in ssh_config(5) format (ssh -o option)', + ) + parser.add_argument( + '-o', + metavar='