diff --git a/.gitreview b/.gitreview index 347ef3e..3acda24 100644 --- a/.gitreview +++ b/.gitreview @@ -1,4 +1,5 @@ [gerrit] host=review.openstack.org port=29418 -project=openstack/python-senlinclient.git +project=openstack/deb-python-senlinclient.git +defaultbranch=debian/newton diff --git a/debian/changelog b/debian/changelog index 64f98d4..718e6a3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +python-senlinclient (1.0.0-1) experimental; urgency=medium + + * New upstream release. + * Fixed (build-)depends for this release. + + -- Thomas Goirand Wed, 14 Sep 2016 09:25:36 +0200 + python-senlinclient (0.5.0-1) experimental; urgency=medium * New upstream release. diff --git a/debian/control b/debian/control index a9132ad..67dec5e 100644 --- a/debian/control +++ b/debian/control @@ -17,42 +17,40 @@ Build-Depends: debhelper (>= 9), python3-setuptools, Build-Depends-Indep: git, python-babel (>= 2.3.4), - python-cliff (>= 1.15.0), python-coverage, python-fixtures (>= 3.0.0), python-hacking, - python-heatclient (>= 1.1.0), + python-heatclient (>= 1.4.0), python-mock (>= 2.0), - python-mox3 (>= 0.7.0), - python-openstacksdk (>= 0.8.6), python-openstackclient (>= 2.1.0), + python-openstacksdk (>= 0.9.4), + python-osc-lib (>= 1.0.2), python-oslo.i18n (>= 2.1.0), python-oslo.serialization (>= 1.10.0), - python-oslo.utils (>= 3.11.0), + python-oslo.utils (>= 3.16.0), python-oslosphinx (>= 2.5.0), python-oslotest (>= 1.10.0), python-prettytable, python-requests (>= 2.10.0), - python-requests-mock (>= 0.7.0), + python-requests-mock (>= 1.0.0), python-six (>= 1.9.0), python-testscenarios, python-testtools (>= 1.4.0), python-yaml, python3-babel (>= 2.3.4), - python3-cliff (>= 1.15.0), python3-fixtures (>= 3.0.0), - python3-heatclient (>= 1.1.0), + python3-heatclient (>= 1.4.0), python3-mock (>= 2.0), - python3-mox3 (>= 0.7.0), - python3-openstacksdk (>= 0.8.6), python3-openstackclient (>= 2.1.0), + python3-openstacksdk (>= 0.9.4), + python3-osc-lib (>= 1.0.2), python3-oslo.i18n (>= 2.1.0), python3-oslo.serialization (>= 1.10.0), - python3-oslo.utils (>= 3.11.0), + python3-oslo.utils (>= 3.16.0), python3-oslotest (>= 1.10.0), python3-prettytable, python3-requests (>= 2.10.0), - python3-requests-mock (>= 0.7.0), + python3-requests-mock (>= 1.0.0), python3-six (>= 1.9.0), python3-subunit, python3-testscenarios, @@ -61,20 +59,20 @@ Build-Depends-Indep: git, subunit, testrepository, Standards-Version: 3.9.8 -Vcs-Browser: https://anonscm.debian.org/cgit/openstack/python-senlinclient.git/ -Vcs-Git: https://anonscm.debian.org/git/openstack/python-senlinclient.git +Vcs-Browser: https://git.openstack.org/cgit/openstack/deb-python-senlinclient +Vcs-Git: https://git.openstack.org/openstack/deb-python-senlinclient Homepage: https://github.com/openstack/python-senlinclient Package: python-senlinclient Architecture: all Depends: python-babel (>= 2.3.4), - python-cliff (>= 1.15.0), - python-heatclient (>= 1.1.0), - python-openstacksdk (>= 0.8.6), + python-heatclient (>= 1.4.0), python-openstackclient (>= 2.1.0), + python-openstacksdk (>= 0.9.4), + python-osc-lib (>= 1.0.2), python-oslo.i18n (>= 1.5.0), python-oslo.serialization (>= 1.10.0), - python-oslo.utils (>= 3.11.0), + python-oslo.utils (>= 3.16.0), python-pbr (>= 1.8), python-prettytable, python-requests (>= 2.10.0), @@ -93,13 +91,13 @@ Description: OpenStack Clustering API Client Library - Python 2.7 Package: python3-senlinclient Architecture: all Depends: python3-babel (>= 2.3.4), - python3-cliff (>= 1.15.0), - python3-heatclient (>= 1.1.0), - python3-openstacksdk (>= 0.8.6), + python3-heatclient (>= 1.4.0), python3-openstackclient (>= 2.1.0), + python3-openstacksdk (>= 0.9.4), + python3-osc-lib (>= 1.0.2), python3-oslo.i18n (>= 1.5.0), python3-oslo.serialization (>= 1.10.0), - python3-oslo.utils (>= 3.11.0), + python3-oslo.utils (>= 3.16.0), python3-pbr (>= 1.8), python3-prettytable, python3-requests (>= 2.10.0), diff --git a/releasenotes/notes/cli-deprecation-241b9569b85f8fbd.yaml b/releasenotes/notes/cli-deprecation-241b9569b85f8fbd.yaml new file mode 100644 index 0000000..d156131 --- /dev/null +++ b/releasenotes/notes/cli-deprecation-241b9569b85f8fbd.yaml @@ -0,0 +1,4 @@ +--- +other: + - The 'senlin' CLI will be removed in April 2017. This message is now + explicitly printed when senlin CLI commands are invoked. diff --git a/releasenotes/notes/cluster-collect-a9d1bc8c2e799c7c.yaml b/releasenotes/notes/cluster-collect-a9d1bc8c2e799c7c.yaml new file mode 100644 index 0000000..9724c9c --- /dev/null +++ b/releasenotes/notes/cluster-collect-a9d1bc8c2e799c7c.yaml @@ -0,0 +1,5 @@ +--- +features: + - A new command 'senlin cluster-collect' and its corresponding OSC plugin + command has been added. This new command can be used to aggregate a + specific property across a cluster. diff --git a/releasenotes/notes/cluster-policy-list-42ff03ef25d64dd1.yaml b/releasenotes/notes/cluster-policy-list-42ff03ef25d64dd1.yaml new file mode 100644 index 0000000..55f65ec --- /dev/null +++ b/releasenotes/notes/cluster-policy-list-42ff03ef25d64dd1.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - The cluster policy list command was broken by new SDK changes and then + fixed. The 'enabled' field is now renamed to 'is_enabled'. diff --git a/releasenotes/notes/cluster-run-210247ab70b289a5.yaml b/releasenotes/notes/cluster-run-210247ab70b289a5.yaml new file mode 100644 index 0000000..064f3b6 --- /dev/null +++ b/releasenotes/notes/cluster-run-210247ab70b289a5.yaml @@ -0,0 +1,5 @@ +--- +features: + - A new CLI command 'senlin cluster-run' and a new OSC plugin command + 'openstack cluster run' have been added. Use the 'help' command to find + out how to use it. diff --git a/releasenotes/notes/deletion-output-a841931367a2689d.yaml b/releasenotes/notes/deletion-output-a841931367a2689d.yaml new file mode 100644 index 0000000..a8f3fad --- /dev/null +++ b/releasenotes/notes/deletion-output-a841931367a2689d.yaml @@ -0,0 +1,5 @@ +--- +features: + - The senlin CLI 'node-delete' and the OSC plugin command + 'cluster node delete' now outputs the action IDs when successful. Error + messages are printed when appropriate. diff --git a/releasenotes/notes/micro-version-a292ce3b00d886af.yaml b/releasenotes/notes/micro-version-a292ce3b00d886af.yaml new file mode 100644 index 0000000..6ed3285 --- /dev/null +++ b/releasenotes/notes/micro-version-a292ce3b00d886af.yaml @@ -0,0 +1,4 @@ +--- +features: + - The senlinclient now supports API micro-versioning. Current supported + version is 'clustering 1.2'. diff --git a/releasenotes/notes/policy-validate-193a5ebb7db3440a.yaml b/releasenotes/notes/policy-validate-193a5ebb7db3440a.yaml new file mode 100644 index 0000000..d1bfb3c --- /dev/null +++ b/releasenotes/notes/policy-validate-193a5ebb7db3440a.yaml @@ -0,0 +1,4 @@ +--- +features: + - A policy-validate command has been added to senlin command line. + OSC support is added as well. diff --git a/releasenotes/notes/profile-validate-587f1a964e93c0bf.yaml b/releasenotes/notes/profile-validate-587f1a964e93c0bf.yaml new file mode 100644 index 0000000..2f466e8 --- /dev/null +++ b/releasenotes/notes/profile-validate-587f1a964e93c0bf.yaml @@ -0,0 +1,4 @@ +--- +features: + - A profile-validate command has been added to command line. It can be + used for validating the spec of a profile without creating it. diff --git a/releasenotes/notes/python-3.5-c9fd8e34c4046357.yaml b/releasenotes/notes/python-3.5-c9fd8e34c4046357.yaml new file mode 100644 index 0000000..9c64d6f --- /dev/null +++ b/releasenotes/notes/python-3.5-c9fd8e34c4046357.yaml @@ -0,0 +1,3 @@ +--- +features: + - The support to python 3.5 has been verified and gated. diff --git a/releasenotes/notes/receiver-create-8305d4efbdf35f35.yaml b/releasenotes/notes/receiver-create-8305d4efbdf35f35.yaml new file mode 100644 index 0000000..dea8a54 --- /dev/null +++ b/releasenotes/notes/receiver-create-8305d4efbdf35f35.yaml @@ -0,0 +1,5 @@ +--- +other: + - The receiver creation command (both senlin CLI and OSC plugin command) + now allow 'cluster' and 'action' to be left unspecified if the receiver + type is not 'webhook'. diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index 5a2629f..f662fbd 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import pbr.version + # Senlin Release Notes documentation build configuration file, created by # sphinx-quickstart on Tue Nov 3 17:40:50 2015. # @@ -63,8 +65,8 @@ copyright = u'2015, Senlin Developers' # built documents. # # The short X.Y version. -import pbr.version -senlin_version = pbr.version.VersionInfo('python-muranoclient') + +senlin_version = pbr.version.VersionInfo('python-senlinclient') # The full version, including alpha/beta/rc tags. release = senlin_version.version_string_with_vcs() # The short X.Y version. diff --git a/releasenotes/source/locale/zh_CN/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/zh_CN/LC_MESSAGES/releasenotes.po new file mode 100644 index 0000000..5cdb4b3 --- /dev/null +++ b/releasenotes/source/locale/zh_CN/LC_MESSAGES/releasenotes.po @@ -0,0 +1,40 @@ +# zzxwill , 2016. #zanata +msgid "" +msgstr "" +"Project-Id-Version: Senlin Client Release Notes 0.5.1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-07-04 03:44+0000\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"PO-Revision-Date: 2016-06-25 02:41+0000\n" +"Last-Translator: zzxwill \n" +"Language-Team: Chinese (China)\n" +"Language: zh-CN\n" +"X-Generator: Zanata 3.7.3\n" +"Plural-Forms: nplurals=1; plural=0\n" + +msgid "0.5.0" +msgstr "0.5.0" + +msgid "Added command for node-check and node-recover." +msgstr "已为node-check和node-recover添加了命令。" + +msgid "Current Series Release Notes" +msgstr "当前版本发布说明" + +msgid "New Features" +msgstr "新特性" + +msgid "" +"OSC commands for cluster scaling are changed from 'cluster scale in' and " +"'cluster scale out' to 'cluster shrink' and 'cluster expand' respectively." +msgstr "" +"集群扩展的OSC命令分别从'cluster scale in'和'cluster scale out'改成了'cluster " +"shrink'和'cluster expand'。" + +msgid "Senlin Client Release Notes" +msgstr "Senlin Client发布说明" + +msgid "Upgrade Notes" +msgstr "升级说明" diff --git a/requirements.txt b/requirements.txt index e0318fe..32e9b0c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,14 +4,13 @@ Babel>=2.3.4 # BSD pbr>=1.6 # Apache-2.0 -cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 PrettyTable<0.8,>=0.7 # BSD -openstacksdk>=0.8.6 # Apache-2.0 +openstacksdk>=0.9.4 # Apache-2.0 +osc-lib>=1.0.2 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 -oslo.utils>=3.11.0 # Apache-2.0 -python-heatclient>=1.1.0 # Apache-2.0 -python-openstackclient>=2.1.0 # Apache-2.0 +oslo.utils>=3.16.0 # Apache-2.0 +python-heatclient>=1.4.0 # Apache-2.0 PyYAML>=3.1.0 # MIT requests>=2.10.0 # Apache-2.0 six>=1.9.0 # MIT diff --git a/senlinclient/cliargs.py b/senlinclient/cliargs.py index 694d017..e2f28dc 100644 --- a/senlinclient/cliargs.py +++ b/senlinclient/cliargs.py @@ -13,7 +13,6 @@ import argparse from senlinclient.common.i18n import _ -from senlinclient.common import sdk from senlinclient.common import utils @@ -140,35 +139,6 @@ def add_global_identity_args(parser): default=utils.env('OS_ACCESS_INFO'), help=_('Access info, defaults to env[OS_ACCESS_INFO]')) - parser.add_argument( - '--os-api-name', dest='user_preferences', - metavar='=', - action=sdk.ProfileAction, - default=sdk.ProfileAction.env('OS_API_NAME'), - help=_('Desired API names, defaults to env[OS_API_NAME]')) - - parser.add_argument( - '--os-api-region', dest='user_preferences', - metavar='=', - action=sdk.ProfileAction, - default=sdk.ProfileAction.env('OS_API_REGION', 'OS_REGION_NAME'), - help=_('Desired API region, defaults to env[OS_API_REGION]')) - - parser.add_argument( - '--os-api-version', dest='user_preferences', - metavar='=', - action=sdk.ProfileAction, - default=sdk.ProfileAction.env('OS_API_VERSION'), - help=_('Desired API versions, defaults to env[OS_API_VERSION]')) - - parser.add_argument( - '--os-api-interface', dest='user_preferences', - metavar='=', - action=sdk.ProfileAction, - default=sdk.ProfileAction.env('OS_INTERFACE'), - help=_('Desired API interface, defaults to env[OS_INTERFACE]')) - - # parser.add_argument( # '--os-cert', # help=_('Path of certificate file to use in SSL connection. This ' diff --git a/senlinclient/common/format_utils.py b/senlinclient/common/format_utils.py index 370729d..f7973bc 100644 --- a/senlinclient/common/format_utils.py +++ b/senlinclient/common/format_utils.py @@ -10,10 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -from cliff import show +from osc_lib.command import command -class RawFormat(show.ShowOne): +class RawFormat(command.ShowOne): def produce_output(self, parsed_args, column_names, data): if data is None: diff --git a/senlinclient/common/sdk.py b/senlinclient/common/sdk.py index 65caead..6f1a7c2 100644 --- a/senlinclient/common/sdk.py +++ b/senlinclient/common/sdk.py @@ -10,118 +10,12 @@ # License for the specific language governing permissions and limitations # under the License. -import argparse from openstack import connection from openstack import exceptions from openstack import profile -from openstack import resource as base -from openstack import utils -import os - -from six.moves.urllib import parse as url_parse from senlinclient.common import exc -# Alias here for consistency -prop = base.prop - - -class ProfileAction(argparse.Action): - """A custom action to parse user preferences as key=value pairs - - Stores results in users preferences object. - """ - prof = profile.Profile() - - @classmethod - def env(cls, *vars): - for v in vars: - values = os.environ.get(v, None) - if values is None: - continue - cls.set_option(v, values) - return cls.prof - return cls.prof - - @classmethod - def set_option(cls, var, values): - if var == '--os-extensions': - cls.prof.load_extension(values) - return - if var == 'OS_REGION_NAME': - var = 'region' - var = var.replace('--os-api-', '') - var = var.replace('OS_API_', '') - var = var.lower() - for kvp in values.split(','): - if '=' in kvp: - service, value = kvp.split('=') - else: - service = cls.prof.ALL - value = kvp - if var == 'name': - cls.prof.set_name(service, value) - elif var == 'region': - cls.prof.set_region(service, value) - elif var == 'version': - cls.prof.set_version(service, value) - elif var == 'interface': - cls.prof.set_interface(service, value) - - def __call__(self, parser, namespace, values, option_string=None): - if getattr(namespace, self.dest, None) is None: - setattr(namespace, self.dest, ProfileAction.prof) - self.set_option(option_string, values) - - -class Resource(base.Resource): - """Senlin version of resource. - - These classes are here because the OpenStack SDK base version is making - some assumptions about operations that cannot be satisfied in Senlin. - """ - - def create(self, session, extra_attrs=False): - """Create a remote resource from this instance. - - :param extra_attrs: If true, all attributions that - included in response will be collected and returned - to user after resource creation - - """ - resp = self.create_by_id(session, self._attrs, self.id, path_args=self) - self._attrs[self.id_attribute] = resp[self.id_attribute] - if extra_attrs: - for attr in resp: - self._attrs[attr] = resp[attr] - self._reset_dirty() - - return self - - @classmethod - def get_data_with_args(cls, session, resource_id, args=None): - if not cls.allow_retrieve: - raise exceptions.MethodNotSupported('list') - - url = utils.urljoin(cls.base_path, resource_id) - if args: - args.pop('id') - url = '%s?%s' % (url, url_parse.urlencode(args)) - resp = session.get(url, endpoint_filter=cls.service) - body = resp.json() - if cls.resource_key: - body = body[cls.resource_key] - - return body - - def get_with_args(self, session, args=None): - body = self.get_data_with_args(session, self.id, args=args) - - self._attrs.update(body) - self._loaded = True - - return self - def create_connection(prof=None, user_agent=None, **kwargs): if not prof: @@ -133,6 +27,7 @@ def create_connection(prof=None, user_agent=None, **kwargs): if region_name: prof.set_region('clustering', region_name) + prof.set_api_version('clustering', '1.2') try: conn = connection.Connection(profile=prof, user_agent=user_agent, **kwargs) diff --git a/senlinclient/common/utils.py b/senlinclient/common/utils.py index bbdd52e..2e003c6 100644 --- a/senlinclient/common/utils.py +++ b/senlinclient/common/utils.py @@ -80,6 +80,8 @@ def format_nested_dict(d, fields, column_names): value = d[field] if not isinstance(value, six.string_types): value = jsonutils.dumps(value, indent=2, ensure_ascii=False) + if value is None: + value = '-' pt.add_row([field, value.strip('"')]) return pt.get_string() @@ -129,14 +131,16 @@ def _print_list(objs, fields, formatters=None, sortby_index=0, row = [] for field in fields: if field in formatters: - row.append(formatters[field](o)) + data = formatters[field](o) else: if field in mixed_case_fields: field_name = field.replace(' ', '_') else: field_name = field.lower().replace(' ', '_') data = getattr(o, field_name, '') - row.append(data) + if data is None: + data = '-' + row.append(data) pt.add_row(row) if six.PY3: @@ -169,9 +173,12 @@ def print_dict(d, formatters=None): for field in d.keys(): if field in formatters: - pt.add_row([field, formatters[field](d[field])]) + data = formatters[field](d[field]) else: - pt.add_row([field, d[field]]) + data = d[field] + if data is None: + data = '-' + pt.add_row([field, data]) content = pt.get_string(sortby='Property') if six.PY3: @@ -180,6 +187,14 @@ def print_dict(d, formatters=None): print(encodeutils.safe_encode(content)) +def print_action_result(rid, res): + if res[0] == "OK": + output = _("accepted by action %s") % res[1] + else: + output = _("failed due to '%s'") % res[1] + print(_(" %(cid)s: %(output)s") % {"cid": rid, "output": output}) + + def format_parameters(params, parse_semicolon=True): """Reformat parameters into dict of format expected by the API.""" if not params: @@ -240,7 +255,7 @@ def process_stack_spec(spec): new_spec = { # TODO(Qiming): add context support 'disable_rollback': spec.get('disable_rollback', True), - 'context': spec.get('context', {}), + 'context': spec.get('context', {}), 'parameters': spec.get('parameters', {}), 'timeout': spec.get('timeout', 60), 'template': template, diff --git a/senlinclient/locale/senlinclient.pot b/senlinclient/locale/senlinclient.pot deleted file mode 100644 index c6600aa..0000000 --- a/senlinclient/locale/senlinclient.pot +++ /dev/null @@ -1,1013 +0,0 @@ -# Translations template for python-senlinclient. -# Copyright (C) 2016 ORGANIZATION -# This file is distributed under the same license as the python-senlinclient -# project. -# FIRST AUTHOR , 2016. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: python-senlinclient 0.3.1.dev32\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-02-20 23:26-0500\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.1.1\n" - -#: senlinclient/cliargs.py:24 -msgid "Authentication plugin, default to env[OS_AUTH_PLUGIN]" -msgstr "" - -#: senlinclient/cliargs.py:29 -msgid "Defaults to env[OS_AUTH_URL]" -msgstr "" - -#: senlinclient/cliargs.py:34 -msgid "Defaults to env[OS_PROJECT_ID]." -msgstr "" - -#: senlinclient/cliargs.py:39 -msgid "Defaults to env[OS_PROJECT_NAME]." -msgstr "" - -#: senlinclient/cliargs.py:44 -msgid "Defaults to env[OS_TENANT_ID]." -msgstr "" - -#: senlinclient/cliargs.py:49 -msgid "Defaults to env[OS_TENANT_NAME]." -msgstr "" - -#: senlinclient/cliargs.py:54 -msgid "Domain ID for scope of authorization, defaults to env[OS_DOMAIN_ID]." -msgstr "" - -#: senlinclient/cliargs.py:60 -msgid "Domain name for scope of authorization, defaults to env[OS_DOMAIN_NAME]." -msgstr "" - -#: senlinclient/cliargs.py:67 -msgid "" -"Project domain ID for scope of authorization, defaults to " -"env[OS_PROJECT_DOMAIN_ID]." -msgstr "" - -#: senlinclient/cliargs.py:74 -msgid "" -"Project domain name for scope of authorization, defaults to " -"env[OS_PROJECT_DOMAIN_NAME]." -msgstr "" - -#: senlinclient/cliargs.py:81 -msgid "" -"User domain ID for scope of authorization, defaults to " -"env[OS_USER_DOMAIN_ID]." -msgstr "" - -#: senlinclient/cliargs.py:88 -msgid "" -"User domain name for scope of authorization, defaults to " -"env[OS_USER_DOMAIN_NAME]." -msgstr "" - -#: senlinclient/cliargs.py:94 -msgid "Defaults to env[OS_USERNAME]." -msgstr "" - -#: senlinclient/cliargs.py:99 -msgid "Defaults to env[OS_USER_ID]." -msgstr "" - -#: senlinclient/cliargs.py:104 -msgid "Defaults to env[OS_PASSWORD]" -msgstr "" - -#: senlinclient/cliargs.py:109 -msgid "Defaults to env[OS_TRUST_ID]" -msgstr "" - -#: senlinclient/cliargs.py:116 -msgid "" -"Path of CA TLS certificate(s) used to verify the remote server's " -"certificate. Without this option senlin looks for the default system CA " -"certificates." -msgstr "" - -#: senlinclient/cliargs.py:123 -msgid "Verify server certificate (default)" -msgstr "" - -#: senlinclient/cliargs.py:127 -msgid "" -"Explicitly allow senlinclient to perform \"insecure SSL\" (HTTPS) " -"requests. The server's certificate will not be verified against any " -"certificate authorities. This option should be used with caution." -msgstr "" - -#: senlinclient/cliargs.py:135 -msgid "" -"A string token to bootstrap the Keystone database, defaults to " -"env[OS_TOKEN]" -msgstr "" - -#: senlinclient/cliargs.py:141 -msgid "Access info, defaults to env[OS_ACCESS_INFO]" -msgstr "" - -#: senlinclient/cliargs.py:148 -msgid "Desired API names, defaults to env[OS_API_NAME]" -msgstr "" - -#: senlinclient/cliargs.py:155 -msgid "Desired API region, defaults to env[OS_API_REGION]" -msgstr "" - -#: senlinclient/cliargs.py:162 -msgid "Desired API versions, defaults to env[OS_API_VERSION]" -msgstr "" - -#: senlinclient/cliargs.py:169 -msgid "Desired API interface, defaults to env[OS_INTERFACE]" -msgstr "" - -#: senlinclient/cliargs.py:191 -msgid "Shows the client version and exits." -msgstr "" - -#: senlinclient/cliargs.py:196 -msgid "Defaults to env[SENLINCLIENT_DEBUG]." -msgstr "" - -#: senlinclient/cliargs.py:200 -msgid "Print more verbose output." -msgstr "" - -#: senlinclient/cliargs.py:204 -msgid "" -"Number of seconds to wait for an API response, defaults to system socket " -"timeout" -msgstr "" - -#: senlinclient/cliargs.py:210 -msgid "Version number for Senlin API to use, Default to \"1\"." -msgstr "" - -#: senlinclient/shell.py:105 -msgid "" -"HMAC key to use for encrypting context data for performance profiling of " -"operation. This key should be the value of HMAC key configured in " -"osprofiler middleware in senlin, it is specified in the paste deploy " -"configuration (/etc/senlin/api-paste.ini). Without the key, profiling " -"will not be triggered even if osprofiler is enabled on server side." -msgstr "" - -#: senlinclient/shell.py:134 -msgid "Display help for ." -msgstr "" - -#: senlinclient/shell.py:151 senlinclient/tests/unit/test_shell.py:261 -msgid "You must provide an auth url via --os-auth-url (or env[OS_AUTH_URL])" -msgstr "" - -#: senlinclient/shell.py:157 senlinclient/tests/unit/test_shell.py:269 -msgid "You must provide a user name, a user_id or a token for authentication" -msgstr "" - -#: senlinclient/shell.py:163 -msgid "" -"Both user name and user ID are specified, Senlin will use user ID for " -"authentication" -msgstr "" - -#: senlinclient/shell.py:165 senlinclient/shell.py:195 -#: senlinclient/shell.py:203 senlinclient/tests/unit/test_shell.py:316 -#: senlinclient/tests/unit/test_shell.py:325 -#, python-format -msgid "WARNING: %s" -msgstr "" - -#: senlinclient/shell.py:170 senlinclient/tests/unit/test_shell.py:289 -msgid "" -"Either user domain ID (--user-domain-id / env[OS_USER_DOMAIN_ID]) or user" -" domain name (--user-domain-name / env[OS_USER_DOMAIN_NAME]) must be " -"specified, because user name may not be unique." -msgstr "" - -#: senlinclient/shell.py:179 -#, python-format -msgid "You must provide a password for user %s" -msgstr "" - -#: senlinclient/shell.py:187 senlinclient/tests/unit/test_shell.py:305 -msgid "" -"Either project/tenant ID or project/tenant name must be specified, or " -"else Senlin cannot know which project to use." -msgstr "" - -#: senlinclient/shell.py:192 senlinclient/tests/unit/test_shell.py:313 -msgid "" -"Neither project ID nor project name is specified. Senlin will use user's " -"default project which may result in authentication error." -msgstr "" - -#: senlinclient/shell.py:200 senlinclient/tests/unit/test_shell.py:322 -msgid "" -"Both project/tenant name and project/tenant ID are specified, Senlin will" -" use project ID for authentication" -msgstr "" - -#: senlinclient/shell.py:210 senlinclient/tests/unit/test_shell.py:333 -msgid "" -"Either project domain ID (--project-domain-id / " -"env[OS_PROJECT_DOMAIN_ID]) orr project domain name (--project-domain-name" -" / env[OS_PROJECT_DOMAIN_NAME must be specified, because project/tenant " -"name may not be unique." -msgstr "" - -#: senlinclient/shell.py:246 -msgid "Type \"senlin help \" for help on a specific command." -msgstr "" - -#: senlinclient/shell.py:299 -#, python-format -msgid "Trace ID: %s" -msgstr "" - -#: senlinclient/shell.py:300 -#, python-format -msgid "" -"To display trace use next command:\n" -"osprofiler trace show --html %s " -msgstr "" - -#: senlinclient/shell.py:311 -msgid "... terminating senlin client" -msgstr "" - -#: senlinclient/common/exc.py:52 -msgid "Key \"error\" not exists" -msgstr "" - -#: senlinclient/common/exc.py:66 -#, python-format -msgid "" -"ERROR: %(message)s\n" -"%(traceback)s" -msgstr "" - -#: senlinclient/common/exc.py:70 -#, python-format -msgid "ERROR(%(code)s): %(message)s" -msgstr "" - -#: senlinclient/common/exc.py:275 -#, python-format -msgid "Unknown exception: %s" -msgstr "" - -#: senlinclient/common/exc.py:281 -#, python-format -msgid "Malformed exception record, missing field \"%s\"" -msgstr "" - -#: senlinclient/common/exc.py:282 -#, python-format -msgid "Original error record: %s" -msgstr "" - -#: senlinclient/common/utils.py:117 -#, python-format -msgid "" -"Field labels list %(labels)s has different number of elements than fields" -" list %(fields)s" -msgstr "" - -#: senlinclient/common/utils.py:199 -#, python-format -msgid "Malformed parameter(%s). Use the key=value format." -msgstr "" - -#: senlinclient/common/utils.py:217 senlinclient/common/utils.py:228 -#, python-format -msgid "The specified file is not a valid YAML file: %s" -msgstr "" - -#: senlinclient/common/utils.py:231 -msgid "No template found in the given spec file" -msgstr "" - -#: senlinclient/common/utils.py:259 -#, python-format -msgid "The format(%s) is unsupported." -msgstr "" - -#: senlinclient/osc/v1/node.py:34 -msgid "ID or name of cluster from which nodes are to be listed" -msgstr "" - -#: senlinclient/osc/v1/node.py:39 -msgid "" -"Filter parameters to apply on returned nodes. This can be specified " -"multiple times, or once with parameters separated by a semicolon. The " -"valid filter keys are: ['status','name']" -msgstr "" - -#: senlinclient/osc/v1/node.py:48 -msgid "" -"Sorting option which is a string containing a list of keys separated by " -"commas. Each key can be optionally appended by a sort direction (:asc or " -":desc). The valid sort keys are:'['index', 'name', 'status', 'init_at', " -"'created_at', 'updated_at']'" -msgstr "" - -#: senlinclient/osc/v1/node.py:57 -msgid "Limit the number of nodes returned" -msgstr "" - -#: senlinclient/osc/v1/node.py:62 -msgid "Only return nodes that appear after the given node ID" -msgstr "" - -#: senlinclient/osc/v1/node.py:67 -msgid "" -"Indicate that this node list should include nodes from all projects. This" -" option is subject to access policy checking. Default is False" -msgstr "" - -#: senlinclient/osc/v1/node.py:74 -msgid "Print full IDs in list" -msgstr "" - -#: senlinclient/osc/v1/profile.py:90 senlinclient/v1/shell.py:81 -msgid "Limit the number of profiles returned." -msgstr "" - -#: senlinclient/osc/v1/profile.py:95 senlinclient/v1/shell.py:83 -msgid "Only return profiles that appear after the given ID." -msgstr "" - -#: senlinclient/osc/v1/profile.py:100 -msgid "" -"Sorting option which is a string containing a list of keys separated by " -"commas. Each key can be optionally appended by a sort direction (:asc or " -":desc). The valid sort_keys are:['type', 'name', 'created_at', " -"'updated_at']" -msgstr "" - -#: senlinclient/osc/v1/profile.py:109 senlinclient/v1/shell.py:89 -msgid "" -"Indicate that the list should include profiles from all projects. This " -"option is subject to access policy checking. Default is False." -msgstr "" - -#: senlinclient/osc/v1/profile.py:117 senlinclient/v1/shell.py:93 -#: senlinclient/v1/shell.py:268 senlinclient/v1/shell.py:382 -#: senlinclient/v1/shell.py:518 senlinclient/v1/shell.py:693 -#: senlinclient/v1/shell.py:796 senlinclient/v1/shell.py:958 -#: senlinclient/v1/shell.py:1073 senlinclient/v1/shell.py:1129 -msgid "Print full IDs in list." -msgstr "" - -#: senlinclient/osc/v1/profile.py:157 -msgid "Name or ID of profile(s) to delete" -msgstr "" - -#: senlinclient/osc/v1/profile.py:162 -msgid "Skip yes/no prompt (assume yes)" -msgstr "" - -#: senlinclient/osc/v1/profile.py:173 -msgid "Are you sure you want to delete this profile(s) [y/N]?" -msgstr "" - -#: senlinclient/osc/v1/profile.py:193 -#, python-format -msgid "Failed to delete %(count)s of the %(total)s specified profile(s)." -msgstr "" - -#: senlinclient/osc/v1/profile.py:211 -msgid "The spec file used to create the profile" -msgstr "" - -#: senlinclient/osc/v1/profile.py:216 senlinclient/osc/v1/profile.py:272 -msgid "" -"Metadata values to be attached to the profile. This can be specified " -"multiple times, or once with key-value pairs separated by a semicolon" -msgstr "" - -#: senlinclient/osc/v1/profile.py:224 -msgid "Name of the profile to create" -msgstr "" - -#: senlinclient/osc/v1/profile.py:237 -#: senlinclient/tests/unit/v1/test_shell.py:207 senlinclient/v1/shell.py:152 -msgid "Missing 'type' key in spec file." -msgstr "" - -#: senlinclient/osc/v1/profile.py:239 -#: senlinclient/tests/unit/v1/test_shell.py:215 senlinclient/v1/shell.py:154 -msgid "Missing 'version' key in spec file." -msgstr "" - -#: senlinclient/osc/v1/profile.py:241 -#: senlinclient/tests/unit/v1/test_shell.py:223 senlinclient/v1/shell.py:156 -msgid "Missing 'properties' key in spec file." -msgstr "" - -#: senlinclient/osc/v1/profile.py:267 -msgid "The new name for the profile" -msgstr "" - -#: senlinclient/osc/v1/profile.py:280 -msgid "Name or ID of the profile to update" -msgstr "" - -#: senlinclient/osc/v1/profile.py:299 senlinclient/v1/shell.py:122 -#: senlinclient/v1/shell.py:200 -#, python-format -msgid "Profile not found: %s" -msgstr "" - -#: senlinclient/osc/v1/profile_type.py:53 -msgid "Profile type to retrieve" -msgstr "" - -#: senlinclient/osc/v1/profile_type.py:64 senlinclient/v1/shell.py:63 -#, python-format -msgid "Profile Type not found: %s" -msgstr "" - -#: senlinclient/tests/unit/test_shell.py:250 -#, python-format -msgid "'%s' is not a valid subcommand" -msgstr "" - -#: senlinclient/tests/unit/test_shell.py:279 -msgid "" -"WARNING: Both user name and user ID are specified, Senlin will use user " -"ID for authentication" -msgstr "" - -#: senlinclient/tests/unit/test_utils.py:62 -msgid "Malformed parameter(status:ACTIVE). Use the key=value format." -msgstr "" - -#: senlinclient/tests/unit/v1/test_shell.py:104 -msgid "Profile Type not found: wrong_type" -msgstr "" - -#: senlinclient/tests/unit/v1/test_shell.py:167 -msgid "Profile not found: wrong_id" -msgstr "" - -#: senlinclient/tests/unit/v1/test_shell.py:269 -msgid "Profile not found: FAKE_ID" -msgstr "" - -#: senlinclient/tests/unit/v1/test_shell.py:289 senlinclient/v1/shell.py:218 -msgid "Failed to delete some of the specified profile(s)." -msgstr "" - -#: senlinclient/tests/unit/v1/test_shell.py:335 -msgid "Policy type not found: BAD" -msgstr "" - -#: senlinclient/tests/unit/v1/test_shell.py:409 -msgid "Receiver not found: wrong_id" -msgstr "" - -#: senlinclient/tests/unit/v1/test_shell.py:461 senlinclient/v1/shell.py:1047 -msgid "Failed to delete some of the specified receiver(s)." -msgstr "" - -#: senlinclient/tests/unit/v1/test_shell.py:519 -msgid "Policy not found: fake_policy_id" -msgstr "" - -#: senlinclient/tests/unit/v1/test_shell.py:589 senlinclient/v1/shell.py:355 -msgid "Failed to delete some of the specified policy(s)." -msgstr "" - -#: senlinclient/tests/unit/v1/test_shell.py:677 senlinclient/v1/shell.py:469 -msgid "Failed to delete some of the specified clusters." -msgstr "" - -#: senlinclient/tests/unit/v1/test_shell.py:786 senlinclient/v1/shell.py:607 -msgid "Only one of 'capacity', 'adjustment' and 'percentage' can be specified." -msgstr "" - -#: senlinclient/tests/unit/v1/test_shell.py:812 senlinclient/v1/shell.py:615 -msgid "Cluster capacity must be larger than or equal to zero." -msgstr "" - -#: senlinclient/tests/unit/v1/test_shell.py:830 senlinclient/v1/shell.py:622 -msgid "Adjustment cannot be zero." -msgstr "" - -#: senlinclient/tests/unit/v1/test_shell.py:847 senlinclient/v1/shell.py:628 -msgid "Percentage cannot be zero." -msgstr "" - -#: senlinclient/tests/unit/v1/test_shell.py:858 senlinclient/v1/shell.py:634 -msgid "Min step is only used with percentage." -msgstr "" - -#: senlinclient/tests/unit/v1/test_shell.py:870 senlinclient/v1/shell.py:638 -msgid "Min size cannot be less than zero." -msgstr "" - -#: senlinclient/tests/unit/v1/test_shell.py:883 senlinclient/v1/shell.py:640 -msgid "Min size cannot be larger than max size." -msgstr "" - -#: senlinclient/tests/unit/v1/test_shell.py:896 senlinclient/v1/shell.py:643 -msgid "Min size cannot be larger than the specified capacity" -msgstr "" - -#: senlinclient/tests/unit/v1/test_shell.py:909 senlinclient/v1/shell.py:648 -msgid "Max size cannot be less than the specified capacity." -msgstr "" - -#: senlinclient/tests/unit/v1/test_shell.py:1135 senlinclient/v1/shell.py:900 -msgid "Failed to delete some of the specified nodes." -msgstr "" - -#: senlinclient/tests/unit/v1/test_shell.py:1281 -msgid "Action not found: fake_id" -msgstr "" - -#: senlinclient/v1/shell.py:52 -msgid "Profile type to retrieve." -msgstr "" - -#: senlinclient/v1/shell.py:55 senlinclient/v1/shell.py:236 -#, python-format -msgid "The template output format, one of: %s." -msgstr "" - -#: senlinclient/v1/shell.py:76 -msgid "" -"Filter parameters to apply on returned profiles. This can be specified " -"multiple times, or once with parameters separated by a semicolon." -msgstr "" - -#: senlinclient/v1/shell.py:85 senlinclient/v1/shell.py:260 -#: senlinclient/v1/shell.py:369 senlinclient/v1/shell.py:689 -#: senlinclient/v1/shell.py:784 senlinclient/v1/shell.py:950 -#: senlinclient/v1/shell.py:1065 senlinclient/v1/shell.py:1121 -msgid "" -"Sorting option which is a string containing a list of keys separated by " -"commas. Each key can be optionally appened by a sort direction (:asc or " -":desc)" -msgstr "" - -#: senlinclient/v1/shell.py:136 -msgid "The spec file used to create the profile." -msgstr "" - -#: senlinclient/v1/shell.py:138 senlinclient/v1/shell.py:182 -msgid "" -"Metadata values to be attached to the profile. This can be specified " -"multiple times, or once with key-value pairs separated by a semicolon." -msgstr "" - -#: senlinclient/v1/shell.py:143 -msgid "Name of the profile to create." -msgstr "" - -#: senlinclient/v1/shell.py:173 -msgid "Name or ID of profile to show." -msgstr "" - -#: senlinclient/v1/shell.py:180 -msgid "The new name for the profile." -msgstr "" - -#: senlinclient/v1/shell.py:187 -msgid "Name or ID of the profile to update." -msgstr "" - -#: senlinclient/v1/shell.py:206 -msgid "Name or ID of profile(s) to delete." -msgstr "" - -#: senlinclient/v1/shell.py:233 -msgid "Policy type to retrieve." -msgstr "" - -#: senlinclient/v1/shell.py:243 -#, python-format -msgid "Policy type not found: %s" -msgstr "" - -#: senlinclient/v1/shell.py:256 -msgid "Limit the number of policies returned." -msgstr "" - -#: senlinclient/v1/shell.py:258 -msgid "Only return policies that appear after the given ID." -msgstr "" - -#: senlinclient/v1/shell.py:264 -msgid "" -"Indicate that the list should include policies from all projects. This " -"option is subject to access policy checking. Default is False." -msgstr "" - -#: senlinclient/v1/shell.py:294 -#, python-format -msgid "Policy not found: %s" -msgstr "" - -#: senlinclient/v1/shell.py:304 -msgid "The spec file used to create the policy." -msgstr "" - -#: senlinclient/v1/shell.py:306 -msgid "Name of the policy to create." -msgstr "" - -#: senlinclient/v1/shell.py:320 senlinclient/v1/shell.py:329 -msgid "Name of the policy to be updated." -msgstr "" - -#: senlinclient/v1/shell.py:327 -msgid "New name of the policy to be updated." -msgstr "" - -#: senlinclient/v1/shell.py:343 -msgid "Name or ID of policy(s) to delete." -msgstr "" - -#: senlinclient/v1/shell.py:364 -msgid "" -"Filter parameters to apply on returned clusters. This can be specified " -"multiple times, or once with parameters separated by a semicolon." -msgstr "" - -#: senlinclient/v1/shell.py:373 -msgid "Limit the number of clusters returned." -msgstr "" - -#: senlinclient/v1/shell.py:375 -msgid "Only return clusters that appear after the given cluster ID." -msgstr "" - -#: senlinclient/v1/shell.py:378 -msgid "" -"Indicate that the cluster list should include clusters from all projects." -" This option is subject to access policy checking. Default is False." -msgstr "" - -#: senlinclient/v1/shell.py:411 -#, python-format -msgid "Cluster not found: %s" -msgstr "" - -#: senlinclient/v1/shell.py:421 -msgid "Profile Id used for this cluster." -msgstr "" - -#: senlinclient/v1/shell.py:423 -msgid "Min size of the cluster. Default to 0." -msgstr "" - -#: senlinclient/v1/shell.py:425 -msgid "Max size of the cluster. Default to -1, means unlimited." -msgstr "" - -#: senlinclient/v1/shell.py:427 -msgid "" -"Desired capacity of the cluster. Default to min_size if min_size is " -"specified else 0." -msgstr "" - -#: senlinclient/v1/shell.py:430 -msgid "Cluster creation timeout in seconds." -msgstr "" - -#: senlinclient/v1/shell.py:432 senlinclient/v1/shell.py:479 -msgid "" -"Metadata values to be attached to the cluster. This can be specified " -"multiple times, or once with key-value pairs separated by a semicolon." -msgstr "" - -#: senlinclient/v1/shell.py:437 -msgid "Name of the cluster to create." -msgstr "" - -#: senlinclient/v1/shell.py:457 -msgid "Name or ID of cluster(s) to delete." -msgstr "" - -#: senlinclient/v1/shell.py:475 senlinclient/v1/shell.py:908 -msgid "ID of new profile to use." -msgstr "" - -#: senlinclient/v1/shell.py:477 -msgid "New timeout (in seconds) value for the cluster." -msgstr "" - -#: senlinclient/v1/shell.py:484 -msgid "New name for the cluster to update." -msgstr "" - -#: senlinclient/v1/shell.py:486 -msgid "Name or ID of cluster to be updated." -msgstr "" - -#: senlinclient/v1/shell.py:502 -msgid "Name or ID of cluster to show." -msgstr "" - -#: senlinclient/v1/shell.py:509 senlinclient/v1/shell.py:779 -msgid "" -"Filter parameters to apply on returned nodes. This can be specified " -"multiple times, or once with parameters separated by a semicolon." -msgstr "" - -#: senlinclient/v1/shell.py:514 senlinclient/v1/shell.py:788 -msgid "Limit the number of nodes returned." -msgstr "" - -#: senlinclient/v1/shell.py:516 senlinclient/v1/shell.py:790 -msgid "Only return nodes that appear after the given node ID." -msgstr "" - -#: senlinclient/v1/shell.py:520 -msgid "Name or ID of cluster to nodes from." -msgstr "" - -#: senlinclient/v1/shell.py:546 -msgid "ID of nodes to be added; multiple nodes can be separated with \",\"" -msgstr "" - -#: senlinclient/v1/shell.py:549 senlinclient/v1/shell.py:561 -#: senlinclient/v1/shell.py:591 senlinclient/v1/shell.py:666 -#: senlinclient/v1/shell.py:676 senlinclient/v1/shell.py:736 -#: senlinclient/v1/shell.py:750 senlinclient/v1/shell.py:762 -msgid "Name or ID of cluster to operate on." -msgstr "" - -#: senlinclient/v1/shell.py:558 -msgid "ID of nodes to be deleted; multiple nodes can be separated with \",\"." -msgstr "" - -#: senlinclient/v1/shell.py:570 -msgid "The desired number of nodes of the cluster." -msgstr "" - -#: senlinclient/v1/shell.py:572 -msgid "" -"A positive integer meaning the number of nodes to add, or a negative " -"integer indicating the number of nodes to remove." -msgstr "" - -#: senlinclient/v1/shell.py:576 -msgid "" -"A value that is interpreted as the percentage of size adjustment. This " -"value can be positive or negative." -msgstr "" - -#: senlinclient/v1/shell.py:579 -msgid "" -"An integer specifying the number of nodes for adjustment when " -" is specified." -msgstr "" - -#: senlinclient/v1/shell.py:582 -msgid "" -"A boolean specifying whether the resize should be performed on a best-" -"effort basis when the new capacity may go beyond size constraints." -msgstr "" - -#: senlinclient/v1/shell.py:586 -msgid "New lower bound of cluster size." -msgstr "" - -#: senlinclient/v1/shell.py:588 -msgid "" -"New upper bound of cluster size. A value of -1 indicates no upper limit " -"on cluster size." -msgstr "" - -#: senlinclient/v1/shell.py:664 -msgid "Number of nodes to be added to the specified cluster." -msgstr "" - -#: senlinclient/v1/shell.py:674 -msgid "Number of nodes to be deleted from the specified cluster." -msgstr "" - -#: senlinclient/v1/shell.py:684 -msgid "" -"Filter parameters to apply on returned results. This can be specified " -"multiple times, or once with parameters separated by a semicolon." -msgstr "" - -#: senlinclient/v1/shell.py:695 -msgid "Name or ID of cluster to query on." -msgstr "" - -#: senlinclient/v1/shell.py:721 -msgid "ID or name of the policy to query on." -msgstr "" - -#: senlinclient/v1/shell.py:723 -msgid "ID or name of the cluster to query on." -msgstr "" - -#: senlinclient/v1/shell.py:731 -msgid "ID or name of policy to be attached." -msgstr "" - -#: senlinclient/v1/shell.py:733 -msgid "Whether the policy should be enabled once attached. Default to enabled." -msgstr "" - -#: senlinclient/v1/shell.py:748 -msgid "ID or name of policy to be detached." -msgstr "" - -#: senlinclient/v1/shell.py:758 -msgid "ID or name of policy to be updated." -msgstr "" - -#: senlinclient/v1/shell.py:760 -msgid "Whether the policy should be enabled." -msgstr "" - -#: senlinclient/v1/shell.py:777 -msgid "ID or name of cluster from which nodes are to be listed." -msgstr "" - -#: senlinclient/v1/shell.py:792 -msgid "" -"Indicate that this node list should include nodes from all projects. This" -" option is subject to access policy checking. Default is False." -msgstr "" - -#: senlinclient/v1/shell.py:837 senlinclient/v1/shell.py:924 -#, python-format -msgid "Node not found: %s" -msgstr "" - -#: senlinclient/v1/shell.py:852 -msgid "Profile Id used for this node." -msgstr "" - -#: senlinclient/v1/shell.py:854 -msgid "Cluster Id for this node." -msgstr "" - -#: senlinclient/v1/shell.py:856 senlinclient/v1/shell.py:910 -msgid "Role for this node in the specific cluster." -msgstr "" - -#: senlinclient/v1/shell.py:858 -msgid "" -"Metadata values to be attached to the node. This can be specified " -"multiple times, or once with key-value pairs separated by a semicolon." -msgstr "" - -#: senlinclient/v1/shell.py:863 -msgid "Name of the node to create." -msgstr "" - -#: senlinclient/v1/shell.py:879 -msgid "Include physical object details." -msgstr "" - -#: senlinclient/v1/shell.py:881 -msgid "Name or ID of the node to show the details for." -msgstr "" - -#: senlinclient/v1/shell.py:888 -msgid "Name or ID of node(s) to delete." -msgstr "" - -#: senlinclient/v1/shell.py:906 -msgid "New name for the node." -msgstr "" - -#: senlinclient/v1/shell.py:912 -msgid "" -"Metadata values to be attached to the node. Metadata can be specified " -"multiple times, or once with key-value pairs separated by a semicolon." -msgstr "" - -#: senlinclient/v1/shell.py:917 -msgid "Name or ID of node to update." -msgstr "" - -#: senlinclient/v1/shell.py:941 -msgid "" -"Filter parameters to apply on returned receivers. This can be specified " -"multiple times, or once with parameters separated by a semicolon." -msgstr "" - -#: senlinclient/v1/shell.py:946 -msgid "Limit the number of receivers returned." -msgstr "" - -#: senlinclient/v1/shell.py:948 -msgid "Only return receivers that appear after the given ID." -msgstr "" - -#: senlinclient/v1/shell.py:954 -msgid "" -"Indicate that the list should include receivers from all projects. This " -"option is subject to access policy checking. Default is False." -msgstr "" - -#: senlinclient/v1/shell.py:989 -#, python-format -msgid "Receiver not found: %s" -msgstr "" - -#: senlinclient/v1/shell.py:1001 -msgid "Name or ID of the receiver to show." -msgstr "" - -#: senlinclient/v1/shell.py:1008 -msgid "Type of the receiver to create." -msgstr "" - -#: senlinclient/v1/shell.py:1010 -msgid "Targeted cluster for this receiver." -msgstr "" - -#: senlinclient/v1/shell.py:1012 -msgid "Name or ID of the targeted action to be triggered." -msgstr "" - -#: senlinclient/v1/shell.py:1014 -msgid "" -"A dictionary of parameters that will be passed to target action when the " -"receiver is triggered." -msgstr "" - -#: senlinclient/v1/shell.py:1018 -msgid "Name of the receiver to create." -msgstr "" - -#: senlinclient/v1/shell.py:1035 -msgid "Name or ID of receiver(s) to delete." -msgstr "" - -#: senlinclient/v1/shell.py:1056 -msgid "" -"Filter parameters to apply on returned events. This can be specified " -"multiple times, or once with parameters separated by a semicolon." -msgstr "" - -#: senlinclient/v1/shell.py:1061 -msgid "Limit the number of events returned." -msgstr "" - -#: senlinclient/v1/shell.py:1063 -msgid "Only return events that appear after the given event ID." -msgstr "" - -#: senlinclient/v1/shell.py:1069 -msgid "" -"Whether events from all projects should be listed. Default to False. " -"Setting this to True may demand for an admin privilege." -msgstr "" - -#: senlinclient/v1/shell.py:1101 -msgid "ID of event to display details for." -msgstr "" - -#: senlinclient/v1/shell.py:1107 -#, python-format -msgid "Event not found: %s" -msgstr "" - -#: senlinclient/v1/shell.py:1116 -msgid "" -"Filter parameters to apply on returned actions. This can be specified " -"multiple times, or once with parameters separated by a semicolon." -msgstr "" - -#: senlinclient/v1/shell.py:1125 -msgid "Limit the number of actions returned." -msgstr "" - -#: senlinclient/v1/shell.py:1127 -msgid "Only return actions that appear after the given node ID." -msgstr "" - -#: senlinclient/v1/shell.py:1169 -msgid "Name or ID of the action to show the details for." -msgstr "" - -#: senlinclient/v1/shell.py:1175 -#, python-format -msgid "Action not found: %s" -msgstr "" - diff --git a/senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient-log-info.po b/senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient-log-info.po new file mode 100644 index 0000000..3ff60e9 --- /dev/null +++ b/senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient-log-info.po @@ -0,0 +1,21 @@ +# Zheng Xi Zhou , 2016. #zanata +msgid "" +msgstr "" +"Project-Id-Version: python-senlinclient 0.4.2.dev33\n" +"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" +"POT-Creation-Date: 2016-06-06 07:37+0000\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"PO-Revision-Date: 2016-06-16 05:48+0000\n" +"Last-Translator: Zheng Xi Zhou \n" +"Language-Team: Chinese (China)\n" +"Language: zh-CN\n" +"X-Generator: Zanata 3.7.3\n" +"Plural-Forms: nplurals=1; plural=0\n" + +msgid "Ctrl-c detected." +msgstr "检测到Ctrl-c。" + +msgid "Ctrl-d detected" +msgstr "检测到Ctrl-d。" diff --git a/senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient-log-warning.po b/senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient-log-warning.po new file mode 100644 index 0000000..e6e1452 --- /dev/null +++ b/senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient-log-warning.po @@ -0,0 +1,19 @@ +# Zheng Xi Zhou , 2016. #zanata +msgid "" +msgstr "" +"Project-Id-Version: python-senlinclient 0.4.2.dev33\n" +"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" +"POT-Creation-Date: 2016-06-06 07:37+0000\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"PO-Revision-Date: 2016-06-16 05:49+0000\n" +"Last-Translator: Zheng Xi Zhou \n" +"Language-Team: Chinese (China)\n" +"Language: zh-CN\n" +"X-Generator: Zanata 3.7.3\n" +"Plural-Forms: nplurals=1; plural=0\n" + +#, python-format +msgid "\"%(old)s\" is deprecated, please use \"%(new)s\" instead." +msgstr "\"%(old)s\"已弃用,请使用\"%(new)s\"。" diff --git a/senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient.po b/senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient.po new file mode 100644 index 0000000..cf5bfe7 --- /dev/null +++ b/senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient.po @@ -0,0 +1,1256 @@ +# Andreas Jaeger , 2016. #zanata +# zzxwill , 2016. #zanata +msgid "" +msgstr "" +"Project-Id-Version: python-senlinclient 0.5.1.dev40\n" +"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" +"POT-Creation-Date: 2016-08-22 13:00+0000\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"PO-Revision-Date: 2016-08-22 09:48+0000\n" +"Last-Translator: zzxwill \n" +"Language-Team: Chinese (China)\n" +"Language: zh-CN\n" +"X-Generator: Zanata 3.7.3\n" +"Plural-Forms: nplurals=1; plural=0\n" + +#, python-format +msgid "'%s' is not a valid subcommand" +msgstr "'%s'不是合法的子命令" + +#, fuzzy +msgid "... terminating senlin client" +msgstr "。。。终止senlin client" + +msgid "" +"A dictionary of parameters that will be passed to target action when the " +"receiver is triggered" +msgstr "字典类型的参数在receiver被触发时将传递给目标动作" + +msgid "" +"A dictionary of parameters that will be passed to target action when the " +"receiver is triggered." +msgstr "字典类型的参数在receiver被触发时将传递给目标动作。" + +msgid "" +"A positive integer meaning the number of nodes to add, or a negative integer " +"indicating the number of nodes to remove" +msgstr "正整数意味着要添加的节点个数,或者一个负整数,表明要删除的节点个数" + +msgid "" +"A positive integer meaning the number of nodes to add, or a negative integer " +"indicating the number of nodes to remove." +msgstr "正整数意味着要添加的节点个数,或者一个负整数,表明要删除的节点个数。" + +#, fuzzy +msgid "" +"A string token to bootstrap the Keystone database, defaults to env[OS_TOKEN]" +msgstr "用来bootstrap Keystone数据库的字符串令牌,默认是env[OS_TOKEN]" + +msgid "" +"A value that is interpreted as the percentage of size adjustment. This value " +"can be positive or negative" +msgstr "一个值被解释为大小调整的百分比,这个值可以是正数,也可以是负数" + +msgid "" +"A value that is interpreted as the percentage of size adjustment. This value " +"can be positive or negative." +msgstr "一个值被解释为大小调整的百分比,这个值可以是正数,也可以是负数。" + +msgid "Access info, defaults to env[OS_ACCESS_INFO]" +msgstr "访问信息,默认是env[OS_ACCESS_INFO]" + +#, python-format +msgid "Action not found: %s" +msgstr "Action没找到:%s" + +msgid "Action not found: fake_id" +msgstr "Action没找到: fake_id" + +msgid "Adjustment cannot be zero." +msgstr "调整值不能为0。" + +msgid "" +"An integer specifying the number of nodes for adjustment when " +"is specified" +msgstr "当指定时,整数值表明节点调整的数目" + +msgid "" +"An integer specifying the number of nodes for adjustment when " +"is specified." +msgstr "当指定时,整数值表明节点调整的数目。" + +msgid "Are you sure you want to delete this cluster(s) [y/N]?" +msgstr "你确认你想删除这个集群吗[y/N]?" + +msgid "Are you sure you want to delete this node(s) [y/N]?" +msgstr "你确认你想删除这个节点吗[y/N]?" + +msgid "Are you sure you want to delete this policy(s) [y/N]?" +msgstr "你确认你想删除这个策略吗[y/N]?" + +msgid "Are you sure you want to delete this profile(s) [y/N]?" +msgstr "你确认你想删除这个样版吗[y/N]?" + +msgid "Are you sure you want to delete this receiver(s) [y/N]?" +msgstr "你确认你想删除这个receiver吗[y/N]?" + +msgid "Authentication plugin, default to env[OS_AUTH_PLUGIN]" +msgstr "认证插件,默认是env[OS_AUTH_PLUGIN]" + +msgid "" +"Both project/tenant name and project/tenant ID are specified, Senlin will " +"use project ID for authentication" +msgstr "当项目/租户名称和项目/租户ID都提供时,Senlin将使用项目ID来认证" + +msgid "" +"Both user name and user ID are specified, Senlin will use user ID for " +"authentication" +msgstr "当用户名和用户ID都提供时,Senlin将用用户ID来认证" + +msgid "Cluster Id for this node." +msgstr "该节点的集群Id。" + +msgid "Cluster Id or Name for this node" +msgstr "该节点的集群Id或名称" + +msgid "Cluster capacity must be larger than or equal to zero." +msgstr "集群的容量必须大于或等于0。" + +msgid "Cluster creation timeout in seconds" +msgstr "集群创建超时时限(秒)" + +msgid "Cluster creation timeout in seconds." +msgstr "集群创建超时时限(秒)。" + +#, python-format +msgid "Cluster not found: %s" +msgstr "集群没找到:%s" + +msgid "Defaults to env[OS_AUTH_URL]" +msgstr "默认是env[OS_AUTH_URL]" + +msgid "Defaults to env[OS_PASSWORD]" +msgstr "默认是env[OS_PASSWORD]" + +msgid "Defaults to env[OS_PROJECT_ID]." +msgstr "默认是env[OS_PROJECT_ID]。" + +msgid "Defaults to env[OS_PROJECT_NAME]." +msgstr "默认是env[OS_PROJECT_NAME]。" + +msgid "Defaults to env[OS_TENANT_ID]." +msgstr "默认是env[OS_TENANT_ID]。" + +msgid "Defaults to env[OS_TENANT_NAME]." +msgstr "默认是env[OS_TENANT_NAME]。" + +msgid "Defaults to env[OS_TRUST_ID]" +msgstr "默认是env[OS_TRUST_ID]" + +msgid "Defaults to env[OS_USERNAME]." +msgstr "默认是env[OS_USERNAME]。" + +msgid "Defaults to env[OS_USER_ID]." +msgstr "默认是env[OS_USER_ID]。" + +msgid "Defaults to env[SENLINCLIENT_DEBUG]." +msgstr "默认是env[SENLINCLIENT_DEBUG]。" + +msgid "" +"Desired capacity of the cluster. Default to min_size if min_size is " +"specified else 0." +msgstr "集群期望的容量,如果min_size给定了,默认是min_size,否则是0。" + +msgid "Display help for ." +msgstr "显示的帮助信息。" + +msgid "Domain ID for scope of authorization, defaults to env[OS_DOMAIN_ID]." +msgstr "授权范围的域ID,默认是env[OS_DOMAIN_ID]。" + +msgid "" +"Domain name for scope of authorization, defaults to env[OS_DOMAIN_NAME]." +msgstr "授权范围的域名称,默认是env[OS_DOMAIN_NAME]。" + +#, python-format +msgid "ERROR(%(code)s): %(message)s" +msgstr "错误(%(code)s): %(message)s" + +#, python-format +msgid "" +"ERROR: %(message)s\n" +"%(traceback)s" +msgstr "" +"错误: %(message)s\n" +"%(traceback)s" + +msgid "" +"Either project/tenant ID or project/tenant name must be specified, or else " +"Senlin cannot know which project to use." +msgstr "必须指定项目/租户ID或项目/租户名称,否则Senlin不知道该使用哪个项目。" + +msgid "" +"Either user domain ID (--user-domain-id / env[OS_USER_DOMAIN_ID]) or user " +"domain name (--user-domain-name / env[OS_USER_DOMAIN_NAME]) must be " +"specified, because user name may not be unique." +msgstr "" +"因为用户名可能不唯一,必须指定域ID(--user-domain-id / " +"env[OS_USER_DOMAIN_ID])或用户域名称(--user-domain-name / " +"env[OS_USER_DOMAIN_NAME])。" + +#, python-format +msgid "Event not found: %s" +msgstr "事件没有找到:%s" + +msgid "" +"Explicitly allow senlinclient to perform \"insecure SSL\" (HTTPS) requests. " +"The server's certificate will not be verified against any certificate " +"authorities. This option should be used with caution." +msgstr "" +"明确规定,senlinclient可以执行\"insecure SSL\" (HTTPS)的请求。服务器的证书将" +"不会去校验任意的证书授权。使用这个选项时,应该多注意。 " + +msgid "Failed to check some of the specified nodes." +msgstr "检查部分给定的节点失败。" + +#, python-format +msgid "Failed to delete %(count)s of the %(total)s specified cluster(s)." +msgstr "删除%(total)s个给定的集群中的%(count)s个失败。" + +#, python-format +msgid "Failed to delete %(count)s of the %(total)s specified node(s)." +msgstr "删除%(total)s个给定的节点中的%(count)s个失败。" + +#, python-format +msgid "Failed to delete %(count)s of the %(total)s specified policy(s)." +msgstr "删除%(total)s个给定的策略中的%(count)s个失败。" + +#, python-format +msgid "Failed to delete %(count)s of the %(total)s specified profile(s)." +msgstr "删除%(total)s个给定的样版中的%(count)s个失败。" + +#, python-format +msgid "Failed to delete %(count)s of the %(total)s specified receiver(s)." +msgstr "删除%(total)s个给定的receiver中的%(count)s个失败。" + +msgid "Failed to delete some of the specified clusters." +msgstr "删除部分给定的集群失败。" + +msgid "Failed to delete some of the specified nodes." +msgstr "删除部分给定的节点失败。" + +msgid "Failed to delete some of the specified policy(s)." +msgstr "删除部分给定的策略失败。" + +msgid "Failed to delete some of the specified profile(s)." +msgstr "删除部分给定的样版失败。" + +msgid "Failed to delete some of the specified receiver(s)." +msgstr "删除部分给定的receiver失败。" + +msgid "Failed to recover some of the specified nodes." +msgstr "恢复部分给定的节点失败。" + +#, python-format +msgid "" +"Field labels list %(labels)s has different number of elements than fields " +"list %(fields)s" +msgstr "字段标签列表%(labels)s与字段列表%(fields)s相比,元素数目不相同" + +msgid "" +"Filter parameters to apply on returned actions. This can be specified " +"multiple times, or once with parameters separated by a semicolon." +msgstr "" +"过滤参数以应用在返回的action上。该参数可以指定多次,也可以用分号分隔参数之后" +"执行一次。" + +msgid "" +"Filter parameters to apply on returned actions. This can be specified " +"multiple times, or once with parameters separated by a semicolon. The valid " +"filter keys are: ['name', 'target', 'action', 'status']" +msgstr "" +"过滤参数以应用在返回的action上。该操作可以指定多次,或者用分号分隔参数之后指" +"定一次。有效的过滤键是['name', 'target', 'action', 'status']" + +msgid "" +"Filter parameters to apply on returned clusters. This can be specified " +"multiple times, or once with parameters separated by a semicolon." +msgstr "" +"过滤参数以应用在返回的集群上。该参数可以指定多次,也可以用分号分隔参数之后执" +"行一次。" + +msgid "" +"Filter parameters to apply on returned clusters. This can be specified " +"multiple times, or once with parameters separated by a semicolon. The valid " +"filter keys are: ['status', 'name']" +msgstr "" +"过滤参数以应用在返回的集群上。该操作可以指定多次,或者用分号分隔参数之后指定" +"一次。有效的过滤键是['status', 'name']" + +msgid "" +"Filter parameters to apply on returned events. This can be specified " +"multiple times, or once with parameters separated by a semicolon." +msgstr "" +"过滤参数以应用在返回的事件上。该参数可以指定多次,也可以用分号分隔参数之后执" +"行一次。" + +msgid "" +"Filter parameters to apply on returned events. This can be specified " +"multiple times, or once with parameters separated by a semicolon. The valid " +"filter keys are: ['level', 'obj_type', 'obj_id' ,'cluster_id', 'obj_name', " +"'action']" +msgstr "" +"过滤参数以应用在返回的事件上。该参数可以指定多次,也可以用分号分隔参数之后执" +"行一次。有效的过滤键是['level', 'obj_type', 'obj_id' ,'cluster_id', " +"'obj_name', 'action']" + +msgid "" +"Filter parameters to apply on returned nodes. This can be specified multiple " +"times, or once with parameters separated by a semicolon" +msgstr "" +"过滤参数以应用在返回的节点上。该参数可以指定多次,也可以用分号分隔参数之后执" +"行一次" + +msgid "" +"Filter parameters to apply on returned nodes. This can be specified multiple " +"times, or once with parameters separated by a semicolon." +msgstr "" +"过滤参数以应用在返回的节点上。该参数可以指定多次,也可以用分号分隔参数之后执" +"行一次。" + +msgid "" +"Filter parameters to apply on returned nodes. This can be specified multiple " +"times, or once with parameters separated by a semicolon. The valid filter " +"keys are: ['status','name']" +msgstr "" +"过滤参数以应用在返回的节点上。该参数可以指定多次,也可以用分号分隔参数之后执" +"行一次。有效的过滤键是['status','name']" + +msgid "" +"Filter parameters to apply on returned policies. This can be specified " +"multiple times, or once with parameters separated by a semicolon." +msgstr "" +"过滤参数以应用在返回的策略上。该参数可以指定多次,也可以用分号分隔参数之后执" +"行一次。" + +msgid "" +"Filter parameters to apply on returned policies. This can be specified " +"multiple times, or once with parameters separated by a semicolon. The valid " +"filter keys are: ['type', 'name']" +msgstr "" +"过滤参数以应用在返回的策略上。该参数可以指定多次,也可以用分号分隔参数之后执" +"行一次。有效的过滤键是['type', 'name']" + +msgid "" +"Filter parameters to apply on returned profiles. This can be specified " +"multiple times, or once with parameters separated by a semicolon." +msgstr "" +"过滤参数以应用在返回的样版上。该参数可以指定多次,也可以用分号分隔参数之后执" +"行一次。" + +msgid "" +"Filter parameters to apply on returned profiles. This can be specified " +"multiple times, or once with parameters separated by a semicolon. The valid " +"filter keys are: ['type', 'name', 'metadata']" +msgstr "" +"过滤参数以应用在返回的样版上。该参数可以指定多次,也可以用分号分隔参数之后执" +"行一次。有效的过滤键是['type', 'name', 'metadata']" + +msgid "" +"Filter parameters to apply on returned receivers. This can be specified " +"multiple times, or once with parameters separated by a semicolon." +msgstr "" +"过滤参数以应用在返回的receiver上。该参数可以指定多次,也可以用分隔分割参数之" +"后执行一次。" + +msgid "" +"Filter parameters to apply on returned receivers. This can be specified " +"multiple times, or once with parameters separated by a semicolon. The valid " +"filter keys are: ['name', 'type', 'action', 'cluster_id']" +msgstr "" +"过滤参数以应用在返回的receiver上。该操作可以执行多次,或者用分号分隔参数之后" +"执行一次。有效的过滤键是['name', 'type', 'action', 'cluster_id']" + +msgid "" +"Filter parameters to apply on returned results. This can be specified " +"multiple times, or once with parameters separated by a semicolon." +msgstr "" +"过滤参数以应用在返回的结果上。该参数可以指定多次,也可以用分号分隔参数之后执" +"行一次。" + +msgid "" +"Filter parameters to apply on returned results. This can be specified " +"multiple times, or once with parameters separated by a semicolon. The valid " +"filter keys are: ['type', 'name']" +msgstr "" +"过滤参数以应用在返回的结果上。该参数可以指定多次,也可以用分号分隔参数之后执" +"行一次。有效的过滤键是['type', 'name']" + +msgid "ID of event to display details for" +msgstr "显示事件详情的ID" + +msgid "ID of event to display details for." +msgstr "显示事件详情的ID。" + +msgid "ID of new profile to use" +msgstr "将要使用的样版的ID" + +msgid "ID of new profile to use." +msgstr "将要使用的样版的ID。" + +msgid "ID of node(s) to check." +msgstr "待检查的节点的ID。" + +msgid "ID of node(s) to recover." +msgstr "待恢复的节点的ID。" + +msgid "ID of nodes to be added; multiple nodes can be separated with \",\"" +msgstr "待添加节点的ID;多个节点可以用“,”分隔" + +msgid "ID of nodes to be deleted; multiple nodes can be separated with \",\"." +msgstr "待删除的节点的ID;多个节点可以用“,”分隔。" + +msgid "ID or name of cluster from which nodes are to be listed" +msgstr "集群的ID或名字,该集群中的节点都将被列出来" + +msgid "ID or name of cluster from which nodes are to be listed." +msgstr "集群的ID或名字,该集群中的节点都将被列出来。" + +msgid "ID or name of cluster(s) to operate on." +msgstr "将要操作的集群的ID或名字。" + +msgid "ID or name of new profile to use" +msgstr "将要使用的新样版的ID或名字" + +msgid "ID or name of node(s) to check." +msgstr "将要检查的节点的ID或名字。" + +msgid "ID or name of node(s) to recover." +msgstr "将要恢复的节点的ID或名字。" + +msgid "" +"ID or name of nodes to be added; multiple nodes can be separated with \",\"" +msgstr "待添加节点的ID或名称;多个节点可以用“,”分隔" + +msgid "ID or name of policy to be attached" +msgstr "将要关联的策略的ID或名字" + +msgid "ID or name of policy to be attached." +msgstr "将要关联的策略的ID或名字。" + +msgid "ID or name of policy to be detached" +msgstr "将要解除关联的策略的ID或名字" + +msgid "ID or name of policy to be detached." +msgstr "将要解除关联的策略的ID或名字。" + +msgid "ID or name of policy to be updated" +msgstr "将被更新的策略的ID或名字" + +msgid "ID or name of policy to be updated." +msgstr "将被更新的策略的ID或名字。" + +msgid "ID or name of the cluster to query on" +msgstr "将要查询的集群的ID或名字" + +msgid "ID or name of the cluster to query on." +msgstr "将要查询的集群的ID或名字。" + +msgid "ID or name of the policy to query on" +msgstr "将要查询的策略的ID或名字" + +msgid "ID or name of the policy to query on." +msgstr "将要查询的策略的ID或名字。" + +msgid "Include physical object details" +msgstr "包含物理对象的详情" + +msgid "Include physical object details." +msgstr "包含物理对象的详情。" + +msgid "" +"Indicate that the cluster list should include clusters from all projects. " +"This option is subject to access policy checking. Default is False" +msgstr "" +"这表明集群列表应该包含所有项目的集群。该选项从属于访问策略检查,默认是False" + +msgid "" +"Indicate that the cluster list should include clusters from all projects. " +"This option is subject to access policy checking. Default is False." +msgstr "" +"这表明集群列表应该包含所有项目的集群。该选项从属于访问策略检查,默认是False。" + +msgid "" +"Indicate that the list should include policies from all projects. This " +"option is subject to access policy checking. Default is False" +msgstr "" +"这表明列表应该包含所有项目的策略。该选项从属于访问策略检查,默认是False" + +msgid "" +"Indicate that the list should include policies from all projects. This " +"option is subject to access policy checking. Default is False." +msgstr "" +"这表明列表应该包含所有项目的策略。该选项从属于访问策略检查,默认是False。" + +msgid "" +"Indicate that the list should include profiles from all projects. This " +"option is subject to access policy checking. Default is False" +msgstr "" +"这表明列表应该包含所有项目的样版。该选项从属于访问策略检查,默认是False" + +msgid "" +"Indicate that the list should include profiles from all projects. This " +"option is subject to access policy checking. Default is False." +msgstr "" +"这表明列表应该包含所有项目的样版。该选项从属于访问策略检查,默认是False。" + +msgid "" +"Indicate that the list should include receivers from all projects. This " +"option is subject to access policy checking. Default is False" +msgstr "" +"这表明列表应该包含所有项目的receiver。该选项从属于访问策略检查,默认是False" + +msgid "" +"Indicate that the list should include receivers from all projects. This " +"option is subject to access policy checking. Default is False." +msgstr "" +"这表明列表应该包含所有项目的receiver。该选项从属于访问策略检查,默认是False。" + +msgid "" +"Indicate that this node list should include nodes from all projects. This " +"option is subject to access policy checking. Default is False" +msgstr "" +"这表明节点列表应该包含所有项目的节点。该选项从属于访问策略检查,默认是False" + +msgid "" +"Indicate that this node list should include nodes from all projects. This " +"option is subject to access policy checking. Default is False." +msgstr "" +"这表明节点列表应该包含所有项目的节点。该选项从属于访问策略检查,默认是False。" + +msgid "Key \"error\" not exists" +msgstr "键\"error\"不存在" + +msgid "Limit the number of actions returned" +msgstr "限定action返回的个数" + +msgid "Limit the number of actions returned." +msgstr "限定action返回的个数。" + +msgid "Limit the number of clusters returned" +msgstr "限定集群返回的个数" + +msgid "Limit the number of clusters returned." +msgstr "限定集群返回的个数。" + +msgid "Limit the number of events returned" +msgstr "限定事件返回的个数" + +msgid "Limit the number of events returned." +msgstr "限定事件返回的个数。" + +msgid "Limit the number of nodes returned" +msgstr "限定节点返回的个数" + +msgid "Limit the number of nodes returned." +msgstr "限定节点返回的个数。" + +msgid "Limit the number of policies returned" +msgstr "限定策略返回的个数" + +msgid "Limit the number of policies returned." +msgstr "限定策略返回的个数。" + +msgid "Limit the number of profiles returned" +msgstr "限定样版返回的个数" + +msgid "Limit the number of profiles returned." +msgstr "限定样版返回的个数。" + +msgid "Limit the number of receivers returned" +msgstr "限定receiver返回的个数" + +msgid "Limit the number of receivers returned." +msgstr "限定receiver返回的个数。" + +#, python-format +msgid "Malformed exception record, missing field \"%s\"" +msgstr "异常记录的格式不正确,缺少字段\"%s\"" + +#, python-format +msgid "Malformed parameter(%s). Use the key=value format." +msgstr "参数(%s)格式不正确,使用key=value格式。" + +msgid "Malformed parameter(status:ACTIVE). Use the key=value format." +msgstr "参数(status:ACTIVE)格式不正确,使用key=value格式。" + +msgid "Max size cannot be less than the specified capacity." +msgstr "最大值不能小于给定的容量。" + +msgid "Max size of the cluster. Default to -1, means unlimited" +msgstr "集群容量上限。默认为-1,表示无限制" + +msgid "Max size of the cluster. Default to -1, means unlimited." +msgstr "集群容量上限。默认为-1,表示无限制。" + +msgid "" +"Metadata values to be attached to the cluster. This can be specified " +"multiple times, or once with key-value pairs separated by a semicolon" +msgstr "" +"将要关联到集群的元数据。该元数据可以指定多次,也可以用分号分割键值对之后指定" +"一次" + +msgid "" +"Metadata values to be attached to the cluster. This can be specified " +"multiple times, or once with key-value pairs separated by a semicolon." +msgstr "" +"将要关联到集群的元数据。该元数据可以指定多次,也可以用分号分割键值对之后指定" +"一次。" + +msgid "" +"Metadata values to be attached to the node. Metadata can be specified " +"multiple times, or once with key-value pairs separated by a semicolon" +msgstr "" +"将要关联到节点的元数据。该元数据可以指定多次,也可以用分号分隔键值对之后指定" +"一次" + +msgid "" +"Metadata values to be attached to the node. Metadata can be specified " +"multiple times, or once with key-value pairs separated by a semicolon." +msgstr "" +"将要关联到节点的元数据。该元数据可以指定多次,也可以用分号分隔键值对之后指定" +"一次。" + +msgid "" +"Metadata values to be attached to the node. This can be specified multiple " +"times, or once with key-value pairs separated by a semicolon" +msgstr "" +"将要关联到节点的元数据。该元数据可以指定多次,也可以用分号分隔键值对之后指定" +"一次" + +msgid "" +"Metadata values to be attached to the node. This can be specified multiple " +"times, or once with key-value pairs separated by a semicolon." +msgstr "" +"将要关联到节点的元数据。该元数据可以指定多次,也可以用分号分隔键值对之后指定" +"一次。" + +msgid "" +"Metadata values to be attached to the profile. This can be specified " +"multiple times, or once with key-value pairs separated by a semicolon" +msgstr "" +"将要关联到样版的元数据。该元数据可以指定多次,也可以用分号分隔键值对之后指定" +"一次" + +msgid "" +"Metadata values to be attached to the profile. This can be specified " +"multiple times, or once with key-value pairs separated by a semicolon." +msgstr "" +"将要关联到样版的元数据。该元数据可以指定多次,也可以用分号分隔键值对之后指定" +"一次。" + +msgid "Min size cannot be larger than max size." +msgstr "最小值不能大于最大值。" + +msgid "Min size cannot be larger than the specified capacity" +msgstr "最小值不能大于给定的容量" + +msgid "Min size cannot be less than zero." +msgstr "最小值不能小于0。" + +msgid "Min size of the cluster. Default to 0" +msgstr "集群容量下限。默认为0" + +msgid "Min size of the cluster. Default to 0." +msgstr "集群容量下限。默认为0。" + +#, fuzzy +msgid "Min step is only used with percentage." +msgstr "最小步骤只能使用百分数。" + +msgid "Missing 'properties' key in spec file." +msgstr "规格文件里缺少键'properties'。" + +msgid "Missing 'type' key in spec file." +msgstr "规格文件里缺少键'type'。" + +msgid "Missing 'version' key in spec file." +msgstr "规格文件里缺少键'version'。" + +msgid "Name of the cluster to create" +msgstr "要创建的集群的名称" + +msgid "Name of the cluster to create." +msgstr "要创建的集群的名称。" + +msgid "Name of the node to create" +msgstr "要创建的节点的名称" + +msgid "Name of the node to create." +msgstr "待创建节点的名称。" + +msgid "Name of the policy to be updated." +msgstr "将被更新的策略的名字。" + +msgid "Name of the policy to create" +msgstr "要创建的策略的名称" + +msgid "Name of the policy to create." +msgstr "要创建的策略的名称。" + +msgid "Name of the profile to create" +msgstr "要创建的样版的名称" + +msgid "Name of the profile to create." +msgstr "要创建的样版的名称。" + +msgid "Name of the receiver to create" +msgstr "要创建的receiver的名称" + +msgid "Name of the receiver to create." +msgstr "待创建的receiver的名称。" + +msgid "Name or ID of cluster to be updated" +msgstr "将要更新的集群的名称或ID" + +msgid "Name or ID of cluster to be updated." +msgstr "将要更新的集群的名称或ID。" + +msgid "Name or ID of cluster to nodes from" +msgstr "该集群的节点的名字或ID" + +msgid "Name or ID of cluster to nodes from." +msgstr "该集群的节点的名字或ID。" + +msgid "Name or ID of cluster to operate on" +msgstr "将要操作的集群的名字或ID" + +msgid "Name or ID of cluster to operate on." +msgstr "将要操作的集群的名字或ID。" + +msgid "Name or ID of cluster to query on" +msgstr "将要查询的集群的名字或ID" + +msgid "Name or ID of cluster to query on." +msgstr "将要查询的集群的名字或ID。" + +msgid "Name or ID of cluster to show" +msgstr "将要显示的集群的名字或ID" + +msgid "Name or ID of cluster to show." +msgstr "将要显示的集群的名字或ID。" + +msgid "Name or ID of cluster(s) to delete" +msgstr "将要删除的集群的名称或ID" + +msgid "Name or ID of cluster(s) to delete." +msgstr "将要删除的集群的名称或ID。" + +msgid "Name or ID of cluster(s) to operate on." +msgstr "待操作的集群的名字或ID。" + +msgid "Name or ID of node to update" +msgstr "将要更新的节点的名字或ID" + +msgid "Name or ID of node to update." +msgstr "待更新的节点的名字或ID。" + +msgid "Name or ID of node(s) to delete" +msgstr "要删除的节点的名称或ID" + +msgid "Name or ID of node(s) to delete." +msgstr "待删除的节点的名称或ID。" + +msgid "" +"Name or ID of nodes to be deleted; multiple nodes can be separated with \",\"" +msgstr "待删除的节点的名称或ID;多个节点可以用“,”分隔" + +msgid "Name or ID of policy(s) to delete" +msgstr "将要删除的策略的名称或ID" + +msgid "Name or ID of policy(s) to delete." +msgstr "将要删除的策略的名称或ID。" + +msgid "Name or ID of profile to show." +msgstr "将要显示的样版的名字或ID。" + +msgid "Name or ID of profile(s) to delete" +msgstr "将要删除的样版的名称或ID" + +msgid "Name or ID of profile(s) to delete." +msgstr "将要删除的策略的名称或ID。" + +msgid "Name or ID of receiver(s) to delete" +msgstr "要删除的receiver的名称或ID" + +msgid "Name or ID of receiver(s) to delete." +msgstr "待删除的receiver的名称或ID。" + +msgid "Name or ID of the action to show the details for" +msgstr "显示action详情的名字或ID" + +msgid "Name or ID of the action to show the details for." +msgstr "显示action详情的名字或ID。" + +msgid "Name or ID of the node to show the details for" +msgstr "显示节点详情的名字或ID。" + +msgid "Name or ID of the node to show the details for." +msgstr "显示节点详情的名字或ID。" + +msgid "Name or ID of the policy to be updated" +msgstr "将要更新的策略的名称或ID" + +msgid "Name or ID of the profile to update" +msgstr "将要更新的样版的名字或ID" + +msgid "Name or ID of the profile to update." +msgstr "将要更新的样版的名字或ID。" + +msgid "Name or ID of the receiver to show" +msgstr "待显示的receiver的名字或ID" + +msgid "Name or ID of the receiver to show." +msgstr "待显示的receiver的名字或ID。" + +msgid "Name or ID of the targeted action to be triggered" +msgstr "将被触发的目标action的名称或ID" + +msgid "Name or ID of the targeted action to be triggered." +msgstr "将被触发的目标action的名称或ID。" + +msgid "Name or Id of the policy to show" +msgstr "将要显示的策略的名字或ID" + +msgid "" +"Neither project ID nor project name is specified. Senlin will use user's " +"default project which may result in authentication error." +msgstr "" +"项目ID和项目名称都未指定,Senlin将使用用户默认的项目,这可能会造成授权错误。" + +msgid "New lower bound of cluster size" +msgstr "集群大小的新下限值" + +msgid "New lower bound of cluster size." +msgstr "集群大小的新下限值。" + +msgid "New name for the cluster to update" +msgstr "将要更新的集群的新名称" + +msgid "New name for the cluster to update." +msgstr "将要更新的集群的新名称。" + +msgid "New name for the node" +msgstr "节点的新名称" + +msgid "New name for the node." +msgstr "节点的新名称。" + +msgid "New name of the policy to be updated" +msgstr "将被更新的策略的新名字" + +msgid "New name of the policy to be updated." +msgstr "将被更新的策略的新名字。" + +msgid "New timeout (in seconds) value for the cluster" +msgstr "集群新的超时时间(秒)" + +msgid "New timeout (in seconds) value for the cluster." +msgstr "集群新的超时时间(秒)。" + +msgid "" +"New upper bound of cluster size. A value of -1 indicates no upper limit on " +"cluster size" +msgstr "集群容量的上限值。默认为-1,表示集群大小无限制" + +msgid "" +"New upper bound of cluster size. A value of -1 indicates no upper limit on " +"cluster size." +msgstr "集群容量的上限值。默认为-1,表示集群大小无限制。" + +msgid "No template found in the given spec file" +msgstr "给定的样版文件里找不到模板" + +#, python-format +msgid "Node not found: %s" +msgstr "节点没有找到:%s" + +msgid "Number of nodes to be added to the specified cluster" +msgstr "将要加入该集群中的节点的个数" + +msgid "Number of nodes to be added to the specified cluster." +msgstr "将要加入该集群中的节点的个数。" + +msgid "Number of nodes to be deleted from the specified cluster" +msgstr "将要从该集群中删除的节点的个数" + +msgid "Number of nodes to be deleted from the specified cluster." +msgstr "将要从该集群中删除的节点的个数。" + +msgid "" +"Number of seconds to wait for an API response, defaults to system socket " +"timeout" +msgstr "等待API响应的时间(秒),默认是系统socket的过期时间" + +msgid "Only one of 'capacity', 'adjustment' and 'percentage' can be specified." +msgstr "只能指定'capacity', 'adjustment'和'percentage'中的一个值。" + +msgid "Only return actions that appear after the given node ID" +msgstr "仅仅返回给定节点ID后出现的action" + +msgid "Only return actions that appear after the given node ID." +msgstr "仅仅返回给定节点ID后出现的action。" + +msgid "Only return clusters that appear after the given cluster ID" +msgstr "仅仅返回给定集群ID后出现的集群" + +msgid "Only return clusters that appear after the given cluster ID." +msgstr "仅仅返回给定集群ID后出现的集群。" + +msgid "Only return events that appear after the given event ID" +msgstr "仅仅返回给定事件ID后出现的事件" + +msgid "Only return events that appear after the given event ID." +msgstr "仅仅返回给定事件ID后出现的事件。" + +msgid "Only return nodes that appear after the given node ID" +msgstr "仅仅返回给定节点ID后出现的节点" + +msgid "Only return nodes that appear after the given node ID." +msgstr "仅仅返回给定节点ID后出现的节点。" + +msgid "Only return policies that appear after the given ID" +msgstr "仅仅返回给定ID后出现的策略" + +msgid "Only return policies that appear after the given ID." +msgstr "仅仅返回给定ID后出现的策略。" + +msgid "Only return profiles that appear after the given ID" +msgstr "仅仅返回给定ID后出现的样版" + +msgid "Only return profiles that appear after the given ID." +msgstr "仅仅返回给定ID后出现的样版。" + +msgid "Only return receivers that appear after the given ID" +msgstr "仅仅返回给定ID后出现的receiver" + +msgid "Only return receivers that appear after the given ID." +msgstr "仅仅返回给定ID后出现的receiver。" + +#, python-format +msgid "Original error record: %s" +msgstr "初始错误记录:%s" + +msgid "" +"Path of CA TLS certificate(s) used to verify the remote server's " +"certificate. Without this option senlin looks for the default system CA " +"certificates." +msgstr "" +"CA TLS证书路径是用来验证远程服务器的证书。如果没有这个属性,senlin会查找默认" +"的系统CA证书。" + +msgid "Percentage cannot be zero." +msgstr "百分数不能为0。" + +#, python-format +msgid "Policy Type not found: %s" +msgstr "策略类型没找到:%s" + +#, python-format +msgid "Policy not found: %s" +msgstr "策略没找到:%s" + +msgid "Policy not found: fake_policy_id" +msgstr "策略没找到:fake_policy_id" + +#, python-format +msgid "Policy type not found: %s" +msgstr "策略类型没找到:%s" + +msgid "Policy type not found: BAD" +msgstr "策略类型没找到:BAD" + +msgid "Policy type to retrieve" +msgstr "将要获取的策略类型" + +msgid "Policy type to retrieve." +msgstr "将要获取的策略类型。" + +msgid "Print full IDs in list" +msgstr "将全部ID打印到列表里" + +msgid "Print full IDs in list." +msgstr "将全部ID打印到列表里。" + +msgid "Print more verbose output." +msgstr "打印更多详细的输出。" + +msgid "Profile Id or Name used for this node" +msgstr "用于该节点的样板的Id或名称" + +msgid "Profile Id used for this cluster" +msgstr "用于该节点的样版ID" + +msgid "Profile Id used for this cluster." +msgstr "用于该节点的样版ID。" + +msgid "Profile Id used for this node." +msgstr "用于该节点的样版Id。" + +#, python-format +msgid "Profile Type not found: %s" +msgstr "找不到样版类型:%s" + +msgid "Profile Type not found: wrong_type" +msgstr "样版没有找到:wrong_type" + +#, python-format +msgid "Profile not found: %s" +msgstr "找不到样版:%s" + +msgid "Profile not found: FAKE_ID" +msgstr "样版没有找到:FAKE_ID" + +msgid "Profile not found: wrong_id" +msgstr "样版没有找到:wrong_id" + +msgid "Profile type to retrieve" +msgstr "将要获取的样版类型" + +msgid "Profile type to retrieve." +msgstr "将要获取的样版类型。" + +msgid "" +"Project domain ID for scope of authorization, defaults to " +"env[OS_PROJECT_DOMAIN_ID]." +msgstr "授权范围的项目域ID,默认是env[OS_PROJECT_DOMAIN_ID]。" + +msgid "" +"Project domain name for scope of authorization, defaults to " +"env[OS_PROJECT_DOMAIN_NAME]." +msgstr "授权范围的项目域名称,默认是env[OS_PROJECT_DOMAIN_NAME]。" + +#, python-format +msgid "Receiver not found: %s" +msgstr "Receiver没找到:%s" + +msgid "Receiver not found: wrong_id" +msgstr "Receiver没找到:wrong_id" + +msgid "Role for this node in the specific cluster" +msgstr "在给定集群里的该节点的角色" + +msgid "Role for this node in the specific cluster." +msgstr "特定集群中的这个节点的角色。" + +msgid "Shows the client version and exits." +msgstr "显示client版本并退出。" + +msgid "Skip yes/no prompt (assume yes)" +msgstr "跳过yes确认/没有提示(默认是yes)" + +msgid "" +"Sorting option which is a string containing a list of keys separated by " +"commas. Each key can be optionally appended by a sort direction (:asc or :" +"desc)" +msgstr "" +"排序选项是一个字符串,该字符串包含了一系列用逗号分隔的键。每个键可以附加上一" +"个排序方向值(:asc或:desc),这个排序方向值是可选的" + +msgid "" +"Sorting option which is a string containing a list of keys separated by " +"commas. Each key can be optionally appended by a sort direction (:asc or :" +"desc). The valid sort keys are: ['type', 'name', 'created_at', 'updated_at']" +msgstr "" +"排序选项是一个字符串,该字符串包含了一系列用逗号分隔的键。每个键可以附加上一" +"个排序方向值(:asc或:desc),这个排序方向值是可选的。有效的过滤键是['type', " +"'name', 'created_at', 'updated_at']" + +msgid "" +"Sorting option which is a string containing a list of keys separated by " +"commas. Each key can be optionally appended by a sort direction (:asc or :" +"desc). The valid sort keys are: ['name', 'status', 'init_at', 'created_at', " +"'updated_at']" +msgstr "" +"排序选项是一个字符串,该字符串包含了一系列用逗号分隔的键。每个键可以附加上一" +"个排序方向值(:asc或:desc),这个排序方向值是可选的。有效的过滤键是:" +"['name', 'status', 'init_at', 'created_at', 'updated_at']" + +msgid "" +"Sorting option which is a string containing a list of keys separated by " +"commas. Each key can be optionally appended by a sort direction (:asc or :" +"desc). The valid sort keys are: ['name', 'target', 'action', 'created_at', " +"'status']" +msgstr "" +"排序选项是一个字符串,该字符串包含了一系列用逗号分隔的键。每个键可以附加上一" +"个排序方向值(:asc或:desc),这个排序方向值是可选的。有效的过滤键是:" +"['name', 'target', 'action', 'created_at', 'status']" + +msgid "" +"Sorting option which is a string containing a list of keys separated by " +"commas. Each key can be optionally appended by a sort direction (:asc or :" +"desc). The valid sort keys are: ['name', 'type', 'action', 'cluster_id', " +"'created_at']" +msgstr "" +"排序选项是一个字符串,该字符串包含了一系列用逗号分隔的键。每个键可以附加上一" +"个排序方向值(:asc或:desc),这个排序方向值是可选的。有效的过滤键是:" +"['name', 'type', 'action', 'cluster_id', 'created_at']" + +msgid "" +"Sorting option which is a string containing a list of keys separated by " +"commas. Each key can be optionally appended by a sort direction (:asc or :" +"desc). The valid sort keys are: ['timestamp', 'level', 'obj_type', " +"'obj_name', 'user', 'action', 'status']" +msgstr "" +"排序选项是一个字符串,该字符串包含了一系列用逗号分隔的键。每个键可以附加上一" +"个排序方向值(:asc或:desc),这个排序方向值是可选的。有效的过滤键是:" +"['timestamp', 'level', 'obj_type', 'obj_name', 'user', 'action', 'status']" + +msgid "" +"Sorting option which is a string containing a list of keys separated by " +"commas. Each key can be optionally appended by a sort direction (:asc or :" +"desc). The valid sort keys are: ['type', 'name', 'created_at', 'updated_at']" +msgstr "" +"排序选项是一个字符串,该字符串包含了一系列用逗号分隔的键。每个键可以附加上一" +"个排序方向值(:asc或:desc),这个排序方向值是可选的。有效的过滤键是:" +"['type', 'name', 'created_at', 'updated_at']" + +msgid "" +"Sorting option which is a string containing a list of keys separated by " +"commas. Each key can be optionally appended by a sort direction (:asc or :" +"desc). The valid sort keys are:'['index', 'name', 'status', 'init_at', " +"'created_at', 'updated_at']'" +msgstr "" +"排序选项是一个字符串,该字符串包含了一系列用逗号分隔的键。每个键可以附加上一" +"个排序方向值(:asc或:desc),这个排序方向值是可选的。有效的过滤键" +"是:'['index', 'name', 'status', 'init_at', 'created_at', 'updated_at']'" + +msgid "" +"Sorting option which is a string containing a list of keys separated by " +"commas. Each key can be optionally appended by a sort direction (:asc or :" +"desc). The valid sort_keys are:['type', 'name', 'created_at', 'updated_at']" +msgstr "" +"排序选项是一个字符串,该字符串包含了一系列用逗号分隔的键。每个键可以附加上一" +"个排序方向值(:asc或:desc),这个排序方向值是可选的。有效的过滤键是['type', " +"'name', 'created_at', 'updated_at']" + +msgid "Targeted cluster for this receiver" +msgstr "receiver的目标集群。" + +msgid "Targeted cluster for this receiver." +msgstr "receiver的目标集群。" + +msgid "The desired number of nodes of the cluster" +msgstr "该集群中期望的节点个数" + +msgid "The desired number of nodes of the cluster." +msgstr "该集群中期望的节点个数。" + +#, python-format +msgid "The format(%s) is unsupported." +msgstr "该格式(%s)不支持。" + +msgid "The new name for the profile" +msgstr "该样版的新名字" + +msgid "The new name for the profile." +msgstr "该样版的新名字。" + +msgid "The spec file used to create the policy" +msgstr "用于创建策略的规格文件" + +msgid "The spec file used to create the policy." +msgstr "用来创建策略的规格文件。" + +msgid "The spec file used to create the profile" +msgstr "用于创建样版的规格文件" + +msgid "The spec file used to create the profile." +msgstr "用于创建样版的规格文件。" + +#, python-format +msgid "The specified file is not a valid YAML file: %s" +msgstr "所提供的文件不是合法的YAML文件:%s" + +#, python-format +msgid "The template output format, one of: %s." +msgstr "模板输出格式,需要是%s中的一种。" + +#, fuzzy, python-format +msgid "Trace ID: %s" +msgstr "Trace ID: %s" + +msgid "Type \"senlin help \" for help on a specific command." +msgstr "输入“senlin help ”来获取该命令的帮助信息。" + +msgid "Type of the receiver to create" +msgstr "要创建的receiver的类型" + +msgid "Type of the receiver to create." +msgstr "待创建的receiver的类型。" + +#, python-format +msgid "Unknown exception: %s" +msgstr "未知异常:%s" + +msgid "" +"User domain ID for scope of authorization, defaults to " +"env[OS_USER_DOMAIN_ID]." +msgstr "授权范围的用户域ID,默认是env[OS_USER_DOMAIN_ID]。" + +msgid "" +"User domain name for scope of authorization, defaults to " +"env[OS_USER_DOMAIN_NAME]." +msgstr "授权范围的用户域名称,默认是env[OS_USER_DOMAIN_NAME]。" + +msgid "Verify server certificate (default)" +msgstr "验证默认的服务器证书" + +msgid "Version number for Senlin API to use, Default to \"1\"." +msgstr "Senlin API使用的版本号,默认是1。" + +#, python-format +msgid "WARNING: %s" +msgstr "警告: %s" + +msgid "" +"WARNING: Both user name and user ID are specified, Senlin will use user ID " +"for authentication" +msgstr "警告:当用户名和用户ID都提供时,Senlin将用用户ID来认证" + +msgid "" +"Whether events from all projects should be listed. Default to False. " +"Setting this to True may demand for an admin privilege" +msgstr "" +"是否所有项目的事件都应该列出来,默认是False。设置该属性为True可能需要admin权" +"限" + +msgid "" +"Whether events from all projects should be listed. Default to False. " +"Setting this to True may demand for an admin privilege." +msgstr "" +"是否所有项目的事件都应该列出来,默认是False。设置该属性为True可能需要admin权" +"限。" + +msgid "Whether the policy should be enabled" +msgstr "该策略是否会使其生效" + +msgid "Whether the policy should be enabled once attached. Default to True" +msgstr "策略一旦关联是否应该设置为有效,默认是True" + +msgid "Whether the policy should be enabled once attached. Default to enabled." +msgstr "策略一旦关联是否应该设置为有效,默认是enabled。" + +msgid "Whether the policy should be enabled." +msgstr "该策略是否会使其生效。" + +#, python-format +msgid "You must provide a password for user %s" +msgstr "你必须为用户%s提供密码" + +msgid "You must provide a user name, a user_id or a token for authentication" +msgstr "你必须提供用户名,user_id或令牌用于认证" + +msgid "You must provide an auth url via --os-auth-url (or env[OS_AUTH_URL])" +msgstr "你必须通过--os-auth-url(或env[OS_AUTH_URL])来提供认证链接" diff --git a/senlinclient/plugin.py b/senlinclient/plugin.py index bc92e30..66002bb 100644 --- a/senlinclient/plugin.py +++ b/senlinclient/plugin.py @@ -13,19 +13,24 @@ import logging from openstack import connection -from openstackclient.common import utils +from openstack import profile +from osc_lib import utils LOG = logging.getLogger(__name__) DEFAULT_CLUSTERING_API_VERSION = '1' API_VERSION_OPTION = 'os_clustering_api_version' API_NAME = 'clustering' +CURRENT_API_VERSION = '1.2' def make_client(instance): """Returns a clustering proxy""" + prof = profile.Profile() + prof.set_api_version(API_NAME, CURRENT_API_VERSION) - conn = connection.Connection(authenticator=instance.session.auth) + conn = connection.Connection(profile=prof, + authenticator=instance.session.auth) LOG.debug('Connection: %s', conn) LOG.debug('Clustering client initialized using OpenStackSDK: %s', conn.cluster) diff --git a/senlinclient/shell.py b/senlinclient/shell.py index 5954982..75dd973 100644 --- a/senlinclient/shell.py +++ b/senlinclient/shell.py @@ -181,8 +181,8 @@ class SenlinShell(object): raise exc.CommandError(msg) # project name or ID is needed, or else sdk may find the wrong project - if (not (args.project_id or args.project_name or args.tenant_id - or args.tenant_name)): + if (not (args.project_id or args.project_name or args.tenant_id or + args.tenant_name)): if not (args.user_id): msg = _('Either project/tenant ID or project/tenant name ' 'must be specified, or else Senlin cannot know ' @@ -209,9 +209,9 @@ class SenlinShell(object): not (args.project_domain_id or args.project_domain_name)): msg = _('Either project domain ID (--project-domain-id / ' 'env[OS_PROJECT_DOMAIN_ID]) orr project domain name ' - '(--project-domain-name / env[OS_PROJECT_DOMAIN_NAME ' - 'must be specified, because project/tenant name may ' - 'not be unique.') + '(--project-domain-name / ' + 'env[OS_PROJECT_DOMAIN_NAME]) must be specified, ' + 'because project/tenant name may not be unique.') raise exc.CommandError(msg) def _setup_senlin_client(self, api_ver, args): @@ -235,8 +235,7 @@ class SenlinShell(object): 'trust_id': args.trust_id, } - return senlin_client.Client('1', args.user_preferences, USER_AGENT, - **kwargs) + return senlin_client.Client('1', user_agent=USER_AGENT, **kwargs) def main(self, argv): # Parse args once to find version diff --git a/senlinclient/tests/unit/test_cliargs.py b/senlinclient/tests/unit/test_cliargs.py index 8bc6df8..89833f3 100644 --- a/senlinclient/tests/unit/test_cliargs.py +++ b/senlinclient/tests/unit/test_cliargs.py @@ -41,10 +41,6 @@ class TestCliArgs(testtools.TestCase): '--os-trust-id', '--os-token', '--os-access-info', - '--os-api-name', - '--os-api-region', - '--os-api-version', - '--os-api-interface' ] options = [arg[0][0] for arg in parser.add_argument.call_args_list] diff --git a/senlinclient/tests/unit/test_format_utils.py b/senlinclient/tests/unit/test_format_utils.py index f540c06..b4f4705 100644 --- a/senlinclient/tests/unit/test_format_utils.py +++ b/senlinclient/tests/unit/test_format_utils.py @@ -13,7 +13,7 @@ import json import yaml -from openstackclient.tests import utils +from osc_lib.tests import utils from senlinclient.common import format_utils diff --git a/senlinclient/tests/unit/test_sdk.py b/senlinclient/tests/unit/test_sdk.py index dbbb0ce..57cbcc3 100644 --- a/senlinclient/tests/unit/test_sdk.py +++ b/senlinclient/tests/unit/test_sdk.py @@ -11,7 +11,6 @@ # under the License. import mock -import os import testtools from openstack import connection as sdk_connection @@ -22,42 +21,6 @@ from senlinclient.common import sdk class TestSdk(testtools.TestCase): - @mock.patch('senlinclient.common.sdk.ProfileAction.set_option') - def test_env(self, mock_set_option): - os.environ['test_senlin_sdk_env'] = '1' - sdk.ProfileAction.env('test_senlin_sdk_env') - mock_set_option.assert_called_once_with('test_senlin_sdk_env', '1') - - @mock.patch('senlinclient.common.sdk.ProfileAction.prof') - def test_set_option_set_name(self, mock_prof): - mock_prof.ALL = 'mock_prof.ALL' - sdk.ProfileAction.set_option('name', 'test=val1') - mock_prof.set_name.assert_called_once_with('test', 'val1') - mock_prof.reset_mock() - sdk.ProfileAction.set_option('name', 'val2') - mock_prof.set_name.assert_called_once_with(mock_prof.ALL, 'val2') - - @mock.patch('senlinclient.common.sdk.ProfileAction.prof') - def test_set_option_set_region(self, mock_prof): - mock_prof.ALL = 'mock_prof.ALL' - sdk.ProfileAction.set_option('OS_REGION_NAME', 'test=val1') - mock_prof.set_region.assert_called_once_with('test', 'val1') - mock_prof.reset_mock() - sdk.ProfileAction.set_option('OS_REGION_NAME', 'val2') - mock_prof.set_region.assert_called_once_with(mock_prof.ALL, 'val2') - - @mock.patch('senlinclient.common.sdk.ProfileAction.prof') - def test_set_option_set_version(self, mock_prof): - mock_prof.ALL = 'mock_prof.ALL' - sdk.ProfileAction.set_option('version', 'test=val1') - mock_prof.set_version.assert_called_once_with('test', 'val1') - - @mock.patch('senlinclient.common.sdk.ProfileAction.prof') - def test_set_option_set_interface(self, mock_prof): - mock_prof.ALL = 'mock_prof.ALL' - sdk.ProfileAction.set_option('interface', 'test=val1') - mock_prof.set_interface.assert_called_once_with('test', 'val1') - @mock.patch.object(sdk_connection, 'Connection') def test_create_connection_with_profile(self, mock_connection): mock_prof = mock.Mock() diff --git a/senlinclient/tests/unit/test_shell.py b/senlinclient/tests/unit/test_shell.py index dbeaaf7..edabb0c 100644 --- a/senlinclient/tests/unit/test_shell.py +++ b/senlinclient/tests/unit/test_shell.py @@ -332,9 +332,9 @@ class ShellTest(testtools.TestCase): args.project_domain_name = None msg = _('Either project domain ID (--project-domain-id / ' 'env[OS_PROJECT_DOMAIN_ID]) orr project domain name ' - '(--project-domain-name / env[OS_PROJECT_DOMAIN_NAME ' - 'must be specified, because project/tenant name may ' - 'not be unique.') + '(--project-domain-name / ' + 'env[OS_PROJECT_DOMAIN_NAME]) must be specified, ' + 'because project/tenant name may not be unique.') ex = self.assertRaises(exc.CommandError, sh._check_identity_arguments, args) self.assertEqual(msg, six.text_type(ex)) @@ -366,7 +366,7 @@ class ShellTest(testtools.TestCase): mock_conn.return_value = conn conn.session = mock.Mock() sh._setup_senlin_client('1', args) - mock_conn.assert_called_once_with(args.user_preferences, USER_AGENT, + mock_conn.assert_called_once_with(prof=None, user_agent=USER_AGENT, **kwargs) client = mock.Mock() senlin_client.Client = mock.MagicMock(return_value=client) diff --git a/senlinclient/tests/unit/test_utils.py b/senlinclient/tests/unit/test_utils.py index fb8c1a7..0fd2a27 100644 --- a/senlinclient/tests/unit/test_utils.py +++ b/senlinclient/tests/unit/test_utils.py @@ -130,7 +130,7 @@ class PrintListTestCase(testtools.TestCase): +-----------+-----------+ """, cso.read()) - def test_print_list_with_None_data(self): + def test_print_list_with_None_string(self): Row = collections.namedtuple('Row', ['foo', 'bar']) to_print = [Row(foo='fake_foo1', bar='None'), Row(foo='fake_foo2', bar='fake_bar1')] @@ -144,6 +144,22 @@ class PrintListTestCase(testtools.TestCase): | fake_foo1 | None | | fake_foo2 | fake_bar1 | +-----------+-----------+ +""", cso.read()) + + def test_print_list_with_None_data(self): + Row = collections.namedtuple('Row', ['foo', 'bar']) + to_print = [Row(foo='fake_foo1', bar=None), + Row(foo='fake_foo2', bar='fake_bar1')] + with CaptureStdout() as cso: + utils.print_list(to_print, ['foo', 'bar']) + # Output should be sorted by the first key (foo) + self.assertEqual("""\ ++-----------+-----------+ +| foo | bar | ++-----------+-----------+ +| fake_foo1 | - | +| fake_foo2 | fake_bar1 | ++-----------+-----------+ """, cso.read()) def test_print_list_with_list_sortby(self): @@ -211,4 +227,20 @@ class PrintDictTestCase(testtools.TestCase): | bar | fake_bar | | foo | fake_foo | +----------+----------+ +""", cso.read()) + + def test_print_dict_with_None_data(self): + Row = collections.namedtuple('Row', ['foo', 'bar']) + to_print = [Row(foo='fake_foo1', bar=None), + Row(foo='fake_foo2', bar='fake_bar1')] + with CaptureStdout() as cso: + utils.print_list(to_print, ['foo', 'bar']) + # Output should be sorted by the first key (foo) + self.assertEqual("""\ ++-----------+-----------+ +| foo | bar | ++-----------+-----------+ +| fake_foo1 | - | +| fake_foo2 | fake_bar1 | ++-----------+-----------+ """, cso.read()) diff --git a/senlinclient/tests/unit/v1/fakes.py b/senlinclient/tests/unit/v1/fakes.py index d591454..4619897 100644 --- a/senlinclient/tests/unit/v1/fakes.py +++ b/senlinclient/tests/unit/v1/fakes.py @@ -16,7 +16,7 @@ import requests import six import sys -from openstackclient.tests import utils +from osc_lib.tests import utils AUTH_TOKEN = "foobar" @@ -141,7 +141,7 @@ class FakeResource(object): self._loaded = loaded def _add_details(self, info): - for (k, v) in six.iteritems(info): + for (k, v) in info.items(): setattr(self, k, v) def __repr__(self): diff --git a/senlinclient/tests/unit/v1/test_action.py b/senlinclient/tests/unit/v1/test_action.py index 21927dc..9fe8237 100644 --- a/senlinclient/tests/unit/v1/test_action.py +++ b/senlinclient/tests/unit/v1/test_action.py @@ -15,7 +15,7 @@ import mock from openstack.cluster.v1 import action as sdk_action from openstack import exceptions as sdk_exc -from openstackclient.common import exceptions as exc +from osc_lib import exceptions as exc from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import action as osc_action @@ -159,7 +159,7 @@ class TestActionList(TestAction): class TestActionShow(TestAction): - get_response = { + response = { "action": "CLUSTER_DELETE", "cause": "RPC Request", "context": {}, @@ -184,7 +184,7 @@ class TestActionShow(TestAction): super(TestActionShow, self).setUp() self.cmd = osc_action.ShowAction(self.app, None) self.mock_client.get_action = mock.Mock( - return_value=sdk_action.Action(attrs=self.get_response)) + return_value=sdk_action.Action(**self.response)) def test_action_show(self): arglist = ['my_action'] diff --git a/senlinclient/tests/unit/v1/test_build_info.py b/senlinclient/tests/unit/v1/test_build_info.py index ba78774..d49344c 100644 --- a/senlinclient/tests/unit/v1/test_build_info.py +++ b/senlinclient/tests/unit/v1/test_build_info.py @@ -12,7 +12,7 @@ import mock -from openstack.cluster.v1 import build_info as sdk_build_info +from openstack.cluster.v1 import build_info as sbi from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import build_info as osc_build_info @@ -33,7 +33,7 @@ class TestBuildInfo(fakes.TestClusteringv1): self.cmd = osc_build_info.BuildInfo(self.app, None) self.mock_client = self.app.client_manager.clustering self.mock_client.get_build_info = mock.Mock( - return_value=sdk_build_info.BuildInfo(None, self.response)) + return_value=sbi.BuildInfo(**self.response['build_info'])) def test_build_info(self): arglist = [] diff --git a/senlinclient/tests/unit/v1/test_client.py b/senlinclient/tests/unit/v1/test_client.py index 605300a..2c5ea96 100644 --- a/senlinclient/tests/unit/v1/test_client.py +++ b/senlinclient/tests/unit/v1/test_client.py @@ -33,16 +33,17 @@ class ClientTest(testtools.TestCase): self.assertEqual(self.conn, sc.conn) self.assertEqual(self.service, sc.service) - mock_conn.assert_called_once_with(None, None) + mock_conn.assert_called_once_with(prof=None, user_agent=None) def test_init_with_params(self, mock_conn): mock_conn.return_value = self.conn - sc = client.Client(preferences='FOO', user_agent='BAR', zoo='LARR') + sc = client.Client(prof='FOO', user_agent='BAR', zoo='LARR') self.assertEqual(self.conn, sc.conn) self.assertEqual(self.service, sc.service) - mock_conn.assert_called_once_with('FOO', 'BAR', zoo='LARR') + mock_conn.assert_called_once_with(prof='FOO', user_agent='BAR', + zoo='LARR') def test_profile_types(self, mock_conn): mock_conn.return_value = self.conn @@ -335,16 +336,16 @@ class ClientTest(testtools.TestCase): res = sc.get_node('FOOBAR') self.assertEqual(self.service.get_node.return_value, res) - self.service.get_node.assert_called_once_with('FOOBAR', args=None) + self.service.get_node.assert_called_once_with('FOOBAR', details=False) def test_get_node_with_details(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() - res = sc.get_node('FOOBAR', args={'show_details': True}) + res = sc.get_node('FOOBAR', details=True) self.assertEqual(self.service.get_node.return_value, res) self.service.get_node.assert_called_once_with( - 'FOOBAR', args={'show_details': True}) + 'FOOBAR', details=True) def test_create_node(self, mock_conn): mock_conn.return_value = self.conn diff --git a/senlinclient/tests/unit/v1/test_cluster.py b/senlinclient/tests/unit/v1/test_cluster.py index 6495ea0..b3ff6c6 100644 --- a/senlinclient/tests/unit/v1/test_cluster.py +++ b/senlinclient/tests/unit/v1/test_cluster.py @@ -11,12 +11,13 @@ # under the License. import copy -import mock -import six +import subprocess +import mock from openstack.cluster.v1 import cluster as sdk_cluster from openstack import exceptions as sdk_exc -from openstackclient.common import exceptions as exc +from osc_lib import exceptions as exc +import six from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import cluster as osc_cluster @@ -43,7 +44,7 @@ class TestClusterList(TestCluster): "metadata": {}, "min_size": 0, "name": "cluster1", - "nodes": [ + "node_ids": [ "b07c57c8-7ab2-47bf-bdf8-e894c0c601b9", "ecc23d3e-bb68-48f8-8260-c9cf6bcb6e61", "da1e9c87-e584-4626-a120-022da5062dac" @@ -147,7 +148,7 @@ class TestClusterList(TestCluster): class TestClusterShow(TestCluster): - get_response = {"cluster": { + response = {"cluster": { "created_at": "2015-02-11T15:13:20", "data": {}, "desired_capacity": 0, @@ -158,7 +159,7 @@ class TestClusterShow(TestCluster): "metadata": {}, "min_size": 0, "name": "my_cluster", - "nodes": [], + "node_ids": [], "policies": [], "profile_id": "edc63d0a-2ca4-48fa-9854-27926da76a4a", "profile_name": "mystack", @@ -174,8 +175,7 @@ class TestClusterShow(TestCluster): super(TestClusterShow, self).setUp() self.cmd = osc_cluster.ShowCluster(self.app, None) self.mock_client.get_cluster = mock.Mock( - return_value=sdk_cluster.Cluster( - attrs=self.get_response['cluster'])) + return_value=sdk_cluster.Cluster(**self.response['cluster'])) def test_cluster_show(self): arglist = ['my_cluster'] @@ -205,7 +205,7 @@ class TestClusterCreate(TestCluster): "metadata": {}, "min_size": 0, "name": "test_cluster", - "nodes": [], + "node_ids": [], "policies": [], "profile_id": "edc63d0a-2ca4-48fa-9854-27926da76a4a", "profile_name": "mystack", @@ -231,9 +231,9 @@ class TestClusterCreate(TestCluster): super(TestClusterCreate, self).setUp() self.cmd = osc_cluster.CreateCluster(self.app, None) self.mock_client.create_cluster = mock.Mock( - return_value=sdk_cluster.Cluster(attrs=self.response['cluster'])) + return_value=sdk_cluster.Cluster(**self.response['cluster'])) self.mock_client.get_cluster = mock.Mock( - return_value=sdk_cluster.Cluster(attrs=self.response['cluster'])) + return_value=sdk_cluster.Cluster(**self.response['cluster'])) def test_cluster_create_defaults(self): arglist = ['test_cluster', '--profile', 'mystack'] @@ -275,7 +275,7 @@ class TestClusterUpdate(TestCluster): "metadata": {}, "min_size": 0, "name": "test_cluster", - "nodes": [], + "node_ids": [], "policies": [], "profile_id": "edc63d0a-2ca4-48fa-9854-27926da76a4a", "profile_name": "mystack", @@ -301,11 +301,11 @@ class TestClusterUpdate(TestCluster): super(TestClusterUpdate, self).setUp() self.cmd = osc_cluster.UpdateCluster(self.app, None) self.mock_client.update_cluster = mock.Mock( - return_value=sdk_cluster.Cluster(attrs=self.response['cluster'])) + return_value=sdk_cluster.Cluster(**self.response['cluster'])) self.mock_client.get_cluster = mock.Mock( - return_value=sdk_cluster.Cluster(attrs=self.response['cluster'])) + return_value=sdk_cluster.Cluster(**self.response['cluster'])) self.mock_client.find_cluster = mock.Mock( - return_value=sdk_cluster.Cluster(attrs=self.response['cluster'])) + return_value=sdk_cluster.Cluster(**self.response['cluster'])) def test_cluster_update_defaults(self): arglist = ['--name', 'new_cluster', '--metadata', 'nk1=nv1;nk2=nv2', @@ -330,7 +330,8 @@ class TestClusterDelete(TestCluster): def setUp(self): super(TestClusterDelete, self).setUp() self.cmd = osc_cluster.DeleteCluster(self.app, None) - self.mock_client.delete_cluster = mock.Mock() + mock_cluster = mock.Mock(location='abc/fake_action_id') + self.mock_client.delete_cluster = mock.Mock(return_value=mock_cluster) def test_cluster_delete(self): arglist = ['cluster1', 'cluster2', 'cluster3'] @@ -354,10 +355,12 @@ class TestClusterDelete(TestCluster): arglist = ['my_cluster'] self.mock_client.delete_cluster.side_effect = sdk_exc.ResourceNotFound parsed_args = self.check_parser(self.cmd, arglist, []) - error = self.assertRaises(exc.CommandError, self.cmd.take_action, - parsed_args) - self.assertIn('Failed to delete 1 of the 1 specified cluster(s).', - str(error)) + + self.cmd.take_action(parsed_args) + + self.mock_client.delete_cluster.assert_has_calls( + [mock.call('my_cluster', False)] + ) def test_cluster_delete_one_found_one_not_found(self): arglist = ['cluster1', 'cluster2'] @@ -365,13 +368,12 @@ class TestClusterDelete(TestCluster): [None, sdk_exc.ResourceNotFound] ) parsed_args = self.check_parser(self.cmd, arglist, []) - error = self.assertRaises(exc.CommandError, - self.cmd.take_action, parsed_args) + + self.cmd.take_action(parsed_args) + self.mock_client.delete_cluster.assert_has_calls( [mock.call('cluster1', False), mock.call('cluster2', False)] ) - self.assertEqual('Failed to delete 1 of the 2 specified cluster(s).', - str(error)) @mock.patch('sys.stdin', spec=six.StringIO) def test_cluster_delete_prompt_yes(self, mock_stdin): @@ -425,14 +427,20 @@ class TestClusterResize(TestCluster): self.assertEqual("Only one of 'capacity', 'adjustment' " "and 'percentage' can be specified.", str(error)) - def test_cluster_resize_none_params(self): + def test_cluster_resize_only_constraints(self): arglist = ['--min-size', '1', '--max-size', '20', 'my_cluster'] parsed_args = self.check_parser(self.cmd, arglist, []) - error = self.assertRaises(exc.CommandError, - self.cmd.take_action, - parsed_args) - self.assertEqual("At least one of 'capacity', 'adjustment' and " - "'percentage' should be specified.", str(error)) + self.cmd.take_action(parsed_args) + kwargs = { + 'min_size': 1, + 'max_size': 20, + 'adjustment_type': None, + 'min_step': None, + 'number': None, + 'strict': False + } + self.mock_client.cluster_resize.assert_called_with('my_cluster', + **kwargs) def test_cluster_resize_capacity(self): arglist = ['--capacity', '2', '--min-size', '1', '--max-size', '20', @@ -777,7 +785,7 @@ class TestClusterRecover(TestCluster): self.mock_client.recover_cluster = mock.Mock( return_value=self.response) - def test_cluster_recoverk(self): + def test_cluster_recover(self): arglist = ['cluster1', 'cluster2', 'cluster3'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) @@ -793,3 +801,216 @@ class TestClusterRecover(TestCluster): error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertIn('Cluster not found: cluster1', str(error)) + + +class TestClusterCollect(TestCluster): + response = [ + { + "node_id": "8bb476c3-0f4c-44ee-9f64-c7b0260814de", + "attr_value": "value 1", + }, + { + "node_id": "7d85f602-a948-4a30-afd4-e84f47471c15", + "attr_value": "value 2", + } + ] + + def setUp(self): + super(TestClusterCollect, self).setUp() + self.cmd = osc_cluster.ClusterCollect(self.app, None) + self.mock_client.collect_cluster_attrs = mock.Mock( + return_value=self.response) + + def test_cluster_collect(self): + arglist = ['--path', 'path.to.attr', 'cluster1'] + parsed_args = self.check_parser(self.cmd, arglist, []) + columns, data = self.cmd.take_action(parsed_args) + self.mock_client.collect_cluster_attrs.assert_called_once_with( + 'cluster1', 'path.to.attr') + self.assertEqual(['node_id', 'attr_value'], columns) + + def test_cluster_collect_with_full_id(self): + arglist = ['--path', 'path.to.attr', '--full-id', 'cluster1'] + parsed_args = self.check_parser(self.cmd, arglist, []) + columns, data = self.cmd.take_action(parsed_args) + self.mock_client.collect_cluster_attrs.assert_called_once_with( + 'cluster1', 'path.to.attr') + self.assertEqual(['node_id', 'attr_value'], columns) + + +class TestClusterRun(TestCluster): + attrs = [ + mock.Mock(node_id="NODE_ID1", + attr_value={"addresses": 'ADDRESS CONTENT 1'}), + mock.Mock(node_id="NODE_ID2", + attr_value={"addresses": 'ADDRESS CONTENT 2'}) + ] + + def setUp(self): + super(TestClusterRun, self).setUp() + self.cmd = osc_cluster.ClusterRun(self.app, None) + self.mock_client.collect_cluster_attrs = mock.Mock( + return_value=self.attrs) + + @mock.patch('subprocess.Popen') + def test__run_script(self, mock_proc): + x_proc = mock.Mock(returncode=0) + x_stdout = 'OUTPUT' + x_stderr = 'ERROR' + x_proc.communicate.return_value = (x_stdout, x_stderr) + mock_proc.return_value = x_proc + + addr = { + 'private': [ + { + 'OS-EXT-IPS:type': 'floating', + 'version': 4, + 'addr': '1.2.3.4', + } + ] + } + output = {} + + self.cmd._run_script('NODE_ID', addr, 'private', 'floating', 22, + 'john', False, 'identity_path', 'echo foo', + '-f bar', + output=output) + mock_proc.assert_called_once_with( + ['ssh', '-4', '-p22', '-i identity_path', '-f bar', 'john@1.2.3.4', + 'echo foo'], + stdout=subprocess.PIPE) + self.assertEqual( + {'status': 'SUCCEEDED (0)', 'output': 'OUTPUT', 'error': 'ERROR'}, + output) + + def test__run_script_network_not_found(self): + addr = {'foo': 'bar'} + output = {} + + self.cmd._run_script('NODE_ID', addr, 'private', 'floating', 22, + 'john', False, 'identity_path', 'echo foo', + '-f bar', + output=output) + self.assertEqual( + {'status': 'FAILED', + 'error': "Node 'NODE_ID' is not attached to network 'private'." + }, + output) + + def test__run_script_more_than_one_network(self): + addr = {'foo': 'bar', 'koo': 'tar'} + output = {} + + self.cmd._run_script('NODE_ID', addr, '', 'floating', 22, 'john', + False, 'identity_path', 'echo foo', '-f bar', + output=output) + self.assertEqual( + {'status': 'FAILED', + 'error': "Node 'NODE_ID' is attached to more than one " + "network. Please pick the network to use."}, + output) + + def test__run_script_no_network(self): + addr = {} + output = {} + + self.cmd._run_script('NODE_ID', addr, '', 'floating', 22, 'john', + False, 'identity_path', 'echo foo', '-f bar', + output=output) + + self.assertEqual( + {'status': 'FAILED', + 'error': "Node 'NODE_ID' is not attached to any network."}, + output) + + def test__run_script_no_matching_address(self): + addr = { + 'private': [ + { + 'OS-EXT-IPS:type': 'fixed', + 'version': 4, + 'addr': '1.2.3.4', + } + ] + } + output = {} + + self.cmd._run_script('NODE_ID', addr, 'private', 'floating', 22, + 'john', False, 'identity_path', 'echo foo', + '-f bar', + output=output) + self.assertEqual( + {'status': 'FAILED', + 'error': "No address that matches network 'private' and " + "type 'floating' of IPv4 has been found for node " + "'NODE_ID'."}, + output) + + def test__run_script_more_than_one_address(self): + addr = { + 'private': [ + { + 'OS-EXT-IPS:type': 'fixed', + 'version': 4, + 'addr': '1.2.3.4', + }, + { + 'OS-EXT-IPS:type': 'fixed', + 'version': 4, + 'addr': '5.6.7.8', + }, + ] + } + + output = {} + + self.cmd._run_script('NODE_ID', addr, 'private', 'fixed', 22, 'john', + False, 'identity_path', 'echo foo', '-f bar', + output=output) + self.assertEqual( + {'status': 'FAILED', + 'error': "More than one IPv4 fixed address found."}, + output) + + @mock.patch('threading.Thread') + @mock.patch.object(osc_cluster.ClusterRun, '_run_script') + def test_cluster_run(self, mock_script, mock_thread): + arglist = [ + '--port', '22', + '--address-type', 'fixed', + '--network', 'private', + '--user', 'root', + '--identity-file', 'path-to-identity', + '--ssh-options', '-f boo', + '--script', 'script-file', + 'cluster1' + ] + parsed_args = self.check_parser(self.cmd, arglist, []) + + th1 = mock.Mock() + th2 = mock.Mock() + mock_thread.side_effect = [th1, th2] + fake_script = 'blah blah' + with mock.patch('senlinclient.v1.cluster.open', + mock.mock_open(read_data=fake_script)) as mock_open: + self.cmd.take_action(parsed_args) + + self.mock_client.collect_cluster_attrs.assert_called_once_with( + 'cluster1', 'details') + mock_open.assert_called_once_with('script-file', 'r') + mock_thread.assert_has_calls([ + mock.call(target=mock_script, + args=('NODE_ID1', 'ADDRESS CONTENT 1', 'private', + 'fixed', 22, 'root', False, 'path-to-identity', + 'blah blah', '-f boo'), + kwargs={'output': {}}), + mock.call(target=mock_script, + args=('NODE_ID2', 'ADDRESS CONTENT 2', 'private', + 'fixed', 22, 'root', False, 'path-to-identity', + 'blah blah', '-f boo'), + kwargs={'output': {}}) + ]) + th1.start.assert_called_once_with() + th2.start.assert_called_once_with() + th1.join.assert_called_once_with() + th2.join.assert_called_once_with() diff --git a/senlinclient/tests/unit/v1/test_cluster_policy.py b/senlinclient/tests/unit/v1/test_cluster_policy.py index c034552..b0d342a 100644 --- a/senlinclient/tests/unit/v1/test_cluster_policy.py +++ b/senlinclient/tests/unit/v1/test_cluster_policy.py @@ -12,7 +12,7 @@ import mock -from openstack.cluster.v1 import cluster_policy as sdk_cluster_policy +from openstack.cluster.v1 import cluster_policy as scp from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import cluster_policy as osc_cluster_policy @@ -25,7 +25,7 @@ class TestClusterPolicy(fakes.TestClusteringv1): class TestClusterPolicyList(TestClusterPolicy): - columns = ['policy_id', 'policy_name', 'policy_type', 'enabled'] + columns = ['policy_id', 'policy_name', 'policy_type', 'is_enabled'] response = {"cluster_policies": [ { "cluster_id": "7d85f602-a948-4a30-afd4-e84f47471c15", @@ -76,7 +76,7 @@ class TestClusterPolicyList(TestClusterPolicy): class TestClusterPolicyShow(TestClusterPolicy): - get_response = {"cluster_policy": { + response = {"cluster_policy": { "cluster_id": "7d85f602-a948-4a30-afd4-e84f47471c15", "cluster_name": "my_cluster", "enabled": True, @@ -90,8 +90,7 @@ class TestClusterPolicyShow(TestClusterPolicy): super(TestClusterPolicyShow, self).setUp() self.cmd = osc_cluster_policy.ClusterPolicyShow(self.app, None) self.mock_client.get_cluster_policy = mock.Mock( - return_value=sdk_cluster_policy.ClusterPolicy(None, - self.get_response)) + return_value=scp.ClusterPolicy(**self.response['cluster_policy'])) def test_cluster_policy_show(self): arglist = ['--policy', 'my_policy', 'my_cluster'] diff --git a/senlinclient/tests/unit/v1/test_event.py b/senlinclient/tests/unit/v1/test_event.py index 7cd2f6a..b5042ec 100644 --- a/senlinclient/tests/unit/v1/test_event.py +++ b/senlinclient/tests/unit/v1/test_event.py @@ -15,7 +15,7 @@ import mock from openstack.cluster.v1 import event as sdk_event from openstack import exceptions as sdk_exc -from openstackclient.common import exceptions as exc +from osc_lib import exceptions as exc from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import event as osc_event @@ -60,7 +60,7 @@ class TestEventList(TestEvent): super(TestEventList, self).setUp() self.cmd = osc_event.ListEvent(self.app, None) self.mock_client.events = mock.Mock( - return_value=sdk_event.Event(None, {})) + return_value=[sdk_event.Event(**self.response['events'][0])]) def test_event_list_defaults(self): arglist = [] @@ -95,8 +95,7 @@ class TestEventList(TestEvent): self.assertEqual(self.columns, columns) def test_event_list_sort_invalid_key(self): - self.mock_client.events = mock.Mock( - return_value=self.response) + self.mock_client.events = mock.Mock(return_value=self.response) kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'bad_key' arglist = ['--sort', 'bad_key'] @@ -106,8 +105,7 @@ class TestEventList(TestEvent): self.cmd.take_action, parsed_args) def test_event_list_sort_invalid_direction(self): - self.mock_client.events = mock.Mock( - return_value=self.response) + self.mock_client.events = mock.Mock(return_value=self.response) kwargs = copy.deepcopy(self.defaults) kwargs['sort'] = 'name:bad_direction' arglist = ['--sort', 'name:bad_direction'] @@ -136,7 +134,7 @@ class TestEventList(TestEvent): class TestEventShow(TestEvent): - get_response = {"event": { + response = {"event": { "action": "create", "cluster_id": 'null', "id": "2d255b9c-8f36-41a2-a137-c0175ccc29c3", @@ -155,7 +153,7 @@ class TestEventShow(TestEvent): super(TestEventShow, self).setUp() self.cmd = osc_event.ShowEvent(self.app, None) self.mock_client.get_event = mock.Mock( - return_value=sdk_event.Event(None, self.get_response)) + return_value=sdk_event.Event(**self.response['event'])) def test_event_show(self): arglist = ['my_event'] diff --git a/senlinclient/tests/unit/v1/test_node.py b/senlinclient/tests/unit/v1/test_node.py index 6bfdada..516ee63 100644 --- a/senlinclient/tests/unit/v1/test_node.py +++ b/senlinclient/tests/unit/v1/test_node.py @@ -16,7 +16,7 @@ import six from openstack.cluster.v1 import node as sdk_node from openstack import exceptions as sdk_exc -from openstackclient.common import exceptions as exc +from osc_lib import exceptions as exc from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import node as osc_node @@ -144,7 +144,7 @@ class TestNodeList(TestNode): class TestNodeShow(TestNode): - get_response = {"node": { + response = {"node": { "cluster_id": None, "created_at": "2015-02-10T12:03:16", "data": {}, @@ -171,20 +171,20 @@ class TestNodeShow(TestNode): super(TestNodeShow, self).setUp() self.cmd = osc_node.ShowNode(self.app, None) self.mock_client.get_node = mock.Mock( - return_value=sdk_node.Node(attrs=self.get_response['node'])) + return_value=sdk_node.Node(**self.response['node'])) def test_node_show(self): arglist = ['my_node'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) - self.mock_client.get_node.assert_called_with('my_node', args=None) + self.mock_client.get_node.assert_called_with('my_node', details=False) def test_node_show_with_details(self): arglist = ['my_node', '--details'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.mock_client.get_node.assert_called_with( - 'my_node', args={'show_details': True}) + 'my_node', details=True) def test_node_show_not_found(self): arglist = ['my_node'] @@ -230,9 +230,9 @@ class TestNodeCreate(TestNode): super(TestNodeCreate, self).setUp() self.cmd = osc_node.CreateNode(self.app, None) self.mock_client.create_node = mock.Mock( - return_value=sdk_node.Node(attrs=self.response['node'])) + return_value=sdk_node.Node(**self.response['node'])) self.mock_client.get_node = mock.Mock( - return_value=sdk_node.Node(attrs=self.response['node'])) + return_value=sdk_node.Node(**self.response['node'])) def test_node_create_defaults(self): arglist = ['my_node', '--profile', 'mystack'] @@ -305,11 +305,11 @@ class TestNodeUpdate(TestNode): super(TestNodeUpdate, self).setUp() self.cmd = osc_node.UpdateNode(self.app, None) self.mock_client.update_node = mock.Mock( - return_value=sdk_node.Node(attrs=self.response['node'])) + return_value=sdk_node.Node(**self.response['node'])) self.mock_client.get_node = mock.Mock( - return_value=sdk_node.Node(attrs=self.response['node'])) + return_value=sdk_node.Node(**self.response['node'])) self.mock_client.find_node = mock.Mock( - return_value=sdk_node.Node(attrs=self.response['node'])) + return_value=sdk_node.Node(**self.response['node'])) def test_node_update_defaults(self): arglist = ['--name', 'new_node', '--metadata', 'nk1=nv1;nk2=nv2', @@ -335,7 +335,8 @@ class TestNodeDelete(TestNode): def setUp(self): super(TestNodeDelete, self).setUp() self.cmd = osc_node.DeleteNode(self.app, None) - self.mock_client.delete_node = mock.Mock() + mock_node = mock.Mock(location='loc/fake_action_id') + self.mock_client.delete_node = mock.Mock(return_value=mock_node) def test_node_delete(self): arglist = ['node1', 'node2', 'node3'] @@ -359,10 +360,12 @@ class TestNodeDelete(TestNode): arglist = ['my_node'] self.mock_client.delete_node.side_effect = sdk_exc.ResourceNotFound parsed_args = self.check_parser(self.cmd, arglist, []) - error = self.assertRaises(exc.CommandError, self.cmd.take_action, - parsed_args) - self.assertIn('Failed to delete 1 of the 1 specified node(s).', - str(error)) + + self.cmd.take_action(parsed_args) + + self.mock_client.delete_node.assert_has_calls( + [mock.call('my_node', False)] + ) def test_node_delete_one_found_one_not_found(self): arglist = ['node1', 'node2'] @@ -370,13 +373,12 @@ class TestNodeDelete(TestNode): [None, sdk_exc.ResourceNotFound] ) parsed_args = self.check_parser(self.cmd, arglist, []) - error = self.assertRaises(exc.CommandError, - self.cmd.take_action, parsed_args) + + self.cmd.take_action(parsed_args) + self.mock_client.delete_node.assert_has_calls( [mock.call('node1', False), mock.call('node2', False)] ) - self.assertEqual('Failed to delete 1 of the 2 specified node(s).', - str(error)) @mock.patch('sys.stdin', spec=six.StringIO) def test_node_delete_prompt_yes(self, mock_stdin): diff --git a/senlinclient/tests/unit/v1/test_policy.py b/senlinclient/tests/unit/v1/test_policy.py index 350a7d9..9c4164d 100644 --- a/senlinclient/tests/unit/v1/test_policy.py +++ b/senlinclient/tests/unit/v1/test_policy.py @@ -16,7 +16,7 @@ import six from openstack.cluster.v1 import policy as sdk_policy from openstack import exceptions as sdk_exc -from openstackclient.common import exceptions as exc +from osc_lib import exceptions as exc from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import policy as osc_policy @@ -141,7 +141,7 @@ class TestPolicyList(TestPolicy): class TestPolicyShow(TestPolicy): - get_response = {"policy": { + response = {"policy": { "created_at": "2015-03-02T07:40:31", "data": {}, "domain": 'null', @@ -170,7 +170,7 @@ class TestPolicyShow(TestPolicy): super(TestPolicyShow, self).setUp() self.cmd = osc_policy.ShowPolicy(self.app, None) self.mock_client.get_policy = mock.Mock( - return_value=sdk_policy.Policy(attrs=self.get_response['policy'])) + return_value=sdk_policy.Policy(**self.response['policy'])) def test_policy_show(self): arglist = ['sp001'] @@ -230,9 +230,9 @@ class TestPolicyCreate(TestPolicy): super(TestPolicyCreate, self).setUp() self.cmd = osc_policy.CreatePolicy(self.app, None) self.mock_client.create_policy = mock.Mock( - return_value=sdk_policy.Policy(attrs=self.response['policy'])) + return_value=sdk_policy.Policy(**self.response['policy'])) self.mock_client.get_policy = mock.Mock( - return_value=sdk_policy.Policy(attrs=self.response['policy'])) + return_value=sdk_policy.Policy(**self.response['policy'])) def test_policy_create_defaults(self): arglist = ['my_policy', '--spec-file', self.spec_path] @@ -274,11 +274,11 @@ class TestPolicyUpdate(TestPolicy): super(TestPolicyUpdate, self).setUp() self.cmd = osc_policy.UpdatePolicy(self.app, None) self.mock_client.update_policy = mock.Mock( - return_value=sdk_policy.Policy(attrs=self.response['policy'])) + return_value=sdk_policy.Policy(**self.response['policy'])) self.mock_client.get_policy = mock.Mock( - return_value=sdk_policy.Policy(attrs=self.response['policy'])) + return_value=sdk_policy.Policy(**self.response['policy'])) self.mock_client.find_policy = mock.Mock( - return_value=sdk_policy.Policy(attrs=self.response['policy'])) + return_value=sdk_policy.Policy(**self.response['policy'])) def test_policy_update_defaults(self): arglist = ['--name', 'new_policy', '9f779ddf'] @@ -368,3 +368,56 @@ class TestPolicyDelete(TestPolicy): mock_stdin.readline.assert_called_with() self.mock_client.delete_policy.assert_not_called() + + +class TestPolicyValidate(TestPolicy): + spec_path = 'senlinclient/tests/test_specs/deletion_policy.yaml' + response = {"policy": { + "created_at": None, + "data": {}, + "domain": 'null', + "id": None, + "name": "validated_policy", + "project": "5f1cc92b578e4e25a3b284179cf20a9b", + "spec": { + "description": "A policy for choosing victim node(s) from a " + "cluster for deletion.", + "properties": { + "criteria": "OLDEST_FIRST", + "destroy_after_deletion": True, + "grace_period": 60, + "reduce_desired_capacity": False + }, + "type": "senlin.policy.deletion", + "version": 1.0 + }, + "type": "senlin.policy.deletion-1.0", + "updated_at": 'null', + "user": "2d7aca950f3e465d8ef0c81720faf6ff" + }} + defaults = { + "spec": { + "version": 1, + "type": "senlin.policy.deletion", + "description": "A policy for choosing victim node(s) from a " + "cluster for deletion.", + "properties": { + "destroy_after_deletion": True, + "grace_period": 60, + "reduce_desired_capacity": False, + "criteria": "OLDEST_FIRST" + } + } + } + + def setUp(self): + super(TestPolicyValidate, self).setUp() + self.cmd = osc_policy.ValidatePolicy(self.app, None) + self.mock_client.validate_policy = mock.Mock( + return_value=sdk_policy.Policy(**self.response['policy'])) + + def test_policy_validate(self): + arglist = ['--spec-file', self.spec_path] + parsed_args = self.check_parser(self.cmd, arglist, []) + self.cmd.take_action(parsed_args) + self.mock_client.validate_policy.assert_called_with(**self.defaults) diff --git a/senlinclient/tests/unit/v1/test_policy_type.py b/senlinclient/tests/unit/v1/test_policy_type.py index 956dc16..3064469 100644 --- a/senlinclient/tests/unit/v1/test_policy_type.py +++ b/senlinclient/tests/unit/v1/test_policy_type.py @@ -14,7 +14,7 @@ import mock from openstack.cluster.v1 import policy_type as sdk_policy_type from openstack import exceptions as sdk_exc -from openstackclient.common import exceptions as exc +from osc_lib import exceptions as exc from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import policy_type as osc_policy_type @@ -29,15 +29,9 @@ class TestPolicyType(fakes.TestClusteringv1): class TestPolicyTypeList(TestPolicyType): expected_columns = ['name'] list_response = [ - sdk_policy_type.PolicyType({'name': 'BBB', - 'schema': { - 'foo': 'bar'}}), - sdk_policy_type.PolicyType({'name': 'AAA', - 'schema': { - 'foo': 'bar'}}), - sdk_policy_type.PolicyType({'name': 'CCC', - 'schema': { - 'foo': 'bar'}}), + sdk_policy_type.PolicyType(name='BBB', schema={'foo': 'bar'}), + sdk_policy_type.PolicyType(name='AAA', schema={'foo': 'bar'}), + sdk_policy_type.PolicyType(name='CCC', schema={'foo': 'bar'}), ] expected_rows = [ ['AAA'], @@ -71,7 +65,7 @@ class TestPolicyTypeShow(TestPolicyType): super(TestPolicyTypeShow, self).setUp() self.cmd = osc_policy_type.PolicyTypeShow(self.app, None) self.mock_client.get_policy_type = mock.Mock( - return_value=sdk_policy_type.PolicyType(self.response) + return_value=sdk_policy_type.PolicyType(**self.response) ) def test_policy_type_show(self): diff --git a/senlinclient/tests/unit/v1/test_profile.py b/senlinclient/tests/unit/v1/test_profile.py index d84e0d0..7782e1e 100644 --- a/senlinclient/tests/unit/v1/test_profile.py +++ b/senlinclient/tests/unit/v1/test_profile.py @@ -16,8 +16,8 @@ import six from openstack.cluster.v1 import profile as sdk_profile from openstack import exceptions as sdk_exc -from openstackclient.common import exceptions as exc -from openstackclient.common import utils +from osc_lib import exceptions as exc +from osc_lib import utils from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import profile as osc_profile @@ -30,7 +30,7 @@ class TestProfile(fakes.TestClusteringv1): class TestProfileShow(TestProfile): - get_response = {"profile": { + response = {"profile": { "created_at": "2015-03-01T14:28:25", "domain": 'false', "id": "7fa885cd-fa39-4531-a42d-780af95c84a4", @@ -92,8 +92,7 @@ class TestProfileShow(TestProfile): super(TestProfileShow, self).setUp() self.cmd = osc_profile.ShowProfile(self.app, None) self.mock_client.get_profile = mock.Mock( - return_value=sdk_profile.Profile( - attrs=self.get_response['profile'])) + return_value=sdk_profile.Profile(**self.response['profile'])) utils.get_dict_properties = mock.Mock(return_value='') def test_profile_show(self): @@ -305,13 +304,15 @@ class TestProfileCreate(TestProfile): "updated_at": None, "user": "2d7aca950f3e465d8ef0c81720faf6ff"}} - defaults = {"spec": { - "version": 1.0, - "type": "os.nova.server", - "properties": { - "flavor": 1, - "name": "cirros_server", - "image": "cirros-0.3.4-x86_64-uec"} + defaults = { + "spec": { + "version": 1.0, + "type": "os.nova.server", + "properties": { + "flavor": 1, + "name": "cirros_server", + "image": "cirros-0.3.4-x86_64-uec" + }, }, "name": "my_profile", "metadata": {} @@ -321,9 +322,9 @@ class TestProfileCreate(TestProfile): super(TestProfileCreate, self).setUp() self.cmd = osc_profile.CreateProfile(self.app, None) self.mock_client.create_profile = mock.Mock( - return_value=sdk_profile.Profile(attrs=self.response['profile'])) + return_value=sdk_profile.Profile(**self.response['profile'])) self.mock_client.get_profile = mock.Mock( - return_value=sdk_profile.Profile(attrs=self.response['profile'])) + return_value=sdk_profile.Profile(**self.response['profile'])) utils.get_dict_properties = mock.Mock(return_value='') def test_profile_create_defaults(self): @@ -376,11 +377,11 @@ class TestProfileUpdate(TestProfile): super(TestProfileUpdate, self).setUp() self.cmd = osc_profile.UpdateProfile(self.app, None) self.mock_client.update_profile = mock.Mock( - return_value=sdk_profile.Profile(attrs=self.response['profile'])) + return_value=sdk_profile.Profile(**self.response['profile'])) self.mock_client.get_profile = mock.Mock( - return_value=sdk_profile.Profile(attrs=self.response['profile'])) + return_value=sdk_profile.Profile(**self.response['profile'])) self.mock_client.find_profile = mock.Mock( - return_value=sdk_profile.Profile(attrs=self.response['profile'])) + return_value=sdk_profile.Profile(**self.response['profile'])) utils.get_dict_properties = mock.Mock(return_value='') def test_profile_update_defaults(self): @@ -401,3 +402,49 @@ class TestProfileUpdate(TestProfile): self.cmd.take_action, parsed_args) self.assertIn('Profile not found: c6b8b252', str(error)) + + +class TestProfileValidate(TestProfile): + + spec_path = 'senlinclient/tests/test_specs/nova_server.yaml' + response = {"profile": { + "created_at": None, + "domain": None, + "id": None, + "metadata": None, + "name": "validated_profile", + "project": "5f1cc92b578e4e25a3b284179cf20a9b", + "spec": {"properties": { + "flavor": 1, + "image": "cirros-0.3.4-x86_64-uec", + "name": "cirros_server"}, + "type": "os.nova.server", + "version": 1.0}, + "type": "os.nova.server-1.0", + "updated_at": None, + "user": "2d7aca950f3e465d8ef0c81720faf6ff"}} + + defaults = { + "spec": { + "version": 1.0, + "type": "os.nova.server", + "properties": { + "flavor": 1, + "name": "cirros_server", + "image": "cirros-0.3.4-x86_64-uec" + }, + } + } + + def setUp(self): + super(TestProfileValidate, self).setUp() + self.cmd = osc_profile.ValidateProfile(self.app, None) + self.mock_client.validate_profile = mock.Mock( + return_value=sdk_profile.Profile(**self.response['profile'])) + utils.get_dict_properties = mock.Mock(return_value='') + + def test_profile_validate(self): + arglist = ['--spec-file', self.spec_path] + parsed_args = self.check_parser(self.cmd, arglist, []) + self.cmd.take_action(parsed_args) + self.mock_client.validate_profile.assert_called_with(**self.defaults) diff --git a/senlinclient/tests/unit/v1/test_profile_type.py b/senlinclient/tests/unit/v1/test_profile_type.py index 5a1fa0f..66e8d12 100644 --- a/senlinclient/tests/unit/v1/test_profile_type.py +++ b/senlinclient/tests/unit/v1/test_profile_type.py @@ -14,7 +14,7 @@ import mock from openstack.cluster.v1 import profile_type as sdk_profile_type from openstack import exceptions as sdk_exc -from openstackclient.common import exceptions as exc +from osc_lib import exceptions as exc from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import profile_type as osc_profile_type @@ -29,18 +29,9 @@ class TestProfileType(fakes.TestClusteringv1): class TestProfileTypeList(TestProfileType): expected_columns = ['name'] list_response = [ - sdk_profile_type.ProfileType({'name': 'BBB', - 'schema': { - 'foo': 'bar'}} - ), - sdk_profile_type.ProfileType({'name': 'AAA', - 'schema': { - 'foo': 'bar'}} - ), - sdk_profile_type.ProfileType({'name': 'CCC', - 'schema': { - 'foo': 'bar'}} - ), + sdk_profile_type.ProfileType(name='BBB', schema={'foo': 'bar'}), + sdk_profile_type.ProfileType(name='AAA', schema={'foo': 'bar'}), + sdk_profile_type.ProfileType(name='CCC', schema={'foo': 'bar'}), ] expected_rows = [ ['AAA'], @@ -74,7 +65,7 @@ class TestProfileTypeShow(TestProfileType): super(TestProfileTypeShow, self).setUp() self.cmd = osc_profile_type.ProfileTypeShow(self.app, None) self.mock_client.get_profile_type = mock.Mock( - return_value=sdk_profile_type.ProfileType(self.response) + return_value=sdk_profile_type.ProfileType(**self.response) ) def test_profile_type_show(self): diff --git a/senlinclient/tests/unit/v1/test_receiver.py b/senlinclient/tests/unit/v1/test_receiver.py index 053bdc7..751f69e 100644 --- a/senlinclient/tests/unit/v1/test_receiver.py +++ b/senlinclient/tests/unit/v1/test_receiver.py @@ -16,8 +16,9 @@ import six from openstack.cluster.v1 import receiver as sdk_receiver from openstack import exceptions as sdk_exc -from openstackclient.common import exceptions as exc +from osc_lib import exceptions as exc +from senlinclient.common.i18n import _ from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import receiver as osc_receiver @@ -67,8 +68,7 @@ class TestReceiverList(TestReceiver): def setUp(self): super(TestReceiverList, self).setUp() self.cmd = osc_receiver.ListReceiver(self.app, None) - self.mock_client.receivers = mock.Mock( - return_value=self.response) + self.mock_client.receivers = mock.Mock(return_value=self.response) def test_receiver_list_defaults(self): arglist = [] @@ -172,9 +172,8 @@ class TestReceiverShow(TestReceiver): def setUp(self): super(TestReceiverShow, self).setUp() self.cmd = osc_receiver.ShowReceiver(self.app, None) - self.mock_client.get_receiver = mock.Mock( - return_value=sdk_receiver.Receiver( - attrs=self.get_response['receiver'])) + x_receiver = sdk_receiver.Receiver(**self.get_response['receiver']) + self.mock_client.get_receiver = mock.Mock(return_value=x_receiver) def test_receiver_show(self): arglist = ['my_receiver'] @@ -231,13 +230,11 @@ class TestReceiverCreate(TestReceiver): super(TestReceiverCreate, self).setUp() self.cmd = osc_receiver.CreateReceiver(self.app, None) self.mock_client.create_receiver = mock.Mock( - return_value=sdk_receiver.Receiver( - attrs=self.response['receiver'])) + return_value=sdk_receiver.Receiver(**self.response['receiver'])) self.mock_client.get_receiver = mock.Mock( - return_value=sdk_receiver.Receiver( - attrs=self.response['receiver'])) + return_value=sdk_receiver.Receiver(**self.response['receiver'])) - def test_receiver_create(self): + def test_receiver_create_webhook(self): arglist = ['my_receiver', '--action', 'CLUSTER_SCALE_OUT', '--cluster', 'my_cluster', '--params', 'count=1', '--type', 'webhook'] @@ -245,6 +242,26 @@ class TestReceiverCreate(TestReceiver): self.cmd.take_action(parsed_args) self.mock_client.create_receiver.assert_called_with(**self.args) + def test_receiver_create_webhook_failed(self): + arglist = ['my_receiver', '--action', 'CLUSTER_SCALE_OUT', + '--params', 'count=1', '--type', 'webhook'] + parsed_args = self.check_parser(self.cmd, arglist, []) + error = self.assertRaises(exc.CommandError, self.cmd.take_action, + parsed_args) + self.assertIn(_('cluster and action parameters are required to create ' + 'webhook type of receiver'), str(error)) + + def test_receiver_create_non_webhook(self): + arglist = ['my_receiver', '--params', 'count=1', + '--type', 'foo'] + parsed_args = self.check_parser(self.cmd, arglist, []) + self.cmd.take_action(parsed_args) + args = copy.deepcopy(self.args) + args['type'] = 'foo' + args['cluster_id'] = None + args['action'] = None + self.mock_client.create_receiver.assert_called_with(**args) + class TestReceiverDelete(TestReceiver): def setUp(self): diff --git a/senlinclient/tests/unit/v1/test_shell.py b/senlinclient/tests/unit/v1/test_shell.py index e2d1ef2..c7a520b 100644 --- a/senlinclient/tests/unit/v1/test_shell.py +++ b/senlinclient/tests/unit/v1/test_shell.py @@ -11,6 +11,8 @@ # under the License. import copy +import subprocess + import mock import six import testtools @@ -66,8 +68,9 @@ class ShellTest(testtools.TestCase): 'api': utils.json_formatter, 'engine': utils.json_formatter, } - mock_print.assert_called_once_with(result, formatters=formatters) - self.assertTrue(service.get_build_info.called) + mock_print.assert_called_once_with(result.to_dict(), + formatters=formatters) + service.get_build_info.assert_called_once_with() @mock.patch.object(utils, 'print_list') def test_do_profile_type_list(self, mock_print): @@ -297,6 +300,48 @@ class ShellTest(testtools.TestCase): self.assertEqual(msg, six.text_type(ex)) service.delete_profile.assert_called_with('profile2', False) + @mock.patch.object(utils, 'process_stack_spec') + @mock.patch.object(utils, 'get_spec_content') + def test_do_profile_validate(self, mock_get, mock_proc): + args = self._make_args({'spec_file': mock.Mock()}) + spec = copy.deepcopy(self.profile_spec) + mock_get.return_value = spec + params = { + 'spec': spec, + } + service = mock.Mock() + profile = mock.Mock() + profile.to_dict.return_value = {} + service.validate_profile.return_value = profile + + sh.do_profile_validate(service, args) + + service.validate_profile.assert_called_once_with(**params) + + # Miss 'type' key in spec file + del spec['type'] + ex = self.assertRaises(exc.CommandError, + sh.do_profile_validate, + service, args) + self.assertEqual(_("Missing 'type' key in spec file."), + six.text_type(ex)) + # Miss 'version' key in spec file + spec['type'] = 'os.heat.stack' + del spec['version'] + ex = self.assertRaises(exc.CommandError, + sh.do_profile_validate, + service, args) + self.assertEqual(_("Missing 'version' key in spec file."), + six.text_type(ex)) + # Miss 'properties' key in spec file + spec['version'] = 1.0 + del spec['properties'] + ex = self.assertRaises(exc.CommandError, + sh.do_profile_validate, + service, args) + self.assertEqual(_("Missing 'properties' key in spec file."), + six.text_type(ex)) + @mock.patch.object(utils, 'print_list') def test_do_policy_type_list(self, mock_print): service = mock.Mock() @@ -425,7 +470,7 @@ class ShellTest(testtools.TestCase): receiver_id='receiver_id') @mock.patch.object(sh, '_show_receiver') - def test_do_receiver_create(self, mock_show): + def test_do_receiver_create_webhook(self, mock_show): service = mock.Mock() args = { 'name': 'receiver1', @@ -449,6 +494,47 @@ class ShellTest(testtools.TestCase): service.create_receiver.assert_called_once_with(**params) mock_show.assert_called_once_with(service, 'FAKE_ID') + def test_do_receiver_create_webhook_failed(self): + service = mock.Mock() + args = { + 'name': 'receiver1', + 'type': 'webhook', + 'cluster': None, + 'action': None, + 'params': {} + } + args = self._make_args(args) + ex = self.assertRaises(exc.CommandError, + sh.do_receiver_create, service, args) + msg = _("cluster and action parameters are required to create webhook" + " type of receiver.") + self.assertEqual(msg, six.text_type(ex)) + + @mock.patch.object(sh, '_show_receiver') + def test_do_receiver_create_non_webhook(self, mock_show): + service = mock.Mock() + args = { + 'name': 'receiver1', + 'type': 'foo', + 'cluster': None, + 'action': None, + 'params': {} + } + args = self._make_args(args) + params = { + 'name': 'receiver1', + 'type': 'foo', + 'cluster_id': None, + 'action': None, + 'params': {} + } + receiver = mock.Mock() + receiver.id = 'FAKE_ID' + service.create_receiver.return_value = receiver + sh.do_receiver_create(service, args) + service.create_receiver.assert_called_once_with(**params) + mock_show.assert_called_once_with(service, 'FAKE_ID') + def test_do_receiver_delete(self): service = mock.Mock() args = {'id': ['FAKE']} @@ -598,6 +684,25 @@ class ShellTest(testtools.TestCase): msg = _("Failed to delete some of the specified policy(s).") self.assertEqual(msg, six.text_type(ex)) + @mock.patch.object(utils, 'get_spec_content') + def test_do_policy_validate(self, mock_get): + service = mock.Mock() + spec = mock.Mock() + mock_get.return_value = spec + args = { + 'spec_file': 'policy_file', + } + args = self._make_args({'spec_file': 'policy_file'}) + attrs = { + 'spec': spec, + } + policy = mock.Mock() + policy.to_dict.return_value = {} + service.validate_policy.return_value = policy + sh.do_policy_validate(service, args) + mock_get.assert_called_once_with(args.spec_file) + service.validate_policy.assert_called_once_with(**attrs) + @mock.patch.object(utils, 'print_list') def test_do_cluster_list(self, mock_print): service = mock.Mock() @@ -636,7 +741,7 @@ class ShellTest(testtools.TestCase): service.get_cluster.return_value = cluster formatters = { 'metadata': utils.json_formatter, - 'nodes': utils.list_formatter, + 'node_ids': utils.list_formatter, } cluster_dict = mock.Mock() cluster.to_dict.return_value = cluster_dict @@ -675,16 +780,174 @@ class ShellTest(testtools.TestCase): sh.do_cluster_delete(service, args) service.delete_cluster.assert_called_once_with('CID', False) - def test_do_cluster_delete_not_found(self): - service = mock.Mock() - args = {'id': ['cluster_id']} - args = self._make_args(args) + @mock.patch('subprocess.Popen') + def test__run_script(self, mock_proc): + x_proc = mock.Mock(returncode=0) + x_stdout = 'OUTPUT' + x_stderr = 'ERROR' + x_proc.communicate.return_value = (x_stdout, x_stderr) + mock_proc.return_value = x_proc - service.delete_cluster.side_effect = oexc.ResourceNotFound - ex = self.assertRaises(exc.CommandError, - sh.do_cluster_delete, service, args) - msg = _('Failed to delete some of the specified clusters.') - self.assertEqual(msg, six.text_type(ex)) + addr = { + 'private': [ + { + 'OS-EXT-IPS:type': 'floating', + 'version': 4, + 'addr': '1.2.3.4', + } + ] + } + output = {} + + sh._run_script('NODE_ID', addr, 'private', 'floating', 22, 'john', + False, 'identity_path', 'echo foo', '-f bar', + output=output) + mock_proc.assert_called_once_with( + ['ssh', '-4', '-p22', '-i identity_path', '-f bar', 'john@1.2.3.4', + 'echo foo'], + stdout=subprocess.PIPE) + self.assertEqual( + {'status': 'SUCCEEDED (0)', 'output': 'OUTPUT', 'error': 'ERROR'}, + output) + + def test__run_script_network_not_found(self): + addr = {'foo': 'bar'} + output = {} + + sh._run_script('NODE_ID', addr, 'private', 'floating', 22, 'john', + False, 'identity_path', 'echo foo', '-f bar', + output=output) + self.assertEqual( + {'status': 'FAILED', + 'reason': "Node 'NODE_ID' is not attached to network 'private'." + }, + output) + + def test__run_script_more_than_one_network(self): + addr = {'foo': 'bar', 'koo': 'tar'} + output = {} + + sh._run_script('NODE_ID', addr, '', 'floating', 22, 'john', + False, 'identity_path', 'echo foo', '-f bar', + output=output) + self.assertEqual( + {'status': 'FAILED', + 'reason': "Node 'NODE_ID' is attached to more than one " + "network. Please pick the network to use."}, + output) + + def test__run_script_no_network(self): + addr = {} + output = {} + + sh._run_script('NODE_ID', addr, '', 'floating', 22, 'john', + False, 'identity_path', 'echo foo', '-f bar', + output=output) + + self.assertEqual( + {'status': 'FAILED', + 'reason': "Node 'NODE_ID' is not attached to any network."}, + output) + + def test__run_script_no_matching_address(self): + addr = { + 'private': [ + { + 'OS-EXT-IPS:type': 'fixed', + 'version': 4, + 'addr': '1.2.3.4', + } + ] + } + output = {} + + sh._run_script('NODE_ID', addr, 'private', 'floating', 22, 'john', + False, 'identity_path', 'echo foo', '-f bar', + output=output) + self.assertEqual( + {'status': 'FAILED', + 'reason': "No address that would match network 'private' and " + "type 'floating' of IPv4 has been found for node " + "'NODE_ID'."}, + output) + + def test__run_script_more_than_one_address(self): + addr = { + 'private': [ + { + 'OS-EXT-IPS:type': 'fixed', + 'version': 4, + 'addr': '1.2.3.4', + }, + { + 'OS-EXT-IPS:type': 'fixed', + 'version': 4, + 'addr': '5.6.7.8', + }, + ] + } + + output = {} + + sh._run_script('NODE_ID', addr, 'private', 'fixed', 22, 'john', + False, 'identity_path', 'echo foo', '-f bar', + output=output) + self.assertEqual( + {'status': 'FAILED', + 'reason': "More than one IPv4 fixed address found."}, + output) + + @mock.patch('threading.Thread') + @mock.patch.object(sh, '_run_script') + def test_do_cluster_run(self, mock_run, mock_thread): + service = mock.Mock() + args = { + 'script': 'script_name', + 'network': 'network_name', + 'address_type': 'fixed', + 'port': 22, + 'user': 'root', + 'ipv6': False, + 'identity_file': 'identity_filename', + 'ssh_options': '-f oo', + } + args = self._make_args(args) + args.id = 'CID' + addr1 = {'addresses': 'ADDR CONTENT 1'} + addr2 = {'addresses': 'ADDR CONTENT 2'} + attributes = [ + mock.Mock(node_id='NODE1', attr_value=addr1), + mock.Mock(node_id='NODE2', attr_value=addr2) + ] + service.collect_cluster_attrs.return_value = attributes + + th1 = mock.Mock() + th2 = mock.Mock() + mock_thread.side_effect = [th1, th2] + fake_script = 'blah blah' + with mock.patch('senlinclient.v1.shell.open', + mock.mock_open(read_data=fake_script)) as mock_open: + sh.do_cluster_run(service, args) + + service.collect_cluster_attrs.assert_called_once_with( + args.id, 'details') + mock_open.assert_called_once_with('script_name', 'r') + mock_thread.assert_has_calls([ + mock.call(target=mock_run, + args=('NODE1', 'ADDR CONTENT 1', 'network_name', + 'fixed', 22, 'root', False, 'identity_filename', + 'blah blah', '-f oo'), + kwargs={'output': {}}), + mock.call(target=mock_run, + args=('NODE2', 'ADDR CONTENT 2', 'network_name', + 'fixed', 22, 'root', False, 'identity_filename', + 'blah blah', '-f oo'), + kwargs={'output': {}}) + ]) + th1.start.assert_called_once_with() + th2.start.assert_called_once_with() + th1.join.assert_called_once_with() + th2.join.assert_called_once_with() @mock.patch.object(sh, '_show_cluster') def test_do_cluster_update(self, mock_show): @@ -819,7 +1082,7 @@ class ShellTest(testtools.TestCase): sh.do_cluster_resize, service, args) msg = _('Cluster capacity must be larger than ' - ' or equal to zero.') + 'or equal to zero.') self.assertEqual(msg, six.text_type(ex)) # adjustment @@ -947,7 +1210,7 @@ class ShellTest(testtools.TestCase): @mock.patch.object(utils, 'print_list') def test_do_cluster_policy_list(self, mock_print): - fields = ['policy_id', 'policy_name', 'policy_type', 'enabled'] + fields = ['policy_id', 'policy_name', 'policy_type', 'is_enabled'] service = mock.Mock() args = { 'id': 'C1', @@ -1056,6 +1319,65 @@ class ShellTest(testtools.TestCase): service.recover_cluster.assert_called_once_with('cluster1') + def test_do_cluster_collect(self): + service = mock.Mock() + args = self._make_args({ + 'path': 'path.to.attr', + 'list': False, + 'full_id': False, + 'id': 'cluster1' + }) + service.collect_cluster_attrs = mock.Mock( + return_value=[mock.Mock(node_id='FAKE1', attr_value='VALUE1')] + ) + + sh.do_cluster_collect(service, args) + + service.collect_cluster_attrs.assert_called_once_with( + 'cluster1', 'path.to.attr') + + @mock.patch.object(utils, 'print_list') + def test_do_cluster_collect_as_list(self, mock_print): + service = mock.Mock() + args = self._make_args({ + 'path': 'path.to.attr', + 'list': True, + 'full_id': True, + 'id': 'cluster1' + }) + attrs = [mock.Mock(node_id='FAKE1', attr_value='VALUE1')] + fields = ['node_id', 'attr_value'] + formatters = {'attr_value': utils.json_formatter} + service.collect_cluster_attrs = mock.Mock(return_value=attrs) + + sh.do_cluster_collect(service, args) + + service.collect_cluster_attrs.assert_called_once_with( + 'cluster1', 'path.to.attr') + mock_print.assert_called_once_with(attrs, fields, + formatters=formatters) + + @mock.patch.object(utils, 'print_list') + def test_do_cluster_collect_as_list_with_shortid(self, mock_print): + service = mock.Mock() + args = self._make_args({ + 'path': 'path.to.attr', + 'list': True, + 'full_id': False, + 'id': 'cluster1' + }) + attrs = [mock.Mock(node_id='FAKE1', attr_value='VALUE1')] + fields = ['node_id', 'attr_value'] + formatters = {'node_id': mock.ANY, 'attr_value': utils.json_formatter} + service.collect_cluster_attrs = mock.Mock(return_value=attrs) + + sh.do_cluster_collect(service, args) + + service.collect_cluster_attrs.assert_called_once_with( + 'cluster1', 'path.to.attr') + mock_print.assert_called_once_with(attrs, fields, + formatters=formatters) + @mock.patch.object(utils, 'print_list') def test_do_node_list(self, mock_print): service = mock.Mock() @@ -1104,7 +1426,7 @@ class ShellTest(testtools.TestCase): sh._show_node(service, node_id, show_details=False) - service.get_node.assert_called_once_with(node_id, args=None) + service.get_node.assert_called_once_with(node_id, details=False) mock_print.assert_called_once_with(data, formatters=formatters) @mock.patch.object(sh, '_show_node') @@ -1152,17 +1474,6 @@ class ShellTest(testtools.TestCase): service.delete_node.assert_called_once_with('node1', False) - def test_do_node_delete_not_found(self): - service = mock.Mock() - ex = oexc.ResourceNotFound - service.delete_node.side_effect = ex - - args = self._make_args({'id': ['node1']}) - ex = self.assertRaises(exc.CommandError, - sh.do_node_delete, service, args) - msg = _('Failed to delete some of the specified nodes.') - self.assertEqual(msg, six.text_type(ex)) - def test_do_node_check(self): service = mock.Mock() args = self._make_args({'id': ['node1']}) @@ -1232,7 +1543,7 @@ class ShellTest(testtools.TestCase): def test_do_event_list(self, mock_print): service = mock.Mock() fields = ['id', 'timestamp', 'obj_type', 'obj_id', 'obj_name', - 'action', 'status', 'status_reason', 'level'] + 'action', 'status', 'level', 'cluster_id'] args = { 'sort': 'timestamp:asc', 'limit': 20, diff --git a/senlinclient/v1/action.py b/senlinclient/v1/action.py index 2ab4a3a..f5d7ed6 100644 --- a/senlinclient/v1/action.py +++ b/senlinclient/v1/action.py @@ -13,19 +13,17 @@ """Clustering v1 action implementations""" import logging -import six -from cliff import lister -from cliff import show from openstack import exceptions as sdk_exc -from openstackclient.common import exceptions as exc -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import exceptions as exc +from osc_lib import utils from senlinclient.common.i18n import _ from senlinclient.common import utils as senlin_utils -class ListAction(lister.Lister): +class ListAction(command.Lister): """List actions.""" log = logging.getLogger(__name__ + ".ListAction") @@ -104,12 +102,13 @@ class ListAction(lister.Lister): return ( columns, - (utils.get_item_properties(a, columns, formatters=formatters) + (utils.get_item_properties(a.to_dict(), columns, + formatters=formatters) for a in actions) ) -class ShowAction(show.ShowOne): +class ShowAction(command.ShowOne): """Show detailed info about the specified action.""" log = logging.getLogger(__name__ + ".ShowAction") @@ -141,6 +140,7 @@ class ShowAction(show.ShowOne): 'depends_on': senlin_utils.list_formatter, 'depended_by': senlin_utils.list_formatter, } - columns = sorted(list(six.iterkeys(action))) - return columns, utils.get_dict_properties(action.to_dict(), columns, + data = action.to_dict() + columns = sorted(data.keys()) + return columns, utils.get_dict_properties(data, columns, formatters=formatters) diff --git a/senlinclient/v1/build_info.py b/senlinclient/v1/build_info.py index 9de5dca..9c6dbb5 100644 --- a/senlinclient/v1/build_info.py +++ b/senlinclient/v1/build_info.py @@ -13,15 +13,14 @@ """Clustering v1 build_info action implementations""" import logging -import six -from cliff import show -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import utils from senlinclient.common import utils as senlin_utils -class BuildInfo(show.ShowOne): +class BuildInfo(command.ShowOne): """Retrieve build information.""" log = logging.getLogger(__name__ + ".BuildInfo") @@ -40,6 +39,10 @@ class BuildInfo(show.ShowOne): 'api': senlin_utils.json_formatter, 'engine': senlin_utils.json_formatter, } - columns = sorted(list(six.iterkeys(result))) - return columns, utils.get_dict_properties(result.to_dict(), columns, + data = { + 'api': result.api, + 'engine': result.engine, + } + columns = ['api', 'engine'] + return columns, utils.get_dict_properties(data, columns, formatters=formatters) diff --git a/senlinclient/v1/client.py b/senlinclient/v1/client.py index 4667fa9..1852b39 100644 --- a/senlinclient/v1/client.py +++ b/senlinclient/v1/client.py @@ -15,8 +15,9 @@ from senlinclient.common import sdk class Client(object): - def __init__(self, preferences=None, user_agent=None, **kwargs): - self.conn = sdk.create_connection(preferences, user_agent, **kwargs) + def __init__(self, prof=None, user_agent=None, **kwargs): + self.conn = sdk.create_connection(prof=prof, user_agent=user_agent, + **kwargs) self.service = self.conn.cluster ###################################################################### @@ -83,6 +84,14 @@ class Client(object): """ return self.service.delete_profile(profile, ignore_missing) + def validate_profile(self, **attrs): + """Validate a profile spec + + Doc link: + http://developer.openstack.org/api-ref-clustering-v1.html#validateProfile + """ + return self.service.validate_profile(**attrs) + def policy_types(self, **query): """List policy types @@ -141,6 +150,14 @@ class Client(object): """ return self.service.delete_policy(policy, ignore_missing) + def validate_policy(self, **attrs): + """validate a policy spec + + Doc link: + http://developer.openstack.org/api-ref-clustering-v1.html#validatePolicy + """ + return self.service.validate_policy(**attrs) + def clusters(self, **queries): """List clusters @@ -263,6 +280,14 @@ class Client(object): """ return self.service.cluster_update_policy(cluster, policy, **attrs) + def cluster_collect(self, cluster, path): + """Resize cluster + + Doc link: + http://developer.openstack.org/api-ref-clustering-v1.html#clusterAction + """ + return self.service.cluster_collect(cluster, path) + def check_cluster(self, cluster, **params): """Check cluster's health status @@ -295,13 +320,13 @@ class Client(object): """ return self.service.create_node(**attrs) - def get_node(self, node, args=None): + def get_node(self, node, details=False): """Show node details Doc link: http://developer.openstack.org/api-ref-clustering-v1.html#showNode """ - return self.service.get_node(node, args=args) + return self.service.get_node(node, details=details) def update_node(self, node, **attrs): """Update node diff --git a/senlinclient/v1/cluster.py b/senlinclient/v1/cluster.py index ebb0a6d..7724588 100644 --- a/senlinclient/v1/cluster.py +++ b/senlinclient/v1/cluster.py @@ -13,22 +13,23 @@ """Clustering v1 cluster action implementations""" import logging -import six +import subprocess import sys +import threading +import time -from cliff import command -from cliff import lister -from cliff import show from openstack import exceptions as sdk_exc -from openstackclient.common import exceptions as exc -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import exceptions as exc +from osc_lib import utils +import six from senlinclient.common.i18n import _ from senlinclient.common.i18n import _LI from senlinclient.common import utils as senlin_utils -class ListCluster(lister.Lister): +class ListCluster(command.Lister): """List the user's clusters.""" log = logging.getLogger(__name__ + ".ListCluster") @@ -106,7 +107,7 @@ class ListCluster(lister.Lister): ) -class ShowCluster(show.ShowOne): +class ShowCluster(command.ShowOne): """Show details of the cluster.""" log = logging.getLogger(__name__ + ".ShowCluster") @@ -135,26 +136,21 @@ def _show_cluster(senlin_client, cluster_id): formatters = { 'metadata': senlin_utils.json_formatter, - 'nodes': senlin_utils.list_formatter + 'node_ids': senlin_utils.list_formatter } - columns = sorted(list(six.iterkeys(cluster))) - return columns, utils.get_dict_properties(cluster.to_dict(), columns, + data = cluster.to_dict() + columns = sorted(data.keys()) + return columns, utils.get_dict_properties(data, columns, formatters=formatters) -class CreateCluster(show.ShowOne): +class CreateCluster(command.ShowOne): """Create the cluster.""" log = logging.getLogger(__name__ + ".CreateCluster") def get_parser(self, prog_name): parser = super(CreateCluster, self).get_parser(prog_name) - parser.add_argument( - '--profile', - metavar='', - required=True, - help=_('Profile Id used for this cluster') - ) parser.add_argument( '--min-size', metavar='', @@ -188,6 +184,12 @@ class CreateCluster(show.ShowOne): 'key-value pairs separated by a semicolon.'), action='append' ) + parser.add_argument( + '--profile', + metavar='', + required=True, + help=_('Profile Id used for this cluster') + ) parser.add_argument( 'name', metavar='', @@ -215,7 +217,7 @@ class CreateCluster(show.ShowOne): return _show_cluster(senlin_client, cluster.id) -class UpdateCluster(show.ShowOne): +class UpdateCluster(command.ShowOne): """Update the cluster.""" log = logging.getLogger(__name__ + ".UpdateCluster") @@ -309,20 +311,16 @@ class DeleteCluster(command.Command): self.log.info(_LI('Ctrl-d detected')) return - failure_count = 0 - + result = {} for cid in parsed_args.cluster: try: - senlin_client.delete_cluster(cid, False) + cluster = senlin_client.delete_cluster(cid, False) + result[cid] = ('OK', cluster.location.split('/')[-1]) except Exception as ex: - failure_count += 1 - print(ex) - if failure_count: - raise exc.CommandError(_('Failed to delete %(count)s of the ' - '%(total)s specified cluster(s).') % - {'count': failure_count, - 'total': len(parsed_args.cluster)}) - print('Request accepted') + result[cid] = ('ERROR', six.text_type(ex)) + + for rid, res in result.items(): + senlin_utils.print_action_result(rid, res) class ResizeCluster(command.Command): @@ -405,11 +403,6 @@ class ResizeCluster(command.Command): raise exc.CommandError(_("Only one of 'capacity', 'adjustment' and" " 'percentage' can be specified.")) - if sum(v is None for v in (capacity, adjustment, percentage)) == 3: - raise exc.CommandError(_("At least one of 'capacity', " - "'adjustment' and 'percentage' " - "should be specified.")) - action_args['adjustment_type'] = None action_args['number'] = None @@ -527,12 +520,6 @@ class ClusterPolicyAttach(command.Command): def get_parser(self, prog_name): parser = super(ClusterPolicyAttach, self).get_parser(prog_name) - parser.add_argument( - '--policy', - metavar='', - required=True, - help=_('ID or name of policy to be attached') - ) parser.add_argument( '--enabled', default=True, @@ -540,6 +527,12 @@ class ClusterPolicyAttach(command.Command): help=_('Whether the policy should be enabled once attached. ' 'Default to True') ) + parser.add_argument( + '--policy', + metavar='', + required=True, + help=_('ID or name of policy to be attached') + ) parser.add_argument( 'cluster', metavar='', @@ -589,7 +582,7 @@ class ClusterPolicyDetach(command.Command): print('Request accepted by action: %s' % resp['action']) -class ClusterNodeList(lister.Lister): +class ClusterNodeList(command.Lister): """List nodes from cluster.""" log = logging.getLogger(__name__ + ".ClusterNodeList") @@ -772,3 +765,246 @@ class RecoverCluster(command.Command): print('Cluster recover request on cluster %(cid)s is accepted by ' 'action %(action)s.' % {'cid': cid, 'action': resp['action']}) + + +class ClusterCollect(command.Lister): + """Recover the cluster(s).""" + log = logging.getLogger(__name__ + ".ClusterCollect") + + def get_parser(self, prog_name): + parser = super(ClusterCollect, self).get_parser(prog_name) + parser.add_argument( + '--full-id', + default=False, + action="store_true", + help=_('Print full IDs in list') + ) + parser.add_argument( + '--path', + metavar='', + required=True, + help=_('JSON path expression for attribute to be collected') + ) + parser.add_argument( + 'cluster', + metavar='', + help=_('ID or name of cluster(s) to operate on.') + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + senlin_client = self.app.client_manager.clustering + attrs = senlin_client.collect_cluster_attrs(parsed_args.cluster, + parsed_args.path) + columns = ['node_id', 'attr_value'] + formatters = {} + if not parsed_args.full_id: + formatters = { + 'node_id': lambda x: x[:8] + } + return (columns, + (utils.get_item_properties(a, columns, formatters=formatters) + for a in attrs)) + + +class ClusterRun(command.Command): + """Run scripts on cluster.""" + log = logging.getLogger(__name__ + ".ClusterRun") + + def get_parser(self, prog_name): + parser = super(ClusterRun, self).get_parser(prog_name) + parser.add_argument( + '--port', + metavar='', + type=int, + default=22, + help=_('The TCP port to use for SSH connection') + ) + parser.add_argument( + '--address-type', + metavar='', + default='floating', + help=_("The type of IP address to use. Possible values include " + "'fixed' and 'floating' (the default)") + ) + parser.add_argument( + '--network', + metavar='', + default='', + help=_("The network to use for SSH connection") + ) + parser.add_argument( + '--ipv6', + action="store_true", + default=False, + help=_("Whether the IPv6 address should be used for SSH. Default " + "to use IPv4 address.") + ) + parser.add_argument( + '--user', + metavar='', + default='root', + help=_("The login name to use for SSH connection. Default to " + "'root'.") + ) + parser.add_argument( + '--identity-file', + metavar='', + help=_("The private key file to use, same as the '-i' SSH option") + ) + parser.add_argument( + '--ssh-options', + metavar='', + default="", + help=_("Extra options to pass to SSH. See: man ssh.") + ) + parser.add_argument( + '--script', + metavar='