summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/api/auth.py52
-rw-r--r--openstackclient/api/auth_plugin.py99
-rw-r--r--openstackclient/common/availability_zone.py20
-rw-r--r--openstackclient/common/clientmanager.py78
-rw-r--r--openstackclient/common/extension.py21
-rw-r--r--openstackclient/common/quota.py5
-rw-r--r--openstackclient/compute/v2/agent.py12
-rw-r--r--openstackclient/compute/v2/aggregate.py34
-rw-r--r--openstackclient/compute/v2/console.py12
-rw-r--r--openstackclient/compute/v2/flavor.py77
-rw-r--r--openstackclient/compute/v2/server.py36
-rw-r--r--openstackclient/compute/v2/server_group.py7
-rw-r--r--openstackclient/compute/v2/server_image.py16
-rw-r--r--openstackclient/compute/v2/service.py13
-rw-r--r--openstackclient/identity/v2_0/catalog.py8
-rw-r--r--openstackclient/identity/v2_0/project.py7
-rw-r--r--openstackclient/identity/v2_0/role.py7
-rw-r--r--openstackclient/identity/v2_0/service.py10
-rw-r--r--openstackclient/identity/v2_0/user.py9
-rw-r--r--openstackclient/identity/v3/catalog.py8
-rw-r--r--openstackclient/identity/v3/domain.py6
-rw-r--r--openstackclient/identity/v3/federation_protocol.py7
-rw-r--r--openstackclient/identity/v3/group.py6
-rw-r--r--openstackclient/identity/v3/identity_provider.py7
-rw-r--r--openstackclient/identity/v3/mapping.py6
-rw-r--r--openstackclient/identity/v3/project.py7
-rw-r--r--openstackclient/identity/v3/role.py6
-rw-r--r--openstackclient/identity/v3/service.py2
-rw-r--r--openstackclient/identity/v3/user.py8
-rw-r--r--openstackclient/image/v1/image.py25
-rw-r--r--openstackclient/image/v2/image.py35
-rw-r--r--openstackclient/network/common.py18
-rw-r--r--openstackclient/network/v2/address_scope.py11
-rw-r--r--openstackclient/network/v2/port.py6
-rw-r--r--openstackclient/network/v2/router.py19
-rw-r--r--openstackclient/network/v2/security_group_rule.py4
-rw-r--r--openstackclient/network/v2/subnet.py10
-rw-r--r--openstackclient/tests/compute/v2/fakes.py44
-rw-r--r--openstackclient/tests/compute/v2/test_aggregate.py65
-rw-r--r--openstackclient/tests/compute/v2/test_console.py149
-rw-r--r--openstackclient/tests/compute/v2/test_flavor.py101
-rw-r--r--openstackclient/tests/compute/v2/test_server_image.py5
-rw-r--r--openstackclient/tests/compute/v2/test_service.py56
-rw-r--r--openstackclient/tests/image/v1/test_image.py4
-rw-r--r--openstackclient/tests/network/v2/fakes.py9
-rw-r--r--openstackclient/tests/network/v2/test_router.py66
-rw-r--r--openstackclient/tests/network/v2/test_subnet.py90
47 files changed, 952 insertions, 351 deletions
diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py
index 0018e76e..0c82fe9b 100644
--- a/openstackclient/api/auth.py
+++ b/openstackclient/api/auth.py
@@ -86,7 +86,7 @@ def select_auth_plugin(options):
auth_plugin_name = 'v2password'
else:
# let keystoneclient figure it out itself
- auth_plugin_name = 'osc_password'
+ auth_plugin_name = 'password'
elif options.auth.get('token'):
if options.identity_api_version == '3':
auth_plugin_name = 'v3token'
@@ -98,17 +98,19 @@ def select_auth_plugin(options):
else:
# The ultimate default is similar to the original behaviour,
# but this time with version discovery
- auth_plugin_name = 'osc_password'
+ auth_plugin_name = 'password'
LOG.debug("Auth plugin %s selected", auth_plugin_name)
return auth_plugin_name
def build_auth_params(auth_plugin_name, cmd_options):
- auth_params = dict(cmd_options.auth)
if auth_plugin_name:
LOG.debug('auth_type: %s', auth_plugin_name)
auth_plugin_loader = base.get_plugin_loader(auth_plugin_name)
+ auth_params = {opt.dest: opt.default
+ for opt in base.get_plugin_options(auth_plugin_name)}
+ auth_params.update(dict(cmd_options.auth))
# grab tenant from project for v2.0 API compatibility
if auth_plugin_name.startswith("v2"):
if 'project_id' in auth_params:
@@ -121,6 +123,7 @@ def build_auth_params(auth_plugin_name, cmd_options):
LOG.debug('no auth_type')
# delay the plugin choice, grab every option
auth_plugin_loader = None
+ auth_params = dict(cmd_options.auth)
plugin_options = set([o.replace('-', '_') for o in get_options_list()])
for option in plugin_options:
LOG.debug('fetching option %s', option)
@@ -147,29 +150,28 @@ def check_valid_authorization_options(options, auth_plugin_name):
def check_valid_authentication_options(options, auth_plugin_name):
"""Validate authentication options, and provide helpful error messages."""
- msgs = []
- if auth_plugin_name.endswith('password'):
- if not options.auth.get('username'):
- msgs.append(_('Set a username with --os-username, OS_USERNAME,'
- ' or auth.username'))
- if not options.auth.get('auth_url'):
- msgs.append(_('Set an authentication URL, with --os-auth-url,'
- ' OS_AUTH_URL or auth.auth_url'))
- elif auth_plugin_name.endswith('token'):
- if not options.auth.get('token'):
- msgs.append(_('Set a token with --os-token, OS_TOKEN or '
- 'auth.token'))
- if not options.auth.get('auth_url'):
- msgs.append(_('Set a service AUTH_URL, with --os-auth-url, '
- 'OS_AUTH_URL or auth.auth_url'))
- elif auth_plugin_name == 'token_endpoint':
- if not options.auth.get('token'):
- msgs.append(_('Set a token with --os-token, OS_TOKEN or '
- 'auth.token'))
- if not options.auth.get('url'):
- msgs.append(_('Set a service URL, with --os-url, OS_URL or '
- 'auth.url'))
+ # Get all the options defined within the plugin.
+ plugin_opts = base.get_plugin_options(auth_plugin_name)
+ plugin_opts = {opt.dest: opt for opt in plugin_opts}
+ # NOTE(aloga): this is an horrible hack. We need a way to specify the
+ # required options in the plugins. Using the "required" argument for
+ # the oslo_config.cfg.Opt does not work, as it is not possible to load the
+ # plugin if the option is not defined, so the error will simply be:
+ # "NoMatchingPlugin: The plugin foobar could not be found"
+ msgs = []
+ if 'password' in plugin_opts and not options.auth.get('username'):
+ msgs.append(_('Set a username with --os-username, OS_USERNAME,'
+ ' or auth.username'))
+ if 'auth_url' in plugin_opts and not options.auth.get('auth_url'):
+ msgs.append(_('Set a service AUTH_URL, with --os-auth-url, '
+ 'OS_AUTH_URL or auth.auth_url'))
+ if 'url' in plugin_opts and not options.auth.get('url'):
+ msgs.append(_('Set a service URL, with --os-url, '
+ 'OS_URL or auth.url'))
+ if 'token' in plugin_opts and not options.auth.get('token'):
+ msgs.append(_('Set a token with --os-token, '
+ 'OS_TOKEN or auth.token'))
if msgs:
raise exc.CommandError(
_('Missing parameter(s): \n%s') % '\n'.join(msgs))
diff --git a/openstackclient/api/auth_plugin.py b/openstackclient/api/auth_plugin.py
index 36dc5160..dc47a688 100644
--- a/openstackclient/api/auth_plugin.py
+++ b/openstackclient/api/auth_plugin.py
@@ -15,90 +15,51 @@
import logging
-from oslo_config import cfg
-from six.moves.urllib import parse as urlparse
-
-from keystoneauth1.loading._plugins import admin_token as token_endpoint
-from keystoneauth1.loading._plugins.identity import generic as ksa_password
+from keystoneauth1 import loading
+from keystoneauth1 import token_endpoint
from openstackclient.i18n import _
LOG = logging.getLogger(__name__)
-class TokenEndpoint(token_endpoint.AdminToken):
+class TokenEndpoint(loading.BaseLoader):
"""Auth plugin to handle traditional token/endpoint usage
- Implements the methods required to handle token authentication
- with a user-specified token and service endpoint; no Identity calls
- are made for re-scoping, service catalog lookups or the like.
-
- The purpose of this plugin is to get rid of the special-case paths
- in the code to handle this authentication format. Its primary use
- is for bootstrapping the Keystone database.
+ Keystoneauth contains a Token plugin class that now correctly
+ handles the token/endpoint auth compatible with OSC. However,
+ the AdminToken loader deprecates the 'url' argument, which breaks
+ OSC compatibility, so make one that works.
"""
- def load_from_options(self, url, token):
+ @property
+ def plugin_class(self):
+ return token_endpoint.Token
+
+ def load_from_options(self, url, token, **kwargs):
"""A plugin for static authentication with an existing token
:param string url: Service endpoint
:param string token: Existing token
"""
- return super(TokenEndpoint, self).load_from_options(endpoint=url,
- token=token)
-
- def get_options(self):
- options = super(TokenEndpoint, self).get_options()
- options.extend([
- # Maintain name 'url' for compatibility
- cfg.StrOpt('url',
- help=_('Specific service endpoint to use')),
- cfg.StrOpt('token',
- secret=True,
- help=_('Authentication token to use')),
- ])
+ return super(TokenEndpoint, self).load_from_options(
+ endpoint=url,
+ token=token,
+ )
+ def get_options(self):
+ """Return the legacy options"""
+
+ options = [
+ loading.Opt(
+ 'url',
+ help=_('Specific service endpoint to use'),
+ ),
+ loading.Opt(
+ 'token',
+ secret=True,
+ help=_('Authentication token to use'),
+ ),
+ ]
return options
-
-
-class OSCGenericPassword(ksa_password.Password):
- """Auth plugin hack to work around broken Keystone configurations
-
- The default Keystone configuration uses http://localhost:xxxx in
- admin_endpoint and public_endpoint and are returned in the links.href
- attribute by the version routes. Deployments that do not set these
- are unusable with newer keystoneclient version discovery.
-
- """
-
- def create_plugin(self, session, version, url, raw_status=None):
- """Handle default Keystone endpoint configuration
-
- Build the actual API endpoint from the scheme, host and port of the
- original auth URL and the rest from the returned version URL.
- """
-
- ver_u = urlparse.urlparse(url)
-
- # Only hack this if it is the default setting
- if ver_u.netloc.startswith('localhost'):
- auth_u = urlparse.urlparse(self.auth_url)
- # from original auth_url: scheme, netloc
- # from api_url: path, query (basically, the rest)
- url = urlparse.urlunparse((
- auth_u.scheme,
- auth_u.netloc,
- ver_u.path,
- ver_u.params,
- ver_u.query,
- ver_u.fragment,
- ))
- LOG.debug('Version URL updated: %s', url)
-
- return super(OSCGenericPassword, self).create_plugin(
- session=session,
- version=version,
- url=url,
- raw_status=raw_status,
- )
diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py
index af161d1f..89d77d15 100644
--- a/openstackclient/common/availability_zone.py
+++ b/openstackclient/common/availability_zone.py
@@ -14,6 +14,7 @@
"""Availability Zone action implementations"""
import copy
+import logging
from novaclient import exceptions as nova_exceptions
from osc_lib.command import command
@@ -23,6 +24,9 @@ import six
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
def _xform_common_availability_zone(az, zone_info):
if hasattr(az, 'zoneState'):
zone_info['zone_status'] = ('available' if az.zoneState['available']
@@ -136,11 +140,11 @@ class ListAvailabilityZone(command.Lister):
try:
data = volume_client.availability_zones.list()
except Exception as e:
- self.log.debug('Volume availability zone exception: ' + str(e))
+ LOG.debug('Volume availability zone exception: %s', e)
if parsed_args.volume:
- message = "Availability zones list not supported by " \
- "Block Storage API"
- self.log.warning(message)
+ message = _("Availability zones list not supported by "
+ "Block Storage API")
+ LOG.warning(message)
result = []
for zone in data:
@@ -154,11 +158,11 @@ class ListAvailabilityZone(command.Lister):
network_client.find_extension('Availability Zone',
ignore_missing=False)
except Exception as e:
- self.log.debug('Network availability zone exception: ' + str(e))
+ LOG.debug('Network availability zone exception: ', e)
if parsed_args.network:
- message = "Availability zones list not supported by " \
- "Network API"
- self.log.warning(message)
+ message = _("Availability zones list not supported by "
+ "Network API")
+ LOG.warning(message)
return []
result = []
diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py
index 5dbfb417..3c35b529 100644
--- a/openstackclient/common/clientmanager.py
+++ b/openstackclient/common/clientmanager.py
@@ -140,8 +140,49 @@ class ClientManager(object):
# prior to dereferrencing auth_ref.
self._auth_setup_completed = False
+ def _set_default_scope_options(self):
+ # TODO(mordred): This is a usability improvement that's broadly useful
+ # We should port it back up into os-client-config.
+ default_domain = self._cli_options.default_domain
+
+ # NOTE(hieulq): If USER_DOMAIN_NAME, USER_DOMAIN_ID, PROJECT_DOMAIN_ID
+ # or PROJECT_DOMAIN_NAME is present and API_VERSION is 2.0, then
+ # ignore all domain related configs.
+ if (self._api_version.get('identity') == '2.0' and
+ self.auth_plugin_name.endswith('password')):
+ domain_props = ['project_domain_name', 'project_domain_id',
+ 'user_domain_name', 'user_domain_id']
+ for prop in domain_props:
+ if self._auth_params.pop(prop, None) is not None:
+ LOG.warning("Ignoring domain related configs " +
+ prop + " because identity API version is 2.0")
+ return
+
+ # NOTE(aloga): The scope parameters below only apply to v3 and v3
+ # related auth plugins, so we stop the parameter checking if v2 is
+ # being used.
+ if (self._api_version.get('identity') != '3' or
+ self.auth_plugin_name.startswith('v2')):
+ return
+
+ # NOTE(stevemar): If PROJECT_DOMAIN_ID or PROJECT_DOMAIN_NAME is
+ # present, then do not change the behaviour. Otherwise, set the
+ # PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability.
+ if ('project_domain_id' in self._auth_params and
+ not self._auth_params.get('project_domain_id') and
+ not self._auth_params.get('project_domain_name')):
+ self._auth_params['project_domain_id'] = default_domain
+
+ # NOTE(stevemar): If USER_DOMAIN_ID or USER_DOMAIN_NAME is present,
+ # then do not change the behaviour. Otherwise, set the
+ # USER_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability.
+ if ('user_domain_id' in self._auth_params and
+ not self._auth_params.get('user_domain_id') and
+ not self._auth_params.get('user_domain_name')):
+ self._auth_params['user_domain_id'] = default_domain
+
def setup_auth(self):
- """Set up authentication.
+ """Set up authentication
This is deferred until authentication is actually attempted because
it gets in the way of things that do not require auth.
@@ -169,40 +210,7 @@ class ClientManager(object):
self._cli_options,
)
- # TODO(mordred): This is a usability improvement that's broadly useful
- # We should port it back up into os-client-config.
- default_domain = self._cli_options.default_domain
- # NOTE(stevemar): If PROJECT_DOMAIN_ID or PROJECT_DOMAIN_NAME is
- # present, then do not change the behaviour. Otherwise, set the
- # PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability.
- if (self._api_version.get('identity') == '3' and
- self.auth_plugin_name.endswith('password') and
- not self._auth_params.get('project_domain_id') and
- not self.auth_plugin_name.startswith('v2') and
- not self._auth_params.get('project_domain_name')):
- self._auth_params['project_domain_id'] = default_domain
-
- # NOTE(stevemar): If USER_DOMAIN_ID or USER_DOMAIN_NAME is present,
- # then do not change the behaviour. Otherwise, set the USER_DOMAIN_ID
- # to 'OS_DEFAULT_DOMAIN' for better usability.
- if (self._api_version.get('identity') == '3' and
- self.auth_plugin_name.endswith('password') and
- not self.auth_plugin_name.startswith('v2') and
- not self._auth_params.get('user_domain_id') and
- not self._auth_params.get('user_domain_name')):
- self._auth_params['user_domain_id'] = default_domain
-
- # NOTE(hieulq): If USER_DOMAIN_NAME, USER_DOMAIN_ID, PROJECT_DOMAIN_ID
- # or PROJECT_DOMAIN_NAME is present and API_VERSION is 2.0, then
- # ignore all domain related configs.
- if (self._api_version.get('identity') == '2.0' and
- self.auth_plugin_name.endswith('password')):
- domain_props = ['project_domain_name', 'project_domain_id',
- 'user_domain_name', 'user_domain_id']
- for prop in domain_props:
- if self._auth_params.pop(prop, None) is not None:
- LOG.warning("Ignoring domain related configs " +
- prop + " because identity API version is 2.0")
+ self._set_default_scope_options()
# For compatibility until all clients can be updated
if 'project_name' in self._auth_params:
diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py
index 327fc223..de480016 100644
--- a/openstackclient/common/extension.py
+++ b/openstackclient/common/extension.py
@@ -16,6 +16,7 @@
"""Extension action implementations"""
import itertools
+import logging
from osc_lib.command import command
from osc_lib import utils
@@ -23,6 +24,9 @@ from osc_lib import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class ListExtension(command.Lister):
"""List API extensions"""
@@ -80,24 +84,25 @@ class ListExtension(command.Lister):
try:
data += identity_client.extensions.list()
except Exception:
- message = "Extensions list not supported by Identity API"
- self.log.warning(message)
+ message = _("Extensions list not supported by Identity API")
+ LOG.warning(message)
if parsed_args.compute or show_all:
compute_client = self.app.client_manager.compute
try:
data += compute_client.list_extensions.show_all()
except Exception:
- message = "Extensions list not supported by Compute API"
- self.log.warning(message)
+ message = _("Extensions list not supported by Compute API")
+ LOG.warning(message)
if parsed_args.volume or show_all:
volume_client = self.app.client_manager.volume
try:
data += volume_client.list_extensions.show_all()
except Exception:
- message = "Extensions list not supported by Block Storage API"
- self.log.warning(message)
+ message = _("Extensions list not supported by "
+ "Block Storage API")
+ LOG.warning(message)
# Resource classes for the above
extension_tuples = (
@@ -125,7 +130,7 @@ class ListExtension(command.Lister):
dict_tuples
)
except Exception:
- message = "Extensions list not supported by Network API"
- self.log.warning(message)
+ message = _("Extensions list not supported by Network API")
+ LOG.warning(message)
return (columns, extension_tuples)
diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py
index 087d8ea3..69415f0d 100644
--- a/openstackclient/common/quota.py
+++ b/openstackclient/common/quota.py
@@ -142,11 +142,6 @@ class SetQuota(command.Command):
if value is not None:
compute_kwargs[k] = value
- if (compute_kwargs == {} and volume_kwargs == {}
- and network_kwargs == {}):
- sys.stderr.write("No quotas updated\n")
- return
-
if parsed_args.project:
project = utils.find_resource(
identity_client.projects,
diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py
index ce6898c1..62be2424 100644
--- a/openstackclient/compute/v2/agent.py
+++ b/openstackclient/compute/v2/agent.py
@@ -15,6 +15,8 @@
"""Agent action implementations"""
+import logging
+
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
@@ -23,6 +25,9 @@ import six
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class CreateAgent(command.ShowOne):
"""Create compute agent command"""
@@ -96,14 +101,13 @@ class DeleteAgent(command.Command):
compute_client.agents.delete(id)
except Exception as e:
result += 1
- self.app.log.error(_("Failed to delete agent with "
- "ID '%(id)s': %(e)s")
- % {'id': id, 'e': e})
+ LOG.error(_("Failed to delete agent with ID '%(id)s': %(e)s"),
+ {'id': id, 'e': e})
if result > 0:
total = len(parsed_args.id)
msg = (_("%(result)s of %(total)s agents failed "
- "to delete.") % {'result': result, 'total': total})
+ "to delete.") % {'result': result, 'total': total})
raise exceptions.CommandError(msg)
diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py
index 8000c93a..2e2838c5 100644
--- a/openstackclient/compute/v2/aggregate.py
+++ b/openstackclient/compute/v2/aggregate.py
@@ -16,14 +16,20 @@
"""Compute v2 Aggregate action implementations"""
+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 AddAggregateHost(command.ShowOne):
"""Add host to aggregate"""
@@ -99,25 +105,37 @@ class CreateAggregate(command.ShowOne):
class DeleteAggregate(command.Command):
- """Delete an existing aggregate"""
+ """Delete existing aggregate(s)"""
def get_parser(self, prog_name):
parser = super(DeleteAggregate, self).get_parser(prog_name)
parser.add_argument(
'aggregate',
metavar='<aggregate>',
- help=_("Aggregate to delete (name or ID)")
+ nargs='+',
+ help=_("Aggregate(s) to delete (name or ID)")
)
return parser
def take_action(self, parsed_args):
-
compute_client = self.app.client_manager.compute
- data = utils.find_resource(
- compute_client.aggregates,
- parsed_args.aggregate,
- )
- compute_client.aggregates.delete(data.id)
+ result = 0
+ for a in parsed_args.aggregate:
+ try:
+ data = utils.find_resource(
+ compute_client.aggregates, a)
+ compute_client.aggregates.delete(data.id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete aggregate with name or "
+ "ID '%(aggregate)s': %(e)s")
+ % {'aggregate': a, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.aggregate)
+ msg = (_("%(result)s of %(total)s aggregates failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListAggregate(command.Lister):
diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py
index 97d3f318..4179fa5c 100644
--- a/openstackclient/compute/v2/console.py
+++ b/openstackclient/compute/v2/console.py
@@ -93,7 +93,7 @@ class ShowConsoleURL(command.ShowOne):
'--spice',
dest='url_type',
action='store_const',
- const='spice',
+ const='spice-html5',
help=_("Show SPICE console URL")
)
return parser
@@ -105,14 +105,20 @@ class ShowConsoleURL(command.ShowOne):
parsed_args.server,
)
+ data = None
if parsed_args.url_type in ['novnc', 'xvpvnc']:
data = server.get_vnc_console(parsed_args.url_type)
- if parsed_args.url_type in ['spice']:
+ if parsed_args.url_type in ['spice-html5']:
data = server.get_spice_console(parsed_args.url_type)
if not data:
return ({}, {})
info = {}
- info.update(data['console'])
+ # NOTE(Rui Chen): Return 'remote_console' in compute microversion API
+ # 2.6 and later, return 'console' in compute
+ # microversion API from 2.0 to 2.5, do compatibility
+ # handle for different microversion API.
+ console_data = data.get('remote_console', data.get('console'))
+ info.update(console_data)
return zip(*sorted(six.iteritems(info)))
diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py
index ab2bc85d..01d7da75 100644
--- a/openstackclient/compute/v2/flavor.py
+++ b/openstackclient/compute/v2/flavor.py
@@ -15,6 +15,8 @@
"""Flavor action implementations"""
+import logging
+
from osc_lib.cli import parseractions
from osc_lib.command import command
from osc_lib import exceptions
@@ -25,6 +27,9 @@ from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
+LOG = logging.getLogger(__name__)
+
+
def _find_flavor(compute_client, flavor):
try:
return compute_client.flavors.get(flavor)
@@ -116,10 +121,22 @@ class CreateFlavor(command.ShowOne):
action="store_false",
help=_("Flavor is not available to other projects")
)
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ help=_("Allow <project> to access private flavor (name or ID) "
+ "(Must be used with --private option)"),
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
return parser
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
+ identity_client = self.app.client_manager.identity
+
+ if parsed_args.project and parsed_args.public:
+ msg = _("--project is only allowed with --private")
+ raise exceptions.CommandError(msg)
args = (
parsed_args.name,
@@ -136,25 +153,54 @@ class CreateFlavor(command.ShowOne):
flavor = compute_client.flavors.create(*args)._info.copy()
flavor.pop("links")
+ if parsed_args.project:
+ try:
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ compute_client.flavor_access.add_tenant_access(
+ parsed_args.id, project_id)
+ except Exception as e:
+ msg = _("Failed to add project %(project)s access to "
+ "flavor: %(e)s")
+ LOG.error(msg % {'project': parsed_args.project, 'e': e})
+
return zip(*sorted(six.iteritems(flavor)))
class DeleteFlavor(command.Command):
- """Delete flavor"""
+ """Delete flavor(s)"""
def get_parser(self, prog_name):
parser = super(DeleteFlavor, self).get_parser(prog_name)
parser.add_argument(
"flavor",
metavar="<flavor>",
- help=_("Flavor to delete (name or ID)")
+ nargs='+',
+ help=_("Flavor(s) to delete (name or ID)")
)
return parser
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
- flavor = _find_flavor(compute_client, parsed_args.flavor)
- compute_client.flavors.delete(flavor.id)
+ result = 0
+ for f in parsed_args.flavor:
+ try:
+ flavor = _find_flavor(compute_client, f)
+ compute_client.flavors.delete(flavor.id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete flavor with name or "
+ "ID '%(flavor)s': %(e)s")
+ % {'flavor': f, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.flavor)
+ msg = (_("%(result)s of %(total)s flavors failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListFlavor(command.Lister):
@@ -274,16 +320,12 @@ class SetFlavor(command.Command):
flavor = _find_flavor(compute_client, parsed_args.flavor)
- if not parsed_args.property and not parsed_args.project:
- raise exceptions.CommandError(_("Nothing specified to be set."))
-
result = 0
if parsed_args.property:
try:
flavor.set_keys(parsed_args.property)
except Exception as e:
- self.app.log.error(
- _("Failed to set flavor property: %s") % str(e))
+ LOG.error(_("Failed to set flavor property: %s"), e)
result += 1
if parsed_args.project:
@@ -300,13 +342,12 @@ class SetFlavor(command.Command):
compute_client.flavor_access.add_tenant_access(
flavor.id, project_id)
except Exception as e:
- self.app.log.error(_("Failed to set flavor access to"
- " project: %s") % str(e))
+ LOG.error(_("Failed to set flavor access to project: %s"), e)
result += 1
if result > 0:
raise exceptions.CommandError(_("Command Failed: One or more of"
- " the operations failed"))
+ " the operations failed"))
class ShowFlavor(command.ShowOne):
@@ -365,16 +406,12 @@ class UnsetFlavor(command.Command):
flavor = _find_flavor(compute_client, parsed_args.flavor)
- if not parsed_args.property and not parsed_args.project:
- raise exceptions.CommandError(_("Nothing specified to be unset."))
-
result = 0
if parsed_args.property:
try:
flavor.unset_keys(parsed_args.property)
except Exception as e:
- self.app.log.error(
- _("Failed to unset flavor property: %s") % str(e))
+ LOG.error(_("Failed to unset flavor property: %s"), e)
result += 1
if parsed_args.project:
@@ -391,10 +428,10 @@ class UnsetFlavor(command.Command):
compute_client.flavor_access.remove_tenant_access(
flavor.id, project_id)
except Exception as e:
- self.app.log.error(_("Failed to remove flavor access from"
- " project: %s") % str(e))
+ LOG.error(_("Failed to remove flavor access from project: %s"),
+ e)
result += 1
if result > 0:
raise exceptions.CommandError(_("Command Failed: One or more of"
- " the operations failed"))
+ " the operations failed"))
diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py
index 42736d66..7e4b0dc1 100644
--- a/openstackclient/compute/v2/server.py
+++ b/openstackclient/compute/v2/server.py
@@ -18,6 +18,7 @@
import argparse
import getpass
import io
+import logging
import os
import sys
@@ -36,6 +37,9 @@ from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
+LOG = logging.getLogger(__name__)
+
+
def _format_servers_list_networks(networks):
"""Return a formatted string of a server's networks
@@ -521,8 +525,8 @@ class CreateServer(command.ShowOne):
scheduler_hints=hints,
config_drive=config_drive)
- self.log.debug('boot_args: %s', boot_args)
- self.log.debug('boot_kwargs: %s', boot_kwargs)
+ LOG.debug('boot_args: %s', boot_args)
+ LOG.debug('boot_kwargs: %s', boot_kwargs)
# Wrap the call to catch exceptions in order to close files
try:
@@ -543,8 +547,8 @@ class CreateServer(command.ShowOne):
):
sys.stdout.write('\n')
else:
- self.log.error(_('Error creating server: %s'),
- parsed_args.server_name)
+ LOG.error(_('Error creating server: %s'),
+ parsed_args.server_name)
sys.stdout.write(_('Error creating server\n'))
raise SystemExit
@@ -612,8 +616,8 @@ class DeleteServer(command.Command):
):
sys.stdout.write('\n')
else:
- self.log.error(_('Error deleting server: %s'),
- server_obj.id)
+ LOG.error(_('Error deleting server: %s'),
+ server_obj.id)
sys.stdout.write(_('Error deleting server\n'))
raise SystemExit
@@ -762,7 +766,7 @@ class ListServer(command.Lister):
'all_tenants': parsed_args.all_projects,
'user_id': user_id,
}
- self.log.debug('search options: %s', search_opts)
+ LOG.debug('search options: %s', search_opts)
if parsed_args.long:
columns = (
@@ -939,8 +943,8 @@ class MigrateServer(command.Command):
):
sys.stdout.write(_('Complete\n'))
else:
- self.log.error(_('Error migrating server: %s'),
- server.id)
+ LOG.error(_('Error migrating server: %s'),
+ server.id)
sys.stdout.write(_('Error migrating server\n'))
raise SystemExit
@@ -1015,8 +1019,8 @@ class RebootServer(command.Command):
):
sys.stdout.write(_('Complete\n'))
else:
- self.log.error(_('Error rebooting server: %s'),
- server.id)
+ LOG.error(_('Error rebooting server: %s'),
+ server.id)
sys.stdout.write(_('Error rebooting server\n'))
raise SystemExit
@@ -1068,8 +1072,8 @@ class RebuildServer(command.ShowOne):
):
sys.stdout.write(_('Complete\n'))
else:
- self.log.error(_('Error rebuilding server: %s'),
- server.id)
+ LOG.error(_('Error rebuilding server: %s'),
+ server.id)
sys.stdout.write(_('Error rebuilding server\n'))
raise SystemExit
@@ -1222,8 +1226,8 @@ class ResizeServer(command.Command):
):
sys.stdout.write(_('Complete\n'))
else:
- self.log.error(_('Error resizing server: %s'),
- server.id)
+ LOG.error(_('Error resizing server: %s'),
+ server.id)
sys.stdout.write(_('Error resizing server\n'))
raise SystemExit
elif parsed_args.confirm:
@@ -1538,7 +1542,7 @@ class SshServer(command.Command):
ip_address = _get_ip_address(server.addresses,
parsed_args.address_type,
ip_address_family)
- self.log.debug("ssh command: %s", (cmd % (login, ip_address)))
+ LOG.debug("ssh command: %s", (cmd % (login, ip_address)))
os.system(cmd % (login, ip_address))
diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py
index 955b147e..d51b1ec2 100644
--- a/openstackclient/compute/v2/server_group.py
+++ b/openstackclient/compute/v2/server_group.py
@@ -15,6 +15,8 @@
"""Compute v2 Server Group action implementations"""
+import logging
+
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
@@ -22,6 +24,9 @@ from osc_lib import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
_formatters = {
'policies': utils.format_list,
'members': utils.format_list,
@@ -95,7 +100,7 @@ class DeleteServerGroup(command.Command):
# Catch all exceptions in order to avoid to block the next deleting
except Exception as e:
result += 1
- self.app.log.error(e)
+ LOG.error(e)
if result > 0:
total = len(parsed_args.server_group)
diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py
index 85ee7f2d..285c7fd2 100644
--- a/openstackclient/compute/v2/server_image.py
+++ b/openstackclient/compute/v2/server_image.py
@@ -15,17 +15,21 @@
"""Compute v2 Server action implementations"""
+import logging
import sys
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
from oslo_utils import importutils
import six
-from openstackclient.common import command
-from openstackclient.common import exceptions
-from openstackclient.common import utils
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
def _show_progress(progress):
if progress:
sys.stdout.write('\rProgress: %s' % progress)
@@ -90,10 +94,8 @@ class CreateServerImage(command.ShowOne):
):
sys.stdout.write('\n')
else:
- self.log.error(
- _('Error creating server image: %s') %
- parsed_args.server,
- )
+ LOG.error(_('Error creating server image: %s'),
+ parsed_args.server)
raise exceptions.CommandError
if self.app.client_manager._api_version['image'] == '1':
diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py
index d10af2ca..2e40dd7f 100644
--- a/openstackclient/compute/v2/service.py
+++ b/openstackclient/compute/v2/service.py
@@ -15,6 +15,8 @@
"""Service action implementations"""
+import logging
+
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
@@ -23,6 +25,9 @@ from openstackclient.i18n import _
from openstackclient.i18n import _LE
+LOG = logging.getLogger(__name__)
+
+
class DeleteService(command.Command):
"""Delete service command"""
@@ -68,7 +73,7 @@ class ListService(command.Lister):
compute_client = self.app.client_manager.compute
if parsed_args.long:
columns = (
- "Id",
+ "ID",
"Binary",
"Host",
"Zone",
@@ -79,7 +84,7 @@ class ListService(command.Lister):
)
else:
columns = (
- "Id",
+ "ID",
"Binary",
"Host",
"Zone",
@@ -171,7 +176,7 @@ class SetService(command.Command):
cs.disable(parsed_args.host, parsed_args.service)
except Exception:
status = "enabled" if enabled else "disabled"
- self.log.error(_LE("Failed to set service status to %s"), status)
+ LOG.error(_LE("Failed to set service status to %s"), status)
result += 1
force_down = None
@@ -185,7 +190,7 @@ class SetService(command.Command):
force_down=force_down)
except Exception:
state = "down" if force_down else "up"
- self.log.error(_LE("Failed to set service state to %s"), state)
+ LOG.error(_LE("Failed to set service state to %s"), state)
result += 1
if result > 0:
diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py
index 62e6b1f9..7a15cf3a 100644
--- a/openstackclient/identity/v2_0/catalog.py
+++ b/openstackclient/identity/v2_0/catalog.py
@@ -13,6 +13,8 @@
"""Identity v2 Service Catalog action implementations"""
+import logging
+
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
@@ -21,6 +23,9 @@ import six
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
def _format_endpoints(eps=None):
if not eps:
return ""
@@ -92,8 +97,7 @@ class ShowCatalog(command.ShowOne):
break
if not data:
- self.app.log.error(_('service %s not found\n') %
- parsed_args.service)
+ LOG.error(_('service %s not found\n'), parsed_args.service)
return ((), ())
return zip(*sorted(six.iteritems(data)))
diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py
index c4f730e0..6c5db13c 100644
--- a/openstackclient/identity/v2_0/project.py
+++ b/openstackclient/identity/v2_0/project.py
@@ -15,6 +15,8 @@
"""Identity v2 Project action implementations"""
+import logging
+
from keystoneauth1 import exceptions as ks_exc
from osc_lib.cli import parseractions
from osc_lib.command import command
@@ -24,6 +26,9 @@ import six
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class CreateProject(command.ShowOne):
"""Create new project"""
@@ -87,7 +92,7 @@ class CreateProject(command.ShowOne):
identity_client.tenants,
parsed_args.name,
)
- self.log.info(_('Returning existing project %s'), project.name)
+ LOG.info(_('Returning existing project %s'), project.name)
else:
raise e
diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py
index 4c3fe6e2..6d06230c 100644
--- a/openstackclient/identity/v2_0/role.py
+++ b/openstackclient/identity/v2_0/role.py
@@ -15,6 +15,8 @@
"""Identity v2 Role action implementations"""
+import logging
+
from keystoneauth1 import exceptions as ks_exc
from osc_lib.command import command
from osc_lib import exceptions
@@ -24,6 +26,9 @@ import six
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class AddRole(command.ShowOne):
"""Add role to project:user"""
@@ -94,7 +99,7 @@ class CreateRole(command.ShowOne):
identity_client.roles,
parsed_args.role_name,
)
- self.log.info(_('Returning existing role %s'), role.name)
+ LOG.info(_('Returning existing role %s'), role.name)
else:
raise e
diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py
index 947f361c..df332eb6 100644
--- a/openstackclient/identity/v2_0/service.py
+++ b/openstackclient/identity/v2_0/service.py
@@ -16,6 +16,7 @@
"""Service action implementations"""
import argparse
+import logging
from osc_lib.command import command
from osc_lib import exceptions
@@ -26,6 +27,9 @@ from openstackclient.i18n import _
from openstackclient.identity import common
+LOG = logging.getLogger(__name__)
+
+
class CreateService(command.ShowOne):
"""Create new service"""
@@ -69,8 +73,8 @@ class CreateService(command.ShowOne):
# display deprecation message.
elif type:
name = type_or_name
- self.log.warning(_('The argument --type is deprecated, use service'
- ' create --name <service-name> type instead.'))
+ LOG.warning(_('The argument --type is deprecated, use service'
+ ' create --name <service-name> type instead.'))
# If --name option is present the positional is handled as <type>.
# Making --type optional is new, but back-compatible
elif name:
@@ -94,7 +98,7 @@ class DeleteService(command.Command):
parser.add_argument(
'service',
metavar='<service>',
- help=_('Service to delete (name or ID)'),
+ help=_('Service to delete (type, name or ID)'),
)
return parser
diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py
index 3ee2a65e..0f327830 100644
--- a/openstackclient/identity/v2_0/user.py
+++ b/openstackclient/identity/v2_0/user.py
@@ -15,6 +15,8 @@
"""Identity v2.0 User action implementations"""
+import logging
+
from keystoneauth1 import exceptions as ks_exc
from osc_lib.command import command
from osc_lib import utils
@@ -23,6 +25,9 @@ import six
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class CreateUser(command.ShowOne):
"""Create new user"""
@@ -103,7 +108,7 @@ class CreateUser(command.ShowOne):
identity_client.users,
parsed_args.name,
)
- self.log.info(_('Returning existing user %s'), user.name)
+ LOG.info(_('Returning existing user %s'), user.name)
else:
raise e
@@ -240,7 +245,7 @@ class SetUser(command.Command):
parser.add_argument(
'user',
metavar='<user>',
- help=_('User to change (name or ID)'),
+ help=_('User to modify (name or ID)'),
)
parser.add_argument(
'--name',
diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py
index a6e141c9..a62d0a93 100644
--- a/openstackclient/identity/v3/catalog.py
+++ b/openstackclient/identity/v3/catalog.py
@@ -13,6 +13,8 @@
"""Identity v3 Service Catalog action implementations"""
+import logging
+
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
@@ -21,6 +23,9 @@ import six
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
def _format_endpoints(eps=None):
if not eps:
return ""
@@ -87,8 +92,7 @@ class ShowCatalog(command.ShowOne):
break
if not data:
- self.app.log.error(_('service %s not found\n') %
- parsed_args.service)
+ LOG.error(_('service %s not found\n'), parsed_args.service)
return ((), ())
return zip(*sorted(six.iteritems(data)))
diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py
index 8eb7bc91..001d5201 100644
--- a/openstackclient/identity/v3/domain.py
+++ b/openstackclient/identity/v3/domain.py
@@ -15,6 +15,7 @@
"""Identity v3 Domain action implementations"""
+import logging
import sys
from keystoneauth1 import exceptions as ks_exc
@@ -25,6 +26,9 @@ import six
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class CreateDomain(command.ShowOne):
"""Create new domain"""
@@ -75,7 +79,7 @@ class CreateDomain(command.ShowOne):
if parsed_args.or_show:
domain = utils.find_resource(identity_client.domains,
parsed_args.name)
- self.log.info(_('Returning existing domain %s'), domain.name)
+ LOG.info(_('Returning existing domain %s'), domain.name)
else:
raise e
diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py
index 377ddcce..09480245 100644
--- a/openstackclient/identity/v3/federation_protocol.py
+++ b/openstackclient/identity/v3/federation_protocol.py
@@ -14,6 +14,8 @@
"""Identity v3 Protocols actions implementations"""
+import logging
+
from osc_lib.command import command
from osc_lib import utils
import six
@@ -21,6 +23,9 @@ import six
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class CreateProtocol(command.ShowOne):
"""Create new federation protocol"""
@@ -145,7 +150,7 @@ class SetProtocol(command.Command):
identity_client = self.app.client_manager.identity
if not parsed_args.mapping:
- self.app.log.error(_("No changes requested"))
+ LOG.error(_("No changes requested"))
return
protocol = identity_client.federation.protocols.update(
diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py
index fb0a25b9..8351fe64 100644
--- a/openstackclient/identity/v3/group.py
+++ b/openstackclient/identity/v3/group.py
@@ -15,6 +15,7 @@
"""Group action implementations"""
+import logging
import sys
from keystoneauth1 import exceptions as ks_exc
@@ -26,6 +27,9 @@ from openstackclient.i18n import _
from openstackclient.identity import common
+LOG = logging.getLogger(__name__)
+
+
class AddUserToGroup(command.Command):
"""Add user to group"""
@@ -161,7 +165,7 @@ class CreateGroup(command.ShowOne):
group = utils.find_resource(identity_client.groups,
parsed_args.name,
domain_id=domain)
- self.log.info(_('Returning existing group %s'), group.name)
+ LOG.info(_('Returning existing group %s'), group.name)
else:
raise e
diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py
index 0b530a26..5c638f9b 100644
--- a/openstackclient/identity/v3/identity_provider.py
+++ b/openstackclient/identity/v3/identity_provider.py
@@ -13,6 +13,8 @@
"""Identity v3 IdentityProvider action implementations"""
+import logging
+
from osc_lib.command import command
from osc_lib import utils
import six
@@ -20,6 +22,9 @@ import six
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class CreateIdentityProvider(command.ShowOne):
"""Create new identity provider"""
@@ -169,7 +174,7 @@ class SetIdentityProvider(command.Command):
not parsed_args.remote_id and
not parsed_args.remote_id_file and
not parsed_args.description):
- self.log.error(_('No changes requested'))
+ LOG.error(_('No changes requested'))
return (None, None)
# Always set remote_ids if either is passed in
diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py
index 5937474e..74ead228 100644
--- a/openstackclient/identity/v3/mapping.py
+++ b/openstackclient/identity/v3/mapping.py
@@ -16,6 +16,7 @@
"""Identity v3 federation mapping action implementations"""
import json
+import logging
from osc_lib.command import command
from osc_lib import exceptions
@@ -25,6 +26,9 @@ import six
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
class _RulesReader(object):
"""Helper class capable of reading rules from files"""
@@ -159,7 +163,7 @@ class SetMapping(command.Command, _RulesReader):
identity_client = self.app.client_manager.identity
if not parsed_args.rules:
- self.app.log.error(_("No changes requested"))
+ LOG.error(_("No changes requested"))
return
rules = self._read_rules(parsed_args.rules)
diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py
index 6145dcf1..4db5bef1 100644
--- a/openstackclient/identity/v3/project.py
+++ b/openstackclient/identity/v3/project.py
@@ -15,6 +15,8 @@
"""Project action implementations"""
+import logging
+
from keystoneauth1 import exceptions as ks_exc
from osc_lib.cli import parseractions
from osc_lib.command import command
@@ -25,6 +27,9 @@ from openstackclient.i18n import _
from openstackclient.identity import common
+LOG = logging.getLogger(__name__)
+
+
class CreateProject(command.ShowOne):
"""Create new project"""
@@ -111,7 +116,7 @@ class CreateProject(command.ShowOne):
project = utils.find_resource(identity_client.projects,
parsed_args.name,
domain_id=domain)
- self.log.info(_('Returning existing project %s'), project.name)
+ LOG.info(_('Returning existing project %s'), project.name)
else:
raise e
diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py
index 1bb27585..965ca3f5 100644
--- a/openstackclient/identity/v3/role.py
+++ b/openstackclient/identity/v3/role.py
@@ -15,6 +15,7 @@
"""Identity v3 Role action implementations"""
+import logging
import sys
from keystoneauth1 import exceptions as ks_exc
@@ -26,6 +27,9 @@ from openstackclient.i18n import _
from openstackclient.identity import common
+LOG = logging.getLogger(__name__)
+
+
def _add_identity_and_resource_options_to_parser(parser):
domain_or_project = parser.add_mutually_exclusive_group()
domain_or_project.add_argument(
@@ -165,7 +169,7 @@ class CreateRole(command.ShowOne):
if parsed_args.or_show:
role = utils.find_resource(identity_client.roles,
parsed_args.name)
- self.log.info(_('Returning existing role %s'), role.name)
+ LOG.info(_('Returning existing role %s'), role.name)
else:
raise e
diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py
index 7beadc9d..195b2701 100644
--- a/openstackclient/identity/v3/service.py
+++ b/openstackclient/identity/v3/service.py
@@ -130,7 +130,7 @@ class SetService(command.Command):
parser.add_argument(
'service',
metavar='<service>',
- help=_('Service to update (type, name or ID)'),
+ help=_('Service to modify (type, name or ID)'),
)
parser.add_argument(
'--type',
diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py
index 39125e2c..b0c80c14 100644
--- a/openstackclient/identity/v3/user.py
+++ b/openstackclient/identity/v3/user.py
@@ -16,6 +16,7 @@
"""Identity v3 User action implementations"""
import copy
+import logging
import sys
from keystoneauth1 import exceptions as ks_exc
@@ -27,6 +28,9 @@ from openstackclient.i18n import _
from openstackclient.identity import common
+LOG = logging.getLogger(__name__)
+
+
class CreateUser(command.ShowOne):
"""Create new user"""
@@ -122,7 +126,7 @@ class CreateUser(command.ShowOne):
user = utils.find_resource(identity_client.users,
parsed_args.name,
domain_id=domain_id)
- self.log.info(_('Returning existing user %s'), user.name)
+ LOG.info(_('Returning existing user %s'), user.name)
else:
raise e
@@ -273,7 +277,7 @@ class SetUser(command.Command):
parser.add_argument(
'user',
metavar='<user>',
- help=_('User to change (name or ID)'),
+ help=_('User to modify (name or ID)'),
)
parser.add_argument(
'--name',
diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py
index 1644809d..e7b60e5a 100644
--- a/openstackclient/image/v1/image.py
+++ b/openstackclient/image/v1/image.py
@@ -17,6 +17,7 @@
import argparse
import io
+import logging
import os
import sys
@@ -39,6 +40,9 @@ DEFAULT_CONTAINER_FORMAT = 'bare'
DEFAULT_DISK_FORMAT = 'raw'
+LOG = logging.getLogger(__name__)
+
+
def _format_visibility(data):
"""Return a formatted visibility string
@@ -189,10 +193,8 @@ class CreateImage(command.ShowOne):
image_client = self.app.client_manager.image
if getattr(parsed_args, 'owner', None) is not None:
- self.log.warning(_(
- 'The --owner option is deprecated, '
- 'please use --project instead.'
- ))
+ LOG.warning(_('The --owner option is deprecated, '
+ 'please use --project instead.'))
# Build an attribute dict from the parsed args, only include
# attributes that were actually set on the command line
@@ -608,10 +610,8 @@ class SetImage(command.Command):
image_client = self.app.client_manager.image
if getattr(parsed_args, 'owner', None) is not None:
- self.log.warning(_(
- 'The --owner option is deprecated, '
- 'please use --project instead.'
- ))
+ LOG.warning(_('The --owner option is deprecated, '
+ 'please use --project instead.'))
kwargs = {}
copy_attrs = ('name', 'owner', 'min_disk', 'min_ram', 'properties',
@@ -684,18 +684,13 @@ class SetImage(command.Command):
# will do a chunked transfer
kwargs["data"] = sys.stdin
else:
- self.log.warning(_('Use --stdin to enable read '
- 'image data from standard '
- 'input'))
+ LOG.warning(_('Use --stdin to enable read image '
+ 'data from standard input'))
if image.properties and parsed_args.properties:
image.properties.update(kwargs['properties'])
kwargs['properties'] = image.properties
- if not kwargs:
- self.log.warning('no arguments specified')
- return
-
image = image_client.images.update(image.id, **kwargs)
finally:
# Clean up open files - make sure data isn't a string
diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py
index 53f9530b..309b1b6b 100644
--- a/openstackclient/image/v2/image.py
+++ b/openstackclient/image/v2/image.py
@@ -16,6 +16,7 @@
"""Image V2 Action Implementations"""
import argparse
+import logging
from glanceclient.common import utils as gc_utils
from osc_lib.cli import parseractions
@@ -33,6 +34,9 @@ DEFAULT_CONTAINER_FORMAT = 'bare'
DEFAULT_DISK_FORMAT = 'raw'
+LOG = logging.getLogger(__name__)
+
+
def _format_image(image):
"""Format an image to make it more consistent with OSC operations. """
@@ -280,10 +284,8 @@ class CreateImage(command.ShowOne):
project_arg = parsed_args.project
if parsed_args.owner:
project_arg = parsed_args.owner
- self.log.warning(_(
- 'The --owner option is deprecated, '
- 'please use --project instead.'
- ))
+ LOG.warning(_('The --owner option is deprecated, '
+ 'please use --project instead.'))
if project_arg:
kwargs['owner'] = common.find_project(
identity_client,
@@ -301,7 +303,7 @@ class CreateImage(command.ShowOne):
"the same time"))
if fp is None and parsed_args.file:
- self.log.warning(_("Failed to get an image file."))
+ LOG.warning(_("Failed to get an image file."))
return {}, {}
if parsed_args.owner:
@@ -384,9 +386,9 @@ class DeleteImage(command.Command):
image_client.images.delete(image_obj.id)
except Exception as e:
del_result += 1
- self.app.log.error(_("Failed to delete image with "
- "name or ID '%(image)s': %(e)s")
- % {'image': image, 'e': e})
+ LOG.error(_("Failed to delete image with name or "
+ "ID '%(image)s': %(e)s"),
+ {'image': image, 'e': e})
total = len(parsed_args.images)
if (del_result > 0):
@@ -806,10 +808,8 @@ class SetImage(command.Command):
project_arg = parsed_args.project
if parsed_args.owner:
project_arg = parsed_args.owner
- self.log.warning(_(
- 'The --owner option is deprecated, '
- 'please use --project instead.'
- ))
+ LOG.warning(_('The --owner option is deprecated, '
+ 'please use --project instead.'))
if project_arg:
kwargs['owner'] = common.find_project(
identity_client,
@@ -836,7 +836,8 @@ class SetImage(command.Command):
image = image_client.images.update(image.id, **kwargs)
except Exception as e:
if activation_status is not None:
- print("Image %s was %s." % (image.id, activation_status))
+ LOG.info(_("Image %(id)s was %(status)s."),
+ {'id': image.id, 'status': activation_status})
raise e
@@ -908,8 +909,8 @@ class UnsetImage(command.Command):
try:
image_client.image_tags.delete(image.id, k)
except Exception:
- self.log.error(_("tag unset failed,"
- " '%s' is a nonexistent tag ") % k)
+ LOG.error(_("tag unset failed, '%s' is a "
+ "nonexistent tag "), k)
tagret += 1
if parsed_args.properties:
@@ -917,8 +918,8 @@ class UnsetImage(command.Command):
try:
assert(k in image.keys())
except AssertionError:
- self.log.error(_("property unset failed,"
- " '%s' is a nonexistent property ") % k)
+ LOG.error(_("property unset failed, '%s' is a "
+ "nonexistent property "), k)
propret += 1
image_client.images.update(
image.id,
diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py
index 16cc7379..f62840fc 100644
--- a/openstackclient/network/common.py
+++ b/openstackclient/network/common.py
@@ -12,6 +12,7 @@
#
import abc
+import logging
from osc_lib.command import command
from osc_lib import exceptions
@@ -20,6 +21,9 @@ import six
from openstackclient.i18n import _
+LOG = logging.getLogger(__name__)
+
+
@six.add_metaclass(abc.ABCMeta)
class NetworkAndComputeCommand(command.Command):
"""Network and Compute Command
@@ -39,10 +43,10 @@ class NetworkAndComputeCommand(command.Command):
parsed_args)
def get_parser(self, prog_name):
- self.log.debug('get_parser(%s)', prog_name)
+ LOG.debug('get_parser(%s)', prog_name)
parser = super(NetworkAndComputeCommand, self).get_parser(prog_name)
parser = self.update_parser_common(parser)
- self.log.debug('common parser: %s', parser)
+ LOG.debug('common parser: %s', parser)
if self.app.client_manager.is_network_endpoint_enabled():
return self.update_parser_network(parser)
else:
@@ -102,7 +106,7 @@ class NetworkAndComputeDelete(NetworkAndComputeCommand):
"name_or_id": r,
"e": e,
}
- self.app.log.error(msg)
+ LOG.error(msg)
ret += 1
if ret:
@@ -134,10 +138,10 @@ class NetworkAndComputeLister(command.Lister):
parsed_args)
def get_parser(self, prog_name):
- self.log.debug('get_parser(%s)', prog_name)
+ LOG.debug('get_parser(%s)', prog_name)
parser = super(NetworkAndComputeLister, self).get_parser(prog_name)
parser = self.update_parser_common(parser)
- self.log.debug('common parser: %s', parser)
+ LOG.debug('common parser: %s', parser)
if self.app.client_manager.is_network_endpoint_enabled():
return self.update_parser_network(parser)
else:
@@ -185,10 +189,10 @@ class NetworkAndComputeShowOne(command.ShowOne):
parsed_args)
def get_parser(self, prog_name):
- self.log.debug('get_parser(%s)', prog_name)
+ LOG.debug('get_parser(%s)', prog_name)
parser = super(NetworkAndComputeShowOne, self).get_parser(prog_name)
parser = self.update_parser_common(parser)
- self.log.debug('common parser: %s', parser)
+ LOG.debug('common parser: %s', parser)
if self.app.client_manager.is_network_endpoint_enabled():
return self.update_parser_network(parser)
else:
diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py
index bc1a96c3..6cd13f8c 100644
--- a/openstackclient/network/v2/address_scope.py
+++ b/openstackclient/network/v2/address_scope.py
@@ -13,6 +13,8 @@
"""Address scope action implementations"""
+import logging
+
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
@@ -21,6 +23,9 @@ from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
+LOG = logging.getLogger(__name__)
+
+
def _get_columns(item):
columns = list(item.keys())
if 'tenant_id' in columns:
@@ -122,9 +127,9 @@ class DeleteAddressScope(command.Command):
client.delete_address_scope(obj)
except Exception as e:
result += 1
- self.app.log.error(_("Failed to delete address scope with "
- "name or ID '%(scope)s': %(e)s")
- % {'scope': scope, 'e': e})
+ LOG.error(_("Failed to delete address scope with "
+ "name or ID '%(scope)s': %(e)s"),
+ {'scope': scope, 'e': e})
if result > 0:
total = len(parsed_args.address_scope)
diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py
index 1c5db706..57461f89 100644
--- a/openstackclient/network/v2/port.py
+++ b/openstackclient/network/v2/port.py
@@ -293,9 +293,9 @@ class DeletePort(command.Command):
client.delete_port(obj)
except Exception as e:
result += 1
- self.app.log.error(_("Failed to delete port with "
- "name or ID '%(port)s': %(e)s")
- % {'port': port, 'e': e})
+ LOG.error(_("Failed to delete port with "
+ "name or ID '%(port)s': %(e)s"),
+ {'port': port, 'e': e})
if result > 0:
total = len(parsed_args.port)
diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py
index 92ecf84d..6271a878 100644
--- a/openstackclient/network/v2/router.py
+++ b/openstackclient/network/v2/router.py
@@ -19,6 +19,7 @@ import logging
from osc_lib.cli import parseractions
from osc_lib.command import command
+from osc_lib import exceptions
from osc_lib import utils
from openstackclient.i18n import _
@@ -222,9 +223,23 @@ class DeleteRouter(command.Command):
def take_action(self, parsed_args):
client = self.app.client_manager.network
+ result = 0
+
for router in parsed_args.router:
- obj = client.find_router(router)
- client.delete_router(obj)
+ try:
+ obj = client.find_router(router, ignore_missing=False)
+ client.delete_router(obj)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete router with "
+ "name or ID '%(router)s': %(e)s")
+ % {'router': router, 'e': e})
+
+ if result > 0:
+ total = len(parsed_args.router)
+ msg = (_("%(result)s of %(total)s routers failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
class ListRouter(command.Lister):
diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py
index 18863223..e3be44ec 100644
--- a/openstackclient/network/v2/security_group_rule.py
+++ b/openstackclient/network/v2/security_group_rule.py
@@ -526,8 +526,8 @@ class ShowSecurityGroupRule(common.NetworkAndComputeShowOne):
break
if obj is None:
- msg = _("Could not find security group rule with ID ") + \
- parsed_args.rule
+ msg = _("Could not find security group rule "
+ "with ID '%s'") % parsed_args.rule
raise exceptions.CommandError(msg)
# NOTE(rtheis): Format security group rule
diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py
index a2e32622..752923f7 100644
--- a/openstackclient/network/v2/subnet.py
+++ b/openstackclient/network/v2/subnet.py
@@ -142,6 +142,9 @@ def _get_attrs(client_manager, parsed_args, is_create=True):
attrs['ipv6_ra_mode'] = parsed_args.ipv6_ra_mode
if parsed_args.ipv6_address_mode is not None:
attrs['ipv6_address_mode'] = parsed_args.ipv6_address_mode
+ if 'network_segment' in parsed_args:
+ attrs['segment_id'] = client.find_segment(
+ parsed_args.network_segment, ignore_missing=False).id
if 'gateway' in parsed_args and parsed_args.gateway is not None:
gateway = parsed_args.gateway.lower()
@@ -255,6 +258,13 @@ class CreateSubnet(command.ShowOne):
help=_("IPv6 address mode, "
"valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]")
)
+ if self.app.options.os_beta_command:
+ parser.add_argument(
+ '--network-segment',
+ metavar='<network-segment>',
+ help=_("Network segment to associate with this subnet "
+ "(ID only)")
+ )
parser.add_argument(
'--network',
required=True,
diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py
index 97d0c96f..b7f17fbc 100644
--- a/openstackclient/tests/compute/v2/fakes.py
+++ b/openstackclient/tests/compute/v2/fakes.py
@@ -87,6 +87,42 @@ class FakeAggregate(object):
loaded=True)
return aggregate
+ @staticmethod
+ def create_aggregates(attrs=None, count=2):
+ """Create multiple fake aggregates.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of aggregates to fake
+ :return:
+ A list of FakeResource objects faking the aggregates
+ """
+ aggregates = []
+ for i in range(0, count):
+ aggregates.append(FakeAggregate.create_one_aggregate(attrs))
+
+ return aggregates
+
+ @staticmethod
+ def get_aggregates(aggregates=None, count=2):
+ """Get an iterable MagicMock object with a list of faked aggregates.
+
+ If aggregates list is provided, then initialize the Mock object
+ with the list. Otherwise create one.
+
+ :param List aggregates:
+ A list of FakeResource objects faking aggregates
+ :param int count:
+ The number of aggregates to fake
+ :return:
+ An iterable Mock object with side_effect set to a list of faked
+ aggregates
+ """
+ if aggregates is None:
+ aggregates = FakeAggregate.create_aggregates(count)
+ return mock.MagicMock(side_effect=aggregates)
+
class FakeComputev2Client(object):
@@ -612,15 +648,19 @@ class FakeService(object):
:param Dictionary attrs:
A dictionary with all attributes
:return:
- A FakeResource object, with id, name, ram, vcpus, properties
+ A FakeResource object, with id, host, binary
"""
attrs = attrs or {}
# Set default attributes.
service_info = {
+ 'id': 'id-' + uuid.uuid4().hex,
'host': 'host-' + uuid.uuid4().hex,
'binary': 'binary-' + uuid.uuid4().hex,
'status': 'enabled',
+ 'zone': 'zone-' + uuid.uuid4().hex,
+ 'state': 'state-' + uuid.uuid4().hex,
+ 'updated_at': 'time-' + uuid.uuid4().hex,
'disabled_reason': 'earthquake',
}
@@ -732,7 +772,7 @@ class FakeFlavor(object):
flavors
"""
if flavors is None:
- flavors = FakeServer.create_flavors(count)
+ flavors = FakeFlavor.create_flavors(count)
return mock.MagicMock(side_effect=flavors)
diff --git a/openstackclient/tests/compute/v2/test_aggregate.py b/openstackclient/tests/compute/v2/test_aggregate.py
index 58dd7755..3ebca35f 100644
--- a/openstackclient/tests/compute/v2/test_aggregate.py
+++ b/openstackclient/tests/compute/v2/test_aggregate.py
@@ -13,6 +13,12 @@
# under the License.
#
+import mock
+from mock import call
+
+from osc_lib import exceptions
+from osc_lib import utils
+
from openstackclient.compute.v2 import aggregate
from openstackclient.tests.compute.v2 import fakes as compute_fakes
from openstackclient.tests import utils as tests_utils
@@ -135,25 +141,74 @@ class TestAggregateCreate(TestAggregate):
class TestAggregateDelete(TestAggregate):
+ fake_ags = compute_fakes.FakeAggregate.create_aggregates(count=2)
+
def setUp(self):
super(TestAggregateDelete, self).setUp()
- self.aggregate_mock.get.return_value = self.fake_ag
+ self.aggregate_mock.get = (
+ compute_fakes.FakeAggregate.get_aggregates(self.fake_ags))
self.cmd = aggregate.DeleteAggregate(self.app, None)
def test_aggregate_delete(self):
arglist = [
- 'ag1',
+ self.fake_ags[0].id
]
verifylist = [
- ('aggregate', 'ag1'),
+ ('aggregate', [self.fake_ags[0].id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate)
- self.aggregate_mock.delete.assert_called_once_with(self.fake_ag.id)
+ self.aggregate_mock.get.assert_called_once_with(self.fake_ags[0].id)
+ self.aggregate_mock.delete.assert_called_once_with(self.fake_ags[0].id)
self.assertIsNone(result)
+ def test_delete_multiple_aggregates(self):
+ arglist = []
+ for a in self.fake_ags:
+ arglist.append(a.id)
+ verifylist = [
+ ('aggregate', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for a in self.fake_ags:
+ calls.append(call(a.id))
+ self.aggregate_mock.delete.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_delete_multiple_agggregates_with_exception(self):
+ arglist = [
+ self.fake_ags[0].id,
+ 'unexist_aggregate',
+ ]
+ verifylist = [
+ ('aggregate', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [self.fake_ags[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 aggregates failed to delete.',
+ str(e))
+
+ find_mock.assert_any_call(self.aggregate_mock, self.fake_ags[0].id)
+ find_mock.assert_any_call(self.aggregate_mock, 'unexist_aggregate')
+
+ self.assertEqual(2, find_mock.call_count)
+ self.aggregate_mock.delete.assert_called_once_with(
+ self.fake_ags[0].id
+ )
+
class TestAggregateList(TestAggregate):
diff --git a/openstackclient/tests/compute/v2/test_console.py b/openstackclient/tests/compute/v2/test_console.py
new file mode 100644
index 00000000..6be08126
--- /dev/null
+++ b/openstackclient/tests/compute/v2/test_console.py
@@ -0,0 +1,149 @@
+# Copyright 2016 Huawei, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+import mock
+
+from openstackclient.compute.v2 import console
+from openstackclient.tests.compute.v2 import fakes as compute_fakes
+
+
+class TestConsole(compute_fakes.TestComputev2):
+
+ def setUp(self):
+ super(TestConsole, self).setUp()
+ self.servers_mock = self.app.client_manager.compute.servers
+ self.servers_mock.reset_mock()
+
+
+class TestConsoleUrlShow(TestConsole):
+
+ def setUp(self):
+ super(TestConsoleUrlShow, self).setUp()
+ fake_console_data = {'remote_console': {'url': 'http://localhost',
+ 'protocol': 'fake_protocol',
+ 'type': 'fake_type'}}
+ methods = {
+ 'get_vnc_console': fake_console_data,
+ 'get_spice_console': fake_console_data,
+ 'get_serial_console': fake_console_data,
+ 'get_rdp_console': fake_console_data,
+ 'get_mks_console': fake_console_data,
+ }
+ self.fake_server = compute_fakes.FakeServer.create_one_server(
+ methods=methods)
+ self.servers_mock.get.return_value = self.fake_server
+
+ self.columns = (
+ 'protocol',
+ 'type',
+ 'url',
+ )
+ self.data = (
+ fake_console_data['remote_console']['protocol'],
+ fake_console_data['remote_console']['type'],
+ fake_console_data['remote_console']['url']
+ )
+
+ self.cmd = console.ShowConsoleURL(self.app, None)
+
+ def test_console_url_show_by_default(self):
+ arglist = [
+ 'foo_vm',
+ ]
+ verifylist = [
+ ('url_type', 'novnc'),
+ ('server', 'foo_vm'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+ self.fake_server.get_vnc_console.assert_called_once_with('novnc')
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_console_url_show_with_novnc(self):
+ arglist = [
+ '--novnc',
+ 'foo_vm',
+ ]
+ verifylist = [
+ ('url_type', 'novnc'),
+ ('server', 'foo_vm'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+ self.fake_server.get_vnc_console.assert_called_once_with('novnc')
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_console_url_show_with_xvpvnc(self):
+ arglist = [
+ '--xvpvnc',
+ 'foo_vm',
+ ]
+ verifylist = [
+ ('url_type', 'xvpvnc'),
+ ('server', 'foo_vm'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+ self.fake_server.get_vnc_console.assert_called_once_with('xvpvnc')
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_console_url_show_with_spice(self):
+ arglist = [
+ '--spice',
+ 'foo_vm',
+ ]
+ verifylist = [
+ ('url_type', 'spice-html5'),
+ ('server', 'foo_vm'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+ self.fake_server.get_spice_console.assert_called_once_with(
+ 'spice-html5')
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_console_url_show_compatible(self):
+ methods = {
+ 'get_vnc_console': {'console': {'url': 'http://localhost',
+ 'type': 'fake_type'}},
+ }
+ old_fake_server = compute_fakes.FakeServer.create_one_server(
+ methods=methods)
+ old_columns = (
+ 'type',
+ 'url',
+ )
+ old_data = (
+ methods['get_vnc_console']['console']['type'],
+ methods['get_vnc_console']['console']['url']
+ )
+ arglist = [
+ 'foo_vm',
+ ]
+ verifylist = [
+ ('url_type', 'novnc'),
+ ('server', 'foo_vm'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ with mock.patch.object(self.servers_mock, 'get',
+ return_value=old_fake_server):
+ columns, data = self.cmd.take_action(parsed_args)
+ old_fake_server.get_vnc_console.assert_called_once_with('novnc')
+ self.assertEqual(old_columns, columns)
+ self.assertEqual(old_data, data)
diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py
index 4365a540..da76b6d7 100644
--- a/openstackclient/tests/compute/v2/test_flavor.py
+++ b/openstackclient/tests/compute/v2/test_flavor.py
@@ -14,6 +14,8 @@
#
import copy
+import mock
+from mock import call
from osc_lib import exceptions
from osc_lib import utils
@@ -75,6 +77,12 @@ class TestFlavorCreate(TestFlavor):
def setUp(self):
super(TestFlavorCreate, self).setUp()
+ # Return a project
+ self.projects_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.PROJECT),
+ loaded=True,
+ )
self.flavors_mock.create.return_value = self.flavor
self.cmd = flavor.CreateFlavor(self.app, None)
@@ -161,6 +169,7 @@ class TestFlavorCreate(TestFlavor):
'--vcpus', str(self.flavor.vcpus),
'--rxtx-factor', str(self.flavor.rxtx_factor),
'--private',
+ '--project', identity_fakes.project_id,
]
verifylist = [
('name', self.flavor.name),
@@ -172,6 +181,7 @@ class TestFlavorCreate(TestFlavor):
('vcpus', self.flavor.vcpus),
('rxtx_factor', self.flavor.rxtx_factor),
('public', False),
+ ('project', identity_fakes.project_id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -188,10 +198,28 @@ class TestFlavorCreate(TestFlavor):
)
columns, data = self.cmd.take_action(parsed_args)
self.flavors_mock.create.assert_called_once_with(*args)
-
+ self.flavor_access_mock.add_tenant_access.assert_called_with(
+ self.flavor.id,
+ identity_fakes.project_id,
+ )
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+ def test_public_flavor_create_with_project(self):
+ arglist = [
+ '--project', identity_fakes.project_id,
+ self.flavor.name,
+ ]
+ verifylist = [
+ ('project', identity_fakes.project_id),
+ ('name', self.flavor.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+
def test_flavor_create_no_options(self):
arglist = []
verifylist = None
@@ -204,47 +232,73 @@ class TestFlavorCreate(TestFlavor):
class TestFlavorDelete(TestFlavor):
- flavor = compute_fakes.FakeFlavor.create_one_flavor()
+ flavors = compute_fakes.FakeFlavor.create_flavors(count=2)
def setUp(self):
super(TestFlavorDelete, self).setUp()
- self.flavors_mock.get.return_value = self.flavor
+ self.flavors_mock.get = (
+ compute_fakes.FakeFlavor.get_flavors(self.flavors))
self.flavors_mock.delete.return_value = None
self.cmd = flavor.DeleteFlavor(self.app, None)
def test_flavor_delete(self):
arglist = [
- self.flavor.id
+ self.flavors[0].id
]
verifylist = [
- ('flavor', self.flavor.id),
+ ('flavor', [self.flavors[0].id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.flavors_mock.delete.assert_called_with(self.flavor.id)
+ self.flavors_mock.delete.assert_called_with(self.flavors[0].id)
self.assertIsNone(result)
- def test_flavor_delete_with_unexist_flavor(self):
- self.flavors_mock.get.side_effect = exceptions.NotFound(None)
- self.flavors_mock.find.side_effect = exceptions.NotFound(None)
+ def test_delete_multiple_flavors(self):
+ arglist = []
+ for f in self.flavors:
+ arglist.append(f.id)
+ verifylist = [
+ ('flavor', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for f in self.flavors:
+ calls.append(call(f.id))
+ self.flavors_mock.delete.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_multi_flavors_delete_with_exception(self):
arglist = [
- 'unexist_flavor'
+ self.flavors[0].id,
+ 'unexist_flavor',
]
verifylist = [
- ('flavor', 'unexist_flavor'),
+ ('flavor', [self.flavors[0].id, 'unexist_flavor'])
]
-
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- self.assertRaises(
- exceptions.CommandError,
- self.cmd.take_action,
- parsed_args)
+ find_mock_result = [self.flavors[0], exceptions.CommandError]
+ self.flavors_mock.get = (
+ mock.MagicMock(side_effect=find_mock_result)
+ )
+ self.flavors_mock.find.side_effect = exceptions.NotFound(None)
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 flavors failed to delete.', str(e))
+
+ self.flavors_mock.get.assert_any_call(self.flavors[0].id)
+ self.flavors_mock.get.assert_any_call('unexist_flavor')
+ self.flavors_mock.delete.assert_called_once_with(self.flavors[0].id)
class TestFlavorList(TestFlavor):
@@ -542,8 +596,12 @@ class TestFlavorSet(TestFlavor):
('flavor', self.flavor.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- self.assertRaises(exceptions.CommandError, self.cmd.take_action,
- parsed_args)
+ result = self.cmd.take_action(parsed_args)
+
+ self.flavors_mock.find.assert_called_with(name=parsed_args.flavor,
+ is_public=None)
+ self.flavor_access_mock.add_tenant_access.assert_not_called()
+ self.assertIsNone(result)
class TestFlavorShow(TestFlavor):
@@ -717,5 +775,8 @@ class TestFlavorUnset(TestFlavor):
('flavor', self.flavor.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- self.assertRaises(exceptions.CommandError, self.cmd.take_action,
- parsed_args)
+
+ result = self.cmd.take_action(parsed_args)
+ self.assertIsNone(result)
+
+ self.flavor_access_mock.remove_tenant_access.assert_not_called()
diff --git a/openstackclient/tests/compute/v2/test_server_image.py b/openstackclient/tests/compute/v2/test_server_image.py
index 660e9817..8a8bd9bc 100644
--- a/openstackclient/tests/compute/v2/test_server_image.py
+++ b/openstackclient/tests/compute/v2/test_server_image.py
@@ -12,8 +12,9 @@
#
import mock
-from openstackclient.common import exceptions
-from openstackclient.common import utils as common_utils
+from osc_lib import exceptions
+from osc_lib import utils as common_utils
+
from openstackclient.compute.v2 import server_image
from openstackclient.tests.compute.v2 import fakes as compute_fakes
from openstackclient.tests.image.v2 import fakes as image_fakes
diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py
index b360c9dc..e41d633a 100644
--- a/openstackclient/tests/compute/v2/test_service.py
+++ b/openstackclient/tests/compute/v2/test_service.py
@@ -62,11 +62,35 @@ class TestServiceDelete(TestService):
class TestServiceList(TestService):
+ service = compute_fakes.FakeService.create_one_service()
+
+ columns = (
+ 'ID',
+ 'Binary',
+ 'Host',
+ 'Zone',
+ 'Status',
+ 'State',
+ 'Updated At',
+ )
+ columns_long = columns + (
+ 'Disabled Reason',
+ )
+
+ data = [(
+ service.id,
+ service.binary,
+ service.host,
+ service.zone,
+ service.status,
+ service.state,
+ service.updated_at,
+ )]
+ data_long = [data[0] + (service.disabled_reason, )]
+
def setUp(self):
super(TestServiceList, self).setUp()
- self.service = compute_fakes.FakeService.create_one_service()
-
self.service_mock.list.return_value = [self.service]
# Get the command object to test
@@ -93,8 +117,8 @@ class TestServiceList(TestService):
self.service.binary,
)
- self.assertNotIn("Disabled Reason", columns)
- self.assertNotIn(self.service.disabled_reason, list(data)[0])
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
def test_service_list_with_long_option(self):
arglist = [
@@ -114,8 +138,13 @@ class TestServiceList(TestService):
# containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args)
- self.assertIn("Disabled Reason", columns)
- self.assertIn(self.service.disabled_reason, list(data)[0])
+ self.service_mock.list.assert_called_with(
+ self.service.host,
+ self.service.binary,
+ )
+
+ self.assertEqual(self.columns_long, columns)
+ self.assertEqual(self.data_long, list(data))
class TestServiceSet(TestService):
@@ -330,12 +359,9 @@ class TestServiceSet(TestService):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- with mock.patch.object(self.cmd.log, 'error') as mock_log:
- with mock.patch.object(self.service_mock, 'enable',
- side_effect=Exception()):
- self.assertRaises(exceptions.CommandError,
- self.cmd.take_action, parsed_args)
- mock_log.assert_called_once_with(
- "Failed to set service status to %s", "enabled")
- self.service_mock.force_down.assert_called_once_with(
- self.service.host, self.service.binary, force_down=True)
+ with mock.patch.object(self.service_mock, 'enable',
+ side_effect=Exception()):
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action, parsed_args)
+ self.service_mock.force_down.assert_called_once_with(
+ self.service.host, self.service.binary, force_down=True)
diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py
index 99c0b0ee..14aa331f 100644
--- a/openstackclient/tests/image/v1/test_image.py
+++ b/openstackclient/tests/image/v1/test_image.py
@@ -475,8 +475,8 @@ class TestImageSet(TestImage):
result = self.cmd.take_action(parsed_args)
- # Verify update() was not called, if it was show the args
- self.assertEqual(self.images_mock.update.call_args_list, [])
+ self.images_mock.update.assert_called_with(image_fakes.image_id,
+ **{})
self.assertIsNone(result)
def test_image_set_options(self):
diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py
index a23efc2d..50d9899c 100644
--- a/openstackclient/tests/network/v2/fakes.py
+++ b/openstackclient/tests/network/v2/fakes.py
@@ -352,7 +352,7 @@ class FakeNetworkSegment(object):
# Set default attributes.
network_segment_attrs = {
- 'id': 'segment-id-' + uuid.uuid4().hex,
+ 'id': 'network-segment-id-' + uuid.uuid4().hex,
'network_id': 'network-id-' + uuid.uuid4().hex,
'network_type': 'vlan',
'physical_network': 'physical-network-name-' + uuid.uuid4().hex,
@@ -738,9 +738,10 @@ class FakeSubnet(object):
'host_routes': [],
'ip_version': 4,
'gateway_ip': '10.10.10.1',
- 'ipv6_address_mode': 'None',
- 'ipv6_ra_mode': 'None',
- 'subnetpool_id': 'None',
+ 'ipv6_address_mode': None,
+ 'ipv6_ra_mode': None,
+ 'segment_id': None,
+ 'subnetpool_id': None,
}
# Overwrite default attributes.
diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py
index 16296139..e3da253a 100644
--- a/openstackclient/tests/network/v2/test_router.py
+++ b/openstackclient/tests/network/v2/test_router.py
@@ -12,7 +12,9 @@
#
import mock
+from mock import call
+from osc_lib import exceptions
from osc_lib import utils as osc_utils
from openstackclient.network.v2 import router
@@ -202,32 +204,82 @@ class TestCreateRouter(TestRouter):
class TestDeleteRouter(TestRouter):
- # The router to delete.
- _router = network_fakes.FakeRouter.create_one_router()
+ # The routers to delete.
+ _routers = network_fakes.FakeRouter.create_routers(count=2)
def setUp(self):
super(TestDeleteRouter, self).setUp()
self.network.delete_router = mock.Mock(return_value=None)
- self.network.find_router = mock.Mock(return_value=self._router)
+ self.network.find_router = (
+ network_fakes.FakeRouter.get_routers(self._routers))
# Get the command object to test
self.cmd = router.DeleteRouter(self.app, self.namespace)
- def test_delete(self):
+ def test_router_delete(self):
arglist = [
- self._router.name,
+ self._routers[0].name,
]
verifylist = [
- ('router', [self._router.name]),
+ ('router', [self._routers[0].name]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.network.delete_router.assert_called_once_with(self._routers[0])
+ self.assertIsNone(result)
+
+ def test_multi_routers_delete(self):
+ arglist = []
+ verifylist = []
+
+ for r in self._routers:
+ arglist.append(r.name)
+ verifylist = [
+ ('router', arglist),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.network.delete_router.assert_called_once_with(self._router)
+
+ calls = []
+ for r in self._routers:
+ calls.append(call(r))
+ self.network.delete_router.assert_has_calls(calls)
self.assertIsNone(result)
+ def test_multi_routers_delete_with_exception(self):
+ arglist = [
+ self._routers[0].name,
+ 'unexist_router',
+ ]
+ verifylist = [
+ ('router',
+ [self._routers[0].name, 'unexist_router']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ find_mock_result = [self._routers[0], exceptions.CommandError]
+ self.network.find_router = (
+ mock.MagicMock(side_effect=find_mock_result)
+ )
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 routers failed to delete.', str(e))
+
+ self.network.find_router.assert_any_call(
+ self._routers[0].name, ignore_missing=False)
+ self.network.find_router.assert_any_call(
+ 'unexist_router', ignore_missing=False)
+ self.network.delete_router.assert_called_once_with(
+ self._routers[0]
+ )
+
class TestListRouter(TestRouter):
diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py
index a57a0308..99b558c0 100644
--- a/openstackclient/tests/network/v2/test_subnet.py
+++ b/openstackclient/tests/network/v2/test_subnet.py
@@ -91,6 +91,14 @@ class TestCreateSubnet(TestSubnet):
}
)
+ # The network segment to be returned from find_segment
+ _network_segment = \
+ network_fakes.FakeNetworkSegment.create_one_network_segment(
+ attrs={
+ 'network_id': _subnet.network_id,
+ }
+ )
+
columns = (
'allocation_pools',
'cidr',
@@ -105,6 +113,7 @@ class TestCreateSubnet(TestSubnet):
'name',
'network_id',
'project_id',
+ 'segment_id',
'subnetpool_id',
)
@@ -122,6 +131,7 @@ class TestCreateSubnet(TestSubnet):
_subnet.name,
_subnet.network_id,
_subnet.project_id,
+ _subnet.segment_id,
_subnet.subnetpool_id,
)
@@ -139,6 +149,7 @@ class TestCreateSubnet(TestSubnet):
_subnet_from_pool.name,
_subnet_from_pool.network_id,
_subnet_from_pool.project_id,
+ _subnet_from_pool.segment_id,
_subnet_from_pool.subnetpool_id,
)
@@ -156,6 +167,7 @@ class TestCreateSubnet(TestSubnet):
_subnet_ipv6.name,
_subnet_ipv6.network_id,
_subnet_ipv6.project_id,
+ _subnet_ipv6.segment_id,
_subnet_ipv6.subnetpool_id,
)
@@ -189,6 +201,15 @@ class TestCreateSubnet(TestSubnet):
loaded=True,
)
+ # Mock SDK calls for all tests.
+ self.network.find_network = mock.Mock(return_value=self._network)
+ self.network.find_segment = mock.Mock(
+ return_value=self._network_segment
+ )
+ self.network.find_subnet_pool = mock.Mock(
+ return_value=self._subnet_pool
+ )
+
def test_create_no_options(self):
arglist = []
verifylist = []
@@ -199,11 +220,9 @@ class TestCreateSubnet(TestSubnet):
self.check_parser, self.cmd, arglist, verifylist)
def test_create_default_options(self):
- # Mock create_subnet and find_network sdk calls to return the
- # values we want for this test
+ # Mock SDK calls for this test.
self.network.create_subnet = mock.Mock(return_value=self._subnet)
self._network.id = self._subnet.network_id
- self.network.find_network = mock.Mock(return_value=self._network)
arglist = [
"--subnet-range", self._subnet.cidr,
@@ -233,14 +252,10 @@ class TestCreateSubnet(TestSubnet):
self.assertEqual(self.data, data)
def test_create_from_subnet_pool_options(self):
- # Mock create_subnet, find_subnet_pool, and find_network sdk calls
- # to return the values we want for this test
+ # Mock SDK calls for this test.
self.network.create_subnet = \
mock.Mock(return_value=self._subnet_from_pool)
self._network.id = self._subnet_from_pool.network_id
- self.network.find_network = mock.Mock(return_value=self._network)
- self.network.find_subnet_pool = \
- mock.Mock(return_value=self._subnet_pool)
arglist = [
self._subnet_from_pool.name,
@@ -293,11 +308,9 @@ class TestCreateSubnet(TestSubnet):
self.assertEqual(self.data_subnet_pool, data)
def test_create_options_subnet_range_ipv6(self):
- # Mock create_subnet and find_network sdk calls to return the
- # values we want for this test
+ # Mock SDK calls for this test.
self.network.create_subnet = mock.Mock(return_value=self._subnet_ipv6)
self._network.id = self._subnet_ipv6.network_id
- self.network.find_network = mock.Mock(return_value=self._network)
arglist = [
self._subnet_ipv6.name,
@@ -360,6 +373,59 @@ class TestCreateSubnet(TestSubnet):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data_ipv6, data)
+ def test_create_no_beta_command_options(self):
+ arglist = [
+ "--subnet-range", self._subnet.cidr,
+ "--network-segment", self._network_segment.id,
+ "--network", self._subnet.network_id,
+ self._subnet.name,
+ ]
+ verifylist = [
+ ('name', self._subnet.name),
+ ('subnet_range', self._subnet.cidr),
+ ('network-segment', self._network_segment.id),
+ ('network', self._subnet.network_id),
+ ]
+ self.app.options.os_beta_command = False
+ self.assertRaises(tests_utils.ParserException,
+ self.check_parser, self.cmd, arglist, verifylist)
+
+ def test_create_with_network_segment(self):
+ # Mock SDK calls for this test.
+ self.network.create_subnet = mock.Mock(return_value=self._subnet)
+ self._network.id = self._subnet.network_id
+
+ arglist = [
+ "--subnet-range", self._subnet.cidr,
+ "--network-segment", self._network_segment.id,
+ "--network", self._subnet.network_id,
+ self._subnet.name,
+ ]
+ verifylist = [
+ ('name', self._subnet.name),
+ ('subnet_range', self._subnet.cidr),
+ ('network_segment', self._network_segment.id),
+ ('network', self._subnet.network_id),
+ ('ip_version', self._subnet.ip_version),
+ ('gateway', 'auto'),
+
+ ]
+
+ self.app.options.os_beta_command = True
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.create_subnet.assert_called_once_with(**{
+ 'cidr': self._subnet.cidr,
+ 'enable_dhcp': self._subnet.enable_dhcp,
+ 'ip_version': self._subnet.ip_version,
+ 'name': self._subnet.name,
+ 'network_id': self._subnet.network_id,
+ 'segment_id': self._network_segment.id,
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
class TestDeleteSubnet(TestSubnet):
@@ -646,6 +712,7 @@ class TestShowSubnet(TestSubnet):
'name',
'network_id',
'project_id',
+ 'segment_id',
'subnetpool_id',
)
@@ -663,6 +730,7 @@ class TestShowSubnet(TestSubnet):
_subnet.name,
_subnet.network_id,
_subnet.tenant_id,
+ _subnet.segment_id,
_subnet.subnetpool_id,
)