From 12e3327869537036bb42a147aa3d53f8d7bb6075 Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Fri, 19 Jul 2013 12:56:18 -0500 Subject: Add function to get uplink speed of a node via pyipmi's bmc --- cxmanage_api/node.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index be89335..8055dd4 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1418,6 +1418,16 @@ class Node(object): iface=iface ) + def get_uplink_speed(self): + """Get the uplink speed of this node. + >>> node.get_uplink_speed() + 1 + + :return: The uplink speed of this node, in Gbps + :rtype: integer + + """ + return self.bmc.fabric_config_get_uplink_speed() def _run_fabric_command(self, function_name, **kwargs): """Handles the basics of sending a node a command for fabric data.""" -- cgit v1.2.1 From b01bcc0d815f11e4e0bd1c6466f0afa79c54d260 Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Fri, 19 Jul 2013 13:10:18 -0500 Subject: Add a function to get the uplink speed for every node in the fabric --- cxmanage_api/fabric.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cxmanage_api/fabric.py b/cxmanage_api/fabric.py index 9f4dd76..5b3e5e2 100644 --- a/cxmanage_api/fabric.py +++ b/cxmanage_api/fabric.py @@ -214,6 +214,22 @@ class Fabric(object): """ return self.primary_node.get_fabric_uplink_info() + def get_uplink_speed(self, async=False): + """Gets the uplink speed of every node in the fabric. + + >>> fabric.get_uplink_speed() + {0: 1, 1: 0, 2: 0, 3: 0} + + :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 uplink info for each node. + :rtype: dictionary + + """ + return self._run_on_all_nodes(async, "get_uplink_speed") + def get_power(self, async=False): """Returns the power status for all nodes. -- cgit v1.2.1 From f5628b1b260167d6823e0a5c0839d516c4a95914 Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Mon, 22 Jul 2013 10:41:43 -0500 Subject: Modified get_uplink_speed() to use the new bmc function --- cxmanage_api/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 8055dd4..186c149 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1427,7 +1427,7 @@ class Node(object): :rtype: integer """ - return self.bmc.fabric_config_get_uplink_speed() + return self.bmc.fabric_get_uplink_speed() def _run_fabric_command(self, function_name, **kwargs): """Handles the basics of sending a node a command for fabric data.""" -- cgit v1.2.1 From befd8737f2c89e6ab74f79796f9520c2a82a90f0 Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Mon, 22 Jul 2013 14:51:04 -0500 Subject: Add a function to get uplink info through cxmanage_api Added a function to get uplink info through node.py Changed fabric.get_uplink_info() to use node.py's new function (node.get_fabric_uplink_info() is untouched) --- cxmanage_api/fabric.py | 14 ++++++-------- cxmanage_api/node.py | 11 +++++++++++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/cxmanage_api/fabric.py b/cxmanage_api/fabric.py index 5b3e5e2..2e140f5 100644 --- a/cxmanage_api/fabric.py +++ b/cxmanage_api/fabric.py @@ -193,16 +193,14 @@ class Fabric(object): """ return self.primary_node.get_fabric_macaddrs() - def get_uplink_info(self): + def get_uplink_info(self, async=False): """Gets the fabric uplink info. >>> fabric.get_uplink_info() - { - 0: {0: 0, 1: 0, 2: 0} - 1: {0: 0, 1: 0, 2: 0} - 2: {0: 0, 1: 0, 2: 0} - 3: {0: 0, 1: 0, 2: 0} - } + {0: 'Node 0: eth0 0, eth1 0, mgmt 0', + 1: 'Node 1: eth0 0, eth1 0, mgmt 0', + 2: 'Node 2: eth0 0, eth1 0, mgmt 0', + 3: 'Node 3: eth0 0, eth1 0, mgmt 0'} :param async: Flag that determines if the command result (dictionary) is returned or a Task object (can get status, etc.). @@ -212,7 +210,7 @@ class Fabric(object): :rtype: dictionary """ - return self.primary_node.get_fabric_uplink_info() + return self._run_on_all_nodes(async, "get_uplink_info") def get_uplink_speed(self, async=False): """Gets the uplink speed of every node in the fabric. diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 186c149..9edd672 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1429,6 +1429,17 @@ class Node(object): """ return self.bmc.fabric_get_uplink_speed() + def get_uplink_info(self): + """Get the uplink information for this node. + >>> node.get_uplink_info() + 'Node 0: eth0 0, eth1 0, mgmt 0' + + :return: The uplink information for this node + :rtype: string + + """ + return self.bmc.fabric_get_uplink_info().strip() + def _run_fabric_command(self, function_name, **kwargs): """Handles the basics of sending a node a command for fabric data.""" filename = temp_file() -- cgit v1.2.1 From 5db2e3c8ce78140d8a8c190e841781291ecbdb40 Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Fri, 26 Jul 2013 14:51:13 -0500 Subject: (CXMAN-197) Change A9boot Name for Midway Added a function to determine whether a node is running on Highbank or Midway based on firmware version (This method of checking should change to something better in the future) Moved the COMPONENTS from __init__ and made it a function in node.py Changed all references to COMPONENTS to correctly call node.get_components() node.get_components() returns "A9" or "A15" in the list depending on which chip the node is using. --- cxmanage/__init__.py | 13 ---------- cxmanage/commands/info.py | 5 ++-- cxmanage/commands/tspackage.py | 6 ++--- cxmanage_api/node.py | 55 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 18 deletions(-) diff --git a/cxmanage/__init__.py b/cxmanage/__init__.py index e2d416a..50b760a 100644 --- a/cxmanage/__init__.py +++ b/cxmanage/__init__.py @@ -322,16 +322,3 @@ def _print_command_status(tasks, counter): dots = "".join(["." for x in range(counter % 4)]).ljust(3) sys.stdout.write(message % (successes, errors, nodes_left, dots)) sys.stdout.flush() - - -# These are needed for ipinfo and whenever version information is printed -COMPONENTS = [ - ("ecme_version", "ECME version"), - ("cdb_version", "CDB version"), - ("stage2_version", "Stage2boot version"), - ("bootlog_version", "Bootlog version"), - ("a9boot_version", "A9boot version"), - ("uboot_version", "Uboot version"), - ("ubootenv_version", "Ubootenv version"), - ("dtb_version", "DTB version") -] diff --git a/cxmanage/commands/info.py b/cxmanage/commands/info.py index b1a03c0..1c307c6 100644 --- a/cxmanage/commands/info.py +++ b/cxmanage/commands/info.py @@ -29,7 +29,6 @@ # DAMAGE. from cxmanage import get_tftp, get_nodes, get_node_strings, run_command -from cxmanage import COMPONENTS def info_command(args): @@ -42,7 +41,6 @@ def info_command(args): def info_basic_command(args): """Print basic info""" - components = COMPONENTS tftp = get_tftp(args) nodes = get_nodes(args, tftp) @@ -56,6 +54,9 @@ def info_basic_command(args): for node in nodes: if node in results: result = results[node] + # Get mappings between attributes and formatted strings + components = node.get_components() + print "[ Info from %s ]" % node_strings[node] print "Hardware version : %s" % result.hardware_version print "Firmware version : %s" % result.firmware_version diff --git a/cxmanage/commands/tspackage.py b/cxmanage/commands/tspackage.py index 1a37e6e..744479a 100644 --- a/cxmanage/commands/tspackage.py +++ b/cxmanage/commands/tspackage.py @@ -10,7 +10,6 @@ import tarfile import tempfile from cxmanage import get_tftp, get_nodes, run_command -from cxmanage import COMPONENTS def tspackage_command(args): @@ -93,8 +92,6 @@ def write_version_info(args, nodes): """ info_results, _ = run_command(args, nodes, "get_versions") - # This will be used when writing version info to file - components = COMPONENTS for node in nodes: lines = [] # The lines of text to write to file @@ -118,6 +115,9 @@ def write_version_info(args, nodes): "Firmware version : %s" % info_result.firmware_version ) + + # Get mappings between attributes and formatted strings + components = node.get_components() for var, description in components: if hasattr(info_result, var): version = getattr(info_result, var) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index b499f36..46f5562 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1672,4 +1672,59 @@ class Node(object): "Unable to increment SIMG priority, too high") return priority + def get_chip_name(self): + """Returns the name of the "server-side" chip used by this node. + + >>> node.get_chip_name + 'Highbank' + + :rtype: string + + Currently we check the firmware version to determine whether the chip + used by this node is Highbank or Midway. + + """ + versions = self.get_versions() + fwversion = versions.firmware_version + + if "1000" in fwversion: + return "Highbank" + elif "2000" in fwversion: + return "Midway" + else: + # Cannot tell chip from firmware version; default to Highbank + return "Highbank" + + def get_components(self): + """Get a list of tuples that map InfoBasicResult object attributes to + nicely-formatted strings. + + :rtype: list of tuples + + The first item in a tuple is the name of an attribute of a + pyipmi.info.InfoBasicResult object. The second item is a + human-readable, ready-to-print string describing that attribute. + + """ + components = [] + components.append(("ecme_version", "ECME version")) + components.append(("cdb_version", "CDB version")) + components.append(("stage2_version", "Stage2boot version")) + components.append(("bootlog_version", "Bootlog version")) + if self.get_chip_name() == "Highbank": + components.append( + ("a9boot_version", "A9boot version") + ) + elif self.get_chip_name() == "Midway": + # InfoBasicResult objects still reference the A15 as A9 + components.append( + ("a9boot_version", "A15boot version") + ) + components.append(("uboot_version", "Uboot version")) + components.append(("ubootenv_version", "Ubootenv version")) + components.append(("dtb_version", "DTB version")) + + return components + + # End of file: ./node.py -- cgit v1.2.1 From f965e13b5363e89367f7b73da43c57122b21d15f Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Mon, 29 Jul 2013 11:08:30 -0500 Subject: (CXMAN-197) Change A9 Boot Name for Midway Moved COMPONENTS back to cxmanage/__init__.py Changed info.py and tspackage to reference the correct COMPONENTS Removed get_components() from node.py node.get_versions() changes the a#_boot string rather than get_components() --- cxmanage/__init__.py | 15 ++++++++++ cxmanage/commands/info.py | 5 +++- cxmanage/commands/tspackage.py | 4 +-- cxmanage_api/node.py | 67 ++++++++++++++---------------------------- 4 files changed, 43 insertions(+), 48 deletions(-) diff --git a/cxmanage/__init__.py b/cxmanage/__init__.py index 50b760a..b34761a 100644 --- a/cxmanage/__init__.py +++ b/cxmanage/__init__.py @@ -322,3 +322,18 @@ def _print_command_status(tasks, counter): dots = "".join(["." for x in range(counter % 4)]).ljust(3) sys.stdout.write(message % (successes, errors, nodes_left, dots)) sys.stdout.flush() + + +# These are needed for ipinfo and whenever version information is printed +COMPONENTS = [ + ("ecme_version", "ECME version"), + ("cdb_version", "CDB version"), + ("stage2_version", "Stage2boot version"), + ("bootlog_version", "Bootlog version"), + ("a9boot_version", "A9boot version"), + ("a15boot_version", "A15boot version"), + ("uboot_version", "Uboot version"), + ("ubootenv_version", "Ubootenv version"), + ("dtb_version", "DTB version") +] + diff --git a/cxmanage/commands/info.py b/cxmanage/commands/info.py index 1c307c6..531c939 100644 --- a/cxmanage/commands/info.py +++ b/cxmanage/commands/info.py @@ -28,7 +28,9 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. + from cxmanage import get_tftp, get_nodes, get_node_strings, run_command +from cxmanage import COMPONENTS def info_command(args): @@ -55,11 +57,12 @@ def info_basic_command(args): if node in results: result = results[node] # Get mappings between attributes and formatted strings - components = node.get_components() + components = COMPONENTS print "[ Info from %s ]" % node_strings[node] print "Hardware version : %s" % result.hardware_version print "Firmware version : %s" % result.firmware_version + # var is the variable, string is the printable string of var for var, string in components: if hasattr(result, var): version = getattr(result, var) diff --git a/cxmanage/commands/tspackage.py b/cxmanage/commands/tspackage.py index 744479a..aab1886 100644 --- a/cxmanage/commands/tspackage.py +++ b/cxmanage/commands/tspackage.py @@ -9,7 +9,7 @@ import shutil import tarfile import tempfile -from cxmanage import get_tftp, get_nodes, run_command +from cxmanage import get_tftp, get_nodes, run_command, COMPONENTS def tspackage_command(args): @@ -117,7 +117,7 @@ def write_version_info(args, nodes): ) # Get mappings between attributes and formatted strings - components = node.get_components() + components = COMPONENTS for var, description in components: if hasattr(info_result, var): version = getattr(info_result, var) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 46f5562..e79e039 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -930,15 +930,24 @@ class Node(object): """ result = self.bmc.get_info_basic() - fwinfo = self.get_firmware_info() - components = [("cdb_version", "CDB"), - ("stage2_version", "S2_ELF"), - ("bootlog_version", "BOOT_LOG"), - ("a9boot_version", "A9_EXEC"), - ("uboot_version", "A9_UBOOT"), - ("ubootenv_version", "UBOOTENV"), - ("dtb_version", "DTB")] + + # components maps variables to firmware partition types + components = [ + ("cdb_version", "CDB"), + ("stage2_version", "S2_ELF"), + ("bootlog_version", "BOOT_LOG") + ] + # Use A9 or A15 if on Highbank or Midway, respectively + if self.get_chip_name() == "Highbank": + components.append(("a9boot_version", "A9_EXEC")) + elif self.get_chip_name() == "Midway": + # The BMC (and fwinfo) still reference the A15 as an A9 + components.append(("a15boot_version", "A9_EXEC")) + components.append(("uboot_version", "A9_UBOOT")) + components.append(("ubootenv_version", "UBOOTENV")) + components.append(("dtb_version", "DTB")) + for var, ptype in components: try: partition = self._get_partition(fwinfo, ptype, "ACTIVE") @@ -1684,47 +1693,15 @@ class Node(object): used by this node is Highbank or Midway. """ - versions = self.get_versions() - fwversion = versions.firmware_version + result = self.bmc.get_info_basic() + fw_version = result.firmware_version - if "1000" in fwversion: + if fw_version.startswith("ECX-1000"): return "Highbank" - elif "2000" in fwversion: + elif fw_version.startswith("ECX-2000"): return "Midway" else: - # Cannot tell chip from firmware version; default to Highbank - return "Highbank" - - def get_components(self): - """Get a list of tuples that map InfoBasicResult object attributes to - nicely-formatted strings. - - :rtype: list of tuples - - The first item in a tuple is the name of an attribute of a - pyipmi.info.InfoBasicResult object. The second item is a - human-readable, ready-to-print string describing that attribute. - - """ - components = [] - components.append(("ecme_version", "ECME version")) - components.append(("cdb_version", "CDB version")) - components.append(("stage2_version", "Stage2boot version")) - components.append(("bootlog_version", "Bootlog version")) - if self.get_chip_name() == "Highbank": - components.append( - ("a9boot_version", "A9boot version") - ) - elif self.get_chip_name() == "Midway": - # InfoBasicResult objects still reference the A15 as A9 - components.append( - ("a9boot_version", "A15boot version") - ) - components.append(("uboot_version", "Uboot version")) - components.append(("ubootenv_version", "Ubootenv version")) - components.append(("dtb_version", "DTB version")) - - return components + return "Unknown" # End of file: ./node.py -- cgit v1.2.1 From 88a2f7931baaf5eec666f9d35f2dd97c33884f8b Mon Sep 17 00:00:00 2001 From: "matthew.hodgins" Date: Thu, 1 Aug 2013 12:17:40 -0500 Subject: added get_wafer_id to DummyNode --- cxmanage_test/fabric_test.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cxmanage_test/fabric_test.py b/cxmanage_test/fabric_test.py index f2720c9..3333444 100644 --- a/cxmanage_test/fabric_test.py +++ b/cxmanage_test/fabric_test.py @@ -44,6 +44,7 @@ from pyipmi import make_bmc NUM_NODES = 128 ADDRESSES = ["192.168.100.%i" % x for x in range(1, NUM_NODES + 1)] + class FabricTest(unittest.TestCase): """ Test the various Fabric commands """ def setUp(self): @@ -301,7 +302,7 @@ class FabricTest(unittest.TestCase): maps = self.fabric.get_routing_table() for nn, node in self.fabric.nodes.items(): self.assertIn('get_routing_table', node.executed) - + def test_get_depth_chart(self): """Test the depth_chart method""" maps = self.fabric.get_depth_chart() @@ -428,6 +429,9 @@ class FabricTest(unittest.TestCase): class DummyNode(object): """ Dummy node for the nodemanager tests """ + + wafer_id_unique = 0 + def __init__(self, ip_address, username="admin", password="admin", tftp=None, *args, **kwargs): self.executed = [] @@ -435,6 +439,8 @@ class DummyNode(object): self.tftp = tftp self.bmc = make_bmc(DummyBMC, hostname=ip_address, username=username, password=password, verbose=False) + self.wafer_id = 'FAKEWAFERID%s' % DummyNode.wafer_id_unique + DummyNode.wafer_id_unique += 1 def get_power(self): self.executed.append("get_power") @@ -598,6 +604,9 @@ class DummyNode(object): def set_uplink(self, uplink, iface): self.executed.append(('set_uplink', uplink, iface)) + def get_wafer_id(self): + return self.wafer_id + class DummyFailNode(DummyNode): """ Dummy node that should fail on some commands """ -- cgit v1.2.1 From ab8f1c150107e421a7dd4ca7a8473a54dbddf7a6 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Thu, 1 Aug 2013 17:27:40 -0500 Subject: CXMAN-211: Change InternalTftp to a Thread-based implementation We don't want that os.fork() mess. The disadvantage to a Thread approach is that we lose the kill() method, but who uses that? If we later need the kill() functionality, we can come along and use the multiprocessing module to do it properly. --- cxmanage_api/tftp.py | 78 +++++++++++++--------------------------------------- 1 file changed, 19 insertions(+), 59 deletions(-) diff --git a/cxmanage_api/tftp.py b/cxmanage_api/tftp.py index 02b7c49..5bfc3dc 100644 --- a/cxmanage_api/tftp.py +++ b/cxmanage_api/tftp.py @@ -29,9 +29,6 @@ # DAMAGE. -import os -import sys -import atexit import shutil import socket import logging @@ -43,7 +40,7 @@ from cxmanage_api import temp_dir from tftpy.TftpShared import TftpException -class InternalTftp(object): +class InternalTftp(Thread): """Internally serves files using the `Trivial File Transfer Protocol `_. >>> # Typical instantiation ... @@ -62,54 +59,27 @@ class InternalTftp(object): """ def __init__(self, ip_address=None, port=0, verbose=False): - """Default constructor for the InternalTftp class.""" + super(InternalTftp, self).__init__() + self.daemon = True + self.tftp_dir = temp_dir() self.verbose = verbose - pipe = os.pipe() - pid = os.fork() - if (not pid): - # Force tftpy to use sys.stdout and sys.stderr - try: - os.dup2(sys.stdout.fileno(), 1) - os.dup2(sys.stderr.fileno(), 2) - - except AttributeError, err_msg: - if (self.verbose): - print ('Passing on exception: %s' % err_msg) - pass - - # Create a PortThread class only if needed ... - class PortThread(Thread): - """Thread that sends the port number through the pipe.""" - def run(self): - """Run function override.""" - # Need to wait for the server to open its socket - while not server.sock: - pass - with os.fdopen(pipe[1], "w") as a_file: - a_file.write("%i\n" % server.sock.getsockname()[1]) - # - # Create an Internal TFTP server thread - # - server = TftpServer(tftproot=self.tftp_dir) - thread = PortThread() - thread.start() - try: - if not self.verbose: - setLogLevel(logging.CRITICAL) - # Start accepting connections ... - server.listen(listenport=port) - except KeyboardInterrupt: - # User @ keyboard cancelled server ... - if (self.verbose): - traceback.format_exc() - sys.exit(0) - - self.server = pid + + self.server = TftpServer(tftproot=self.tftp_dir) self.ip_address = ip_address - with os.fdopen(pipe[0]) as a_fd: - self.port = int(a_fd.readline()) - atexit.register(self.kill) + self.port = port + self.start() + + # Get the port we actually hosted on (this covers the port=0 case) + while not self.server.sock: + pass + self.port = self.server.sock.getsockname()[1] + + def run(self): + """ Run the server. Listens indefinitely. """ + if not self.verbose: + setLogLevel(logging.CRITICAL) + self.server.listen(listenport=self.port) def get_address(self, relative_host=None): """Returns the ipv4 address of this server. @@ -136,16 +106,6 @@ class InternalTftp(object): sock.close() return ipv4 - def kill(self): - """Kills the InternalTftpServer. - - >>> i_tftp.kill() - - """ - if (self.server): - os.kill(self.server, 15) - self.server = None - def get_file(self, src, dest): """Download a file from the tftp server to local_path. -- cgit v1.2.1 From d70d2b090dbb2c9646e7b58425f10a9e9c009919 Mon Sep 17 00:00:00 2001 From: "matthew.hodgins" Date: Fri, 2 Aug 2013 12:03:25 -0500 Subject: removing wafer_id from DummyNode. Added guid to DummyBMC. --- cxmanage_test/fabric_test.py | 7 ------- cxmanage_test/node_test.py | 21 ++++++++++++++++++--- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/cxmanage_test/fabric_test.py b/cxmanage_test/fabric_test.py index 3333444..05fe835 100644 --- a/cxmanage_test/fabric_test.py +++ b/cxmanage_test/fabric_test.py @@ -430,8 +430,6 @@ class FabricTest(unittest.TestCase): class DummyNode(object): """ Dummy node for the nodemanager tests """ - wafer_id_unique = 0 - def __init__(self, ip_address, username="admin", password="admin", tftp=None, *args, **kwargs): self.executed = [] @@ -439,8 +437,6 @@ class DummyNode(object): self.tftp = tftp self.bmc = make_bmc(DummyBMC, hostname=ip_address, username=username, password=password, verbose=False) - self.wafer_id = 'FAKEWAFERID%s' % DummyNode.wafer_id_unique - DummyNode.wafer_id_unique += 1 def get_power(self): self.executed.append("get_power") @@ -604,9 +600,6 @@ class DummyNode(object): def set_uplink(self, uplink, iface): self.executed.append(('set_uplink', uplink, iface)) - def get_wafer_id(self): - return self.wafer_id - class DummyFailNode(DummyNode): """ Dummy node that should fail on some commands """ diff --git a/cxmanage_test/node_test.py b/cxmanage_test/node_test.py index b6f860b..2934914 100644 --- a/cxmanage_test/node_test.py +++ b/cxmanage_test/node_test.py @@ -421,6 +421,9 @@ class NodeTest(unittest.TestCase): class DummyBMC(LanBMC): + + GUID_UNIQUE = 0 + """ Dummy BMC for the node tests """ def __init__(self, **kwargs): super(DummyBMC, self).__init__(**kwargs) @@ -435,6 +438,18 @@ class DummyBMC(LanBMC): Partition(6, 11, 1388544, 12288) # ubootenv ] self.ipaddr_base = '192.168.100.1' + self.unique_guid = 'FAKEGUID%s' % DummyBMC.GUID_UNIQUE + DummyBMC.GUID_UNIQUE += 1 + + def guid(self): + """Returns the GUID""" + self.executed.append("guid") + + class Result: + def __init__(self, dummybmc): + self.system_guid = dummybmc.unique_guid + self.time_stamp = None + return Result(self) def set_chassis_power(self, mode): """ Set chassis power """ @@ -594,7 +609,7 @@ class DummyBMC(LanBMC): link_map.append('Link 1: Node 2') link_map.append('Link 3: Node 1') link_map.append('Link 4: Node 3') - + work_dir = tempfile.mkdtemp(prefix="cxmanage_test-") with open('%s/%s' % (work_dir, filename), 'w') as lm_file: for lmap in link_map: @@ -624,7 +639,7 @@ class DummyBMC(LanBMC): routing_table.append('Node 13: rt - 0.2.0.0.1') routing_table.append('Node 14: rt - 0.2.0.0.1') routing_table.append('Node 15: rt - 0.2.0.0.1') - + work_dir = tempfile.mkdtemp(prefix="cxmanage_test-") with open('%s/%s' % (work_dir, filename), 'w') as rt_file: for rtable in routing_table: @@ -663,7 +678,7 @@ class DummyBMC(LanBMC): depth_chart.append('Node 14: Shortest Distance 3 hops via neighbor 10: other hops/neighbors -') depth_chart.append('Node 15: Shortest Distance 4 hops via neighbor 14: other hops/neighbors - 5/12') - + work_dir = tempfile.mkdtemp(prefix="cxmanage_test-") with open('%s/%s' % (work_dir, filename), 'w') as dc_file: for dchart in depth_chart: -- cgit v1.2.1 From 1da7ad9a93e0e2311545410ec0ea2acde6f9af38 Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Fri, 2 Aug 2013 12:27:15 -0500 Subject: Fixed tests that check get_uplink_info() and get_uplink_speed() --- cxmanage_test/fabric_test.py | 21 +++++++++++++++++---- cxmanage_test/node_test.py | 27 +++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/cxmanage_test/fabric_test.py b/cxmanage_test/fabric_test.py index 3333444..c1db164 100644 --- a/cxmanage_test/fabric_test.py +++ b/cxmanage_test/fabric_test.py @@ -76,11 +76,16 @@ class FabricTest(unittest.TestCase): self.assertEqual(node.executed, []) def test_get_uplink_info(self): - """ Test get_mac_addresses command """ + """ Test get_uplink_info command """ self.fabric.get_uplink_info() - self.assertEqual(self.nodes[0].executed, ["get_fabric_uplink_info"]) - for node in self.nodes[1:]: - self.assertEqual(node.executed, []) + for node in self.nodes: + self.assertEqual(node.executed, ["get_uplink_info"]) + + def test_get_uplink_speed(self): + """ Test get_uplink_speed command """ + self.fabric.get_uplink_speed() + for node in self.nodes: + self.assertEqual(node.executed, ["get_uplink_speed"]) def test_get_uplink(self): """ Test get_uplink command """ @@ -548,6 +553,14 @@ class DummyNode(object): results[n] = {'eth0': 0, 'eth1': 0, 'mgmt': 0} return results + def get_uplink_info(self): + self.executed.append('get_uplink_info') + return 'Node 0: eth0 0, eth1 0, mgmt 0' + + def get_uplink_speed(self): + self.executed.append('get_uplink_speed') + return 1 + def get_link_stats(self, link=0): self.executed.append(('get_link_stats', link)) return { diff --git a/cxmanage_test/node_test.py b/cxmanage_test/node_test.py index b6f860b..e218cc4 100644 --- a/cxmanage_test/node_test.py +++ b/cxmanage_test/node_test.py @@ -365,6 +365,23 @@ class NodeTest(unittest.TestCase): for x in node.bmc.executed: self.assertEqual(x, "fabric_config_get_uplink_info") + def test_get_uplink_info(self): + """ Test node.get_uplink_info method """ + for node in self.nodes: + result = node.get_uplink_info() + + for x in node.bmc.executed: + self.assertEqual(x, "get_uplink_info") + + def test_get_uplink_speed(self): + """ Test node.get_uplink_info method """ + for node in self.nodes: + result = node.get_uplink_speed() + + for x in node.bmc.executed: + self.assertEqual(x, "get_uplink_speed") + + def test_get_linkmap(self): """ Test node.get_linkmap method """ for node in self.nodes: @@ -867,6 +884,16 @@ class DummyBMC(LanBMC): def fabric_rm_macaddr(self, nodeid=0, iface=0, macaddr=None): self.executed.append('fabric_rm_macaddr') + def fabric_get_uplink_info(self): + """Corresponds to Node.get_uplink_info()""" + self.executed.append('get_uplink_info') + return 'Node 0: eth0 0, eth1 0, mgmt 0' + + def fabric_get_uplink_speed(self): + """Corresponds to Node.get_uplink_speed()""" + self.executed.append('get_uplink_speed') + return 1 + class Partition: def __init__(self, partition, type, offset=0, -- cgit v1.2.1 From 04a950a865d6c075c6a95eb10c5df3ae2cddd332 Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Fri, 2 Aug 2013 13:49:23 -0500 Subject: Fixed test_get_versions() --- cxmanage_test/node_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cxmanage_test/node_test.py b/cxmanage_test/node_test.py index e218cc4..e5dabe2 100644 --- a/cxmanage_test/node_test.py +++ b/cxmanage_test/node_test.py @@ -326,7 +326,8 @@ class NodeTest(unittest.TestCase): result = node.get_versions() self.assertEqual(node.bmc.executed, ["get_info_basic", - "get_firmware_info", "info_card"]) + "get_firmware_info", "get_info_basic", + "get_info_basic", "info_card"]) for attr in ["iana", "firmware_version", "ecme_version", "ecme_timestamp"]: self.assertTrue(hasattr(result, attr)) -- cgit v1.2.1 From 2f5821b91a67499d46d76df45fc8162c5f7c12df Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Mon, 5 Aug 2013 10:53:25 -0500 Subject: Moved code from node.get_chip_name() to node.get_versions() node.get_chip_name() is no longer supported. Changed fabric_test.py and node_test.py to reflect these changes. --- cxmanage_api/node.py | 36 +++++++++++------------------------- cxmanage_test/fabric_test.py | 1 + cxmanage_test/node_test.py | 3 +-- 3 files changed, 13 insertions(+), 27 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index e79e039..19066b6 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -931,6 +931,7 @@ class Node(object): """ result = self.bmc.get_info_basic() fwinfo = self.get_firmware_info() + fw_version = result.firmware_version # components maps variables to firmware partition types components = [ @@ -938,12 +939,19 @@ class Node(object): ("stage2_version", "S2_ELF"), ("bootlog_version", "BOOT_LOG") ] - # Use A9 or A15 if on Highbank or Midway, respectively - if self.get_chip_name() == "Highbank": + # Use firmware version to determine the chip type and name + # In the future, we may want to determine the chip name some other way + if fw_version.startswith("ECX-1000"): components.append(("a9boot_version", "A9_EXEC")) - elif self.get_chip_name() == "Midway": + setattr(result, "chip_name", "Highbank") + elif fw_version.startswith("ECX-2000"): # The BMC (and fwinfo) still reference the A15 as an A9 components.append(("a15boot_version", "A9_EXEC")) + setattr(result, "chip_name", "Midway") + else: + # Default to A9 and unknown name + components.append(("a9boot_version", "A9_EXEC")) + setattr(result, "chip_name", "Unknown") components.append(("uboot_version", "A9_UBOOT")) components.append(("ubootenv_version", "UBOOTENV")) components.append(("dtb_version", "DTB")) @@ -1681,27 +1689,5 @@ class Node(object): "Unable to increment SIMG priority, too high") return priority - def get_chip_name(self): - """Returns the name of the "server-side" chip used by this node. - - >>> node.get_chip_name - 'Highbank' - - :rtype: string - - Currently we check the firmware version to determine whether the chip - used by this node is Highbank or Midway. - - """ - result = self.bmc.get_info_basic() - fw_version = result.firmware_version - - if fw_version.startswith("ECX-1000"): - return "Highbank" - elif fw_version.startswith("ECX-2000"): - return "Midway" - else: - return "Unknown" - # End of file: ./node.py diff --git a/cxmanage_test/fabric_test.py b/cxmanage_test/fabric_test.py index e39dc77..83cf9fe 100644 --- a/cxmanage_test/fabric_test.py +++ b/cxmanage_test/fabric_test.py @@ -510,6 +510,7 @@ class DummyNode(object): self.ecme_timestamp = "0" self.a9boot_version = "v0.0.0" self.uboot_version = "v0.0.0" + self.chip_name = "Unknown" return Result() def ipmitool_command(self, ipmitool_args): diff --git a/cxmanage_test/node_test.py b/cxmanage_test/node_test.py index e7c673f..ec3e8cd 100644 --- a/cxmanage_test/node_test.py +++ b/cxmanage_test/node_test.py @@ -326,8 +326,7 @@ class NodeTest(unittest.TestCase): result = node.get_versions() self.assertEqual(node.bmc.executed, ["get_info_basic", - "get_firmware_info", "get_info_basic", - "get_info_basic", "info_card"]) + "get_firmware_info", "info_card"]) for attr in ["iana", "firmware_version", "ecme_version", "ecme_timestamp"]: self.assertTrue(hasattr(result, attr)) -- cgit v1.2.1 From 7adb81f7cc7b443c011a764f60ec0c8913d476b7 Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Mon, 5 Aug 2013 12:46:25 -0500 Subject: Added keyword arguments to _run_on_all_nodes() --- cxmanage_api/fabric.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cxmanage_api/fabric.py b/cxmanage_api/fabric.py index 7fc0192..c9b633e 100644 --- a/cxmanage_api/fabric.py +++ b/cxmanage_api/fabric.py @@ -1068,11 +1068,12 @@ class Fabric(object): """ return self._run_on_all_nodes(async, "get_depth_chart") - def _run_on_all_nodes(self, async, name, *args): + def _run_on_all_nodes(self, async, name, *args, **kwargs): """Start a command on all nodes.""" tasks = {} for node_id, node in self.nodes.iteritems(): - tasks[node_id] = self.task_queue.put(getattr(node, name), *args) + tasks[node_id] = self.task_queue.put(getattr(node, name), *args, + **kwargs) if async: return tasks -- cgit v1.2.1 From ff798ac2b2952d8340fcc40158a103dbc38a740f Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Fri, 9 Aug 2013 15:20:06 -0500 Subject: Create Commands to Get FRU Versions node.py Added private method to read FRUs Added get_node_fru_version() Added get_slot_fru_version() Removed unneeded import Fabric.py Added methods to get node FRU version and slot FRU version Added a fru_version command for cxmanage: fru_version.py Minor formatting in scripts/cxmanage --- cxmanage/commands/fru_version.py | 69 ++++++++++++++++++++++++++++++++++++++++ cxmanage_api/fabric.py | 38 ++++++++++++++++++++++ cxmanage_api/node.py | 64 ++++++++++++++++++++++++++++++++++++- scripts/cxmanage | 17 ++++++++-- 4 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 cxmanage/commands/fru_version.py diff --git a/cxmanage/commands/fru_version.py b/cxmanage/commands/fru_version.py new file mode 100644 index 0000000..02ea0eb --- /dev/null +++ b/cxmanage/commands/fru_version.py @@ -0,0 +1,69 @@ + +# Copyright (c) 2013, 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. + + +from cxmanage import get_tftp, get_nodes, get_node_strings, run_command + + +def node_fru_version_command(args): + """Get the node FRU version for each node. """ + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + results, errors = run_command(args, nodes, 'get_node_fru_version') + + # Print results if we were successful + if results: + node_strings = get_node_strings(args, results, justify=True) + for node in nodes: + print("%s: %s" % (node_strings[node], results[node])) + + print("") # For readability + + if not args.quiet and errors: + print('Some errors occured during the command.\n') + + +def slot_fru_version_command(args): + """Get the slot FRU version for each node. """ + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + results, errors = run_command(args, nodes, 'get_slot_fru_version') + + # Print results if we were successful + if results: + node_strings = get_node_strings(args, results, justify=True) + for node in nodes: + print("%s: %s" % (node_strings[node], results[node])) + + print("") # For readability + + if not args.quiet and errors: + print('Some errors occured during the command.\n') diff --git a/cxmanage_api/fabric.py b/cxmanage_api/fabric.py index c9b633e..807df90 100644 --- a/cxmanage_api/fabric.py +++ b/cxmanage_api/fabric.py @@ -1068,6 +1068,44 @@ class Fabric(object): """ return self._run_on_all_nodes(async, "get_depth_chart") + def get_node_fru_version(self, async=False): + """Get each node's node FRU version. + + >>> fabric.get_node_fru_version() + {0: 'bf7b471716113d5b9c47c6a5dd25f7a83f5c235c', + 1: 'bf7b471716113d5b9c47c6a5dd25f7a83f5c235c', + 2: 'bf7b471716113d5b9c47c6a5dd25f7a83f5c235c', + 3: 'bf7b471716113d5b9c47c6a5dd25f7a83f5c235c'} + + :param async: Flag that determines if the command result (dictionary) + is returned or a Task object (can get status, etc.). + :type async: boolean + + :returns: The node FRU versions for each node in the fabric + :rtype: dictionary + + """ + return self._run_on_all_nodes(async, "get_node_fru_version") + + def get_slot_fru_version(self, async=False): + """Get each node's slot FRU version. + + >>> fabric.get_slot_fru_version() + {0: 'bf7b471716113d5b9c47c6a5dd25f7a83f5c235c', + 1: 'bf7b471716113d5b9c47c6a5dd25f7a83f5c235c', + 2: 'bf7b471716113d5b9c47c6a5dd25f7a83f5c235c', + 3: 'bf7b471716113d5b9c47c6a5dd25f7a83f5c235c'} + + :param async: Flag that determines if the command result (dictionary) + is returned or a Task object (can get status, etc.). + :type async: boolean + + :returns: The slot FRU versions for each node in the fabric + :rtype: dictionary + + """ + return self._run_on_all_nodes(async, "get_slot_fru_version") + def _run_on_all_nodes(self, async, name, *args, **kwargs): """Start a command on all nodes.""" tasks = {} diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 19066b6..e5d3eba 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -32,7 +32,6 @@ import os import re import time -import shutil import tempfile import subprocess @@ -1458,6 +1457,58 @@ class Node(object): """ return self.bmc.fabric_get_uplink_info().strip() + def get_node_fru_version(self): + """Get the node FRU version. + >>> node.get_node_fru_version + 'bf7b471716113d5b9c47c6a5dd25f7a83f5c235c' + + :return: The node FRU version of this node + :rtype: string + + This is essentially the equivalent of ipmitool FRU read 81 filename + and reading only the version from that file. + The in-file offset for the node FRU version is 516, and the + length of the version string is 40 bytes. + + """ + version = self._read_fru(81, offset=516, bytes_to_read=40) + # If there is an error reading the FRU, every byte could be x00 + if version == "\x00"*len(version): + raise Exception("No node FRU detected") + + # If the version string is less than 40 bytes long, remove the x00's + version = version.replace("\x00", "") + + return version + + def get_slot_fru_version(self): + """Get the slot FRU version. + >>> node.get_slot_fru_version + 'Unknown' + + :return: The slot FRU version of this node + :rtype: string + + This is essentially the equivalent of ipmitool FRU read 82 filename + and reading only the version from that file. + The in-file offset for the node FRU version is 516, and the + length of the version string is 40 bytes. + + Note that some system boards do not have slot FRUs, and are + therefore expected to raise an exception. + + """ + version = self._read_fru(82, offset=516, bytes_to_read=40) + # If there is an error reading the FRU, every byte could be x00 + if version == "\x00"*len(version): + raise Exception("No slot FRU detected. Perhaps the system " + \ + "board does not have slot FRUs?") + + # If the version string is less than 40 bytes long, remove the x00's + version = version.replace("\x00", "") + + return version + def _run_fabric_command(self, function_name, **kwargs): """Handles the basics of sending a node a command for fabric data.""" filename = temp_file() @@ -1689,5 +1740,16 @@ class Node(object): "Unable to increment SIMG priority, too high") return priority + def _read_fru(node, 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. + + """ + # Use a temporary file to store the FRU image + with tempfile.NamedTemporaryFile(delete=True) as hexfile: + node.bmc.fru_read(fru_number, hexfile.name) + hexfile.seek(offset) + return(hexfile.read(bytes_to_read)) + # End of file: ./node.py diff --git a/scripts/cxmanage b/scripts/cxmanage index ccde835..a3edb37 100755 --- a/scripts/cxmanage +++ b/scripts/cxmanage @@ -48,6 +48,8 @@ from cxmanage.commands.info import info_command from cxmanage.commands.ipmitool import ipmitool_command from cxmanage.commands.ipdiscover import ipdiscover_command from cxmanage.commands.tspackage import tspackage_command +from cxmanage.commands.fru_version import node_fru_version_command, \ + slot_fru_version_command PYIPMI_VERSION = '0.8.0' @@ -292,10 +294,21 @@ def build_parser(): parser.add_argument('hostname', help='nodes to operate on (see examples below)') - # tspackage command - tspackage = subparsers.add_parser('tspackage', help='Get all data from each node') + # tspackage command ("troubleshoot package") + tspackage = subparsers.add_parser('tspackage', + help='Save information about this node/fabric to a .tar') tspackage.set_defaults(func=tspackage_command) + # node_fru_version command + node_fru_version = subparsers.add_parser('node_fru_version', + help='Get the node FRU version') + node_fru_version.set_defaults(func=node_fru_version_command) + + # slot_fru_version command + slot_fru_version = subparsers.add_parser('slot_fru_version', + help='Get the slot FRU version') + slot_fru_version.set_defaults(func=slot_fru_version_command) + return parser -- cgit v1.2.1 From 36f088f3afa8ada6d3ef8d046875288a6c4d3457 Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Fri, 9 Aug 2013 16:37:02 -0500 Subject: FRU Versions Cxmanage Command Added NoFRUVersionError Added FRU versions to node.get_versions(). (Now cxmanage info and tspackage get FRU versions.) --- cxmanage/__init__.py | 4 +++- cxmanage_api/cx_exceptions.py | 20 ++++++++++++++++++++ cxmanage_api/node.py | 21 +++++++++++++++++---- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/cxmanage/__init__.py b/cxmanage/__init__.py index b34761a..5bcf6e4 100644 --- a/cxmanage/__init__.py +++ b/cxmanage/__init__.py @@ -334,6 +334,8 @@ COMPONENTS = [ ("a15boot_version", "A15boot version"), ("uboot_version", "Uboot version"), ("ubootenv_version", "Ubootenv version"), - ("dtb_version", "DTB version") + ("dtb_version", "DTB version"), + ("node_fru_version", "Node FRU version"), + ("slot_fru_version", "Slot FRU version") ] diff --git a/cxmanage_api/cx_exceptions.py b/cxmanage_api/cx_exceptions.py index f390392..0cfa778 100644 --- a/cxmanage_api/cx_exceptions.py +++ b/cxmanage_api/cx_exceptions.py @@ -363,5 +363,25 @@ class IPDiscoveryError(Exception): """String representation of this Exception class.""" return self.msg +class NoFRUVersionError(Exception): + """Raised when a node does not detect a FRU version. + + >>> from cxmanage_api.cx_exceptions import NoFRUError + >>> raise NoFRUError('My custom exception text!') + Traceback (most recent call last): + File "", line 1, in + cxmanage_api.cx_exceptions.NoFRUError: My custom exception text! + + :param msg: Exceptions message and details to return to the user. + :type msg: string + :raised: When a node fails to detect a FRU version + + """ + + def __init__(self, msg="No FRU version detected"): + super(NoFRUVersionError, self).__init__() + self.msg = msg + def __str__(self): + return self.msg # End of file: exceptions.py diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index e5d3eba..01e3d68 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -49,7 +49,7 @@ from cxmanage_api.ip_retriever import IPRetriever as IPRETRIEVER from cxmanage_api.cx_exceptions import TimeoutError, NoSensorError, \ SocmanVersionError, FirmwareConfigError, PriorityIncrementError, \ NoPartitionError, TransferFailure, ImageSizeError, \ - PartitionInUseError, UbootenvError + PartitionInUseError, UbootenvError, NoFRUVersionError class Node(object): @@ -955,6 +955,20 @@ class Node(object): components.append(("ubootenv_version", "UBOOTENV")) components.append(("dtb_version", "DTB")) + # Get the node FRU version + try: + node_fru_version = self.get_node_fru_version() + setattr(result, "node_fru_version", node_fru_version) + except NoFRUVersionError: + setattr(result, "node_fru_version", "No node FRU detected") + + # Get the slot FRU version + try: + slot_fru_version = self.get_slot_fru_version() + setattr(result, "slot_fru_version", slot_fru_version) + except NoFRUVersionError: + setattr(result, "slot_fru_version", "No slot FRU detected") + for var, ptype in components: try: partition = self._get_partition(fwinfo, ptype, "ACTIVE") @@ -1474,7 +1488,7 @@ class Node(object): version = self._read_fru(81, offset=516, bytes_to_read=40) # If there is an error reading the FRU, every byte could be x00 if version == "\x00"*len(version): - raise Exception("No node FRU detected") + raise NoFRUVersionError("No node FRU detected") # If the version string is less than 40 bytes long, remove the x00's version = version.replace("\x00", "") @@ -1501,8 +1515,7 @@ class Node(object): version = self._read_fru(82, offset=516, bytes_to_read=40) # If there is an error reading the FRU, every byte could be x00 if version == "\x00"*len(version): - raise Exception("No slot FRU detected. Perhaps the system " + \ - "board does not have slot FRUs?") + raise NoFRUVersionError("No slot FRU detected.") # If the version string is less than 40 bytes long, remove the x00's version = version.replace("\x00", "") -- cgit v1.2.1 From 09a7007df005d5d6c664093b853e10de03c414bb Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Mon, 12 Aug 2013 12:30:11 -0500 Subject: Added Test Cases for get_node_fru_version() and get_slot_fru_version() --- cxmanage_test/fabric_test.py | 20 ++++++++++++++++++++ cxmanage_test/node_test.py | 27 ++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/cxmanage_test/fabric_test.py b/cxmanage_test/fabric_test.py index 83cf9fe..b147d8e 100644 --- a/cxmanage_test/fabric_test.py +++ b/cxmanage_test/fabric_test.py @@ -413,6 +413,18 @@ class FabricTest(unittest.TestCase): else: self.assertEqual(node.bmc.executed, []) + def test_get_node_fru_version(self): + """ Test the get_node_fru_version method """ + self.fabric.get_node_fru_version() + for node in self.nodes: + self.assertEqual(node.executed, ["get_node_fru_version"]) + + def test_get_slot_fru_version(self): + """ Test the get_slot_fru_version method """ + self.fabric.get_slot_fru_version() + for slot in self.nodes: + self.assertEqual(slot.executed, ["get_slot_fru_version"]) + def test_composite_bmc(self): """ Test the CompositeBMC member """ with self.assertRaises(AttributeError): @@ -614,6 +626,14 @@ class DummyNode(object): def set_uplink(self, uplink, iface): self.executed.append(('set_uplink', uplink, iface)) + def get_node_fru_version(self): + self.executed.append("get_node_fru_version") + return "0.0" + + def get_slot_fru_version(self): + self.executed.append("get_slot_fru_version") + return "0.0" + class DummyFailNode(DummyNode): """ Dummy node that should fail on some commands """ diff --git a/cxmanage_test/node_test.py b/cxmanage_test/node_test.py index ec3e8cd..8b905a7 100644 --- a/cxmanage_test/node_test.py +++ b/cxmanage_test/node_test.py @@ -326,7 +326,8 @@ class NodeTest(unittest.TestCase): result = node.get_versions() self.assertEqual(node.bmc.executed, ["get_info_basic", - "get_firmware_info", "info_card"]) + "get_firmware_info", "node_fru_read", "slot_fru_read", + "info_card"]) for attr in ["iana", "firmware_version", "ecme_version", "ecme_timestamp"]: self.assertTrue(hasattr(result, attr)) @@ -436,6 +437,18 @@ class NodeTest(unittest.TestCase): node.set_uplink(iface=0, uplink=0) self.assertEqual(node.get_uplink(iface=0), 0) + def test_get_node_fru_version(self): + """ Test node.get_node_fru_version method """ + for node in self.nodes: + result = node.get_node_fru_version() + self.assertEqual(node.bmc.executed, ['node_fru_read']) + + def test_get_slot_fru_version(self): + """ Test node.get_slot_fru_version method """ + for node in self.nodes: + result = node.get_slot_fru_version() + self.assertEqual(node.bmc.executed, ['slot_fru_read']) + class DummyBMC(LanBMC): @@ -909,6 +922,18 @@ class DummyBMC(LanBMC): self.executed.append('get_uplink_speed') return 1 + def fru_read(self, fru_number, filename): + if fru_number == 81: + self.executed.append('node_fru_read') + elif fru_number == 82: + self.executed.append('slot_fru_read') + else: + self.executed.append('fru_read') + + with open(filename, "w") as fru_image: + # Writes a fake FRU image with version "0.0" + fru_image.write("x00" * 516 + "0.0" + "x00"*7673) + class Partition: def __init__(self, partition, type, offset=0, -- cgit v1.2.1 From f99dd28e632826f540ea9abdb8591d98d47a4d43 Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Tue, 13 Aug 2013 10:07:38 -0500 Subject: Adding cxmanage_test to required packages --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4e2f47d..f4f087f 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ from setuptools import setup setup( name='cxmanage', version='0.9.0', - packages=['cxmanage', 'cxmanage.commands', 'cxmanage_api'], + packages=['cxmanage', 'cxmanage.commands', 'cxmanage_api', 'cxmanage_test'], scripts=['scripts/cxmanage', 'scripts/sol_tabs'], description='Calxeda Management Utility', # NOTE: As of right now, the pyipmi version requirement needs to be updated -- cgit v1.2.1 From a2cada8b840538bead465bfed52a661ccb86f041 Mon Sep 17 00:00:00 2001 From: evasquez Date: Thu, 15 Aug 2013 09:25:47 -0500 Subject: Fixed some code comments that were effecting sphinx doc output. --- cxmanage_api/fabric.py | 1 + cxmanage_api/node.py | 24 ++++++++++++++---------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/cxmanage_api/fabric.py b/cxmanage_api/fabric.py index 807df90..9d47814 100644 --- a/cxmanage_api/fabric.py +++ b/cxmanage_api/fabric.py @@ -877,6 +877,7 @@ class Fabric(object): :return: mac address mask :rtype: string + """ return self.primary_node.bmc.fabric_config_get_macaddr_mask() diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 01e3d68..b70fe40 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -954,7 +954,7 @@ class Node(object): components.append(("uboot_version", "A9_UBOOT")) components.append(("ubootenv_version", "UBOOTENV")) components.append(("dtb_version", "DTB")) - + # Get the node FRU version try: node_fru_version = self.get_node_fru_version() @@ -1451,6 +1451,7 @@ class Node(object): def get_uplink_speed(self): """Get the uplink speed of this node. + >>> node.get_uplink_speed() 1 @@ -1462,6 +1463,7 @@ class Node(object): def get_uplink_info(self): """Get the uplink information for this node. + >>> node.get_uplink_info() 'Node 0: eth0 0, eth1 0, mgmt 0' @@ -1473,6 +1475,7 @@ class Node(object): def get_node_fru_version(self): """Get the node FRU version. + >>> node.get_node_fru_version 'bf7b471716113d5b9c47c6a5dd25f7a83f5c235c' @@ -1483,7 +1486,7 @@ class Node(object): and reading only the version from that file. The in-file offset for the node FRU version is 516, and the length of the version string is 40 bytes. - + """ version = self._read_fru(81, offset=516, bytes_to_read=40) # If there is an error reading the FRU, every byte could be x00 @@ -1492,11 +1495,12 @@ class Node(object): # If the version string is less than 40 bytes long, remove the x00's version = version.replace("\x00", "") - + return version def get_slot_fru_version(self): """Get the slot FRU version. + >>> node.get_slot_fru_version 'Unknown' @@ -1507,10 +1511,10 @@ class Node(object): and reading only the version from that file. The in-file offset for the node FRU version is 516, and the length of the version string is 40 bytes. - - Note that some system boards do not have slot FRUs, and are + + Note that some system boards do not have slot FRUs, and are therefore expected to raise an exception. - + """ version = self._read_fru(82, offset=516, bytes_to_read=40) # If there is an error reading the FRU, every byte could be x00 @@ -1519,7 +1523,7 @@ class Node(object): # If the version string is less than 40 bytes long, remove the x00's version = version.replace("\x00", "") - + return version def _run_fabric_command(self, function_name, **kwargs): @@ -1753,10 +1757,10 @@ class Node(object): "Unable to increment SIMG priority, too high") return priority - def _read_fru(node, fru_number, offset=0, bytes_to_read=-1): - """Read from node's fru starting at offset. + def _read_fru(node, 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. - + """ # Use a temporary file to store the FRU image with tempfile.NamedTemporaryFile(delete=True) as hexfile: -- cgit v1.2.1 From d30d2716c97b8836cc139edcc387988d2be93df8 Mon Sep 17 00:00:00 2001 From: Rye Terrell Date: Fri, 16 Aug 2013 14:52:49 -0500 Subject: add flag that allows the caller to only try to turn on or off nodes that are not turned on or off, respectively. --- cxmanage_api/fabric.py | 8 ++++++-- cxmanage_api/node.py | 13 +++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/cxmanage_api/fabric.py b/cxmanage_api/fabric.py index 9d47814..c13f55c 100644 --- a/cxmanage_api/fabric.py +++ b/cxmanage_api/fabric.py @@ -288,7 +288,7 @@ class Fabric(object): """ return self._run_on_all_nodes(async, "get_power") - def set_power(self, mode, async=False): + def set_power(self, mode, async=False, ignore_existing_state=False): """Send an IPMI power command to all nodes. >>> # On ... @@ -304,9 +304,13 @@ class Fabric(object): :param async: Flag that determines if the command result (dictionary) is returned or a Command object (can get status, etc.). :type async: boolean + :param ignore_existing_state: Flag that allows the caller to only try + to turn on or off nodes that are not + turned on or off, respectively. + :type ignore_existing_state: boolean """ - self._run_on_all_nodes(async, "set_power", mode) + self._run_on_all_nodes(async, "set_power", mode, ignore_existing_state) def get_power_policy(self, async=False): """Gets the power policy from all nodes. diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index b70fe40..049ab36 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -221,7 +221,7 @@ class Node(object): """ return self.bmc.get_chassis_status().power_on - def set_power(self, mode): + def set_power(self, mode, ignore_existing_state=False): """Send an IPMI power command to this target. >>> # To turn the power 'off' @@ -235,8 +235,17 @@ class Node(object): :param mode: Mode to set the power state to. ('on'/'off') :type mode: string - + :param ignore_existing_state: Flag that allows the caller to only try + to turn on or off the node if it is not + turned on or off, respectively. + :type ignore_existing_state: boolean + """ + if ignore_existing_state: + if self.get_power() and mode == "on": + return + if not self.get_power() and mode == "off": + return self.bmc.set_chassis_power(mode=mode) def get_power_policy(self): -- cgit v1.2.1 From a9c26d21065275a1a53b917e64685c50a8a20f61 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Tue, 20 Aug 2013 17:33:28 -0500 Subject: CXMAN-218: Remove FRU versions from node.get_versions() FRU reads on SB12 are pretty slow -- the whole process can take up to 30 minutes on a 24-node fabric. Oiy. --- cxmanage/__init__.py | 2 -- cxmanage_api/node.py | 14 -------------- cxmanage_test/node_test.py | 3 +-- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/cxmanage/__init__.py b/cxmanage/__init__.py index 5bcf6e4..9491581 100644 --- a/cxmanage/__init__.py +++ b/cxmanage/__init__.py @@ -335,7 +335,5 @@ COMPONENTS = [ ("uboot_version", "Uboot version"), ("ubootenv_version", "Ubootenv version"), ("dtb_version", "DTB version"), - ("node_fru_version", "Node FRU version"), - ("slot_fru_version", "Slot FRU version") ] diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 049ab36..e379f97 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -964,20 +964,6 @@ class Node(object): components.append(("ubootenv_version", "UBOOTENV")) components.append(("dtb_version", "DTB")) - # Get the node FRU version - try: - node_fru_version = self.get_node_fru_version() - setattr(result, "node_fru_version", node_fru_version) - except NoFRUVersionError: - setattr(result, "node_fru_version", "No node FRU detected") - - # Get the slot FRU version - try: - slot_fru_version = self.get_slot_fru_version() - setattr(result, "slot_fru_version", slot_fru_version) - except NoFRUVersionError: - setattr(result, "slot_fru_version", "No slot FRU detected") - for var, ptype in components: try: partition = self._get_partition(fwinfo, ptype, "ACTIVE") diff --git a/cxmanage_test/node_test.py b/cxmanage_test/node_test.py index 8b905a7..fe3d3f2 100644 --- a/cxmanage_test/node_test.py +++ b/cxmanage_test/node_test.py @@ -326,8 +326,7 @@ class NodeTest(unittest.TestCase): result = node.get_versions() self.assertEqual(node.bmc.executed, ["get_info_basic", - "get_firmware_info", "node_fru_read", "slot_fru_read", - "info_card"]) + "get_firmware_info", "info_card"]) for attr in ["iana", "firmware_version", "ecme_version", "ecme_timestamp"]: self.assertTrue(hasattr(result, attr)) -- cgit v1.2.1 From 310d7aae2b26a671d2b6f7240beeeb573c7c04f9 Mon Sep 17 00:00:00 2001 From: Zuhair Parvez Date: Tue, 20 Aug 2013 10:37:14 -0500 Subject: AIT-196 Added fabric_get_node_id to DummyBMC --- cxmanage_test/node_test.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cxmanage_test/node_test.py b/cxmanage_test/node_test.py index fe3d3f2..d46dabd 100644 --- a/cxmanage_test/node_test.py +++ b/cxmanage_test/node_test.py @@ -626,6 +626,13 @@ class DummyBMC(LanBMC): self.type = "TestBoard" self.revision = "0" return Result() + + node_count = 0 + def fabric_get_node_id(self): + self.executed.append('get_node_id') + result = DummyBMC.node_count + DummyBMC.node_count += 1 + return result def fabric_info_get_link_map(self, filename, tftp_addr=None): """Upload a link_map file from the node to TFTP""" -- cgit v1.2.1 From 5c807fbda907cd6d3dec490e170f1a8ac03adf22 Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Fri, 23 Aug 2013 15:22:30 -0500 Subject: tspackage Collects Node FRU and Slot FRU Versions Re-added since cxmanage info does not get FRU versions anymore Added copyright information nojira --- cxmanage/commands/tspackage.py | 82 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/cxmanage/commands/tspackage.py b/cxmanage/commands/tspackage.py index aab1886..88f46e3 100644 --- a/cxmanage/commands/tspackage.py +++ b/cxmanage/commands/tspackage.py @@ -1,7 +1,40 @@ -#!/usr/bin/env python - -# Copyright 2013 Calxeda, Inc. All Rights Reserved. - +# Copyright 2013 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. + + +"""A cxmanage command to collect information about a node and archive it. + +Example: +cxmanage tspackage 10.10.10.10 + +""" import os import time @@ -50,6 +83,14 @@ def tspackage_command(args): print("Getting version information...") write_version_info(args, nodes) + if not quiet: + print("Getting node FRU version...") + write_node_fru_version(args, nodes) + + if not quiet: + print("Getting slot FRU version...") + write_slot_fru_version(args, nodes) + if not quiet: print("Getting boot order...") write_boot_order(args, nodes) @@ -127,6 +168,39 @@ def write_version_info(args, nodes): write_to_file(node, lines) +def write_node_fru_version(args, nodes): + """Write the node and slot FRU versions for each node to their + respective files. + + """ + node_fru_results, _ = run_command(args, nodes, "get_node_fru_version") + + for node in nodes: + lines = [] # Lines of text to write to file + if node in node_fru_results: + lines.append("%s: %s" % \ + ("Node FRU Version".ljust(19), node_fru_results[node])) + else: + lines.append("\nWARNING: No node FRU found!") + write_to_file(node, lines) + +def write_slot_fru_version(args, nodes): + """Write the node and slot FRU versions for each node to their + respective files. + + """ + slot_fru_results, _ = run_command(args, nodes, "get_slot_fru_version") + + for node in nodes: + lines = [] # Lines of text to write to file + if node in slot_fru_results: + lines.append("%s: %s" % \ + ("Slot FRU Version".ljust(19), slot_fru_results[node])) + else: + lines.append("Error reading slot FRU. Perhaps the system board " + + "does not have slot FRUs?") + + write_to_file(node, lines) def write_mac_addrs(args, nodes): """Write the MAC addresses for each node to their respective files.""" -- cgit v1.2.1 From 4f2af62877ecbcf9791fd9c0505275c2f8abbbae Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 10:23:45 -0500 Subject: nojira: Fixing pylint warning for no docstring. No functional change. --- cxmanage_api/node.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index e379f97..42b98bc 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1,3 +1,6 @@ +"""Calxeda: node.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -239,7 +242,7 @@ class Node(object): to turn on or off the node if it is not turned on or off, respectively. :type ignore_existing_state: boolean - + """ if ignore_existing_state: if self.get_power() and mode == "on": -- cgit v1.2.1 From f60cc9afbab7d5968f5b84d8572c7c0812586247 Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 10:34:18 -0500 Subject: nojira: Fixing pylint convention violations. --- cxmanage_api/node.py | 55 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 42b98bc..54f65a8 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -188,7 +188,8 @@ class Node(object): :param macaddr: MAC address to add :type macaddr: string - :raises IpmiError: If errors in the command occur with BMC communication. + :raises IpmiError: If errors in the command occur with BMC \ +communication. """ self.bmc.fabric_add_macaddr(iface=iface, macaddr=macaddr) @@ -203,7 +204,8 @@ class Node(object): :param macaddr: MAC address to remove :type macaddr: string - :raises IpmiError: If errors in the command occur with BMC communication. + :raises IpmiError: If errors in the command occur with BMC \ +communication. """ self.bmc.fabric_rm_macaddr(iface=iface, macaddr=macaddr) @@ -260,7 +262,8 @@ class Node(object): :return: The Nodes current power policy. :rtype: string - :raises IpmiError: If errors in the command occur with BMC communication. + :raises IpmiError: If errors in the command occur with BMC \ +communication. """ return self.bmc.get_chassis_status().power_restore_policy @@ -316,9 +319,12 @@ class Node(object): >>> node.get_sel() ['1 | 06/21/2013 | 16:13:31 | System Event #0xf4 |', - '0 | 06/27/2013 | 20:25:18 | System Boot Initiated #0xf1 | Initiated by power up | Asserted', - '1 | 06/27/2013 | 20:25:35 | Watchdog 2 #0xfd | Hard reset | Asserted', - '2 | 06/27/2013 | 20:25:18 | System Boot Initiated #0xf1 | Initiated by power up | Asserted', + '0 | 06/27/2013 | 20:25:18 | System Boot Initiated #0xf1 | \ +Initiated by power up | Asserted', + '1 | 06/27/2013 | 20:25:35 | Watchdog 2 #0xfd | Hard reset | \ +Asserted', + '2 | 06/27/2013 | 20:25:18 | System Boot Initiated #0xf1 | \ +Initiated by power up | Asserted', '3 | 06/27/2013 | 21:01:13 | System Event #0xf4 |', ... ] @@ -529,7 +535,8 @@ class Node(object): :return: Returns a list of FWInfo objects for each :rtype: list - :raises IpmiError: If errors in the command occur with BMC communication. + :raises IpmiError: If errors in the command occur with BMC \ +communication. """ fwinfo = [x for x in self.bmc.get_firmware_info() @@ -579,7 +586,8 @@ class Node(object): :return: Returns a list of FWInfo objects for each :rtype: list - :raises IpmiError: If errors in the command occur with BMC communication. + :raises IpmiError: If errors in the command occur with BMC \ +communication. """ return [vars(info) for info in self.get_firmware_info()] @@ -832,7 +840,8 @@ class Node(object): >>> node.config_reset() - :raises IpmiError: If errors in the command occur with BMC communication. + :raises IpmiError: If errors in the command occur with BMC \ +communication. """ # Reset CDB @@ -936,7 +945,8 @@ class Node(object): :returns: The results of IPMI info basic command. :rtype: pyipmi.info.InfoBasicResult - :raises IpmiError: If errors in the command occur with BMC communication. + :raises IpmiError: If errors in the command occur with BMC \ +communication. :raises Exception: If there are errors within the command response. """ @@ -1012,7 +1022,8 @@ class Node(object): :returns: The results of IPMI info basic command. :rtype: dictionary - :raises IpmiError: If errors in the command occur with BMC communication. + :raises IpmiError: If errors in the command occur with BMC \ +communication. :raises Exception: If there are errors within the command response. """ @@ -1376,8 +1387,10 @@ class Node(object): :return: The IP address of the server. :rtype: string - :raises IpmiError: If errors in the command occur with BMC communication. - :raises IPDiscoveryError: If the server is off, or the IP can't be obtained. + :raises IpmiError: If errors in the command occur with BMC \ +communication. + :raises IPDiscoveryError: If the server is off, or the IP can't be \ +obtained. """ verbosity = 2 if self.verbose else 0 @@ -1613,7 +1626,11 @@ class Node(object): for x in xrange(2): try: - self.bmc.register_firmware_write(basename, partition_id, image.type) + self.bmc.register_firmware_write( + basename, + partition_id, + image.type + ) self.ecme_tftp.put_file(filename, basename) break except (IpmiError, TftpException): @@ -1638,7 +1655,11 @@ class Node(object): for x in xrange(2): try: - self.bmc.register_firmware_read(basename, partition_id, image_type) + self.bmc.register_firmware_read( + basename, + partition_id, + image_type + ) self.ecme_tftp.get_file(basename, filename) break except (IpmiError, TftpException): @@ -1735,7 +1756,9 @@ class Node(object): if (image.type in ["CDB", "BOOT_LOG"] and partition.in_use == "1"): raise PartitionInUseError( - "Can't upload to a CDB/BOOT_LOG partition that's in use") + "Can't upload to a CDB/BOOT_LOG partition " + + "that's in use" + ) # Try a TFTP download. Would try an upload too, but nowhere to put it. partition = self._get_partition(fwinfo, "SOC_ELF", "FIRST") -- cgit v1.2.1 From 6d4548bedceb9159a76ea9e6a4bdd09213262408 Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 10:34:56 -0500 Subject: nojira: fixing pylint ERROR. --- cxmanage_api/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 54f65a8..9f74e8c 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1778,7 +1778,7 @@ obtained. "Unable to increment SIMG priority, too high") return priority - def _read_fru(node, fru_number, offset=0, bytes_to_read= -1): + def _read_fru(self, node, 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. -- cgit v1.2.1 From 04380fda7556f5b66c6f98b954a79a08c6f5044a Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 10:41:26 -0500 Subject: nojira: Fixed pylint ERROR. --- cxmanage_api/node.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 9f74e8c..fff4bb1 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1778,14 +1778,14 @@ obtained. "Unable to increment SIMG priority, too high") return priority - def _read_fru(self, node, fru_number, offset=0, bytes_to_read= -1): + 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. """ # Use a temporary file to store the FRU image with tempfile.NamedTemporaryFile(delete=True) as hexfile: - node.bmc.fru_read(fru_number, hexfile.name) + self.bmc.fru_read(fru_number, hexfile.name) hexfile.seek(offset) return(hexfile.read(bytes_to_read)) -- cgit v1.2.1 From dadc16ebb69d09e8e4d3b3c59e3174d17a92470a Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 10:43:02 -0500 Subject: nojira: Fixed pylint convention warning. --- cxmanage_api/node.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index fff4bb1..6bfd12f 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -747,8 +747,9 @@ communication. ) filename = temp_file() - with open(filename, "w") as f: - f.write(ubootenv.get_contents()) + with open(filename, "w") as file_: + file_.write(ubootenv.get_contents()) + ubootenv_image = self.image(filename, image.type, False, image.daddr, image.skip_crc32, image.version) @@ -845,7 +846,7 @@ communication. """ # Reset CDB - result = self.bmc.reset_firmware() + self.bmc.reset_firmware() # Reset ubootenv fwinfo = self.get_firmware_info() -- cgit v1.2.1 From 65731cb58cbc6015f3b6259622d1e2e077a37cbf Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 11:02:19 -0500 Subject: nojira: Fixed pylint warnings. --- cxmanage_api/node.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 6bfd12f..5d33e64 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1350,7 +1350,7 @@ communication. dchrt_entries = {} dchrt_entries['shortest'] = (neighbor, hops) try: - other_hops_neighbors = elements[12].strip().split('[,\s]+') + other_hops_neighbors = elements[12].strip().split("[,\s]+") hops = [] for entry in other_hops_neighbors: pair = entry.strip().split('/') @@ -1765,7 +1765,8 @@ obtained. partition = self._get_partition(fwinfo, "SOC_ELF", "FIRST") self._download_image(partition) - def _get_next_priority(self, fwinfo, package): + @staticmethod + def _get_next_priority(fwinfo, package): """ Get the next priority """ priority = None image_types = [x.type for x in package.images] -- cgit v1.2.1 From 11d05dd3ff877a4c77e6b7c88e7811dd51525d20 Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 11:03:05 -0500 Subject: nojira: Fixed pylint warning. --- cxmanage_api/node.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 5d33e64..52cfe58 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -881,8 +881,8 @@ communication. priority = max(int(x.priority, 16) for x in [first_part, active_part]) filename = temp_file() - with open(filename, "w") as f: - f.write(ubootenv.get_contents()) + with open(filename, "w") as file_: + file_.write(ubootenv.get_contents()) ubootenv_image = self.image(filename, image.type, False, image.daddr, image.skip_crc32, image.version) -- cgit v1.2.1 From ef6bb3ab96f6a2785ab96aef09c31f86f80dd2ed Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 11:20:34 -0500 Subject: nojira: fixed pylint warnings. --- cxmanage_api/node.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 52cfe58..83bd107 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -917,8 +917,8 @@ communication. priority = max(int(x.priority, 16) for x in [first_part, active_part]) filename = temp_file() - with open(filename, "w") as f: - f.write(ubootenv.get_contents()) + with open(filename, "w") as file_: + file_.write(ubootenv.get_contents()) ubootenv_image = self.image(filename, image.type, False, image.daddr, image.skip_crc32, image.version) @@ -1186,8 +1186,8 @@ communication. node_id = int(line.replace('Node ', '')[0]) ul_info = line.replace('Node %s:' % node_id, '').strip().split(',') node_data = {} - for ul in ul_info: - data = tuple(ul.split()) + for ul_ in ul_info: + data = tuple(ul_.split()) node_data[data[0]] = int(data[1]) results[node_id] = node_data @@ -1350,13 +1350,16 @@ communication. dchrt_entries = {} dchrt_entries['shortest'] = (neighbor, hops) try: - other_hops_neighbors = elements[12].strip().split("[,\s]+") + other_hops_neighbors = elements[12].strip().split( + r'[,\s]+' + ) hops = [] for entry in other_hops_neighbors: pair = entry.strip().split('/') hops.append((int(pair[1]), int(pair[0]))) dchrt_entries['others'] = hops - except: + + except Exception: pass results[target] = dchrt_entries @@ -1546,7 +1549,7 @@ obtained. getattr(self.bmc, function_name)(filename=basename, **kwargs) self.ecme_tftp.get_file(basename, filename) - except (IpmiError, TftpException) as e: + except (IpmiError, TftpException): getattr(self.bmc, function_name)( filename=basename, tftp_addr=self.tftp_address, @@ -1566,7 +1569,8 @@ obtained. return filename - def _get_partition(self, fwinfo, image_type, partition_arg): + @staticmethod + def _get_partition(fwinfo, image_type, partition_arg): """Get a partition for this image type based on the argument.""" # Filter partitions for this type partitions = [x for x in fwinfo if @@ -1625,7 +1629,7 @@ obtained. filename = image.render_to_simg(priority, daddr) basename = os.path.basename(filename) - for x in xrange(2): + for _ in xrange(2): try: self.bmc.register_firmware_write( basename, @@ -1654,7 +1658,7 @@ obtained. partition_id = int(partition.partition) image_type = partition.type.split()[1][1:-1] - for x in xrange(2): + for _ in xrange(2): try: self.bmc.register_firmware_read( basename, -- cgit v1.2.1 From de0da79868875c4118802a38b8b15e639609d9b3 Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 11:22:09 -0500 Subject: nojira: Removed TODO/FIXME. --- cxmanage_api/node.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 83bd107..983fd60 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1725,13 +1725,9 @@ obtained. % (required_version, ecme_version)) # Check slot0 vs. slot2 - # TODO: remove this check if (package.config and info.firmware_version != "Unknown" and - len(info.firmware_version) < 32): - if "slot2" in info.firmware_version: - firmware_config = "slot2" - else: - firmware_config = "default" + len(info.firmware_version) < 32): + firmware_config = "default" if (package.config != firmware_config): raise FirmwareConfigError( -- cgit v1.2.1 From ffa94bee55fc25c7f0fddf46730633c7a3496c7c Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 11:41:49 -0500 Subject: nojira: fixed line too long. --- cxmanage_api/node.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 983fd60..f41e059 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -30,7 +30,7 @@ # 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. - +# pylint: disable=C0302 import os import re @@ -55,6 +55,7 @@ from cxmanage_api.cx_exceptions import TimeoutError, NoSensorError, \ PartitionInUseError, UbootenvError, NoFRUVersionError +# pylint: disable=R0902 class Node(object): """A node is a single instance of an ECME. @@ -609,8 +610,9 @@ communication. try: self._check_firmware(package, partition_arg, priority) return True - except (SocmanVersionError, FirmwareConfigError, PriorityIncrementError, - NoPartitionError, ImageSizeError, PartitionInUseError): + except (SocmanVersionError, FirmwareConfigError, + PriorityIncrementError, NoPartitionError, ImageSizeError, + PartitionInUseError): return False def update_firmware(self, package, partition_arg="INACTIVE", -- cgit v1.2.1 From a48c8ff30e80e655f3f50c98fb175fa20029d8a8 Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 11:49:06 -0500 Subject: nojira: fixed pylint warns. --- cxmanage_api/node.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index f41e059..1bc051c 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1,6 +1,6 @@ +# pylint: disable=C0302 """Calxeda: node.py""" - # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -30,7 +30,6 @@ # 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. -# pylint: disable=C0302 import os import re @@ -55,7 +54,7 @@ from cxmanage_api.cx_exceptions import TimeoutError, NoSensorError, \ PartitionInUseError, UbootenvError, NoFRUVersionError -# pylint: disable=R0902 +# pylint: disable=R0902, R0904 class Node(object): """A node is a single instance of an ECME. @@ -81,7 +80,7 @@ class Node(object): :type ubootenv: `UbootEnv `_ """ - + # pylint: disable=R0913 def __init__(self, ip_address, username="admin", password="admin", tftp=None, ecme_tftp_port=5001, verbose=False, bmc=None, image=None, ubootenv=None, ipretriever=None): @@ -615,6 +614,7 @@ communication. PartitionInUseError): return False + # pylint: disable=R0914, R0912, R0915 def update_firmware(self, package, partition_arg="INACTIVE", priority=None): """ Update firmware on this target. @@ -1361,7 +1361,7 @@ communication. hops.append((int(pair[1]), int(pair[0]))) dchrt_entries['others'] = hops - except Exception: + except Exception: # pylint: disable=W0703 pass results[target] = dchrt_entries -- cgit v1.2.1 From 6b9b7f43636d5cd584ef0d1c26d8943baea677ba Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 11:50:49 -0500 Subject: nojira: Adding pylintrc. --- pylint.rc | 274 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 pylint.rc diff --git a/pylint.rc b/pylint.rc new file mode 100644 index 0000000..124425b --- /dev/null +++ b/pylint.rc @@ -0,0 +1,274 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Profiled execution. +profile=no + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + + +[MESSAGES CONTROL] + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. See also the "--disable" option for examples. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +# I0011 - locally disabling PyLint +# R0801 - Similar lines in 2 files (mostly affects unit tests) +disable=I0011, R0801 + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (RP0004). +comment=no + +# Template used to display messages. This is a python new-style format string +# used to format the massage information. See doc for all details +#msg-template= + + +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + +# Regular expression which should only match correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct module level names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct attribute names in class +# bodies +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=__.*__ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the beginning of the name of dummy variables +# (i.e. not used). +dummy-variables-rgx=_$|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). +ignored-classes=SQLObject + +# When zope mode is activated, add a predefined set of Zope acquired attributes +# to generated-members. +zope=no + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E0201 when accessed. Python regular +# expressions are accepted. +generated-members=REQUEST,acl_users,aq_parent + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=80 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception -- cgit v1.2.1 From c96663d8ede8166dd501f01a0c06befa7d733c1a Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 11:52:21 -0500 Subject: nojira: formatted code. --- cxmanage_test/node_test.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cxmanage_test/node_test.py b/cxmanage_test/node_test.py index d46dabd..b1ec34a 100644 --- a/cxmanage_test/node_test.py +++ b/cxmanage_test/node_test.py @@ -1,3 +1,6 @@ +"""Unit tests for the Node class.""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -27,7 +30,6 @@ # 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. -"""Unit tests for the Node class.""" import random @@ -626,7 +628,7 @@ class DummyBMC(LanBMC): self.type = "TestBoard" self.revision = "0" return Result() - + node_count = 0 def fabric_get_node_id(self): self.executed.append('get_node_id') -- cgit v1.2.1 From 4f9c7e9c3da5078a2dca4edfbaf5b35dca0394ac Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 12:01:54 -0500 Subject: nojira: Telling pyling to skip this file. its a conf. file. --- cxmanage_api/docs/source/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cxmanage_api/docs/source/conf.py b/cxmanage_api/docs/source/conf.py index 6ac0b01..e3b47cf 100644 --- a/cxmanage_api/docs/source/conf.py +++ b/cxmanage_api/docs/source/conf.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# pylint: skip-file # # Cxmanage Python API documentation build configuration file, created by # sphinx-quickstart on Fri Dec 07 16:31:44 2012. -- cgit v1.2.1 From d4d7395638bf3dc6e5e1f7c1901a4d820b636222 Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 12:04:14 -0500 Subject: nojira: Adding .project and .pydev to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 6e62db1..7a69fa9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ tags *.pyc cxmanage.egg-info +.project +.pydevproject -- cgit v1.2.1 From 1b05eb9dfed5b0cada5e56254b6f048d0da7f9db Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 12:05:17 -0500 Subject: nojira: fixing pylint warns. --- cxmanage_api/node.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 1bc051c..358af95 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1,6 +1,7 @@ # pylint: disable=C0302 """Calxeda: node.py""" + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. -- cgit v1.2.1 From 26675748a5450dada6d49083b0d81b0786b668a5 Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 12:12:11 -0500 Subject: nojira: Fixed line too long. --- cxmanage_test/node_test.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cxmanage_test/node_test.py b/cxmanage_test/node_test.py index b1ec34a..f757f9f 100644 --- a/cxmanage_test/node_test.py +++ b/cxmanage_test/node_test.py @@ -1,3 +1,4 @@ +# pylint: disable=C0302 """Unit tests for the Node class.""" @@ -119,8 +120,12 @@ class NodeTest(unittest.TestCase): self.assertEqual(node.bmc.executed, ["sdr_list"]) self.assertEqual(len(result), 2) - self.assertTrue(result["Node Power"].sensor_reading.endswith("Watts")) - self.assertTrue(result["Board Temp"].sensor_reading.endswith("degrees C")) + self.assertTrue( + result["Node Power"].sensor_reading.endswith("Watts") + ) + self.assertTrue( + result["Board Temp"].sensor_reading.endswith("degrees C") + ) def test_is_updatable(self): """ Test node.is_updatable method """ -- cgit v1.2.1 From 3ec6965b84b0ae242b5cce8d6bf929d20265584c Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 12:45:53 -0500 Subject: nojira: fixed pylint warnings, etc. --- cxmanage_test/node_test.py | 194 +++++++++++++++++++++++++++++++-------------- 1 file changed, 136 insertions(+), 58 deletions(-) diff --git a/cxmanage_test/node_test.py b/cxmanage_test/node_test.py index f757f9f..3c0e4a0 100644 --- a/cxmanage_test/node_test.py +++ b/cxmanage_test/node_test.py @@ -52,9 +52,11 @@ from cxmanage_api.cx_exceptions import IPDiscoveryError NUM_NODES = 4 -ADDRESSES = ["192.168.100.%i" % x for x in range(1, NUM_NODES + 1)] +ADDRESSES = ["192.168.100.%i" % n for n in range(1, NUM_NODES + 1)] TFTP = InternalTftp() + +# pylint: disable=R0904, W0201 class NodeTest(unittest.TestCase): """ Tests involving cxmanage Nodes """ @@ -64,6 +66,12 @@ class NodeTest(unittest.TestCase): ipretriever=DummyIPRetriever, verbose=True) for ip in ADDRESSES] + self.server_ip = None + self.fabric_ipsrc = None + self.fabric_ls_policy = None + self.fabric_linkspeed = None + self.fabric_lu_factor = None + # Give each node a node_id count = 0 for node in self.nodes: @@ -343,8 +351,8 @@ class NodeTest(unittest.TestCase): for node in self.nodes: result = node.get_fabric_ipinfo() - for x in node.bmc.executed: - self.assertEqual(x, "fabric_config_get_ip_info") + for found in node.bmc.executed: + self.assertEqual(found, "fabric_config_get_ip_info") self.assertEqual(result, dict([(i, ADDRESSES[i]) for i in range(NUM_NODES)])) @@ -354,8 +362,8 @@ class NodeTest(unittest.TestCase): for node in self.nodes: result = node.get_fabric_macaddrs() - for x in node.bmc.executed: - self.assertEqual(x, "fabric_config_get_mac_addresses") + for found in node.bmc.executed: + self.assertEqual(found, "fabric_config_get_mac_addresses") self.assertEqual(len(result), NUM_NODES) for node_id in xrange(NUM_NODES): @@ -367,56 +375,56 @@ class NodeTest(unittest.TestCase): def test_get_fabric_uplink_info(self): """ Test node.get_fabric_uplink_info method """ for node in self.nodes: - result = node.get_fabric_uplink_info() + node.get_fabric_uplink_info() - for x in node.bmc.executed: - self.assertEqual(x, "fabric_config_get_uplink_info") + for found in node.bmc.executed: + self.assertEqual(found, "fabric_config_get_uplink_info") def test_get_uplink_info(self): """ Test node.get_uplink_info method """ for node in self.nodes: - result = node.get_uplink_info() + node.get_uplink_info() - for x in node.bmc.executed: - self.assertEqual(x, "get_uplink_info") + for found in node.bmc.executed: + self.assertEqual(found, "get_uplink_info") def test_get_uplink_speed(self): """ Test node.get_uplink_info method """ for node in self.nodes: - result = node.get_uplink_speed() + node.get_uplink_speed() - for x in node.bmc.executed: - self.assertEqual(x, "get_uplink_speed") + for found in node.bmc.executed: + self.assertEqual(found, "get_uplink_speed") def test_get_linkmap(self): """ Test node.get_linkmap method """ for node in self.nodes: - result = node.get_linkmap() + node.get_linkmap() - for x in node.bmc.executed: - self.assertEqual(x, "fabric_info_get_link_map") + for found in node.bmc.executed: + self.assertEqual(found, "fabric_info_get_link_map") def test_get_routing_table(self): """ Test node.get_routing_table method """ for node in self.nodes: - result = node.get_routing_table() + node.get_routing_table() - for x in node.bmc.executed: - self.assertEqual(x, "fabric_info_get_routing_table") + for found in node.bmc.executed: + self.assertEqual(found, "fabric_info_get_routing_table") def test_get_depth_chart(self): """ Test node.get_depth_chart method """ for node in self.nodes: - result = node.get_depth_chart() + node.get_depth_chart() - for x in node.bmc.executed: - self.assertEqual(x, "fabric_info_get_depth_chart") + for found in node.bmc.executed: + self.assertEqual(found, "fabric_info_get_depth_chart") def test_get_link_stats(self): """ Test node.get_link_stats() """ for node in self.nodes: - result = node.get_link_stats() + node.get_link_stats() self.assertEqual(node.bmc.executed[0], ('fabric_get_linkstats', 0)) def test_get_server_ip(self): @@ -446,21 +454,23 @@ class NodeTest(unittest.TestCase): def test_get_node_fru_version(self): """ Test node.get_node_fru_version method """ for node in self.nodes: - result = node.get_node_fru_version() + node.get_node_fru_version() self.assertEqual(node.bmc.executed, ['node_fru_read']) def test_get_slot_fru_version(self): """ Test node.get_slot_fru_version method """ for node in self.nodes: - result = node.get_slot_fru_version() + node.get_slot_fru_version() self.assertEqual(node.bmc.executed, ['slot_fru_read']) - +# pylint: disable=R0902 class DummyBMC(LanBMC): + """ Dummy BMC for the node tests """ + GUID_UNIQUE = 0 - """ Dummy BMC for the node tests """ + def __init__(self, **kwargs): super(DummyBMC, self).__init__(**kwargs) self.executed = [] @@ -481,7 +491,9 @@ class DummyBMC(LanBMC): """Returns the GUID""" self.executed.append("guid") - class Result: + # pylint: disable=R0903 + class Result(object): + """Results class.""" def __init__(self, dummybmc): self.system_guid = dummybmc.unique_guid self.time_stamp = None @@ -495,7 +507,9 @@ class DummyBMC(LanBMC): """ Get chassis status """ self.executed.append("get_chassis_status") - class Result: + # pylint: disable=R0903 + class Result(object): + """Results class.""" def __init__(self): self.power_on = False self.power_restore_policy = "always-off" @@ -541,8 +555,11 @@ class DummyBMC(LanBMC): self.partitions[partition].fwinfo.priority = "%8x" % simg.priority self.partitions[partition].fwinfo.daddr = "%8x" % simg.daddr - class Result: + # pylint: disable=R0903 + class Result(object): + """Results class.""" def __init__(self): + """Default constructor for the Result class.""" self.tftp_handle_id = 0 return Result() @@ -560,7 +577,9 @@ class DummyBMC(LanBMC): tftp.put_file("%s/%s" % (work_dir, filename), filename) shutil.rmtree(work_dir) - class Result: + # pylint: disable=R0903 + class Result(object): + """Results class.""" def __init__(self): self.tftp_handle_id = 0 return Result() @@ -578,16 +597,23 @@ class DummyBMC(LanBMC): def get_firmware_status(self, handle): self.executed.append("get_firmware_status") - class Result: + # pylint: disable=R0903 + class Result(object): + """Results class.""" def __init__(self): self.status = "Complete" + + del handle + return Result() def check_firmware(self, partition): self.executed.append(("check_firmware", partition)) self.partitions[partition].checks += 1 - class Result: + # pylint: disable=R0903 + class Result(object): + """Results class.""" def __init__(self): self.crc32 = 0 self.error = None @@ -617,7 +643,9 @@ class DummyBMC(LanBMC): """ Get basic SoC info from this node """ self.executed.append("get_info_basic") - class Result: + # pylint: disable=R0903 + class Result(object): + """Results class.""" def __init__(self): self.iana = int("0x0096CD", 16) self.firmware_version = "ECX-0000-v0.0.0" @@ -628,7 +656,9 @@ class DummyBMC(LanBMC): def get_info_card(self): self.executed.append("info_card") - class Result: + # pylint: disable=R0903 + class Result(object): + """Results class.""" def __init__(self): self.type = "TestBoard" self.revision = "0" @@ -705,21 +735,66 @@ class DummyBMC(LanBMC): raise IpmiError('No tftp address!') depth_chart = [] - depth_chart.append('Node 1: Shortest Distance 0 hops via neighbor 0: other hops/neighbors -') - depth_chart.append('Node 2: Shortest Distance 0 hops via neighbor 0: other hops/neighbors - 1/3') - depth_chart.append('Node 3: Shortest Distance 0 hops via neighbor 0: other hops/neighbors - 1/2') - depth_chart.append('Node 4: Shortest Distance 2 hops via neighbor 6: other hops/neighbors - 3/7') - depth_chart.append('Node 5: Shortest Distance 3 hops via neighbor 4: other hops/neighbors -') - depth_chart.append('Node 6: Shortest Distance 1 hops via neighbor 2: other hops/neighbors -') - depth_chart.append('Node 7: Shortest Distance 2 hops via neighbor 6: other hops/neighbors - 3/4') - depth_chart.append('Node 8: Shortest Distance 3 hops via neighbor 10: other hops/neighbors - 4/11') - depth_chart.append('Node 9: Shortest Distance 4 hops via neighbor 8: other hops/neighbors -') - depth_chart.append('Node 10: Shortest Distance 2 hops via neighbor 6: other hops/neighbors -') - depth_chart.append('Node 11: Shortest Distance 3 hops via neighbor 10: other hops/neighbors - 4/8') - depth_chart.append('Node 12: Shortest Distance 4 hops via neighbor 14: other hops/neighbors - 5/15') - depth_chart.append('Node 13: Shortest Distance 5 hops via neighbor 12: other hops/neighbors -') - depth_chart.append('Node 14: Shortest Distance 3 hops via neighbor 10: other hops/neighbors -') - depth_chart.append('Node 15: Shortest Distance 4 hops via neighbor 14: other hops/neighbors - 5/12') + depth_chart.append( + 'Node 1: Shortest Distance 0 hops via neighbor 0: ' + + 'other hops/neighbors -' + ) + depth_chart.append( + 'Node 2: Shortest Distance 0 hops via neighbor 0: ' + + 'other hops/neighbors - 1/3' + ) + depth_chart.append( + 'Node 3: Shortest Distance 0 hops via neighbor 0: ' + + 'other hops/neighbors - 1/2' + ) + depth_chart.append( + 'Node 4: Shortest Distance 2 hops via neighbor 6: ' + + 'other hops/neighbors - 3/7' + ) + depth_chart.append( + 'Node 5: Shortest Distance 3 hops via neighbor 4: ' + + 'other hops/neighbors -' + ) + depth_chart.append( + 'Node 6: Shortest Distance 1 hops via neighbor 2: ' + + 'other hops/neighbors -' + ) + depth_chart.append( + 'Node 7: Shortest Distance 2 hops via neighbor 6: ' + + 'other hops/neighbors - 3/4' + ) + depth_chart.append( + 'Node 8: Shortest Distance 3 hops via neighbor 10: ' + + 'other hops/neighbors - 4/11' + ) + depth_chart.append( + 'Node 9: Shortest Distance 4 hops via neighbor 8: ' + + 'other hops/neighbors -' + ) + depth_chart.append( + 'Node 10: Shortest Distance 2 hops via neighbor 6: ' + + 'other hops/neighbors -' + ) + depth_chart.append( + 'Node 11: Shortest Distance 3 hops via neighbor 10: ' + + 'other hops/neighbors - 4/8' + ) + depth_chart.append( + 'Node 12: Shortest Distance 4 hops via neighbor 14: ' + + 'other hops/neighbors - 5/15' + ) + depth_chart.append( + 'Node 13: Shortest Distance 5 hops via neighbor 12: ' + + 'other hops/neighbors -' + ) + depth_chart.append( + 'Node 14: Shortest Distance 3 hops via neighbor 10: ' + + 'other hops/neighbors -' + ) + depth_chart.append( + 'Node 15: Shortest Distance 4 hops via neighbor 14: ' + + 'other hops/neighbors - 5/12' + ) work_dir = tempfile.mkdtemp(prefix="cxmanage_test-") @@ -736,6 +811,7 @@ class DummyBMC(LanBMC): shutil.rmtree(work_dir) + # pylint: disable=W0222 def fabric_get_linkstats(self, filename, tftp_addr=None, link=None): """Upload a link_stats file from the node to TFTP""" @@ -948,21 +1024,23 @@ class DummyBMC(LanBMC): fru_image.write("x00" * 516 + "0.0" + "x00"*7673) -class Partition: - def __init__(self, partition, type, offset=0, +# pylint: disable=R0913, R0903 +class Partition(object): + """Partition class.""" + def __init__(self, partition, type_, offset=0, size=0, priority=0, daddr=0, in_use=None): self.updates = 0 self.retrieves = 0 self.checks = 0 self.activates = 0 - self.fwinfo = FWInfoEntry(partition, type, offset, size, priority, + self.fwinfo = FWInfoEntry(partition, type_, offset, size, priority, daddr, in_use) -class FWInfoEntry: +class FWInfoEntry(object): """ Firmware info for a single partition """ - def __init__(self, partition, type, offset=0, size=0, priority=0, daddr=0, + def __init__(self, partition, type_, offset=0, size=0, priority=0, daddr=0, in_use=None): self.partition = "%2i" % partition self.type = { @@ -970,7 +1048,7 @@ class FWInfoEntry: 3: "03 (SOC_ELF)", 10: "0a (CDB)", 11: "0b (UBOOTENV)" - }[type] + }[type_] self.offset = "%8x" % offset self.size = "%8x" % size self.priority = "%8x" % priority @@ -991,7 +1069,7 @@ class DummyUbootEnv(UbootEnv): """ Do nothing """ pass - +# pylint: disable=R0903 class DummyIPRetriever(object): """ Dummy IP retriever """ -- cgit v1.2.1 From e04924f7bef151a584e4aed3d7d09e9ff5b2baa4 Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 13:28:12 -0500 Subject: nojira: fixed pylint warnings. --- cxmanage_test/fabric_test.py | 103 ++++++++++++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 26 deletions(-) diff --git a/cxmanage_test/fabric_test.py b/cxmanage_test/fabric_test.py index b147d8e..91396d6 100644 --- a/cxmanage_test/fabric_test.py +++ b/cxmanage_test/fabric_test.py @@ -1,3 +1,5 @@ +"""Calxeda: fabric_test.py """ + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -45,12 +47,14 @@ NUM_NODES = 128 ADDRESSES = ["192.168.100.%i" % x for x in range(1, NUM_NODES + 1)] +# pylint: disable=R0904 class FabricTest(unittest.TestCase): """ Test the various Fabric commands """ def setUp(self): # Set up the controller and add targets self.fabric = Fabric("192.168.100.1", node=DummyNode) - self.nodes = [DummyNode(x) for x in ADDRESSES] + self.nodes = [DummyNode(i) for i in ADDRESSES] + # pylint: disable=W0212 self.fabric._nodes = dict((i, self.nodes[i]) for i in xrange(NUM_NODES)) @@ -175,7 +179,9 @@ class FabricTest(unittest.TestCase): ipmitool_args = "power status" self.fabric.ipmitool_command(ipmitool_args) for node in self.nodes: - self.assertEqual(node.executed, [("ipmitool_command", ipmitool_args)]) + self.assertEqual( + node.executed, [("ipmitool_command", ipmitool_args)] + ) def test_get_server_ip(self): """ Test get_server_ip command """ @@ -187,8 +193,11 @@ class FabricTest(unittest.TestCase): def test_failed_command(self): """ Test a failed command """ - fail_nodes = [DummyFailNode(x) for x in ADDRESSES] - self.fabric._nodes = dict((i, fail_nodes[i]) for i in xrange(NUM_NODES)) + fail_nodes = [DummyFailNode(i) for i in ADDRESSES] + # pylint: disable=W0212 + self.fabric._nodes = dict( + (i, fail_nodes[i]) for i in xrange(NUM_NODES) + ) try: self.fabric.get_power() self.fail() @@ -224,7 +233,7 @@ class FabricTest(unittest.TestCase): # it's there to make sure the ipsrc_mode value gets passed to the bmc. self.assertEqual(bmc.fabric_ipsrc, ipsrc) - def test_apply_factory_default_config(self): + def test_apply_fdc(self): """Test the apply_factory_default_config method""" self.fabric.apply_factory_default_config() @@ -292,26 +301,26 @@ class FabricTest(unittest.TestCase): def test_get_link_stats(self): """Test the get_link_stats() method.""" for i in range(0, 5): - stats = self.fabric.get_link_stats(i) - for nn, node in self.fabric.nodes.items(): + self.fabric.get_link_stats(i) + for node in self.fabric.nodes.values(): self.assertIn(('get_link_stats', i), node.executed) def test_get_linkmap(self): """Test the get_linkmap method""" - maps = self.fabric.get_linkmap() - for nn, node in self.fabric.nodes.items(): + self.fabric.get_linkmap() + for node in self.fabric.nodes.values(): self.assertIn('get_linkmap', node.executed) def test_get_routing_table(self): """Test the get_routing_table method""" - maps = self.fabric.get_routing_table() - for nn, node in self.fabric.nodes.items(): + self.fabric.get_routing_table() + for node in self.fabric.nodes.values(): self.assertIn('get_routing_table', node.executed) def test_get_depth_chart(self): """Test the depth_chart method""" - maps = self.fabric.get_depth_chart() - for nn, node in self.fabric.nodes.items(): + self.fabric.get_depth_chart() + for node in self.fabric.nodes.values(): self.assertIn('get_depth_chart', node.executed) def test_get_link_users_factor(self): @@ -428,7 +437,7 @@ class FabricTest(unittest.TestCase): def test_composite_bmc(self): """ Test the CompositeBMC member """ with self.assertRaises(AttributeError): - self.fabric.cbmc.fake_method + self.fabric.cbmc.fake_method() self.fabric.cbmc.set_chassis_power("off") results = self.fabric.cbmc.get_chassis_status() @@ -447,6 +456,7 @@ class FabricTest(unittest.TestCase): class DummyNode(object): """ Dummy node for the nodemanager tests """ + # pylint: disable=W0613 def __init__(self, ip_address, username="admin", password="admin", tftp=None, *args, **kwargs): self.executed = [] @@ -456,34 +466,43 @@ class DummyNode(object): password=password, verbose=False) def get_power(self): + """Simulate get_power(). """ self.executed.append("get_power") return False def set_power(self, mode): + """Simulate set_power(). """ self.executed.append(("set_power", mode)) def get_power_policy(self): + """Simulate get_power_policy(). """ self.executed.append("get_power_policy") return "always-off" def set_power_policy(self, mode): + """Simulate set_power_policy(). """ self.executed.append(("set_power_policy", mode)) def mc_reset(self): + """Simulate mc_reset(). """ self.executed.append("mc_reset") def get_firmware_info(self): + """Simulate get_firmware_info(). """ self.executed.append("get_firmware_info") def is_updatable(self, package, partition_arg="INACTIVE", priority=None): + """Simulate is_updateable(). """ self.executed.append(("is_updatable", package)) def update_firmware(self, package, partition_arg="INACTIVE", priority=None): + """Simulate update_firmware(). """ self.executed.append(("update_firmware", package)) time.sleep(random.randint(0, 2)) def get_sensors(self, name=""): + """Simulate get_sensors(). """ self.executed.append("get_sensors") power_value = "%f (+/- 0) Watts" % random.uniform(0, 10) temp_value = "%f (+/- 0) degrees C" % random.uniform(30, 50) @@ -491,29 +510,37 @@ class DummyNode(object): TestSensor("Node Power", power_value), TestSensor("Board Temp", temp_value) ] - return [x for x in sensors if name.lower() in x.sensor_name.lower()] + return [s for s in sensors if name.lower() in s.sensor_name.lower()] def config_reset(self): + """Simulate config_reset(). """ self.executed.append("config_reset") def set_boot_order(self, boot_args): + """Simulate set_boot_order().""" self.executed.append(("set_boot_order", boot_args)) def get_boot_order(self): + """Simulate get_boot_order(). """ self.executed.append("get_boot_order") return ["disk", "pxe"] def set_pxe_interface(self, interface): + """Simulate set_pxe_interface(). """ self.executed.append(("set_pxe_interface", interface)) def get_pxe_interface(self): + """Simulate get_pxe_interface(). """ self.executed.append("get_pxe_interface") return "eth0" def get_versions(self): + """Simulate get_versions(). """ self.executed.append("get_versions") - class Result: + # pylint: disable=R0902, R0903 + class Result(object): + """Result Class. """ def __init__(self): self.header = "Calxeda SoC (0x0096CD)" self.hardware_version = "TestBoard X00" @@ -526,10 +553,12 @@ class DummyNode(object): return Result() def ipmitool_command(self, ipmitool_args): + """Simulate ipmitool_command(). """ self.executed.append(("ipmitool_command", ipmitool_args)) return "Dummy output" def get_ubootenv(self): + """Simulate get_ubootenv(). """ self.executed.append("get_ubootenv") ubootenv = UbootEnv() @@ -537,15 +566,20 @@ class DummyNode(object): ubootenv.variables["bootcmd_default"] = "run bootcmd_sata" return ubootenv - def get_fabric_ipinfo(self): + @staticmethod + def get_fabric_ipinfo(): + """Simulates get_fabric_ipinfo(). """ return {} + # pylint: disable=R0913 def get_server_ip(self, interface, ipv6, user, password, aggressive): + """Simulate get_server_ip(). """ self.executed.append(("get_server_ip", interface, ipv6, user, password, aggressive)) return "192.168.200.1" def get_fabric_macaddrs(self): + """Simulate get_fabric_macaddrs(). """ self.executed.append("get_fabric_macaddrs") result = {} for node in range(NUM_NODES): @@ -556,21 +590,25 @@ class DummyNode(object): return result def get_fabric_uplink_info(self): + """Simulate get_fabric_uplink_info(). """ self.executed.append('get_fabric_uplink_info') results = {} - for n in range(1, NUM_NODES): - results[n] = {'eth0': 0, 'eth1': 0, 'mgmt': 0} + for nid in range(1, NUM_NODES): + results[nid] = {'eth0': 0, 'eth1': 0, 'mgmt': 0} return results def get_uplink_info(self): + """Simulate get_uplink_info(). """ self.executed.append('get_uplink_info') return 'Node 0: eth0 0, eth1 0, mgmt 0' def get_uplink_speed(self): + """Simulate get_uplink_speed(). """ self.executed.append('get_uplink_speed') return 1 def get_link_stats(self, link=0): + """Simulate get_link_stats(). """ self.executed.append(('get_link_stats', link)) return { 'FS_LC%s_BYTE_CNT_0' % link: '0x0', @@ -595,42 +633,49 @@ class DummyNode(object): } def get_linkmap(self): + """Simulate get_linkmap(). """ self.executed.append('get_linkmap') results = {} - for n in range(0, NUM_NODES): - results[n] = {n: {1: 2, 3: 1, 4: 3}} + for nid in range(0, NUM_NODES): + results[nid] = {nid: {1: 2, 3: 1, 4: 3}} return results def get_routing_table(self): + """Simulate get_routing_table(). """ self.executed.append('get_routing_table') results = {} - for n in range(0, NUM_NODES): - results[n] = {n: {1: [0, 0, 0, 3, 0], + for nid in range(0, NUM_NODES): + results[nid] = {nid: {1: [0, 0, 0, 3, 0], 2: [0, 3, 0, 0, 2], 3: [0, 2, 0, 0, 3]}} return results def get_depth_chart(self): + """Simulate get_depth_chart(). """ self.executed.append('get_depth_chart') results = {} - for n in range(0, NUM_NODES): - results[n] = {n: {1: {'shortest': (0, 0)}, + for nid in range(0, NUM_NODES): + results[nid] = {nid: {1: {'shortest': (0, 0)}, 2: {'hops': [(3, 1)], 'shortest': (0, 0)}, 3: {'hops': [(2, 1)], 'shortest': (0, 0)}}} return results def get_uplink(self, iface): + """Simulate get_uplink(). """ self.executed.append(('get_uplink', iface)) return 0 def set_uplink(self, uplink, iface): + """Simulate set_uplink(). """ self.executed.append(('set_uplink', uplink, iface)) def get_node_fru_version(self): + """Simulate get_node_fru_version(). """ self.executed.append("get_node_fru_version") return "0.0" def get_slot_fru_version(self): + """Simulate get_slot_fru_version(). """ self.executed.append("get_slot_fru_version") return "0.0" @@ -639,14 +684,20 @@ class DummyFailNode(DummyNode): """ Dummy node that should fail on some commands """ class DummyFailError(Exception): + """Dummy Fail Error class.""" pass def get_power(self): + """Simulate get_power(). """ self.executed.append("get_power") raise DummyFailNode.DummyFailError -class DummyImage: +# pylint: disable=R0903 +class DummyImage(object): + """Dummy Image class.""" + def __init__(self, filename, image_type, *args): self.filename = filename self.type = image_type + self.args = args -- cgit v1.2.1 From 58daaa8dcc1ffae143ff7fafbc86addd73cd7bab Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 13:31:11 -0500 Subject: nojira: Fixed whitespace. --- cxmanage_api/ip_retriever.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/cxmanage_api/ip_retriever.py b/cxmanage_api/ip_retriever.py index 411465b..7e62e0e 100644 --- a/cxmanage_api/ip_retriever.py +++ b/cxmanage_api/ip_retriever.py @@ -46,8 +46,8 @@ from pyipmi.bmc import LanBMC class IPRetriever(threading.Thread): - """The IPRetriever class takes an ECME address and when run will - connect to the Linux Server from the ECME over SOL and use + """The IPRetriever class takes an ECME address and when run will + connect to the Linux Server from the ECME over SOL and use ifconfig to determine the IP address. """ verbosity = None @@ -55,7 +55,7 @@ class IPRetriever(threading.Thread): retry = None timeout = None interface = None - + ecme_ip = None ecme_user = None ecme_password = None @@ -63,14 +63,14 @@ class IPRetriever(threading.Thread): server_ip = None server_user = None server_password = None - + def __init__(self, ecme_ip, aggressive=False, verbosity=0, **kwargs): """Initializes the IPRetriever class. The IPRetriever needs the only the first node to know where to start. """ super(IPRetriever, self).__init__() self.daemon = True - + if hasattr(ecme_ip, 'ip_address'): self.ecme_ip = ecme_ip.ip_address else: @@ -78,7 +78,7 @@ class IPRetriever(threading.Thread): self.aggressive = aggressive self.verbosity = verbosity - + # Everything here is optional self.timeout = kwargs.get('timeout', 120) self.retry = kwargs.get('retry', 0) @@ -95,20 +95,20 @@ class IPRetriever(threading.Thread): self._ip_pattern = kwargs['_ip_pattern'] else: - self.set_interface(kwargs.get('interface', None), + self.set_interface(kwargs.get('interface', None), kwargs.get('ipv6', False)) if 'bmc' in kwargs: self._bmc = kwargs['bmc'] else: - self._bmc = make_bmc(LanBMC, verbose=(self.verbosity>1), - hostname=self.ecme_ip, + self._bmc = make_bmc(LanBMC, verbose=(self.verbosity > 1), + hostname=self.ecme_ip, username=self.ecme_user, password=self.ecme_password) if 'config_path' in kwargs: self.read_config(kwargs['config_path']) - + def set_interface(self, interface=None, ipv6=False): @@ -119,11 +119,11 @@ class IPRetriever(threading.Thread): self.interface = interface if not ipv6: - self._ip_pattern = re.compile('\d+\.'*3 + '\d+') + self._ip_pattern = re.compile('\d+\.' * 3 + '\d+') self._inet_pattern = re.compile('inet addr:(%s)' % self._ip_pattern.pattern) else: - self._ip_pattern = re.compile('[0-9a-fA-F:]*:'*2 + '[0-9a-fA-F:]+') + self._ip_pattern = re.compile('[0-9a-fA-F:]*:' * 2 + '[0-9a-fA-F:]+') self._inet_pattern = re.compile('inet6 addr: ?(%s)' % self._ip_pattern.pattern) @@ -137,7 +137,7 @@ class IPRetriever(threading.Thread): def run(self): - """Attempts to finds the server IP address associated with the + """Attempts to finds the server IP address associated with the ECME IP. If successful, server_ip will contain the IP address. """ if self.server_ip is not None: @@ -160,7 +160,7 @@ class IPRetriever(threading.Thread): """ server = Server(self._bmc) - if cycle: + if cycle: self._log('Powering server off') server.power_off() sleep(5) @@ -201,15 +201,15 @@ class IPRetriever(threading.Thread): raise IPDiscoveryError('Could not find interface %s' % self.interface) - else: # Failed to find interface. Returning None + else: # Failed to find interface. Returning None return None - + def sol_try_command(self, command): """Connects to the server over a SOL connection. Attempts to run the given command on the server without knowing the state of the server. The command must return None if - it fails. If aggresive is True, then the server may be + it fails. If aggresive is True, then the server may be restarted or power cycled to try and reset the state. """ server = Server(self._bmc) @@ -303,7 +303,7 @@ class IPRetriever(threading.Thread): else: # Assume where are at a prompt and able to run the command value = command(session) - + if value is not None: self._bmc.deactivate_payload() return value -- cgit v1.2.1 From 39fc7061fb9731b00276ace4c1131aa20c10de03 Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 13:34:30 -0500 Subject: nojira: Fixed pylint warnings. --- cxmanage_api/ip_retriever.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/cxmanage_api/ip_retriever.py b/cxmanage_api/ip_retriever.py index 7e62e0e..39fbb30 100644 --- a/cxmanage_api/ip_retriever.py +++ b/cxmanage_api/ip_retriever.py @@ -1,4 +1,5 @@ -#!/usr/bin/env python +"""Calxeda: ip_retriever.py""" + # Copyright (c) 2012, Calxeda Inc. # @@ -30,6 +31,7 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. + import sys import re import json @@ -45,6 +47,7 @@ from pyipmi.server import Server from pyipmi.bmc import LanBMC +# pylint: disable=R0902 class IPRetriever(threading.Thread): """The IPRetriever class takes an ECME address and when run will connect to the Linux Server from the ECME over SOL and use @@ -119,11 +122,13 @@ class IPRetriever(threading.Thread): self.interface = interface if not ipv6: - self._ip_pattern = re.compile('\d+\.' * 3 + '\d+') + self._ip_pattern = re.compile(r'\d+\.' * 3 + r'\d+') self._inet_pattern = re.compile('inet addr:(%s)' % self._ip_pattern.pattern) else: - self._ip_pattern = re.compile('[0-9a-fA-F:]*:' * 2 + '[0-9a-fA-F:]+') + self._ip_pattern = re.compile( + '[0-9a-fA-F:]*:' * 2 + '[0-9a-fA-F:]+' + ) self._inet_pattern = re.compile('inet6 addr: ?(%s)' % self._ip_pattern.pattern) @@ -144,7 +149,7 @@ class IPRetriever(threading.Thread): self._log('Using stored IP %s' % self.server_ip) return - for attempt in range(self.retry + 1): + for _ in range(self.retry + 1): self.server_ip = self.sol_try_command(self.sol_find_ip) if self.server_ip is not None: @@ -205,6 +210,7 @@ class IPRetriever(threading.Thread): return None + # pylint: disable=R0912, R0915 def sol_try_command(self, command): """Connects to the server over a SOL connection. Attempts to run the given command on the server without knowing -- cgit v1.2.1 From 59fc5e25ef6503825ff059259309851d6d7d06d2 Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 13:40:03 -0500 Subject: nojira: Fixed pylint warnings. --- cxmanage_api/__init__.py | 3 +++ cxmanage_api/docs/generate_api_rst.py | 2 +- cxmanage_api/loggers.py | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/cxmanage_api/__init__.py b/cxmanage_api/__init__.py index 2228b38..6d66381 100644 --- a/cxmanage_api/__init__.py +++ b/cxmanage_api/__init__.py @@ -1,3 +1,6 @@ +"""Calxeda: __init__.py """ + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. diff --git a/cxmanage_api/docs/generate_api_rst.py b/cxmanage_api/docs/generate_api_rst.py index 1e5a901..553d3c8 100755 --- a/cxmanage_api/docs/generate_api_rst.py +++ b/cxmanage_api/docs/generate_api_rst.py @@ -62,7 +62,7 @@ def get_source(source_dir): source = {API_NAME : {}} paths = glob.glob(os.path.join(source_dir, '*.py')) for path in paths: - f_path, f_ext = os.path.splitext(path) + f_path, _ = os.path.splitext(path) f_name = f_path.split(source_dir)[1] if (not f_name in BLACKLIST): if TITLES.has_key(f_name): diff --git a/cxmanage_api/loggers.py b/cxmanage_api/loggers.py index da7c202..9657ef9 100644 --- a/cxmanage_api/loggers.py +++ b/cxmanage_api/loggers.py @@ -117,6 +117,7 @@ class Logger(object): return '\n'.join(result) + # pylint: disable=R0201 def write(self, message): """Writes a log message. -- cgit v1.2.1 From 8c4f2d3a2320f7f31c6b2db0d9a9f9938f864820 Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 13:44:57 -0500 Subject: nojira: fixed pylint warnings. --- cxmanage_api/fabric.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/cxmanage_api/fabric.py b/cxmanage_api/fabric.py index c13f55c..8a42ea7 100644 --- a/cxmanage_api/fabric.py +++ b/cxmanage_api/fabric.py @@ -1,3 +1,7 @@ +# pylint: disable=C0302 +"""Calxeda: fabric.py """ + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -34,6 +38,7 @@ from cxmanage_api.node import Node as NODE from cxmanage_api.cx_exceptions import CommandFailedError +# pylint: disable=R0902,R0903, R0904 class Fabric(object): """ The Fabric class provides management of multiple nodes. @@ -103,6 +108,7 @@ class Fabric(object): return function + # pylint: disable=R0913 def __init__(self, ip_address, username="admin", password="admin", tftp=None, ecme_tftp_port=5001, task_queue=None, verbose=False, node=None): @@ -603,7 +609,8 @@ class Fabric(object): } .. seealso:: - `Node.get_versions() `_ + `Node.get_versions() \ +`_ :param async: Flag that determines if the command result (dictionary) is returned or a Command object (can get status, etc.). @@ -642,7 +649,8 @@ class Fabric(object): } .. seealso:: - `Node.get_versions_dict() `_ + `Node.get_versions_dict() \ +`_ :param async: Flag that determines if the command result (dictionary) is returned or a Task object (can get status, etc.). @@ -701,6 +709,7 @@ class Fabric(object): """ return self._run_on_all_nodes(async, "get_ubootenv") + # pylint: disable=R0913 def get_server_ip(self, interface=None, ipv6=False, user="user1", password="1Password", aggressive=False, async=False): """Get the server IP address from all nodes. The nodes must be powered @@ -831,7 +840,7 @@ class Fabric(object): :param nodeid: Node id from which the macaddr is to be remove :type nodeid: integer - :param iface: interface on the node from which the macaddr is to be removed + :param iface: interface on the node which the macaddr is to be removed :type iface: integer :param macaddr: mac address to be removed :type macaddr: string @@ -994,7 +1003,7 @@ class Fabric(object): }} >>> # >>> # Output trimmed for brevity ... - >>> # The data shown for node 0 is the same type of data presented for each + >>> # The data shown for node 0 is the same type of data for each >>> # node in the fabric. >>> # -- cgit v1.2.1 From 4a109b36292a6589b57465401ee47ad8a42970e2 Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 13:51:00 -0500 Subject: nojira: made python 3 print compatible. --- cxmanage/__init__.py | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/cxmanage/__init__.py b/cxmanage/__init__.py index 9491581..91f38ae 100644 --- a/cxmanage/__init__.py +++ b/cxmanage/__init__.py @@ -1,3 +1,6 @@ +"""Calxeda: __init__.py """ + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -28,6 +31,7 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. + import sys import time @@ -84,7 +88,7 @@ def get_nodes(args, tftp, verify_prompt=False): if args.all_nodes: if not args.quiet: - print "Getting IP addresses..." + print("Getting IP addresses...") results, errors = run_command(args, nodes, "get_fabric_ipinfo") @@ -104,13 +108,13 @@ def get_nodes(args, tftp, verify_prompt=False): node_strings = get_node_strings(args, all_nodes, justify=False) if not args.quiet and all_nodes: - print "Discovered the following IP addresses:" + print("Discovered the following IP addresses:") for node in all_nodes: print node_strings[node] print if errors: - print "ERROR: Failed to get IP addresses. Aborting.\n" + print("ERROR: Failed to get IP addresses. Aborting.\n") sys.exit(1) if args.nodes: @@ -119,9 +123,12 @@ def get_nodes(args, tftp, verify_prompt=False): % (len(all_nodes), args.nodes)) sys.exit(1) elif verify_prompt and not args.force: - print "NOTE: Please check node count! Ensure discovery of all nodes in the cluster." - print "Power cycle your system if the discovered node count does not equal nodes in" - print "your system.\n" + print( + "NOTE: Please check node count! Ensure discovery of all " + + "nodes in the cluster. Power cycle your system if the " + + "discovered node count does not equal nodes in" + + "your system.\n" + ) if not prompt_yes("Discovered %i nodes. Continue?" % len(all_nodes)): sys.exit(1) @@ -182,11 +189,13 @@ def run_command(args, nodes, name, *method_args): elif task.status == "Failed": errors[node] = task.error else: - errors[node] = KeyboardInterrupt("Aborted by keyboard interrupt") + errors[node] = KeyboardInterrupt( + "Aborted by keyboard interrupt" + ) if not args.quiet: _print_command_status(tasks, counter) - print "\n" + print("\n") # Handle errors should_retry = False @@ -206,9 +215,9 @@ def run_command(args, nodes, name, *method_args): elif args.retry >= 1: should_retry = True if args.retry == 1: - print "Retrying command 1 more time..." + print("Retrying command 1 more time...") elif args.retry > 1: - print "Retrying command %i more times..." % args.retry + print("Retrying command %i more times..." % args.retry) args.retry -= 1 if should_retry: @@ -300,17 +309,20 @@ def _print_errors(args, nodes, errors): """ Print errors if they occured """ if errors: node_strings = get_node_strings(args, nodes, justify=True) - print "Command failed on these hosts" + print("Command failed on these hosts") for node in nodes: if node in errors: - print "%s: %s" % (node_strings[node], errors[node]) + print("%s: %s" % (node_strings[node], errors[node])) print # Print a special message for TFTP errors if all(isinstance(x, TftpException) for x in errors.itervalues()): - print "There may be networking issues (when behind NAT) between the host (where" - print "cxmanage is running) and the Calxeda node when establishing a TFTP session." - print "Please refer to the documentation for more information.\n" + print( + "There may be networking issues (when behind NAT) between " + + "the host (where cxmanage is running) and the Calxeda node " + + "when establishing a TFTP session. Please refer to the " + + "documentation for more information.\n" + ) def _print_command_status(tasks, counter): -- cgit v1.2.1 From b400dbe3dc16cc44c8be7b7e855474d28d19fc64 Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 14:01:30 -0500 Subject: nojira: fixed pylint warnings. --- cxmanage/__init__.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/cxmanage/__init__.py b/cxmanage/__init__.py index 91f38ae..5b2e191 100644 --- a/cxmanage/__init__.py +++ b/cxmanage/__init__.py @@ -75,7 +75,7 @@ def get_tftp(args): return InternalTftp(verbose=args.verbose) - +# pylint: disable=R0912 def get_nodes(args, tftp, verify_prompt=False): """Get nodes""" hosts = [] @@ -142,6 +142,7 @@ def get_node_strings(args, nodes, justify=False): """ Get string representations for the nodes. """ # Use the private _node_id instead of node_id. Strange choice, # but we want to avoid accidentally polling the BMC. + # pylint: disable=W0212 if args.ids and all(x._node_id != None for x in nodes): strings = ["Node %i (%s)" % (x._node_id, x.ip_address) for x in nodes] else: @@ -154,7 +155,9 @@ def get_node_strings(args, nodes, justify=False): return dict(zip(nodes, strings)) +# pylint: disable=R0915 def run_command(args, nodes, name, *method_args): + """Runs a command on nodes.""" if args.threads != None: task_queue = TaskQueue(threads=args.threads, delay=args.command_delay) else: @@ -229,6 +232,7 @@ def run_command(args, nodes, name, *method_args): def prompt_yes(prompt): + """Prompts the user. """ sys.stdout.write("%s (y/n) " % prompt) sys.stdout.flush() while True: @@ -241,8 +245,11 @@ def prompt_yes(prompt): return False -def parse_host_entry(entry, hostfiles=set()): +def parse_host_entry(entry, hostfiles=None): """parse a host entry""" + if not(hostfiles): + hostfiles = set() + try: return parse_hostfile_entry(entry, hostfiles) except ValueError: @@ -252,8 +259,11 @@ def parse_host_entry(entry, hostfiles=set()): return [entry] -def parse_hostfile_entry(entry, hostfiles=set()): +def parse_hostfile_entry(entry, hostfiles=None): """parse a hostfile entry, returning a list of hosts""" + if not(hostfiles): + hostfiles = set() + if entry.startswith('file='): filename = entry[5:] elif entry.startswith('hostfile='): @@ -284,12 +294,13 @@ def parse_ip_range_entry(entry): start, end = entry.split('-') # Convert start address to int - start_bytes = map(int, start.split('.')) + start_bytes = [int(x) for x in start.split('.')] + start_i = ((start_bytes[0] << 24) | (start_bytes[1] << 16) | (start_bytes[2] << 8) | (start_bytes[3])) # Convert end address to int - end_bytes = map(int, end.split('.')) + end_bytes = [int(x) for x in end.split('.')] end_i = ((end_bytes[0] << 24) | (end_bytes[1] << 16) | (end_bytes[2] << 8) | (end_bytes[3])) -- cgit v1.2.1 From 3ace51cad09a69766a99c50018c4adc5b2342ab9 Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 15:04:25 -0500 Subject: nojira: fixed pylint warns. --- cxmanage_api/ubootenv.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/cxmanage_api/ubootenv.py b/cxmanage_api/ubootenv.py index cd1a35a..da9b37c 100644 --- a/cxmanage_api/ubootenv.py +++ b/cxmanage_api/ubootenv.py @@ -1,3 +1,5 @@ +"""Calxeda: ubootenv.py """ + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -43,7 +45,7 @@ UBOOTENV_V2_VARIABLES = ["bootcmd0", "init_scsi", "bootcmd_scsi", "init_pxe", "bootcmd_pxe", "devnum"] -class UbootEnv: +class UbootEnv(object): """Represents a U-Boot Environment. >>> from cxmanage_api.ubootenv import UbootEnv @@ -68,6 +70,7 @@ class UbootEnv: part = line.partition("=") self.variables[part[0]] = part[2] + # pylint: disable=R0912 def set_boot_order(self, boot_args): """Sets the boot order specified in the uboot environment. @@ -117,6 +120,7 @@ class UbootEnv: commands.append("run bootcmd_sata") elif arg.startswith("disk"): try: + # pylint: disable=W0141 dev, part = map(int, arg[4:].split(":")) bootdevice = "%i:%i" % (dev, part) except ValueError: @@ -130,13 +134,15 @@ class UbootEnv: commands.append("run init_scsi && run bootcmd_scsi") elif arg.startswith("disk"): try: + # pylint: disable=W0141 dev, part = map(int, arg[4:].split(":")) bootdevice = "%i:%i" % (dev, part) except ValueError: bootdevice = str(int(arg[4:])) commands.append( - "setenv devnum %s && run init_scsi && run bootcmd_scsi" - % bootdevice) + "setenv devnum %s && run init_scsi && run bootcmd_scsi" + % bootdevice + ) if retry and reset: raise ValueError("retry and reset are mutually exclusive") @@ -208,7 +214,7 @@ class UbootEnv: if not boot_args: boot_args = ["none"] - validate_boot_args(boot_args) # sanity check + validate_boot_args(boot_args) # sanity check return boot_args @@ -230,10 +236,6 @@ class UbootEnv: if interface == self.get_pxe_interface(): return - commands = [] - retry = False - reset = False - if interface == "eth0": self.variables["ethprime"] = "xgmac0" elif (interface == "eth1"): @@ -303,6 +305,7 @@ def validate_boot_args(boot_args): continue elif arg.startswith("disk"): try: + # pylint: disable=W0141 map(int, arg[4:].split(":")) except ValueError: try: -- cgit v1.2.1 From 9be00de74e3f8c5e62c50b52fd0336098d1f98ca Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 15:11:01 -0500 Subject: nojira: fixed pylint warns. --- cxmanage/commands/config.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/cxmanage/commands/config.py b/cxmanage/commands/config.py index 3d5b060..bbe5256 100644 --- a/cxmanage/commands/config.py +++ b/cxmanage/commands/config.py @@ -1,3 +1,6 @@ +"""Calxeda: config.py """ + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -28,9 +31,10 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. + from cxmanage import get_tftp, get_nodes, get_node_strings, run_command -from cxmanage_api.ubootenv import UbootEnv, validate_boot_args, \ +from cxmanage_api.ubootenv import validate_boot_args, \ validate_pxe_interface @@ -42,7 +46,7 @@ def config_reset_command(args): if not args.quiet: print "Sending config reset command..." - results, errors = run_command(args, nodes, "config_reset") + _, errors = run_command(args, nodes, "config_reset") if not args.quiet and not errors: print "Command completed successfully.\n" @@ -63,7 +67,7 @@ def config_boot_command(args): if not args.quiet: print "Setting boot order..." - results, errors = run_command(args, nodes, "set_boot_order", + _, errors = run_command(args, nodes, "set_boot_order", args.boot_order) if not args.quiet and not errors: @@ -73,6 +77,7 @@ def config_boot_command(args): def config_boot_status_command(args): + """Get boot status command.""" tftp = get_tftp(args) nodes = get_nodes(args, tftp) @@ -108,7 +113,7 @@ def config_pxe_command(args): if not args.quiet: print "Setting pxe interface..." - results, errors = run_command(args, nodes, "set_pxe_interface", + _, errors = run_command(args, nodes, "set_pxe_interface", args.interface) if not args.quiet and not errors: @@ -118,6 +123,7 @@ def config_pxe_command(args): def config_pxe_status_command(args): + """Gets pxe status.""" tftp = get_tftp(args) nodes = get_nodes(args, tftp) -- cgit v1.2.1 From 3a8ee1d146885a3978be52a2304966676b65afa7 Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 26 Aug 2013 15:12:50 -0500 Subject: nojira: fixed pylint warns. --- cxmanage/commands/fru_version.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/cxmanage/commands/fru_version.py b/cxmanage/commands/fru_version.py index 02ea0eb..1dd318c 100644 --- a/cxmanage/commands/fru_version.py +++ b/cxmanage/commands/fru_version.py @@ -1,3 +1,5 @@ +"""Calxeda: fru_version.py """ + # Copyright (c) 2013, Calxeda Inc. # @@ -38,32 +40,32 @@ def node_fru_version_command(args): tftp = get_tftp(args) nodes = get_nodes(args, tftp) results, errors = run_command(args, nodes, 'get_node_fru_version') - + # Print results if we were successful if results: node_strings = get_node_strings(args, results, justify=True) for node in nodes: print("%s: %s" % (node_strings[node], results[node])) - - print("") # For readability + + print("") # For readability if not args.quiet and errors: print('Some errors occured during the command.\n') -def slot_fru_version_command(args): +def slot_fru_version_command(args): """Get the slot FRU version for each node. """ tftp = get_tftp(args) nodes = get_nodes(args, tftp) results, errors = run_command(args, nodes, 'get_slot_fru_version') - + # Print results if we were successful - if results: + if results: node_strings = get_node_strings(args, results, justify=True) for node in nodes: print("%s: %s" % (node_strings[node], results[node])) - print("") # For readability + print("") # For readability if not args.quiet and errors: print('Some errors occured during the command.\n') -- cgit v1.2.1 From d9b6bf191872d7b77e36729b903fcb74c2d795d4 Mon Sep 17 00:00:00 2001 From: evasquez Date: Tue, 27 Aug 2013 08:45:01 -0500 Subject: nojira: fixed pylint warns. --- cxmanage/commands/fw.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/cxmanage/commands/fw.py b/cxmanage/commands/fw.py index 99ed4fe..611315e 100644 --- a/cxmanage/commands/fw.py +++ b/cxmanage/commands/fw.py @@ -1,3 +1,5 @@ +"""Calxeda: fw.py """ + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -37,7 +39,7 @@ from cxmanage import get_tftp, get_nodes, get_node_strings, run_command, \ from cxmanage_api.image import Image from cxmanage_api.firmware_package import FirmwarePackage - +# pylint: disable=R0912 def fwupdate_command(args): """update firmware on a cluster or host""" def do_update(): @@ -46,7 +48,7 @@ def fwupdate_command(args): if not args.quiet: print "Checking hosts..." - results, errors = run_command(args, nodes, "_check_firmware", + _, errors = run_command(args, nodes, "_check_firmware", package, args.partition, args.priority) if errors: print "ERROR: Firmware update aborted." @@ -55,7 +57,7 @@ def fwupdate_command(args): if not args.quiet: print "Updating firmware..." - results, errors = run_command(args, nodes, "update_firmware", package, + _, errors = run_command(args, nodes, "update_firmware", package, args.partition, args.priority) if errors: print "ERROR: Firmware update failed." @@ -79,7 +81,7 @@ def fwupdate_command(args): print "ERROR: MC reset is unsafe on ECME version v%s" % version print "Please power cycle the system and start a new fwupdate." return True - + if not args.quiet: print "Resetting nodes..." @@ -104,16 +106,21 @@ def fwupdate_command(args): args.skip_crc32, args.fw_version) package = FirmwarePackage() package.images.append(image) - except ValueError as e: - print "ERROR: %s" % e + except ValueError as err: + print "ERROR: %s" % err return True if not args.all_nodes: if args.force: - print 'WARNING: Updating firmware without --all-nodes is dangerous.' + print( + 'WARNING: Updating firmware without --all-nodes' + + ' is dangerous.' + ) else: if not prompt_yes( - 'WARNING: Updating firmware without --all-nodes is dangerous. Continue?'): + 'WARNING: Updating firmware without ' + + '--all-nodes is dangerous. Continue?' + ): return 1 tftp = get_tftp(args) -- cgit v1.2.1 From c46008c3f6e842373521b1b18d6dc3af0a459841 Mon Sep 17 00:00:00 2001 From: evasquez Date: Tue, 27 Aug 2013 08:47:45 -0500 Subject: nojira: fixed pylint warns. --- cxmanage/commands/power.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cxmanage/commands/power.py b/cxmanage/commands/power.py index b5b6015..623c38d 100644 --- a/cxmanage/commands/power.py +++ b/cxmanage/commands/power.py @@ -1,3 +1,6 @@ +"""Calxeda: power.py """ + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -39,7 +42,7 @@ def power_command(args): if not args.quiet: print 'Sending power %s command...' % args.power_mode - results, errors = run_command(args, nodes, 'set_power', args.power_mode) + _, errors = run_command(args, nodes, 'set_power', args.power_mode) if not args.quiet and not errors: print 'Command completed successfully.\n' @@ -48,6 +51,7 @@ def power_command(args): def power_status_command(args): + """Executes the power status command with args.""" tftp = get_tftp(args) nodes = get_nodes(args, tftp) @@ -72,13 +76,14 @@ def power_status_command(args): def power_policy_command(args): + """Executes power policy command with args.""" tftp = get_tftp(args) nodes = get_nodes(args, tftp) if not args.quiet: print 'Setting power policy to %s...' % args.policy - results, errors = run_command(args, nodes, 'set_power_policy', + _, errors = run_command(args, nodes, 'set_power_policy', args.policy) if not args.quiet and not errors: @@ -88,6 +93,7 @@ def power_policy_command(args): def power_policy_status_command(args): + """Executes the power policy status command with args.""" tftp = get_tftp(args) nodes = get_nodes(args, tftp) -- cgit v1.2.1 From ce806151e61c95a2fcf60cf83ebf3d222fa79fa5 Mon Sep 17 00:00:00 2001 From: evasquez Date: Tue, 27 Aug 2013 08:51:03 -0500 Subject: nojira: fixed pylint warnings. --- cxmanage_test/__init__.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/cxmanage_test/__init__.py b/cxmanage_test/__init__.py index 2033b60..61bf116 100644 --- a/cxmanage_test/__init__.py +++ b/cxmanage_test/__init__.py @@ -1,3 +1,6 @@ +"""Calxeda: __init__.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -29,8 +32,6 @@ # DAMAGE. -""" Various objects used by tests """ - import os import random import tempfile @@ -39,17 +40,20 @@ from cxmanage_api.image import Image def random_file(size): """ Create a random file """ - contents = "".join([chr(random.randint(0, 255)) for a in range(size)]) - fd, filename = tempfile.mkstemp(prefix='cxmanage_test-') - with os.fdopen(fd, "w") as f: - f.write(contents) + contents = "".join([chr(random.randint(0, 255)) for _ in range(size)]) + file_, filename = tempfile.mkstemp(prefix='cxmanage_test-') + with os.fdopen(file_, "w") as file_handle: + file_handle.write(contents) + return filename class TestImage(Image): + """TestImage Class.""" def verify(self): return True -class TestSensor: +# pylint: disable=R0903 +class TestSensor(object): """ Sensor result from bmc/target """ def __init__(self, sensor_name, sensor_reading): self.sensor_name = sensor_name -- cgit v1.2.1 From ff75828258eccbca8199c76d6403c611e67ff766 Mon Sep 17 00:00:00 2001 From: evasquez Date: Tue, 27 Aug 2013 08:54:27 -0500 Subject: nojira: pylint ... more of it ... --- cxmanage_api/tasks.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/cxmanage_api/tasks.py b/cxmanage_api/tasks.py index 7ed7851..98fdd3e 100644 --- a/cxmanage_api/tasks.py +++ b/cxmanage_api/tasks.py @@ -1,3 +1,6 @@ +"""Calxeda: tasks.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -73,8 +76,9 @@ class Task(object): try: self.result = self._method(*self._args, **self._kwargs) self.status = "Completed" - except Exception as e: - self.error = e + # pylint: disable=W0703 + except Exception as err: + self.error = err self.status = "Failed" self._finished.set() @@ -167,8 +171,11 @@ class TaskWorker(Thread): while True: sleep(self._delay) task = self._task_queue.get() + # pylint: disable=W0212 task._run() - except: + # pylint: disable=W0703 + except Exception: + # pylint: disable=W0212 self._task_queue._remove_worker() DEFAULT_TASK_QUEUE = TaskQueue() -- cgit v1.2.1 From 46e57fd22500e33ae807ab6302105381d91d3745 Mon Sep 17 00:00:00 2001 From: evasquez Date: Tue, 27 Aug 2013 08:55:31 -0500 Subject: nojira: pylint --- setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.py b/setup.py index f4f087f..56b7088 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,6 @@ +"""Calxeda: setup.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. -- cgit v1.2.1 From c4d0f51633d3a9485541146b4213e543f6d13b0f Mon Sep 17 00:00:00 2001 From: evasquez Date: Tue, 27 Aug 2013 08:56:42 -0500 Subject: nojira: pylint --- cxmanage_api/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cxmanage_api/__init__.py b/cxmanage_api/__init__.py index 6d66381..4e7c0e4 100644 --- a/cxmanage_api/__init__.py +++ b/cxmanage_api/__init__.py @@ -50,8 +50,8 @@ def temp_file(): :rtype: string """ - fd, filename = tempfile.mkstemp(dir=WORK_DIR) - os.close(fd) + file_, filename = tempfile.mkstemp(dir=WORK_DIR) + os.close(file_) return filename def temp_dir(): -- cgit v1.2.1 From 5e066057941ad399eaa252f96b783f7fe1ea0209 Mon Sep 17 00:00:00 2001 From: evasquez Date: Tue, 27 Aug 2013 09:06:28 -0500 Subject: nojira: pylint --- cxmanage/commands/fabric.py | 5 ++++- cxmanage/commands/info.py | 4 ++++ cxmanage/commands/ipdiscover.py | 3 +++ cxmanage/commands/ipmitool.py | 4 ++++ cxmanage/commands/mc.py | 6 +++++- cxmanage/commands/sensor.py | 6 +++++- cxmanage/commands/tspackage.py | 17 +++++++++++------ 7 files changed, 36 insertions(+), 9 deletions(-) diff --git a/cxmanage/commands/fabric.py b/cxmanage/commands/fabric.py index 3bf84c2..8bc3f65 100644 --- a/cxmanage/commands/fabric.py +++ b/cxmanage/commands/fabric.py @@ -1,3 +1,6 @@ +"""Calxeda: fabric.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -41,7 +44,7 @@ def ipinfo_command(args): if not args.quiet: print "Getting IP addresses..." - results, errors = run_command(args, nodes, "get_fabric_ipinfo") + results, _ = run_command(args, nodes, "get_fabric_ipinfo") for node in nodes: if node in results: diff --git a/cxmanage/commands/info.py b/cxmanage/commands/info.py index 531c939..b15d2c6 100644 --- a/cxmanage/commands/info.py +++ b/cxmanage/commands/info.py @@ -1,3 +1,6 @@ +"""Calxeda: info.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -76,6 +79,7 @@ def info_basic_command(args): def info_ubootenv_command(args): + """Print uboot info""" tftp = get_tftp(args) nodes = get_nodes(args, tftp) diff --git a/cxmanage/commands/ipdiscover.py b/cxmanage/commands/ipdiscover.py index f619d16..bd09413 100644 --- a/cxmanage/commands/ipdiscover.py +++ b/cxmanage/commands/ipdiscover.py @@ -1,3 +1,6 @@ +"""Calxeda: ipdiscover.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. diff --git a/cxmanage/commands/ipmitool.py b/cxmanage/commands/ipmitool.py index f8baf80..ac21e00 100644 --- a/cxmanage/commands/ipmitool.py +++ b/cxmanage/commands/ipmitool.py @@ -1,3 +1,6 @@ +"""Calxeda: ipmitool.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -28,6 +31,7 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. + from cxmanage import get_tftp, get_nodes, get_node_strings, run_command diff --git a/cxmanage/commands/mc.py b/cxmanage/commands/mc.py index 2573540..6c42615 100644 --- a/cxmanage/commands/mc.py +++ b/cxmanage/commands/mc.py @@ -1,3 +1,6 @@ +"""Calxeda: mc.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -28,6 +31,7 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. + from cxmanage import get_tftp, get_nodes, run_command @@ -39,7 +43,7 @@ def mcreset_command(args): if not args.quiet: print 'Sending MC reset command...' - results, errors = run_command(args, nodes, 'mc_reset') + _, errors = run_command(args, nodes, 'mc_reset') if not args.quiet and not errors: print 'Command completed successfully.\n' diff --git a/cxmanage/commands/sensor.py b/cxmanage/commands/sensor.py index c3fed32..3a27143 100644 --- a/cxmanage/commands/sensor.py +++ b/cxmanage/commands/sensor.py @@ -1,3 +1,6 @@ +"""Calxeda: sensor.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -28,9 +31,10 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. -from cxmanage import get_tftp, get_nodes, get_node_strings, run_command +from cxmanage import get_tftp, get_nodes, get_node_strings, run_command +# pylint: disable=R0914 def sensor_command(args): """read sensor values from a cluster or host""" tftp = get_tftp(args) diff --git a/cxmanage/commands/tspackage.py b/cxmanage/commands/tspackage.py index 88f46e3..d6ee198 100644 --- a/cxmanage/commands/tspackage.py +++ b/cxmanage/commands/tspackage.py @@ -1,3 +1,6 @@ +"""Calxeda: tspackage.py""" + + # Copyright 2013 Calxeda, Inc. # # All rights reserved. @@ -29,12 +32,13 @@ # DAMAGE. -"""A cxmanage command to collect information about a node and archive it. - -Example: -cxmanage tspackage 10.10.10.10 +# +# A cxmanage command to collect information about a node and archive it. +# +# Example: +# cxmanage tspackage 10.10.10.10 +# -""" import os import time @@ -227,7 +231,7 @@ def write_mac_addrs(args, nodes): write_to_file(node, lines) - +# pylint: disable=R0914 def write_sensor_info(args, nodes): """Write sensor information for each node to their respective files.""" args.sensor_name = "" @@ -332,6 +336,7 @@ def write_sel(args, nodes): for event in results[node]: lines.append(event) + # pylint: disable=W0703 except Exception as error: lines.append("Could not get SEL! " + str(error)) if not args.quiet: -- cgit v1.2.1 From d1a1f42b1e725bcc6604912c688f7b38f65c02d6 Mon Sep 17 00:00:00 2001 From: evasquez Date: Tue, 27 Aug 2013 09:50:27 -0500 Subject: nojira: pylint fixes --- cxmanage_api/cx_exceptions.py | 10 +++++++--- cxmanage_api/firmware_package.py | 7 ++++++- cxmanage_api/image.py | 10 +++++++--- cxmanage_api/simg.py | 6 +++++- cxmanage_api/tftp.py | 10 +++++++--- cxmanage_test/tftp_test.py | 13 +++++++++++-- 6 files changed, 43 insertions(+), 13 deletions(-) diff --git a/cxmanage_api/cx_exceptions.py b/cxmanage_api/cx_exceptions.py index 0cfa778..d924fe9 100644 --- a/cxmanage_api/cx_exceptions.py +++ b/cxmanage_api/cx_exceptions.py @@ -1,3 +1,6 @@ +"""Calxeda: cx_exceptions.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -28,10 +31,10 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. -"""Defines the custom exceptions used by the cxmanage_api project.""" -from pyipmi import IpmiError -from tftpy.TftpShared import TftpException +# +# Defines the custom exceptions used by the cxmanage_api project. +# class TimeoutError(Exception): @@ -304,6 +307,7 @@ class CommandFailedError(Exception): def __init__(self, results, errors): """Default constructor for the CommandFailedError class.""" + super(CommandFailedError, self).__init__() self.results = results self.errors = errors diff --git a/cxmanage_api/firmware_package.py b/cxmanage_api/firmware_package.py index 433b596..0be4f07 100644 --- a/cxmanage_api/firmware_package.py +++ b/cxmanage_api/firmware_package.py @@ -1,3 +1,6 @@ +"""Calxeda: firmware_package.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -38,7 +41,8 @@ from cxmanage_api import temp_dir from cxmanage_api.image import Image -class FirmwarePackage: +# pylint: disable=R0903 +class FirmwarePackage(object): """A firmware update package contains multiple images & version information. .. note:: @@ -54,6 +58,7 @@ class FirmwarePackage: """ + # pylint: disable=R0912 def __init__(self, filename=None): """Default constructor for the FirmwarePackage class.""" self.images = [] diff --git a/cxmanage_api/image.py b/cxmanage_api/image.py index 87b73a0..a6456d4 100644 --- a/cxmanage_api/image.py +++ b/cxmanage_api/image.py @@ -1,3 +1,6 @@ +"""Calxeda: image.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -38,7 +41,7 @@ from cxmanage_api.simg import valid_simg, get_simg_contents from cxmanage_api.cx_exceptions import InvalidImageError -class Image: +class Image(object): """An Image consists of: an image type, a filename, and SIMG header info. >>> from cxmanage_api.image import Image @@ -62,6 +65,7 @@ class Image: """ + # pylint: disable=R0913 def __init__(self, filename, image_type, simg=None, daddr=None, skip_crc32=False, version=None): """Default constructor for the Image class.""" @@ -114,8 +118,8 @@ class Image: skip_crc32=self.skip_crc32, align=align, version=self.version) filename = temp_file() - with open(filename, "w") as f: - f.write(simg) + with open(filename, "w") as file_: + file_.write(simg) # Make sure the simg was built correctly if (not valid_simg(open(filename).read())): diff --git a/cxmanage_api/simg.py b/cxmanage_api/simg.py index 6ae8bf8..d8901b8 100644 --- a/cxmanage_api/simg.py +++ b/cxmanage_api/simg.py @@ -1,3 +1,6 @@ +"""Calxeda: simg.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -38,7 +41,8 @@ HEADER_LENGTH = 60 MIN_HEADER_LENGTH = 28 -class SIMGHeader: +# pylint: disable=R0913, R0903, R0902 +class SIMGHeader(object): """Container for an SIMG header. >>> from cxmanage_api.simg import SIMGHeader diff --git a/cxmanage_api/tftp.py b/cxmanage_api/tftp.py index 5bfc3dc..0b33db8 100644 --- a/cxmanage_api/tftp.py +++ b/cxmanage_api/tftp.py @@ -1,3 +1,6 @@ +"""Calxeda: tftp.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -41,7 +44,8 @@ from tftpy.TftpShared import TftpException class InternalTftp(Thread): - """Internally serves files using the `Trivial File Transfer Protocol `_. + """Internally serves files using the `Trivial File Transfer Protocol \ +`_. >>> # Typical instantiation ... >>> from cxmanage_api.tftp import InternalTftp @@ -113,7 +117,7 @@ class InternalTftp(Thread): :param src: Source file path on the tftp_server. :type src: string - :param dest: Destination path (on your machine) to copy the TFTP file to. + :param dest: Destination path (local machine) to copy the TFTP file to. :type dest: string """ @@ -182,7 +186,7 @@ class ExternalTftp(object): >>> e_tftp.get_address() '1.2.3.4' - :param relative_host: Unused parameter present only for function signature. + :param relative_host: Unused parameter, for function signature. :type relative_host: None :returns: The ip address of the external TFTP server. diff --git a/cxmanage_test/tftp_test.py b/cxmanage_test/tftp_test.py index 784211a..94d9e38 100644 --- a/cxmanage_test/tftp_test.py +++ b/cxmanage_test/tftp_test.py @@ -1,3 +1,6 @@ +"""Calxeda: tftp_test.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -27,7 +30,11 @@ # 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. -"""Unit tests for the Internal and External Tftp classes.""" + + +# +# Unit tests for the Internal and External Tftp classes. +# import os @@ -51,6 +58,7 @@ def _get_relative_host(): except socket.error: raise +# pylint: disable=R0904 class InternalTftpTest(unittest.TestCase): """ Tests the functions of the InternalTftp class.""" @@ -79,7 +87,7 @@ class InternalTftpTest(unittest.TestCase): self.assertEqual(open(filename).read(), contents) os.remove(filename) - def test_get_address_with_relative_host(self): + def test_get_address_with_relhost(self): """Tests the get_address(relative_host) function with a relative_host specified. """ @@ -97,6 +105,7 @@ class InternalTftpTest(unittest.TestCase): sock.close() +# pylint: disable=R0904 class ExternalTftpTest(unittest.TestCase): """Tests the ExternalTftp class. ..note: -- cgit v1.2.1 From dc3901471d18178d92bfa4bacadb0b2fce28ae3d Mon Sep 17 00:00:00 2001 From: evasquez Date: Tue, 27 Aug 2013 10:21:57 -0500 Subject: nojira: restore exception export. pylint fixes --- cxmanage_api/cx_exceptions.py | 10 ++++++++++ cxmanage_test/image_test.py | 10 ++++++++-- cxmanage_test/tasks_test.py | 12 +++++++++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/cxmanage_api/cx_exceptions.py b/cxmanage_api/cx_exceptions.py index d924fe9..df2dcc7 100644 --- a/cxmanage_api/cx_exceptions.py +++ b/cxmanage_api/cx_exceptions.py @@ -32,6 +32,16 @@ # DAMAGE. +# +# We expose these here so a user does not have to import from pyipmi or tftpy. +# +# pylint: disable=W0611 +# +from pyipmi import IpmiError + +from tftpy.TftpShared import TftpException + + # # Defines the custom exceptions used by the cxmanage_api project. # diff --git a/cxmanage_test/image_test.py b/cxmanage_test/image_test.py index 609aa5b..fcbb011 100644 --- a/cxmanage_test/image_test.py +++ b/cxmanage_test/image_test.py @@ -1,3 +1,6 @@ +"""Calxeda: image_test.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -39,6 +42,8 @@ from cxmanage_api.tftp import InternalTftp from cxmanage_test import random_file, TestImage + +# pylint: disable=R0904 class ImageTest(unittest.TestCase): """ Tests involving cxmanage images @@ -72,13 +77,14 @@ class ImageTest(unittest.TestCase): self.assertEqual(header.daddr, daddr) self.assertEqual(simg[header.imgoff:], contents) - def test_multiple_uploads(self): + @staticmethod + def test_multiple_uploads(): """ Test to make sure FDs are being closed """ # Create image filename = random_file(1024) image = TestImage(filename, "RAW") - for x in xrange(2048): + for _ in xrange(2048): image.render_to_simg(0, 0) os.remove(filename) diff --git a/cxmanage_test/tasks_test.py b/cxmanage_test/tasks_test.py index a936b06..5ede9e4 100644 --- a/cxmanage_test/tasks_test.py +++ b/cxmanage_test/tasks_test.py @@ -1,3 +1,6 @@ +"""Calxeda: task_test.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -28,16 +31,21 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. + import unittest import time from cxmanage_api.tasks import TaskQueue + +# pylint: disable=R0904 class TaskTest(unittest.TestCase): + """Test for the TaskQueue Class.""" + def test_task_queue(self): """ Test the task queue """ task_queue = TaskQueue() - counters = [Counter() for x in xrange(128)] + counters = [Counter() for _ in xrange(128)] tasks = [task_queue.put(counters[i].add, i) for i in xrange(128)] for task in tasks: @@ -61,6 +69,8 @@ class TaskTest(unittest.TestCase): self.assertGreaterEqual(finish - start, 2.0) + +# pylint: disable=R0903 class Counter(object): """ Simple counter object for testing purposes """ def __init__(self): -- cgit v1.2.1 From 61199107e95b0111f45ae5b9fa17664ecb4d6206 Mon Sep 17 00:00:00 2001 From: evasquez Date: Tue, 27 Aug 2013 10:24:42 -0500 Subject: nojira: pylint fixes --- cxmanage/commands/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cxmanage/commands/__init__.py b/cxmanage/commands/__init__.py index 2160043..571a3c5 100644 --- a/cxmanage/commands/__init__.py +++ b/cxmanage/commands/__init__.py @@ -1,3 +1,6 @@ +"""Calxeda: __init__.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. -- cgit v1.2.1 From 4c57ee4d1ff8618862c541549e48a5ad37e2e4b9 Mon Sep 17 00:00:00 2001 From: evasquez Date: Tue, 27 Aug 2013 10:29:45 -0500 Subject: nojira: removed comment. --- cxmanage/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/cxmanage/__init__.py b/cxmanage/__init__.py index 5b2e191..438d568 100644 --- a/cxmanage/__init__.py +++ b/cxmanage/__init__.py @@ -96,8 +96,6 @@ def get_nodes(args, tftp, verify_prompt=False): for node in nodes: if node in results: for node_id, ip_address in sorted(results[node].iteritems()): - # TODO: make this more efficient. We can use a set of IP - # addresses instead of searching a list every time... new_node = Node(ip_address=ip_address, username=args.user, password=args.password, tftp=tftp, ecme_tftp_port=args.ecme_tftp_port, -- cgit v1.2.1 From a87fba11df888c09abf48caeab06ca79419ab3b4 Mon Sep 17 00:00:00 2001 From: "matthew.hodgins" Date: Tue, 27 Aug 2013 13:14:15 -0500 Subject: AIT-221 allow dummynode to have its power state set beforehand --- cxmanage_test/fabric_test.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cxmanage_test/fabric_test.py b/cxmanage_test/fabric_test.py index 91396d6..fe1a80c 100644 --- a/cxmanage_test/fabric_test.py +++ b/cxmanage_test/fabric_test.py @@ -460,6 +460,7 @@ class DummyNode(object): def __init__(self, ip_address, username="admin", password="admin", tftp=None, *args, **kwargs): self.executed = [] + self.power_state = False self.ip_address = ip_address self.tftp = tftp self.bmc = make_bmc(DummyBMC, hostname=ip_address, username=username, @@ -468,7 +469,7 @@ class DummyNode(object): def get_power(self): """Simulate get_power(). """ self.executed.append("get_power") - return False + return self.power_state def set_power(self, mode): """Simulate set_power(). """ @@ -572,7 +573,8 @@ class DummyNode(object): return {} # pylint: disable=R0913 - def get_server_ip(self, interface, ipv6, user, password, aggressive): + def get_server_ip(self, interface=None, ipv6=False, user="user1", + password="1Password", aggressive=False): """Simulate get_server_ip(). """ self.executed.append(("get_server_ip", interface, ipv6, user, password, aggressive)) -- cgit v1.2.1 From ead8c0a583636004ff0fc468c649d882a4ec6dc1 Mon Sep 17 00:00:00 2001 From: "matthew.hodgins" Date: Tue, 27 Aug 2013 13:22:59 -0500 Subject: nojira cx_manage tests need to output XML for Jenkins reporting --- run_tests | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/run_tests b/run_tests index f0d7831..bfc64c4 100755 --- a/run_tests +++ b/run_tests @@ -32,6 +32,7 @@ import unittest +import xmlrunner from cxmanage_test import tftp_test, image_test, node_test, fabric_test, \ tasks_test @@ -43,7 +44,7 @@ def main(): suite = unittest.TestSuite() for module in test_modules: suite.addTest(loader.loadTestsFromModule(module)) - unittest.TextTestRunner(verbosity=2).run(suite) + xmlrunner.XMLTestRunner(verbosity=2).run(suite) if __name__ == "__main__": main() -- cgit v1.2.1 From f51dbfc1d48e2384d8dacb0865675496a1092153 Mon Sep 17 00:00:00 2001 From: "matthew.hodgins" Date: Tue, 27 Aug 2013 13:33:25 -0500 Subject: AIT-222 added missing xmlrunner package --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 56b7088..c2b5bb5 100644 --- a/setup.py +++ b/setup.py @@ -47,6 +47,7 @@ setup( 'pexpect', 'pyipmi>=0.8.0', 'argparse', + 'unittest-xml-reporting<1.6.0' ], extras_require={ 'docs': ['sphinx', 'cloud_sptheme'], -- cgit v1.2.1 From 3a491410cb19b537bd525b402130152f05af3b6a Mon Sep 17 00:00:00 2001 From: George Kraft Date: Thu, 29 Aug 2013 10:24:53 -0500 Subject: CXMAN-221: Rename "cxmanage" python package to "cxmanage_api.cli" Groups up all the cxmanage functionality under one package. Yay --- cxmanage/__init__.py | 360 ------------------------- cxmanage/commands/__init__.py | 32 --- cxmanage/commands/config.py | 146 ---------- cxmanage/commands/fabric.py | 83 ------ cxmanage/commands/fru_version.py | 71 ----- cxmanage/commands/fw.py | 172 ------------ cxmanage/commands/info.py | 103 ------- cxmanage/commands/ipdiscover.py | 59 ---- cxmanage/commands/ipmitool.py | 64 ----- cxmanage/commands/mc.py | 51 ---- cxmanage/commands/power.py | 116 -------- cxmanage/commands/sensor.py | 87 ------ cxmanage/commands/tspackage.py | 448 ------------------------------- cxmanage_api/cli/__init__.py | 360 +++++++++++++++++++++++++ cxmanage_api/cli/commands/__init__.py | 32 +++ cxmanage_api/cli/commands/config.py | 146 ++++++++++ cxmanage_api/cli/commands/fabric.py | 83 ++++++ cxmanage_api/cli/commands/fru_version.py | 71 +++++ cxmanage_api/cli/commands/fw.py | 172 ++++++++++++ cxmanage_api/cli/commands/info.py | 103 +++++++ cxmanage_api/cli/commands/ipdiscover.py | 59 ++++ cxmanage_api/cli/commands/ipmitool.py | 63 +++++ cxmanage_api/cli/commands/mc.py | 51 ++++ cxmanage_api/cli/commands/power.py | 116 ++++++++ cxmanage_api/cli/commands/sensor.py | 87 ++++++ cxmanage_api/cli/commands/tspackage.py | 448 +++++++++++++++++++++++++++++++ scripts/cxmanage | 26 +- setup.py | 7 +- 28 files changed, 1810 insertions(+), 1806 deletions(-) delete mode 100644 cxmanage/__init__.py delete mode 100644 cxmanage/commands/__init__.py delete mode 100644 cxmanage/commands/config.py delete mode 100644 cxmanage/commands/fabric.py delete mode 100644 cxmanage/commands/fru_version.py delete mode 100644 cxmanage/commands/fw.py delete mode 100644 cxmanage/commands/info.py delete mode 100644 cxmanage/commands/ipdiscover.py delete mode 100644 cxmanage/commands/ipmitool.py delete mode 100644 cxmanage/commands/mc.py delete mode 100644 cxmanage/commands/power.py delete mode 100644 cxmanage/commands/sensor.py delete mode 100644 cxmanage/commands/tspackage.py create mode 100644 cxmanage_api/cli/__init__.py create mode 100644 cxmanage_api/cli/commands/__init__.py create mode 100644 cxmanage_api/cli/commands/config.py create mode 100644 cxmanage_api/cli/commands/fabric.py create mode 100644 cxmanage_api/cli/commands/fru_version.py create mode 100644 cxmanage_api/cli/commands/fw.py create mode 100644 cxmanage_api/cli/commands/info.py create mode 100644 cxmanage_api/cli/commands/ipdiscover.py create mode 100644 cxmanage_api/cli/commands/ipmitool.py create mode 100644 cxmanage_api/cli/commands/mc.py create mode 100644 cxmanage_api/cli/commands/power.py create mode 100644 cxmanage_api/cli/commands/sensor.py create mode 100644 cxmanage_api/cli/commands/tspackage.py diff --git a/cxmanage/__init__.py b/cxmanage/__init__.py deleted file mode 100644 index 438d568..0000000 --- a/cxmanage/__init__.py +++ /dev/null @@ -1,360 +0,0 @@ -"""Calxeda: __init__.py """ - - -# 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. - - -import sys -import time - -from cxmanage_api.tftp import InternalTftp, ExternalTftp -from cxmanage_api.node import Node -from cxmanage_api.tasks import TaskQueue -from cxmanage_api.cx_exceptions import TftpException - - -def get_tftp(args): - """Get a TFTP server""" - if args.internal_tftp: - tftp_args = args.internal_tftp.split(':') - if len(tftp_args) == 1: - ip_address = tftp_args[0] - port = 0 - elif len(tftp_args) == 2: - ip_address = tftp_args[0] - port = int(tftp_args[1]) - else: - print ('ERROR: %s is not a valid argument for --internal-tftp' - % args.internal_tftp) - sys.exit(1) - return InternalTftp(ip_address=ip_address, port=port, - verbose=args.verbose) - - elif args.external_tftp: - tftp_args = args.external_tftp.split(':') - if len(tftp_args) == 1: - ip_address = tftp_args[0] - port = 69 - elif len(tftp_args) == 2: - ip_address = tftp_args[0] - port = int(tftp_args[1]) - else: - print ('ERROR: %s is not a valid argument for --external-tftp' - % args.external_tftp) - sys.exit(1) - return ExternalTftp(ip_address=ip_address, port=port, - verbose=args.verbose) - - return InternalTftp(verbose=args.verbose) - -# pylint: disable=R0912 -def get_nodes(args, tftp, verify_prompt=False): - """Get nodes""" - hosts = [] - for entry in args.hostname.split(','): - hosts.extend(parse_host_entry(entry)) - - nodes = [Node(ip_address=x, username=args.user, password=args.password, - tftp=tftp, ecme_tftp_port=args.ecme_tftp_port, - verbose=args.verbose) for x in hosts] - - if args.all_nodes: - if not args.quiet: - print("Getting IP addresses...") - - results, errors = run_command(args, nodes, "get_fabric_ipinfo") - - all_nodes = [] - for node in nodes: - if node in results: - for node_id, ip_address in sorted(results[node].iteritems()): - new_node = Node(ip_address=ip_address, username=args.user, - password=args.password, tftp=tftp, - ecme_tftp_port=args.ecme_tftp_port, - verbose=args.verbose) - new_node.node_id = node_id - if not new_node in all_nodes: - all_nodes.append(new_node) - - node_strings = get_node_strings(args, all_nodes, justify=False) - if not args.quiet and all_nodes: - print("Discovered the following IP addresses:") - for node in all_nodes: - print node_strings[node] - print - - if errors: - print("ERROR: Failed to get IP addresses. Aborting.\n") - sys.exit(1) - - if args.nodes: - if len(all_nodes) != args.nodes: - print ("ERROR: Discovered %i nodes, expected %i. Aborting.\n" - % (len(all_nodes), args.nodes)) - sys.exit(1) - elif verify_prompt and not args.force: - print( - "NOTE: Please check node count! Ensure discovery of all " + - "nodes in the cluster. Power cycle your system if the " + - "discovered node count does not equal nodes in" + - "your system.\n" - ) - if not prompt_yes("Discovered %i nodes. Continue?" - % len(all_nodes)): - sys.exit(1) - - return all_nodes - - return nodes - - -def get_node_strings(args, nodes, justify=False): - """ Get string representations for the nodes. """ - # Use the private _node_id instead of node_id. Strange choice, - # but we want to avoid accidentally polling the BMC. - # pylint: disable=W0212 - if args.ids and all(x._node_id != None for x in nodes): - strings = ["Node %i (%s)" % (x._node_id, x.ip_address) for x in nodes] - else: - strings = [x.ip_address for x in nodes] - - if justify: - just_size = max(16, max(len(x) for x in strings) + 1) - strings = [x.ljust(just_size) for x in strings] - - return dict(zip(nodes, strings)) - - -# pylint: disable=R0915 -def run_command(args, nodes, name, *method_args): - """Runs a command on nodes.""" - if args.threads != None: - task_queue = TaskQueue(threads=args.threads, delay=args.command_delay) - else: - task_queue = TaskQueue(delay=args.command_delay) - - tasks = {} - for node in nodes: - tasks[node] = task_queue.put(getattr(node, name), *method_args) - - results = {} - errors = {} - try: - counter = 0 - while any(x.is_alive() for x in tasks.values()): - if not args.quiet: - _print_command_status(tasks, counter) - counter += 1 - time.sleep(0.25) - - for node, task in tasks.iteritems(): - if task.status == "Completed": - results[node] = task.result - else: - errors[node] = task.error - - except KeyboardInterrupt: - args.retry = 0 - - for node, task in tasks.iteritems(): - if task.status == "Completed": - results[node] = task.result - elif task.status == "Failed": - errors[node] = task.error - else: - errors[node] = KeyboardInterrupt( - "Aborted by keyboard interrupt" - ) - - if not args.quiet: - _print_command_status(tasks, counter) - print("\n") - - # Handle errors - should_retry = False - if errors: - _print_errors(args, nodes, errors) - if args.retry == None: - sys.stdout.write("Retry command on failed hosts? (y/n): ") - sys.stdout.flush() - while True: - command = raw_input().strip().lower() - if command in ['y', 'yes']: - should_retry = True - break - elif command in ['n', 'no']: - print - break - elif args.retry >= 1: - should_retry = True - if args.retry == 1: - print("Retrying command 1 more time...") - elif args.retry > 1: - print("Retrying command %i more times..." % args.retry) - args.retry -= 1 - - if should_retry: - nodes = [x for x in nodes if x in errors] - new_results, errors = run_command(args, nodes, name, *method_args) - results.update(new_results) - - return results, errors - - -def prompt_yes(prompt): - """Prompts the user. """ - sys.stdout.write("%s (y/n) " % prompt) - sys.stdout.flush() - while True: - command = raw_input().strip().lower() - if command in ['y', 'yes']: - print - return True - elif command in ['n', 'no']: - print - return False - - -def parse_host_entry(entry, hostfiles=None): - """parse a host entry""" - if not(hostfiles): - hostfiles = set() - - try: - return parse_hostfile_entry(entry, hostfiles) - except ValueError: - try: - return parse_ip_range_entry(entry) - except ValueError: - return [entry] - - -def parse_hostfile_entry(entry, hostfiles=None): - """parse a hostfile entry, returning a list of hosts""" - if not(hostfiles): - hostfiles = set() - - if entry.startswith('file='): - filename = entry[5:] - elif entry.startswith('hostfile='): - filename = entry[9:] - else: - raise ValueError('%s is not a hostfile entry' % entry) - - if filename in hostfiles: - return [] - hostfiles.add(filename) - - entries = [] - try: - for line in open(filename): - for element in line.partition('#')[0].split(): - for hostfile_entry in element.split(','): - entries.extend(parse_host_entry(hostfile_entry, hostfiles)) - except IOError: - print 'ERROR: %s is not a valid hostfile entry' % entry - sys.exit(1) - - return entries - - -def parse_ip_range_entry(entry): - """ Get a list of ip addresses in a given range""" - try: - start, end = entry.split('-') - - # Convert start address to int - start_bytes = [int(x) for x in start.split('.')] - - start_i = ((start_bytes[0] << 24) | (start_bytes[1] << 16) - | (start_bytes[2] << 8) | (start_bytes[3])) - - # Convert end address to int - end_bytes = [int(x) for x in end.split('.')] - end_i = ((end_bytes[0] << 24) | (end_bytes[1] << 16) - | (end_bytes[2] << 8) | (end_bytes[3])) - - # Get ip addresses in range - addresses = [] - for i in range(start_i, end_i + 1): - address_bytes = [(i >> (24 - 8 * x)) & 0xff for x in range(4)] - addresses.append('%i.%i.%i.%i' % tuple(address_bytes)) - - except (ValueError, IndexError): - raise ValueError('%s is not an IP range' % entry) - - return addresses - - -def _print_errors(args, nodes, errors): - """ Print errors if they occured """ - if errors: - node_strings = get_node_strings(args, nodes, justify=True) - print("Command failed on these hosts") - for node in nodes: - if node in errors: - print("%s: %s" % (node_strings[node], errors[node])) - print - - # Print a special message for TFTP errors - if all(isinstance(x, TftpException) for x in errors.itervalues()): - print( - "There may be networking issues (when behind NAT) between " + - "the host (where cxmanage is running) and the Calxeda node " + - "when establishing a TFTP session. Please refer to the " + - "documentation for more information.\n" - ) - - -def _print_command_status(tasks, counter): - """ Print the status of a command """ - message = "\r%i successes | %i errors | %i nodes left | %s" - successes = len([x for x in tasks.values() if x.status == "Completed"]) - errors = len([x for x in tasks.values() if x.status == "Failed"]) - nodes_left = len(tasks) - successes - errors - dots = "".join(["." for x in range(counter % 4)]).ljust(3) - sys.stdout.write(message % (successes, errors, nodes_left, dots)) - sys.stdout.flush() - - -# These are needed for ipinfo and whenever version information is printed -COMPONENTS = [ - ("ecme_version", "ECME version"), - ("cdb_version", "CDB version"), - ("stage2_version", "Stage2boot version"), - ("bootlog_version", "Bootlog version"), - ("a9boot_version", "A9boot version"), - ("a15boot_version", "A15boot version"), - ("uboot_version", "Uboot version"), - ("ubootenv_version", "Ubootenv version"), - ("dtb_version", "DTB version"), -] - diff --git a/cxmanage/commands/__init__.py b/cxmanage/commands/__init__.py deleted file mode 100644 index 571a3c5..0000000 --- a/cxmanage/commands/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -"""Calxeda: __init__.py""" - - -# 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. diff --git a/cxmanage/commands/config.py b/cxmanage/commands/config.py deleted file mode 100644 index bbe5256..0000000 --- a/cxmanage/commands/config.py +++ /dev/null @@ -1,146 +0,0 @@ -"""Calxeda: config.py """ - - -# 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. - - -from cxmanage import get_tftp, get_nodes, get_node_strings, run_command - -from cxmanage_api.ubootenv import validate_boot_args, \ - validate_pxe_interface - - -def config_reset_command(args): - """reset to factory default settings""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp, verify_prompt=True) - - if not args.quiet: - print "Sending config reset command..." - - _, errors = run_command(args, nodes, "config_reset") - - if not args.quiet and not errors: - print "Command completed successfully.\n" - - return len(errors) > 0 - - -def config_boot_command(args): - """set A9 boot order""" - if args.boot_order == ['status']: - return config_boot_status_command(args) - - validate_boot_args(args.boot_order) - - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Setting boot order..." - - _, errors = run_command(args, nodes, "set_boot_order", - args.boot_order) - - if not args.quiet and not errors: - print "Command completed successfully.\n" - - return len(errors) > 0 - - -def config_boot_status_command(args): - """Get boot status command.""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Getting boot order..." - results, errors = run_command(args, nodes, "get_boot_order") - - # Print results - if results: - node_strings = get_node_strings(args, results, justify=True) - print "Boot order" - for node in nodes: - if node in results: - print "%s: %s" % (node_strings[node], ",".join(results[node])) - print - - if not args.quiet and errors: - print "Some errors occured during the command.\n" - - return len(errors) > 0 - - -def config_pxe_command(args): - """set the PXE boot interface""" - if args.interface == "status": - return config_pxe_status_command(args) - - validate_pxe_interface(args.interface) - - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Setting pxe interface..." - - _, errors = run_command(args, nodes, "set_pxe_interface", - args.interface) - - if not args.quiet and not errors: - print "Command completed successfully.\n" - - return len(errors) > 0 - - -def config_pxe_status_command(args): - """Gets pxe status.""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Getting pxe interface..." - results, errors = run_command(args, nodes, "get_pxe_interface") - - # Print results - if results: - node_strings = get_node_strings(args, results, justify=True) - print "PXE interface" - for node in nodes: - if node in results: - print "%s: %s" % (node_strings[node], results[node]) - print - - if not args.quiet and errors: - print "Some errors occured during the command.\n" - - return len(errors) > 0 diff --git a/cxmanage/commands/fabric.py b/cxmanage/commands/fabric.py deleted file mode 100644 index 8bc3f65..0000000 --- a/cxmanage/commands/fabric.py +++ /dev/null @@ -1,83 +0,0 @@ -"""Calxeda: fabric.py""" - - -# 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. - -from cxmanage import get_tftp, get_nodes, run_command - - -def ipinfo_command(args): - """get ip info from a cluster or host""" - args.all_nodes = False - - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Getting IP addresses..." - - results, _ = run_command(args, nodes, "get_fabric_ipinfo") - - for node in nodes: - if node in results: - print 'IP info from %s' % node.ip_address - for node_id, node_address in results[node].iteritems(): - print 'Node %i: %s' % (node_id, node_address) - print - - return 0 - - -def macaddrs_command(args): - """get mac addresses from a cluster or host""" - args.all_nodes = False - - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Getting MAC addresses..." - results, errors = run_command(args, nodes, "get_fabric_macaddrs") - - for node in nodes: - if node in results: - print "MAC addresses from %s" % node.ip_address - for node_id in results[node]: - for port in results[node][node_id]: - for mac_address in results[node][node_id][port]: - print "Node %i, Port %i: %s" % (node_id, port, - mac_address) - print - - if not args.quiet and errors: - print "Some errors occured during the command.\n" - - return len(errors) == 0 diff --git a/cxmanage/commands/fru_version.py b/cxmanage/commands/fru_version.py deleted file mode 100644 index 1dd318c..0000000 --- a/cxmanage/commands/fru_version.py +++ /dev/null @@ -1,71 +0,0 @@ -"""Calxeda: fru_version.py """ - - -# Copyright (c) 2013, 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. - - -from cxmanage import get_tftp, get_nodes, get_node_strings, run_command - - -def node_fru_version_command(args): - """Get the node FRU version for each node. """ - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - results, errors = run_command(args, nodes, 'get_node_fru_version') - - # Print results if we were successful - if results: - node_strings = get_node_strings(args, results, justify=True) - for node in nodes: - print("%s: %s" % (node_strings[node], results[node])) - - print("") # For readability - - if not args.quiet and errors: - print('Some errors occured during the command.\n') - - -def slot_fru_version_command(args): - """Get the slot FRU version for each node. """ - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - results, errors = run_command(args, nodes, 'get_slot_fru_version') - - # Print results if we were successful - if results: - node_strings = get_node_strings(args, results, justify=True) - for node in nodes: - print("%s: %s" % (node_strings[node], results[node])) - - print("") # For readability - - if not args.quiet and errors: - print('Some errors occured during the command.\n') diff --git a/cxmanage/commands/fw.py b/cxmanage/commands/fw.py deleted file mode 100644 index 611315e..0000000 --- a/cxmanage/commands/fw.py +++ /dev/null @@ -1,172 +0,0 @@ -"""Calxeda: fw.py """ - -# 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. - - -from pkg_resources import parse_version - -from cxmanage import get_tftp, get_nodes, get_node_strings, run_command, \ - prompt_yes - -from cxmanage_api.image import Image -from cxmanage_api.firmware_package import FirmwarePackage - -# pylint: disable=R0912 -def fwupdate_command(args): - """update firmware on a cluster or host""" - def do_update(): - """ Do a single firmware check+update. Returns True on failure. """ - if not args.force: - if not args.quiet: - print "Checking hosts..." - - _, errors = run_command(args, nodes, "_check_firmware", - package, args.partition, args.priority) - if errors: - print "ERROR: Firmware update aborted." - return True - - if not args.quiet: - print "Updating firmware..." - - _, errors = run_command(args, nodes, "update_firmware", package, - args.partition, args.priority) - if errors: - print "ERROR: Firmware update failed." - return True - - return False - - def do_reset(): - """ Reset and wait. Returns True on failure. """ - if not args.quiet: - print "Checking ECME versions..." - - results, errors = run_command(args, nodes, "get_versions") - if errors: - print "ERROR: MC reset aborted. Backup partitions not updated." - return True - - for result in results.values(): - version = result.ecme_version.lstrip("v") - if parse_version(version) < parse_version("1.2.0"): - print "ERROR: MC reset is unsafe on ECME version v%s" % version - print "Please power cycle the system and start a new fwupdate." - return True - - if not args.quiet: - print "Resetting nodes..." - - results, errors = run_command(args, nodes, "mc_reset", True) - if errors: - print "ERROR: MC reset failed. Backup partitions not updated." - return True - - return False - - if args.image_type == "PACKAGE": - package = FirmwarePackage(args.filename) - else: - try: - simg = None - if args.force_simg: - simg = False - elif args.skip_simg: - simg = True - - image = Image(args.filename, args.image_type, simg, args.daddr, - args.skip_crc32, args.fw_version) - package = FirmwarePackage() - package.images.append(image) - except ValueError as err: - print "ERROR: %s" % err - return True - - if not args.all_nodes: - if args.force: - print( - 'WARNING: Updating firmware without --all-nodes' + - ' is dangerous.' - ) - else: - if not prompt_yes( - 'WARNING: Updating firmware without ' + - '--all-nodes is dangerous. Continue?' - ): - return 1 - - tftp = get_tftp(args) - nodes = get_nodes(args, tftp, verify_prompt=True) - - errors = do_update() - - if args.full and not errors: - errors = do_reset() - if not errors: - errors = do_update() - - if not args.quiet and not errors: - print "Command completed successfully.\n" - - return errors - - -def fwinfo_command(args): - """print firmware info""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Getting firmware info..." - - results, errors = run_command(args, nodes, "get_firmware_info") - - node_strings = get_node_strings(args, results, justify=False) - for node in nodes: - if node in results: - print "[ Firmware info for %s ]" % node_strings[node] - - for partition in results[node]: - print "Partition : %s" % partition.partition - print "Type : %s" % partition.type - print "Offset : %s" % partition.offset - print "Size : %s" % partition.size - print "Priority : %s" % partition.priority - print "Daddr : %s" % partition.daddr - print "Flags : %s" % partition.flags - print "Version : %s" % partition.version - print "In Use : %s" % partition.in_use - print - - if not args.quiet and errors: - print "Some errors occured during the command.\n" - - return len(errors) > 0 diff --git a/cxmanage/commands/info.py b/cxmanage/commands/info.py deleted file mode 100644 index b15d2c6..0000000 --- a/cxmanage/commands/info.py +++ /dev/null @@ -1,103 +0,0 @@ -"""Calxeda: info.py""" - - -# 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. - - -from cxmanage import get_tftp, get_nodes, get_node_strings, run_command -from cxmanage import COMPONENTS - - -def info_command(args): - """print info from a cluster or host""" - if args.info_type in [None, 'basic']: - return info_basic_command(args) - elif args.info_type == 'ubootenv': - return info_ubootenv_command(args) - - -def info_basic_command(args): - """Print basic info""" - - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Getting info..." - results, errors = run_command(args, nodes, "get_versions") - - # Print results - node_strings = get_node_strings(args, results, justify=False) - for node in nodes: - if node in results: - result = results[node] - # Get mappings between attributes and formatted strings - components = COMPONENTS - - print "[ Info from %s ]" % node_strings[node] - print "Hardware version : %s" % result.hardware_version - print "Firmware version : %s" % result.firmware_version - # var is the variable, string is the printable string of var - for var, string in components: - if hasattr(result, var): - version = getattr(result, var) - print "%s: %s" % (string.ljust(19), version) - print - - if not args.quiet and errors: - print "Some errors occured during the command.\n" - - return len(errors) > 0 - - -def info_ubootenv_command(args): - """Print uboot info""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Getting u-boot environment..." - results, errors = run_command(args, nodes, "get_ubootenv") - - # Print results - node_strings = get_node_strings(args, results, justify=False) - for node in nodes: - if node in results: - ubootenv = results[node] - print "[ U-Boot Environment from %s ]" % node_strings[node] - for variable in ubootenv.variables: - print "%s=%s" % (variable, ubootenv.variables[variable]) - print - - if not args.quiet and errors: - print "Some errors occured during the command.\n" - - return len(errors) > 0 diff --git a/cxmanage/commands/ipdiscover.py b/cxmanage/commands/ipdiscover.py deleted file mode 100644 index bd09413..0000000 --- a/cxmanage/commands/ipdiscover.py +++ /dev/null @@ -1,59 +0,0 @@ -"""Calxeda: ipdiscover.py""" - - -# 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. - -from cxmanage import get_tftp, get_nodes, get_node_strings, run_command - - -def ipdiscover_command(args): - """discover server IP addresses""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print 'Getting server-side IP addresses...' - - results, errors = run_command(args, nodes, 'get_server_ip', args.interface, - args.ipv6, args.server_user, args.server_password, args.aggressive) - - if results: - node_strings = get_node_strings(args, results, justify=True) - print 'IP addresses (ECME, Server)' - for node in nodes: - if node in results: - print '%s: %s' % (node_strings[node], results[node]) - print - - if not args.quiet and errors: - print 'Some errors occurred during the command.' - - return len(errors) > 0 diff --git a/cxmanage/commands/ipmitool.py b/cxmanage/commands/ipmitool.py deleted file mode 100644 index ac21e00..0000000 --- a/cxmanage/commands/ipmitool.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Calxeda: ipmitool.py""" - - -# 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. - - -from cxmanage import get_tftp, get_nodes, get_node_strings, run_command - - -def ipmitool_command(args): - """run arbitrary ipmitool command""" - if args.lanplus: - ipmitool_args = ['-I', 'lanplus'] + args.ipmitool_args - else: - ipmitool_args = args.ipmitool_args - - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Running IPMItool command..." - results, errors = run_command(args, nodes, "ipmitool_command", - ipmitool_args) - - # Print results - node_strings = get_node_strings(args, results, justify=False) - for node in nodes: - if node in results and results[node] != "": - print "[ IPMItool output from %s ]" % node_strings[node] - print results[node] - print - - if not args.quiet and errors: - print "Some errors occured during the command.\n" - - return len(errors) > 0 diff --git a/cxmanage/commands/mc.py b/cxmanage/commands/mc.py deleted file mode 100644 index 6c42615..0000000 --- a/cxmanage/commands/mc.py +++ /dev/null @@ -1,51 +0,0 @@ -"""Calxeda: mc.py""" - - -# 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. - - -from cxmanage import get_tftp, get_nodes, run_command - - -def mcreset_command(args): - """reset the management controllers of a cluster or host""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print 'Sending MC reset command...' - - _, errors = run_command(args, nodes, 'mc_reset') - - if not args.quiet and not errors: - print 'Command completed successfully.\n' - - return len(errors) > 0 diff --git a/cxmanage/commands/power.py b/cxmanage/commands/power.py deleted file mode 100644 index 623c38d..0000000 --- a/cxmanage/commands/power.py +++ /dev/null @@ -1,116 +0,0 @@ -"""Calxeda: power.py """ - - -# 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. - -from cxmanage import get_tftp, get_nodes, get_node_strings, run_command - - -def power_command(args): - """change the power state of a cluster or host""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print 'Sending power %s command...' % args.power_mode - - _, errors = run_command(args, nodes, 'set_power', args.power_mode) - - if not args.quiet and not errors: - print 'Command completed successfully.\n' - - return len(errors) > 0 - - -def power_status_command(args): - """Executes the power status command with args.""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print 'Getting power status...' - results, errors = run_command(args, nodes, 'get_power') - - # Print results - if results: - node_strings = get_node_strings(args, results, justify=True) - print 'Power status' - for node in nodes: - if node in results: - result = 'on' if results[node] else 'off' - print '%s: %s' % (node_strings[node], result) - print - - if not args.quiet and errors: - print 'Some errors occured during the command.\n' - - return len(errors) > 0 - - -def power_policy_command(args): - """Executes power policy command with args.""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print 'Setting power policy to %s...' % args.policy - - _, errors = run_command(args, nodes, 'set_power_policy', - args.policy) - - if not args.quiet and not errors: - print 'Command completed successfully.\n' - - return len(errors) > 0 - - -def power_policy_status_command(args): - """Executes the power policy status command with args.""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print 'Getting power policy status...' - results, errors = run_command(args, nodes, 'get_power_policy') - - # Print results - if results: - node_strings = get_node_strings(args, results, justify=True) - print 'Power policy status' - for node in nodes: - if node in results: - print '%s: %s' % (node_strings[node], results[node]) - print - - if not args.quiet and errors: - print 'Some errors occured during the command.\n' - - return len(errors) > 0 diff --git a/cxmanage/commands/sensor.py b/cxmanage/commands/sensor.py deleted file mode 100644 index 3a27143..0000000 --- a/cxmanage/commands/sensor.py +++ /dev/null @@ -1,87 +0,0 @@ -"""Calxeda: sensor.py""" - - -# 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. - - -from cxmanage import get_tftp, get_nodes, get_node_strings, run_command - -# pylint: disable=R0914 -def sensor_command(args): - """read sensor values from a cluster or host""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Getting sensor readings..." - results, errors = run_command(args, nodes, "get_sensors", - args.sensor_name) - - sensors = {} - for node in nodes: - if node in results: - for sensor_name, sensor in results[node].iteritems(): - if not sensor_name in sensors: - sensors[sensor_name] = [] - - reading = sensor.sensor_reading.replace("(+/- 0) ", "") - try: - value = float(reading.split()[0]) - suffix = reading.lstrip("%f " % value) - sensors[sensor_name].append((node, value, suffix)) - except ValueError: - sensors[sensor_name].append((node, reading, "")) - - node_strings = get_node_strings(args, results, justify=True) - jsize = len(node_strings.itervalues().next()) - for sensor_name, readings in sensors.iteritems(): - print sensor_name - - for node, reading, suffix in readings: - print "%s: %.2f %s" % (node_strings[node], reading, suffix) - - try: - if all(suffix == x[2] for x in readings): - minimum = min(x[1] for x in readings) - maximum = max(x[1] for x in readings) - average = sum(x[1] for x in readings) / len(readings) - print "%s: %.2f %s" % ("Minimum".ljust(jsize), minimum, suffix) - print "%s: %.2f %s" % ("Maximum".ljust(jsize), maximum, suffix) - print "%s: %.2f %s" % ("Average".ljust(jsize), average, suffix) - except ValueError: - pass - - print - - if not args.quiet and errors: - print "Some errors occured during the command.\n" - - return len(errors) > 0 diff --git a/cxmanage/commands/tspackage.py b/cxmanage/commands/tspackage.py deleted file mode 100644 index d6ee198..0000000 --- a/cxmanage/commands/tspackage.py +++ /dev/null @@ -1,448 +0,0 @@ -"""Calxeda: tspackage.py""" - - -# Copyright 2013 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. - - -# -# A cxmanage command to collect information about a node and archive it. -# -# Example: -# cxmanage tspackage 10.10.10.10 -# - - -import os -import time -import shutil -import tarfile -import tempfile - -from cxmanage import get_tftp, get_nodes, run_command, COMPONENTS - - -def tspackage_command(args): - """Get information pertaining to each node. - This includes: - Version info (like cxmanage info) - MAC addresses - Sensor readings - Sensor data records - Firmware info - Boot order - SELs (System Event Logs), - Depth charts - Routing Tables - - This data will be written to a set of files. Each node will get its own - file. All of these files will be archived and saved to the user's current - directory. - - Internally, this command is called from: - ~/virtual_testenv/workspace/cx_manage_util/scripts/cxmanage - - """ - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - # Make a temporary directory to store the node information files - original_dir = os.getcwd() - temp_dir = tempfile.mkdtemp() - os.chdir(temp_dir) - tspackage_dir = "tspackage.%s" % time.strftime("%Y%m%d%H%M%S") - os.mkdir(tspackage_dir) - os.chdir(tspackage_dir) - - quiet = args.quiet - - if not quiet: - print("Getting version information...") - write_version_info(args, nodes) - - if not quiet: - print("Getting node FRU version...") - write_node_fru_version(args, nodes) - - if not quiet: - print("Getting slot FRU version...") - write_slot_fru_version(args, nodes) - - if not quiet: - print("Getting boot order...") - write_boot_order(args, nodes) - - if not quiet: - print("Getting MAC addresses...") - write_mac_addrs(args, nodes) - - if not quiet: - print("Getting sensor information...") - write_sensor_info(args, nodes) - - if not quiet: - print("Getting firmware information...") - write_fwinfo(args, nodes) - - if not quiet: - print("Getting system event logs...") - write_sel(args, nodes) - - if not quiet: - print("Getting depth charts...") - write_depth_chart(args, nodes) - - if not quiet: - print("Getting routing tables...") - write_routing_table(args, nodes) - - # Archive the files - archive(os.getcwd(), original_dir) - - # The original files are already archived, so we can delete them. - shutil.rmtree(temp_dir) - - -def write_version_info(args, nodes): - """Write the version info (like cxmanage info) for each node - to their respective files. - - """ - info_results, _ = run_command(args, nodes, "get_versions") - - - for node in nodes: - lines = [] # The lines of text to write to file - - # Since this is the first line of the file, we don't need a \n - write_to_file( - node, - "[ Version Info for Node %d ]" % node.node_id, - add_newlines=False - ) - - lines.append("ECME IP Address : %s" % node.ip_address) - - if node in info_results: - info_result = info_results[node] - lines.append( - "Hardware version : %s" % - info_result.hardware_version - ) - lines.append( - "Firmware version : %s" % - info_result.firmware_version - ) - - # Get mappings between attributes and formatted strings - components = COMPONENTS - for var, description in components: - if hasattr(info_result, var): - version = getattr(info_result, var) - lines.append("%s: %s" % (description.ljust(19), version)) - else: - lines.append("No version information could be found.") - - write_to_file(node, lines) - -def write_node_fru_version(args, nodes): - """Write the node and slot FRU versions for each node to their - respective files. - - """ - node_fru_results, _ = run_command(args, nodes, "get_node_fru_version") - - for node in nodes: - lines = [] # Lines of text to write to file - if node in node_fru_results: - lines.append("%s: %s" % \ - ("Node FRU Version".ljust(19), node_fru_results[node])) - else: - lines.append("\nWARNING: No node FRU found!") - write_to_file(node, lines) - -def write_slot_fru_version(args, nodes): - """Write the node and slot FRU versions for each node to their - respective files. - - """ - slot_fru_results, _ = run_command(args, nodes, "get_slot_fru_version") - - for node in nodes: - lines = [] # Lines of text to write to file - if node in slot_fru_results: - lines.append("%s: %s" % \ - ("Slot FRU Version".ljust(19), slot_fru_results[node])) - else: - lines.append("Error reading slot FRU. Perhaps the system board " + - "does not have slot FRUs?") - - write_to_file(node, lines) - -def write_mac_addrs(args, nodes): - """Write the MAC addresses for each node to their respective files.""" - mac_addr_results, _ = run_command( - args, - nodes, - "get_fabric_macaddrs" - ) - - for node in nodes: - lines = [] # Lines of text to write to file - # \n is used here to give a blank line before this section - lines.append("\n[ MAC Addresses for Node %d ]" % node.node_id) - - if node in mac_addr_results: - for port in mac_addr_results[node][node.node_id]: - for mac_address in mac_addr_results[node][node.node_id][port]: - lines.append( - "Node %i, Port %i: %s" % - (node.node_id, port, mac_address) - ) - else: - lines.append("\nWARNING: No MAC addresses found!") - - write_to_file(node, lines) - -# pylint: disable=R0914 -def write_sensor_info(args, nodes): - """Write sensor information for each node to their respective files.""" - args.sensor_name = "" - - results, _ = run_command(args, nodes, "get_sensors", - args.sensor_name) - - sensors = {} - for node in nodes: - lines = [] # Lines of text to write to file - # \n is used here to give a blank line before this section - lines.append("\n[ Sensors for Node %d ]" % node.node_id) - - if node in results: - for sensor_name, sensor in results[node].iteritems(): - if not sensor_name in sensors: - sensors[sensor_name] = [] - - reading = sensor.sensor_reading.replace("(+/- 0) ", "") - try: - value = float(reading.split()[0]) - suffix = reading.lstrip("%f " % value) - sensors[sensor_name].append((node, value, suffix)) - except ValueError: - sensors[sensor_name].append((node, reading, "")) - else: - print("Could not get sensor info!") - lines.append("Could not get sensor info!") - - for sensor_name, readings in sensors.iteritems(): - for reading_node, reading, suffix in readings: - if reading_node.ip_address == node.ip_address: - left_side = "{:<18}".format(sensor_name) - right_side = ": %.2f %s" % (reading, suffix) - lines.append(left_side + right_side) - - write_to_file(node, lines) - - -def write_fwinfo(args, nodes): - """Write information about each node's firware partitions - to its respective file. - - """ - results, _ = run_command(args, nodes, "get_firmware_info") - - for node in nodes: - lines = [] # Lines of text to write to file - # \n is used here to give a blank line before this section - lines.append("\n[ Firmware Info for Node %d ]" % node.node_id) - - if node in results: - first_partition = True # The first partiton doesn't need \n - - for partition in results[node]: - if first_partition: - lines.append("Partition : %s" % partition.partition) - first_partition = False - else: - lines.append("\nPartition : %s" % partition.partition) - lines.append("Type : %s" % partition.type) - lines.append("Offset : %s" % partition.offset) - lines.append("Size : %s" % partition.size) - lines.append("Priority : %s" % partition.priority) - lines.append("Daddr : %s" % partition.daddr) - lines.append("Flags : %s" % partition.flags) - lines.append("Version : %s" % partition.version) - lines.append("In Use : %s" % partition.in_use) - else: - lines.append("Could not get firmware info!") - write_to_file(node, lines) - - -def write_boot_order(args, nodes): - """Write the boot order of each node to their respective files.""" - results, _ = run_command(args, nodes, "get_boot_order") - - for node in nodes: - lines = [] # Lines of text to write to file - # \n is used here to give a blank line before this section - lines.append("\n[ Boot Order for Node %d ]" % node.node_id) - - if node in results: - lines.append(", ".join(results[node])) - else: - lines.append("Could not get boot order!") - - write_to_file(node, lines) - - -def write_sel(args, nodes): - """Write the SEL for each node to their respective files.""" - results, _ = run_command(args, nodes, "get_sel") - - for node in nodes: - lines = [] # Lines of text to write to file - # \n is used here to give a blank line before this section - lines.append("\n[ System Event Log for Node %d ]" % node.node_id) - - try: - if node in results: - for event in results[node]: - lines.append(event) - - # pylint: disable=W0703 - except Exception as error: - lines.append("Could not get SEL! " + str(error)) - if not args.quiet: - print("Failed to get system event log for " + node.ip_address) - - write_to_file(node, lines) - - -def write_depth_chart(args, nodes): - """Write the depth chart for each node to their respective files.""" - depth_results, _ = run_command(args, nodes, "get_depth_chart") - - for node in nodes: - lines = [] # Lines of text to write to file - # \n is used here to give a blank line before this section - lines.append("\n[ Depth Chart for Node %d ]" % node.node_id) - - if node in depth_results: - depth_chart = depth_results[node] - for key in depth_chart: - subchart = depth_chart[key] - lines.append("To node " + str(key)) - - # The 'shortest' entry is one tuple, but - # the 'others' are a list. - for subkey in subchart: - if str(subkey) == "shortest": - lines.append( - " " + str(subkey) + - " : " + str(subchart[subkey]) - ) - else: - for entry in subchart[subkey]: - lines.append( - " " + str(subkey) + - " : " + str(entry) - ) - - else: - lines.append("Could not get depth chart!") - - write_to_file(node, lines) - - -def write_routing_table(args, nodes): - """Write the routing table for each node to their respective files.""" - routing_results, _ = run_command(args, nodes, "get_routing_table") - - for node in nodes: - lines = [] # Lines of text to write to file - # \n is used here to give a blank line before this section - lines.append("\n[ Routing Table for Node %d ]" % node.node_id) - - if node in routing_results: - table = routing_results[node] - for node_to in table: - lines.append(str(node_to) + " : " + str(table[node_to])) - else: - lines.append("Could not get routing table!") - - write_to_file(node, lines) - - -def write_to_file(node, to_write, add_newlines=True): - """Append to_write to an info file for every node in nodes. - - :param node: Node object to write about - :type node: Node object - :param to_write: Text to write to the files - :type to_write: List of strings - :param add_newlines: Whether to add newline characters before - every item in to_write. True by default. True will add newline - characters. - :type add_newlines: bool - - """ - with open("node" + str(node.node_id) + ".txt", 'a') as node_file: - if add_newlines: - # join() doesn't add a newline before the first item - to_write[0] = "\n" + to_write[0] - node_file.write("\n".join(to_write)) - else: - node_file.write("".join(to_write)) - - -def archive(directory_to_archive, destination): - """Creates a .tar containing everything in the directory_to_archive. - The .tar is saved to destination with the same name as the original - directory_to_archive, but with .tar appended. - - :param directory_to_archive: A path to the directory to be archived. - :type directory_to_archive: string - - :param destination: A path to the location the .tar should be saved - :type destination: string - - """ - os.chdir(os.path.dirname(directory_to_archive)) - - tar_name = os.path.basename(directory_to_archive) + ".tar" - tar_name = os.path.join(destination, tar_name) - - with tarfile.open(tar_name, "w") as tar: - tar.add(os.path.basename(directory_to_archive)) - - print( - "Finished! One archive created:\n" + - os.path.join(destination, tar_name) - ) diff --git a/cxmanage_api/cli/__init__.py b/cxmanage_api/cli/__init__.py new file mode 100644 index 0000000..438d568 --- /dev/null +++ b/cxmanage_api/cli/__init__.py @@ -0,0 +1,360 @@ +"""Calxeda: __init__.py """ + + +# 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. + + +import sys +import time + +from cxmanage_api.tftp import InternalTftp, ExternalTftp +from cxmanage_api.node import Node +from cxmanage_api.tasks import TaskQueue +from cxmanage_api.cx_exceptions import TftpException + + +def get_tftp(args): + """Get a TFTP server""" + if args.internal_tftp: + tftp_args = args.internal_tftp.split(':') + if len(tftp_args) == 1: + ip_address = tftp_args[0] + port = 0 + elif len(tftp_args) == 2: + ip_address = tftp_args[0] + port = int(tftp_args[1]) + else: + print ('ERROR: %s is not a valid argument for --internal-tftp' + % args.internal_tftp) + sys.exit(1) + return InternalTftp(ip_address=ip_address, port=port, + verbose=args.verbose) + + elif args.external_tftp: + tftp_args = args.external_tftp.split(':') + if len(tftp_args) == 1: + ip_address = tftp_args[0] + port = 69 + elif len(tftp_args) == 2: + ip_address = tftp_args[0] + port = int(tftp_args[1]) + else: + print ('ERROR: %s is not a valid argument for --external-tftp' + % args.external_tftp) + sys.exit(1) + return ExternalTftp(ip_address=ip_address, port=port, + verbose=args.verbose) + + return InternalTftp(verbose=args.verbose) + +# pylint: disable=R0912 +def get_nodes(args, tftp, verify_prompt=False): + """Get nodes""" + hosts = [] + for entry in args.hostname.split(','): + hosts.extend(parse_host_entry(entry)) + + nodes = [Node(ip_address=x, username=args.user, password=args.password, + tftp=tftp, ecme_tftp_port=args.ecme_tftp_port, + verbose=args.verbose) for x in hosts] + + if args.all_nodes: + if not args.quiet: + print("Getting IP addresses...") + + results, errors = run_command(args, nodes, "get_fabric_ipinfo") + + all_nodes = [] + for node in nodes: + if node in results: + for node_id, ip_address in sorted(results[node].iteritems()): + new_node = Node(ip_address=ip_address, username=args.user, + password=args.password, tftp=tftp, + ecme_tftp_port=args.ecme_tftp_port, + verbose=args.verbose) + new_node.node_id = node_id + if not new_node in all_nodes: + all_nodes.append(new_node) + + node_strings = get_node_strings(args, all_nodes, justify=False) + if not args.quiet and all_nodes: + print("Discovered the following IP addresses:") + for node in all_nodes: + print node_strings[node] + print + + if errors: + print("ERROR: Failed to get IP addresses. Aborting.\n") + sys.exit(1) + + if args.nodes: + if len(all_nodes) != args.nodes: + print ("ERROR: Discovered %i nodes, expected %i. Aborting.\n" + % (len(all_nodes), args.nodes)) + sys.exit(1) + elif verify_prompt and not args.force: + print( + "NOTE: Please check node count! Ensure discovery of all " + + "nodes in the cluster. Power cycle your system if the " + + "discovered node count does not equal nodes in" + + "your system.\n" + ) + if not prompt_yes("Discovered %i nodes. Continue?" + % len(all_nodes)): + sys.exit(1) + + return all_nodes + + return nodes + + +def get_node_strings(args, nodes, justify=False): + """ Get string representations for the nodes. """ + # Use the private _node_id instead of node_id. Strange choice, + # but we want to avoid accidentally polling the BMC. + # pylint: disable=W0212 + if args.ids and all(x._node_id != None for x in nodes): + strings = ["Node %i (%s)" % (x._node_id, x.ip_address) for x in nodes] + else: + strings = [x.ip_address for x in nodes] + + if justify: + just_size = max(16, max(len(x) for x in strings) + 1) + strings = [x.ljust(just_size) for x in strings] + + return dict(zip(nodes, strings)) + + +# pylint: disable=R0915 +def run_command(args, nodes, name, *method_args): + """Runs a command on nodes.""" + if args.threads != None: + task_queue = TaskQueue(threads=args.threads, delay=args.command_delay) + else: + task_queue = TaskQueue(delay=args.command_delay) + + tasks = {} + for node in nodes: + tasks[node] = task_queue.put(getattr(node, name), *method_args) + + results = {} + errors = {} + try: + counter = 0 + while any(x.is_alive() for x in tasks.values()): + if not args.quiet: + _print_command_status(tasks, counter) + counter += 1 + time.sleep(0.25) + + for node, task in tasks.iteritems(): + if task.status == "Completed": + results[node] = task.result + else: + errors[node] = task.error + + except KeyboardInterrupt: + args.retry = 0 + + for node, task in tasks.iteritems(): + if task.status == "Completed": + results[node] = task.result + elif task.status == "Failed": + errors[node] = task.error + else: + errors[node] = KeyboardInterrupt( + "Aborted by keyboard interrupt" + ) + + if not args.quiet: + _print_command_status(tasks, counter) + print("\n") + + # Handle errors + should_retry = False + if errors: + _print_errors(args, nodes, errors) + if args.retry == None: + sys.stdout.write("Retry command on failed hosts? (y/n): ") + sys.stdout.flush() + while True: + command = raw_input().strip().lower() + if command in ['y', 'yes']: + should_retry = True + break + elif command in ['n', 'no']: + print + break + elif args.retry >= 1: + should_retry = True + if args.retry == 1: + print("Retrying command 1 more time...") + elif args.retry > 1: + print("Retrying command %i more times..." % args.retry) + args.retry -= 1 + + if should_retry: + nodes = [x for x in nodes if x in errors] + new_results, errors = run_command(args, nodes, name, *method_args) + results.update(new_results) + + return results, errors + + +def prompt_yes(prompt): + """Prompts the user. """ + sys.stdout.write("%s (y/n) " % prompt) + sys.stdout.flush() + while True: + command = raw_input().strip().lower() + if command in ['y', 'yes']: + print + return True + elif command in ['n', 'no']: + print + return False + + +def parse_host_entry(entry, hostfiles=None): + """parse a host entry""" + if not(hostfiles): + hostfiles = set() + + try: + return parse_hostfile_entry(entry, hostfiles) + except ValueError: + try: + return parse_ip_range_entry(entry) + except ValueError: + return [entry] + + +def parse_hostfile_entry(entry, hostfiles=None): + """parse a hostfile entry, returning a list of hosts""" + if not(hostfiles): + hostfiles = set() + + if entry.startswith('file='): + filename = entry[5:] + elif entry.startswith('hostfile='): + filename = entry[9:] + else: + raise ValueError('%s is not a hostfile entry' % entry) + + if filename in hostfiles: + return [] + hostfiles.add(filename) + + entries = [] + try: + for line in open(filename): + for element in line.partition('#')[0].split(): + for hostfile_entry in element.split(','): + entries.extend(parse_host_entry(hostfile_entry, hostfiles)) + except IOError: + print 'ERROR: %s is not a valid hostfile entry' % entry + sys.exit(1) + + return entries + + +def parse_ip_range_entry(entry): + """ Get a list of ip addresses in a given range""" + try: + start, end = entry.split('-') + + # Convert start address to int + start_bytes = [int(x) for x in start.split('.')] + + start_i = ((start_bytes[0] << 24) | (start_bytes[1] << 16) + | (start_bytes[2] << 8) | (start_bytes[3])) + + # Convert end address to int + end_bytes = [int(x) for x in end.split('.')] + end_i = ((end_bytes[0] << 24) | (end_bytes[1] << 16) + | (end_bytes[2] << 8) | (end_bytes[3])) + + # Get ip addresses in range + addresses = [] + for i in range(start_i, end_i + 1): + address_bytes = [(i >> (24 - 8 * x)) & 0xff for x in range(4)] + addresses.append('%i.%i.%i.%i' % tuple(address_bytes)) + + except (ValueError, IndexError): + raise ValueError('%s is not an IP range' % entry) + + return addresses + + +def _print_errors(args, nodes, errors): + """ Print errors if they occured """ + if errors: + node_strings = get_node_strings(args, nodes, justify=True) + print("Command failed on these hosts") + for node in nodes: + if node in errors: + print("%s: %s" % (node_strings[node], errors[node])) + print + + # Print a special message for TFTP errors + if all(isinstance(x, TftpException) for x in errors.itervalues()): + print( + "There may be networking issues (when behind NAT) between " + + "the host (where cxmanage is running) and the Calxeda node " + + "when establishing a TFTP session. Please refer to the " + + "documentation for more information.\n" + ) + + +def _print_command_status(tasks, counter): + """ Print the status of a command """ + message = "\r%i successes | %i errors | %i nodes left | %s" + successes = len([x for x in tasks.values() if x.status == "Completed"]) + errors = len([x for x in tasks.values() if x.status == "Failed"]) + nodes_left = len(tasks) - successes - errors + dots = "".join(["." for x in range(counter % 4)]).ljust(3) + sys.stdout.write(message % (successes, errors, nodes_left, dots)) + sys.stdout.flush() + + +# These are needed for ipinfo and whenever version information is printed +COMPONENTS = [ + ("ecme_version", "ECME version"), + ("cdb_version", "CDB version"), + ("stage2_version", "Stage2boot version"), + ("bootlog_version", "Bootlog version"), + ("a9boot_version", "A9boot version"), + ("a15boot_version", "A15boot version"), + ("uboot_version", "Uboot version"), + ("ubootenv_version", "Ubootenv version"), + ("dtb_version", "DTB version"), +] + diff --git a/cxmanage_api/cli/commands/__init__.py b/cxmanage_api/cli/commands/__init__.py new file mode 100644 index 0000000..571a3c5 --- /dev/null +++ b/cxmanage_api/cli/commands/__init__.py @@ -0,0 +1,32 @@ +"""Calxeda: __init__.py""" + + +# 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. diff --git a/cxmanage_api/cli/commands/config.py b/cxmanage_api/cli/commands/config.py new file mode 100644 index 0000000..bde21ca --- /dev/null +++ b/cxmanage_api/cli/commands/config.py @@ -0,0 +1,146 @@ +"""Calxeda: config.py """ + + +# 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. + + +from cxmanage_api.cli import get_tftp, get_nodes, get_node_strings, run_command + +from cxmanage_api.ubootenv import validate_boot_args, \ + validate_pxe_interface + + +def config_reset_command(args): + """reset to factory default settings""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp, verify_prompt=True) + + if not args.quiet: + print "Sending config reset command..." + + _, errors = run_command(args, nodes, "config_reset") + + if not args.quiet and not errors: + print "Command completed successfully.\n" + + return len(errors) > 0 + + +def config_boot_command(args): + """set A9 boot order""" + if args.boot_order == ['status']: + return config_boot_status_command(args) + + validate_boot_args(args.boot_order) + + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Setting boot order..." + + _, errors = run_command(args, nodes, "set_boot_order", + args.boot_order) + + if not args.quiet and not errors: + print "Command completed successfully.\n" + + return len(errors) > 0 + + +def config_boot_status_command(args): + """Get boot status command.""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Getting boot order..." + results, errors = run_command(args, nodes, "get_boot_order") + + # Print results + if results: + node_strings = get_node_strings(args, results, justify=True) + print "Boot order" + for node in nodes: + if node in results: + print "%s: %s" % (node_strings[node], ",".join(results[node])) + print + + if not args.quiet and errors: + print "Some errors occured during the command.\n" + + return len(errors) > 0 + + +def config_pxe_command(args): + """set the PXE boot interface""" + if args.interface == "status": + return config_pxe_status_command(args) + + validate_pxe_interface(args.interface) + + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Setting pxe interface..." + + _, errors = run_command(args, nodes, "set_pxe_interface", + args.interface) + + if not args.quiet and not errors: + print "Command completed successfully.\n" + + return len(errors) > 0 + + +def config_pxe_status_command(args): + """Gets pxe status.""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Getting pxe interface..." + results, errors = run_command(args, nodes, "get_pxe_interface") + + # Print results + if results: + node_strings = get_node_strings(args, results, justify=True) + print "PXE interface" + for node in nodes: + if node in results: + print "%s: %s" % (node_strings[node], results[node]) + print + + if not args.quiet and errors: + print "Some errors occured during the command.\n" + + return len(errors) > 0 diff --git a/cxmanage_api/cli/commands/fabric.py b/cxmanage_api/cli/commands/fabric.py new file mode 100644 index 0000000..9a410c1 --- /dev/null +++ b/cxmanage_api/cli/commands/fabric.py @@ -0,0 +1,83 @@ +"""Calxeda: fabric.py""" + + +# 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. + +from cxmanage_api.cli import get_tftp, get_nodes, run_command + + +def ipinfo_command(args): + """get ip info from a cluster or host""" + args.all_nodes = False + + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Getting IP addresses..." + + results, _ = run_command(args, nodes, "get_fabric_ipinfo") + + for node in nodes: + if node in results: + print 'IP info from %s' % node.ip_address + for node_id, node_address in results[node].iteritems(): + print 'Node %i: %s' % (node_id, node_address) + print + + return 0 + + +def macaddrs_command(args): + """get mac addresses from a cluster or host""" + args.all_nodes = False + + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Getting MAC addresses..." + results, errors = run_command(args, nodes, "get_fabric_macaddrs") + + for node in nodes: + if node in results: + print "MAC addresses from %s" % node.ip_address + for node_id in results[node]: + for port in results[node][node_id]: + for mac_address in results[node][node_id][port]: + print "Node %i, Port %i: %s" % (node_id, port, + mac_address) + print + + if not args.quiet and errors: + print "Some errors occured during the command.\n" + + return len(errors) == 0 diff --git a/cxmanage_api/cli/commands/fru_version.py b/cxmanage_api/cli/commands/fru_version.py new file mode 100644 index 0000000..65d0418 --- /dev/null +++ b/cxmanage_api/cli/commands/fru_version.py @@ -0,0 +1,71 @@ +"""Calxeda: fru_version.py """ + + +# Copyright (c) 2013, 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. + + +from cxmanage_api.cli import get_tftp, get_nodes, get_node_strings, run_command + + +def node_fru_version_command(args): + """Get the node FRU version for each node. """ + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + results, errors = run_command(args, nodes, 'get_node_fru_version') + + # Print results if we were successful + if results: + node_strings = get_node_strings(args, results, justify=True) + for node in nodes: + print("%s: %s" % (node_strings[node], results[node])) + + print("") # For readability + + if not args.quiet and errors: + print('Some errors occured during the command.\n') + + +def slot_fru_version_command(args): + """Get the slot FRU version for each node. """ + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + results, errors = run_command(args, nodes, 'get_slot_fru_version') + + # Print results if we were successful + if results: + node_strings = get_node_strings(args, results, justify=True) + for node in nodes: + print("%s: %s" % (node_strings[node], results[node])) + + print("") # For readability + + if not args.quiet and errors: + print('Some errors occured during the command.\n') diff --git a/cxmanage_api/cli/commands/fw.py b/cxmanage_api/cli/commands/fw.py new file mode 100644 index 0000000..b131bf9 --- /dev/null +++ b/cxmanage_api/cli/commands/fw.py @@ -0,0 +1,172 @@ +"""Calxeda: fw.py """ + +# 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. + + +from pkg_resources import parse_version + +from cxmanage_api.cli import get_tftp, get_nodes, get_node_strings, \ + run_command, prompt_yes + +from cxmanage_api.image import Image +from cxmanage_api.firmware_package import FirmwarePackage + +# pylint: disable=R0912 +def fwupdate_command(args): + """update firmware on a cluster or host""" + def do_update(): + """ Do a single firmware check+update. Returns True on failure. """ + if not args.force: + if not args.quiet: + print "Checking hosts..." + + _, errors = run_command(args, nodes, "_check_firmware", + package, args.partition, args.priority) + if errors: + print "ERROR: Firmware update aborted." + return True + + if not args.quiet: + print "Updating firmware..." + + _, errors = run_command(args, nodes, "update_firmware", package, + args.partition, args.priority) + if errors: + print "ERROR: Firmware update failed." + return True + + return False + + def do_reset(): + """ Reset and wait. Returns True on failure. """ + if not args.quiet: + print "Checking ECME versions..." + + results, errors = run_command(args, nodes, "get_versions") + if errors: + print "ERROR: MC reset aborted. Backup partitions not updated." + return True + + for result in results.values(): + version = result.ecme_version.lstrip("v") + if parse_version(version) < parse_version("1.2.0"): + print "ERROR: MC reset is unsafe on ECME version v%s" % version + print "Please power cycle the system and start a new fwupdate." + return True + + if not args.quiet: + print "Resetting nodes..." + + results, errors = run_command(args, nodes, "mc_reset", True) + if errors: + print "ERROR: MC reset failed. Backup partitions not updated." + return True + + return False + + if args.image_type == "PACKAGE": + package = FirmwarePackage(args.filename) + else: + try: + simg = None + if args.force_simg: + simg = False + elif args.skip_simg: + simg = True + + image = Image(args.filename, args.image_type, simg, args.daddr, + args.skip_crc32, args.fw_version) + package = FirmwarePackage() + package.images.append(image) + except ValueError as err: + print "ERROR: %s" % err + return True + + if not args.all_nodes: + if args.force: + print( + 'WARNING: Updating firmware without --all-nodes' + + ' is dangerous.' + ) + else: + if not prompt_yes( + 'WARNING: Updating firmware without ' + + '--all-nodes is dangerous. Continue?' + ): + return 1 + + tftp = get_tftp(args) + nodes = get_nodes(args, tftp, verify_prompt=True) + + errors = do_update() + + if args.full and not errors: + errors = do_reset() + if not errors: + errors = do_update() + + if not args.quiet and not errors: + print "Command completed successfully.\n" + + return errors + + +def fwinfo_command(args): + """print firmware info""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Getting firmware info..." + + results, errors = run_command(args, nodes, "get_firmware_info") + + node_strings = get_node_strings(args, results, justify=False) + for node in nodes: + if node in results: + print "[ Firmware info for %s ]" % node_strings[node] + + for partition in results[node]: + print "Partition : %s" % partition.partition + print "Type : %s" % partition.type + print "Offset : %s" % partition.offset + print "Size : %s" % partition.size + print "Priority : %s" % partition.priority + print "Daddr : %s" % partition.daddr + print "Flags : %s" % partition.flags + print "Version : %s" % partition.version + print "In Use : %s" % partition.in_use + print + + if not args.quiet and errors: + print "Some errors occured during the command.\n" + + return len(errors) > 0 diff --git a/cxmanage_api/cli/commands/info.py b/cxmanage_api/cli/commands/info.py new file mode 100644 index 0000000..0f0b2ca --- /dev/null +++ b/cxmanage_api/cli/commands/info.py @@ -0,0 +1,103 @@ +"""Calxeda: info.py""" + + +# 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. + + +from cxmanage_api.cli import get_tftp, get_nodes, get_node_strings, \ + run_command, COMPONENTS + + +def info_command(args): + """print info from a cluster or host""" + if args.info_type in [None, 'basic']: + return info_basic_command(args) + elif args.info_type == 'ubootenv': + return info_ubootenv_command(args) + + +def info_basic_command(args): + """Print basic info""" + + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Getting info..." + results, errors = run_command(args, nodes, "get_versions") + + # Print results + node_strings = get_node_strings(args, results, justify=False) + for node in nodes: + if node in results: + result = results[node] + # Get mappings between attributes and formatted strings + components = COMPONENTS + + print "[ Info from %s ]" % node_strings[node] + print "Hardware version : %s" % result.hardware_version + print "Firmware version : %s" % result.firmware_version + # var is the variable, string is the printable string of var + for var, string in components: + if hasattr(result, var): + version = getattr(result, var) + print "%s: %s" % (string.ljust(19), version) + print + + if not args.quiet and errors: + print "Some errors occured during the command.\n" + + return len(errors) > 0 + + +def info_ubootenv_command(args): + """Print uboot info""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Getting u-boot environment..." + results, errors = run_command(args, nodes, "get_ubootenv") + + # Print results + node_strings = get_node_strings(args, results, justify=False) + for node in nodes: + if node in results: + ubootenv = results[node] + print "[ U-Boot Environment from %s ]" % node_strings[node] + for variable in ubootenv.variables: + print "%s=%s" % (variable, ubootenv.variables[variable]) + print + + if not args.quiet and errors: + print "Some errors occured during the command.\n" + + return len(errors) > 0 diff --git a/cxmanage_api/cli/commands/ipdiscover.py b/cxmanage_api/cli/commands/ipdiscover.py new file mode 100644 index 0000000..c6c3dee --- /dev/null +++ b/cxmanage_api/cli/commands/ipdiscover.py @@ -0,0 +1,59 @@ +"""Calxeda: ipdiscover.py""" + + +# 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. + +from cxmanage_api.cli import get_tftp, get_nodes, get_node_strings, run_command + + +def ipdiscover_command(args): + """discover server IP addresses""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print 'Getting server-side IP addresses...' + + results, errors = run_command(args, nodes, 'get_server_ip', args.interface, + args.ipv6, args.server_user, args.server_password, args.aggressive) + + if results: + node_strings = get_node_strings(args, results, justify=True) + print 'IP addresses (ECME, Server)' + for node in nodes: + if node in results: + print '%s: %s' % (node_strings[node], results[node]) + print + + if not args.quiet and errors: + print 'Some errors occurred during the command.' + + return len(errors) > 0 diff --git a/cxmanage_api/cli/commands/ipmitool.py b/cxmanage_api/cli/commands/ipmitool.py new file mode 100644 index 0000000..2c54b37 --- /dev/null +++ b/cxmanage_api/cli/commands/ipmitool.py @@ -0,0 +1,63 @@ +"""Calxeda: ipmitool.py""" + + +# 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. + + +from cxmanage_api.cli import get_tftp, get_nodes, get_node_strings, run_command + +def ipmitool_command(args): + """run arbitrary ipmitool command""" + if args.lanplus: + ipmitool_args = ['-I', 'lanplus'] + args.ipmitool_args + else: + ipmitool_args = args.ipmitool_args + + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Running IPMItool command..." + results, errors = run_command(args, nodes, "ipmitool_command", + ipmitool_args) + + # Print results + node_strings = get_node_strings(args, results, justify=False) + for node in nodes: + if node in results and results[node] != "": + print "[ IPMItool output from %s ]" % node_strings[node] + print results[node] + print + + if not args.quiet and errors: + print "Some errors occured during the command.\n" + + return len(errors) > 0 diff --git a/cxmanage_api/cli/commands/mc.py b/cxmanage_api/cli/commands/mc.py new file mode 100644 index 0000000..ac258ab --- /dev/null +++ b/cxmanage_api/cli/commands/mc.py @@ -0,0 +1,51 @@ +"""Calxeda: mc.py""" + + +# 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. + + +from cxmanage_api.cli import get_tftp, get_nodes, run_command + + +def mcreset_command(args): + """reset the management controllers of a cluster or host""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print 'Sending MC reset command...' + + _, errors = run_command(args, nodes, 'mc_reset') + + if not args.quiet and not errors: + print 'Command completed successfully.\n' + + return len(errors) > 0 diff --git a/cxmanage_api/cli/commands/power.py b/cxmanage_api/cli/commands/power.py new file mode 100644 index 0000000..623c38d --- /dev/null +++ b/cxmanage_api/cli/commands/power.py @@ -0,0 +1,116 @@ +"""Calxeda: power.py """ + + +# 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. + +from cxmanage import get_tftp, get_nodes, get_node_strings, run_command + + +def power_command(args): + """change the power state of a cluster or host""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print 'Sending power %s command...' % args.power_mode + + _, errors = run_command(args, nodes, 'set_power', args.power_mode) + + if not args.quiet and not errors: + print 'Command completed successfully.\n' + + return len(errors) > 0 + + +def power_status_command(args): + """Executes the power status command with args.""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print 'Getting power status...' + results, errors = run_command(args, nodes, 'get_power') + + # Print results + if results: + node_strings = get_node_strings(args, results, justify=True) + print 'Power status' + for node in nodes: + if node in results: + result = 'on' if results[node] else 'off' + print '%s: %s' % (node_strings[node], result) + print + + if not args.quiet and errors: + print 'Some errors occured during the command.\n' + + return len(errors) > 0 + + +def power_policy_command(args): + """Executes power policy command with args.""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print 'Setting power policy to %s...' % args.policy + + _, errors = run_command(args, nodes, 'set_power_policy', + args.policy) + + if not args.quiet and not errors: + print 'Command completed successfully.\n' + + return len(errors) > 0 + + +def power_policy_status_command(args): + """Executes the power policy status command with args.""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print 'Getting power policy status...' + results, errors = run_command(args, nodes, 'get_power_policy') + + # Print results + if results: + node_strings = get_node_strings(args, results, justify=True) + print 'Power policy status' + for node in nodes: + if node in results: + print '%s: %s' % (node_strings[node], results[node]) + print + + if not args.quiet and errors: + print 'Some errors occured during the command.\n' + + return len(errors) > 0 diff --git a/cxmanage_api/cli/commands/sensor.py b/cxmanage_api/cli/commands/sensor.py new file mode 100644 index 0000000..3a27143 --- /dev/null +++ b/cxmanage_api/cli/commands/sensor.py @@ -0,0 +1,87 @@ +"""Calxeda: sensor.py""" + + +# 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. + + +from cxmanage import get_tftp, get_nodes, get_node_strings, run_command + +# pylint: disable=R0914 +def sensor_command(args): + """read sensor values from a cluster or host""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Getting sensor readings..." + results, errors = run_command(args, nodes, "get_sensors", + args.sensor_name) + + sensors = {} + for node in nodes: + if node in results: + for sensor_name, sensor in results[node].iteritems(): + if not sensor_name in sensors: + sensors[sensor_name] = [] + + reading = sensor.sensor_reading.replace("(+/- 0) ", "") + try: + value = float(reading.split()[0]) + suffix = reading.lstrip("%f " % value) + sensors[sensor_name].append((node, value, suffix)) + except ValueError: + sensors[sensor_name].append((node, reading, "")) + + node_strings = get_node_strings(args, results, justify=True) + jsize = len(node_strings.itervalues().next()) + for sensor_name, readings in sensors.iteritems(): + print sensor_name + + for node, reading, suffix in readings: + print "%s: %.2f %s" % (node_strings[node], reading, suffix) + + try: + if all(suffix == x[2] for x in readings): + minimum = min(x[1] for x in readings) + maximum = max(x[1] for x in readings) + average = sum(x[1] for x in readings) / len(readings) + print "%s: %.2f %s" % ("Minimum".ljust(jsize), minimum, suffix) + print "%s: %.2f %s" % ("Maximum".ljust(jsize), maximum, suffix) + print "%s: %.2f %s" % ("Average".ljust(jsize), average, suffix) + except ValueError: + pass + + print + + if not args.quiet and errors: + print "Some errors occured during the command.\n" + + return len(errors) > 0 diff --git a/cxmanage_api/cli/commands/tspackage.py b/cxmanage_api/cli/commands/tspackage.py new file mode 100644 index 0000000..d6ee198 --- /dev/null +++ b/cxmanage_api/cli/commands/tspackage.py @@ -0,0 +1,448 @@ +"""Calxeda: tspackage.py""" + + +# Copyright 2013 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. + + +# +# A cxmanage command to collect information about a node and archive it. +# +# Example: +# cxmanage tspackage 10.10.10.10 +# + + +import os +import time +import shutil +import tarfile +import tempfile + +from cxmanage import get_tftp, get_nodes, run_command, COMPONENTS + + +def tspackage_command(args): + """Get information pertaining to each node. + This includes: + Version info (like cxmanage info) + MAC addresses + Sensor readings + Sensor data records + Firmware info + Boot order + SELs (System Event Logs), + Depth charts + Routing Tables + + This data will be written to a set of files. Each node will get its own + file. All of these files will be archived and saved to the user's current + directory. + + Internally, this command is called from: + ~/virtual_testenv/workspace/cx_manage_util/scripts/cxmanage + + """ + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + # Make a temporary directory to store the node information files + original_dir = os.getcwd() + temp_dir = tempfile.mkdtemp() + os.chdir(temp_dir) + tspackage_dir = "tspackage.%s" % time.strftime("%Y%m%d%H%M%S") + os.mkdir(tspackage_dir) + os.chdir(tspackage_dir) + + quiet = args.quiet + + if not quiet: + print("Getting version information...") + write_version_info(args, nodes) + + if not quiet: + print("Getting node FRU version...") + write_node_fru_version(args, nodes) + + if not quiet: + print("Getting slot FRU version...") + write_slot_fru_version(args, nodes) + + if not quiet: + print("Getting boot order...") + write_boot_order(args, nodes) + + if not quiet: + print("Getting MAC addresses...") + write_mac_addrs(args, nodes) + + if not quiet: + print("Getting sensor information...") + write_sensor_info(args, nodes) + + if not quiet: + print("Getting firmware information...") + write_fwinfo(args, nodes) + + if not quiet: + print("Getting system event logs...") + write_sel(args, nodes) + + if not quiet: + print("Getting depth charts...") + write_depth_chart(args, nodes) + + if not quiet: + print("Getting routing tables...") + write_routing_table(args, nodes) + + # Archive the files + archive(os.getcwd(), original_dir) + + # The original files are already archived, so we can delete them. + shutil.rmtree(temp_dir) + + +def write_version_info(args, nodes): + """Write the version info (like cxmanage info) for each node + to their respective files. + + """ + info_results, _ = run_command(args, nodes, "get_versions") + + + for node in nodes: + lines = [] # The lines of text to write to file + + # Since this is the first line of the file, we don't need a \n + write_to_file( + node, + "[ Version Info for Node %d ]" % node.node_id, + add_newlines=False + ) + + lines.append("ECME IP Address : %s" % node.ip_address) + + if node in info_results: + info_result = info_results[node] + lines.append( + "Hardware version : %s" % + info_result.hardware_version + ) + lines.append( + "Firmware version : %s" % + info_result.firmware_version + ) + + # Get mappings between attributes and formatted strings + components = COMPONENTS + for var, description in components: + if hasattr(info_result, var): + version = getattr(info_result, var) + lines.append("%s: %s" % (description.ljust(19), version)) + else: + lines.append("No version information could be found.") + + write_to_file(node, lines) + +def write_node_fru_version(args, nodes): + """Write the node and slot FRU versions for each node to their + respective files. + + """ + node_fru_results, _ = run_command(args, nodes, "get_node_fru_version") + + for node in nodes: + lines = [] # Lines of text to write to file + if node in node_fru_results: + lines.append("%s: %s" % \ + ("Node FRU Version".ljust(19), node_fru_results[node])) + else: + lines.append("\nWARNING: No node FRU found!") + write_to_file(node, lines) + +def write_slot_fru_version(args, nodes): + """Write the node and slot FRU versions for each node to their + respective files. + + """ + slot_fru_results, _ = run_command(args, nodes, "get_slot_fru_version") + + for node in nodes: + lines = [] # Lines of text to write to file + if node in slot_fru_results: + lines.append("%s: %s" % \ + ("Slot FRU Version".ljust(19), slot_fru_results[node])) + else: + lines.append("Error reading slot FRU. Perhaps the system board " + + "does not have slot FRUs?") + + write_to_file(node, lines) + +def write_mac_addrs(args, nodes): + """Write the MAC addresses for each node to their respective files.""" + mac_addr_results, _ = run_command( + args, + nodes, + "get_fabric_macaddrs" + ) + + for node in nodes: + lines = [] # Lines of text to write to file + # \n is used here to give a blank line before this section + lines.append("\n[ MAC Addresses for Node %d ]" % node.node_id) + + if node in mac_addr_results: + for port in mac_addr_results[node][node.node_id]: + for mac_address in mac_addr_results[node][node.node_id][port]: + lines.append( + "Node %i, Port %i: %s" % + (node.node_id, port, mac_address) + ) + else: + lines.append("\nWARNING: No MAC addresses found!") + + write_to_file(node, lines) + +# pylint: disable=R0914 +def write_sensor_info(args, nodes): + """Write sensor information for each node to their respective files.""" + args.sensor_name = "" + + results, _ = run_command(args, nodes, "get_sensors", + args.sensor_name) + + sensors = {} + for node in nodes: + lines = [] # Lines of text to write to file + # \n is used here to give a blank line before this section + lines.append("\n[ Sensors for Node %d ]" % node.node_id) + + if node in results: + for sensor_name, sensor in results[node].iteritems(): + if not sensor_name in sensors: + sensors[sensor_name] = [] + + reading = sensor.sensor_reading.replace("(+/- 0) ", "") + try: + value = float(reading.split()[0]) + suffix = reading.lstrip("%f " % value) + sensors[sensor_name].append((node, value, suffix)) + except ValueError: + sensors[sensor_name].append((node, reading, "")) + else: + print("Could not get sensor info!") + lines.append("Could not get sensor info!") + + for sensor_name, readings in sensors.iteritems(): + for reading_node, reading, suffix in readings: + if reading_node.ip_address == node.ip_address: + left_side = "{:<18}".format(sensor_name) + right_side = ": %.2f %s" % (reading, suffix) + lines.append(left_side + right_side) + + write_to_file(node, lines) + + +def write_fwinfo(args, nodes): + """Write information about each node's firware partitions + to its respective file. + + """ + results, _ = run_command(args, nodes, "get_firmware_info") + + for node in nodes: + lines = [] # Lines of text to write to file + # \n is used here to give a blank line before this section + lines.append("\n[ Firmware Info for Node %d ]" % node.node_id) + + if node in results: + first_partition = True # The first partiton doesn't need \n + + for partition in results[node]: + if first_partition: + lines.append("Partition : %s" % partition.partition) + first_partition = False + else: + lines.append("\nPartition : %s" % partition.partition) + lines.append("Type : %s" % partition.type) + lines.append("Offset : %s" % partition.offset) + lines.append("Size : %s" % partition.size) + lines.append("Priority : %s" % partition.priority) + lines.append("Daddr : %s" % partition.daddr) + lines.append("Flags : %s" % partition.flags) + lines.append("Version : %s" % partition.version) + lines.append("In Use : %s" % partition.in_use) + else: + lines.append("Could not get firmware info!") + write_to_file(node, lines) + + +def write_boot_order(args, nodes): + """Write the boot order of each node to their respective files.""" + results, _ = run_command(args, nodes, "get_boot_order") + + for node in nodes: + lines = [] # Lines of text to write to file + # \n is used here to give a blank line before this section + lines.append("\n[ Boot Order for Node %d ]" % node.node_id) + + if node in results: + lines.append(", ".join(results[node])) + else: + lines.append("Could not get boot order!") + + write_to_file(node, lines) + + +def write_sel(args, nodes): + """Write the SEL for each node to their respective files.""" + results, _ = run_command(args, nodes, "get_sel") + + for node in nodes: + lines = [] # Lines of text to write to file + # \n is used here to give a blank line before this section + lines.append("\n[ System Event Log for Node %d ]" % node.node_id) + + try: + if node in results: + for event in results[node]: + lines.append(event) + + # pylint: disable=W0703 + except Exception as error: + lines.append("Could not get SEL! " + str(error)) + if not args.quiet: + print("Failed to get system event log for " + node.ip_address) + + write_to_file(node, lines) + + +def write_depth_chart(args, nodes): + """Write the depth chart for each node to their respective files.""" + depth_results, _ = run_command(args, nodes, "get_depth_chart") + + for node in nodes: + lines = [] # Lines of text to write to file + # \n is used here to give a blank line before this section + lines.append("\n[ Depth Chart for Node %d ]" % node.node_id) + + if node in depth_results: + depth_chart = depth_results[node] + for key in depth_chart: + subchart = depth_chart[key] + lines.append("To node " + str(key)) + + # The 'shortest' entry is one tuple, but + # the 'others' are a list. + for subkey in subchart: + if str(subkey) == "shortest": + lines.append( + " " + str(subkey) + + " : " + str(subchart[subkey]) + ) + else: + for entry in subchart[subkey]: + lines.append( + " " + str(subkey) + + " : " + str(entry) + ) + + else: + lines.append("Could not get depth chart!") + + write_to_file(node, lines) + + +def write_routing_table(args, nodes): + """Write the routing table for each node to their respective files.""" + routing_results, _ = run_command(args, nodes, "get_routing_table") + + for node in nodes: + lines = [] # Lines of text to write to file + # \n is used here to give a blank line before this section + lines.append("\n[ Routing Table for Node %d ]" % node.node_id) + + if node in routing_results: + table = routing_results[node] + for node_to in table: + lines.append(str(node_to) + " : " + str(table[node_to])) + else: + lines.append("Could not get routing table!") + + write_to_file(node, lines) + + +def write_to_file(node, to_write, add_newlines=True): + """Append to_write to an info file for every node in nodes. + + :param node: Node object to write about + :type node: Node object + :param to_write: Text to write to the files + :type to_write: List of strings + :param add_newlines: Whether to add newline characters before + every item in to_write. True by default. True will add newline + characters. + :type add_newlines: bool + + """ + with open("node" + str(node.node_id) + ".txt", 'a') as node_file: + if add_newlines: + # join() doesn't add a newline before the first item + to_write[0] = "\n" + to_write[0] + node_file.write("\n".join(to_write)) + else: + node_file.write("".join(to_write)) + + +def archive(directory_to_archive, destination): + """Creates a .tar containing everything in the directory_to_archive. + The .tar is saved to destination with the same name as the original + directory_to_archive, but with .tar appended. + + :param directory_to_archive: A path to the directory to be archived. + :type directory_to_archive: string + + :param destination: A path to the location the .tar should be saved + :type destination: string + + """ + os.chdir(os.path.dirname(directory_to_archive)) + + tar_name = os.path.basename(directory_to_archive) + ".tar" + tar_name = os.path.join(destination, tar_name) + + with tarfile.open(tar_name, "w") as tar: + tar.add(os.path.basename(directory_to_archive)) + + print( + "Finished! One archive created:\n" + + os.path.join(destination, tar_name) + ) diff --git a/scripts/cxmanage b/scripts/cxmanage index a3edb37..ce7748c 100755 --- a/scripts/cxmanage +++ b/scripts/cxmanage @@ -36,19 +36,19 @@ import pkg_resources import subprocess import sys -from cxmanage.commands.power import power_command, power_status_command, \ - power_policy_command, power_policy_status_command -from cxmanage.commands.mc import mcreset_command -from cxmanage.commands.fw import fwupdate_command, fwinfo_command -from cxmanage.commands.sensor import sensor_command -from cxmanage.commands.fabric import ipinfo_command, macaddrs_command -from cxmanage.commands.config import config_reset_command, config_boot_command, \ - config_pxe_command -from cxmanage.commands.info import info_command -from cxmanage.commands.ipmitool import ipmitool_command -from cxmanage.commands.ipdiscover import ipdiscover_command -from cxmanage.commands.tspackage import tspackage_command -from cxmanage.commands.fru_version import node_fru_version_command, \ +from cxmanage_api.cli.commands.power import power_command, \ + power_status_command, power_policy_command, power_policy_status_command +from cxmanage_api.cli.commands.mc import mcreset_command +from cxmanage_api.cli.commands.fw import fwupdate_command, fwinfo_command +from cxmanage_api.cli.commands.sensor import sensor_command +from cxmanage_api.cli.commands.fabric import ipinfo_command, macaddrs_command +from cxmanage_api.cli.commands.config import config_reset_command, \ + config_boot_command, config_pxe_command +from cxmanage_api.cli.commands.info import info_command +from cxmanage_api.cli.commands.ipmitool import ipmitool_command +from cxmanage_api.cli.commands.ipdiscover import ipdiscover_command +from cxmanage_api.cli.commands.tspackage import tspackage_command +from cxmanage_api.cli.commands.fru_version import node_fru_version_command, \ slot_fru_version_command diff --git a/setup.py b/setup.py index c2b5bb5..daba960 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,12 @@ from setuptools import setup setup( name='cxmanage', version='0.9.0', - packages=['cxmanage', 'cxmanage.commands', 'cxmanage_api', 'cxmanage_test'], + packages=[ + 'cxmanage_api', + 'cxmanage_api.cli', + 'cxmanage_api.cli.commands', + 'cxmanage_test' + ], scripts=['scripts/cxmanage', 'scripts/sol_tabs'], description='Calxeda Management Utility', # NOTE: As of right now, the pyipmi version requirement needs to be updated -- cgit v1.2.1