Merge "Support node-adopt/preview CLI"
This commit is contained in:
commit
5776a9c5c3
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -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"}
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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>',
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user