diff --git a/confluent_server/confluent/core.py b/confluent_server/confluent/core.py index 7d5f2a63..0cb320dc 100644 --- a/confluent_server/confluent/core.py +++ b/confluent_server/confluent/core.py @@ -110,6 +110,10 @@ noderesources = { 'default': 'ipmi', }), }, + 'identify': PluginRoute({ + 'pluginattrs': ['hardwaremanagement.method'], + 'default': 'ipmi', + }), 'boot': { 'nextdevice': PluginRoute({ 'pluginattrs': ['hardwaremanagement.method'], @@ -166,6 +170,8 @@ def show_user(name, configmanager): def stripnode(iterablersp, node): for i in iterablersp: + if i is None: + raise exc.NotImplementedException("Not Implemented") i.strip_node(node) yield i diff --git a/confluent_server/confluent/exceptions.py b/confluent_server/confluent/exceptions.py index 7ed2235b..67a39a41 100644 --- a/confluent_server/confluent/exceptions.py +++ b/confluent_server/confluent/exceptions.py @@ -36,6 +36,7 @@ class TargetEndpointUnreachable(ConfluentException): # was unreachable. http code 504 pass + class TargetEndpointBadCredentials(ConfluentException): # target was reachable, but authentication/authorization # failed @@ -45,3 +46,9 @@ class TargetEndpointBadCredentials(ConfluentException): class ForbiddenRequest(ConfluentException): # The client request is not allowed by authorization engine pass + + +class NotImplementedException(ConfluentException): + # The current configuration/plugin is unable to perform + # the requested task. http code 501 + pass \ No newline at end of file diff --git a/confluent_server/confluent/httpapi.py b/confluent_server/confluent/httpapi.py index 58149237..f826f722 100644 --- a/confluent_server/confluent/httpapi.py +++ b/confluent_server/confluent/httpapi.py @@ -390,8 +390,9 @@ def resourcehandler_backend(env, start_response): except exc.TargetEndpointBadCredentials: start_response('502 Bad Credentials', headers) yield '502 - Bad Credentials' - - + except exc.NotImplementedException: + start_response('501 Not Implemented', headers) + yield '501 Not Implemented' def _assemble_html(responses, resource, querydict, url, extension): yield '' \ diff --git a/confluent_server/confluent/messages.py b/confluent_server/confluent/messages.py index 0c9ed55f..25b76bf2 100644 --- a/confluent_server/confluent/messages.py +++ b/confluent_server/confluent/messages.py @@ -171,6 +171,13 @@ class ConfluentChoiceMessage(ConfluentMessage): valid_values = set() valid_paramset = {} + def __init__(self, node, state): + self.kvpairs = { + node: { + self.keyname: {'value': state}, + } + } + def html(self, extension=''): snippet = "" for key in self.kvpairs.iterkeys(): @@ -247,6 +254,8 @@ def get_input_message(path, operation, inputdata, nodes=None): return InputAttributes(path, inputdata, nodes) elif path == ['boot', 'nextdevice'] and operation != 'retrieve': return InputBootDevice(path, nodes, inputdata) + elif path == [ 'identify' ] and operation != 'retrieve': + return InputIdentifyMessage(path, nodes, inputdata) elif inputdata: raise exc.InvalidArgumentException() @@ -308,7 +317,49 @@ class InputAttributes(ConfluentMessage): return nodeattr -class InputPowerMessage(ConfluentMessage): +class ConfluentInputMessage(ConfluentMessage): + keyname = 'state' + + def __init__(self, path, nodes, inputdata): + self.inputbynode = {} + if not inputdata: + raise exc.InvalidArgumentException('missing input data') + if self.keyname not in inputdata: + #assume we have nested information + for key in nodes: + if key not in inputdata: + raise exc.InvalidArgumentException(key + ' not in request') + datum = inputdata[key] + if self.keyname not in datum: + raise exc.InvalidArgumentException( + 'missing {0} argument'.format(self.keyname)) + elif datum[self.keyname] not in self.valid_values: + raise exc.InvalidArgumentException( + datum[self.keyname] + ' is not one of ' + + ','.join(self.valid_values)) + self.inputbynode[key] = datum[self.keyname] + else: # we have a state argument not by node + datum = inputdata + if self.keyname not in datum: + raise exc.InvalidArgumentException('missing {0} argument'.format(self.keyname)) + elif datum[self.keyname] not in self.valid_values: + raise exc.InvalidArgumentException(datum[self.keyname] + + ' is not one of ' + + ','.join(self.valid_values)) + for node in nodes: + self.inputbynode[node] = datum[self.keyname] + + +class InputIdentifyMessage(ConfluentInputMessage): + valid_values = set([ + 'on', + 'off', + ]) + + keyname = 'identify' + + +class InputPowerMessage(ConfluentInputMessage): valid_values = set([ 'on', 'off', @@ -316,37 +367,8 @@ class InputPowerMessage(ConfluentMessage): 'boot', ]) - def __init__(self, path, nodes, inputdata): - self.powerbynode = {} - if not inputdata: - raise exc.InvalidArgumentException('missing input data') - if 'state' not in inputdata: - #assume we have nested information - for key in nodes: - if key not in inputdata: - raise exc.InvalidArgumentException(key + ' not in request') - datum = inputdata[key] - if 'state' not in datum: - raise exc.InvalidArgumentException( - 'missing state argument') - elif datum['state'] not in self.valid_values: - raise exc.InvalidArgumentException( - datum['state'] + ' is not one of ' + - ','.join(self.valid_values)) - self.powerbynode[key] = datum['state'] - else: # we have a state argument not by node - datum = inputdata - if 'state' not in datum: - raise exc.InvalidArgumentException('missing state argument') - elif datum['state'] not in self.valid_values: - raise exc.InvalidArgumentException(datum['state'] + - ' is not one of ' + - ','.join(self.valid_values)) - for node in nodes: - self.powerbynode[node] = datum['state'] - def powerstate(self, node): - return self.powerbynode[node] + return self.inputbynode[node] class BootDevice(ConfluentChoiceMessage): @@ -428,6 +450,14 @@ class InputBootDevice(BootDevice): return self.bootmodebynode.get(node, 'unspecified') +class IdentifyState(ConfluentChoiceMessage): + valid_values = set([ + 'on', + 'off', + ]) + keyname = 'identify' + + class PowerState(ConfluentChoiceMessage): valid_values = set([ 'on', @@ -435,13 +465,9 @@ class PowerState(ConfluentChoiceMessage): 'reset', 'boot', ]) + keyname = 'state' + - def __init__(self, node, state): - self.kvpairs = { - node: { - 'state': {'value': state}, - } - } class SensorReadings(ConfluentMessage): diff --git a/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py b/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py index 9cd7bb4b..5bc6cb76 100644 --- a/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py +++ b/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py @@ -213,6 +213,8 @@ def perform_request(operator, node, element, configdata, inputdata, cfg): excmsg = str(ipmiexc) if excmsg == 'Session no longer connected': return msg.ConfluentTargetTimeout(node) + else: + raise persistent_ipmicmds = {} @@ -275,6 +277,8 @@ class IpmiHandler(object): return self.bootdevice() elif self.element == ['health', 'hardware']: return self.health() + elif self.element == ['identify']: + return self.identify() @staticmethod def _str_health(health): @@ -331,6 +335,12 @@ class IpmiHandler(object): return msg.BootDevice(node=self.node, device=bootdev['bootdev']) + def identify(self): + if 'update' == self.op: + identifystate = self.inputdata.inputbynode[self.node] == 'on' + self.ipmicmd.set_identify(on=identifystate) + return msg.IdentifyState( + node=self.node, state=self.inputdata.inputbynode[self.node]) def power(self): if 'read' == self.op: power = self.ipmicmd.get_power() diff --git a/confluent_server/confluent/sockapi.py b/confluent_server/confluent/sockapi.py index 1e8b3795..da857de3 100644 --- a/confluent_server/confluent/sockapi.py +++ b/confluent_server/confluent/sockapi.py @@ -112,6 +112,10 @@ def sessionhdl(connection, authname, skipauth=False): tlvdata.send(connection, {'errorcode': 504, 'error': 'Unreachable Target'}) tlvdata.send(connection, {'_requestdone': 1}) + except exc.NotImplementedException: + tlvdata.send(connection, {'errorcode': 501, + 'error': 'Not Implemented'}) + tlvdata.send(connection, {'_requestdone': 1}) except SystemExit: sys.exit(0) except: