diff options
Diffstat (limited to 'openstackclient')
135 files changed, 6374 insertions, 1239 deletions
diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py index 63c55370..b2385ef7 100644 --- a/openstackclient/common/availability_zone.py +++ b/openstackclient/common/availability_zone.py @@ -88,7 +88,7 @@ def _xform_network_availability_zone(az): class ListAvailabilityZone(command.Lister): - """List availability zones and their status""" + _description = _("List availability zones and their status") def get_parser(self, prog_name): parser = super(ListAvailabilityZone, self).get_parser(prog_name) diff --git a/openstackclient/common/client_config.py b/openstackclient/common/client_config.py index 30286df8..1ac53f7f 100644 --- a/openstackclient/common/client_config.py +++ b/openstackclient/common/client_config.py @@ -17,6 +17,8 @@ import logging from os_client_config import config from os_client_config import exceptions as occ_exceptions +from oslo_utils import strutils +import six LOG = logging.getLogger(__name__) @@ -149,6 +151,7 @@ class OSC_Config(config.OpenStackConfig): # present, then do not change the behaviour. Otherwise, set the # PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. if ( + auth_type in ("password", "v3password", "v3totp") and not config['auth'].get('project_domain_id') and not config['auth'].get('project_domain_name') ): @@ -180,7 +183,9 @@ class OSC_Config(config.OpenStackConfig): config = self._auth_v2_ignore_v3(config) config = self._auth_default_domain(config) - LOG.debug("auth_config_hook(): %s" % config) + if LOG.isEnabledFor(logging.DEBUG): + LOG.debug("auth_config_hook(): %s", + strutils.mask_password(six.text_type(config))) return config def load_auth_plugin(self, config): diff --git a/openstackclient/common/configuration.py b/openstackclient/common/configuration.py index 016e9191..57825bb0 100644 --- a/openstackclient/common/configuration.py +++ b/openstackclient/common/configuration.py @@ -23,7 +23,7 @@ REDACTED = "<redacted>" class ShowConfiguration(command.ShowOne): - """Display configuration details""" + _description = _("Display configuration details") def get_parser(self, prog_name): parser = super(ShowConfiguration, self).get_parser(prog_name) diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py index de480016..991f3afc 100644 --- a/openstackclient/common/extension.py +++ b/openstackclient/common/extension.py @@ -28,7 +28,7 @@ LOG = logging.getLogger(__name__) class ListExtension(command.Lister): - """List API extensions""" + _description = _("List API extensions") def get_parser(self, prog_name): parser = super(ListExtension, self).get_parser(prog_name) diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py index f7aa82f6..957f1d02 100644 --- a/openstackclient/common/limits.py +++ b/openstackclient/common/limits.py @@ -25,7 +25,7 @@ from openstackclient.identity import common as identity_common class ShowLimits(command.Lister): - """Show compute and block storage limits""" + _description = _("Show compute and block storage limits") def get_parser(self, prog_name): parser = super(ShowLimits, self).get_parser(prog_name) diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py index 7c5fcd55..15719a30 100644 --- a/openstackclient/common/module.py +++ b/openstackclient/common/module.py @@ -25,7 +25,7 @@ from openstackclient.i18n import _ class ListCommand(command.Lister): - """List recognized commands by group""" + _description = _("List recognized commands by group") auth_required = False @@ -53,7 +53,7 @@ class ListCommand(command.Lister): class ListModule(command.ShowOne): - """List module versions""" + _description = _("List module versions") auth_required = False diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 8f099cc9..58368c56 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -80,7 +80,7 @@ NETWORK_QUOTAS = { class SetQuota(command.Command): - """Set quotas for project or class""" + _description = _("Set quotas for project or class") def _build_options_list(self): if self.app.client_manager.is_network_endpoint_enabled(): @@ -186,7 +186,7 @@ class SetQuota(command.Command): class ShowQuota(command.ShowOne): - """Show quotas for project or class""" + _description = _("Show quotas for project or class") def get_parser(self, prog_name): parser = super(ShowQuota, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index 76c1b3b7..151dcc1e 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__) class CreateAgent(command.ShowOne): - """Create compute agent""" + _description = _("Create compute agent") def get_parser(self, prog_name): parser = super(CreateAgent, self).get_parser(prog_name) @@ -81,7 +81,7 @@ class CreateAgent(command.ShowOne): class DeleteAgent(command.Command): - """Delete compute agent(s)""" + _description = _("Delete compute agent(s)") def get_parser(self, prog_name): parser = super(DeleteAgent, self).get_parser(prog_name) @@ -112,7 +112,7 @@ class DeleteAgent(command.Command): class ListAgent(command.Lister): - """List compute agents""" + _description = _("List compute agents") def get_parser(self, prog_name): parser = super(ListAgent, self).get_parser(prog_name) @@ -142,7 +142,7 @@ class ListAgent(command.Lister): class SetAgent(command.Command): - """Set compute agent properties""" + _description = _("Set compute agent properties") def get_parser(self, prog_name): parser = super(SetAgent, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 58d529e9..76ba5cc6 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -31,7 +31,7 @@ LOG = logging.getLogger(__name__) class AddAggregateHost(command.ShowOne): - """Add host to aggregate""" + _description = _("Add host to aggregate") def get_parser(self, prog_name): parser = super(AddAggregateHost, self).get_parser(prog_name) @@ -62,7 +62,7 @@ class AddAggregateHost(command.ShowOne): class CreateAggregate(command.ShowOne): - """Create a new aggregate""" + _description = _("Create a new aggregate") def get_parser(self, prog_name): parser = super(CreateAggregate, self).get_parser(prog_name) @@ -105,7 +105,7 @@ class CreateAggregate(command.ShowOne): class DeleteAggregate(command.Command): - """Delete existing aggregate(s)""" + _description = _("Delete existing aggregate(s)") def get_parser(self, prog_name): parser = super(DeleteAggregate, self).get_parser(prog_name) @@ -139,7 +139,7 @@ class DeleteAggregate(command.Command): class ListAggregate(command.Lister): - """List all aggregates""" + _description = _("List all aggregates") def get_parser(self, prog_name): parser = super(ListAggregate, self).get_parser(prog_name) @@ -188,7 +188,7 @@ class ListAggregate(command.Lister): class RemoveAggregateHost(command.ShowOne): - """Remove host from aggregate""" + _description = _("Remove host from aggregate") def get_parser(self, prog_name): parser = super(RemoveAggregateHost, self).get_parser(prog_name) @@ -222,7 +222,7 @@ class RemoveAggregateHost(command.ShowOne): class SetAggregate(command.Command): - """Set aggregate properties""" + _description = _("Set aggregate properties") def get_parser(self, prog_name): parser = super(SetAggregate, self).get_parser(prog_name) @@ -298,7 +298,7 @@ class SetAggregate(command.Command): class ShowAggregate(command.ShowOne): - """Display aggregate details""" + _description = _("Display aggregate details") def get_parser(self, prog_name): parser = super(ShowAggregate, self).get_parser(prog_name) @@ -334,7 +334,7 @@ class ShowAggregate(command.ShowOne): class UnsetAggregate(command.Command): - """Unset aggregate properties""" + _description = _("Unset aggregate properties") def get_parser(self, prog_name): parser = super(UnsetAggregate, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index 02be99d5..358df501 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -26,7 +26,7 @@ from openstackclient.i18n import _ class ShowConsoleLog(command.Command): - """Show server's console output""" + _description = _("Show server's console output") def get_parser(self, prog_name): parser = super(ShowConsoleLog, self).get_parser(prog_name) @@ -64,7 +64,7 @@ class ShowConsoleLog(command.Command): class ShowConsoleURL(command.ShowOne): - """Show server's remote console URL""" + _description = _("Show server's remote console URL") def get_parser(self, prog_name): parser = super(ShowConsoleURL, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/fixedip.py b/openstackclient/compute/v2/fixedip.py index c14d29fa..0c0b619e 100644 --- a/openstackclient/compute/v2/fixedip.py +++ b/openstackclient/compute/v2/fixedip.py @@ -24,7 +24,7 @@ from openstackclient.i18n import _ class AddFixedIP(command.Command): - """Add fixed IP address to server""" + _description = _("Add fixed IP address to server") # TODO(tangchen): Remove this class and ``ip fixed add`` command # two cycles after Mitaka. @@ -64,7 +64,7 @@ class AddFixedIP(command.Command): class RemoveFixedIP(command.Command): - """Remove fixed IP address from server""" + _description = _("Remove fixed IP address from server") # TODO(tangchen): Remove this class and ``ip fixed remove`` command # two cycles after Mitaka. diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index b3f09ce5..f20d154b 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -49,7 +49,7 @@ def _find_flavor(compute_client, flavor): class CreateFlavor(command.ShowOne): - """Create new flavor""" + _description = _("Create new flavor") def get_parser(self, prog_name): parser = super(CreateFlavor, self).get_parser(prog_name) @@ -186,7 +186,7 @@ class CreateFlavor(command.ShowOne): class DeleteFlavor(command.Command): - """Delete flavor(s)""" + _description = _("Delete flavor(s)") def get_parser(self, prog_name): parser = super(DeleteFlavor, self).get_parser(prog_name) @@ -219,7 +219,7 @@ class DeleteFlavor(command.Command): class ListFlavor(command.Lister): - """List flavors""" + _description = _("List flavors") def get_parser(self, prog_name): parser = super(ListFlavor, self).get_parser(prog_name) @@ -303,7 +303,7 @@ class ListFlavor(command.Lister): class SetFlavor(command.Command): - """Set flavor properties""" + _description = _("Set flavor properties") def get_parser(self, prog_name): parser = super(SetFlavor, self).get_parser(prog_name) @@ -366,7 +366,7 @@ class SetFlavor(command.Command): class ShowFlavor(command.ShowOne): - """Display flavor details""" + _description = _("Display flavor details") def get_parser(self, prog_name): parser = super(ShowFlavor, self).get_parser(prog_name) @@ -409,7 +409,7 @@ class ShowFlavor(command.ShowOne): class UnsetFlavor(command.Command): - """Unset flavor properties""" + _description = _("Unset flavor properties") def get_parser(self, prog_name): parser = super(UnsetFlavor, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py index 8398ea57..69595bed 100644 --- a/openstackclient/compute/v2/floatingip.py +++ b/openstackclient/compute/v2/floatingip.py @@ -24,7 +24,7 @@ from openstackclient.i18n import _ class AddFloatingIP(command.Command): - """Add floating IP address to server""" + _description = _("Add floating IP address to server") # TODO(tangchen): Remove this class and ``ip floating add`` command # two cycles after Mitaka. @@ -61,7 +61,7 @@ class AddFloatingIP(command.Command): class RemoveFloatingIP(command.Command): - """Remove floating IP address from server""" + _description = _("Remove floating IP address from server") # TODO(tangchen): Remove this class and ``ip floating remove`` command # two cycles after Mitaka. diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py index 4785377e..a495b367 100644 --- a/openstackclient/compute/v2/host.py +++ b/openstackclient/compute/v2/host.py @@ -22,7 +22,7 @@ from openstackclient.i18n import _ class ListHost(command.Lister): - """List hosts""" + _description = _("List hosts") def get_parser(self, prog_name): parser = super(ListHost, self).get_parser(prog_name) @@ -48,7 +48,8 @@ class ListHost(command.Lister): class SetHost(command.Command): - """Set host properties""" + _description = _("Set host properties") + def get_parser(self, prog_name): parser = super(SetHost, self).get_parser(prog_name) parser.add_argument( @@ -107,7 +108,7 @@ class SetHost(command.Command): class ShowHost(command.Lister): - """Display host details""" + _description = _("Display host details") def get_parser(self, prog_name): parser = super(ShowHost, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index 0222e899..406aa917 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -26,7 +26,7 @@ from openstackclient.i18n import _ class ListHypervisor(command.Lister): - """List hypervisors""" + _description = _("List hypervisors") def get_parser(self, prog_name): parser = super(ListHypervisor, self).get_parser(prog_name) @@ -35,14 +35,24 @@ class ListHypervisor(command.Lister): metavar="<hostname>", help=_("Filter hypervisors using <hostname> substring") ) + parser.add_argument( + '--long', + action='store_true', + help=_("List additional fields in output") + ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute columns = ( "ID", - "Hypervisor Hostname" + "Hypervisor Hostname", + "Hypervisor Type", + "Host IP", + "State" ) + if parsed_args.long: + columns += ("vCPUs Used", "vCPUs", "Memory MB Used", "Memory MB") if parsed_args.matching: data = compute_client.hypervisors.search(parsed_args.matching) @@ -56,7 +66,7 @@ class ListHypervisor(command.Lister): class ShowHypervisor(command.ShowOne): - """Display hypervisor details""" + _description = _("Display hypervisor details") def get_parser(self, prog_name): parser = super(ShowHypervisor, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/hypervisor_stats.py b/openstackclient/compute/v2/hypervisor_stats.py index c6fd2992..b0413005 100644 --- a/openstackclient/compute/v2/hypervisor_stats.py +++ b/openstackclient/compute/v2/hypervisor_stats.py @@ -17,9 +17,11 @@ from osc_lib.command import command import six +from openstackclient.i18n import _ + class ShowHypervisorStats(command.ShowOne): - """Display hypervisor stats details""" + _description = _("Display hypervisor stats details") def take_action(self, parsed_args): compute_client = self.app.client_manager.compute diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index d30fd429..a63cbfec 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -32,19 +32,20 @@ LOG = logging.getLogger(__name__) class CreateKeypair(command.ShowOne): - """Create new public key""" + _description = _("Create new public or private key for server ssh access") def get_parser(self, prog_name): parser = super(CreateKeypair, self).get_parser(prog_name) parser.add_argument( 'name', metavar='<name>', - help=_("New public key name") + help=_("New public or private key name") ) parser.add_argument( '--public-key', metavar='<file>', - help=_("Filename for public key to add") + help=_("Filename for public key to add. If not used, " + "creates a private key.") ) return parser @@ -82,7 +83,7 @@ class CreateKeypair(command.ShowOne): class DeleteKeypair(command.Command): - """Delete public key(s)""" + _description = _("Delete public or private key(s)") def get_parser(self, prog_name): parser = super(DeleteKeypair, self).get_parser(prog_name) @@ -90,7 +91,7 @@ class DeleteKeypair(command.Command): 'name', metavar='<key>', nargs='+', - help=_("Public key(s) to delete (name only)") + help=_("Name of key(s) to delete (name only)") ) return parser @@ -104,19 +105,19 @@ class DeleteKeypair(command.Command): compute_client.keypairs.delete(data.name) except Exception as e: result += 1 - LOG.error(_("Failed to delete public key with name " + LOG.error(_("Failed to delete key with name " "'%(name)s': %(e)s") % {'name': n, 'e': e}) if result > 0: total = len(parsed_args.name) - msg = (_("%(result)s of %(total)s public keys failed " + msg = (_("%(result)s of %(total)s keys failed " "to delete.") % {'result': result, 'total': total}) raise exceptions.CommandError(msg) class ListKeypair(command.Lister): - """List public key fingerprints""" + _description = _("List key fingerprints") def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -133,20 +134,20 @@ class ListKeypair(command.Lister): class ShowKeypair(command.ShowOne): - """Display public key details""" + _description = _("Display key details") def get_parser(self, prog_name): parser = super(ShowKeypair, self).get_parser(prog_name) parser.add_argument( 'name', metavar='<key>', - help=_("Public key to display (name only)") + help=_("Public or private key to display (name only)") ) parser.add_argument( '--public-key', action='store_true', default=False, - help=_("Show only bare public key (name only)") + help=_("Show only bare public key paired with the generated key") ) return parser diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 1ca31497..48d8b2d0 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -175,7 +175,7 @@ def _show_progress(progress): class AddFixedIP(command.Command): - """Add fixed IP address to server""" + _description = _("Add fixed IP address to server") def get_parser(self, prog_name): parser = super(AddFixedIP, self).get_parser(prog_name) @@ -205,7 +205,7 @@ class AddFixedIP(command.Command): class AddFloatingIP(command.Command): - """Add floating IP address to server""" + _description = _("Add floating IP address to server") def get_parser(self, prog_name): parser = super(AddFloatingIP, self).get_parser(prog_name) @@ -232,7 +232,7 @@ class AddFloatingIP(command.Command): class AddServerSecurityGroup(command.Command): - """Add security group to server""" + _description = _("Add security group to server") def get_parser(self, prog_name): parser = super(AddServerSecurityGroup, self).get_parser(prog_name) @@ -264,7 +264,7 @@ class AddServerSecurityGroup(command.Command): class AddServerVolume(command.Command): - """Add volume to server""" + _description = _("Add volume to server") def get_parser(self, prog_name): parser = super(AddServerVolume, self).get_parser(prog_name) @@ -306,7 +306,7 @@ class AddServerVolume(command.Command): class CreateServer(command.ShowOne): - """Create a new server""" + _description = _("Create a new server") def get_parser(self, prog_name): parser = super(CreateServer, self).get_parser(prog_name) @@ -642,7 +642,7 @@ class CreateServerDump(command.Command): class DeleteServer(command.Command): - """Delete server(s)""" + _description = _("Delete server(s)") def get_parser(self, prog_name): parser = super(DeleteServer, self).get_parser(prog_name) @@ -680,7 +680,7 @@ class DeleteServer(command.Command): class ListServer(command.Lister): - """List servers""" + _description = _("List servers") def get_parser(self, prog_name): parser = super(ListServer, self).get_parser(prog_name) @@ -923,7 +923,8 @@ class ListServer(command.Lister): class LockServer(command.Command): - """Lock server(s). A non-admin user will not be able to execute actions""" + _description = _("Lock server(s). A non-admin user will not be able to " + "execute actions") def get_parser(self, prog_name): parser = super(LockServer, self).get_parser(prog_name) @@ -956,7 +957,7 @@ class LockServer(command.Command): # then adding the groups doesn't seem to work class MigrateServer(command.Command): - """Migrate server to different host""" + _description = _("Migrate server to different host") def get_parser(self, prog_name): parser = super(MigrateServer, self).get_parser(prog_name) @@ -973,15 +974,15 @@ class MigrateServer(command.Command): migration_group = parser.add_mutually_exclusive_group() migration_group.add_argument( '--shared-migration', - dest='shared_migration', - action='store_true', - default=True, + dest='block_migration', + action='store_false', + default=False, help=_('Perform a shared live migration (default)'), ) migration_group.add_argument( '--block-migration', - dest='shared_migration', - action='store_false', + dest='block_migration', + action='store_true', help=_('Perform a block live migration'), ) disk_group = parser.add_mutually_exclusive_group() @@ -1016,9 +1017,9 @@ class MigrateServer(command.Command): ) if parsed_args.live: server.live_migrate( - parsed_args.live, - parsed_args.shared_migration, - parsed_args.disk_overcommit, + host=parsed_args.live, + block_migration=parsed_args.block_migration, + disk_over_commit=parsed_args.disk_overcommit, ) else: server.migrate() @@ -1038,7 +1039,7 @@ class MigrateServer(command.Command): class PauseServer(command.Command): - """Pause server(s)""" + _description = _("Pause server(s)") def get_parser(self, prog_name): parser = super(PauseServer, self).get_parser(prog_name) @@ -1060,7 +1061,7 @@ class PauseServer(command.Command): class RebootServer(command.Command): - """Perform a hard or soft server reboot""" + _description = _("Perform a hard or soft server reboot") def get_parser(self, prog_name): parser = super(RebootServer, self).get_parser(prog_name) @@ -1114,7 +1115,7 @@ class RebootServer(command.Command): class RebuildServer(command.ShowOne): - """Rebuild server""" + _description = _("Rebuild server") def get_parser(self, prog_name): parser = super(RebuildServer, self).get_parser(prog_name) @@ -1170,7 +1171,7 @@ class RebuildServer(command.ShowOne): class RemoveFixedIP(command.Command): - """Remove fixed IP address from server""" + _description = _("Remove fixed IP address from server") def get_parser(self, prog_name): parser = super(RemoveFixedIP, self).get_parser(prog_name) @@ -1197,7 +1198,7 @@ class RemoveFixedIP(command.Command): class RemoveFloatingIP(command.Command): - """Remove floating IP address from server""" + _description = _("Remove floating IP address from server") def get_parser(self, prog_name): parser = super(RemoveFloatingIP, self).get_parser(prog_name) @@ -1225,7 +1226,7 @@ class RemoveFloatingIP(command.Command): class RemoveServerSecurityGroup(command.Command): - """Remove security group from server""" + _description = _("Remove security group from server") def get_parser(self, prog_name): parser = super(RemoveServerSecurityGroup, self).get_parser(prog_name) @@ -1257,7 +1258,7 @@ class RemoveServerSecurityGroup(command.Command): class RemoveServerVolume(command.Command): - """Remove volume from server""" + _description = _("Remove volume from server") def get_parser(self, prog_name): parser = super(RemoveServerVolume, self).get_parser(prog_name) @@ -1293,7 +1294,7 @@ class RemoveServerVolume(command.Command): class RescueServer(command.ShowOne): - """Put server in rescue mode""" + _description = _("Put server in rescue mode") def get_parser(self, prog_name): parser = super(RescueServer, self).get_parser(prog_name) @@ -1315,7 +1316,7 @@ class RescueServer(command.ShowOne): class ResizeServer(command.Command): - """Scale server to a new flavor""" + _description = _("Scale server to a new flavor") def get_parser(self, prog_name): parser = super(ResizeServer, self).get_parser(prog_name) @@ -1380,7 +1381,7 @@ class ResizeServer(command.Command): class RestoreServer(command.Command): - """Restore server(s)""" + _description = _("Restore server(s)") def get_parser(self, prog_name): parser = super(RestoreServer, self).get_parser(prog_name) @@ -1402,7 +1403,7 @@ class RestoreServer(command.Command): class ResumeServer(command.Command): - """Resume server(s)""" + _description = _("Resume server(s)") def get_parser(self, prog_name): parser = super(ResumeServer, self).get_parser(prog_name) @@ -1425,7 +1426,7 @@ class ResumeServer(command.Command): class SetServer(command.Command): - """Set server properties""" + _description = _("Set server properties") def get_parser(self, prog_name): parser = super(SetServer, self).get_parser(prog_name) @@ -1490,7 +1491,7 @@ class SetServer(command.Command): class ShelveServer(command.Command): - """Shelve server(s)""" + _description = _("Shelve server(s)") def get_parser(self, prog_name): parser = super(ShelveServer, self).get_parser(prog_name) @@ -1512,7 +1513,7 @@ class ShelveServer(command.Command): class ShowServer(command.ShowOne): - """Show server details""" + _description = _("Show server details") def get_parser(self, prog_name): parser = super(ShowServer, self).get_parser(prog_name) @@ -1546,7 +1547,7 @@ class ShowServer(command.ShowOne): class SshServer(command.Command): - """SSH to server""" + _description = _("SSH to server") def get_parser(self, prog_name): parser = super(SshServer, self).get_parser(prog_name) @@ -1690,7 +1691,7 @@ class SshServer(command.Command): class StartServer(command.Command): - """Start server(s).""" + _description = _("Start server(s).") def get_parser(self, prog_name): parser = super(StartServer, self).get_parser(prog_name) @@ -1712,7 +1713,7 @@ class StartServer(command.Command): class StopServer(command.Command): - """Stop server(s).""" + _description = _("Stop server(s).") def get_parser(self, prog_name): parser = super(StopServer, self).get_parser(prog_name) @@ -1734,7 +1735,7 @@ class StopServer(command.Command): class SuspendServer(command.Command): - """Suspend server(s)""" + _description = _("Suspend server(s)") def get_parser(self, prog_name): parser = super(SuspendServer, self).get_parser(prog_name) @@ -1757,7 +1758,7 @@ class SuspendServer(command.Command): class UnlockServer(command.Command): - """Unlock server(s)""" + _description = _("Unlock server(s)") def get_parser(self, prog_name): parser = super(UnlockServer, self).get_parser(prog_name) @@ -1780,7 +1781,7 @@ class UnlockServer(command.Command): class UnpauseServer(command.Command): - """Unpause server(s)""" + _description = _("Unpause server(s)") def get_parser(self, prog_name): parser = super(UnpauseServer, self).get_parser(prog_name) @@ -1803,7 +1804,7 @@ class UnpauseServer(command.Command): class UnrescueServer(command.Command): - """Restore server from rescue mode""" + _description = _("Restore server from rescue mode") def get_parser(self, prog_name): parser = super(UnrescueServer, self).get_parser(prog_name) @@ -1824,7 +1825,7 @@ class UnrescueServer(command.Command): class UnsetServer(command.Command): - """Unset server properties""" + _description = _("Unset server properties") def get_parser(self, prog_name): parser = super(UnsetServer, self).get_parser(prog_name) @@ -1858,7 +1859,7 @@ class UnsetServer(command.Command): class UnshelveServer(command.Command): - """Unshelve server(s)""" + _description = _("Unshelve server(s)") def get_parser(self, prog_name): parser = super(UnshelveServer, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/server_backup.py b/openstackclient/compute/v2/server_backup.py index c0e2e5ee..ddcf9101 100644 --- a/openstackclient/compute/v2/server_backup.py +++ b/openstackclient/compute/v2/server_backup.py @@ -33,7 +33,7 @@ def _show_progress(progress): class CreateServerBackup(command.ShowOne): - """Create a server backup image""" + _description = _("Create a server backup image") IMAGE_API_VERSIONS = { "1": "openstackclient.image.v1.image", diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index d51b1ec2..6bcfc6ae 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -43,7 +43,7 @@ def _get_columns(info): class CreateServerGroup(command.ShowOne): - """Create a new server group.""" + _description = _("Create a new server group.") def get_parser(self, prog_name): parser = super(CreateServerGroup, self).get_parser(prog_name) @@ -77,7 +77,7 @@ class CreateServerGroup(command.ShowOne): class DeleteServerGroup(command.Command): - """Delete existing server group(s).""" + _description = _("Delete existing server group(s).") def get_parser(self, prog_name): parser = super(DeleteServerGroup, self).get_parser(prog_name) @@ -112,7 +112,7 @@ class DeleteServerGroup(command.Command): class ListServerGroup(command.Lister): - """List all server groups.""" + _description = _("List all server groups.") def get_parser(self, prog_name): parser = super(ListServerGroup, self).get_parser(prog_name) @@ -161,7 +161,7 @@ class ListServerGroup(command.Lister): class ShowServerGroup(command.ShowOne): - """Display server group details.""" + _description = _("Display server group details.") def get_parser(self, prog_name): parser = super(ShowServerGroup, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py index 285c7fd2..c66e0674 100644 --- a/openstackclient/compute/v2/server_image.py +++ b/openstackclient/compute/v2/server_image.py @@ -37,7 +37,7 @@ def _show_progress(progress): class CreateServerImage(command.ShowOne): - """Create a new server disk image from an existing server""" + _description = _("Create a new server disk image from an existing server") IMAGE_API_VERSIONS = { "1": "openstackclient.image.v1.image", diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 53624f55..9c384f05 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__) class DeleteService(command.Command): - """Delete compute service(s)""" + _description = _("Delete compute service(s)") def get_parser(self, prog_name): parser = super(DeleteService, self).get_parser(prog_name) @@ -61,7 +61,7 @@ class DeleteService(command.Command): class ListService(command.Lister): - """List compute services""" + _description = _("List compute services") def get_parser(self, prog_name): parser = super(ListService, self).get_parser(prog_name) @@ -115,7 +115,7 @@ class ListService(command.Lister): class SetService(command.Command): - """Set compute service properties""" + _description = _("Set compute service properties") def get_parser(self, prog_name): parser = super(SetService, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index 2f35b01b..89601ae3 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -26,7 +26,7 @@ from openstackclient.i18n import _ class ListUsage(command.Lister): - """List resource usage per project""" + _description = _("List resource usage per project") def get_parser(self, prog_name): parser = super(ListUsage, self).get_parser(prog_name) @@ -115,7 +115,7 @@ class ListUsage(command.Lister): class ShowUsage(command.ShowOne): - """Show resource usage for a single project""" + _description = _("Show resource usage for a single project") def get_parser(self, prog_name): parser = super(ShowUsage, self).get_parser(prog_name) diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index 7a15cf3a..993bdd53 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -43,7 +43,7 @@ def _format_endpoints(eps=None): class ListCatalog(command.Lister): - """List services in the service catalog""" + _description = _("List services in the service catalog") def take_action(self, parsed_args): @@ -66,7 +66,7 @@ class ListCatalog(command.Lister): class ShowCatalog(command.ShowOne): - """Display service catalog details""" + _description = _("Display service catalog details") def get_parser(self, prog_name): parser = super(ShowCatalog, self).get_parser(prog_name) diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py index 4873cd55..2572c4f0 100644 --- a/openstackclient/identity/v2_0/ec2creds.py +++ b/openstackclient/identity/v2_0/ec2creds.py @@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__) class CreateEC2Creds(command.ShowOne): - """Create EC2 credentials""" + _description = _("Create EC2 credentials") def get_parser(self, prog_name): parser = super(CreateEC2Creds, self).get_parser(prog_name) @@ -86,7 +86,7 @@ class CreateEC2Creds(command.ShowOne): class DeleteEC2Creds(command.Command): - """Delete EC2 credentials""" + _description = _("Delete EC2 credentials") def get_parser(self, prog_name): parser = super(DeleteEC2Creds, self).get_parser(prog_name) @@ -133,7 +133,7 @@ class DeleteEC2Creds(command.Command): class ListEC2Creds(command.Lister): - """List EC2 credentials""" + _description = _("List EC2 credentials") def get_parser(self, prog_name): parser = super(ListEC2Creds, self).get_parser(prog_name) @@ -168,7 +168,7 @@ class ListEC2Creds(command.Lister): class ShowEC2Creds(command.ShowOne): - """Display EC2 credentials details""" + _description = _("Display EC2 credentials details") def get_parser(self, prog_name): parser = super(ShowEC2Creds, self).get_parser(prog_name) diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index 5a3b3186..7e0751a7 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__) class CreateEndpoint(command.ShowOne): - """Create new endpoint""" + _description = _("Create new endpoint") def get_parser(self, prog_name): parser = super(CreateEndpoint, self).get_parser(prog_name) @@ -80,7 +80,7 @@ class CreateEndpoint(command.ShowOne): class DeleteEndpoint(command.Command): - """Delete endpoint(s)""" + _description = _("Delete endpoint(s)") def get_parser(self, prog_name): parser = super(DeleteEndpoint, self).get_parser(prog_name) @@ -113,7 +113,7 @@ class DeleteEndpoint(command.Command): class ListEndpoint(command.Lister): - """List endpoints""" + _description = _("List endpoints") def get_parser(self, prog_name): parser = super(ListEndpoint, self).get_parser(prog_name) @@ -146,7 +146,7 @@ class ListEndpoint(command.Lister): class ShowEndpoint(command.ShowOne): - """Display endpoint details""" + _description = _("Display endpoint details") def get_parser(self, prog_name): parser = super(ShowEndpoint, self).get_parser(prog_name) diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index fc5c9201..8526d6bd 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__) class CreateProject(command.ShowOne): - """Create new project""" + _description = _("Create new project") def get_parser(self, prog_name): parser = super(CreateProject, self).get_parser(prog_name) @@ -102,7 +102,7 @@ class CreateProject(command.ShowOne): class DeleteProject(command.Command): - """Delete project(s)""" + _description = _("Delete project(s)") def get_parser(self, prog_name): parser = super(DeleteProject, self).get_parser(prog_name) @@ -126,7 +126,7 @@ class DeleteProject(command.Command): class ListProject(command.Lister): - """List projects""" + _description = _("List projects") def get_parser(self, prog_name): parser = super(ListProject, self).get_parser(prog_name) @@ -152,7 +152,7 @@ class ListProject(command.Lister): class SetProject(command.Command): - """Set project properties""" + _description = _("Set project properties") def get_parser(self, prog_name): parser = super(SetProject, self).get_parser(prog_name) @@ -221,7 +221,7 @@ class SetProject(command.Command): class ShowProject(command.ShowOne): - """Display project details""" + _description = _("Display project details") def get_parser(self, prog_name): parser = super(ShowProject, self).get_parser(prog_name) @@ -279,7 +279,7 @@ class ShowProject(command.ShowOne): class UnsetProject(command.Command): - """Unset project properties""" + _description = _("Unset project properties") def get_parser(self, prog_name): parser = super(UnsetProject, self).get_parser(prog_name) diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index b4b67bad..0a28a70a 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__) class AddRole(command.ShowOne): - """Add role to project:user""" + _description = _("Add role to project:user") def get_parser(self, prog_name): parser = super(AddRole, self).get_parser(prog_name) @@ -73,7 +73,7 @@ class AddRole(command.ShowOne): class CreateRole(command.ShowOne): - """Create new role""" + _description = _("Create new role") def get_parser(self, prog_name): parser = super(CreateRole, self).get_parser(prog_name) @@ -109,7 +109,7 @@ class CreateRole(command.ShowOne): class DeleteRole(command.Command): - """Delete role(s)""" + _description = _("Delete role(s)") def get_parser(self, prog_name): parser = super(DeleteRole, self).get_parser(prog_name) @@ -133,7 +133,7 @@ class DeleteRole(command.Command): class ListRole(command.Lister): - """List roles""" + _description = _("List roles") def get_parser(self, prog_name): parser = super(ListRole, self).get_parser(prog_name) @@ -223,7 +223,7 @@ class ListRole(command.Lister): class ListUserRole(command.Lister): - """List user-role assignments""" + _description = _("List user-role assignments") def get_parser(self, prog_name): parser = super(ListUserRole, self).get_parser(prog_name) @@ -293,7 +293,7 @@ class ListUserRole(command.Lister): class RemoveRole(command.Command): - """Remove role from project : user""" + _description = _("Remove role from project : user") def get_parser(self, prog_name): parser = super(RemoveRole, self).get_parser(prog_name) @@ -331,7 +331,7 @@ class RemoveRole(command.Command): class ShowRole(command.ShowOne): - """Display role details""" + _description = _("Display role details") def get_parser(self, prog_name): parser = super(ShowRole, self).get_parser(prog_name) diff --git a/openstackclient/identity/v2_0/role_assignment.py b/openstackclient/identity/v2_0/role_assignment.py index 44f55c3b..8236bbfc 100644 --- a/openstackclient/identity/v2_0/role_assignment.py +++ b/openstackclient/identity/v2_0/role_assignment.py @@ -21,7 +21,7 @@ from openstackclient.i18n import _ # noqa class ListRoleAssignment(command.Lister): - """List role assignments""" + _description = _("List role assignments") def get_parser(self, prog_name): parser = super(ListRoleAssignment, self).get_parser(prog_name) diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index e318643c..f70f0fa9 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -31,7 +31,7 @@ LOG = logging.getLogger(__name__) class CreateService(command.ShowOne): - """Create new service""" + _description = _("Create new service") def get_parser(self, prog_name): parser = super(CreateService, self).get_parser(prog_name) @@ -91,7 +91,7 @@ class CreateService(command.ShowOne): class DeleteService(command.Command): - """Delete service(s)""" + _description = _("Delete service(s)") def get_parser(self, prog_name): parser = super(DeleteService, self).get_parser(prog_name) @@ -125,7 +125,7 @@ class DeleteService(command.Command): class ListService(command.Lister): - """List services""" + _description = _("List services") def get_parser(self, prog_name): parser = super(ListService, self).get_parser(prog_name) @@ -151,7 +151,7 @@ class ListService(command.Lister): class ShowService(command.ShowOne): - """Display service details""" + _description = _("Display service details") def get_parser(self, prog_name): parser = super(ShowService, self).get_parser(prog_name) diff --git a/openstackclient/identity/v2_0/token.py b/openstackclient/identity/v2_0/token.py index 4b7e988b..3b08b475 100644 --- a/openstackclient/identity/v2_0/token.py +++ b/openstackclient/identity/v2_0/token.py @@ -23,7 +23,7 @@ from openstackclient.i18n import _ class IssueToken(command.ShowOne): - """Issue new token""" + _description = _("Issue new token") # scoped token is optional required_scope = False @@ -53,7 +53,7 @@ class IssueToken(command.ShowOne): class RevokeToken(command.Command): - """Revoke existing token""" + _description = _("Revoke existing token") def get_parser(self, prog_name): parser = super(RevokeToken, self).get_parser(prog_name) diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index d2075150..ddd5b981 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__) class CreateUser(command.ShowOne): - """Create new user""" + _description = _("Create new user") def get_parser(self, prog_name): parser = super(CreateUser, self).get_parser(prog_name) @@ -94,6 +94,10 @@ class CreateUser(command.ShowOne): if parsed_args.password_prompt: parsed_args.password = utils.get_password(self.app.stdin) + if not parsed_args.password: + LOG.warning(_("No password was supplied, authentication will fail " + "when a user does not have a password.")) + try: user = identity_client.users.create( parsed_args.name, @@ -126,7 +130,7 @@ class CreateUser(command.ShowOne): class DeleteUser(command.Command): - """Delete user(s)""" + _description = _("Delete user(s)") def get_parser(self, prog_name): parser = super(DeleteUser, self).get_parser(prog_name) @@ -150,7 +154,7 @@ class DeleteUser(command.Command): class ListUser(command.Lister): - """List users""" + _description = _("List users") def get_parser(self, prog_name): parser = super(ListUser, self).get_parser(prog_name) @@ -238,7 +242,7 @@ class ListUser(command.Lister): class SetUser(command.Command): - """Set user properties""" + _description = _("Set user properties") def get_parser(self, prog_name): parser = super(SetUser, self).get_parser(prog_name) @@ -292,6 +296,10 @@ class SetUser(command.Command): if parsed_args.password_prompt: parsed_args.password = utils.get_password(self.app.stdin) + if '' == parsed_args.password: + LOG.warning(_("No password was supplied, authentication will fail " + "when a user does not have a password.")) + user = utils.find_resource( identity_client.users, parsed_args.user, @@ -328,7 +336,7 @@ class SetUser(command.Command): class ShowUser(command.ShowOne): - """Display user details""" + _description = _("Display user details") def get_parser(self, prog_name): parser = super(ShowUser, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py index a62d0a93..28f4fada 100644 --- a/openstackclient/identity/v3/catalog.py +++ b/openstackclient/identity/v3/catalog.py @@ -38,7 +38,7 @@ def _format_endpoints(eps=None): class ListCatalog(command.Lister): - """List services in the service catalog""" + _description = _("List services in the service catalog") def take_action(self, parsed_args): @@ -61,7 +61,7 @@ class ListCatalog(command.Lister): class ShowCatalog(command.ShowOne): - """Display service catalog details""" + _description = _("Display service catalog details") def get_parser(self, prog_name): parser = super(ShowCatalog, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py index b41a37ca..bcb29db4 100644 --- a/openstackclient/identity/v3/consumer.py +++ b/openstackclient/identity/v3/consumer.py @@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__) class CreateConsumer(command.ShowOne): - """Create new consumer""" + _description = _("Create new consumer") def get_parser(self, prog_name): parser = super(CreateConsumer, self).get_parser(prog_name) @@ -50,7 +50,7 @@ class CreateConsumer(command.ShowOne): class DeleteConsumer(command.Command): - """Delete consumer(s)""" + _description = _("Delete consumer(s)") def get_parser(self, prog_name): parser = super(DeleteConsumer, self).get_parser(prog_name) @@ -84,7 +84,7 @@ class DeleteConsumer(command.Command): class ListConsumer(command.Lister): - """List consumers""" + _description = _("List consumers") def take_action(self, parsed_args): columns = ('ID', 'Description') @@ -97,7 +97,7 @@ class ListConsumer(command.Lister): class SetConsumer(command.Command): - """Set consumer properties""" + _description = _("Set consumer properties") def get_parser(self, prog_name): parser = super(SetConsumer, self).get_parser(prog_name) @@ -126,7 +126,7 @@ class SetConsumer(command.Command): class ShowConsumer(command.ShowOne): - """Display consumer details""" + _description = _("Display consumer details") def get_parser(self, prog_name): parser = super(ShowConsumer, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index 0ef94cf4..68287f2a 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__) class CreateCredential(command.ShowOne): - """Create new credential""" + _description = _("Create new credential") def get_parser(self, prog_name): parser = super(CreateCredential, self).get_parser(prog_name) @@ -78,7 +78,7 @@ class CreateCredential(command.ShowOne): class DeleteCredential(command.Command): - """Delete credential(s)""" + _description = _("Delete credential(s)") def get_parser(self, prog_name): parser = super(DeleteCredential, self).get_parser(prog_name) @@ -110,7 +110,7 @@ class DeleteCredential(command.Command): class ListCredential(command.Lister): - """List credentials""" + _description = _("List credentials") def take_action(self, parsed_args): columns = ('ID', 'Type', 'User ID', 'Blob', 'Project ID') @@ -124,7 +124,7 @@ class ListCredential(command.Lister): class SetCredential(command.Command): - """Set credential properties""" + _description = _("Set credential properties") def get_parser(self, prog_name): parser = super(SetCredential, self).get_parser(prog_name) @@ -180,7 +180,7 @@ class SetCredential(command.Command): class ShowCredential(command.ShowOne): - """Display credential details""" + _description = _("Display credential details") def get_parser(self, prog_name): parser = super(ShowCredential, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index 76e47d32..59ab0f07 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -31,7 +31,7 @@ LOG = logging.getLogger(__name__) class CreateDomain(command.ShowOne): - """Create new domain""" + _description = _("Create new domain") def get_parser(self, prog_name): parser = super(CreateDomain, self).get_parser(prog_name) @@ -89,7 +89,7 @@ class CreateDomain(command.ShowOne): class DeleteDomain(command.Command): - """Delete domain(s)""" + _description = _("Delete domain(s)") def get_parser(self, prog_name): parser = super(DeleteDomain, self).get_parser(prog_name) @@ -122,7 +122,7 @@ class DeleteDomain(command.Command): class ListDomain(command.Lister): - """List domains""" + _description = _("List domains") def take_action(self, parsed_args): columns = ('ID', 'Name', 'Enabled', 'Description') @@ -135,7 +135,7 @@ class ListDomain(command.Lister): class SetDomain(command.Command): - """Set domain properties""" + _description = _("Set domain properties") def get_parser(self, prog_name): parser = super(SetDomain, self).get_parser(prog_name) @@ -186,7 +186,7 @@ class SetDomain(command.Command): class ShowDomain(command.ShowOne): - """Display domain details""" + _description = _("Display domain details") def get_parser(self, prog_name): parser = super(ShowDomain, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py index 7ad01719..9854efda 100644 --- a/openstackclient/identity/v3/ec2creds.py +++ b/openstackclient/identity/v3/ec2creds.py @@ -55,7 +55,7 @@ def _determine_ec2_user(parsed_args, client_manager): class CreateEC2Creds(command.ShowOne): - """Create EC2 credentials""" + _description = _("Create EC2 credentials") def get_parser(self, prog_name): parser = super(CreateEC2Creds, self).get_parser(prog_name) @@ -112,7 +112,7 @@ class CreateEC2Creds(command.ShowOne): class DeleteEC2Creds(command.Command): - """Delete EC2 credentials""" + _description = _("Delete EC2 credentials") def get_parser(self, prog_name): parser = super(DeleteEC2Creds, self).get_parser(prog_name) @@ -151,7 +151,7 @@ class DeleteEC2Creds(command.Command): class ListEC2Creds(command.Lister): - """List EC2 credentials""" + _description = _("List EC2 credentials") def get_parser(self, prog_name): parser = super(ListEC2Creds, self).get_parser(prog_name) @@ -179,7 +179,7 @@ class ListEC2Creds(command.Lister): class ShowEC2Creds(command.ShowOne): - """Display EC2 credentials details""" + _description = _("Display EC2 credentials details") def get_parser(self, prog_name): parser = super(ShowEC2Creds, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 73b37a43..39fd49ef 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -37,7 +37,7 @@ def get_service_name(service): class CreateEndpoint(command.ShowOne): - """Create new endpoint""" + _description = _("Create new endpoint") def get_parser(self, prog_name): parser = super(CreateEndpoint, self).get_parser(prog_name) @@ -99,7 +99,7 @@ class CreateEndpoint(command.ShowOne): class DeleteEndpoint(command.Command): - """Delete endpoint(s)""" + _description = _("Delete endpoint(s)") def get_parser(self, prog_name): parser = super(DeleteEndpoint, self).get_parser(prog_name) @@ -133,7 +133,7 @@ class DeleteEndpoint(command.Command): class ListEndpoint(command.Lister): - """List endpoints""" + _description = _("List endpoints") def get_parser(self, prog_name): parser = super(ListEndpoint, self).get_parser(prog_name) @@ -181,7 +181,7 @@ class ListEndpoint(command.Lister): class SetEndpoint(command.Command): - """Set endpoint properties""" + _description = _("Set endpoint properties") def get_parser(self, prog_name): parser = super(SetEndpoint, self).get_parser(prog_name) @@ -252,7 +252,7 @@ class SetEndpoint(command.Command): class ShowEndpoint(command.ShowOne): - """Display endpoint details""" + _description = _("Display endpoint details") def get_parser(self, prog_name): parser = super(ShowEndpoint, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py index 3fde9027..0752e8f6 100644 --- a/openstackclient/identity/v3/federation_protocol.py +++ b/openstackclient/identity/v3/federation_protocol.py @@ -28,7 +28,7 @@ LOG = logging.getLogger(__name__) class CreateProtocol(command.ShowOne): - """Create new federation protocol""" + _description = _("Create new federation protocol") def get_parser(self, prog_name): parser = super(CreateProtocol, self).get_parser(prog_name) @@ -72,7 +72,7 @@ class CreateProtocol(command.ShowOne): class DeleteProtocol(command.Command): - """Delete federation protocol(s)""" + _description = _("Delete federation protocol(s)") def get_parser(self, prog_name): parser = super(DeleteProtocol, self).get_parser(prog_name) @@ -113,7 +113,7 @@ class DeleteProtocol(command.Command): class ListProtocols(command.Lister): - """List federation protocols""" + _description = _("List federation protocols") def get_parser(self, prog_name): parser = super(ListProtocols, self).get_parser(prog_name) @@ -139,7 +139,7 @@ class ListProtocols(command.Lister): class SetProtocol(command.Command): - """Set federation protocol properties""" + _description = _("Set federation protocol properties") def get_parser(self, prog_name): parser = super(SetProtocol, self).get_parser(prog_name) @@ -179,7 +179,7 @@ class SetProtocol(command.Command): class ShowProtocol(command.ShowOne): - """Display federation protocol details""" + _description = _("Display federation protocol details") def get_parser(self, prog_name): parser = super(ShowProtocol, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index f780810a..df684c12 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -31,7 +31,7 @@ LOG = logging.getLogger(__name__) class AddUserToGroup(command.Command): - """Add user to group""" + _description = _("Add user to group") def get_parser(self, prog_name): parser = super(AddUserToGroup, self).get_parser(prog_name) @@ -76,7 +76,7 @@ class AddUserToGroup(command.Command): class CheckUserInGroup(command.Command): - """Check user membership in group""" + _description = _("Check user membership in group") def get_parser(self, prog_name): parser = super(CheckUserInGroup, self).get_parser(prog_name) @@ -121,7 +121,7 @@ class CheckUserInGroup(command.Command): class CreateGroup(command.ShowOne): - """Create new group""" + _description = _("Create new group") def get_parser(self, prog_name): parser = super(CreateGroup, self).get_parser(prog_name) @@ -174,7 +174,7 @@ class CreateGroup(command.ShowOne): class DeleteGroup(command.Command): - """Delete group(s)""" + _description = _("Delete group(s)") def get_parser(self, prog_name): parser = super(DeleteGroup, self).get_parser(prog_name) @@ -202,7 +202,7 @@ class DeleteGroup(command.Command): class ListGroup(command.Lister): - """List groups""" + _description = _("List groups") def get_parser(self, prog_name): parser = super(ListGroup, self).get_parser(prog_name) @@ -262,7 +262,7 @@ class ListGroup(command.Lister): class RemoveUserFromGroup(command.Command): - """Remove user from group""" + _description = _("Remove user from group") def get_parser(self, prog_name): parser = super(RemoveUserFromGroup, self).get_parser(prog_name) @@ -307,7 +307,7 @@ class RemoveUserFromGroup(command.Command): class SetGroup(command.Command): - """Set group properties""" + _description = _("Set group properties") def get_parser(self, prog_name): parser = super(SetGroup, self).get_parser(prog_name) @@ -347,7 +347,7 @@ class SetGroup(command.Command): class ShowGroup(command.ShowOne): - """Display group details""" + _description = _("Display group details") def get_parser(self, prog_name): parser = super(ShowGroup, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index b6b03188..163dcb5f 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -27,7 +27,7 @@ LOG = logging.getLogger(__name__) class CreateIdentityProvider(command.ShowOne): - """Create new identity provider""" + _description = _("Create new identity provider") def get_parser(self, prog_name): parser = super(CreateIdentityProvider, self).get_parser(prog_name) @@ -94,7 +94,7 @@ class CreateIdentityProvider(command.ShowOne): class DeleteIdentityProvider(command.Command): - """Delete identity provider(s)""" + _description = _("Delete identity provider(s)") def get_parser(self, prog_name): parser = super(DeleteIdentityProvider, self).get_parser(prog_name) @@ -126,7 +126,7 @@ class DeleteIdentityProvider(command.Command): class ListIdentityProvider(command.Lister): - """List identity providers""" + _description = _("List identity providers") def take_action(self, parsed_args): columns = ('ID', 'Enabled', 'Description') @@ -140,7 +140,7 @@ class ListIdentityProvider(command.Lister): class SetIdentityProvider(command.Command): - """Set identity provider properties""" + _description = _("Set identity provider properties") def get_parser(self, prog_name): parser = super(SetIdentityProvider, self).get_parser(prog_name) @@ -211,7 +211,7 @@ class SetIdentityProvider(command.Command): class ShowIdentityProvider(command.ShowOne): - """Display identity provider details""" + _description = _("Display identity provider details") def get_parser(self, prog_name): parser = super(ShowIdentityProvider, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py index 09181a0b..dbb1b068 100644 --- a/openstackclient/identity/v3/mapping.py +++ b/openstackclient/identity/v3/mapping.py @@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__) class _RulesReader(object): - """Helper class capable of reading rules from files""" + _description = _("Helper class capable of reading rules from files") def _read_rules(self, path): """Read and parse rules from path @@ -82,7 +82,7 @@ class _RulesReader(object): class CreateMapping(command.ShowOne, _RulesReader): - """Create new mapping""" + _description = _("Create new mapping") def get_parser(self, prog_name): parser = super(CreateMapping, self).get_parser(prog_name) @@ -111,7 +111,7 @@ class CreateMapping(command.ShowOne, _RulesReader): class DeleteMapping(command.Command): - """Delete mapping(s)""" + _description = _("Delete mapping(s)") def get_parser(self, prog_name): parser = super(DeleteMapping, self).get_parser(prog_name) @@ -143,7 +143,7 @@ class DeleteMapping(command.Command): class ListMapping(command.Lister): - """List mappings""" + _description = _("List mappings") def take_action(self, parsed_args): # NOTE(marek-denis): Since rules can be long and tedious I have decided @@ -157,7 +157,7 @@ class ListMapping(command.Lister): class SetMapping(command.Command, _RulesReader): - """Set mapping properties""" + _description = _("Set mapping properties") def get_parser(self, prog_name): parser = super(SetMapping, self).get_parser(prog_name) @@ -187,7 +187,7 @@ class SetMapping(command.Command, _RulesReader): class ShowMapping(command.ShowOne): - """Display mapping details""" + _description = _("Display mapping details") def get_parser(self, prog_name): parser = super(ShowMapping, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index 596eae01..c511652e 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__) class CreatePolicy(command.ShowOne): - """Create new policy""" + _description = _("Create new policy") def get_parser(self, prog_name): parser = super(CreatePolicy, self).get_parser(prog_name) @@ -61,7 +61,7 @@ class CreatePolicy(command.ShowOne): class DeletePolicy(command.Command): - """Delete policy(s)""" + _description = _("Delete policy(s)") def get_parser(self, prog_name): parser = super(DeletePolicy, self).get_parser(prog_name) @@ -93,7 +93,7 @@ class DeletePolicy(command.Command): class ListPolicy(command.Lister): - """List policies""" + _description = _("List policies") def get_parser(self, prog_name): parser = super(ListPolicy, self).get_parser(prog_name) @@ -121,7 +121,7 @@ class ListPolicy(command.Lister): class SetPolicy(command.Command): - """Set policy properties""" + _description = _("Set policy properties") def get_parser(self, prog_name): parser = super(SetPolicy, self).get_parser(prog_name) @@ -159,7 +159,7 @@ class SetPolicy(command.Command): class ShowPolicy(command.ShowOne): - """Display policy details""" + _description = _("Display policy details") def get_parser(self, prog_name): parser = super(ShowPolicy, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 56c4fbc8..a6348659 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -31,7 +31,7 @@ LOG = logging.getLogger(__name__) class CreateProject(command.ShowOne): - """Create new project""" + _description = _("Create new project") def get_parser(self, prog_name): parser = super(CreateProject, self).get_parser(prog_name) @@ -125,7 +125,7 @@ class CreateProject(command.ShowOne): class DeleteProject(command.Command): - """Delete project(s)""" + _description = _("Delete project(s)") def get_parser(self, prog_name): parser = super(DeleteProject, self).get_parser(prog_name) @@ -160,7 +160,7 @@ class DeleteProject(command.Command): class ListProject(command.Lister): - """List projects""" + _description = _("List projects") def get_parser(self, prog_name): parser = super(ListProject, self).get_parser(prog_name) @@ -216,7 +216,7 @@ class ListProject(command.Lister): class SetProject(command.Command): - """Set project properties""" + _description = _("Set project properties") def get_parser(self, prog_name): parser = super(SetProject, self).get_parser(prog_name) @@ -283,7 +283,7 @@ class SetProject(command.Command): class ShowProject(command.ShowOne): - """Display project details""" + _description = _("Display project details") def get_parser(self, prog_name): parser = super(ShowProject, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py index b7c51f93..d3e712e3 100644 --- a/openstackclient/identity/v3/region.py +++ b/openstackclient/identity/v3/region.py @@ -27,7 +27,7 @@ LOG = logging.getLogger(__name__) class CreateRegion(command.ShowOne): - """Create new region""" + _description = _("Create new region") def get_parser(self, prog_name): parser = super(CreateRegion, self).get_parser(prog_name) @@ -66,7 +66,7 @@ class CreateRegion(command.ShowOne): class DeleteRegion(command.Command): - """Delete region(s)""" + _description = _("Delete region(s)") def get_parser(self, prog_name): parser = super(DeleteRegion, self).get_parser(prog_name) @@ -98,7 +98,7 @@ class DeleteRegion(command.Command): class ListRegion(command.Lister): - """List regions""" + _description = _("List regions") def get_parser(self, prog_name): parser = super(ListRegion, self).get_parser(prog_name) @@ -128,7 +128,7 @@ class ListRegion(command.Lister): class SetRegion(command.Command): - """Set region properties""" + _description = _("Set region properties") def get_parser(self, prog_name): parser = super(SetRegion, self).get_parser(prog_name) @@ -162,7 +162,7 @@ class SetRegion(command.Command): class ShowRegion(command.ShowOne): - """Display region details""" + _description = _("Display region details") def get_parser(self, prog_name): parser = super(ShowRegion, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 8b911746..c9d0fbf3 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -109,7 +109,8 @@ def _process_identity_and_resource_options(parsed_args, class AddRole(command.Command): - """Adds a role assignment to a user or group on a domain or project""" + _description = _("Adds a role assignment to a user or group on a domain " + "or project") def get_parser(self, prog_name): parser = super(AddRole, self).get_parser(prog_name) @@ -151,7 +152,7 @@ class AddRole(command.Command): class CreateRole(command.ShowOne): - """Create new role""" + _description = _("Create new role") def get_parser(self, prog_name): parser = super(CreateRole, self).get_parser(prog_name) @@ -198,7 +199,7 @@ class CreateRole(command.ShowOne): class DeleteRole(command.Command): - """Delete role(s)""" + _description = _("Delete role(s)") def get_parser(self, prog_name): parser = super(DeleteRole, self).get_parser(prog_name) @@ -233,7 +234,7 @@ class DeleteRole(command.Command): class ListRole(command.Lister): - """List roles""" + _description = _("List roles") def get_parser(self, prog_name): parser = super(ListRole, self).get_parser(prog_name) @@ -371,7 +372,8 @@ class ListRole(command.Lister): class RemoveRole(command.Command): - """Removes a role assignment from domain/project : user/group""" + _description = _("Removes a role assignment from domain/project : " + "user/group") def get_parser(self, prog_name): parser = super(RemoveRole, self).get_parser(prog_name) @@ -415,7 +417,7 @@ class RemoveRole(command.Command): class SetRole(command.Command): - """Set role properties""" + _description = _("Set role properties") def get_parser(self, prog_name): parser = super(SetRole, self).get_parser(prog_name) @@ -452,7 +454,7 @@ class SetRole(command.Command): class ShowRole(command.ShowOne): - """Display role details""" + _description = _("Display role details") def get_parser(self, prog_name): parser = super(ShowRole, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index d25cc6ce..9da050de 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -11,7 +11,7 @@ # under the License. # -"""Identity v3 Assignment action implementations """ +"""Identity v3 Assignment action implementations""" from osc_lib.command import command from osc_lib import utils @@ -21,7 +21,7 @@ from openstackclient.identity import common class ListRoleAssignment(command.Lister): - """List role assignments""" + _description = _("List role assignments") def get_parser(self, prog_name): parser = super(ListRoleAssignment, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index 97e64dc6..7daf8919 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__) class CreateService(command.ShowOne): - """Create new service""" + _description = _("Create new service") def get_parser(self, prog_name): parser = super(CreateService, self).get_parser(prog_name) @@ -81,7 +81,7 @@ class CreateService(command.ShowOne): class DeleteService(command.Command): - """Delete service(s)""" + _description = _("Delete service(s)") def get_parser(self, prog_name): parser = super(DeleteService, self).get_parser(prog_name) @@ -114,7 +114,7 @@ class DeleteService(command.Command): class ListService(command.Lister): - """List services""" + _description = _("List services") def get_parser(self, prog_name): parser = super(ListService, self).get_parser(prog_name) @@ -140,7 +140,7 @@ class ListService(command.Lister): class SetService(command.Command): - """Set service properties""" + _description = _("Set service properties") def get_parser(self, prog_name): parser = super(SetService, self).get_parser(prog_name) @@ -201,7 +201,7 @@ class SetService(command.Command): class ShowService(command.ShowOne): - """Display service details""" + _description = _("Display service details") def get_parser(self, prog_name): parser = super(ShowService, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index 8548ae1f..459dc00b 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -27,7 +27,7 @@ LOG = logging.getLogger(__name__) class CreateServiceProvider(command.ShowOne): - """Create new service provider""" + _description = _("Create new service provider") def get_parser(self, prog_name): parser = super(CreateServiceProvider, self).get_parser(prog_name) @@ -87,7 +87,7 @@ class CreateServiceProvider(command.ShowOne): class DeleteServiceProvider(command.Command): - """Delete service provider(s)""" + _description = _("Delete service provider(s)") def get_parser(self, prog_name): parser = super(DeleteServiceProvider, self).get_parser(prog_name) @@ -119,7 +119,7 @@ class DeleteServiceProvider(command.Command): class ListServiceProvider(command.Lister): - """List service providers""" + _description = _("List service providers") def take_action(self, parsed_args): service_client = self.app.client_manager.identity @@ -134,7 +134,7 @@ class ListServiceProvider(command.Lister): class SetServiceProvider(command.Command): - """Set service provider properties""" + _description = _("Set service provider properties") def get_parser(self, prog_name): parser = super(SetServiceProvider, self).get_parser(prog_name) @@ -192,7 +192,7 @@ class SetServiceProvider(command.Command): class ShowServiceProvider(command.ShowOne): - """Display service provider details""" + _description = _("Display service provider details") def get_parser(self, prog_name): parser = super(ShowServiceProvider, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index 2cd304e6..7a66f23b 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -25,7 +25,7 @@ from openstackclient.identity import common class AuthorizeRequestToken(command.ShowOne): - """Authorize a request token""" + _description = _("Authorize a request token") def get_parser(self, prog_name): parser = super(AuthorizeRequestToken, self).get_parser(prog_name) @@ -66,7 +66,7 @@ class AuthorizeRequestToken(command.ShowOne): class CreateAccessToken(command.ShowOne): - """Create an access token""" + _description = _("Create an access token") def get_parser(self, prog_name): parser = super(CreateAccessToken, self).get_parser(prog_name) @@ -112,7 +112,7 @@ class CreateAccessToken(command.ShowOne): class CreateRequestToken(command.ShowOne): - """Create a request token""" + _description = _("Create a request token") def get_parser(self, prog_name): parser = super(CreateRequestToken, self).get_parser(prog_name) @@ -164,7 +164,7 @@ class CreateRequestToken(command.ShowOne): class IssueToken(command.ShowOne): - """Issue new token""" + _description = _("Issue new token") # scoped token is optional required_scope = False @@ -196,7 +196,7 @@ class IssueToken(command.ShowOne): class RevokeToken(command.Command): - """Revoke existing token""" + _description = _("Revoke existing token") def get_parser(self, prog_name): parser = super(RevokeToken, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py index bbc86adb..62d72ea1 100644 --- a/openstackclient/identity/v3/trust.py +++ b/openstackclient/identity/v3/trust.py @@ -24,7 +24,7 @@ from openstackclient.identity import common class CreateTrust(command.ShowOne): - """Create new trust""" + _description = _("Create new trust") def get_parser(self, prog_name): parser = super(CreateTrust, self).get_parser(prog_name) @@ -131,7 +131,7 @@ class CreateTrust(command.ShowOne): class DeleteTrust(command.Command): - """Delete trust(s)""" + _description = _("Delete trust(s)") def get_parser(self, prog_name): parser = super(DeleteTrust, self).get_parser(prog_name) @@ -151,7 +151,7 @@ class DeleteTrust(command.Command): class ListTrust(command.Lister): - """List trusts""" + _description = _("List trusts") def take_action(self, parsed_args): columns = ('ID', 'Expires At', 'Impersonation', 'Project ID', @@ -165,7 +165,7 @@ class ListTrust(command.Lister): class ShowTrust(command.ShowOne): - """Display trust details""" + _description = _("Display trust details") def get_parser(self, prog_name): parser = super(ShowTrust, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/unscoped_saml.py b/openstackclient/identity/v3/unscoped_saml.py index f116174b..f7598f17 100644 --- a/openstackclient/identity/v3/unscoped_saml.py +++ b/openstackclient/identity/v3/unscoped_saml.py @@ -18,35 +18,14 @@ the user can list domains and projects they are allowed to access, and request a scoped token.""" from osc_lib.command import command -from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ -UNSCOPED_AUTH_PLUGINS = ['v3unscopedsaml', 'v3unscopedadfs', 'v3oidc'] - - -def auth_with_unscoped_saml(func): - """Check the unscoped federated context""" - - def _decorated(self, parsed_args): - auth_plugin_name = self.app.client_manager.auth_plugin_name - if auth_plugin_name in UNSCOPED_AUTH_PLUGINS: - return func(self, parsed_args) - else: - msg = (_('This command requires the use of an unscoped SAML ' - 'authentication plugin. Please use argument ' - '--os-auth-type with one of the following ' - 'plugins: %s') % ', '.join(UNSCOPED_AUTH_PLUGINS)) - raise exceptions.CommandError(msg) - return _decorated - - class ListAccessibleDomains(command.Lister): - """List accessible domains""" + _description = _("List accessible domains") - @auth_with_unscoped_saml def take_action(self, parsed_args): columns = ('ID', 'Enabled', 'Name', 'Description') identity_client = self.app.client_manager.identity @@ -59,9 +38,8 @@ class ListAccessibleDomains(command.Lister): class ListAccessibleProjects(command.Lister): - """List accessible projects""" + _description = _("List accessible projects") - @auth_with_unscoped_saml def take_action(self, parsed_args): columns = ('ID', 'Domain ID', 'Enabled', 'Name') identity_client = self.app.client_manager.identity diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index dc47ef8d..796cf28c 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -31,7 +31,7 @@ LOG = logging.getLogger(__name__) class CreateUser(command.ShowOne): - """Create new user""" + _description = _("Create new user") def get_parser(self, prog_name): parser = super(CreateUser, self).get_parser(prog_name) @@ -110,6 +110,10 @@ class CreateUser(command.ShowOne): if parsed_args.password_prompt: parsed_args.password = utils.get_password(self.app.stdin) + if not parsed_args.password: + LOG.warning(_("No password was supplied, authentication will fail " + "when a user does not have a password.")) + try: user = identity_client.users.create( name=parsed_args.name, @@ -134,7 +138,7 @@ class CreateUser(command.ShowOne): class DeleteUser(command.Command): - """Delete user(s)""" + _description = _("Delete user(s)") def get_parser(self, prog_name): parser = super(DeleteUser, self).get_parser(prog_name) @@ -169,7 +173,7 @@ class DeleteUser(command.Command): class ListUser(command.Lister): - """List users""" + _description = _("List users") def get_parser(self, prog_name): parser = super(ListUser, self).get_parser(prog_name) @@ -269,7 +273,7 @@ class ListUser(command.Lister): class SetUser(command.Command): - """Set user properties""" + _description = _("Set user properties") def get_parser(self, prog_name): parser = super(SetUser, self).get_parser(prog_name) @@ -329,6 +333,10 @@ class SetUser(command.Command): if parsed_args.password_prompt: parsed_args.password = utils.get_password(self.app.stdin) + if '' == parsed_args.password: + LOG.warning(_("No password was supplied, authentication will fail " + "when a user does not have a password.")) + user = utils.find_resource( identity_client.users, parsed_args.user, @@ -357,7 +365,7 @@ class SetUser(command.Command): class SetPasswordUser(command.Command): - """Change current user password""" + _description = _("Change current user password") required_scope = False @@ -408,11 +416,15 @@ class SetPasswordUser(command.Command): password = utils.get_password( self.app.stdin, prompt="New Password:") + if '' == password: + LOG.warning(_("No password was supplied, authentication will fail " + "when a user does not have a password.")) + identity_client.users.update_password(current_password, password) class ShowUser(command.ShowOne): - """Display user details""" + _description = _("Display user details") def get_parser(self, prog_name): parser = super(ShowUser, self).get_parser(prog_name) diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py index 1be6c765..b67c291f 100644 --- a/openstackclient/image/client.py +++ b/openstackclient/image/client.py @@ -22,7 +22,7 @@ from openstackclient.i18n import _ LOG = logging.getLogger(__name__) -DEFAULT_API_VERSION = '1' +DEFAULT_API_VERSION = '2' API_VERSION_OPTION = 'os_image_api_version' API_NAME = "image" API_VERSIONS = { diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 5f669c64..eb79cd2f 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -38,6 +38,8 @@ from openstackclient.i18n import _ DEFAULT_CONTAINER_FORMAT = 'bare' DEFAULT_DISK_FORMAT = 'raw' +DISK_CHOICES = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2", "vhdx", + "vdi", "iso"] LOG = logging.getLogger(__name__) @@ -59,7 +61,7 @@ def _format_visibility(data): class CreateImage(command.ShowOne): - """Create/upload an image""" + _description = _("Create/upload an image") def get_parser(self, prog_name): parser = super(CreateImage, self).get_parser(prog_name) @@ -89,8 +91,9 @@ class CreateImage(command.ShowOne): "--disk-format", default=DEFAULT_DISK_FORMAT, metavar="<disk-format>", - help=_("Image disk format " - "(default: %s)") % DEFAULT_DISK_FORMAT, + choices=DISK_CHOICES, + help=_("Image disk format. The supported options are: %s. " + "The default format is: raw") % ', '.join(DISK_CHOICES) ) parser.add_argument( "--size", @@ -277,7 +280,7 @@ class CreateImage(command.ShowOne): class DeleteImage(command.Command): - """Delete image(s)""" + _description = _("Delete image(s)") def get_parser(self, prog_name): parser = super(DeleteImage, self).get_parser(prog_name) @@ -300,7 +303,7 @@ class DeleteImage(command.Command): class ListImage(command.Lister): - """List available images""" + _description = _("List available images") def get_parser(self, prog_name): parser = super(ListImage, self).get_parser(prog_name) @@ -350,8 +353,9 @@ class ListImage(command.Lister): parser.add_argument( '--sort', metavar="<key>[:<direction>]", + default='name:asc', help=_("Sort output by selected keys and directions(asc or desc) " - "(default: asc), multiple keys and directions can be " + "(default: name:asc), multiple keys and directions can be " "specified separated by comma"), ) return parser @@ -440,7 +444,7 @@ class ListImage(command.Lister): class SaveImage(command.Command): - """Save an image locally""" + _description = _("Save an image locally") def get_parser(self, prog_name): parser = super(SaveImage, self).get_parser(prog_name) @@ -468,7 +472,7 @@ class SaveImage(command.Command): class SetImage(command.Command): - """Set image properties""" + _description = _("Set image properties") def get_parser(self, prog_name): parser = super(SetImage, self).get_parser(prog_name) @@ -502,14 +506,12 @@ class SetImage(command.Command): container_choices, choices=container_choices ) - disk_choices = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2", - "vdi", "iso"] parser.add_argument( "--disk-format", metavar="<disk-format>", - help=_("Disk format of image. Acceptable formats: %s") % - disk_choices, - choices=disk_choices + choices=DISK_CHOICES, + help=_("Image disk format. The supported options are: %s.") % + ', '.join(DISK_CHOICES) ) parser.add_argument( "--size", @@ -702,7 +704,7 @@ class SetImage(command.Command): class ShowImage(command.ShowOne): - """Display image details""" + _description = _("Display image details") def get_parser(self, prog_name): parser = super(ShowImage, self).get_parser(prog_name) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 0712e09c..1d167605 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -32,13 +32,15 @@ from openstackclient.identity import common DEFAULT_CONTAINER_FORMAT = 'bare' DEFAULT_DISK_FORMAT = 'raw' +DISK_CHOICES = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2", "vhdx", + "vdi", "iso"] LOG = logging.getLogger(__name__) def _format_image(image): - """Format an image to make it more consistent with OSC operations. """ + """Format an image to make it more consistent with OSC operations.""" info = {} properties = {} @@ -69,7 +71,7 @@ def _format_image(image): class AddProjectToImage(command.ShowOne): - """Associate project with image""" + _description = _("Associate project with image") def get_parser(self, prog_name): parser = super(AddProjectToImage, self).get_parser(prog_name) @@ -107,7 +109,7 @@ class AddProjectToImage(command.ShowOne): class CreateImage(command.ShowOne): - """Create/upload an image""" + _description = _("Create/upload an image") deadopts = ('size', 'location', 'copy-from', 'checksum', 'store') @@ -140,9 +142,10 @@ class CreateImage(command.ShowOne): parser.add_argument( "--disk-format", default=DEFAULT_DISK_FORMAT, + choices=DISK_CHOICES, metavar="<disk-format>", - help=_("Image disk format " - "(default: %s)") % DEFAULT_DISK_FORMAT, + help=_("Image disk format. The supported options are: %s. " + "The default format is: raw") % ', '.join(DISK_CHOICES) ) parser.add_argument( "--min-disk", @@ -361,7 +364,7 @@ class CreateImage(command.ShowOne): class DeleteImage(command.Command): - """Delete image(s)""" + _description = _("Delete image(s)") def get_parser(self, prog_name): parser = super(DeleteImage, self).get_parser(prog_name) @@ -398,7 +401,7 @@ class DeleteImage(command.Command): class ListImage(command.Lister): - """List available images""" + _description = _("List available images") def get_parser(self, prog_name): parser = super(ListImage, self).get_parser(prog_name) @@ -447,8 +450,9 @@ class ListImage(command.Lister): parser.add_argument( '--sort', metavar="<key>[:<direction>]", + default='name:asc', help=_("Sort output by selected keys and directions(asc or desc) " - "(default: asc), multiple keys and directions can be " + "(default: name:asc), multiple keys and directions can be " "specified separated by comma"), ) parser.add_argument( @@ -482,7 +486,6 @@ class ListImage(command.Lister): if parsed_args.marker: kwargs['marker'] = utils.find_resource(image_client.images, parsed_args.marker).id - if parsed_args.long: columns = ( 'ID', @@ -515,7 +518,19 @@ class ListImage(command.Lister): column_headers = columns # List of image data received - data = image_client.api.image_list(**kwargs) + data = [] + if 'marker' in kwargs: + data = image_client.api.image_list(**kwargs) + else: + # No pages received yet, so start the page marker at None. + marker = None + while True: + page = image_client.api.image_list(marker=marker, **kwargs) + if not page: + break + data.extend(page) + # Set the marker to the id of the last item we received + marker = page[-1]['id'] if parsed_args.property: # NOTE(dtroyer): coerce to a list to subscript it in py3 @@ -542,7 +557,7 @@ class ListImage(command.Lister): class RemoveProjectImage(command.Command): - """Disassociate project with image""" + _description = _("Disassociate project with image") def get_parser(self, prog_name): parser = super(RemoveProjectImage, self).get_parser(prog_name) @@ -575,7 +590,7 @@ class RemoveProjectImage(command.Command): class SaveImage(command.Command): - """Save an image locally""" + _description = _("Save an image locally") def get_parser(self, prog_name): parser = super(SaveImage, self).get_parser(prog_name) @@ -603,7 +618,7 @@ class SaveImage(command.Command): class SetImage(command.Command): - """Set image properties""" + _description = _("Set image properties") deadopts = ('visibility',) @@ -650,8 +665,9 @@ class SetImage(command.Command): parser.add_argument( "--disk-format", metavar="<disk-format>", - help=_("Image disk format " - "(default: %s)") % DEFAULT_DISK_FORMAT, + choices=DISK_CHOICES, + help=_("Image disk format. The supported options are: %s") % + ', '.join(DISK_CHOICES) ) protected_group = parser.add_mutually_exclusive_group() protected_group.add_argument( @@ -844,7 +860,7 @@ class SetImage(command.Command): class ShowImage(command.ShowOne): - """Display image details""" + _description = _("Display image details") def get_parser(self, prog_name): parser = super(ShowImage, self).get_parser(prog_name) @@ -867,7 +883,7 @@ class ShowImage(command.ShowOne): class UnsetImage(command.Command): - """Unset image tags and properties""" + _description = _("Unset image tags and properties") def get_parser(self, prog_name): parser = super(UnsetImage, self).get_parser(prog_name) diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index 2b1a5656..a2e700be 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -14,6 +14,7 @@ import abc import logging +import openstack.exceptions from osc_lib.command import command from osc_lib import exceptions import six @@ -181,12 +182,16 @@ class NetworkAndComputeShowOne(command.ShowOne): """ def take_action(self, parsed_args): - if self.app.client_manager.is_network_endpoint_enabled(): - return self.take_action_network(self.app.client_manager.network, - parsed_args) - else: - return self.take_action_compute(self.app.client_manager.compute, - parsed_args) + try: + if self.app.client_manager.is_network_endpoint_enabled(): + return self.take_action_network( + self.app.client_manager.network, parsed_args) + else: + return self.take_action_compute( + self.app.client_manager.compute, parsed_args) + except openstack.exceptions.HttpException as exc: + msg = _("Error while executing command: %s") % exc.message + raise exceptions.CommandError(msg) def get_parser(self, prog_name): LOG.debug('get_parser(%s)', prog_name) diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index 95f6c947..0d8f80d0 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -58,7 +58,7 @@ def _get_attrs(client_manager, parsed_args): # TODO(rtheis): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. class CreateAddressScope(command.ShowOne): - """Create a new Address Scope""" + _description = _("Create a new Address Scope") def get_parser(self, prog_name): parser = super(CreateAddressScope, self).get_parser(prog_name) @@ -106,7 +106,7 @@ class CreateAddressScope(command.ShowOne): class DeleteAddressScope(command.Command): - """Delete address scope(s)""" + _description = _("Delete address scope(s)") def get_parser(self, prog_name): parser = super(DeleteAddressScope, self).get_parser(prog_name) @@ -141,7 +141,7 @@ class DeleteAddressScope(command.Command): class ListAddressScope(command.Lister): - """List address scopes""" + _description = _("List address scopes") def take_action(self, parsed_args): client = self.app.client_manager.network @@ -170,7 +170,7 @@ class ListAddressScope(command.Lister): # TODO(rtheis): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. class SetAddressScope(command.Command): - """Set address scope properties""" + _description = _("Set address scope properties") def get_parser(self, prog_name): parser = super(SetAddressScope, self).get_parser(prog_name) @@ -214,7 +214,7 @@ class SetAddressScope(command.Command): class ShowAddressScope(command.ShowOne): - """Display address scope details""" + _description = _("Display address scope details") def get_parser(self, prog_name): parser = super(ShowAddressScope, self).get_parser(prog_name) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index bb75540c..7b8374e2 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -18,7 +18,16 @@ import logging from osc_lib import utils from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common from openstackclient.network import common +from openstackclient.network import sdk_utils + + +def _get_network_columns(item): + column_map = { + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_columns(item): @@ -58,11 +67,20 @@ def _get_attrs(client_manager, parsed_args): if parsed_args.description is not None: attrs['description'] = parsed_args.description + if parsed_args.project: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + return attrs class CreateFloatingIP(common.NetworkAndComputeShowOne): - """Create floating IP""" + _description = _("Create floating IP") def update_parser_common(self, parser): # In Compute v2 network, floating IPs could be allocated from floating @@ -105,14 +123,20 @@ class CreateFloatingIP(common.NetworkAndComputeShowOne): metavar='<description>', help=_('Set floating IP description') ) + parser.add_argument( + '--project', + metavar='<project>', + help=_("Owner's project (name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) return parser def take_action_network(self, client, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_ip(**attrs) - columns = _get_columns(obj) + display_columns, columns = _get_network_columns(obj) data = utils.get_item_properties(obj, columns) - return (columns, data) + return (display_columns, data) def take_action_compute(self, client, parsed_args): obj = client.floating_ips.create(parsed_args.network) @@ -122,7 +146,7 @@ class CreateFloatingIP(common.NetworkAndComputeShowOne): class CreateIPFloating(CreateFloatingIP): - """Create floating IP""" + _description = _("Create floating IP") # TODO(tangchen): Remove this class and ``ip floating create`` command # two cycles after Mitaka. @@ -146,7 +170,7 @@ class CreateIPFloating(CreateFloatingIP): class DeleteFloatingIP(common.NetworkAndComputeDelete): - """Delete floating IP(s)""" + _description = _("Delete floating IP(s)") # Used by base class to find resources in parsed_args. resource = 'floating_ip' @@ -171,7 +195,7 @@ class DeleteFloatingIP(common.NetworkAndComputeDelete): class DeleteIPFloating(DeleteFloatingIP): - """Delete floating IP(s)""" + _description = _("Delete floating IP(s)") # TODO(tangchen): Remove this class and ``ip floating delete`` command # two cycles after Mitaka. @@ -195,23 +219,63 @@ class DeleteIPFloating(DeleteFloatingIP): class ListFloatingIP(common.NetworkAndComputeLister): - """List floating IP(s)""" + _description = _("List floating IP(s)") + + def update_parser_network(self, parser): + parser.add_argument( + '--network', + metavar='<network>', + help=_("List floating IP(s) according to " + "given network (name or ID)") + ) + parser.add_argument( + '--port', + metavar='<port>', + help=_("List floating IP(s) according to " + "given port (name or ID)") + ) + parser.add_argument( + '--fixed-ip-address', + metavar='<fixed-ip-address>', + help=_("List floating IP(s) according to " + "given fixed IP address") + ) + + return parser def take_action_network(self, client, parsed_args): + network_client = self.app.client_manager.network + columns = ( 'id', 'floating_ip_address', 'fixed_ip_address', 'port_id', + 'floating_network_id', + 'project_id', ) headers = ( 'ID', 'Floating IP Address', 'Fixed IP Address', 'Port', + 'Floating Network', + 'Project', ) query = {} + + if parsed_args.network is not None: + network = network_client.find_network(parsed_args.network, + ignore_missing=False) + query['floating_network_id'] = network.id + if parsed_args.port is not None: + port = network_client.find_port(parsed_args.port, + ignore_missing=False) + query['port_id'] = port.id + if parsed_args.fixed_ip_address is not None: + query['fixed_ip_address'] = parsed_args.fixed_ip_address + data = client.ips(**query) return (headers, @@ -246,7 +310,7 @@ class ListFloatingIP(common.NetworkAndComputeLister): class ListIPFloating(ListFloatingIP): - """List floating IP(s)""" + _description = _("List floating IP(s)") # TODO(tangchen): Remove this class and ``ip floating list`` command # two cycles after Mitaka. @@ -270,7 +334,7 @@ class ListIPFloating(ListFloatingIP): class ShowFloatingIP(common.NetworkAndComputeShowOne): - """Display floating IP details""" + _description = _("Display floating IP details") def update_parser_common(self, parser): parser.add_argument( @@ -282,9 +346,9 @@ class ShowFloatingIP(common.NetworkAndComputeShowOne): def take_action_network(self, client, parsed_args): obj = client.find_ip(parsed_args.floating_ip, ignore_missing=False) - columns = _get_columns(obj) + display_columns, columns = _get_network_columns(obj) data = utils.get_item_properties(obj, columns) - return (columns, data) + return (display_columns, data) def take_action_compute(self, client, parsed_args): obj = utils.find_resource( @@ -297,7 +361,7 @@ class ShowFloatingIP(common.NetworkAndComputeShowOne): class ShowIPFloating(ShowFloatingIP): - """Display floating IP details""" + _description = _("Display floating IP details") # TODO(tangchen): Remove this class and ``ip floating show`` command # two cycles after Mitaka. diff --git a/openstackclient/network/v2/floating_ip_pool.py b/openstackclient/network/v2/floating_ip_pool.py index c78ca06a..73e94ead 100644 --- a/openstackclient/network/v2/floating_ip_pool.py +++ b/openstackclient/network/v2/floating_ip_pool.py @@ -23,7 +23,7 @@ from openstackclient.network import common class ListFloatingIPPool(common.NetworkAndComputeLister): - """List pools of floating IP addresses""" + _description = _("List pools of floating IP addresses") def take_action_network(self, client, parsed_args): msg = _("Floating ip pool operations are only available for " @@ -43,7 +43,7 @@ class ListFloatingIPPool(common.NetworkAndComputeLister): class ListIPFloatingPool(ListFloatingIPPool): - """List pools of floating IP addresses""" + _description = _("List pools of floating IP addresses") # TODO(tangchen): Remove this class and ``ip floating pool list`` command # two cycles after Mitaka. diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py index 839e1d00..a80fe1c4 100644 --- a/openstackclient/network/v2/ip_availability.py +++ b/openstackclient/network/v2/ip_availability.py @@ -35,7 +35,7 @@ def _get_columns(item): # TODO(ankur-gupta-f): Use the SDK resource mapped attribute names once # the OSC minimum requirements include SDK 1.0. class ListIPAvailability(command.Lister): - """List IP availability for network""" + _description = _("List IP availability for network") def get_parser(self, prog_name): parser = super(ListIPAvailability, self).get_parser(prog_name) @@ -94,7 +94,7 @@ class ListIPAvailability(command.Lister): class ShowIPAvailability(command.ShowOne): - """Show network IP availability details""" + _description = _("Show network IP availability details") def get_parser(self, prog_name): parser = super(ShowIPAvailability, self).get_parser(prog_name) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index dbf1b601..1c06c462 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -42,6 +42,7 @@ def _get_columns(item): columns = list(item.keys()) if 'tenant_id' in columns: columns.remove('tenant_id') + if 'project_id' not in columns: columns.append('project_id') return tuple(sorted(columns)) @@ -98,6 +99,13 @@ def _get_attrs(client_manager, parsed_args): attrs['provider:physical_network'] = parsed_args.physical_network if parsed_args.segmentation_id: attrs['provider:segmentation_id'] = parsed_args.segmentation_id + if parsed_args.qos_policy is not None: + network_client = client_manager.network + _qos_policy = network_client.find_qos_policy(parsed_args.qos_policy, + ignore_missing=False) + attrs['qos_policy_id'] = _qos_policy.id + if 'no_qos_policy' in parsed_args and parsed_args.no_qos_policy: + attrs['qos_policy_id'] = None # Update VLAN Transparency for networks if parsed_args.transparent_vlan: attrs['vlan_transparent'] = True @@ -155,7 +163,7 @@ def _get_attrs_compute(client_manager, parsed_args): class CreateNetwork(common.NetworkAndComputeShowOne): - """Create new network""" + _description = _("Create new network") def update_parser_common(self, parser): parser.add_argument( @@ -248,6 +256,11 @@ class CreateNetwork(common.NetworkAndComputeShowOne): help=_("Do not use the network as the default external network " "(default)") ) + parser.add_argument( + '--qos-policy', + metavar='<qos-policy>', + help=_("QoS policy to attach to this network (name or ID)") + ) _add_additional_network_options(parser) return parser @@ -275,7 +288,7 @@ class CreateNetwork(common.NetworkAndComputeShowOne): class DeleteNetwork(common.NetworkAndComputeDelete): - """Delete network(s)""" + _description = _("Delete network(s)") # Used by base class to find resources in parsed_args. resource = 'network' @@ -301,9 +314,9 @@ class DeleteNetwork(common.NetworkAndComputeDelete): class ListNetwork(common.NetworkAndComputeLister): - """List networks""" + _description = _("List networks") - def update_parser_common(self, parser): + def update_parser_network(self, parser): router_ext_group = parser.add_mutually_exclusive_group() router_ext_group.add_argument( '--external', @@ -360,6 +373,29 @@ class ListNetwork(common.NetworkAndComputeLister): help=_("List networks according to their status " "('ACTIVE', 'BUILD', 'DOWN', 'ERROR')") ) + parser.add_argument( + '--provider-network-type', + metavar='<provider-network-type>', + choices=['flat', 'geneve', 'gre', 'local', + 'vlan', 'vxlan'], + help=_("List networks according to their physical mechanisms. " + "The supported options are: flat, geneve, gre, local, " + "vlan, vxlan.") + ) + parser.add_argument( + '--provider-physical-network', + metavar='<provider-physical-network>', + dest='physical_network', + help=_("List networks according to name of the physical network") + ) + parser.add_argument( + '--provider-segment', + metavar='<provider-segment>', + dest='segmentation_id', + help=_("List networks according to VLAN ID for VLAN networks " + "or Tunnel ID for GENEVE/GRE/VXLAN networks") + ) + return parser def take_action_network(self, client, parsed_args): @@ -432,6 +468,13 @@ class ListNetwork(common.NetworkAndComputeLister): if parsed_args.status: args['status'] = parsed_args.status + if parsed_args.provider_network_type: + args['provider:network_type'] = parsed_args.provider_network_type + if parsed_args.physical_network: + args['provider:physical_network'] = parsed_args.physical_network + if parsed_args.segmentation_id: + args['provider:segmentation_id'] = parsed_args.segmentation_id + data = client.networks(**args) return (column_headers, @@ -462,7 +505,7 @@ class ListNetwork(common.NetworkAndComputeLister): class SetNetwork(command.Command): - """Set network properties""" + _description = _("Set network properties") def get_parser(self, prog_name): parser = super(SetNetwork, self).get_parser(prog_name) @@ -541,6 +584,17 @@ class SetNetwork(command.Command): action='store_true', help=_("Do not use the network as the default external network") ) + qos_group = parser.add_mutually_exclusive_group() + qos_group.add_argument( + '--qos-policy', + metavar='<qos-policy>', + help=_("QoS policy to attach to this network (name or ID)") + ) + qos_group.add_argument( + '--no-qos-policy', + action='store_true', + help=_("Remove the QoS policy attached to this network") + ) _add_additional_network_options(parser) return parser @@ -553,7 +607,7 @@ class SetNetwork(command.Command): class ShowNetwork(common.NetworkAndComputeShowOne): - """Show network details""" + _description = _("Show network details") def update_parser_common(self, parser): parser.add_argument( diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index fdb34bb7..65708499 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -36,7 +36,7 @@ _formatters = { class DeleteNetworkAgent(command.Command): - """Delete network agent(s)""" + _description = _("Delete network agent(s)") def get_parser(self, prog_name): parser = super(DeleteNetworkAgent, self).get_parser(prog_name) @@ -70,7 +70,7 @@ class DeleteNetworkAgent(command.Command): class ListNetworkAgent(command.Lister): - """List network agents""" + _description = _("List network agents") def take_action(self, parsed_args): client = self.app.client_manager.network @@ -100,7 +100,7 @@ class ListNetworkAgent(command.Lister): class SetNetworkAgent(command.Command): - """Set network agent properties""" + _description = _("Set network agent properties") def get_parser(self, prog_name): parser = super(SetNetworkAgent, self).get_parser(prog_name) @@ -141,7 +141,7 @@ class SetNetworkAgent(command.Command): class ShowNetworkAgent(command.ShowOne): - """Display network agent details""" + _description = _("Display network agent details") def get_parser(self, prog_name): parser = super(ShowNetworkAgent, self).get_parser(prog_name) diff --git a/openstackclient/network/v2/network_qos_policy.py b/openstackclient/network/v2/network_qos_policy.py new file mode 100644 index 00000000..5ccbe36b --- /dev/null +++ b/openstackclient/network/v2/network_qos_policy.py @@ -0,0 +1,238 @@ +# Copyright (c) 2016, Intel Corporation. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common +from openstackclient.network import sdk_utils + + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + column_map = { + 'is_shared': 'shared', + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + +def _get_attrs(client_manager, parsed_args): + attrs = {} + if parsed_args.name is not None: + attrs['name'] = str(parsed_args.name) + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + if parsed_args.share: + attrs['shared'] = True + if parsed_args.no_share: + attrs['shared'] = False + if parsed_args.project is not None: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + + return attrs + + +# TODO(abhiraut): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. +class CreateNetworkQosPolicy(command.ShowOne): + _description = _("Create a QoS policy") + + def get_parser(self, prog_name): + parser = super(CreateNetworkQosPolicy, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='<name>', + help=_("Name of QoS policy to create") + ) + parser.add_argument( + '--description', + metavar='<description>', + help=_("Description of the QoS policy") + ) + share_group = parser.add_mutually_exclusive_group() + share_group.add_argument( + '--share', + action='store_true', + default=None, + help=_("Make the QoS policy accessible by other projects") + ) + share_group.add_argument( + '--no-share', + action='store_true', + help=_("Make the QoS policy not accessible by other projects " + "(default)") + ) + parser.add_argument( + '--project', + metavar='<project>', + help=_("Owner's project (name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = _get_attrs(self.app.client_manager, parsed_args) + obj = client.create_qos_policy(**attrs) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + return (display_columns, data) + + +class DeleteNetworkQosPolicy(command.Command): + _description = _("Delete Qos Policy(s)") + + def get_parser(self, prog_name): + parser = super(DeleteNetworkQosPolicy, self).get_parser(prog_name) + parser.add_argument( + 'policy', + metavar="<qos-policy>", + nargs="+", + help=_("QoS policy(s) to delete (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + result = 0 + + for policy in parsed_args.policy: + try: + obj = client.find_qos_policy(policy, ignore_missing=False) + client.delete_qos_policy(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete QoS policy " + "name or ID '%(qos_policy)s': %(e)s"), + {'qos_policy': policy, 'e': e}) + + if result > 0: + total = len(parsed_args.policy) + msg = (_("%(result)s of %(total)s QoS policies failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +# TODO(abhiraut): Use only the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. +class ListNetworkQosPolicy(command.Lister): + _description = _("List QoS policies") + + def take_action(self, parsed_args): + client = self.app.client_manager.network + columns = ( + 'id', + 'name', + 'is_shared', + 'project_id', + ) + column_headers = ( + 'ID', + 'Name', + 'Shared', + 'Project', + ) + data = client.qos_policies() + + return (column_headers, + (utils.get_item_properties( + s, columns, formatters={}, + ) for s in data)) + + +# TODO(abhiraut): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. +class SetNetworkQosPolicy(command.Command): + _description = _("Set QoS policy properties") + + def get_parser(self, prog_name): + parser = super(SetNetworkQosPolicy, self).get_parser(prog_name) + parser.add_argument( + 'policy', + metavar="<qos-policy>", + help=_("QoS policy to modify (name or ID)") + ) + parser.add_argument( + '--name', + metavar="<name>", + help=_('Set QoS policy name') + ) + parser.add_argument( + '--description', + metavar='<description>', + help=_("Description of the QoS policy") + ) + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--share', + action='store_true', + help=_('Make the QoS policy accessible by other projects'), + ) + enable_group.add_argument( + '--no-share', + action='store_true', + help=_('Make the QoS policy not accessible by other projects'), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_qos_policy( + parsed_args.policy, + ignore_missing=False) + attrs = {} + if parsed_args.name is not None: + attrs['name'] = parsed_args.name + if parsed_args.share: + attrs['shared'] = True + if parsed_args.no_share: + attrs['shared'] = False + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + client.update_qos_policy(obj, **attrs) + + +class ShowNetworkQosPolicy(command.ShowOne): + _description = _("Display QoS policy details") + + def get_parser(self, prog_name): + parser = super(ShowNetworkQosPolicy, self).get_parser(prog_name) + parser.add_argument( + 'policy', + metavar="<qos-policy>", + help=_("QoS policy to display (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_qos_policy(parsed_args.policy, + ignore_missing=False) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return (display_columns, data) diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index bb29579f..e837af3a 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -21,20 +21,18 @@ from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) def _get_columns(item): - columns = list(item.keys()) - if 'tenant_id' in columns: - columns.remove('tenant_id') - columns.append('project_id') - if 'target_tenant' in columns: - columns.remove('target_tenant') - columns.append('target_project_id') - return tuple(sorted(columns)) + column_map = { + 'target_tenant': 'target_project_id', + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_attrs(client_manager, parsed_args): @@ -47,9 +45,9 @@ def _get_attrs(client_manager, parsed_args): object_id = network_client.find_network( parsed_args.rbac_object, ignore_missing=False).id if parsed_args.type == 'qos_policy': - # TODO(Huanxuan Ao): Support finding a object ID by obejct name - # after qos policy finding supported in SDK. - object_id = parsed_args.rbac_object + object_id = network_client.find_qos_policy( + parsed_args.rbac_object, + ignore_missing=False).id attrs['object_id'] = object_id identity_client = client_manager.identity @@ -70,16 +68,17 @@ def _get_attrs(client_manager, parsed_args): return attrs +# TODO(abhiraut): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class CreateNetworkRBAC(command.ShowOne): - """Create network RBAC policy""" + _description = _("Create network RBAC policy") def get_parser(self, prog_name): parser = super(CreateNetworkRBAC, self).get_parser(prog_name) parser.add_argument( 'rbac_object', metavar="<rbac-object>", - help=_("The object to which this RBAC policy affects (name or " - "ID for network objects, ID only for QoS policy objects)") + help=_("The object to which this RBAC policy affects (name or ID)") ) parser.add_argument( '--type', @@ -123,13 +122,13 @@ class CreateNetworkRBAC(command.ShowOne): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_rbac_policy(**attrs) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) - return columns, data + return display_columns, data class DeleteNetworkRBAC(command.Command): - """Delete network RBAC policy(s)""" + _description = _("Delete network RBAC policy(s)") def get_parser(self, prog_name): parser = super(DeleteNetworkRBAC, self).get_parser(prog_name) @@ -163,7 +162,7 @@ class DeleteNetworkRBAC(command.Command): class ListNetworkRBAC(command.Lister): - """List network RBAC policies""" + _description = _("List network RBAC policies") def take_action(self, parsed_args): client = self.app.client_manager.network @@ -186,8 +185,10 @@ class ListNetworkRBAC(command.Lister): ) for s in data)) +# TODO(abhiraut): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class SetNetworkRBAC(command.Command): - """Set network RBAC policy properties""" + _description = _("Set network RBAC policy properties") def get_parser(self, prog_name): parser = super(SetNetworkRBAC, self).get_parser(prog_name) @@ -228,7 +229,7 @@ class SetNetworkRBAC(command.Command): class ShowNetworkRBAC(command.ShowOne): - """Display network RBAC policy details""" + _description = _("Display network RBAC policy details") def get_parser(self, prog_name): parser = super(ShowNetworkRBAC, self).get_parser(prog_name) @@ -243,6 +244,6 @@ class ShowNetworkRBAC(command.ShowOne): client = self.app.client_manager.network obj = client.find_rbac_policy(parsed_args.rbac_policy, ignore_missing=False) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) - return columns, data + return display_columns, data diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py index 94722f1e..709dc296 100644 --- a/openstackclient/network/v2/network_segment.py +++ b/openstackclient/network/v2/network_segment.py @@ -20,13 +20,18 @@ from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ +from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) +def _get_columns(item): + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, {}) + + class CreateNetworkSegment(command.ShowOne): - """Create new network segment""" + _description = _("Create new network segment") def get_parser(self, prog_name): parser = super(CreateNetworkSegment, self).get_parser(prog_name) @@ -84,13 +89,13 @@ class CreateNetworkSegment(command.ShowOne): if parsed_args.segment is not None: attrs['segmentation_id'] = parsed_args.segment obj = client.create_segment(**attrs) - columns = tuple(sorted(obj.keys())) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) - return (columns, data) + return (display_columns, data) class DeleteNetworkSegment(command.Command): - """Delete network segment(s)""" + _description = _("Delete network segment(s)") def get_parser(self, prog_name): parser = super(DeleteNetworkSegment, self).get_parser(prog_name) @@ -125,7 +130,7 @@ class DeleteNetworkSegment(command.Command): class ListNetworkSegment(command.Lister): - """List network segments""" + _description = _("List network segments") def get_parser(self, prog_name): parser = super(ListNetworkSegment, self).get_parser(prog_name) @@ -185,7 +190,7 @@ class ListNetworkSegment(command.Lister): class SetNetworkSegment(command.Command): - """Set network segment properties""" + _description = _("Set network segment properties") def get_parser(self, prog_name): parser = super(SetNetworkSegment, self).get_parser(prog_name) @@ -219,7 +224,7 @@ class SetNetworkSegment(command.Command): class ShowNetworkSegment(command.ShowOne): - """Display network segment details""" + _description = _("Display network segment details") def get_parser(self, prog_name): parser = super(ShowNetworkSegment, self).get_parser(prog_name) @@ -236,6 +241,6 @@ class ShowNetworkSegment(command.ShowOne): parsed_args.network_segment, ignore_missing=False ) - columns = tuple(sorted(obj.keys())) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) - return (columns, data) + return (display_columns, data) diff --git a/openstackclient/network/v2/network_service_provider.py b/openstackclient/network/v2/network_service_provider.py new file mode 100644 index 00000000..3aa33c23 --- /dev/null +++ b/openstackclient/network/v2/network_service_provider.py @@ -0,0 +1,41 @@ +# 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. +# + +"""Network Service Providers Implementation""" + +from osc_lib.command import command +from osc_lib import utils + + +class ListNetworkServiceProvider(command.Lister): + """List Service Providers""" + + def take_action(self, parsed_args): + client = self.app.client_manager.network + + columns = ( + 'service_type', + 'name', + 'is_default', + ) + column_headers = ( + 'Service Type', + 'Name', + 'Default', + ) + + data = client.service_providers() + return(column_headers, + (utils.get_item_properties( + s, columns, + ) for s in data)) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 92b286a9..bce3e2d3 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -50,7 +50,8 @@ def _get_columns(item): columns = list(item.keys()) if 'tenant_id' in columns: columns.remove('tenant_id') - columns.append('project_id') + if 'project_id' not in columns: + columns.append('project_id') binding_columns = [ 'binding:host_id', 'binding:profile', @@ -109,6 +110,8 @@ def _get_attrs(client_manager, parsed_args): 'The --host-id option is deprecated, ' 'please use --host instead.' )) + if parsed_args.description is not None: + attrs['description'] = parsed_args.description if parsed_args.fixed_ip is not None: attrs['fixed_ips'] = parsed_args.fixed_ip if parsed_args.device: @@ -126,6 +129,8 @@ def _get_attrs(client_manager, parsed_args): if parsed_args.host: attrs['binding:host_id'] = parsed_args.host + if parsed_args.dns_name is not None: + attrs['dns_name'] = parsed_args.dns_name # It is possible that name is not updated during 'port set' if parsed_args.name is not None: attrs['name'] = str(parsed_args.name) @@ -146,6 +151,12 @@ def _get_attrs(client_manager, parsed_args): ).id attrs['tenant_id'] = project_id + if parsed_args.disable_port_security: + attrs['port_security_enabled'] = False + + if parsed_args.enable_port_security: + attrs['port_security_enabled'] = True + return attrs @@ -180,50 +191,61 @@ def _prepare_fixed_ips(client_manager, parsed_args): def _add_updatable_args(parser): - # NOTE(dtroyer): --device-id is deprecated in Mar 2016. Do not - # remove before 3.x release or Mar 2017. - device_group = parser.add_mutually_exclusive_group() - device_group.add_argument( - '--device', - metavar='<device-id>', - help=_("Port device ID") - ) - device_group.add_argument( - '--device-id', - metavar='<device-id>', - help=argparse.SUPPRESS, - ) - parser.add_argument( - '--device-owner', - metavar='<device-owner>', - help=_("Device owner of this port. This is the entity that uses " - "the port (for example, network:dhcp).") - ) - parser.add_argument( - '--vnic-type', - metavar='<vnic-type>', - choices=['direct', 'direct-physical', 'macvtap', - 'normal', 'baremetal'], - help=_("VNIC type for this port (direct | direct-physical | " - "macvtap | normal | baremetal, default: normal)") - ) - # NOTE(dtroyer): --host-id is deprecated in Mar 2016. Do not - # remove before 3.x release or Mar 2017. - host_group = parser.add_mutually_exclusive_group() - host_group.add_argument( - '--host', - metavar='<host-id>', - help=_("Allocate port on host <host-id> (ID only)") - ) - host_group.add_argument( - '--host-id', - metavar='<host-id>', - help=argparse.SUPPRESS, - ) + parser.add_argument( + '--description', + metavar='<description>', + help=_("Description of this port") + ) + # NOTE(dtroyer): --device-id is deprecated in Mar 2016. Do not + # remove before 3.x release or Mar 2017. + device_group = parser.add_mutually_exclusive_group() + device_group.add_argument( + '--device', + metavar='<device-id>', + help=_("Port device ID") + ) + device_group.add_argument( + '--device-id', + metavar='<device-id>', + help=argparse.SUPPRESS, + ) + parser.add_argument( + '--device-owner', + metavar='<device-owner>', + help=_("Device owner of this port. This is the entity that uses " + "the port (for example, network:dhcp).") + ) + parser.add_argument( + '--vnic-type', + metavar='<vnic-type>', + choices=['direct', 'direct-physical', 'macvtap', + 'normal', 'baremetal'], + help=_("VNIC type for this port (direct | direct-physical | " + "macvtap | normal | baremetal, default: normal)") + ) + # NOTE(dtroyer): --host-id is deprecated in Mar 2016. Do not + # remove before 3.x release or Mar 2017. + host_group = parser.add_mutually_exclusive_group() + host_group.add_argument( + '--host', + metavar='<host-id>', + help=_("Allocate port on host <host-id> (ID only)") + ) + host_group.add_argument( + '--host-id', + metavar='<host-id>', + help=argparse.SUPPRESS, + ) + parser.add_argument( + '--dns-name', + metavar='dns-name', + help=_("Set DNS name to this port " + "(requires DNS integration extension)") + ) class CreatePort(command.ShowOne): - """Create a new port""" + _description = _("Create a new port") def get_parser(self, prog_name): parser = super(CreatePort, self).get_parser(prog_name) @@ -281,7 +303,34 @@ class CreatePort(command.ShowOne): help=_("Name of this port") ) # TODO(singhj): Add support for extended options: - # qos,security groups,dhcp, address pairs + # qos,dhcp, address pairs + secgroups = parser.add_mutually_exclusive_group() + secgroups.add_argument( + '--security-group', + metavar='<security-group>', + action='append', + dest='security_groups', + help=_("Security group to associate with this port (name or ID) " + "(repeat option to set multiple security groups)") + ) + secgroups.add_argument( + '--no-security-group', + dest='no_security_group', + action='store_true', + help=_("Associate no security groups with this port") + ) + port_security = parser.add_mutually_exclusive_group() + port_security.add_argument( + '--enable-port-security', + action='store_true', + help=_("Enable port security for this port (Default)") + ) + port_security.add_argument( + '--disable-port-security', + action='store_true', + help=_("Disable port security for this port") + ) + return parser def take_action(self, parsed_args): @@ -291,6 +340,14 @@ class CreatePort(command.ShowOne): parsed_args.network = _network.id _prepare_fixed_ips(self.app.client_manager, parsed_args) attrs = _get_attrs(self.app.client_manager, parsed_args) + + if parsed_args.security_groups: + attrs['security_groups'] = [client.find_security_group( + sg, ignore_missing=False).id + for sg in parsed_args.security_groups] + if parsed_args.no_security_group: + attrs['security_groups'] = [] + obj = client.create_port(**attrs) columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) @@ -299,7 +356,7 @@ class CreatePort(command.ShowOne): class DeletePort(command.Command): - """Delete port(s)""" + _description = _("Delete port(s)") def get_parser(self, prog_name): parser = super(DeletePort, self).get_parser(prog_name) @@ -333,7 +390,7 @@ class DeletePort(command.Command): class ListPort(command.Lister): - """List ports""" + _description = _("List ports") def get_parser(self, prog_name): parser = super(ListPort, self).get_parser(prog_name) @@ -360,6 +417,17 @@ class ListPort(command.Lister): metavar='<server>', help=_("List only ports attached to this server (name or ID)"), ) + parser.add_argument( + '--mac-address', + metavar='<mac-address>', + help=_("List only ports with this MAC address") + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help=_("List additional fields in output") + ) return parser def take_action(self, parsed_args): @@ -371,15 +439,20 @@ class ListPort(command.Lister): 'name', 'mac_address', 'fixed_ips', + 'status', ) column_headers = ( 'ID', 'Name', 'MAC Address', 'Fixed IP Addresses', + 'Status', ) filters = {} + if parsed_args.long: + columns += ('security_groups', 'device_owner',) + column_headers += ('Security Groups', 'Device Owner',) if parsed_args.device_owner is not None: filters['device_owner'] = parsed_args.device_owner if parsed_args.router: @@ -394,6 +467,8 @@ class ListPort(command.Lister): network = network_client.find_network(parsed_args.network, ignore_missing=False) filters['network_id'] = network.id + if parsed_args.mac_address: + filters['mac_address'] = parsed_args.mac_address data = network_client.ports(**filters) @@ -405,7 +480,7 @@ class ListPort(command.Lister): class SetPort(command.Command): - """Set port properties""" + _description = _("Set port properties") def get_parser(self, prog_name): parser = super(SetPort, self).get_parser(prog_name) @@ -463,6 +538,32 @@ class SetPort(command.Command): metavar="<port>", help=_("Port to modify (name or ID)") ) + parser.add_argument( + '--security-group', + metavar='<security-group>', + action='append', + dest='security_groups', + help=_("Security group to associate with this port (name or ID) " + "(repeat option to set multiple security groups)") + ) + parser.add_argument( + '--no-security-group', + dest='no_security_group', + action='store_true', + help=_("Clear existing security groups associated with this port") + ) + port_security = parser.add_mutually_exclusive_group() + port_security.add_argument( + '--enable-port-security', + action='store_true', + help=_("Enable port security for this port") + ) + port_security.add_argument( + '--disable-port-security', + action='store_true', + help=_("Disable port security for this port") + ) + return parser def take_action(self, parsed_args): @@ -490,12 +591,23 @@ class SetPort(command.Command): attrs['fixed_ips'] += [ip for ip in obj.fixed_ips if ip] elif parsed_args.no_fixed_ip: attrs['fixed_ips'] = [] + if parsed_args.security_groups and parsed_args.no_security_group: + attrs['security_groups'] = [client.find_security_group(sg, + ignore_missing=False).id + for sg in parsed_args.security_groups] + elif parsed_args.security_groups: + attrs['security_groups'] = obj.security_groups + for sg in parsed_args.security_groups: + sg_id = client.find_security_group(sg, ignore_missing=False).id + attrs['security_groups'].append(sg_id) + elif parsed_args.no_security_group: + attrs['security_groups'] = [] client.update_port(obj, **attrs) class ShowPort(command.ShowOne): - """Display port details""" + _description = _("Display port details") def get_parser(self, prog_name): parser = super(ShowPort, self).get_parser(prog_name) @@ -515,7 +627,7 @@ class ShowPort(command.ShowOne): class UnsetPort(command.Command): - """Unset port properties""" + _description = _("Unset port properties") def get_parser(self, prog_name): parser = super(UnsetPort, self).get_parser(prog_name) @@ -536,6 +648,15 @@ class UnsetPort(command.Command): help=_("Desired key which should be removed from binding:profile" "(repeat option to unset multiple binding:profile data)")) parser.add_argument( + '--security-group', + metavar='<security-group>', + action='append', + dest='security_groups', + help=_("Security group which should be removed this port (name " + "or ID) (repeat option to unset multiple security groups)") + ) + + parser.add_argument( 'port', metavar="<port>", help=_("Port to modify (name or ID)") @@ -550,6 +671,7 @@ class UnsetPort(command.Command): # Unset* classes tmp_fixed_ips = copy.deepcopy(obj.fixed_ips) tmp_binding_profile = copy.deepcopy(obj.binding_profile) + tmp_secgroups = copy.deepcopy(obj.security_groups) _prepare_fixed_ips(self.app.client_manager, parsed_args) attrs = {} if parsed_args.fixed_ip: @@ -568,5 +690,16 @@ class UnsetPort(command.Command): msg = _("Port does not contain binding-profile %s") % key raise exceptions.CommandError(msg) attrs['binding:profile'] = tmp_binding_profile + if parsed_args.security_groups: + try: + for sg in parsed_args.security_groups: + sg_id = client.find_security_group( + sg, ignore_missing=False).id + tmp_secgroups.remove(sg_id) + except ValueError: + msg = _("Port does not contain security group %s") % sg + raise exceptions.CommandError(msg) + attrs['security_groups'] = tmp_secgroups + if attrs: client.update_port(obj, **attrs) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 48a3a92c..cdd634d0 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -25,6 +25,7 @@ from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) @@ -59,11 +60,10 @@ _formatters = { def _get_columns(item): - columns = list(item.keys()) - if 'tenant_id' in columns: - columns.remove('tenant_id') - columns.append('project_id') - return tuple(sorted(columns)) + column_map = { + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_attrs(client_manager, parsed_args): @@ -100,7 +100,7 @@ def _get_attrs(client_manager, parsed_args): class AddPortToRouter(command.Command): - """Add a port to a router""" + _description = _("Add a port to a router") def get_parser(self, prog_name): parser = super(AddPortToRouter, self).get_parser(prog_name) @@ -124,7 +124,7 @@ class AddPortToRouter(command.Command): class AddSubnetToRouter(command.Command): - """Add a subnet to a router""" + _description = _("Add a subnet to a router") def get_parser(self, prog_name): parser = super(AddSubnetToRouter, self).get_parser(prog_name) @@ -151,7 +151,7 @@ class AddSubnetToRouter(command.Command): class CreateRouter(command.ShowOne): - """Create a new router""" + _description = _("Create a new router") def get_parser(self, prog_name): parser = super(CreateRouter, self).get_parser(prog_name) @@ -215,14 +215,14 @@ class CreateRouter(command.ShowOne): attrs['ha'] = parsed_args.ha obj = client.create_router(**attrs) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return (columns, data) + return (display_columns, data) class DeleteRouter(command.Command): - """Delete router(s)""" + _description = _("Delete router(s)") def get_parser(self, prog_name): parser = super(DeleteRouter, self).get_parser(prog_name) @@ -256,21 +256,43 @@ class DeleteRouter(command.Command): class ListRouter(command.Lister): - """List routers""" + _description = _("List routers") def get_parser(self, prog_name): parser = super(ListRouter, self).get_parser(prog_name) parser.add_argument( + '--name', + metavar='<name>', + help=_("List routers according to their name") + ) + admin_state_group = parser.add_mutually_exclusive_group() + admin_state_group.add_argument( + '--enable', + action='store_true', + help=_("List enabled routers") + ) + admin_state_group.add_argument( + '--disable', + action='store_true', + help=_("List disabled routers") + ) + parser.add_argument( '--long', action='store_true', default=False, help=_("List additional fields in output") ) + parser.add_argument( + '--project', + metavar='<project>', + help=_("List routers according to their project (name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity client = self.app.client_manager.network - columns = ( 'id', 'name', @@ -289,6 +311,24 @@ class ListRouter(command.Lister): 'HA', 'Project', ) + + args = {} + + if parsed_args.name is not None: + args['name'] = parsed_args.name + + if parsed_args.enable: + args['admin_state_up'] = True + elif parsed_args.disable: + args['admin_state_up'] = False + + if parsed_args.project: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + args['tenant_id'] = project_id if parsed_args.long: columns = columns + ( 'routes', @@ -308,7 +348,7 @@ class ListRouter(command.Lister): 'Availability zones', ) - data = client.routers() + data = client.routers(**args) return (column_headers, (utils.get_item_properties( s, columns, @@ -317,7 +357,7 @@ class ListRouter(command.Lister): class RemovePortFromRouter(command.Command): - """Remove a port from a router""" + _description = _("Remove a port from a router") def get_parser(self, prog_name): parser = super(RemovePortFromRouter, self).get_parser(prog_name) @@ -341,7 +381,7 @@ class RemovePortFromRouter(command.Command): class RemoveSubnetFromRouter(command.Command): - """Remove a subnet from a router""" + _description = _("Remove a subnet from a router") def get_parser(self, prog_name): parser = super(RemoveSubnetFromRouter, self).get_parser(prog_name) @@ -368,7 +408,7 @@ class RemoveSubnetFromRouter(command.Command): class SetRouter(command.Command): - """Set router properties""" + _description = _("Set router properties") def get_parser(self, prog_name): parser = super(SetRouter, self).get_parser(prog_name) @@ -433,11 +473,19 @@ class SetRouter(command.Command): action='store_true', help=argparse.SUPPRESS, ) - - # TODO(tangchen): Support setting 'ha' property in 'router set' - # command. It appears that changing the ha state is supported by - # neutron under certain conditions. - + routes_ha = parser.add_mutually_exclusive_group() + routes_ha.add_argument( + '--ha', + action='store_true', + help=_("Set the router as highly available " + "(disabled router only)") + ) + routes_ha.add_argument( + '--no-ha', + action='store_true', + help=_("Clear high availablability attribute of the router " + "(disabled router only)") + ) # TODO(tangchen): Support setting 'external_gateway_info' property in # 'router set' command. @@ -451,6 +499,10 @@ class SetRouter(command.Command): attrs = _get_attrs(self.app.client_manager, parsed_args) # Get the route attributes. + if parsed_args.ha: + attrs['ha'] = True + elif parsed_args.no_ha: + attrs['ha'] = False if parsed_args.no_route: attrs['routes'] = [] elif parsed_args.clear_routes: @@ -470,7 +522,7 @@ class SetRouter(command.Command): class ShowRouter(command.ShowOne): - """Display router details""" + _description = _("Display router details") def get_parser(self, prog_name): parser = super(ShowRouter, self).get_parser(prog_name) @@ -484,13 +536,14 @@ class ShowRouter(command.ShowOne): def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.find_router(parsed_args.router, ignore_missing=False) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return (columns, data) + + return (display_columns, data) class UnsetRouter(command.Command): - """Unset router properties""" + _description = _("Unset router properties") def get_parser(self, prog_name): parser = super(UnsetRouter, self).get_parser(prog_name) @@ -520,12 +573,11 @@ class UnsetRouter(command.Command): if parsed_args.routes: try: for route in parsed_args.routes: + route['nexthop'] = route.pop('gateway') tmp_routes.remove(route) except ValueError: msg = (_("Router does not contain route %s") % route) raise exceptions.CommandError(msg) - for route in tmp_routes: - route['nexthop'] = route.pop('gateway') attrs['routes'] = tmp_routes if attrs: client.update_router(obj, **attrs) diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index f832f721..5420bc8b 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -81,8 +81,9 @@ def _get_columns(item): columns.remove('security_group_rules') property_column_mappings.append(('rules', 'security_group_rules')) if 'tenant_id' in columns: - columns.append('project_id') columns.remove('tenant_id') + if 'project_id' not in columns: + columns.append('project_id') property_column_mappings.append(('project_id', 'tenant_id')) display_columns = sorted(columns) @@ -95,7 +96,7 @@ def _get_columns(item): class CreateSecurityGroup(common.NetworkAndComputeShowOne): - """Create a new security group""" + _description = _("Create a new security group") def update_parser_common(self, parser): parser.add_argument( @@ -165,7 +166,7 @@ class CreateSecurityGroup(common.NetworkAndComputeShowOne): class DeleteSecurityGroup(common.NetworkAndComputeDelete): - """Delete security group(s)""" + _description = _("Delete security group(s)") # Used by base class to find resources in parsed_args. resource = 'group' @@ -190,7 +191,7 @@ class DeleteSecurityGroup(common.NetworkAndComputeDelete): class ListSecurityGroup(common.NetworkAndComputeLister): - """List security groups""" + _description = _("List security groups") def update_parser_network(self, parser): # Maintain and hide the argument for backwards compatibility. @@ -201,6 +202,13 @@ class ListSecurityGroup(common.NetworkAndComputeLister): default=False, help=argparse.SUPPRESS, ) + parser.add_argument( + '--project', + metavar='<project>', + help=_("List security groups according to the project " + "(name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) return parser def update_parser_compute(self, parser): @@ -228,7 +236,16 @@ class ListSecurityGroup(common.NetworkAndComputeLister): ) for s in data)) def take_action_network(self, client, parsed_args): - return self._get_return_data(client.security_groups()) + filters = {} + if parsed_args.project: + identity_client = self.app.client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + filters['tenant_id'] = project_id + return self._get_return_data(client.security_groups(**filters)) def take_action_compute(self, client, parsed_args): search = {'all_tenants': parsed_args.all_projects} @@ -238,7 +255,7 @@ class ListSecurityGroup(common.NetworkAndComputeLister): class SetSecurityGroup(common.NetworkAndComputeCommand): - """Set security group properties""" + _description = _("Set security group properties") def update_parser_common(self, parser): parser.add_argument( @@ -293,7 +310,7 @@ class SetSecurityGroup(common.NetworkAndComputeCommand): class ShowSecurityGroup(common.NetworkAndComputeShowOne): - """Display security group details""" + _description = _("Display security group details") def update_parser_common(self, parser): parser.add_argument( diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index e3be44ec..b878d875 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -14,6 +14,7 @@ """Security Group Rule action implementations""" import argparse +import logging try: from novaclient.v2 import security_group_rules as compute_secgroup_rules @@ -28,9 +29,13 @@ import six from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common +from openstackclient.network import sdk_utils from openstackclient.network import utils as network_utils +LOG = logging.getLogger(__name__) + + def _format_security_group_rule_show(obj): data = network_utils.transform_compute_security_group_rule(obj) return zip(*sorted(six.iteritems(data))) @@ -63,11 +68,10 @@ def _format_network_port_range(rule): def _get_columns(item): - columns = list(item.keys()) - if 'tenant_id' in columns: - columns.remove('tenant_id') - columns.append('project_id') - return tuple(sorted(columns)) + column_map = { + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _convert_to_lowercase(string): @@ -85,8 +89,10 @@ def _is_icmp_protocol(protocol): return False +# TODO(abhiraut): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): - """Create a new security group rule""" + _description = _("Create a new security group rule") def update_parser_common(self, parser): parser.add_argument( @@ -94,22 +100,40 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): metavar='<group>', help=_("Create rule in this security group (name or ID)") ) - source_group = parser.add_mutually_exclusive_group() - source_group.add_argument( + remote_group = parser.add_mutually_exclusive_group() + remote_group.add_argument( + "--remote-ip", + metavar="<ip-address>", + help=_("Remote IP address block (may use CIDR notation; " + "default for IPv4 rule: 0.0.0.0/0)"), + ) + remote_group.add_argument( + "--remote-group", + metavar="<group>", + help=_("Remote security group (name or ID)"), + ) + # Handle deprecated options + # NOTE(dtroyer): --src-ip and --src-group were deprecated in Nov 2016. + # Do not remove before 4.x release or Nov 2017. + remote_group.add_argument( "--src-ip", metavar="<ip-address>", - help=_("Source IP address block (may use CIDR notation; " - "default for IPv4 rule: 0.0.0.0/0)") + help=argparse.SUPPRESS, ) - source_group.add_argument( + remote_group.add_argument( "--src-group", metavar="<group>", - help=_("Source security group (name or ID)") + help=argparse.SUPPRESS, ) return parser def update_parser_network(self, parser): parser.add_argument( + '--description', + metavar='<description>', + help=_("Set security group rule description") + ) + parser.add_argument( '--dst-port', metavar='<port-range>', action=parseractions.RangeAction, @@ -235,6 +259,9 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): attrs = {} attrs['protocol'] = self._get_protocol(parsed_args) + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + # NOTE(rtheis): A direction must be specified and ingress # is the default. if parsed_args.ingress or not parsed_args.egress: @@ -277,13 +304,31 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): if parsed_args.icmp_code: attrs['port_range_max'] = parsed_args.icmp_code - if parsed_args.src_group is not None: + # NOTE(dtroyer): --src-ip and --src-group were deprecated in Nov 2016. + # Do not remove before 4.x release or Nov 2017. + if not (parsed_args.remote_group is None and + parsed_args.src_group is None): attrs['remote_group_id'] = client.find_security_group( - parsed_args.src_group, + parsed_args.remote_group or parsed_args.src_group, ignore_missing=False ).id - elif parsed_args.src_ip is not None: - attrs['remote_ip_prefix'] = parsed_args.src_ip + if parsed_args.src_group: + LOG.warning( + _("The %(old)s option is deprecated, " + "please use %(new)s instead.") % + {'old': '--src-group', 'new': '--remote-group'}, + ) + elif not (parsed_args.remote_ip is None and + parsed_args.src_ip is None): + attrs['remote_ip_prefix'] = ( + parsed_args.remote_ip or parsed_args.src_ip + ) + if parsed_args.src_ip: + LOG.warning( + _("The %(old)s option is deprecated, " + "please use %(new)s instead.") % + {'old': '--src-ip', 'new': '--remote-ip'}, + ) elif attrs['ethertype'] == 'IPv4': attrs['remote_ip_prefix'] = '0.0.0.0/0' attrs['security_group_id'] = security_group_id @@ -298,9 +343,9 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): # Create and show the security group rule. obj = client.create_security_group_rule(**attrs) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) - return (columns, data) + return (display_columns, data) def take_action_compute(self, client, parsed_args): group = utils.find_resource( @@ -312,29 +357,46 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): from_port, to_port = -1, -1 else: from_port, to_port = parsed_args.dst_port - src_ip = None - if parsed_args.src_group is not None: - parsed_args.src_group = utils.find_resource( + + # NOTE(dtroyer): --src-ip and --src-group were deprecated in Nov 2016. + # Do not remove before 4.x release or Nov 2017. + remote_ip = None + if not (parsed_args.remote_group is None and + parsed_args.src_group is None): + parsed_args.remote_group = utils.find_resource( client.security_groups, - parsed_args.src_group, + parsed_args.remote_group or parsed_args.src_group, ).id - if parsed_args.src_ip is not None: - src_ip = parsed_args.src_ip + if parsed_args.src_group: + LOG.warning( + _("The %(old)s option is deprecated, " + "please use %(new)s instead.") % + {'old': '--src-group', 'new': '--remote-group'}, + ) + if not (parsed_args.remote_ip is None and + parsed_args.src_ip is None): + remote_ip = parsed_args.remote_ip or parsed_args.src_ip + if parsed_args.src_ip: + LOG.warning( + _("The %(old)s option is deprecated, " + "please use %(new)s instead.") % + {'old': '--src-ip', 'new': '--remote-ip'}, + ) else: - src_ip = '0.0.0.0/0' + remote_ip = '0.0.0.0/0' obj = client.security_group_rules.create( group.id, protocol, from_port, to_port, - src_ip, - parsed_args.src_group, + remote_ip, + parsed_args.remote_group, ) return _format_security_group_rule_show(obj._info) class DeleteSecurityGroupRule(common.NetworkAndComputeDelete): - """Delete security group rule(s)""" + _description = _("Delete security group rule(s)") # Used by base class to find resources in parsed_args. resource = 'rule' @@ -359,7 +421,7 @@ class DeleteSecurityGroupRule(common.NetworkAndComputeDelete): class ListSecurityGroupRule(common.NetworkAndComputeLister): - """List security group rules""" + _description = _("List security group rules") def update_parser_common(self, parser): parser.add_argument( @@ -380,6 +442,28 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister): help=argparse.SUPPRESS ) parser.add_argument( + '--protocol', + metavar='<protocol>', + type=_convert_to_lowercase, + help=_("List rules by the IP protocol (" + "ah, dhcp, egp, esp, gre, icmp, igmp, " + "ipv6-encap, ipv6-frag, ipv6-icmp, ipv6-nonxt, " + "ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, " + "udp, udplite, vrrp and integer representations [0-255])." + ) + ) + direction_group = parser.add_mutually_exclusive_group() + direction_group.add_argument( + '--ingress', + action='store_true', + help=_("List rules applied to incoming network traffic") + ) + direction_group.add_argument( + '--egress', + action='store_true', + help=_("List rules applied to outgoing network traffic") + ) + parser.add_argument( '--long', action='store_true', default=False, @@ -443,6 +527,14 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister): query = {'security_group_id': security_group_id} else: columns = columns + ('security_group_id',) + + if parsed_args.ingress: + query['direction'] = 'ingress' + if parsed_args.egress: + query['direction'] = 'egress' + if parsed_args.protocol is not None: + query['protocol'] = parsed_args.protocol + rules = list(client.security_group_rules(**query)) # Reformat the rules to display a port range instead @@ -494,7 +586,7 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister): class ShowSecurityGroupRule(common.NetworkAndComputeShowOne): - """Display security group rule details""" + _description = _("Display security group rule details") def update_parser_common(self, parser): parser.add_argument( @@ -507,9 +599,9 @@ class ShowSecurityGroupRule(common.NetworkAndComputeShowOne): def take_action_network(self, client, parsed_args): obj = client.find_security_group_rule(parsed_args.rule, ignore_missing=False) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) - return (columns, data) + return (display_columns, data) def take_action_compute(self, client, parsed_args): # NOTE(rtheis): Unfortunately, compute does not have an API diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 1b778c91..292b7c06 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -23,6 +23,7 @@ from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) @@ -84,6 +85,15 @@ def _get_common_parse_arguments(parser, is_create=True): help=_("DNS server for this subnet " "(repeat option to set multiple DNS servers)") ) + + if not is_create: + parser.add_argument( + '--no-dns-nameservers', + action='store_true', + help=_("Clear existing information of DNS Nameservers. " + "Specify both --dns-nameserver and --no-dns-nameserver " + "to overwrite the current DNS Nameserver information.") + ) parser.add_argument( '--host-route', metavar='destination=<subnet>,gateway=<ip-address>', @@ -117,11 +127,12 @@ def _get_common_parse_arguments(parser, is_create=True): def _get_columns(item): - columns = list(item.keys()) - if 'tenant_id' in columns: - columns.remove('tenant_id') - columns.append('project_id') - return tuple(sorted(columns)) + column_map = { + 'is_dhcp_enabled': 'enable_dhcp', + 'subnet_pool_id': 'subnetpool_id', + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) def convert_entries_to_nexthop(entries): @@ -217,8 +228,10 @@ def _get_attrs(client_manager, parsed_args, is_create=True): return attrs +# TODO(abhiraut): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class CreateSubnet(command.ShowOne): - """Create a subnet""" + _description = _("Create a subnet") def get_parser(self, prog_name): parser = super(CreateSubnet, self).get_parser(prog_name) @@ -323,13 +336,13 @@ class CreateSubnet(command.ShowOne): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_subnet(**attrs) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return (columns, data) + return (display_columns, data) class DeleteSubnet(command.Command): - """Delete subnet(s)""" + _description = _("Delete subnet(s)") def get_parser(self, prog_name): parser = super(DeleteSubnet, self).get_parser(prog_name) @@ -362,8 +375,10 @@ class DeleteSubnet(command.Command): raise exceptions.CommandError(msg) +# TODO(abhiraut): Use only the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class ListSubnet(command.Lister): - """List subnets""" + _description = _("List subnets") def get_parser(self, prog_name): parser = super(ListSubnet, self).get_parser(prog_name) @@ -443,8 +458,10 @@ class ListSubnet(command.Lister): filters['ip_version'] = parsed_args.ip_version if parsed_args.dhcp: filters['enable_dhcp'] = True + filters['is_dhcp_enabled'] = True elif parsed_args.no_dhcp: filters['enable_dhcp'] = False + filters['is_dhcp_enabled'] = False if parsed_args.service_types: filters['service_types'] = parsed_args.service_types if parsed_args.project: @@ -454,6 +471,7 @@ class ListSubnet(command.Lister): parsed_args.project_domain, ).id filters['tenant_id'] = project_id + filters['project_id'] = project_id if parsed_args.network: network_id = network_client.find_network(parsed_args.network, ignore_missing=False).id @@ -472,7 +490,7 @@ class ListSubnet(command.Lister): headers += ('Project', 'DHCP', 'Name Servers', 'Allocation Pools', 'Host Routes', 'IP Version', 'Gateway', 'Service Types') - columns += ('tenant_id', 'enable_dhcp', 'dns_nameservers', + columns += ('project_id', 'is_dhcp_enabled', 'dns_nameservers', 'allocation_pools', 'host_routes', 'ip_version', 'gateway_ip', 'service_types') @@ -483,8 +501,10 @@ class ListSubnet(command.Lister): ) for s in data)) +# TODO(abhiraut): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class SetSubnet(command.Command): - """Set subnet properties""" + _description = _("Set subnet properties") def get_parser(self, prog_name): parser = super(SetSubnet, self).get_parser(prog_name) @@ -532,7 +552,10 @@ class SetSubnet(command.Command): attrs = _get_attrs(self.app.client_manager, parsed_args, is_create=False) if 'dns_nameservers' in attrs: - attrs['dns_nameservers'] += obj.dns_nameservers + if not parsed_args.no_dns_nameservers: + attrs['dns_nameservers'] += obj.dns_nameservers + elif parsed_args.no_dns_nameservers: + attrs['dns_nameservers'] = [] if 'host_routes' in attrs: if not parsed_args.no_host_route: attrs['host_routes'] += obj.host_routes @@ -542,7 +565,7 @@ class SetSubnet(command.Command): if not parsed_args.no_allocation_pool: attrs['allocation_pools'] += obj.allocation_pools elif parsed_args.no_allocation_pool: - attrs['allocation_pools'] = '' + attrs['allocation_pools'] = [] if 'service_types' in attrs: attrs['service_types'] += obj.service_types client.update_subnet(obj, **attrs) @@ -550,7 +573,7 @@ class SetSubnet(command.Command): class ShowSubnet(command.ShowOne): - """Display subnet details""" + _description = _("Display subnet details") def get_parser(self, prog_name): parser = super(ShowSubnet, self).get_parser(prog_name) @@ -564,13 +587,13 @@ class ShowSubnet(command.ShowOne): def take_action(self, parsed_args): obj = self.app.client_manager.network.find_subnet(parsed_args.subnet, ignore_missing=False) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return (columns, data) + return (display_columns, data) class UnsetSubnet(command.Command): - """Unset subnet properties""" + _description = _("Unset subnet properties") def get_parser(self, prog_name): parser = super(UnsetSubnet, self).get_parser(prog_name) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index a01d2f7b..a5a24424 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -12,6 +12,7 @@ # """Subnet pool action implementations""" + import copy import logging @@ -22,17 +23,21 @@ from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) def _get_columns(item): - columns = list(item.keys()) - if 'tenant_id' in columns: - columns.remove('tenant_id') - columns.append('project_id') - return tuple(sorted(columns)) + column_map = { + 'default_prefix_length': 'default_prefixlen', + 'is_shared': 'shared', + 'maximum_prefix_length': 'max_prefixlen', + 'minimum_prefix_length': 'min_prefixlen', + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) _formatters = { @@ -134,8 +139,10 @@ def _add_default_options(parser): ) +# TODO(rtheis): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class CreateSubnetPool(command.ShowOne): - """Create subnet pool""" + _description = _("Create subnet pool") def get_parser(self, prog_name): parser = super(CreateSubnetPool, self).get_parser(prog_name) @@ -184,13 +191,13 @@ class CreateSubnetPool(command.ShowOne): if "prefixes" not in attrs: attrs['prefixes'] = [] obj = client.create_subnet_pool(**attrs) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return (columns, data) + return (display_columns, data) class DeleteSubnetPool(command.Command): - """Delete subnet pool(s)""" + _description = _("Delete subnet pool(s)") def get_parser(self, prog_name): parser = super(DeleteSubnetPool, self).get_parser(prog_name) @@ -223,8 +230,10 @@ class DeleteSubnetPool(command.Command): raise exceptions.CommandError(msg) +# TODO(rtheis): Use only the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class ListSubnetPool(command.Lister): - """List subnet pools""" + _description = _("List subnet pools") def get_parser(self, prog_name): parser = super(ListSubnetPool, self).get_parser(prog_name) @@ -238,40 +247,42 @@ class ListSubnetPool(command.Lister): shared_group.add_argument( '--share', action='store_true', - help=_("List subnets shared between projects"), + help=_("List subnet pools shared between projects"), ) shared_group.add_argument( '--no-share', action='store_true', - help=_("List subnets not shared between projects"), + help=_("List subnet pools not shared between projects"), ) default_group = parser.add_mutually_exclusive_group() default_group.add_argument( '--default', action='store_true', - help=_("List subnets used as the default external subnet pool"), + help=_("List subnet pools used as the default external " + "subnet pool"), ) default_group.add_argument( '--no-default', action='store_true', - help=_("List subnets not used as the default external subnet pool") + help=_("List subnet pools not used as the default external " + "subnet pool") ) parser.add_argument( '--project', metavar='<project>', - help=_("List subnets according to their project (name or ID)") + help=_("List subnet pools according to their project (name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--name', metavar='<name>', - help=_("List only subnets of given name in output") + help=_("List only subnet pools of given name in output") ) parser.add_argument( '--address-scope', metavar='<address-scope>', - help=_("List only subnets of given address scope (name or ID) " - "in output") + help=_("List only subnet pools of given address scope " + "(name or ID) in output") ) return parser @@ -281,8 +292,10 @@ class ListSubnetPool(command.Lister): filters = {} if parsed_args.share: filters['shared'] = True + filters['is_shared'] = True elif parsed_args.no_share: filters['shared'] = False + filters['is_shared'] = False if parsed_args.default: filters['is_default'] = True elif parsed_args.no_default: @@ -294,6 +307,7 @@ class ListSubnetPool(command.Lister): parsed_args.project_domain, ).id filters['tenant_id'] = project_id + filters['project_id'] = project_id if parsed_args.name is not None: filters['name'] = parsed_args.name if parsed_args.address_scope: @@ -308,8 +322,8 @@ class ListSubnetPool(command.Lister): if parsed_args.long: headers += ('Default Prefix Length', 'Address Scope', 'Default Subnet Pool', 'Shared') - columns += ('default_prefixlen', 'address_scope_id', - 'is_default', 'shared') + columns += ('default_prefix_length', 'address_scope_id', + 'is_default', 'is_shared') return (headers, (utils.get_item_properties( @@ -318,8 +332,10 @@ class ListSubnetPool(command.Lister): ) for s in data)) +# TODO(rtheis): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class SetSubnetPool(command.Command): - """Set subnet pool properties""" + _description = _("Set subnet pool properties") def get_parser(self, prog_name): parser = super(SetSubnetPool, self).get_parser(prog_name) @@ -371,7 +387,7 @@ class SetSubnetPool(command.Command): class ShowSubnetPool(command.ShowOne): - """Display subnet pool details""" + _description = _("Display subnet pool details") def get_parser(self, prog_name): parser = super(ShowSubnetPool, self).get_parser(prog_name) @@ -388,13 +404,13 @@ class ShowSubnetPool(command.ShowOne): parsed_args.subnet_pool, ignore_missing=False ) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return (columns, data) + return (display_columns, data) class UnsetSubnetPool(command.Command): - """Unset subnet pool properties""" + _description = _("Unset subnet pool properties") def get_parser(self, prog_name): parser = super(UnsetSubnetPool, self).get_parser(prog_name) diff --git a/openstackclient/object/v1/account.py b/openstackclient/object/v1/account.py index 801fe450..4847f8bb 100644 --- a/openstackclient/object/v1/account.py +++ b/openstackclient/object/v1/account.py @@ -18,9 +18,11 @@ from osc_lib.command import command from osc_lib import utils import six +from openstackclient.i18n import _ + class SetAccount(command.Command): - """Set account properties""" + _description = _("Set account properties") def get_parser(self, prog_name): parser = super(SetAccount, self).get_parser(prog_name) @@ -29,8 +31,8 @@ class SetAccount(command.Command): metavar="<key=value>", required=True, action=parseractions.KeyValueAction, - help="Set a property on this account " - "(repeat option to set multiple properties)" + help=_("Set a property on this account " + "(repeat option to set multiple properties)") ) return parser @@ -41,7 +43,7 @@ class SetAccount(command.Command): class ShowAccount(command.ShowOne): - """Display account details""" + _description = _("Display account details") def take_action(self, parsed_args): data = self.app.client_manager.object_store.account_show() @@ -51,7 +53,7 @@ class ShowAccount(command.ShowOne): class UnsetAccount(command.Command): - """Unset account properties""" + _description = _("Unset account properties") def get_parser(self, prog_name): parser = super(UnsetAccount, self).get_parser(prog_name) @@ -61,8 +63,8 @@ class UnsetAccount(command.Command): required=True, action='append', default=[], - help='Property to remove from account ' - '(repeat option to remove multiple properties)', + help=_('Property to remove from account ' + '(repeat option to remove multiple properties)'), ) return parser diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index 2f0d4ac2..88fb8602 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__) class CreateContainer(command.Lister): - """Create new container""" + _description = _("Create new container") def get_parser(self, prog_name): parser = super(CreateContainer, self).get_parser(prog_name) @@ -37,7 +37,7 @@ class CreateContainer(command.Lister): 'containers', metavar='<container-name>', nargs="+", - help='New container name(s)', + help=_('New container name(s)'), ) return parser @@ -63,7 +63,7 @@ class CreateContainer(command.Lister): class DeleteContainer(command.Command): - """Delete container""" + _description = _("Delete container") def get_parser(self, prog_name): parser = super(DeleteContainer, self).get_parser(prog_name) @@ -71,13 +71,13 @@ class DeleteContainer(command.Command): '--recursive', '-r', action='store_true', default=False, - help='Recursively delete objects and container', + help=_('Recursively delete objects and container'), ) parser.add_argument( 'containers', metavar='<container>', nargs="+", - help='Container(s) to delete', + help=_('Container(s) to delete'), ) return parser @@ -98,42 +98,42 @@ class DeleteContainer(command.Command): class ListContainer(command.Lister): - """List containers""" + _description = _("List containers") def get_parser(self, prog_name): parser = super(ListContainer, self).get_parser(prog_name) parser.add_argument( "--prefix", metavar="<prefix>", - help="Filter list using <prefix>", + help=_("Filter list using <prefix>"), ) parser.add_argument( "--marker", metavar="<marker>", - help="Anchor for paging", + help=_("Anchor for paging"), ) parser.add_argument( "--end-marker", metavar="<end-marker>", - help="End anchor for paging", + help=_("End anchor for paging"), ) parser.add_argument( "--limit", metavar="<limit>", type=int, - help="Limit the number of containers returned", + help=_("Limit the number of containers returned"), ) parser.add_argument( '--long', action='store_true', default=False, - help='List additional fields in output', + help=_('List additional fields in output'), ) parser.add_argument( '--all', action='store_true', default=False, - help='List all containers (default is 10000)', + help=_('List all containers (default is 10000)'), ) return parser @@ -168,14 +168,14 @@ class ListContainer(command.Lister): class SaveContainer(command.Command): - """Save container contents locally""" + _description = _("Save container contents locally") def get_parser(self, prog_name): parser = super(SaveContainer, self).get_parser(prog_name) parser.add_argument( 'container', metavar='<container>', - help='Container to save', + help=_('Container to save'), ) return parser @@ -186,22 +186,22 @@ class SaveContainer(command.Command): class SetContainer(command.Command): - """Set container properties""" + _description = _("Set container properties") def get_parser(self, prog_name): parser = super(SetContainer, self).get_parser(prog_name) parser.add_argument( 'container', metavar='<container>', - help='Container to modify', + help=_('Container to modify'), ) parser.add_argument( "--property", metavar="<key=value>", required=True, action=parseractions.KeyValueAction, - help="Set a property on this container " - "(repeat option to set multiple properties)" + help=_("Set a property on this container " + "(repeat option to set multiple properties)") ) return parser @@ -213,14 +213,14 @@ class SetContainer(command.Command): class ShowContainer(command.ShowOne): - """Display container details""" + _description = _("Display container details") def get_parser(self, prog_name): parser = super(ShowContainer, self).get_parser(prog_name) parser.add_argument( 'container', metavar='<container>', - help='Container to display', + help=_('Container to display'), ) return parser @@ -236,14 +236,14 @@ class ShowContainer(command.ShowOne): class UnsetContainer(command.Command): - """Unset container properties""" + _description = _("Unset container properties") def get_parser(self, prog_name): parser = super(UnsetContainer, self).get_parser(prog_name) parser.add_argument( 'container', metavar='<container>', - help='Container to modify', + help=_('Container to modify'), ) parser.add_argument( '--property', @@ -251,8 +251,8 @@ class UnsetContainer(command.Command): required=True, action='append', default=[], - help='Property to remove from container ' - '(repeat option to remove multiple properties)', + help=_('Property to remove from container ' + '(repeat option to remove multiple properties)'), ) return parser diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index 88f6815e..71b6f520 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -30,26 +30,26 @@ LOG = logging.getLogger(__name__) class CreateObject(command.Lister): - """Upload object to container""" + _description = _("Upload object to container") def get_parser(self, prog_name): parser = super(CreateObject, self).get_parser(prog_name) parser.add_argument( 'container', metavar='<container>', - help='Container for new object', + help=_('Container for new object'), ) parser.add_argument( 'objects', metavar='<filename>', nargs="+", - help='Local filename(s) to upload', + help=_('Local filename(s) to upload'), ) parser.add_argument( '--name', metavar='<name>', - help='Upload a file and rename it. ' - 'Can only be used when uploading a single object' + help=_('Upload a file and rename it. ' + 'Can only be used when uploading a single object') ) return parser @@ -81,20 +81,20 @@ class CreateObject(command.Lister): class DeleteObject(command.Command): - """Delete object from container""" + _description = _("Delete object from container") def get_parser(self, prog_name): parser = super(DeleteObject, self).get_parser(prog_name) parser.add_argument( 'container', metavar='<container>', - help='Delete object(s) from <container>', + help=_('Delete object(s) from <container>'), ) parser.add_argument( 'objects', metavar='<object>', nargs="+", - help='Object(s) to delete', + help=_('Object(s) to delete'), ) return parser @@ -108,52 +108,52 @@ class DeleteObject(command.Command): class ListObject(command.Lister): - """List objects""" + _description = _("List objects") def get_parser(self, prog_name): parser = super(ListObject, self).get_parser(prog_name) parser.add_argument( "container", metavar="<container>", - help="Container to list", + help=_("Container to list"), ) parser.add_argument( "--prefix", metavar="<prefix>", - help="Filter list using <prefix>", + help=_("Filter list using <prefix>"), ) parser.add_argument( "--delimiter", metavar="<delimiter>", - help="Roll up items with <delimiter>", + help=_("Roll up items with <delimiter>"), ) parser.add_argument( "--marker", metavar="<marker>", - help="Anchor for paging", + help=_("Anchor for paging"), ) parser.add_argument( "--end-marker", metavar="<end-marker>", - help="End anchor for paging", + help=_("End anchor for paging"), ) parser.add_argument( "--limit", metavar="<limit>", type=int, - help="Limit the number of objects returned", + help=_("Limit the number of objects returned"), ) parser.add_argument( '--long', action='store_true', default=False, - help='List additional fields in output', + help=_('List additional fields in output'), ) parser.add_argument( '--all', action='store_true', default=False, - help='List all objects in container (default is 10000)', + help=_('List all objects in container (default is 10000)'), ) return parser @@ -197,24 +197,24 @@ class ListObject(command.Lister): class SaveObject(command.Command): - """Save object locally""" + _description = _("Save object locally") def get_parser(self, prog_name): parser = super(SaveObject, self).get_parser(prog_name) parser.add_argument( "--file", metavar="<filename>", - help="Destination filename (defaults to object name)", + help=_("Destination filename (defaults to object name)"), ) parser.add_argument( 'container', metavar='<container>', - help='Download <object> from <container>', + help=_('Download <object> from <container>'), ) parser.add_argument( "object", metavar="<object>", - help="Object to save", + help=_("Object to save"), ) return parser @@ -227,27 +227,27 @@ class SaveObject(command.Command): class SetObject(command.Command): - """Set object properties""" + _description = _("Set object properties") def get_parser(self, prog_name): parser = super(SetObject, self).get_parser(prog_name) parser.add_argument( 'container', metavar='<container>', - help='Modify <object> from <container>', + help=_('Modify <object> from <container>'), ) parser.add_argument( 'object', metavar='<object>', - help='Object to modify', + help=_('Object to modify'), ) parser.add_argument( "--property", metavar="<key=value>", required=True, action=parseractions.KeyValueAction, - help="Set a property on this object " - "(repeat option to set multiple properties)" + help=_("Set a property on this object " + "(repeat option to set multiple properties)") ) return parser @@ -260,19 +260,19 @@ class SetObject(command.Command): class ShowObject(command.ShowOne): - """Display object details""" + _description = _("Display object details") def get_parser(self, prog_name): parser = super(ShowObject, self).get_parser(prog_name) parser.add_argument( 'container', metavar='<container>', - help='Display <object> from <container>', + help=_('Display <object> from <container>'), ) parser.add_argument( 'object', metavar='<object>', - help='Object to display', + help=_('Object to display'), ) return parser @@ -289,19 +289,19 @@ class ShowObject(command.ShowOne): class UnsetObject(command.Command): - """Unset object properties""" + _description = _("Unset object properties") def get_parser(self, prog_name): parser = super(UnsetObject, self).get_parser(prog_name) parser.add_argument( 'container', metavar='<container>', - help='Modify <object> from <container>', + help=_('Modify <object> from <container>'), ) parser.add_argument( 'object', metavar='<object>', - help='Object to modify', + help=_('Object to modify'), ) parser.add_argument( '--property', @@ -309,8 +309,8 @@ class UnsetObject(command.Command): required=True, action='append', default=[], - help='Property to remove from object ' - '(repeat option to remove multiple properties)', + help=_('Property to remove from object ' + '(repeat option to remove multiple properties)'), ) return parser diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 3971b6ef..e08eee61 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -93,10 +93,12 @@ class OpenStackShell(shell.OpenStackShell): mod_versions = getattr(mod, 'API_VERSIONS', None) if not skip_old_check and mod_versions: if version_opt not in mod_versions: + sorted_versions = sorted( + mod.API_VERSIONS.keys(), + key=lambda s: list(map(int, s.split('.')))) self.log.warning( - "%s version %s is not in supported versions %s" - % (api, version_opt, - ', '.join(list(mod.API_VERSIONS.keys())))) + "%s version %s is not in supported versions: %s" + % (api, version_opt, ', '.join(sorted_versions))) # Command groups deal only with major versions version = '.v' + version_opt.replace('.', '_').split('_')[0] @@ -146,10 +148,10 @@ class OpenStackShell(shell.OpenStackShell): 'auth_type': self._auth_type, }, ) - except (IOError, OSError) as e: + except (IOError, OSError): self.log.critical("Could not read clouds.yaml configuration file") self.print_help_if_requested() - raise e + raise if not self.options.debug: self.options.debug = None diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py index 298b2454..885abc02 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -63,8 +63,8 @@ class TestCase(testtools.TestCase): return cls.openstack('extension list ' + opts) @classmethod - def get_opts(cls, fields, format='value'): - return ' -f {0} {1}'.format(format, + def get_opts(cls, fields, output_format='value'): + return ' -f {0} {1}'.format(output_format, ' '.join(['-c ' + it for it in fields])) @classmethod diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py new file mode 100644 index 00000000..07dea31b --- /dev/null +++ b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py @@ -0,0 +1,55 @@ +# Copyright (c) 2016, Intel Corporation. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from openstackclient.tests.functional import base + + +class QosPolicyTests(base.TestCase): + """Functional tests for QoS policy. """ + NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + opts = cls.get_opts(cls.FIELDS) + raw_output = cls.openstack('network qos policy create ' + cls.NAME + + opts) + cls.assertOutput(cls.NAME + "\n", raw_output) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('network qos policy delete ' + cls.NAME) + cls.assertOutput('', raw_output) + + def test_qos_policy_list(self): + opts = self.get_opts(self.HEADERS) + raw_output = self.openstack('network qos policy list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_qos_policy_show(self): + opts = self.get_opts(self.FIELDS) + raw_output = self.openstack('network qos policy show ' + self.NAME + + opts) + self.assertEqual(self.NAME + "\n", raw_output) + + def test_qos_policy_set(self): + self.openstack('network qos policy set --share ' + self.NAME) + opts = self.get_opts(['shared']) + raw_output = self.openstack('network qos policy show ' + self.NAME + + opts) + self.assertEqual("True\n", raw_output) diff --git a/openstackclient/tests/functional/network/v2/test_network_service_provider.py b/openstackclient/tests/functional/network/v2/test_network_service_provider.py new file mode 100644 index 00000000..379de430 --- /dev/null +++ b/openstackclient/tests/functional/network/v2/test_network_service_provider.py @@ -0,0 +1,26 @@ +# Copyright (c) 2016, Intel Corporation. +# 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. + +from openstackclient.tests.functional import base + + +class TestNetworkServiceProvider(base.TestCase): + """Functional tests for network service provider""" + + SERVICE_TYPE = ['L3_ROUTER_NAT'] + + def test_network_service_provider_list(self): + raw_output = self.openstack('network service provider list') + self.assertIn(self.SERVICE_TYPE, raw_output) diff --git a/openstackclient/tests/functional/volume/v1/test_snapshot.py b/openstackclient/tests/functional/volume/v1/test_snapshot.py index c6d65ccc..1e1c6b21 100644 --- a/openstackclient/tests/functional/volume/v1/test_snapshot.py +++ b/openstackclient/tests/functional/volume/v1/test_snapshot.py @@ -16,8 +16,8 @@ import uuid from openstackclient.tests.functional.volume.v1 import common -class SnapshotTests(common.BaseVolumeTests): - """Functional tests for snapshot. """ +class VolumeSnapshotTests(common.BaseVolumeTests): + """Functional tests for volume snapshot. """ VOLLY = uuid.uuid4().hex NAME = uuid.uuid4().hex @@ -36,24 +36,25 @@ class SnapshotTests(common.BaseVolumeTests): @classmethod def setUpClass(cls): - super(SnapshotTests, cls).setUpClass() + super(VolumeSnapshotTests, cls).setUpClass() cls.openstack('volume create --size 1 ' + cls.VOLLY) cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 3) opts = cls.get_opts(['status']) - raw_output = cls.openstack('snapshot create --name ' + cls.NAME + - ' ' + cls.VOLLY + opts) + raw_output = cls.openstack('volume snapshot create --volume ' + + cls.VOLLY + ' ' + cls.NAME + opts) cls.assertOutput('creating\n', raw_output) - cls.wait_for_status('snapshot show ' + cls.NAME, 'available\n', 3) + cls.wait_for_status( + 'volume snapshot show ' + cls.NAME, 'available\n', 3) @classmethod def tearDownClass(cls): # Rename test raw_output = cls.openstack( - 'snapshot set --name ' + cls.OTHER_NAME + ' ' + cls.NAME) + 'volume snapshot set --name ' + cls.OTHER_NAME + ' ' + cls.NAME) cls.assertOutput('', raw_output) # Delete test raw_output_snapshot = cls.openstack( - 'snapshot delete ' + cls.OTHER_NAME) + 'volume snapshot delete ' + cls.OTHER_NAME) cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 6) raw_output_volume = cls.openstack('volume delete --force ' + cls.VOLLY) cls.assertOutput('', raw_output_snapshot) @@ -61,26 +62,27 @@ class SnapshotTests(common.BaseVolumeTests): def test_snapshot_list(self): opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('snapshot list' + opts) + raw_output = self.openstack('volume snapshot list' + opts) self.assertIn(self.NAME, raw_output) def test_snapshot_set_unset_properties(self): raw_output = self.openstack( - 'snapshot set --property a=b --property c=d ' + self.NAME) + 'volume snapshot set --property a=b --property c=d ' + self.NAME) self.assertEqual("", raw_output) opts = self.get_opts(["properties"]) - raw_output = self.openstack('snapshot show ' + self.NAME + opts) + raw_output = self.openstack('volume snapshot show ' + self.NAME + opts) self.assertEqual("a='b', c='d'\n", raw_output) - raw_output = self.openstack('snapshot unset --property a ' + self.NAME) + raw_output = self.openstack( + 'volume snapshot unset --property a ' + self.NAME) self.assertEqual("", raw_output) - raw_output = self.openstack('snapshot show ' + self.NAME + opts) + raw_output = self.openstack('volume snapshot show ' + self.NAME + opts) self.assertEqual("c='d'\n", raw_output) def test_snapshot_set_description(self): raw_output = self.openstack( - 'snapshot set --description backup ' + self.NAME) + 'volume snapshot set --description backup ' + self.NAME) self.assertEqual("", raw_output) opts = self.get_opts(["display_description", "display_name"]) - raw_output = self.openstack('snapshot show ' + self.NAME + opts) + raw_output = self.openstack('volume snapshot show ' + self.NAME + opts) self.assertEqual("backup\n" + self.NAME + "\n", raw_output) diff --git a/openstackclient/tests/functional/volume/v2/test_snapshot.py b/openstackclient/tests/functional/volume/v2/test_snapshot.py index fcbc31cb..4eb69e9d 100644 --- a/openstackclient/tests/functional/volume/v2/test_snapshot.py +++ b/openstackclient/tests/functional/volume/v2/test_snapshot.py @@ -16,8 +16,8 @@ import uuid from openstackclient.tests.functional.volume.v2 import common -class SnapshotTests(common.BaseVolumeTests): - """Functional tests for snapshot. """ +class VolumeSnapshotTests(common.BaseVolumeTests): + """Functional tests for volume snapshot. """ VOLLY = uuid.uuid4().hex NAME = uuid.uuid4().hex @@ -36,24 +36,25 @@ class SnapshotTests(common.BaseVolumeTests): @classmethod def setUpClass(cls): - super(SnapshotTests, cls).setUpClass() + super(VolumeSnapshotTests, cls).setUpClass() cls.openstack('volume create --size 1 ' + cls.VOLLY) cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 3) opts = cls.get_opts(['status']) - raw_output = cls.openstack('snapshot create --name ' + cls.NAME + - ' ' + cls.VOLLY + opts) + raw_output = cls.openstack('volume snapshot create --volume ' + + cls.VOLLY + ' ' + cls.NAME + opts) cls.assertOutput('creating\n', raw_output) - cls.wait_for_status('snapshot show ' + cls.NAME, 'available\n', 3) + cls.wait_for_status( + 'volume snapshot show ' + cls.NAME, 'available\n', 3) @classmethod def tearDownClass(cls): # Rename test raw_output = cls.openstack( - 'snapshot set --name ' + cls.OTHER_NAME + ' ' + cls.NAME) + 'volume snapshot set --name ' + cls.OTHER_NAME + ' ' + cls.NAME) cls.assertOutput('', raw_output) # Delete test raw_output_snapshot = cls.openstack( - 'snapshot delete ' + cls.OTHER_NAME) + 'volume snapshot delete ' + cls.OTHER_NAME) cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 6) raw_output_volume = cls.openstack('volume delete --force ' + cls.VOLLY) cls.assertOutput('', raw_output_snapshot) @@ -61,26 +62,27 @@ class SnapshotTests(common.BaseVolumeTests): def test_snapshot_list(self): opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('snapshot list' + opts) + raw_output = self.openstack('volume snapshot list' + opts) self.assertIn(self.NAME, raw_output) def test_snapshot_properties(self): raw_output = self.openstack( - 'snapshot set --property a=b --property c=d ' + self.NAME) + 'volume snapshot set --property a=b --property c=d ' + self.NAME) self.assertEqual("", raw_output) opts = self.get_opts(["properties"]) - raw_output = self.openstack('snapshot show ' + self.NAME + opts) + raw_output = self.openstack('volume snapshot show ' + self.NAME + opts) self.assertEqual("a='b', c='d'\n", raw_output) - raw_output = self.openstack('snapshot unset --property a ' + self.NAME) + raw_output = self.openstack( + 'volume snapshot unset --property a ' + self.NAME) self.assertEqual("", raw_output) - raw_output = self.openstack('snapshot show ' + self.NAME + opts) + raw_output = self.openstack('volume snapshot show ' + self.NAME + opts) self.assertEqual("c='d'\n", raw_output) def test_snapshot_set(self): raw_output = self.openstack( - 'snapshot set --description backup ' + self.NAME) + 'volume snapshot set --description backup ' + self.NAME) self.assertEqual("", raw_output) opts = self.get_opts(["description", "name"]) - raw_output = self.openstack('snapshot show ' + self.NAME + opts) + raw_output = self.openstack('volume snapshot show ' + self.NAME + opts) self.assertEqual("backup\n" + self.NAME + "\n", raw_output) diff --git a/openstackclient/tests/functional/volume/v2/test_volume.py b/openstackclient/tests/functional/volume/v2/test_volume.py index fb880578..ea891cba 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume.py +++ b/openstackclient/tests/functional/volume/v2/test_volume.py @@ -106,11 +106,12 @@ class VolumeTests(common.BaseVolumeTests): opts = self.get_opts(self.FIELDS) # Create snapshot from test volume - raw_output = self.openstack('snapshot create ' + self.NAME + - ' --name ' + self.SNAPSHOT_NAME + opts) + raw_output = self.openstack('volume snapshot create ' + + self.SNAPSHOT_NAME + + ' --volume ' + self.NAME + opts) expected = self.SNAPSHOT_NAME + '\n' self.assertOutput(expected, raw_output) - self.wait_for("snapshot", self.SNAPSHOT_NAME, "available") + self.wait_for("volume snapshot", self.SNAPSHOT_NAME, "available") # Create volume from snapshot raw_output = self.openstack('volume create --size 2 --snapshot ' + @@ -126,7 +127,8 @@ class VolumeTests(common.BaseVolumeTests): self.assertOutput('', raw_output) # Delete test snapshot - raw_output = self.openstack('snapshot delete ' + self.SNAPSHOT_NAME) + raw_output = self.openstack( + 'volume snapshot delete ' + self.SNAPSHOT_NAME) self.assertOutput('', raw_output) self.wait_for("volume", self.NAME, "available") diff --git a/openstackclient/tests/unit/compute/v2/test_hypervisor.py b/openstackclient/tests/unit/compute/v2/test_hypervisor.py index e39570af..7200d04e 100644 --- a/openstackclient/tests/unit/compute/v2/test_hypervisor.py +++ b/openstackclient/tests/unit/compute/v2/test_hypervisor.py @@ -48,19 +48,63 @@ class TestHypervisorList(TestHypervisor): self.columns = ( "ID", - "Hypervisor Hostname" + "Hypervisor Hostname", + "Hypervisor Type", + "Host IP", + "State" + ) + self.columns_long = ( + "ID", + "Hypervisor Hostname", + "Hypervisor Type", + "Host IP", + "State", + "vCPUs Used", + "vCPUs", + "Memory MB Used", + "Memory MB" ) self.data = ( ( self.hypervisors[0].id, self.hypervisors[0].hypervisor_hostname, + self.hypervisors[0].hypervisor_type, + self.hypervisors[0].host_ip, + self.hypervisors[0].state ), ( self.hypervisors[1].id, self.hypervisors[1].hypervisor_hostname, + self.hypervisors[1].hypervisor_type, + self.hypervisors[1].host_ip, + self.hypervisors[1].state ), ) + self.data_long = ( + ( + self.hypervisors[0].id, + self.hypervisors[0].hypervisor_hostname, + self.hypervisors[0].hypervisor_type, + self.hypervisors[0].host_ip, + self.hypervisors[0].state, + self.hypervisors[0].vcpus_used, + self.hypervisors[0].vcpus, + self.hypervisors[0].memory_mb_used, + self.hypervisors[0].memory_mb + ), + ( + self.hypervisors[1].id, + self.hypervisors[1].hypervisor_hostname, + self.hypervisors[1].hypervisor_type, + self.hypervisors[1].host_ip, + self.hypervisors[1].state, + self.hypervisors[1].vcpus_used, + self.hypervisors[1].vcpus, + self.hypervisors[1].memory_mb_used, + self.hypervisors[1].memory_mb + ), + ) # Get the command object to test self.cmd = hypervisor.ListHypervisor(self.app, None) @@ -93,6 +137,9 @@ class TestHypervisorList(TestHypervisor): ( self.hypervisors[0].id, self.hypervisors[0].hypervisor_hostname, + self.hypervisors[1].hypervisor_type, + self.hypervisors[1].host_ip, + self.hypervisors[1].state, ), ) @@ -123,6 +170,24 @@ class TestHypervisorList(TestHypervisor): self.cmd.take_action, parsed_args) + def test_hypervisor_list_long_option(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + self.hypervisors_mock.list.assert_called_with() + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, tuple(data)) + class TestHypervisorShow(TestHypervisor): diff --git a/openstackclient/tests/unit/compute/v2/test_keypair.py b/openstackclient/tests/unit/compute/v2/test_keypair.py index cb008545..efc5463c 100644 --- a/openstackclient/tests/unit/compute/v2/test_keypair.py +++ b/openstackclient/tests/unit/compute/v2/test_keypair.py @@ -179,8 +179,7 @@ class TestKeypairDelete(TestKeypair): self.cmd.take_action(parsed_args) self.fail('CommandError should be raised.') except exceptions.CommandError as e: - self.assertEqual('1 of 2 public keys failed to delete.', - str(e)) + self.assertEqual('1 of 2 keys failed to delete.', str(e)) find_mock.assert_any_call( self.keypairs_mock, self.keypairs[0].name) diff --git a/openstackclient/tests/unit/identity/v3/test_unscoped_saml.py b/openstackclient/tests/unit/identity/v3/test_unscoped_saml.py index 9e4e1876..34655263 100644 --- a/openstackclient/tests/unit/identity/v3/test_unscoped_saml.py +++ b/openstackclient/tests/unit/identity/v3/test_unscoped_saml.py @@ -12,8 +12,6 @@ import copy -from osc_lib import exceptions - from openstackclient.identity.v3 import unscoped_saml from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -48,7 +46,6 @@ class TestDomainList(TestUnscopedSAML): self.cmd = unscoped_saml.ListAccessibleDomains(self.app, None) def test_accessible_domains_list(self): - self.app.client_manager.auth_plugin_name = 'v3unscopedsaml' arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -70,17 +67,6 @@ class TestDomainList(TestUnscopedSAML): ), ) self.assertEqual(datalist, tuple(data)) - def test_accessible_domains_list_wrong_auth(self): - auth = identity_fakes.FakeAuth("wrong auth") - self.app.client_manager.identity.session.auth = auth - arglist = [] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, - parsed_args) - class TestProjectList(TestUnscopedSAML): @@ -99,7 +85,6 @@ class TestProjectList(TestUnscopedSAML): self.cmd = unscoped_saml.ListAccessibleProjects(self.app, None) def test_accessible_projects_list(self): - self.app.client_manager.auth_plugin_name = 'v3unscopedsaml' arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -120,14 +105,3 @@ class TestProjectList(TestUnscopedSAML): identity_fakes.project_name, ), ) self.assertEqual(datalist, tuple(data)) - - def test_accessible_projects_list_wrong_auth(self): - auth = identity_fakes.FakeAuth("wrong auth") - self.app.client_manager.identity.session.auth = auth - arglist = [] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, - parsed_args) diff --git a/openstackclient/tests/unit/image/v1/fakes.py b/openstackclient/tests/unit/image/v1/fakes.py index a8e52fa3..080356ee 100644 --- a/openstackclient/tests/unit/image/v1/fakes.py +++ b/openstackclient/tests/unit/image/v1/fakes.py @@ -13,7 +13,9 @@ # under the License. # +import copy import mock +import uuid from openstackclient.tests.unit import fakes from openstackclient.tests.unit import utils @@ -74,3 +76,45 @@ class TestImagev1(utils.TestCommand): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) + + +class FakeImage(object): + """Fake one or more images.""" + + @staticmethod + def create_one_image(attrs=None): + """Create a fake image. + + :param Dictionary attrs: + A dictionary with all attrbutes of image + :return: + A FakeResource object with id, name, owner, protected, + visibility and tags attrs + """ + attrs = attrs or {} + + # Set default attribute + image_info = { + 'id': str(uuid.uuid4()), + 'name': 'image-name' + uuid.uuid4().hex, + 'owner': 'image-owner' + uuid.uuid4().hex, + 'container_format': '', + 'disk_format': '', + 'min_disk': 0, + 'min_ram': 0, + 'is_public': True, + 'protected': False, + 'properties': { + 'Alpha': 'a', + 'Beta': 'b', + 'Gamma': 'g'}, + } + + # Overwrite default attributes if there are some attributes set + image_info.update(attrs) + + image = fakes.FakeResource( + info=copy.deepcopy(image_info), + loaded=True) + + return image diff --git a/openstackclient/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py index a6bc80a0..036c8336 100644 --- a/openstackclient/tests/unit/image/v1/test_image.py +++ b/openstackclient/tests/unit/image/v1/test_image.py @@ -17,6 +17,7 @@ import copy import mock from osc_lib import exceptions +from osc_lib import utils from openstackclient.image.v1 import image from openstackclient.tests.unit import fakes @@ -35,25 +36,39 @@ class TestImage(image_fakes.TestImagev1): class TestImageCreate(TestImage): + new_image = image_fakes.FakeImage.create_one_image() + columns = ( + 'container_format', + 'disk_format', + 'id', + 'is_public', + 'min_disk', + 'min_ram', + 'name', + 'owner', + 'properties', + 'protected', + ) + data = ( + new_image.container_format, + new_image.disk_format, + new_image.id, + new_image.is_public, + new_image.min_disk, + new_image.min_ram, + new_image.name, + new_image.owner, + utils.format_dict(new_image.properties), + new_image.protected, + ) + def setUp(self): super(TestImageCreate, self).setUp() - self.images_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(image_fakes.IMAGE), - loaded=True, - ) + self.images_mock.create.return_value = self.new_image # This is the return value for utils.find_resource() - self.images_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(image_fakes.IMAGE), - loaded=True, - ) - self.images_mock.update.return_value = fakes.FakeResource( - None, - copy.deepcopy(image_fakes.IMAGE), - loaded=True, - ) + self.images_mock.get.return_value = self.new_image + self.images_mock.update.return_value = self.new_image # Get the command object to test self.cmd = image.CreateImage(self.app, None) @@ -65,12 +80,12 @@ class TestImageCreate(TestImage): } self.images_mock.configure_mock(**mock_exception) arglist = [ - image_fakes.image_name, + self.new_image.name, ] verifylist = [ ('container_format', image.DEFAULT_CONTAINER_FORMAT), ('disk_format', image.DEFAULT_DISK_FORMAT), - ('name', image_fakes.image_name), + ('name', self.new_image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -81,7 +96,7 @@ class TestImageCreate(TestImage): # ImageManager.create(name=, **) self.images_mock.create.assert_called_with( - name=image_fakes.image_name, + name=self.new_image.name, container_format=image.DEFAULT_CONTAINER_FORMAT, disk_format=image.DEFAULT_DISK_FORMAT, data=mock.ANY, @@ -90,8 +105,8 @@ class TestImageCreate(TestImage): # Verify update() was not called, if it was show the args self.assertEqual(self.images_mock.update.call_args_list, []) - self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) def test_image_reserve_options(self): mock_exception = { @@ -101,17 +116,17 @@ class TestImageCreate(TestImage): self.images_mock.configure_mock(**mock_exception) arglist = [ '--container-format', 'ovf', - '--disk-format', 'fs', + '--disk-format', 'ami', '--min-disk', '10', '--min-ram', '4', '--protected', '--private', '--project', 'q', - image_fakes.image_name, + self.new_image.name, ] verifylist = [ ('container_format', 'ovf'), - ('disk_format', 'fs'), + ('disk_format', 'ami'), ('min_disk', 10), ('min_ram', 4), ('protected', True), @@ -119,7 +134,7 @@ class TestImageCreate(TestImage): ('public', False), ('private', True), ('project', 'q'), - ('name', image_fakes.image_name), + ('name', self.new_image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -130,9 +145,9 @@ class TestImageCreate(TestImage): # ImageManager.create(name=, **) self.images_mock.create.assert_called_with( - name=image_fakes.image_name, + name=self.new_image.name, container_format='ovf', - disk_format='fs', + disk_format='ami', min_disk=10, min_ram=4, protected=True, @@ -144,14 +159,14 @@ class TestImageCreate(TestImage): # Verify update() was not called, if it was show the args self.assertEqual(self.images_mock.update.call_args_list, []) - self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) @mock.patch('openstackclient.image.v1.image.io.open', name='Open') def test_image_create_file(self, mock_open): mock_file = mock.Mock(name='File') mock_open.return_value = mock_file - mock_open.read.return_value = image_fakes.image_data + mock_open.read.return_value = self.data mock_exception = { 'find.side_effect': exceptions.CommandError('x'), 'get.side_effect': exceptions.CommandError('x'), @@ -164,7 +179,7 @@ class TestImageCreate(TestImage): '--public', '--property', 'Alpha=1', '--property', 'Beta=2', - image_fakes.image_name, + self.new_image.name, ] verifylist = [ ('file', 'filer'), @@ -173,7 +188,7 @@ class TestImageCreate(TestImage): ('public', True), ('private', False), ('properties', {'Alpha': '1', 'Beta': '2'}), - ('name', image_fakes.image_name), + ('name', self.new_image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -193,7 +208,7 @@ class TestImageCreate(TestImage): # ImageManager.create(name=, **) self.images_mock.create.assert_called_with( - name=image_fakes.image_name, + name=self.new_image.name, container_format=image.DEFAULT_CONTAINER_FORMAT, disk_format=image.DEFAULT_DISK_FORMAT, protected=False, @@ -208,21 +223,19 @@ class TestImageCreate(TestImage): # Verify update() was not called, if it was show the args self.assertEqual(self.images_mock.update.call_args_list, []) - self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) class TestImageDelete(TestImage): + _image = image_fakes.FakeImage.create_one_image() + def setUp(self): super(TestImageDelete, self).setUp() # This is the return value for utils.find_resource() - self.images_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(image_fakes.IMAGE), - loaded=True, - ) + self.images_mock.get.return_value = self._image self.images_mock.delete.return_value = None # Get the command object to test @@ -230,21 +243,23 @@ class TestImageDelete(TestImage): def test_image_delete_no_options(self): arglist = [ - image_fakes.image_id, + self._image.id, ] verifylist = [ - ('images', [image_fakes.image_id]), + ('images', [self._image.id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.images_mock.delete.assert_called_with(image_fakes.image_id) + self.images_mock.delete.assert_called_with(self._image.id) self.assertIsNone(result) class TestImageList(TestImage): + _image = image_fakes.FakeImage.create_one_image() + columns = ( 'ID', 'Name', @@ -252,18 +267,33 @@ class TestImageList(TestImage): ) datalist = ( ( - image_fakes.image_id, - image_fakes.image_name, + _image.id, + _image.name, '', ), ) + # create a image_info as the side_effect of the fake image_list() + info = { + 'id': _image.id, + 'name': _image.name, + 'owner': _image.owner, + 'container_format': _image.container_format, + 'disk_format': _image.disk_format, + 'min_disk': _image.min_disk, + 'min_ram': _image.min_ram, + 'is_public': _image.is_public, + 'protected': _image.protected, + 'properties': _image.properties, + } + image_info = copy.deepcopy(info) + def setUp(self): super(TestImageList, self).setUp() self.api_mock = mock.Mock() self.api_mock.image_list.side_effect = [ - [copy.deepcopy(image_fakes.IMAGE)], [], + [self.image_info], [], ] self.app.client_manager.image.api = self.api_mock @@ -285,7 +315,7 @@ class TestImageList(TestImage): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( detailed=True, - marker=image_fakes.image_id, + marker=self._image.id, ) self.assertEqual(self.columns, columns) @@ -309,7 +339,7 @@ class TestImageList(TestImage): self.api_mock.image_list.assert_called_with( detailed=True, public=True, - marker=image_fakes.image_id, + marker=self._image.id, ) self.assertEqual(self.columns, columns) @@ -333,7 +363,7 @@ class TestImageList(TestImage): self.api_mock.image_list.assert_called_with( detailed=True, private=True, - marker=image_fakes.image_id, + marker=self._image.id, ) self.assertEqual(self.columns, columns) @@ -354,7 +384,7 @@ class TestImageList(TestImage): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( detailed=True, - marker=image_fakes.image_id, + marker=self._image.id, ) collist = ( @@ -373,8 +403,8 @@ class TestImageList(TestImage): self.assertEqual(collist, columns) datalist = (( - image_fakes.image_id, - image_fakes.image_name, + self._image.id, + self._image.name, '', '', '', @@ -382,7 +412,7 @@ class TestImageList(TestImage): '', 'public', False, - image_fakes.image_owner, + self._image.owner, "Alpha='a', Beta='b', Gamma='g'", ), ) self.assertEqual(datalist, tuple(data)) @@ -390,7 +420,7 @@ class TestImageList(TestImage): @mock.patch('openstackclient.api.utils.simple_filter') def test_image_list_property_option(self, sf_mock): sf_mock.side_effect = [ - [copy.deepcopy(image_fakes.IMAGE)], [], + [self.image_info], [], ] arglist = [ @@ -407,10 +437,10 @@ class TestImageList(TestImage): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( detailed=True, - marker=image_fakes.image_id, + marker=self._image.id, ) sf_mock.assert_called_with( - [image_fakes.IMAGE], + [self.image_info], attr='a', value='1', property_field='properties', @@ -422,7 +452,7 @@ class TestImageList(TestImage): @mock.patch('osc_lib.utils.sort_items') def test_image_list_sort_option(self, si_mock): si_mock.side_effect = [ - [copy.deepcopy(image_fakes.IMAGE)], [], + [self.image_info], [], ] arglist = ['--sort', 'name:asc'] @@ -435,10 +465,10 @@ class TestImageList(TestImage): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( detailed=True, - marker=image_fakes.image_id, + marker=self._image.id, ) si_mock.assert_called_with( - [image_fakes.IMAGE], + [self.image_info], 'name:asc' ) @@ -448,36 +478,30 @@ class TestImageList(TestImage): class TestImageSet(TestImage): + _image = image_fakes.FakeImage.create_one_image() + def setUp(self): super(TestImageSet, self).setUp() # This is the return value for utils.find_resource() - self.images_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(image_fakes.IMAGE), - loaded=True, - ) - self.images_mock.update.return_value = fakes.FakeResource( - None, - copy.deepcopy(image_fakes.IMAGE), - loaded=True, - ) + self.images_mock.get.return_value = self._image + self.images_mock.update.return_value = self._image # Get the command object to test self.cmd = image.SetImage(self.app, None) def test_image_set_no_options(self): arglist = [ - image_fakes.image_name, + self._image.name, ] verifylist = [ - ('image', image_fakes.image_name), + ('image', self._image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.images_mock.update.assert_called_with(image_fakes.image_id, + self.images_mock.update.assert_called_with(self._image.id, **{}) self.assertIsNone(result) @@ -490,7 +514,7 @@ class TestImageSet(TestImage): '--disk-format', 'vmdk', '--size', '35165824', '--project', 'new-owner', - image_fakes.image_name, + self._image.name, ] verifylist = [ ('name', 'new-name'), @@ -500,7 +524,7 @@ class TestImageSet(TestImage): ('disk_format', 'vmdk'), ('size', 35165824), ('project', 'new-owner'), - ('image', image_fakes.image_name), + ('image', self._image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -517,7 +541,7 @@ class TestImageSet(TestImage): } # ImageManager.update(image, **kwargs) self.images_mock.update.assert_called_with( - image_fakes.image_id, + self._image.id, **kwargs ) self.assertIsNone(result) @@ -526,14 +550,14 @@ class TestImageSet(TestImage): arglist = [ '--protected', '--private', - image_fakes.image_name, + self._image.name, ] verifylist = [ ('protected', True), ('unprotected', False), ('public', False), ('private', True), - ('image', image_fakes.image_name), + ('image', self._image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -545,7 +569,7 @@ class TestImageSet(TestImage): } # ImageManager.update(image, **kwargs) self.images_mock.update.assert_called_with( - image_fakes.image_id, + self._image.id, **kwargs ) self.assertIsNone(result) @@ -554,14 +578,14 @@ class TestImageSet(TestImage): arglist = [ '--unprotected', '--public', - image_fakes.image_name, + self._image.name, ] verifylist = [ ('protected', False), ('unprotected', True), ('public', True), ('private', False), - ('image', image_fakes.image_name), + ('image', self._image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -573,7 +597,7 @@ class TestImageSet(TestImage): } # ImageManager.update(image, **kwargs) self.images_mock.update.assert_called_with( - image_fakes.image_id, + self._image.id, **kwargs ) self.assertIsNone(result) @@ -582,11 +606,11 @@ class TestImageSet(TestImage): arglist = [ '--property', 'Alpha=1', '--property', 'Beta=2', - image_fakes.image_name, + self._image.name, ] verifylist = [ ('properties', {'Alpha': '1', 'Beta': '2'}), - ('image', image_fakes.image_name), + ('image', self._image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -601,7 +625,7 @@ class TestImageSet(TestImage): } # ImageManager.update(image, **kwargs) self.images_mock.update.assert_called_with( - image_fakes.image_id, + self._image.id, **kwargs ) self.assertIsNone(result) @@ -624,7 +648,7 @@ class TestImageSet(TestImage): "volume_type": 'volume_type', "container_format": image.DEFAULT_CONTAINER_FORMAT, "disk_format": image.DEFAULT_DISK_FORMAT, - "image": image_fakes.image_name, + "image": self._image.name, } full_response = {"os-volume_upload_image": response} volumes_mock.upload_to_image.return_value = (201, full_response) @@ -632,7 +656,7 @@ class TestImageSet(TestImage): arglist = [ '--volume', 'volly', '--name', 'updated_image', - image_fakes.image_name, + self._image.name, ] verifylist = [ ('private', False), @@ -642,7 +666,7 @@ class TestImageSet(TestImage): ('volume', 'volly'), ('force', False), ('name', 'updated_image'), - ('image', image_fakes.image_name), + ('image', self._image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -653,13 +677,13 @@ class TestImageSet(TestImage): volumes_mock.upload_to_image.assert_called_with( 'vol1', False, - image_fakes.image_name, + self._image.name, '', '', ) # ImageManager.update(image_id, remove_props=, **) self.images_mock.update.assert_called_with( - image_fakes.image_id, + self._image.id, name='updated_image', volume='volly', ) @@ -668,24 +692,46 @@ class TestImageSet(TestImage): class TestImageShow(TestImage): + _image = image_fakes.FakeImage.create_one_image() + columns = ( + 'container_format', + 'disk_format', + 'id', + 'is_public', + 'min_disk', + 'min_ram', + 'name', + 'owner', + 'properties', + 'protected', + ) + data = ( + _image.container_format, + _image.disk_format, + _image.id, + _image.is_public, + _image.min_disk, + _image.min_ram, + _image.name, + _image.owner, + utils.format_dict(_image.properties), + _image.protected, + ) + def setUp(self): super(TestImageShow, self).setUp() - self.images_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(image_fakes.IMAGE), - loaded=True, - ) + self.images_mock.get.return_value = self._image # Get the command object to test self.cmd = image.ShowImage(self.app, None) def test_image_show(self): arglist = [ - image_fakes.image_id, + self._image.id, ] verifylist = [ - ('image', image_fakes.image_id), + ('image', self._image.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -694,8 +740,8 @@ class TestImageShow(TestImage): # data to be shown. columns, data = self.cmd.take_action(parsed_args) self.images_mock.get.assert_called_with( - image_fakes.image_id, + self._image.id, ) - self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index ebc9c3a7..a054e513 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -130,7 +130,7 @@ class TestImageCreate(TestImage): self.images_mock.configure_mock(**mock_exception) arglist = [ '--container-format', 'ovf', - '--disk-format', 'fs', + '--disk-format', 'ami', '--min-disk', '10', '--min-ram', '4', ('--protected' @@ -143,7 +143,7 @@ class TestImageCreate(TestImage): ] verifylist = [ ('container_format', 'ovf'), - ('disk_format', 'fs'), + ('disk_format', 'ami'), ('min_disk', 10), ('min_ram', 4), ('protected', self.new_image.protected), @@ -165,7 +165,7 @@ class TestImageCreate(TestImage): self.images_mock.create.assert_called_with( name=self.new_image.name, container_format='ovf', - disk_format='fs', + disk_format='ami', min_disk=10, min_ram=4, owner=self.project.id, @@ -193,7 +193,7 @@ class TestImageCreate(TestImage): arglist = [ '--container-format', 'ovf', - '--disk-format', 'fs', + '--disk-format', 'ami', '--min-disk', '10', '--min-ram', '4', '--owner', 'unexist_owner', @@ -203,7 +203,7 @@ class TestImageCreate(TestImage): ] verifylist = [ ('container_format', 'ovf'), - ('disk_format', 'fs'), + ('disk_format', 'ami'), ('min_disk', 10), ('min_ram', 4), ('owner', 'unexist_owner'), @@ -227,7 +227,7 @@ class TestImageCreate(TestImage): arglist = [ '--container-format', 'ovf', - '--disk-format', 'fs', + '--disk-format', 'ami', '--min-disk', '10', '--min-ram', '4', '--protected', @@ -237,7 +237,7 @@ class TestImageCreate(TestImage): ] verifylist = [ ('container_format', 'ovf'), - ('disk_format', 'fs'), + ('disk_format', 'ami'), ('min_disk', 10), ('min_ram', 4), ('protected', True), @@ -535,7 +535,9 @@ class TestImageList(TestImage): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with() + self.api_mock.image_list.assert_called_with( + marker=self._image.id, + ) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) @@ -558,6 +560,7 @@ class TestImageList(TestImage): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( public=True, + marker=self._image.id, ) self.assertEqual(self.columns, columns) @@ -581,6 +584,7 @@ class TestImageList(TestImage): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( private=True, + marker=self._image.id, ) self.assertEqual(self.columns, columns) @@ -604,6 +608,7 @@ class TestImageList(TestImage): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( shared=True, + marker=self._image.id, ) self.assertEqual(self.columns, columns) @@ -622,7 +627,9 @@ class TestImageList(TestImage): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with() + self.api_mock.image_list.assert_called_with( + marker=self._image.id, + ) collist = ( 'ID', @@ -670,7 +677,9 @@ class TestImageList(TestImage): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with() + self.api_mock.image_list.assert_called_with( + marker=self._image.id, + ) sf_mock.assert_called_with( [self._image], attr='a', @@ -693,7 +702,9 @@ class TestImageList(TestImage): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with() + self.api_mock.image_list.assert_called_with( + marker=self._image.id, + ) si_mock.assert_called_with( [self._image], 'name:asc' @@ -712,7 +723,7 @@ class TestImageList(TestImage): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( - limit=1, + limit=1, marker=self._image.id ) self.assertEqual(self.columns, columns) diff --git a/openstackclient/tests/unit/network/test_common.py b/openstackclient/tests/unit/network/test_common.py index 325aad2a..4b9a754b 100644 --- a/openstackclient/tests/unit/network/test_common.py +++ b/openstackclient/tests/unit/network/test_common.py @@ -14,6 +14,8 @@ import argparse import mock +import openstack +from openstackclient.common import exceptions from openstackclient.network import common from openstackclient.tests.unit import utils @@ -172,3 +174,15 @@ class TestNetworkAndComputeShowOne(TestNetworkAndCompute): def setUp(self): super(TestNetworkAndComputeShowOne, self).setUp() self.cmd = FakeNetworkAndComputeShowOne(self.app, self.namespace) + + def test_take_action_with_http_exception(self): + with mock.patch.object(self.cmd, 'take_action_network') as m_action: + m_action.side_effect = openstack.exceptions.HttpException("bar") + self.assertRaisesRegex(exceptions.CommandError, "bar", + self.cmd.take_action, mock.Mock()) + + self.app.client_manager.network_endpoint_enabled = False + with mock.patch.object(self.cmd, 'take_action_compute') as m_action: + m_action.side_effect = openstack.exceptions.HttpException("bar") + self.assertRaisesRegex(exceptions.CommandError, "bar", + self.cmd.take_action, mock.Mock()) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 527649b8..97d07076 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -298,11 +298,14 @@ class FakeNetwork(object): 'shared': False, 'subnets': ['a', 'b'], 'provider_network_type': 'vlan', + 'provider_physical_network': 'physnet1', + 'provider_segmentation_id': "400", 'router:external': True, 'availability_zones': [], 'availability_zone_hints': [], 'is_default': False, 'port_security_enabled': True, + 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, } # Overwrite default attributes. @@ -434,6 +437,7 @@ class FakePort(object): 'binding:vif_details': {}, 'binding:vif_type': 'ovs', 'binding:vnic_type': 'normal', + 'description': 'description-' + uuid.uuid4().hex, 'device_id': 'device-id-' + uuid.uuid4().hex, 'device_owner': 'compute:nova', 'dns_assignment': [{}], @@ -640,6 +644,157 @@ class FakeNetworkRBAC(object): return mock.Mock(side_effect=rbac_policies) +class FakeNetworkQosBandwidthLimitRule(object): + """Fake one or more QoS bandwidth limit rules.""" + + @staticmethod + def create_one_qos_bandwidth_limit_rule(attrs=None): + """Create a fake QoS bandwidth limit rule. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with id, qos_policy_id, max_kbps and + max_burst_kbps attributes. + """ + attrs = attrs or {} + + # Set default attributes. + qos_bandwidth_limit_rule_attrs = { + 'id': 'qos-bandwidth-limit-rule-id-' + uuid.uuid4().hex, + 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, + 'max_kbps': 1500, + 'max_burst_kbps': 1200, + } + + # Overwrite default attributes. + qos_bandwidth_limit_rule_attrs.update(attrs) + + qos_bandwidth_limit_rule = fakes.FakeResource( + info=copy.deepcopy(qos_bandwidth_limit_rule_attrs), + loaded=True) + + return qos_bandwidth_limit_rule + + @staticmethod + def create_qos_bandwidth_limit_rules(attrs=None, count=2): + """Create multiple fake QoS bandwidth limit rules. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of QoS bandwidth limit rules to fake + :return: + A list of FakeResource objects faking the QoS bandwidth limit rules + """ + qos_policies = [] + for i in range(0, count): + qos_policies.append(FakeNetworkQosBandwidthLimitRule. + create_one_qos_bandwidth_limit_rule(attrs)) + + return qos_policies + + @staticmethod + def get_qos_bandwidth_limit_rules(qos_rules=None, count=2): + """Get a list of faked QoS bandwidth limit rules. + + If QoS bandwidth limit rules list is provided, then initialize the + Mock object with the list. Otherwise create one. + + :param List address scopes: + A list of FakeResource objects faking QoS bandwidth limit rules + :param int count: + The number of QoS bandwidth limit rules to fake + :return: + An iterable Mock object with side_effect set to a list of faked + qos bandwidth limit rules + """ + if qos_rules is None: + qos_rules = (FakeNetworkQosBandwidthLimitRule. + create_qos_bandwidth_limit_rules(count)) + return mock.Mock(side_effect=qos_rules) + + +class FakeNetworkQosPolicy(object): + """Fake one or more QoS policies.""" + + @staticmethod + def create_one_qos_policy(attrs=None): + """Create a fake QoS policy. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with name, id, etc. + """ + attrs = attrs or {} + qos_id = attrs.get('id') or 'qos-policy-id-' + uuid.uuid4().hex + rule_attrs = {'qos_policy_id': qos_id} + rules = [ + FakeNetworkQosBandwidthLimitRule. + create_one_qos_bandwidth_limit_rule(rule_attrs)] + + # Set default attributes. + qos_policy_attrs = { + 'name': 'qos-policy-name-' + uuid.uuid4().hex, + 'id': qos_id, + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'shared': False, + 'description': 'qos-policy-description-' + uuid.uuid4().hex, + 'rules': rules, + } + + # Overwrite default attributes. + qos_policy_attrs.update(attrs) + + qos_policy = fakes.FakeResource( + info=copy.deepcopy(qos_policy_attrs), + loaded=True) + + # Set attributes with special mapping in OpenStack SDK. + qos_policy.is_shared = qos_policy_attrs['shared'] + qos_policy.project_id = qos_policy_attrs['tenant_id'] + + return qos_policy + + @staticmethod + def create_qos_policies(attrs=None, count=2): + """Create multiple fake QoS policies. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of QoS policies to fake + :return: + A list of FakeResource objects faking the QoS policies + """ + qos_policies = [] + for i in range(0, count): + qos_policies.append( + FakeNetworkQosPolicy.create_one_qos_policy(attrs)) + + return qos_policies + + @staticmethod + def get_qos_policies(qos_policies=None, count=2): + """Get an iterable MagicMock object with a list of faked QoS policies. + + If qos policies list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List address scopes: + A list of FakeResource objects faking qos policies + :param int count: + The number of QoS policies to fake + :return: + An iterable Mock object with side_effect set to a list of faked + QoS policies + """ + if qos_policies is None: + qos_policies = FakeNetworkQosPolicy.create_qos_policies(count) + return mock.Mock(side_effect=qos_policies) + + class FakeRouter(object): """Fake one or more routers.""" @@ -808,6 +963,8 @@ class FakeSecurityGroupRule(object): # Set default attributes. security_group_rule_attrs = { + 'description': 'security-group-rule-description-' + + uuid.uuid4().hex, 'direction': 'ingress', 'ethertype': 'IPv4', 'id': 'security-group-rule-id-' + uuid.uuid4().hex, @@ -914,6 +1071,8 @@ class FakeSubnet(object): loaded=True) # Set attributes with special mappings in OpenStack SDK. + subnet.is_dhcp_enabled = subnet_attrs['enable_dhcp'] + subnet.subnet_pool_id = subnet_attrs['subnetpool_id'] subnet.project_id = subnet_attrs['tenant_id'] return subnet @@ -1073,6 +1232,11 @@ class FakeSubnetPool(object): ) # Set attributes with special mapping in OpenStack SDK. + subnet_pool.default_prefix_length = \ + subnet_pool_attrs['default_prefixlen'] + subnet_pool.is_shared = subnet_pool_attrs['shared'] + subnet_pool.maximum_prefix_length = subnet_pool_attrs['max_prefixlen'] + subnet_pool.minimum_prefix_length = subnet_pool_attrs['min_prefixlen'] subnet_pool.project_id = subnet_pool_attrs['tenant_id'] return subnet_pool @@ -1114,3 +1278,38 @@ class FakeSubnetPool(object): if subnet_pools is None: subnet_pools = FakeSubnetPool.create_subnet_pools(count) return mock.Mock(side_effect=subnet_pools) + + +class FakeNetworkServiceProvider(object): + """Fake Network Service Providers""" + + @staticmethod + def create_one_network_service_provider(attrs=None): + """Create service provider""" + attrs = attrs or {} + + service_provider = { + 'name': 'provider-name-' + uuid.uuid4().hex, + 'service_type': 'service-type-' + uuid.uuid4().hex, + 'default': False, + } + + service_provider.update(attrs) + + provider = fakes.FakeResource( + info=copy.deepcopy(service_provider), + loaded=True) + provider.is_default = service_provider['default'] + + return provider + + @staticmethod + def create_network_service_providers(attrs=None, count=2): + """Create multiple service providers""" + + service_providers = [] + for i in range(0, count): + service_providers.append(FakeNetworkServiceProvider. + create_one_network_service_provider( + attrs)) + return service_providers diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip.py b/openstackclient/tests/unit/network/v2/test_floating_ip.py index 1f30f2e9..b3d211ba 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip.py @@ -18,6 +18,7 @@ from osc_lib import exceptions from openstackclient.network.v2 import floating_ip from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils @@ -31,6 +32,7 @@ class TestFloatingIPNetwork(network_fakes.TestNetworkV2): # Get a shortcut to the network client self.network = self.app.client_manager.network + self.projects_mock = self.app.client_manager.identity.projects class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): @@ -145,6 +147,54 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_floating_ip_create_project(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + self.floating_ip.floating_network_id, + ] + verifylist = [ + ('network', self.floating_ip.floating_network_id), + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_ip.assert_called_once_with(**{ + 'floating_network_id': self.floating_ip.floating_network_id, + 'tenant_id': project.id, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_floating_ip_create_project_domain(self): + project = identity_fakes_v3.FakeProject.create_one_project() + domain = identity_fakes_v3.FakeDomain.create_one_domain() + self.projects_mock.get.return_value = project + arglist = [ + "--project", project.name, + "--project-domain", domain.name, + self.floating_ip.floating_network_id, + ] + verifylist = [ + ('network', self.floating_ip.floating_network_id), + ('project', project.name), + ('project_domain', domain.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_ip.assert_called_once_with(**{ + 'floating_network_id': self.floating_ip.floating_network_id, + 'tenant_id': project.id, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork): @@ -231,12 +281,20 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): # The floating ips to list up floating_ips = network_fakes.FakeFloatingIP.create_floating_ips(count=3) + fake_network = network_fakes.FakeNetwork.create_one_network({ + 'id': 'fake_network_id', + }) + fake_port = network_fakes.FakePort.create_one_port({ + 'id': 'fake_port_id', + }) columns = ( 'ID', 'Floating IP Address', 'Fixed IP Address', 'Port', + 'Floating Network', + 'Project', ) data = [] @@ -246,12 +304,16 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): ip.floating_ip_address, ip.fixed_ip_address, ip.port_id, + ip.floating_network_id, + ip.tenant_id, )) def setUp(self): super(TestListFloatingIPNetwork, self).setUp() self.network.ips = mock.Mock(return_value=self.floating_ips) + self.network.find_network = mock.Mock(return_value=self.fake_network) + self.network.find_port = mock.Mock(return_value=self.fake_port) # Get the command object to test self.cmd = floating_ip.ListFloatingIP(self.app, self.namespace) @@ -263,7 +325,58 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): columns, data = self.cmd.take_action(parsed_args) - self.network.ips.assert_called_once_with(**{}) + self.network.ips.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_floating_ip_list_network(self): + arglist = [ + '--network', 'fake_network_id', + ] + verifylist = [ + ('network', 'fake_network_id'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ips.assert_called_once_with(**{ + 'floating_network_id': 'fake_network_id', + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_floating_ip_list_port(self): + arglist = [ + '--port', 'fake_port_id', + ] + verifylist = [ + ('port', 'fake_port_id'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ips.assert_called_once_with(**{ + 'port_id': 'fake_port_id', + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_floating_ip_list_fixed_ip_address(self): + arglist = [ + '--fixed-ip-address', self.floating_ips[0].fixed_ip_address, + ] + verifylist = [ + ('fixed_ip_address', self.floating_ips[0].fixed_ip_address), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ips.assert_called_once_with(**{ + 'fixed_ip_address': self.floating_ips[0].fixed_ip_address, + }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -296,7 +409,7 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork): floating_ip.floating_network_id, floating_ip.id, floating_ip.port_id, - floating_ip.tenant_id, + floating_ip.project_id, floating_ip.router_id, floating_ip.status, ) diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index 50a60c2d..96b1b102 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -53,6 +53,8 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'availability_zone_hints': ["nova"], } ) + qos_policy = (network_fakes.FakeNetworkQosPolicy. + create_one_qos_policy(attrs={'id': _network.qos_policy_id})) columns = ( 'admin_state_up', @@ -65,6 +67,9 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'port_security_enabled', 'project_id', 'provider_network_type', + 'provider_physical_network', + 'provider_segmentation_id', + 'qos_policy_id', 'router:external', 'shared', 'status', @@ -82,6 +87,9 @@ class TestCreateNetworkIdentityV3(TestNetwork): _network.is_port_security_enabled, _network.project_id, _network.provider_network_type, + _network.provider_physical_network, + _network.provider_segmentation_id, + _network.qos_policy_id, network._format_router_external(_network.is_router_external), _network.shared, _network.status, @@ -98,6 +106,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): self.projects_mock.get.return_value = self.project self.domains_mock.get.return_value = self.domain + self.network.find_qos_policy = mock.Mock(return_value=self.qos_policy) def test_create_no_options(self): arglist = [] @@ -140,6 +149,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): "--provider-network-type", "vlan", "--provider-physical-network", "physnet1", "--provider-segment", "400", + "--qos-policy", self.qos_policy.id, "--transparent-vlan", "--enable-port-security", self._network.name, @@ -156,6 +166,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): ('provider_network_type', 'vlan'), ('physical_network', 'physnet1'), ('segmentation_id', '400'), + ('qos_policy', self.qos_policy.id), ('transparent_vlan', True), ('enable_port_security', True), ('name', self._network.name), @@ -176,6 +187,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'provider:network_type': 'vlan', 'provider:physical_network': 'physnet1', 'provider:segmentation_id': '400', + 'qos_policy_id': self.qos_policy.id, 'vlan_transparent': True, 'port_security_enabled': True, }) @@ -229,6 +241,9 @@ class TestCreateNetworkIdentityV2(TestNetwork): 'port_security_enabled', 'project_id', 'provider_network_type', + 'provider_physical_network', + 'provider_segmentation_id', + 'qos_policy_id', 'router:external', 'shared', 'status', @@ -246,6 +261,9 @@ class TestCreateNetworkIdentityV2(TestNetwork): _network.is_port_security_enabled, _network.project_id, _network.provider_network_type, + _network.provider_physical_network, + _network.provider_segmentation_id, + _network.qos_policy_id, network._format_router_external(_network.is_router_external), _network.shared, _network.status, @@ -609,7 +627,7 @@ class TestListNetwork(TestNetwork): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) - def test_networ_list_project_domain(self): + def test_network_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() self.projects_mock.get.return_value = project arglist = [ @@ -625,6 +643,8 @@ class TestListNetwork(TestNetwork): filters = {'tenant_id': project.id} self.network.networks.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) def test_network_list_share(self): arglist = [ @@ -679,11 +699,64 @@ class TestListNetwork(TestNetwork): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_network_list_provider_network_type(self): + network_type = self._network[0].provider_network_type + arglist = [ + '--provider-network-type', network_type, + ] + verifylist = [ + ('provider_network_type', network_type), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.networks.assert_called_once_with( + **{'provider:network_type': network_type} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_network_list_provider_physical_network(self): + physical_network = self._network[0].provider_physical_network + arglist = [ + '--provider-physical-network', physical_network, + ] + verifylist = [ + ('physical_network', physical_network), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.networks.assert_called_once_with( + **{'provider:physical_network': physical_network} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_network_list_provider_segment(self): + segmentation_id = self._network[0].provider_segmentation_id + arglist = [ + '--provider-segment', segmentation_id, + ] + verifylist = [ + ('segmentation_id', segmentation_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.networks.assert_called_once_with( + **{'provider:segmentation_id': segmentation_id} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestSetNetwork(TestNetwork): # The network to set. _network = network_fakes.FakeNetwork.create_one_network() + qos_policy = (network_fakes.FakeNetworkQosPolicy. + create_one_qos_policy(attrs={'id': _network.qos_policy_id})) def setUp(self): super(TestSetNetwork, self).setUp() @@ -691,6 +764,7 @@ class TestSetNetwork(TestNetwork): self.network.update_network = mock.Mock(return_value=None) self.network.find_network = mock.Mock(return_value=self._network) + self.network.find_qos_policy = mock.Mock(return_value=self.qos_policy) # Get the command object to test self.cmd = network.SetNetwork(self.app, self.namespace) @@ -709,6 +783,7 @@ class TestSetNetwork(TestNetwork): '--provider-segment', '400', '--no-transparent-vlan', '--enable-port-security', + '--qos-policy', self.qos_policy.name, ] verifylist = [ ('network', self._network.name), @@ -723,6 +798,7 @@ class TestSetNetwork(TestNetwork): ('segmentation_id', '400'), ('no_transparent_vlan', True), ('enable_port_security', True), + ('qos_policy', self.qos_policy.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -740,6 +816,7 @@ class TestSetNetwork(TestNetwork): 'provider:segmentation_id': '400', 'vlan_transparent': False, 'port_security_enabled': True, + 'qos_policy_id': self.qos_policy.id, } self.network.update_network.assert_called_once_with( self._network, **attrs) @@ -752,6 +829,7 @@ class TestSetNetwork(TestNetwork): '--no-share', '--internal', '--disable-port-security', + '--no-qos-policy', ] verifylist = [ ('network', self._network.name), @@ -759,6 +837,7 @@ class TestSetNetwork(TestNetwork): ('no_share', True), ('internal', True), ('disable_port_security', True), + ('no_qos_policy', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -769,6 +848,7 @@ class TestSetNetwork(TestNetwork): 'shared': False, 'router:external': False, 'port_security_enabled': False, + 'qos_policy_id': None, } self.network.update_network.assert_called_once_with( self._network, **attrs) @@ -803,6 +883,9 @@ class TestShowNetwork(TestNetwork): 'port_security_enabled', 'project_id', 'provider_network_type', + 'provider_physical_network', + 'provider_segmentation_id', + 'qos_policy_id', 'router:external', 'shared', 'status', @@ -820,6 +903,9 @@ class TestShowNetwork(TestNetwork): _network.is_port_security_enabled, _network.project_id, _network.provider_network_type, + _network.provider_physical_network, + _network.provider_segmentation_id, + _network.qos_policy_id, network._format_router_external(_network.is_router_external), _network.shared, _network.status, @@ -1109,10 +1195,7 @@ class TestListNetworkCompute(TestNetworkCompute): def test_network_list_no_options(self): arglist = [] - verifylist = [ - ('external', False), - ('long', False), - ] + verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # In base command class Lister in cliff, abstract method take_action() diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_policy.py b/openstackclient/tests/unit/network/v2/test_network_qos_policy.py new file mode 100644 index 00000000..bd30579a --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_network_qos_policy.py @@ -0,0 +1,380 @@ +# Copyright (c) 2016, Intel Corporation. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +from mock import call + +from osc_lib import exceptions + +from openstackclient.network.v2 import network_qos_policy +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils + + +class TestQosPolicy(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestQosPolicy, self).setUp() + # Get a shortcut to the network client + self.network = self.app.client_manager.network + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + + +class TestCreateNetworkQosPolicy(TestQosPolicy): + + project = identity_fakes_v3.FakeProject.create_one_project() + + # The new qos policy created. + new_qos_policy = ( + network_fakes.FakeNetworkQosPolicy.create_one_qos_policy( + attrs={ + 'tenant_id': project.id, + } + )) + columns = ( + 'description', + 'id', + 'name', + 'project_id', + 'rules', + 'shared', + ) + + data = ( + new_qos_policy.description, + new_qos_policy.id, + new_qos_policy.name, + new_qos_policy.project_id, + new_qos_policy.rules, + new_qos_policy.shared, + ) + + def setUp(self): + super(TestCreateNetworkQosPolicy, self).setUp() + self.network.create_qos_policy = mock.Mock( + return_value=self.new_qos_policy) + + # Get the command object to test + self.cmd = network_qos_policy.CreateNetworkQosPolicy( + self.app, self.namespace) + + self.projects_mock.get.return_value = self.project + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_default_options(self): + arglist = [ + self.new_qos_policy.name, + ] + verifylist = [ + ('project', None), + ('name', self.new_qos_policy.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_qos_policy.assert_called_once_with(**{ + 'name': self.new_qos_policy.name + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_all_options(self): + arglist = [ + '--share', + '--project', self.project.name, + self.new_qos_policy.name, + '--description', 'QoS policy description', + ] + verifylist = [ + ('share', True), + ('project', self.project.name), + ('name', self.new_qos_policy.name), + ('description', 'QoS policy description'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_qos_policy.assert_called_once_with(**{ + 'shared': True, + 'tenant_id': self.project.id, + 'name': self.new_qos_policy.name, + 'description': 'QoS policy description', + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestDeleteNetworkQosPolicy(TestQosPolicy): + + # The address scope to delete. + _qos_policies = ( + network_fakes.FakeNetworkQosPolicy.create_qos_policies(count=2)) + + def setUp(self): + super(TestDeleteNetworkQosPolicy, self).setUp() + self.network.delete_qos_policy = mock.Mock(return_value=None) + self.network.find_qos_policy = ( + network_fakes.FakeNetworkQosPolicy.get_qos_policies( + qos_policies=self._qos_policies) + ) + + # Get the command object to test + self.cmd = network_qos_policy.DeleteNetworkQosPolicy( + self.app, self.namespace) + + def test_qos_policy_delete(self): + arglist = [ + self._qos_policies[0].name, + ] + verifylist = [ + ('policy', [self._qos_policies[0].name]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.find_qos_policy.assert_called_once_with( + self._qos_policies[0].name, ignore_missing=False) + self.network.delete_qos_policy.assert_called_once_with( + self._qos_policies[0]) + self.assertIsNone(result) + + def test_multi_qos_policies_delete(self): + arglist = [] + + for a in self._qos_policies: + arglist.append(a.name) + verifylist = [ + ('policy', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for a in self._qos_policies: + calls.append(call(a)) + self.network.delete_qos_policy.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_qos_policies_delete_with_exception(self): + arglist = [ + self._qos_policies[0].name, + 'unexist_qos_policy', + ] + verifylist = [ + ('policy', + [self._qos_policies[0].name, 'unexist_qos_policy']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._qos_policies[0], exceptions.CommandError] + self.network.find_qos_policy = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 QoS policies failed to delete.', str(e)) + + self.network.find_qos_policy.assert_any_call( + self._qos_policies[0].name, ignore_missing=False) + self.network.find_qos_policy.assert_any_call( + 'unexist_qos_policy', ignore_missing=False) + self.network.delete_qos_policy.assert_called_once_with( + self._qos_policies[0] + ) + + +class TestListNetworkQosPolicy(TestQosPolicy): + + # The QoS policies to list up. + qos_policies = ( + network_fakes.FakeNetworkQosPolicy.create_qos_policies(count=3)) + columns = ( + 'ID', + 'Name', + 'Shared', + 'Project', + ) + data = [] + for qos_policy in qos_policies: + data.append(( + qos_policy.id, + qos_policy.name, + qos_policy.shared, + qos_policy.project_id, + )) + + def setUp(self): + super(TestListNetworkQosPolicy, self).setUp() + self.network.qos_policies = mock.Mock(return_value=self.qos_policies) + + # Get the command object to test + self.cmd = network_qos_policy.ListNetworkQosPolicy(self.app, + self.namespace) + + def test_qos_policy_list(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.qos_policies.assert_called_once_with(**{}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestSetNetworkQosPolicy(TestQosPolicy): + + # The QoS policy to set. + _qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() + + def setUp(self): + super(TestSetNetworkQosPolicy, self).setUp() + self.network.update_qos_policy = mock.Mock(return_value=None) + self.network.find_qos_policy = mock.Mock( + return_value=self._qos_policy) + + # Get the command object to test + self.cmd = network_qos_policy.SetNetworkQosPolicy(self.app, + self.namespace) + + def test_set_nothing(self): + arglist = [self._qos_policy.name, ] + verifylist = [ + ('policy', self._qos_policy.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = {} + self.network.update_qos_policy.assert_called_with( + self._qos_policy, **attrs) + self.assertIsNone(result) + + def test_set_name_share_description(self): + arglist = [ + '--name', 'new_qos_policy', + '--share', + '--description', 'QoS policy description', + self._qos_policy.name, + ] + verifylist = [ + ('name', 'new_qos_policy'), + ('share', True), + ('description', 'QoS policy description'), + ('policy', self._qos_policy.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'name': 'new_qos_policy', + 'description': 'QoS policy description', + 'shared': True, + } + self.network.update_qos_policy.assert_called_with( + self._qos_policy, **attrs) + self.assertIsNone(result) + + def test_set_no_share(self): + arglist = [ + '--no-share', + self._qos_policy.name, + ] + verifylist = [ + ('no_share', True), + ('policy', self._qos_policy.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'shared': False + } + self.network.update_qos_policy.assert_called_with( + self._qos_policy, **attrs) + self.assertIsNone(result) + + +class TestShowNetworkQosPolicy(TestQosPolicy): + + # The QoS policy to show. + _qos_policy = ( + network_fakes.FakeNetworkQosPolicy.create_one_qos_policy()) + columns = ( + 'description', + 'id', + 'name', + 'project_id', + 'rules', + 'shared', + ) + data = ( + _qos_policy.description, + _qos_policy.id, + _qos_policy.name, + _qos_policy.project_id, + _qos_policy.rules, + _qos_policy.shared, + ) + + def setUp(self): + super(TestShowNetworkQosPolicy, self).setUp() + self.network.find_qos_policy = mock.Mock(return_value=self._qos_policy) + + # Get the command object to test + self.cmd = network_qos_policy.ShowNetworkQosPolicy(self.app, + self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_show_all_options(self): + arglist = [ + self._qos_policy.name, + ] + verifylist = [ + ('policy', self._qos_policy.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_qos_policy.assert_called_once_with( + self._qos_policy.name, ignore_missing=False) + self.assertEqual(self.columns, columns) + self.assertEqual(list(self.data), list(data)) diff --git a/openstackclient/tests/unit/network/v2/test_network_rbac.py b/openstackclient/tests/unit/network/v2/test_network_rbac.py index c526ae4e..b884dbc0 100644 --- a/openstackclient/tests/unit/network/v2/test_network_rbac.py +++ b/openstackclient/tests/unit/network/v2/test_network_rbac.py @@ -36,6 +36,7 @@ class TestNetworkRBAC(network_fakes.TestNetworkV2): class TestCreateNetworkRBAC(TestNetworkRBAC): network_object = network_fakes.FakeNetwork.create_one_network() + qos_object = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() project = identity_fakes_v3.FakeProject.create_one_project() rbac_policy = network_fakes.FakeNetworkRBAC.create_one_network_rbac( attrs={'tenant_id': project.id, @@ -71,6 +72,8 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): return_value=self.rbac_policy) self.network.find_network = mock.Mock( return_value=self.network_object) + self.network.find_qos_policy = mock.Mock( + return_value=self.qos_object) self.projects_mock.get.return_value = self.project def test_network_rbac_create_no_type(self): @@ -194,6 +197,43 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_network_rbac_create_qos_object(self): + self.rbac_policy.object_type = 'qos_policy' + self.rbac_policy.object_id = self.qos_object.id + arglist = [ + '--type', 'qos_policy', + '--action', self.rbac_policy.action, + '--target-project', self.rbac_policy.target_tenant, + self.qos_object.name, + ] + verifylist = [ + ('type', 'qos_policy'), + ('action', self.rbac_policy.action), + ('target_project', self.rbac_policy.target_tenant), + ('rbac_object', self.qos_object.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_rbac_policy.assert_called_with(**{ + 'object_id': self.qos_object.id, + 'object_type': 'qos_policy', + 'action': self.rbac_policy.action, + 'target_tenant': self.rbac_policy.target_tenant, + }) + self.data = [ + self.rbac_policy.action, + self.rbac_policy.id, + self.qos_object.id, + 'qos_policy', + self.rbac_policy.tenant_id, + self.rbac_policy.target_tenant, + ] + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestDeleteNetworkRBAC(TestNetworkRBAC): diff --git a/openstackclient/tests/unit/network/v2/test_network_service_provider.py b/openstackclient/tests/unit/network/v2/test_network_service_provider.py new file mode 100644 index 00000000..5ba85ddb --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_network_service_provider.py @@ -0,0 +1,71 @@ +# Copyright (c) 2016, Intel Corporation. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock + +from openstackclient.network.v2 import network_service_provider \ + as service_provider +from openstackclient.tests.unit.network.v2 import fakes + + +class TestNetworkServiceProvider(fakes.TestNetworkV2): + + def setUp(self): + super(TestNetworkServiceProvider, self).setUp() + self.network = self.app.client_manager.network + + +class TestListNetworkServiceProvider(TestNetworkServiceProvider): + provider_list = \ + fakes.FakeNetworkServiceProvider.create_network_service_providers( + count=2 + ) + + columns = ( + 'Service Type', + 'Name', + 'Default', + ) + + data = [] + + for provider in provider_list: + data.append(( + provider.service_type, + provider.name, + provider.is_default, + )) + + def setUp(self): + super(TestListNetworkServiceProvider, self).setUp() + self.network.service_providers = mock.Mock( + return_value=self.provider_list + ) + + self.cmd = \ + service_provider.ListNetworkServiceProvider(self.app, + self.namespace) + + def test_network_service_provider_list(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.service_providers.assert_called_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index a2aceab1..aeb9884a 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -41,6 +41,7 @@ class TestPort(network_fakes.TestNetworkV2): 'binding_vif_details', 'binding_vif_type', 'binding_vnic_type', + 'description', 'device_id', 'device_owner', 'dns_assignment', @@ -65,6 +66,7 @@ class TestPort(network_fakes.TestNetworkV2): utils.format_dict(fake_port.binding_vif_details), fake_port.binding_vif_type, fake_port.binding_vnic_type, + fake_port.description, fake_port.device_id, fake_port.device_owner, utils.format_list_of_dicts(fake_port.dns_assignment), @@ -130,6 +132,7 @@ class TestCreatePort(TestPort): '--mac-address', 'aa:aa:aa:aa:aa:aa', '--fixed-ip', 'subnet=%s,ip-address=10.0.0.2' % self.fake_subnet.id, + '--description', self._port.description, '--device', 'deviceid', '--device-owner', 'fakeowner', '--disable', @@ -137,6 +140,7 @@ class TestCreatePort(TestPort): '--binding-profile', 'foo=bar', '--binding-profile', 'foo2=bar2', '--network', self._port.network_id, + '--dns-name', '8.8.8.8', 'test-port', ] @@ -146,12 +150,14 @@ class TestCreatePort(TestPort): 'fixed_ip', [{'subnet': self.fake_subnet.id, 'ip-address': '10.0.0.2'}] ), + ('description', self._port.description), ('device', 'deviceid'), ('device_owner', 'fakeowner'), ('disable', True), ('vnic_type', 'macvtap'), ('binding_profile', {'foo': 'bar', 'foo2': 'bar2'}), ('network', self._port.network_id), + ('dns_name', '8.8.8.8'), ('name', 'test-port'), ] @@ -163,12 +169,14 @@ class TestCreatePort(TestPort): 'mac_address': 'aa:aa:aa:aa:aa:aa', 'fixed_ips': [{'subnet_id': self.fake_subnet.id, 'ip_address': '10.0.0.2'}], + 'description': self._port.description, 'device_id': 'deviceid', 'device_owner': 'fakeowner', 'admin_state_up': False, 'binding:vnic_type': 'macvtap', 'binding:profile': {'foo': 'bar', 'foo2': 'bar2'}, 'network_id': self._port.network_id, + 'dns_name': '8.8.8.8', 'name': 'test-port', }) @@ -228,6 +236,169 @@ class TestCreatePort(TestPort): self.assertEqual(ref_columns, columns) self.assertEqual(ref_data, data) + def test_create_with_security_group(self): + secgroup = network_fakes.FakeSecurityGroup.create_one_security_group() + self.network.find_security_group = mock.Mock(return_value=secgroup) + arglist = [ + '--network', self._port.network_id, + '--security-group', secgroup.id, + 'test-port', + ] + + verifylist = [ + ('network', self._port.network_id,), + ('enable', True), + ('security_groups', [secgroup.id]), + ('name', 'test-port'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'security_groups': [secgroup.id], + 'name': 'test-port', + }) + + ref_columns, ref_data = self._get_common_cols_data(self._port) + self.assertEqual(ref_columns, columns) + self.assertEqual(ref_data, data) + + def test_create_port_with_dns_name(self): + arglist = [ + '--network', self._port.network_id, + '--dns-name', '8.8.8.8', + 'test-port', + ] + verifylist = [ + ('network', self._port.network_id,), + ('enable', True), + ('dns_name', '8.8.8.8'), + ('name', 'test-port'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'dns_name': '8.8.8.8', + 'name': 'test-port', + }) + + ref_columns, ref_data = self._get_common_cols_data(self._port) + self.assertEqual(ref_columns, columns) + self.assertEqual(ref_data, data) + + def test_create_with_security_groups(self): + sg_1 = network_fakes.FakeSecurityGroup.create_one_security_group() + sg_2 = network_fakes.FakeSecurityGroup.create_one_security_group() + self.network.find_security_group = mock.Mock(side_effect=[sg_1, sg_2]) + arglist = [ + '--network', self._port.network_id, + '--security-group', sg_1.id, + '--security-group', sg_2.id, + 'test-port', + ] + verifylist = [ + ('network', self._port.network_id,), + ('enable', True), + ('security_groups', [sg_1.id, sg_2.id]), + ('name', 'test-port'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'security_groups': [sg_1.id, sg_2.id], + 'name': 'test-port', + }) + + ref_columns, ref_data = self._get_common_cols_data(self._port) + self.assertEqual(ref_columns, columns) + self.assertEqual(ref_data, data) + + def test_create_with_no_secuirty_groups(self): + arglist = [ + '--network', self._port.network_id, + '--no-security-group', + 'test-port', + ] + verifylist = [ + ('network', self._port.network_id), + ('enable', True), + ('no_security_group', True), + ('name', 'test-port'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'security_groups': [], + 'name': 'test-port', + }) + + ref_columns, ref_data = self._get_common_cols_data(self._port) + self.assertEqual(ref_columns, columns) + self.assertEqual(ref_data, data) + + def test_create_port_security_enabled(self): + arglist = [ + '--network', self._port.network_id, + '--enable-port-security', + 'test-port', + ] + verifylist = [ + ('network', self._port.network_id,), + ('enable', True), + ('enable_port_security', True), + ('name', 'test-port'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'port_security_enabled': True, + 'name': 'test-port', + }) + + def test_create_port_security_disabled(self): + arglist = [ + '--network', self._port.network_id, + '--disable-port-security', + 'test-port', + ] + verifylist = [ + ('network', self._port.network_id,), + ('enable', True), + ('disable_port_security', True), + ('name', 'test-port'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'port_security_enabled': False, + 'name': 'test-port', + }) + class TestDeletePort(TestPort): @@ -317,6 +488,17 @@ class TestListPort(TestPort): 'Name', 'MAC Address', 'Fixed IP Addresses', + 'Status', + ) + + columns_long = ( + 'ID', + 'Name', + 'MAC Address', + 'Fixed IP Addresses', + 'Status', + 'Security Groups', + 'Device Owner', ) data = [] @@ -326,6 +508,19 @@ class TestListPort(TestPort): prt.name, prt.mac_address, utils.format_list_of_dicts(prt.fixed_ips), + prt.status, + )) + + data_long = [] + for prt in _ports: + data_long.append(( + prt.id, + prt.name, + prt.mac_address, + utils.format_list_of_dicts(prt.fixed_ips), + prt.status, + utils.format_list(prt.security_groups), + prt.device_owner, )) def setUp(self): @@ -419,12 +614,14 @@ class TestListPort(TestPort): '--device-owner', self._ports[0].device_owner, '--router', 'fake-router-name', '--network', 'fake-network-name', + '--mac-address', self._ports[0].mac_address, ] verifylist = [ ('device_owner', self._ports[0].device_owner), ('router', 'fake-router-name'), - ('network', 'fake-network-name') + ('network', 'fake-network-name'), + ('mac_address', self._ports[0].mac_address) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -434,11 +631,48 @@ class TestListPort(TestPort): self.network.ports.assert_called_once_with(**{ 'device_owner': self._ports[0].device_owner, 'device_id': 'fake-router-id', - 'network_id': 'fake-network-id' + 'network_id': 'fake-network-id', + 'mac_address': self._ports[0].mac_address }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_port_list_mac_address_opt(self): + arglist = [ + '--mac-address', self._ports[0].mac_address, + ] + + verifylist = [ + ('mac_address', self._ports[0].mac_address) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ports.assert_called_once_with(**{ + 'mac_address': self._ports[0].mac_address + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_list_port_with_long(self): + arglist = [ + '--long', + ] + + verifylist = [ + ('long', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ports.assert_called_once_with() + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) + class TestSetPort(TestPort): @@ -473,6 +707,25 @@ class TestSetPort(TestPort): self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) + def test_set_dns_name(self): + arglist = [ + '--dns-name', '8.8.8.8', + self._port.name, + ] + verifylist = [ + ('dns_name', '8.8.8.8'), + ('port', self._port.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'dns_name': '8.8.8.8', + } + self.network.update_port.assert_called_once_with(self._port, **attrs) + self.assertIsNone(result) + def test_append_fixed_ip(self): _testport = network_fakes.FakePort.create_one_port( {'fixed_ips': [{'ip_address': '0.0.0.1'}]}) @@ -565,6 +818,7 @@ class TestSetPort(TestPort): def test_set_that(self): arglist = [ + '--description', 'newDescription', '--enable', '--vnic-type', 'macvtap', '--binding-profile', 'foo=bar', @@ -573,6 +827,7 @@ class TestSetPort(TestPort): self._port.name, ] verifylist = [ + ('description', 'newDescription'), ('enable', True), ('vnic_type', 'macvtap'), ('binding_profile', {'foo': 'bar'}), @@ -589,6 +844,7 @@ class TestSetPort(TestPort): 'binding:vnic_type': 'macvtap', 'binding:profile': {'foo': 'bar'}, 'binding:host_id': 'binding-host-id-xxxx', + 'description': 'newDescription', 'name': 'newName', } self.network.update_port.assert_called_once_with(self._port, **attrs) @@ -651,6 +907,131 @@ class TestSetPort(TestPort): self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) + def test_set_security_group(self): + sg = network_fakes.FakeSecurityGroup.create_one_security_group() + self.network.find_security_group = mock.Mock(return_value=sg) + arglist = [ + '--security-group', sg.id, + self._port.name, + ] + verifylist = [ + ('security_groups', [sg.id]), + ('port', self._port.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'security_groups': [sg.id], + } + self.network.update_port.assert_called_once_with(self._port, **attrs) + self.assertIsNone(result) + + def test_append_security_group(self): + sg_1 = network_fakes.FakeSecurityGroup.create_one_security_group() + sg_2 = network_fakes.FakeSecurityGroup.create_one_security_group() + sg_3 = network_fakes.FakeSecurityGroup.create_one_security_group() + self.network.find_security_group = mock.Mock(side_effect=[sg_2, sg_3]) + _testport = network_fakes.FakePort.create_one_port( + {'security_groups': [sg_1.id]}) + self.network.find_port = mock.Mock(return_value=_testport) + arglist = [ + '--security-group', sg_2.id, + '--security-group', sg_3.id, + _testport.name, + ] + verifylist = [ + ('security_groups', [sg_2.id, sg_3.id]), + ('port', _testport.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'security_groups': [sg_1.id, sg_2.id, sg_3.id], + } + self.network.update_port.assert_called_once_with(_testport, **attrs) + self.assertIsNone(result) + + def test_set_no_security_groups(self): + arglist = [ + '--no-security-group', + self._port.name, + ] + verifylist = [ + ('no_security_group', True), + ('port', self._port.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'security_groups': [], + } + self.network.update_port.assert_called_once_with(self._port, **attrs) + self.assertIsNone(result) + + def test_overwrite_security_group(self): + sg1 = network_fakes.FakeSecurityGroup.create_one_security_group() + sg2 = network_fakes.FakeSecurityGroup.create_one_security_group() + _testport = network_fakes.FakePort.create_one_port( + {'security_groups': [sg1.id]}) + self.network.find_port = mock.Mock(return_value=_testport) + self.network.find_security_group = mock.Mock(return_value=sg2) + arglist = [ + '--security-group', sg2.id, + '--no-security-group', + _testport.name, + ] + verifylist = [ + ('security_groups', [sg2.id]), + ('no_security_group', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'security_groups': [sg2.id], + } + self.network.update_port.assert_called_once_with(_testport, **attrs) + self.assertIsNone(result) + + def test_port_security_enabled(self): + arglist = [ + '--enable-port-security', + self._port.id, + ] + verifylist = [ + ('enable_port_security', True), + ('port', self._port.id,) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.network.update_port.assert_called_once_with(self._port, **{ + 'port_security_enabled': True, + }) + + def test_port_security_disabled(self): + arglist = [ + '--disable-port-security', + self._port.id, + ] + verifylist = [ + ('disable_port_security', True), + ('port', self._port.id,) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.network.update_port.assert_called_once_with(self._port, **{ + 'port_security_enabled': False, + }) + class TestShowPort(TestPort): @@ -767,3 +1148,47 @@ class TestUnsetPort(TestPort): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + + def test_unset_security_group(self): + _fake_sg1 = network_fakes.FakeSecurityGroup.create_one_security_group() + _fake_sg2 = network_fakes.FakeSecurityGroup.create_one_security_group() + _fake_port = network_fakes.FakePort.create_one_port( + {'security_groups': [_fake_sg1.id, _fake_sg2.id]}) + self.network.find_port = mock.Mock(return_value=_fake_port) + self.network.find_security_group = mock.Mock(return_value=_fake_sg2) + arglist = [ + '--security-group', _fake_sg2.id, + _fake_port.name, + ] + verifylist = [ + ('security_groups', [_fake_sg2.id]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'security_groups': [_fake_sg1.id] + } + self.network.update_port.assert_called_once_with( + _fake_port, **attrs) + self.assertIsNone(result) + + def test_unset_port_security_group_not_existent(self): + _fake_sg1 = network_fakes.FakeSecurityGroup.create_one_security_group() + _fake_sg2 = network_fakes.FakeSecurityGroup.create_one_security_group() + _fake_port = network_fakes.FakePort.create_one_port( + {'security_groups': [_fake_sg1.id]}) + self.network.find_security_group = mock.Mock(return_value=_fake_sg2) + arglist = [ + '--security-group', _fake_sg2.id, + _fake_port.name, + ] + verifylist = [ + ('security_groups', [_fake_sg2.id]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 6a445862..b0409447 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -18,6 +18,7 @@ from osc_lib import exceptions from osc_lib import utils as osc_utils from openstackclient.network.v2 import router +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils @@ -29,6 +30,7 @@ class TestRouter(network_fakes.TestNetworkV2): # Get a shortcut to the network client self.network = self.app.client_manager.network + self.projects_mock = self.app.client_manager.identity.projects class TestAddPortToRouter(TestRouter): @@ -427,6 +429,97 @@ class TestListRouter(TestRouter): self.assertEqual(self.columns_long_no_az, columns) self.assertEqual(self.data_long_no_az, list(data)) + def test_list_name(self): + test_name = "fakename" + arglist = [ + '--name', test_name, + ] + verifylist = [ + ('long', False), + ('name', test_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.routers.assert_called_once_with( + **{'name': test_name} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_router_list_enable(self): + arglist = [ + '--enable', + ] + verifylist = [ + ('long', False), + ('enable', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.routers.assert_called_once_with( + **{'admin_state_up': True} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_router_list_disable(self): + arglist = [ + '--disable', + ] + verifylist = [ + ('long', False), + ('disable', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.routers.assert_called_once_with( + **{'admin_state_up': False} + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_router_list_project(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + ] + verifylist = [ + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id} + + self.network.routers.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_router_list_project_domain(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + '--project-domain', project.domain_id, + ] + verifylist = [ + ('project', project.id), + ('project_domain', project.domain_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id} + + self.network.routers.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestRemovePortFromRouter(TestRouter): '''Remove port from a Router ''' @@ -529,6 +622,7 @@ class TestSetRouter(TestRouter): '--enable', '--distributed', '--name', 'noob', + '--no-ha', '--description', 'router', ] verifylist = [ @@ -537,6 +631,7 @@ class TestSetRouter(TestRouter): ('distributed', True), ('name', 'noob'), ('description', 'router'), + ('no_ha', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -546,6 +641,7 @@ class TestSetRouter(TestRouter): 'admin_state_up': True, 'distributed': True, 'name': 'noob', + 'ha': False, 'description': 'router', } self.network.update_router.assert_called_once_with( @@ -557,11 +653,13 @@ class TestSetRouter(TestRouter): self._router.name, '--disable', '--centralized', + '--ha', ] verifylist = [ ('router', self._router.name), ('disable', True), ('centralized', True), + ('ha', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -570,6 +668,7 @@ class TestSetRouter(TestRouter): attrs = { 'admin_state_up': False, 'distributed': False, + 'ha': True, } self.network.update_router.assert_called_once_with( self._router, **attrs) @@ -773,9 +872,9 @@ class TestUnsetRouter(TestRouter): super(TestUnsetRouter, self).setUp() self._testrouter = network_fakes.FakeRouter.create_one_router( {'routes': [{"destination": "192.168.101.1/24", - "gateway": "172.24.4.3"}, + "nexthop": "172.24.4.3"}, {"destination": "192.168.101.2/24", - "gateway": "172.24.4.3"}], }) + "nexthop": "172.24.4.3"}], }) self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet() self.network.find_router = mock.Mock(return_value=self._testrouter) self.network.update_router = mock.Mock(return_value=None) diff --git a/openstackclient/tests/unit/network/v2/test_security_group.py b/openstackclient/tests/unit/network/v2/test_security_group.py index 2615b77a..43aa07cc 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group.py +++ b/openstackclient/tests/unit/network/v2/test_security_group.py @@ -444,6 +444,44 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_security_group_list_project(self): + project = identity_fakes.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + ] + verifylist = [ + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id} + + self.network.security_groups.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_security_group_list_project_domain(self): + project = identity_fakes.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + '--project-domain', project.domain_id, + ] + verifylist = [ + ('project', project.id), + ('project_domain', project.domain_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id} + + self.network.security_groups.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestListSecurityGroupCompute(TestSecurityGroupCompute): diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule.py b/openstackclient/tests/unit/network/v2/test_security_group_rule.py index 96d58e5c..5fe9013e 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule.py @@ -60,6 +60,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): network_fakes.FakeSecurityGroup.create_one_security_group() expected_columns = ( + 'description', 'direction', 'ethertype', 'id', @@ -81,6 +82,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.network.create_security_group_rule = mock.Mock( return_value=self._security_group_rule) self.expected_data = ( + self._security_group_rule.description, self._security_group_rule.direction, self._security_group_rule.ethertype, self._security_group_rule.id, @@ -119,6 +121,15 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) + def test_create_all_remote_options(self): + arglist = [ + '--remote-ip', '10.10.0.0/24', + '--remote-group', self._security_group.id, + self._security_group.id, + ] + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, []) + def test_create_bad_ethertype(self): arglist = [ '--ethertype', 'foo', @@ -213,7 +224,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) - def test_create_source_group(self): + def test_create_remote_group(self): self._setup_security_group_rule({ 'port_range_max': 22, 'port_range_min': 22, @@ -248,6 +259,34 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) + def test_create_source_group(self): + self._setup_security_group_rule({ + 'remote_group_id': self._security_group.id, + }) + arglist = [ + '--ingress', + '--src-group', self._security_group.name, + self._security_group.id, + ] + verifylist = [ + ('ingress', True), + ('src_group', self._security_group.name), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_security_group_rule.assert_called_once_with(**{ + 'direction': self._security_group_rule.direction, + 'ethertype': self._security_group_rule.ethertype, + 'protocol': self._security_group_rule.protocol, + 'remote_group_id': self._security_group_rule.remote_group_id, + 'security_group_id': self._security_group.id, + }) + self.assertEqual(self.expected_columns, columns) + self.assertEqual(self.expected_data, data) + def test_create_source_ip(self): self._setup_security_group_rule({ 'protocol': 'icmp', @@ -277,6 +316,35 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) + def test_create_remote_ip(self): + self._setup_security_group_rule({ + 'protocol': 'icmp', + 'remote_ip_prefix': '10.0.2.0/24', + }) + arglist = [ + '--protocol', self._security_group_rule.protocol, + '--remote-ip', self._security_group_rule.remote_ip_prefix, + self._security_group.id, + ] + verifylist = [ + ('protocol', self._security_group_rule.protocol), + ('remote_ip', self._security_group_rule.remote_ip_prefix), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_security_group_rule.assert_called_once_with(**{ + 'direction': self._security_group_rule.direction, + 'ethertype': self._security_group_rule.ethertype, + 'protocol': self._security_group_rule.protocol, + 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, + 'security_group_id': self._security_group.id, + }) + self.assertEqual(self.expected_columns, columns) + self.assertEqual(self.expected_data, data) + def test_create_network_options(self): self._setup_security_group_rule({ 'direction': 'egress', @@ -452,6 +520,33 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) + def test_create_with_description(self): + self._setup_security_group_rule({ + 'description': 'Setting SGR', + }) + arglist = [ + '--description', self._security_group_rule.description, + self._security_group.id, + ] + verifylist = [ + ('description', self._security_group_rule.description), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_security_group_rule.assert_called_once_with(**{ + 'description': self._security_group_rule.description, + 'direction': self._security_group_rule.direction, + 'ethertype': self._security_group_rule.ethertype, + 'protocol': self._security_group_rule.protocol, + 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, + 'security_group_id': self._security_group.id, + }) + self.assertEqual(self.expected_columns, columns) + self.assertEqual(self.expected_data, data) + class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): @@ -498,6 +593,15 @@ class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) + def test_create_all_remote_options(self): + arglist = [ + '--remote-ip', '10.10.0.0/24', + '--remote-group', self._security_group.id, + self._security_group.id, + ] + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, []) + def test_create_bad_protocol(self): arglist = [ '--protocol', 'foo', @@ -588,6 +692,38 @@ class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) + def test_create_remote_group(self): + expected_columns, expected_data = self._setup_security_group_rule({ + 'from_port': 22, + 'to_port': 22, + 'group': {'name': self._security_group.name}, + }) + arglist = [ + '--dst-port', str(self._security_group_rule.from_port), + '--remote-group', self._security_group.name, + self._security_group.id, + ] + verifylist = [ + ('dst_port', (self._security_group_rule.from_port, + self._security_group_rule.to_port)), + ('remote_group', self._security_group.name), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_group_rules.create.assert_called_once_with( + self._security_group.id, + self._security_group_rule.ip_protocol, + self._security_group_rule.from_port, + self._security_group_rule.to_port, + self._security_group_rule.ip_range['cidr'], + self._security_group.id, + ) + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, data) + def test_create_source_ip(self): expected_columns, expected_data = self._setup_security_group_rule({ 'ip_protocol': 'icmp', @@ -620,6 +756,38 @@ class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) + def test_create_remote_ip(self): + expected_columns, expected_data = self._setup_security_group_rule({ + 'ip_protocol': 'icmp', + 'from_port': -1, + 'to_port': -1, + 'ip_range': {'cidr': '10.0.2.0/24'}, + }) + arglist = [ + '--protocol', self._security_group_rule.ip_protocol, + '--remote-ip', self._security_group_rule.ip_range['cidr'], + self._security_group.id, + ] + verifylist = [ + ('protocol', self._security_group_rule.ip_protocol), + ('remote_ip', self._security_group_rule.ip_range['cidr']), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_group_rules.create.assert_called_once_with( + self._security_group.id, + self._security_group_rule.ip_protocol, + self._security_group_rule.from_port, + self._security_group_rule.to_port, + self._security_group_rule.ip_range['cidr'], + None, + ) + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, data) + def test_create_proto_option(self): expected_columns, expected_data = self._setup_security_group_rule({ 'ip_protocol': 'icmp', @@ -942,6 +1110,60 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.assertEqual(self.expected_columns_no_group, columns) self.assertEqual(self.expected_data_no_group, list(data)) + def test_list_with_protocol(self): + self._security_group_rule_tcp.port_range_min = 80 + arglist = [ + '--protocol', 'tcp', + ] + verifylist = [ + ('protocol', 'tcp'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.security_group_rules.assert_called_once_with(**{ + 'protocol': 'tcp', + }) + self.assertEqual(self.expected_columns_no_group, columns) + self.assertEqual(self.expected_data_no_group, list(data)) + + def test_list_with_ingress(self): + self._security_group_rule_tcp.port_range_min = 80 + arglist = [ + '--ingress', + ] + verifylist = [ + ('ingress', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.security_group_rules.assert_called_once_with(**{ + 'direction': 'ingress', + }) + self.assertEqual(self.expected_columns_no_group, columns) + self.assertEqual(self.expected_data_no_group, list(data)) + + def test_list_with_wrong_egress(self): + self._security_group_rule_tcp.port_range_min = 80 + arglist = [ + '--egress', + ] + verifylist = [ + ('egress', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.security_group_rules.assert_called_once_with(**{ + 'direction': 'egress', + }) + self.assertEqual(self.expected_columns_no_group, columns) + self.assertEqual(self.expected_data_no_group, list(data)) + class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): @@ -1075,6 +1297,7 @@ class TestShowSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): network_fakes.FakeSecurityGroupRule.create_one_security_group_rule() columns = ( + 'description', 'direction', 'ethertype', 'id', @@ -1088,6 +1311,7 @@ class TestShowSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): ) data = ( + _security_group_rule.description, _security_group_rule.direction, _security_group_rule.ethertype, _security_group_rule.id, diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index 2d51aa4a..47de5616 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -636,7 +636,7 @@ class TestListSubnet(TestSubnet): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'enable_dhcp': True} + filters = {'enable_dhcp': True, 'is_dhcp_enabled': True} self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -652,7 +652,7 @@ class TestListSubnet(TestSubnet): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'enable_dhcp': False} + filters = {'enable_dhcp': False, 'is_dhcp_enabled': False} self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -685,7 +685,7 @@ class TestListSubnet(TestSubnet): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id} + filters = {'tenant_id': project.id, 'project_id': project.id} self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -723,7 +723,7 @@ class TestListSubnet(TestSubnet): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id} + filters = {'tenant_id': project.id, 'project_id': project.id} self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -925,13 +925,16 @@ class TestSetSubnet(TestSubnet): {'host_routes': [{'destination': '10.20.20.0/24', 'nexthop': '10.20.20.1'}], 'allocation_pools': [{'start': '8.8.8.200', - 'end': '8.8.8.250'}], }) + 'end': '8.8.8.250'}], + 'dns_nameservers': ["10.0.0.1"], }) self.network.find_subnet = mock.Mock(return_value=_testsubnet) arglist = [ '--host-route', 'destination=10.30.30.30/24,gateway=10.30.30.1', '--no-host-route', '--allocation-pool', 'start=8.8.8.100,end=8.8.8.150', '--no-allocation-pool', + '--dns-nameserver', '10.1.10.1', + '--no-dns-nameservers', _testsubnet.name, ] verifylist = [ @@ -939,6 +942,8 @@ class TestSetSubnet(TestSubnet): "destination": "10.30.30.30/24", "gateway": "10.30.30.1"}]), ('allocation_pools', [{ 'start': '8.8.8.100', 'end': '8.8.8.150'}]), + ('dns_nameservers', ['10.1.10.1']), + ('no_dns_nameservers', True), ('no_host_route', True), ('no_allocation_pool', True), ] @@ -948,6 +953,7 @@ class TestSetSubnet(TestSubnet): 'host_routes': [{ "destination": "10.30.30.30/24", "nexthop": "10.30.30.1"}], 'allocation_pools': [{'start': '8.8.8.100', 'end': '8.8.8.150'}], + 'dns_nameservers': ["10.1.10.1"], } self.network.update_subnet.assert_called_once_with( _testsubnet, **attrs) diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py index fa6ffff3..f12537e7 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -435,7 +435,7 @@ class TestListSubnetPool(TestSubnetPool): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'shared': False} + filters = {'shared': False, 'is_shared': False} self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -451,7 +451,7 @@ class TestListSubnetPool(TestSubnetPool): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'shared': True} + filters = {'shared': True, 'is_shared': True} self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -501,7 +501,7 @@ class TestListSubnetPool(TestSubnetPool): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id} + filters = {'tenant_id': project.id, 'project_id': project.id} self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -521,7 +521,7 @@ class TestListSubnetPool(TestSubnetPool): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id} + filters = {'tenant_id': project.id, 'project_id': project.id} self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index 3999543c..78a8227e 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -23,115 +23,6 @@ from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes from openstackclient.tests.unit import utils -volume_id = 'vvvvvvvv-vvvv-vvvv-vvvvvvvv' -volume_name = 'nigel' -volume_description = 'Nigel Tufnel' -volume_status = 'available' -volume_size = 120 -volume_type = 'to-eleven' -volume_zone = 'stonehenge' -volume_metadata = { - 'Alpha': 'a', - 'Beta': 'b', - 'Gamma': 'g', -} -volume_metadata_str = "Alpha='a', Beta='b', Gamma='g'" - -VOLUME = { - 'id': volume_id, - 'display_name': volume_name, - 'display_description': volume_description, - 'size': volume_size, - 'status': volume_status, - 'attach_status': 'detached', - 'availability_zone': volume_zone, - 'volume_type': volume_type, - 'metadata': volume_metadata, -} - -extension_name = 'SchedulerHints' -extension_namespace = 'http://docs.openstack.org/'\ - 'block-service/ext/scheduler-hints/api/v2' -extension_description = 'Pass arbitrary key/value'\ - 'pairs to the scheduler.' -extension_updated = '2014-02-07T12:00:0-00:00' -extension_alias = 'OS-SCH-HNT' -extension_links = '[{"href":'\ - '"https://github.com/openstack/block-api", "type":'\ - ' "text/html", "rel": "describedby"}]' - -EXTENSION = { - 'name': extension_name, - 'namespace': extension_namespace, - 'description': extension_description, - 'updated': extension_updated, - 'alias': extension_alias, - 'links': extension_links, -} - -# NOTE(dtroyer): duplicating here the minimum image info needed to test -# volume create --image until circular references can be -# avoided by refactoring the test fakes. - -image_id = 'im1' -image_name = 'graven' - - -IMAGE = { - 'id': image_id, - 'name': image_name, -} - -type_id = "5520dc9e-6f9b-4378-a719-729911c0f407" -type_name = "fake-lvmdriver-1" - -TYPE = { - 'id': type_id, - 'name': type_name -} - -qos_id = '6f2be1de-997b-4230-b76c-a3633b59e8fb' -qos_consumer = 'front-end' -qos_default_consumer = 'both' -qos_name = "fake-qos-specs" -qos_specs = { - 'foo': 'bar', - 'iops': '9001' -} -qos_association = { - 'association_type': 'volume_type', - 'name': type_name, - 'id': type_id -} - -QOS = { - 'id': qos_id, - 'consumer': qos_consumer, - 'name': qos_name -} - -QOS_DEFAULT_CONSUMER = { - 'id': qos_id, - 'consumer': qos_default_consumer, - 'name': qos_name -} - -QOS_WITH_SPECS = { - 'id': qos_id, - 'consumer': qos_consumer, - 'name': qos_name, - 'specs': qos_specs -} - -QOS_WITH_ASSOCIATIONS = { - 'id': qos_id, - 'consumer': qos_consumer, - 'name': qos_name, - 'specs': qos_specs, - 'associations': [qos_association] -} - - class FakeTransfer(object): """Fake one or more Transfer.""" @@ -306,6 +197,32 @@ class FakeQos(object): return qos @staticmethod + def create_one_qos_association(attrs=None): + """Create a fake Qos specification association. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with id, name, association_type, etc. + """ + attrs = attrs or {} + + # Set default attributes. + qos_association_info = { + "id": 'type-id-' + uuid.uuid4().hex, + "name": 'type-name-' + uuid.uuid4().hex, + "association_type": 'volume_type', + } + + # Overwrite default attributes. + qos_association_info.update(attrs) + + qos_association = fakes.FakeResource( + info=copy.deepcopy(qos_association_info), + loaded=True) + return qos_association + + @staticmethod def create_qoses(attrs=None, count=2): """Create multiple fake Qos specifications. diff --git a/openstackclient/tests/unit/volume/v1/test_backup.py b/openstackclient/tests/unit/volume/v1/test_backup.py index 32c2fd22..1097d3f1 100644 --- a/openstackclient/tests/unit/volume/v1/test_backup.py +++ b/openstackclient/tests/unit/volume/v1/test_backup.py @@ -249,26 +249,65 @@ class TestBackupList(TestBackup): self.volumes_mock.list.return_value = [self.volume] self.backups_mock.list.return_value = self.backups + self.volumes_mock.get.return_value = self.volume # Get the command to test self.cmd = backup.ListVolumeBackup(self.app, None) def test_backup_list_without_options(self): arglist = [] - verifylist = [("long", False)] + verifylist = [ + ("long", False), + ("name", None), + ("status", None), + ("volume", None), + ('all_projects', False), + ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + search_opts = { + "name": None, + "status": None, + "volume_id": None, + "all_tenants": False, + } + self.volumes_mock.get.assert_not_called() + self.backups_mock.list.assert_called_with( + search_opts=search_opts, + ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) def test_backup_list_with_options(self): - arglist = ["--long"] - verifylist = [("long", True)] + arglist = [ + "--long", + "--name", self.backups[0].name, + "--status", "error", + "--volume", self.volume.id, + "--all-projects" + ] + verifylist = [ + ("long", True), + ("name", self.backups[0].name), + ("status", "error"), + ("volume", self.volume.id), + ('all_projects', True), + ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + search_opts = { + "name": self.backups[0].name, + "status": "error", + "volume_id": self.volume.id, + "all_tenants": True, + } + self.volumes_mock.get.assert_called_once_with(self.volume.id) + self.backups_mock.list.assert_called_with( + search_opts=search_opts, + ) self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) diff --git a/openstackclient/tests/unit/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py index 1982980a..464038e7 100644 --- a/openstackclient/tests/unit/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py @@ -13,14 +13,12 @@ # under the License. # -import copy import mock from mock import call from osc_lib import exceptions from osc_lib import utils -from openstackclient.tests.unit import fakes from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes from openstackclient.volume.v1 import qos_specs @@ -39,154 +37,124 @@ class TestQos(volume_fakes.TestVolumev1): class TestQosAssociate(TestQos): + volume_type = volume_fakes.FakeType.create_one_type() + qos_spec = volume_fakes.FakeQos.create_one_qos() + def setUp(self): super(TestQosAssociate, self).setUp() + self.qos_mock.get.return_value = self.qos_spec + self.types_mock.get.return_value = self.volume_type # Get the command object to test self.cmd = qos_specs.AssociateQos(self.app, None) def test_qos_associate(self): - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True - ) - self.types_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.TYPE), - loaded=True - ) arglist = [ - volume_fakes.qos_id, - volume_fakes.type_id + self.qos_spec.id, + self.volume_type.id ] verifylist = [ - ('qos_spec', volume_fakes.qos_id), - ('volume_type', volume_fakes.type_id) + ('qos_spec', self.qos_spec.id), + ('volume_type', self.volume_type.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.qos_mock.associate.assert_called_with( - volume_fakes.qos_id, - volume_fakes.type_id + self.qos_spec.id, + self.volume_type.id ) self.assertIsNone(result) class TestQosCreate(TestQos): + new_qos_spec = volume_fakes.FakeQos.create_one_qos() columns = ( 'consumer', 'id', 'name', + 'specs' ) datalist = ( - volume_fakes.qos_consumer, - volume_fakes.qos_id, - volume_fakes.qos_name + new_qos_spec.consumer, + new_qos_spec.id, + new_qos_spec.name, + new_qos_spec.specs ) def setUp(self): super(TestQosCreate, self).setUp() + self.qos_mock.create.return_value = self.new_qos_spec # Get the command object to test self.cmd = qos_specs.CreateQos(self.app, None) def test_qos_create_without_properties(self): - self.qos_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS_DEFAULT_CONSUMER), - loaded=True - ) - arglist = [ - volume_fakes.qos_name, + self.new_qos_spec.name, ] verifylist = [ - ('name', volume_fakes.qos_name), + ('name', self.new_qos_spec.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.qos_mock.create.assert_called_with( - volume_fakes.qos_name, - {'consumer': volume_fakes.qos_default_consumer} + self.new_qos_spec.name, + {'consumer': 'both'} ) self.assertEqual(self.columns, columns) - datalist = ( - volume_fakes.qos_default_consumer, - volume_fakes.qos_id, - volume_fakes.qos_name - ) - self.assertEqual(datalist, data) + self.assertEqual(self.datalist, data) def test_qos_create_with_consumer(self): - self.qos_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True - ) - arglist = [ - volume_fakes.qos_name, - '--consumer', volume_fakes.qos_consumer + '--consumer', self.new_qos_spec.consumer, + self.new_qos_spec.name, ] verifylist = [ - ('name', volume_fakes.qos_name), - ('consumer', volume_fakes.qos_consumer) + ('consumer', self.new_qos_spec.consumer), + ('name', self.new_qos_spec.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.qos_mock.create.assert_called_with( - volume_fakes.qos_name, - {'consumer': volume_fakes.qos_consumer} + self.new_qos_spec.name, + {'consumer': self.new_qos_spec.consumer} ) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) def test_qos_create_with_properties(self): - self.qos_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS_WITH_SPECS), - loaded=True - ) - arglist = [ - volume_fakes.qos_name, - '--consumer', volume_fakes.qos_consumer, + '--consumer', self.new_qos_spec.consumer, '--property', 'foo=bar', - '--property', 'iops=9001' + '--property', 'iops=9001', + self.new_qos_spec.name, ] verifylist = [ - ('name', volume_fakes.qos_name), - ('consumer', volume_fakes.qos_consumer), - ('property', volume_fakes.qos_specs) + ('consumer', self.new_qos_spec.consumer), + ('property', self.new_qos_spec.specs), + ('name', self.new_qos_spec.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - specs = volume_fakes.qos_specs.copy() - specs.update({'consumer': volume_fakes.qos_consumer}) + self.new_qos_spec.specs.update( + {'consumer': self.new_qos_spec.consumer}) self.qos_mock.create.assert_called_with( - volume_fakes.qos_name, - specs + self.new_qos_spec.name, + self.new_qos_spec.specs ) - columns = self.columns + ( - 'specs', - ) - self.assertEqual(columns, columns) - datalist = self.datalist + ( - volume_fakes.qos_specs, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) class TestQosDelete(TestQos): @@ -294,79 +262,62 @@ class TestQosDelete(TestQos): class TestQosDisassociate(TestQos): + volume_type = volume_fakes.FakeType.create_one_type() + qos_spec = volume_fakes.FakeQos.create_one_qos() + def setUp(self): super(TestQosDisassociate, self).setUp() + self.qos_mock.get.return_value = self.qos_spec + self.types_mock.get.return_value = self.volume_type # Get the command object to test self.cmd = qos_specs.DisassociateQos(self.app, None) def test_qos_disassociate_with_volume_type(self): - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True - ) - self.types_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.TYPE), - loaded=True - ) arglist = [ - volume_fakes.qos_id, - '--volume-type', volume_fakes.type_id + '--volume-type', self.volume_type.id, + self.qos_spec.id, ] verifylist = [ - ('qos_spec', volume_fakes.qos_id), - ('volume_type', volume_fakes.type_id) + ('volume_type', self.volume_type.id), + ('qos_spec', self.qos_spec.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.qos_mock.disassociate.assert_called_with( - volume_fakes.qos_id, - volume_fakes.type_id + self.qos_spec.id, + self.volume_type.id ) self.assertIsNone(result) def test_qos_disassociate_with_all_volume_types(self): - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True - ) - arglist = [ - volume_fakes.qos_id, - '--all' + '--all', + self.qos_spec.id, ] verifylist = [ - ('qos_spec', volume_fakes.qos_id) + ('qos_spec', self.qos_spec.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.qos_mock.disassociate_all.assert_called_with(volume_fakes.qos_id) + self.qos_mock.disassociate_all.assert_called_with(self.qos_spec.id) self.assertIsNone(result) class TestQosList(TestQos): + qos_spec = volume_fakes.FakeQos.create_one_qos() + qos_association = volume_fakes.FakeQos.create_one_qos_association() + def setUp(self): super(TestQosList, self).setUp() - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS_WITH_ASSOCIATIONS), - loaded=True, - ) - self.qos_mock.list.return_value = [self.qos_mock.get.return_value] - self.qos_mock.get_associations.return_value = [fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.qos_association), - loaded=True, - )] + self.qos_mock.list.return_value = [self.qos_spec] + self.qos_mock.get_associations.return_value = [self.qos_association] # Get the command object to test self.cmd = qos_specs.ListQos(self.app, None) @@ -389,81 +340,72 @@ class TestQosList(TestQos): ) self.assertEqual(collist, columns) datalist = (( - volume_fakes.qos_id, - volume_fakes.qos_name, - volume_fakes.qos_consumer, - volume_fakes.type_name, - utils.format_dict(volume_fakes.qos_specs), + self.qos_spec.id, + self.qos_spec.name, + self.qos_spec.consumer, + self.qos_association.name, + utils.format_dict(self.qos_spec.specs), ), ) self.assertEqual(datalist, tuple(data)) class TestQosSet(TestQos): + qos_spec = volume_fakes.FakeQos.create_one_qos() + def setUp(self): super(TestQosSet, self).setUp() + self.qos_mock.get.return_value = self.qos_spec # Get the command object to test self.cmd = qos_specs.SetQos(self.app, None) def test_qos_set_with_properties_with_id(self): - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS_WITH_SPECS), - loaded=True - ) arglist = [ - volume_fakes.qos_id, '--property', 'foo=bar', - '--property', 'iops=9001' + '--property', 'iops=9001', + self.qos_spec.id, ] verifylist = [ - ('qos_spec', volume_fakes.qos_id), - ('property', volume_fakes.qos_specs) + ('property', self.qos_spec.specs), + ('qos_spec', self.qos_spec.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.qos_mock.set_keys.assert_called_with( - volume_fakes.qos_id, - volume_fakes.qos_specs + self.qos_spec.id, + self.qos_spec.specs ) self.assertIsNone(result) class TestQosShow(TestQos): + qos_spec = volume_fakes.FakeQos.create_one_qos() + qos_association = volume_fakes.FakeQos.create_one_qos_association() + def setUp(self): super(TestQosShow, self).setUp() - - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS_WITH_ASSOCIATIONS), - loaded=True, - ) - self.qos_mock.get_associations.return_value = [fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.qos_association), - loaded=True, - )] - + self.qos_mock.get.return_value = self.qos_spec + self.qos_mock.get_associations.return_value = [self.qos_association] # Get the command object to test self.cmd = qos_specs.ShowQos(self.app, None) def test_qos_show(self): arglist = [ - volume_fakes.qos_id + self.qos_spec.id ] verifylist = [ - ('qos_spec', volume_fakes.qos_id) + ('qos_spec', self.qos_spec.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.qos_mock.get.assert_called_with( - volume_fakes.qos_id + self.qos_spec.id ) collist = ( @@ -475,56 +417,53 @@ class TestQosShow(TestQos): ) self.assertEqual(collist, columns) datalist = ( - volume_fakes.type_name, - volume_fakes.qos_consumer, - volume_fakes.qos_id, - volume_fakes.qos_name, - utils.format_dict(volume_fakes.qos_specs), + self.qos_association.name, + self.qos_spec.consumer, + self.qos_spec.id, + self.qos_spec.name, + utils.format_dict(self.qos_spec.specs), ) self.assertEqual(datalist, tuple(data)) class TestQosUnset(TestQos): + qos_spec = volume_fakes.FakeQos.create_one_qos() + def setUp(self): super(TestQosUnset, self).setUp() + self.qos_mock.get.return_value = self.qos_spec # Get the command object to test self.cmd = qos_specs.UnsetQos(self.app, None) def test_qos_unset_with_properties(self): - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True - ) arglist = [ - volume_fakes.qos_id, '--property', 'iops', - '--property', 'foo' + '--property', 'foo', + self.qos_spec.id, ] - verifylist = [ - ('qos_spec', volume_fakes.qos_id), - ('property', ['iops', 'foo']) + ('property', ['iops', 'foo']), + ('qos_spec', self.qos_spec.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.qos_mock.unset_keys.assert_called_with( - volume_fakes.qos_id, + self.qos_spec.id, ['iops', 'foo'] ) self.assertIsNone(result) def test_qos_unset_nothing(self): arglist = [ - volume_fakes.qos_id, + self.qos_spec.id, ] verifylist = [ - ('qos_spec', volume_fakes.qos_id), + ('qos_spec', self.qos_spec.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/unit/volume/v1/test_snapshot.py b/openstackclient/tests/unit/volume/v1/test_snapshot.py index edfbdc19..8e30d6a9 100644 --- a/openstackclient/tests/unit/volume/v1/test_snapshot.py +++ b/openstackclient/tests/unit/volume/v1/test_snapshot.py @@ -19,7 +19,7 @@ from osc_lib import exceptions from osc_lib import utils from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes -from openstackclient.volume.v1 import snapshot +from openstackclient.volume.v1 import volume_snapshot class TestSnapshot(volume_fakes.TestVolumev1): @@ -67,20 +67,20 @@ class TestSnapshotCreate(TestSnapshot): self.volumes_mock.get.return_value = self.volume self.snapshots_mock.create.return_value = self.new_snapshot # Get the command object to test - self.cmd = snapshot.CreateSnapshot(self.app, None) + self.cmd = volume_snapshot.CreateVolumeSnapshot(self.app, None) def test_snapshot_create(self): arglist = [ - "--name", self.new_snapshot.display_name, + "--volume", self.new_snapshot.volume_id, "--description", self.new_snapshot.display_description, "--force", - self.new_snapshot.volume_id, + self.new_snapshot.display_name, ] verifylist = [ - ("name", self.new_snapshot.display_name), + ("volume", self.new_snapshot.volume_id), ("description", self.new_snapshot.display_description), ("force", True), - ("volume", self.new_snapshot.volume_id), + ("snapshot_name", self.new_snapshot.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -97,7 +97,7 @@ class TestSnapshotCreate(TestSnapshot): def test_snapshot_create_without_name(self): arglist = [ - self.new_snapshot.volume_id, + "--volume", self.new_snapshot.volume_id, "--description", self.new_snapshot.display_description, "--force" ] @@ -119,6 +119,32 @@ class TestSnapshotCreate(TestSnapshot): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_snapshot_create_without_volume(self): + arglist = [ + "--description", self.new_snapshot.display_description, + "--force", + self.new_snapshot.display_name + ] + verifylist = [ + ("description", self.new_snapshot.display_description), + ("force", True), + ("snapshot_name", self.new_snapshot.display_name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.get.assert_called_once_with( + self.new_snapshot.display_name) + self.snapshots_mock.create.assert_called_once_with( + self.new_snapshot.volume_id, + True, + self.new_snapshot.display_name, + self.new_snapshot.display_description, + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestSnapshotDelete(TestSnapshot): @@ -132,7 +158,7 @@ class TestSnapshotDelete(TestSnapshot): self.snapshots_mock.delete.return_value = None # Get the command object to mock - self.cmd = snapshot.DeleteSnapshot(self.app, None) + self.cmd = volume_snapshot.DeleteVolumeSnapshot(self.app, None) def test_snapshot_delete(self): arglist = [ @@ -244,7 +270,7 @@ class TestSnapshotList(TestSnapshot): self.volumes_mock.list.return_value = [self.volume] self.snapshots_mock.list.return_value = self.snapshots # Get the command to test - self.cmd = snapshot.ListSnapshot(self.app, None) + self.cmd = volume_snapshot.ListVolumeSnapshot(self.app, None) def test_snapshot_list_without_options(self): arglist = [] @@ -307,7 +333,7 @@ class TestSnapshotSet(TestSnapshot): self.snapshots_mock.get.return_value = self.snapshot self.snapshots_mock.set_metadata.return_value = None # Get the command object to mock - self.cmd = snapshot.SetSnapshot(self.app, None) + self.cmd = volume_snapshot.SetVolumeSnapshot(self.app, None) def test_snapshot_set_all(self): arglist = [ @@ -404,7 +430,7 @@ class TestSnapshotShow(TestSnapshot): self.snapshots_mock.get.return_value = self.snapshot # Get the command object to test - self.cmd = snapshot.ShowSnapshot(self.app, None) + self.cmd = volume_snapshot.ShowVolumeSnapshot(self.app, None) def test_snapshot_show(self): arglist = [ @@ -432,7 +458,7 @@ class TestSnapshotUnset(TestSnapshot): self.snapshots_mock.get.return_value = self.snapshot self.snapshots_mock.delete_metadata.return_value = None # Get the command object to mock - self.cmd = snapshot.UnsetSnapshot(self.app, None) + self.cmd = volume_snapshot.UnsetVolumeSnapshot(self.app, None) def test_snapshot_unset(self): arglist = [ diff --git a/openstackclient/tests/unit/volume/v1/test_type.py b/openstackclient/tests/unit/volume/v1/test_type.py index 23a1186d..81ad8301 100644 --- a/openstackclient/tests/unit/volume/v1/test_type.py +++ b/openstackclient/tests/unit/volume/v1/test_type.py @@ -158,11 +158,13 @@ class TestTypeList(TestType): columns = ( "ID", - "Name" + "Name", + "Is Public", ) columns_long = ( "ID", "Name", + "Is Public", "Properties" ) @@ -171,12 +173,14 @@ class TestTypeList(TestType): data.append(( t.id, t.name, + t.is_public, )) data_long = [] for t in volume_types: data_long.append(( t.id, t.name, + t.is_public, utils.format_dict(t.extra_specs), )) diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index 73c00844..7a44dea8 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -14,15 +14,14 @@ # import argparse -import copy import mock from mock import call from osc_lib import exceptions from osc_lib import utils -from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.unit.image.v1 import fakes as image_fakes from openstackclient.tests.unit import utils as tests_utils from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes from openstackclient.volume.v1 import volume @@ -58,10 +57,6 @@ class TestVolume(volume_fakes.TestVolumev1): return volumes -# TODO(dtroyer): The volume create tests are incomplete, only the minimal -# options and the options that require additional processing -# are implemented at this time. - class TestVolumeCreate(TestVolume): project = identity_fakes.FakeProject.create_one_project() @@ -321,19 +316,16 @@ class TestVolumeCreate(TestVolume): self.assertEqual(self.datalist, data) def test_volume_create_image_id(self): - self.images_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.IMAGE), - loaded=True, - ) + image = image_fakes.FakeImage.create_one_image() + self.images_mock.get.return_value = image arglist = [ - '--image', volume_fakes.image_id, + '--image', image.id, '--size', str(self.new_volume.size), self.new_volume.display_name, ] verifylist = [ - ('image', volume_fakes.image_id), + ('image', image.id), ('size', self.new_volume.size), ('name', self.new_volume.display_name), ] @@ -360,26 +352,23 @@ class TestVolumeCreate(TestVolume): None, None, None, - volume_fakes.image_id, + image.id, ) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) def test_volume_create_image_name(self): - self.images_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.IMAGE), - loaded=True, - ) + image = image_fakes.FakeImage.create_one_image() + self.images_mock.get.return_value = image arglist = [ - '--image', volume_fakes.image_name, + '--image', image.name, '--size', str(self.new_volume.size), self.new_volume.display_name, ] verifylist = [ - ('image', volume_fakes.image_name), + ('image', image.name), ('size', self.new_volume.size), ('name', self.new_volume.display_name), ] @@ -406,7 +395,7 @@ class TestVolumeCreate(TestVolume): None, None, None, - volume_fakes.image_id, + image.id, ) self.assertEqual(self.columns, columns) @@ -739,6 +728,68 @@ class TestVolumeList(TestVolume): self.cmd, arglist, verifylist) +class TestVolumeMigrate(TestVolume): + + _volume = volume_fakes.FakeVolume.create_one_volume() + + def setUp(self): + super(TestVolumeMigrate, self).setUp() + + self.volumes_mock.get.return_value = self._volume + self.volumes_mock.migrate_volume.return_value = None + # Get the command object to test + self.cmd = volume.MigrateVolume(self.app, None) + + def test_volume_migrate(self): + arglist = [ + "--host", "host@backend-name#pool", + self._volume.id, + ] + verifylist = [ + ("force_host_copy", False), + ("host", "host@backend-name#pool"), + ("volume", self._volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.get.assert_called_once_with(self._volume.id) + self.volumes_mock.migrate_volume.assert_called_once_with( + self._volume.id, "host@backend-name#pool", False) + self.assertIsNone(result) + + def test_volume_migrate_with_option(self): + arglist = [ + "--force-host-copy", + "--host", "host@backend-name#pool", + self._volume.id, + ] + verifylist = [ + ("force_host_copy", True), + ("host", "host@backend-name#pool"), + ("volume", self._volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.get.assert_called_once_with(self._volume.id) + self.volumes_mock.migrate_volume.assert_called_once_with( + self._volume.id, "host@backend-name#pool", True) + self.assertIsNone(result) + + def test_volume_migrate_without_host(self): + arglist = [ + self._volume.id, + ] + verifylist = [ + ("force_host_copy", False), + ("volume", self._volume.id), + ] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + class TestVolumeSet(TestVolume): _volume = volume_fakes.FakeVolume.create_one_volume() @@ -844,8 +895,7 @@ class TestVolumeSet(TestVolume): ) self.assertIsNone(result) - @mock.patch.object(volume.LOG, 'error') - def test_volume_set_size_smaller(self, mock_log_error): + def test_volume_set_size_smaller(self): self._volume.status = 'available' arglist = [ '--size', '1', @@ -860,15 +910,11 @@ class TestVolumeSet(TestVolume): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - - mock_log_error.assert_called_with("New size must be greater " - "than %s GB", - self._volume.size) - self.assertIsNone(result) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) - @mock.patch.object(volume.LOG, 'error') - def test_volume_set_size_not_available(self, mock_log_error): + def test_volume_set_size_not_available(self): self._volume.status = 'error' arglist = [ '--size', '130', @@ -883,12 +929,9 @@ class TestVolumeSet(TestVolume): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - - mock_log_error.assert_called_with("Volume is in %s state, it must be " - "available before size can be " - "extended", 'error') - self.assertIsNone(result) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) def test_volume_set_property(self): arglist = [ @@ -896,6 +939,8 @@ class TestVolumeSet(TestVolume): self._volume.display_name, ] verifylist = [ + ('read_only', False), + ('read_write', False), ('name', None), ('description', None), ('size', None), @@ -916,6 +961,7 @@ class TestVolumeSet(TestVolume): self._volume.id, metadata ) + self.volumes_mock.update_readonly_flag.assert_not_called() self.assertIsNone(result) def test_volume_set_bootable(self): @@ -943,6 +989,44 @@ class TestVolumeSet(TestVolume): self.volumes_mock.set_bootable.assert_called_with( self._volume.id, verifylist[index][0][1]) + def test_volume_set_readonly(self): + arglist = [ + '--read-only', + self._volume.id + ] + verifylist = [ + ('read_only', True), + ('read_write', False), + ('volume', self._volume.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.update_readonly_flag.assert_called_once_with( + self._volume.id, + True) + self.assertIsNone(result) + + def test_volume_set_read_write(self): + arglist = [ + '--read-write', + self._volume.id + ] + verifylist = [ + ('read_only', False), + ('read_write', True), + ('volume', self._volume.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.update_readonly_flag.assert_called_once_with( + self._volume.id, + False) + self.assertIsNone(result) + class TestVolumeShow(TestVolume): diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 5e1d16e1..3137bfb0 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -224,6 +224,8 @@ class FakeVolumeClient(object): self.quota_classes.resource_class = fakes.FakeResource(None, {}) self.consistencygroups = mock.Mock() self.consistencygroups.resource_class = fakes.FakeResource(None, {}) + self.cgsnapshots = mock.Mock() + self.cgsnapshots.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] @@ -248,10 +250,7 @@ class TestVolume(utils.TestCommand): class FakeVolume(object): - """Fake one or more volumes. - - TODO(xiexs): Currently, only volume API v2 is supported by this class. - """ + """Fake one or more volumes.""" @staticmethod def create_one_volume(attrs=None): @@ -547,6 +546,106 @@ class FakeConsistencyGroup(object): return consistency_groups + @staticmethod + def get_consistency_groups(consistency_groups=None, count=2): + """Note: + + Get an iterable MagicMock object with a list of faked + consistency_groups. + + If consistency_groups list is provided, then initialize + the Mock object with the list. Otherwise create one. + + :param List consistency_groups: + A list of FakeResource objects faking consistency_groups + :param Integer count: + The number of consistency_groups to be faked + :return + An iterable Mock object with side_effect set to a list of faked + consistency_groups + """ + if consistency_groups is None: + consistency_groups = (FakeConsistencyGroup. + create_consistency_groups(count)) + + return mock.Mock(side_effect=consistency_groups) + + +class FakeConsistencyGroupSnapshot(object): + """Fake one or more consistency group snapshot.""" + + @staticmethod + def create_one_consistency_group_snapshot(attrs=None): + """Create a fake consistency group snapshot. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with id, name, description, etc. + """ + attrs = attrs or {} + + # Set default attributes. + consistency_group_snapshot_info = { + "id": 'id-' + uuid.uuid4().hex, + "name": 'backup-name-' + uuid.uuid4().hex, + "description": 'description-' + uuid.uuid4().hex, + "status": "error", + "consistencygroup_id": 'consistency-group-id' + uuid.uuid4().hex, + "created_at": 'time-' + uuid.uuid4().hex, + } + + # Overwrite default attributes. + consistency_group_snapshot_info.update(attrs) + + consistency_group_snapshot = fakes.FakeResource( + info=copy.deepcopy(consistency_group_snapshot_info), + loaded=True) + return consistency_group_snapshot + + @staticmethod + def create_consistency_group_snapshots(attrs=None, count=2): + """Create multiple fake consistency group snapshots. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of consistency group snapshots to fake + :return: + A list of FakeResource objects faking the + consistency group snapshots + """ + consistency_group_snapshots = [] + for i in range(0, count): + consistency_group_snapshot = ( + FakeConsistencyGroupSnapshot. + create_one_consistency_group_snapshot(attrs) + ) + consistency_group_snapshots.append(consistency_group_snapshot) + + return consistency_group_snapshots + + @staticmethod + def get_consistency_group_snapshots(snapshots=None, count=2): + """Get an iterable MagicMock object with a list of faked cgsnapshots. + + If consistenct group snapshots list is provided, then initialize + the Mock object with the list. Otherwise create one. + + :param List snapshots: + A list of FakeResource objects faking consistency group snapshots + :param Integer count: + The number of consistency group snapshots to be faked + :return + An iterable Mock object with side_effect set to a list of faked + consistency groups + """ + if snapshots is None: + snapshots = (FakeConsistencyGroupSnapshot. + create_consistency_group_snapshots(count)) + + return mock.Mock(side_effect=snapshots) + class FakeExtension(object): """Fake one or more extension.""" diff --git a/openstackclient/tests/unit/volume/v2/test_backup.py b/openstackclient/tests/unit/volume/v2/test_backup.py index 306c9eb3..10e7aac5 100644 --- a/openstackclient/tests/unit/volume/v2/test_backup.py +++ b/openstackclient/tests/unit/volume/v2/test_backup.py @@ -280,26 +280,78 @@ class TestBackupList(TestBackup): self.volumes_mock.list.return_value = [self.volume] self.backups_mock.list.return_value = self.backups + self.volumes_mock.get.return_value = self.volume + self.backups_mock.get.return_value = self.backups[0] # Get the command to test self.cmd = backup.ListVolumeBackup(self.app, None) def test_backup_list_without_options(self): arglist = [] - verifylist = [("long", False)] + verifylist = [ + ("long", False), + ("name", None), + ("status", None), + ("volume", None), + ("marker", None), + ("limit", None), + ('all_projects', False), + ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + search_opts = { + "name": None, + "status": None, + "volume_id": None, + 'all_tenants': False, + } + self.volumes_mock.get.assert_not_called() + self.backups_mock.get.assert_not_called() + self.backups_mock.list.assert_called_with( + search_opts=search_opts, + marker=None, + limit=None, + ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) def test_backup_list_with_options(self): - arglist = ["--long"] - verifylist = [("long", True)] + arglist = [ + "--long", + "--name", self.backups[0].name, + "--status", "error", + "--volume", self.volume.id, + "--marker", self.backups[0].id, + "--all-projects", + "--limit", "3", + ] + verifylist = [ + ("long", True), + ("name", self.backups[0].name), + ("status", "error"), + ("volume", self.volume.id), + ("marker", self.backups[0].id), + ('all_projects', True), + ("limit", 3), + ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + search_opts = { + "name": self.backups[0].name, + "status": "error", + "volume_id": self.volume.id, + 'all_tenants': True, + } + self.volumes_mock.get.assert_called_once_with(self.volume.id) + self.backups_mock.get.assert_called_once_with(self.backups[0].id) + self.backups_mock.list.assert_called_with( + search_opts=search_opts, + marker=self.backups[0].id, + limit=3, + ) self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py index 00e1b60e..5beb6ef2 100644 --- a/openstackclient/tests/unit/volume/v2/test_consistency_group.py +++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py @@ -12,6 +12,10 @@ # under the License. # +import mock +from mock import call + +from osc_lib import exceptions from osc_lib import utils from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes @@ -28,6 +32,235 @@ class TestConsistencyGroup(volume_fakes.TestVolume): self.app.client_manager.volume.consistencygroups) self.consistencygroups_mock.reset_mock() + self.types_mock = self.app.client_manager.volume.volume_types + self.types_mock.reset_mock() + + +class TestConsistencyGroupCreate(TestConsistencyGroup): + + volume_type = volume_fakes.FakeType.create_one_type() + new_consistency_group = ( + volume_fakes.FakeConsistencyGroup.create_one_consistency_group()) + + columns = ( + 'availability_zone', + 'created_at', + 'description', + 'id', + 'name', + 'status', + 'volume_types', + ) + data = ( + new_consistency_group.availability_zone, + new_consistency_group.created_at, + new_consistency_group.description, + new_consistency_group.id, + new_consistency_group.name, + new_consistency_group.status, + new_consistency_group.volume_types, + ) + + def setUp(self): + super(TestConsistencyGroupCreate, self).setUp() + self.consistencygroups_mock.create.return_value = ( + self.new_consistency_group) + self.consistencygroups_mock.create_from_src.return_value = ( + self.new_consistency_group) + self.consistencygroups_mock.get.return_value = ( + self.new_consistency_group) + self.types_mock.get.return_value = self.volume_type + + # Get the command object to test + self.cmd = consistency_group.CreateConsistencyGroup(self.app, None) + + def test_consistency_group_create(self): + arglist = [ + '--volume-type', self.volume_type.id, + '--description', self.new_consistency_group.description, + '--availability-zone', + self.new_consistency_group.availability_zone, + self.new_consistency_group.name, + ] + verifylist = [ + ('volume_type', self.volume_type.id), + ('description', self.new_consistency_group.description), + ('availability_zone', + self.new_consistency_group.availability_zone), + ('name', self.new_consistency_group.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.types_mock.get.assert_called_once_with( + self.volume_type.id) + self.consistencygroups_mock.get.assert_not_called() + self.consistencygroups_mock.create.assert_called_once_with( + self.volume_type.id, + name=self.new_consistency_group.name, + description=self.new_consistency_group.description, + availability_zone=self.new_consistency_group.availability_zone, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_consistency_group_create_without_name(self): + arglist = [ + '--volume-type', self.volume_type.id, + '--description', self.new_consistency_group.description, + '--availability-zone', + self.new_consistency_group.availability_zone, + ] + verifylist = [ + ('volume_type', self.volume_type.id), + ('description', self.new_consistency_group.description), + ('availability_zone', + self.new_consistency_group.availability_zone), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.types_mock.get.assert_called_once_with( + self.volume_type.id) + self.consistencygroups_mock.get.assert_not_called() + self.consistencygroups_mock.create.assert_called_once_with( + self.volume_type.id, + name=None, + description=self.new_consistency_group.description, + availability_zone=self.new_consistency_group.availability_zone, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_consistency_group_create_from_source(self): + arglist = [ + '--consistency-group-source', self.new_consistency_group.id, + '--description', self.new_consistency_group.description, + self.new_consistency_group.name, + ] + verifylist = [ + ('consistency_group_source', self.new_consistency_group.id), + ('description', self.new_consistency_group.description), + ('name', self.new_consistency_group.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.types_mock.get.assert_not_called() + self.consistencygroups_mock.get.assert_called_once_with( + self.new_consistency_group.id) + self.consistencygroups_mock.create_from_src.assert_called_with( + None, + self.new_consistency_group.id, + name=self.new_consistency_group.name, + description=self.new_consistency_group.description, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestConsistencyGroupDelete(TestConsistencyGroup): + + consistency_groups =\ + volume_fakes.FakeConsistencyGroup.create_consistency_groups(count=2) + + def setUp(self): + super(TestConsistencyGroupDelete, self).setUp() + + self.consistencygroups_mock.get = volume_fakes.FakeConsistencyGroup.\ + get_consistency_groups(self.consistency_groups) + self.consistencygroups_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = consistency_group.DeleteConsistencyGroup(self.app, None) + + def test_consistency_group_delete(self): + arglist = [ + self.consistency_groups[0].id + ] + verifylist = [ + ("consistency_groups", [self.consistency_groups[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.consistencygroups_mock.delete.assert_called_with( + self.consistency_groups[0].id, False) + self.assertIsNone(result) + + def test_consistency_group_delete_with_force(self): + arglist = [ + '--force', + self.consistency_groups[0].id, + ] + verifylist = [ + ('force', True), + ("consistency_groups", [self.consistency_groups[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.consistencygroups_mock.delete.assert_called_with( + self.consistency_groups[0].id, True) + self.assertIsNone(result) + + def test_delete_multiple_consistency_groups(self): + arglist = [] + for b in self.consistency_groups: + arglist.append(b.id) + verifylist = [ + ('consistency_groups', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for b in self.consistency_groups: + calls.append(call(b.id, False)) + self.consistencygroups_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_consistency_groups_with_exception(self): + arglist = [ + self.consistency_groups[0].id, + 'unexist_consistency_group', + ] + verifylist = [ + ('consistency_groups', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.consistency_groups[0], + exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 consistency groups failed to delete.', + str(e)) + + find_mock.assert_any_call(self.consistencygroups_mock, + self.consistency_groups[0].id) + find_mock.assert_any_call(self.consistencygroups_mock, + 'unexist_consistency_group') + + self.assertEqual(2, find_mock.call_count) + self.consistencygroups_mock.delete.assert_called_once_with( + self.consistency_groups[0].id, False + ) + class TestConsistencyGroupList(TestConsistencyGroup): @@ -120,3 +353,46 @@ class TestConsistencyGroupList(TestConsistencyGroup): detailed=True, search_opts={'all_tenants': False}) self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) + + +class TestConsistencyGroupShow(TestConsistencyGroup): + columns = ( + 'availability_zone', + 'created_at', + 'description', + 'id', + 'name', + 'status', + 'volume_types', + ) + + def setUp(self): + super(TestConsistencyGroupShow, self).setUp() + + self.consistency_group = ( + volume_fakes.FakeConsistencyGroup.create_one_consistency_group()) + self.data = ( + self.consistency_group.availability_zone, + self.consistency_group.created_at, + self.consistency_group.description, + self.consistency_group.id, + self.consistency_group.name, + self.consistency_group.status, + self.consistency_group.volume_types, + ) + self.consistencygroups_mock.get.return_value = self.consistency_group + self.cmd = consistency_group.ShowConsistencyGroup(self.app, None) + + def test_consistency_group_show(self): + arglist = [ + self.consistency_group.id + ] + verifylist = [ + ("consistency_group", self.consistency_group.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.consistencygroups_mock.get.assert_called_once_with( + self.consistency_group.id) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group_snapshot.py b/openstackclient/tests/unit/volume/v2/test_consistency_group_snapshot.py new file mode 100644 index 00000000..3bfe93df --- /dev/null +++ b/openstackclient/tests/unit/volume/v2/test_consistency_group_snapshot.py @@ -0,0 +1,351 @@ +# +# 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. +# + +from mock import call + +from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes +from openstackclient.volume.v2 import consistency_group_snapshot + + +class TestConsistencyGroupSnapshot(volume_fakes.TestVolume): + + def setUp(self): + super(TestConsistencyGroupSnapshot, self).setUp() + + # Get a shortcut to the TransferManager Mock + self.cgsnapshots_mock = ( + self.app.client_manager.volume.cgsnapshots) + self.cgsnapshots_mock.reset_mock() + self.consistencygroups_mock = ( + self.app.client_manager.volume.consistencygroups) + self.consistencygroups_mock.reset_mock() + + +class TestConsistencyGroupSnapshotCreate(TestConsistencyGroupSnapshot): + + _consistency_group_snapshot = ( + volume_fakes. + FakeConsistencyGroupSnapshot. + create_one_consistency_group_snapshot() + ) + consistency_group = ( + volume_fakes.FakeConsistencyGroup.create_one_consistency_group()) + + columns = ( + 'consistencygroup_id', + 'created_at', + 'description', + 'id', + 'name', + 'status', + ) + data = ( + _consistency_group_snapshot.consistencygroup_id, + _consistency_group_snapshot.created_at, + _consistency_group_snapshot.description, + _consistency_group_snapshot.id, + _consistency_group_snapshot.name, + _consistency_group_snapshot.status, + ) + + def setUp(self): + super(TestConsistencyGroupSnapshotCreate, self).setUp() + self.cgsnapshots_mock.create.return_value = ( + self._consistency_group_snapshot) + self.consistencygroups_mock.get.return_value = ( + self.consistency_group) + + # Get the command object to test + self.cmd = (consistency_group_snapshot. + CreateConsistencyGroupSnapshot(self.app, None)) + + def test_consistency_group_snapshot_create(self): + arglist = [ + '--consistency-group', self.consistency_group.id, + '--description', self._consistency_group_snapshot.description, + self._consistency_group_snapshot.name, + ] + verifylist = [ + ('consistency_group', self.consistency_group.id), + ('description', self._consistency_group_snapshot.description), + ('snapshot_name', self._consistency_group_snapshot.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.consistencygroups_mock.get.assert_called_once_with( + self.consistency_group.id) + self.cgsnapshots_mock.create.assert_called_once_with( + self.consistency_group.id, + name=self._consistency_group_snapshot.name, + description=self._consistency_group_snapshot.description, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_consistency_group_snapshot_create_no_consistency_group(self): + arglist = [ + '--description', self._consistency_group_snapshot.description, + self._consistency_group_snapshot.name, + ] + verifylist = [ + ('description', self._consistency_group_snapshot.description), + ('snapshot_name', self._consistency_group_snapshot.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.consistencygroups_mock.get.assert_called_once_with( + self._consistency_group_snapshot.name) + self.cgsnapshots_mock.create.assert_called_once_with( + self.consistency_group.id, + name=self._consistency_group_snapshot.name, + description=self._consistency_group_snapshot.description, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestConsistencyGroupSnapshotDelete(TestConsistencyGroupSnapshot): + + consistency_group_snapshots = ( + volume_fakes.FakeConsistencyGroupSnapshot. + create_consistency_group_snapshots(count=2) + ) + + def setUp(self): + super(TestConsistencyGroupSnapshotDelete, self).setUp() + + self.cgsnapshots_mock.get = ( + volume_fakes.FakeConsistencyGroupSnapshot. + get_consistency_group_snapshots(self.consistency_group_snapshots) + ) + self.cgsnapshots_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = (consistency_group_snapshot. + DeleteConsistencyGroupSnapshot(self.app, None)) + + def test_consistency_group_snapshot_delete(self): + arglist = [ + self.consistency_group_snapshots[0].id + ] + verifylist = [ + ("consistency_group_snapshot", + [self.consistency_group_snapshots[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.cgsnapshots_mock.delete.assert_called_once_with( + self.consistency_group_snapshots[0].id) + self.assertIsNone(result) + + def test_multiple_consistency_group_snapshots_delete(self): + arglist = [] + for c in self.consistency_group_snapshots: + arglist.append(c.id) + verifylist = [ + ('consistency_group_snapshot', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for c in self.consistency_group_snapshots: + calls.append(call(c.id)) + self.cgsnapshots_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + +class TestConsistencyGroupSnapshotList(TestConsistencyGroupSnapshot): + + consistency_group_snapshots = ( + volume_fakes.FakeConsistencyGroupSnapshot. + create_consistency_group_snapshots(count=2) + ) + consistency_group = ( + volume_fakes.FakeConsistencyGroup.create_one_consistency_group() + ) + + columns = [ + 'ID', + 'Status', + 'Name', + ] + columns_long = [ + 'ID', + 'Status', + 'ConsistencyGroup ID', + 'Name', + 'Description', + 'Created At', + ] + data = [] + for c in consistency_group_snapshots: + data.append(( + c.id, + c.status, + c.name, + )) + data_long = [] + for c in consistency_group_snapshots: + data_long.append(( + c.id, + c.status, + c.consistencygroup_id, + c.name, + c.description, + c.created_at, + )) + + def setUp(self): + super(TestConsistencyGroupSnapshotList, self).setUp() + + self.cgsnapshots_mock.list.return_value = ( + self.consistency_group_snapshots) + self.consistencygroups_mock.get.return_value = self.consistency_group + # Get the command to test + self.cmd = ( + consistency_group_snapshot. + ListConsistencyGroupSnapshot(self.app, None) + ) + + def test_consistency_group_snapshot_list_without_options(self): + arglist = [] + verifylist = [ + ("all_projects", False), + ("long", False), + ("status", None), + ("consistency_group", None), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + search_opts = { + 'all_tenants': False, + 'status': None, + 'consistencygroup_id': None, + } + self.cgsnapshots_mock.list.assert_called_once_with( + detailed=True, search_opts=search_opts) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_consistency_group_snapshot_list_with_long(self): + arglist = [ + "--long", + ] + verifylist = [ + ("all_projects", False), + ("long", True), + ("status", None), + ("consistency_group", None), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + search_opts = { + 'all_tenants': False, + 'status': None, + 'consistencygroup_id': None, + } + self.cgsnapshots_mock.list.assert_called_once_with( + detailed=True, search_opts=search_opts) + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) + + def test_consistency_group_snapshot_list_with_options(self): + arglist = [ + "--all-project", + "--status", self.consistency_group_snapshots[0].status, + "--consistency-group", self.consistency_group.id, + ] + verifylist = [ + ("all_projects", True), + ("long", False), + ("status", self.consistency_group_snapshots[0].status), + ("consistency_group", self.consistency_group.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + search_opts = { + 'all_tenants': True, + 'status': self.consistency_group_snapshots[0].status, + 'consistencygroup_id': self.consistency_group.id, + } + self.consistencygroups_mock.get.assert_called_once_with( + self.consistency_group.id) + self.cgsnapshots_mock.list.assert_called_once_with( + detailed=True, search_opts=search_opts) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestConsistencyGroupSnapshotShow(TestConsistencyGroupSnapshot): + + _consistency_group_snapshot = ( + volume_fakes. + FakeConsistencyGroupSnapshot. + create_one_consistency_group_snapshot() + ) + + columns = ( + 'consistencygroup_id', + 'created_at', + 'description', + 'id', + 'name', + 'status', + ) + data = ( + _consistency_group_snapshot.consistencygroup_id, + _consistency_group_snapshot.created_at, + _consistency_group_snapshot.description, + _consistency_group_snapshot.id, + _consistency_group_snapshot.name, + _consistency_group_snapshot.status, + ) + + def setUp(self): + super(TestConsistencyGroupSnapshotShow, self).setUp() + + self.cgsnapshots_mock.get.return_value = ( + self._consistency_group_snapshot) + self.cmd = (consistency_group_snapshot. + ShowConsistencyGroupSnapshot(self.app, None)) + + def test_consistency_group_snapshot_show(self): + arglist = [ + self._consistency_group_snapshot.id + ] + verifylist = [ + ("consistency_group_snapshot", self._consistency_group_snapshot.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.cgsnapshots_mock.get.assert_called_once_with( + self._consistency_group_snapshot.id) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/openstackclient/tests/unit/volume/v2/test_snapshot.py b/openstackclient/tests/unit/volume/v2/test_snapshot.py index d355662d..b67dd6eb 100644 --- a/openstackclient/tests/unit/volume/v2/test_snapshot.py +++ b/openstackclient/tests/unit/volume/v2/test_snapshot.py @@ -20,7 +20,7 @@ from osc_lib import exceptions from osc_lib import utils from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes -from openstackclient.volume.v2 import snapshot +from openstackclient.volume.v2 import volume_snapshot class TestSnapshot(volume_fakes.TestVolume): @@ -68,23 +68,23 @@ class TestSnapshotCreate(TestSnapshot): self.volumes_mock.get.return_value = self.volume self.snapshots_mock.create.return_value = self.new_snapshot # Get the command object to test - self.cmd = snapshot.CreateSnapshot(self.app, None) + self.cmd = volume_snapshot.CreateVolumeSnapshot(self.app, None) def test_snapshot_create(self): arglist = [ - "--name", self.new_snapshot.name, + "--volume", self.new_snapshot.volume_id, "--description", self.new_snapshot.description, "--force", '--property', 'Alpha=a', '--property', 'Beta=b', - self.new_snapshot.volume_id, + self.new_snapshot.name, ] verifylist = [ - ("name", self.new_snapshot.name), + ("volume", self.new_snapshot.volume_id), ("description", self.new_snapshot.description), ("force", True), ('property', {'Alpha': 'a', 'Beta': 'b'}), - ("volume", self.new_snapshot.volume_id), + ("snapshot_name", self.new_snapshot.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -102,7 +102,7 @@ class TestSnapshotCreate(TestSnapshot): def test_snapshot_create_without_name(self): arglist = [ - self.new_snapshot.volume_id, + "--volume", self.new_snapshot.volume_id, "--description", self.new_snapshot.description, "--force" ] @@ -125,6 +125,33 @@ class TestSnapshotCreate(TestSnapshot): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_snapshot_create_without_volume(self): + arglist = [ + "--description", self.new_snapshot.description, + "--force", + self.new_snapshot.name + ] + verifylist = [ + ("description", self.new_snapshot.description), + ("force", True), + ("snapshot_name", self.new_snapshot.name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.get.assert_called_once_with( + self.new_snapshot.name) + self.snapshots_mock.create.assert_called_once_with( + self.new_snapshot.volume_id, + force=True, + name=self.new_snapshot.name, + description=self.new_snapshot.description, + metadata=None, + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestSnapshotDelete(TestSnapshot): @@ -138,7 +165,7 @@ class TestSnapshotDelete(TestSnapshot): self.snapshots_mock.delete.return_value = None # Get the command object to mock - self.cmd = snapshot.DeleteSnapshot(self.app, None) + self.cmd = volume_snapshot.DeleteVolumeSnapshot(self.app, None) def test_snapshot_delete(self): arglist = [ @@ -250,7 +277,7 @@ class TestSnapshotList(TestSnapshot): self.volumes_mock.list.return_value = [self.volume] self.snapshots_mock.list.return_value = self.snapshots # Get the command to test - self.cmd = snapshot.ListSnapshot(self.app, None) + self.cmd = volume_snapshot.ListVolumeSnapshot(self.app, None) def test_snapshot_list_without_options(self): arglist = [] @@ -330,7 +357,7 @@ class TestSnapshotSet(TestSnapshot): self.snapshots_mock.set_metadata.return_value = None self.snapshots_mock.update.return_value = None # Get the command object to mock - self.cmd = snapshot.SetSnapshot(self.app, None) + self.cmd = volume_snapshot.SetVolumeSnapshot(self.app, None) def test_snapshot_set(self): arglist = [ @@ -457,7 +484,7 @@ class TestSnapshotShow(TestSnapshot): self.snapshots_mock.get.return_value = self.snapshot # Get the command object to test - self.cmd = snapshot.ShowSnapshot(self.app, None) + self.cmd = volume_snapshot.ShowVolumeSnapshot(self.app, None) def test_snapshot_show(self): arglist = [ @@ -485,7 +512,7 @@ class TestSnapshotUnset(TestSnapshot): self.snapshots_mock.get.return_value = self.snapshot self.snapshots_mock.delete_metadata.return_value = None # Get the command object to mock - self.cmd = snapshot.UnsetSnapshot(self.app, None) + self.cmd = volume_snapshot.UnsetVolumeSnapshot(self.app, None) def test_snapshot_unset(self): arglist = [ diff --git a/openstackclient/tests/unit/volume/v2/test_type.py b/openstackclient/tests/unit/volume/v2/test_type.py index 84f87e3b..325872d7 100644 --- a/openstackclient/tests/unit/volume/v2/test_type.py +++ b/openstackclient/tests/unit/volume/v2/test_type.py @@ -165,7 +165,8 @@ class TestTypeList(TestType): columns = [ "ID", - "Name" + "Name", + "Is Public", ] columns_long = columns + [ "Description", @@ -177,12 +178,14 @@ class TestTypeList(TestType): data.append(( t.id, t.name, + t.is_public, )) data_long = [] for t in volume_types: data_long.append(( t.id, t.name, + t.is_public, t.description, utils.format_dict(t.extra_specs), )) diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index f4a7c142..41728342 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -46,6 +46,9 @@ class TestVolume(volume_fakes.TestVolume): self.snapshots_mock = self.app.client_manager.volume.volume_snapshots self.snapshots_mock.reset_mock() + self.types_mock = self.app.client_manager.volume.volume_types + self.types_mock.reset_mock() + self.consistencygroups_mock = ( self.app.client_manager.volume.consistencygroups) self.consistencygroups_mock.reset_mock() @@ -996,13 +999,106 @@ class TestVolumeList(TestVolume): self.cmd, arglist, verifylist) +class TestVolumeMigrate(TestVolume): + + _volume = volume_fakes.FakeVolume.create_one_volume() + + def setUp(self): + super(TestVolumeMigrate, self).setUp() + + self.volumes_mock.get.return_value = self._volume + self.volumes_mock.migrate_volume.return_value = None + # Get the command object to test + self.cmd = volume.MigrateVolume(self.app, None) + + def test_volume_migrate(self): + arglist = [ + "--host", "host@backend-name#pool", + self._volume.id, + ] + verifylist = [ + ("force_host_copy", False), + ("lock_volume", False), + ("unlock_volume", False), + ("host", "host@backend-name#pool"), + ("volume", self._volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.get.assert_called_once_with(self._volume.id) + self.volumes_mock.migrate_volume.assert_called_once_with( + self._volume.id, "host@backend-name#pool", False, False) + self.assertIsNone(result) + + def test_volume_migrate_with_option(self): + arglist = [ + "--force-host-copy", + "--lock-volume", + "--host", "host@backend-name#pool", + self._volume.id, + ] + verifylist = [ + ("force_host_copy", True), + ("lock_volume", True), + ("unlock_volume", False), + ("host", "host@backend-name#pool"), + ("volume", self._volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.get.assert_called_once_with(self._volume.id) + self.volumes_mock.migrate_volume.assert_called_once_with( + self._volume.id, "host@backend-name#pool", True, True) + self.assertIsNone(result) + + def test_volume_migrate_with_unlock_volume(self): + arglist = [ + "--unlock-volume", + "--host", "host@backend-name#pool", + self._volume.id, + ] + verifylist = [ + ("force_host_copy", False), + ("lock_volume", False), + ("unlock_volume", True), + ("host", "host@backend-name#pool"), + ("volume", self._volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.get.assert_called_once_with(self._volume.id) + self.volumes_mock.migrate_volume.assert_called_once_with( + self._volume.id, "host@backend-name#pool", False, False) + self.assertIsNone(result) + + def test_volume_migrate_without_host(self): + arglist = [ + self._volume.id, + ] + verifylist = [ + ("force_host_copy", False), + ("lock_volume", False), + ("unlock_volume", False), + ("volume", self._volume.id), + ] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + class TestVolumeSet(TestVolume): + volume_type = volume_fakes.FakeType.create_one_type() + def setUp(self): super(TestVolumeSet, self).setUp() self.new_volume = volume_fakes.FakeVolume.create_one_volume() self.volumes_mock.get.return_value = self.new_volume + self.types_mock.get.return_value = self.volume_type # Get the command object to test self.cmd = volume.SetVolume(self.app, None) @@ -1033,6 +1129,8 @@ class TestVolumeSet(TestVolume): self.new_volume.id ] verifylist = [ + ('read_only', False), + ('read_write', False), ('state', 'error'), ('volume', self.new_volume.id) ] @@ -1042,6 +1140,7 @@ class TestVolumeSet(TestVolume): result = self.cmd.take_action(parsed_args) self.volumes_mock.reset_state.assert_called_with( self.new_volume.id, 'error') + self.volumes_mock.update_readonly_flag.assert_not_called() self.assertIsNone(result) def test_volume_set_state_failed(self): @@ -1090,6 +1189,104 @@ class TestVolumeSet(TestVolume): self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, verifylist[index][0][1]) + def test_volume_set_readonly(self): + arglist = [ + '--read-only', + self.new_volume.id + ] + verifylist = [ + ('read_only', True), + ('read_write', False), + ('volume', self.new_volume.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.update_readonly_flag.assert_called_once_with( + self.new_volume.id, + True) + self.assertIsNone(result) + + def test_volume_set_read_write(self): + arglist = [ + '--read-write', + self.new_volume.id + ] + verifylist = [ + ('read_only', False), + ('read_write', True), + ('volume', self.new_volume.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.update_readonly_flag.assert_called_once_with( + self.new_volume.id, + False) + self.assertIsNone(result) + + def test_volume_set_type(self): + arglist = [ + '--type', self.volume_type.id, + self.new_volume.id + ] + verifylist = [ + ('retype_policy', None), + ('type', self.volume_type.id), + ('volume', self.new_volume.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.retype.assert_called_once_with( + self.new_volume.id, + self.volume_type.id, + 'never') + self.assertIsNone(result) + + def test_volume_set_type_with_policy(self): + arglist = [ + '--retype-policy', 'on-demand', + '--type', self.volume_type.id, + self.new_volume.id + ] + verifylist = [ + ('retype_policy', 'on-demand'), + ('type', self.volume_type.id), + ('volume', self.new_volume.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.retype.assert_called_once_with( + self.new_volume.id, + self.volume_type.id, + 'on-demand') + self.assertIsNone(result) + + @mock.patch.object(volume.LOG, 'warning') + def test_volume_set_with_only_retype_policy(self, mock_warning): + arglist = [ + '--retype-policy', 'on-demand', + self.new_volume.id + ] + verifylist = [ + ('retype_policy', 'on-demand'), + ('volume', self.new_volume.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.retype.assert_not_called() + mock_warning.assert_called_with("'--retype-policy' option will " + "not work without '--type' option") + self.assertIsNone(result) + class TestVolumeShow(TestVolume): diff --git a/openstackclient/tests/unit/volume/v2/test_volume_host.py b/openstackclient/tests/unit/volume/v2/test_volume_host.py new file mode 100644 index 00000000..aad7bb0b --- /dev/null +++ b/openstackclient/tests/unit/volume/v2/test_volume_host.py @@ -0,0 +1,86 @@ +# +# 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. +# + +from openstackclient.tests.unit.volume.v2 import fakes as host_fakes +from openstackclient.volume.v2 import volume_host + + +class TestVolumeHost(host_fakes.TestVolume): + + def setUp(self): + super(TestVolumeHost, self).setUp() + + self.host_mock = self.app.client_manager.volume.services + self.host_mock.reset_mock() + + +class TestVolumeHostSet(TestVolumeHost): + + service = host_fakes.FakeService.create_one_service() + + def setUp(self): + super(TestVolumeHostSet, self).setUp() + + self.host_mock.freeze_host.return_value = None + self.host_mock.thaw_host.return_value = None + + self.cmd = volume_host.SetVolumeHost(self.app, None) + + def test_volume_host_set_nothing(self): + arglist = [ + self.service.host, + ] + verifylist = [ + ('host', self.service.host), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.host_mock.freeze_host.assert_not_called() + self.host_mock.thaw_host.assert_not_called() + self.assertIsNone(result) + + def test_volume_host_set_enable(self): + arglist = [ + '--enable', + self.service.host, + ] + verifylist = [ + ('enable', True), + ('host', self.service.host), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.host_mock.thaw_host.assert_called_with(self.service.host) + self.host_mock.freeze_host.assert_not_called() + self.assertIsNone(result) + + def test_volume_host_set_disable(self): + arglist = [ + '--disable', + self.service.host, + ] + verifylist = [ + ('disable', True), + ('host', self.service.host), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.host_mock.freeze_host.assert_called_with(self.service.host) + self.host_mock.thaw_host.assert_not_called() + self.assertIsNone(result) diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index c9d0ca0d..9ac1302a 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__) class CreateVolumeBackup(command.ShowOne): - """Create new volume backup""" + _description = _("Create new volume backup") def get_parser(self, prog_name): parser = super(CreateVolumeBackup, self).get_parser(prog_name) @@ -73,7 +73,7 @@ class CreateVolumeBackup(command.ShowOne): class CreateBackup(CreateVolumeBackup): - """Create new backup""" + _description = _("Create new backup") # TODO(Huanxuan Ao): Remove this class and ``backup create`` command # two cycles after Newton. @@ -90,7 +90,7 @@ class CreateBackup(CreateVolumeBackup): class DeleteVolumeBackup(command.Command): - """Delete volume backup(s)""" + _description = _("Delete volume backup(s)") def get_parser(self, prog_name): parser = super(DeleteVolumeBackup, self).get_parser(prog_name) @@ -125,7 +125,7 @@ class DeleteVolumeBackup(command.Command): class DeleteBackup(DeleteVolumeBackup): - """Delete backup(s)""" + _description = _("Delete backup(s)") # TODO(Huanxuan Ao): Remove this class and ``backup delete`` command # two cycles after Newton. @@ -142,7 +142,7 @@ class DeleteBackup(DeleteVolumeBackup): class ListVolumeBackup(command.Lister): - """List volume backups""" + _description = _("List volume backups") def get_parser(self, prog_name): parser = super(ListVolumeBackup, self).get_parser(prog_name) @@ -152,9 +152,36 @@ class ListVolumeBackup(command.Lister): default=False, help=_('List additional fields in output'), ) + parser.add_argument( + "--name", + metavar="<name>", + help=_("Filters results by the backup name") + ) + parser.add_argument( + "--status", + metavar="<status>", + choices=['creating', 'available', 'deleting', + 'error', 'restoring', 'error_restoring'], + help=_("Filters results by the backup status " + "('creating', 'available', 'deleting', " + "'error', 'restoring' or 'error_restoring')") + ) + parser.add_argument( + "--volume", + metavar="<volume>", + help=_("Filters results by the volume which they " + "backup (name or ID)") + ) + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help=_('Include all projects (admin only)'), + ) return parser def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume def _format_volume_id(volume_id): """Return a volume name if available @@ -180,13 +207,25 @@ class ListVolumeBackup(command.Lister): # Cache the volume list volume_cache = {} try: - for s in self.app.client_manager.volume.volumes.list(): + for s in volume_client.volumes.list(): volume_cache[s.id] = s except Exception: # Just forget it if there's any trouble pass - data = self.app.client_manager.volume.backups.list() + filter_volume_id = None + if parsed_args.volume: + filter_volume_id = utils.find_resource(volume_client.volumes, + parsed_args.volume).id + search_opts = { + 'name': parsed_args.name, + 'status': parsed_args.status, + 'volume_id': filter_volume_id, + 'all_tenants': parsed_args.all_projects, + } + data = volume_client.backups.list( + search_opts=search_opts, + ) return (column_headers, (utils.get_item_properties( @@ -196,7 +235,7 @@ class ListVolumeBackup(command.Lister): class ListBackup(ListVolumeBackup): - """List backups""" + _description = _("List backups") # TODO(Huanxuan Ao): Remove this class and ``backup list`` command # two cycles after Newton. @@ -213,7 +252,7 @@ class ListBackup(ListVolumeBackup): class RestoreVolumeBackup(command.Command): - """Restore volume backup""" + _description = _("Restore volume backup") def get_parser(self, prog_name): parser = super(RestoreVolumeBackup, self).get_parser(prog_name) @@ -240,7 +279,7 @@ class RestoreVolumeBackup(command.Command): class RestoreBackup(RestoreVolumeBackup): - """Restore backup""" + _description = _("Restore backup") # TODO(Huanxuan Ao): Remove this class and ``backup restore`` command # two cycles after Newton. @@ -257,7 +296,7 @@ class RestoreBackup(RestoreVolumeBackup): class ShowVolumeBackup(command.ShowOne): - """Display volume backup details""" + _description = _("Display volume backup details") def get_parser(self, prog_name): parser = super(ShowVolumeBackup, self).get_parser(prog_name) @@ -277,7 +316,7 @@ class ShowVolumeBackup(command.ShowOne): class ShowBackup(ShowVolumeBackup): - """Display backup details""" + _description = _("Display backup details") # TODO(Huanxuan Ao): Remove this class and ``backup show`` command # two cycles after Newton. diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index 93c24a21..b824b351 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__) class AssociateQos(command.Command): - """Associate a QoS specification to a volume type""" + _description = _("Associate a QoS specification to a volume type") def get_parser(self, prog_name): parser = super(AssociateQos, self).get_parser(prog_name) @@ -57,7 +57,7 @@ class AssociateQos(command.Command): class CreateQos(command.ShowOne): - """Create new QoS specification""" + _description = _("Create new QoS specification") def get_parser(self, prog_name): parser = super(CreateQos, self).get_parser(prog_name) @@ -99,7 +99,7 @@ class CreateQos(command.ShowOne): class DeleteQos(command.Command): - """Delete QoS specification""" + _description = _("Delete QoS specification") def get_parser(self, prog_name): parser = super(DeleteQos, self).get_parser(prog_name) @@ -139,7 +139,7 @@ class DeleteQos(command.Command): class DisassociateQos(command.Command): - """Disassociate a QoS specification from a volume type""" + _description = _("Disassociate a QoS specification from a volume type") def get_parser(self, prog_name): parser = super(DisassociateQos, self).get_parser(prog_name) @@ -177,7 +177,7 @@ class DisassociateQos(command.Command): class ListQos(command.Lister): - """List QoS specifications""" + _description = _("List QoS specifications") def take_action(self, parsed_args): volume_client = self.app.client_manager.volume @@ -202,7 +202,7 @@ class ListQos(command.Lister): class SetQos(command.Command): - """Set QoS specification properties""" + _description = _("Set QoS specification properties") def get_parser(self, prog_name): parser = super(SetQos, self).get_parser(prog_name) @@ -231,7 +231,7 @@ class SetQos(command.Command): class ShowQos(command.ShowOne): - """Display QoS specification details""" + _description = _("Display QoS specification details") def get_parser(self, prog_name): parser = super(ShowQos, self).get_parser(prog_name) @@ -260,7 +260,7 @@ class ShowQos(command.ShowOne): class UnsetQos(command.Command): - """Unset QoS specification properties""" + _description = _("Unset QoS specification properties") def get_parser(self, prog_name): parser = super(UnsetQos, self).get_parser(prog_name) diff --git a/openstackclient/volume/v1/service.py b/openstackclient/volume/v1/service.py index 867c4b9c..d468c6ff 100644 --- a/openstackclient/volume/v1/service.py +++ b/openstackclient/volume/v1/service.py @@ -22,7 +22,7 @@ from openstackclient.i18n import _ class ListService(command.Lister): - """List service command""" + _description = _("List service command") def get_parser(self, prog_name): parser = super(ListService, self).get_parser(prog_name) @@ -76,7 +76,7 @@ class ListService(command.Lister): class SetService(command.Command): - """Set volume service properties""" + _description = _("Set volume service properties") def get_parser(self, prog_name): parser = super(SetService, self).get_parser(prog_name) diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index bc92c0f5..e9e3894b 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -13,6 +13,10 @@ # under the License. # +# TODO(Huanxuan Ao): Remove this file and "snapshot create", "snapshot delete", +# "snapshot set", "snapshot show" and "snapshot unset" +# commands two cycles after Ocata. + """Volume v1 Snapshot action implementations""" import copy @@ -27,11 +31,13 @@ import six from openstackclient.i18n import _ +deprecated = True +LOG_DEP = logging.getLogger('deprecated') LOG = logging.getLogger(__name__) class CreateSnapshot(command.ShowOne): - """Create new snapshot""" + _description = _("Create new snapshot") def get_parser(self, prog_name): parser = super(CreateSnapshot, self).get_parser(prog_name) @@ -61,6 +67,8 @@ class CreateSnapshot(command.ShowOne): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot create" instead.')) volume_client = self.app.client_manager.volume volume_id = utils.find_resource(volume_client.volumes, parsed_args.volume).id @@ -79,7 +87,7 @@ class CreateSnapshot(command.ShowOne): class DeleteSnapshot(command.Command): - """Delete snapshot(s)""" + _description = _("Delete snapshot(s)") def get_parser(self, prog_name): parser = super(DeleteSnapshot, self).get_parser(prog_name) @@ -92,6 +100,8 @@ class DeleteSnapshot(command.Command): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot delete" instead.')) volume_client = self.app.client_manager.volume result = 0 @@ -114,7 +124,7 @@ class DeleteSnapshot(command.Command): class ListSnapshot(command.Lister): - """List snapshots""" + _description = _("List snapshots") def get_parser(self, prog_name): parser = super(ListSnapshot, self).get_parser(prog_name) @@ -133,6 +143,8 @@ class ListSnapshot(command.Lister): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot list" instead.')) def _format_volume_id(volume_id): """Return a volume name if available @@ -185,7 +197,7 @@ class ListSnapshot(command.Lister): class SetSnapshot(command.Command): - """Set snapshot properties""" + _description = _("Set snapshot properties") def get_parser(self, prog_name): parser = super(SetSnapshot, self).get_parser(prog_name) @@ -214,6 +226,8 @@ class SetSnapshot(command.Command): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot set" instead.')) volume_client = self.app.client_manager.volume snapshot = utils.find_resource(volume_client.volume_snapshots, parsed_args.snapshot) @@ -246,7 +260,7 @@ class SetSnapshot(command.Command): class ShowSnapshot(command.ShowOne): - """Display snapshot details""" + _description = _("Display snapshot details") def get_parser(self, prog_name): parser = super(ShowSnapshot, self).get_parser(prog_name) @@ -258,6 +272,8 @@ class ShowSnapshot(command.ShowOne): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot show" instead.')) volume_client = self.app.client_manager.volume snapshot = utils.find_resource(volume_client.volume_snapshots, parsed_args.snapshot) @@ -270,7 +286,7 @@ class ShowSnapshot(command.ShowOne): class UnsetSnapshot(command.Command): - """Unset snapshot properties""" + _description = _("Unset snapshot properties") def get_parser(self, prog_name): parser = super(UnsetSnapshot, self).get_parser(prog_name) @@ -289,6 +305,8 @@ class UnsetSnapshot(command.Command): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot unset" instead.')) volume_client = self.app.client_manager.volume snapshot = utils.find_resource( volume_client.volume_snapshots, parsed_args.snapshot) diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index cafe8ce6..0087bad4 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -45,7 +45,7 @@ def _check_size_arg(args): class CreateVolume(command.ShowOne): - """Create new volume""" + _description = _("Create new volume") def get_parser(self, prog_name): parser = super(CreateVolume, self).get_parser(prog_name) @@ -178,7 +178,7 @@ class CreateVolume(command.ShowOne): class DeleteVolume(command.Command): - """Delete volume(s)""" + _description = _("Delete volume(s)") def get_parser(self, prog_name): parser = super(DeleteVolume, self).get_parser(prog_name) @@ -223,7 +223,7 @@ class DeleteVolume(command.Command): class ListVolume(command.Lister): - """List volumes""" + _description = _("List volumes") def get_parser(self, prog_name): parser = super(ListVolume, self).get_parser(prog_name) @@ -344,8 +344,39 @@ class ListVolume(command.Lister): ) for s in data)) +class MigrateVolume(command.Command): + _description = _("Migrate volume to a new host") + + def get_parser(self, prog_name): + parser = super(MigrateVolume, self).get_parser(prog_name) + parser.add_argument( + 'volume', + metavar="<volume>", + help=_("Volume to migrate (name or ID)") + ) + parser.add_argument( + '--host', + metavar="<host>", + required=True, + help=_("Destination host (takes the form: host@backend-name#pool)") + ) + parser.add_argument( + '--force-host-copy', + action="store_true", + help=_("Enable generic host-based force-migration, " + "which bypasses driver optimizations") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + volume = utils.find_resource(volume_client.volumes, parsed_args.volume) + volume_client.volumes.migrate_volume(volume.id, parsed_args.host, + parsed_args.force_host_copy,) + + class SetVolume(command.Command): - """Set volume properties""" + _description = _("Set volume properties") def get_parser(self, prog_name): parser = super(SetVolume, self).get_parser(prog_name) @@ -388,42 +419,82 @@ class SetVolume(command.Command): action="store_true", help=_("Mark volume as non-bootable") ) + readonly_group = parser.add_mutually_exclusive_group() + readonly_group.add_argument( + "--read-only", + action="store_true", + help=_("Set volume to read-only access mode") + ) + readonly_group.add_argument( + "--read-write", + action="store_true", + help=_("Set volume to read-write access mode") + ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume = utils.find_resource(volume_client.volumes, parsed_args.volume) + result = 0 if parsed_args.size: - if volume.status != 'available': - LOG.error(_("Volume is in %s state, it must be available " - "before size can be extended"), volume.status) - return - if parsed_args.size <= volume.size: - LOG.error(_("New size must be greater than %s GB"), - volume.size) - return - volume_client.volumes.extend(volume.id, parsed_args.size) - + try: + if volume.status != 'available': + msg = (_("Volume is in %s state, it must be available " + "before size can be extended") % volume.status) + raise exceptions.CommandError(msg) + if parsed_args.size <= volume.size: + msg = (_("New size must be greater than %s GB") + % volume.size) + raise exceptions.CommandError(msg) + volume_client.volumes.extend(volume.id, parsed_args.size) + except Exception as e: + LOG.error(_("Failed to set volume size: %s"), e) + result += 1 if parsed_args.property: - volume_client.volumes.set_metadata(volume.id, parsed_args.property) + try: + volume_client.volumes.set_metadata( + volume.id, + parsed_args.property) + except Exception as e: + LOG.error(_("Failed to set volume property: %s"), e) + result += 1 if parsed_args.bootable or parsed_args.non_bootable: try: volume_client.volumes.set_bootable( volume.id, parsed_args.bootable) except Exception as e: LOG.error(_("Failed to set volume bootable property: %s"), e) + result += 1 + if parsed_args.read_only or parsed_args.read_write: + try: + volume_client.volumes.update_readonly_flag( + volume.id, + parsed_args.read_only) + except Exception as e: + LOG.error(_("Failed to set volume read-only access " + "mode flag: %s"), e) + result += 1 kwargs = {} if parsed_args.name: kwargs['display_name'] = parsed_args.name if parsed_args.description: kwargs['display_description'] = parsed_args.description if kwargs: - volume_client.volumes.update(volume.id, **kwargs) + try: + volume_client.volumes.update(volume.id, **kwargs) + except Exception as e: + LOG.error(_("Failed to update volume display name " + "or display description: %s"), e) + result += 1 + + if result > 0: + raise exceptions.CommandError(_("One or more of the " + "set operations failed")) class ShowVolume(command.ShowOne): - """Show volume details""" + _description = _("Show volume details") def get_parser(self, prog_name): parser = super(ShowVolume, self).get_parser(prog_name) @@ -453,7 +524,7 @@ class ShowVolume(command.ShowOne): class UnsetVolume(command.Command): - """Unset volume properties""" + _description = _("Unset volume properties") def get_parser(self, prog_name): parser = super(UnsetVolume, self).get_parser(prog_name) diff --git a/openstackclient/volume/v1/volume_snapshot.py b/openstackclient/volume/v1/volume_snapshot.py new file mode 100644 index 00000000..c2ecf75b --- /dev/null +++ b/openstackclient/volume/v1/volume_snapshot.py @@ -0,0 +1,305 @@ +# 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 +# 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. +# + +"""Volume v1 Snapshot action implementations""" + +import copy +import logging + +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils +import six + +from openstackclient.i18n import _ + + +LOG = logging.getLogger(__name__) + + +class CreateVolumeSnapshot(command.ShowOne): + """Create new volume snapshot""" + + def get_parser(self, prog_name): + parser = super(CreateVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + 'snapshot_name', + metavar='<snapshot-name>', + nargs="?", + help=_('Name of the snapshot (default to None)'), + ) + parser.add_argument( + '--volume', + metavar='<volume>', + help=_('Volume to snapshot (name or ID) ' + '(default is <snapshot-name>)'), + ) + parser.add_argument( + '--description', + metavar='<description>', + help=_('Description of the snapshot'), + ) + parser.add_argument( + '--force', + dest='force', + action='store_true', + default=False, + help=_('Create a snapshot attached to an instance. ' + 'Default is False'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + volume = parsed_args.volume + if not parsed_args.volume: + volume = parsed_args.snapshot_name + volume_id = utils.find_resource(volume_client.volumes, + volume).id + snapshot = volume_client.volume_snapshots.create( + volume_id, + parsed_args.force, + parsed_args.snapshot_name, + parsed_args.description + ) + + snapshot._info.update( + {'properties': utils.format_dict(snapshot._info.pop('metadata'))} + ) + + return zip(*sorted(six.iteritems(snapshot._info))) + + +class DeleteVolumeSnapshot(command.Command): + """Delete volume snapshot(s)""" + + def get_parser(self, prog_name): + parser = super(DeleteVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + 'snapshots', + metavar='<snapshot>', + nargs="+", + help=_('Snapshot(s) to delete (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + result = 0 + + for i in parsed_args.snapshots: + try: + snapshot_id = utils.find_resource( + volume_client.volume_snapshots, i).id + volume_client.volume_snapshots.delete(snapshot_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete snapshot with " + "name or ID '%(snapshot)s': %(e)s"), + {'snapshot': i, 'e': e}) + + if result > 0: + total = len(parsed_args.snapshots) + msg = (_("%(result)s of %(total)s snapshots failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListVolumeSnapshot(command.Lister): + """List volume snapshots""" + + def get_parser(self, prog_name): + parser = super(ListVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help=_('Include all projects (admin only)'), + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help=_('List additional fields in output'), + ) + return parser + + def take_action(self, parsed_args): + + def _format_volume_id(volume_id): + """Return a volume name if available + + :param volume_id: a volume ID + :rtype: either the volume ID or name + """ + + volume = volume_id + if volume_id in volume_cache.keys(): + volume = volume_cache[volume_id].display_name + return volume + + if parsed_args.long: + columns = ['ID', 'Display Name', 'Display Description', 'Status', + 'Size', 'Created At', 'Volume ID', 'Metadata'] + column_headers = copy.deepcopy(columns) + column_headers[6] = 'Volume' + column_headers[7] = 'Properties' + else: + columns = ['ID', 'Display Name', 'Display Description', 'Status', + 'Size'] + column_headers = copy.deepcopy(columns) + + # Always update Name and Description + column_headers[1] = 'Name' + column_headers[2] = 'Description' + + # Cache the volume list + volume_cache = {} + try: + for s in self.app.client_manager.volume.volumes.list(): + volume_cache[s.id] = s + except Exception: + # Just forget it if there's any trouble + pass + + search_opts = { + 'all_tenants': parsed_args.all_projects, + } + + data = self.app.client_manager.volume.volume_snapshots.list( + search_opts=search_opts) + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters={'Metadata': utils.format_dict, + 'Volume ID': _format_volume_id}, + ) for s in data)) + + +class SetVolumeSnapshot(command.Command): + """Set volume snapshot properties""" + + def get_parser(self, prog_name): + parser = super(SetVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + 'snapshot', + metavar='<snapshot>', + help=_('Snapshot to modify (name or ID)') + ) + parser.add_argument( + '--name', + metavar='<name>', + help=_('New snapshot name') + ) + parser.add_argument( + '--description', + metavar='<description>', + help=_('New snapshot description') + ) + parser.add_argument( + '--property', + metavar='<key=value>', + action=parseractions.KeyValueAction, + help=_('Property to add/change for this snapshot ' + '(repeat option to set multiple properties)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + snapshot = utils.find_resource(volume_client.volume_snapshots, + parsed_args.snapshot) + + result = 0 + if parsed_args.property: + try: + volume_client.volume_snapshots.set_metadata( + snapshot.id, parsed_args.property) + except Exception as e: + LOG.error(_("Failed to set snapshot property: %s"), e) + result += 1 + + kwargs = {} + if parsed_args.name: + kwargs['display_name'] = parsed_args.name + if parsed_args.description: + kwargs['display_description'] = parsed_args.description + if kwargs: + try: + snapshot.update(**kwargs) + except Exception as e: + LOG.error(_("Failed to update snapshot display name " + "or display description: %s"), e) + result += 1 + + if result > 0: + raise exceptions.CommandError(_("One or more of the " + "set operations failed")) + + +class ShowVolumeSnapshot(command.ShowOne): + """Display volume snapshot details""" + + def get_parser(self, prog_name): + parser = super(ShowVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + 'snapshot', + metavar='<snapshot>', + help=_('Snapshot to display (name or ID)') + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + snapshot = utils.find_resource(volume_client.volume_snapshots, + parsed_args.snapshot) + + snapshot._info.update( + {'properties': utils.format_dict(snapshot._info.pop('metadata'))} + ) + + return zip(*sorted(six.iteritems(snapshot._info))) + + +class UnsetVolumeSnapshot(command.Command): + """Unset volume snapshot properties""" + + def get_parser(self, prog_name): + parser = super(UnsetVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + 'snapshot', + metavar='<snapshot>', + help=_('Snapshot to modify (name or ID)'), + ) + parser.add_argument( + '--property', + metavar='<key>', + action='append', + help=_('Property to remove from snapshot ' + '(repeat option to remove multiple properties)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + snapshot = utils.find_resource( + volume_client.volume_snapshots, parsed_args.snapshot) + + if parsed_args.property: + volume_client.volume_snapshots.delete_metadata( + snapshot.id, + parsed_args.property, + ) diff --git a/openstackclient/volume/v1/volume_transfer_request.py b/openstackclient/volume/v1/volume_transfer_request.py index 4d6f2161..f24d5a56 100644 --- a/openstackclient/volume/v1/volume_transfer_request.py +++ b/openstackclient/volume/v1/volume_transfer_request.py @@ -28,7 +28,7 @@ LOG = logging.getLogger(__name__) class AcceptTransferRequest(command.ShowOne): - """Accept volume transfer request.""" + _description = _("Accept volume transfer request.") def get_parser(self, prog_name): parser = super(AcceptTransferRequest, self).get_parser(prog_name) @@ -56,7 +56,7 @@ class AcceptTransferRequest(command.ShowOne): class CreateTransferRequest(command.ShowOne): - """Create volume transfer request.""" + _description = _("Create volume transfer request.") def get_parser(self, prog_name): parser = super(CreateTransferRequest, self).get_parser(prog_name) @@ -85,7 +85,7 @@ class CreateTransferRequest(command.ShowOne): class DeleteTransferRequest(command.Command): - """Delete volume transfer request(s).""" + _description = _("Delete volume transfer request(s).") def get_parser(self, prog_name): parser = super(DeleteTransferRequest, self).get_parser(prog_name) @@ -120,7 +120,7 @@ class DeleteTransferRequest(command.Command): class ListTransferRequest(command.Lister): - """Lists all volume transfer requests.""" + _description = _("Lists all volume transfer requests.") def get_parser(self, prog_name): parser = super(ListTransferRequest, self).get_parser(prog_name) @@ -151,7 +151,7 @@ class ListTransferRequest(command.Lister): class ShowTransferRequest(command.ShowOne): - """Show volume transfer request details.""" + _description = _("Show volume transfer request details.") def get_parser(self, prog_name): parser = super(ShowTransferRequest, self).get_parser(prog_name) diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index 61e9f7fc..8adce322 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__) class CreateVolumeType(command.ShowOne): - """Create new volume type""" + _description = _("Create new volume type") def get_parser(self, prog_name): parser = super(CreateVolumeType, self).get_parser(prog_name) @@ -61,7 +61,7 @@ class CreateVolumeType(command.ShowOne): class DeleteVolumeType(command.Command): - """Delete volume type(s)""" + _description = _("Delete volume type(s)") def get_parser(self, prog_name): parser = super(DeleteVolumeType, self).get_parser(prog_name) @@ -97,7 +97,7 @@ class DeleteVolumeType(command.Command): class ListVolumeType(command.Lister): - """List volume types""" + _description = _("List volume types") def get_parser(self, prog_name): parser = super(ListVolumeType, self).get_parser(prog_name) @@ -111,10 +111,10 @@ class ListVolumeType(command.Lister): def take_action(self, parsed_args): if parsed_args.long: - columns = ('ID', 'Name', 'Extra Specs') - column_headers = ('ID', 'Name', 'Properties') + columns = ('ID', 'Name', 'Is Public', 'Extra Specs') + column_headers = ('ID', 'Name', 'Is Public', 'Properties') else: - columns = ('ID', 'Name') + columns = ('ID', 'Name', 'Is Public') column_headers = columns data = self.app.client_manager.volume.volume_types.list() return (column_headers, @@ -125,7 +125,7 @@ class ListVolumeType(command.Lister): class SetVolumeType(command.Command): - """Set volume type properties""" + _description = _("Set volume type properties") def get_parser(self, prog_name): parser = super(SetVolumeType, self).get_parser(prog_name) @@ -153,7 +153,7 @@ class SetVolumeType(command.Command): class ShowVolumeType(command.ShowOne): - """Display volume type details""" + _description = _("Display volume type details") def get_parser(self, prog_name): parser = super(ShowVolumeType, self).get_parser(prog_name) @@ -175,7 +175,7 @@ class ShowVolumeType(command.ShowOne): class UnsetVolumeType(command.Command): - """Unset volume type properties""" + _description = _("Unset volume type properties") def get_parser(self, prog_name): parser = super(UnsetVolumeType, self).get_parser(prog_name) diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index 2ca35b24..00389fcb 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -17,6 +17,7 @@ import copy import logging +from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -29,7 +30,7 @@ LOG = logging.getLogger(__name__) class CreateVolumeBackup(command.ShowOne): - """Create new volume backup""" + _description = _("Create new volume backup") def get_parser(self, prog_name): parser = super(CreateVolumeBackup, self).get_parser(prog_name) @@ -94,7 +95,7 @@ class CreateVolumeBackup(command.ShowOne): class CreateBackup(CreateVolumeBackup): - """Create new backup""" + _description = _("Create new backup") # TODO(Huanxuan Ao): Remove this class and ``backup create`` command # two cycles after Newton. @@ -111,7 +112,7 @@ class CreateBackup(CreateVolumeBackup): class DeleteVolumeBackup(command.Command): - """Delete volume backup(s)""" + _description = _("Delete volume backup(s)") def get_parser(self, prog_name): parser = super(DeleteVolumeBackup, self).get_parser(prog_name) @@ -152,7 +153,7 @@ class DeleteVolumeBackup(command.Command): class DeleteBackup(DeleteVolumeBackup): - """Delete backup(s)""" + _description = _("Delete backup(s)") # TODO(Huanxuan Ao): Remove this class and ``backup delete`` command # two cycles after Newton. @@ -169,7 +170,7 @@ class DeleteBackup(DeleteVolumeBackup): class ListVolumeBackup(command.Lister): - """List volume backups""" + _description = _("List volume backups") def get_parser(self, prog_name): parser = super(ListVolumeBackup, self).get_parser(prog_name) @@ -179,9 +180,48 @@ class ListVolumeBackup(command.Lister): default=False, help=_("List additional fields in output") ) + parser.add_argument( + "--name", + metavar="<name>", + help=_("Filters results by the backup name") + ) + parser.add_argument( + "--status", + metavar="<status>", + choices=['creating', 'available', 'deleting', + 'error', 'restoring', 'error_restoring'], + help=_("Filters results by the backup status " + "('creating', 'available', 'deleting', " + "'error', 'restoring' or 'error_restoring')") + ) + parser.add_argument( + "--volume", + metavar="<volume>", + help=_("Filters results by the volume which they " + "backup (name or ID)") + ) + parser.add_argument( + '--marker', + metavar='<marker>', + help=_('The last backup of the previous page (name or ID)'), + ) + parser.add_argument( + '--limit', + type=int, + action=parseractions.NonNegativeAction, + metavar='<limit>', + help=_('Maximum number of backups to display'), + ) + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help=_('Include all projects (admin only)'), + ) return parser def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume def _format_volume_id(volume_id): """Return a volume name if available @@ -207,13 +247,31 @@ class ListVolumeBackup(command.Lister): # Cache the volume list volume_cache = {} try: - for s in self.app.client_manager.volume.volumes.list(): + for s in volume_client.volumes.list(): volume_cache[s.id] = s except Exception: # Just forget it if there's any trouble pass - data = self.app.client_manager.volume.backups.list() + filter_volume_id = None + if parsed_args.volume: + filter_volume_id = utils.find_resource(volume_client.volumes, + parsed_args.volume).id + marker_backup_id = None + if parsed_args.marker: + marker_backup_id = utils.find_resource(volume_client.backups, + parsed_args.marker).id + search_opts = { + 'name': parsed_args.name, + 'status': parsed_args.status, + 'volume_id': filter_volume_id, + 'all_tenants': parsed_args.all_projects, + } + data = volume_client.backups.list( + search_opts=search_opts, + marker=marker_backup_id, + limit=parsed_args.limit, + ) return (column_headers, (utils.get_item_properties( @@ -223,7 +281,7 @@ class ListVolumeBackup(command.Lister): class ListBackup(ListVolumeBackup): - """List backups""" + _description = _("List backups") # TODO(Huanxuan Ao): Remove this class and ``backup list`` command # two cycles after Newton. @@ -240,7 +298,7 @@ class ListBackup(ListVolumeBackup): class RestoreVolumeBackup(command.ShowOne): - """Restore volume backup""" + _description = _("Restore volume backup") def get_parser(self, prog_name): parser = super(RestoreVolumeBackup, self).get_parser(prog_name) @@ -265,7 +323,7 @@ class RestoreVolumeBackup(command.ShowOne): class RestoreBackup(RestoreVolumeBackup): - """Restore backup""" + _description = _("Restore backup") # TODO(Huanxuan Ao): Remove this class and ``backup restore`` command # two cycles after Newton. @@ -282,7 +340,7 @@ class RestoreBackup(RestoreVolumeBackup): class SetVolumeBackup(command.Command): - """Set volume backup properties""" + _description = _("Set volume backup properties") def get_parser(self, prog_name): parser = super(SetVolumeBackup, self).get_parser(prog_name) @@ -344,7 +402,7 @@ class SetVolumeBackup(command.Command): class ShowVolumeBackup(command.ShowOne): - """Display volume backup details""" + _description = _("Display volume backup details") def get_parser(self, prog_name): parser = super(ShowVolumeBackup, self).get_parser(prog_name) @@ -364,7 +422,7 @@ class ShowVolumeBackup(command.ShowOne): class ShowBackup(ShowVolumeBackup): - """Display backup details""" + _description = _("Display backup details") # TODO(Huanxuan Ao): Remove this class and ``backup show`` command # two cycles after Newton. diff --git a/openstackclient/volume/v2/consistency_group.py b/openstackclient/volume/v2/consistency_group.py index 39f2d577..661bcbe5 100644 --- a/openstackclient/volume/v2/consistency_group.py +++ b/openstackclient/volume/v2/consistency_group.py @@ -14,14 +14,137 @@ """Volume v2 consistency group action implementations""" +import logging + from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils +import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + +class DeleteConsistencyGroup(command.Command): + _description = _("Delete consistency group(s).") + + def get_parser(self, prog_name): + parser = super(DeleteConsistencyGroup, self).get_parser(prog_name) + parser.add_argument( + 'consistency_groups', + metavar='<consistency-group>', + nargs="+", + help=_('Consistency group(s) to delete (name or ID)'), + ) + parser.add_argument( + '--force', + action='store_true', + default=False, + help=_("Allow delete in state other than error or available"), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + result = 0 + + for i in parsed_args.consistency_groups: + try: + consistency_group_id = utils.find_resource( + volume_client.consistencygroups, i).id + volume_client.consistencygroups.delete( + consistency_group_id, parsed_args.force) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete consistency group with " + "name or ID '%(consistency_group)s':%(e)s") + % {'consistency_group': i, 'e': e}) + + if result > 0: + total = len(parsed_args.consistency_groups) + msg = (_("%(result)s of %(total)s consistency groups failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +LOG = logging.getLogger(__name__) + + +class CreateConsistencyGroup(command.ShowOne): + _description = _("Create new consistency group.") + + def get_parser(self, prog_name): + parser = super(CreateConsistencyGroup, self).get_parser(prog_name) + parser.add_argument( + "name", + metavar="<name>", + nargs="?", + help=_("Name of new consistency group (default to None)") + ) + exclusive_group = parser.add_mutually_exclusive_group(required=True) + exclusive_group.add_argument( + "--volume-type", + metavar="<volume-type>", + help=_("Volume type of this consistency group (name or ID)") + ) + exclusive_group.add_argument( + "--consistency-group-source", + metavar="<consistency-group>", + help=_("Existing consistency group (name or ID)") + ) + parser.add_argument( + "--description", + metavar="<description>", + help=_("Description of this consistency group") + ) + parser.add_argument( + "--availability-zone", + metavar="<availability-zone>", + help=_("Availability zone for this consistency group " + "(not available if creating consistency group " + "from source)"), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + if parsed_args.volume_type: + volume_type_id = utils.find_resource( + volume_client.volume_types, + parsed_args.volume_type).id + consistency_group = volume_client.consistencygroups.create( + volume_type_id, + name=parsed_args.name, + description=parsed_args.description, + availability_zone=parsed_args.availability_zone + ) + elif parsed_args.consistency_group_source: + if parsed_args.availability_zone: + msg = _("'--availability-zone' option will not work " + "if creating consistency group from source") + LOG.warning(msg) + consistency_group_id = utils.find_resource( + volume_client.consistencygroups, + parsed_args.consistency_group_source).id + consistency_group_snapshot = None + # TODO(Huanxuan Ao): Support for creating from consistency group + # snapshot after adding "consistency_group_snapshot" resource + consistency_group = ( + volume_client.consistencygroups.create_from_src( + consistency_group_snapshot, + consistency_group_id, + name=parsed_args.name, + description=parsed_args.description + ) + ) + + return zip(*sorted(six.iteritems(consistency_group._info))) + + class ListConsistencyGroup(command.Lister): - """List consistency groups.""" + _description = _("List consistency groups.") def get_parser(self, prog_name): parser = super(ListConsistencyGroup, self).get_parser(prog_name) @@ -55,3 +178,23 @@ class ListConsistencyGroup(command.Lister): s, columns, formatters={'Volume Types': utils.format_list}) for s in consistency_groups)) + + +class ShowConsistencyGroup(command.ShowOne): + _description = _("Display consistency group details.") + + def get_parser(self, prog_name): + parser = super(ShowConsistencyGroup, self).get_parser(prog_name) + parser.add_argument( + "consistency_group", + metavar="<consistency-group>", + help=_("Consistency group to display (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + consistency_group = utils.find_resource( + volume_client.consistencygroups, + parsed_args.consistency_group) + return zip(*sorted(six.iteritems(consistency_group._info))) diff --git a/openstackclient/volume/v2/consistency_group_snapshot.py b/openstackclient/volume/v2/consistency_group_snapshot.py new file mode 100644 index 00000000..540deb01 --- /dev/null +++ b/openstackclient/volume/v2/consistency_group_snapshot.py @@ -0,0 +1,190 @@ +# +# 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. +# + +"""Volume v2 consistency group snapshot action implementations""" + +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils +import six + +from openstackclient.i18n import _ + + +LOG = logging.getLogger(__name__) + + +class CreateConsistencyGroupSnapshot(command.ShowOne): + _description = _("Create new consistency group snapshot.") + + def get_parser(self, prog_name): + parser = super( + CreateConsistencyGroupSnapshot, self).get_parser(prog_name) + parser.add_argument( + "snapshot_name", + metavar="<snapshot-name>", + nargs="?", + help=_("Name of new consistency group snapshot (default to None)") + ) + parser.add_argument( + "--consistency-group", + metavar="<consistency-group>", + help=_("Consistency group to snapshot (name or ID) " + "(default to be the same as <snapshot-name>)") + ) + parser.add_argument( + "--description", + metavar="<description>", + help=_("Description of this consistency group snapshot") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + consistency_group = parsed_args.consistency_group + if not parsed_args.consistency_group: + # If "--consistency-group" not specified, then consistency_group + # will be the same as the new consistency group snapshot name + consistency_group = parsed_args.snapshot_name + consistency_group_id = utils.find_resource( + volume_client.consistencygroups, + consistency_group).id + consistency_group_snapshot = volume_client.cgsnapshots.create( + consistency_group_id, + name=parsed_args.snapshot_name, + description=parsed_args.description, + ) + + return zip(*sorted(six.iteritems(consistency_group_snapshot._info))) + + +class DeleteConsistencyGroupSnapshot(command.Command): + _description = _("Delete consistency group snapshot(s).") + + def get_parser(self, prog_name): + parser = super( + DeleteConsistencyGroupSnapshot, self).get_parser(prog_name) + parser.add_argument( + "consistency_group_snapshot", + metavar="<consistency-group-snapshot>", + nargs="+", + help=_("Consistency group snapshot(s) to delete (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + result = 0 + + for snapshot in parsed_args.consistency_group_snapshot: + try: + snapshot_id = utils.find_resource(volume_client.cgsnapshots, + snapshot).id + + volume_client.cgsnapshots.delete(snapshot_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete consistency group snapshot " + "with name or ID '%(snapshot)s': %(e)s") + % {'snapshot': snapshot, 'e': e}) + + if result > 0: + total = len(parsed_args.consistency_group_snapshot) + msg = (_("%(result)s of %(total)s consistency group snapshots " + "failed to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListConsistencyGroupSnapshot(command.Lister): + _description = _("List consistency group snapshots.") + + def get_parser(self, prog_name): + parser = super( + ListConsistencyGroupSnapshot, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + action="store_true", + help=_('Show detail for all projects (admin only) ' + '(defaults to False)') + ) + parser.add_argument( + '--long', + action="store_true", + help=_('List additional fields in output') + ) + parser.add_argument( + '--status', + metavar="<status>", + choices=['available', 'error', 'creating', 'deleting', + 'error-deleting'], + help=_('Filters results by a status ("available", "error", ' + '"creating", "deleting" or "error_deleting")') + ) + parser.add_argument( + '--consistency-group', + metavar="<consistency-group>", + help=_('Filters results by a consistency group (name or ID)') + ) + return parser + + def take_action(self, parsed_args): + if parsed_args.long: + columns = ['ID', 'Status', 'ConsistencyGroup ID', + 'Name', 'Description', 'Created At'] + else: + columns = ['ID', 'Status', 'Name'] + volume_client = self.app.client_manager.volume + consistency_group_id = None + if parsed_args.consistency_group: + consistency_group_id = utils.find_resource( + volume_client.consistencygroups, + parsed_args.consistency_group, + ).id + search_opts = { + 'all_tenants': parsed_args.all_projects, + 'status': parsed_args.status, + 'consistencygroup_id': consistency_group_id, + } + consistency_group_snapshots = volume_client.cgsnapshots.list( + detailed=True, + search_opts=search_opts, + ) + + return (columns, ( + utils.get_item_properties( + s, columns) + for s in consistency_group_snapshots)) + + +class ShowConsistencyGroupSnapshot(command.ShowOne): + _description = _("Display consistency group snapshot details") + + def get_parser(self, prog_name): + parser = super( + ShowConsistencyGroupSnapshot, self).get_parser(prog_name) + parser.add_argument( + "consistency_group_snapshot", + metavar="<consistency-group-snapshot>", + help=_("Consistency group snapshot to display (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + consistency_group_snapshot = utils.find_resource( + volume_client.cgsnapshots, + parsed_args.consistency_group_snapshot) + return zip(*sorted(six.iteritems(consistency_group_snapshot._info))) diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index 9797f1a6..b7f49eca 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__) class AssociateQos(command.Command): - """Associate a QoS specification to a volume type""" + _description = _("Associate a QoS specification to a volume type") def get_parser(self, prog_name): parser = super(AssociateQos, self).get_parser(prog_name) @@ -57,7 +57,7 @@ class AssociateQos(command.Command): class CreateQos(command.ShowOne): - """Create new QoS specification""" + _description = _("Create new QoS specification") def get_parser(self, prog_name): parser = super(CreateQos, self).get_parser(prog_name) @@ -99,7 +99,7 @@ class CreateQos(command.ShowOne): class DeleteQos(command.Command): - """Delete QoS specification""" + _description = _("Delete QoS specification") def get_parser(self, prog_name): parser = super(DeleteQos, self).get_parser(prog_name) @@ -139,7 +139,7 @@ class DeleteQos(command.Command): class DisassociateQos(command.Command): - """Disassociate a QoS specification from a volume type""" + _description = _("Disassociate a QoS specification from a volume type") def get_parser(self, prog_name): parser = super(DisassociateQos, self).get_parser(prog_name) @@ -177,7 +177,7 @@ class DisassociateQos(command.Command): class ListQos(command.Lister): - """List QoS specifications""" + _description = _("List QoS specifications") def take_action(self, parsed_args): volume_client = self.app.client_manager.volume @@ -202,7 +202,7 @@ class ListQos(command.Lister): class SetQos(command.Command): - """Set QoS specification properties""" + _description = _("Set QoS specification properties") def get_parser(self, prog_name): parser = super(SetQos, self).get_parser(prog_name) @@ -231,7 +231,7 @@ class SetQos(command.Command): class ShowQos(command.ShowOne): - """Display QoS specification details""" + _description = _("Display QoS specification details") def get_parser(self, prog_name): parser = super(ShowQos, self).get_parser(prog_name) @@ -260,7 +260,7 @@ class ShowQos(command.ShowOne): class UnsetQos(command.Command): - """Unset QoS specification properties""" + _description = _("Unset QoS specification properties") def get_parser(self, prog_name): parser = super(UnsetQos, self).get_parser(prog_name) diff --git a/openstackclient/volume/v2/service.py b/openstackclient/volume/v2/service.py index 867c4b9c..d468c6ff 100644 --- a/openstackclient/volume/v2/service.py +++ b/openstackclient/volume/v2/service.py @@ -22,7 +22,7 @@ from openstackclient.i18n import _ class ListService(command.Lister): - """List service command""" + _description = _("List service command") def get_parser(self, prog_name): parser = super(ListService, self).get_parser(prog_name) @@ -76,7 +76,7 @@ class ListService(command.Lister): class SetService(command.Command): - """Set volume service properties""" + _description = _("Set volume service properties") def get_parser(self, prog_name): parser = super(SetService, self).get_parser(prog_name) diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index 4994e0da..a18887e3 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -12,6 +12,10 @@ # under the License. # +# TODO(Huanxuan Ao): Remove this file and "snapshot create", "snapshot delete", +# "snapshot set", "snapshot show" and "snapshot unset" +# commands two cycles after Ocata. + """Volume v2 snapshot action implementations""" import copy @@ -26,11 +30,13 @@ import six from openstackclient.i18n import _ +deprecated = True +LOG_DEP = logging.getLogger('deprecated') LOG = logging.getLogger(__name__) class CreateSnapshot(command.ShowOne): - """Create new snapshot""" + _description = _("Create new snapshot") def get_parser(self, prog_name): parser = super(CreateSnapshot, self).get_parser(prog_name) @@ -66,6 +72,8 @@ class CreateSnapshot(command.ShowOne): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot create" instead.')) volume_client = self.app.client_manager.volume volume_id = utils.find_resource( volume_client.volumes, parsed_args.volume).id @@ -83,7 +91,7 @@ class CreateSnapshot(command.ShowOne): class DeleteSnapshot(command.Command): - """Delete volume snapshot(s)""" + _description = _("Delete volume snapshot(s)") def get_parser(self, prog_name): parser = super(DeleteSnapshot, self).get_parser(prog_name) @@ -96,6 +104,8 @@ class DeleteSnapshot(command.Command): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot delete" instead.')) volume_client = self.app.client_manager.volume result = 0 @@ -118,7 +128,7 @@ class DeleteSnapshot(command.Command): class ListSnapshot(command.Lister): - """List snapshots""" + _description = _("List snapshots") def get_parser(self, prog_name): parser = super(ListSnapshot, self).get_parser(prog_name) @@ -149,6 +159,8 @@ class ListSnapshot(command.Lister): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot list" instead.')) def _format_volume_id(volume_id): """Return a volume name if available @@ -199,7 +211,7 @@ class ListSnapshot(command.Lister): class SetSnapshot(command.Command): - """Set snapshot properties""" + _description = _("Set snapshot properties") def get_parser(self, prog_name): parser = super(SetSnapshot, self).get_parser(prog_name) @@ -239,6 +251,8 @@ class SetSnapshot(command.Command): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot set" instead.')) volume_client = self.app.client_manager.volume snapshot = utils.find_resource(volume_client.volume_snapshots, parsed_args.snapshot) @@ -280,7 +294,7 @@ class SetSnapshot(command.Command): class ShowSnapshot(command.ShowOne): - """Display snapshot details""" + _description = _("Display snapshot details") def get_parser(self, prog_name): parser = super(ShowSnapshot, self).get_parser(prog_name) @@ -292,6 +306,8 @@ class ShowSnapshot(command.ShowOne): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot show" instead.')) volume_client = self.app.client_manager.volume snapshot = utils.find_resource( volume_client.volume_snapshots, parsed_args.snapshot) @@ -302,7 +318,7 @@ class ShowSnapshot(command.ShowOne): class UnsetSnapshot(command.Command): - """Unset snapshot properties""" + _description = _("Unset snapshot properties") def get_parser(self, prog_name): parser = super(UnsetSnapshot, self).get_parser(prog_name) @@ -322,6 +338,8 @@ class UnsetSnapshot(command.Command): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot unset" instead.')) volume_client = self.app.client_manager.volume snapshot = utils.find_resource( volume_client.volume_snapshots, parsed_args.snapshot) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index e7405114..80abfb55 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -45,7 +45,7 @@ def _check_size_arg(args): class CreateVolume(command.ShowOne): - """Create new volume""" + _description = _("Create new volume") def get_parser(self, prog_name): parser = super(CreateVolume, self).get_parser(prog_name) @@ -211,7 +211,7 @@ class CreateVolume(command.ShowOne): class DeleteVolume(command.Command): - """Delete volume(s)""" + _description = _("Delete volume(s)") def get_parser(self, prog_name): parser = super(DeleteVolume, self).get_parser(prog_name) @@ -263,7 +263,7 @@ class DeleteVolume(command.Command): class ListVolume(command.Lister): - """List volumes""" + _description = _("List volumes") def get_parser(self, prog_name): parser = super(ListVolume, self).get_parser(prog_name) @@ -409,8 +409,55 @@ class ListVolume(command.Lister): ) for s in data)) +class MigrateVolume(command.Command): + _description = _("Migrate volume to a new host") + + def get_parser(self, prog_name): + parser = super(MigrateVolume, self).get_parser(prog_name) + parser.add_argument( + 'volume', + metavar="<volume>", + help=_("Volume to migrate (name or ID)") + ) + parser.add_argument( + '--host', + metavar="<host>", + required=True, + help=_("Destination host (takes the form: host@backend-name#pool)") + ) + parser.add_argument( + '--force-host-copy', + action="store_true", + help=_("Enable generic host-based force-migration, " + "which bypasses driver optimizations") + ) + lock_group = parser.add_mutually_exclusive_group() + lock_group.add_argument( + '--lock-volume', + action="store_true", + help=_("If specified, the volume state will be locked " + "and will not allow a migration to be aborted " + "(possibly by another operation)") + ) + lock_group.add_argument( + '--unlock-volume', + action="store_true", + help=_("If specified, the volume state will not be " + "locked and the a migration can be aborted " + "(default) (possibly by another operation)") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + volume = utils.find_resource(volume_client.volumes, parsed_args.volume) + volume_client.volumes.migrate_volume(volume.id, parsed_args.host, + parsed_args.force_host_copy, + parsed_args.lock_volume,) + + class SetVolume(command.Command): - """Set volume properties""" + _description = _("Set volume properties") def get_parser(self, prog_name): parser = super(SetVolume, self).get_parser(prog_name) @@ -462,6 +509,19 @@ class SetVolume(command.Command): 'in the database with no regard to actual status, ' 'exercise caution when using)'), ) + parser.add_argument( + '--type', + metavar='<volume-type>', + help=_('New volume type (name or ID)'), + ) + parser.add_argument( + '--retype-policy', + metavar='<retype-policy>', + choices=['never', 'on-demand'], + help=_('Migration policy while re-typing volume ' + '("never" or "on-demand", default is "never" ) ' + '(available only when "--type" option is specified)'), + ) bootable_group = parser.add_mutually_exclusive_group() bootable_group.add_argument( "--bootable", @@ -473,6 +533,17 @@ class SetVolume(command.Command): action="store_true", help=_("Mark volume as non-bootable") ) + readonly_group = parser.add_mutually_exclusive_group() + readonly_group.add_argument( + "--read-only", + action="store_true", + help=_("Set volume to read-only access mode") + ) + readonly_group.add_argument( + "--read-write", + action="store_true", + help=_("Set volume to read-write access mode") + ) return parser def take_action(self, parsed_args): @@ -484,10 +555,11 @@ class SetVolume(command.Command): try: if volume.status != 'available': msg = (_("Volume is in %s state, it must be available " - "before size can be extended"), volume.status) + "before size can be extended") % volume.status) raise exceptions.CommandError(msg) if parsed_args.size <= volume.size: - msg = _("New size must be greater than %s GB"), volume.size + msg = (_("New size must be greater than %s GB") + % volume.size) raise exceptions.CommandError(msg) volume_client.volumes.extend(volume.id, parsed_args.size) except Exception as e: @@ -522,6 +594,37 @@ class SetVolume(command.Command): except Exception as e: LOG.error(_("Failed to set volume bootable property: %s"), e) result += 1 + if parsed_args.read_only or parsed_args.read_write: + try: + volume_client.volumes.update_readonly_flag( + volume.id, + parsed_args.read_only) + except Exception as e: + LOG.error(_("Failed to set volume read-only access " + "mode flag: %s"), e) + result += 1 + if parsed_args.type: + # get the migration policy + migration_policy = 'never' + if parsed_args.retype_policy: + migration_policy = parsed_args.retype_policy + try: + # find the volume type + volume_type = utils.find_resource( + volume_client.volume_types, + parsed_args.type) + # reset to the new volume type + volume_client.volumes.retype( + volume.id, + volume_type.id, + migration_policy) + except Exception as e: + LOG.error(_("Failed to set volume type: %s"), e) + result += 1 + elif parsed_args.retype_policy: + # If the "--retype-policy" is specified without "--type" + LOG.warning(_("'--retype-policy' option will not work " + "without '--type' option")) kwargs = {} if parsed_args.name: @@ -542,7 +645,7 @@ class SetVolume(command.Command): class ShowVolume(command.ShowOne): - """Display volume details""" + _description = _("Display volume details") def get_parser(self, prog_name): parser = super(ShowVolume, self).get_parser(prog_name) @@ -573,7 +676,7 @@ class ShowVolume(command.ShowOne): class UnsetVolume(command.Command): - """Unset volume properties""" + _description = _("Unset volume properties") def get_parser(self, prog_name): parser = super(UnsetVolume, self).get_parser(prog_name) diff --git a/openstackclient/volume/v2/volume_host.py b/openstackclient/volume/v2/volume_host.py new file mode 100644 index 00000000..376e5024 --- /dev/null +++ b/openstackclient/volume/v2/volume_host.py @@ -0,0 +1,50 @@ +# +# 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. +# + +"""Volume v2 host action implementations""" + +from osc_lib.command import command + +from openstackclient.i18n import _ + + +class SetVolumeHost(command.Command): + _description = _("Set volume host properties") + + def get_parser(self, prog_name): + parser = super(SetVolumeHost, self).get_parser(prog_name) + parser.add_argument( + "host", + metavar="<host-name>", + help=_("Name of volume host") + ) + enabled_group = parser.add_mutually_exclusive_group() + enabled_group.add_argument( + "--disable", + action="store_true", + help=_("Freeze and disable the specified volume host.") + ) + enabled_group.add_argument( + "--enable", + action="store_true", + help=_("Thaw and enable the specified volume host.") + ) + return parser + + def take_action(self, parsed_args): + service_client = self.app.client_manager.volume + if parsed_args.enable: + service_client.services.thaw_host(parsed_args.host) + if parsed_args.disable: + service_client.services.freeze_host(parsed_args.host) diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py new file mode 100644 index 00000000..43f30326 --- /dev/null +++ b/openstackclient/volume/v2/volume_snapshot.py @@ -0,0 +1,338 @@ +# +# 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. +# + +"""Volume v2 snapshot action implementations""" + +import copy +import logging + +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils +import six + +from openstackclient.i18n import _ + + +LOG = logging.getLogger(__name__) + + +class CreateVolumeSnapshot(command.ShowOne): + """Create new volume snapshot""" + + def get_parser(self, prog_name): + parser = super(CreateVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + "snapshot_name", + metavar="<snapshot-name>", + nargs="?", + help=_("Name of the new snapshot (default to None)") + ) + parser.add_argument( + "--volume", + metavar="<volume>", + help=_("Volume to snapshot (name or ID) " + "(default is <snapshot-name>)") + ) + parser.add_argument( + "--description", + metavar="<description>", + help=_("Description of the snapshot") + ) + parser.add_argument( + "--force", + action="store_true", + default=False, + help=_("Create a snapshot attached to an instance. " + "Default is False") + ) + parser.add_argument( + "--property", + metavar="<key=value>", + action=parseractions.KeyValueAction, + help=_("Set a property to this snapshot " + "(repeat option to set multiple properties)"), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + volume = parsed_args.volume + if not parsed_args.volume: + volume = parsed_args.snapshot_name + volume_id = utils.find_resource( + volume_client.volumes, volume).id + snapshot = volume_client.volume_snapshots.create( + volume_id, + force=parsed_args.force, + name=parsed_args.snapshot_name, + description=parsed_args.description, + metadata=parsed_args.property, + ) + snapshot._info.update( + {'properties': utils.format_dict(snapshot._info.pop('metadata'))} + ) + return zip(*sorted(six.iteritems(snapshot._info))) + + +class DeleteVolumeSnapshot(command.Command): + """Delete volume snapshot(s)""" + + def get_parser(self, prog_name): + parser = super(DeleteVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + "snapshots", + metavar="<snapshot>", + nargs="+", + help=_("Snapshot(s) to delete (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + result = 0 + + for i in parsed_args.snapshots: + try: + snapshot_id = utils.find_resource( + volume_client.volume_snapshots, i).id + volume_client.volume_snapshots.delete(snapshot_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete snapshot with " + "name or ID '%(snapshot)s': %(e)s") + % {'snapshot': i, 'e': e}) + + if result > 0: + total = len(parsed_args.snapshots) + msg = (_("%(result)s of %(total)s snapshots failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListVolumeSnapshot(command.Lister): + """List volume snapshots""" + + def get_parser(self, prog_name): + parser = super(ListVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help=_('Include all projects (admin only)'), + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help=_('List additional fields in output'), + ) + parser.add_argument( + '--marker', + metavar='<marker>', + help=_('The last snapshot ID of the previous page'), + ) + parser.add_argument( + '--limit', + type=int, + action=parseractions.NonNegativeAction, + metavar='<limit>', + help=_('Maximum number of snapshots to display'), + ) + return parser + + def take_action(self, parsed_args): + + def _format_volume_id(volume_id): + """Return a volume name if available + + :param volume_id: a volume ID + :rtype: either the volume ID or name + """ + + volume = volume_id + if volume_id in volume_cache.keys(): + volume = volume_cache[volume_id].name + return volume + + if parsed_args.long: + columns = ['ID', 'Name', 'Description', 'Status', + 'Size', 'Created At', 'Volume ID', 'Metadata'] + column_headers = copy.deepcopy(columns) + column_headers[6] = 'Volume' + column_headers[7] = 'Properties' + else: + columns = ['ID', 'Name', 'Description', 'Status', 'Size'] + column_headers = copy.deepcopy(columns) + + # Cache the volume list + volume_cache = {} + try: + for s in self.app.client_manager.volume.volumes.list(): + volume_cache[s.id] = s + except Exception: + # Just forget it if there's any trouble + pass + + search_opts = { + 'all_tenants': parsed_args.all_projects, + } + + data = self.app.client_manager.volume.volume_snapshots.list( + search_opts=search_opts, + marker=parsed_args.marker, + limit=parsed_args.limit, + ) + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters={'Metadata': utils.format_dict, + 'Volume ID': _format_volume_id}, + ) for s in data)) + + +class SetVolumeSnapshot(command.Command): + """Set volume snapshot properties""" + + def get_parser(self, prog_name): + parser = super(SetVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + 'snapshot', + metavar='<snapshot>', + help=_('Snapshot to modify (name or ID)') + ) + parser.add_argument( + '--name', + metavar='<name>', + help=_('New snapshot name') + ) + parser.add_argument( + '--description', + metavar='<description>', + help=_('New snapshot description') + ) + parser.add_argument( + '--property', + metavar='<key=value>', + action=parseractions.KeyValueAction, + help=_('Property to add/change for this snapshot ' + '(repeat option to set multiple properties)'), + ) + parser.add_argument( + '--state', + metavar='<state>', + choices=['available', 'error', 'creating', 'deleting', + 'error-deleting'], + help=_('New snapshot state. ("available", "error", "creating", ' + '"deleting", or "error_deleting") (admin only) ' + '(This option simply changes the state of the snapshot ' + 'in the database with no regard to actual status, ' + 'exercise caution when using)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + snapshot = utils.find_resource(volume_client.volume_snapshots, + parsed_args.snapshot) + + result = 0 + if parsed_args.property: + try: + volume_client.volume_snapshots.set_metadata( + snapshot.id, parsed_args.property) + except Exception as e: + LOG.error(_("Failed to set snapshot property: %s"), e) + result += 1 + + if parsed_args.state: + try: + volume_client.volume_snapshots.reset_state( + snapshot.id, parsed_args.state) + except Exception as e: + LOG.error(_("Failed to set snapshot state: %s"), e) + result += 1 + + kwargs = {} + if parsed_args.name: + kwargs['name'] = parsed_args.name + if parsed_args.description: + kwargs['description'] = parsed_args.description + if kwargs: + try: + volume_client.volume_snapshots.update( + snapshot.id, **kwargs) + except Exception as e: + LOG.error(_("Failed to update snapshot name " + "or description: %s"), e) + result += 1 + + if result > 0: + raise exceptions.CommandError(_("One or more of the " + "set operations failed")) + + +class ShowVolumeSnapshot(command.ShowOne): + """Display volume snapshot details""" + + def get_parser(self, prog_name): + parser = super(ShowVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + "snapshot", + metavar="<snapshot>", + help=_("Snapshot to display (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + snapshot = utils.find_resource( + volume_client.volume_snapshots, parsed_args.snapshot) + snapshot._info.update( + {'properties': utils.format_dict(snapshot._info.pop('metadata'))} + ) + return zip(*sorted(six.iteritems(snapshot._info))) + + +class UnsetVolumeSnapshot(command.Command): + """Unset volume snapshot properties""" + + def get_parser(self, prog_name): + parser = super(UnsetVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + 'snapshot', + metavar='<snapshot>', + help=_('Snapshot to modify (name or ID)'), + ) + parser.add_argument( + '--property', + metavar='<key>', + action='append', + default=[], + help=_('Property to remove from snapshot ' + '(repeat option to remove multiple properties)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + snapshot = utils.find_resource( + volume_client.volume_snapshots, parsed_args.snapshot) + + if parsed_args.property: + volume_client.volume_snapshots.delete_metadata( + snapshot.id, + parsed_args.property, + ) diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py index 9008fe3c..aefe594a 100644 --- a/openstackclient/volume/v2/volume_transfer_request.py +++ b/openstackclient/volume/v2/volume_transfer_request.py @@ -28,7 +28,7 @@ LOG = logging.getLogger(__name__) class AcceptTransferRequest(command.ShowOne): - """Accept volume transfer request.""" + _description = _("Accept volume transfer request.") def get_parser(self, prog_name): parser = super(AcceptTransferRequest, self).get_parser(prog_name) @@ -56,7 +56,7 @@ class AcceptTransferRequest(command.ShowOne): class CreateTransferRequest(command.ShowOne): - """Create volume transfer request.""" + _description = _("Create volume transfer request.") def get_parser(self, prog_name): parser = super(CreateTransferRequest, self).get_parser(prog_name) @@ -85,7 +85,7 @@ class CreateTransferRequest(command.ShowOne): class DeleteTransferRequest(command.Command): - """Delete volume transfer request(s).""" + _description = _("Delete volume transfer request(s).") def get_parser(self, prog_name): parser = super(DeleteTransferRequest, self).get_parser(prog_name) @@ -120,7 +120,7 @@ class DeleteTransferRequest(command.Command): class ListTransferRequest(command.Lister): - """Lists all volume transfer requests.""" + _description = _("Lists all volume transfer requests.") def get_parser(self, prog_name): parser = super(ListTransferRequest, self).get_parser(prog_name) @@ -151,7 +151,7 @@ class ListTransferRequest(command.Lister): class ShowTransferRequest(command.ShowOne): - """Show volume transfer request details.""" + _description = _("Show volume transfer request details.") def get_parser(self, prog_name): parser = super(ShowTransferRequest, self).get_parser(prog_name) diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 80a1f21b..42ebb53e 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__) class CreateVolumeType(command.ShowOne): - """Create new volume type""" + _description = _("Create new volume type") def get_parser(self, prog_name): parser = super(CreateVolumeType, self).get_parser(prog_name) @@ -116,7 +116,7 @@ class CreateVolumeType(command.ShowOne): class DeleteVolumeType(command.Command): - """Delete volume type(s)""" + _description = _("Delete volume type(s)") def get_parser(self, prog_name): parser = super(DeleteVolumeType, self).get_parser(prog_name) @@ -152,7 +152,7 @@ class DeleteVolumeType(command.Command): class ListVolumeType(command.Lister): - """List volume types""" + _description = _("List volume types") def get_parser(self, prog_name): parser = super(ListVolumeType, self).get_parser(prog_name) @@ -176,10 +176,11 @@ class ListVolumeType(command.Lister): def take_action(self, parsed_args): if parsed_args.long: - columns = ['ID', 'Name', 'Description', 'Extra Specs'] - column_headers = ['ID', 'Name', 'Description', 'Properties'] + columns = ['ID', 'Name', 'Is Public', 'Description', 'Extra Specs'] + column_headers = [ + 'ID', 'Name', 'Is Public', 'Description', 'Properties'] else: - columns = ['ID', 'Name'] + columns = ['ID', 'Name', 'Is Public'] column_headers = columns is_public = None @@ -197,7 +198,7 @@ class ListVolumeType(command.Lister): class SetVolumeType(command.Command): - """Set volume type properties""" + _description = _("Set volume type properties") def get_parser(self, prog_name): parser = super(SetVolumeType, self).get_parser(prog_name) @@ -213,7 +214,7 @@ class SetVolumeType(command.Command): ) parser.add_argument( '--description', - metavar='<name>', + metavar='<description>', help=_('Set volume type description'), ) parser.add_argument( @@ -286,7 +287,7 @@ class SetVolumeType(command.Command): class ShowVolumeType(command.ShowOne): - """Display volume type details""" + _description = _("Display volume type details") def get_parser(self, prog_name): parser = super(ShowVolumeType, self).get_parser(prog_name) @@ -324,7 +325,7 @@ class ShowVolumeType(command.ShowOne): class UnsetVolumeType(command.Command): - """Unset volume type properties""" + _description = _("Unset volume type properties") def get_parser(self, prog_name): parser = super(UnsetVolumeType, self).get_parser(prog_name) |
