mirror of
https://github.com/xcat2/confluent.git
synced 2026-06-18 09:30:50 +00:00
Implement ability for user to kick off confluent ansible runs
Add nodeapply -A and associated API. This permits orchestrating plays without touching the nodes directly by the user.
This commit is contained in:
@@ -36,6 +36,36 @@ import confluent.client as client
|
||||
import confluent.sortutil as sortutil
|
||||
devnull = None
|
||||
|
||||
def run_automation(noderange, category, c):
|
||||
automationbynode = {}
|
||||
for res in c.update('/noderange/{0}/deployment/remote_config/run'.format(noderange), {
|
||||
'category': category,
|
||||
}):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode |= res.get('errorcode', 1)
|
||||
if 'created' in res:
|
||||
nodename = res['created'].split('/')[2]
|
||||
automationbynode[nodename] = res['created']
|
||||
while automationbynode:
|
||||
for node in list(automationbynode):
|
||||
for res in c.read(automationbynode[node]):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode |= res.get('errorcode', 1)
|
||||
for result in res.get('results', []):
|
||||
sys.stdout.write('{0}: Task [{1}] {2}\n'.format(
|
||||
node, result['task_name'], result['state']))
|
||||
for warning in result.get('warnings', []):
|
||||
sys.stderr.write('{0}: [WARNING] {1}\n'.format(node, warning))
|
||||
if 'errorinfo' in result:
|
||||
for errorline in result['errorinfo'].splitlines():
|
||||
sys.stderr.write('{0}: [ERROR] {1}\n'.format(node, errorline))
|
||||
if res.get('complete', False):
|
||||
del automationbynode[node]
|
||||
sys.stdout.write('{0}: Automation complete\n'.format(node))
|
||||
|
||||
|
||||
def run():
|
||||
global devnull
|
||||
devnull = open(os.devnull, 'rb')
|
||||
@@ -51,6 +81,8 @@ def run():
|
||||
help='Run the syncfiles associated with the currently completed OS profile on the noderange')
|
||||
argparser.add_option('-P', '--scripts',
|
||||
help='Re-run specified scripts, with full path under scripts, e.g. post.d/first,firstboot.d/second')
|
||||
argparser.add_option('-A', '--automation',
|
||||
help='Run the automation scripts associated with the current OS profile on the noderange, specifying category (onboot.d/firstboot.d/post.d)')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to run remote ssh command to, '
|
||||
@@ -74,16 +106,13 @@ def run():
|
||||
exitcode = 0
|
||||
|
||||
c.stop_if_noderange_over(args[0], options.maxnodes)
|
||||
if options.automation:
|
||||
run_automation(args[0], options.automation, c)
|
||||
|
||||
nodemap = {}
|
||||
cmdparms = []
|
||||
nodes = []
|
||||
for res in c.read('/noderange/{0}/nodes/'.format(args[0])):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode |= res.get('errorcode', 1)
|
||||
break
|
||||
node = res['item']['href'][:-1]
|
||||
nodes.append(node)
|
||||
|
||||
|
||||
cmdstorun = []
|
||||
if options.security:
|
||||
@@ -94,8 +123,17 @@ def run():
|
||||
for script in options.scripts.split(','):
|
||||
cmdstorun.append(['run_remote', script])
|
||||
if not cmdstorun:
|
||||
if options.automation:
|
||||
sys.exit(0)
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
for res in c.read('/noderange/{0}/nodes/'.format(args[0])):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode |= res.get('errorcode', 1)
|
||||
break
|
||||
node = res['item']['href'][:-1]
|
||||
nodes.append(node)
|
||||
idxbynode = {}
|
||||
cmdvbase = ['bash', '/etc/confluent/functions']
|
||||
for sshnode in nodes:
|
||||
|
||||
@@ -76,7 +76,7 @@ import shutil
|
||||
|
||||
vinz = None
|
||||
pluginmap = {}
|
||||
dispatch_plugins = (b'ipmi', u'ipmi', b'redfish', u'redfish', b'tsmsol', u'tsmsol', b'geist', u'geist', b'deltapdu', u'deltapdu', b'eatonpdu', u'eatonpdu', b'affluent', u'affluent', b'cnos', u'cnos', b'enos', u'enos')
|
||||
dispatch_plugins = (b'remoteconfig', b'ipmi', u'ipmi', b'redfish', u'redfish', b'tsmsol', u'tsmsol', b'geist', u'geist', b'deltapdu', u'deltapdu', b'eatonpdu', u'eatonpdu', b'affluent', u'affluent', b'cnos', u'cnos', b'enos', u'enos')
|
||||
|
||||
PluginCollection = plugin.PluginCollection
|
||||
|
||||
@@ -476,7 +476,15 @@ def _init_core():
|
||||
}),
|
||||
'ident_image': PluginRoute({
|
||||
'handler': 'identimage'
|
||||
})
|
||||
}),
|
||||
'remote_config': {
|
||||
'run': PluginRoute({
|
||||
'handler': 'remoteconfig'
|
||||
}),
|
||||
'active': PluginCollection({
|
||||
'handler': 'remoteconfig'
|
||||
}),
|
||||
},
|
||||
},
|
||||
'events': {
|
||||
'hardware': {
|
||||
|
||||
@@ -580,6 +580,8 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False,
|
||||
return InputLicense(path, nodes, inputdata, configmanager)
|
||||
elif path == ['deployment', 'lock'] and inputdata:
|
||||
return InputDeploymentLock(path, nodes, inputdata)
|
||||
elif path == ['deployment', 'remote_config', 'run'] and inputdata:
|
||||
return InputRemoteConfig(path, nodes, inputdata)
|
||||
elif path == ['deployment', 'ident_image']:
|
||||
return InputIdentImage(path, nodes, inputdata)
|
||||
elif path == ['console', 'ikvm']:
|
||||
@@ -994,6 +996,10 @@ class InputDeploymentLock(ConfluentInputMessage):
|
||||
keyname = 'lock'
|
||||
valid_values = ['autolock', 'unlocked', 'locked']
|
||||
|
||||
class InputRemoteConfig(ConfluentInputMessage):
|
||||
keyname = 'category'
|
||||
valid_values = ['post.d', 'firstboot.d', 'onboot.d']
|
||||
|
||||
class DeploymentLock(ConfluentChoiceMessage):
|
||||
valid_values = set([
|
||||
'autolock',
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import confluent.selfservice as selfservice
|
||||
import confluent.messages as msg
|
||||
import confluent.runansible as runansible
|
||||
|
||||
_user_initiated_runs = {}
|
||||
def update(nodes, element, configmanager, inputdata):
|
||||
if element[-1] != 'run':
|
||||
raise ValueError('Invalid element for remoteconfig plugin')
|
||||
for node in nodes:
|
||||
category = inputdata.inputbynode[node]['category']
|
||||
playlist = selfservice.list_ansible_scripts(configmanager, node, category)
|
||||
if playlist:
|
||||
_user_initiated_runs[node] = True
|
||||
runansible.run_playbooks(playlist, [node])
|
||||
yield msg.CreatedResource(
|
||||
'/nodes/{0}/deployment/remote_config/active/{0}'.format(node))
|
||||
else:
|
||||
yield msg.ConfluentNodeError('No remote configuration for category "{0}"', node)
|
||||
|
||||
def retrieve(nodes, element, configmanager, inputdata):
|
||||
for node in nodes:
|
||||
if element[-1] == 'active':
|
||||
rst = runansible.running_status.get(node, None)
|
||||
if not rst:
|
||||
return
|
||||
yield msg.ChildCollection(node)
|
||||
elif element[-2] == 'active' and element[-1] == node:
|
||||
rst = runansible.running_status.get(node, None)
|
||||
if not rst:
|
||||
return
|
||||
playstatus = rst.dump_dict()
|
||||
if playstatus['complete'] and _user_initiated_runs.get(node, False):
|
||||
del runansible.running_status[node]
|
||||
del _user_initiated_runs[node]
|
||||
yield msg.KeyValueData(playstatus, node)
|
||||
|
||||
|
||||
|
||||
@@ -519,19 +519,7 @@ def handle_request(env, start_response):
|
||||
yield ''
|
||||
elif env['PATH_INFO'].startswith('/self/remoteconfig/') and 'POST' == operation:
|
||||
scriptcat = env['PATH_INFO'].replace('/self/remoteconfig/', '')
|
||||
playlist = []
|
||||
for privacy in ('public', 'private'):
|
||||
slist, profile = get_scriptlist(
|
||||
scriptcat, cfg, nodename,
|
||||
'/var/lib/confluent/{0}/os/{{0}}/ansible/{{1}}'.format(privacy))
|
||||
dirname = '/var/lib/confluent/{2}/os/{0}/ansible/{1}/'.format(
|
||||
profile, scriptcat, privacy)
|
||||
if not os.path.isdir(dirname):
|
||||
dirname = '/var/lib/confluent/{2}/os/{0}/ansible/{1}.d/'.format(
|
||||
profile, scriptcat, privacy)
|
||||
for filename in slist:
|
||||
if filename.endswith('.yaml') or filename.endswith('.yml'):
|
||||
playlist.append(os.path.join(dirname, filename))
|
||||
playlist = list_ansible_scripts(cfg, nodename, scriptcat)
|
||||
if playlist:
|
||||
runansible.run_playbooks(playlist, [nodename])
|
||||
start_response('202 Queued', ())
|
||||
@@ -603,6 +591,22 @@ def handle_request(env, start_response):
|
||||
start_response('404 Not Found', ())
|
||||
yield 'Not found'
|
||||
|
||||
def list_ansible_scripts(cfg, nodename, scriptcat):
|
||||
playlist = []
|
||||
for privacy in ('public', 'private'):
|
||||
slist, profile = get_scriptlist(
|
||||
scriptcat, cfg, nodename,
|
||||
'/var/lib/confluent/{0}/os/{{0}}/ansible/{{1}}'.format(privacy))
|
||||
dirname = '/var/lib/confluent/{2}/os/{0}/ansible/{1}/'.format(
|
||||
profile, scriptcat, privacy)
|
||||
if not os.path.isdir(dirname):
|
||||
dirname = '/var/lib/confluent/{2}/os/{0}/ansible/{1}.d/'.format(
|
||||
profile, scriptcat, privacy)
|
||||
for filename in slist:
|
||||
if filename.endswith('.yaml') or filename.endswith('.yml'):
|
||||
playlist.append(os.path.join(dirname, filename))
|
||||
return playlist
|
||||
|
||||
def get_scriptlist(scriptcat, cfg, nodename, pathtemplate):
|
||||
if '..' in scriptcat:
|
||||
return None, None
|
||||
|
||||
Reference in New Issue
Block a user