diff options
author | Sheldon Sandbekkhaug <sheldon.sandbekkhaug@calxeda.com> | 2013-07-22 17:16:40 -0500 |
---|---|---|
committer | Sheldon Sandbekkhaug <sheldon.sandbekkhaug@calxeda.com> | 2013-07-22 17:16:40 -0500 |
commit | 26bf2b8a0248815e0430042617c73b2f1b7c9d4f (patch) | |
tree | 9c7ca611cd0d19c72a10044b9814cbb357350ebb | |
parent | befd8737f2c89e6ab74f79796f9520c2a82a90f0 (diff) | |
parent | f09a00207940c1413a7e3f5fe728479cf8f1f079 (diff) | |
download | cxmanage-26bf2b8a0248815e0430042617c73b2f1b7c9d4f.tar.gz |
Trivial merge
Merge branch 'master' of ssh://git.calxeda.com/var/git/cx_manage_util
-rw-r--r-- | cxmanage_api/cx_exceptions.py | 19 | ||||
-rw-r--r-- | cxmanage_api/fabric.py | 96 | ||||
-rw-r--r-- | cxmanage_api/node.py | 7 | ||||
-rw-r--r-- | cxmanage_api/tasks.py | 9 | ||||
-rw-r--r-- | cxmanage_api/ubootenv.py | 16 | ||||
-rw-r--r-- | cxmanage_test/fabric_test.py | 67 | ||||
-rw-r--r-- | cxmanage_test/node_test.py | 14 | ||||
-rwxr-xr-x | scripts/cxmanage | 4 | ||||
-rw-r--r-- | setup.py | 4 |
9 files changed, 203 insertions, 33 deletions
diff --git a/cxmanage_api/cx_exceptions.py b/cxmanage_api/cx_exceptions.py index b74a927..f390392 100644 --- a/cxmanage_api/cx_exceptions.py +++ b/cxmanage_api/cx_exceptions.py @@ -259,26 +259,25 @@ class InvalidImageError(Exception): return self.msg -class UnknownBootCmdError(Exception): - """Raised when the boot command is not: run bootcmd_pxe, run bootcmd_sata, - run bootcmd_mmc, setenv bootdevice, or reset. +class UbootenvError(Exception): + """Raised when the UbootEnv class fails to interpret the ubootenv + environment variables. - >>> from cxmanage_api.cx_exceptions import UnknownBootCmdError - >>> raise UnknownBootCmdError('My custom exception text!') + >>> from cxmanage_api.cx_exceptions import UbootenvError + >>> raise UbootenvError('My custom exception text!') Traceback (most recent call last): File "<stdin>", line 1, in <module> - cxmanage_api.cx_exceptions.UnknownBootCmdError: My custom exception text! + cxmanage_api.cx_exceptions.UbootenvError: My custom exception text! :param msg: Exceptions message and details to return to the user. :type msg: string - :raised: When the boot command is not: run bootcmd_pxe, run bootcmd_sata, - run bootcmd_mmc, setenv bootdevice, or reset. + :raised: When ubootenv settings are unrecognizable. """ def __init__(self, msg): - """Default constructor for the UnknownBootCmdError class.""" - super(UnknownBootCmdError, self).__init__() + """Default constructor for the UbootenvError class.""" + super(UbootenvError, self).__init__() self.msg = msg def __str__(self): diff --git a/cxmanage_api/fabric.py b/cxmanage_api/fabric.py index 2e140f5..7fc0192 100644 --- a/cxmanage_api/fabric.py +++ b/cxmanage_api/fabric.py @@ -56,6 +56,53 @@ class Fabric(object): :type node: `Node <node.html>`_ """ + class CompositeBMC(object): + """ Composite BMC object. Provides a mechanism to run BMC + commands in parallel across all nodes. + """ + + def __init__(self, fabric): + self.fabric = fabric + + def __getattr__(self, name): + """ If the underlying BMCs have a method by this name, then return + a callable function that does it in parallel across all nodes. + """ + nodes = self.fabric.nodes + task_queue = self.fabric.task_queue + + for node in nodes.values(): + if ((not hasattr(node.bmc, name)) or + (not hasattr(getattr(node.bmc, name), "__call__"))): + raise AttributeError( + "'CompositeBMC' object has no attribute '%s'" + % name + ) + + def function(*args, **kwargs): + """ Run the named BMC command in parallel across all nodes. """ + tasks = {} + for node_id, node in nodes.iteritems(): + tasks[node_id] = task_queue.put( + getattr(node.bmc, name), + *args, + **kwargs + ) + + results = {} + errors = {} + for node_id, task in tasks.items(): + task.join() + if task.status == "Completed": + results[node_id] = task.result + else: + errors[node_id] = task.error + if errors: + raise CommandFailedError(results, errors) + return results + + return function + def __init__(self, ip_address, username="admin", password="admin", tftp=None, ecme_tftp_port=5001, task_queue=None, verbose=False, node=None): @@ -68,6 +115,7 @@ class Fabric(object): self.task_queue = task_queue self.verbose = verbose self.node = node + self.cbmc = Fabric.CompositeBMC(self) self._nodes = {} @@ -183,10 +231,6 @@ class Fabric(object): 3: ['fc:2f:40:88:b3:6c', 'fc:2f:40:88:b3:6d', 'fc:2f:40:88:b3:6e'] } - :param async: Flag that determines if the command result (dictionary) - is returned or a Task object (can get status, etc.). - :type async: boolean - :return: The MAC addresses for each node. :rtype: dictionary @@ -792,6 +836,50 @@ class Fabric(object): self.primary_node.bmc.fabric_rm_macaddr(nodeid=nodeid, iface=iface, macaddr=macaddr) + def set_macaddr_base(self, macaddr): + """ Set a base MAC address for a custom range. + + >>> fabric.set_macaddr_base("66:55:44:33:22:11") + + :param macaddr: mac address base to use + :type macaddr: string + + """ + self.primary_node.bmc.fabric_config_set_macaddr_base(macaddr=macaddr) + + def get_macaddr_base(self): + """ Get the base MAC address for custom ranges. + + >>> fabric.get_macaddr_base() + '08:00:00:00:08:5c' + + :return: mac address base + :rtype: string + """ + return self.primary_node.bmc.fabric_config_get_macaddr_base() + + def set_macaddr_mask(self, mask): + """ Set MAC address mask for a custom range. + + >>> fabric.set_macaddr_mask("ff:ff:ff:ff:ff:00") + + :param macaddr: mac address mask to use + :type macaddr: string + + """ + self.primary_node.bmc.fabric_config_set_macaddr_mask(mask=mask) + + def get_macaddr_mask(self): + """ Get the MAC address mask for custom ranges. + + >>> fabric.get_macaddr_mask() + '08:00:00:00:08:5c' + + :return: mac address mask + :rtype: string + """ + return self.primary_node.bmc.fabric_config_get_macaddr_mask() + def get_linkspeed_policy(self): """Get the global linkspeed policy for the fabric. In the partition world this means the linkspeed for Configuration 0, Partition 0, diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 9edd672..b499f36 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -49,7 +49,8 @@ from cxmanage_api.ubootenv import UbootEnv as UBOOTENV from cxmanage_api.ip_retriever import IPRetriever as IPRETRIEVER from cxmanage_api.cx_exceptions import TimeoutError, NoSensorError, \ SocmanVersionError, FirmwareConfigError, PriorityIncrementError, \ - NoPartitionError, TransferFailure, ImageSizeError, PartitionInUseError + NoPartitionError, TransferFailure, ImageSizeError, \ + PartitionInUseError, UbootenvError class Node(object): @@ -723,7 +724,7 @@ class Node(object): ubootenv.set_pxe_interface(old_ubootenv.get_pxe_interface()) logger.info( - "Set boot order to " + old_ubootenv.get_boot_order() + "Set boot order to %s" % old_ubootenv.get_boot_order() ) filename = temp_file() @@ -739,7 +740,7 @@ class Node(object): "Done uploading ubootenv image to first " + \ "partition ('running partition')" ) - except (ValueError, Exception): + except (ValueError, UbootenvError): self._upload_image(image, running_part, priority) updated_partitions += [running_part, factory_part] diff --git a/cxmanage_api/tasks.py b/cxmanage_api/tasks.py index 6b5cfde..7ed7851 100644 --- a/cxmanage_api/tasks.py +++ b/cxmanage_api/tasks.py @@ -43,7 +43,7 @@ class Task(object): :type args: list """ - def __init__(self, method, *args): + def __init__(self, method, *args, **kwargs): """Default constructor for the Task class.""" self.status = "Queued" self.result = None @@ -51,6 +51,7 @@ class Task(object): self._method = method self._args = args + self._kwargs = kwargs self._finished = Event() def join(self): @@ -70,7 +71,7 @@ class Task(object): """Execute this task. Should only be called by TaskWorker.""" self.status = "In Progress" try: - self.result = self._method(*self._args) + self.result = self._method(*self._args, **self._kwargs) self.status = "Completed" except Exception as e: self.error = e @@ -96,7 +97,7 @@ class TaskQueue(object): self._queue = deque() self._workers = 0 - def put(self, method, *args): + def put(self, method, *args, **kwargs): """Add a task to the task queue, and spawn a worker if we're not full. :param method: Named method to run. @@ -110,7 +111,7 @@ class TaskQueue(object): """ self._lock.acquire() - task = Task(method, *args) + task = Task(method, *args, **kwargs) self._queue.append(task) if self._workers < self.threads: diff --git a/cxmanage_api/ubootenv.py b/cxmanage_api/ubootenv.py index 12d550d..cd1a35a 100644 --- a/cxmanage_api/ubootenv.py +++ b/cxmanage_api/ubootenv.py @@ -33,7 +33,7 @@ import struct from cxmanage_api.simg import has_simg, get_simg_contents from cxmanage_api.crc32 import get_crc32 -from cxmanage_api.cx_exceptions import UnknownBootCmdError +from cxmanage_api.cx_exceptions import UbootenvError ENVIRONMENT_SIZE = 8192 @@ -87,7 +87,7 @@ class UbootEnv: :raises ValueError: If an invalid boot device is specified. :raises ValueError: If 'retry' and 'reset' args are used together. - :raises Exception: If the u-boot environment is unrecognized + :raises UbootenvError: If the u-boot environment is unrecognized """ validate_boot_args(boot_args) @@ -103,7 +103,7 @@ class UbootEnv: elif all(x in self.variables for x in UBOOTENV_V2_VARIABLES): version = 2 else: - raise Exception("Unrecognized u-boot environment") + raise UbootenvError("Unrecognized u-boot environment") for arg in boot_args: if arg == "retry": @@ -159,7 +159,7 @@ class UbootEnv: :returns: Boot order for this U-Boot Environment. :rtype: string - :raises UnknownBootCmdError: If a boot command is unrecognized. + :raises UbootenvError: If a boot command is unrecognized. """ boot_args = [] @@ -171,7 +171,7 @@ class UbootEnv: elif target == "scsi": boot_args.append("disk") else: - raise UnknownBootCmdError("Unrecognized boot target: %s" + raise UbootenvError("Unrecognized boot target: %s" % target) else: if "bootcmd_default" in self.variables: @@ -198,7 +198,7 @@ class UbootEnv: boot_args.append("reset") break else: - raise UnknownBootCmdError("Unrecognized boot command: %s" + raise UbootenvError("Unrecognized boot command: %s" % command) if retry: @@ -249,7 +249,7 @@ class UbootEnv: :returns: Boot order for this U-Boot Environment. :rtype: string - :raises Exception: If the u-boot environment value is not recognized. + :raises ValueError: If the u-boot environment value is not recognized. """ @@ -264,7 +264,7 @@ class UbootEnv: elif (xgmac == "xgmac1"): return "eth1" else: - raise Exception("Unrecognized value for ethprime") + raise ValueError("Unrecognized value for ethprime") else: return "eth0" diff --git a/cxmanage_test/fabric_test.py b/cxmanage_test/fabric_test.py index 0dd2593..f2720c9 100644 --- a/cxmanage_test/fabric_test.py +++ b/cxmanage_test/fabric_test.py @@ -359,6 +359,73 @@ class FabricTest(unittest.TestCase): bmc = self.fabric.primary_node.bmc self.assertIn ('fabric_rm_macaddr', bmc.executed) + def test_set_macaddr_base(self): + """Test the set_macaddr_base method""" + self.fabric.set_macaddr_base("00:11:22:33:44:55") + for node in self.fabric.nodes.values(): + if node == self.fabric.primary_node: + self.assertEqual( + node.bmc.executed, + [("fabric_config_set_macaddr_base", "00:11:22:33:44:55")] + ) + else: + self.assertEqual(node.bmc.executed, []) + + def test_get_macaddr_base(self): + """Test the get_macaddr_base method""" + self.assertEqual(self.fabric.get_macaddr_base(), "00:00:00:00:00:00") + for node in self.fabric.nodes.values(): + if node == self.fabric.primary_node: + self.assertEqual( + node.bmc.executed, + ["fabric_config_get_macaddr_base"] + ) + else: + self.assertEqual(node.bmc.executed, []) + + def test_set_macaddr_mask(self): + """Test the set_macaddr_mask method""" + self.fabric.set_macaddr_mask("00:11:22:33:44:55") + for node in self.fabric.nodes.values(): + if node == self.fabric.primary_node: + self.assertEqual( + node.bmc.executed, + [("fabric_config_set_macaddr_mask", "00:11:22:33:44:55")] + ) + else: + self.assertEqual(node.bmc.executed, []) + + def test_get_macaddr_mask(self): + """Test the get_macaddr_mask method""" + self.assertEqual(self.fabric.get_macaddr_mask(), "00:00:00:00:00:00") + for node in self.fabric.nodes.values(): + if node == self.fabric.primary_node: + self.assertEqual( + node.bmc.executed, + ["fabric_config_get_macaddr_mask"] + ) + else: + self.assertEqual(node.bmc.executed, []) + + def test_composite_bmc(self): + """ Test the CompositeBMC member """ + with self.assertRaises(AttributeError): + self.fabric.cbmc.fake_method + + self.fabric.cbmc.set_chassis_power("off") + results = self.fabric.cbmc.get_chassis_status() + + self.assertEqual(len(results), len(self.fabric.nodes)) + for node_id in self.fabric.nodes: + self.assertFalse(results[node_id].power_on) + + for node in self.fabric.nodes.values(): + self.assertEqual(node.bmc.executed, [ + ("set_chassis_power", "off"), + "get_chassis_status" + ]) + + class DummyNode(object): """ Dummy node for the nodemanager tests """ def __init__(self, ip_address, username="admin", password="admin", diff --git a/cxmanage_test/node_test.py b/cxmanage_test/node_test.py index 4f843a7..b6f860b 100644 --- a/cxmanage_test/node_test.py +++ b/cxmanage_test/node_test.py @@ -847,6 +847,20 @@ class DummyBMC(LanBMC): self.fabric_lu_factor = lu_factor self.executed.append('fabric_config_set_link_users_factor') + def fabric_config_set_macaddr_base(self, macaddr): + self.executed.append(('fabric_config_set_macaddr_base', macaddr)) + + def fabric_config_get_macaddr_base(self): + self.executed.append('fabric_config_get_macaddr_base') + return "00:00:00:00:00:00" + + def fabric_config_set_macaddr_mask(self, mask): + self.executed.append(('fabric_config_set_macaddr_mask', mask)) + + def fabric_config_get_macaddr_mask(self): + self.executed.append('fabric_config_get_macaddr_mask') + return "00:00:00:00:00:00" + def fabric_add_macaddr(self, nodeid=0, iface=0, macaddr=None): self.executed.append('fabric_add_macaddr') diff --git a/scripts/cxmanage b/scripts/cxmanage index 889fe4e..ccde835 100755 --- a/scripts/cxmanage +++ b/scripts/cxmanage @@ -50,8 +50,8 @@ from cxmanage.commands.ipdiscover import ipdiscover_command from cxmanage.commands.tspackage import tspackage_command -PYIPMI_VERSION = '0.7.1' -IPMITOOL_VERSION = '1.8.11.0-cx5' +PYIPMI_VERSION = '0.8.0' +IPMITOOL_VERSION = '1.8.11.0-cx7' PARSER_EPILOG = """examples: @@ -33,7 +33,7 @@ from setuptools import setup setup( name='cxmanage', - version='0.8.2', + version='0.9.0', packages=['cxmanage', 'cxmanage.commands', 'cxmanage_api'], scripts=['scripts/cxmanage', 'scripts/sol_tabs'], description='Calxeda Management Utility', @@ -42,7 +42,7 @@ setup( install_requires=[ 'tftpy', 'pexpect', - 'pyipmi>=0.7.1', + 'pyipmi>=0.8.0', 'argparse', ], extras_require={ |