summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cxmanage_api/cx_exceptions.py26
-rw-r--r--cxmanage_api/fabric.py81
-rw-r--r--cxmanage_api/loggers.py8
-rw-r--r--cxmanage_api/node.py46
-rwxr-xr-xscripts/cxmanage4
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: