Merge "Support node-adopt/preview CLI"

This commit is contained in:
Jenkins 2017-09-06 06:01:43 +00:00 committed by Gerrit Code Review
commit 5776a9c5c3
9 changed files with 412 additions and 2 deletions

View File

@ -27,7 +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.5')
prof.set_api_version('clustering', '1.7')
try:
conn = connection.Connection(profile=prof, user_agent=user_agent,
**kwargs)

View File

@ -225,6 +225,22 @@ def format_parameters(params, parse_semicolon=True):
return parameters
def format_json_parameter(param):
'''Return JSON dict from JSON formatted param.
:parameter param JSON formatted string
:return JSON dict
'''
if not param:
return {}
try:
return jsonutils.loads(param)
except ValueError:
msg = _('Malformed parameter(%s). Use the JSON format.') % param
raise exc.CommandError(msg)
def get_spec_content(filename):
with open(filename, 'r') as f:
try:

View File

@ -23,7 +23,7 @@ LOG = logging.getLogger(__name__)
DEFAULT_CLUSTERING_API_VERSION = '1'
API_VERSION_OPTION = 'os_clustering_api_version'
API_NAME = 'clustering'
CURRENT_API_VERSION = '1.5'
CURRENT_API_VERSION = '1.7'
def make_client(instance):

View File

@ -458,6 +458,158 @@ class TestNodeRecover(TestNode):
self.assertIn('Node not found: node1', str(error))
class TestNodeAdopt(TestNode):
defaults = {
"identity": "fake-resource-id",
"metadata": {},
"name": "my_node",
"overrides": {},
"role": None,
"snapshot": False,
"type": "os.nova.server-1.0"
}
def setUp(self):
super(TestNodeAdopt, self).setUp()
self.cmd = osc_node.AdoptNode(self.app, None)
fake_node = mock.Mock(
action="2366d440-c73e-4961-9254-6d1c3af7c167",
cluster_id="",
created_at=None,
data={},
domain=None,
id="0df0931b-e251-4f2e-8719-4ebfda3627ba",
index=-1,
init_time="2015-03-05T08:53:15",
metadata={},
physical_id=None,
profile_id="edc63d0a-2ca4-48fa-9854-27926da76a4a",
profile_name="mystack",
project_id="6e18cc2bdbeb48a5b3cad2dc499f6804",
role="master",
status="INIT",
status_reason="Initializing",
updated_at=None,
user_id="5e5bf8027826429c96af157f68dc9072"
)
fake_node.name = "my_node"
fake_node.to_dict = mock.Mock(return_value={})
self.mock_client.adopt_node = mock.Mock(return_value=fake_node)
self.mock_client.get_node = mock.Mock(return_value=fake_node)
def test_node_adopt_defaults(self):
arglist = ['--identity', 'fake-resource-id',
'--type', 'os.nova.server-1.0',
'--name', 'my_node']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.mock_client.adopt_node.assert_called_with(False, **self.defaults)
def test_node_adopt_with_metadata(self):
arglist = ['--identity', 'fake-resource-id',
'--type', 'os.nova.server-1.0',
'--metadata', 'key1=value1;key2=value2',
'--name', 'my_node']
kwargs = copy.deepcopy(self.defaults)
kwargs['metadata'] = {'key1': 'value1', 'key2': 'value2'}
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.mock_client.adopt_node.assert_called_with(False, **kwargs)
def test_node_adopt_with_override(self):
arglist = ['--identity', 'fake-resource-id',
'--type', 'os.nova.server-1.0',
'--overrides',
'{"networks": [{"network": "fake-net-name"}]}',
'--name', 'my_node']
kwargs = copy.deepcopy(self.defaults)
kwargs['overrides'] = {'networks': [{'network': 'fake-net-name'}]}
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.mock_client.adopt_node.assert_called_with(False, **kwargs)
def test_node_adopt_with_role(self):
arglist = ['--identity', 'fake-resource-id',
'--type', 'os.nova.server-1.0',
'--role', 'master',
'--name', 'my_node']
kwargs = copy.deepcopy(self.defaults)
kwargs['role'] = 'master'
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.mock_client.adopt_node.assert_called_with(False, **kwargs)
def test_node_adopt_with_snapshot(self):
arglist = ['--identity', 'fake-resource-id',
'--type', 'os.nova.server-1.0',
'--snapshot',
'--name', 'my_node']
parsed_args = self.check_parser(self.cmd, arglist, [])
kwargs = copy.deepcopy(self.defaults)
kwargs['snapshot'] = True
self.cmd.take_action(parsed_args)
self.mock_client.adopt_node.assert_called_with(False, **kwargs)
class TestNodeAdoptPreview(TestNode):
defaults = {
"identity": "fake-resource-id",
"overrides": {},
"snapshot": False,
"type": "os.nova.server-1.0"
}
def setUp(self):
super(TestNodeAdoptPreview, self).setUp()
self.cmd = osc_node.AdoptNode(self.app, None)
self.fake_node_preview = {
"node_profile": {
"node_preview": {
"properties": {
},
"type": "os.nova.server",
"version": "1.0"}
}
}
self.mock_client.adopt_node = mock.Mock(
return_value=self.fake_node_preview)
self.mock_client.get_node = mock.Mock(
return_value=self.fake_node_preview)
def test_node_adopt_preview_default(self):
arglist = ['--identity', 'fake-resource-id',
'--type', 'os.nova.server-1.0',
'--preview']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.mock_client.adopt_node.assert_called_with(True, **self.defaults)
def test_node_adopt_preview_with_overrides(self):
arglist = ['--identity', 'fake-resource-id',
'--type', 'os.nova.server-1.0',
'--overrides',
'{"networks": [{"network": "fake-net-name"}]}',
'--preview']
parsed_args = self.check_parser(self.cmd, arglist, [])
kwargs = copy.deepcopy(self.defaults)
kwargs['overrides'] = {'networks': [{'network': 'fake-net-name'}]}
self.cmd.take_action(parsed_args)
self.mock_client.adopt_node.assert_called_with(True, **kwargs)
def test_node_adopt_preview_with_snapshot(self):
arglist = ['--identity', 'fake-resource-id',
'--type', 'os.nova.server-1.0',
'--snapshot', '--preview']
parsed_args = self.check_parser(self.cmd, arglist, [])
kwargs = copy.deepcopy(self.defaults)
kwargs['snapshot'] = True
self.cmd.take_action(parsed_args)
self.mock_client.adopt_node.assert_called_with(True, **kwargs)
class TestNodeOp(TestNode):
response = {"action": "1db0f5c5-9183-4c47-9ef1-a5a97402a2c1"}

View File

@ -1561,6 +1561,76 @@ class ShellTest(testtools.TestCase):
service.create_node.assert_called_once_with(**attrs)
mock_show.assert_called_once_with(service, 'node_id')
@mock.patch.object(sh, '_show_node')
def test_do_node_adopt(self, mock_show):
args = {
'identity': 'fake-resoruce-id',
'name': 'adopt-node1',
'role': 'master',
'metadata': ['user=demo'],
'snapshot': None,
'overrides': '{"networks": [{"network": "fake-net-name"}]}',
'type': 'os.nova.server-1.0',
'preview': False
}
args = self._make_args(args)
attrs = {
'identity': 'fake-resoruce-id',
'name': 'adopt-node1',
'role': 'master',
'metadata': {'user': 'demo'},
'overrides': {'networks': [{'network': 'fake-net-name'}]},
'snapshot': None,
'type': 'os.nova.server-1.0',
}
service = mock.Mock()
node = mock.Mock()
node.id = 'node_id'
service.adopt_node.return_value = node
sh.do_node_adopt(service, args)
service.adopt_node.assert_called_once_with(**attrs)
mock_show.assert_called_once_with(service, 'node_id')
@mock.patch.object(utils, 'print_dict')
@mock.patch.object(utils, 'nested_dict_formatter')
def test_do_node_adopt_preview(self, mock_nest, mock_print):
args = {
'identity': 'fake-resoruce-id',
'snapshot': None,
'overrides': '{"networks": [{"network": "fake-net-name"}]}',
'type': 'os.nova.server-1.0',
'preview': True
}
args = self._make_args(args)
attrs = {
'identity': 'fake-resoruce-id',
'overrides': {'networks': [{'network': 'fake-net-name'}]},
'snapshot': None,
'type': 'os.nova.server-1.0',
}
fake_preview = {
"node_profile": {
"node_preview": {
"properties": {
},
"type": "os.nova.server",
"version": "1.0"}
}
}
service = mock.Mock()
service.adopt_node.return_value = fake_preview
sh.do_node_adopt(service, args)
service.adopt_node.assert_called_once_with(True, **attrs)
formatters = {}
formatters['node_preview'] = utils.nested_dict_formatter(
['type', 'version', 'properties'],
['property', 'value'])
mock_print.assert_called_once_with(fake_preview['node_profile'],
formatters=formatters)
@mock.patch.object(sh, '_show_node')
def test_do_node_show(self, mock_show):
service = mock.Mock()

View File

@ -346,6 +346,15 @@ class Client(object):
"""
return self.service.create_node(**attrs)
def adopt_node(self, preview=False, **attrs):
"""Adopt a node
Doc link:
https://developer.openstack.org/api-ref/clustering/#adopt-node
https://developer.openstack.org/api-ref/clustering/#adopt-node-preview
"""
return self.service.adopt_node(preview, **attrs)
def get_node(self, node, details=False):
"""Show node details

View File

@ -163,6 +163,7 @@ def _show_node(senlin_client, node_id, show_details=False):
if show_details and data['details']:
formatters['details'] = senlin_utils.nested_dict_formatter(
list(data['details'].keys()), ['property', 'value'])
columns = sorted(data.keys())
return columns, utils.get_dict_properties(data, columns,
formatters=formatters)
@ -398,6 +399,102 @@ class RecoverNode(command.Command):
% {'nid': nid, 'action': resp['action']})
class AdoptNode(command.ShowOne):
"""Adopt (or preview) the node."""
log = logging.getLogger(__name__ + ".AdoptNode")
def get_parser(self, prog_name):
parser = super(AdoptNode, self).get_parser(prog_name)
parser.add_argument(
'--identity',
metavar='<identity>',
required=True,
help=_('Physical resource id.'))
parser.add_argument(
'--type',
metavar='<type>',
required=True,
help=_('The name of the profile type.')
)
parser.add_argument(
'--role',
metavar='<role>',
help=_('Role for this node in the specific cluster.')
)
parser.add_argument(
'--metadata',
metavar='<"key1=value1;key2=value2...">',
help=_('Metadata values to be attached to the node. '
'This can be specified multiple times, or once with '
'key-value pairs separated by a semicolon.'),
action='append'
)
parser.add_argument(
'--name',
metavar='<node-name>',
help=_('Name of the node to adopt.')
)
parser.add_argument(
'--overrides',
metavar='<json>',
help=_('JSON formatted specification for overriding this node '
'properties.')
)
parser.add_argument(
'--preview',
default=False,
help=_('Whether preview the node adopt request. If set, '
'only previewing this node and do not adopt.'),
action='store_true',
)
parser.add_argument(
'--snapshot',
default=False,
help=_('Whether a shapshot of the existing physical object '
'should be created before the object is adopted as '
'a node.'),
action='store_true'
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
senlin_client = self.app.client_manager.clustering
preview = True if parsed_args.preview else False
attrs = {
'identity': parsed_args.identity,
'overrides': senlin_utils.format_json_parameter(
parsed_args.overrides),
'snapshot': parsed_args.snapshot,
'type': parsed_args.type
}
if not preview:
attrs.update({
'name': parsed_args.name,
'role': parsed_args.role,
'metadata': senlin_utils.format_parameters(
parsed_args.metadata),
})
node = senlin_client.adopt_node(preview, **attrs)
if not preview:
return _show_node(senlin_client, node.id)
else:
formatters = {}
formatters['node_preview'] = senlin_utils.nested_dict_formatter(
['type', 'version', 'properties'],
['property', 'value'])
data = node['node_profile']
columns = sorted(data.keys())
return columns, utils.get_dict_properties(data, columns,
formatters=formatters)
class NodeOp(command.Lister):
"""Perform an operation on a node."""
log = logging.getLogger(__name__ + ".NodeOp")

View File

@ -1329,6 +1329,71 @@ def do_node_create(service, args):
_show_node(service, node.id)
@utils.arg('-i', '--identity', metavar='<IDENTITY>', required=True,
help=_('Physical resource id.'))
@utils.arg('-t', '--type', metavar='<TYPE>', required=True,
help=_('The name of the profile type.'))
@utils.arg('-r', '--role', metavar='<ROLE>',
help=_('Role for this node in the specific cluster.'))
@utils.arg('-M', '--metadata', metavar='<"KEY1=VALUE1;KEY2=VALUE2...">',
help=_('Metadata values to be attached to the node. '
'This can be specified multiple times, or once with '
'key-value pairs separated by a semicolon.'),
action='append')
@utils.arg('-n', '--name', metavar='<NAME>',
help=_('The name for the node.'))
@utils.arg('-o', '--overrides', metavar='<JSON">',
help=_('JSON formatted specification for overriding this node '
'properties.'))
@utils.arg('-p', '--preview', default=False,
help=_('Whether preview the node adopt request. If set, '
'only previewing this node and do not adopt.'),
action='store_true')
@utils.arg('-s', '--snapshot', default=False,
help=_('Whether a shapshot of the existing physical object should '
'be created before the object is adopted as a node.'),
action='store_true')
def do_node_adopt(service, args):
"""Adopt (or preview) a node."""
show_deprecated('senlin node-adopt', 'openstack cluster node adopt')
if args.preview:
_do_node_adopt_preview(service, args)
else:
_do_node_adopt(service, args)
def _do_node_adopt_preview(service, args):
attrs = {
'identity': args.identity,
'overrides': utils.format_json_parameter(args.overrides),
'snapshot': args.snapshot,
'type': args.type
}
node = service.adopt_node(True, **attrs)
formatters = {}
formatters['node_preview'] = utils.nested_dict_formatter(
['type', 'version', 'properties'],
['property', 'value'])
utils.print_dict(node['node_profile'], formatters=formatters)
def _do_node_adopt(service, args):
attrs = {
'identity': args.identity,
'name': args.name,
'role': args.role,
'metadata': utils.format_parameters(args.metadata),
'overrides': utils.format_json_parameter(args.overrides),
'snapshot': args.snapshot,
'type': args.type
}
node = service.adopt_node(**attrs)
_show_node(service, node.id)
@utils.arg('-D', '--details', default=False, action="store_true",
help=_('Include physical object details.'))
@utils.arg('id', metavar='<NODE>',

View File

@ -43,6 +43,7 @@ openstack.clustering.v1 =
cluster_members_add = senlinclient.v1.cluster:ClusterNodeAdd
cluster_members_del = senlinclient.v1.cluster:ClusterNodeDel
cluster_members_replace = senlinclient.v1.cluster:ClusterNodeReplace
cluster_node_adopt = senlinclient.v1.node:AdoptNode
cluster_node_check = senlinclient.v1.node:CheckNode
cluster_node_create = senlinclient.v1.node:CreateNode
cluster_node_delete = senlinclient.v1.node:DeleteNode