From 10ac9cb4d768ff2181386009de29a95e9740e41e Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Mon, 6 Jun 2016 08:15:33 +0200 Subject: [PATCH 01/47] Remove unused POT file Translation source files are not needed anymore in repositories, remove the outdated and unused file. Update tox.ini file so that tests pass even if there are no pot files i ntree. See http://docs.openstack.org/infra/manual/creators.html#checking-translation-imports Change-Id: Ibb8845ebcb9c07a4c0127520b12d711e0a1baca0 --- senlinclient/locale/senlinclient.pot | 1013 -------------------------- tox.ini | 2 +- 2 files changed, 1 insertion(+), 1014 deletions(-) delete mode 100644 senlinclient/locale/senlinclient.pot 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/tox.ini b/tox.ini index 23c94bb..e462ee5 100644 --- a/tox.ini +++ b/tox.ini @@ -18,7 +18,7 @@ whitelist_externals = find commands = flake8 # Check that .po and .pot files are valid: - bash -c "find senlinclient -type f -regex '.*\.pot?' -print0|xargs -0 -n 1 msgfmt --check-format -o /dev/null" + bash -c "find senlinclient -type f -regex '.*\.pot?' -print0|xargs -0 -n 1 --no-run-if-empty msgfmt --check-format -o /dev/null" whitelist_externals = bash [testenv:venv] From 84916acf84652835f80e9aa492f13b40b127adf7 Mon Sep 17 00:00:00 2001 From: yanyanhu Date: Mon, 13 Jun 2016 04:32:16 -0400 Subject: [PATCH 02/47] Fix openstack cluster resize User should be allowed to resize cluster without changing its size. This patch removes incorrect constraint defined in openstack cluster resize implementation. Change-Id: I7592f76d23a583bcc696a86dc75ada41be6b0f9d --- senlinclient/tests/unit/v1/test_cluster.py | 18 ++++++++++++------ senlinclient/v1/cluster.py | 5 ----- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/senlinclient/tests/unit/v1/test_cluster.py b/senlinclient/tests/unit/v1/test_cluster.py index 6495ea0..bb741df 100644 --- a/senlinclient/tests/unit/v1/test_cluster.py +++ b/senlinclient/tests/unit/v1/test_cluster.py @@ -425,14 +425,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', diff --git a/senlinclient/v1/cluster.py b/senlinclient/v1/cluster.py index ebb0a6d..3ceef5f 100644 --- a/senlinclient/v1/cluster.py +++ b/senlinclient/v1/cluster.py @@ -405,11 +405,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 From 8cc30e14bba2716bdb2854dadc25b9aeda10e24d Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 16 Jun 2016 06:52:54 +0000 Subject: [PATCH 03/47] Imported Translations from Zanata For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: I10da077fa4548c86587d50b67d81c06f76bcf2cc --- .../locale/zh_CN/LC_MESSAGES/releasenotes.po | 40 +++++++++++++++++++ .../LC_MESSAGES/senlinclient-log-info.po | 21 ++++++++++ .../LC_MESSAGES/senlinclient-log-warning.po | 19 +++++++++ 3 files changed, 80 insertions(+) create mode 100644 releasenotes/source/locale/zh_CN/LC_MESSAGES/releasenotes.po create mode 100644 senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient-log-info.po create mode 100644 senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient-log-warning.po 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..617e9c2 --- /dev/null +++ b/releasenotes/source/locale/zh_CN/LC_MESSAGES/releasenotes.po @@ -0,0 +1,40 @@ +# Zheng Xi Zhou , 2016. #zanata +msgid "" +msgstr "" +"Project-Id-Version: Senlin Client Release Notes 0.4.2\n" +"Report-Msgid-Bugs-To: \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:52+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 "0.4.1-33" +msgstr "0.4.1-33" + +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/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\"。" From 1351bcb418a70f275d3d0640b2448ed64aaa9729 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 18 Jun 2016 06:55:22 +0000 Subject: [PATCH 04/47] Imported Translations from Zanata For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: Idce2225d3a3c468c167eedf9d46246bfd044a705 --- .../source/locale/zh_CN/LC_MESSAGES/releasenotes.po | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/releasenotes/source/locale/zh_CN/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/zh_CN/LC_MESSAGES/releasenotes.po index 617e9c2..32bddf3 100644 --- a/releasenotes/source/locale/zh_CN/LC_MESSAGES/releasenotes.po +++ b/releasenotes/source/locale/zh_CN/LC_MESSAGES/releasenotes.po @@ -1,21 +1,21 @@ # Zheng Xi Zhou , 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: Senlin Client Release Notes 0.4.2\n" +"Project-Id-Version: Senlin Client Release Notes 0.5.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-06-06 07:37+0000\n" +"POT-Creation-Date: 2016-06-16 09:35+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:52+0000\n" +"PO-Revision-Date: 2016-06-17 08:53+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 "0.4.1-33" -msgstr "0.4.1-33" +msgid "0.5.0" +msgstr "0.5.0" msgid "Added command for node-check and node-recover." msgstr "已为node-check和node-recover添加了命令。" From b5dc38d8ff779e88fe1ca9322a40db18463817c7 Mon Sep 17 00:00:00 2001 From: zzxwill Date: Thu, 23 Jun 2016 04:08:54 -0400 Subject: [PATCH 05/47] Enhance message related to OS_PROJECT_DOMAIN_NAME env[OS_PROJECT_DOMAIN_NAME] aren't complete in the two messages. Enhance them. Change-Id: Ia6c63cfa648be48d40bb2473ffbdcbd3304ee344 --- senlinclient/shell.py | 6 +++--- senlinclient/tests/unit/test_shell.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/senlinclient/shell.py b/senlinclient/shell.py index 5954982..0c16727 100644 --- a/senlinclient/shell.py +++ b/senlinclient/shell.py @@ -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): diff --git a/senlinclient/tests/unit/test_shell.py b/senlinclient/tests/unit/test_shell.py index dbeaaf7..e0eb883 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)) From 9891067bb0485221e3f24a2e2095b40e5fe9e451 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 24 Jun 2016 11:56:06 +0800 Subject: [PATCH 06/47] Use osc-lib instead of openstackclient osc-lib is a package of common support modules for writing OSC plugins. All common functions, classes such as exceptions, utils, logs and so on have been moved from openstackclient to osc-lib. So use osc-lib instead of openstackclient. Change-Id: I94fceb763e7370596357db949f5e3eef091ee561 --- requirements.txt | 2 +- senlinclient/plugin.py | 2 +- senlinclient/tests/unit/test_format_utils.py | 2 +- senlinclient/tests/unit/v1/fakes.py | 2 +- senlinclient/tests/unit/v1/test_action.py | 2 +- senlinclient/tests/unit/v1/test_cluster.py | 2 +- senlinclient/tests/unit/v1/test_event.py | 2 +- senlinclient/tests/unit/v1/test_node.py | 2 +- senlinclient/tests/unit/v1/test_policy.py | 2 +- senlinclient/tests/unit/v1/test_policy_type.py | 2 +- senlinclient/tests/unit/v1/test_profile.py | 4 ++-- senlinclient/tests/unit/v1/test_profile_type.py | 2 +- senlinclient/tests/unit/v1/test_receiver.py | 2 +- senlinclient/v1/action.py | 4 ++-- senlinclient/v1/build_info.py | 2 +- senlinclient/v1/cluster.py | 4 ++-- senlinclient/v1/cluster_policy.py | 2 +- senlinclient/v1/event.py | 4 ++-- senlinclient/v1/node.py | 4 ++-- senlinclient/v1/policy.py | 4 ++-- senlinclient/v1/policy_type.py | 3 ++- senlinclient/v1/profile.py | 4 ++-- senlinclient/v1/profile_type.py | 2 +- senlinclient/v1/receiver.py | 4 ++-- 24 files changed, 33 insertions(+), 32 deletions(-) diff --git a/requirements.txt b/requirements.txt index e0318fe..4703cf3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,11 +7,11 @@ 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 +osc-lib>=0.1.0 # 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 PyYAML>=3.1.0 # MIT requests>=2.10.0 # Apache-2.0 six>=1.9.0 # MIT diff --git a/senlinclient/plugin.py b/senlinclient/plugin.py index bc92e30..d48865f 100644 --- a/senlinclient/plugin.py +++ b/senlinclient/plugin.py @@ -13,7 +13,7 @@ import logging from openstack import connection -from openstackclient.common import utils +from osc_lib import utils LOG = logging.getLogger(__name__) 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/v1/fakes.py b/senlinclient/tests/unit/v1/fakes.py index d591454..951594e 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" diff --git a/senlinclient/tests/unit/v1/test_action.py b/senlinclient/tests/unit/v1/test_action.py index 21927dc..c4f33fb 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 diff --git a/senlinclient/tests/unit/v1/test_cluster.py b/senlinclient/tests/unit/v1/test_cluster.py index bb741df..5ca088c 100644 --- a/senlinclient/tests/unit/v1/test_cluster.py +++ b/senlinclient/tests/unit/v1/test_cluster.py @@ -16,7 +16,7 @@ import six 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 from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import cluster as osc_cluster diff --git a/senlinclient/tests/unit/v1/test_event.py b/senlinclient/tests/unit/v1/test_event.py index 7cd2f6a..b854e49 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 diff --git a/senlinclient/tests/unit/v1/test_node.py b/senlinclient/tests/unit/v1/test_node.py index 6bfdada..1acc23d 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 diff --git a/senlinclient/tests/unit/v1/test_policy.py b/senlinclient/tests/unit/v1/test_policy.py index 350a7d9..cadfd6c 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 diff --git a/senlinclient/tests/unit/v1/test_policy_type.py b/senlinclient/tests/unit/v1/test_policy_type.py index 956dc16..59c501b 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 diff --git a/senlinclient/tests/unit/v1/test_profile.py b/senlinclient/tests/unit/v1/test_profile.py index d84e0d0..a2c410c 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 diff --git a/senlinclient/tests/unit/v1/test_profile_type.py b/senlinclient/tests/unit/v1/test_profile_type.py index 5a1fa0f..2d61d70 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 diff --git a/senlinclient/tests/unit/v1/test_receiver.py b/senlinclient/tests/unit/v1/test_receiver.py index 053bdc7..6982106 100644 --- a/senlinclient/tests/unit/v1/test_receiver.py +++ b/senlinclient/tests/unit/v1/test_receiver.py @@ -16,7 +16,7 @@ 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.tests.unit.v1 import fakes from senlinclient.v1 import receiver as osc_receiver diff --git a/senlinclient/v1/action.py b/senlinclient/v1/action.py index 2ab4a3a..1e0187b 100644 --- a/senlinclient/v1/action.py +++ b/senlinclient/v1/action.py @@ -18,8 +18,8 @@ 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 import exceptions as exc +from osc_lib import utils from senlinclient.common.i18n import _ from senlinclient.common import utils as senlin_utils diff --git a/senlinclient/v1/build_info.py b/senlinclient/v1/build_info.py index 9de5dca..9d1b9f3 100644 --- a/senlinclient/v1/build_info.py +++ b/senlinclient/v1/build_info.py @@ -16,7 +16,7 @@ import logging import six from cliff import show -from openstackclient.common import utils +from osc_lib import utils from senlinclient.common import utils as senlin_utils diff --git a/senlinclient/v1/cluster.py b/senlinclient/v1/cluster.py index 3ceef5f..3dc77a1 100644 --- a/senlinclient/v1/cluster.py +++ b/senlinclient/v1/cluster.py @@ -20,8 +20,8 @@ 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 import exceptions as exc +from osc_lib import utils from senlinclient.common.i18n import _ from senlinclient.common.i18n import _LI diff --git a/senlinclient/v1/cluster_policy.py b/senlinclient/v1/cluster_policy.py index e7462f2..21cc111 100644 --- a/senlinclient/v1/cluster_policy.py +++ b/senlinclient/v1/cluster_policy.py @@ -18,7 +18,7 @@ import six from cliff import command from cliff import lister from cliff import show -from openstackclient.common import utils +from osc_lib import utils from senlinclient.common.i18n import _ from senlinclient.common import utils as senlin_utils diff --git a/senlinclient/v1/event.py b/senlinclient/v1/event.py index cc2ee8b..8df137d 100644 --- a/senlinclient/v1/event.py +++ b/senlinclient/v1/event.py @@ -18,8 +18,8 @@ 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 import exceptions as exc +from osc_lib import utils from senlinclient.common.i18n import _ from senlinclient.common import utils as senlin_utils diff --git a/senlinclient/v1/node.py b/senlinclient/v1/node.py index 9e2ac63..c9d8813 100644 --- a/senlinclient/v1/node.py +++ b/senlinclient/v1/node.py @@ -20,8 +20,8 @@ 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 import exceptions as exc +from osc_lib import utils from senlinclient.common.i18n import _ from senlinclient.common.i18n import _LI diff --git a/senlinclient/v1/policy.py b/senlinclient/v1/policy.py index f4bd00d..e51bed3 100644 --- a/senlinclient/v1/policy.py +++ b/senlinclient/v1/policy.py @@ -19,8 +19,8 @@ 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 import exceptions as exc +from osc_lib import utils from senlinclient.common.i18n import _ from senlinclient.common.i18n import _LI diff --git a/senlinclient/v1/policy_type.py b/senlinclient/v1/policy_type.py index 5df67d4..465ecba 100644 --- a/senlinclient/v1/policy_type.py +++ b/senlinclient/v1/policy_type.py @@ -17,7 +17,8 @@ import six from cliff import lister from openstack import exceptions as sdk_exc -from openstackclient.common import exceptions as exc +from osc_lib import exceptions as exc + from senlinclient.common import format_utils from senlinclient.common.i18n import _ diff --git a/senlinclient/v1/profile.py b/senlinclient/v1/profile.py index cfd0602..6b6b0c1 100644 --- a/senlinclient/v1/profile.py +++ b/senlinclient/v1/profile.py @@ -19,8 +19,8 @@ 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 import exceptions as exc +from osc_lib import utils from senlinclient.common.i18n import _ from senlinclient.common.i18n import _LI diff --git a/senlinclient/v1/profile_type.py b/senlinclient/v1/profile_type.py index 563dbd8..b82043b 100644 --- a/senlinclient/v1/profile_type.py +++ b/senlinclient/v1/profile_type.py @@ -17,7 +17,7 @@ import six from cliff import lister from openstack import exceptions as sdk_exc -from openstackclient.common import exceptions as exc +from osc_lib import exceptions as exc from senlinclient.common import format_utils from senlinclient.common.i18n import _ diff --git a/senlinclient/v1/receiver.py b/senlinclient/v1/receiver.py index d8215bc..58d0758 100644 --- a/senlinclient/v1/receiver.py +++ b/senlinclient/v1/receiver.py @@ -20,8 +20,8 @@ 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 import exceptions as exc +from osc_lib import utils from senlinclient.common.i18n import _ from senlinclient.common.i18n import _LI From b2021600fb7db0267b8fcff11fc310f7f7028e8c Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 25 Jun 2016 20:26:37 +0000 Subject: [PATCH 07/47] Updated from global requirements Change-Id: I3b0aa2dcc3e282733a1b05e6bb4889d1d94a4acb --- test-requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test-requirements.txt b/test-requirements.txt index c2cf2a4..693ff30 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -7,13 +7,13 @@ hacking<0.11,>=0.10.0 coverage>=3.6 # Apache-2.0 discover # BSD fixtures>=3.0.0 # Apache-2.0/BSD -requests-mock>=0.7.0 # Apache-2.0 +requests-mock>=1.0 # Apache-2.0 mock>=2.0 # BSD mox3>=0.7.0 # Apache-2.0 oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 -sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD +sphinx!=1.3b1,<1.3,>=1.2.1 # BSD testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=1.4.0 # MIT -reno>=1.6.2 # Apache2 +reno>=1.8.0 # Apache2 From 4437ed83bea6ed402f3d91626b2cea618139a329 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 27 Jun 2016 20:14:37 +0800 Subject: [PATCH 08/47] Use osc_lib instead of cliff Base classes of commands are defined in cliff, but have been encapsulated again in osc-lib for all plugin clients. So use osc-lib instead of cliff. Change-Id: I7b64c4dbedf270720f222471d57b9e20ab5a046c --- requirements.txt | 1 - senlinclient/common/format_utils.py | 4 ++-- senlinclient/v1/action.py | 7 +++---- senlinclient/v1/build_info.py | 4 ++-- senlinclient/v1/cluster.py | 14 ++++++-------- senlinclient/v1/cluster_policy.py | 8 +++----- senlinclient/v1/event.py | 7 +++---- senlinclient/v1/node.py | 12 +++++------- senlinclient/v1/policy.py | 12 +++++------- senlinclient/v1/policy_type.py | 4 ++-- senlinclient/v1/profile.py | 12 +++++------- senlinclient/v1/profile_type.py | 4 ++-- senlinclient/v1/receiver.py | 10 ++++------ 13 files changed, 42 insertions(+), 57 deletions(-) diff --git a/requirements.txt b/requirements.txt index 4703cf3..39edb2a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,6 @@ 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 osc-lib>=0.1.0 # Apache-2.0 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/v1/action.py b/senlinclient/v1/action.py index 1e0187b..19f6849 100644 --- a/senlinclient/v1/action.py +++ b/senlinclient/v1/action.py @@ -15,9 +15,8 @@ import logging import six -from cliff import lister -from cliff import show from openstack import exceptions as sdk_exc +from osc_lib.command import command from osc_lib import exceptions as exc from osc_lib import utils @@ -25,7 +24,7 @@ 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") @@ -109,7 +108,7 @@ class ListAction(lister.Lister): ) -class ShowAction(show.ShowOne): +class ShowAction(command.ShowOne): """Show detailed info about the specified action.""" log = logging.getLogger(__name__ + ".ShowAction") diff --git a/senlinclient/v1/build_info.py b/senlinclient/v1/build_info.py index 9d1b9f3..bba0d89 100644 --- a/senlinclient/v1/build_info.py +++ b/senlinclient/v1/build_info.py @@ -15,13 +15,13 @@ import logging import six -from cliff import show +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") diff --git a/senlinclient/v1/cluster.py b/senlinclient/v1/cluster.py index 3dc77a1..28378cb 100644 --- a/senlinclient/v1/cluster.py +++ b/senlinclient/v1/cluster.py @@ -16,10 +16,8 @@ import logging import six import sys -from cliff import command -from cliff import lister -from cliff import show from openstack import exceptions as sdk_exc +from osc_lib.command import command from osc_lib import exceptions as exc from osc_lib import utils @@ -28,7 +26,7 @@ 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 +104,7 @@ class ListCluster(lister.Lister): ) -class ShowCluster(show.ShowOne): +class ShowCluster(command.ShowOne): """Show details of the cluster.""" log = logging.getLogger(__name__ + ".ShowCluster") @@ -142,7 +140,7 @@ def _show_cluster(senlin_client, cluster_id): formatters=formatters) -class CreateCluster(show.ShowOne): +class CreateCluster(command.ShowOne): """Create the cluster.""" log = logging.getLogger(__name__ + ".CreateCluster") @@ -215,7 +213,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") @@ -584,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") diff --git a/senlinclient/v1/cluster_policy.py b/senlinclient/v1/cluster_policy.py index 21cc111..466c517 100644 --- a/senlinclient/v1/cluster_policy.py +++ b/senlinclient/v1/cluster_policy.py @@ -15,16 +15,14 @@ import logging import six -from cliff import command -from cliff import lister -from cliff import show +from osc_lib.command import command from osc_lib import utils from senlinclient.common.i18n import _ from senlinclient.common import utils as senlin_utils -class ClusterPolicyList(lister.Lister): +class ClusterPolicyList(command.Lister): """List policies from cluster.""" log = logging.getLogger(__name__ + ".ClusterPolicyList") @@ -88,7 +86,7 @@ class ClusterPolicyList(lister.Lister): ) -class ClusterPolicyShow(show.ShowOne): +class ClusterPolicyShow(command.ShowOne): """Show a specific policy that is bound to the specified cluster.""" log = logging.getLogger(__name__ + ".ClusterPolicyShow") diff --git a/senlinclient/v1/event.py b/senlinclient/v1/event.py index 8df137d..d5fa731 100644 --- a/senlinclient/v1/event.py +++ b/senlinclient/v1/event.py @@ -15,9 +15,8 @@ import logging import six -from cliff import lister -from cliff import show from openstack import exceptions as sdk_exc +from osc_lib.command import command from osc_lib import exceptions as exc from osc_lib import utils @@ -25,7 +24,7 @@ from senlinclient.common.i18n import _ from senlinclient.common import utils as senlin_utils -class ListEvent(lister.Lister): +class ListEvent(command.Lister): """List events.""" log = logging.getLogger(__name__ + ".ListEvent") @@ -106,7 +105,7 @@ class ListEvent(lister.Lister): ) -class ShowEvent(show.ShowOne): +class ShowEvent(command.ShowOne): """Describe the event.""" log = logging.getLogger(__name__ + ".ShowEvent") diff --git a/senlinclient/v1/node.py b/senlinclient/v1/node.py index c9d8813..c92b01b 100644 --- a/senlinclient/v1/node.py +++ b/senlinclient/v1/node.py @@ -16,10 +16,8 @@ import logging import six import sys -from cliff import command -from cliff import lister -from cliff import show from openstack import exceptions as sdk_exc +from osc_lib.command import command from osc_lib import exceptions as exc from osc_lib import utils @@ -28,7 +26,7 @@ from senlinclient.common.i18n import _LI from senlinclient.common import utils as senlin_utils -class ListNode(lister.Lister): +class ListNode(command.Lister): """Show list of nodes.""" log = logging.getLogger(__name__ + ".ListNode") @@ -118,7 +116,7 @@ class ListNode(lister.Lister): ) -class ShowNode(show.ShowOne): +class ShowNode(command.ShowOne): """Show detailed info about the specified node.""" log = logging.getLogger(__name__ + ".ShowNode") @@ -167,7 +165,7 @@ def _show_node(senlin_client, node_id, show_details=False): formatters=formatters) -class CreateNode(show.ShowOne): +class CreateNode(command.ShowOne): """Create the node.""" log = logging.getLogger(__name__ + ".CreateNode") @@ -221,7 +219,7 @@ class CreateNode(show.ShowOne): return _show_node(senlin_client, node.id) -class UpdateNode(show.ShowOne): +class UpdateNode(command.ShowOne): """Update the node.""" log = logging.getLogger(__name__ + ".UpdateNode") diff --git a/senlinclient/v1/policy.py b/senlinclient/v1/policy.py index e51bed3..c888f6d 100644 --- a/senlinclient/v1/policy.py +++ b/senlinclient/v1/policy.py @@ -15,10 +15,8 @@ import logging import sys -from cliff import command -from cliff import lister -from cliff import show from openstack import exceptions as sdk_exc +from osc_lib.command import command from osc_lib import exceptions as exc from osc_lib import utils @@ -27,7 +25,7 @@ from senlinclient.common.i18n import _LI from senlinclient.common import utils as senlin_utils -class ListPolicy(lister.Lister): +class ListPolicy(command.Lister): """List policies that meet the criteria.""" log = logging.getLogger(__name__ + ".ListPolicy") @@ -105,7 +103,7 @@ class ListPolicy(lister.Lister): ) -class ShowPolicy(show.ShowOne): +class ShowPolicy(command.ShowOne): """Show the policy details.""" log = logging.getLogger(__name__ + ".ShowPolicy") @@ -151,7 +149,7 @@ def _show_policy(senlin_client, policy_id): formatters=formatters) -class CreatePolicy(show.ShowOne): +class CreatePolicy(command.ShowOne): """Create a policy.""" log = logging.getLogger(__name__ + ".CreatePolicy") @@ -185,7 +183,7 @@ class CreatePolicy(show.ShowOne): return _show_policy(senlin_client, policy.id) -class UpdatePolicy(show.ShowOne): +class UpdatePolicy(command.ShowOne): """Update a policy.""" log = logging.getLogger(__name__ + ".UpdatePolicy") diff --git a/senlinclient/v1/policy_type.py b/senlinclient/v1/policy_type.py index 465ecba..e175c65 100644 --- a/senlinclient/v1/policy_type.py +++ b/senlinclient/v1/policy_type.py @@ -15,15 +15,15 @@ import logging import six -from cliff import lister from openstack import exceptions as sdk_exc +from osc_lib.command import command from osc_lib import exceptions as exc from senlinclient.common import format_utils from senlinclient.common.i18n import _ -class PolicyTypeList(lister.Lister): +class PolicyTypeList(command.Lister): """List the available policy types.""" log = logging.getLogger(__name__ + ".PolicyTypeList") diff --git a/senlinclient/v1/profile.py b/senlinclient/v1/profile.py index 6b6b0c1..56b57d2 100644 --- a/senlinclient/v1/profile.py +++ b/senlinclient/v1/profile.py @@ -15,10 +15,8 @@ import logging import sys -from cliff import command -from cliff import lister -from cliff import show from openstack import exceptions as sdk_exc +from osc_lib.command import command from osc_lib import exceptions as exc from osc_lib import utils @@ -27,7 +25,7 @@ from senlinclient.common.i18n import _LI from senlinclient.common import utils as senlin_utils -class ShowProfile(show.ShowOne): +class ShowProfile(command.ShowOne): """Show profile details.""" log = logging.getLogger(__name__ + ".ShowProfile") @@ -76,7 +74,7 @@ def _show_profile(senlin_client, profile_id): formatters=formatters) -class ListProfile(lister.Lister): +class ListProfile(command.Lister): """List profiles that meet the criteria.""" log = logging.getLogger(__name__ + ".ListProfile") @@ -207,7 +205,7 @@ class DeleteProfile(command.Command): print('Profile deleted: %s' % parsed_args.profile) -class CreateProfile(show.ShowOne): +class CreateProfile(command.ShowOne): """Create a profile.""" log = logging.getLogger(__name__ + ".CreateProfile") @@ -264,7 +262,7 @@ class CreateProfile(show.ShowOne): return _show_profile(senlin_client, profile_id=profile.id) -class UpdateProfile(show.ShowOne): +class UpdateProfile(command.ShowOne): """Update a profile.""" log = logging.getLogger(__name__ + ".UpdateProfile") diff --git a/senlinclient/v1/profile_type.py b/senlinclient/v1/profile_type.py index b82043b..dc7cfb0 100644 --- a/senlinclient/v1/profile_type.py +++ b/senlinclient/v1/profile_type.py @@ -15,14 +15,14 @@ import logging import six -from cliff import lister from openstack import exceptions as sdk_exc +from osc_lib.command import command from osc_lib import exceptions as exc from senlinclient.common import format_utils from senlinclient.common.i18n import _ -class ProfileTypeList(lister.Lister): +class ProfileTypeList(command.Lister): """List the available profile types.""" log = logging.getLogger(__name__ + ".ProfileTypeList") diff --git a/senlinclient/v1/receiver.py b/senlinclient/v1/receiver.py index 58d0758..79b0e3e 100644 --- a/senlinclient/v1/receiver.py +++ b/senlinclient/v1/receiver.py @@ -16,10 +16,8 @@ import logging import six import sys -from cliff import command -from cliff import lister -from cliff import show from openstack import exceptions as sdk_exc +from osc_lib.command import command from osc_lib import exceptions as exc from osc_lib import utils @@ -28,7 +26,7 @@ from senlinclient.common.i18n import _LI from senlinclient.common import utils as senlin_utils -class ListReceiver(lister.Lister): +class ListReceiver(command.Lister): """List receivers that meet the criteria.""" log = logging.getLogger(__name__ + ".ListReceiver") @@ -110,7 +108,7 @@ class ListReceiver(lister.Lister): ) -class ShowReceiver(show.ShowOne): +class ShowReceiver(command.ShowOne): """Show the receiver details.""" log = logging.getLogger(__name__ + ".ShowReceiver") @@ -147,7 +145,7 @@ def _show_receiver(senlin_client, receiver_id): formatters=formatters) -class CreateReceiver(show.ShowOne): +class CreateReceiver(command.ShowOne): """Create a receiver.""" log = logging.getLogger(__name__ + ".CreateReceiver") From f1b2791d085c4a92bc2380befcfc5060bd0fde81 Mon Sep 17 00:00:00 2001 From: zzxwill Date: Fri, 24 Jun 2016 23:12:24 -0400 Subject: [PATCH 09/47] Delete extra space Delete extra spaces in two messages Change-Id: I7a233f098e5aba2e4f7baa8720bbdfad42dd7a71 --- senlinclient/tests/unit/v1/test_shell.py | 2 +- senlinclient/v1/shell.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/senlinclient/tests/unit/v1/test_shell.py b/senlinclient/tests/unit/v1/test_shell.py index e2d1ef2..98e84cc 100644 --- a/senlinclient/tests/unit/v1/test_shell.py +++ b/senlinclient/tests/unit/v1/test_shell.py @@ -819,7 +819,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 diff --git a/senlinclient/v1/shell.py b/senlinclient/v1/shell.py index 6ef78e3..24853f1 100644 --- a/senlinclient/v1/shell.py +++ b/senlinclient/v1/shell.py @@ -659,7 +659,7 @@ def do_cluster_resize(service, args): if capacity is not None: if capacity < 0: raise exc.CommandError(_('Cluster capacity must be larger than ' - ' or equal to zero.')) + 'or equal to zero.')) action_args['adjustment_type'] = 'EXACT_CAPACITY' action_args['number'] = capacity From 986f64803126ce99417f858ee14817d19585113f Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 4 Jul 2016 06:38:05 +0000 Subject: [PATCH 10/47] Imported Translations from Zanata For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: I1c16f7dcdb2f74aebfd7f6b613e0c4389c4668f8 --- .../locale/zh_CN/LC_MESSAGES/releasenotes.po | 10 +- .../locale/zh_CN/LC_MESSAGES/senlinclient.po | 1237 +++++++++++++++++ 2 files changed, 1242 insertions(+), 5 deletions(-) create mode 100644 senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient.po diff --git a/releasenotes/source/locale/zh_CN/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/zh_CN/LC_MESSAGES/releasenotes.po index 32bddf3..5cdb4b3 100644 --- a/releasenotes/source/locale/zh_CN/LC_MESSAGES/releasenotes.po +++ b/releasenotes/source/locale/zh_CN/LC_MESSAGES/releasenotes.po @@ -1,14 +1,14 @@ -# Zheng Xi Zhou , 2016. #zanata +# 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-06-16 09:35+0000\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-17 08:53+0000\n" -"Last-Translator: Zheng Xi Zhou \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" @@ -37,4 +37,4 @@ msgid "Senlin Client Release Notes" msgstr "Senlin Client发布说明" msgid "Upgrade Notes" -msgstr "升级说明。" +msgstr "升级说明" 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..31e6e7e --- /dev/null +++ b/senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient.po @@ -0,0 +1,1237 @@ +# Andreas Jaeger , 2016. #zanata +# zzxwill , 2016. #zanata +msgid "" +msgstr "" +"Project-Id-Version: python-senlinclient 0.5.1.dev13\n" +"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" +"POT-Creation-Date: 2016-07-04 03:45+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-30 12:00+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 API interface, defaults to env[OS_INTERFACE]" +msgstr "必填的API接口,默认是env[OS_INTERFACE]" + +msgid "Desired API names, defaults to env[OS_API_NAME]" +msgstr "必填的API名字,默认是env[OS_API_NAME]" + +msgid "Desired API region, defaults to env[OS_API_REGION]" +msgstr "必填的API区域,默认是env[OS_API_REGION]" + +msgid "Desired API versions, defaults to env[OS_API_VERSION]" +msgstr "Desired API版本,默认是env[OS_API_VERSION]" + +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 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 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 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 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中的一种。" + +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])来提供认证链接" From 35d401a9fd98c1761ca40487113f36ff78f981cd Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 4 Jul 2016 11:42:10 +0000 Subject: [PATCH 11/47] Updated from global requirements Change-Id: I8a1ad192731e3473d6dd816e4819485a2e6f079d --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 39edb2a..eb4a1d0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ openstacksdk>=0.8.6 # Apache-2.0 osc-lib>=0.1.0 # 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 +oslo.utils>=3.14.0 # Apache-2.0 python-heatclient>=1.1.0 # Apache-2.0 PyYAML>=3.1.0 # MIT requests>=2.10.0 # Apache-2.0 From 27048ae5ba3d27b0e8c79d917193232ca301bbb9 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 9 Jul 2016 19:27:17 +0000 Subject: [PATCH 12/47] Updated from global requirements Change-Id: I85a0aecb3ef0d660f9c31499d6d955c50af587e1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index eb4a1d0..fa2147e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ openstacksdk>=0.8.6 # Apache-2.0 osc-lib>=0.1.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 -oslo.utils>=3.14.0 # Apache-2.0 +oslo.utils>=3.15.0 # Apache-2.0 python-heatclient>=1.1.0 # Apache-2.0 PyYAML>=3.1.0 # MIT requests>=2.10.0 # Apache-2.0 From 5c836784df199f4d3d9f21f85a8520b75adc4c76 Mon Sep 17 00:00:00 2001 From: PanFengyun Date: Tue, 12 Jul 2016 20:14:29 +0800 Subject: [PATCH 13/47] Prints '-' instead of 'None' when data is None Client prints 'None' when data is None , and client also prints 'None' when data is a string 'None'. But string 'None' is different to None. To clarify the confusion, Client should print '-' instead of 'None' when data is None. Nova Client and Cinder Client has clarified the confusion. Change-Id: I6a8908fcb2df39d7f2c5fc4055cc41779d668d6c --- senlinclient/common/utils.py | 15 ++++++++---- senlinclient/tests/unit/test_utils.py | 34 ++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/senlinclient/common/utils.py b/senlinclient/common/utils.py index bbdd52e..021d88f 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: 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()) From cae3ad5e5bb908f311cbc3e50a57beb6b9330a26 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 13 Jul 2016 17:22:34 +0000 Subject: [PATCH 14/47] Updated from global requirements Change-Id: I4d8f67c548e0b81a095a461c394709bf95c1133b --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fa2147e..bf09087 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ Babel>=2.3.4 # BSD pbr>=1.6 # Apache-2.0 PrettyTable<0.8,>=0.7 # BSD openstacksdk>=0.8.6 # Apache-2.0 -osc-lib>=0.1.0 # Apache-2.0 +osc-lib>=0.3.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 oslo.utils>=3.15.0 # Apache-2.0 From 3595a657c341248f8f46e90c503f9d280e11f020 Mon Sep 17 00:00:00 2001 From: PanFengyun Date: Thu, 14 Jul 2016 11:16:19 +0800 Subject: [PATCH 15/47] Add Python 3.5 classifier and venv Now that there is a passing gate job, we can claim support for Python 3.5 in the classifier. This patch also adds the convenience py35 venv. Change-Id: I130c613af2ecba8015ef8326b53d9cd8719c33c4 --- setup.cfg | 1 + tox.ini | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 3f4141c..aae14c9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,6 +17,7 @@ classifier = Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.4 + Programming Language :: Python :: 3.5 [files] packages = diff --git a/tox.ini b/tox.ini index e462ee5..fd6935c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py34,py27,pypy,pep8,releasenotes +envlist = py35,py34,py27,pypy,pep8,releasenotes minversion = 1.6 skipsdist = True From bb68b27bb571f2e7e207705d6bf479627efbf699 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 19 Jul 2016 21:14:27 +0000 Subject: [PATCH 16/47] Updated from global requirements Change-Id: I8205f02ec7ad2a533a5c961b17a173efbb5f0eb6 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bf09087..2c2f24b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ Babel>=2.3.4 # BSD pbr>=1.6 # Apache-2.0 PrettyTable<0.8,>=0.7 # BSD -openstacksdk>=0.8.6 # Apache-2.0 +openstacksdk>=0.9.0 # Apache-2.0 osc-lib>=0.3.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 From 8dc113695df3517dbaa0df9b478cd6c07ce46599 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 21 Jul 2016 04:10:37 +0000 Subject: [PATCH 17/47] Updated from global requirements Change-Id: Ie4f9215549ca3e301d250e73c26695029ebb52e0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2c2f24b..5bc6641 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ Babel>=2.3.4 # BSD pbr>=1.6 # Apache-2.0 PrettyTable<0.8,>=0.7 # BSD openstacksdk>=0.9.0 # Apache-2.0 -osc-lib>=0.3.0 # Apache-2.0 +osc-lib>=0.4.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 oslo.utils>=3.15.0 # Apache-2.0 From dd21b7aa0b71b40d59bc10e77361b94d266ed33d Mon Sep 17 00:00:00 2001 From: tengqm Date: Fri, 29 Jul 2016 02:15:56 -0400 Subject: [PATCH 18/47] Update senlinclient for new sdk version This patch fixes problem found after sdk version 0.9.1 which includes a new implementation of senlin cluster resources. Change-Id: I67b265731f34080df85233dac09c1256f43aecdb --- releasenotes/source/conf.py | 4 +- senlinclient/common/utils.py | 2 +- senlinclient/shell.py | 4 +- senlinclient/tests/unit/v1/test_action.py | 4 +- senlinclient/tests/unit/v1/test_build_info.py | 4 +- senlinclient/tests/unit/v1/test_cluster.py | 15 ++++--- .../tests/unit/v1/test_cluster_policy.py | 7 ++-- senlinclient/tests/unit/v1/test_event.py | 12 +++--- senlinclient/tests/unit/v1/test_node.py | 14 +++---- senlinclient/tests/unit/v1/test_policy.py | 14 +++---- .../tests/unit/v1/test_policy_type.py | 14 ++----- senlinclient/tests/unit/v1/test_profile.py | 31 +++++++------- .../tests/unit/v1/test_profile_type.py | 17 ++------ senlinclient/tests/unit/v1/test_receiver.py | 14 +++---- senlinclient/tests/unit/v1/test_shell.py | 9 ++-- senlinclient/v1/action.py | 8 ++-- senlinclient/v1/build_info.py | 9 ++-- senlinclient/v1/client.py | 8 ++++ senlinclient/v1/cluster.py | 5 ++- senlinclient/v1/cluster_policy.py | 8 ++-- senlinclient/v1/event.py | 14 +++---- senlinclient/v1/node.py | 10 ++--- senlinclient/v1/policy_type.py | 5 ++- senlinclient/v1/profile_type.py | 5 ++- senlinclient/v1/receiver.py | 5 ++- senlinclient/v1/shell.py | 42 +++++++++++++++---- tox.ini | 1 + 27 files changed, 157 insertions(+), 128 deletions(-) diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index 5a2629f..ca940a7 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,7 +65,7 @@ copyright = u'2015, Senlin Developers' # built documents. # # The short X.Y version. -import pbr.version + senlin_version = pbr.version.VersionInfo('python-muranoclient') # The full version, including alpha/beta/rc tags. release = senlin_version.version_string_with_vcs() diff --git a/senlinclient/common/utils.py b/senlinclient/common/utils.py index bbdd52e..d30890b 100644 --- a/senlinclient/common/utils.py +++ b/senlinclient/common/utils.py @@ -240,7 +240,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/shell.py b/senlinclient/shell.py index 5954982..64969c9 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 ' diff --git a/senlinclient/tests/unit/v1/test_action.py b/senlinclient/tests/unit/v1/test_action.py index 21927dc..a419c25 100644 --- a/senlinclient/tests/unit/v1/test_action.py +++ b/senlinclient/tests/unit/v1/test_action.py @@ -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_cluster.py b/senlinclient/tests/unit/v1/test_cluster.py index 6495ea0..c0e6875 100644 --- a/senlinclient/tests/unit/v1/test_cluster.py +++ b/senlinclient/tests/unit/v1/test_cluster.py @@ -147,7 +147,7 @@ class TestClusterList(TestCluster): class TestClusterShow(TestCluster): - get_response = {"cluster": { + response = {"cluster": { "created_at": "2015-02-11T15:13:20", "data": {}, "desired_capacity": 0, @@ -174,8 +174,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'] @@ -231,9 +230,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'] @@ -301,11 +300,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', diff --git a/senlinclient/tests/unit/v1/test_cluster_policy.py b/senlinclient/tests/unit/v1/test_cluster_policy.py index c034552..568617f 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 @@ -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..0014107 100644 --- a/senlinclient/tests/unit/v1/test_event.py +++ b/senlinclient/tests/unit/v1/test_event.py @@ -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..a412fd3 100644 --- a/senlinclient/tests/unit/v1/test_node.py +++ b/senlinclient/tests/unit/v1/test_node.py @@ -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,7 +171,7 @@ 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'] @@ -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', diff --git a/senlinclient/tests/unit/v1/test_policy.py b/senlinclient/tests/unit/v1/test_policy.py index 350a7d9..079a74b 100644 --- a/senlinclient/tests/unit/v1/test_policy.py +++ b/senlinclient/tests/unit/v1/test_policy.py @@ -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'] diff --git a/senlinclient/tests/unit/v1/test_policy_type.py b/senlinclient/tests/unit/v1/test_policy_type.py index 956dc16..aac5c6f 100644 --- a/senlinclient/tests/unit/v1/test_policy_type.py +++ b/senlinclient/tests/unit/v1/test_policy_type.py @@ -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..5b4d0a4 100644 --- a/senlinclient/tests/unit/v1/test_profile.py +++ b/senlinclient/tests/unit/v1/test_profile.py @@ -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): diff --git a/senlinclient/tests/unit/v1/test_profile_type.py b/senlinclient/tests/unit/v1/test_profile_type.py index 5a1fa0f..9a5aa39 100644 --- a/senlinclient/tests/unit/v1/test_profile_type.py +++ b/senlinclient/tests/unit/v1/test_profile_type.py @@ -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..6c14175 100644 --- a/senlinclient/tests/unit/v1/test_receiver.py +++ b/senlinclient/tests/unit/v1/test_receiver.py @@ -67,8 +67,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 +171,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,11 +229,9 @@ 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): arglist = ['my_receiver', '--action', 'CLUSTER_SCALE_OUT', diff --git a/senlinclient/tests/unit/v1/test_shell.py b/senlinclient/tests/unit/v1/test_shell.py index e2d1ef2..28596a4 100644 --- a/senlinclient/tests/unit/v1/test_shell.py +++ b/senlinclient/tests/unit/v1/test_shell.py @@ -66,8 +66,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): @@ -1104,7 +1105,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') @@ -1232,7 +1233,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..5f176ee 100644 --- a/senlinclient/v1/action.py +++ b/senlinclient/v1/action.py @@ -104,7 +104,8 @@ 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) ) @@ -141,6 +142,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(list(six.iterkeys(data))) + 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..af0100d 100644 --- a/senlinclient/v1/build_info.py +++ b/senlinclient/v1/build_info.py @@ -13,7 +13,6 @@ """Clustering v1 build_info action implementations""" import logging -import six from cliff import show from openstackclient.common import utils @@ -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..444dfe7 100644 --- a/senlinclient/v1/client.py +++ b/senlinclient/v1/client.py @@ -263,6 +263,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 diff --git a/senlinclient/v1/cluster.py b/senlinclient/v1/cluster.py index ebb0a6d..dca6842 100644 --- a/senlinclient/v1/cluster.py +++ b/senlinclient/v1/cluster.py @@ -137,8 +137,9 @@ def _show_cluster(senlin_client, cluster_id): 'metadata': senlin_utils.json_formatter, 'nodes': 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(list(six.iterkeys(data))) + return columns, utils.get_dict_properties(data, columns, formatters=formatters) diff --git a/senlinclient/v1/cluster_policy.py b/senlinclient/v1/cluster_policy.py index e7462f2..9abf20f 100644 --- a/senlinclient/v1/cluster_policy.py +++ b/senlinclient/v1/cluster_policy.py @@ -83,7 +83,8 @@ class ClusterPolicyList(lister.Lister): } return ( columns, - (utils.get_item_properties(p, columns, formatters=formatters) + (utils.get_item_properties(p.to_dict(), columns, + formatters=formatters) for p in policies) ) @@ -113,8 +114,9 @@ class ClusterPolicyShow(show.ShowOne): senlin_client = self.app.client_manager.clustering policy = senlin_client.get_cluster_policy(parsed_args.policy, parsed_args.cluster) - columns = sorted(list(six.iterkeys(policy))) - return columns, utils.get_dict_properties(policy.to_dict(), columns) + data = policy.to_dict() + columns = sorted(list(six.iterkeys(data))) + return columns, utils.get_dict_properties(data, columns) class ClusterPolicyUpdate(command.Command): diff --git a/senlinclient/v1/event.py b/senlinclient/v1/event.py index cc2ee8b..68ade48 100644 --- a/senlinclient/v1/event.py +++ b/senlinclient/v1/event.py @@ -99,11 +99,10 @@ class ListEvent(lister.Lister): formatters['obj_id'] = lambda x: x[:8] if x else '' events = senlin_client.events(**queries) - return ( - columns, - (utils.get_item_properties(e, columns, formatters=formatters) - for e in events) - ) + return (columns, + (utils.get_item_properties(e.to_dict(), columns, + formatters=formatters) + for e in events)) class ShowEvent(show.ShowOne): @@ -129,5 +128,6 @@ class ShowEvent(show.ShowOne): except sdk_exc.ResourceNotFound: raise exc.CommandError(_("Event not found: %s") % parsed_args.event) - columns = sorted(list(six.iterkeys(event))) - return columns, utils.get_dict_properties(event.to_dict(), columns) + data = event.to_dict() + columns = sorted(list(six.iterkeys(data))) + return columns, utils.get_dict_properties(data, columns) diff --git a/senlinclient/v1/node.py b/senlinclient/v1/node.py index 9e2ac63..2627763 100644 --- a/senlinclient/v1/node.py +++ b/senlinclient/v1/node.py @@ -158,12 +158,12 @@ def _show_node(senlin_client, node_id, show_details=False): 'metadata': senlin_utils.json_formatter, 'data': senlin_utils.json_formatter, } - if show_details and node: + data = node.to_dict() + if show_details: formatters['details'] = senlin_utils.nested_dict_formatter( - list(node['details'].keys()), ['property', 'value']) - - columns = sorted(list(six.iterkeys(node))) - return columns, utils.get_dict_properties(node.to_dict(), columns, + list(data['details'].keys()), ['property', 'value']) + columns = sorted(list(six.iterkeys(data))) + return columns, utils.get_dict_properties(data, columns, formatters=formatters) diff --git a/senlinclient/v1/policy_type.py b/senlinclient/v1/policy_type.py index 5df67d4..534ac13 100644 --- a/senlinclient/v1/policy_type.py +++ b/senlinclient/v1/policy_type.py @@ -63,6 +63,7 @@ class PolicyTypeShow(format_utils.YamlFormat): except sdk_exc.ResourceNotFound: raise exc.CommandError(_('Policy Type not found: %s') % parsed_args.type_name) - rows = list(six.itervalues(res)) - columns = list(six.iterkeys(res)) + data = res.to_dict() + rows = list(six.itervalues(data)) + columns = list(six.iterkeys(data)) return columns, rows diff --git a/senlinclient/v1/profile_type.py b/senlinclient/v1/profile_type.py index 563dbd8..7dfbf3b 100644 --- a/senlinclient/v1/profile_type.py +++ b/senlinclient/v1/profile_type.py @@ -63,6 +63,7 @@ class ProfileTypeShow(format_utils.YamlFormat): except sdk_exc.ResourceNotFound: raise exc.CommandError(_('Profile Type not found: %s') % parsed_args.type_name) - rows = list(six.itervalues(res)) - columns = list(six.iterkeys(res)) + data = res.to_dict() + rows = list(six.itervalues(data)) + columns = list(six.iterkeys(data)) return columns, rows diff --git a/senlinclient/v1/receiver.py b/senlinclient/v1/receiver.py index d8215bc..98b1b9a 100644 --- a/senlinclient/v1/receiver.py +++ b/senlinclient/v1/receiver.py @@ -142,8 +142,9 @@ def _show_receiver(senlin_client, receiver_id): 'params': senlin_utils.json_formatter, 'channel': senlin_utils.json_formatter, } - columns = sorted(list(six.iterkeys(receiver))) - return columns, utils.get_dict_properties(receiver.to_dict(), columns, + data = receiver.to_dict() + columns = sorted(list(six.iterkeys(data))) + return columns, utils.get_dict_properties(data, columns, formatters=formatters) diff --git a/senlinclient/v1/shell.py b/senlinclient/v1/shell.py index 6ef78e3..ce5ca7f 100644 --- a/senlinclient/v1/shell.py +++ b/senlinclient/v1/shell.py @@ -36,7 +36,7 @@ def do_build_info(service, args=None): :param args: Additional command line arguments, if any. """ show_deprecated('senlin build-info', 'openstack cluster build info') - result = service.get_build_info() + result = service.get_build_info().to_dict() formatters = { 'api': utils.json_formatter, @@ -491,6 +491,33 @@ def do_cluster_create(service, args): _show_cluster(service, cluster.id) +@utils.arg('-p', '--path', metavar='', + help=_('A Json path string specifying the attribute to collect.')) +@utils.arg('-L', '--list', default=False, action="store_true", + help=_('Print a full list that contains both node ids and ' + 'attribute values instead of values only. Default is True.')) +@utils.arg('-F', '--full-id', default=False, action="store_true", + help=_('Print full IDs in list.')) +@utils.arg('id', metavar='', + help=_('Name or ID of cluster(s) to operate on.')) +def do_cluster_collect(service, args): + """Collect attributes across a cluster.""" + show_deprecated('senlin cluster-collect', 'openstack cluster collect') + + attrs = service.cluster_collect(args.id, args.path) + if args.list: + fields = ['node_id', 'value'] + formatters = { + 'value': utils.json_formatter + } + if not args.full_id: + formatters['node_id'] = lambda x: x.node_id[:8] + utils.print_list(attrs, fields, formatters=formatters) + else: + for attr in attrs: + print(attr.value) + + @utils.arg('id', metavar='', nargs='+', help=_('Name or ID of cluster(s) to delete.')) def do_cluster_delete(service, args): @@ -624,7 +651,7 @@ def do_cluster_node_del(service, args): @utils.arg('-t', '--min-step', metavar='', type=int, help=_('An integer specifying the number of nodes for adjustment ' 'when is specified.')) -@utils.arg('-s', '--strict', action='store_true', default=False, +@utils.arg('-s', '--strict', action='store_true', default=False, help=_('A boolean specifying whether the resize should be ' 'performed on a best-effort basis when the new capacity ' 'may go beyond size constraints.')) @@ -910,9 +937,8 @@ def do_node_list(service, args): def _show_node(service, node_id, show_details=False): """Show detailed info about the specified node.""" - args = {'show_details': True} if show_details else None try: - node = service.get_node(node_id, args=args) + node = service.get_node(node_id, details=show_details) except sdk_exc.ResourceNotFound: raise exc.CommandError(_('Node not found: %s') % node_id) @@ -923,7 +949,7 @@ def _show_node(service, node_id, show_details=False): data = node.to_dict() if show_details: formatters['details'] = utils.nested_dict_formatter( - list(node['details'].keys()), ['property', 'value']) + list(data['details'].keys()), ['property', 'value']) utils.print_dict(data, formatters=formatters) @@ -1203,7 +1229,7 @@ def do_event_list(service, args): """List events.""" show_deprecated('senlin event-list', 'openstack cluster event list') fields = ['id', 'timestamp', 'obj_type', 'obj_id', 'obj_name', 'action', - 'status', 'status_reason', 'level'] + 'status', 'level', 'cluster_id'] queries = { 'sort': args.sort, 'limit': args.limit, @@ -1218,6 +1244,8 @@ def do_event_list(service, args): if not args.full_id: formatters['id'] = lambda x: x.id[:8] formatters['obj_id'] = lambda x: x.obj_id[:8] if x.obj_id else '' + formatters['cluster_id'] = (lambda x: x.cluster_id[:8] + if x.cluster_id else '') events = service.events(**queries) utils.print_list(events, fields, formatters=formatters) @@ -1282,7 +1310,7 @@ def do_action_list(service, args): formatters['depended_by'] = f_depby else: formatters['id'] = lambda x: x.id[:8] - formatters['target'] = lambda x: x.target[:8] + formatters['target'] = lambda x: x.target_id[:8] f_depon = lambda x: '\n'.join(a[:8] for a in x.depends_on) f_depby = lambda x: '\n'.join(a[:8] for a in x.depended_by) formatters['depends_on'] = f_depon diff --git a/tox.ini b/tox.ini index 23c94bb..54674bf 100644 --- a/tox.ini +++ b/tox.ini @@ -35,6 +35,7 @@ commands= commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [flake8] +ignore = D100,D101,D102,D103,D104,D105,D200,D201,D202,D204,D205,D300,D301,D400,D401 show-source = True exclude=.venv,.git,.tox,dist,*openstack/common*,*lib/python*,*egg,build max-complexity=20 From 69ba1c6d26074a1ecc38d5b5cfa20cc133195691 Mon Sep 17 00:00:00 2001 From: tengqm Date: Sun, 31 Jul 2016 22:08:48 -0400 Subject: [PATCH 19/47] Fix nodes display in cluster-show This patch fixes the problem caused by latest changes to openstacksdk where 'nodes' was changed to 'node_ids'. Closes-Bug: 1608325 Change-Id: Ie68a3af9103343b2e314d2e90d5b067cf3b8ed81 --- senlinclient/tests/unit/v1/test_cluster.py | 8 ++++---- senlinclient/tests/unit/v1/test_shell.py | 2 +- senlinclient/v1/cluster.py | 2 +- senlinclient/v1/shell.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/senlinclient/tests/unit/v1/test_cluster.py b/senlinclient/tests/unit/v1/test_cluster.py index fc8621e..3bba71f 100644 --- a/senlinclient/tests/unit/v1/test_cluster.py +++ b/senlinclient/tests/unit/v1/test_cluster.py @@ -43,7 +43,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" @@ -158,7 +158,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", @@ -204,7 +204,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", @@ -274,7 +274,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", diff --git a/senlinclient/tests/unit/v1/test_shell.py b/senlinclient/tests/unit/v1/test_shell.py index 48e3356..b5cc399 100644 --- a/senlinclient/tests/unit/v1/test_shell.py +++ b/senlinclient/tests/unit/v1/test_shell.py @@ -637,7 +637,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 diff --git a/senlinclient/v1/cluster.py b/senlinclient/v1/cluster.py index bd803b4..5a3a52f 100644 --- a/senlinclient/v1/cluster.py +++ b/senlinclient/v1/cluster.py @@ -133,7 +133,7 @@ def _show_cluster(senlin_client, cluster_id): formatters = { 'metadata': senlin_utils.json_formatter, - 'nodes': senlin_utils.list_formatter + 'node_ids': senlin_utils.list_formatter } data = cluster.to_dict() columns = sorted(list(six.iterkeys(data))) diff --git a/senlinclient/v1/shell.py b/senlinclient/v1/shell.py index ca2b372..4734ad7 100644 --- a/senlinclient/v1/shell.py +++ b/senlinclient/v1/shell.py @@ -449,7 +449,7 @@ def _show_cluster(service, cluster_id): formatters = { 'metadata': utils.json_formatter, - 'nodes': utils.list_formatter, + 'node_ids': utils.list_formatter, } utils.print_dict(cluster.to_dict(), formatters=formatters) From 7a286d347d6898c9363281b9df199bb2cf464ad9 Mon Sep 17 00:00:00 2001 From: zhurong Date: Tue, 2 Aug 2016 06:48:41 +0000 Subject: [PATCH 20/47] Fix typo Change-Id: Id15a20f9cd9f19af32145ec69cd02e5d9795a3c4 --- releasenotes/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index ca940a7..f662fbd 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -66,7 +66,7 @@ copyright = u'2015, Senlin Developers' # # The short X.Y 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. From 6a015d938a8f076fe600830ce7c220a3c90c76d7 Mon Sep 17 00:00:00 2001 From: qinchunhua Date: Fri, 29 Jul 2016 23:41:00 -0400 Subject: [PATCH 21/47] Remove discover from test-requirements It's only needed for python < 2.7 which is not supported. Change-Id: If52134b792f0c67eb5c68e4ee725f44f037bdbb4 --- test-requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 693ff30..823ce41 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,7 +5,6 @@ # Hacking already pins down pep8, pyflakes and flake8 hacking<0.11,>=0.10.0 coverage>=3.6 # Apache-2.0 -discover # BSD fixtures>=3.0.0 # Apache-2.0/BSD requests-mock>=1.0 # Apache-2.0 mock>=2.0 # BSD From 46f196260df4a3c951281d3c81398f0d7f567ff0 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 3 Aug 2016 09:07:38 +0000 Subject: [PATCH 22/47] Updated from global requirements Change-Id: I22c835339b99154e99edad3ffddfe3868d0fe3e1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5bc6641..d8f292d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ openstacksdk>=0.9.0 # Apache-2.0 osc-lib>=0.4.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 -oslo.utils>=3.15.0 # Apache-2.0 +oslo.utils>=3.16.0 # Apache-2.0 python-heatclient>=1.1.0 # Apache-2.0 PyYAML>=3.1.0 # MIT requests>=2.10.0 # Apache-2.0 From ae07c57bf2a9eafba2037635bd9f3f60d6766736 Mon Sep 17 00:00:00 2001 From: tengqm Date: Tue, 9 Aug 2016 05:17:09 -0400 Subject: [PATCH 23/47] Add support to micro-version This patch adds support to api-microversioning from senlinclient. Change-Id: Ic37acfca53473d7a3e9a234288886b98e88e63b9 --- senlinclient/common/sdk.py | 1 + senlinclient/v1/shell.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/senlinclient/common/sdk.py b/senlinclient/common/sdk.py index 65caead..b26f68e 100644 --- a/senlinclient/common/sdk.py +++ b/senlinclient/common/sdk.py @@ -133,6 +133,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/v1/shell.py b/senlinclient/v1/shell.py index 4734ad7..f3a86b4 100644 --- a/senlinclient/v1/shell.py +++ b/senlinclient/v1/shell.py @@ -515,7 +515,7 @@ def do_cluster_collect(service, args): utils.print_list(attrs, fields, formatters=formatters) else: for attr in attrs: - print(attr.value) + print(attr.attr_value) @utils.arg('id', metavar='', nargs='+', From f7ecc4bb86f729cde83ed070b3fc49a51005de64 Mon Sep 17 00:00:00 2001 From: tengqm Date: Tue, 9 Aug 2016 11:42:53 -0400 Subject: [PATCH 24/47] Add cluster_collect support This patch adds the cluster_collect command. Change-Id: I750a33ceb9353aa445dba8464affd8ae7798eba6 --- senlinclient/plugin.py | 7 ++- senlinclient/shell.py | 4 +- senlinclient/tests/unit/test_shell.py | 4 +- senlinclient/tests/unit/v1/test_client.py | 7 +-- senlinclient/tests/unit/v1/test_cluster.py | 35 +++++++++++++ senlinclient/tests/unit/v1/test_shell.py | 59 ++++++++++++++++++++++ senlinclient/v1/client.py | 5 +- senlinclient/v1/cluster.py | 41 +++++++++++++++ senlinclient/v1/shell.py | 6 +-- setup.cfg | 1 + 10 files changed, 156 insertions(+), 13 deletions(-) diff --git a/senlinclient/plugin.py b/senlinclient/plugin.py index d48865f..66002bb 100644 --- a/senlinclient/plugin.py +++ b/senlinclient/plugin.py @@ -13,6 +13,7 @@ import logging from openstack import connection +from openstack import profile from osc_lib import utils LOG = logging.getLogger(__name__) @@ -20,12 +21,16 @@ 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 ea40849..ecc129d 100644 --- a/senlinclient/shell.py +++ b/senlinclient/shell.py @@ -235,8 +235,8 @@ class SenlinShell(object): 'trust_id': args.trust_id, } - return senlin_client.Client('1', args.user_preferences, USER_AGENT, - **kwargs) + return senlin_client.Client('1', prof=args.user_preferences, + user_agent=USER_AGENT, **kwargs) def main(self, argv): # Parse args once to find version diff --git a/senlinclient/tests/unit/test_shell.py b/senlinclient/tests/unit/test_shell.py index e0eb883..9c27990 100644 --- a/senlinclient/tests/unit/test_shell.py +++ b/senlinclient/tests/unit/test_shell.py @@ -366,8 +366,8 @@ 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, - **kwargs) + mock_conn.assert_called_once_with(prof=args.user_preferences, + user_agent=USER_AGENT, **kwargs) client = mock.Mock() senlin_client.Client = mock.MagicMock(return_value=client) self.assertEqual(client, sh._setup_senlin_client('1', args)) diff --git a/senlinclient/tests/unit/v1/test_client.py b/senlinclient/tests/unit/v1/test_client.py index 605300a..a12eb76 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 diff --git a/senlinclient/tests/unit/v1/test_cluster.py b/senlinclient/tests/unit/v1/test_cluster.py index 3bba71f..98bfe7f 100644 --- a/senlinclient/tests/unit/v1/test_cluster.py +++ b/senlinclient/tests/unit/v1/test_cluster.py @@ -798,3 +798,38 @@ 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) diff --git a/senlinclient/tests/unit/v1/test_shell.py b/senlinclient/tests/unit/v1/test_shell.py index b5cc399..8919d25 100644 --- a/senlinclient/tests/unit/v1/test_shell.py +++ b/senlinclient/tests/unit/v1/test_shell.py @@ -1057,6 +1057,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() diff --git a/senlinclient/v1/client.py b/senlinclient/v1/client.py index 444dfe7..5054014 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 ###################################################################### diff --git a/senlinclient/v1/cluster.py b/senlinclient/v1/cluster.py index 5a3a52f..42ad22b 100644 --- a/senlinclient/v1/cluster.py +++ b/senlinclient/v1/cluster.py @@ -766,3 +766,44 @@ 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)) diff --git a/senlinclient/v1/shell.py b/senlinclient/v1/shell.py index f3a86b4..48152bf 100644 --- a/senlinclient/v1/shell.py +++ b/senlinclient/v1/shell.py @@ -504,11 +504,11 @@ def do_cluster_collect(service, args): """Collect attributes across a cluster.""" show_deprecated('senlin cluster-collect', 'openstack cluster collect') - attrs = service.cluster_collect(args.id, args.path) + attrs = service.collect_cluster_attrs(args.id, args.path) if args.list: - fields = ['node_id', 'value'] + fields = ['node_id', 'attr_value'] formatters = { - 'value': utils.json_formatter + 'attr_value': utils.json_formatter } if not args.full_id: formatters['node_id'] = lambda x: x.node_id[:8] diff --git a/setup.cfg b/setup.cfg index 3f4141c..86f9589 100644 --- a/setup.cfg +++ b/setup.cfg @@ -78,6 +78,7 @@ openstack.clustering.v1 = cluster_expand = senlinclient.v1.cluster:ScaleOutCluster cluster_show = senlinclient.v1.cluster:ShowCluster cluster_update = senlinclient.v1.cluster:UpdateCluster + cluster_collect = senlinclient.v1.cluster:ClusterCollect [global] setup-hooks = From 1c6496caebf440985dd35f8b416acdef32bc82f0 Mon Sep 17 00:00:00 2001 From: tengqm Date: Wed, 10 Aug 2016 02:57:17 -0400 Subject: [PATCH 25/47] Remove local cached version of Resource class We used to have a local version of Resource class due to insufficient support in SDK back in the old days. Now I believe all those barriers are removed. So the local cached version doesn't make much senses. This patch proposes to remove them forever. Change-Id: Ie4f09e29f584813bf354002aee0f8680c7688c59 --- senlinclient/common/sdk.py | 55 ++------------------------------------ 1 file changed, 2 insertions(+), 53 deletions(-) diff --git a/senlinclient/common/sdk.py b/senlinclient/common/sdk.py index b26f68e..8604960 100644 --- a/senlinclient/common/sdk.py +++ b/senlinclient/common/sdk.py @@ -11,14 +11,12 @@ # under the License. import argparse +import os + 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 @@ -74,55 +72,6 @@ class ProfileAction(argparse.Action): 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: prof = profile.Profile() From 92f52735cbf3db33e3877b0bfd9854a33b632e59 Mon Sep 17 00:00:00 2001 From: tengqm Date: Wed, 10 Aug 2016 03:12:59 -0400 Subject: [PATCH 26/47] Remove 'ProfileAction' and related arguments The 'ProfileAction' class and the related user_preferences are pre-history relics. Keeping them in senlinclient is only causing confusion considering that 1) senlin shell will be deprecated someday; 2) SDK has some tweaks to the design that we cannot follow up; 3) these arguments are rarely used. Change-Id: Ia79a069bde4fd94bc1bd00f719b4e6b4a56770f8 --- senlinclient/cliargs.py | 30 -------------- senlinclient/common/sdk.py | 55 ------------------------- senlinclient/shell.py | 3 +- senlinclient/tests/unit/test_cliargs.py | 4 -- senlinclient/tests/unit/test_sdk.py | 37 ----------------- senlinclient/tests/unit/test_shell.py | 4 +- 6 files changed, 3 insertions(+), 130 deletions(-) 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/sdk.py b/senlinclient/common/sdk.py index 8604960..6f1a7c2 100644 --- a/senlinclient/common/sdk.py +++ b/senlinclient/common/sdk.py @@ -10,67 +10,12 @@ # License for the specific language governing permissions and limitations # under the License. -import argparse -import os - from openstack import connection from openstack import exceptions from openstack import profile -from openstack import resource as base 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) - def create_connection(prof=None, user_agent=None, **kwargs): if not prof: diff --git a/senlinclient/shell.py b/senlinclient/shell.py index ecc129d..75dd973 100644 --- a/senlinclient/shell.py +++ b/senlinclient/shell.py @@ -235,8 +235,7 @@ class SenlinShell(object): 'trust_id': args.trust_id, } - return senlin_client.Client('1', prof=args.user_preferences, - user_agent=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_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 9c27990..edabb0c 100644 --- a/senlinclient/tests/unit/test_shell.py +++ b/senlinclient/tests/unit/test_shell.py @@ -366,8 +366,8 @@ 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(prof=args.user_preferences, - user_agent=USER_AGENT, **kwargs) + mock_conn.assert_called_once_with(prof=None, user_agent=USER_AGENT, + **kwargs) client = mock.Mock() senlin_client.Client = mock.MagicMock(return_value=client) self.assertEqual(client, sh._setup_senlin_client('1', args)) From a619b79c3b70714bfc5355c67859658a32e38f16 Mon Sep 17 00:00:00 2001 From: Ethan Lynn Date: Thu, 11 Aug 2016 22:13:40 +0800 Subject: [PATCH 27/47] Reorder required parameters This patch put required parameters to an visible position. Change-Id: Ib68ab28c8f616d912b81ee38ec6325e087f6c648 --- senlinclient/v1/cluster.py | 24 ++++++++++++------------ senlinclient/v1/node.py | 12 ++++++------ senlinclient/v1/profile.py | 12 ++++++------ senlinclient/v1/receiver.py | 14 +++++++------- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/senlinclient/v1/cluster.py b/senlinclient/v1/cluster.py index 42ad22b..d612f25 100644 --- a/senlinclient/v1/cluster.py +++ b/senlinclient/v1/cluster.py @@ -148,12 +148,6 @@ class CreateCluster(command.ShowOne): 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='', @@ -187,6 +181,12 @@ class CreateCluster(command.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='', @@ -521,12 +521,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, @@ -534,6 +528,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='', diff --git a/senlinclient/v1/node.py b/senlinclient/v1/node.py index 780bc6f..6a5e158 100644 --- a/senlinclient/v1/node.py +++ b/senlinclient/v1/node.py @@ -172,12 +172,6 @@ class CreateNode(command.ShowOne): def get_parser(self, prog_name): parser = super(CreateNode, self).get_parser(prog_name) - parser.add_argument( - '--profile', - metavar='', - required=True, - help=_('Profile Id or Name used for this node') - ) parser.add_argument( '--cluster', metavar='', @@ -196,6 +190,12 @@ class CreateNode(command.ShowOne): 'key-value pairs separated by a semicolon'), action='append' ) + parser.add_argument( + '--profile', + metavar='', + required=True, + help=_('Profile Id or Name used for this node') + ) parser.add_argument( 'name', metavar='', diff --git a/senlinclient/v1/profile.py b/senlinclient/v1/profile.py index 56b57d2..db2500e 100644 --- a/senlinclient/v1/profile.py +++ b/senlinclient/v1/profile.py @@ -212,12 +212,6 @@ class CreateProfile(command.ShowOne): def get_parser(self, prog_name): parser = super(CreateProfile, self).get_parser(prog_name) - parser.add_argument( - '--spec-file', - metavar='', - required=True, - help=_('The spec file used to create the profile') - ) parser.add_argument( '--metadata', metavar='', @@ -226,6 +220,12 @@ class CreateProfile(command.ShowOne): 'key-value pairs separated by a semicolon'), action='append' ) + parser.add_argument( + '--spec-file', + metavar='', + required=True, + help=_('The spec file used to create the profile') + ) parser.add_argument( 'name', metavar='', diff --git a/senlinclient/v1/receiver.py b/senlinclient/v1/receiver.py index c2ce9dd..fda140b 100644 --- a/senlinclient/v1/receiver.py +++ b/senlinclient/v1/receiver.py @@ -159,6 +159,13 @@ class CreateReceiver(command.ShowOne): default='webhook', help=_('Type of the receiver to create') ) + parser.add_argument( + '--params', + metavar='', + help=_('A dictionary of parameters that will be passed to target ' + 'action when the receiver is triggered'), + action='append' + ) parser.add_argument( '--cluster', metavar='', @@ -171,13 +178,6 @@ class CreateReceiver(command.ShowOne): required=True, help=_('Name or ID of the targeted action to be triggered') ) - parser.add_argument( - '--params', - metavar='', - help=_('A dictionary of parameters that will be passed to target ' - 'action when the receiver is triggered'), - action='append' - ) parser.add_argument( 'name', metavar='', From 1ee08778da812faef7d78ce0af6bef183d93e720 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 19 Aug 2016 08:29:42 +0000 Subject: [PATCH 28/47] Imported Translations from Zanata For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: I57a4ece3e978ed8ee215ef83d42d2529550abd2f --- .../locale/zh_CN/LC_MESSAGES/senlinclient.po | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient.po b/senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient.po index 31e6e7e..854b2a0 100644 --- a/senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient.po +++ b/senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient.po @@ -2,13 +2,13 @@ # zzxwill , 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: python-senlinclient 0.5.1.dev13\n" +"Project-Id-Version: python-senlinclient 0.5.1.dev37\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2016-07-04 03:45+0000\n" +"POT-Creation-Date: 2016-08-12 10:11+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-30 12:00+0000\n" +"PO-Revision-Date: 2016-08-19 03:05+0000\n" "Last-Translator: zzxwill \n" "Language-Team: Chinese (China)\n" "Language: zh-CN\n" @@ -158,18 +158,6 @@ msgstr "默认是env[OS_USER_ID]。" msgid "Defaults to env[SENLINCLIENT_DEBUG]." msgstr "默认是env[SENLINCLIENT_DEBUG]。" -msgid "Desired API interface, defaults to env[OS_INTERFACE]" -msgstr "必填的API接口,默认是env[OS_INTERFACE]" - -msgid "Desired API names, defaults to env[OS_API_NAME]" -msgstr "必填的API名字,默认是env[OS_API_NAME]" - -msgid "Desired API region, defaults to env[OS_API_REGION]" -msgstr "必填的API区域,默认是env[OS_API_REGION]" - -msgid "Desired API versions, defaults to env[OS_API_VERSION]" -msgstr "Desired API版本,默认是env[OS_API_VERSION]" - msgid "" "Desired capacity of the cluster. Default to min_size if min_size is " "specified else 0." @@ -399,6 +387,9 @@ msgstr "" "过滤参数以应用在返回的结果上。该参数可以指定多次,也可以用分号分隔参数之后执" "行一次。有效的过滤键是['type', 'name']" +msgid "ID of event to display details for." +msgstr "显示事件详情的ID。" + msgid "ID of new profile to use" msgstr "将要使用的样版的ID" @@ -784,6 +775,12 @@ 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 node to show the details for." +msgstr "显示action详情的名字或ID。" + msgid "Name or ID of the policy to be updated" msgstr "将要更新的策略的名称或ID" From e450261b02c2d99fabe1a785111e228381e050a9 Mon Sep 17 00:00:00 2001 From: Ethan Lynn Date: Tue, 23 Aug 2016 10:44:20 +0800 Subject: [PATCH 29/47] Add profile validate operation to senlinclient Add profile validate operation to senlinclient Change-Id: I7fd1e5e3fa9115ba270f2c0e5f645ac5183622e3 --- senlinclient/tests/unit/v1/test_profile.py | 46 ++++++++++++++++ senlinclient/tests/unit/v1/test_shell.py | 42 +++++++++++++++ senlinclient/v1/client.py | 8 +++ senlinclient/v1/profile.py | 62 ++++++++++++++++++++++ senlinclient/v1/shell.py | 38 +++++++++++++ setup.cfg | 1 + 6 files changed, 197 insertions(+) diff --git a/senlinclient/tests/unit/v1/test_profile.py b/senlinclient/tests/unit/v1/test_profile.py index 411d489..7782e1e 100644 --- a/senlinclient/tests/unit/v1/test_profile.py +++ b/senlinclient/tests/unit/v1/test_profile.py @@ -402,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_shell.py b/senlinclient/tests/unit/v1/test_shell.py index 8919d25..7c2e39f 100644 --- a/senlinclient/tests/unit/v1/test_shell.py +++ b/senlinclient/tests/unit/v1/test_shell.py @@ -298,6 +298,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() diff --git a/senlinclient/v1/client.py b/senlinclient/v1/client.py index 5054014..2b89da2 100644 --- a/senlinclient/v1/client.py +++ b/senlinclient/v1/client.py @@ -84,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 diff --git a/senlinclient/v1/profile.py b/senlinclient/v1/profile.py index db2500e..4d8197e 100644 --- a/senlinclient/v1/profile.py +++ b/senlinclient/v1/profile.py @@ -307,3 +307,65 @@ class UpdateProfile(command.ShowOne): parsed_args.profile) senlin_client.update_profile(profile.id, **params) return _show_profile(senlin_client, profile_id=profile.id) + + +class ValidateProfile(command.ShowOne): + """Validate a profile.""" + + log = logging.getLogger(__name__ + ".ValidateProfile") + + def get_parser(self, prog_name): + parser = super(ValidateProfile, self).get_parser(prog_name) + parser.add_argument( + '--spec-file', + metavar='', + required=True, + help=_('The spec file used to create the profile') + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + senlin_client = self.app.client_manager.clustering + + spec = senlin_utils.get_spec_content(parsed_args.spec_file) + type_name = spec.get('type', None) + type_version = spec.get('version', None) + properties = spec.get('properties', None) + if type_name is None: + raise exc.CommandError(_("Missing 'type' key in spec file.")) + if type_version is None: + raise exc.CommandError(_("Missing 'version' key in spec file.")) + if properties is None: + raise exc.CommandError(_("Missing 'properties' key in spec file.")) + + if type_name == 'os.heat.stack': + stack_properties = senlin_utils.process_stack_spec(properties) + spec['properties'] = stack_properties + + params = { + 'spec': spec, + } + + profile = senlin_client.validate_profile(**params) + + formatters = {} + formatters['metadata'] = senlin_utils.json_formatter + formatters['spec'] = senlin_utils.nested_dict_formatter( + ['type', 'version', 'properties'], + ['property', 'value']) + + columns = [ + 'created_at', + 'domain', + 'id', + 'metadata', + 'name', + 'project', + 'spec', + 'type', + 'updated_at', + 'user' + ] + return columns, utils.get_dict_properties(profile.to_dict(), columns, + formatters=formatters) diff --git a/senlinclient/v1/shell.py b/senlinclient/v1/shell.py index 48152bf..fcddd7f 100644 --- a/senlinclient/v1/shell.py +++ b/senlinclient/v1/shell.py @@ -241,6 +241,44 @@ def do_profile_delete(service, args): print('Profile deleted: %s' % args.id) +@utils.arg('-s', '--spec-file', metavar='', required=True, + help=_('The spec file used to create the profile.')) +def do_profile_validate(service, args): + """Validate a profile.""" + show_deprecated('senlin profile-validate', + 'openstack cluster profile validate') + spec = utils.get_spec_content(args.spec_file) + type_name = spec.get('type', None) + type_version = spec.get('version', None) + properties = spec.get('properties', None) + if type_name is None: + raise exc.CommandError(_("Missing 'type' key in spec file.")) + if type_version is None: + raise exc.CommandError(_("Missing 'version' key in spec file.")) + if properties is None: + raise exc.CommandError(_("Missing 'properties' key in spec file.")) + + if type_name == 'os.heat.stack': + stack_properties = utils.process_stack_spec(properties) + spec['properties'] = stack_properties + + params = { + 'spec': spec, + } + + profile = service.validate_profile(**params) + + formatters = { + 'metadata': utils.json_formatter, + } + + formatters['spec'] = utils.nested_dict_formatter( + ['type', 'version', 'properties'], + ['property', 'value']) + + utils.print_dict(profile.to_dict(), formatters=formatters) + + # POLICY TYPES diff --git a/setup.cfg b/setup.cfg index 6947ec1..c89aa04 100644 --- a/setup.cfg +++ b/setup.cfg @@ -69,6 +69,7 @@ openstack.clustering.v1 = cluster_profile_type_list = senlinclient.v1.profile_type:ProfileTypeList cluster_profile_type_show = senlinclient.v1.profile_type:ProfileTypeShow cluster_profile_update = senlinclient.v1.profile:UpdateProfile + cluster_profile_validate = senlinclient.v1.profile:ValidateProfile cluster_receiver_create = senlinclient.v1.receiver:CreateReceiver cluster_receiver_delete = senlinclient.v1.receiver:DeleteReceiver cluster_receiver_list = senlinclient.v1.receiver:ListReceiver From 3a212cde40f78b55b511af018ad7039d0fdd330f Mon Sep 17 00:00:00 2001 From: Ethan Lynn Date: Tue, 23 Aug 2016 11:33:50 +0800 Subject: [PATCH 30/47] Add policy validate operation to senlinclient Add policy validate operation to senlinclient Change-Id: Ic014bbe199e00973e7fc0bfd39fdb08241c64d8f --- senlinclient/tests/unit/v1/test_policy.py | 53 +++++++++++++++++++++++ senlinclient/tests/unit/v1/test_shell.py | 19 ++++++++ senlinclient/v1/client.py | 8 ++++ senlinclient/v1/policy.py | 44 +++++++++++++++++++ senlinclient/v1/shell.py | 18 ++++++++ setup.cfg | 1 + 6 files changed, 143 insertions(+) diff --git a/senlinclient/tests/unit/v1/test_policy.py b/senlinclient/tests/unit/v1/test_policy.py index cd3851c..9c4164d 100644 --- a/senlinclient/tests/unit/v1/test_policy.py +++ b/senlinclient/tests/unit/v1/test_policy.py @@ -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_shell.py b/senlinclient/tests/unit/v1/test_shell.py index 7c2e39f..5e2e916 100644 --- a/senlinclient/tests/unit/v1/test_shell.py +++ b/senlinclient/tests/unit/v1/test_shell.py @@ -641,6 +641,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() diff --git a/senlinclient/v1/client.py b/senlinclient/v1/client.py index 2b89da2..af644a7 100644 --- a/senlinclient/v1/client.py +++ b/senlinclient/v1/client.py @@ -150,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 diff --git a/senlinclient/v1/policy.py b/senlinclient/v1/policy.py index c888f6d..6fa62c2 100644 --- a/senlinclient/v1/policy.py +++ b/senlinclient/v1/policy.py @@ -269,3 +269,47 @@ class DeletePolicy(command.Command): {'count': failure_count, 'total': len(parsed_args.policy)}) print('Policy deleted: %s' % parsed_args.policy) + + +class ValidatePolicy(command.ShowOne): + """Validate a policy.""" + + log = logging.getLogger(__name__ + ".ValidatePolicy") + + def get_parser(self, prog_name): + parser = super(ValidatePolicy, self).get_parser(prog_name) + parser.add_argument( + '--spec-file', + metavar='', + required=True, + help=_('The spec file used to create the policy') + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + senlin_client = self.app.client_manager.clustering + spec = senlin_utils.get_spec_content(parsed_args.spec_file) + attrs = { + 'spec': spec, + } + + policy = senlin_client.validate_policy(**attrs) + formatters = { + 'spec': senlin_utils.json_formatter + } + columns = [ + 'created_at', + 'data', + 'domain', + 'id', + 'name', + 'project', + 'spec', + 'type', + 'updated_at', + 'user' + ] + return columns, utils.get_dict_properties(policy.to_dict(), columns, + formatters=formatters) diff --git a/senlinclient/v1/shell.py b/senlinclient/v1/shell.py index fcddd7f..08103f0 100644 --- a/senlinclient/v1/shell.py +++ b/senlinclient/v1/shell.py @@ -431,6 +431,24 @@ def do_policy_delete(service, args): print('Policy deleted: %s' % args.id) +@utils.arg('-s', '--spec-file', metavar='', required=True, + help=_('The spec file used to create the policy.')) +def do_policy_validate(service, args): + """VAlidate a policy spec.""" + show_deprecated('senlin policy-validate', + 'openstack cluster policy validate') + spec = utils.get_spec_content(args.spec_file) + attrs = { + 'spec': spec, + } + + policy = service.validate_policy(**attrs) + formatters = { + 'metadata': utils.json_formatter, + 'spec': utils.json_formatter, + } + utils.print_dict(policy.to_dict(), formatters=formatters) + # CLUSTERS diff --git a/setup.cfg b/setup.cfg index c89aa04..cb648b3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -59,6 +59,7 @@ openstack.clustering.v1 = cluster_policy_detach = senlinclient.v1.cluster:ClusterPolicyDetach cluster_policy_list = senlinclient.v1.policy:ListPolicy cluster_policy_show = senlinclient.v1.policy:ShowPolicy + cluster_policy_validate = senlinclient.v1.policy:ValidatePolicy cluster_policy_type_list = senlinclient.v1.policy_type:PolicyTypeList cluster_policy_type_show = senlinclient.v1.policy_type:PolicyTypeShow cluster_policy_update = senlinclient.v1.policy:UpdatePolicy From e3e30ca3a26d61688b6a083c912d83db85182919 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 23 Aug 2016 08:27:00 +0000 Subject: [PATCH 31/47] Imported Translations from Zanata For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: I49cdbd28cd0efc2a4005b375a854fc8f1870a73c --- .../locale/zh_CN/LC_MESSAGES/senlinclient.po | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient.po b/senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient.po index 854b2a0..cf5bfe7 100644 --- a/senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient.po +++ b/senlinclient/locale/zh_CN/LC_MESSAGES/senlinclient.po @@ -2,13 +2,13 @@ # zzxwill , 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: python-senlinclient 0.5.1.dev37\n" +"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-12 10:11+0000\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-19 03:05+0000\n" +"PO-Revision-Date: 2016-08-22 09:48+0000\n" "Last-Translator: zzxwill \n" "Language-Team: Chinese (China)\n" "Language: zh-CN\n" @@ -387,6 +387,9 @@ msgstr "" "过滤参数以应用在返回的结果上。该参数可以指定多次,也可以用分号分隔参数之后执" "行一次。有效的过滤键是['type', 'name']" +msgid "ID of event to display details for" +msgstr "显示事件详情的ID" + msgid "ID of event to display details for." msgstr "显示事件详情的ID。" @@ -714,6 +717,12 @@ 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" @@ -738,6 +747,9 @@ 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" @@ -775,11 +787,17 @@ 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 "显示action详情的名字或ID。" +msgstr "显示节点详情的名字或ID。" msgid "Name or ID of the policy to be updated" msgstr "将要更新的策略的名称或ID" @@ -1159,6 +1177,10 @@ msgstr "所提供的文件不是合法的YAML文件:%s" 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 ”来获取该命令的帮助信息。" From 7b83eec5b814e8be1a70165db2f03c8d077b544e Mon Sep 17 00:00:00 2001 From: Lu lei Date: Tue, 23 Aug 2016 10:47:12 +0800 Subject: [PATCH 32/47] py3:Rmove six.iteritems/iterkeys in python-senlinclient MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1、As mentioned in [1], we should avoid using six.iteritems/keys achieve iterators. We can use dict.items/keys instead, as it will return iterators in PY3 as well. And dict.items/keys will more readable. 2、In py2, the performance about list should be negligible, see the link [2]. [1] https://wiki.openstack.org/wiki/Python3 [2] http://lists.openstack.org/pipermail/openstack-dev/2015-June/066391.html Change-Id: I75d12f56821666ebaec8350acba3a5fc29957f1e --- senlinclient/tests/unit/v1/fakes.py | 2 +- senlinclient/v1/action.py | 3 +-- senlinclient/v1/cluster.py | 3 +-- senlinclient/v1/cluster_policy.py | 3 +-- senlinclient/v1/event.py | 3 +-- senlinclient/v1/node.py | 3 +-- senlinclient/v1/policy_type.py | 5 ++--- senlinclient/v1/profile_type.py | 5 ++--- senlinclient/v1/receiver.py | 3 +-- 9 files changed, 11 insertions(+), 19 deletions(-) diff --git a/senlinclient/tests/unit/v1/fakes.py b/senlinclient/tests/unit/v1/fakes.py index 951594e..4619897 100644 --- a/senlinclient/tests/unit/v1/fakes.py +++ b/senlinclient/tests/unit/v1/fakes.py @@ -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/v1/action.py b/senlinclient/v1/action.py index 379e137..f5d7ed6 100644 --- a/senlinclient/v1/action.py +++ b/senlinclient/v1/action.py @@ -13,7 +13,6 @@ """Clustering v1 action implementations""" import logging -import six from openstack import exceptions as sdk_exc from osc_lib.command import command @@ -142,6 +141,6 @@ class ShowAction(command.ShowOne): 'depended_by': senlin_utils.list_formatter, } data = action.to_dict() - columns = sorted(list(six.iterkeys(data))) + columns = sorted(data.keys()) return columns, utils.get_dict_properties(data, columns, formatters=formatters) diff --git a/senlinclient/v1/cluster.py b/senlinclient/v1/cluster.py index d612f25..3b80b96 100644 --- a/senlinclient/v1/cluster.py +++ b/senlinclient/v1/cluster.py @@ -13,7 +13,6 @@ """Clustering v1 cluster action implementations""" import logging -import six import sys from openstack import exceptions as sdk_exc @@ -136,7 +135,7 @@ def _show_cluster(senlin_client, cluster_id): 'node_ids': senlin_utils.list_formatter } data = cluster.to_dict() - columns = sorted(list(six.iterkeys(data))) + columns = sorted(data.keys()) return columns, utils.get_dict_properties(data, columns, formatters=formatters) diff --git a/senlinclient/v1/cluster_policy.py b/senlinclient/v1/cluster_policy.py index aed724b..b950526 100644 --- a/senlinclient/v1/cluster_policy.py +++ b/senlinclient/v1/cluster_policy.py @@ -13,7 +13,6 @@ """Clustering v1 cluster policy action implementations""" import logging -import six from osc_lib.command import command from osc_lib import utils @@ -113,7 +112,7 @@ class ClusterPolicyShow(command.ShowOne): policy = senlin_client.get_cluster_policy(parsed_args.policy, parsed_args.cluster) data = policy.to_dict() - columns = sorted(list(six.iterkeys(data))) + columns = sorted(data.keys()) return columns, utils.get_dict_properties(data, columns) diff --git a/senlinclient/v1/event.py b/senlinclient/v1/event.py index 1fa1767..3325928 100644 --- a/senlinclient/v1/event.py +++ b/senlinclient/v1/event.py @@ -13,7 +13,6 @@ """Clustering v1 event action implementations""" import logging -import six from openstack import exceptions as sdk_exc from osc_lib.command import command @@ -128,5 +127,5 @@ class ShowEvent(command.ShowOne): raise exc.CommandError(_("Event not found: %s") % parsed_args.event) data = event.to_dict() - columns = sorted(list(six.iterkeys(data))) + columns = sorted(data.keys()) return columns, utils.get_dict_properties(data, columns) diff --git a/senlinclient/v1/node.py b/senlinclient/v1/node.py index 6a5e158..9cbe01d 100644 --- a/senlinclient/v1/node.py +++ b/senlinclient/v1/node.py @@ -13,7 +13,6 @@ """Clustering v1 node action implementations""" import logging -import six import sys from openstack import exceptions as sdk_exc @@ -160,7 +159,7 @@ def _show_node(senlin_client, node_id, show_details=False): if show_details: formatters['details'] = senlin_utils.nested_dict_formatter( list(data['details'].keys()), ['property', 'value']) - columns = sorted(list(six.iterkeys(data))) + columns = sorted(data.keys()) return columns, utils.get_dict_properties(data, columns, formatters=formatters) diff --git a/senlinclient/v1/policy_type.py b/senlinclient/v1/policy_type.py index 7dd40b9..d6ad86f 100644 --- a/senlinclient/v1/policy_type.py +++ b/senlinclient/v1/policy_type.py @@ -13,7 +13,6 @@ """Clustering v1 policy type action implementations""" import logging -import six from openstack import exceptions as sdk_exc from osc_lib.command import command @@ -65,6 +64,6 @@ class PolicyTypeShow(format_utils.YamlFormat): raise exc.CommandError(_('Policy Type not found: %s') % parsed_args.type_name) data = res.to_dict() - rows = list(six.itervalues(data)) - columns = list(six.iterkeys(data)) + rows = data.values() + columns = data.keys() return columns, rows diff --git a/senlinclient/v1/profile_type.py b/senlinclient/v1/profile_type.py index 8f9ba0a..4398340 100644 --- a/senlinclient/v1/profile_type.py +++ b/senlinclient/v1/profile_type.py @@ -13,7 +13,6 @@ """Clustering v1 profile type action implementations""" import logging -import six from openstack import exceptions as sdk_exc from osc_lib.command import command @@ -64,6 +63,6 @@ class ProfileTypeShow(format_utils.YamlFormat): raise exc.CommandError(_('Profile Type not found: %s') % parsed_args.type_name) data = res.to_dict() - rows = list(six.itervalues(data)) - columns = list(six.iterkeys(data)) + rows = data.values() + columns = data.keys() return columns, rows diff --git a/senlinclient/v1/receiver.py b/senlinclient/v1/receiver.py index fda140b..205c0dc 100644 --- a/senlinclient/v1/receiver.py +++ b/senlinclient/v1/receiver.py @@ -13,7 +13,6 @@ """Clustering v1 receiver action implementations""" import logging -import six import sys from openstack import exceptions as sdk_exc @@ -141,7 +140,7 @@ def _show_receiver(senlin_client, receiver_id): 'channel': senlin_utils.json_formatter, } data = receiver.to_dict() - columns = sorted(list(six.iterkeys(data))) + columns = sorted(data.keys()) return columns, utils.get_dict_properties(data, columns, formatters=formatters) From 2eca413257e43c799cb3a0bbd14d62a62a9c98fa Mon Sep 17 00:00:00 2001 From: Lu lei Date: Wed, 24 Aug 2016 14:30:07 +0800 Subject: [PATCH 33/47] Remove mox3 in test-requirement.txt of senlinclient mox3 was used for unit tests. But now we replace it with mock. So I think there no need to keep it in test-requirement.txt. Change-Id: I9170a3943e4e5b3c2c5a947ec04dc49726a7e423 --- test-requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 823ce41..e2ca68c 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -8,7 +8,6 @@ coverage>=3.6 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD requests-mock>=1.0 # Apache-2.0 mock>=2.0 # BSD -mox3>=0.7.0 # Apache-2.0 oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 sphinx!=1.3b1,<1.3,>=1.2.1 # BSD From 49f18b0897a53be229decc3426d5243ec6f69d26 Mon Sep 17 00:00:00 2001 From: Ethan Lynn Date: Wed, 24 Aug 2016 15:52:36 +0800 Subject: [PATCH 34/47] Fix _show_node error Passing the correct args to get_node function. Change-Id: I0c0c1180e1027ba5425fa9ddec1088874ddf63d9 Closes-Bug: #1616347 --- senlinclient/tests/unit/v1/test_node.py | 4 ++-- senlinclient/v1/node.py | 5 ++--- senlinclient/v1/shell.py | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/senlinclient/tests/unit/v1/test_node.py b/senlinclient/tests/unit/v1/test_node.py index 8cc2d11..caef437 100644 --- a/senlinclient/tests/unit/v1/test_node.py +++ b/senlinclient/tests/unit/v1/test_node.py @@ -177,14 +177,14 @@ class TestNodeShow(TestNode): 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'] diff --git a/senlinclient/v1/node.py b/senlinclient/v1/node.py index 6a5e158..2ada677 100644 --- a/senlinclient/v1/node.py +++ b/senlinclient/v1/node.py @@ -146,9 +146,8 @@ class ShowNode(command.ShowOne): def _show_node(senlin_client, node_id, show_details=False): """Show detailed info about the specified node.""" - args = {'show_details': True} if show_details else None try: - node = senlin_client.get_node(node_id, args=args) + node = senlin_client.get_node(node_id, details=show_details) except sdk_exc.ResourceNotFound: raise exc.CommandError(_('Node not found: %s') % node_id) @@ -157,7 +156,7 @@ def _show_node(senlin_client, node_id, show_details=False): 'data': senlin_utils.json_formatter, } data = node.to_dict() - if show_details: + if show_details and data['details']: formatters['details'] = senlin_utils.nested_dict_formatter( list(data['details'].keys()), ['property', 'value']) columns = sorted(list(six.iterkeys(data))) diff --git a/senlinclient/v1/shell.py b/senlinclient/v1/shell.py index 48152bf..c53192f 100644 --- a/senlinclient/v1/shell.py +++ b/senlinclient/v1/shell.py @@ -947,7 +947,7 @@ def _show_node(service, node_id, show_details=False): 'data': utils.json_formatter, } data = node.to_dict() - if show_details: + if show_details and data['details']: formatters['details'] = utils.nested_dict_formatter( list(data['details'].keys()), ['property', 'value']) From 57aa17c9ec8d85a2b3908ea883695ad1e2da3454 Mon Sep 17 00:00:00 2001 From: Ethan Lynn Date: Wed, 24 Aug 2016 18:29:06 +0800 Subject: [PATCH 35/47] Fix get_node Passing the correct args. Change-Id: Ib890e6f7742fe2673570b2e141a3905d51e46caa --- senlinclient/tests/unit/v1/test_client.py | 6 +++--- senlinclient/v1/client.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/senlinclient/tests/unit/v1/test_client.py b/senlinclient/tests/unit/v1/test_client.py index a12eb76..2c5ea96 100644 --- a/senlinclient/tests/unit/v1/test_client.py +++ b/senlinclient/tests/unit/v1/test_client.py @@ -336,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/v1/client.py b/senlinclient/v1/client.py index 5054014..6b73ede 100644 --- a/senlinclient/v1/client.py +++ b/senlinclient/v1/client.py @@ -304,13 +304,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 From e9502497a17bc0f75c8284b2a5dd2fbea1fc1c9c Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 25 Aug 2016 01:11:43 +0000 Subject: [PATCH 36/47] Updated from global requirements Change-Id: I54a622c81a5ca66932206bb2d795f7857e0c4052 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d8f292d..3997394 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ Babel>=2.3.4 # BSD pbr>=1.6 # Apache-2.0 PrettyTable<0.8,>=0.7 # BSD openstacksdk>=0.9.0 # Apache-2.0 -osc-lib>=0.4.0 # 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.16.0 # Apache-2.0 From 74958f8fed7947c3629f4c76faaeb3c6c7ed0041 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 25 Aug 2016 09:31:14 +0000 Subject: [PATCH 37/47] Updated from global requirements Change-Id: I31f07cff6acf84ced386cc973081f4417795560c --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3997394..6814264 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ Babel>=2.3.4 # BSD pbr>=1.6 # Apache-2.0 PrettyTable<0.8,>=0.7 # BSD -openstacksdk>=0.9.0 # 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 From a5f8b227e3ccce14db4122854c0c45ea2f18d98e Mon Sep 17 00:00:00 2001 From: tengqm Date: Thu, 25 Aug 2016 03:32:43 -0400 Subject: [PATCH 38/47] Add 'cluster-run' command This patch adds a 'cluster-run' command to the senlin CLI. Change-Id: I756d7422c9b4687153cc8e5467e186409fbdd7fc --- senlinclient/tests/unit/v1/test_shell.py | 171 +++++++++++++++++++++++ senlinclient/v1/shell.py | 165 +++++++++++++++++++++- 2 files changed, 331 insertions(+), 5 deletions(-) diff --git a/senlinclient/tests/unit/v1/test_shell.py b/senlinclient/tests/unit/v1/test_shell.py index 5e2e916..c6790c0 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 @@ -748,6 +750,175 @@ class ShellTest(testtools.TestCase): msg = _('Failed to delete some of the specified clusters.') self.assertEqual(msg, six.text_type(ex)) + @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 = {} + + 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): service = mock.Mock() diff --git a/senlinclient/v1/shell.py b/senlinclient/v1/shell.py index 1bf68c4..f7b8afa 100644 --- a/senlinclient/v1/shell.py +++ b/senlinclient/v1/shell.py @@ -11,8 +11,13 @@ # under the License. import logging +import subprocess +import threading +import time from openstack import exceptions as sdk_exc +import six + from senlinclient.common import exc from senlinclient.common.i18n import _ from senlinclient.common.i18n import _LW @@ -22,11 +27,9 @@ logger = logging.getLogger(__name__) def show_deprecated(deprecated, recommended): - logger.warning(_LW('"%(old)s" is deprecated, ' - 'please use "%(new)s" instead.'), - {'old': deprecated, - 'new': recommended} - ) + logger.warning( + _LW('"%(old)s" is deprecated, please use "%(new)s" instead.'), + {'old': deprecated, 'new': recommended}) def do_build_info(service, args=None): @@ -593,6 +596,158 @@ def do_cluster_delete(service, args): print('Request accepted') +def _run_script(node_id, addr, net, addr_type, port, user, ipv6, identity_file, + script, options, output=None): + version = 6 if ipv6 else 4 + + # Select the network to use. + if net: + addresses = addr.get(net) + if not addresses: + output['status'] = _('FAILED') + output['reason'] = _("Node '%(node)s' is not attached to network " + "'%(net)s'.") % {'node': node_id, 'net': net} + return + else: + # network not specified + if len(addr) > 1: + output['status'] = _('FAILED') + output['reason'] = _("Node '%(node)s' is attached to more than " + "one network. Please pick the network to " + "use.") % {'node': node_id} + return + elif not addr: + output['status'] = _('FAILED') + output['reason'] = _("Node '%(node)s' is not attached to any " + "network.") % {'node': node_id} + return + else: + addresses = list(six.itervalues(addr))[0] + + # Select the address in the selected network. + # If the extension is not present, we assume the address to be floating. + matching_addresses = [] + for a in addresses: + a_type = a.get('OS-EXT-IPS:type', 'floating') + a_version = a.get('version') + if (a_version == version and a_type == addr_type): + matching_addresses.append(a.get('addr')) + + if not matching_addresses: + output['status'] = _('FAILED') + output['reason'] = _("No address that would match network '%(net)s' " + "and type '%(type)s' of IPv%(ver)s has been " + "found for node '%(node)s'." + ) % {'net': net, 'type': addr_type, + 'ver': version, 'node': node_id} + return + + if len(matching_addresses) > 1: + output['status'] = _('FAILED') + output['reason'] = _("More than one IPv%(ver)s %(type)s address " + "found.") % {'ver': version, 'type': addr_type} + return + + ip_address = str(matching_addresses[0]) + identity = '-i %s' % identity_file if identity_file else '' + + cmd = [ + 'ssh', + '-%d' % version, + '-p%d' % port, + identity, + options, + '%s@%s' % (user, ip_address), + '%s' % script + ] + logger.debug("%s" % cmd) + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) + (stdout, stderr) = proc.communicate() + while proc.returncode is None: + time.sleep(1) + if proc.returncode == 0: + output['status'] = _('SUCCEEDED (0)') + output['output'] = stdout + if stderr: + output['error'] = stderr + else: + output['status'] = _('FAILED (%d)') % proc.returncode + output['output'] = stdout + if stderr: + output['error'] = stderr + + +@utils.arg("-p", "--port", metavar="", type=int, default=22, + help=_("Optional flag to indicate the port to use (Default=22).")) +@utils.arg("-t", "--address-type", type=str, default="floating", + help=_("Optional flag to indicate which IP type to use. Possible " + "values includes 'fixed' and 'floating' (the Default).")) +@utils.arg("-n", "--network", metavar='', default='', + help=_('Network to use for the ssh.')) +@utils.arg("-6", "--ipv6", action="store_true", default=False, + help=_("Optional flag to indicate whether to use an IPv6 address " + "attached to a server. (Defaults to IPv4 address)")) +@utils.arg("-u", "--user", metavar="", default="root", + help=_("Login to use.")) +@utils.arg("-i", "--identity-file", + help=_("Private key file, same as the '-i' option to the ssh " + "command.")) +@utils.arg("-O", "--ssh-options", default="", + help=_("Extra options to pass to ssh. see: man ssh.")) +@utils.arg("-s", "--script", metavar="", required=True, + help=_("Script file to run.")) +@utils.arg("id", metavar="", + help=_('Name or ID of the cluster.')) +def do_cluster_run(service, args): + """Run shell scripts on all nodes of a cluster.""" + if '@' in args.id: + user, cluster = args.id.split('@', 1) + args.user = user + args.cluster = cluster + + try: + attributes = service.collect_cluster_attrs(args.id, 'details') + except sdk_exc.ResourceNotFound: + raise exc.CommandError(_("Cluster not found: %s") % args.id) + + script = None + try: + f = open(args.script, 'r') + script = f.read() + except Exception: + raise exc.CommandError(_("Cound not open script file: %s") % + args.script) + + tasks = dict() + for attr in attributes: + node_id = attr.node_id + addr = attr.attr_value['addresses'] + + output = dict() + th = threading.Thread( + target=_run_script, + args=(node_id, addr, args.network, args.address_type, args.port, + args.user, args.ipv6, args.identity_file, + script, args.ssh_options), + kwargs={'output': output}) + th.start() + tasks[th] = (node_id, output) + + for t in tasks: + t.join() + + for t in tasks: + node_id, result = tasks[t] + print("node: %s" % node_id) + print("status: %s" % result.get('status')) + if "reason" in result: + print("reason: %s" % result.get('reason')) + if "output" in result: + print("output:\n%s" % result.get('output')) + if "error" in result: + print("error:\n%s" % result.get('error')) + + @utils.arg('-p', '--profile', metavar='', help=_('ID of new profile to use.')) @utils.arg('-t', '--timeout', metavar='', From 951f7ee5195fa8a6b63355e1d155533ae3e95b41 Mon Sep 17 00:00:00 2001 From: tengqm Date: Sat, 27 Aug 2016 09:48:55 -0400 Subject: [PATCH 39/47] Fix cluster-policy-list The 'enabled' field has been renamed to 'is_enabled' by the sdk, this patch changes the field name for listing. Change-Id: Ia020226bb20522d7eb03af02bd69dfa5845876ce --- senlinclient/tests/unit/v1/test_cluster_policy.py | 2 +- senlinclient/tests/unit/v1/test_shell.py | 2 +- senlinclient/v1/cluster_policy.py | 2 +- senlinclient/v1/shell.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/senlinclient/tests/unit/v1/test_cluster_policy.py b/senlinclient/tests/unit/v1/test_cluster_policy.py index 568617f..b0d342a 100644 --- a/senlinclient/tests/unit/v1/test_cluster_policy.py +++ b/senlinclient/tests/unit/v1/test_cluster_policy.py @@ -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", diff --git a/senlinclient/tests/unit/v1/test_shell.py b/senlinclient/tests/unit/v1/test_shell.py index 5e2e916..79cfb0a 100644 --- a/senlinclient/tests/unit/v1/test_shell.py +++ b/senlinclient/tests/unit/v1/test_shell.py @@ -1009,7 +1009,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', diff --git a/senlinclient/v1/cluster_policy.py b/senlinclient/v1/cluster_policy.py index b950526..4071423 100644 --- a/senlinclient/v1/cluster_policy.py +++ b/senlinclient/v1/cluster_policy.py @@ -63,7 +63,7 @@ class ClusterPolicyList(command.Lister): self.log.debug("take_action(%s)", parsed_args) senlin_client = self.app.client_manager.clustering - columns = ['policy_id', 'policy_name', 'policy_type', 'enabled'] + columns = ['policy_id', 'policy_name', 'policy_type', 'is_enabled'] cluster = senlin_client.get_cluster(parsed_args.cluster) queries = { 'sort': parsed_args.sort, diff --git a/senlinclient/v1/shell.py b/senlinclient/v1/shell.py index 1bf68c4..88b9f46 100644 --- a/senlinclient/v1/shell.py +++ b/senlinclient/v1/shell.py @@ -828,7 +828,7 @@ def do_cluster_policy_list(service, args): """List policies from cluster.""" show_deprecated('senlin cluster-policy-list', 'openstack cluster policy binding list') - fields = ['policy_id', 'policy_name', 'policy_type', 'enabled'] + fields = ['policy_id', 'policy_name', 'policy_type', 'is_enabled'] cluster = service.get_cluster(args.id) queries = { From 71d0f6c66a5a793e3c4422203b39f73495a4fc86 Mon Sep 17 00:00:00 2001 From: jonnary Date: Mon, 29 Aug 2016 10:52:28 +0800 Subject: [PATCH 40/47] Fix "openstack cluster policy binding list " Before this patch,"openstack cluster policy binding list " will list nothing. Change-Id: Ife35efd28af8207d6dfbc077dd70550c76ab565d --- senlinclient/v1/cluster_policy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/senlinclient/v1/cluster_policy.py b/senlinclient/v1/cluster_policy.py index b950526..8fd3dad 100644 --- a/senlinclient/v1/cluster_policy.py +++ b/senlinclient/v1/cluster_policy.py @@ -80,7 +80,7 @@ class ClusterPolicyList(command.Lister): } return ( columns, - (utils.get_item_properties(p.to_dict(), columns, + (utils.get_item_properties(p, columns, formatters=formatters) for p in policies) ) From 94e6ec96450e0c75d387f3323a7a96c720d875ba Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 29 Aug 2016 06:17:27 +0000 Subject: [PATCH 41/47] Updated from global requirements Change-Id: I6eedc3e6530c471ab786bb7f9ae55149a86cfe44 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6814264..32e9b0c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ 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.16.0 # Apache-2.0 -python-heatclient>=1.1.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 From 6b812ff40574cec97666d36f9977b4b7b715f453 Mon Sep 17 00:00:00 2001 From: tengqm Date: Mon, 29 Aug 2016 04:32:48 -0400 Subject: [PATCH 42/47] OSC plugin command for cluster-run Change-Id: I9530f3ca9a679f011c94019af2fe185172ff90ad --- senlinclient/tests/unit/v1/test_cluster.py | 185 +++++++++++++++++- senlinclient/v1/cluster.py | 206 +++++++++++++++++++++ setup.cfg | 1 + 3 files changed, 389 insertions(+), 3 deletions(-) diff --git a/senlinclient/tests/unit/v1/test_cluster.py b/senlinclient/tests/unit/v1/test_cluster.py index 98bfe7f..49c9ec9 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 osc_lib import exceptions as exc +import six from senlinclient.tests.unit.v1 import fakes from senlinclient.v1 import cluster as osc_cluster @@ -782,7 +783,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) @@ -833,3 +834,181 @@ class TestClusterCollect(TestCluster): 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/v1/cluster.py b/senlinclient/v1/cluster.py index 3b80b96..b100152 100644 --- a/senlinclient/v1/cluster.py +++ b/senlinclient/v1/cluster.py @@ -13,12 +13,16 @@ """Clustering v1 cluster action implementations""" import logging +import subprocess import sys +import threading +import time from openstack import exceptions as sdk_exc 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 @@ -806,3 +810,205 @@ class ClusterCollect(command.Lister): 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='