fuel2: add `node ansible-inventory` command
This command makes fuel2 client generate an ansible inventory file based on the list of cluster nodes. Nodes are grouped by roles, if a particular node has more than one role, it will appear in the inventory file several times. More details on the ansible inventory file format: http://docs.ansible.com/ansible/intro_inventory.html This could be useful when managing Fuel deployed clusters by the means of ansible. Change-Id: I2bcb5756ccbeae96f3614e1bda3caf7c8289973f
This commit is contained in:
parent
4635d4b375
commit
de0482ce15
|
@ -12,6 +12,8 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import collections
|
||||
import operator
|
||||
import os
|
||||
|
||||
import six
|
||||
|
@ -322,3 +324,49 @@ class NodeAttributesUpload(NodeMixIn, base.BaseCommand):
|
|||
self.app.stdout.write(
|
||||
"Attributes for node {0} were uploaded."
|
||||
.format(parsed_args.id) + os.linesep)
|
||||
|
||||
|
||||
class NodeAnsibleInventory(NodeMixIn, base.BaseCommand):
|
||||
"""Generate ansible inventory file based on the nodes list."""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(NodeAnsibleInventory, self).get_parser(prog_name)
|
||||
|
||||
# if this is a required argument, we'll avoid ambiguity of having nodes
|
||||
# of multiple different clusters in the same inventory file
|
||||
parser.add_argument(
|
||||
'-e',
|
||||
'--env',
|
||||
type=int,
|
||||
required=True,
|
||||
help='Use only nodes that are in the specified environment')
|
||||
|
||||
parser.add_argument(
|
||||
'-l',
|
||||
'--labels',
|
||||
type=utils.str_to_unicode,
|
||||
nargs='+',
|
||||
help='Use only nodes that have specific labels')
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
data = self.client.get_all(environment_id=parsed_args.env,
|
||||
labels=parsed_args.labels)
|
||||
|
||||
nodes_by_role = collections.defaultdict(list)
|
||||
for node in data:
|
||||
for role in node['roles']:
|
||||
nodes_by_role[role].append(node)
|
||||
|
||||
for role, nodes in sorted(nodes_by_role.items()):
|
||||
self.app.stdout.write(u'[{role}]\n'.format(role=role))
|
||||
self.app.stdout.write(
|
||||
u'\n'.join(
|
||||
u'{name} ansible_host={ip}'.format(name=node['hostname'],
|
||||
ip=node['ip'])
|
||||
for node in sorted(nodes_by_role[role],
|
||||
key=operator.itemgetter('hostname'))
|
||||
)
|
||||
)
|
||||
self.app.stdout.write(u'\n\n')
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import io
|
||||
|
||||
import mock
|
||||
import six
|
||||
|
||||
|
@ -76,6 +78,56 @@ class TestNodeCommand(test_engine.BaseCLITest):
|
|||
self.assertIsInstance(
|
||||
self.m_client.get_all.call_args[1].get('labels')[0], six.text_type)
|
||||
|
||||
def test_node_list_ansible_inventory(self):
|
||||
self.m_client.get_all.return_value = [
|
||||
fake_node.get_fake_node(hostname='node-1',
|
||||
ip='10.20.0.2',
|
||||
roles=['compute']),
|
||||
fake_node.get_fake_node(hostname='node-2',
|
||||
ip='10.20.0.3',
|
||||
roles=['compute', 'ceph-osd']),
|
||||
fake_node.get_fake_node(hostname='node-3',
|
||||
ip='10.20.0.4',
|
||||
roles=['controller']),
|
||||
fake_node.get_fake_node(hostname='node-4',
|
||||
ip='10.20.0.5',
|
||||
roles=['controller', 'mongo']),
|
||||
fake_node.get_fake_node(hostname='node-5',
|
||||
ip='10.20.0.6',
|
||||
roles=['controller', 'ceph-osd']),
|
||||
]
|
||||
|
||||
expected_output = '''\
|
||||
[ceph-osd]
|
||||
node-2 ansible_host=10.20.0.3
|
||||
node-5 ansible_host=10.20.0.6
|
||||
|
||||
[compute]
|
||||
node-1 ansible_host=10.20.0.2
|
||||
node-2 ansible_host=10.20.0.3
|
||||
|
||||
[controller]
|
||||
node-3 ansible_host=10.20.0.4
|
||||
node-4 ansible_host=10.20.0.5
|
||||
node-5 ansible_host=10.20.0.6
|
||||
|
||||
[mongo]
|
||||
node-4 ansible_host=10.20.0.5
|
||||
|
||||
'''
|
||||
|
||||
args = 'node ansible-inventory --env 1'
|
||||
with mock.patch('sys.stdout', new=io.StringIO()) as mstdout:
|
||||
rv = self.exec_command(args)
|
||||
actual_output = mstdout.getvalue()
|
||||
|
||||
self.assertFalse(rv)
|
||||
self.assertEqual(expected_output, actual_output)
|
||||
|
||||
self.m_get_client.assert_called_once_with('node', mock.ANY)
|
||||
self.m_client.get_all.assert_called_once_with(
|
||||
environment_id=1, labels=None)
|
||||
|
||||
def test_node_show(self):
|
||||
node_id = 42
|
||||
args = 'node show {node_id}'.format(node_id=node_id)
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
|
||||
def get_fake_node(cluster=None, hostname=None, node_id=None, cpu_model=None,
|
||||
roles=None, mac=None, memory_b=None, os_platform=None,
|
||||
status=None, node_name=None, group_id=None, labels=None):
|
||||
status=None, node_name=None, group_id=None, labels=None,
|
||||
ip=None):
|
||||
"""Creates a fake node
|
||||
|
||||
Returns the serialized and parametrized representation of a dumped Fuel
|
||||
|
@ -35,7 +36,7 @@ def get_fake_node(cluster=None, hostname=None, node_id=None, cpu_model=None,
|
|||
'error_type': None,
|
||||
'cluster': cluster or 1,
|
||||
'id': node_id or 42,
|
||||
'ip': '10.20.0.4',
|
||||
'ip': ip or '10.20.0.4',
|
||||
'kernel_params': None,
|
||||
'group_id': group_id or 1,
|
||||
'mac': mac or 'd6:11:3f:b0:f1:43',
|
||||
|
|
|
@ -56,6 +56,7 @@ fuelclient =
|
|||
node_list=fuelclient.commands.node:NodeList
|
||||
node_show=fuelclient.commands.node:NodeShow
|
||||
node_update=fuelclient.commands.node:NodeUpdate
|
||||
node_ansible-inventory=fuelclient.commands.node:NodeAnsibleInventory
|
||||
plugins_sync=fuelclient.commands.plugins:PluginsSync
|
||||
task_list=fuelclient.commands.task:TaskList
|
||||
task_show=fuelclient.commands.task:TaskShow
|
||||
|
|
Loading…
Reference in New Issue