diff options
-rw-r--r-- | cxmanage_api/cx_exceptions.py | 26 | ||||
-rw-r--r-- | cxmanage_api/fabric.py | 81 | ||||
-rw-r--r-- | cxmanage_api/loggers.py | 8 | ||||
-rw-r--r-- | cxmanage_api/node.py | 46 | ||||
-rwxr-xr-x | scripts/cxmanage | 4 |
5 files changed, 113 insertions, 52 deletions
diff --git a/cxmanage_api/cx_exceptions.py b/cxmanage_api/cx_exceptions.py index 5e0c931..7ea2fe7 100644 --- a/cxmanage_api/cx_exceptions.py +++ b/cxmanage_api/cx_exceptions.py @@ -296,6 +296,32 @@ class InvalidImageError(Exception): return self.msg +class NodeMismatchError(Exception): + """Raised when a node that is supposed to be an updated version of the + current node is not. + + >>> from cxmanage_api.cx_exceptions import NodeMismatchError + >>> raise InvalidImageError('My custom exception text!') + Traceback (most recent call last): + File "<stdin>", line 1, in <module> + cxmanage_api.cx_exceptions.InvalidImageError: My custom exception text! + + :param msg: Exceptions message and details to return to the user. + :type msg: string + :raised: When a node passed in to refresh() another node does not match. + + """ + + def __init__(self, msg): + """Default constructor for the NodeMismatchError class.""" + super(NodeMismatchError, self).__init__() + self.msg = msg + + def __str__(self): + """String representation of this Exception class.""" + return self.msg + + class UbootenvError(Exception): """Raised when the UbootEnv class fails to interpret the ubootenv environment variables. diff --git a/cxmanage_api/fabric.py b/cxmanage_api/fabric.py index 57b2957..4a84743 100644 --- a/cxmanage_api/fabric.py +++ b/cxmanage_api/fabric.py @@ -40,7 +40,7 @@ from cxmanage_api.tftp import InternalTftp from cxmanage_api.node import Node as NODE from cxmanage_api.credentials import Credentials from cxmanage_api.cx_exceptions import CommandFailedError, TimeoutError, \ - IpmiError, TftpException, ParseError + IpmiError, TftpException, ParseError # pylint: disable=R0902,R0903, R0904 @@ -215,42 +215,48 @@ class Fabric(object): def get_nodes(): """Returns a dictionary of nodes reported by the primary node IP""" new_nodes = {} - node = self.node( + root_node = self.node( ip_address=self.ip_address, credentials=self.credentials, tftp=self.tftp, ecme_tftp_port=self.ecme_tftp_port, verbose=self.verbose ) - ipinfo = node.get_fabric_ipinfo() - for node_id, node_address in ipinfo.iteritems(): - new_nodes[node_id] = self.node( + ipinfo = root_node.get_fabric_ipinfo() + for node_id, node_address in ipinfo.items(): + node = self.node( ip_address=node_address, credentials=self.credentials, tftp=self.tftp, ecme_tftp_port=self.ecme_tftp_port, verbose=self.verbose ) - new_nodes[node_id].node_id = node_id + node.node_id = node_id + new_nodes[node.guid] = node return new_nodes initial_node_count = len(self._nodes) - self._nodes = {} + old_nodes = {node.guid: node for node in self._nodes.values()} if wait: deadline = time.time() + timeout while time.time() < deadline: try: - self._nodes = get_nodes() - if len(self._nodes) >= initial_node_count: - # make sure the nodes respond to an IPMI command - self._run_on_all_nodes(False, "get_power") + new_nodes = get_nodes() + if len(new_nodes) >= initial_node_count: break except (IpmiError, TftpException, ParseError): pass else: raise TimeoutError( "Fabric refresh timed out. Rediscovered %i of %i nodes" - % (len(self._nodes), initial_node_count) + % (len(new_nodes), initial_node_count) ) else: - self._nodes = get_nodes() + new_nodes = get_nodes() + + for guid, node in new_nodes.items(): + if guid in old_nodes: + old_nodes[guid].refresh(node) + new_nodes[guid] = old_nodes[guid] + + self._nodes = {node.node_id: node for node in new_nodes.values()} def get_mac_addresses(self): """Gets MAC addresses from all nodes. @@ -330,9 +336,15 @@ class Fabric(object): 'fabric_config_get_networks' ) regex = re.compile('\d+ Network (\w+), private=(\d)') - for line in open(filename, 'r').readlines(): - name, private = regex.findall(line)[0] - results[name] = (int(private) != 0) + contents = open(filename, 'r').readlines() + for line in contents: + try: + name, private = regex.findall(line)[0] + results[name] = (int(private) != 0) + except IndexError: + raise CommandFailedException( + 'Unable to parse networks: %s' % '\n'.join(contents) + ) return results @@ -598,6 +610,27 @@ class Fabric(object): """ return self._run_on_all_nodes(async, "get_sensors", search) + def get_uplink_status(self): + """Get the uplink status for this node + + >>> node.get_uplink_status() + {0: True, 1: False, 2: True, 3: True} + + :return: A dictionary mapping uplink to status + :rtype: dict + + """ + results = {} + uplink_status = self.primary_node.bmc.fabric_get_uplink_status() + regex = re.compile(r'U(\d+)\(N\d+\) (\w+):') + for uplink, status in regex.findall(uplink_status): + if(status == 'Good'): + results[uplink] = True + else: + results[uplink] = False + + return results + def get_firmware_info(self, async=False): """Gets the firmware info from all nodes. @@ -659,7 +692,7 @@ class Fabric(object): return self._run_on_all_nodes(async, "get_firmware_info_dict") def is_updatable(self, package, partition_arg="INACTIVE", priority=None, - async=False): + async=False): """Checks to see if all nodes can be updated with this fw package. >>> fabric.is_updatable(package=fwpkg) @@ -681,10 +714,10 @@ class Fabric(object): """ return self._run_on_all_nodes(async, "is_updatable", package, - partition_arg, priority) + partition_arg, priority) def update_firmware(self, package, partition_arg="INACTIVE", - priority=None, async=False): + priority=None, async=False): """Updates the firmware on all nodes. >>> fabric.update_firmware(package=fwpkg) @@ -700,7 +733,7 @@ class Fabric(object): :type async: boolean """ self._run_on_all_nodes(async, "update_firmware", package, - partition_arg, priority) + partition_arg, priority) def config_reset(self, async=False): """Resets the configuration on all nodes to factory defaults. @@ -874,7 +907,7 @@ class Fabric(object): """ return self._run_on_all_nodes(asynchronous, "ipmitool_command", - ipmitool_args) + ipmitool_args) def get_ubootenv(self, async=False): """Gets the u-boot environment from all nodes. @@ -1016,7 +1049,7 @@ class Fabric(object): """ self.primary_node.bmc.fabric_add_macaddr(nodeid=nodeid, iface=iface, - macaddr=macaddr) + macaddr=macaddr) def rm_macaddr(self, nodeid, iface, macaddr): """Remove a macaddr to a node/interface in the fabric. @@ -1032,7 +1065,7 @@ class Fabric(object): """ self.primary_node.bmc.fabric_rm_macaddr(nodeid=nodeid, iface=iface, - macaddr=macaddr) + macaddr=macaddr) def set_macaddr_base(self, macaddr): """ Set a base MAC address for a custom range. @@ -1160,7 +1193,7 @@ class Fabric(object): """ self.primary_node.bmc.fabric_config_set_uplink(uplink=uplink, - iface=iface) + iface=iface) def get_link_stats(self, link=0, async=False): """Get the link_stats for each node in the fabric. diff --git a/cxmanage_api/loggers.py b/cxmanage_api/loggers.py index 1e4f4ab..26edf21 100644 --- a/cxmanage_api/loggers.py +++ b/cxmanage_api/loggers.py @@ -61,7 +61,7 @@ class Logger(object): the write() method so that it writes message in the appropriate manner. >>> # To use this class for inheritance ... - >>> from cx_automation.loggers import Logger + >>> from cxmanage_api.loggers import Logger >>> :param log_level: Verbosity level of logging for this logger. @@ -193,7 +193,7 @@ class StandardOutLogger(Logger): Only the write method has to be implemented. >>> # Typical instantiation ... - >>> from cx_automation.loggers import StandardOutLogger + >>> from cxmanage_api.loggers import StandardOutLogger >>> logger = StandardOutLogger() @@ -287,13 +287,13 @@ class FileLogger(Logger): class CompositeLogger(object): """Takes a list of loggers and writes the same output to them all. - >>> from cx_automation.loggers import StandardOutLogger, FileLogger + >>> from cxmanage_api.loggers import StandardOutLogger, FileLogger >>> # Let's say you want to log to a file while also seeing the output. >>> # Create a StandardOutLogger to 'see' output. >>> slogger = StandarOutLogger(...) >>> # Create a FileLogger to log to a file. >>> flogger = FileLogger(...) - >>> from cx_automation.loggers import CompositeLogger + >>> from cxmanage_api.loggers import CompositeLogger >>> # Create a composite logger and you can log to both simultaneously! >>> logger = CompositeLogger(loggers=[slogger, flogger]) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index d88207a..f247495 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -55,7 +55,8 @@ from cxmanage_api.credentials import Credentials from cxmanage_api.cx_exceptions import TimeoutError, NoSensorError, \ SocmanVersionError, FirmwareConfigError, PriorityIncrementError, \ NoPartitionError, TransferFailure, ImageSizeError, \ - PartitionInUseError, UbootenvError, EEPROMUpdateError, ParseError + PartitionInUseError, UbootenvError, EEPROMUpdateError, ParseError, \ + NodeMismatchError # pylint: disable=R0902, R0904 @@ -180,6 +181,24 @@ class Node(object): """ self._node_id = value + def refresh(self, new_node): + """ Updates mutable properties for this node, based from another node + object. + + :param updated_node: The node we want to update from. + :type updated_node: Node + + :raises NodeMismatchError: If the passed in node does not match this + node. + + """ + if not isinstance(new_node, Node) or not new_node.guid == self.guid: + raise NodeMismatchError( + 'Passed in node does not match node to be updated' + ) + self.ip_address = new_node.ip_address + self.node_id = new_node.node_id + def get_mac_addresses(self): """Gets a dictionary of MAC addresses for this node. The dictionary maps each port/interface to a list of MAC addresses for that interface. @@ -1188,13 +1207,13 @@ communication. try: node_id = int(node_id) except ValueError: - pass # may be a physical node ID, "0.0" for example + pass # may be a physical node ID, "0.0" for example ip_address = elements[2] except IndexError: raise ParseError("Failed to parse ipinfo\n%s" % contents) try: - socket.inet_aton(ip_address) # IP validity check + socket.inet_aton(ip_address) # IP validity check except socket.error: if allow_errors: continue @@ -1587,27 +1606,6 @@ obtained. return results - def get_uplink_status(self): - """Get the uplink status for this node - - >>> node.get_uplink_status() - {0: True, 1: False, 2: True, 3: True} - - :return: A dictionary mapping uplink to status - :rtype: dict - - """ - results = {} - uplink_status = self.bmc.fabric_get_uplink_status() - regex = re.compile(r'U(\d+)\(N\d+\) (\w+):') - for uplink, status in regex.findall(uplink_status): - if(status == 'Good'): - results[uplink] = True - else: - results[uplink] = False - - return results - def read_fru(self, fru_number, offset=0, bytes_to_read=-1): """Read from node's fru starting at offset. This is equivalent to the ipmitool fru read command. diff --git a/scripts/cxmanage b/scripts/cxmanage index 072e03f..c9e64f8 100755 --- a/scripts/cxmanage +++ b/scripts/cxmanage @@ -85,6 +85,10 @@ FWUPDATE_IMAGE_TYPES = ['PACKAGE'] + sorted([ 'BOOT_LOG', 'UEFI_ENV', 'DIAG_ELF', + 'XACTION_LOG', + 'ROM_INFO', + 'PARTITION_TABLE', + 'LUAMAN', ]) EEPROMUPDATE_EPILOG = """examples: |