summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ceilometerclient/client.py203
-rw-r--r--ceilometerclient/shell.py245
-rw-r--r--ceilometerclient/tests/test_client.py2
-rw-r--r--ceilometerclient/tests/test_shell.py68
-rw-r--r--ceilometerclient/tests/v2/test_alarms.py40
-rw-r--r--ceilometerclient/tests/v2/test_events.py45
-rw-r--r--ceilometerclient/tests/v2/test_resources.py11
-rw-r--r--ceilometerclient/tests/v2/test_shell.py50
-rw-r--r--ceilometerclient/v2/alarms.py16
-rw-r--r--ceilometerclient/v2/events.py5
-rw-r--r--ceilometerclient/v2/resources.py5
-rw-r--r--ceilometerclient/v2/shell.py6
12 files changed, 516 insertions, 180 deletions
diff --git a/ceilometerclient/client.py b/ceilometerclient/client.py
index 7bfe1a6..b66e547 100644
--- a/ceilometerclient/client.py
+++ b/ceilometerclient/client.py
@@ -10,38 +10,133 @@
# License for the specific language governing permissions and limitations
# under the License.
-from keystoneclient.v2_0 import client as ksclient
+from keystoneclient.auth.identity import v2 as v2_auth
+from keystoneclient.auth.identity import v3 as v3_auth
+from keystoneclient import discover
+from keystoneclient import session
import six
from ceilometerclient.common import utils
-
-
-def _get_ksclient(**kwargs):
- """Get an endpoint and auth token from Keystone.
-
- :param kwargs: keyword args containing credentials:
- * username: name of user
- * password: user's password
- * auth_url: endpoint to authenticate against
- * cacert: path of CA TLS certificate
- * insecure: allow insecure SSL (no cert verification)
- * tenant_{name|id}: name or ID of tenant
- """
- return ksclient.Client(username=kwargs.get('username'),
- password=kwargs.get('password'),
- tenant_id=kwargs.get('tenant_id'),
- tenant_name=kwargs.get('tenant_name'),
- auth_url=kwargs.get('auth_url'),
- region_name=kwargs.get('region_name'),
- cacert=kwargs.get('cacert'),
- insecure=kwargs.get('insecure'))
-
-
-def _get_endpoint(client, **kwargs):
- """Get an endpoint using the provided keystone client."""
- return client.service_catalog.url_for(
- service_type=kwargs.get('service_type') or 'metering',
- endpoint_type=kwargs.get('endpoint_type') or 'publicURL')
+from ceilometerclient import exc
+
+
+def _get_keystone_session(**kwargs):
+ # TODO(fabgia): the heavy lifting here should be really done by Keystone.
+ # Unfortunately Keystone does not support a richer method to perform
+ # discovery and return a single viable URL. A bug against Keystone has
+ # been filed: https://bugs.launchpad.net/pyhton-keystoneclient/+bug/1330677
+
+ # first create a Keystone session
+ cacert = kwargs.pop('cacert', None)
+ cert = kwargs.pop('cert', None)
+ key = kwargs.pop('key', None)
+ insecure = kwargs.pop('insecure', False)
+ auth_url = kwargs.pop('auth_url', None)
+ project_id = kwargs.pop('project_id', None)
+ project_name = kwargs.pop('project_name', None)
+
+ if insecure:
+ verify = False
+ else:
+ verify = cacert or True
+
+ if cert and key:
+ # passing cert and key together is deprecated in favour of the
+ # requests lib form of having the cert and key as a tuple
+ cert = (cert, key)
+
+ # create the keystone client session
+ ks_session = session.Session(verify=verify, cert=cert)
+
+ try:
+ # discover the supported keystone versions using the auth endpoint url
+ ks_discover = discover.Discover(session=ks_session, auth_url=auth_url)
+ # Determine which authentication plugin to use.
+ v2_auth_url = ks_discover.url_for('2.0')
+ v3_auth_url = ks_discover.url_for('3.0')
+ except Exception:
+ raise exc.CommandError('Unable to determine the Keystone version '
+ 'to authenticate with using the given '
+ 'auth_url: %s' % auth_url)
+
+ username = kwargs.pop('username', None)
+ user_id = kwargs.pop('user_id', None)
+ user_domain_name = kwargs.pop('user_domain_name', None)
+ user_domain_id = kwargs.pop('user_domain_id', None)
+ project_domain_name = kwargs.pop('project_domain_name', None)
+ project_domain_id = kwargs.pop('project_domain_id', None)
+ auth = None
+
+ if v3_auth_url and v2_auth_url:
+ # the auth_url does not have the versions specified
+ # e.g. http://no.where:5000
+ # Keystone will return both v2 and v3 as viable options
+ # but we need to decide based on the arguments passed
+ # what version is callable
+ if (user_domain_name or user_domain_id or project_domain_name or
+ project_domain_id):
+ # domain is supported only in v3
+ auth = v3_auth.Password(
+ v3_auth_url,
+ username=username,
+ user_id=user_id,
+ user_domain_name=user_domain_name,
+ user_domain_id=user_domain_id,
+ project_domain_name=project_domain_name,
+ project_domain_id=project_domain_id,
+ **kwargs)
+ else:
+ # no domain, then use v2
+ auth = v2_auth.Password(
+ v2_auth_url,
+ username,
+ kwargs.pop('password', None),
+ tenant_id=project_id,
+ tenant_name=project_name)
+ elif v3_auth_url:
+ # the auth_url as v3 specified
+ # e.g. http://no.where:5000/v3
+ # Keystone will return only v3 as viable option
+ auth = v3_auth.Password(
+ v3_auth_url,
+ username=username,
+ user_id=user_id,
+ user_domain_name=user_domain_name,
+ user_domain_id=user_domain_id,
+ project_domain_name=project_domain_name,
+ project_domain_id=project_domain_id,
+ **kwargs)
+ elif v2_auth_url:
+ # the auth_url as v2 specified
+ # e.g. http://no.where:5000/v2.0
+ # Keystone will return only v2 as viable option
+ auth = v2_auth.Password(
+ v2_auth_url,
+ username,
+ kwargs.pop('password', None),
+ tenant_id=project_id,
+ tenant_name=project_name)
+ else:
+ raise exc.CommandError('Unable to determine the Keystone version '
+ 'to authenticate with using the given '
+ 'auth_url.')
+
+ ks_session.auth = auth
+ return ks_session
+
+
+def _get_endpoint(ks_session, **kwargs):
+ """Get an endpoint using the provided keystone session."""
+
+ # set service specific endpoint types
+ endpoint_type = kwargs.get('endpoint_type') or 'publicURL'
+ service_type = kwargs.get('service_type') or 'metering'
+
+ endpoint = ks_session.get_endpoint(service_type=service_type,
+ endpoint_type=endpoint_type,
+ region_name=kwargs.get('region_name'))
+
+ return endpoint
def get_client(api_version, **kwargs):
@@ -55,10 +150,19 @@ def get_client(api_version, **kwargs):
or:
* os_username: name of user
* os_password: user's password
+ * os_user_id: user's id
+ * os_user_domain_id: the domain id of the user
+ * os_user_domain_name: the domain name of the user
+ * os_project_id: the user project id
+ * os_tenant_id: V2 alternative to os_project_id
+ * os_project_name: the user project name
+ * os_tenant_name: V2 alternative to os_project_name
+ * os_project_domain_name: domain name for the user project
+ * os_project_domain_id: domain id for the user project
* os_auth_url: endpoint to authenticate against
- * os_cacert: path of CA TLS certificate
+ * os_cert|os_cacert: path of CA TLS certificate
+ * os_key: SSL private key
* insecure: allow insecure SSL (no cert verification)
- * os_tenant_{name|id}: name or ID of tenant
"""
token = kwargs.get('os_auth_token')
if token and not six.callable(token):
@@ -66,36 +170,41 @@ def get_client(api_version, **kwargs):
if token and kwargs.get('ceilometer_url'):
endpoint = kwargs.get('ceilometer_url')
- elif (kwargs.get('os_username') and
- kwargs.get('os_password') and
- kwargs.get('os_auth_url') and
- (kwargs.get('os_tenant_id') or kwargs.get('os_tenant_name'))):
-
+ else:
+ project_id = kwargs.get('os_project_id') or kwargs.get('os_tenant_id')
+ project_name = (kwargs.get('os_project_name') or
+ kwargs.get('os_tenant_name'))
ks_kwargs = {
'username': kwargs.get('os_username'),
'password': kwargs.get('os_password'),
- 'tenant_id': kwargs.get('os_tenant_id'),
- 'tenant_name': kwargs.get('os_tenant_name'),
+ 'user_id': kwargs.get('os_user_id'),
+ 'user_domain_id': kwargs.get('os_user_domain_id'),
+ 'user_domain_name': kwargs.get('os_user_domain_name'),
+ 'project_id': project_id,
+ 'project_name': project_name,
+ 'project_domain_name': kwargs.get('os_project_domain_name'),
+ 'project_domain_id': kwargs.get('os_project_domain_id'),
'auth_url': kwargs.get('os_auth_url'),
- 'region_name': kwargs.get('os_region_name'),
- 'service_type': kwargs.get('os_service_type'),
- 'endpoint_type': kwargs.get('os_endpoint_type'),
'cacert': kwargs.get('os_cacert'),
- 'insecure': kwargs.get('insecure'),
+ 'cert': kwargs.get('os_cert'),
+ 'key': kwargs.get('os_key'),
+ 'insecure': kwargs.get('insecure')
}
- _ksclient = _get_ksclient(**ks_kwargs)
- token = token or (lambda: _ksclient.auth_token)
+
+ # retrieve session
+ ks_session = _get_keystone_session(**ks_kwargs)
+ token = token or (lambda: ks_session.get_token())
endpoint = kwargs.get('ceilometer_url') or \
- _get_endpoint(_ksclient, **ks_kwargs)
+ _get_endpoint(ks_session, **ks_kwargs)
cli_kwargs = {
'token': token,
'insecure': kwargs.get('insecure'),
'timeout': kwargs.get('timeout'),
'cacert': kwargs.get('os_cacert'),
- 'cert_file': kwargs.get('cert_file'),
- 'key_file': kwargs.get('key_file'),
+ 'cert_file': kwargs.get('os_cert'),
+ 'key_file': kwargs.get('os_key')
}
return Client(api_version, endpoint, **cli_kwargs)
diff --git a/ceilometerclient/shell.py b/ceilometerclient/shell.py
index 9d87b2c..98f3851 100644
--- a/ceilometerclient/shell.py
+++ b/ceilometerclient/shell.py
@@ -32,36 +32,11 @@ from ceilometerclient.openstack.common import strutils
class CeilometerShell(object):
- def get_base_parser(self):
- parser = argparse.ArgumentParser(
- prog='ceilometer',
- description=__doc__.strip(),
- epilog='See "ceilometer help COMMAND" '
- 'for help on a specific command.',
- add_help=False,
- formatter_class=HelpFormatter,
- )
-
- # Global arguments
- parser.add_argument('-h', '--help',
- action='store_true',
- help=argparse.SUPPRESS,
- )
-
- parser.add_argument('--version',
- action='version',
- version=ceilometerclient.__version__)
-
- parser.add_argument('-d', '--debug',
- default=bool(cliutils.env('CEILOMETERCLIENT_DEBUG')
- ),
- action='store_true',
- help='Defaults to env[CEILOMETERCLIENT_DEBUG].')
-
- parser.add_argument('-v', '--verbose',
- default=False, action="store_true",
- help="Print more verbose output.")
-
+ def _append_identity_args(self, parser):
+ # FIXME(fabgia): identity related parameters should be passed by the
+ # Keystone client itself to avoid constant update in all the services
+ # clients. When this fix is merged this method can be made obsolete.
+ # Bug: https://bugs.launchpad.net/python-keystoneclient/+bug/1332337
parser.add_argument('-k', '--insecure',
default=False,
action='store_true',
@@ -72,32 +47,7 @@ class CeilometerShell(object):
"authorities. This option should be used with "
"caution.")
- parser.add_argument('--cert-file',
- help='Path of certificate file to use in SSL '
- 'connection. This file can optionally be prepended'
- ' with the private key.')
-
- parser.add_argument('--key-file',
- help='Path of client key to use in SSL connection.'
- ' This option is not necessary if your key is '
- 'prepended to your cert file.')
-
- parser.add_argument('--os-cacert',
- metavar='<ca-certificate-file>',
- dest='os_cacert',
- default=cliutils.env('OS_CACERT'),
- help='Path of CA TLS certificate(s) used to verify'
- 'the remote server\'s certificate. Without this '
- 'option ceilometer looks for the default system '
- 'CA certificates.')
- parser.add_argument('--ca-file',
- dest='os_cacert',
- help='DEPRECATED! Use --os-cacert.')
-
- parser.add_argument('--timeout',
- default=600,
- help='Number of seconds to wait for a response.')
-
+ # User related options
parser.add_argument('--os-username',
default=cliutils.env('OS_USERNAME'),
help='Defaults to env[OS_USERNAME].')
@@ -105,6 +55,10 @@ class CeilometerShell(object):
parser.add_argument('--os_username',
help=argparse.SUPPRESS)
+ parser.add_argument('--os-user-id',
+ default=cliutils.env('OS_USER_ID'),
+ help='Defaults to env[OS_USER_ID].')
+
parser.add_argument('--os-password',
default=cliutils.env('OS_PASSWORD'),
help='Defaults to env[OS_PASSWORD].')
@@ -112,9 +66,43 @@ class CeilometerShell(object):
parser.add_argument('--os_password',
help=argparse.SUPPRESS)
+ # Domain related options
+ parser.add_argument('--os-user-domain-id',
+ default=cliutils.env('OS_USER_DOMAIN_ID'),
+ help='Defaults to env[OS_USER_DOMAIN_ID].')
+
+ parser.add_argument('--os-user-domain-name',
+ default=cliutils.env('OS_USER_DOMAIN_NAME'),
+ help='Defaults to env[OS_USER_DOMAIN_NAME].')
+
+ parser.add_argument('--os-project-domain-id',
+ default=cliutils.env('OS_PROJECT_DOMAIN_ID'),
+ help='Defaults to env[OS_PROJECT_DOMAIN_ID].')
+
+ parser.add_argument('--os-project-domain-name',
+ default=cliutils.env('OS_PROJECT_DOMAIN_NAME'),
+ help='Defaults to env[OS_PROJECT_DOMAIN_NAME].')
+
+ # Project V3 or Tenant V2 related options
+ parser.add_argument('--os-project-id',
+ default=cliutils.env('OS_PROJECT_ID'),
+ help='Another way to specify tenant ID. '
+ 'This option is mutually exclusive with '
+ ' --os-tenant-id. '
+ 'Defaults to env[OS_PROJECT_ID].')
+
+ parser.add_argument('--os-project-name',
+ default=cliutils.env('OS_PROJECT_NAME'),
+ help='Another way to specify tenant name. '
+ 'This option is mutually exclusive with '
+ ' --os-tenant-name. '
+ 'Defaults to env[OS_PROJECT_NAME].')
+
parser.add_argument('--os-tenant-id',
default=cliutils.env('OS_TENANT_ID'),
- help='Defaults to env[OS_TENANT_ID].')
+ help='This option is mutually exclusive with '
+ ' --os-project-id. '
+ 'Defaults to env[OS_PROJECT_ID].')
parser.add_argument('--os_tenant_id',
help=argparse.SUPPRESS)
@@ -126,6 +114,7 @@ class CeilometerShell(object):
parser.add_argument('--os_tenant_name',
help=argparse.SUPPRESS)
+ # Auth related options
parser.add_argument('--os-auth-url',
default=cliutils.env('OS_AUTH_URL'),
help='Defaults to env[OS_AUTH_URL].')
@@ -133,6 +122,47 @@ class CeilometerShell(object):
parser.add_argument('--os_auth_url',
help=argparse.SUPPRESS)
+ parser.add_argument('--os-auth-token',
+ default=cliutils.env('OS_AUTH_TOKEN'),
+ help='Defaults to env[OS_AUTH_TOKEN].')
+
+ parser.add_argument('--os_auth_token',
+ help=argparse.SUPPRESS)
+
+ parser.add_argument('--os-cacert',
+ metavar='<ca-certificate-file>',
+ dest='os_cacert',
+ default=cliutils.env('OS_CACERT'),
+ help='Path of CA TLS certificate(s) used to verify'
+ 'the remote server\'s certificate. Without this '
+ 'option ceilometer looks for the default system '
+ 'CA certificates.')
+
+ parser.add_argument('--os-cert',
+ help='Path of certificate file to use in SSL '
+ 'connection. This file can optionally be '
+ 'prepended with the private key.')
+
+ parser.add_argument('--os-key',
+ help='Path of client key to use in SSL '
+ 'connection. This option is not necessary '
+ 'if your key is prepended to your cert file.')
+
+ # Service Catalog related options
+ parser.add_argument('--os-service-type',
+ default=cliutils.env('OS_SERVICE_TYPE'),
+ help='Defaults to env[OS_SERVICE_TYPE].')
+
+ parser.add_argument('--os_service_type',
+ help=argparse.SUPPRESS)
+
+ parser.add_argument('--os-endpoint-type',
+ default=cliutils.env('OS_ENDPOINT_TYPE'),
+ help='Defaults to env[OS_ENDPOINT_TYPE].')
+
+ parser.add_argument('--os_endpoint_type',
+ help=argparse.SUPPRESS)
+
parser.add_argument('--os-region-name',
default=cliutils.env('OS_REGION_NAME'),
help='Defaults to env[OS_REGION_NAME].')
@@ -140,12 +170,52 @@ class CeilometerShell(object):
parser.add_argument('--os_region_name',
help=argparse.SUPPRESS)
- parser.add_argument('--os-auth-token',
- default=cliutils.env('OS_AUTH_TOKEN'),
- help='Defaults to env[OS_AUTH_TOKEN].')
+ # Deprecated options
+ parser.add_argument('--ca-file',
+ dest='os_cacert',
+ help='DEPRECATED! Use --os-cacert.')
- parser.add_argument('--os_auth_token',
- help=argparse.SUPPRESS)
+ parser.add_argument('--cert-file',
+ dest='os_cert',
+ help='DEPRECATED! Use --os-cert.')
+
+ parser.add_argument('--key-file',
+ dest='os_key',
+ help='DEPRECATED! Use --os-key.')
+
+ def get_base_parser(self):
+ parser = argparse.ArgumentParser(
+ prog='ceilometer',
+ description=__doc__.strip(),
+ epilog='See "ceilometer help COMMAND" '
+ 'for help on a specific command.',
+ add_help=False,
+ formatter_class=HelpFormatter,
+ )
+
+ # Global arguments
+ parser.add_argument('-h', '--help',
+ action='store_true',
+ help=argparse.SUPPRESS,
+ )
+
+ parser.add_argument('--version',
+ action='version',
+ version=ceilometerclient.__version__)
+
+ parser.add_argument('-d', '--debug',
+ default=bool(cliutils.env('CEILOMETERCLIENT_DEBUG')
+ ),
+ action='store_true',
+ help='Defaults to env[CEILOMETERCLIENT_DEBUG].')
+
+ parser.add_argument('-v', '--verbose',
+ default=False, action="store_true",
+ help="Print more verbose output.")
+
+ parser.add_argument('--timeout',
+ default=600,
+ help='Number of seconds to wait for a response.')
parser.add_argument('--ceilometer-url',
default=cliutils.env('CEILOMETER_URL'),
@@ -163,19 +233,9 @@ class CeilometerShell(object):
parser.add_argument('--ceilometer_api_version',
help=argparse.SUPPRESS)
- parser.add_argument('--os-service-type',
- default=cliutils.env('OS_SERVICE_TYPE'),
- help='Defaults to env[OS_SERVICE_TYPE].')
-
- parser.add_argument('--os_service_type',
- help=argparse.SUPPRESS)
-
- parser.add_argument('--os-endpoint-type',
- default=cliutils.env('OS_ENDPOINT_TYPE'),
- help='Defaults to env[OS_ENDPOINT_TYPE].')
-
- parser.add_argument('--os_endpoint_type',
- help=argparse.SUPPRESS)
+ # FIXME(fabgia): identity related parameters should be passed by the
+ # Keystone client itself.
+ self._append_identity_args(parser)
return parser
@@ -249,6 +309,14 @@ class CeilometerShell(object):
# Return parsed args
return api_version, subcommand_parser.parse_args(argv)
+ def no_project_and_domain_set(self, args):
+ if not (args.os_project_id or (args.os_project_name and
+ (args.os_user_domain_name or args.os_user_domain_id)) or
+ (args.os_tenant_id or args.os_tenant_name)):
+ return True
+ else:
+ return False
+
def main(self, argv):
parsed = self.parse_args(argv)
if parsed == 0:
@@ -274,10 +342,17 @@ class CeilometerShell(object):
"either --os-password or via "
"env[OS_PASSWORD]")
- if not (args.os_tenant_id or args.os_tenant_name):
- raise exc.CommandError("You must provide a tenant_id via "
- "either --os-tenant-id or via "
- "env[OS_TENANT_ID]")
+ if self.no_project_and_domain_set(args):
+ # steer users towards Keystone V3 API
+ raise exc.CommandError("You must provide a project_id via "
+ "either --os-project-id or via "
+ "env[OS_PROJECT_ID] and "
+ "a domain_name via either "
+ "--os-user-domain-name or via "
+ "env[OS_USER_DOMAIN_NAME] or "
+ "a domain_id via either "
+ "--os-user-domain-id or via "
+ "env[OS_USER_DOMAIN_ID]")
if not args.os_auth_url:
raise exc.CommandError("You must provide an auth url via "
@@ -323,6 +398,18 @@ class CeilometerShell(object):
class HelpFormatter(argparse.HelpFormatter):
+ INDENT_BEFORE_ARGUMENTS = 6
+ MAX_WIDTH_ARGUMENTS = 32
+
+ def add_arguments(self, actions):
+ for action in filter(lambda x: not x.option_strings, actions):
+ for choice in action.choices:
+ length = len(choice) + self.INDENT_BEFORE_ARGUMENTS
+ if(length > self._max_help_position and
+ length <= self.MAX_WIDTH_ARGUMENTS):
+ self._max_help_position = length
+ super(HelpFormatter, self).add_arguments(actions)
+
def start_section(self, heading):
# Title-case the headings
heading = '%s%s' % (heading[0].upper(), heading[1:])
diff --git a/ceilometerclient/tests/test_client.py b/ceilometerclient/tests/test_client.py
index de46c9a..14aaca6 100644
--- a/ceilometerclient/tests/test_client.py
+++ b/ceilometerclient/tests/test_client.py
@@ -20,7 +20,7 @@ from ceilometerclient.v2 import client as v2client
FAKE_ENV = {'os_username': 'username',
'os_password': 'password',
'os_tenant_name': 'tenant_name',
- 'os_auth_url': 'http://no.where',
+ 'os_auth_url': 'http://no.where:5000/',
'os_auth_token': '1234',
'ceilometer_url': 'http://no.where'}
diff --git a/ceilometerclient/tests/test_shell.py b/ceilometerclient/tests/test_shell.py
index be6e115..5f0ffba 100644
--- a/ceilometerclient/tests/test_shell.py
+++ b/ceilometerclient/tests/test_shell.py
@@ -14,7 +14,7 @@ import re
import sys
import fixtures
-from keystoneclient.v2_0 import client as ksclient
+from keystoneclient import session as ks_session
import mock
import six
from testtools import matchers
@@ -24,25 +24,31 @@ from ceilometerclient import shell as ceilometer_shell
from ceilometerclient.tests import utils
from ceilometerclient.v1 import client as v1client
-FAKE_ENV = {'OS_USERNAME': 'username',
- 'OS_PASSWORD': 'password',
- 'OS_TENANT_NAME': 'tenant_name',
- 'OS_AUTH_URL': 'http://no.where'}
+FAKE_V2_ENV = {'OS_USERNAME': 'username',
+ 'OS_PASSWORD': 'password',
+ 'OS_TENANT_NAME': 'tenant_name',
+ 'OS_AUTH_URL': 'http://localhost:5000/v2.0'}
+
+FAKE_V3_ENV = {'OS_USERNAME': 'username',
+ 'OS_PASSWORD': 'password',
+ 'OS_USER_DOMAIN_NAME': 'domain_name',
+ 'OS_PROJECT_ID': '1234567890',
+ 'OS_AUTH_URL': 'http://localhost:5000/v3'}
class ShellTest(utils.BaseTestCase):
re_options = re.DOTALL | re.MULTILINE
# Patch os.environ to avoid required auth info.
- def make_env(self, exclude=None):
- env = dict((k, v) for k, v in FAKE_ENV.items() if k != exclude)
+ def make_env(self, env_version, exclude=None):
+ env = dict((k, v) for k, v in env_version.items() if k != exclude)
self.useFixture(fixtures.MonkeyPatch('os.environ', env))
def setUp(self):
super(ShellTest, self).setUp()
@mock.patch('sys.stdout', new=six.StringIO())
- @mock.patch.object(ksclient, 'Client')
+ @mock.patch.object(ks_session, 'Session')
@mock.patch.object(v1client.http.HTTPClient, 'json_request')
@mock.patch.object(v1client.http.HTTPClient, 'raw_request')
def shell(self, argstr, mock_ksclient, mock_json, mock_raw):
@@ -85,28 +91,60 @@ class ShellTest(utils.BaseTestCase):
self.assertThat(help_text,
matchers.MatchesRegex(r, self.re_options))
+
+class ShellKeystoneV2Test(ShellTest):
+
+ def test_auth_param(self):
+ self.make_env(FAKE_V2_ENV, exclude='OS_USERNAME')
+ self.test_help()
+
+ @mock.patch.object(ks_session, 'Session')
+ def test_debug_switch_raises_error(self, mock_ksclient):
+ mock_ksclient.side_effect = exc.HTTPUnauthorized
+ self.make_env(FAKE_V2_ENV)
+ args = ['--debug', 'event-list']
+ self.assertRaises(exc.HTTPUnauthorized, ceilometer_shell.main, args)
+
+ @mock.patch.object(ks_session, 'Session')
+ def test_dash_d_switch_raises_error(self, mock_ksclient):
+ mock_ksclient.side_effect = exc.CommandError("FAIL")
+ self.make_env(FAKE_V2_ENV)
+ args = ['-d', 'event-list']
+ self.assertRaises(exc.CommandError, ceilometer_shell.main, args)
+
+ @mock.patch('sys.stderr')
+ @mock.patch.object(ks_session, 'Session')
+ def test_no_debug_switch_no_raises_errors(self, mock_ksclient, __):
+ mock_ksclient.side_effect = exc.HTTPUnauthorized("FAIL")
+ self.make_env(FAKE_V2_ENV)
+ args = ['event-list']
+ self.assertRaises(SystemExit, ceilometer_shell.main, args)
+
+
+class ShellKeystoneV3Test(ShellTest):
+
def test_auth_param(self):
- self.make_env(exclude='OS_USERNAME')
+ self.make_env(FAKE_V3_ENV, exclude='OS_USER_DOMAIN_NAME')
self.test_help()
- @mock.patch.object(ksclient, 'Client')
+ @mock.patch.object(ks_session, 'Session')
def test_debug_switch_raises_error(self, mock_ksclient):
mock_ksclient.side_effect = exc.HTTPUnauthorized
- self.make_env()
+ self.make_env(FAKE_V3_ENV)
args = ['--debug', 'event-list']
self.assertRaises(exc.HTTPUnauthorized, ceilometer_shell.main, args)
- @mock.patch.object(ksclient, 'Client')
+ @mock.patch.object(ks_session, 'Session')
def test_dash_d_switch_raises_error(self, mock_ksclient):
mock_ksclient.side_effect = exc.CommandError("FAIL")
- self.make_env()
+ self.make_env(FAKE_V3_ENV)
args = ['-d', 'event-list']
self.assertRaises(exc.CommandError, ceilometer_shell.main, args)
@mock.patch('sys.stderr')
- @mock.patch.object(ksclient, 'Client')
+ @mock.patch.object(ks_session, 'Session')
def test_no_debug_switch_no_raises_errors(self, mock_ksclient, __):
mock_ksclient.side_effect = exc.HTTPUnauthorized("FAIL")
- self.make_env()
+ self.make_env(FAKE_V3_ENV)
args = ['event-list']
self.assertRaises(SystemExit, ceilometer_shell.main, args)
diff --git a/ceilometerclient/tests/v2/test_alarms.py b/ceilometerclient/tests/v2/test_alarms.py
index 13d04d1..a0e13ac 100644
--- a/ceilometerclient/tests/v2/test_alarms.py
+++ b/ceilometerclient/tests/v2/test_alarms.py
@@ -201,6 +201,10 @@ fixtures = {
{},
UPDATED_ALARM,
),
+ 'DELETE': (
+ {},
+ None,
+ ),
},
'/v2/alarms/alarm-id/state':
{
@@ -341,7 +345,41 @@ class AlarmManagerTest(testtools.TestCase):
('DELETE', '/v2/alarms/victim-id', {}, None),
]
self.assertEqual(self.api.calls, expect)
- self.assertTrue(deleted is None)
+ self.assertIsNone(deleted)
+
+ def test_get_from_alarm_class(self):
+ alarm = self.mgr.get(alarm_id='alarm-id')
+ self.assertTrue(alarm)
+ alarm.get()
+ expect = [
+ ('GET', '/v2/alarms/alarm-id', {}, None),
+ ('GET', '/v2/alarms/alarm-id', {}, None)
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertEqual('alarm-id', alarm.alarm_id)
+ self.assertEqual(alarm.threshold_rule, alarm.rule)
+
+ def test_get_state_from_alarm_class(self):
+ alarm = self.mgr.get(alarm_id='alarm-id')
+ self.assertTrue(alarm)
+ state = alarm.get_state()
+ expect = [
+ ('GET', '/v2/alarms/alarm-id', {}, None),
+ ('GET', '/v2/alarms/alarm-id/state', {}, None)
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertEqual('alarm', state)
+
+ def test_delete_from_alarm_class(self):
+ alarm = self.mgr.get(alarm_id='alarm-id')
+ self.assertTrue(alarm)
+ deleted = alarm.delete()
+ expect = [
+ ('GET', '/v2/alarms/alarm-id', {}, None),
+ ('DELETE', '/v2/alarms/alarm-id', {}, None)
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertIsNone(deleted)
def _do_test_get_history(self, q, url):
history = self.mgr.get_history(q=q, alarm_id='alarm-id')
diff --git a/ceilometerclient/tests/v2/test_events.py b/ceilometerclient/tests/v2/test_events.py
index 96cf7f5..4935d8b 100644
--- a/ceilometerclient/tests/v2/test_events.py
+++ b/ceilometerclient/tests/v2/test_events.py
@@ -22,22 +22,22 @@ fixtures = {
{},
[
{
+ 'message_id': '1',
'event_type': 'Foo',
'generated': '1970-01-01T00:00:00',
- 'traits': {'trait_A': 'abc',
- 'message_id': '1'},
+ 'traits': {'trait_A': 'abc'},
},
{
+ 'message_id': '2',
'event_type': 'Foo',
'generated': '1970-01-01T00:00:00',
- 'traits': {'trait_A': 'def',
- 'message_id': '2'},
+ 'traits': {'trait_A': 'def'},
},
{
+ 'message_id': '3',
'event_type': 'Bar',
'generated': '1970-01-01T00:00:00',
- 'traits': {'trait_B': 'bartrait',
- 'message_id': '3'},
+ 'traits': {'trait_B': 'bartrait'},
},
]
),
@@ -48,18 +48,18 @@ fixtures = {
{},
[
{
+ 'message_id': '1',
'event_type': 'Foo',
'generated': '1970-01-01T00:00:00',
'traits': {'trait_A': 'abc',
- 'hostname': 'localhost',
- 'message_id': '1'},
+ 'hostname': 'localhost'},
},
{
+ 'message_id': '2',
'event_type': 'Foo',
'generated': '1970-01-01T00:00:00',
'traits': {'trait_A': 'def',
- 'hostname': 'localhost',
- 'message_id': '2'},
+ 'hostname': 'localhost'},
}
]
),
@@ -70,18 +70,18 @@ fixtures = {
{},
[
{
+ 'message_id': '1',
'event_type': 'Foo',
'generated': '1970-01-01T00:00:00',
'traits': {'trait_A': 'abc',
- 'hostname': 'foreignhost',
- 'message_id': '1'},
+ 'hostname': 'foreignhost'},
},
{
+ 'message_id': '2',
'event_type': 'Foo',
'generated': '1970-01-01T00:00:00',
'traits': {'trait_A': 'def',
- 'hostname': 'foreignhost',
- 'message_id': '2'},
+ 'hostname': 'foreignhost'},
}
]
),
@@ -93,12 +93,12 @@ fixtures = {
{},
[
{
+ 'message_id': '1',
'event_type': 'Bar',
'generated': '1970-01-01T00:00:00',
'traits': {'trait_A': 'abc',
'hostname': 'localhost',
- 'num_cpus': '5',
- 'message_id': '1'},
+ 'num_cpus': '5'},
},
]
),
@@ -109,10 +109,10 @@ fixtures = {
'GET': (
{},
{
+ 'message_id': '2',
'event_type': 'Foo',
'generated': '1970-01-01T00:00:00',
'traits': {'trait_A': 'def',
- 'message_id': '2',
'intTrait': '42'},
}
),
@@ -186,3 +186,14 @@ class EventManagerTest(utils.BaseTestCase):
]
self.assertEqual(self.api.calls, expect)
self.assertEqual(len(events), 1)
+
+ def test_get_from_event_class(self):
+ event = self.mgr.get(2)
+ self.assertTrue(event)
+ event.get()
+ expect = [
+ ('GET', '/v2/events/2', {}, None),
+ ('GET', '/v2/events/2', {}, None),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertEqual('Foo', event.event_type)
diff --git a/ceilometerclient/tests/v2/test_resources.py b/ceilometerclient/tests/v2/test_resources.py
index 13a29fe..d62ce40 100644
--- a/ceilometerclient/tests/v2/test_resources.py
+++ b/ceilometerclient/tests/v2/test_resources.py
@@ -104,3 +104,14 @@ class ResourceManagerTest(utils.BaseTestCase):
self.assertEqual(self.api.calls, expect)
self.assertEqual(len(resources), 1)
self.assertEqual(resources[0].resource_id, 'a')
+
+ def test_get_from_resource_class(self):
+ resource = self.mgr.get(resource_id='a')
+ self.assertTrue(resource)
+ resource.get()
+ expect = [
+ ('GET', '/v2/resources/a', {}, None),
+ ('GET', '/v2/resources/a', {}, None),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertEqual('a', resource.resource_id)
diff --git a/ceilometerclient/tests/v2/test_shell.py b/ceilometerclient/tests/v2/test_shell.py
index 819e938..96ab38f 100644
--- a/ceilometerclient/tests/v2/test_shell.py
+++ b/ceilometerclient/tests/v2/test_shell.py
@@ -178,6 +178,20 @@ class ShellAlarmCommandTest(utils.BaseTestCase):
"type": "threshold",
"name": "cpu_high"}
+ THRESHOLD_ALARM_CLI_ARGS = [
+ '--name', 'cpu_high',
+ '--description', 'instance running hot',
+ '--meter-name', 'cpu_util',
+ '--threshold', '70.0',
+ '--comparison-operator', 'gt',
+ '--statistic', 'avg',
+ '--period', '600',
+ '--evaluation-periods', '3',
+ '--alarm-action', 'log://',
+ '--alarm-action', 'http://example.com/alarm/state',
+ '--query', 'resource_id=INSTANCE_ID'
+ ]
+
def setUp(self):
super(ShellAlarmCommandTest, self).setUp()
self.cc = mock.Mock()
@@ -201,7 +215,7 @@ class ShellAlarmCommandTest(utils.BaseTestCase):
if repeat_actions is not None:
self.assertEqual(repeat_actions, kwargs.get('repeat_actions'))
else:
- self.assertFalse('repeat_actions' in kwargs)
+ self.assertNotIn('repeat_actions', kwargs)
def test_alarm_update_repeat_actions_untouched(self):
method = ceilometer_shell.do_alarm_update
@@ -241,31 +255,33 @@ class ShellAlarmCommandTest(utils.BaseTestCase):
@mock.patch('sys.stdout', new=six.StringIO())
def test_alarm_threshold_create_args(self):
+ argv = ['alarm-threshold-create'] + self.THRESHOLD_ALARM_CLI_ARGS
+ self._test_alarm_threshold_action_args('create', argv)
+
+ def test_alarm_threshold_update_args(self):
+ argv = ['alarm-threshold-update',
+ '--alarm_id', 'x'] + self.THRESHOLD_ALARM_CLI_ARGS
+ self._test_alarm_threshold_action_args('update', argv)
+
+ @mock.patch('sys.stdout', new=six.StringIO())
+ def _test_alarm_threshold_action_args(self, action, argv):
shell = base_shell.CeilometerShell()
- argv = ['alarm-threshold-create',
- '--name', 'cpu_high',
- '--description', 'instance running hot',
- '--meter-name', 'cpu_util',
- '--threshold', '70.0',
- '--comparison-operator', 'gt',
- '--statistic', 'avg',
- '--period', '600',
- '--evaluation-periods', '3',
- '--alarm-action', 'log://',
- '--alarm-action', 'http://example.com/alarm/state',
- '--query', 'resource_id=INSTANCE_ID']
_, args = shell.parse_args(argv)
alarm = alarms.Alarm(mock.Mock(), self.ALARM)
- self.cc.alarms.create.return_value = alarm
+ getattr(self.cc.alarms, action).return_value = alarm
- ceilometer_shell.do_alarm_threshold_create(self.cc, args)
- _, kwargs = self.cc.alarms.create.call_args
+ func = getattr(ceilometer_shell, 'do_alarm_threshold_' + action)
+ func(self.cc, args)
+ _, kwargs = getattr(self.cc.alarms, action).call_args
+ self._check_alarm_threshold_args(kwargs)
+
+ def _check_alarm_threshold_args(self, kwargs):
self.assertEqual('cpu_high', kwargs.get('name'))
self.assertEqual('instance running hot', kwargs.get('description'))
actions = ['log://', 'http://example.com/alarm/state']
self.assertEqual(actions, kwargs.get('alarm_actions'))
- self.assertTrue('threshold_rule' in kwargs)
+ self.assertIn('threshold_rule', kwargs)
rule = kwargs['threshold_rule']
self.assertEqual('cpu_util', rule.get('meter_name'))
self.assertEqual(70.0, rule.get('threshold'))
diff --git a/ceilometerclient/v2/alarms.py b/ceilometerclient/v2/alarms.py
index 50fa1e4..7c46fb2 100644
--- a/ceilometerclient/v2/alarms.py
+++ b/ceilometerclient/v2/alarms.py
@@ -19,6 +19,7 @@ import warnings
from ceilometerclient.common import base
from ceilometerclient.common import utils
+from ceilometerclient import exc
from ceilometerclient.v2 import options
@@ -48,8 +49,16 @@ class Alarm(base.Resource):
# that look like the Alarm storage object
if k == 'rule':
k = '%s_rule' % self.type
+ if k == 'id':
+ return self.alarm_id
return super(Alarm, self).__getattr__(k)
+ def delete(self):
+ return self.manager.delete(self.alarm_id)
+
+ def get_state(self):
+ return self.manager.get_state(self.alarm_id)
+
class AlarmChange(base.Resource):
def __repr__(self):
@@ -74,6 +83,13 @@ class AlarmManager(base.Manager):
return self._list(self._path(alarm_id), expect_single=True)[0]
except IndexError:
return None
+ except exc.HTTPNotFound:
+ # When we try to get deleted alarm HTTPNotFound occurs
+ # or when alarm doesn't exists this exception don't must
+ # go deeper because cleanUp() (method which remove all
+ # created things like instance, alarm, etc.) at scenario
+ # tests doesn't know how to process it
+ return None
@classmethod
def _compat_legacy_alarm_kwargs(cls, kwargs, create=False):
diff --git a/ceilometerclient/v2/events.py b/ceilometerclient/v2/events.py
index bf96d4c..32380d6 100644
--- a/ceilometerclient/v2/events.py
+++ b/ceilometerclient/v2/events.py
@@ -20,6 +20,11 @@ class Event(base.Resource):
def __repr__(self):
return "<Event %s>" % self._info
+ def __getattr__(self, k):
+ if k == 'id':
+ return self.message_id
+ return super(Event, self).__getattr__(k)
+
class EventManager(base.Manager):
resource_class = Event
diff --git a/ceilometerclient/v2/resources.py b/ceilometerclient/v2/resources.py
index 897033e..99e6fd5 100644
--- a/ceilometerclient/v2/resources.py
+++ b/ceilometerclient/v2/resources.py
@@ -21,6 +21,11 @@ class Resource(base.Resource):
def __repr__(self):
return "<Resource %s>" % self._info
+ def __getattr__(self, k):
+ if k == 'id':
+ return self.resource_id
+ return super(Resource, self).__getattr__(k)
+
class ResourceManager(base.Manager):
resource_class = Resource
diff --git a/ceilometerclient/v2/shell.py b/ceilometerclient/v2/shell.py
index 9be9b02..b13ab20 100644
--- a/ceilometerclient/v2/shell.py
+++ b/ceilometerclient/v2/shell.py
@@ -322,9 +322,8 @@ def _display_alarm(alarm):
help='ID of the alarm to show.')
def do_alarm_show(cc, args={}):
'''Show an alarm.'''
- try:
- alarm = cc.alarms.get(args.alarm_id)
- except exc.HTTPNotFound:
+ alarm = cc.alarms.get(args.alarm_id)
+ if alarm is None:
raise exc.CommandError('Alarm not found: %s' % args.alarm_id)
else:
_display_alarm(alarm)
@@ -545,6 +544,7 @@ def do_alarm_update(cc, args={}):
dest='threshold_rule/threshold',
help='Threshold to evaluate against.')
@utils.arg('-q', '--query', metavar='<QUERY>',
+ dest='threshold_rule/query',
help='key[op]data_type::value; list. data_type is optional, '
'but if supplied must be string, integer, float, or boolean.')
@utils.arg('--repeat-actions', dest='repeat_actions',