2
0
mirror of https://github.com/xcat2/confluent.git synced 2026-01-11 02:22:31 +00:00

Add deployment lock mechanism

This allows users to opt into disabling setting further profile changes.

Nodes may be 'unlocked' (normal), 'autolock' (will lock on next
completion), or 'locked' (unable to change the pending OS profile)
This commit is contained in:
Jarrod Johnson
2025-05-01 09:25:05 -04:00
parent 0c0cac140d
commit 71f5ce2b29
6 changed files with 62 additions and 0 deletions

View File

@@ -117,6 +117,16 @@ def main(args):
else:
sys.stderr.write('No deployment profiles available, try osdeploy import or imgutil capture\n')
sys.exit(1)
lockednodes = []
for lockinfo in c.read('/noderange/{0}/deployment/lock'.format(args.noderange)):
for node in lockinfo.get('databynode', {}):
lockstate = lockinfo['databynode'][node]['lock']['value']
if lockstate == 'locked':
lockednodes.append(node)
if lockednodes:
sys.stderr.write('Requested noderange has nodes with locked deployment: ' + ','.join(lockednodes))
sys.stderr.write('\n')
sys.exit(1)
armonce(args.noderange, c)
setpending(args.noderange, args.profile, c)
else:

View File

@@ -215,6 +215,13 @@ node = {
'Using this requires that collective members be '
'defined as nodes for noderange expansion')
},
'deployment.lock': {
'description': ('Indicates whether deployment actions should be impeded. '
'If locked, it indicates that a pending profile should not be applied. '
'If "autolock", then locked will be set when current pending deployment completes. '
),
'validlist': ('autolock', 'locked')
},
'deployment.pendingprofile': {
'description': ('An OS profile that is pending deployment. This indicates to '
'the network boot subsystem what should be offered when a potential '

View File

@@ -453,6 +453,9 @@ def _init_core():
'default': 'ipmi',
}),
'deployment': {
'lock': PluginRoute({
'handler': 'attributes'
}),
'ident_image': PluginRoute({
'handler': 'identimage'
})

View File

@@ -574,6 +574,8 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False,
elif '/'.join(path).startswith(
'configuration/management_controller/licenses') and inputdata:
return InputLicense(path, nodes, inputdata, configmanager)
elif path == ['deployment', 'lock'] and inputdata:
return InputDeploymentLock(path, nodes, inputdata)
elif path == ['deployment', 'ident_image']:
return InputIdentImage(path, nodes, inputdata)
elif path == ['console', 'ikvm']:
@@ -957,6 +959,18 @@ class InputIdentImage(ConfluentInputMessage):
keyname = 'ident_image'
valid_values = ['create']
class InputDeploymentLock(ConfluentInputMessage):
keyname = 'lock'
valid_values = ['autolock', 'unlocked', 'locked']
class DeploymentLock(ConfluentChoiceMessage):
valid_values = set([
'autolock',
'locked',
'unlocked',
])
keyname = 'lock'
class InputIkvmParams(ConfluentInputMessage):
keyname = 'method'
valid_values = ['unix', 'wss', 'url']

View File

@@ -109,6 +109,13 @@ def retrieve_nodegroup(nodegroup, element, configmanager, inputdata, clearwarnby
def retrieve_nodes(nodes, element, configmanager, inputdata, clearwarnbynode):
attributes = configmanager.get_node_attributes(nodes)
if element[-1] == 'lock':
for node in nodes:
lockstate = attributes.get(node, {}).get('deployment.lock', {}).get('value', None)
if lockstate not in ('locked', 'autolock'):
lockstate = 'unlocked'
yield msg.DeploymentLock(node, lockstate)
return
if element[-1] == 'all':
for node in util.natural_sort(nodes):
if clearwarnbynode and node in clearwarnbynode:
@@ -247,12 +254,20 @@ def yield_rename_resources(namemap, isnode):
else:
yield msg.RenamedResource(node, namemap[node])
def update_locks(nodes, configmanager, inputdata):
for node in nodes:
updatestate = inputdata.inputbynode[node]
configmanager.set_node_attributes({node: {'deployment.lock': updatestate}})
yield msg.DeploymentLock(node, updatestate)
def update_nodes(nodes, element, configmanager, inputdata):
updatedict = {}
if not nodes:
raise exc.InvalidArgumentException(
'No action to take, noderange is empty (if trying to define '
'group attributes, use nodegroupattrib)')
if element[-1] == 'lock':
return update_locks(nodes, configmanager, inputdata)
if element[-1] == 'check':
for node in nodes:
check = inputdata.get_attributes(node, allattributes.node)
@@ -273,6 +288,15 @@ def update_nodes(nodes, element, configmanager, inputdata):
configmanager.rename_nodes(namemap)
return yield_rename_resources(namemap, isnode=True)
clearwarnbynode = {}
for node in nodes:
updatenode = inputdata.get_attributes(node, allattributes.node)
if updatenode and 'deployment.lock' in updatenode:
raise exc.InvalidArgumentException('Deployment lock must be manipulated by {node}/deployment/lock api')
if updatenode and ('deployment.pendingprofile' in updatenode or 'deployment.apiarmed' in updatenode):
lockcheck = configmanager.get_node_attributes(node, 'deployment.lock')
lockstate = lockcheck.get(node, {}).get('deployment.lock', {}).get('value', None)
if lockstate == 'locked':
raise exc.InvalidArgumentException('Request to set deployment for a node that has locked deployment')
for node in nodes:
updatenode = inputdata.get_attributes(node, allattributes.node)
clearattribs = []

View File

@@ -490,6 +490,10 @@ def handle_request(env, start_response):
updates['deployment.pendingprofile'] = {'value': ''}
if targattr == 'deployment.profile':
updates['deployment.stagedprofile'] = {'value': ''}
dls = cfg.get_node_attributes(nodename, 'deployment.lock')
dls = dls.get(nodename, {}).get('deployment.lock', {}).get('value', None)
if dls == 'autolock':
updates['deployment.lock'] = 'locked'
currprof = currattr.get(targattr, {}).get('value', '')
if currprof != pending:
updates[targattr] = {'value': pending}