diff options
Diffstat (limited to 'pyipmi/tools')
-rw-r--r-- | pyipmi/tools/__init__.py | 35 | ||||
-rw-r--r-- | pyipmi/tools/ipmi_pef_config.py | 126 | ||||
-rw-r--r-- | pyipmi/tools/ipmi_pet.py | 126 | ||||
-rw-r--r-- | pyipmi/tools/ipmidcmi.py | 126 | ||||
-rw-r--r-- | pyipmi/tools/ipmitool.py | 111 | ||||
-rw-r--r-- | pyipmi/tools/responseparser.py | 269 |
6 files changed, 793 insertions, 0 deletions
diff --git a/pyipmi/tools/__init__.py b/pyipmi/tools/__init__.py new file mode 100644 index 0000000..fcaba71 --- /dev/null +++ b/pyipmi/tools/__init__.py @@ -0,0 +1,35 @@ +# Copyright (c) 2012, Calxeda Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Calxeda Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + + +"""Implementations of Tool""" +from ipmitool import IpmiTool +from ipmidcmi import IpmiDcmi +from ipmi_pef_config import IpmiPEFConfig diff --git a/pyipmi/tools/ipmi_pef_config.py b/pyipmi/tools/ipmi_pef_config.py new file mode 100644 index 0000000..4923f2c --- /dev/null +++ b/pyipmi/tools/ipmi_pef_config.py @@ -0,0 +1,126 @@ +# Copyright (c) 2012, Calxeda Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Calxeda Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + + +"""An implementation of Tool for ipmi-pef-config (module of FreeIPMI) support""" + +import subprocess, sys, pexpect +from pyipmi import Tool, IpmiError, InteractiveCommand + +class IpmiPEFConfig(Tool): + """Implements interaction with ipmi-pef-config + + Currently only supports one off commands, persistent sessions will come. + """ + def __init__(self, *args, **kwargs): + super(IpmiPEFConfig, self).__init__(*args, **kwargs) + self._ipmi_pef_config_path = self._find_ipmi_pef_config_path() + + def _find_ipmi_pef_config_path(self): + """Get the path to the ipmi-pef-config bin. + + freeipmi puts all of its binaries in /usr/sbin, which lots of + things don't have in their default path. We hardcode the path to + it here, but only if it can't be found in $PATH (via bash's which).""" + + try: + found = subprocess.check_output(['which', 'ipmi-pef-config']).strip() + except subprocess.CalledProcessError: + return '/usr/sbin/ipmi-pef-config' + + return found + + def run(self, command): + """Run a command via ipmi-pef-config""" + ipmi_args = self._ipmi_args(command) + + arg_str = 'Running %s' % ' '.join(ipmi_args) + self._log(arg_str) + print arg_str + + if isinstance(command, InteractiveCommand): + command = ipmi_args[0] + args = ipmi_args[1:] + proc = self._start_command(command, args) + return proc + + out, err = self._execute(command, ipmi_args) + return command.parse_results(out, err) + + def _ipmi_args(self, command): + """Return the command line arguments to ipmi-pef-config for command""" + args = [self._ipmi_pef_config_path] + args.extend(self._config_args) + args.extend(command.ipmi_pef_config_args) + return map(str, args) + + @property + def _config_args(self): + """Return the config dependent command line arguments + + This arguments are generated from the BMC's config, not from + a specific command. They will be the same from command to + command. + """ + params_to_args = { + 'hostname' : '-h', + 'password' : '-p', + 'username' : '-u', + 'authtype' : '-a', + 'level' : '-l' + } + + base = [] + bmc_params = self._handle.bmc.params + + for param, val in bmc_params.iteritems(): + arg = params_to_args.get(param) + if arg and val: + base.extend([arg, str(val)]) + + return base + + def _execute(self, command, args): + """Execute an ipmi-pef-config command""" + proc = subprocess.Popen(args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, err = proc.communicate() + self._log(out) + self._log(err) + sys.stdout.write(out) + sys.stderr.write(err) + if proc.returncode != 0: + command.handle_command_error(out, err) + return out, err + + def _start_command(self, command, args, timeout=5): + return pexpect.spawn(command, args, timeout=timeout, + logfile=self._handle._log_file) diff --git a/pyipmi/tools/ipmi_pet.py b/pyipmi/tools/ipmi_pet.py new file mode 100644 index 0000000..05ce17e --- /dev/null +++ b/pyipmi/tools/ipmi_pet.py @@ -0,0 +1,126 @@ +# Copyright (c) 2012, Calxeda Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Calxeda Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + + +"""An implementation of Tool for ipmi-pet (module of FreeIPMI) support""" + +import subprocess, sys, pexpect +from pyipmi import Tool, IpmiError, InteractiveCommand + +class IpmiPET(Tool): + """Implements interaction with ipmi-pet + + Currently only supports one off commands, persistent sessions will come. + """ + def __init__(self, *args, **kwargs): + super(IpmiPET, self).__init__(*args, **kwargs) + self._ipmi_pet_path = self._find_ipmi_pet_path() + + def _find_ipmi_pet_path(self): + """Get the path to the ipmi-pet bin. + + freeipmi puts all of its binaries in /usr/sbin, which lots of + things don't have in their default path. We hardcode the path to + it here, but only if it can't be found in $PATH (via bash's which).""" + + try: + found = subprocess.check_output(['which', 'ipmi-pet']).strip() + except subprocess.CalledProcessError: + return '/usr/sbin/ipmi-pet' + + return found + + def run(self, command): + """Run a command via ipmi-pet""" + ipmi_args = self._ipmi_args(command) + + arg_str = 'Running %s' % ' '.join(ipmi_args) + self._log(arg_str) + print arg_str + + if isinstance(command, InteractiveCommand): + command = ipmi_args[0] + args = ipmi_args[1:] + proc = self._start_command(command, args) + return proc + + out, err = self._execute(command, ipmi_args) + return command.parse_results(out, err) + + def _ipmi_args(self, command): + """Return the command line arguments to ipmi-pet for command""" + args = [self._ipmi_pet_path] + args.extend(self._config_args) + args.extend(command.ipmi_pet_args) + return map(str, args) + + @property + def _config_args(self): + """Return the config dependent command line arguments + + This arguments are generated from the BMC's config, not from + a specific command. They will be the same from command to + command. + """ + params_to_args = { + 'hostname' : '-h', + 'password' : '-p', + 'username' : '-u', + 'authtype' : '-a', + 'level' : '-l' + } + + base = [] + bmc_params = self._handle.bmc.params + + for param, val in bmc_params.iteritems(): + arg = params_to_args.get(param) + if arg and val: + base.extend([arg, str(val)]) + + return base + + def _execute(self, command, args): + """Execute an ipmi-pet command""" + proc = subprocess.Popen(args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, err = proc.communicate() + self._log(out) + self._log(err) + sys.stdout.write(out) + sys.stderr.write(err) + if proc.returncode != 0: + command.handle_command_error(out, err) + return out, err + + def _start_command(self, command, args, timeout=5): + return pexpect.spawn(command, args, timeout=timeout, + logfile=self._handle._log_file) diff --git a/pyipmi/tools/ipmidcmi.py b/pyipmi/tools/ipmidcmi.py new file mode 100644 index 0000000..a659b60 --- /dev/null +++ b/pyipmi/tools/ipmidcmi.py @@ -0,0 +1,126 @@ +# Copyright (c) 2012, Calxeda Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Calxeda Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + + +"""An implementation of Tool for ipmi-dcmi (module of FreeIPMI) support""" + +import subprocess, sys, pexpect +from pyipmi import Tool, IpmiError, InteractiveCommand + +class IpmiDcmi(Tool): + """Implements interaction with ipmi-dcmi + + Currently only supports one off commands, persistent sessions will come. + """ + def __init__(self, *args, **kwargs): + super(IpmiDcmi, self).__init__(*args, **kwargs) + self._ipmidcmi_path = self._find_ipmidcmi_path() + + def _find_ipmidcmi_path(self): + """Get the path to the ipmi-dcmi bin. + + freeipmi puts all of its binaries in /usr/sbin, which lots of + things don't have in their default path. We hardcode the path to + it here, but only if it can't be found in $PATH (via bash's which).""" + + try: + found = subprocess.check_output(['which', 'ipmi-dcmi']).strip() + except subprocess.CalledProcessError: + return '/usr/sbin/ipmi-dcmi' + + return found + + def run(self, command): + """Run a command via ipmi-dcmi""" + ipmi_args = self._ipmi_args(command) + + arg_str = 'Running %s' % ' '.join(ipmi_args) + self._log(arg_str) + print arg_str + + if isinstance(command, InteractiveCommand): + command = ipmi_args[0] + args = ipmi_args[1:] + proc = self._start_command(command, args) + return proc + + out, err = self._execute(command, ipmi_args) + return command.parse_results(out, err) + + def _ipmi_args(self, command): + """Return the command line arguments to ipmi-dcmi for command""" + args = [self._ipmidcmi_path] + args.extend(self._config_args) + args.extend(command.ipmidcmi_args) + return map(str, args) + + @property + def _config_args(self): + """Return the config dependent command line arguments + + This arguments are generated from the BMC's config, not from + a specific command. They will be the same from command to + command. + """ + params_to_args = { + 'hostname' : '-h', + 'password' : '-p', + 'username' : '-u', + 'authtype' : '-a', + 'level' : '-l' + } + + base = [] + bmc_params = self._handle.bmc.params + + for param, val in bmc_params.iteritems(): + arg = params_to_args.get(param) + if arg and val: + base.extend([arg, str(val)]) + + return base + + def _execute(self, command, args): + """Execute an ipmi-dcmi command""" + proc = subprocess.Popen(args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, err = proc.communicate() + self._log(out) + self._log(err) + sys.stdout.write(out) + sys.stderr.write(err) + if proc.returncode != 0: + command.handle_command_error(out, err) + return out, err + + def _start_command(self, command, args, timeout=5): + return pexpect.spawn(command, args, timeout=timeout, + logfile=self._handle._log_file) diff --git a/pyipmi/tools/ipmitool.py b/pyipmi/tools/ipmitool.py new file mode 100644 index 0000000..75cb179 --- /dev/null +++ b/pyipmi/tools/ipmitool.py @@ -0,0 +1,111 @@ +# Copyright (c) 2012, Calxeda Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Calxeda Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + + +"""An implementation of Tool for ipmitool support""" + +import os, subprocess, pexpect +from pyipmi import Tool, InteractiveCommand + +class IpmiTool(Tool): + """Implements interaction with impitool + + Currently only supports one off commands, persistent sessions will come. + """ + + def run(self, command): + """Run a command via ipmitool""" + ipmi_args = self._ipmi_args(command) + + arg_str = 'Running %s' % ' '.join(ipmi_args) + self._log(arg_str) + + if isinstance(command, InteractiveCommand): + command = ipmi_args[0] + args = ipmi_args[1:] + proc = self._start_command(command, args) + return proc + + out, err = self._execute(command, ipmi_args) + return command.parse_results(out, err) + + def _ipmi_args(self, command): + """Return the command line arguments to ipmitool for command""" + if 'IPMITOOL_PATH' in os.environ: + args = [os.environ['IPMITOOL_PATH']] + else: + args = ['ipmitool'] + args.extend(self._config_args) + args.extend(command.ipmitool_args) + return map(str, args) + + @property + def _config_args(self): + """Return the config dependent command line arguments + + This arguments are generated from the BMC's config, not from + a specific command. They will be the same from command to + command. + """ + params_to_args = { + 'hostname' : '-H', + 'password' : '-P', + 'username' : '-U', + 'authtype' : '-A', + 'level' : '-L', + 'port' : '-p', + 'interface' : '-I' + } + + base = [] + bmc_params = self._handle.bmc.params + + for param, val in bmc_params.iteritems(): + arg = params_to_args.get(param) + if arg and val: + base.extend([arg, str(val)]) + + return base + + def _execute(self, command, args): + """Execute an ipmitool command""" + proc = subprocess.Popen(args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, err = proc.communicate() + self._log(out) + self._log(err) + if proc.returncode != 0: + command.handle_command_error(out, err) + return out, err + + def _start_command(self, command, args, timeout=5): + return pexpect.spawn(command, args, timeout=timeout, + logfile=self._handle._log_file) diff --git a/pyipmi/tools/responseparser.py b/pyipmi/tools/responseparser.py new file mode 100644 index 0000000..abbf785 --- /dev/null +++ b/pyipmi/tools/responseparser.py @@ -0,0 +1,269 @@ +# Copyright (c) 2012, Calxeda Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Calxeda Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + + +"""Tool-independent mix-in for parsing IPMI results""" + +from pyipmi import IpmiError +import string +import inspect + + +def str_to_list(val, **params): + """convert string to list of substrings (default: single words)""" + val = val.strip() + if val == '': + return [] + + delimiter = params.get('delimiter', " ") + return map(string.strip, val.split(delimiter)) + + +def str2bool(val): + """True if val is 'true', 'yes' or 'enabled, otherwise false""" + return val.lower() in ['true', 'yes', 'enabled'] + + +def str_to_dict(val, **params): + """Returns the contents of the string 'val' as a dictionary""" + result = {} + operator = params.get('operator', ':') + delimiter = params.get('delimiter', '\n') + value_parser = params.get('value_parser', str) + + params['operator'] = params.get('value_operator', None) + params['delimiter'] = params.get('value_delimiter', None) + + entries = val.split(delimiter) + for entry in entries: + key, op, value = entry.partition(operator) + result[field_to_attr(key.strip())] = value_parser(value) + return result + + +def paren_pair(val): + """Convert 'foo (bar)' to ['foo', 'bar']""" + return [p.strip(' )') for p in val.split('(')] + + +def field_to_attr(field_name): + """Convert a field name to an attribute name + + Make the field all lowercase and replace ' ' with '_' + (replace space with underscore) + """ + result = field_name.lower() + if result[0:1].isdigit(): + result = "n_" + result + result = result.replace(' ', '_') + result = result.replace('/', '_') + result = result.replace('-', '_') + result = result.replace('.', '_') + result = result.replace('+', '_plus') + return result + + +class ResponseParserMixIn(object): + """Add this MixIn to a Command to enable it to parse response strings into + response data structures""" + + """ + Supplied parse methods are parse_colon_record() (the default) and + parse_colon_record_list(). Override the default in a derived class + by setting the "response_parser" field to the name of the desired + method. + """ + + def parse_colon_record(self, response, err): + """Parse records of key : value separated lines + + This expects response to be a string of newline separated + field/value pairs, with each field/value being separated by a + colon and optional whitespace. + + Records this parses look like this: + + Sensor Data Type : Blah + Somefield : Somevalue + + The type of the result returned and the conversion of key/values + in the text result to attribute names/values in the returned object + are determined by calling get_response_types on this command instance, + which gives a way for the result type and mapping to change based + on the contents of the response. + """ + result_type, mapping = self.get_response_types(response) + + if result_type == None: + return None + + obj = result_type() + line, sep, rest = response.partition('\n') + left_over = [] + while line != '': + colon_index = 10000000 + if line.find(':') != -1: + colon_index = line.index(':') + equal_index = 10000000 + if line.find('=') != -1: + equal_index = line.index('=') + if colon_index == 10000000 and equal_index == 10000000: + line, sep, rest = rest.partition('\n') + continue + + field_seperator = min([colon_index, equal_index]) + field = line[0:field_seperator].strip() + value = line[field_seperator + 1:].strip() + + field_info = mapping.get(field) + + if field_info == None: + left_over.append((field, value)) + line, sep, rest = rest.partition('\n') + continue + + lines_to_get = field_info.get('lines', 1) - 1 + while lines_to_get > 0: + line, sep, rest = rest.partition('\n') + value += '\n' + line + lines_to_get -= 1 + + self.field_to_objval(obj, field_info, field, value) + line, sep, rest = rest.partition('\n') + return obj + + + def parse_colon_record_list(self, response, err): + """Parse multiple groups of colon records + + Like colon records, but with multiple groups, each separated + by a blank line (two consecutive newline characters). + + This returns a list of result objects rather than a single + result object. The type of each result object can vary based + on its contents, so the list isn't always of the same type + of objects. + """ + results = [] + records = response.split('\n\n') + for record in records: + obj = self.parse_colon_record(record.strip(), err) + + if obj == None: + continue + + results.append(obj) + + return results + + + def parse_single_line(self, response, err): + obj = self.result_type() + attr_name = self.response_fields['attr'] + setattr(obj, attr_name, response.strip()) + return obj + + + def field_to_objval(self, obj, field_info, field_name, value): + """Assign a field's value to an attribute of obj + + Arguments: + obj -- the object to set the attribute on. this is some record type + object - the exact varies depending on the command being + executed. + field_info -- a dict describing the field. See "Field Info" below for + more info. + field_name -- the name of the field as given in the IPMI results. + this will be used as the name of the attribute unless a + 'attr' key/value is given in the field_info dict. + value -- the value of the field as given in the IPMI results. This + value will be assigned to the attribute unless a 'parser' + key/value is specified in the field_info dict + + Field Info: + If an 'attr' key/value is present, the value will be used for the + attribute name of this field instead of 'field_name'. + + If a 'parser' key/value is present, the value will be passed to + it, and the result will be assigned to the attribute. The default + parser is str(). + """ + str_func = lambda x: str(x) + attr_name = field_info.get('attr', field_to_attr(field_name)) + attr_parser = supplied_parser = field_info.get('parser', str_func) + + args, varargs, keywords, defaults = inspect.getargspec(attr_parser) + if keywords == None: + attr_parser = lambda x, **y: supplied_parser(x) + setattr(obj, attr_name, attr_parser(value, **field_info)) + + def get_response_types(self, response): + """Return the result type and field mappings + + The result type is the class of the result to be used. The field + mappings are given in self.response_fields, and are a + dict mapping field names to field info dicts. See 'field info' in + the doc for field_to_objval above. + + Arguments: + response -- the text of the command response. It's not used in + this base method, but might be used in a subclass's version of + this method to allow different result types and mappings to be + used based on the contents of the response. + """ + return self.result_type, self.response_fields + + def parse_response(self, out, err): + """Parse the response to a command + + Arguments: + out -- the text response of an IPMI command from stdout + err -- the text response of an IPMI command from stderr + """ + return self.response_parser(out, err) + + def parse_results(self, out, err): + """Parse the results if a result type is specified + + If there is not 'result_type' attribute for this this command, return + None. + """ + try: + result_type = self.result_type + except AttributeError: + return None + + return self.parse_response(out, err) + + def handle_command_error(self, out, err): + """Handle an error from running the command""" + raise IpmiError(err.strip()) + + response_parser = parse_colon_record |