summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/image/client.py2
-rw-r--r--openstackclient/image/v1/image.py18
-rw-r--r--openstackclient/image/v2/image.py30
-rw-r--r--openstackclient/network/v2/floating_ip.py16
-rw-r--r--openstackclient/network/v2/network_qos_policy.py29
-rw-r--r--openstackclient/network/v2/network_rbac.py26
-rw-r--r--openstackclient/network/v2/port.py3
-rw-r--r--openstackclient/network/v2/router.py34
-rw-r--r--openstackclient/network/v2/security_group.py21
-rw-r--r--openstackclient/network/v2/security_group_rule.py20
-rw-r--r--openstackclient/network/v2/subnet.py31
-rw-r--r--openstackclient/shell.py4
-rw-r--r--openstackclient/tests/functional/volume/v1/test_snapshot.py32
-rw-r--r--openstackclient/tests/functional/volume/v2/test_snapshot.py32
-rw-r--r--openstackclient/tests/functional/volume/v2/test_volume.py10
-rw-r--r--openstackclient/tests/unit/image/v1/test_image.py6
-rw-r--r--openstackclient/tests/unit/image/v2/test_image.py35
-rw-r--r--openstackclient/tests/unit/network/v2/fakes.py3
-rw-r--r--openstackclient/tests/unit/network/v2/test_floating_ip.py50
-rw-r--r--openstackclient/tests/unit/network/v2/test_router.py41
-rw-r--r--openstackclient/tests/unit/network/v2/test_security_group.py38
-rw-r--r--openstackclient/tests/unit/network/v2/test_subnet.py8
-rw-r--r--openstackclient/tests/unit/volume/v1/test_snapshot.py50
-rw-r--r--openstackclient/tests/unit/volume/v1/test_type.py6
-rw-r--r--openstackclient/tests/unit/volume/v1/test_volume.py4
-rw-r--r--openstackclient/tests/unit/volume/v2/fakes.py107
-rw-r--r--openstackclient/tests/unit/volume/v2/test_consistency_group.py276
-rw-r--r--openstackclient/tests/unit/volume/v2/test_consistency_group_snapshot.py351
-rw-r--r--openstackclient/tests/unit/volume/v2/test_snapshot.py51
-rw-r--r--openstackclient/tests/unit/volume/v2/test_type.py5
-rw-r--r--openstackclient/tests/unit/volume/v2/test_volume.py66
-rw-r--r--openstackclient/volume/v1/snapshot.py18
-rw-r--r--openstackclient/volume/v1/volume_snapshot.py305
-rw-r--r--openstackclient/volume/v1/volume_type.py6
-rw-r--r--openstackclient/volume/v2/consistency_group.py143
-rw-r--r--openstackclient/volume/v2/consistency_group_snapshot.py190
-rw-r--r--openstackclient/volume/v2/snapshot.py18
-rw-r--r--openstackclient/volume/v2/volume.py35
-rw-r--r--openstackclient/volume/v2/volume_snapshot.py338
-rw-r--r--openstackclient/volume/v2/volume_type.py7
40 files changed, 2299 insertions, 166 deletions
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 cf389d17..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__)
@@ -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",
@@ -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
@@ -502,14 +506,12 @@ class SetImage(command.Command):
container_choices,
choices=container_choices
)
- disk_choices = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2",
- "vhdx", "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",
diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py
index 657277c0..1d167605 100644
--- a/openstackclient/image/v2/image.py
+++ b/openstackclient/image/v2/image.py
@@ -32,6 +32,8 @@ 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__)
@@ -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",
@@ -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
@@ -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(
diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py
index c787cd2f..7b8374e2 100644
--- a/openstackclient/network/v2/floating_ip.py
+++ b/openstackclient/network/v2/floating_ip.py
@@ -18,6 +18,7 @@ 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
@@ -66,6 +67,15 @@ 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
@@ -113,6 +123,12 @@ 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):
diff --git a/openstackclient/network/v2/network_qos_policy.py b/openstackclient/network/v2/network_qos_policy.py
index 75cb1d91..5ccbe36b 100644
--- a/openstackclient/network/v2/network_qos_policy.py
+++ b/openstackclient/network/v2/network_qos_policy.py
@@ -21,17 +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')
- return tuple(sorted(columns))
+ 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):
@@ -56,6 +57,8 @@ 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 CreateNetworkQosPolicy(command.ShowOne):
_description = _("Create a QoS policy")
@@ -96,9 +99,9 @@ class CreateNetworkQosPolicy(command.ShowOne):
client = self.app.client_manager.network
attrs = _get_attrs(self.app.client_manager, parsed_args)
obj = client.create_qos_policy(**attrs)
- columns = _get_columns(obj)
+ display_columns, columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters={})
- return columns, data
+ return (display_columns, data)
class DeleteNetworkQosPolicy(command.Command):
@@ -135,6 +138,8 @@ class DeleteNetworkQosPolicy(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 ListNetworkQosPolicy(command.Lister):
_description = _("List QoS policies")
@@ -143,8 +148,8 @@ class ListNetworkQosPolicy(command.Lister):
columns = (
'id',
'name',
- 'shared',
- 'tenant_id',
+ 'is_shared',
+ 'project_id',
)
column_headers = (
'ID',
@@ -160,6 +165,8 @@ class ListNetworkQosPolicy(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 SetNetworkQosPolicy(command.Command):
_description = _("Set QoS policy properties")
@@ -226,6 +233,6 @@ class ShowNetworkQosPolicy(command.ShowOne):
client = self.app.client_manager.network
obj = client.find_qos_policy(parsed_args.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_rbac.py b/openstackclient/network/v2/network_rbac.py
index 2bcdd811..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):
@@ -70,6 +68,8 @@ 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):
_description = _("Create network RBAC policy")
@@ -122,9 +122,9 @@ 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):
@@ -185,6 +185,8 @@ 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):
_description = _("Set network RBAC policy properties")
@@ -242,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/port.py b/openstackclient/network/v2/port.py
index c960ba25..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',
diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py
index cbd412b5..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):
@@ -215,10 +215,10 @@ 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):
@@ -282,11 +282,17 @@ class ListRouter(command.Lister):
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',
@@ -316,6 +322,13 @@ class ListRouter(command.Lister):
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',
@@ -523,9 +536,10 @@ 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):
diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py
index 554dd61d..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)
@@ -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}
diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py
index 05fafc0d..b878d875 100644
--- a/openstackclient/network/v2/security_group_rule.py
+++ b/openstackclient/network/v2/security_group_rule.py
@@ -29,6 +29,7 @@ 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
@@ -67,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):
@@ -89,6 +89,8 @@ 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):
_description = _("Create a new security group rule")
@@ -341,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(
@@ -597,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 5366dff6..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__)
@@ -126,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):
@@ -226,6 +228,8 @@ 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):
_description = _("Create a subnet")
@@ -332,9 +336,9 @@ 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):
@@ -371,6 +375,8 @@ 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):
_description = _("List subnets")
@@ -452,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:
@@ -463,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
@@ -481,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')
@@ -492,6 +501,8 @@ 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):
_description = _("Set subnet properties")
@@ -576,9 +587,9 @@ 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):
diff --git a/openstackclient/shell.py b/openstackclient/shell.py
index be4b5283..e08eee61 100644
--- a/openstackclient/shell.py
+++ b/openstackclient/shell.py
@@ -148,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/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/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py
index aef74f04..036c8336 100644
--- a/openstackclient/tests/unit/image/v1/test_image.py
+++ b/openstackclient/tests/unit/image/v1/test_image.py
@@ -116,7 +116,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',
@@ -126,7 +126,7 @@ class TestImageCreate(TestImage):
]
verifylist = [
('container_format', 'ovf'),
- ('disk_format', 'fs'),
+ ('disk_format', 'ami'),
('min_disk', 10),
('min_ram', 4),
('protected', True),
@@ -147,7 +147,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,
protected=True,
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/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py
index 37f10147..c18511f7 100644
--- a/openstackclient/tests/unit/network/v2/fakes.py
+++ b/openstackclient/tests/unit/network/v2/fakes.py
@@ -746,6 +746,7 @@ class FakeNetworkQosPolicy(object):
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
@@ -1064,6 +1065,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
diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip.py b/openstackclient/tests/unit/network/v2/test_floating_ip.py
index 10f3067d..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):
diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py
index 24984e47..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):
@@ -476,6 +478,45 @@ class TestListRouter(TestRouter):
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))
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_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py
index e7406575..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)
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 25707288..7a44dea8 100644
--- a/openstackclient/tests/unit/volume/v1/test_volume.py
+++ b/openstackclient/tests/unit/volume/v1/test_volume.py
@@ -57,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()
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_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 fc99bf6e..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()
@@ -1088,11 +1091,14 @@ class TestVolumeMigrate(TestVolume):
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)
@@ -1221,6 +1227,66 @@ class TestVolumeSet(TestVolume):
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/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py
index 0f91ee72..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,6 +31,8 @@ import six
from openstackclient.i18n import _
+deprecated = True
+LOG_DEP = logging.getLogger('deprecated')
LOG = logging.getLogger(__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
@@ -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
@@ -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
@@ -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)
@@ -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)
@@ -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_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_type.py b/openstackclient/volume/v1/volume_type.py
index 4f159239..8adce322 100644
--- a/openstackclient/volume/v1/volume_type.py
+++ b/openstackclient/volume/v1/volume_type.py
@@ -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,
diff --git a/openstackclient/volume/v2/consistency_group.py b/openstackclient/volume/v2/consistency_group.py
index 0754fdc7..661bcbe5 100644
--- a/openstackclient/volume/v2/consistency_group.py
+++ b/openstackclient/volume/v2/consistency_group.py
@@ -14,12 +14,135 @@
"""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):
_description = _("List consistency groups.")
@@ -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/snapshot.py b/openstackclient/volume/v2/snapshot.py
index 8cda112a..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,6 +30,8 @@ import six
from openstackclient.i18n import _
+deprecated = True
+LOG_DEP = logging.getLogger('deprecated')
LOG = logging.getLogger(__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
@@ -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
@@ -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
@@ -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)
@@ -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)
@@ -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 0531e0aa..80abfb55 100644
--- a/openstackclient/volume/v2/volume.py
+++ b/openstackclient/volume/v2/volume.py
@@ -509,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",
@@ -590,6 +603,28 @@ class SetVolume(command.Command):
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:
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_type.py b/openstackclient/volume/v2/volume_type.py
index d0df677d..42ebb53e 100644
--- a/openstackclient/volume/v2/volume_type.py
+++ b/openstackclient/volume/v2/volume_type.py
@@ -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