diff --git a/confluent_client/bin/nodereseat b/confluent_client/bin/nodereseat new file mode 100644 index 00000000..a4c829ff --- /dev/null +++ b/confluent_client/bin/nodereseat @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2015-2017 Lenovo +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import optparse +import os +import signal +import sys +try: + signal.signal(signal.SIGPIPE, signal.SIG_DFL) +except AttributeError: + pass + +path = os.path.dirname(os.path.realpath(__file__)) +path = os.path.realpath(os.path.join(path, '..', 'lib', 'python')) +if path.startswith('/opt'): + sys.path.append(path) + +import confluent.client as client + +argparser = optparse.OptionParser(usage="Usage: %prog ") +(options, args) = argparser.parse_args() +try: + noderange = args[0] +except IndexError: + argparser.print_help() + sys.exit(1) + +session = client.Command() +exitcode = 0 + +errorNodes = set([]) + +success = session.simple_noderange_command(noderange, 'power/reseat', 'reseat', key='reseat', errnodes=errorNodes) # = 0 if successful + +# Determine which nodes were successful and print them + +allNodes = set([]) + +for node in session.read('/noderange/{0}/nodes/'.format(noderange)): + allNodes.add(node['item']['href'].replace("/", "")) + +goodNodes = allNodes - errorNodes + +for node in goodNodes: + print node + ": Reseat successful" + + +sys.exit(success) diff --git a/confluent_server/confluent/core.py b/confluent_server/confluent/core.py index 2b523c9b..b5bee1a4 100644 --- a/confluent_server/confluent/core.py +++ b/confluent_server/confluent/core.py @@ -185,6 +185,11 @@ def _init_core(): 'handler': 'ssh', }), }, + '_enclosure': { + 'reseat_bay': PluginRoute( + {'pluginattrs': ['hardwaremanagement.method'], + 'default': 'ipmi'}), + }, 'shell': { # another special case similar to console 'sessions': PluginCollection({ @@ -250,6 +255,7 @@ def _init_core(): 'pluginattrs': ['hardwaremanagement.method'], 'default': 'ipmi', }), + 'reseat': PluginRoute({'handler': 'enclosure'}), }, 'sensors': { 'hardware': { diff --git a/confluent_server/confluent/messages.py b/confluent_server/confluent/messages.py index 30ad9c13..fa38a3c9 100644 --- a/confluent_server/confluent/messages.py +++ b/confluent_server/confluent/messages.py @@ -364,6 +364,9 @@ class ChildCollection(LinkRelation): def get_input_message(path, operation, inputdata, nodes=None, multinode=False): if path[0] == 'power' and path[1] == 'state' and operation != 'retrieve': return InputPowerMessage(path, nodes, inputdata) + elif (path in (['power', 'reseat'], ['_enclosure', 'reseat_bay']) and + operation != 'retrieve'): + return InputReseatMessage(path, nodes, inputdata) elif path == ['attributes', 'expression']: return InputExpression(path, inputdata, nodes) elif path[0] in ('attributes', 'users') and operation != 'retrieve': @@ -624,7 +627,7 @@ class ConfluentInputMessage(ConfluentMessage): if self.keyname not in datum: raise exc.InvalidArgumentException( 'missing {0} argument'.format(self.keyname)) - elif datum[self.keyname] not in self.valid_values: + elif not self.is_valid_key(datum[self.keyname]): raise exc.InvalidArgumentException( datum[self.keyname] + ' is not one of ' + ','.join(self.valid_values)) @@ -634,13 +637,15 @@ class ConfluentInputMessage(ConfluentMessage): if self.keyname not in datum: raise exc.InvalidArgumentException( 'missing {0} argument'.format(self.keyname)) - elif datum[self.keyname] not in self.valid_values: + elif not self.is_valid_key(datum[self.keyname]): raise exc.InvalidArgumentException(datum[self.keyname] + ' is not one of ' + ','.join(self.valid_values)) for node in nodes: self.inputbynode[node] = datum[self.keyname] + def is_valid_key(self, key): + return key in self.valid_values class InputIdentifyMessage(ConfluentInputMessage): valid_values = set([ @@ -664,6 +669,16 @@ class InputPowerMessage(ConfluentInputMessage): def powerstate(self, node): return self.inputbynode[node] +class InputReseatMessage(ConfluentInputMessage): + valid_values = set([ + 'reseat', + ]) + + keyname = 'reseat' + + def is_valid_key(self, key): + return key in self.valid_values or isinstance(key, int) + class InputBMCReset(ConfluentInputMessage): valid_values = set([ @@ -875,6 +890,13 @@ class IdentifyState(ConfluentChoiceMessage): keyname = 'identify' +class ReseatResult(ConfluentChoiceMessage): + valid_values = set([ + 'success', + ]) + keyname = 'reseat' + + class PowerState(ConfluentChoiceMessage): valid_values = set([ 'on', diff --git a/confluent_server/confluent/plugins/hardwaremanagement/enclosure.py b/confluent_server/confluent/plugins/hardwaremanagement/enclosure.py new file mode 100644 index 00000000..701333fd --- /dev/null +++ b/confluent_server/confluent/plugins/hardwaremanagement/enclosure.py @@ -0,0 +1,26 @@ +# Copyright 2017 Lenovo +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import confluent.core as core + +def update(nodes, element, configmanager, inputdata): + emebs = configmanager.get_node_attributes( + nodes, (u'enclosure.manager', u'enclosure.bay')) + for node in nodes: + em = emebs[node]['enclosure.manager']['value'] + eb = emebs[node]['enclosure.bay']['value'] + for rsp in core.handle_path( + '/nodes/{0}/_enclosure/reseat_bay'.format(em), + 'update', configmanager, + inputdata={'reseat': int(eb)}): + yield rsp diff --git a/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py b/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py index e0403587..5fb50ec5 100644 --- a/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py +++ b/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py @@ -432,6 +432,8 @@ class IpmiHandler(object): raise Exception(self.error) if self.element == ['power', 'state']: self.power() + elif self.element == ['_enclosure', 'reseat_bay']: + self.reseat_bay() elif self.element == ['boot', 'nextdevice']: self.bootdevice() elif self.element == ['health', 'hardware']: @@ -820,6 +822,11 @@ class IpmiHandler(object): else: raise exc.InvalidArgumentException('health is read-only') + def reseat_bay(self): + bay = self.inputdata.inputbynode[self.node] + self.ipmicmd.reseat_bay(bay) + self.output.put(msg.ReseatResult(self.node, 'success')) + def bootdevice(self): if 'read' == self.op: bootdev = self.ipmicmd.get_bootdev()