diff --git a/confluent_client/bin/nodedeploy b/confluent_client/bin/nodedeploy index 15e78f37..1e172fea 100755 --- a/confluent_client/bin/nodedeploy +++ b/confluent_client/bin/nodedeploy @@ -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: diff --git a/confluent_server/confluent/config/attributes.py b/confluent_server/confluent/config/attributes.py index 248063f2..4f6531bd 100644 --- a/confluent_server/confluent/config/attributes.py +++ b/confluent_server/confluent/config/attributes.py @@ -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 ' diff --git a/confluent_server/confluent/core.py b/confluent_server/confluent/core.py index e25e82d2..0e754b9f 100644 --- a/confluent_server/confluent/core.py +++ b/confluent_server/confluent/core.py @@ -453,6 +453,9 @@ def _init_core(): 'default': 'ipmi', }), 'deployment': { + 'lock': PluginRoute({ + 'handler': 'attributes' + }), 'ident_image': PluginRoute({ 'handler': 'identimage' }) diff --git a/confluent_server/confluent/messages.py b/confluent_server/confluent/messages.py index 6dbe031f..04ca43f7 100644 --- a/confluent_server/confluent/messages.py +++ b/confluent_server/confluent/messages.py @@ -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'] diff --git a/confluent_server/confluent/plugins/configuration/attributes.py b/confluent_server/confluent/plugins/configuration/attributes.py index 14607af5..434d6c50 100644 --- a/confluent_server/confluent/plugins/configuration/attributes.py +++ b/confluent_server/confluent/plugins/configuration/attributes.py @@ -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 = [] diff --git a/confluent_server/confluent/selfservice.py b/confluent_server/confluent/selfservice.py index de8eb832..6df8ff17 100644 --- a/confluent_server/confluent/selfservice.py +++ b/confluent_server/confluent/selfservice.py @@ -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}