Merge tag '1.0.0' into debian/newton

python-senlinclient 1.0.0 release

  * New upstream release.
  * Fixed (build-)depends for this release.

Change-Id: I0176a850863b5b624c5d699792f0fe6eb217fac5
This commit is contained in:
Thomas Goirand
2016-09-14 09:24:41 +02:00
61 changed files with 3123 additions and 1631 deletions

View File

@@ -1,4 +1,5 @@
[gerrit]
host=review.openstack.org
port=29418
project=openstack/python-senlinclient.git
project=openstack/deb-python-senlinclient.git
defaultbranch=debian/newton

7
debian/changelog vendored
View File

@@ -1,3 +1,10 @@
python-senlinclient (1.0.0-1) experimental; urgency=medium
* New upstream release.
* Fixed (build-)depends for this release.
-- Thomas Goirand <zigo@debian.org> Wed, 14 Sep 2016 09:25:36 +0200
python-senlinclient (0.5.0-1) experimental; urgency=medium
* New upstream release.

42
debian/control vendored
View File

@@ -17,42 +17,40 @@ Build-Depends: debhelper (>= 9),
python3-setuptools,
Build-Depends-Indep: git,
python-babel (>= 2.3.4),
python-cliff (>= 1.15.0),
python-coverage,
python-fixtures (>= 3.0.0),
python-hacking,
python-heatclient (>= 1.1.0),
python-heatclient (>= 1.4.0),
python-mock (>= 2.0),
python-mox3 (>= 0.7.0),
python-openstacksdk (>= 0.8.6),
python-openstackclient (>= 2.1.0),
python-openstacksdk (>= 0.9.4),
python-osc-lib (>= 1.0.2),
python-oslo.i18n (>= 2.1.0),
python-oslo.serialization (>= 1.10.0),
python-oslo.utils (>= 3.11.0),
python-oslo.utils (>= 3.16.0),
python-oslosphinx (>= 2.5.0),
python-oslotest (>= 1.10.0),
python-prettytable,
python-requests (>= 2.10.0),
python-requests-mock (>= 0.7.0),
python-requests-mock (>= 1.0.0),
python-six (>= 1.9.0),
python-testscenarios,
python-testtools (>= 1.4.0),
python-yaml,
python3-babel (>= 2.3.4),
python3-cliff (>= 1.15.0),
python3-fixtures (>= 3.0.0),
python3-heatclient (>= 1.1.0),
python3-heatclient (>= 1.4.0),
python3-mock (>= 2.0),
python3-mox3 (>= 0.7.0),
python3-openstacksdk (>= 0.8.6),
python3-openstackclient (>= 2.1.0),
python3-openstacksdk (>= 0.9.4),
python3-osc-lib (>= 1.0.2),
python3-oslo.i18n (>= 2.1.0),
python3-oslo.serialization (>= 1.10.0),
python3-oslo.utils (>= 3.11.0),
python3-oslo.utils (>= 3.16.0),
python3-oslotest (>= 1.10.0),
python3-prettytable,
python3-requests (>= 2.10.0),
python3-requests-mock (>= 0.7.0),
python3-requests-mock (>= 1.0.0),
python3-six (>= 1.9.0),
python3-subunit,
python3-testscenarios,
@@ -61,20 +59,20 @@ Build-Depends-Indep: git,
subunit,
testrepository,
Standards-Version: 3.9.8
Vcs-Browser: https://anonscm.debian.org/cgit/openstack/python-senlinclient.git/
Vcs-Git: https://anonscm.debian.org/git/openstack/python-senlinclient.git
Vcs-Browser: https://git.openstack.org/cgit/openstack/deb-python-senlinclient
Vcs-Git: https://git.openstack.org/openstack/deb-python-senlinclient
Homepage: https://github.com/openstack/python-senlinclient
Package: python-senlinclient
Architecture: all
Depends: python-babel (>= 2.3.4),
python-cliff (>= 1.15.0),
python-heatclient (>= 1.1.0),
python-openstacksdk (>= 0.8.6),
python-heatclient (>= 1.4.0),
python-openstackclient (>= 2.1.0),
python-openstacksdk (>= 0.9.4),
python-osc-lib (>= 1.0.2),
python-oslo.i18n (>= 1.5.0),
python-oslo.serialization (>= 1.10.0),
python-oslo.utils (>= 3.11.0),
python-oslo.utils (>= 3.16.0),
python-pbr (>= 1.8),
python-prettytable,
python-requests (>= 2.10.0),
@@ -93,13 +91,13 @@ Description: OpenStack Clustering API Client Library - Python 2.7
Package: python3-senlinclient
Architecture: all
Depends: python3-babel (>= 2.3.4),
python3-cliff (>= 1.15.0),
python3-heatclient (>= 1.1.0),
python3-openstacksdk (>= 0.8.6),
python3-heatclient (>= 1.4.0),
python3-openstackclient (>= 2.1.0),
python3-openstacksdk (>= 0.9.4),
python3-osc-lib (>= 1.0.2),
python3-oslo.i18n (>= 1.5.0),
python3-oslo.serialization (>= 1.10.0),
python3-oslo.utils (>= 3.11.0),
python3-oslo.utils (>= 3.16.0),
python3-pbr (>= 1.8),
python3-prettytable,
python3-requests (>= 2.10.0),

View File

@@ -0,0 +1,4 @@
---
other:
- The 'senlin' CLI will be removed in April 2017. This message is now
explicitly printed when senlin CLI commands are invoked.

View File

@@ -0,0 +1,5 @@
---
features:
- A new command 'senlin cluster-collect' and its corresponding OSC plugin
command has been added. This new command can be used to aggregate a
specific property across a cluster.

View File

@@ -0,0 +1,4 @@
---
fixes:
- The cluster policy list command was broken by new SDK changes and then
fixed. The 'enabled' field is now renamed to 'is_enabled'.

View File

@@ -0,0 +1,5 @@
---
features:
- A new CLI command 'senlin cluster-run' and a new OSC plugin command
'openstack cluster run' have been added. Use the 'help' command to find
out how to use it.

View File

@@ -0,0 +1,5 @@
---
features:
- The senlin CLI 'node-delete' and the OSC plugin command
'cluster node delete' now outputs the action IDs when successful. Error
messages are printed when appropriate.

View File

@@ -0,0 +1,4 @@
---
features:
- The senlinclient now supports API micro-versioning. Current supported
version is 'clustering 1.2'.

View File

@@ -0,0 +1,4 @@
---
features:
- A policy-validate command has been added to senlin command line.
OSC support is added as well.

View File

@@ -0,0 +1,4 @@
---
features:
- A profile-validate command has been added to command line. It can be
used for validating the spec of a profile without creating it.

View File

@@ -0,0 +1,3 @@
---
features:
- The support to python 3.5 has been verified and gated.

View File

@@ -0,0 +1,5 @@
---
other:
- The receiver creation command (both senlin CLI and OSC plugin command)
now allow 'cluster' and 'action' to be left unspecified if the receiver
type is not 'webhook'.

View File

@@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import pbr.version
# Senlin Release Notes documentation build configuration file, created by
# sphinx-quickstart on Tue Nov 3 17:40:50 2015.
#
@@ -63,8 +65,8 @@ copyright = u'2015, Senlin Developers'
# built documents.
#
# The short X.Y version.
import pbr.version
senlin_version = pbr.version.VersionInfo('python-muranoclient')
senlin_version = pbr.version.VersionInfo('python-senlinclient')
# The full version, including alpha/beta/rc tags.
release = senlin_version.version_string_with_vcs()
# The short X.Y version.

View File

@@ -0,0 +1,40 @@
# zzxwill <zzxwill@gmail.com>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: Senlin Client Release Notes 0.5.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-07-04 03:44+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2016-06-25 02:41+0000\n"
"Last-Translator: zzxwill <zzxwill@gmail.com>\n"
"Language-Team: Chinese (China)\n"
"Language: zh-CN\n"
"X-Generator: Zanata 3.7.3\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid "0.5.0"
msgstr "0.5.0"
msgid "Added command for node-check and node-recover."
msgstr "已为node-check和node-recover添加了命令。"
msgid "Current Series Release Notes"
msgstr "当前版本发布说明"
msgid "New Features"
msgstr "新特性"
msgid ""
"OSC commands for cluster scaling are changed from 'cluster scale in' and "
"'cluster scale out' to 'cluster shrink' and 'cluster expand' respectively."
msgstr ""
"集群扩展的OSC命令分别从'cluster scale in'和'cluster scale out'改成了'cluster "
"shrink'和'cluster expand'。"
msgid "Senlin Client Release Notes"
msgstr "Senlin Client发布说明"
msgid "Upgrade Notes"
msgstr "升级说明"

View File

@@ -4,14 +4,13 @@
Babel>=2.3.4 # BSD
pbr>=1.6 # Apache-2.0
cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0
PrettyTable<0.8,>=0.7 # BSD
openstacksdk>=0.8.6 # Apache-2.0
openstacksdk>=0.9.4 # Apache-2.0
osc-lib>=1.0.2 # Apache-2.0
oslo.i18n>=2.1.0 # Apache-2.0
oslo.serialization>=1.10.0 # Apache-2.0
oslo.utils>=3.11.0 # Apache-2.0
python-heatclient>=1.1.0 # Apache-2.0
python-openstackclient>=2.1.0 # Apache-2.0
oslo.utils>=3.16.0 # Apache-2.0
python-heatclient>=1.4.0 # Apache-2.0
PyYAML>=3.1.0 # MIT
requests>=2.10.0 # Apache-2.0
six>=1.9.0 # MIT

View File

@@ -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='<service>=<name>',
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='<service>=<region>',
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='<service>=<version>',
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='<service>=<interface>',
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 '

View File

@@ -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:

View File

@@ -10,118 +10,12 @@
# License for the specific language governing permissions and limitations
# under the License.
import argparse
from openstack import connection
from openstack import exceptions
from openstack import profile
from openstack import resource as base
from openstack import utils
import os
from six.moves.urllib import parse as url_parse
from senlinclient.common import exc
# Alias here for consistency
prop = base.prop
class ProfileAction(argparse.Action):
"""A custom action to parse user preferences as key=value pairs
Stores results in users preferences object.
"""
prof = profile.Profile()
@classmethod
def env(cls, *vars):
for v in vars:
values = os.environ.get(v, None)
if values is None:
continue
cls.set_option(v, values)
return cls.prof
return cls.prof
@classmethod
def set_option(cls, var, values):
if var == '--os-extensions':
cls.prof.load_extension(values)
return
if var == 'OS_REGION_NAME':
var = 'region'
var = var.replace('--os-api-', '')
var = var.replace('OS_API_', '')
var = var.lower()
for kvp in values.split(','):
if '=' in kvp:
service, value = kvp.split('=')
else:
service = cls.prof.ALL
value = kvp
if var == 'name':
cls.prof.set_name(service, value)
elif var == 'region':
cls.prof.set_region(service, value)
elif var == 'version':
cls.prof.set_version(service, value)
elif var == 'interface':
cls.prof.set_interface(service, value)
def __call__(self, parser, namespace, values, option_string=None):
if getattr(namespace, self.dest, None) is None:
setattr(namespace, self.dest, ProfileAction.prof)
self.set_option(option_string, values)
class Resource(base.Resource):
"""Senlin version of resource.
These classes are here because the OpenStack SDK base version is making
some assumptions about operations that cannot be satisfied in Senlin.
"""
def create(self, session, extra_attrs=False):
"""Create a remote resource from this instance.
:param extra_attrs: If true, all attributions that
included in response will be collected and returned
to user after resource creation
"""
resp = self.create_by_id(session, self._attrs, self.id, path_args=self)
self._attrs[self.id_attribute] = resp[self.id_attribute]
if extra_attrs:
for attr in resp:
self._attrs[attr] = resp[attr]
self._reset_dirty()
return self
@classmethod
def get_data_with_args(cls, session, resource_id, args=None):
if not cls.allow_retrieve:
raise exceptions.MethodNotSupported('list')
url = utils.urljoin(cls.base_path, resource_id)
if args:
args.pop('id')
url = '%s?%s' % (url, url_parse.urlencode(args))
resp = session.get(url, endpoint_filter=cls.service)
body = resp.json()
if cls.resource_key:
body = body[cls.resource_key]
return body
def get_with_args(self, session, args=None):
body = self.get_data_with_args(session, self.id, args=args)
self._attrs.update(body)
self._loaded = True
return self
def create_connection(prof=None, user_agent=None, **kwargs):
if not prof:
@@ -133,6 +27,7 @@ def create_connection(prof=None, user_agent=None, **kwargs):
if region_name:
prof.set_region('clustering', region_name)
prof.set_api_version('clustering', '1.2')
try:
conn = connection.Connection(profile=prof, user_agent=user_agent,
**kwargs)

View File

@@ -80,6 +80,8 @@ def format_nested_dict(d, fields, column_names):
value = d[field]
if not isinstance(value, six.string_types):
value = jsonutils.dumps(value, indent=2, ensure_ascii=False)
if value is None:
value = '-'
pt.add_row([field, value.strip('"')])
return pt.get_string()
@@ -129,14 +131,16 @@ def _print_list(objs, fields, formatters=None, sortby_index=0,
row = []
for field in fields:
if field in formatters:
row.append(formatters[field](o))
data = formatters[field](o)
else:
if field in mixed_case_fields:
field_name = field.replace(' ', '_')
else:
field_name = field.lower().replace(' ', '_')
data = getattr(o, field_name, '')
row.append(data)
if data is None:
data = '-'
row.append(data)
pt.add_row(row)
if six.PY3:
@@ -169,9 +173,12 @@ def print_dict(d, formatters=None):
for field in d.keys():
if field in formatters:
pt.add_row([field, formatters[field](d[field])])
data = formatters[field](d[field])
else:
pt.add_row([field, d[field]])
data = d[field]
if data is None:
data = '-'
pt.add_row([field, data])
content = pt.get_string(sortby='Property')
if six.PY3:
@@ -180,6 +187,14 @@ def print_dict(d, formatters=None):
print(encodeutils.safe_encode(content))
def print_action_result(rid, res):
if res[0] == "OK":
output = _("accepted by action %s") % res[1]
else:
output = _("failed due to '%s'") % res[1]
print(_(" %(cid)s: %(output)s") % {"cid": rid, "output": output})
def format_parameters(params, parse_semicolon=True):
"""Reformat parameters into dict of format expected by the API."""
if not params:
@@ -240,7 +255,7 @@ def process_stack_spec(spec):
new_spec = {
# TODO(Qiming): add context support
'disable_rollback': spec.get('disable_rollback', True),
'context': spec.get('context', {}),
'context': spec.get('context', {}),
'parameters': spec.get('parameters', {}),
'timeout': spec.get('timeout', 60),
'template': template,

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
# Zheng Xi Zhou <zzxwill@gmail.com>, 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 <zzxwill@gmail.com>\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。"

View File

@@ -0,0 +1,19 @@
# Zheng Xi Zhou <zzxwill@gmail.com>, 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 <zzxwill@gmail.com>\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\"。"

File diff suppressed because it is too large Load Diff

View File

@@ -13,19 +13,24 @@
import logging
from openstack import connection
from openstackclient.common import utils
from openstack import profile
from osc_lib import utils
LOG = logging.getLogger(__name__)
DEFAULT_CLUSTERING_API_VERSION = '1'
API_VERSION_OPTION = 'os_clustering_api_version'
API_NAME = 'clustering'
CURRENT_API_VERSION = '1.2'
def make_client(instance):
"""Returns a clustering proxy"""
prof = profile.Profile()
prof.set_api_version(API_NAME, CURRENT_API_VERSION)
conn = connection.Connection(authenticator=instance.session.auth)
conn = connection.Connection(profile=prof,
authenticator=instance.session.auth)
LOG.debug('Connection: %s', conn)
LOG.debug('Clustering client initialized using OpenStackSDK: %s',
conn.cluster)

View File

@@ -181,8 +181,8 @@ class SenlinShell(object):
raise exc.CommandError(msg)
# project name or ID is needed, or else sdk may find the wrong project
if (not (args.project_id or args.project_name or args.tenant_id
or args.tenant_name)):
if (not (args.project_id or args.project_name or args.tenant_id or
args.tenant_name)):
if not (args.user_id):
msg = _('Either project/tenant ID or project/tenant name '
'must be specified, or else Senlin cannot know '
@@ -209,9 +209,9 @@ class SenlinShell(object):
not (args.project_domain_id or args.project_domain_name)):
msg = _('Either project domain ID (--project-domain-id / '
'env[OS_PROJECT_DOMAIN_ID]) orr project domain name '
'(--project-domain-name / env[OS_PROJECT_DOMAIN_NAME '
'must be specified, because project/tenant name may '
'not be unique.')
'(--project-domain-name / '
'env[OS_PROJECT_DOMAIN_NAME]) must be specified, '
'because project/tenant name may not be unique.')
raise exc.CommandError(msg)
def _setup_senlin_client(self, api_ver, args):
@@ -235,8 +235,7 @@ class SenlinShell(object):
'trust_id': args.trust_id,
}
return senlin_client.Client('1', args.user_preferences, USER_AGENT,
**kwargs)
return senlin_client.Client('1', user_agent=USER_AGENT, **kwargs)
def main(self, argv):
# Parse args once to find version

View File

@@ -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]

View File

@@ -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

View File

@@ -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()

View File

@@ -332,9 +332,9 @@ class ShellTest(testtools.TestCase):
args.project_domain_name = None
msg = _('Either project domain ID (--project-domain-id / '
'env[OS_PROJECT_DOMAIN_ID]) orr project domain name '
'(--project-domain-name / env[OS_PROJECT_DOMAIN_NAME '
'must be specified, because project/tenant name may '
'not be unique.')
'(--project-domain-name / '
'env[OS_PROJECT_DOMAIN_NAME]) must be specified, '
'because project/tenant name may not be unique.')
ex = self.assertRaises(exc.CommandError,
sh._check_identity_arguments, args)
self.assertEqual(msg, six.text_type(ex))
@@ -366,7 +366,7 @@ class ShellTest(testtools.TestCase):
mock_conn.return_value = conn
conn.session = mock.Mock()
sh._setup_senlin_client('1', args)
mock_conn.assert_called_once_with(args.user_preferences, USER_AGENT,
mock_conn.assert_called_once_with(prof=None, user_agent=USER_AGENT,
**kwargs)
client = mock.Mock()
senlin_client.Client = mock.MagicMock(return_value=client)

View File

@@ -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())

View File

@@ -16,7 +16,7 @@ import requests
import six
import sys
from openstackclient.tests import utils
from osc_lib.tests import utils
AUTH_TOKEN = "foobar"
@@ -141,7 +141,7 @@ class FakeResource(object):
self._loaded = loaded
def _add_details(self, info):
for (k, v) in six.iteritems(info):
for (k, v) in info.items():
setattr(self, k, v)
def __repr__(self):

View File

@@ -15,7 +15,7 @@ import mock
from openstack.cluster.v1 import action as sdk_action
from openstack import exceptions as sdk_exc
from openstackclient.common import exceptions as exc
from osc_lib import exceptions as exc
from senlinclient.tests.unit.v1 import fakes
from senlinclient.v1 import action as osc_action
@@ -159,7 +159,7 @@ class TestActionList(TestAction):
class TestActionShow(TestAction):
get_response = {
response = {
"action": "CLUSTER_DELETE",
"cause": "RPC Request",
"context": {},
@@ -184,7 +184,7 @@ class TestActionShow(TestAction):
super(TestActionShow, self).setUp()
self.cmd = osc_action.ShowAction(self.app, None)
self.mock_client.get_action = mock.Mock(
return_value=sdk_action.Action(attrs=self.get_response))
return_value=sdk_action.Action(**self.response))
def test_action_show(self):
arglist = ['my_action']

View File

@@ -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 = []

View File

@@ -33,16 +33,17 @@ class ClientTest(testtools.TestCase):
self.assertEqual(self.conn, sc.conn)
self.assertEqual(self.service, sc.service)
mock_conn.assert_called_once_with(None, None)
mock_conn.assert_called_once_with(prof=None, user_agent=None)
def test_init_with_params(self, mock_conn):
mock_conn.return_value = self.conn
sc = client.Client(preferences='FOO', user_agent='BAR', zoo='LARR')
sc = client.Client(prof='FOO', user_agent='BAR', zoo='LARR')
self.assertEqual(self.conn, sc.conn)
self.assertEqual(self.service, sc.service)
mock_conn.assert_called_once_with('FOO', 'BAR', zoo='LARR')
mock_conn.assert_called_once_with(prof='FOO', user_agent='BAR',
zoo='LARR')
def test_profile_types(self, mock_conn):
mock_conn.return_value = self.conn
@@ -335,16 +336,16 @@ class ClientTest(testtools.TestCase):
res = sc.get_node('FOOBAR')
self.assertEqual(self.service.get_node.return_value, res)
self.service.get_node.assert_called_once_with('FOOBAR', args=None)
self.service.get_node.assert_called_once_with('FOOBAR', details=False)
def test_get_node_with_details(self, mock_conn):
mock_conn.return_value = self.conn
sc = client.Client()
res = sc.get_node('FOOBAR', args={'show_details': True})
res = sc.get_node('FOOBAR', details=True)
self.assertEqual(self.service.get_node.return_value, res)
self.service.get_node.assert_called_once_with(
'FOOBAR', args={'show_details': True})
'FOOBAR', details=True)
def test_create_node(self, mock_conn):
mock_conn.return_value = self.conn

View File

@@ -11,12 +11,13 @@
# under the License.
import copy
import mock
import six
import subprocess
import mock
from openstack.cluster.v1 import cluster as sdk_cluster
from openstack import exceptions as sdk_exc
from openstackclient.common import exceptions as exc
from osc_lib import exceptions as exc
import six
from senlinclient.tests.unit.v1 import fakes
from senlinclient.v1 import cluster as osc_cluster
@@ -43,7 +44,7 @@ class TestClusterList(TestCluster):
"metadata": {},
"min_size": 0,
"name": "cluster1",
"nodes": [
"node_ids": [
"b07c57c8-7ab2-47bf-bdf8-e894c0c601b9",
"ecc23d3e-bb68-48f8-8260-c9cf6bcb6e61",
"da1e9c87-e584-4626-a120-022da5062dac"
@@ -147,7 +148,7 @@ class TestClusterList(TestCluster):
class TestClusterShow(TestCluster):
get_response = {"cluster": {
response = {"cluster": {
"created_at": "2015-02-11T15:13:20",
"data": {},
"desired_capacity": 0,
@@ -158,7 +159,7 @@ class TestClusterShow(TestCluster):
"metadata": {},
"min_size": 0,
"name": "my_cluster",
"nodes": [],
"node_ids": [],
"policies": [],
"profile_id": "edc63d0a-2ca4-48fa-9854-27926da76a4a",
"profile_name": "mystack",
@@ -174,8 +175,7 @@ class TestClusterShow(TestCluster):
super(TestClusterShow, self).setUp()
self.cmd = osc_cluster.ShowCluster(self.app, None)
self.mock_client.get_cluster = mock.Mock(
return_value=sdk_cluster.Cluster(
attrs=self.get_response['cluster']))
return_value=sdk_cluster.Cluster(**self.response['cluster']))
def test_cluster_show(self):
arglist = ['my_cluster']
@@ -205,7 +205,7 @@ class TestClusterCreate(TestCluster):
"metadata": {},
"min_size": 0,
"name": "test_cluster",
"nodes": [],
"node_ids": [],
"policies": [],
"profile_id": "edc63d0a-2ca4-48fa-9854-27926da76a4a",
"profile_name": "mystack",
@@ -231,9 +231,9 @@ class TestClusterCreate(TestCluster):
super(TestClusterCreate, self).setUp()
self.cmd = osc_cluster.CreateCluster(self.app, None)
self.mock_client.create_cluster = mock.Mock(
return_value=sdk_cluster.Cluster(attrs=self.response['cluster']))
return_value=sdk_cluster.Cluster(**self.response['cluster']))
self.mock_client.get_cluster = mock.Mock(
return_value=sdk_cluster.Cluster(attrs=self.response['cluster']))
return_value=sdk_cluster.Cluster(**self.response['cluster']))
def test_cluster_create_defaults(self):
arglist = ['test_cluster', '--profile', 'mystack']
@@ -275,7 +275,7 @@ class TestClusterUpdate(TestCluster):
"metadata": {},
"min_size": 0,
"name": "test_cluster",
"nodes": [],
"node_ids": [],
"policies": [],
"profile_id": "edc63d0a-2ca4-48fa-9854-27926da76a4a",
"profile_name": "mystack",
@@ -301,11 +301,11 @@ class TestClusterUpdate(TestCluster):
super(TestClusterUpdate, self).setUp()
self.cmd = osc_cluster.UpdateCluster(self.app, None)
self.mock_client.update_cluster = mock.Mock(
return_value=sdk_cluster.Cluster(attrs=self.response['cluster']))
return_value=sdk_cluster.Cluster(**self.response['cluster']))
self.mock_client.get_cluster = mock.Mock(
return_value=sdk_cluster.Cluster(attrs=self.response['cluster']))
return_value=sdk_cluster.Cluster(**self.response['cluster']))
self.mock_client.find_cluster = mock.Mock(
return_value=sdk_cluster.Cluster(attrs=self.response['cluster']))
return_value=sdk_cluster.Cluster(**self.response['cluster']))
def test_cluster_update_defaults(self):
arglist = ['--name', 'new_cluster', '--metadata', 'nk1=nv1;nk2=nv2',
@@ -330,7 +330,8 @@ class TestClusterDelete(TestCluster):
def setUp(self):
super(TestClusterDelete, self).setUp()
self.cmd = osc_cluster.DeleteCluster(self.app, None)
self.mock_client.delete_cluster = mock.Mock()
mock_cluster = mock.Mock(location='abc/fake_action_id')
self.mock_client.delete_cluster = mock.Mock(return_value=mock_cluster)
def test_cluster_delete(self):
arglist = ['cluster1', 'cluster2', 'cluster3']
@@ -354,10 +355,12 @@ class TestClusterDelete(TestCluster):
arglist = ['my_cluster']
self.mock_client.delete_cluster.side_effect = sdk_exc.ResourceNotFound
parsed_args = self.check_parser(self.cmd, arglist, [])
error = self.assertRaises(exc.CommandError, self.cmd.take_action,
parsed_args)
self.assertIn('Failed to delete 1 of the 1 specified cluster(s).',
str(error))
self.cmd.take_action(parsed_args)
self.mock_client.delete_cluster.assert_has_calls(
[mock.call('my_cluster', False)]
)
def test_cluster_delete_one_found_one_not_found(self):
arglist = ['cluster1', 'cluster2']
@@ -365,13 +368,12 @@ class TestClusterDelete(TestCluster):
[None, sdk_exc.ResourceNotFound]
)
parsed_args = self.check_parser(self.cmd, arglist, [])
error = self.assertRaises(exc.CommandError,
self.cmd.take_action, parsed_args)
self.cmd.take_action(parsed_args)
self.mock_client.delete_cluster.assert_has_calls(
[mock.call('cluster1', False), mock.call('cluster2', False)]
)
self.assertEqual('Failed to delete 1 of the 2 specified cluster(s).',
str(error))
@mock.patch('sys.stdin', spec=six.StringIO)
def test_cluster_delete_prompt_yes(self, mock_stdin):
@@ -425,14 +427,20 @@ class TestClusterResize(TestCluster):
self.assertEqual("Only one of 'capacity', 'adjustment' "
"and 'percentage' can be specified.", str(error))
def test_cluster_resize_none_params(self):
def test_cluster_resize_only_constraints(self):
arglist = ['--min-size', '1', '--max-size', '20', 'my_cluster']
parsed_args = self.check_parser(self.cmd, arglist, [])
error = self.assertRaises(exc.CommandError,
self.cmd.take_action,
parsed_args)
self.assertEqual("At least one of 'capacity', 'adjustment' and "
"'percentage' should be specified.", str(error))
self.cmd.take_action(parsed_args)
kwargs = {
'min_size': 1,
'max_size': 20,
'adjustment_type': None,
'min_step': None,
'number': None,
'strict': False
}
self.mock_client.cluster_resize.assert_called_with('my_cluster',
**kwargs)
def test_cluster_resize_capacity(self):
arglist = ['--capacity', '2', '--min-size', '1', '--max-size', '20',
@@ -777,7 +785,7 @@ class TestClusterRecover(TestCluster):
self.mock_client.recover_cluster = mock.Mock(
return_value=self.response)
def test_cluster_recoverk(self):
def test_cluster_recover(self):
arglist = ['cluster1', 'cluster2', 'cluster3']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
@@ -793,3 +801,216 @@ class TestClusterRecover(TestCluster):
error = self.assertRaises(exc.CommandError, self.cmd.take_action,
parsed_args)
self.assertIn('Cluster not found: cluster1', str(error))
class TestClusterCollect(TestCluster):
response = [
{
"node_id": "8bb476c3-0f4c-44ee-9f64-c7b0260814de",
"attr_value": "value 1",
},
{
"node_id": "7d85f602-a948-4a30-afd4-e84f47471c15",
"attr_value": "value 2",
}
]
def setUp(self):
super(TestClusterCollect, self).setUp()
self.cmd = osc_cluster.ClusterCollect(self.app, None)
self.mock_client.collect_cluster_attrs = mock.Mock(
return_value=self.response)
def test_cluster_collect(self):
arglist = ['--path', 'path.to.attr', 'cluster1']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.mock_client.collect_cluster_attrs.assert_called_once_with(
'cluster1', 'path.to.attr')
self.assertEqual(['node_id', 'attr_value'], columns)
def test_cluster_collect_with_full_id(self):
arglist = ['--path', 'path.to.attr', '--full-id', 'cluster1']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.mock_client.collect_cluster_attrs.assert_called_once_with(
'cluster1', 'path.to.attr')
self.assertEqual(['node_id', 'attr_value'], columns)
class TestClusterRun(TestCluster):
attrs = [
mock.Mock(node_id="NODE_ID1",
attr_value={"addresses": 'ADDRESS CONTENT 1'}),
mock.Mock(node_id="NODE_ID2",
attr_value={"addresses": 'ADDRESS CONTENT 2'})
]
def setUp(self):
super(TestClusterRun, self).setUp()
self.cmd = osc_cluster.ClusterRun(self.app, None)
self.mock_client.collect_cluster_attrs = mock.Mock(
return_value=self.attrs)
@mock.patch('subprocess.Popen')
def test__run_script(self, mock_proc):
x_proc = mock.Mock(returncode=0)
x_stdout = 'OUTPUT'
x_stderr = 'ERROR'
x_proc.communicate.return_value = (x_stdout, x_stderr)
mock_proc.return_value = x_proc
addr = {
'private': [
{
'OS-EXT-IPS:type': 'floating',
'version': 4,
'addr': '1.2.3.4',
}
]
}
output = {}
self.cmd._run_script('NODE_ID', addr, 'private', 'floating', 22,
'john', False, 'identity_path', 'echo foo',
'-f bar',
output=output)
mock_proc.assert_called_once_with(
['ssh', '-4', '-p22', '-i identity_path', '-f bar', 'john@1.2.3.4',
'echo foo'],
stdout=subprocess.PIPE)
self.assertEqual(
{'status': 'SUCCEEDED (0)', 'output': 'OUTPUT', 'error': 'ERROR'},
output)
def test__run_script_network_not_found(self):
addr = {'foo': 'bar'}
output = {}
self.cmd._run_script('NODE_ID', addr, 'private', 'floating', 22,
'john', False, 'identity_path', 'echo foo',
'-f bar',
output=output)
self.assertEqual(
{'status': 'FAILED',
'error': "Node 'NODE_ID' is not attached to network 'private'."
},
output)
def test__run_script_more_than_one_network(self):
addr = {'foo': 'bar', 'koo': 'tar'}
output = {}
self.cmd._run_script('NODE_ID', addr, '', 'floating', 22, 'john',
False, 'identity_path', 'echo foo', '-f bar',
output=output)
self.assertEqual(
{'status': 'FAILED',
'error': "Node 'NODE_ID' is attached to more than one "
"network. Please pick the network to use."},
output)
def test__run_script_no_network(self):
addr = {}
output = {}
self.cmd._run_script('NODE_ID', addr, '', 'floating', 22, 'john',
False, 'identity_path', 'echo foo', '-f bar',
output=output)
self.assertEqual(
{'status': 'FAILED',
'error': "Node 'NODE_ID' is not attached to any network."},
output)
def test__run_script_no_matching_address(self):
addr = {
'private': [
{
'OS-EXT-IPS:type': 'fixed',
'version': 4,
'addr': '1.2.3.4',
}
]
}
output = {}
self.cmd._run_script('NODE_ID', addr, 'private', 'floating', 22,
'john', False, 'identity_path', 'echo foo',
'-f bar',
output=output)
self.assertEqual(
{'status': 'FAILED',
'error': "No address that matches network 'private' and "
"type 'floating' of IPv4 has been found for node "
"'NODE_ID'."},
output)
def test__run_script_more_than_one_address(self):
addr = {
'private': [
{
'OS-EXT-IPS:type': 'fixed',
'version': 4,
'addr': '1.2.3.4',
},
{
'OS-EXT-IPS:type': 'fixed',
'version': 4,
'addr': '5.6.7.8',
},
]
}
output = {}
self.cmd._run_script('NODE_ID', addr, 'private', 'fixed', 22, 'john',
False, 'identity_path', 'echo foo', '-f bar',
output=output)
self.assertEqual(
{'status': 'FAILED',
'error': "More than one IPv4 fixed address found."},
output)
@mock.patch('threading.Thread')
@mock.patch.object(osc_cluster.ClusterRun, '_run_script')
def test_cluster_run(self, mock_script, mock_thread):
arglist = [
'--port', '22',
'--address-type', 'fixed',
'--network', 'private',
'--user', 'root',
'--identity-file', 'path-to-identity',
'--ssh-options', '-f boo',
'--script', 'script-file',
'cluster1'
]
parsed_args = self.check_parser(self.cmd, arglist, [])
th1 = mock.Mock()
th2 = mock.Mock()
mock_thread.side_effect = [th1, th2]
fake_script = 'blah blah'
with mock.patch('senlinclient.v1.cluster.open',
mock.mock_open(read_data=fake_script)) as mock_open:
self.cmd.take_action(parsed_args)
self.mock_client.collect_cluster_attrs.assert_called_once_with(
'cluster1', 'details')
mock_open.assert_called_once_with('script-file', 'r')
mock_thread.assert_has_calls([
mock.call(target=mock_script,
args=('NODE_ID1', 'ADDRESS CONTENT 1', 'private',
'fixed', 22, 'root', False, 'path-to-identity',
'blah blah', '-f boo'),
kwargs={'output': {}}),
mock.call(target=mock_script,
args=('NODE_ID2', 'ADDRESS CONTENT 2', 'private',
'fixed', 22, 'root', False, 'path-to-identity',
'blah blah', '-f boo'),
kwargs={'output': {}})
])
th1.start.assert_called_once_with()
th2.start.assert_called_once_with()
th1.join.assert_called_once_with()
th2.join.assert_called_once_with()

View File

@@ -12,7 +12,7 @@
import mock
from openstack.cluster.v1 import cluster_policy as sdk_cluster_policy
from openstack.cluster.v1 import cluster_policy as scp
from senlinclient.tests.unit.v1 import fakes
from senlinclient.v1 import cluster_policy as osc_cluster_policy
@@ -25,7 +25,7 @@ class TestClusterPolicy(fakes.TestClusteringv1):
class TestClusterPolicyList(TestClusterPolicy):
columns = ['policy_id', 'policy_name', 'policy_type', 'enabled']
columns = ['policy_id', 'policy_name', 'policy_type', 'is_enabled']
response = {"cluster_policies": [
{
"cluster_id": "7d85f602-a948-4a30-afd4-e84f47471c15",
@@ -76,7 +76,7 @@ class TestClusterPolicyList(TestClusterPolicy):
class TestClusterPolicyShow(TestClusterPolicy):
get_response = {"cluster_policy": {
response = {"cluster_policy": {
"cluster_id": "7d85f602-a948-4a30-afd4-e84f47471c15",
"cluster_name": "my_cluster",
"enabled": True,
@@ -90,8 +90,7 @@ class TestClusterPolicyShow(TestClusterPolicy):
super(TestClusterPolicyShow, self).setUp()
self.cmd = osc_cluster_policy.ClusterPolicyShow(self.app, None)
self.mock_client.get_cluster_policy = mock.Mock(
return_value=sdk_cluster_policy.ClusterPolicy(None,
self.get_response))
return_value=scp.ClusterPolicy(**self.response['cluster_policy']))
def test_cluster_policy_show(self):
arglist = ['--policy', 'my_policy', 'my_cluster']

View File

@@ -15,7 +15,7 @@ import mock
from openstack.cluster.v1 import event as sdk_event
from openstack import exceptions as sdk_exc
from openstackclient.common import exceptions as exc
from osc_lib import exceptions as exc
from senlinclient.tests.unit.v1 import fakes
from senlinclient.v1 import event as osc_event
@@ -60,7 +60,7 @@ class TestEventList(TestEvent):
super(TestEventList, self).setUp()
self.cmd = osc_event.ListEvent(self.app, None)
self.mock_client.events = mock.Mock(
return_value=sdk_event.Event(None, {}))
return_value=[sdk_event.Event(**self.response['events'][0])])
def test_event_list_defaults(self):
arglist = []
@@ -95,8 +95,7 @@ class TestEventList(TestEvent):
self.assertEqual(self.columns, columns)
def test_event_list_sort_invalid_key(self):
self.mock_client.events = mock.Mock(
return_value=self.response)
self.mock_client.events = mock.Mock(return_value=self.response)
kwargs = copy.deepcopy(self.defaults)
kwargs['sort'] = 'bad_key'
arglist = ['--sort', 'bad_key']
@@ -106,8 +105,7 @@ class TestEventList(TestEvent):
self.cmd.take_action, parsed_args)
def test_event_list_sort_invalid_direction(self):
self.mock_client.events = mock.Mock(
return_value=self.response)
self.mock_client.events = mock.Mock(return_value=self.response)
kwargs = copy.deepcopy(self.defaults)
kwargs['sort'] = 'name:bad_direction'
arglist = ['--sort', 'name:bad_direction']
@@ -136,7 +134,7 @@ class TestEventList(TestEvent):
class TestEventShow(TestEvent):
get_response = {"event": {
response = {"event": {
"action": "create",
"cluster_id": 'null',
"id": "2d255b9c-8f36-41a2-a137-c0175ccc29c3",
@@ -155,7 +153,7 @@ class TestEventShow(TestEvent):
super(TestEventShow, self).setUp()
self.cmd = osc_event.ShowEvent(self.app, None)
self.mock_client.get_event = mock.Mock(
return_value=sdk_event.Event(None, self.get_response))
return_value=sdk_event.Event(**self.response['event']))
def test_event_show(self):
arglist = ['my_event']

View File

@@ -16,7 +16,7 @@ import six
from openstack.cluster.v1 import node as sdk_node
from openstack import exceptions as sdk_exc
from openstackclient.common import exceptions as exc
from osc_lib import exceptions as exc
from senlinclient.tests.unit.v1 import fakes
from senlinclient.v1 import node as osc_node
@@ -144,7 +144,7 @@ class TestNodeList(TestNode):
class TestNodeShow(TestNode):
get_response = {"node": {
response = {"node": {
"cluster_id": None,
"created_at": "2015-02-10T12:03:16",
"data": {},
@@ -171,20 +171,20 @@ class TestNodeShow(TestNode):
super(TestNodeShow, self).setUp()
self.cmd = osc_node.ShowNode(self.app, None)
self.mock_client.get_node = mock.Mock(
return_value=sdk_node.Node(attrs=self.get_response['node']))
return_value=sdk_node.Node(**self.response['node']))
def test_node_show(self):
arglist = ['my_node']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.mock_client.get_node.assert_called_with('my_node', args=None)
self.mock_client.get_node.assert_called_with('my_node', details=False)
def test_node_show_with_details(self):
arglist = ['my_node', '--details']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.mock_client.get_node.assert_called_with(
'my_node', args={'show_details': True})
'my_node', details=True)
def test_node_show_not_found(self):
arglist = ['my_node']
@@ -230,9 +230,9 @@ class TestNodeCreate(TestNode):
super(TestNodeCreate, self).setUp()
self.cmd = osc_node.CreateNode(self.app, None)
self.mock_client.create_node = mock.Mock(
return_value=sdk_node.Node(attrs=self.response['node']))
return_value=sdk_node.Node(**self.response['node']))
self.mock_client.get_node = mock.Mock(
return_value=sdk_node.Node(attrs=self.response['node']))
return_value=sdk_node.Node(**self.response['node']))
def test_node_create_defaults(self):
arglist = ['my_node', '--profile', 'mystack']
@@ -305,11 +305,11 @@ class TestNodeUpdate(TestNode):
super(TestNodeUpdate, self).setUp()
self.cmd = osc_node.UpdateNode(self.app, None)
self.mock_client.update_node = mock.Mock(
return_value=sdk_node.Node(attrs=self.response['node']))
return_value=sdk_node.Node(**self.response['node']))
self.mock_client.get_node = mock.Mock(
return_value=sdk_node.Node(attrs=self.response['node']))
return_value=sdk_node.Node(**self.response['node']))
self.mock_client.find_node = mock.Mock(
return_value=sdk_node.Node(attrs=self.response['node']))
return_value=sdk_node.Node(**self.response['node']))
def test_node_update_defaults(self):
arglist = ['--name', 'new_node', '--metadata', 'nk1=nv1;nk2=nv2',
@@ -335,7 +335,8 @@ class TestNodeDelete(TestNode):
def setUp(self):
super(TestNodeDelete, self).setUp()
self.cmd = osc_node.DeleteNode(self.app, None)
self.mock_client.delete_node = mock.Mock()
mock_node = mock.Mock(location='loc/fake_action_id')
self.mock_client.delete_node = mock.Mock(return_value=mock_node)
def test_node_delete(self):
arglist = ['node1', 'node2', 'node3']
@@ -359,10 +360,12 @@ class TestNodeDelete(TestNode):
arglist = ['my_node']
self.mock_client.delete_node.side_effect = sdk_exc.ResourceNotFound
parsed_args = self.check_parser(self.cmd, arglist, [])
error = self.assertRaises(exc.CommandError, self.cmd.take_action,
parsed_args)
self.assertIn('Failed to delete 1 of the 1 specified node(s).',
str(error))
self.cmd.take_action(parsed_args)
self.mock_client.delete_node.assert_has_calls(
[mock.call('my_node', False)]
)
def test_node_delete_one_found_one_not_found(self):
arglist = ['node1', 'node2']
@@ -370,13 +373,12 @@ class TestNodeDelete(TestNode):
[None, sdk_exc.ResourceNotFound]
)
parsed_args = self.check_parser(self.cmd, arglist, [])
error = self.assertRaises(exc.CommandError,
self.cmd.take_action, parsed_args)
self.cmd.take_action(parsed_args)
self.mock_client.delete_node.assert_has_calls(
[mock.call('node1', False), mock.call('node2', False)]
)
self.assertEqual('Failed to delete 1 of the 2 specified node(s).',
str(error))
@mock.patch('sys.stdin', spec=six.StringIO)
def test_node_delete_prompt_yes(self, mock_stdin):

View File

@@ -16,7 +16,7 @@ import six
from openstack.cluster.v1 import policy as sdk_policy
from openstack import exceptions as sdk_exc
from openstackclient.common import exceptions as exc
from osc_lib import exceptions as exc
from senlinclient.tests.unit.v1 import fakes
from senlinclient.v1 import policy as osc_policy
@@ -141,7 +141,7 @@ class TestPolicyList(TestPolicy):
class TestPolicyShow(TestPolicy):
get_response = {"policy": {
response = {"policy": {
"created_at": "2015-03-02T07:40:31",
"data": {},
"domain": 'null',
@@ -170,7 +170,7 @@ class TestPolicyShow(TestPolicy):
super(TestPolicyShow, self).setUp()
self.cmd = osc_policy.ShowPolicy(self.app, None)
self.mock_client.get_policy = mock.Mock(
return_value=sdk_policy.Policy(attrs=self.get_response['policy']))
return_value=sdk_policy.Policy(**self.response['policy']))
def test_policy_show(self):
arglist = ['sp001']
@@ -230,9 +230,9 @@ class TestPolicyCreate(TestPolicy):
super(TestPolicyCreate, self).setUp()
self.cmd = osc_policy.CreatePolicy(self.app, None)
self.mock_client.create_policy = mock.Mock(
return_value=sdk_policy.Policy(attrs=self.response['policy']))
return_value=sdk_policy.Policy(**self.response['policy']))
self.mock_client.get_policy = mock.Mock(
return_value=sdk_policy.Policy(attrs=self.response['policy']))
return_value=sdk_policy.Policy(**self.response['policy']))
def test_policy_create_defaults(self):
arglist = ['my_policy', '--spec-file', self.spec_path]
@@ -274,11 +274,11 @@ class TestPolicyUpdate(TestPolicy):
super(TestPolicyUpdate, self).setUp()
self.cmd = osc_policy.UpdatePolicy(self.app, None)
self.mock_client.update_policy = mock.Mock(
return_value=sdk_policy.Policy(attrs=self.response['policy']))
return_value=sdk_policy.Policy(**self.response['policy']))
self.mock_client.get_policy = mock.Mock(
return_value=sdk_policy.Policy(attrs=self.response['policy']))
return_value=sdk_policy.Policy(**self.response['policy']))
self.mock_client.find_policy = mock.Mock(
return_value=sdk_policy.Policy(attrs=self.response['policy']))
return_value=sdk_policy.Policy(**self.response['policy']))
def test_policy_update_defaults(self):
arglist = ['--name', 'new_policy', '9f779ddf']
@@ -368,3 +368,56 @@ class TestPolicyDelete(TestPolicy):
mock_stdin.readline.assert_called_with()
self.mock_client.delete_policy.assert_not_called()
class TestPolicyValidate(TestPolicy):
spec_path = 'senlinclient/tests/test_specs/deletion_policy.yaml'
response = {"policy": {
"created_at": None,
"data": {},
"domain": 'null',
"id": None,
"name": "validated_policy",
"project": "5f1cc92b578e4e25a3b284179cf20a9b",
"spec": {
"description": "A policy for choosing victim node(s) from a "
"cluster for deletion.",
"properties": {
"criteria": "OLDEST_FIRST",
"destroy_after_deletion": True,
"grace_period": 60,
"reduce_desired_capacity": False
},
"type": "senlin.policy.deletion",
"version": 1.0
},
"type": "senlin.policy.deletion-1.0",
"updated_at": 'null',
"user": "2d7aca950f3e465d8ef0c81720faf6ff"
}}
defaults = {
"spec": {
"version": 1,
"type": "senlin.policy.deletion",
"description": "A policy for choosing victim node(s) from a "
"cluster for deletion.",
"properties": {
"destroy_after_deletion": True,
"grace_period": 60,
"reduce_desired_capacity": False,
"criteria": "OLDEST_FIRST"
}
}
}
def setUp(self):
super(TestPolicyValidate, self).setUp()
self.cmd = osc_policy.ValidatePolicy(self.app, None)
self.mock_client.validate_policy = mock.Mock(
return_value=sdk_policy.Policy(**self.response['policy']))
def test_policy_validate(self):
arglist = ['--spec-file', self.spec_path]
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.mock_client.validate_policy.assert_called_with(**self.defaults)

View File

@@ -14,7 +14,7 @@ import mock
from openstack.cluster.v1 import policy_type as sdk_policy_type
from openstack import exceptions as sdk_exc
from openstackclient.common import exceptions as exc
from osc_lib import exceptions as exc
from senlinclient.tests.unit.v1 import fakes
from senlinclient.v1 import policy_type as osc_policy_type
@@ -29,15 +29,9 @@ class TestPolicyType(fakes.TestClusteringv1):
class TestPolicyTypeList(TestPolicyType):
expected_columns = ['name']
list_response = [
sdk_policy_type.PolicyType({'name': 'BBB',
'schema': {
'foo': 'bar'}}),
sdk_policy_type.PolicyType({'name': 'AAA',
'schema': {
'foo': 'bar'}}),
sdk_policy_type.PolicyType({'name': 'CCC',
'schema': {
'foo': 'bar'}}),
sdk_policy_type.PolicyType(name='BBB', schema={'foo': 'bar'}),
sdk_policy_type.PolicyType(name='AAA', schema={'foo': 'bar'}),
sdk_policy_type.PolicyType(name='CCC', schema={'foo': 'bar'}),
]
expected_rows = [
['AAA'],
@@ -71,7 +65,7 @@ class TestPolicyTypeShow(TestPolicyType):
super(TestPolicyTypeShow, self).setUp()
self.cmd = osc_policy_type.PolicyTypeShow(self.app, None)
self.mock_client.get_policy_type = mock.Mock(
return_value=sdk_policy_type.PolicyType(self.response)
return_value=sdk_policy_type.PolicyType(**self.response)
)
def test_policy_type_show(self):

View File

@@ -16,8 +16,8 @@ import six
from openstack.cluster.v1 import profile as sdk_profile
from openstack import exceptions as sdk_exc
from openstackclient.common import exceptions as exc
from openstackclient.common import utils
from osc_lib import exceptions as exc
from osc_lib import utils
from senlinclient.tests.unit.v1 import fakes
from senlinclient.v1 import profile as osc_profile
@@ -30,7 +30,7 @@ class TestProfile(fakes.TestClusteringv1):
class TestProfileShow(TestProfile):
get_response = {"profile": {
response = {"profile": {
"created_at": "2015-03-01T14:28:25",
"domain": 'false',
"id": "7fa885cd-fa39-4531-a42d-780af95c84a4",
@@ -92,8 +92,7 @@ class TestProfileShow(TestProfile):
super(TestProfileShow, self).setUp()
self.cmd = osc_profile.ShowProfile(self.app, None)
self.mock_client.get_profile = mock.Mock(
return_value=sdk_profile.Profile(
attrs=self.get_response['profile']))
return_value=sdk_profile.Profile(**self.response['profile']))
utils.get_dict_properties = mock.Mock(return_value='')
def test_profile_show(self):
@@ -305,13 +304,15 @@ class TestProfileCreate(TestProfile):
"updated_at": None,
"user": "2d7aca950f3e465d8ef0c81720faf6ff"}}
defaults = {"spec": {
"version": 1.0,
"type": "os.nova.server",
"properties": {
"flavor": 1,
"name": "cirros_server",
"image": "cirros-0.3.4-x86_64-uec"}
defaults = {
"spec": {
"version": 1.0,
"type": "os.nova.server",
"properties": {
"flavor": 1,
"name": "cirros_server",
"image": "cirros-0.3.4-x86_64-uec"
},
},
"name": "my_profile",
"metadata": {}
@@ -321,9 +322,9 @@ class TestProfileCreate(TestProfile):
super(TestProfileCreate, self).setUp()
self.cmd = osc_profile.CreateProfile(self.app, None)
self.mock_client.create_profile = mock.Mock(
return_value=sdk_profile.Profile(attrs=self.response['profile']))
return_value=sdk_profile.Profile(**self.response['profile']))
self.mock_client.get_profile = mock.Mock(
return_value=sdk_profile.Profile(attrs=self.response['profile']))
return_value=sdk_profile.Profile(**self.response['profile']))
utils.get_dict_properties = mock.Mock(return_value='')
def test_profile_create_defaults(self):
@@ -376,11 +377,11 @@ class TestProfileUpdate(TestProfile):
super(TestProfileUpdate, self).setUp()
self.cmd = osc_profile.UpdateProfile(self.app, None)
self.mock_client.update_profile = mock.Mock(
return_value=sdk_profile.Profile(attrs=self.response['profile']))
return_value=sdk_profile.Profile(**self.response['profile']))
self.mock_client.get_profile = mock.Mock(
return_value=sdk_profile.Profile(attrs=self.response['profile']))
return_value=sdk_profile.Profile(**self.response['profile']))
self.mock_client.find_profile = mock.Mock(
return_value=sdk_profile.Profile(attrs=self.response['profile']))
return_value=sdk_profile.Profile(**self.response['profile']))
utils.get_dict_properties = mock.Mock(return_value='')
def test_profile_update_defaults(self):
@@ -401,3 +402,49 @@ class TestProfileUpdate(TestProfile):
self.cmd.take_action,
parsed_args)
self.assertIn('Profile not found: c6b8b252', str(error))
class TestProfileValidate(TestProfile):
spec_path = 'senlinclient/tests/test_specs/nova_server.yaml'
response = {"profile": {
"created_at": None,
"domain": None,
"id": None,
"metadata": None,
"name": "validated_profile",
"project": "5f1cc92b578e4e25a3b284179cf20a9b",
"spec": {"properties": {
"flavor": 1,
"image": "cirros-0.3.4-x86_64-uec",
"name": "cirros_server"},
"type": "os.nova.server",
"version": 1.0},
"type": "os.nova.server-1.0",
"updated_at": None,
"user": "2d7aca950f3e465d8ef0c81720faf6ff"}}
defaults = {
"spec": {
"version": 1.0,
"type": "os.nova.server",
"properties": {
"flavor": 1,
"name": "cirros_server",
"image": "cirros-0.3.4-x86_64-uec"
},
}
}
def setUp(self):
super(TestProfileValidate, self).setUp()
self.cmd = osc_profile.ValidateProfile(self.app, None)
self.mock_client.validate_profile = mock.Mock(
return_value=sdk_profile.Profile(**self.response['profile']))
utils.get_dict_properties = mock.Mock(return_value='')
def test_profile_validate(self):
arglist = ['--spec-file', self.spec_path]
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.mock_client.validate_profile.assert_called_with(**self.defaults)

View File

@@ -14,7 +14,7 @@ import mock
from openstack.cluster.v1 import profile_type as sdk_profile_type
from openstack import exceptions as sdk_exc
from openstackclient.common import exceptions as exc
from osc_lib import exceptions as exc
from senlinclient.tests.unit.v1 import fakes
from senlinclient.v1 import profile_type as osc_profile_type
@@ -29,18 +29,9 @@ class TestProfileType(fakes.TestClusteringv1):
class TestProfileTypeList(TestProfileType):
expected_columns = ['name']
list_response = [
sdk_profile_type.ProfileType({'name': 'BBB',
'schema': {
'foo': 'bar'}}
),
sdk_profile_type.ProfileType({'name': 'AAA',
'schema': {
'foo': 'bar'}}
),
sdk_profile_type.ProfileType({'name': 'CCC',
'schema': {
'foo': 'bar'}}
),
sdk_profile_type.ProfileType(name='BBB', schema={'foo': 'bar'}),
sdk_profile_type.ProfileType(name='AAA', schema={'foo': 'bar'}),
sdk_profile_type.ProfileType(name='CCC', schema={'foo': 'bar'}),
]
expected_rows = [
['AAA'],
@@ -74,7 +65,7 @@ class TestProfileTypeShow(TestProfileType):
super(TestProfileTypeShow, self).setUp()
self.cmd = osc_profile_type.ProfileTypeShow(self.app, None)
self.mock_client.get_profile_type = mock.Mock(
return_value=sdk_profile_type.ProfileType(self.response)
return_value=sdk_profile_type.ProfileType(**self.response)
)
def test_profile_type_show(self):

View File

@@ -16,8 +16,9 @@ import six
from openstack.cluster.v1 import receiver as sdk_receiver
from openstack import exceptions as sdk_exc
from openstackclient.common import exceptions as exc
from osc_lib import exceptions as exc
from senlinclient.common.i18n import _
from senlinclient.tests.unit.v1 import fakes
from senlinclient.v1 import receiver as osc_receiver
@@ -67,8 +68,7 @@ class TestReceiverList(TestReceiver):
def setUp(self):
super(TestReceiverList, self).setUp()
self.cmd = osc_receiver.ListReceiver(self.app, None)
self.mock_client.receivers = mock.Mock(
return_value=self.response)
self.mock_client.receivers = mock.Mock(return_value=self.response)
def test_receiver_list_defaults(self):
arglist = []
@@ -172,9 +172,8 @@ class TestReceiverShow(TestReceiver):
def setUp(self):
super(TestReceiverShow, self).setUp()
self.cmd = osc_receiver.ShowReceiver(self.app, None)
self.mock_client.get_receiver = mock.Mock(
return_value=sdk_receiver.Receiver(
attrs=self.get_response['receiver']))
x_receiver = sdk_receiver.Receiver(**self.get_response['receiver'])
self.mock_client.get_receiver = mock.Mock(return_value=x_receiver)
def test_receiver_show(self):
arglist = ['my_receiver']
@@ -231,13 +230,11 @@ class TestReceiverCreate(TestReceiver):
super(TestReceiverCreate, self).setUp()
self.cmd = osc_receiver.CreateReceiver(self.app, None)
self.mock_client.create_receiver = mock.Mock(
return_value=sdk_receiver.Receiver(
attrs=self.response['receiver']))
return_value=sdk_receiver.Receiver(**self.response['receiver']))
self.mock_client.get_receiver = mock.Mock(
return_value=sdk_receiver.Receiver(
attrs=self.response['receiver']))
return_value=sdk_receiver.Receiver(**self.response['receiver']))
def test_receiver_create(self):
def test_receiver_create_webhook(self):
arglist = ['my_receiver', '--action', 'CLUSTER_SCALE_OUT',
'--cluster', 'my_cluster', '--params', 'count=1',
'--type', 'webhook']
@@ -245,6 +242,26 @@ class TestReceiverCreate(TestReceiver):
self.cmd.take_action(parsed_args)
self.mock_client.create_receiver.assert_called_with(**self.args)
def test_receiver_create_webhook_failed(self):
arglist = ['my_receiver', '--action', 'CLUSTER_SCALE_OUT',
'--params', 'count=1', '--type', 'webhook']
parsed_args = self.check_parser(self.cmd, arglist, [])
error = self.assertRaises(exc.CommandError, self.cmd.take_action,
parsed_args)
self.assertIn(_('cluster and action parameters are required to create '
'webhook type of receiver'), str(error))
def test_receiver_create_non_webhook(self):
arglist = ['my_receiver', '--params', 'count=1',
'--type', 'foo']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
args = copy.deepcopy(self.args)
args['type'] = 'foo'
args['cluster_id'] = None
args['action'] = None
self.mock_client.create_receiver.assert_called_with(**args)
class TestReceiverDelete(TestReceiver):
def setUp(self):

View File

@@ -11,6 +11,8 @@
# under the License.
import copy
import subprocess
import mock
import six
import testtools
@@ -66,8 +68,9 @@ class ShellTest(testtools.TestCase):
'api': utils.json_formatter,
'engine': utils.json_formatter,
}
mock_print.assert_called_once_with(result, formatters=formatters)
self.assertTrue(service.get_build_info.called)
mock_print.assert_called_once_with(result.to_dict(),
formatters=formatters)
service.get_build_info.assert_called_once_with()
@mock.patch.object(utils, 'print_list')
def test_do_profile_type_list(self, mock_print):
@@ -297,6 +300,48 @@ class ShellTest(testtools.TestCase):
self.assertEqual(msg, six.text_type(ex))
service.delete_profile.assert_called_with('profile2', False)
@mock.patch.object(utils, 'process_stack_spec')
@mock.patch.object(utils, 'get_spec_content')
def test_do_profile_validate(self, mock_get, mock_proc):
args = self._make_args({'spec_file': mock.Mock()})
spec = copy.deepcopy(self.profile_spec)
mock_get.return_value = spec
params = {
'spec': spec,
}
service = mock.Mock()
profile = mock.Mock()
profile.to_dict.return_value = {}
service.validate_profile.return_value = profile
sh.do_profile_validate(service, args)
service.validate_profile.assert_called_once_with(**params)
# Miss 'type' key in spec file
del spec['type']
ex = self.assertRaises(exc.CommandError,
sh.do_profile_validate,
service, args)
self.assertEqual(_("Missing 'type' key in spec file."),
six.text_type(ex))
# Miss 'version' key in spec file
spec['type'] = 'os.heat.stack'
del spec['version']
ex = self.assertRaises(exc.CommandError,
sh.do_profile_validate,
service, args)
self.assertEqual(_("Missing 'version' key in spec file."),
six.text_type(ex))
# Miss 'properties' key in spec file
spec['version'] = 1.0
del spec['properties']
ex = self.assertRaises(exc.CommandError,
sh.do_profile_validate,
service, args)
self.assertEqual(_("Missing 'properties' key in spec file."),
six.text_type(ex))
@mock.patch.object(utils, 'print_list')
def test_do_policy_type_list(self, mock_print):
service = mock.Mock()
@@ -425,7 +470,7 @@ class ShellTest(testtools.TestCase):
receiver_id='receiver_id')
@mock.patch.object(sh, '_show_receiver')
def test_do_receiver_create(self, mock_show):
def test_do_receiver_create_webhook(self, mock_show):
service = mock.Mock()
args = {
'name': 'receiver1',
@@ -449,6 +494,47 @@ class ShellTest(testtools.TestCase):
service.create_receiver.assert_called_once_with(**params)
mock_show.assert_called_once_with(service, 'FAKE_ID')
def test_do_receiver_create_webhook_failed(self):
service = mock.Mock()
args = {
'name': 'receiver1',
'type': 'webhook',
'cluster': None,
'action': None,
'params': {}
}
args = self._make_args(args)
ex = self.assertRaises(exc.CommandError,
sh.do_receiver_create, service, args)
msg = _("cluster and action parameters are required to create webhook"
" type of receiver.")
self.assertEqual(msg, six.text_type(ex))
@mock.patch.object(sh, '_show_receiver')
def test_do_receiver_create_non_webhook(self, mock_show):
service = mock.Mock()
args = {
'name': 'receiver1',
'type': 'foo',
'cluster': None,
'action': None,
'params': {}
}
args = self._make_args(args)
params = {
'name': 'receiver1',
'type': 'foo',
'cluster_id': None,
'action': None,
'params': {}
}
receiver = mock.Mock()
receiver.id = 'FAKE_ID'
service.create_receiver.return_value = receiver
sh.do_receiver_create(service, args)
service.create_receiver.assert_called_once_with(**params)
mock_show.assert_called_once_with(service, 'FAKE_ID')
def test_do_receiver_delete(self):
service = mock.Mock()
args = {'id': ['FAKE']}
@@ -598,6 +684,25 @@ class ShellTest(testtools.TestCase):
msg = _("Failed to delete some of the specified policy(s).")
self.assertEqual(msg, six.text_type(ex))
@mock.patch.object(utils, 'get_spec_content')
def test_do_policy_validate(self, mock_get):
service = mock.Mock()
spec = mock.Mock()
mock_get.return_value = spec
args = {
'spec_file': 'policy_file',
}
args = self._make_args({'spec_file': 'policy_file'})
attrs = {
'spec': spec,
}
policy = mock.Mock()
policy.to_dict.return_value = {}
service.validate_policy.return_value = policy
sh.do_policy_validate(service, args)
mock_get.assert_called_once_with(args.spec_file)
service.validate_policy.assert_called_once_with(**attrs)
@mock.patch.object(utils, 'print_list')
def test_do_cluster_list(self, mock_print):
service = mock.Mock()
@@ -636,7 +741,7 @@ class ShellTest(testtools.TestCase):
service.get_cluster.return_value = cluster
formatters = {
'metadata': utils.json_formatter,
'nodes': utils.list_formatter,
'node_ids': utils.list_formatter,
}
cluster_dict = mock.Mock()
cluster.to_dict.return_value = cluster_dict
@@ -675,16 +780,174 @@ class ShellTest(testtools.TestCase):
sh.do_cluster_delete(service, args)
service.delete_cluster.assert_called_once_with('CID', False)
def test_do_cluster_delete_not_found(self):
service = mock.Mock()
args = {'id': ['cluster_id']}
args = self._make_args(args)
@mock.patch('subprocess.Popen')
def test__run_script(self, mock_proc):
x_proc = mock.Mock(returncode=0)
x_stdout = 'OUTPUT'
x_stderr = 'ERROR'
x_proc.communicate.return_value = (x_stdout, x_stderr)
mock_proc.return_value = x_proc
service.delete_cluster.side_effect = oexc.ResourceNotFound
ex = self.assertRaises(exc.CommandError,
sh.do_cluster_delete, service, args)
msg = _('Failed to delete some of the specified clusters.')
self.assertEqual(msg, six.text_type(ex))
addr = {
'private': [
{
'OS-EXT-IPS:type': 'floating',
'version': 4,
'addr': '1.2.3.4',
}
]
}
output = {}
sh._run_script('NODE_ID', addr, 'private', 'floating', 22, 'john',
False, 'identity_path', 'echo foo', '-f bar',
output=output)
mock_proc.assert_called_once_with(
['ssh', '-4', '-p22', '-i identity_path', '-f bar', 'john@1.2.3.4',
'echo foo'],
stdout=subprocess.PIPE)
self.assertEqual(
{'status': 'SUCCEEDED (0)', 'output': 'OUTPUT', 'error': 'ERROR'},
output)
def test__run_script_network_not_found(self):
addr = {'foo': 'bar'}
output = {}
sh._run_script('NODE_ID', addr, 'private', 'floating', 22, 'john',
False, 'identity_path', 'echo foo', '-f bar',
output=output)
self.assertEqual(
{'status': 'FAILED',
'reason': "Node 'NODE_ID' is not attached to network 'private'."
},
output)
def test__run_script_more_than_one_network(self):
addr = {'foo': 'bar', 'koo': 'tar'}
output = {}
sh._run_script('NODE_ID', addr, '', 'floating', 22, 'john',
False, 'identity_path', 'echo foo', '-f bar',
output=output)
self.assertEqual(
{'status': 'FAILED',
'reason': "Node 'NODE_ID' is attached to more than one "
"network. Please pick the network to use."},
output)
def test__run_script_no_network(self):
addr = {}
output = {}
sh._run_script('NODE_ID', addr, '', 'floating', 22, 'john',
False, 'identity_path', 'echo foo', '-f bar',
output=output)
self.assertEqual(
{'status': 'FAILED',
'reason': "Node 'NODE_ID' is not attached to any network."},
output)
def test__run_script_no_matching_address(self):
addr = {
'private': [
{
'OS-EXT-IPS:type': 'fixed',
'version': 4,
'addr': '1.2.3.4',
}
]
}
output = {}
sh._run_script('NODE_ID', addr, 'private', 'floating', 22, 'john',
False, 'identity_path', 'echo foo', '-f bar',
output=output)
self.assertEqual(
{'status': 'FAILED',
'reason': "No address that would match network 'private' and "
"type 'floating' of IPv4 has been found for node "
"'NODE_ID'."},
output)
def test__run_script_more_than_one_address(self):
addr = {
'private': [
{
'OS-EXT-IPS:type': 'fixed',
'version': 4,
'addr': '1.2.3.4',
},
{
'OS-EXT-IPS:type': 'fixed',
'version': 4,
'addr': '5.6.7.8',
},
]
}
output = {}
sh._run_script('NODE_ID', addr, 'private', 'fixed', 22, 'john',
False, 'identity_path', 'echo foo', '-f bar',
output=output)
self.assertEqual(
{'status': 'FAILED',
'reason': "More than one IPv4 fixed address found."},
output)
@mock.patch('threading.Thread')
@mock.patch.object(sh, '_run_script')
def test_do_cluster_run(self, mock_run, mock_thread):
service = mock.Mock()
args = {
'script': 'script_name',
'network': 'network_name',
'address_type': 'fixed',
'port': 22,
'user': 'root',
'ipv6': False,
'identity_file': 'identity_filename',
'ssh_options': '-f oo',
}
args = self._make_args(args)
args.id = 'CID'
addr1 = {'addresses': 'ADDR CONTENT 1'}
addr2 = {'addresses': 'ADDR CONTENT 2'}
attributes = [
mock.Mock(node_id='NODE1', attr_value=addr1),
mock.Mock(node_id='NODE2', attr_value=addr2)
]
service.collect_cluster_attrs.return_value = attributes
th1 = mock.Mock()
th2 = mock.Mock()
mock_thread.side_effect = [th1, th2]
fake_script = 'blah blah'
with mock.patch('senlinclient.v1.shell.open',
mock.mock_open(read_data=fake_script)) as mock_open:
sh.do_cluster_run(service, args)
service.collect_cluster_attrs.assert_called_once_with(
args.id, 'details')
mock_open.assert_called_once_with('script_name', 'r')
mock_thread.assert_has_calls([
mock.call(target=mock_run,
args=('NODE1', 'ADDR CONTENT 1', 'network_name',
'fixed', 22, 'root', False, 'identity_filename',
'blah blah', '-f oo'),
kwargs={'output': {}}),
mock.call(target=mock_run,
args=('NODE2', 'ADDR CONTENT 2', 'network_name',
'fixed', 22, 'root', False, 'identity_filename',
'blah blah', '-f oo'),
kwargs={'output': {}})
])
th1.start.assert_called_once_with()
th2.start.assert_called_once_with()
th1.join.assert_called_once_with()
th2.join.assert_called_once_with()
@mock.patch.object(sh, '_show_cluster')
def test_do_cluster_update(self, mock_show):
@@ -819,7 +1082,7 @@ class ShellTest(testtools.TestCase):
sh.do_cluster_resize,
service, args)
msg = _('Cluster capacity must be larger than '
' or equal to zero.')
'or equal to zero.')
self.assertEqual(msg, six.text_type(ex))
# adjustment
@@ -947,7 +1210,7 @@ class ShellTest(testtools.TestCase):
@mock.patch.object(utils, 'print_list')
def test_do_cluster_policy_list(self, mock_print):
fields = ['policy_id', 'policy_name', 'policy_type', 'enabled']
fields = ['policy_id', 'policy_name', 'policy_type', 'is_enabled']
service = mock.Mock()
args = {
'id': 'C1',
@@ -1056,6 +1319,65 @@ class ShellTest(testtools.TestCase):
service.recover_cluster.assert_called_once_with('cluster1')
def test_do_cluster_collect(self):
service = mock.Mock()
args = self._make_args({
'path': 'path.to.attr',
'list': False,
'full_id': False,
'id': 'cluster1'
})
service.collect_cluster_attrs = mock.Mock(
return_value=[mock.Mock(node_id='FAKE1', attr_value='VALUE1')]
)
sh.do_cluster_collect(service, args)
service.collect_cluster_attrs.assert_called_once_with(
'cluster1', 'path.to.attr')
@mock.patch.object(utils, 'print_list')
def test_do_cluster_collect_as_list(self, mock_print):
service = mock.Mock()
args = self._make_args({
'path': 'path.to.attr',
'list': True,
'full_id': True,
'id': 'cluster1'
})
attrs = [mock.Mock(node_id='FAKE1', attr_value='VALUE1')]
fields = ['node_id', 'attr_value']
formatters = {'attr_value': utils.json_formatter}
service.collect_cluster_attrs = mock.Mock(return_value=attrs)
sh.do_cluster_collect(service, args)
service.collect_cluster_attrs.assert_called_once_with(
'cluster1', 'path.to.attr')
mock_print.assert_called_once_with(attrs, fields,
formatters=formatters)
@mock.patch.object(utils, 'print_list')
def test_do_cluster_collect_as_list_with_shortid(self, mock_print):
service = mock.Mock()
args = self._make_args({
'path': 'path.to.attr',
'list': True,
'full_id': False,
'id': 'cluster1'
})
attrs = [mock.Mock(node_id='FAKE1', attr_value='VALUE1')]
fields = ['node_id', 'attr_value']
formatters = {'node_id': mock.ANY, 'attr_value': utils.json_formatter}
service.collect_cluster_attrs = mock.Mock(return_value=attrs)
sh.do_cluster_collect(service, args)
service.collect_cluster_attrs.assert_called_once_with(
'cluster1', 'path.to.attr')
mock_print.assert_called_once_with(attrs, fields,
formatters=formatters)
@mock.patch.object(utils, 'print_list')
def test_do_node_list(self, mock_print):
service = mock.Mock()
@@ -1104,7 +1426,7 @@ class ShellTest(testtools.TestCase):
sh._show_node(service, node_id, show_details=False)
service.get_node.assert_called_once_with(node_id, args=None)
service.get_node.assert_called_once_with(node_id, details=False)
mock_print.assert_called_once_with(data, formatters=formatters)
@mock.patch.object(sh, '_show_node')
@@ -1152,17 +1474,6 @@ class ShellTest(testtools.TestCase):
service.delete_node.assert_called_once_with('node1', False)
def test_do_node_delete_not_found(self):
service = mock.Mock()
ex = oexc.ResourceNotFound
service.delete_node.side_effect = ex
args = self._make_args({'id': ['node1']})
ex = self.assertRaises(exc.CommandError,
sh.do_node_delete, service, args)
msg = _('Failed to delete some of the specified nodes.')
self.assertEqual(msg, six.text_type(ex))
def test_do_node_check(self):
service = mock.Mock()
args = self._make_args({'id': ['node1']})
@@ -1232,7 +1543,7 @@ class ShellTest(testtools.TestCase):
def test_do_event_list(self, mock_print):
service = mock.Mock()
fields = ['id', 'timestamp', 'obj_type', 'obj_id', 'obj_name',
'action', 'status', 'status_reason', 'level']
'action', 'status', 'level', 'cluster_id']
args = {
'sort': 'timestamp:asc',
'limit': 20,

View File

@@ -13,19 +13,17 @@
"""Clustering v1 action implementations"""
import logging
import six
from cliff import lister
from cliff import show
from openstack import exceptions as sdk_exc
from openstackclient.common import exceptions as exc
from openstackclient.common import utils
from osc_lib.command import command
from osc_lib import exceptions as exc
from osc_lib import utils
from senlinclient.common.i18n import _
from senlinclient.common import utils as senlin_utils
class ListAction(lister.Lister):
class ListAction(command.Lister):
"""List actions."""
log = logging.getLogger(__name__ + ".ListAction")
@@ -104,12 +102,13 @@ class ListAction(lister.Lister):
return (
columns,
(utils.get_item_properties(a, columns, formatters=formatters)
(utils.get_item_properties(a.to_dict(), columns,
formatters=formatters)
for a in actions)
)
class ShowAction(show.ShowOne):
class ShowAction(command.ShowOne):
"""Show detailed info about the specified action."""
log = logging.getLogger(__name__ + ".ShowAction")
@@ -141,6 +140,7 @@ class ShowAction(show.ShowOne):
'depends_on': senlin_utils.list_formatter,
'depended_by': senlin_utils.list_formatter,
}
columns = sorted(list(six.iterkeys(action)))
return columns, utils.get_dict_properties(action.to_dict(), columns,
data = action.to_dict()
columns = sorted(data.keys())
return columns, utils.get_dict_properties(data, columns,
formatters=formatters)

View File

@@ -13,15 +13,14 @@
"""Clustering v1 build_info action implementations"""
import logging
import six
from cliff import show
from openstackclient.common import utils
from osc_lib.command import command
from osc_lib import utils
from senlinclient.common import utils as senlin_utils
class BuildInfo(show.ShowOne):
class BuildInfo(command.ShowOne):
"""Retrieve build information."""
log = logging.getLogger(__name__ + ".BuildInfo")
@@ -40,6 +39,10 @@ class BuildInfo(show.ShowOne):
'api': senlin_utils.json_formatter,
'engine': senlin_utils.json_formatter,
}
columns = sorted(list(six.iterkeys(result)))
return columns, utils.get_dict_properties(result.to_dict(), columns,
data = {
'api': result.api,
'engine': result.engine,
}
columns = ['api', 'engine']
return columns, utils.get_dict_properties(data, columns,
formatters=formatters)

View File

@@ -15,8 +15,9 @@ from senlinclient.common import sdk
class Client(object):
def __init__(self, preferences=None, user_agent=None, **kwargs):
self.conn = sdk.create_connection(preferences, user_agent, **kwargs)
def __init__(self, prof=None, user_agent=None, **kwargs):
self.conn = sdk.create_connection(prof=prof, user_agent=user_agent,
**kwargs)
self.service = self.conn.cluster
######################################################################
@@ -83,6 +84,14 @@ class Client(object):
"""
return self.service.delete_profile(profile, ignore_missing)
def validate_profile(self, **attrs):
"""Validate a profile spec
Doc link:
http://developer.openstack.org/api-ref-clustering-v1.html#validateProfile
"""
return self.service.validate_profile(**attrs)
def policy_types(self, **query):
"""List policy types
@@ -141,6 +150,14 @@ class Client(object):
"""
return self.service.delete_policy(policy, ignore_missing)
def validate_policy(self, **attrs):
"""validate a policy spec
Doc link:
http://developer.openstack.org/api-ref-clustering-v1.html#validatePolicy
"""
return self.service.validate_policy(**attrs)
def clusters(self, **queries):
"""List clusters
@@ -263,6 +280,14 @@ class Client(object):
"""
return self.service.cluster_update_policy(cluster, policy, **attrs)
def cluster_collect(self, cluster, path):
"""Resize cluster
Doc link:
http://developer.openstack.org/api-ref-clustering-v1.html#clusterAction
"""
return self.service.cluster_collect(cluster, path)
def check_cluster(self, cluster, **params):
"""Check cluster's health status
@@ -295,13 +320,13 @@ class Client(object):
"""
return self.service.create_node(**attrs)
def get_node(self, node, args=None):
def get_node(self, node, details=False):
"""Show node details
Doc link:
http://developer.openstack.org/api-ref-clustering-v1.html#showNode
"""
return self.service.get_node(node, args=args)
return self.service.get_node(node, details=details)
def update_node(self, node, **attrs):
"""Update node

View File

@@ -13,22 +13,23 @@
"""Clustering v1 cluster action implementations"""
import logging
import six
import subprocess
import sys
import threading
import time
from cliff import command
from cliff import lister
from cliff import show
from openstack import exceptions as sdk_exc
from openstackclient.common import exceptions as exc
from openstackclient.common import utils
from osc_lib.command import command
from osc_lib import exceptions as exc
from osc_lib import utils
import six
from senlinclient.common.i18n import _
from senlinclient.common.i18n import _LI
from senlinclient.common import utils as senlin_utils
class ListCluster(lister.Lister):
class ListCluster(command.Lister):
"""List the user's clusters."""
log = logging.getLogger(__name__ + ".ListCluster")
@@ -106,7 +107,7 @@ class ListCluster(lister.Lister):
)
class ShowCluster(show.ShowOne):
class ShowCluster(command.ShowOne):
"""Show details of the cluster."""
log = logging.getLogger(__name__ + ".ShowCluster")
@@ -135,26 +136,21 @@ def _show_cluster(senlin_client, cluster_id):
formatters = {
'metadata': senlin_utils.json_formatter,
'nodes': senlin_utils.list_formatter
'node_ids': senlin_utils.list_formatter
}
columns = sorted(list(six.iterkeys(cluster)))
return columns, utils.get_dict_properties(cluster.to_dict(), columns,
data = cluster.to_dict()
columns = sorted(data.keys())
return columns, utils.get_dict_properties(data, columns,
formatters=formatters)
class CreateCluster(show.ShowOne):
class CreateCluster(command.ShowOne):
"""Create the cluster."""
log = logging.getLogger(__name__ + ".CreateCluster")
def get_parser(self, prog_name):
parser = super(CreateCluster, self).get_parser(prog_name)
parser.add_argument(
'--profile',
metavar='<profile>',
required=True,
help=_('Profile Id used for this cluster')
)
parser.add_argument(
'--min-size',
metavar='<min-size>',
@@ -188,6 +184,12 @@ class CreateCluster(show.ShowOne):
'key-value pairs separated by a semicolon.'),
action='append'
)
parser.add_argument(
'--profile',
metavar='<profile>',
required=True,
help=_('Profile Id used for this cluster')
)
parser.add_argument(
'name',
metavar='<cluster-name>',
@@ -215,7 +217,7 @@ class CreateCluster(show.ShowOne):
return _show_cluster(senlin_client, cluster.id)
class UpdateCluster(show.ShowOne):
class UpdateCluster(command.ShowOne):
"""Update the cluster."""
log = logging.getLogger(__name__ + ".UpdateCluster")
@@ -309,20 +311,16 @@ class DeleteCluster(command.Command):
self.log.info(_LI('Ctrl-d detected'))
return
failure_count = 0
result = {}
for cid in parsed_args.cluster:
try:
senlin_client.delete_cluster(cid, False)
cluster = senlin_client.delete_cluster(cid, False)
result[cid] = ('OK', cluster.location.split('/')[-1])
except Exception as ex:
failure_count += 1
print(ex)
if failure_count:
raise exc.CommandError(_('Failed to delete %(count)s of the '
'%(total)s specified cluster(s).') %
{'count': failure_count,
'total': len(parsed_args.cluster)})
print('Request accepted')
result[cid] = ('ERROR', six.text_type(ex))
for rid, res in result.items():
senlin_utils.print_action_result(rid, res)
class ResizeCluster(command.Command):
@@ -405,11 +403,6 @@ class ResizeCluster(command.Command):
raise exc.CommandError(_("Only one of 'capacity', 'adjustment' and"
" 'percentage' can be specified."))
if sum(v is None for v in (capacity, adjustment, percentage)) == 3:
raise exc.CommandError(_("At least one of 'capacity', "
"'adjustment' and 'percentage' "
"should be specified."))
action_args['adjustment_type'] = None
action_args['number'] = None
@@ -527,12 +520,6 @@ class ClusterPolicyAttach(command.Command):
def get_parser(self, prog_name):
parser = super(ClusterPolicyAttach, self).get_parser(prog_name)
parser.add_argument(
'--policy',
metavar='<policy>',
required=True,
help=_('ID or name of policy to be attached')
)
parser.add_argument(
'--enabled',
default=True,
@@ -540,6 +527,12 @@ class ClusterPolicyAttach(command.Command):
help=_('Whether the policy should be enabled once attached. '
'Default to True')
)
parser.add_argument(
'--policy',
metavar='<policy>',
required=True,
help=_('ID or name of policy to be attached')
)
parser.add_argument(
'cluster',
metavar='<cluster>',
@@ -589,7 +582,7 @@ class ClusterPolicyDetach(command.Command):
print('Request accepted by action: %s' % resp['action'])
class ClusterNodeList(lister.Lister):
class ClusterNodeList(command.Lister):
"""List nodes from cluster."""
log = logging.getLogger(__name__ + ".ClusterNodeList")
@@ -772,3 +765,246 @@ class RecoverCluster(command.Command):
print('Cluster recover request on cluster %(cid)s is accepted by '
'action %(action)s.'
% {'cid': cid, 'action': resp['action']})
class ClusterCollect(command.Lister):
"""Recover the cluster(s)."""
log = logging.getLogger(__name__ + ".ClusterCollect")
def get_parser(self, prog_name):
parser = super(ClusterCollect, self).get_parser(prog_name)
parser.add_argument(
'--full-id',
default=False,
action="store_true",
help=_('Print full IDs in list')
)
parser.add_argument(
'--path',
metavar='<path>',
required=True,
help=_('JSON path expression for attribute to be collected')
)
parser.add_argument(
'cluster',
metavar='<cluster>',
help=_('ID or name of cluster(s) to operate on.')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
senlin_client = self.app.client_manager.clustering
attrs = senlin_client.collect_cluster_attrs(parsed_args.cluster,
parsed_args.path)
columns = ['node_id', 'attr_value']
formatters = {}
if not parsed_args.full_id:
formatters = {
'node_id': lambda x: x[:8]
}
return (columns,
(utils.get_item_properties(a, columns, formatters=formatters)
for a in attrs))
class ClusterRun(command.Command):
"""Run scripts on cluster."""
log = logging.getLogger(__name__ + ".ClusterRun")
def get_parser(self, prog_name):
parser = super(ClusterRun, self).get_parser(prog_name)
parser.add_argument(
'--port',
metavar='<port>',
type=int,
default=22,
help=_('The TCP port to use for SSH connection')
)
parser.add_argument(
'--address-type',
metavar='<address_type>',
default='floating',
help=_("The type of IP address to use. Possible values include "
"'fixed' and 'floating' (the default)")
)
parser.add_argument(
'--network',
metavar='<network>',
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='<user>',
default='root',
help=_("The login name to use for SSH connection. Default to "
"'root'.")
)
parser.add_argument(
'--identity-file',
metavar='<identity_file>',
help=_("The private key file to use, same as the '-i' SSH option")
)
parser.add_argument(
'--ssh-options',
metavar='<ssh_options>',
default="",
help=_("Extra options to pass to SSH. See: man ssh.")
)
parser.add_argument(
'--script',
metavar='<script>',
required=True,
help=_("Path name of the script file to run")
)
parser.add_argument(
'cluster',
metavar='<cluster>',
help=_('ID or name of cluster(s) to operate on.')
)
return parser
def take_action(self, args):
self.log.debug("take_action(%s)", args)
service = self.app.client_manager.clustering
if '@' in args.cluster:
user, cluster = args.cluster.split('@', 1)
args.user = user
args.cluster = cluster
try:
attributes = service.collect_cluster_attrs(args.cluster, 'details')
except sdk_exc.ResourceNotFound:
raise exc.CommandError(_("Cluster not found: %s") % args.cluster)
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=self._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'))
def _run_script(self, 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['error'] = _("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['error'] = _("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['error'] = _("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['error'] = _("No address that matches 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['error'] = _("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
]
self.log.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

View File

@@ -13,18 +13,15 @@
"""Clustering v1 cluster policy action implementations"""
import logging
import six
from cliff import command
from cliff import lister
from cliff import show
from openstackclient.common import utils
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")
@@ -66,7 +63,7 @@ class ClusterPolicyList(lister.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,
@@ -83,12 +80,13 @@ class ClusterPolicyList(lister.Lister):
}
return (
columns,
(utils.get_item_properties(p, columns, formatters=formatters)
(utils.get_item_properties(p, columns,
formatters=formatters)
for p in policies)
)
class ClusterPolicyShow(show.ShowOne):
class ClusterPolicyShow(command.ShowOne):
"""Show a specific policy that is bound to the specified cluster."""
log = logging.getLogger(__name__ + ".ClusterPolicyShow")
@@ -113,8 +111,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(data.keys())
return columns, utils.get_dict_properties(data, columns)
class ClusterPolicyUpdate(command.Command):

View File

@@ -13,19 +13,17 @@
"""Clustering v1 event action implementations"""
import logging
import six
from cliff import lister
from cliff import show
from openstack import exceptions as sdk_exc
from openstackclient.common import exceptions as exc
from openstackclient.common import utils
from osc_lib.command import command
from osc_lib import exceptions as exc
from osc_lib import utils
from senlinclient.common.i18n import _
from senlinclient.common import utils as senlin_utils
class ListEvent(lister.Lister):
class ListEvent(command.Lister):
"""List events."""
log = logging.getLogger(__name__ + ".ListEvent")
@@ -99,14 +97,13 @@ 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):
class ShowEvent(command.ShowOne):
"""Describe the event."""
log = logging.getLogger(__name__ + ".ShowEvent")
@@ -129,5 +126,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(data.keys())
return columns, utils.get_dict_properties(data, columns)

View File

@@ -13,22 +13,20 @@
"""Clustering v1 node action implementations"""
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 openstackclient.common import exceptions as exc
from openstackclient.common import utils
from osc_lib.command import command
from osc_lib import exceptions as exc
from osc_lib import utils
import six
from senlinclient.common.i18n import _
from senlinclient.common.i18n import _LI
from senlinclient.common import utils as senlin_utils
class 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")
@@ -148,9 +146,8 @@ class ShowNode(show.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)
@@ -158,28 +155,22 @@ 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 and data['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(data.keys())
return columns, utils.get_dict_properties(data, columns,
formatters=formatters)
class CreateNode(show.ShowOne):
class CreateNode(command.ShowOne):
"""Create the node."""
log = logging.getLogger(__name__ + ".CreateNode")
def get_parser(self, prog_name):
parser = super(CreateNode, self).get_parser(prog_name)
parser.add_argument(
'--profile',
metavar='<profile>',
required=True,
help=_('Profile Id or Name used for this node')
)
parser.add_argument(
'--cluster',
metavar='<cluster>',
@@ -198,6 +189,12 @@ class CreateNode(show.ShowOne):
'key-value pairs separated by a semicolon'),
action='append'
)
parser.add_argument(
'--profile',
metavar='<profile>',
required=True,
help=_('Profile Id or Name used for this node')
)
parser.add_argument(
'name',
metavar='<node-name>',
@@ -221,7 +218,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")
@@ -318,20 +315,16 @@ class DeleteNode(command.Command):
self.log.info(_LI('Ctrl-d detected'))
return
failure_count = 0
result = {}
for nid in parsed_args.node:
try:
senlin_client.delete_node(nid, False)
node = senlin_client.delete_node(nid, False)
result[nid] = ('OK', node.location.split('/')[-1])
except Exception as ex:
failure_count += 1
print(ex)
if failure_count:
raise exc.CommandError(_('Failed to delete %(count)s of the '
'%(total)s specified node(s).') %
{'count': failure_count,
'total': len(parsed_args.node)})
print('Request accepted')
result[nid] = ('ERROR', six.text_type(ex))
for rid, res in result.items():
senlin_utils.print_action_result(rid, res)
class CheckNode(command.Command):

View File

@@ -15,19 +15,17 @@
import logging
import sys
from cliff import command
from cliff import lister
from cliff import show
from openstack import exceptions as sdk_exc
from openstackclient.common import exceptions as exc
from openstackclient.common import utils
from osc_lib.command import command
from osc_lib import exceptions as exc
from osc_lib import utils
from senlinclient.common.i18n import _
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")
@@ -271,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='<spec-file>',
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)

View File

@@ -13,16 +13,16 @@
"""Clustering v1 policy type action implementations"""
import logging
import six
from cliff import lister
from openstack import exceptions as sdk_exc
from openstackclient.common import exceptions as 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")
@@ -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 = data.values()
columns = data.keys()
return columns, rows

View File

@@ -15,19 +15,17 @@
import logging
import sys
from cliff import command
from cliff import lister
from cliff import show
from openstack import exceptions as sdk_exc
from openstackclient.common import exceptions as exc
from openstackclient.common import utils
from osc_lib.command import command
from osc_lib import exceptions as exc
from osc_lib import utils
from senlinclient.common.i18n import _
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,19 +205,13 @@ 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")
def get_parser(self, prog_name):
parser = super(CreateProfile, self).get_parser(prog_name)
parser.add_argument(
'--spec-file',
metavar='<spec-file>',
required=True,
help=_('The spec file used to create the profile')
)
parser.add_argument(
'--metadata',
metavar='<key1=value1;key2=value2...>',
@@ -228,6 +220,12 @@ class CreateProfile(show.ShowOne):
'key-value pairs separated by a semicolon'),
action='append'
)
parser.add_argument(
'--spec-file',
metavar='<spec-file>',
required=True,
help=_('The spec file used to create the profile')
)
parser.add_argument(
'name',
metavar='<profile-name>',
@@ -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")
@@ -309,3 +307,65 @@ class UpdateProfile(show.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='<spec-file>',
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)

View File

@@ -13,16 +13,15 @@
"""Clustering v1 profile type action implementations"""
import logging
import six
from cliff import lister
from openstack import exceptions as sdk_exc
from openstackclient.common import exceptions as 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")
@@ -63,6 +62,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 = data.values()
columns = data.keys()
return columns, rows

View File

@@ -13,22 +13,19 @@
"""Clustering v1 receiver action implementations"""
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 openstackclient.common import exceptions as exc
from openstackclient.common import utils
from osc_lib.command import command
from osc_lib import exceptions as exc
from osc_lib import utils
from senlinclient.common.i18n import _
from senlinclient.common.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 +107,7 @@ class ListReceiver(lister.Lister):
)
class ShowReceiver(show.ShowOne):
class ShowReceiver(command.ShowOne):
"""Show the receiver details."""
log = logging.getLogger(__name__ + ".ShowReceiver")
@@ -142,12 +139,13 @@ 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(data.keys())
return columns, utils.get_dict_properties(data, columns,
formatters=formatters)
class CreateReceiver(show.ShowOne):
class CreateReceiver(command.ShowOne):
"""Create a receiver."""
log = logging.getLogger(__name__ + ".CreateReceiver")
@@ -160,18 +158,6 @@ class CreateReceiver(show.ShowOne):
default='webhook',
help=_('Type of the receiver to create')
)
parser.add_argument(
'--cluster',
metavar='<cluster>',
required=True,
help=_('Targeted cluster for this receiver')
)
parser.add_argument(
'--action',
metavar='<action>',
required=True,
help=_('Name or ID of the targeted action to be triggered')
)
parser.add_argument(
'--params',
metavar='<key1=value1;key2=value2...>',
@@ -179,6 +165,18 @@ class CreateReceiver(show.ShowOne):
'action when the receiver is triggered'),
action='append'
)
parser.add_argument(
'--cluster',
metavar='<cluster>',
help=_('Targeted cluster for this receiver. Required if '
'receiver type is webhook')
)
parser.add_argument(
'--action',
metavar='<action>',
help=_('Name or ID of the targeted action to be triggered. '
'Required if receiver type is webhook')
)
parser.add_argument(
'name',
metavar='<name>',
@@ -188,6 +186,11 @@ class CreateReceiver(show.ShowOne):
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
if parsed_args.type == 'webhook':
if (not parsed_args.cluster or not parsed_args.action):
msg = _('cluster and action parameters are required to create '
'webhook type of receiver.')
raise exc.CommandError(msg)
senlin_client = self.app.client_manager.clustering
params = {

View File

@@ -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,10 @@ 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 and will be removed by Apr 2017, '
'please use "%(new)s" instead.'),
{'old': deprecated, 'new': recommended})
def do_build_info(service, args=None):
@@ -36,7 +40,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,
@@ -241,6 +245,44 @@ def do_profile_delete(service, args):
print('Profile deleted: %s' % args.id)
@utils.arg('-s', '--spec-file', metavar='<SPEC FILE>', 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
@@ -393,6 +435,24 @@ def do_policy_delete(service, args):
print('Policy deleted: %s' % args.id)
@utils.arg('-s', '--spec-file', metavar='<SPEC_FILE>', 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
@@ -449,7 +509,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)
@@ -491,23 +551,201 @@ def do_cluster_create(service, args):
_show_cluster(service, cluster.id)
@utils.arg('-p', '--path', metavar='<PATH>',
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='<CLUSTER>',
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.collect_cluster_attrs(args.id, args.path)
if args.list:
fields = ['node_id', 'attr_value']
formatters = {
'attr_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.attr_value)
@utils.arg('id', metavar='<CLUSTER>', nargs='+',
help=_('Name or ID of cluster(s) to delete.'))
def do_cluster_delete(service, args):
"""Delete the cluster(s)."""
show_deprecated('senlin cluster-delete', 'openstack cluster delete')
failure_count = 0
result = {}
for cid in args.id:
try:
service.delete_cluster(cid, False)
cluster = service.delete_cluster(cid, False)
result[cid] = ('OK', cluster.location.split('/')[-1])
except Exception as ex:
failure_count += 1
print(ex)
if failure_count > 0:
msg = _('Failed to delete some of the specified clusters.')
raise exc.CommandError(msg)
print('Request accepted')
result[cid] = ('ERROR', six.text_type(ex))
for rid, res in result.items():
utils.print_action_result(rid, res)
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="<PORT>", 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='<NETWORK>', 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="<USER>", 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="<FILE>", required=True,
help=_("Script file to run."))
@utils.arg("id", metavar="<CLUSTER>",
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='<PROFILE>',
@@ -624,7 +862,7 @@ def do_cluster_node_del(service, args):
@utils.arg('-t', '--min-step', metavar='<MIN_STEP>', type=int,
help=_('An integer specifying the number of nodes for adjustment '
'when <PERCENTAGE> 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.'))
@@ -659,7 +897,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
@@ -745,7 +983,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 = {
@@ -910,9 +1148,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)
@@ -921,9 +1158,9 @@ 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(node['details'].keys()), ['property', 'value'])
list(data['details'].keys()), ['property', 'value'])
utils.print_dict(data, formatters=formatters)
@@ -971,18 +1208,17 @@ def do_node_show(service, args):
def do_node_delete(service, args):
"""Delete the node(s)."""
show_deprecated('senlin node-delete', 'openstack cluster node delete')
failure_count = 0
result = {}
for nid in args.id:
try:
service.delete_node(nid, False)
node = service.delete_node(nid, False)
result[nid] = ('OK', node.location.split('/')[-1])
except Exception as ex:
failure_count += 1
print(ex)
if failure_count > 0:
msg = _('Failed to delete some of the specified nodes.')
raise exc.CommandError(msg)
print('Request accepted')
result[nid] = ('ERROR', six.text_type(ex))
for rid, res in result.items():
utils.print_action_result(rid, res)
@utils.arg('-n', '--name', metavar='<NAME>',
@@ -1130,10 +1366,12 @@ def do_receiver_show(service, args):
@utils.arg('-t', '--type', metavar='<TYPE>', default='webhook',
help=_('Type of the receiver to create.'))
@utils.arg('-c', '--cluster', metavar='<CLUSTER>', required=True,
help=_('Targeted cluster for this receiver.'))
@utils.arg('-a', '--action', metavar='<ACTION>', required=True,
help=_('Name or ID of the targeted action to be triggered.'))
@utils.arg('-c', '--cluster', metavar='<CLUSTER>',
help=_('Targeted cluster for this receiver. Required if receiver '
'type is webhook.'))
@utils.arg('-a', '--action', metavar='<ACTION>',
help=_('Name or ID of the targeted action to be triggered. '
'Required if receiver type is webhook.'))
@utils.arg('-P', '--params', metavar='<KEY1=VALUE1;KEY2=VALUE2...>',
help=_('A dictionary of parameters that will be passed to target '
'action when the receiver is triggered.'),
@@ -1145,6 +1383,12 @@ def do_receiver_create(service, args):
show_deprecated('senlin receiver-create',
'openstack cluster receiver create')
if args.type == 'webhook':
if (not args.cluster or not args.action):
msg = _('cluster and action parameters are required to create '
'webhook type of receiver.')
raise exc.CommandError(msg)
params = {
'name': args.name,
'type': args.type,
@@ -1203,7 +1447,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 +1462,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 +1528,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

View File

@@ -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 =
@@ -58,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
@@ -68,6 +70,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
@@ -78,6 +81,8 @@ 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
cluster_run = senlinclient.v1.cluster:ClusterRun
[global]
setup-hooks =

View File

@@ -5,15 +5,13 @@
# 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>=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

View File

@@ -1,5 +1,5 @@
[tox]
envlist = py34,py27,pypy,pep8,releasenotes
envlist = py35,py34,py27,pypy,pep8,releasenotes
minversion = 1.6
skipsdist = True
@@ -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]
@@ -35,8 +35,9 @@ 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
exclude=.venv,.git,.tox,dist,*lib/python*,*egg,build
max-complexity=20
[hacking]