diff options
37 files changed, 918 insertions, 145 deletions
diff --git a/doc/source/command-objects/consistency-group.rst b/doc/source/command-objects/consistency-group.rst index cd972035..910fbba9 100644 --- a/doc/source/command-objects/consistency-group.rst +++ b/doc/source/command-objects/consistency-group.rst @@ -13,7 +13,7 @@ Create new consistency group. .. code:: bash os consistency group create - --volume-type <volume-type> | --consistency-group-source <consistency-group> + --volume-type <volume-type> | --consistency-group-source <consistency-group> | --consistency-group-snapshot <consistency-group-snapshot> [--description <description>] [--availability-zone <availability-zone>] [<name>] @@ -26,6 +26,10 @@ Create new consistency group. Existing consistency group (name or ID) +.. option:: --consistency-group-snapshot <consistency-group-snapshot> + + Existing consistency group snapshot (name or ID) + .. option:: --description <description> Description of this consistency group diff --git a/doc/source/command-objects/floating-ip.rst b/doc/source/command-objects/floating-ip.rst index b2cc8af0..6a5e38b0 100644 --- a/doc/source/command-objects/floating-ip.rst +++ b/doc/source/command-objects/floating-ip.rst @@ -18,6 +18,7 @@ Create floating IP [--floating-ip-address <floating-ip-address>] [--fixed-ip-address <fixed-ip-address>] [--description <description>] + [--project <project> [--project-domain <project-domain>]] <network> .. option:: --subnet <subnet> @@ -45,6 +46,19 @@ Create floating IP Set floating IP description *Network version 2 only* +.. option:: --project <project> + + Owner's project (name or ID) + + *Network version 2 only* + +.. option:: --project-domain <project-domain> + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + + *Network version 2 only* + .. describe:: <network> Network to allocate floating IP from (name or ID) diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 3aff2f77..73c53290 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -28,6 +28,7 @@ Create new port [--enable | --disable] [--mac-address <mac-address>] [--security-group <security-group> | --no-security-group] + [--dns-name <dns-name>] [--project <project> [--project-domain <project-domain>]] [--enable-port-security | --disable-port-security] <name> @@ -91,6 +92,11 @@ Create new port Associate no security groups with this port +.. option:: --dns-name <dns-name> + + Set DNS name to this port + (requires DNS integration extension) + .. option:: --project <project> Owner's project (name or ID) @@ -192,6 +198,7 @@ Set port properties [--security-group <security-group>] [--no-security-group] [--enable-port-security | --disable-port-security] + [--dns-name <dns-name>] <port> .. option:: --description <description> @@ -269,6 +276,11 @@ Set port properties Disable port security for this port +.. option:: --dns-name <dns-name> + + Set DNS name to this port + (requires DNS integration extension) + .. _port_set-port: .. describe:: <port> diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 56b95ffa..059d1a3f 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -137,6 +137,7 @@ List routers [--name <name>] [--enable | --disable] [--long] + [--project <project> [--project-domain <project-domain>]] .. option:: --long @@ -154,6 +155,15 @@ List routers List disabled routers +.. option:: --project <project> + + List routers according to their project (name or ID) + +.. option:: --project-domain <project-domain> + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + router remove port ------------------ diff --git a/doc/source/command-objects/security-group.rst b/doc/source/command-objects/security-group.rst index ba054554..5157f530 100644 --- a/doc/source/command-objects/security-group.rst +++ b/doc/source/command-objects/security-group.rst @@ -67,6 +67,7 @@ List security groups os security group list [--all-projects] + [--project <project> [--project-domain <project-domain>]] .. option:: --all-projects @@ -75,6 +76,19 @@ List security groups *Network version 2 ignores this option and will always display information* *for all projects (admin only).* +.. option:: --project <project> + + List security groups according to the project (name or ID) + + *Network version 2 only* + +.. option:: --project-domain <project-domain> + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + + *Network version 2 only* + security group set ------------------ diff --git a/doc/source/command-objects/volume-host.rst b/doc/source/command-objects/volume-host.rst new file mode 100644 index 00000000..956fce52 --- /dev/null +++ b/doc/source/command-objects/volume-host.rst @@ -0,0 +1,30 @@ +=========== +volume host +=========== + +Volume v2 + +volume host set +--------------- + +Set volume host properties + +.. program:: volume host set +.. code:: bash + + os volume host set + [--enable | --disable] + <host-name> + +.. option:: --enable + + Thaw and enable the specified volume host + +.. option:: --disable + + Freeze and disable the specified volume host + +.. _volume-host-set: +.. describe:: <host-name> + + Name of volume host diff --git a/doc/source/command-objects/volume-snapshot.rst b/doc/source/command-objects/volume-snapshot.rst index b84601f4..2d9406b6 100644 --- a/doc/source/command-objects/volume-snapshot.rst +++ b/doc/source/command-objects/volume-snapshot.rst @@ -71,6 +71,9 @@ List volume snapshots [--long] [--limit <limit>] [--marker <marker>] + [--name <name>] + [--status <status>] + [--volume <volume>] .. option:: --all-projects @@ -80,6 +83,19 @@ List volume snapshots List additional fields in output +.. option:: --status <status> + + Filters results by a status. + ('available', 'error', 'creating', 'deleting' or 'error-deleting') + +.. option:: --name <name> + + Filters results by a name. + +.. option:: --volume <volume> + + Filters results by a volume (name or ID). + .. option:: --limit <limit> Maximum number of snapshots to display diff --git a/doc/source/commands.rst b/doc/source/commands.rst index f7ef3eaa..795dfd53 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -144,6 +144,7 @@ referring to both Compute and Volume quotas. * ``user role``: (**Identity**) roles assigned to a user * ``volume``: (**Volume**) block volumes * ``volume backup``: (**Volume**) backup for volumes +* ``volume host``: (**Volume**) the physical computer for volumes * ``volume qos``: (**Volume**) quality-of-service (QoS) specification for volumes * ``volume snapshot``: (**Volume**) a point-in-time copy of a volume * ``volume type``: (**Volume**) deployment-specific types of volumes available diff --git a/openstackclient/identity/v3/unscoped_saml.py b/openstackclient/identity/v3/unscoped_saml.py index 5940534a..f7598f17 100644 --- a/openstackclient/identity/v3/unscoped_saml.py +++ b/openstackclient/identity/v3/unscoped_saml.py @@ -18,35 +18,14 @@ the user can list domains and projects they are allowed to access, and request a scoped token.""" from osc_lib.command import command -from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ -UNSCOPED_AUTH_PLUGINS = ['v3unscopedsaml', 'v3unscopedadfs', 'v3oidc'] - - -def auth_with_unscoped_saml(func): - """Check the unscoped federated context""" - - def _decorated(self, parsed_args): - auth_plugin_name = self.app.client_manager.auth_plugin_name - if auth_plugin_name in UNSCOPED_AUTH_PLUGINS: - return func(self, parsed_args) - else: - msg = (_('This command requires the use of an unscoped SAML ' - 'authentication plugin. Please use argument ' - '--os-auth-type with one of the following ' - 'plugins: %s') % ', '.join(UNSCOPED_AUTH_PLUGINS)) - raise exceptions.CommandError(msg) - return _decorated - - class ListAccessibleDomains(command.Lister): _description = _("List accessible domains") - @auth_with_unscoped_saml def take_action(self, parsed_args): columns = ('ID', 'Enabled', 'Name', 'Description') identity_client = self.app.client_manager.identity @@ -61,7 +40,6 @@ class ListAccessibleDomains(command.Lister): class ListAccessibleProjects(command.Lister): _description = _("List accessible projects") - @auth_with_unscoped_saml def take_action(self, parsed_args): columns = ('ID', 'Domain ID', 'Enabled', 'Name') identity_client = self.app.client_manager.identity diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 054d1612..1d167605 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -486,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', @@ -519,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 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/ip_availability.py b/openstackclient/network/v2/ip_availability.py index 5960e2fa..a80fe1c4 100644 --- a/openstackclient/network/v2/ip_availability.py +++ b/openstackclient/network/v2/ip_availability.py @@ -18,7 +18,7 @@ from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common - +from openstackclient.network import sdk_utils _formatters = { 'subnet_ip_availability': utils.format_list_of_dicts, @@ -26,13 +26,14 @@ _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) +# TODO(ankur-gupta-f): Use the SDK resource mapped attribute names once +# the OSC minimum requirements include SDK 1.0. class ListIPAvailability(command.Lister): _description = _("List IP availability for network") @@ -84,6 +85,7 @@ class ListIPAvailability(command.Lister): parsed_args.project_domain, ).id filters['tenant_id'] = project_id + filters['project_id'] = project_id data = client.network_ip_availabilities(**filters) return (column_headers, (utils.get_item_properties( @@ -107,6 +109,6 @@ class ShowIPAvailability(command.ShowOne): client = self.app.client_manager.network obj = client.find_network_ip_availability(parsed_args.network, 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) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 6cae87ee..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', @@ -128,6 +129,8 @@ def _get_attrs(client_manager, parsed_args): if parsed_args.host: attrs['binding:host_id'] = parsed_args.host + if parsed_args.dns_name is not None: + attrs['dns_name'] = parsed_args.dns_name # It is possible that name is not updated during 'port set' if parsed_args.name is not None: attrs['name'] = str(parsed_args.name) @@ -233,6 +236,12 @@ def _add_updatable_args(parser): metavar='<host-id>', help=argparse.SUPPRESS, ) + parser.add_argument( + '--dns-name', + metavar='dns-name', + help=_("Set DNS name to this port " + "(requires DNS integration extension)") + ) class CreatePort(command.ShowOne): 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/tests/unit/identity/v3/test_unscoped_saml.py b/openstackclient/tests/unit/identity/v3/test_unscoped_saml.py index 9e4e1876..34655263 100644 --- a/openstackclient/tests/unit/identity/v3/test_unscoped_saml.py +++ b/openstackclient/tests/unit/identity/v3/test_unscoped_saml.py @@ -12,8 +12,6 @@ import copy -from osc_lib import exceptions - from openstackclient.identity.v3 import unscoped_saml from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -48,7 +46,6 @@ class TestDomainList(TestUnscopedSAML): self.cmd = unscoped_saml.ListAccessibleDomains(self.app, None) def test_accessible_domains_list(self): - self.app.client_manager.auth_plugin_name = 'v3unscopedsaml' arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -70,17 +67,6 @@ class TestDomainList(TestUnscopedSAML): ), ) self.assertEqual(datalist, tuple(data)) - def test_accessible_domains_list_wrong_auth(self): - auth = identity_fakes.FakeAuth("wrong auth") - self.app.client_manager.identity.session.auth = auth - arglist = [] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, - parsed_args) - class TestProjectList(TestUnscopedSAML): @@ -99,7 +85,6 @@ class TestProjectList(TestUnscopedSAML): self.cmd = unscoped_saml.ListAccessibleProjects(self.app, None) def test_accessible_projects_list(self): - self.app.client_manager.auth_plugin_name = 'v3unscopedsaml' arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -120,14 +105,3 @@ class TestProjectList(TestUnscopedSAML): identity_fakes.project_name, ), ) self.assertEqual(datalist, tuple(data)) - - def test_accessible_projects_list_wrong_auth(self): - auth = identity_fakes.FakeAuth("wrong auth") - self.app.client_manager.identity.session.auth = auth - arglist = [] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, - parsed_args) diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 2f2212e4..a054e513 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -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 c18511f7..97d07076 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -194,15 +194,18 @@ class FakeIPAvailability(object): """Fake one or more network ip availabilities.""" @staticmethod - def create_one_ip_availability(): + def create_one_ip_availability(attrs=None): """Create a fake list with ip availability stats of a network. + :param Dictionary attrs: + A dictionary with all attributes :return: A FakeResource object with network_name, network_id, etc. """ + attrs = attrs or {} # Set default attributes. - network_ip_availability = { + network_ip_attrs = { 'network_id': 'network-id-' + uuid.uuid4().hex, 'network_name': 'network-name-' + uuid.uuid4().hex, 'tenant_id': '', @@ -210,10 +213,13 @@ class FakeIPAvailability(object): 'total_ips': 254, 'used_ips': 6, } + network_ip_attrs.update(attrs) network_ip_availability = fakes.FakeResource( - info=copy.deepcopy(network_ip_availability), + info=copy.deepcopy(network_ip_attrs), loaded=True) + network_ip_availability.project_id = network_ip_attrs['tenant_id'] + return network_ip_availability @staticmethod 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_ip_availability.py b/openstackclient/tests/unit/network/v2/test_ip_availability.py index c929ab82..4bdbddc4 100644 --- a/openstackclient/tests/unit/network/v2/test_ip_availability.py +++ b/openstackclient/tests/unit/network/v2/test_ip_availability.py @@ -107,6 +107,7 @@ class TestListIPAvailability(TestIPAvailability): columns, data = self.cmd.take_action(parsed_args) filters = {'tenant_id': self.project.id, + 'project_id': self.project.id, 'ip_version': 4} self.network.network_ip_availabilities.assert_called_once_with( diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 9312a897..aeb9884a 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -140,6 +140,7 @@ class TestCreatePort(TestPort): '--binding-profile', 'foo=bar', '--binding-profile', 'foo2=bar2', '--network', self._port.network_id, + '--dns-name', '8.8.8.8', 'test-port', ] @@ -156,6 +157,7 @@ class TestCreatePort(TestPort): ('vnic_type', 'macvtap'), ('binding_profile', {'foo': 'bar', 'foo2': 'bar2'}), ('network', self._port.network_id), + ('dns_name', '8.8.8.8'), ('name', 'test-port'), ] @@ -174,6 +176,7 @@ class TestCreatePort(TestPort): 'binding:vnic_type': 'macvtap', 'binding:profile': {'foo': 'bar', 'foo2': 'bar2'}, 'network_id': self._port.network_id, + 'dns_name': '8.8.8.8', 'name': 'test-port', }) @@ -241,6 +244,7 @@ class TestCreatePort(TestPort): '--security-group', secgroup.id, 'test-port', ] + verifylist = [ ('network', self._port.network_id,), ('enable', True), @@ -262,6 +266,33 @@ class TestCreatePort(TestPort): self.assertEqual(ref_columns, columns) self.assertEqual(ref_data, data) + def test_create_port_with_dns_name(self): + arglist = [ + '--network', self._port.network_id, + '--dns-name', '8.8.8.8', + 'test-port', + ] + verifylist = [ + ('network', self._port.network_id,), + ('enable', True), + ('dns_name', '8.8.8.8'), + ('name', 'test-port'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'dns_name': '8.8.8.8', + 'name': 'test-port', + }) + + ref_columns, ref_data = self._get_common_cols_data(self._port) + self.assertEqual(ref_columns, columns) + self.assertEqual(ref_data, data) + def test_create_with_security_groups(self): sg_1 = network_fakes.FakeSecurityGroup.create_one_security_group() sg_2 = network_fakes.FakeSecurityGroup.create_one_security_group() @@ -676,6 +707,25 @@ class TestSetPort(TestPort): self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) + def test_set_dns_name(self): + arglist = [ + '--dns-name', '8.8.8.8', + self._port.name, + ] + verifylist = [ + ('dns_name', '8.8.8.8'), + ('port', self._port.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'dns_name': '8.8.8.8', + } + self.network.update_port.assert_called_once_with(self._port, **attrs) + self.assertIsNone(result) + def test_append_fixed_ip(self): _testport = network_fakes.FakePort.create_one_port( {'fixed_ips': [{'ip_address': '0.0.0.1'}]}) 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/volume/v1/test_snapshot.py b/openstackclient/tests/unit/volume/v1/test_snapshot.py index 8e30d6a9..fd878f45 100644 --- a/openstackclient/tests/unit/volume/v1/test_snapshot.py +++ b/openstackclient/tests/unit/volume/v1/test_snapshot.py @@ -268,6 +268,7 @@ class TestSnapshotList(TestSnapshot): super(TestSnapshotList, self).setUp() self.volumes_mock.list.return_value = [self.volume] + self.volumes_mock.get.return_value = self.volume self.snapshots_mock.list.return_value = self.snapshots # Get the command to test self.cmd = volume_snapshot.ListVolumeSnapshot(self.app, None) @@ -283,7 +284,13 @@ class TestSnapshotList(TestSnapshot): columns, data = self.cmd.take_action(parsed_args) self.snapshots_mock.list.assert_called_once_with( - search_opts={'all_tenants': False}) + search_opts={ + 'all_tenants': False, + 'display_name': None, + 'status': None, + 'volume_id': None + } + ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -300,11 +307,88 @@ class TestSnapshotList(TestSnapshot): columns, data = self.cmd.take_action(parsed_args) self.snapshots_mock.list.assert_called_once_with( - search_opts={'all_tenants': False} + search_opts={ + 'all_tenants': False, + 'display_name': None, + 'status': None, + 'volume_id': None + } ) self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) + def test_snapshot_list_name_option(self): + arglist = [ + '--name', self.snapshots[0].display_name, + ] + verifylist = [ + ('all_projects', False), + ('long', False), + ('name', self.snapshots[0].display_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + search_opts={ + 'all_tenants': False, + 'display_name': self.snapshots[0].display_name, + 'status': None, + 'volume_id': None + } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_snapshot_list_status_option(self): + arglist = [ + '--status', self.snapshots[0].status, + ] + verifylist = [ + ('all_projects', False), + ('long', False), + ('status', self.snapshots[0].status), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + search_opts={ + 'all_tenants': False, + 'display_name': None, + 'status': self.snapshots[0].status, + 'volume_id': None + } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_snapshot_list_volumeid_option(self): + arglist = [ + '--volume', self.volume.id, + ] + verifylist = [ + ('all_projects', False), + ('long', False), + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + search_opts={ + 'all_tenants': False, + 'display_name': None, + 'status': None, + 'volume_id': self.volume.id + } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + def test_snapshot_list_all_projects(self): arglist = [ '--all-projects', @@ -318,7 +402,13 @@ class TestSnapshotList(TestSnapshot): columns, data = self.cmd.take_action(parsed_args) self.snapshots_mock.list.assert_called_once_with( - search_opts={'all_tenants': True}) + search_opts={ + 'all_tenants': True, + 'display_name': None, + 'status': None, + 'volume_id': None + } + ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 3137bfb0..d5cd72ec 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -480,7 +480,7 @@ class FakeBackup(object): If backups list is provided, then initialize the Mock object with the list. Otherwise create one. - :param List volumes: + :param List backups: A list of FakeResource objects faking backups :param Integer count: The number of backups to be faked @@ -764,7 +764,7 @@ class FakeQos(object): If qoses list is provided, then initialize the Mock object with the list. Otherwise create one. - :param List volumes: + :param List qoses: A list of FakeResource objects faking qoses :param Integer count: The number of qoses to be faked @@ -837,7 +837,7 @@ class FakeSnapshot(object): If snapshots list is provided, then initialize the Mock object with the list. Otherwise create one. - :param List volumes: + :param List snapshots: A list of FakeResource objects faking snapshots :param Integer count: The number of snapshots to be faked diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py index 8a997e18..bc99ca8d 100644 --- a/openstackclient/tests/unit/volume/v2/test_consistency_group.py +++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py @@ -32,6 +32,10 @@ class TestConsistencyGroup(volume_fakes.TestVolume): self.app.client_manager.volume.consistencygroups) self.consistencygroups_mock.reset_mock() + self.cgsnapshots_mock = ( + self.app.client_manager.volume.cgsnapshots) + self.cgsnapshots_mock.reset_mock() + self.types_mock = self.app.client_manager.volume.volume_types self.types_mock.reset_mock() @@ -41,6 +45,11 @@ class TestConsistencyGroupCreate(TestConsistencyGroup): volume_type = volume_fakes.FakeType.create_one_type() new_consistency_group = ( volume_fakes.FakeConsistencyGroup.create_one_consistency_group()) + consistency_group_snapshot = ( + volume_fakes. + FakeConsistencyGroupSnapshot. + create_one_consistency_group_snapshot() + ) columns = ( 'availability_zone', @@ -70,6 +79,8 @@ class TestConsistencyGroupCreate(TestConsistencyGroup): self.consistencygroups_mock.get.return_value = ( self.new_consistency_group) self.types_mock.get.return_value = self.volume_type + self.cgsnapshots_mock.get.return_value = ( + self.consistency_group_snapshot) # Get the command object to test self.cmd = consistency_group.CreateConsistencyGroup(self.app, None) @@ -164,6 +175,34 @@ class TestConsistencyGroupCreate(TestConsistencyGroup): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_consistency_group_create_from_snapshot(self): + arglist = [ + '--consistency-group-snapshot', self.consistency_group_snapshot.id, + '--description', self.new_consistency_group.description, + self.new_consistency_group.name, + ] + verifylist = [ + ('consistency_group_snapshot', self.consistency_group_snapshot.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.cgsnapshots_mock.get.assert_called_once_with( + self.consistency_group_snapshot.id) + self.consistencygroups_mock.create_from_src.assert_called_with( + self.consistency_group_snapshot.id, + None, + 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): diff --git a/openstackclient/tests/unit/volume/v2/test_snapshot.py b/openstackclient/tests/unit/volume/v2/test_snapshot.py index b67dd6eb..bb238135 100644 --- a/openstackclient/tests/unit/volume/v2/test_snapshot.py +++ b/openstackclient/tests/unit/volume/v2/test_snapshot.py @@ -275,6 +275,7 @@ class TestSnapshotList(TestSnapshot): super(TestSnapshotList, self).setUp() self.volumes_mock.list.return_value = [self.volume] + self.volumes_mock.get.return_value = self.volume self.snapshots_mock.list.return_value = self.snapshots # Get the command to test self.cmd = volume_snapshot.ListVolumeSnapshot(self.app, None) @@ -283,14 +284,21 @@ class TestSnapshotList(TestSnapshot): arglist = [] verifylist = [ ('all_projects', False), - ("long", False) + ('long', False) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.snapshots_mock.list.assert_called_once_with( - limit=None, marker=None, search_opts={'all_tenants': False}) + limit=None, marker=None, + search_opts={ + 'all_tenants': False, + 'name': None, + 'status': None, + 'volume_id': None + } + ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -313,7 +321,12 @@ class TestSnapshotList(TestSnapshot): self.snapshots_mock.list.assert_called_once_with( limit=2, marker=self.snapshots[0].id, - search_opts={'all_tenants': False} + search_opts={ + 'all_tenants': False, + 'name': None, + 'status': None, + 'volume_id': None + } ) self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) @@ -331,7 +344,89 @@ class TestSnapshotList(TestSnapshot): columns, data = self.cmd.take_action(parsed_args) self.snapshots_mock.list.assert_called_once_with( - limit=None, marker=None, search_opts={'all_tenants': True}) + limit=None, marker=None, + search_opts={ + 'all_tenants': True, + 'name': None, + 'status': None, + 'volume_id': None + } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_snapshot_list_name_option(self): + arglist = [ + '--name', self.snapshots[0].name, + ] + verifylist = [ + ('all_projects', False), + ('long', False), + ('name', self.snapshots[0].name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + limit=None, marker=None, + search_opts={ + 'all_tenants': False, + 'name': self.snapshots[0].name, + 'status': None, + 'volume_id': None + } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_snapshot_list_status_option(self): + arglist = [ + '--status', self.snapshots[0].status, + ] + verifylist = [ + ('all_projects', False), + ('long', False), + ('status', self.snapshots[0].status), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + limit=None, marker=None, + search_opts={ + 'all_tenants': False, + 'name': None, + 'status': self.snapshots[0].status, + 'volume_id': None + } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_snapshot_list_volumeid_option(self): + arglist = [ + '--volume', self.volume.id, + ] + verifylist = [ + ('all_projects', False), + ('long', False), + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + limit=None, marker=None, + search_opts={ + 'all_tenants': False, + 'name': None, + 'status': None, + 'volume_id': self.volume.id + } + ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) diff --git a/openstackclient/tests/unit/volume/v2/test_volume_host.py b/openstackclient/tests/unit/volume/v2/test_volume_host.py new file mode 100644 index 00000000..aad7bb0b --- /dev/null +++ b/openstackclient/tests/unit/volume/v2/test_volume_host.py @@ -0,0 +1,86 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from openstackclient.tests.unit.volume.v2 import fakes as host_fakes +from openstackclient.volume.v2 import volume_host + + +class TestVolumeHost(host_fakes.TestVolume): + + def setUp(self): + super(TestVolumeHost, self).setUp() + + self.host_mock = self.app.client_manager.volume.services + self.host_mock.reset_mock() + + +class TestVolumeHostSet(TestVolumeHost): + + service = host_fakes.FakeService.create_one_service() + + def setUp(self): + super(TestVolumeHostSet, self).setUp() + + self.host_mock.freeze_host.return_value = None + self.host_mock.thaw_host.return_value = None + + self.cmd = volume_host.SetVolumeHost(self.app, None) + + def test_volume_host_set_nothing(self): + arglist = [ + self.service.host, + ] + verifylist = [ + ('host', self.service.host), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.host_mock.freeze_host.assert_not_called() + self.host_mock.thaw_host.assert_not_called() + self.assertIsNone(result) + + def test_volume_host_set_enable(self): + arglist = [ + '--enable', + self.service.host, + ] + verifylist = [ + ('enable', True), + ('host', self.service.host), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.host_mock.thaw_host.assert_called_with(self.service.host) + self.host_mock.freeze_host.assert_not_called() + self.assertIsNone(result) + + def test_volume_host_set_disable(self): + arglist = [ + '--disable', + self.service.host, + ] + verifylist = [ + ('disable', True), + ('host', self.service.host), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.host_mock.freeze_host.assert_called_with(self.service.host) + self.host_mock.thaw_host.assert_not_called() + self.assertIsNone(result) diff --git a/openstackclient/volume/v1/volume_snapshot.py b/openstackclient/volume/v1/volume_snapshot.py index c2ecf75b..77c93ad4 100644 --- a/openstackclient/volume/v1/volume_snapshot.py +++ b/openstackclient/volume/v1/volume_snapshot.py @@ -135,9 +135,31 @@ class ListVolumeSnapshot(command.Lister): default=False, help=_('List additional fields in output'), ) + parser.add_argument( + '--name', + metavar='<name>', + default=None, + help=_('Filters results by a name.') + ) + 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( + '--volume', + metavar='<volume>', + default=None, + help=_('Filters results by a volume (name or ID).') + ) return parser def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume def _format_volume_id(volume_id): """Return a volume name if available @@ -169,17 +191,25 @@ class ListVolumeSnapshot(command.Lister): # Cache the volume list volume_cache = {} try: - for s in self.app.client_manager.volume.volumes.list(): + for s in volume_client.volumes.list(): volume_cache[s.id] = s except Exception: # Just forget it if there's any trouble pass + volume_id = None + if parsed_args.volume: + volume_id = utils.find_resource( + volume_client.volumes, parsed_args.volume).id + search_opts = { 'all_tenants': parsed_args.all_projects, + 'display_name': parsed_args.name, + 'status': parsed_args.status, + 'volume_id': volume_id, } - data = self.app.client_manager.volume.volume_snapshots.list( + data = volume_client.volume_snapshots.list( search_opts=search_opts) return (column_headers, (utils.get_item_properties( diff --git a/openstackclient/volume/v2/consistency_group.py b/openstackclient/volume/v2/consistency_group.py index f77da59b..2f4f3c95 100644 --- a/openstackclient/volume/v2/consistency_group.py +++ b/openstackclient/volume/v2/consistency_group.py @@ -27,51 +27,6 @@ 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.") @@ -94,6 +49,11 @@ class CreateConsistencyGroup(command.ShowOne): metavar="<consistency-group>", help=_("Existing consistency group (name or ID)") ) + exclusive_group.add_argument( + "--consistency-group-snapshot", + metavar="<consistency-group-snapshot>", + help=_("Existing consistency group snapshot (name or ID)") + ) parser.add_argument( "--description", metavar="<description>", @@ -120,17 +80,23 @@ class CreateConsistencyGroup(command.ShowOne): description=parsed_args.description, availability_zone=parsed_args.availability_zone ) - elif parsed_args.consistency_group_source: + else: 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_id = None consistency_group_snapshot = None - # TODO(Huanxuan Ao): Support for creating from consistency group - # snapshot after adding "consistency_group_snapshot" resource + if parsed_args.consistency_group_source: + consistency_group_id = utils.find_resource( + volume_client.consistencygroups, + parsed_args.consistency_group_source).id + elif parsed_args.consistency_group_snapshot: + consistency_group_snapshot = utils.find_resource( + volume_client.cgsnapshots, + parsed_args.consistency_group_snapshot).id + consistency_group = ( volume_client.consistencygroups.create_from_src( consistency_group_snapshot, @@ -143,6 +109,48 @@ class CreateConsistencyGroup(command.ShowOne): return zip(*sorted(six.iteritems(consistency_group._info))) +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) + + class ListConsistencyGroup(command.Lister): _description = _("List consistency groups.") diff --git a/openstackclient/volume/v2/volume_host.py b/openstackclient/volume/v2/volume_host.py new file mode 100644 index 00000000..376e5024 --- /dev/null +++ b/openstackclient/volume/v2/volume_host.py @@ -0,0 +1,50 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Volume v2 host action implementations""" + +from osc_lib.command import command + +from openstackclient.i18n import _ + + +class SetVolumeHost(command.Command): + _description = _("Set volume host properties") + + def get_parser(self, prog_name): + parser = super(SetVolumeHost, self).get_parser(prog_name) + parser.add_argument( + "host", + metavar="<host-name>", + help=_("Name of volume host") + ) + enabled_group = parser.add_mutually_exclusive_group() + enabled_group.add_argument( + "--disable", + action="store_true", + help=_("Freeze and disable the specified volume host.") + ) + enabled_group.add_argument( + "--enable", + action="store_true", + help=_("Thaw and enable the specified volume host.") + ) + return parser + + def take_action(self, parsed_args): + service_client = self.app.client_manager.volume + if parsed_args.enable: + service_client.services.thaw_host(parsed_args.host) + if parsed_args.disable: + service_client.services.freeze_host(parsed_args.host) diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py index 43f30326..86af6d8c 100644 --- a/openstackclient/volume/v2/volume_snapshot.py +++ b/openstackclient/volume/v2/volume_snapshot.py @@ -151,9 +151,31 @@ class ListVolumeSnapshot(command.Lister): metavar='<limit>', help=_('Maximum number of snapshots to display'), ) + parser.add_argument( + '--name', + metavar='<name>', + default=None, + help=_('Filters results by a name.') + ) + 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( + '--volume', + metavar='<volume>', + default=None, + help=_('Filters results by a volume (name or ID).') + ) return parser def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume def _format_volume_id(volume_id): """Return a volume name if available @@ -180,17 +202,25 @@ class ListVolumeSnapshot(command.Lister): # Cache the volume list volume_cache = {} try: - for s in self.app.client_manager.volume.volumes.list(): + for s in volume_client.volumes.list(): volume_cache[s.id] = s except Exception: # Just forget it if there's any trouble pass + volume_id = None + if parsed_args.volume: + volume_id = utils.find_resource( + volume_client.volumes, parsed_args.volume).id + search_opts = { 'all_tenants': parsed_args.all_projects, + 'name': parsed_args.name, + 'status': parsed_args.status, + 'volume_id': volume_id, } - data = self.app.client_manager.volume.volume_snapshots.list( + data = volume_client.volume_snapshots.list( search_opts=search_opts, marker=parsed_args.marker, limit=parsed_args.limit, diff --git a/releasenotes/notes/bp-cinder-command-support-7e3ae1fb4cd90407.yaml b/releasenotes/notes/bp-cinder-command-support-7e3ae1fb4cd90407.yaml new file mode 100644 index 00000000..1b0ca18d --- /dev/null +++ b/releasenotes/notes/bp-cinder-command-support-7e3ae1fb4cd90407.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``volume host set`` command, it allows a user to enable or disable a volume host. + [Blueprint `cinder-command-support <https://blueprints.launchpad.net/python-openstackclient/+spec/cinder-command-support>`_] + diff --git a/releasenotes/notes/bug-1612136-63aac6377209db38.yaml b/releasenotes/notes/bug-1612136-63aac6377209db38.yaml new file mode 100644 index 00000000..51eaa76d --- /dev/null +++ b/releasenotes/notes/bug-1612136-63aac6377209db38.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--dns-name`` option to ``os port create`` and ``os port set`` commands. + [Bug `1612136 <https://bugs.launchpad.net/python-openstackclient/+bug/1612136>`_] diff --git a/releasenotes/notes/bug-1613231-386b2b1373662052.yaml b/releasenotes/notes/bug-1613231-386b2b1373662052.yaml new file mode 100644 index 00000000..a55af0dd --- /dev/null +++ b/releasenotes/notes/bug-1613231-386b2b1373662052.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add ``--project`` and ``--project-domain`` options to the ``router list``, + ``floating ip create`` and ``security group list`` commands. + [Bug `1613231 <https://bugs.launchpad.net/bugs/1613231>`_] + [Bug `1613629 <https://bugs.launchpad.net/bugs/1613629>`_] + [Bug `1610909 <https://bugs.launchpad.net/bugs/1610909>`_] diff --git a/releasenotes/notes/bug-1645252-219bfd50c8f04846.yaml b/releasenotes/notes/bug-1645252-219bfd50c8f04846.yaml new file mode 100644 index 00000000..12b1e42d --- /dev/null +++ b/releasenotes/notes/bug-1645252-219bfd50c8f04846.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--name``, ``--status`` and ``--volume`` options + to ``volume snapshot list`` command + [Bug `1645252 <https://bugs.launchpad.net/bugs/1645252>`_] @@ -548,6 +548,8 @@ openstack.volume.v2 = volume_backup_set = openstackclient.volume.v2.backup:SetVolumeBackup volume_backup_show = openstackclient.volume.v2.backup:ShowVolumeBackup + volume_host_set = openstackclient.volume.v2.volume_host:SetVolumeHost + volume_snapshot_create = openstackclient.volume.v2.volume_snapshot:CreateVolumeSnapshot volume_snapshot_delete = openstackclient.volume.v2.volume_snapshot:DeleteVolumeSnapshot volume_snapshot_list = openstackclient.volume.v2.volume_snapshot:ListVolumeSnapshot |
