From 530ff68d7fe9eecec3381ca47ba4c2c6e0ed92c3 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Fri, 7 Jun 2013 17:16:29 -0500 Subject: CXMAN-207: Retry firmware TFTP transfers up to 3 times If the initial RRQ or WRQ packet out is dropped, tftpy seems to break and never retries. We need to be more tolerant than that, so just retry the transfer a couple of times. We used to do that anyway. --- cxmanage_api/node.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 9ccae97..efd38ba 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1351,10 +1351,14 @@ class Node(object): filename = image.render_to_simg(priority, daddr) basename = os.path.basename(filename) - try: - self.bmc.register_firmware_write(basename, partition_id, image.type) - self.ecme_tftp.put_file(filename, basename) - except (IpmiError, TftpException): + for x in xrange(2): + try: + self.bmc.register_firmware_write(basename, partition_id, image.type) + self.ecme_tftp.put_file(filename, basename) + break + except (IpmiError, TftpException): + pass + else: # Fall back and use TFTP server self.tftp.put_file(filename, basename) result = self.bmc.update_firmware(basename, partition_id, @@ -1376,10 +1380,14 @@ class Node(object): partition_id = int(partition.partition) image_type = partition.type.split()[1][1:-1] - try: - self.bmc.register_firmware_read(basename, partition_id, image_type) - self.ecme_tftp.get_file(basename, filename) - except (IpmiError, TftpException): + for x in xrange(2): + try: + self.bmc.register_firmware_read(basename, partition_id, image_type) + self.ecme_tftp.get_file(basename, filename) + break + except (IpmiError, TftpException): + pass + else: # Fall back and use TFTP server result = self.bmc.retrieve_firmware(basename, partition_id, image_type, self.tftp_address) -- cgit v1.2.1 From 1df4df675dbf81358880f529a49db01d40c0a248 Mon Sep 17 00:00:00 2001 From: evasquez Date: Thu, 13 Jun 2013 13:44:58 -0500 Subject: AIT:Added a refresh() function for Fabrics. Sometimes when tests power on/off systems or MC_RESET node IPs change. We want the ability to get the new ips for the current fabric object without a complete re-init. --- cxmanage_api/fabric.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/cxmanage_api/fabric.py b/cxmanage_api/fabric.py index 34f435e..6159c47 100644 --- a/cxmanage_api/fabric.py +++ b/cxmanage_api/fabric.py @@ -137,7 +137,8 @@ class Fabric(object): """ if not self._nodes: - self._discover_nodes(self.ip_address) + self.refresh() + return self._nodes @property @@ -154,6 +155,23 @@ class Fabric(object): """ return self.nodes[0] + def refresh(self): + """Gets the nodes of this fabric by pulling IP info from a BMC.""" + self._nodes = {} + node = self.node(ip_address=self.ip_address, username=self.username, + password=self.password, tftp=self.tftp, + ecme_tftp_port=self.ecme_tftp_port, + verbose=self.verbose) + ipinfo = node.get_fabric_ipinfo() + for node_id, node_address in ipinfo.iteritems(): + self._nodes[node_id] = self.node(ip_address=node_address, + username=self.username, + password=self.password, + tftp=self.tftp, + ecme_tftp_port=self.ecme_tftp_port, + verbose=self.verbose) + self._nodes[node_id].node_id = node_id + def get_mac_addresses(self): """Gets MAC addresses from all nodes. @@ -884,21 +902,5 @@ class Fabric(object): raise CommandFailedError(results, errors) return results - def _discover_nodes(self, ip_address, username="admin", password="admin"): - """Gets the nodes of this fabric by pulling IP info from a BMC.""" - node = self.node(ip_address=ip_address, username=username, - password=password, tftp=self.tftp, - ecme_tftp_port=self.ecme_tftp_port, - verbose=self.verbose) - ipinfo = node.get_fabric_ipinfo() - for node_id, node_address in ipinfo.iteritems(): - self._nodes[node_id] = self.node(ip_address=node_address, - username=username, - password=password, - tftp=self.tftp, - ecme_tftp_port=self.ecme_tftp_port, - verbose=self.verbose) - self._nodes[node_id].node_id = node_id - # End of file: ./fabric.py -- cgit v1.2.1 From 0ac16c528c96bae2aee39a9901838059dca0a3f7 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Thu, 20 Jun 2013 16:52:24 -0500 Subject: CXMAN-206: Remove error handling around bmc.check_firmware() We expect it to raise a proper IpmiError now. --- cxmanage_api/node.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index efd38ba..467d896 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -702,11 +702,7 @@ class Node(object): raise Exception("Update failed (partition %i, not activated)" % partition_id) - result = self.bmc.check_firmware(partition_id) - if not hasattr(result, "crc32") or result.error != None: - raise Exception("Update failed (partition %i, post-crc32 fail)" - % partition_id) - + self.bmc.check_firmware(partition_id) def config_reset(self): """Resets configuration to factory defaults. @@ -1368,9 +1364,7 @@ class Node(object): self._wait_for_transfer(result.tftp_handle_id) # Verify crc and activate - result = self.bmc.check_firmware(partition_id) - if ((not hasattr(result, "crc32")) or (result.error != None)): - raise AttributeError("Node reported crc32 check failure") + self.bmc.check_firmware(partition_id) self.bmc.activate_firmware(partition_id) def _download_image(self, partition): -- cgit v1.2.1 From 13f8393881b33b386b0185853b2fdb20fecfe66f Mon Sep 17 00:00:00 2001 From: George Kraft Date: Thu, 20 Jun 2013 17:19:43 -0500 Subject: CXMAN-206: Remove the _parse_ipmierror method Yeah, let's just do that in pyipmi where it belongs. --- cxmanage_api/node.py | 207 +++++++++++++++++---------------------------------- 1 file changed, 70 insertions(+), 137 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 467d896..da1bc0c 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -217,10 +217,7 @@ class Node(object): :rtype: boolean """ - try: - return self.bmc.get_chassis_status().power_on - except IpmiError as e: - raise IpmiError(self._parse_ipmierror(e)) + return self.bmc.get_chassis_status().power_on def set_power(self, mode): """Send an IPMI power command to this target. @@ -238,10 +235,7 @@ class Node(object): :type mode: string """ - try: - self.bmc.set_chassis_power(mode=mode) - except IpmiError as e: - raise IpmiError(self._parse_ipmierror(e)) + self.bmc.set_chassis_power(mode=mode) def get_power_policy(self): """Return power status reported by IPMI. @@ -255,10 +249,7 @@ class Node(object): :raises IpmiError: If errors in the command occur with BMC communication. """ - try: - return self.bmc.get_chassis_status().power_restore_policy - except IpmiError as e: - raise IpmiError(self._parse_ipmierror(e)) + return self.bmc.get_chassis_status().power_restore_policy def set_power_policy(self, state): """Set default power state for Linux side. @@ -273,10 +264,7 @@ class Node(object): :type state: string """ - try: - self.bmc.set_chassis_policy(state) - except IpmiError as e: - raise IpmiError(self._parse_ipmierror(e)) + self.bmc.set_chassis_policy(state) def mc_reset(self, wait=False): """Sends a Master Control reset command to the node. @@ -290,12 +278,7 @@ class Node(object): :raises IPMIError: If there is an IPMI error communicating with the BMC. """ - try: - result = self.bmc.mc_reset("cold") - if (hasattr(result, "error")): - raise Exception(result.error) - except IpmiError as e: - raise IpmiError(self._parse_ipmierror(e)) + self.bmc.mc_reset("cold") if wait: deadline = time.time() + 300.0 @@ -357,11 +340,8 @@ class Node(object): :rtype: dictionary of pyipmi objects """ - try: - sensors = [x for x in self.bmc.sdr_list() - if search.lower() in x.sensor_name.lower()] - except IpmiError as e: - raise IpmiError(self._parse_ipmierror(e)) + sensors = [x for x in self.bmc.sdr_list() + if search.lower() in x.sensor_name.lower()] if (len(sensors) == 0): if (search == ""): @@ -519,30 +499,28 @@ class Node(object): :raises IpmiError: If errors in the command occur with BMC communication. """ - try: - fwinfo = [x for x in self.bmc.get_firmware_info() - if hasattr(x, "partition")] - if (len(fwinfo) == 0): - raise NoFirmwareInfoError("Failed to retrieve firmware info") - - # Clean up the fwinfo results - for entry in fwinfo: - if (entry.version == ""): - entry.version = "Unknown" - - # Flag CDB as "in use" based on socman info - for a in range(1, len(fwinfo)): - previous = fwinfo[a - 1] - current = fwinfo[a] - if (current.type.split()[1][1:-1] == "CDB" and - current.in_use == "Unknown"): - if (previous.type.split()[1][1:-1] != "SOC_ELF"): - current.in_use = "1" - else: - current.in_use = previous.in_use - return fwinfo - except IpmiError as error_details: - raise IpmiError(self._parse_ipmierror(error_details)) + fwinfo = [x for x in self.bmc.get_firmware_info() + if hasattr(x, "partition")] + if (len(fwinfo) == 0): + raise NoFirmwareInfoError("Failed to retrieve firmware info") + + # Clean up the fwinfo results + for entry in fwinfo: + if (entry.version == ""): + entry.version = "Unknown" + + # Flag CDB as "in use" based on socman info + for a in range(1, len(fwinfo)): + previous = fwinfo[a - 1] + current = fwinfo[a] + if (current.type.split()[1][1:-1] == "CDB" and + current.in_use == "Unknown"): + if (previous.type.split()[1][1:-1] != "SOC_ELF"): + current.in_use = "1" + else: + current.in_use = previous.in_use + + return fwinfo def get_firmware_info_dict(self): """Gets firmware info for each partition on the Node. @@ -713,22 +691,20 @@ class Node(object): :raises Exception: If there are errors within the command response. """ - try: - # Reset CDB - result = self.bmc.reset_firmware() - if (hasattr(result, "error")): - raise Exception(result.error) - - # Reset ubootenv - fwinfo = self.get_firmware_info() - running_part = self._get_partition(fwinfo, "UBOOTENV", "FIRST") - factory_part = self._get_partition(fwinfo, "UBOOTENV", "SECOND") - image = self._download_image(factory_part) - self._upload_image(image, running_part) - # Clear SEL - self.bmc.sel_clear() - except IpmiError as e: - raise IpmiError(self._parse_ipmierror(e)) + # Reset CDB + result = self.bmc.reset_firmware() + if (hasattr(result, "error")): + raise Exception(result.error) + + # Reset ubootenv + fwinfo = self.get_firmware_info() + running_part = self._get_partition(fwinfo, "UBOOTENV", "FIRST") + factory_part = self._get_partition(fwinfo, "UBOOTENV", "SECOND") + image = self._download_image(factory_part) + self._upload_image(image, running_part) + + # Clear SEL + self.bmc.sel_clear() def set_boot_order(self, boot_args): """Sets boot-able device order for this node. @@ -784,10 +760,7 @@ class Node(object): :raises Exception: If there are errors within the command response. """ - try: - result = self.bmc.get_info_basic() - except IpmiError as e: - raise IpmiError(self._parse_ipmierror(e)) + result = self.bmc.get_info_basic() fwinfo = self.get_firmware_info() components = [("cdb_version", "CDB"), @@ -905,12 +878,9 @@ class Node(object): :raises TftpException: If the TFTP transfer fails. """ - try: - filename = self._run_fabric_command( - function_name='fabric_config_get_ip_info', - ) - except IpmiError as e: - raise IpmiError(self._parse_ipmierror(e)) + filename = self._run_fabric_command( + function_name='fabric_config_get_ip_info' + ) # Parse addresses from ipinfo file results = {} @@ -941,13 +911,9 @@ class Node(object): :raises TftpException: If the TFTP transfer fails. """ - try: - filename = self._run_fabric_command( - function_name='fabric_config_get_mac_addresses' - ) - - except IpmiError as e: - raise IpmiError(self._parse_ipmierror(e)) + filename = self._run_fabric_command( + function_name='fabric_config_get_mac_addresses' + ) # Parse addresses from ipinfo file results = {} @@ -1055,12 +1021,9 @@ class Node(object): :raises TftpException: If the TFTP transfer fails. """ - try: - filename = self._run_fabric_command( - function_name='fabric_info_get_link_map', - ) - except IpmiError as e: - raise IpmiError(self._parse_ipmierror(e)) + filename = self._run_fabric_command( + function_name='fabric_info_get_link_map', + ) results = {} for line in open(filename): @@ -1086,12 +1049,9 @@ class Node(object): :raises TftpException: If the TFTP transfer fails. """ - try: - filename = self._run_fabric_command( - function_name='fabric_info_get_routing_table', - ) - except IpmiError as e: - raise IpmiError(self._parse_ipmierror(e)) + filename = self._run_fabric_command( + function_name='fabric_info_get_routing_table', + ) results = {} for line in open(filename): @@ -1121,12 +1081,9 @@ class Node(object): :raises TftpException: If the TFTP transfer fails. """ - try: - filename = self._run_fabric_command( - function_name='fabric_info_get_depth_chart', - ) - except IpmiError as e: - raise IpmiError(self._parse_ipmierror(e)) + filename = self._run_fabric_command( + function_name='fabric_info_get_depth_chart', + ) results = {} for line in open(filename): @@ -1205,10 +1162,7 @@ class Node(object): :rtype: float """ - try: - return self.bmc.fabric_get_linkspeed(link=link, actual=actual) - except IpmiError as e: - raise IpmiError(self._parse_ipmierror(e)) + return self.bmc.fabric_get_linkspeed(link=link, actual=actual) def get_uplink(self, iface=0): """Get the uplink a MAC will use when transmitting a packet out of the @@ -1226,10 +1180,7 @@ class Node(object): :raises IpmiError: When any errors are encountered. """ - try: - return self.bmc.fabric_config_get_uplink(iface=iface) - except IpmiError as e: - raise IpmiError(self._parse_ipmierror(e)) + return self.bmc.fabric_config_get_uplink(iface=iface) def set_uplink(self, uplink=0, iface=0): """Set the uplink a MAC will use when transmitting a packet out of the @@ -1248,13 +1199,10 @@ class Node(object): :raises IpmiError: When any errors are encountered. """ - try: - return self.bmc.fabric_config_set_uplink( - uplink=uplink, - iface=iface - ) - except IpmiError as e: - raise IpmiError(self._parse_ipmierror(e)) + return self.bmc.fabric_config_set_uplink( + uplink=uplink, + iface=iface + ) def _run_fabric_command(self, function_name, **kwargs): """Handles the basics of sending a node a command for fabric data.""" @@ -1265,15 +1213,11 @@ class Node(object): self.ecme_tftp.get_file(basename, filename) except (IpmiError, TftpException) as e: - try: - getattr(self.bmc, function_name)( - filename=basename, - tftp_addr=self.tftp_address, - **kwargs - ) - - except IpmiError as e: - raise IpmiError(self._parse_ipmierror(e)) + getattr(self.bmc, function_name)( + filename=basename, + tftp_addr=self.tftp_address, + **kwargs + ) deadline = time.time() + 10 while (time.time() < deadline): @@ -1495,15 +1439,4 @@ class Node(object): "Unable to increment SIMG priority, too high") return priority - def _parse_ipmierror(self, error_details): - """Parse a meaningful message from an IpmiError """ - try: - error = str(error_details).lstrip().splitlines()[0].rstrip() - if (error.startswith('Error: ')): - error = error[7:] - return error - except IndexError: - return 'Unknown IPMItool error.' - - # End of file: ./node.py -- cgit v1.2.1 From ba2c3157af10f43081464035184c94e2ebf65359 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Thu, 20 Jun 2013 17:28:26 -0500 Subject: node.py: Clean up some old, obsolete behavior Don't need to infer anything about the CDB's in-use state anymore. --- cxmanage_api/node.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index da1bc0c..6f45ce4 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -509,17 +509,6 @@ class Node(object): if (entry.version == ""): entry.version = "Unknown" - # Flag CDB as "in use" based on socman info - for a in range(1, len(fwinfo)): - previous = fwinfo[a - 1] - current = fwinfo[a] - if (current.type.split()[1][1:-1] == "CDB" and - current.in_use == "Unknown"): - if (previous.type.split()[1][1:-1] != "SOC_ELF"): - current.in_use = "1" - else: - current.in_use = previous.in_use - return fwinfo def get_firmware_info_dict(self): -- cgit v1.2.1 From fa3e1cb57879989197e8758521f6474ead954d22 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Fri, 21 Jun 2013 16:01:13 -0500 Subject: CXMAN-213: Allow firmware updates with a single UBOOTENV partition If there's only one partition, just treat it like any other image. This means the boot order won't be preserved. --- cxmanage_api/node.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 6f45ce4..50757f7 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -595,6 +595,8 @@ class Node(object): """ fwinfo = self.get_firmware_info() + num_ubootenv_partitions = len([x for x in fwinfo + if "UBOOTENV" in x.type]) # Get the new priority if (priority == None): @@ -603,7 +605,7 @@ class Node(object): updated_partitions = [] for image in package.images: - if (image.type == "UBOOTENV"): + if image.type == "UBOOTENV" and num_ubootenv_partitions >= 2: # Get partitions running_part = self._get_partition(fwinfo, image.type, "FIRST") factory_part = self._get_partition(fwinfo, image.type, @@ -687,10 +689,13 @@ class Node(object): # Reset ubootenv fwinfo = self.get_firmware_info() - running_part = self._get_partition(fwinfo, "UBOOTENV", "FIRST") - factory_part = self._get_partition(fwinfo, "UBOOTENV", "SECOND") - image = self._download_image(factory_part) - self._upload_image(image, running_part) + try: + running_part = self._get_partition(fwinfo, "UBOOTENV", "FIRST") + factory_part = self._get_partition(fwinfo, "UBOOTENV", "SECOND") + image = self._download_image(factory_part) + self._upload_image(image, running_part) + except NoPartitionError: + pass # Only one partition? Don't mess with it! # Clear SEL self.bmc.sel_clear() @@ -1350,6 +1355,8 @@ class Node(object): """Check if this host is ready for an update.""" info = self.get_versions() fwinfo = self.get_firmware_info() + num_ubootenv_partitions = len([x for x in fwinfo + if "UBOOTENV" in x.type]) # Check firmware version if package.version and info.firmware_version: @@ -1394,7 +1401,8 @@ class Node(object): # Check partitions for image in package.images: - if ((image.type == "UBOOTENV") or (partition_arg == "BOTH")): + if (image.type == "UBOOTENV" and num_ubootenv_partitions >= 2 + or partition_arg == "BOTH"): partitions = [self._get_partition(fwinfo, image.type, x) for x in ["FIRST", "SECOND"]] else: -- cgit v1.2.1 From 5b3e94d50784235616ca2383592bea71d50a6239 Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Fri, 21 Jun 2013 16:09:06 -0500 Subject: (CXMAN-194) Create a command to get all data coredump.py Added cxmanage command to get a lot of data and store it in a .tar cxmanage Added the coredump command --- cxmanage/commands/coredump.py | 507 ++++++++++++++++++++++++++++++++++++++++++ scripts/cxmanage | 18 ++ 2 files changed, 525 insertions(+) create mode 100644 cxmanage/commands/coredump.py diff --git a/cxmanage/commands/coredump.py b/cxmanage/commands/coredump.py new file mode 100644 index 0000000..b18b9bc --- /dev/null +++ b/cxmanage/commands/coredump.py @@ -0,0 +1,507 @@ +#!/usr/bin/env python + +# Copyright 2013 Calxeda, Inc. All Rights Reserved. + + +import os +import subprocess + +from subprocess import call + +from cxmanage import get_tftp, get_nodes, run_command + + +def coredump_command(args): + """Get information pertaining to each node. This includes: + IP addresses (ECME and server) + Version info (like cxmanage info) + MAC addresses + Sensor readings + Sensor data records + Firmware info + Boot order + SELs (System Event Logs), + Depth charts + Routing Tables + + All nodes in the fabric should be powered on before running this command. + + 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) + + temp_dir = "coredump" + + try: + os.mkdir(temp_dir) + except: + """ Create a new directory with a unique name. The number + associated with the directory (e.g. the "5" in "coredump5/") + should match the number of the tar file (e.g. "coredump5.tar"). + + """ + temp_dir, _ = get_unused_directory_and_file_names(".", "coredump.tar") + + os.mkdir(temp_dir) + + os.chdir(temp_dir) + + quiet = args.quiet + + write_ip_addresses(args, nodes) + + if not quiet: + print("Getting boot order...") + write_boot_order(args, nodes) + + if not quiet: + print("Getting version information...") + write_version_info(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) + print("Done!") + + if not quiet: + print("Getting sensor data records...") + write_sdr(args, nodes) + print("Done!") + + if not quiet: + print("Getting firmware information...") + write_fwinfo(args, nodes) + + if not quiet: + print("Getting system event logs...") + write_sel(args, nodes) + print("Done!") + + 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 + os.chdir("..") + archive(temp_dir) + + # The original files are already archived. + delete_files(temp_dir) + + return 0 + + +def write_ip_addresses(args, nodes): + """ Write the ECME and server IP addresses for each node + to their respective files. + + """ + ip_discover_results, ip_discover_errors = run_command( + args, + nodes, + 'get_server_ip') + + if (len(ip_discover_errors) > 0 and (not args.quiet)): + print("WARNING: Error discovering server IP addresses on " + "the following nodes:") + for error in ip_discover_errors: + print(node_ip(error)) + + for node in nodes: + write_to_file( + node, + ["Node " + str(node.node_id)], + False + ) + + if node in ip_discover_results: + write_to_file( + node, + ["[ ECME : Server ]", + node_ip(node) + " : " + ip_discover_results[node]] + ) + else: + write_to_file( + node, + ["[ ECME : Server ]", + node_ip(node) + " : (Unknown)"] + ) + + +def write_version_info(args, nodes): + """ Write the version info (like cxmanage info) for each node + to their respective files. + + """ + info_results, info_errors = run_command(args, nodes, "get_versions") + + # This will be used when writing version info to file + 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") + ] + + for node in nodes: + lines = [] # The lines of text to write to file + # \n is used here to give a blank line before this section + lines.append( + "\n[ Version Info for Node " + + str(node.node_id) + " ]" + ) + + 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 + ) + 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_mac_addrs(args, nodes): + """ Write the MAC addresses for each node to their respective files. """ + mac_addr_results, mac_addr_errors = 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 " + str(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) + + +def write_sensor_info(args, nodes): + """ Write sensor information for each node to their respective files. """ + 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 " + str(node.node_id) + " ]") + + try: + sensor_command = "ipmitool sensor -I lanplus -H " + \ + node_ip(node) + " -U admin -P admin list -v" + sensor_call = subprocess.Popen( + sensor_command.split(), + stdout=subprocess.PIPE + ) + sensor_info = sensor_call.communicate()[0] + lines.append(sensor_info) + except Exception as e: + lines.append("Could not get sensor info! " + str(e)) + if not args.quiet: + print("Failed to get sensor information for " + node_ip(node)) + write_to_file(node, lines) + + +def write_sdr(args, nodes): + """ Write the sensor data record for each node to their + respective files. + + """ + 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[ Sensor Data Record for Node " + str( + node.node_id) + " ]") + + try: + sdr_command = "ipmitool sdr -I lanplus -H " + \ + node_ip(node) + " -U admin -P admin info" + sdr_call = subprocess.Popen( + sdr_command.split(), + stdout=subprocess.PIPE + ) + sdr = sdr_call.communicate()[0] + lines.append(sdr) + except Exception as e: + lines.append("Could not get SDR! " + str(e)) + if not args.quiet: + print("Failed to get sensor data record for " + node_ip(node)) + write_to_file(node, lines) + + +def write_fwinfo(args, nodes): + """ Write information about each node's firware partitions + to its respective file. + + """ + results, errors = 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 " + str(node.node_id) + " ]") + + if node in results: + for partition in results[node]: + 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, boot_order_errors = 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 " + str(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. """ + 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 " + str( + node.node_id) + " ]") + + try: + sel_command = "ipmitool sel -I lanplus -H " + node_ip(node) + \ + " -U admin -P admin list" + sel_call = subprocess.Popen( + sel_command.split(), + stdout=subprocess.PIPE + ) + sel = sel_call.communicate()[0] + lines.append(sel) + except Exception as e: + lines.append("Could not get SEL! " + str(e)) + if not args.quiet: + print("Failed to get system event log for " + node_ip(node)) + + write_to_file(node, lines) + + +def write_depth_chart(args, nodes): + """ Write the depth chart for each node to their respective files. """ + depth_results, depth_errors = 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 " + str(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)) + for subkey in subchart: + lines.append(" " + str(subkey) + + " : " + str(subchart[subkey]) + ) + 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, routing_errors = 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 " + str(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, toWrite, add_newlines=True): + """ Append toWrite to an info file for every node in nodes. + + :param node: Node object to write about + :type node: Nobe object + :param toWrite: Text to write to the files + :type toWrite: List of strings + : +param add_newlines: Whether to add newline characters before + every item in toWrite. True by default. True will add newline + characters. + :type add_newlines: bool + + """ + + with open("node" + str(node.node_id) + ".txt", 'a') as file: + for line in toWrite: + if add_newlines: + file.write("\n" + line) + else: + file.write(line) + + +def node_ip(node): + """ Return a string containing the given node's ECME IP address. + :returns: A string containing the node's ECME IP address. + :rtype: string + :param node: The node to get an IP address from. + :type node: Node object + + """ + return str(node)[6:] + + +def delete_files(directory): + """ Remove all files inside directory, and directory itself. """ + command = "rm -r " + directory + call(command.split()) + + +def archive(directory_to_archive): + """ Creates a .tar containing everything in the directory_to_archive. + The .tar is saved to the current directory under the same name as + the directory, but with .tar appended. + + :param directory_to_archive: A path to the directory to be archived. + :type directory_to_archive: string + + """ + + make_tar_command = "tar -cf " + directory_to_archive + ".tar " + \ + directory_to_archive + + call(make_tar_command.split()) + print("Finished! One archive created:\n" + directory_to_archive + ".tar") + + +def get_unused_directory_and_file_names(directory, name): + """ Given a directory and filename, determine an unused directory name + and an unused file name that share the same name before any extensions. + + Returns: A list containing two strings: an unused directory name, and + an unused file name. + + :param directory: A path to the directory to look in. + :type directory: string + :param name: The original name of a file, possibly including an extension + :type name: string + + """ + dir_name, _, _ = name.partition(".") + + dir_number = 0 + file_number = 0 + + while True: + # Try to get an unused dir starting from file_number + new_dir, dir_number = get_unused_name(directory, dir_name, file_number) + + # Try to get an unused name starting from dir_number + new_file, file_number = get_unused_name(directory, name, dir_number) + + # dir_number and file_number always indicate unused names + if dir_number == file_number: + return [new_dir, new_file] + + +def get_unused_name(directory, name, number=0): + """ Get a new filename. This new filename cannot be the same as any + existing file in directory, and should be based off of name. name should + contain one or zero periods, and should not start with a period. + The return type is a list containing the new filename (string), and the + number attached to it (int). + + :param directory: A path to directory + :type directory: string + :param name: The original name of a file, possibly with an extension + :type name: string + :param number: The number to start searching from + :type number: int + + """ + name, dot, extension = name.partition('.') + + # Create a new string, similar to "name0.extension" + destination = name + str(number) + dot + extension + exit = False + + # Increment the number in name#.ext until we find an unused filename + while exit == False: + command = 'ls' + + a_call = subprocess.Popen( + command.split(), + stdout=subprocess.PIPE + ) + call_result = a_call.communicate()[0] + + if destination in call_result: + number = number + 1 + destination = name + str(number) + dot + extension + else: + exit = True + + return [destination, number] diff --git a/scripts/cxmanage b/scripts/cxmanage index 101b30b..5f38fec 100755 --- a/scripts/cxmanage +++ b/scripts/cxmanage @@ -46,6 +46,7 @@ from cxmanage.commands.config import config_reset_command, config_boot_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.coredump import coredump_command PYIPMI_VERSION = '0.7.1' @@ -284,6 +285,23 @@ def build_parser(): parser.add_argument('hostname', help='nodes to operate on (see examples below)') + # Coredump command + coredump = subparsers.add_parser('coredump', help='Get all data from each node') + coredump.add_argument('sensor_name', help='Sensor name to read', + nargs='?', default='') + coredump.add_argument('-A', '--aggressive', action='store_true', + help='discover IPs aggressively') + coredump.add_argument('-U', '--server-user', type=str, default='user1', + metavar='USER', help='Server-side Linux username') + coredump.add_argument('-P', '--server-password', type=str, + default='1Password', metavar='PASSWORD', + help='Server-side Linux password') + coredump.add_argument('-6', '--ipv6', action='store_true', + help='Discover IPv6 addresses') + coredump.add_argument('-I', '--interface', type=str, default=None, + help='Network interface to check') + coredump.set_defaults(func=coredump_command) + return parser -- cgit v1.2.1 From 79db60f312fab7203d5e5b1c3f4fca4c3718744c Mon Sep 17 00:00:00 2001 From: George Kraft Date: Mon, 24 Jun 2013 10:14:38 -0500 Subject: CXMAN-206: Remove NoFirmwareInfoError pyipmi should raise a proper IpmiError now. --- cxmanage_api/cx_exceptions.py | 25 ------------------------- cxmanage_api/node.py | 9 ++------- 2 files changed, 2 insertions(+), 32 deletions(-) diff --git a/cxmanage_api/cx_exceptions.py b/cxmanage_api/cx_exceptions.py index 410b5d7..b74a927 100644 --- a/cxmanage_api/cx_exceptions.py +++ b/cxmanage_api/cx_exceptions.py @@ -109,31 +109,6 @@ class NoSensorError(Exception): return self.msg -class NoFirmwareInfoError(Exception): - """Raised when the firmware info cannot be obtained from a node. - - >>> from cxmanage_api.cx_exceptions import NoFirmwareInfoError - >>> raise NoFirmwareInfoError('My custom exception text!') - Traceback (most recent call last): - File "", line 1, in - cxmanage_api.cx_exceptions.NoFirmwareInfoError: My custom exception text! - - :param msg: Exceptions message and details to return to the user. - :type msg: string - :raised: When the firmware info cannot be obtained from a node. - - """ - - def __init__(self, msg): - """Default constructor for the NoFirmwareInfoError class.""" - super(NoFirmwareInfoError, self).__init__() - self.msg = msg - - def __str__(self): - """String representation of this Exception class.""" - return self.msg - - class SocmanVersionError(Exception): """Raised when there is an error with the users socman version. diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 50757f7..58f0a8a 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -45,9 +45,8 @@ from cxmanage_api.image import Image as IMAGE from cxmanage_api.ubootenv import UbootEnv as UBOOTENV from cxmanage_api.ip_retriever import IPRetriever as IPRETRIEVER from cxmanage_api.cx_exceptions import TimeoutError, NoSensorError, \ - NoFirmwareInfoError, SocmanVersionError, FirmwareConfigError, \ - PriorityIncrementError, NoPartitionError, TransferFailure, \ - ImageSizeError, PartitionInUseError + SocmanVersionError, FirmwareConfigError, PriorityIncrementError, \ + NoPartitionError, TransferFailure, ImageSizeError, PartitionInUseError class Node(object): @@ -495,14 +494,11 @@ class Node(object): :return: Returns a list of FWInfo objects for each :rtype: list - :raises NoFirmwareInfoError: If no fw info exists for any partition. :raises IpmiError: If errors in the command occur with BMC communication. """ fwinfo = [x for x in self.bmc.get_firmware_info() if hasattr(x, "partition")] - if (len(fwinfo) == 0): - raise NoFirmwareInfoError("Failed to retrieve firmware info") # Clean up the fwinfo results for entry in fwinfo: @@ -548,7 +544,6 @@ class Node(object): :return: Returns a list of FWInfo objects for each :rtype: list - :raises NoFirmwareInfoError: If no fw info exists for any partition. :raises IpmiError: If errors in the command occur with BMC communication. """ -- cgit v1.2.1 From 056b2d2f0166ea405dd3047f0aeb604f0c132736 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Mon, 24 Jun 2013 10:25:56 -0500 Subject: CXMAN-206: Remove unnecessary checks around bmc firmware calls We're getting proper IPMI errors now. --- cxmanage_api/node.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 58f0a8a..32f0a07 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -674,13 +674,10 @@ class Node(object): >>> node.config_reset() :raises IpmiError: If errors in the command occur with BMC communication. - :raises Exception: If there are errors within the command response. """ # Reset CDB result = self.bmc.reset_firmware() - if (hasattr(result, "error")): - raise Exception(result.error) # Reset ubootenv fwinfo = self.get_firmware_info() @@ -1292,8 +1289,6 @@ class Node(object): self.tftp.put_file(filename, basename) result = self.bmc.update_firmware(basename, partition_id, image.type, self.tftp_address) - if (not hasattr(result, "tftp_handle_id")): - raise AttributeError("Failed to start firmware upload") self._wait_for_transfer(result.tftp_handle_id) # Verify crc and activate @@ -1318,8 +1313,6 @@ class Node(object): # Fall back and use TFTP server result = self.bmc.retrieve_firmware(basename, partition_id, image_type, self.tftp_address) - if (not hasattr(result, "tftp_handle_id")): - raise AttributeError("Failed to start firmware download") self._wait_for_transfer(result.tftp_handle_id) self.tftp.get_file(basename, filename) @@ -1331,17 +1324,12 @@ class Node(object): """Wait for a firmware transfer to finish.""" deadline = time.time() + 180 result = self.bmc.get_firmware_status(handle) - if (not hasattr(result, "status")): - raise AttributeError('Failed to retrieve firmware transfer status') while (result.status == "In progress"): if (time.time() >= deadline): raise TimeoutError("Transfer timed out after 3 minutes") time.sleep(1) result = self.bmc.get_firmware_status(handle) - if (not hasattr(result, "status")): - raise AttributeError( - "Failed to retrieve firmware transfer status") if (result.status != "Complete"): raise TransferFailure("Node reported TFTP transfer failure") -- cgit v1.2.1 From d2d8c251ef19981b021ae54cb24c25286cd95105 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Mon, 24 Jun 2013 15:18:02 -0500 Subject: CXMAN-205: Test TFTP download before doing firmware updates Should ensure that TFTP communication is working before we start. We can't really test an upload, but uploads and downloads are pretty similar anyway. May want to add some kind of local crc32 check to verify that the downloaded image is actually valid. --- cxmanage_api/image.py | 2 +- cxmanage_api/node.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cxmanage_api/image.py b/cxmanage_api/image.py index 23642c4..87b73a0 100644 --- a/cxmanage_api/image.py +++ b/cxmanage_api/image.py @@ -152,7 +152,7 @@ class Image: :rtype: boolean """ - if (self.type == "SOC_ELF"): + if (self.type == "SOC_ELF" and not self.simg): try: file_process = subprocess.Popen(["file", self.filename], stdout=subprocess.PIPE) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 32f0a07..4908850 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1403,7 +1403,9 @@ class Node(object): raise PartitionInUseError( "Can't upload to a CDB/BOOT_LOG partition that's in use") - return True + # Try a TFTP download. Would try an upload too, but nowhere to put it. + partition = self._get_partition(fwinfo, "SOC_ELF", "FIRST") + self._download_image(partition) def _get_next_priority(self, fwinfo, package): """ Get the next priority """ -- cgit v1.2.1 From 5015e71b3e19a051ecdf65421561c6fe0ff2f28e Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Mon, 24 Jun 2013 15:48:19 -0500 Subject: (CXMAN-194) Create a command to get all data coredump.py/tspackage.py Changed coredump.py to tspackage.py ("troubleshoot package") Removed code to check for server IP Removed requirement to power on nodes Uses python's tempfile module instead of trying to manually find unused temporary filenames No longer calls ipmitool directly, only functions in node.py are used Removed function to get SDR (already had another function to get sensor information) Uses node.ip_address instead of custom function to get ip_addresses of nodes Removed function to delete files. Now uses python's shutil.rmtree() Archives files using python's tarfile module instead of using subprocess.call Archived filenames now contain the date and time they were created. Various minor changes scripts/cxmanage Reflects name change (coredump to tspackage) Removed unneeded arguments for tspackage node.py Added a function to get the SEL cxmanage/__init__.py Moved the components dictionary from cxmanage/commands/info.py to here, so other files can access it info.py Now references the components dictionary from __init__.py --- cxmanage/__init__.py | 16 ++ cxmanage/commands/coredump.py | 507 ----------------------------------------- cxmanage/commands/info.py | 14 +- cxmanage/commands/tspackage.py | 376 ++++++++++++++++++++++++++++++ cxmanage_api/node.py | 3 + scripts/cxmanage | 21 +- 6 files changed, 402 insertions(+), 535 deletions(-) delete mode 100644 cxmanage/commands/coredump.py create mode 100644 cxmanage/commands/tspackage.py diff --git a/cxmanage/__init__.py b/cxmanage/__init__.py index 50b760a..cb9b81e 100644 --- a/cxmanage/__init__.py +++ b/cxmanage/__init__.py @@ -322,3 +322,19 @@ 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() + + +def get_components(): + """ Return the components list needed for ipinfo. """ + 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") + ] + + return components diff --git a/cxmanage/commands/coredump.py b/cxmanage/commands/coredump.py deleted file mode 100644 index b18b9bc..0000000 --- a/cxmanage/commands/coredump.py +++ /dev/null @@ -1,507 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2013 Calxeda, Inc. All Rights Reserved. - - -import os -import subprocess - -from subprocess import call - -from cxmanage import get_tftp, get_nodes, run_command - - -def coredump_command(args): - """Get information pertaining to each node. This includes: - IP addresses (ECME and server) - Version info (like cxmanage info) - MAC addresses - Sensor readings - Sensor data records - Firmware info - Boot order - SELs (System Event Logs), - Depth charts - Routing Tables - - All nodes in the fabric should be powered on before running this command. - - 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) - - temp_dir = "coredump" - - try: - os.mkdir(temp_dir) - except: - """ Create a new directory with a unique name. The number - associated with the directory (e.g. the "5" in "coredump5/") - should match the number of the tar file (e.g. "coredump5.tar"). - - """ - temp_dir, _ = get_unused_directory_and_file_names(".", "coredump.tar") - - os.mkdir(temp_dir) - - os.chdir(temp_dir) - - quiet = args.quiet - - write_ip_addresses(args, nodes) - - if not quiet: - print("Getting boot order...") - write_boot_order(args, nodes) - - if not quiet: - print("Getting version information...") - write_version_info(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) - print("Done!") - - if not quiet: - print("Getting sensor data records...") - write_sdr(args, nodes) - print("Done!") - - if not quiet: - print("Getting firmware information...") - write_fwinfo(args, nodes) - - if not quiet: - print("Getting system event logs...") - write_sel(args, nodes) - print("Done!") - - 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 - os.chdir("..") - archive(temp_dir) - - # The original files are already archived. - delete_files(temp_dir) - - return 0 - - -def write_ip_addresses(args, nodes): - """ Write the ECME and server IP addresses for each node - to their respective files. - - """ - ip_discover_results, ip_discover_errors = run_command( - args, - nodes, - 'get_server_ip') - - if (len(ip_discover_errors) > 0 and (not args.quiet)): - print("WARNING: Error discovering server IP addresses on " - "the following nodes:") - for error in ip_discover_errors: - print(node_ip(error)) - - for node in nodes: - write_to_file( - node, - ["Node " + str(node.node_id)], - False - ) - - if node in ip_discover_results: - write_to_file( - node, - ["[ ECME : Server ]", - node_ip(node) + " : " + ip_discover_results[node]] - ) - else: - write_to_file( - node, - ["[ ECME : Server ]", - node_ip(node) + " : (Unknown)"] - ) - - -def write_version_info(args, nodes): - """ Write the version info (like cxmanage info) for each node - to their respective files. - - """ - info_results, info_errors = run_command(args, nodes, "get_versions") - - # This will be used when writing version info to file - 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") - ] - - for node in nodes: - lines = [] # The lines of text to write to file - # \n is used here to give a blank line before this section - lines.append( - "\n[ Version Info for Node " + - str(node.node_id) + " ]" - ) - - 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 - ) - 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_mac_addrs(args, nodes): - """ Write the MAC addresses for each node to their respective files. """ - mac_addr_results, mac_addr_errors = 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 " + str(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) - - -def write_sensor_info(args, nodes): - """ Write sensor information for each node to their respective files. """ - 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 " + str(node.node_id) + " ]") - - try: - sensor_command = "ipmitool sensor -I lanplus -H " + \ - node_ip(node) + " -U admin -P admin list -v" - sensor_call = subprocess.Popen( - sensor_command.split(), - stdout=subprocess.PIPE - ) - sensor_info = sensor_call.communicate()[0] - lines.append(sensor_info) - except Exception as e: - lines.append("Could not get sensor info! " + str(e)) - if not args.quiet: - print("Failed to get sensor information for " + node_ip(node)) - write_to_file(node, lines) - - -def write_sdr(args, nodes): - """ Write the sensor data record for each node to their - respective files. - - """ - 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[ Sensor Data Record for Node " + str( - node.node_id) + " ]") - - try: - sdr_command = "ipmitool sdr -I lanplus -H " + \ - node_ip(node) + " -U admin -P admin info" - sdr_call = subprocess.Popen( - sdr_command.split(), - stdout=subprocess.PIPE - ) - sdr = sdr_call.communicate()[0] - lines.append(sdr) - except Exception as e: - lines.append("Could not get SDR! " + str(e)) - if not args.quiet: - print("Failed to get sensor data record for " + node_ip(node)) - write_to_file(node, lines) - - -def write_fwinfo(args, nodes): - """ Write information about each node's firware partitions - to its respective file. - - """ - results, errors = 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 " + str(node.node_id) + " ]") - - if node in results: - for partition in results[node]: - 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, boot_order_errors = 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 " + str(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. """ - 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 " + str( - node.node_id) + " ]") - - try: - sel_command = "ipmitool sel -I lanplus -H " + node_ip(node) + \ - " -U admin -P admin list" - sel_call = subprocess.Popen( - sel_command.split(), - stdout=subprocess.PIPE - ) - sel = sel_call.communicate()[0] - lines.append(sel) - except Exception as e: - lines.append("Could not get SEL! " + str(e)) - if not args.quiet: - print("Failed to get system event log for " + node_ip(node)) - - write_to_file(node, lines) - - -def write_depth_chart(args, nodes): - """ Write the depth chart for each node to their respective files. """ - depth_results, depth_errors = 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 " + str(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)) - for subkey in subchart: - lines.append(" " + str(subkey) + - " : " + str(subchart[subkey]) - ) - 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, routing_errors = 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 " + str(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, toWrite, add_newlines=True): - """ Append toWrite to an info file for every node in nodes. - - :param node: Node object to write about - :type node: Nobe object - :param toWrite: Text to write to the files - :type toWrite: List of strings - : -param add_newlines: Whether to add newline characters before - every item in toWrite. True by default. True will add newline - characters. - :type add_newlines: bool - - """ - - with open("node" + str(node.node_id) + ".txt", 'a') as file: - for line in toWrite: - if add_newlines: - file.write("\n" + line) - else: - file.write(line) - - -def node_ip(node): - """ Return a string containing the given node's ECME IP address. - :returns: A string containing the node's ECME IP address. - :rtype: string - :param node: The node to get an IP address from. - :type node: Node object - - """ - return str(node)[6:] - - -def delete_files(directory): - """ Remove all files inside directory, and directory itself. """ - command = "rm -r " + directory - call(command.split()) - - -def archive(directory_to_archive): - """ Creates a .tar containing everything in the directory_to_archive. - The .tar is saved to the current directory under the same name as - the directory, but with .tar appended. - - :param directory_to_archive: A path to the directory to be archived. - :type directory_to_archive: string - - """ - - make_tar_command = "tar -cf " + directory_to_archive + ".tar " + \ - directory_to_archive - - call(make_tar_command.split()) - print("Finished! One archive created:\n" + directory_to_archive + ".tar") - - -def get_unused_directory_and_file_names(directory, name): - """ Given a directory and filename, determine an unused directory name - and an unused file name that share the same name before any extensions. - - Returns: A list containing two strings: an unused directory name, and - an unused file name. - - :param directory: A path to the directory to look in. - :type directory: string - :param name: The original name of a file, possibly including an extension - :type name: string - - """ - dir_name, _, _ = name.partition(".") - - dir_number = 0 - file_number = 0 - - while True: - # Try to get an unused dir starting from file_number - new_dir, dir_number = get_unused_name(directory, dir_name, file_number) - - # Try to get an unused name starting from dir_number - new_file, file_number = get_unused_name(directory, name, dir_number) - - # dir_number and file_number always indicate unused names - if dir_number == file_number: - return [new_dir, new_file] - - -def get_unused_name(directory, name, number=0): - """ Get a new filename. This new filename cannot be the same as any - existing file in directory, and should be based off of name. name should - contain one or zero periods, and should not start with a period. - The return type is a list containing the new filename (string), and the - number attached to it (int). - - :param directory: A path to directory - :type directory: string - :param name: The original name of a file, possibly with an extension - :type name: string - :param number: The number to start searching from - :type number: int - - """ - name, dot, extension = name.partition('.') - - # Create a new string, similar to "name0.extension" - destination = name + str(number) + dot + extension - exit = False - - # Increment the number in name#.ext until we find an unused filename - while exit == False: - command = 'ls' - - a_call = subprocess.Popen( - command.split(), - stdout=subprocess.PIPE - ) - call_result = a_call.communicate()[0] - - if destination in call_result: - number = number + 1 - destination = name + str(number) + dot + extension - else: - exit = True - - return [destination, number] diff --git a/cxmanage/commands/info.py b/cxmanage/commands/info.py index d002906..f552fae 100644 --- a/cxmanage/commands/info.py +++ b/cxmanage/commands/info.py @@ -28,7 +28,8 @@ # 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_components, get_tftp, get_nodes, get_node_strings +from cxmanage import run_command def info_command(args): @@ -41,16 +42,7 @@ def info_command(args): def info_basic_command(args): """Print basic info""" - 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") - ] + components = get_components() tftp = get_tftp(args) nodes = get_nodes(args, tftp) diff --git a/cxmanage/commands/tspackage.py b/cxmanage/commands/tspackage.py new file mode 100644 index 0000000..da18800 --- /dev/null +++ b/cxmanage/commands/tspackage.py @@ -0,0 +1,376 @@ +#!/usr/bin/env python + +# Copyright 2013 Calxeda, Inc. All Rights Reserved. + + +import os +import time +import shutil +import tarfile +import tempfile + +from cxmanage import get_tftp, get_nodes, run_command, get_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 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) + + return 0 + + +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") + + # This will be used when writing version info to file + components = get_components() + + 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 " + str(node.node_id) + " ]", + 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 + ) + 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_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 " + str(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) + + +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 " + str(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: + lines.append( + "%s: %.2f %s" % + (sensor_name, reading, suffix) + ) + + 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 " + str(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 " + str(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 " + str(node.node_id) + " ]" + ) + + try: + if node in results: + for event in results[node]: + lines.append(event) + + 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 " + str(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 " + str(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/node.py b/cxmanage_api/node.py index efd38ba..df3db65 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -314,6 +314,9 @@ class Node(object): else: raise Exception("Reset timed out") + def get_sel(self): + return self.bmc.sel_list() + def get_sensors(self, search=""): """Get a list of sensor objects that match search criteria. diff --git a/scripts/cxmanage b/scripts/cxmanage index 5f38fec..2e0af98 100755 --- a/scripts/cxmanage +++ b/scripts/cxmanage @@ -46,7 +46,7 @@ from cxmanage.commands.config import config_reset_command, config_boot_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.coredump import coredump_command +from cxmanage.commands.tspackage import tspackage_command PYIPMI_VERSION = '0.7.1' @@ -285,22 +285,9 @@ def build_parser(): parser.add_argument('hostname', help='nodes to operate on (see examples below)') - # Coredump command - coredump = subparsers.add_parser('coredump', help='Get all data from each node') - coredump.add_argument('sensor_name', help='Sensor name to read', - nargs='?', default='') - coredump.add_argument('-A', '--aggressive', action='store_true', - help='discover IPs aggressively') - coredump.add_argument('-U', '--server-user', type=str, default='user1', - metavar='USER', help='Server-side Linux username') - coredump.add_argument('-P', '--server-password', type=str, - default='1Password', metavar='PASSWORD', - help='Server-side Linux password') - coredump.add_argument('-6', '--ipv6', action='store_true', - help='Discover IPv6 addresses') - coredump.add_argument('-I', '--interface', type=str, default=None, - help='Network interface to check') - coredump.set_defaults(func=coredump_command) + # tspackage command + tspackage = subparsers.add_parser('tspackage', help='Get all data from each node') + tspackage.set_defaults(func=tspackage_command) return parser -- cgit v1.2.1 From 4976ba35176baf974a4ccf2cf143b9fd797e21f1 Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Tue, 25 Jun 2013 10:01:15 -0500 Subject: (CXMAN-194) Create a command to get all data tspackage.py Formatted comments removed unneeded return statement Changed string formatting node.py Added comments to get_sel() __init__.py Changed get_components() to a global variable COMPONENTS, and updated info.py and tspackage.py accordingly --- cxmanage/__init__.py | 25 +++++++--------- cxmanage/commands/info.py | 6 ++-- cxmanage/commands/tspackage.py | 67 +++++++++++++++++++----------------------- cxmanage_api/node.py | 5 ++++ 4 files changed, 49 insertions(+), 54 deletions(-) diff --git a/cxmanage/__init__.py b/cxmanage/__init__.py index cb9b81e..e2d416a 100644 --- a/cxmanage/__init__.py +++ b/cxmanage/__init__.py @@ -324,17 +324,14 @@ def _print_command_status(tasks, counter): sys.stdout.flush() -def get_components(): - """ Return the components list needed for ipinfo. """ - 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") - ] - - return components +# 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 f552fae..b1a03c0 100644 --- a/cxmanage/commands/info.py +++ b/cxmanage/commands/info.py @@ -28,8 +28,8 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. -from cxmanage import get_components, get_tftp, get_nodes, get_node_strings -from cxmanage import run_command +from cxmanage import get_tftp, get_nodes, get_node_strings, run_command +from cxmanage import COMPONENTS def info_command(args): @@ -42,7 +42,7 @@ def info_command(args): def info_basic_command(args): """Print basic info""" - components = get_components() + components = COMPONENTS tftp = get_tftp(args) nodes = get_nodes(args, tftp) diff --git a/cxmanage/commands/tspackage.py b/cxmanage/commands/tspackage.py index da18800..1a37e6e 100644 --- a/cxmanage/commands/tspackage.py +++ b/cxmanage/commands/tspackage.py @@ -9,11 +9,13 @@ import shutil import tarfile import tempfile -from cxmanage import get_tftp, get_nodes, run_command, get_components +from cxmanage import get_tftp, get_nodes, run_command +from cxmanage import COMPONENTS def tspackage_command(args): - """ Get information pertaining to each node. This includes: + """Get information pertaining to each node. + This includes: Version info (like cxmanage info) MAC addresses Sensor readings @@ -32,7 +34,6 @@ def tspackage_command(args): ~/virtual_testenv/workspace/cx_manage_util/scripts/cxmanage """ - tftp = get_tftp(args) nodes = get_nodes(args, tftp) @@ -84,18 +85,16 @@ def tspackage_command(args): # The original files are already archived, so we can delete them. shutil.rmtree(temp_dir) - return 0 - def write_version_info(args, nodes): - """ Write the version info (like cxmanage info) for each node + """Write the version info (like cxmanage info) for each node to their respective files. """ info_results, _ = run_command(args, nodes, "get_versions") # This will be used when writing version info to file - components = get_components() + components = COMPONENTS for node in nodes: lines = [] # The lines of text to write to file @@ -103,8 +102,8 @@ def write_version_info(args, nodes): # Since this is the first line of the file, we don't need a \n write_to_file( node, - "[ Version Info for Node " + str(node.node_id) + " ]", - False + "[ Version Info for Node %d ]" % node.node_id, + add_newlines=False ) lines.append("ECME IP Address : %s" % node.ip_address) @@ -130,7 +129,7 @@ def write_version_info(args, nodes): def write_mac_addrs(args, nodes): - """ Write the MAC addresses for each node to their respective files. """ + """Write the MAC addresses for each node to their respective files.""" mac_addr_results, _ = run_command( args, nodes, @@ -140,7 +139,7 @@ def write_mac_addrs(args, nodes): 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 " + str(node.node_id) + " ]") + 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]: @@ -151,12 +150,12 @@ def write_mac_addrs(args, nodes): ) else: lines.append("\nWARNING: No MAC addresses found!") + write_to_file(node, lines) def write_sensor_info(args, nodes): - """ Write sensor information for each node to their respective files. """ - + """Write sensor information for each node to their respective files.""" args.sensor_name = "" results, _ = run_command(args, nodes, "get_sensors", @@ -166,7 +165,7 @@ def write_sensor_info(args, nodes): 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 " + str(node.node_id) + " ]") + lines.append("\n[ Sensors for Node %d ]" % node.node_id) if node in results: for sensor_name, sensor in results[node].iteritems(): @@ -187,16 +186,15 @@ def write_sensor_info(args, nodes): for sensor_name, readings in sensors.iteritems(): for reading_node, reading, suffix in readings: if reading_node.ip_address == node.ip_address: - lines.append( - "%s: %.2f %s" % - (sensor_name, reading, suffix) - ) + 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 + """Write information about each node's firware partitions to its respective file. """ @@ -205,7 +203,7 @@ def write_fwinfo(args, nodes): 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 " + str(node.node_id) + " ]") + 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 @@ -230,13 +228,13 @@ def write_fwinfo(args, nodes): def write_boot_order(args, nodes): - """ Write the boot order of each node to their respective files. """ + """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 " + str(node.node_id) + " ]") + lines.append("\n[ Boot Order for Node %d ]" % node.node_id) if node in results: lines.append(", ".join(results[node])) @@ -247,15 +245,13 @@ def write_boot_order(args, nodes): def write_sel(args, nodes): - """ Write the SEL for each node to their respective files. """ + """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 " + str(node.node_id) + " ]" - ) + lines.append("\n[ System Event Log for Node %d ]" % node.node_id) try: if node in results: @@ -271,13 +267,13 @@ def write_sel(args, nodes): def write_depth_chart(args, nodes): - """ Write the depth chart for each node to their respective files. """ + """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 " + str(node.node_id) + " ]") + lines.append("\n[ Depth Chart for Node %d ]" % node.node_id) if node in depth_results: depth_chart = depth_results[node] @@ -297,7 +293,7 @@ def write_depth_chart(args, nodes): for entry in subchart[subkey]: lines.append( " " + str(subkey) + - " : " + str(entry) + " : " + str(entry) ) else: @@ -307,14 +303,13 @@ def write_depth_chart(args, nodes): 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") + """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 " + str(node.node_id) + " ]") + lines.append("\n[ Routing Table for Node %d ]" % node.node_id) if node in routing_results: table = routing_results[node] @@ -327,7 +322,7 @@ def write_routing_table(args, nodes): def write_to_file(node, to_write, add_newlines=True): - """ Append to_write to an info file for every node in nodes. + """Append to_write to an info file for every node in nodes. :param node: Node object to write about :type node: Node object @@ -339,7 +334,6 @@ def write_to_file(node, to_write, add_newlines=True): :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 @@ -350,7 +344,7 @@ def write_to_file(node, to_write, add_newlines=True): def archive(directory_to_archive, destination): - """ Creates a .tar containing everything in the directory_to_archive. + """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. @@ -361,7 +355,6 @@ def archive(directory_to_archive, destination): :type destination: string """ - os.chdir(os.path.dirname(directory_to_archive)) tar_name = os.path.basename(directory_to_archive) + ".tar" diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index df3db65..1db7625 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -315,6 +315,11 @@ class Node(object): raise Exception("Reset timed out") def get_sel(self): + """Get the system event log for this node. + + :returns: The node's system event log + :rtype: string + """ return self.bmc.sel_list() def get_sensors(self, search=""): -- cgit v1.2.1 From 7c8290b9231b31e30540068652992d5a1c5f89b3 Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Fri, 28 Jun 2013 14:44:13 -0500 Subject: (CXMAN-203) Log status during firmware update node.py Save a log of events during a firmware update. scripts/cxmanage.py Added arguments to fwupdate: --no_log (to NOT save logs of a firmware update) By default, logs are saved --log_directory (place to save firmware update logs) Default directory set to "./fwupdate_logs" fw.py Changed the run_command in fwupdate_command to include the new arguments --- cxmanage/commands/fw.py | 3 +- cxmanage_api/node.py | 218 +++++++++++++++++++++++++++++++++++++++++++++++- scripts/cxmanage | 7 ++ 3 files changed, 225 insertions(+), 3 deletions(-) diff --git a/cxmanage/commands/fw.py b/cxmanage/commands/fw.py index 87f810b..65dba15 100644 --- a/cxmanage/commands/fw.py +++ b/cxmanage/commands/fw.py @@ -55,7 +55,8 @@ def fwupdate_command(args): print "Updating firmware..." results, errors = run_command(args, nodes, "update_firmware", package, - args.partition, args.priority) + args.partition, args.priority, args.no_log, + args.log_directory) if errors: print "ERROR: Firmware update failed." return True diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 9e0e266..569a52f 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -31,8 +31,10 @@ import os import re -import subprocess import time +import shutil +import tempfile +import subprocess from pkg_resources import parse_version from pyipmi import make_bmc, IpmiError @@ -579,7 +581,8 @@ class Node(object): return False def update_firmware(self, package, partition_arg="INACTIVE", - priority=None): + priority=None, no_log=False, + log_directory=""): """ Update firmware on this target. >>> from cxmanage_api.firmware_package import FirmwarePackage @@ -592,6 +595,8 @@ class Node(object): :type package: `FirmwarePackage `_ :param partition_arg: Partition to upgrade to. :type partition_arg: string + :param save_log: True to save a log of what happens, False otherwise + :type save_log: bool :raises PriorityIncrementError: If the SIMG Header priority cannot be changed. @@ -601,30 +606,156 @@ class Node(object): num_ubootenv_partitions = len([x for x in fwinfo if "UBOOTENV" in x.type]) + save_log = not no_log + if save_log == True: + # Make a temporary file to hold the log + temp_dir = tempfile.mkdtemp() + _, filename = tempfile.mkstemp(dir=temp_dir) + logfile = open(filename, "a") + + title = "Firmware Update Log for Node %d" % self.node_id + self._append_to_file( + filename, + title, + add_newline=False + ) + + # The additional \n's here are intentional. + start_info = time.strftime("%m/%d/%Y %H:%M:%S") + start_info = start_info + \ + ("\nECME IP address: " + self.ip_address) + + version_info = self.get_versions() + start_info = start_info + "\nOld firmware version: " + \ + version_info.firmware_version + + if package.version: + start_info = start_info + \ + "\nNew firmware version: " + package.version + else: + start_info = start_info + \ + "\nNew firmware version name unavailable." + + start_info = start_info + \ + ("\n\n[ Pre-Update Firmware Info for Node %d ]" % + self.node_id) + + self._append_to_file( + filename, + start_info + ) + + results = self.get_firmware_info() + + first_partition = True # \n's are intentional + for partition in results: + if first_partition: + self._append_to_file( + filename, + "\nPartition : %s" % partition.partition, + add_newline=True + ) + first_partition = False + else: + self._append_to_file( + filename, + "\nPartition : %s" % partition.partition) + info_string = "Type : %s" % partition.type + \ + "\nOffset : %s" % partition.offset + \ + "\nSize : %s" % partition.size + \ + "\nPriority : %s" % partition.priority + \ + "\nDaddr : %s" % partition.daddr + \ + "\nFlags : %s" % partition.flags + \ + "\nVersion : %s" % partition.version + \ + "\nIn Use : %s" % partition.in_use + self._append_to_file( + filename, + info_string + ) + # Get the new priority if (priority == None): priority = self._get_next_priority(fwinfo, package) + if save_log == True: + self._append_to_file( + filename, + "\nPriority: " + str(priority) + ) + + + if save_log == True: + images_to_upload = len(package.images) + self._append_to_file( + filename, + "package.images: Images to upload: %d" % images_to_upload + ) + updated_partitions = [] + image_uploading = 1 for image in package.images: + if save_log == True: + # Extra \n here is intentional. + self._append_to_file( + filename, + "\nUploading image %d of %d" % + (image_uploading, images_to_upload) + ) + if image.type == "UBOOTENV" and num_ubootenv_partitions >= 2: + if save_log == True: + self._append_to_file( + filename, + "Trying ubootenv for image %d..." % image_uploading + ) + # Get partitions running_part = self._get_partition(fwinfo, image.type, "FIRST") factory_part = self._get_partition(fwinfo, image.type, "SECOND") + if save_log == True: + # Extra \n's here for ease of reading output + self._append_to_file( + filename, + "\nFirst ('FIRST') partition:\n" + \ + str(running_part) + \ + "\n\nSecond ('FACTORY') partition:\n" + \ + str(factory_part) + ) + # Update factory ubootenv self._upload_image(image, factory_part, priority) + + if save_log == True: + # Extra \n for output formatting + self._append_to_file( + filename, + "\nDone uploading factory image" + ) # Update running ubootenv old_ubootenv_image = self._download_image(running_part) old_ubootenv = self.ubootenv(open( old_ubootenv_image.filename).read()) + + if save_log == True: + self._append_to_file( + filename, + "Done getting old ubootenv image" + ) + try: ubootenv = self.ubootenv(open(image.filename).read()) ubootenv.set_boot_order(old_ubootenv.get_boot_order()) + if save_log == True: + self._append_to_file( + filename, + "Set boot order to " + old_ubootenv.get_boot_order() + ) + filename = temp_file() with open(filename, "w") as f: f.write(ubootenv.get_contents()) @@ -633,11 +764,24 @@ class Node(object): image.version) self._upload_image(ubootenv_image, running_part, priority) + + if save_log == True: + self._append_to_file( + filename, + "Done uploading ubootenv image to first " + \ + "partition ('running partition')" + ) except (ValueError, Exception): self._upload_image(image, running_part, priority) updated_partitions += [running_part, factory_part] else: + if save_log == True: + self._append_to_file( + filename, + "Using Non-ubootenv for image %d..." % + image_uploading + ) # Get the partitions if (partition_arg == "BOTH"): partitions = [self._get_partition(fwinfo, image.type, @@ -652,10 +796,25 @@ class Node(object): self._upload_image(image, partition, priority) updated_partitions += partitions + + if save_log == True: + self._append_to_file( + filename, + "Done uploading image %d of %d" % + (image_uploading, images_to_upload) + ) + image_uploading = image_uploading + 1 if package.version: self.bmc.set_firmware_version(package.version) + if save_log == True: + # For readability + self._append_to_file( + filename, + "" + ) + # Post verify fwinfo = self.get_firmware_info() for old_partition in updated_partitions: @@ -663,18 +822,59 @@ class Node(object): new_partition = fwinfo[partition_id] if new_partition.type != old_partition.type: + if save_log == True: + self._append_to_file( + filename, + "Update failed (partition %i, type changed)" + % partition_id + ) raise Exception("Update failed (partition %i, type changed)" % partition_id) if int(new_partition.priority, 16) != priority: + if save_log == True: + self._append_to_file( + filename, + "Update failed (partition %i, wrong priority)" + % partition_id + ) raise Exception("Update failed (partition %i, wrong priority)" % partition_id) if int(new_partition.flags, 16) & 2 != 0: + if save_log == True: + self._append_to_file( + filename, + "Update failed (partition %i, not activated)" + % partition_id + ) raise Exception("Update failed (partition %i, not activated)" % partition_id) self.bmc.check_firmware(partition_id) + if save_log == True: + self._append_to_file( + filename, + "Check complete for partition %d" % partition_id + ) + + if save_log == True: + self._append_to_file( + filename, + "\nDone updating firmware." # Extra \n intentional + ) + logfile.close() + + if not os.path.isdir(log_directory): + os.mkdir(log_directory) + + new_filename = "node%d_fwupdate.txt" % self.node_id + new_path = os.path.join(log_directory, new_filename) + shutil.copyfile(filename, new_path) + print("\nLog saved to " + new_path) + + os.remove(filename) + def config_reset(self): """Resets configuration to factory defaults. @@ -1198,6 +1398,20 @@ class Node(object): iface=iface ) + + def _append_to_file(self, filename, string_to_append, add_newline=True): + """Appends string_to_append to filename. + If add_newline is true, a \n will be added to the beginning of + string_to_append. + + """ + with open(filename, "a") as open_file: + if add_newline == True: + open_file.write("\n" + string_to_append) + else: + open_file.write(string_to_append) + + def _run_fabric_command(self, function_name, **kwargs): """Handles the basics of sending a node a command for fabric data.""" filename = temp_file() diff --git a/scripts/cxmanage b/scripts/cxmanage index 2e0af98..bbb6584 100755 --- a/scripts/cxmanage +++ b/scripts/cxmanage @@ -196,6 +196,13 @@ def build_parser(): 'NEWEST', 'INACTIVE' ])) + fwupdate.add_argument('--no_log', + help='Do NOT save a record of the firmware update to file(s).', + action='store_true') + fwupdate.add_argument('--log_directory', + help='Save firmware update logs to the given directory', + default='fwupdate_logs') + simg_args = fwupdate.add_mutually_exclusive_group() simg_args.add_argument('--force-simg', help='Force addition of SIMG header', -- cgit v1.2.1 From 029f54785fb39f7b74326902f0ad72edfc438e2a Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Tue, 2 Jul 2013 10:18:03 -0500 Subject: (CXMAN-203) Firmware Update Logs filelogger.py Simple class to log messages to files node.py Always log messages Use filelogger.py to write messages to file Removed optional arguments for updating firmware (no longer needed) fw.py Saves firmware update logs to directories in /.cxmanage Removed unneeded arguments cxmanage Removed unneeded optional arguments for fwupdate command --- cxmanage/commands/fw.py | 19 ++- cxmanage_api/filelogger.py | 45 ++++++++ cxmanage_api/node.py | 280 +++++++++++++++++---------------------------- scripts/cxmanage | 6 - 4 files changed, 166 insertions(+), 184 deletions(-) create mode 100644 cxmanage_api/filelogger.py diff --git a/cxmanage/commands/fw.py b/cxmanage/commands/fw.py index 65dba15..1495c2a 100644 --- a/cxmanage/commands/fw.py +++ b/cxmanage/commands/fw.py @@ -28,6 +28,11 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. + +import os +import sys +import time + from pkg_resources import parse_version from cxmanage import get_tftp, get_nodes, get_node_strings, run_command, \ @@ -54,9 +59,19 @@ def fwupdate_command(args): if not args.quiet: print "Updating firmware..." + # Create a directory for the firmware update logs + timestamp = time.strftime("%Y%m%d%H%M%S") + dir_name = "fwupdate_fabric_%s_" % nodes[0].ip_address + dir_name = dir_name + timestamp + dir_path = os.path.expanduser(os.path.join("~/", ".cxmanage", dir_name)) + + if not os.path.exists(dir_path): + os.makedirs(dir_path) + + args.dir_path = dir_path + results, errors = run_command(args, nodes, "update_firmware", package, - args.partition, args.priority, args.no_log, - args.log_directory) + args.dir_path, args.partition, args.priority) if errors: print "ERROR: Firmware update failed." return True diff --git a/cxmanage_api/filelogger.py b/cxmanage_api/filelogger.py new file mode 100644 index 0000000..b258862 --- /dev/null +++ b/cxmanage_api/filelogger.py @@ -0,0 +1,45 @@ +#!/usr/bin/env/python + +# Copyright 2013 Calxeda, Inc. All Rights Reserved. + + +import os + + +class FileLogger: + """Simple class that logs messages to a file.""" + + def __init__(self, filename=None): + if filename: + self.file = open(filename, "w") + else: + self.file = None + + def log(self, message, add_newlines=True): + """Write message to the log file. If message is a list of strings, + newline characters will be added between items unless add_newlines + is set to False. + + :param message: Text to write to the files + :type message: string or list of strings + :param add_newlines: Whether to add newline characters before + every item in message if message is a list of strings. + :type add_newlines: bool + + """ + if add_newlines: + if type(message) == str: + self.file.write("\n" + message) + else: + # join() doesn't add a newline before the first item + message[0] = "\n" + message[0] + self.file.write("\n".join(message)) + else: + if type(message) == str: + self.file.write(message) + else: + self.file.write("".join(message)) + + # Make sure we actually write to disk + self.file.flush() + os.fsync(self.file.fileno()) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 569a52f..71696f6 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -31,8 +31,10 @@ import os import re +import sys import time import shutil +import logging import tempfile import subprocess @@ -41,6 +43,7 @@ from pyipmi import make_bmc, IpmiError from pyipmi.bmc import LanBMC as BMC from tftpy.TftpShared import TftpException +from cxmanage_api.filelogger import FileLogger from cxmanage_api import temp_file from cxmanage_api.tftp import InternalTftp, ExternalTftp from cxmanage_api.image import Image as IMAGE @@ -580,9 +583,8 @@ class Node(object): NoPartitionError, ImageSizeError, PartitionInUseError): return False - def update_firmware(self, package, partition_arg="INACTIVE", - priority=None, no_log=False, - log_directory=""): + def update_firmware(self, package, save_to, partition_arg="INACTIVE", + priority=None): """ Update firmware on this target. >>> from cxmanage_api.firmware_package import FirmwarePackage @@ -593,10 +595,10 @@ class Node(object): :param package: Firmware package to deploy. :type package: `FirmwarePackage `_ + :param save_to: Path to the directory logs should be saved to + :type save_to: string :param partition_arg: Partition to upgrade to. :type partition_arg: string - :param save_log: True to save a log of what happens, False otherwise - :type save_log: bool :raises PriorityIncrementError: If the SIMG Header priority cannot be changed. @@ -606,155 +608,112 @@ class Node(object): num_ubootenv_partitions = len([x for x in fwinfo if "UBOOTENV" in x.type]) - save_log = not no_log - if save_log == True: - # Make a temporary file to hold the log - temp_dir = tempfile.mkdtemp() - _, filename = tempfile.mkstemp(dir=temp_dir) - logfile = open(filename, "a") - - title = "Firmware Update Log for Node %d" % self.node_id - self._append_to_file( - filename, - title, - add_newline=False - ) + + new_filename = "node%d_fwupdate.log" % self.node_id + new_filepath = os.path.join(save_to, new_filename) + + logger = FileLogger(new_filepath) + + logger.log( + "Firmware Update Log for Node %d" % self.node_id, + add_newlines=False + ) + logger.log(time.strftime("%m/%d/%Y %H:%M:%S")) + logger.log("ECME IP address: " + self.ip_address) - # The additional \n's here are intentional. - start_info = time.strftime("%m/%d/%Y %H:%M:%S") - start_info = start_info + \ - ("\nECME IP address: " + self.ip_address) + version_info = self.get_versions() + logger.log( + "\nOld firmware version: " + \ + version_info.firmware_version) - version_info = self.get_versions() - start_info = start_info + "\nOld firmware version: " + \ - version_info.firmware_version + if package.version: + logger.log("New firmware version: " + package.version) + else: + logger.log("New firmware version name unavailable.") - if package.version: - start_info = start_info + \ - "\nNew firmware version: " + package.version - else: - start_info = start_info + \ - "\nNew firmware version name unavailable." - - start_info = start_info + \ - ("\n\n[ Pre-Update Firmware Info for Node %d ]" % - self.node_id) + logger.log( + "\n[ Pre-Update Firmware Info for Node %d ]" % + self.node_id + ) - self._append_to_file( - filename, - start_info - ) + results = self.get_firmware_info() - results = self.get_firmware_info() - - first_partition = True # \n's are intentional - for partition in results: - if first_partition: - self._append_to_file( - filename, - "\nPartition : %s" % partition.partition, - add_newline=True - ) - first_partition = False - else: - self._append_to_file( - filename, - "\nPartition : %s" % partition.partition) - info_string = "Type : %s" % partition.type + \ - "\nOffset : %s" % partition.offset + \ - "\nSize : %s" % partition.size + \ - "\nPriority : %s" % partition.priority + \ - "\nDaddr : %s" % partition.daddr + \ - "\nFlags : %s" % partition.flags + \ - "\nVersion : %s" % partition.version + \ - "\nIn Use : %s" % partition.in_use - self._append_to_file( - filename, - info_string - ) + for partition in results: + logger.log("\nPartition : %s" % partition.partition) + info_string = "Type : %s" % partition.type + \ + "\nOffset : %s" % partition.offset + \ + "\nSize : %s" % partition.size + \ + "\nPriority : %s" % partition.priority + \ + "\nDaddr : %s" % partition.daddr + \ + "\nFlags : %s" % partition.flags + \ + "\nVersion : %s" % partition.version + \ + "\nIn Use : %s" % partition.in_use + logger.log(info_string) # Get the new priority if (priority == None): priority = self._get_next_priority(fwinfo, package) - if save_log == True: - self._append_to_file( - filename, - "\nPriority: " + str(priority) + logger.log( + "\nPriority: " + str(priority) ) - - if save_log == True: - images_to_upload = len(package.images) - self._append_to_file( - filename, - "package.images: Images to upload: %d" % images_to_upload + images_to_upload = len(package.images) + logger.log( + "package.images: Images to upload: %d" % images_to_upload ) updated_partitions = [] image_uploading = 1 for image in package.images: - if save_log == True: - # Extra \n here is intentional. - self._append_to_file( - filename, - "\nUploading image %d of %d" % - (image_uploading, images_to_upload) + logger.log( + "\nUploading image %d of %d" % + (image_uploading, images_to_upload) ) if image.type == "UBOOTENV" and num_ubootenv_partitions >= 2: - if save_log == True: - self._append_to_file( - filename, - "Trying ubootenv for image %d..." % image_uploading - ) + logger.log( + "Trying ubootenv for image %d..." % image_uploading + ) # Get partitions running_part = self._get_partition(fwinfo, image.type, "FIRST") factory_part = self._get_partition(fwinfo, image.type, "SECOND") - if save_log == True: - # Extra \n's here for ease of reading output - self._append_to_file( - filename, - "\nFirst ('FIRST') partition:\n" + \ - str(running_part) + \ - "\n\nSecond ('FACTORY') partition:\n" + \ - str(factory_part) - ) + # Extra \n's here for ease of reading output + logger.log( + "\nFirst ('FIRST') partition:\n" + \ + str(running_part) + \ + "\n\nSecond ('FACTORY') partition:\n" + \ + str(factory_part) + ) # Update factory ubootenv self._upload_image(image, factory_part, priority) - if save_log == True: - # Extra \n for output formatting - self._append_to_file( - filename, - "\nDone uploading factory image" - ) + # Extra \n for output formatting + logger.log( + "\nDone uploading factory image" + ) # Update running ubootenv old_ubootenv_image = self._download_image(running_part) old_ubootenv = self.ubootenv(open( old_ubootenv_image.filename).read()) - if save_log == True: - self._append_to_file( - filename, - "Done getting old ubootenv image" - ) + logger.log( + "Done getting old ubootenv image" + ) try: ubootenv = self.ubootenv(open(image.filename).read()) ubootenv.set_boot_order(old_ubootenv.get_boot_order()) - if save_log == True: - self._append_to_file( - filename, - "Set boot order to " + old_ubootenv.get_boot_order() - ) + logger.log( + "Set boot order to " + old_ubootenv.get_boot_order() + ) filename = temp_file() with open(filename, "w") as f: @@ -765,23 +724,19 @@ class Node(object): self._upload_image(ubootenv_image, running_part, priority) - if save_log == True: - self._append_to_file( - filename, - "Done uploading ubootenv image to first " + \ - "partition ('running partition')" - ) + logger.log( + "Done uploading ubootenv image to first " + \ + "partition ('running partition')" + ) except (ValueError, Exception): self._upload_image(image, running_part, priority) updated_partitions += [running_part, factory_part] else: - if save_log == True: - self._append_to_file( - filename, - "Using Non-ubootenv for image %d..." % - image_uploading - ) + logger.log( + "Using Non-ubootenv for image %d..." % + image_uploading + ) # Get the partitions if (partition_arg == "BOTH"): partitions = [self._get_partition(fwinfo, image.type, @@ -797,23 +752,16 @@ class Node(object): updated_partitions += partitions - if save_log == True: - self._append_to_file( - filename, - "Done uploading image %d of %d" % - (image_uploading, images_to_upload) - ) - image_uploading = image_uploading + 1 + logger.log( + "Done uploading image %d of %d" % + (image_uploading, images_to_upload) + ) + image_uploading = image_uploading + 1 if package.version: self.bmc.set_firmware_version(package.version) - if save_log == True: - # For readability - self._append_to_file( - filename, - "" - ) + logger.log("") # For readability # Post verify fwinfo = self.get_firmware_info() @@ -822,59 +770,39 @@ class Node(object): new_partition = fwinfo[partition_id] if new_partition.type != old_partition.type: - if save_log == True: - self._append_to_file( - filename, - "Update failed (partition %i, type changed)" - % partition_id - ) + logger.log( + "Update failed (partition %i, type changed)" + % partition_id + ) raise Exception("Update failed (partition %i, type changed)" % partition_id) if int(new_partition.priority, 16) != priority: - if save_log == True: - self._append_to_file( - filename, - "Update failed (partition %i, wrong priority)" - % partition_id - ) + logger.log( + "Update failed (partition %i, wrong priority)" + % partition_id + ) raise Exception("Update failed (partition %i, wrong priority)" % partition_id) if int(new_partition.flags, 16) & 2 != 0: - if save_log == True: - self._append_to_file( - filename, - "Update failed (partition %i, not activated)" - % partition_id - ) + logger.log( + "Update failed (partition %i, not activated)" + % partition_id + ) raise Exception("Update failed (partition %i, not activated)" % partition_id) self.bmc.check_firmware(partition_id) - if save_log == True: - self._append_to_file( - filename, - "Check complete for partition %d" % partition_id - ) - - if save_log == True: - self._append_to_file( - filename, - "\nDone updating firmware." # Extra \n intentional + logger.log( + "Check complete for partition %d" % partition_id ) - logfile.close() - - if not os.path.isdir(log_directory): - os.mkdir(log_directory) - - new_filename = "node%d_fwupdate.txt" % self.node_id - new_path = os.path.join(log_directory, new_filename) - shutil.copyfile(filename, new_path) - print("\nLog saved to " + new_path) - - os.remove(filename) + logger.log( + "\nDone updating firmware." + ) + + print("\nLog saved to " + new_filepath) def config_reset(self): """Resets configuration to factory defaults. diff --git a/scripts/cxmanage b/scripts/cxmanage index bbb6584..3e9036f 100755 --- a/scripts/cxmanage +++ b/scripts/cxmanage @@ -196,12 +196,6 @@ def build_parser(): 'NEWEST', 'INACTIVE' ])) - fwupdate.add_argument('--no_log', - help='Do NOT save a record of the firmware update to file(s).', - action='store_true') - fwupdate.add_argument('--log_directory', - help='Save firmware update logs to the given directory', - default='fwupdate_logs') simg_args = fwupdate.add_mutually_exclusive_group() simg_args.add_argument('--force-simg', -- cgit v1.2.1 From efa1e0d95070194eae6ce7e1979d4f8805cc0620 Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Tue, 2 Jul 2013 11:46:07 -0500 Subject: (CXMAN-203) Firmware Update Logs node.py Uses loggers.FileLogger instead of FileLogger.py Removed unneeded function _append_to_file() Removed unneeded imports Removed filelogger.py Added loggers.py (copied from cx_automation) --- cxmanage_api/filelogger.py | 45 ----- cxmanage_api/loggers.py | 397 +++++++++++++++++++++++++++++++++++++++++++++ cxmanage_api/node.py | 74 ++++----- 3 files changed, 426 insertions(+), 90 deletions(-) delete mode 100644 cxmanage_api/filelogger.py create mode 100644 cxmanage_api/loggers.py diff --git a/cxmanage_api/filelogger.py b/cxmanage_api/filelogger.py deleted file mode 100644 index b258862..0000000 --- a/cxmanage_api/filelogger.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env/python - -# Copyright 2013 Calxeda, Inc. All Rights Reserved. - - -import os - - -class FileLogger: - """Simple class that logs messages to a file.""" - - def __init__(self, filename=None): - if filename: - self.file = open(filename, "w") - else: - self.file = None - - def log(self, message, add_newlines=True): - """Write message to the log file. If message is a list of strings, - newline characters will be added between items unless add_newlines - is set to False. - - :param message: Text to write to the files - :type message: string or list of strings - :param add_newlines: Whether to add newline characters before - every item in message if message is a list of strings. - :type add_newlines: bool - - """ - if add_newlines: - if type(message) == str: - self.file.write("\n" + message) - else: - # join() doesn't add a newline before the first item - message[0] = "\n" + message[0] - self.file.write("\n".join(message)) - else: - if type(message) == str: - self.file.write(message) - else: - self.file.write("".join(message)) - - # Make sure we actually write to disk - self.file.flush() - os.fsync(self.file.fileno()) diff --git a/cxmanage_api/loggers.py b/cxmanage_api/loggers.py new file mode 100644 index 0000000..da7c202 --- /dev/null +++ b/cxmanage_api/loggers.py @@ -0,0 +1,397 @@ +# 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. + + +"""Loggers is a set of Logging classes used to capture output. + +The most commonly used loggers are StandardOutLogger and FileLogger. +Additionally, these loggers can be combined to write output to more than one +target. + +""" + + +import os +import datetime +import traceback + + +# +# Log Level Definitions +# +LL_DEBUG = 4 +LL_INFO = 3 +LL_WARN = 2 +LL_ERROR = 1 +LL_NONE = 0 +DEFAULT_LL = LL_INFO + + +class Logger(object): + """Base class for all loggers. + + To create a custom logger, inherit from this class, and implement + the write() method so that it writes message in the appropriate manner. + + >>> # To use this class for inheritance ... + >>> from cx_automation.loggers import Logger + >>> + + :param log_level: Verbosity level of logging for this logger. + :type log_level: integer + :param time_stamp: Flag to determine toggle time_stamping each log entry. + :type time_stamp: boolean + :param component: Component tag for the log entry. + :type component: string + + .. note:: + * This class is not intended to be used as a logger itself. + * Only the **write()** method needs to be implemeneted for your custom + logger. + * Log Levels: DEBUG=4, INFO=3, WARN=2, ERROR=1, NONE=0 + * You can turn OFF entry time_stamping by initializing a logger with: + **time_stamp=False** + + """ + + def __init__(self, log_level=DEFAULT_LL, time_stamp=True, component=None): + """Default constructor for the Logger class.""" + self.log_level = log_level + self.time_stamp = time_stamp + + if (component): + self.component = '| ' + component + else: + self.component = '' + + def _get_log(self, msg, level_tag): + """Used internally to create an appropriate log message string. + + :param msg: The message to write. + :type msg: string + :param level_tag: The log level string, e.g. INFO, DEBUG, WARN, etc. + :type level_tag: string + + """ + lines = msg.split('\n') + result = [] + for line in lines: + if (self.time_stamp): + ts_now = str(datetime.datetime.now()) + result.append( + '%s %s | %s : %s' % + (ts_now, self.component, level_tag, line) + ) + else: + result.append( + '%s %s : %s' % + (self.component, level_tag, line) + ) + + return '\n'.join(result) + + def write(self, message): + """Writes a log message. + + .. warning:: + * This method is to be intentionally overridden. + * Implemented by subclasses. + + :param message: The message to write.. + :type message: string + + :raises NotImplementedError: If write() is not overridden. + + """ + del message # For function signature only! + raise NotImplementedError + + def debug(self, message): + """Log a message at DEBUG level. LL_DEBUG = 4 + + >>> logger.debug('This is debug.') + 2012-12-19 11:13:04.329046 | DEBUG | This is debug. + + :param message: The message to write. + :type message: string + + """ + if (self.log_level >= LL_DEBUG): + self.write(self._get_log(message, "DEBUG")) + + def info(self, message): + """Log a message at the INFO level. LL_INFO = 3 + + >>> logger.info('This is informational.') + 2012-12-19 11:11:47.225859 | INFO | This is informational. + + :param message: The message to write. + :type message: string + + """ + if (self.log_level >= LL_INFO): + self.write(self._get_log(msg=message, level_tag="INFO")) + + def warn(self, message): + """Log a message at WARN level. LL_WARN = 2 + + >>> logger.warn('This is a warning') + 2012-12-19 11:11:12.257814 | WARN | This is a warning + + :param message: The message to write. + :type message: string + + """ + if (self.log_level >= LL_WARN): + self.write(self._get_log(msg=message, level_tag="WARN")) + + def error(self, message): + """Log a message at ERROR level. LL_ERROR = 1 + + >>> logger.error('This is an error.') + 2012-12-19 11:14:11.352735 | ERROR | This is an error. + + :param message: The message to write. + :type message: string + + """ + if (self.log_level >= LL_ERROR): + self.write(self._get_log(msg=message, level_tag="ERROR")) + + +class StandardOutLogger(Logger): + """A Logger class that writes to Standard Out (stdout). + + Only the write method has to be implemented. + + >>> # Typical instantiation ... + >>> from cx_automation.loggers import StandardOutLogger + >>> logger = StandardOutLogger() + + + :param log_level: Level of logging for this logger. + :type log_level: integer + :param time_stamp: Flag to determine toggle time_stamping each log entry. + :type time_stamp: boolean + + """ + + def __init__(self, log_level=DEFAULT_LL, time_stamp=True, component=None): + """Default constructor for a StandardOutLogger.""" + self.log_level = log_level + self.time_stamp = time_stamp + self.component = component + super(StandardOutLogger, self).__init__( + log_level=self.log_level, + time_stamp=self.time_stamp, + component=self.component + ) + + def write(self, message): + """Writes a log message to standard out. + + >>> # It simply prints ... + >>> logger.write('This function is called by the Base Class') + This function is called by the Base Class + >>> + + :param message: The message to write. + :type message: string + + """ + print message + + +class FileLogger(Logger): + """A logger that writes to a file. + + >>> # Typical instantiation ... + >>> flogger = FileLogger(abs_path='/home/logfile.out') + + :param log_level: Level of logging for this logger. + :type log_level: integer + :param time_stamp: Flag to determine toggle time_stamping each log entry. + :type time_stamp: boolean + :param name: Name of this logger. + :type name: string + + """ + + def __init__(self, abs_path, time_stamp=True, component=None, + log_level=DEFAULT_LL): + """Default constructor for the FileLogger class.""" + super(FileLogger, self).__init__( + log_level=log_level, + time_stamp=time_stamp, + component=component + ) + self.path = abs_path + try: + if not (os.path.exists(self.path)): + file(self.path, 'w').close() + + except Exception: + raise + + def write(self, message): + """Writes a log message to a log file. + + :param message: The message to write. + :type message: string + + """ + try: + old_umask = os.umask(0000) + with open(self.path, 'a') as file_d: + file_d.write(message + "\n") + file_d.close() + + except Exception: + self.error(traceback.format_exc()) + raise + + finally: + os.umask(old_umask) + if (file_d): + file_d.close() + + +class CompositeLogger(object): + """Takes a list of loggers and writes the same output to them all. + + >>> from cx_automation.loggers import StandardOutLogger, FileLogger + >>> # Let's say you want to log to a file while also seeing the output. + >>> # Create a StandardOutLogger to 'see' output. + >>> slogger = StandarOutLogger(...) + >>> # Create a FileLogger to log to a file. + >>> flogger = FileLogger(...) + >>> from cx_automation.loggers import CompositeLogger + >>> # Create a composite logger and you can log to both simultaneously! + >>> logger = CompositeLogger(loggers=[slogger, flogger]) + + :param loggers: A list of loggers to output to + :type loggers: list + :param log_level: The level to log at. DEFAULT: LL_INFO + :type log_level: integer + + """ + + def __init__(self, loggers, log_level=DEFAULT_LL): + """Default constructor for the CompositeLogger class.""" + self.loggers = loggers + self._log_level = log_level + # + # Set the log level to the same for all loggers ... + # + for logger in self.loggers: + logger.log_level = log_level + + @property + def log_level(self): + """Returns the log_level for ALL loggers. + + >>> logger.log_level + >>> 3 + + :returns: The log_level for ALL loggers. + :rtype: integer + + """ + return self._log_level + + @log_level.setter + def log_level(self, value): + """Sets the log_level for ALL loggers. + + :param value: The value to set the log_level to. + :type value: integer + + """ + self._log_level = value + if (not self._log_level): + return + + for logger in self.loggers: + logger.log_level = value + + def info(self, message): + """Loga a message at the INFO level: LL_INFO = 3 for all loggers. + + >>> logger.info('This is informational.') + 2012-12-19 11:37:17.462879 | INFO | This is informational. + + :param message: The message to write. + :type message: string + + """ + for logger in self.loggers: + logger.info(message) + + def warn(self, message): + """Log a message at WARN level: LL_WARN = 2 for all loggers. + + >>> logger.warn('This is a warning.') + 2012-12-19 11:37:50.614862 | WARN | This is a warning. + + :param message: The message to write. + :type message: string + + """ + for logger in self.loggers: + logger.warn(message) + + def error(self, message): + """Log a message at ERROR level. LL_ERROR = 1 for all loggers. + + >>> logger.error('This is an ERROR!') + 2012-12-19 11:41:18.181123 | ERROR | This is an ERROR! + + :param message: The message to write. + :type message: string + + """ + for logger in self.loggers: + logger.error(message) + + def debug(self, message): + """ + Log a message at DEBUG level. LL_DEBUG = 4 for all loggers. + + >>> logger.debug('This is a DEBUG log entry. Message goes here') + + :param message: The message to write. + :type message: string + + """ + for logger in self.loggers: + logger.debug(message) + + +# End of File: cx_automation/utilites/loggers.py diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 71696f6..d4d997d 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -31,10 +31,8 @@ import os import re -import sys import time import shutil -import logging import tempfile import subprocess @@ -43,7 +41,7 @@ from pyipmi import make_bmc, IpmiError from pyipmi.bmc import LanBMC as BMC from tftpy.TftpShared import TftpException -from cxmanage_api.filelogger import FileLogger +from cxmanage_api import loggers from cxmanage_api import temp_file from cxmanage_api.tftp import InternalTftp, ExternalTftp from cxmanage_api.image import Image as IMAGE @@ -612,26 +610,25 @@ class Node(object): new_filename = "node%d_fwupdate.log" % self.node_id new_filepath = os.path.join(save_to, new_filename) - logger = FileLogger(new_filepath) + logger = loggers.FileLogger(new_filepath) - logger.log( - "Firmware Update Log for Node %d" % self.node_id, - add_newlines=False + logger.info( + "Firmware Update Log for Node %d" % self.node_id ) - logger.log(time.strftime("%m/%d/%Y %H:%M:%S")) - logger.log("ECME IP address: " + self.ip_address) + logger.info(time.strftime("%m/%d/%Y %H:%M:%S")) + logger.info("ECME IP address: " + self.ip_address) version_info = self.get_versions() - logger.log( + logger.info( "\nOld firmware version: " + \ version_info.firmware_version) if package.version: - logger.log("New firmware version: " + package.version) + logger.info("New firmware version: " + package.version) else: - logger.log("New firmware version name unavailable.") + logger.info("New firmware version name unavailable.") - logger.log( + logger.info( "\n[ Pre-Update Firmware Info for Node %d ]" % self.node_id ) @@ -639,7 +636,7 @@ class Node(object): results = self.get_firmware_info() for partition in results: - logger.log("\nPartition : %s" % partition.partition) + logger.info("\nPartition : %s" % partition.partition) info_string = "Type : %s" % partition.type + \ "\nOffset : %s" % partition.offset + \ "\nSize : %s" % partition.size + \ @@ -648,18 +645,18 @@ class Node(object): "\nFlags : %s" % partition.flags + \ "\nVersion : %s" % partition.version + \ "\nIn Use : %s" % partition.in_use - logger.log(info_string) + logger.info(info_string) # Get the new priority if (priority == None): priority = self._get_next_priority(fwinfo, package) - logger.log( + logger.info( "\nPriority: " + str(priority) ) images_to_upload = len(package.images) - logger.log( + logger.info( "package.images: Images to upload: %d" % images_to_upload ) @@ -667,13 +664,13 @@ class Node(object): image_uploading = 1 for image in package.images: - logger.log( + logger.info( "\nUploading image %d of %d" % (image_uploading, images_to_upload) ) if image.type == "UBOOTENV" and num_ubootenv_partitions >= 2: - logger.log( + logger.info( "Trying ubootenv for image %d..." % image_uploading ) @@ -683,7 +680,7 @@ class Node(object): "SECOND") # Extra \n's here for ease of reading output - logger.log( + logger.info( "\nFirst ('FIRST') partition:\n" + \ str(running_part) + \ "\n\nSecond ('FACTORY') partition:\n" + \ @@ -694,7 +691,7 @@ class Node(object): self._upload_image(image, factory_part, priority) # Extra \n for output formatting - logger.log( + logger.info( "\nDone uploading factory image" ) @@ -703,7 +700,7 @@ class Node(object): old_ubootenv = self.ubootenv(open( old_ubootenv_image.filename).read()) - logger.log( + logger.info( "Done getting old ubootenv image" ) @@ -711,7 +708,7 @@ class Node(object): ubootenv = self.ubootenv(open(image.filename).read()) ubootenv.set_boot_order(old_ubootenv.get_boot_order()) - logger.log( + logger.info( "Set boot order to " + old_ubootenv.get_boot_order() ) @@ -724,7 +721,7 @@ class Node(object): self._upload_image(ubootenv_image, running_part, priority) - logger.log( + logger.info( "Done uploading ubootenv image to first " + \ "partition ('running partition')" ) @@ -733,7 +730,7 @@ class Node(object): updated_partitions += [running_part, factory_part] else: - logger.log( + logger.info( "Using Non-ubootenv for image %d..." % image_uploading ) @@ -752,7 +749,7 @@ class Node(object): updated_partitions += partitions - logger.log( + logger.info( "Done uploading image %d of %d" % (image_uploading, images_to_upload) ) @@ -761,7 +758,7 @@ class Node(object): if package.version: self.bmc.set_firmware_version(package.version) - logger.log("") # For readability + logger.info("") # For readability # Post verify fwinfo = self.get_firmware_info() @@ -770,7 +767,7 @@ class Node(object): new_partition = fwinfo[partition_id] if new_partition.type != old_partition.type: - logger.log( + logger.error( "Update failed (partition %i, type changed)" % partition_id ) @@ -778,7 +775,7 @@ class Node(object): % partition_id) if int(new_partition.priority, 16) != priority: - logger.log( + logger.error( "Update failed (partition %i, wrong priority)" % partition_id ) @@ -786,7 +783,7 @@ class Node(object): % partition_id) if int(new_partition.flags, 16) & 2 != 0: - logger.log( + logger.error( "Update failed (partition %i, not activated)" % partition_id ) @@ -794,11 +791,11 @@ class Node(object): % partition_id) self.bmc.check_firmware(partition_id) - logger.log( + logger.info( "Check complete for partition %d" % partition_id ) - logger.log( + logger.info( "\nDone updating firmware." ) @@ -1327,19 +1324,6 @@ class Node(object): ) - def _append_to_file(self, filename, string_to_append, add_newline=True): - """Appends string_to_append to filename. - If add_newline is true, a \n will be added to the beginning of - string_to_append. - - """ - with open(filename, "a") as open_file: - if add_newline == True: - open_file.write("\n" + string_to_append) - else: - open_file.write(string_to_append) - - 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 8e2094df262777677e413d51562e2a5710f35d06 Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Tue, 2 Jul 2013 13:31:27 -0500 Subject: (CXMAN-203) Firmware Update Logs node.py Logs are now saved to: ~/.cxmanage/logs//-fwupdate.log Removed arguments to update_firmware() (formerly needed for the old way of saving logs) Removed a logger.info() stating the time (every message states the time) fw.py Removed unneeded arguments to the call to update_firmware() --- cxmanage/commands/fw.py | 17 +---------------- cxmanage_api/node.py | 16 +++++++++------- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/cxmanage/commands/fw.py b/cxmanage/commands/fw.py index 1495c2a..99ed4fe 100644 --- a/cxmanage/commands/fw.py +++ b/cxmanage/commands/fw.py @@ -29,10 +29,6 @@ # DAMAGE. -import os -import sys -import time - from pkg_resources import parse_version from cxmanage import get_tftp, get_nodes, get_node_strings, run_command, \ @@ -59,19 +55,8 @@ def fwupdate_command(args): if not args.quiet: print "Updating firmware..." - # Create a directory for the firmware update logs - timestamp = time.strftime("%Y%m%d%H%M%S") - dir_name = "fwupdate_fabric_%s_" % nodes[0].ip_address - dir_name = dir_name + timestamp - dir_path = os.path.expanduser(os.path.join("~/", ".cxmanage", dir_name)) - - if not os.path.exists(dir_path): - os.makedirs(dir_path) - - args.dir_path = dir_path - results, errors = run_command(args, nodes, "update_firmware", package, - args.dir_path, args.partition, args.priority) + args.partition, args.priority) if errors: print "ERROR: Firmware update failed." return True diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index d4d997d..d951c86 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -581,7 +581,7 @@ class Node(object): NoPartitionError, ImageSizeError, PartitionInUseError): return False - def update_firmware(self, package, save_to, partition_arg="INACTIVE", + def update_firmware(self, package, partition_arg="INACTIVE", priority=None): """ Update firmware on this target. @@ -593,8 +593,6 @@ class Node(object): :param package: Firmware package to deploy. :type package: `FirmwarePackage `_ - :param save_to: Path to the directory logs should be saved to - :type save_to: string :param partition_arg: Partition to upgrade to. :type partition_arg: string @@ -606,16 +604,20 @@ class Node(object): num_ubootenv_partitions = len([x for x in fwinfo if "UBOOTENV" in x.type]) - - new_filename = "node%d_fwupdate.log" % self.node_id - new_filepath = os.path.join(save_to, new_filename) + new_directory = "~/.cxmanage/logs/%s" % self.ip_address + new_directory = os.path.expanduser(new_directory) + if not os.path.exists(new_directory): + os.makedirs(new_directory) + + timestamp = time.strftime("%Y%m%d%H%M%S") + new_filename = "%s-fwupdate.log" % timestamp + new_filepath = os.path.join(new_directory, new_filename) logger = loggers.FileLogger(new_filepath) logger.info( "Firmware Update Log for Node %d" % self.node_id ) - logger.info(time.strftime("%m/%d/%Y %H:%M:%S")) logger.info("ECME IP address: " + self.ip_address) version_info = self.get_versions() -- cgit v1.2.1 From e72757ff8c9132c826c27f802500e46d5b656845 Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Tue, 2 Jul 2013 15:57:41 -0500 Subject: (CXMAN-203) Firmware Update Logs node.py Cleaned up code --- cxmanage_api/node.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index d951c86..783698c 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -600,9 +600,6 @@ class Node(object): changed. """ - fwinfo = self.get_firmware_info() - num_ubootenv_partitions = len([x for x in fwinfo - if "UBOOTENV" in x.type]) new_directory = "~/.cxmanage/logs/%s" % self.ip_address new_directory = os.path.expanduser(new_directory) @@ -628,16 +625,18 @@ class Node(object): if package.version: logger.info("New firmware version: " + package.version) else: - logger.info("New firmware version name unavailable.") - + logger.warn("New firmware version name unavailable.") + logger.info( "\n[ Pre-Update Firmware Info for Node %d ]" % self.node_id ) - results = self.get_firmware_info() + fwinfo = self.get_firmware_info() + num_ubootenv_partitions = len([x for x in fwinfo + if "UBOOTENV" in x.type]) - for partition in results: + for partition in fwinfo: logger.info("\nPartition : %s" % partition.partition) info_string = "Type : %s" % partition.type + \ "\nOffset : %s" % partition.offset + \ -- cgit v1.2.1 From 926c2f9e0ff4cdba8ee61508b55914cd84d5bbf3 Mon Sep 17 00:00:00 2001 From: Sheldon Sandbekkhaug Date: Tue, 2 Jul 2013 16:45:22 -0500 Subject: node_test.py Give each node a node_id. --- cxmanage_test/node_test.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cxmanage_test/node_test.py b/cxmanage_test/node_test.py index d5d9445..f5fc188 100644 --- a/cxmanage_test/node_test.py +++ b/cxmanage_test/node_test.py @@ -61,6 +61,12 @@ class NodeTest(unittest.TestCase): ipretriever=DummyIPRetriever, verbose=True) for ip in ADDRESSES] + # Give each node a node_id + count = 0 + for node in self.nodes: + node.node_id = count + count = count + 1 + # Set up an internal server self.work_dir = tempfile.mkdtemp(prefix="cxmanage_node_test-") -- cgit v1.2.1 From 352e6bd77cf9fd51477ccee33257a13adf64da68 Mon Sep 17 00:00:00 2001 From: evasquez Date: Thu, 11 Jul 2013 10:13:16 -0500 Subject: Added loggers to the API index for cxmanage_api. --- cxmanage_api/docs/source/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/cxmanage_api/docs/source/index.rst b/cxmanage_api/docs/source/index.rst index 1630ae6..3d91f4e 100644 --- a/cxmanage_api/docs/source/index.rst +++ b/cxmanage_api/docs/source/index.rst @@ -172,6 +172,7 @@ API Docs & Code Examples SIMG U-Boot Environment IP Retriever + Loggers ``Code Examples`` -- cgit v1.2.1 From 17e501385a4550815a84b8601d202882e7776504 Mon Sep 17 00:00:00 2001 From: evasquez Date: Thu, 11 Jul 2013 10:17:14 -0500 Subject: Fixed example usage that had fabric instead of node. Oh yea .. Eclipse was kind enough to clean up some whitespace ... --- cxmanage_api/node.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 783698c..b148972 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -621,14 +621,14 @@ class Node(object): logger.info( "\nOld firmware version: " + \ version_info.firmware_version) - + if package.version: logger.info("New firmware version: " + package.version) else: logger.warn("New firmware version name unavailable.") - + logger.info( - "\n[ Pre-Update Firmware Info for Node %d ]" % + "\n[ Pre-Update Firmware Info for Node %d ]" % self.node_id ) @@ -658,7 +658,7 @@ class Node(object): images_to_upload = len(package.images) logger.info( - "package.images: Images to upload: %d" % images_to_upload + "package.images: Images to upload: %d" % images_to_upload ) updated_partitions = [] @@ -666,10 +666,10 @@ class Node(object): image_uploading = 1 for image in package.images: logger.info( - "\nUploading image %d of %d" % + "\nUploading image %d of %d" % (image_uploading, images_to_upload) ) - + if image.type == "UBOOTENV" and num_ubootenv_partitions >= 2: logger.info( "Trying ubootenv for image %d..." % image_uploading @@ -687,10 +687,10 @@ class Node(object): "\n\nSecond ('FACTORY') partition:\n" + \ str(factory_part) ) - + # Update factory ubootenv self._upload_image(image, factory_part, priority) - + # Extra \n for output formatting logger.info( "\nDone uploading factory image" @@ -700,8 +700,8 @@ class Node(object): old_ubootenv_image = self._download_image(running_part) old_ubootenv = self.ubootenv(open( old_ubootenv_image.filename).read()) - - logger.info( + + logger.info( "Done getting old ubootenv image" ) @@ -749,17 +749,17 @@ class Node(object): self._upload_image(image, partition, priority) updated_partitions += partitions - + logger.info( "Done uploading image %d of %d" % (image_uploading, images_to_upload) ) - image_uploading = image_uploading + 1 + image_uploading = image_uploading + 1 if package.version: self.bmc.set_firmware_version(package.version) - logger.info("") # For readability + logger.info("") # For readability # Post verify fwinfo = self.get_firmware_info() @@ -812,7 +812,7 @@ class Node(object): """ # Reset CDB result = self.bmc.reset_firmware() - + # Reset ubootenv fwinfo = self.get_firmware_info() try: @@ -821,7 +821,7 @@ class Node(object): image = self._download_image(factory_part) self._upload_image(image, running_part) except NoPartitionError: - pass # Only one partition? Don't mess with it! + pass # Only one partition? Don't mess with it! # Clear SEL self.bmc.sel_clear() @@ -1270,7 +1270,7 @@ class Node(object): or if sent to a primary node, the linkspeed setting for the Profile 0 of the currently active Configuration. - >>> fabric.get_linkspeed() + >>> node.get_linkspeed() 2.5 :param link: The fabric link number to read the linkspeed for. @@ -1278,7 +1278,7 @@ class Node(object): :param actual: WhetherThe fabric link number to read the linkspeed for. :type actual: boolean - :return: Linkspeed for the fabric.. + :return: Linkspeed for the fabric. :rtype: float """ @@ -1288,7 +1288,7 @@ class Node(object): """Get the uplink a MAC will use when transmitting a packet out of the cluster. - >>> fabric.get_uplink(iface=1) + >>> node.get_uplink(iface=1) 0 :param iface: The interface for the uplink. @@ -1309,7 +1309,7 @@ class Node(object): >>> # >>> # Set eth0 to uplink 1 ... >>> # - >>> fabric.set_uplink(uplink=1,iface=0) + >>> node.set_uplink(uplink=1,iface=0) :param uplink: The uplink to set. :type uplink: integer -- cgit v1.2.1 From 7ce5df7d9cdbc2265505a68a3e671a5856ab7921 Mon Sep 17 00:00:00 2001 From: evasquez Date: Thu, 11 Jul 2013 10:20:15 -0500 Subject: Added doc example for get_link_stats() --- cxmanage_api/node.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index b148972..2e19be8 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1097,6 +1097,27 @@ class Node(object): def get_link_stats(self, link=0): """Gets the linkstats for the link specified. + >>> node.get_link_stats() + {'FS_LC0_BYTE_CNT_0': '0x0', + 'FS_LC0_BYTE_CNT_1': '0x0', + 'FS_LC0_CFG_0': '0x1000d07f', + 'FS_LC0_CFG_1': '0x105f', + 'FS_LC0_CM_RXDATA_0': '0x0', + 'FS_LC0_CM_RXDATA_1': '0x0', + 'FS_LC0_CM_TXDATA_0': '0x82000002', + 'FS_LC0_CM_TXDATA_1': '0x0', + 'FS_LC0_PKT_CNT_0': '0x0', + 'FS_LC0_PKT_CNT_1': '0x0', + 'FS_LC0_RDRPSCNT': '0x3e791', + 'FS_LC0_RERRSCNT': '0x0', + 'FS_LC0_RMCSCNT': '0x173b923', + 'FS_LC0_RPKTSCNT': '0x0', + 'FS_LC0_RUCSCNT': '0x43cab', + 'FS_LC0_SC_STAT': '0x0', + 'FS_LC0_STATE': '0x1033', + 'FS_LC0_TDRPSCNT': '0x0', + 'FS_LC0_TPKTSCNT': '0x1'} + :param link: The link to get stats for (0-4). :type link: integer -- cgit v1.2.1 From d3813d5e5cd1d14b540385901f25b0bcf8d935ba Mon Sep 17 00:00:00 2001 From: evasquez Date: Thu, 11 Jul 2013 10:21:48 -0500 Subject: Added doc example for get_linkmap() --- cxmanage_api/node.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 2e19be8..9405d2e 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1155,6 +1155,9 @@ class Node(object): def get_linkmap(self): """Gets the src and destination of each link on a node. + >>> node.get_linkmap() + {1: 2, 3: 1, 4: 3} + :return: Returns a map of link_id->node_id. :rtype: dictionary -- cgit v1.2.1 From 16883f61b53b9465456b173189497e293f83aa1b Mon Sep 17 00:00:00 2001 From: evasquez Date: Thu, 11 Jul 2013 10:23:18 -0500 Subject: Added doc example usage for get_routing_table. --- cxmanage_api/node.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 9405d2e..4870607 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1186,6 +1186,9 @@ class Node(object): def get_routing_table(self): """Gets the routing table as instantiated in the fabric switch. + >>> node.get_routing_table() + {1: [0, 0, 0, 3, 0], 2: [0, 3, 0, 0, 2], 3: [0, 2, 0, 0, 3]} + :return: Returns a map of node_id->rt_entries. :rtype: dictionary -- cgit v1.2.1 From 685c10537a91cf2c700bdd5ca19bb533bd1411d6 Mon Sep 17 00:00:00 2001 From: evasquez Date: Thu, 11 Jul 2013 10:25:11 -0500 Subject: Added doc example usage for get_depth_chart() --- cxmanage_api/node.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 4870607..794cec9 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1220,6 +1220,11 @@ class Node(object): """Gets a table indicating the distance from a given node to all other nodes on each fabric link. + >>> node.get_depth_chart() + {1: {'shortest': (0, 0)}, + 2: {'others': [(3, 1)], 'shortest': (0, 0)}, + 3: {'others': [(2, 1)], 'shortest': (0, 0)}} + :return: Returns a map of target->(neighbor, hops), [other (neighbors,hops)] :rtype: dictionary -- cgit v1.2.1 From 9c29f4c5f41feb912c5f9d9776244d37c9b26be3 Mon Sep 17 00:00:00 2001 From: evasquez Date: Thu, 11 Jul 2013 10:26:38 -0500 Subject: Added doc example usage for get_fabric_macaddrs(). --- cxmanage_api/node.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 794cec9..f1113ed 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1024,6 +1024,20 @@ class Node(object): def get_fabric_macaddrs(self): """Gets what macaddr information THIS node knows about the Fabric. + >>> node.get_fabric_macaddrs() + {0: {0: ['fc:2f:40:ab:cd:cc'], + 1: ['fc:2f:40:ab:cd:cd'], + 2: ['fc:2f:40:ab:cd:ce']}, + 1: {0: ['fc:2f:40:3e:66:e0'], + 1: ['fc:2f:40:3e:66:e1'], + 2: ['fc:2f:40:3e:66:e2']}, + 2: {0: ['fc:2f:40:fd:37:34'], + 1: ['fc:2f:40:fd:37:35'], + 2: ['fc:2f:40:fd:37:36']}, + 3: {0: ['fc:2f:40:0e:4a:74'], + 1: ['fc:2f:40:0e:4a:75'], + 2: ['fc:2f:40:0e:4a:76']}} + :return: Returns a map of node_ids->ports->mac_addresses. :rtype: dictionary -- cgit v1.2.1 From 93ccaef5848e53740710bf05f20865f3d587dbab Mon Sep 17 00:00:00 2001 From: evasquez Date: Thu, 11 Jul 2013 10:29:49 -0500 Subject: Added doc example usage for get_sel(). --- cxmanage_api/node.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index f1113ed..3f79398 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -302,6 +302,18 @@ class Node(object): def get_sel(self): """Get the system event log for this node. + >>> 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', + '3 | 06/27/2013 | 21:01:13 | System Event #0xf4 |', + ... + ] + >>> # + >>> # Output trimmed for brevity + >>> # + :returns: The node's system event log :rtype: string """ -- cgit v1.2.1 From cc654adb409a9565c96ee4586d1e0e4996e9c1ac Mon Sep 17 00:00:00 2001 From: evasquez Date: Thu, 11 Jul 2013 10:38:55 -0500 Subject: Added doc examples for get_link_stats() and get_depth_chart(). --- cxmanage_api/fabric.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/cxmanage_api/fabric.py b/cxmanage_api/fabric.py index 6159c47..cad5bc6 100644 --- a/cxmanage_api/fabric.py +++ b/cxmanage_api/fabric.py @@ -829,6 +829,33 @@ class Fabric(object): def get_link_stats(self, link=0, async=False): """Get the link_stats for each node in the fabric. + >>> fabric.get_link_stats() + {0: {'FS_LC0_BYTE_CNT_0': '0x0', + 'FS_LC0_BYTE_CNT_1': '0x0', + 'FS_LC0_CFG_0': '0x1000d07f', + 'FS_LC0_CFG_1': '0x105f', + 'FS_LC0_CM_RXDATA_0': '0x0', + 'FS_LC0_CM_RXDATA_1': '0x0', + 'FS_LC0_CM_TXDATA_0': '0x82000002', + 'FS_LC0_CM_TXDATA_1': '0x0', + 'FS_LC0_PKT_CNT_0': '0x0', + 'FS_LC0_PKT_CNT_1': '0x0', + 'FS_LC0_RDRPSCNT': '0x3e89f', + 'FS_LC0_RERRSCNT': '0x0', + 'FS_LC0_RMCSCNT': '0x174b9bf', + 'FS_LC0_RPKTSCNT': '0x0', + 'FS_LC0_RUCSCNT': '0x43e9b', + 'FS_LC0_SC_STAT': '0x0', + 'FS_LC0_STATE': '0x1033', + 'FS_LC0_TDRPSCNT': '0x0', + 'FS_LC0_TPKTSCNT': '0x1'}, + }} + >>> # + >>> # Output trimmed for brevity ... + >>> # The data shown for node 0 is the same type of data presented for each + >>> # node in the fabric. + >>> # + :param link: The link to get stats for (0-4). :type link: integer @@ -871,6 +898,20 @@ class Fabric(object): def get_depth_chart(self, async=False): """Get the depth_chart for the fabric. + >>> fabric.get_depth_chart() + {0: {1: {'shortest': (0, 0)}, + 2: {'others': [(3, 1)], 'shortest': (0, 0)}, + 3: {'others': [(2, 1)], 'shortest': (0, 0)}}, + 1: {0: {'shortest': (1, 0)}, + 2: {'others': [(3, 2)], 'shortest': (0, 1)}, + 3: {'others': [(2, 2)], 'shortest': (0, 1)}}, + 2: {0: {'others': [(3, 1)], 'shortest': (2, 0)}, + 1: {'shortest': (0, 1)}, + 3: {'others': [(0, 1)], 'shortest': (2, 0)}}, + 3: {0: {'others': [(2, 1)], 'shortest': (3, 0)}, + 1: {'shortest': (0, 1)}, + 2: {'others': [(0, 1)], 'shortest': (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 -- cgit v1.2.1 From f8d6b408331489e6b81ff275f409cfe063e9e333 Mon Sep 17 00:00:00 2001 From: evasquez Date: Thu, 11 Jul 2013 10:43:38 -0500 Subject: Added examples for get_linkmap() and get_routing_table(). Fixed set_uplink() example to be more verbose. --- cxmanage_api/fabric.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cxmanage_api/fabric.py b/cxmanage_api/fabric.py index cad5bc6..fd71fdc 100644 --- a/cxmanage_api/fabric.py +++ b/cxmanage_api/fabric.py @@ -815,7 +815,7 @@ class Fabric(object): def set_uplink(self, uplink=0, iface=0): """Set the uplink for an interface to xmit a packet out of the cluster. - >>> fabric.set_uplink(0,0) + >>> fabric.set_uplink(uplink=0,iface=0) :param uplink: The uplink to set. :type uplink: integer @@ -872,6 +872,9 @@ class Fabric(object): def get_linkmap(self, async=False): """Get the linkmap for each node in the fabric. + >>> fabric.get_linkmap() + {0: {1: 2, 3: 1, 4: 3}, 1: {3: 0}, 2: {3: 0, 4: 3}, 3: {3: 0, 4: 2}} + :param async: Flag that determines if the command result (dictionary) is returned or a Task object (can get status, etc.). :type async: boolean @@ -885,6 +888,12 @@ class Fabric(object): def get_routing_table(self, async=False): """Get the routing_table for the fabric. + >>> fabric.get_routing_table() + {0: {1: [0, 0, 0, 3, 0], 2: [0, 3, 0, 0, 2], 3: [0, 2, 0, 0, 3]}, + 1: {0: [0, 0, 0, 3, 0], 2: [0, 0, 0, 2, 0], 3: [0, 0, 0, 2, 0]}, + 2: {0: [0, 0, 0, 3, 2], 1: [0, 0, 0, 2, 0], 3: [0, 0, 0, 2, 3]}, + 3: {0: [0, 0, 0, 3, 2], 1: [0, 0, 0, 2, 0], 2: [0, 0, 0, 2, 3]}} + :param async: Flag that determines if the command result (dictionary) is returned or a Task object (can get status, etc.). :type async: boolean -- cgit v1.2.1 From 1a1d383f3f2d3147df9bc87cf54d9a10bf0ecbf9 Mon Sep 17 00:00:00 2001 From: evasquez Date: Fri, 12 Jul 2013 12:34:35 -0500 Subject: Moved tftp to lazy init. When we load tests from modules, each testcase gets initialized, therefore a fabric is created for every test case based on the manifest. This caused a problem with LTP which generated ~1000 tests, thus ~1000 fabrics and finally ~1000 Internal tftp servers. Move tftp to lazy init so we only create one when we need one, on demand. --- cxmanage_api/fabric.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cxmanage_api/fabric.py b/cxmanage_api/fabric.py index fd71fdc..61a0860 100644 --- a/cxmanage_api/fabric.py +++ b/cxmanage_api/fabric.py @@ -77,9 +77,6 @@ class Fabric(object): if (not self.task_queue): self.task_queue = DEFAULT_TASK_QUEUE - if (not self._tftp): - self._tftp = InternalTftp() - def __eq__(self, other): """__eq__() override.""" return (isinstance(other, Fabric) and self.nodes == other.nodes) @@ -104,6 +101,9 @@ class Fabric(object): :rtype: `Tftp `_ """ + if (not self._tftp): + self._tftp = InternalTftp() + return self._tftp @tftp.setter -- cgit v1.2.1 From 1495017aef5ff4b7a5900e78d8300a1514cad79f Mon Sep 17 00:00:00 2001 From: Ripal Nathuji Date: Thu, 18 Jul 2013 15:17:10 -0500 Subject: CXMAN-210: Add command for modifying pxe interface You can now run things like: cxmanage config pxe status cxmanage config pxe eth1 cxmanage config pxe eth0 The command modifies the u-boot environment underneath in order to perist the setting. --- cxmanage/commands/config.py | 48 ++++++++++++++++++++++++++++++++- cxmanage_api/fabric.py | 35 ++++++++++++++++++++++++ cxmanage_api/node.py | 35 ++++++++++++++++++++++++ cxmanage_api/ubootenv.py | 64 ++++++++++++++++++++++++++++++++++++++++++++ cxmanage_test/fabric_test.py | 19 +++++++++++++ cxmanage_test/node_test.py | 44 ++++++++++++++++++++++++++++++ scripts/cxmanage | 8 +++++- 7 files changed, 251 insertions(+), 2 deletions(-) diff --git a/cxmanage/commands/config.py b/cxmanage/commands/config.py index ca80928..3d5b060 100644 --- a/cxmanage/commands/config.py +++ b/cxmanage/commands/config.py @@ -30,7 +30,8 @@ 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 UbootEnv, validate_boot_args, \ + validate_pxe_interface def config_reset_command(args): @@ -92,3 +93,48 @@ def config_boot_status_command(args): 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..." + + results, 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): + 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/fabric.py b/cxmanage_api/fabric.py index 61a0860..9f4dd76 100644 --- a/cxmanage_api/fabric.py +++ b/cxmanage_api/fabric.py @@ -494,6 +494,41 @@ class Fabric(object): """ return self._run_on_all_nodes(async, "get_boot_order") + def set_pxe_interface(self, interface, async=False): + """Sets the pxe interface on all nodes. + + >>> fabric.set_pxe_interface(interface='eth0') + + :param interface: Inteface for pxe requests + :type interface: string + :param async: Flag that determines if the command result (dictionary) + is returned or a Command object (can get status, etc.). + :type async: boolean + + """ + self._run_on_all_nodes(async, "set_pxe_interface", interface) + + def get_pxe_interface(self, async=False): + """Gets the pxe interface from all nodes. + + >>> fabric.get_pxe_interface() + { + 0: 'eth0', + 1: 'eth0', + 2: 'eth0', + 3: 'eth0' + } + + :param async: Flag that determines if the command result (dictionary) + is returned or a Command object (can get status, etc.). + :type async: boolean + + :returns: The boot order of each node on this fabric. + :rtype: dictionary or `Task `__ + + """ + return self._run_on_all_nodes(async, "get_pxe_interface") + def get_versions(self, async=False): """Gets the version info from all nodes. diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 3f79398..c083ae7 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -874,6 +874,41 @@ class Node(object): """ return self.get_ubootenv().get_boot_order() + def set_pxe_interface(self, interface): + """Sets pxe interface for this node. + + >>> node.set_boot_order('eth0') + + :param interface: Interface pass on to the uboot environment. + :type boot_args: string + + """ + fwinfo = self.get_firmware_info() + first_part = self._get_partition(fwinfo, "UBOOTENV", "FIRST") + active_part = self._get_partition(fwinfo, "UBOOTENV", "ACTIVE") + + # Download active ubootenv, modify, then upload to first partition + image = self._download_image(active_part) + ubootenv = self.ubootenv(open(image.filename).read()) + ubootenv.set_pxe_interface(interface) + 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()) + + ubootenv_image = self.image(filename, image.type, False, image.daddr, + image.skip_crc32, image.version) + self._upload_image(ubootenv_image, first_part, priority) + + def get_pxe_interface(self): + """Returns the current pxe interface for this node. + + >>> node.get_pxe_interface() + 'eth0' + """ + return self.get_ubootenv().get_pxe_interface() + def get_versions(self): """Get version info from this node. diff --git a/cxmanage_api/ubootenv.py b/cxmanage_api/ubootenv.py index b5b8272..12d550d 100644 --- a/cxmanage_api/ubootenv.py +++ b/cxmanage_api/ubootenv.py @@ -211,6 +211,64 @@ class UbootEnv: validate_boot_args(boot_args) # sanity check return boot_args + + def set_pxe_interface(self, interface): + """Sets the interfacespecified in the uboot environment. + + >>> uboot.set_pxe_interface('eth0') + + .. note:: + * Valid Args: eth0 or eth1 + + :param interface: The interface to set. + :type boot_args: string + + :raises ValueError: If an invalid interface is specified. + + """ + validate_pxe_interface(interface) + if interface == self.get_pxe_interface(): + return + + commands = [] + retry = False + reset = False + + if interface == "eth0": + self.variables["ethprime"] = "xgmac0" + elif (interface == "eth1"): + self.variables["ethprime"] = "xgmac1" + else: + raise ValueError("Invalid pxe interface: %s" % interface) + + def get_pxe_interface(self): + """Returns a string representation of the pxe interface. + + >>> uboot.get_pxe_interface() + 'eth0' + + :returns: Boot order for this U-Boot Environment. + :rtype: string + :raises Exception: If the u-boot environment value is not recognized. + + """ + + # This is based on reading the ethprime environment variable, and + # translating from xgmacX to ethX. By default ethprime is not set + # and eth0 is the assumed default (NOTE: this is brittle) + + if "ethprime" in self.variables: + xgmac = self.variables["ethprime"] + if xgmac == "xgmac0": + return "eth0" + elif (xgmac == "xgmac1"): + return "eth1" + else: + raise Exception("Unrecognized value for ethprime") + else: + return "eth0" + + def get_contents(self): """Returns a raw string representation of the uboot environment. @@ -253,3 +311,9 @@ def validate_boot_args(boot_args): raise ValueError("Invalid boot arg: %s" % arg) else: raise ValueError("Invalid boot arg: %s" % arg) + + +def validate_pxe_interface(interface): + """ Validate pxe interface. Raises a ValueError if the args are invalid.""" + if not interface in ["eth0", "eth1"]: + raise ValueError("Invalid pxe interface: %s" % interface) diff --git a/cxmanage_test/fabric_test.py b/cxmanage_test/fabric_test.py index fb234c5..0dd2593 100644 --- a/cxmanage_test/fabric_test.py +++ b/cxmanage_test/fabric_test.py @@ -140,6 +140,18 @@ class FabricTest(unittest.TestCase): for node in self.nodes: self.assertEqual(node.executed, ["get_boot_order"]) + def test_set_pxe_interface(self): + """ Test set_pxe_interface command """ + self.fabric.set_pxe_interface("eth0") + for node in self.nodes: + self.assertEqual(node.executed, [("set_pxe_interface", "eth0")]) + + def test_get_pxe_interface(self): + """ Test get_pxe_interface command """ + self.fabric.get_pxe_interface() + for node in self.nodes: + self.assertEqual(node.executed, ["get_pxe_interface"]) + def test_get_versions(self): """ Test get_versions command """ self.fabric.get_versions() @@ -405,6 +417,13 @@ class DummyNode(object): self.executed.append("get_boot_order") return ["disk", "pxe"] + def set_pxe_interface(self, interface): + self.executed.append(("set_pxe_interface", interface)) + + def get_pxe_interface(self): + self.executed.append("get_pxe_interface") + return "eth0" + def get_versions(self): self.executed.append("get_versions") diff --git a/cxmanage_test/node_test.py b/cxmanage_test/node_test.py index f5fc188..4f843a7 100644 --- a/cxmanage_test/node_test.py +++ b/cxmanage_test/node_test.py @@ -276,6 +276,50 @@ class NodeTest(unittest.TestCase): self.assertEqual(result, ["disk", "pxe"]) + def test_set_pxe_interface(self): + """ Test node.set_pxe_interface method """ + for node in self.nodes: + node.set_pxe_interface("eth0") + + partitions = node.bmc.partitions + ubootenv_partition = partitions[5] + unchanged_partitions = [x for x in partitions + if x != ubootenv_partition] + + self.assertEqual(ubootenv_partition.updates, 1) + self.assertEqual(ubootenv_partition.retrieves, 1) + self.assertEqual(ubootenv_partition.checks, 1) + self.assertEqual(ubootenv_partition.activates, 1) + + for partition in unchanged_partitions: + self.assertEqual(partition.updates, 0) + self.assertEqual(partition.retrieves, 0) + self.assertEqual(partition.checks, 0) + self.assertEqual(partition.activates, 0) + + def test_get_pxe_interface(self): + """ Test node.get_pxe_interface method """ + for node in self.nodes: + result = node.get_pxe_interface() + + partitions = node.bmc.partitions + ubootenv_partition = partitions[5] + unchanged_partitions = [x for x in partitions + if x != ubootenv_partition] + + self.assertEqual(ubootenv_partition.updates, 0) + self.assertEqual(ubootenv_partition.retrieves, 1) + self.assertEqual(ubootenv_partition.checks, 0) + self.assertEqual(ubootenv_partition.activates, 0) + + for partition in unchanged_partitions: + self.assertEqual(partition.updates, 0) + self.assertEqual(partition.retrieves, 0) + self.assertEqual(partition.checks, 0) + self.assertEqual(partition.activates, 0) + + self.assertEqual(result, "eth0") + def test_get_versions(self): """ Test node.get_versions method """ for node in self.nodes: diff --git a/scripts/cxmanage b/scripts/cxmanage index 3e9036f..889fe4e 100755 --- a/scripts/cxmanage +++ b/scripts/cxmanage @@ -42,7 +42,8 @@ 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 +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 @@ -250,6 +251,11 @@ def build_parser(): type=lambda x: [] if x == 'none' else x.split(',')) boot.set_defaults(func=config_boot_command) + pxe = config_subs.add_parser('pxe', + help='set pxe interface') + pxe.add_argument('interface', help='pxe interface to use') + pxe.set_defaults(func=config_pxe_command) + #info command info = subparsers.add_parser('info', help='get host info') info.add_argument('info_type', nargs='?', -- cgit v1.2.1 From ba5efd8df0d744b2cf27020d21b2219f100ccc57 Mon Sep 17 00:00:00 2001 From: Ripal Nathuji Date: Thu, 18 Jul 2013 15:50:02 -0500 Subject: CXMAN-210: Persist pxe interface setting across firmware updates --- cxmanage_api/node.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index c083ae7..be89335 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -720,6 +720,7 @@ class Node(object): try: ubootenv = self.ubootenv(open(image.filename).read()) ubootenv.set_boot_order(old_ubootenv.get_boot_order()) + ubootenv.set_pxe_interface(old_ubootenv.get_pxe_interface()) logger.info( "Set boot order to " + old_ubootenv.get_boot_order() -- cgit v1.2.1 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 b702cc5a1e9ebf8142d6301f3d5c7457f07ed9cb Mon Sep 17 00:00:00 2001 From: George Kraft Date: Wed, 17 Jul 2013 17:55:31 -0500 Subject: SW-2207: Fabric: Add methods for macaddr_base and macaddr_mask And unit tests! --- cxmanage_api/fabric.py | 48 ++++++++++++++++++++++++++++++++++++++++---- cxmanage_test/fabric_test.py | 48 ++++++++++++++++++++++++++++++++++++++++++++ cxmanage_test/node_test.py | 14 +++++++++++++ 3 files changed, 106 insertions(+), 4 deletions(-) diff --git a/cxmanage_api/fabric.py b/cxmanage_api/fabric.py index 9f4dd76..d0d7a10 100644 --- a/cxmanage_api/fabric.py +++ b/cxmanage_api/fabric.py @@ -183,10 +183,6 @@ class Fabric(object): 3: ['fc:2f:40:88:b3:6c', 'fc:2f:40:88:b3:6d', 'fc:2f:40:88:b3:6e'] } - :param async: Flag that determines if the command result (dictionary) - is returned or a Task object (can get status, etc.). - :type async: boolean - :return: The MAC addresses for each node. :rtype: dictionary @@ -778,6 +774,50 @@ class Fabric(object): self.primary_node.bmc.fabric_rm_macaddr(nodeid=nodeid, iface=iface, macaddr=macaddr) + def set_macaddr_base(self, macaddr): + """ Set a base MAC address for a custom range. + + >>> fabric.set_macaddr_base("66:55:44:33:22:11") + + :param macaddr: mac address base to use + :type macaddr: string + + """ + self.primary_node.bmc.fabric_config_set_macaddr_base(macaddr=macaddr) + + def get_macaddr_base(self): + """ Get the base MAC address for custom ranges. + + >>> fabric.get_macaddr_base() + '08:00:00:00:08:5c' + + :return: mac address base + :rtype: string + """ + return self.primary_node.bmc.fabric_config_get_macaddr_base() + + def set_macaddr_mask(self, mask): + """ Set MAC address mask for a custom range. + + >>> fabric.set_macaddr_mask("ff:ff:ff:ff:ff:00") + + :param macaddr: mac address mask to use + :type macaddr: string + + """ + self.primary_node.bmc.fabric_config_set_macaddr_mask(mask=mask) + + def get_macaddr_mask(self): + """ Get the MAC address mask for custom ranges. + + >>> fabric.get_macaddr_mask() + '08:00:00:00:08:5c' + + :return: mac address mask + :rtype: string + """ + return self.primary_node.bmc.fabric_config_get_macaddr_mask() + def get_linkspeed_policy(self): """Get the global linkspeed policy for the fabric. In the partition world this means the linkspeed for Configuration 0, Partition 0, diff --git a/cxmanage_test/fabric_test.py b/cxmanage_test/fabric_test.py index 0dd2593..9d6de38 100644 --- a/cxmanage_test/fabric_test.py +++ b/cxmanage_test/fabric_test.py @@ -359,6 +359,54 @@ class FabricTest(unittest.TestCase): bmc = self.fabric.primary_node.bmc self.assertIn ('fabric_rm_macaddr', bmc.executed) + def test_set_macaddr_base(self): + """Test the set_macaddr_base method""" + self.fabric.set_macaddr_base("00:11:22:33:44:55") + for node in self.fabric.nodes.values(): + if node == self.fabric.primary_node: + self.assertEqual( + node.bmc.executed, + [("fabric_config_set_macaddr_base", "00:11:22:33:44:55")] + ) + else: + self.assertEqual(node.bmc.executed, []) + + def test_get_macaddr_base(self): + """Test the get_macaddr_base method""" + self.assertEqual(self.fabric.get_macaddr_base(), "00:00:00:00:00:00") + for node in self.fabric.nodes.values(): + if node == self.fabric.primary_node: + self.assertEqual( + node.bmc.executed, + ["fabric_config_get_macaddr_base"] + ) + else: + self.assertEqual(node.bmc.executed, []) + + def test_set_macaddr_mask(self): + """Test the set_macaddr_mask method""" + self.fabric.set_macaddr_mask("00:11:22:33:44:55") + for node in self.fabric.nodes.values(): + if node == self.fabric.primary_node: + self.assertEqual( + node.bmc.executed, + [("fabric_config_set_macaddr_mask", "00:11:22:33:44:55")] + ) + else: + self.assertEqual(node.bmc.executed, []) + + def test_get_macaddr_mask(self): + """Test the get_macaddr_mask method""" + self.assertEqual(self.fabric.get_macaddr_mask(), "00:00:00:00:00:00") + for node in self.fabric.nodes.values(): + if node == self.fabric.primary_node: + self.assertEqual( + node.bmc.executed, + ["fabric_config_get_macaddr_mask"] + ) + else: + self.assertEqual(node.bmc.executed, []) + class DummyNode(object): """ Dummy node for the nodemanager tests """ def __init__(self, ip_address, username="admin", password="admin", diff --git a/cxmanage_test/node_test.py b/cxmanage_test/node_test.py index 4f843a7..b6f860b 100644 --- a/cxmanage_test/node_test.py +++ b/cxmanage_test/node_test.py @@ -847,6 +847,20 @@ class DummyBMC(LanBMC): self.fabric_lu_factor = lu_factor self.executed.append('fabric_config_set_link_users_factor') + def fabric_config_set_macaddr_base(self, macaddr): + self.executed.append(('fabric_config_set_macaddr_base', macaddr)) + + def fabric_config_get_macaddr_base(self): + self.executed.append('fabric_config_get_macaddr_base') + return "00:00:00:00:00:00" + + def fabric_config_set_macaddr_mask(self, mask): + self.executed.append(('fabric_config_set_macaddr_mask', mask)) + + def fabric_config_get_macaddr_mask(self): + self.executed.append('fabric_config_get_macaddr_mask') + return "00:00:00:00:00:00" + def fabric_add_macaddr(self, nodeid=0, iface=0, macaddr=None): self.executed.append('fabric_add_macaddr') -- cgit v1.2.1 From 9ad87dac6b097953be20d95369f0c26a8844b4bb Mon Sep 17 00:00:00 2001 From: George Kraft Date: Thu, 18 Jul 2013 17:02:56 -0500 Subject: Fabric: Add a CompositeBMC class for parallelizing BMC method calls This provides a mechanism so we can easily parallelize BMC calls without having to manually write wrapper functions in the Node and Fabric classes. >>> fabric.cbmc.sel_clear() --- cxmanage_api/fabric.py | 48 ++++++++++++++++++++++++++++++++++++++++++++ cxmanage_api/tasks.py | 9 +++++---- cxmanage_test/fabric_test.py | 19 ++++++++++++++++++ 3 files changed, 72 insertions(+), 4 deletions(-) diff --git a/cxmanage_api/fabric.py b/cxmanage_api/fabric.py index d0d7a10..bd8d6a7 100644 --- a/cxmanage_api/fabric.py +++ b/cxmanage_api/fabric.py @@ -56,6 +56,53 @@ class Fabric(object): :type node: `Node `_ """ + class CompositeBMC(object): + """ Composite BMC object. Provides a mechanism to run BMC + commands in parallel across all nodes. + """ + + def __init__(self, fabric): + self.fabric = fabric + + def __getattr__(self, name): + """ If the underlying BMCs have a method by this name, then return + a callable function that does it in parallel across all nodes. + """ + nodes = self.fabric.nodes + task_queue = self.fabric.task_queue + + for node in nodes.values(): + if ((not hasattr(node.bmc, name)) or + (not hasattr(getattr(node.bmc, name), "__call__"))): + raise AttributeError( + "'CompositeBMC' object has no attribute '%s'" + % name + ) + + def function(*args, **kwargs): + """ Run the named BMC command in parallel across all nodes. """ + tasks = {} + for node_id, node in nodes.iteritems(): + tasks[node_id] = task_queue.put( + getattr(node.bmc, name), + *args, + **kwargs + ) + + results = {} + errors = {} + for node_id, task in tasks.items(): + task.join() + if task.status == "Completed": + results[node_id] = task.result + else: + errors[node_id] = task.error + if errors: + raise CommandFailedError(results, errors) + return results + + return function + def __init__(self, ip_address, username="admin", password="admin", tftp=None, ecme_tftp_port=5001, task_queue=None, verbose=False, node=None): @@ -68,6 +115,7 @@ class Fabric(object): self.task_queue = task_queue self.verbose = verbose self.node = node + self.cbmc = Fabric.CompositeBMC(self) self._nodes = {} diff --git a/cxmanage_api/tasks.py b/cxmanage_api/tasks.py index 6b5cfde..7ed7851 100644 --- a/cxmanage_api/tasks.py +++ b/cxmanage_api/tasks.py @@ -43,7 +43,7 @@ class Task(object): :type args: list """ - def __init__(self, method, *args): + def __init__(self, method, *args, **kwargs): """Default constructor for the Task class.""" self.status = "Queued" self.result = None @@ -51,6 +51,7 @@ class Task(object): self._method = method self._args = args + self._kwargs = kwargs self._finished = Event() def join(self): @@ -70,7 +71,7 @@ class Task(object): """Execute this task. Should only be called by TaskWorker.""" self.status = "In Progress" try: - self.result = self._method(*self._args) + self.result = self._method(*self._args, **self._kwargs) self.status = "Completed" except Exception as e: self.error = e @@ -96,7 +97,7 @@ class TaskQueue(object): self._queue = deque() self._workers = 0 - def put(self, method, *args): + def put(self, method, *args, **kwargs): """Add a task to the task queue, and spawn a worker if we're not full. :param method: Named method to run. @@ -110,7 +111,7 @@ class TaskQueue(object): """ self._lock.acquire() - task = Task(method, *args) + task = Task(method, *args, **kwargs) self._queue.append(task) if self._workers < self.threads: diff --git a/cxmanage_test/fabric_test.py b/cxmanage_test/fabric_test.py index 9d6de38..f2720c9 100644 --- a/cxmanage_test/fabric_test.py +++ b/cxmanage_test/fabric_test.py @@ -407,6 +407,25 @@ class FabricTest(unittest.TestCase): else: self.assertEqual(node.bmc.executed, []) + def test_composite_bmc(self): + """ Test the CompositeBMC member """ + with self.assertRaises(AttributeError): + self.fabric.cbmc.fake_method + + self.fabric.cbmc.set_chassis_power("off") + results = self.fabric.cbmc.get_chassis_status() + + self.assertEqual(len(results), len(self.fabric.nodes)) + for node_id in self.fabric.nodes: + self.assertFalse(results[node_id].power_on) + + for node in self.fabric.nodes.values(): + self.assertEqual(node.bmc.executed, [ + ("set_chassis_power", "off"), + "get_chassis_status" + ]) + + class DummyNode(object): """ Dummy node for the nodemanager tests """ def __init__(self, ip_address, username="admin", password="admin", -- 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 c5ff40e373f98e544f054ac2253ef04ef2d967e0 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Mon, 22 Jul 2013 15:40:42 -0500 Subject: Bump to version v0.9.0, bump ipmitool/pyipmi version requirements pyipmi v0.8.0, ipmitool 1.8.11-cx7 --- scripts/cxmanage | 4 ++-- setup.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/cxmanage b/scripts/cxmanage index 889fe4e..ccde835 100755 --- a/scripts/cxmanage +++ b/scripts/cxmanage @@ -50,8 +50,8 @@ from cxmanage.commands.ipdiscover import ipdiscover_command from cxmanage.commands.tspackage import tspackage_command -PYIPMI_VERSION = '0.7.1' -IPMITOOL_VERSION = '1.8.11.0-cx5' +PYIPMI_VERSION = '0.8.0' +IPMITOOL_VERSION = '1.8.11.0-cx7' PARSER_EPILOG = """examples: diff --git a/setup.py b/setup.py index bd49b13..4e2f47d 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ from setuptools import setup setup( name='cxmanage', - version='0.8.2', + version='0.9.0', packages=['cxmanage', 'cxmanage.commands', 'cxmanage_api'], scripts=['scripts/cxmanage', 'scripts/sol_tabs'], description='Calxeda Management Utility', @@ -42,7 +42,7 @@ setup( install_requires=[ 'tftpy', 'pexpect', - 'pyipmi>=0.7.1', + 'pyipmi>=0.8.0', 'argparse', ], extras_require={ -- cgit v1.2.1 From 9ad84db4aae678ce99bd27498802b563bd7071cf Mon Sep 17 00:00:00 2001 From: George Kraft Date: Mon, 22 Jul 2013 16:14:54 -0500 Subject: node: Remove try/catch of Exception around the UbootEnv class It's squashing another error that prevents ubootenv updates from working -- boot order and pxe config aren't preserved as they should be. To fix this, replace UnknownBootCmdError with UbootenvError. Just something more general to say "we can't recognize the environment". UbootEnv can raise that instead of Exception. --- cxmanage_api/cx_exceptions.py | 19 +++++++++---------- cxmanage_api/node.py | 5 +++-- cxmanage_api/ubootenv.py | 16 ++++++++-------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/cxmanage_api/cx_exceptions.py b/cxmanage_api/cx_exceptions.py index b74a927..f390392 100644 --- a/cxmanage_api/cx_exceptions.py +++ b/cxmanage_api/cx_exceptions.py @@ -259,26 +259,25 @@ class InvalidImageError(Exception): return self.msg -class UnknownBootCmdError(Exception): - """Raised when the boot command is not: run bootcmd_pxe, run bootcmd_sata, - run bootcmd_mmc, setenv bootdevice, or reset. +class UbootenvError(Exception): + """Raised when the UbootEnv class fails to interpret the ubootenv + environment variables. - >>> from cxmanage_api.cx_exceptions import UnknownBootCmdError - >>> raise UnknownBootCmdError('My custom exception text!') + >>> from cxmanage_api.cx_exceptions import UbootenvError + >>> raise UbootenvError('My custom exception text!') Traceback (most recent call last): File "", line 1, in - cxmanage_api.cx_exceptions.UnknownBootCmdError: My custom exception text! + cxmanage_api.cx_exceptions.UbootenvError: My custom exception text! :param msg: Exceptions message and details to return to the user. :type msg: string - :raised: When the boot command is not: run bootcmd_pxe, run bootcmd_sata, - run bootcmd_mmc, setenv bootdevice, or reset. + :raised: When ubootenv settings are unrecognizable. """ def __init__(self, msg): - """Default constructor for the UnknownBootCmdError class.""" - super(UnknownBootCmdError, self).__init__() + """Default constructor for the UbootenvError class.""" + super(UbootenvError, self).__init__() self.msg = msg def __str__(self): diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index be89335..9ae847f 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -49,7 +49,8 @@ from cxmanage_api.ubootenv import UbootEnv as UBOOTENV from cxmanage_api.ip_retriever import IPRetriever as IPRETRIEVER from cxmanage_api.cx_exceptions import TimeoutError, NoSensorError, \ SocmanVersionError, FirmwareConfigError, PriorityIncrementError, \ - NoPartitionError, TransferFailure, ImageSizeError, PartitionInUseError + NoPartitionError, TransferFailure, ImageSizeError, \ + PartitionInUseError, UbootenvError class Node(object): @@ -739,7 +740,7 @@ class Node(object): "Done uploading ubootenv image to first " + \ "partition ('running partition')" ) - except (ValueError, Exception): + except (ValueError, UbootenvError): self._upload_image(image, running_part, priority) updated_partitions += [running_part, factory_part] diff --git a/cxmanage_api/ubootenv.py b/cxmanage_api/ubootenv.py index 12d550d..cd1a35a 100644 --- a/cxmanage_api/ubootenv.py +++ b/cxmanage_api/ubootenv.py @@ -33,7 +33,7 @@ import struct from cxmanage_api.simg import has_simg, get_simg_contents from cxmanage_api.crc32 import get_crc32 -from cxmanage_api.cx_exceptions import UnknownBootCmdError +from cxmanage_api.cx_exceptions import UbootenvError ENVIRONMENT_SIZE = 8192 @@ -87,7 +87,7 @@ class UbootEnv: :raises ValueError: If an invalid boot device is specified. :raises ValueError: If 'retry' and 'reset' args are used together. - :raises Exception: If the u-boot environment is unrecognized + :raises UbootenvError: If the u-boot environment is unrecognized """ validate_boot_args(boot_args) @@ -103,7 +103,7 @@ class UbootEnv: elif all(x in self.variables for x in UBOOTENV_V2_VARIABLES): version = 2 else: - raise Exception("Unrecognized u-boot environment") + raise UbootenvError("Unrecognized u-boot environment") for arg in boot_args: if arg == "retry": @@ -159,7 +159,7 @@ class UbootEnv: :returns: Boot order for this U-Boot Environment. :rtype: string - :raises UnknownBootCmdError: If a boot command is unrecognized. + :raises UbootenvError: If a boot command is unrecognized. """ boot_args = [] @@ -171,7 +171,7 @@ class UbootEnv: elif target == "scsi": boot_args.append("disk") else: - raise UnknownBootCmdError("Unrecognized boot target: %s" + raise UbootenvError("Unrecognized boot target: %s" % target) else: if "bootcmd_default" in self.variables: @@ -198,7 +198,7 @@ class UbootEnv: boot_args.append("reset") break else: - raise UnknownBootCmdError("Unrecognized boot command: %s" + raise UbootenvError("Unrecognized boot command: %s" % command) if retry: @@ -249,7 +249,7 @@ class UbootEnv: :returns: Boot order for this U-Boot Environment. :rtype: string - :raises Exception: If the u-boot environment value is not recognized. + :raises ValueError: If the u-boot environment value is not recognized. """ @@ -264,7 +264,7 @@ class UbootEnv: elif (xgmac == "xgmac1"): return "eth1" else: - raise Exception("Unrecognized value for ethprime") + raise ValueError("Unrecognized value for ethprime") else: return "eth0" -- cgit v1.2.1 From f09a00207940c1413a7e3f5fe728479cf8f1f079 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Mon, 22 Jul 2013 16:22:03 -0500 Subject: node: Fix string/list concatenation error in update_firmware This was preventing the ubootenv boot order and pxe config from being preserved. --- 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 9ae847f..4d7b61e 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -724,7 +724,7 @@ class Node(object): ubootenv.set_pxe_interface(old_ubootenv.get_pxe_interface()) logger.info( - "Set boot order to " + old_ubootenv.get_boot_order() + "Set boot order to %s" % old_ubootenv.get_boot_order() ) filename = temp_file() -- 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 From a80f121ee4ca3f94d45714381fb3032bbf5835bf Mon Sep 17 00:00:00 2001 From: George Kraft Date: Wed, 4 Sep 2013 11:18:13 -0500 Subject: CXMAN-221: Fix imports for the cxmanage_api.cli commands --- cxmanage_api/cli/commands/power.py | 2 +- cxmanage_api/cli/commands/sensor.py | 2 +- cxmanage_api/cli/commands/tspackage.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cxmanage_api/cli/commands/power.py b/cxmanage_api/cli/commands/power.py index 623c38d..1255cbc 100644 --- a/cxmanage_api/cli/commands/power.py +++ b/cxmanage_api/cli/commands/power.py @@ -31,7 +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 +from cxmanage_api.cli import get_tftp, get_nodes, get_node_strings, run_command def power_command(args): diff --git a/cxmanage_api/cli/commands/sensor.py b/cxmanage_api/cli/commands/sensor.py index 3a27143..acbad6e 100644 --- a/cxmanage_api/cli/commands/sensor.py +++ b/cxmanage_api/cli/commands/sensor.py @@ -32,7 +32,7 @@ # DAMAGE. -from cxmanage import get_tftp, get_nodes, get_node_strings, run_command +from cxmanage_api.cli import get_tftp, get_nodes, get_node_strings, run_command # pylint: disable=R0914 def sensor_command(args): diff --git a/cxmanage_api/cli/commands/tspackage.py b/cxmanage_api/cli/commands/tspackage.py index d6ee198..e4ccb51 100644 --- a/cxmanage_api/cli/commands/tspackage.py +++ b/cxmanage_api/cli/commands/tspackage.py @@ -46,7 +46,7 @@ import shutil import tarfile import tempfile -from cxmanage import get_tftp, get_nodes, run_command, COMPONENTS +from cxmanage_api.cli import get_tftp, get_nodes, run_command, COMPONENTS def tspackage_command(args): -- cgit v1.2.1 From eee4cd77c9709b1de4fe447f398d10593e4ba30c Mon Sep 17 00:00:00 2001 From: Greg Lutostanski Date: Thu, 26 Sep 2013 12:31:22 -0500 Subject: CXMAN-230: adding a tmux cxmanage session muxer This will hopefully replace sol_tabs eventually --- scripts/cxmux | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100755 scripts/cxmux diff --git a/scripts/cxmux b/scripts/cxmux new file mode 100755 index 0000000..17150c2 --- /dev/null +++ b/scripts/cxmux @@ -0,0 +1,51 @@ +#!/usr/bin/env python + +import os +import sys +import cxmanage_api.fabric +from optparse import OptionParser + +def main(): + parser = OptionParser("usage: %prog [options] COMMAND ecmeIP", conflict_handler="resolve") + parser.add_option("-s", "--ssh", + action="store_const", const=True, dest="ssh", default=False, + help="Use the SPU IPs rather than ECME IPs") + parser.add_option("-n", "--nosync", + action="store_const", const=False, dest="sync", default=True, + help="Do not syncronize input across terminals") + parser.disable_interspersed_args() + (options, args) = parser.parse_args() + if len(args) == 0: + parser.print_help() + return -1 + elif len(args) < 2: + parser.error("Need to specify COMMAND and ecmeIP") + + command = " ".join(args[:-1]) + ecmeip = args[-1] + name = '%s@%s' % (args[0], ecmeip) + fabric = cxmanage_api.fabric.Fabric(ecmeip) + ips = [node.ip_address for node in fabric.nodes.values()] + if options.ssh: + ips = fabric.get_server_ip().values() + + for i, ip in enumerate(ips): + if i == 0: + os.system('tmux new-window -n "%s"' % name) + os.system('tmux send-keys -l "%s %s"' % (command, ip)) + os.system('tmux send-keys Enter') + continue + + os.system('tmux split-window -h') + os.system('tmux send-keys -l "%s %s"' % (command, ip)) + os.system('tmux send-keys Enter') + os.system('tmux select-layout -t "%s" even-horizontal >/dev/null' % name) + + os.system('tmux select-layout -t "%s" tiled >/dev/null' % name) + if options.sync: + os.system('tmux set-window-option -t "%s" synchronize-panes on >/dev/null' % name) + + return 0 + +if __name__ == '__main__': + sys.exit(main()) -- cgit v1.2.1 From c38aa01a17663f0eeecfa209554aa77d293b0d9b Mon Sep 17 00:00:00 2001 From: "matthew.hodgins" Date: Tue, 24 Sep 2013 15:50:37 -0500 Subject: CXMAN-228 simplify updating EEPROM --- cxmanage_api/cli/__init__.py | 2 +- cxmanage_api/cli/commands/eeprom.py | 130 ++++++++++++++++++++++++++++++++++++ cxmanage_api/cx_exceptions.py | 24 +++++++ cxmanage_api/node.py | 79 +++++++++++++++++++++- scripts/cxmanage | 48 +++++++++---- 5 files changed, 266 insertions(+), 17 deletions(-) create mode 100644 cxmanage_api/cli/commands/eeprom.py diff --git a/cxmanage_api/cli/__init__.py b/cxmanage_api/cli/__init__.py index 438d568..2d98d8a 100644 --- a/cxmanage_api/cli/__init__.py +++ b/cxmanage_api/cli/__init__.py @@ -124,7 +124,7 @@ def get_nodes(args, tftp, verify_prompt=False): 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" + + "discovered node count does not equal nodes in " + "your system.\n" ) if not prompt_yes("Discovered %i nodes. Continue?" diff --git a/cxmanage_api/cli/commands/eeprom.py b/cxmanage_api/cli/commands/eeprom.py new file mode 100644 index 0000000..fa715ff --- /dev/null +++ b/cxmanage_api/cli/commands/eeprom.py @@ -0,0 +1,130 @@ +"""Calxeda: eeprom.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_nodes, get_tftp, run_command, prompt_yes + +def eepromupdate_command(args): + """Updates the EEPROM's on a cluster or host""" + def validate_config(): + """Makes sure the system type is applicable to EEPROM updates""" + for node in nodes: + if('Dual Node' not in node.get_versions().hardware_version): + print 'ERROR: eepromupdate is only valid on TerraNova systems' + return True + + return False + + def validate_images(): + """Makes sure all the necessary images have been provided""" + if(args.eeprom_location == 'node'): + for node in nodes: + node_hw_ver = node.get_versions().hardware_version + if('Uplink' in node_hw_ver): + image = 'dual_uplink_node_%s' % (node.node_id % 4) + else: + image = 'dual_node_%s' % (node.node_id % 4) + if(not [img for img in args.images if image in img]): + print 'ERROR: no valid image for node %s' % node.node_id + return True + + else: + image = args.images[0] + if('tn_storage.single_slot' not in image): + print 'ERROR: %s is an invalid image for slot EEPROM' % image + return True + + return False + + def do_update(): + """Updates the EEPROM images""" + if(args.eeprom_location == 'node'): + for node in nodes: + node_hw_ver = node.get_versions().hardware_version + if('Uplink' in node_hw_ver): + needed_image = 'dual_uplink_node_%s' % (node.node_id % 4) + else: + needed_image = 'dual_node_%s' % (node.node_id % 4) + image = [img for img in args.images if needed_image in img][0] + print 'Updating node EEPROM on node %s' % node.node_id + if(args.verbose): + print ' %s' % image + try: + node.update_node_eeprom(image) + except Exception as err: + print 'ERROR: %s' % str(err) + return True + + print '' # for readability + else: + image = args.images[0] + # First node in every slot gets the slot image + slot_nodes = [node for node in nodes if node.node_id % 4 == 0] + _, errors = run_command( + args, slot_nodes, "update_slot_eeprom", image + ) + if(errors): + print 'ERROR: EEPROM update failed' + return True + + return False + + if not args.all_nodes: + if args.force: + print( + 'WARNING: Updating EEPROM without --all-nodes' + + ' is dangerous.' + ) + else: + if not prompt_yes( + 'WARNING: Updating EEPROM without ' + + '--all-nodes is dangerous. Continue?' + ): + return 1 + + tftp = get_tftp(args) + nodes = get_nodes(args, tftp, verify_prompt=True) + + errors = validate_config() + + if(not errors): + errors = validate_images() + + if(not errors): + errors = do_update() + + if not args.quiet and not errors: + print "Command completed successfully." + print "A power cycle is required for the update to take effect.\n" + + return errors + + diff --git a/cxmanage_api/cx_exceptions.py b/cxmanage_api/cx_exceptions.py index df2dcc7..5f60df7 100644 --- a/cxmanage_api/cx_exceptions.py +++ b/cxmanage_api/cx_exceptions.py @@ -46,6 +46,30 @@ from tftpy.TftpShared import TftpException # Defines the custom exceptions used by the cxmanage_api project. # +class EEPROMUpdateError(Exception): + """Raised when an error is encountered while updating the EEPROM + + >>> from cxmanage_api.cx_exceptions import TimeoutError + >>> raise TimeoutError('My custom exception text!') + Traceback (most recent call last): + File "", line 1, in + cxmanage_api.cx_exceptions.TimeoutError: My custom exception text! + + :param msg: Exceptions message and details to return to the user. + :type msg: string + :raised: When an error is encountered while updating the EEPROM + + """ + + def __init__(self, msg): + """Default constructor for the EEPROMUpdateError class.""" + super(EEPROMUpdateError, self).__init__() + self.msg = msg + + def __str__(self): + """String representation of this Exception class.""" + return self.msg + class TimeoutError(Exception): """Raised when a timeout has been reached. diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 358af95..b0d7922 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -52,7 +52,8 @@ 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, NoFRUVersionError + PartitionInUseError, UbootenvError, NoFRUVersionError, \ + EEPROMUpdateError # pylint: disable=R0902, R0904 @@ -839,6 +840,80 @@ communication. print("\nLog saved to " + new_filepath) + def update_node_eeprom(self, image): + """Updates the node EEPROM + + .. note:: + A power cycle is required for the update to take effect + + >>> node.update_node_eeprom('builds/dual_node_0_v3.0.0.img') + + :param image: The location of an EEPROM image + :type image: string + + :raises EEPROMUpdateError: When an error is encountered while \ +updating the EEPROM + + """ + # Does the image exist? + if(not os.path.exists(image)): + raise EEPROMUpdateError( + '%s does not exist' % image + ) + node_hw_ver = self.get_versions().hardware_version + # Is this configuration valid for EEPROM updates? + if('Dual Node' not in node_hw_ver): + raise EEPROMUpdateError( + 'eepromupdate is only valid on TerraNova systems' + ) + # Is this image valid? + if('Uplink' in node_hw_ver): + image_prefix = 'dual_uplink_node_%s' % (self.node_id % 4) + else: + image_prefix = 'dual_node_%s' % (self.node_id % 4) + if(image_prefix not in image): + raise EEPROMUpdateError( + '%s is not a valid node EEPROM image for this node' % image + ) + # Perform the upgrade + ipmi_command = 'fru write 81 %s' % image + self.ipmitool_command(ipmi_command.split(' ')) + + def update_slot_eeprom(self, image): + """Updates the slot EEPROM + + .. note:: + A power cycle is required for the update to take effect + + >>> node.update_slot_eeprom('builds/tn_storage.single_slot_v3.0.0.img') + + :param image: The location of an EEPROM image + :type image: string + + :raises EEPROMUpdateError: When an error is encountered while \ +updating the EEPROM + + """ + # Does the image exist? + if(not os.path.exists(image)): + raise EEPROMUpdateError( + '%s does not exist' % image + ) + node_hw_ver = self.get_versions().hardware_version + # Is this configuration valid for EEPROM updates? + if('Dual Node' not in node_hw_ver): + raise EEPROMUpdateError( + 'eepromupdate is only valid on TerraNova systems' + ) + # Is this image valid? + if('tn_storage.single_slot' not in image): + raise EEPROMUpdateError( + '%s is an invalid image for slot EEPROM' % image + ) + # Perform the upgrade + ipmi_command = 'fru write 82 %s' % image + self.ipmitool_command(ipmi_command.split(' ')) + def config_reset(self): """Resets configuration to factory defaults. @@ -1783,7 +1858,7 @@ obtained. "Unable to increment SIMG priority, too high") return priority - def _read_fru(self, 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. diff --git a/scripts/cxmanage b/scripts/cxmanage index ce7748c..93518dd 100755 --- a/scripts/cxmanage +++ b/scripts/cxmanage @@ -50,6 +50,7 @@ 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 +from cxmanage_api.cli.commands.eeprom import eepromupdate_command PYIPMI_VERSION = '0.8.0' @@ -86,6 +87,10 @@ FWUPDATE_IMAGE_TYPES = ['PACKAGE'] + sorted([ 'DIAG_ELF', ]) +EEPROMUPDATE_EPILOG = """examples: + cxmanage -a eepromupdate slot tn_storage.single_slot_v3.0.0.img 192.168.1.1 + cxmanage -a eepromupdate node dual_uplink_node_0.img \ +dual_uplink_node_1.img dual_node_0.img dual_node_0.img 192.168.1.1""" def build_parser(): @@ -95,7 +100,7 @@ def build_parser(): formatter_class=argparse.RawDescriptionHelpFormatter, epilog=PARSER_EPILOG) - #global arguments + # global arguments parser.add_argument('-V', '--version', action='store_true', help='Show version information') parser.add_argument('-u', '--user', default='admin', @@ -134,7 +139,7 @@ def build_parser(): subparsers = parser.add_subparsers() - #power command + # power command power = subparsers.add_parser('power', help='control server power') power_subs = power.add_subparsers() @@ -172,26 +177,26 @@ def build_parser(): 'status', help='get the current power policy') power_policy_status.set_defaults(func=power_policy_status_command) - #mcreset command + # mcreset command mcreset = subparsers.add_parser('mcreset', help='reset the management controller') mcreset.set_defaults(func=mcreset_command) - #fwupdate command + # fwupdate command fwupdate = subparsers.add_parser('fwupdate', help='update firmware', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=FWUPDATE_EPILOG) fwupdate.add_argument('image_type', metavar='IMAGE_TYPE', help='image type to use (%s)' % ", ".join(FWUPDATE_IMAGE_TYPES), type=lambda string: string.upper(), - choices = FWUPDATE_IMAGE_TYPES) + choices=FWUPDATE_IMAGE_TYPES) fwupdate.add_argument('filename', help='path to file to upload') fwupdate.add_argument('--full', action='store_true', default=False, help='Update primary AND backup partitions (will reset MC)') fwupdate.add_argument('--partition', help='Specify partition to update', default='INACTIVE', type=lambda string: string.upper(), - choices = list([ + choices=list([ 'FIRST', 'SECOND', 'BOTH', @@ -219,27 +224,42 @@ def build_parser(): help='Version for SIMG header', default=None) fwupdate.set_defaults(func=fwupdate_command) - #fwinfo command + # eepromupdate command + eepromupdate = subparsers.add_parser('eepromupdate', help='update EEPROM', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=EEPROMUPDATE_EPILOG + ) + eepromupdate.add_argument('eeprom_location', + choices=['slot', 'node'], + help='EEPROM location' + ) + eepromupdate.add_argument('images', + nargs='+', + help='path to file(s) to upload' + ) + eepromupdate.set_defaults(func=eepromupdate_command) + + # fwinfo command fwinfo = subparsers.add_parser('fwinfo', help='get FW info') fwinfo.set_defaults(func=fwinfo_command) - #sensor command + # sensor command sensor = subparsers.add_parser('sensor', help='read sensor value') sensor.add_argument('sensor_name', help='Sensor name to read', nargs='?', default='') sensor.set_defaults(func=sensor_command) - #ipinfo command + # ipinfo command ipinfo = subparsers.add_parser('ipinfo', help='get IP info') ipinfo.set_defaults(func=ipinfo_command) - #macaddrs command + # macaddrs command macaddrs = subparsers.add_parser('macaddrs', help='get mac addresses') macaddrs.set_defaults(func=macaddrs_command) - #config command + # config command config = subparsers.add_parser('config', help='configure hosts') config_subs = config.add_subparsers() @@ -258,14 +278,14 @@ def build_parser(): pxe.add_argument('interface', help='pxe interface to use') pxe.set_defaults(func=config_pxe_command) - #info command + # info command info = subparsers.add_parser('info', help='get host info') info.add_argument('info_type', nargs='?', type=lambda string: string.lower(), choices=['basic', 'ubootenv']) info.set_defaults(func=info_command) - #ipmitool command + # ipmitool command ipmitool = subparsers.add_parser('ipmitool', help='run an arbitrary ipmitool command') ipmitool.add_argument('-l', '--lanplus', @@ -275,7 +295,7 @@ def build_parser(): help='ipmitool arguments') ipmitool.set_defaults(func=ipmitool_command) - #ipdiscover command + # ipdiscover command ipdiscover = subparsers.add_parser('ipdiscover', help='discover server-side IP addresses') ipdiscover.add_argument('-A', '--aggressive', action='store_true', -- cgit v1.2.1 From 25d3656b4837842e6b06a65e88a00a977eb3e198 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Tue, 1 Oct 2013 15:13:33 -0500 Subject: CXMAN-225: Retry obtaining TftpServer port if we get a socket.error Seems like a race that occurs between the main thread and TftpServer thread. --- cxmanage_api/tftp.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/cxmanage_api/tftp.py b/cxmanage_api/tftp.py index 0b33db8..91671cc 100644 --- a/cxmanage_api/tftp.py +++ b/cxmanage_api/tftp.py @@ -37,6 +37,7 @@ import socket import logging import traceback +from datetime import datetime, timedelta from tftpy import TftpClient, TftpServer, setLogLevel from threading import Thread from cxmanage_api import temp_dir @@ -74,10 +75,18 @@ class InternalTftp(Thread): 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] + # Get the port we actually hosted on + if port == 0: + deadline = datetime.now() + timedelta(seconds=1) + while datetime.now() < deadline: + try: + self.port = self.server.sock.getsockname()[1] + break + except (AttributeError, socket.error): + pass + else: + # don't catch the error on our last attempt + self.port = self.server.sock.getsockname()[1] def run(self): """ Run the server. Listens indefinitely. """ -- cgit v1.2.1 From 15433b9d93150d212562be13a2b2f10e8b9dd9b3 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Tue, 1 Oct 2013 15:35:04 -0500 Subject: CXMAN-238: Only spawn a single InternalTftp instance by default I don't think each node needs its own TFTP server. --- cxmanage_api/fabric.py | 2 +- cxmanage_api/node.py | 2 +- cxmanage_api/tftp.py | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/cxmanage_api/fabric.py b/cxmanage_api/fabric.py index 8a42ea7..564c97f 100644 --- a/cxmanage_api/fabric.py +++ b/cxmanage_api/fabric.py @@ -156,7 +156,7 @@ class Fabric(object): """ if (not self._tftp): - self._tftp = InternalTftp() + self._tftp = InternalTftp.default() return self._tftp diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index b0d7922..f720d28 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -88,7 +88,7 @@ class Node(object): image=None, ubootenv=None, ipretriever=None): """Default constructor for the Node class.""" if (not tftp): - tftp = InternalTftp() + tftp = InternalTftp.default() # Dependency Integration if (not bmc): diff --git a/cxmanage_api/tftp.py b/cxmanage_api/tftp.py index 91671cc..58b6cee 100644 --- a/cxmanage_api/tftp.py +++ b/cxmanage_api/tftp.py @@ -62,6 +62,14 @@ class InternalTftp(Thread): :type verbose: boolean """ + _default = None + + @staticmethod + def default(): + """ Return the default InternalTftp server """ + if InternalTftp._default == None: + InternalTftp._default = InternalTftp() + return InternalTftp._default def __init__(self, ip_address=None, port=0, verbose=False): super(InternalTftp, self).__init__() -- cgit v1.2.1 From 52057843f2b4002095422069ae0abbfa360de9ed Mon Sep 17 00:00:00 2001 From: George Kraft Date: Tue, 1 Oct 2013 15:41:26 -0500 Subject: CXMAN-225: Increase InternalTftp port deadline to 10 seconds --- cxmanage_api/tftp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cxmanage_api/tftp.py b/cxmanage_api/tftp.py index 58b6cee..59e9774 100644 --- a/cxmanage_api/tftp.py +++ b/cxmanage_api/tftp.py @@ -85,7 +85,7 @@ class InternalTftp(Thread): # Get the port we actually hosted on if port == 0: - deadline = datetime.now() + timedelta(seconds=1) + deadline = datetime.now() + timedelta(seconds=10) while datetime.now() < deadline: try: self.port = self.server.sock.getsockname()[1] -- cgit v1.2.1 From 5039fc3400b7e63fc8f169c872ab9cbe094a553b Mon Sep 17 00:00:00 2001 From: George Kraft Date: Thu, 3 Oct 2013 12:42:08 -0500 Subject: CXMAN-218: Remove node_fru and slot_fru commands Those versions will be part of get_versions() instead. --- cxmanage_api/cli/commands/fru_version.py | 71 -------------------------------- cxmanage_api/cli/commands/tspackage.py | 42 ------------------- cxmanage_api/cx_exceptions.py | 21 ---------- cxmanage_api/fabric.py | 38 ----------------- cxmanage_api/node.py | 56 +------------------------ cxmanage_test/fabric_test.py | 12 ------ cxmanage_test/node_test.py | 12 ------ scripts/cxmanage | 12 ------ 8 files changed, 1 insertion(+), 263 deletions(-) delete mode 100644 cxmanage_api/cli/commands/fru_version.py diff --git a/cxmanage_api/cli/commands/fru_version.py b/cxmanage_api/cli/commands/fru_version.py deleted file mode 100644 index 65d0418..0000000 --- a/cxmanage_api/cli/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_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/tspackage.py b/cxmanage_api/cli/commands/tspackage.py index e4ccb51..2d86aa4 100644 --- a/cxmanage_api/cli/commands/tspackage.py +++ b/cxmanage_api/cli/commands/tspackage.py @@ -87,14 +87,6 @@ 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) @@ -172,40 +164,6 @@ 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.""" mac_addr_results, _ = run_command( diff --git a/cxmanage_api/cx_exceptions.py b/cxmanage_api/cx_exceptions.py index 5f60df7..c4b2e0f 100644 --- a/cxmanage_api/cx_exceptions.py +++ b/cxmanage_api/cx_exceptions.py @@ -401,25 +401,4 @@ 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/fabric.py b/cxmanage_api/fabric.py index 564c97f..ab17677 100644 --- a/cxmanage_api/fabric.py +++ b/cxmanage_api/fabric.py @@ -1082,44 +1082,6 @@ 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 f720d28..f827e8a 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -52,8 +52,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, NoFRUVersionError, \ - EEPROMUpdateError + PartitionInUseError, UbootenvError, EEPROMUpdateError # pylint: disable=R0902, R0904 @@ -1566,59 +1565,6 @@ obtained. """ 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 NoFRUVersionError("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 NoFRUVersionError("No slot FRU detected.") - - # 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() diff --git a/cxmanage_test/fabric_test.py b/cxmanage_test/fabric_test.py index fe1a80c..3be19f2 100644 --- a/cxmanage_test/fabric_test.py +++ b/cxmanage_test/fabric_test.py @@ -422,18 +422,6 @@ 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): diff --git a/cxmanage_test/node_test.py b/cxmanage_test/node_test.py index 3c0e4a0..2f0e83f 100644 --- a/cxmanage_test/node_test.py +++ b/cxmanage_test/node_test.py @@ -451,18 +451,6 @@ 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: - 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: - 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 """ diff --git a/scripts/cxmanage b/scripts/cxmanage index 93518dd..ea93997 100755 --- a/scripts/cxmanage +++ b/scripts/cxmanage @@ -48,8 +48,6 @@ 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 from cxmanage_api.cli.commands.eeprom import eepromupdate_command @@ -319,16 +317,6 @@ def build_parser(): 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 95248ab843168d79f8e362f4ca3a17a491717780 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Thu, 3 Oct 2013 12:49:52 -0500 Subject: CXMAN-218: Clean up the node.get_versions() method No functional change. --- cxmanage_api/node.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index f827e8a..4973242 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1030,30 +1030,29 @@ communication. """ result = self.bmc.get_info_basic() fwinfo = self.get_firmware_info() - fw_version = result.firmware_version # components maps variables to firmware partition types components = [ ("cdb_version", "CDB"), ("stage2_version", "S2_ELF"), - ("bootlog_version", "BOOT_LOG") + ("bootlog_version", "BOOT_LOG"), + ("uboot_version", "A9_UBOOT"), + ("ubootenv_version", "UBOOTENV"), + ("dtb_version", "DTB") ] + # 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"): + if result.firmware_version.startswith("ECX-1000"): + result.chip_name = "Highbank" components.append(("a9boot_version", "A9_EXEC")) - setattr(result, "chip_name", "Highbank") - elif fw_version.startswith("ECX-2000"): - # The BMC (and fwinfo) still reference the A15 as an A9 + elif result.firmware_version.startswith("ECX-2000"): + result.chip_name = "Midway" components.append(("a15boot_version", "A9_EXEC")) - setattr(result, "chip_name", "Midway") else: - # Default to A9 and unknown name + result.chip_name = "Unknown" 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")) for var, ptype in components: try: @@ -1061,16 +1060,17 @@ communication. setattr(result, var, partition.version) except NoPartitionError: pass + try: card = self.bmc.get_info_card() - setattr(result, "hardware_version", "%s X%02i" % - (card.type, int(card.revision))) - except IpmiError as err: - if (self.verbose): - print str(err) + result.hardware_version = "%s X%02i" % ( + card.type, int(card.revision) + ) + except IpmiError: # Should raise an error, but we want to allow the command # to continue gracefully if the ECME is out of date. - setattr(result, "hardware_version", "Unknown") + result.hardware_version = "Unknown" + return result def get_versions_dict(self): -- cgit v1.2.1 From b15d1fda9feedb400ded3b8775c7e2b53ced42e3 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Thu, 3 Oct 2013 13:01:09 -0500 Subject: CXMAN-218: cli: Add EEPROM versions to info/tspackage commands --- cxmanage_api/cli/__init__.py | 32 +++++++++++++++++--------------- cxmanage_api/cli/commands/info.py | 6 +++--- cxmanage_api/cli/commands/tspackage.py | 6 +++--- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/cxmanage_api/cli/__init__.py b/cxmanage_api/cli/__init__.py index 2d98d8a..0d72116 100644 --- a/cxmanage_api/cli/__init__.py +++ b/cxmanage_api/cli/__init__.py @@ -41,6 +41,23 @@ from cxmanage_api.tasks import TaskQueue from cxmanage_api.cx_exceptions import TftpException +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"), + ("node_eeprom_version", "Node EEPROM version"), + ("node_eeprom_config", "Node EEPROM config"), + ("slot_eeprom_version", "Slot EEPROM version"), + ("slot_eeprom_config", "Slot EEPROM config"), +] + + def get_tftp(args): """Get a TFTP server""" if args.internal_tftp: @@ -343,18 +360,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"), - ("a15boot_version", "A15boot version"), - ("uboot_version", "Uboot version"), - ("ubootenv_version", "Ubootenv version"), - ("dtb_version", "DTB version"), -] - diff --git a/cxmanage_api/cli/commands/info.py b/cxmanage_api/cli/commands/info.py index 0f0b2ca..9d92fa9 100644 --- a/cxmanage_api/cli/commands/info.py +++ b/cxmanage_api/cli/commands/info.py @@ -63,13 +63,13 @@ def info_basic_command(args): components = COMPONENTS print "[ Info from %s ]" % node_strings[node] - print "Hardware version : %s" % result.hardware_version - print "Firmware version : %s" % result.firmware_version + 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 "%s: %s" % (string.ljust(20), version) print if not args.quiet and errors: diff --git a/cxmanage_api/cli/commands/tspackage.py b/cxmanage_api/cli/commands/tspackage.py index 2d86aa4..c0c18c5 100644 --- a/cxmanage_api/cli/commands/tspackage.py +++ b/cxmanage_api/cli/commands/tspackage.py @@ -145,11 +145,11 @@ def write_version_info(args, nodes): if node in info_results: info_result = info_results[node] lines.append( - "Hardware version : %s" % + "Hardware version : %s" % info_result.hardware_version ) lines.append( - "Firmware version : %s" % + "Firmware version : %s" % info_result.firmware_version ) @@ -158,7 +158,7 @@ def write_version_info(args, nodes): for var, description in components: if hasattr(info_result, var): version = getattr(info_result, var) - lines.append("%s: %s" % (description.ljust(19), version)) + lines.append("%s: %s" % (description.ljust(20), version)) else: lines.append("No version information could be found.") -- cgit v1.2.1 From d11c20d6eb6bdfb8c788731a1576ffa2721a884e Mon Sep 17 00:00:00 2001 From: Greg Lutostanski Date: Mon, 7 Oct 2013 16:16:47 -0500 Subject: CXMAN-230: Allow cxmux to work with tmux v1.7 & install (remove -l) and package for command install to the PATH. --- scripts/cxmux | 7 +++---- setup.py | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/scripts/cxmux b/scripts/cxmux index 17150c2..5ab9090 100755 --- a/scripts/cxmux +++ b/scripts/cxmux @@ -32,16 +32,15 @@ def main(): for i, ip in enumerate(ips): if i == 0: os.system('tmux new-window -n "%s"' % name) - os.system('tmux send-keys -l "%s %s"' % (command, ip)) + os.system('tmux send-keys "%s %s"' % (command, ip)) os.system('tmux send-keys Enter') continue os.system('tmux split-window -h') - os.system('tmux send-keys -l "%s %s"' % (command, ip)) + os.system('tmux send-keys "%s %s"' % (command, ip)) os.system('tmux send-keys Enter') - os.system('tmux select-layout -t "%s" even-horizontal >/dev/null' % name) + os.system('tmux select-layout -t "%s" tiled >/dev/null' % name) - os.system('tmux select-layout -t "%s" tiled >/dev/null' % name) if options.sync: os.system('tmux set-window-option -t "%s" synchronize-panes on >/dev/null' % name) diff --git a/setup.py b/setup.py index daba960..c44b83a 100644 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ setup( 'cxmanage_api.cli.commands', 'cxmanage_test' ], - scripts=['scripts/cxmanage', 'scripts/sol_tabs'], + scripts=['scripts/cxmanage', 'scripts/sol_tabs', 'scripts/cxmux'], description='Calxeda Management Utility', # NOTE: As of right now, the pyipmi version requirement needs to be updated # at the top of scripts/cxmanage as well. -- cgit v1.2.1 From 87e004da5471c84f48310f7ecdd78e574d95a6ab Mon Sep 17 00:00:00 2001 From: "matthew.hodgins" Date: Tue, 15 Oct 2013 09:35:25 -0500 Subject: AIT-371 adding sel to dummynode for unit testing purposes --- cxmanage_test/fabric_test.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cxmanage_test/fabric_test.py b/cxmanage_test/fabric_test.py index 3be19f2..d2a6e28 100644 --- a/cxmanage_test/fabric_test.py +++ b/cxmanage_test/fabric_test.py @@ -451,8 +451,14 @@ class DummyNode(object): self.power_state = False self.ip_address = ip_address self.tftp = tftp + self.sel = [] self.bmc = make_bmc(DummyBMC, hostname=ip_address, username=username, password=password, verbose=False) + + def get_sel(self): + """Simulate get_sel()""" + self.executed.append('get_sel') + return self.sel def get_power(self): """Simulate get_power(). """ -- cgit v1.2.1 From e62b7e2960ccca5a062712a1ea3bfb87bbf86ec0 Mon Sep 17 00:00:00 2001 From: evasquez Date: Wed, 16 Oct 2013 17:48:54 -0500 Subject: :nojira: Added a --virt-env option to cmux If you need to be in your virtual environment to call ipmitool, then cxmux wasn't able to help much. Now you can call cxmux like this: cxmux --virt-env MyVirtEnv ipmitool -I lanplus -U admin -P admin sol activate -H This will prepend a 'workon MyVirtEnv' before executing your command. Also made the code conform to PEP8 Signed-off-by: evasquez --- scripts/cxmux | 44 +++++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/scripts/cxmux b/scripts/cxmux index 5ab9090..938ad04 100755 --- a/scripts/cxmux +++ b/scripts/cxmux @@ -1,19 +1,35 @@ #!/usr/bin/env python + import os import sys -import cxmanage_api.fabric + from optparse import OptionParser +import cxmanage_api.fabric + + def main(): - parser = OptionParser("usage: %prog [options] COMMAND ecmeIP", conflict_handler="resolve") - parser.add_option("-s", "--ssh", - action="store_const", const=True, dest="ssh", default=False, - help="Use the SPU IPs rather than ECME IPs") - parser.add_option("-n", "--nosync", - action="store_const", const=False, dest="sync", default=True, - help="Do not syncronize input across terminals") + parser = OptionParser( + "usage: %prog [options] COMMAND ecmeIP", conflict_handler="resolve" + ) + parser.add_option( + "-s", "--ssh", + action="store_const", const=True, dest="ssh", default=False, + help="Use the SPU IPs rather than ECME IPs" + ) + parser.add_option( + "-n", "--nosync", + action="store_const", const=False, dest="sync", default=True, + help="Do not syncronize input across terminals" + ) + parser.add_option( + "--virt-env", + action="store", type="string", dest="virt_env", + help="Calls workon before spawning a window" + ) parser.disable_interspersed_args() + (options, args) = parser.parse_args() if len(args) == 0: parser.print_help() @@ -22,6 +38,10 @@ def main(): parser.error("Need to specify COMMAND and ecmeIP") command = " ".join(args[:-1]) + + if options.virt_env: + command = 'workon %s; ' % options.virt_env + command + ecmeip = args[-1] name = '%s@%s' % (args[0], ecmeip) fabric = cxmanage_api.fabric.Fabric(ecmeip) @@ -35,16 +55,18 @@ def main(): os.system('tmux send-keys "%s %s"' % (command, ip)) os.system('tmux send-keys Enter') continue - + os.system('tmux split-window -h') os.system('tmux send-keys "%s %s"' % (command, ip)) os.system('tmux send-keys Enter') os.system('tmux select-layout -t "%s" tiled >/dev/null' % name) if options.sync: - os.system('tmux set-window-option -t "%s" synchronize-panes on >/dev/null' % name) + os.system( + 'tmux set-window-option -t "%s" synchronize-panes on >/dev/null' % + name + ) - return 0 if __name__ == '__main__': sys.exit(main()) -- cgit v1.2.1 From 4a116cac1b2d6319604f957fd34d42a4bba29b21 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Mon, 21 Oct 2013 12:11:28 -0500 Subject: CXMAN-243: Add a wait=True flag to fabric.refresh() --- cxmanage_api/fabric.py | 57 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/cxmanage_api/fabric.py b/cxmanage_api/fabric.py index ab17677..e143c32 100644 --- a/cxmanage_api/fabric.py +++ b/cxmanage_api/fabric.py @@ -32,10 +32,13 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. +import time + from cxmanage_api.tasks import DEFAULT_TASK_QUEUE from cxmanage_api.tftp import InternalTftp from cxmanage_api.node import Node as NODE -from cxmanage_api.cx_exceptions import CommandFailedError +from cxmanage_api.cx_exceptions import CommandFailedError, TimeoutError, \ + IpmiError, TftpException # pylint: disable=R0902,R0903, R0904 @@ -209,22 +212,46 @@ class Fabric(object): """ return self.nodes[0] - def refresh(self): + def refresh(self, wait=False, timeout=600): """Gets the nodes of this fabric by pulling IP info from a BMC.""" + def get_nodes(): + """Returns a dictionary of nodes reported by the primary node IP""" + new_nodes = {} + node = self.node( + ip_address=self.ip_address, username=self.username, + password=self.password, tftp=self.tftp, + ecme_tftp_port=self.ecme_tftp_port, verbose=self.verbose + ) + ipinfo = node.get_fabric_ipinfo() + for node_id, node_address in ipinfo.iteritems(): + new_nodes[node_id] = self.node( + ip_address=node_address, username=self.username, + password=self.password, tftp=self.tftp, + ecme_tftp_port=self.ecme_tftp_port, + verbose=self.verbose + ) + new_nodes[node_id].node_id = node_id + return new_nodes + + initial_node_count = len(self._nodes) self._nodes = {} - node = self.node(ip_address=self.ip_address, username=self.username, - password=self.password, tftp=self.tftp, - ecme_tftp_port=self.ecme_tftp_port, - verbose=self.verbose) - ipinfo = node.get_fabric_ipinfo() - for node_id, node_address in ipinfo.iteritems(): - self._nodes[node_id] = self.node(ip_address=node_address, - username=self.username, - password=self.password, - tftp=self.tftp, - ecme_tftp_port=self.ecme_tftp_port, - verbose=self.verbose) - self._nodes[node_id].node_id = node_id + + if wait: + deadline = time.time() + timeout + while time.time() < deadline: + try: + self._nodes = get_nodes() + if len(self._nodes) >= initial_node_count: + break + except (IpmiError, TftpException): + pass + else: + raise TimeoutError( + "Fabric refresh timed out. Rediscovered %i of %i nodes" + % (len(self._nodes), initial_node_count) + ) + else: + self._nodes = get_nodes() def get_mac_addresses(self): """Gets MAC addresses from all nodes. -- cgit v1.2.1 From 13cf46feb0a15dff935d6d24c00e9e9f352e8d38 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Thu, 24 Oct 2013 15:49:28 -0500 Subject: CXMAN-245: Add __str__ to FirmwarePackage --- cxmanage_api/firmware_package.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cxmanage_api/firmware_package.py b/cxmanage_api/firmware_package.py index 0be4f07..ce698c8 100644 --- a/cxmanage_api/firmware_package.py +++ b/cxmanage_api/firmware_package.py @@ -122,6 +122,9 @@ class FirmwarePackage(object): self.images.append(Image(filename, image_type, simg, daddr, skip_crc32, version)) + def __str__(self): + return self.version + def save_package(self, filename): """Save all images as a firmware package. -- cgit v1.2.1 From cbe820b878ace369bca6c9a83f304723ac34224b Mon Sep 17 00:00:00 2001 From: George Kraft Date: Thu, 24 Oct 2013 15:50:19 -0500 Subject: CXMAN-246: Add node ID to Node.__str__ --- 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 4973242..cd7dc57 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -121,7 +121,7 @@ class Node(object): return hash(self.ip_address) def __str__(self): - return 'Node: %s' % self.ip_address + return 'Node %i (%s)' % (self.node_id, self.ip_address) @property def tftp_address(self): -- cgit v1.2.1 From 051fe46d3f04f0d344ed766861062544679a567f Mon Sep 17 00:00:00 2001 From: George Kraft Date: Mon, 4 Nov 2013 10:09:19 -0600 Subject: CXMAN-250: Factor out the empty file check in fabric commands --- cxmanage_api/node.py | 32 +++----------------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index cd7dc57..9e2b42d 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1181,10 +1181,6 @@ communication. if (node_ip_address != "0.0.0.0"): results[node_id] = node_ip_address - # Make sure we found something - if (not results): - raise TftpException("Node failed to reach TFTP server") - return results def get_fabric_macaddrs(self): @@ -1230,10 +1226,6 @@ communication. results[node_id][port] = [] results[node_id][port].append(mac_address) - # Make sure we found something - if (not results): - raise TftpException("Node failed to reach TFTP server") - return results def get_fabric_uplink_info(self): @@ -1268,10 +1260,6 @@ communication. node_data[data[0]] = int(data[1]) results[node_id] = node_data - # Make sure we found something - if (not results): - raise TftpException("Node failed to reach TFTP server") - return results def get_link_stats(self, link=0): @@ -1326,10 +1314,6 @@ communication. ).replace('(link)', '').strip() ] = reg_value[1].strip() - # Make sure we found something - if (not results): - raise TftpException("Node failed to reach TFTP server") - return results def get_linkmap(self): @@ -1357,10 +1341,6 @@ communication. node_id = int(elements[3].strip()) results[link_id] = node_id - # Make sure we found something - if (not results): - raise TftpException("Node failed to reach TFTP server") - return results def get_routing_table(self): @@ -1390,10 +1370,6 @@ communication. rt_entries.append(int(entry)) results[node_id] = rt_entries - # Make sure we found something - if (not results): - raise TftpException("Node failed to reach TFTP server") - return results def get_depth_chart(self): @@ -1441,10 +1417,6 @@ communication. results[target] = dchrt_entries - # Make sure we found something - if (not results): - raise TftpException("Node failed to reach TFTP server") - return results def get_server_ip(self, interface=None, ipv6=False, user="user1", @@ -1587,10 +1559,12 @@ obtained. self.tftp.get_file(src=basename, dest=filename) if (os.path.getsize(filename) > 0): break - except (TftpException, IOError): pass + if os.path.getsize(filename) == 0: + raise TftpException("Node failed to reach TFTP server") + return filename @staticmethod -- cgit v1.2.1 From dc014156fb71e1779b7757ee3253a83800e494a6 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Mon, 4 Nov 2013 12:22:12 -0600 Subject: CXMAN-250: Retry 3 times to get fabric IP info The ECME can sometimes give us incomplete info, so, let's retry a couple of times and raise a ParseError if they all fail. --- cxmanage_api/cx_exceptions.py | 5 +++++ cxmanage_api/node.py | 39 ++++++++++++++++++++++----------------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/cxmanage_api/cx_exceptions.py b/cxmanage_api/cx_exceptions.py index c4b2e0f..8f94955 100644 --- a/cxmanage_api/cx_exceptions.py +++ b/cxmanage_api/cx_exceptions.py @@ -401,4 +401,9 @@ class IPDiscoveryError(Exception): """String representation of this Exception class.""" return self.msg + +class ParseError(Exception): + """Raised when there's an error parsing some output""" + pass + # End of file: exceptions.py diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 9e2b42d..9b0c43a 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -36,6 +36,7 @@ import os import re import time import tempfile +import socket import subprocess from pkg_resources import parse_version @@ -52,7 +53,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, EEPROMUpdateError + PartitionInUseError, UbootenvError, EEPROMUpdateError, ParseError # pylint: disable=R0902, R0904 @@ -1162,26 +1163,30 @@ communication. :raises IpmiError: If the IPMI command fails. :raises TftpException: If the TFTP transfer fails. + :raises ParseError: If we fail to parse IP info """ - filename = self._run_fabric_command( - function_name='fabric_config_get_ip_info' - ) - - # Parse addresses from ipinfo file - results = {} - for line in open(filename): - if (line.startswith("Node")): - elements = line.split() - node_id = int(elements[1].rstrip(":")) - node_ip_address = elements[2] + for _ in range(3): + try: + filename = self._run_fabric_command( + function_name='fabric_config_get_ip_info' + ) - # Old boards used to return 0.0.0.0 sometimes -- might not be - # an issue anymore. - if (node_ip_address != "0.0.0.0"): - results[node_id] = node_ip_address + results = {} + for line in open(filename): + if line.strip(): + elements = line.split() + node_id = int(elements[1].rstrip(":")) + ip_address = elements[2] + socket.inet_aton(ip_address) + results[node_id] = ip_address + return results + except (IndexError, ValueError, socket.error): + pass - return results + raise ParseError( + "Failed to parse fabric IP info\n%s" % open(filename).read() + ) def get_fabric_macaddrs(self): """Gets what macaddr information THIS node knows about the Fabric. -- cgit v1.2.1 From 20db6ec3ef58d6a8e88c0c40a03dde123bba6650 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Mon, 4 Nov 2013 12:49:01 -0600 Subject: CXMAN-250: Retry 3 times to get fabric MAC addresses --- cxmanage_api/node.py | 56 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 9b0c43a..f734b9b 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1178,7 +1178,7 @@ communication. elements = line.split() node_id = int(elements[1].rstrip(":")) ip_address = elements[2] - socket.inet_aton(ip_address) + socket.inet_aton(ip_address) # IP validity check results[node_id] = ip_address return results except (IndexError, ValueError, socket.error): @@ -1210,28 +1210,46 @@ communication. :raises IpmiError: If the IPMI command fails. :raises TftpException: If the TFTP transfer fails. + :raises ParseError: If we fail to parse macaddrs output """ - filename = self._run_fabric_command( - function_name='fabric_config_get_mac_addresses' - ) - - # Parse addresses from ipinfo file - results = {} - for line in open(filename): - if (line.startswith("Node")): - elements = line.split() - node_id = int(elements[1].rstrip(",")) - port = int(elements[3].rstrip(":")) - mac_address = elements[4] + for _ in range(3): + try: + filename = self._run_fabric_command( + function_name='fabric_config_get_mac_addresses' + ) - if not node_id in results: - results[node_id] = {} - if not port in results[node_id]: - results[node_id][port] = [] - results[node_id][port].append(mac_address) + results = {} + for line in open(filename): + if (line.startswith("Node")): + elements = line.split() + node_id = int(elements[1].rstrip(",")) + port = int(elements[3].rstrip(":")) + mac_address = elements[4] + + # MAC address validity check + octets = [int(x, 16) for x in mac_address.split(":")] + if len(octets) != 6: + raise ParseError( + "Invalid MAC address: %s" % mac_address + ) + elif not all(x <= 255 and x >= 0 for x in octets): + raise ParseError( + "Invalid MAC address: %s" % mac_address + ) + + if not node_id in results: + results[node_id] = {} + if not port in results[node_id]: + results[node_id][port] = [] + results[node_id][port].append(mac_address) + return results + except (ValueError, IndexError, ParseError): + pass - return results + raise ParseError( + "Failed to parse MAC addresses\n%s" % open(filename).read() + ) def get_fabric_uplink_info(self): """Gets what uplink information THIS node knows about the Fabric. -- cgit v1.2.1 From 2a0699876e46ef568129f7216f70d26aa2280b9f Mon Sep 17 00:00:00 2001 From: George Kraft Date: Tue, 5 Nov 2013 12:02:06 -0600 Subject: CXMAN-237: Use sel_elist instead of sel_list --- 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 f734b9b..da0c6b5 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -337,7 +337,7 @@ Initiated by power up | Asserted', :returns: The node's system event log :rtype: string """ - return self.bmc.sel_list() + return self.bmc.sel_elist() def get_sensors(self, search=""): """Get a list of sensor objects that match search criteria. -- cgit v1.2.1 From f0a8672967a708136151a3b42150e7ee50ec2e40 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Tue, 5 Nov 2013 12:12:50 -0600 Subject: CXMAN-242: Make Node._read_fru be a public method --- cxmanage_api/node.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index da0c6b5..c92bdc5 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1560,6 +1560,26 @@ obtained. """ return self.bmc.fabric_get_uplink_info().strip() + 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. + + :param fru_number: FRU image to read + :type fru_number: integer + :param offset: File offset + :type offset: integer + :param bytes_to_read: Number of bytes to read + :type bytes_to_read: integer + + :return: The data read from FRU + :rtype: string + + """ + with tempfile.NamedTemporaryFile(delete=True) as hexfile: + self.bmc.fru_read(fru_number, hexfile.name) + hexfile.seek(offset) + return(hexfile.read(bytes_to_read)) + def _run_fabric_command(self, function_name, **kwargs): """Handles the basics of sending a node a command for fabric data.""" filename = temp_file() @@ -1801,16 +1821,5 @@ obtained. "Unable to increment SIMG priority, too high") return priority - 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: - self.bmc.fru_read(fru_number, hexfile.name) - hexfile.seek(offset) - return(hexfile.read(bytes_to_read)) - # End of file: ./node.py -- cgit v1.2.1 From 081d083cff8c9e4477a93bc1ba5dc213a867021a Mon Sep 17 00:00:00 2001 From: George Kraft Date: Tue, 5 Nov 2013 12:28:57 -0600 Subject: CXMAN-242: Add ECME serial logs to tspackage --- cxmanage_api/cli/commands/tspackage.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/cxmanage_api/cli/commands/tspackage.py b/cxmanage_api/cli/commands/tspackage.py index c0c18c5..10b812d 100644 --- a/cxmanage_api/cli/commands/tspackage.py +++ b/cxmanage_api/cli/commands/tspackage.py @@ -115,6 +115,14 @@ def tspackage_command(args): print("Getting routing tables...") write_routing_table(args, nodes) + if not quiet: + print("Getting serial log...") + write_serial_log(args, nodes) + + if not quiet: + print("Getting crash log...") + write_crash_log(args, nodes) + # Archive the files archive(os.getcwd(), original_dir) @@ -358,6 +366,30 @@ def write_routing_table(args, nodes): write_to_file(node, lines) +def write_serial_log(args, nodes): + """Write the serial log for each node""" + results, errors = run_command(args, nodes, "read_fru", 98) + for node in nodes: + lines = ["\n[ Serial log for Node %d ]" % node.node_id] + if node in results: + lines.append(results[node].strip()) + else: + lines.append(str(errors[node])) + write_to_file(node, lines) + + +def write_crash_log(args, nodes): + """Write the crash log for each node""" + results, errors = run_command(args, nodes, "read_fru", 99) + for node in nodes: + lines = ["\n[ Crash log for Node %d ]" % node.node_id] + if node in results: + lines.append(results[node].strip()) + else: + lines.append(str(errors[node])) + 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. -- cgit v1.2.1 From 717ffbbd14a8669b32bb4056ba52f01b7f0c25b1 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Tue, 5 Nov 2013 13:55:40 -0600 Subject: CXMAN-236: Add client-side info to tspackage Operating system/distro, tool versions, and python packages --- cxmanage_api/cli/commands/tspackage.py | 37 +++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/cxmanage_api/cli/commands/tspackage.py b/cxmanage_api/cli/commands/tspackage.py index 10b812d..c10054a 100644 --- a/cxmanage_api/cli/commands/tspackage.py +++ b/cxmanage_api/cli/commands/tspackage.py @@ -41,10 +41,13 @@ import os -import time +import pkg_resources import shutil +import subprocess +import sys import tarfile import tempfile +import time from cxmanage_api.cli import get_tftp, get_nodes, run_command, COMPONENTS @@ -83,6 +86,8 @@ def tspackage_command(args): quiet = args.quiet + write_client_info() + if not quiet: print("Getting version information...") write_version_info(args, nodes) @@ -130,6 +135,36 @@ def tspackage_command(args): shutil.rmtree(temp_dir) +def write_client_info(): + """ Write client-side info """ + with open("client.txt", "w") as fout: + def write_command(command): + """ Safely write output from a single command to the file """ + try: + fout.write(subprocess.check_output( + command, stderr=subprocess.STDOUT, shell=True + )) + except subprocess.CalledProcessError: + pass + + fout.write("[ Operating System ]\n") + fout.write("Operating system: %s\n" % sys.platform) + write_command("lsb_release -a") + write_command("uname -a") + + fout.write("\n[ Tool versions ]\n") + fout.write("Python %s\n" % sys.version.replace("\n", "")) + cxmanage_version = pkg_resources.require("cxmanage")[0].version + fout.write("cxmanage version %s\n" % cxmanage_version) + pyipmi_version = pkg_resources.require("pyipmi")[0].version + fout.write("pyipmi version %s\n" % pyipmi_version) + ipmitool_path = os.environ.get('IPMITOOL_PATH', 'ipmitool') + write_command("%s -V" % ipmitool_path) + + fout.write("\n[ Python packages ]\n") + write_command("pip freeze") + + def write_version_info(args, nodes): """Write the version info (like cxmanage info) for each node to their respective files. -- cgit v1.2.1 From bcc088e727a5a3b8772a04b74e1bbee383412310 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Tue, 5 Nov 2013 15:03:13 -0600 Subject: CXMAN-251: Clean up whitespace in tspackage output --- cxmanage_api/cli/commands/tspackage.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/cxmanage_api/cli/commands/tspackage.py b/cxmanage_api/cli/commands/tspackage.py index c10054a..e2663ba 100644 --- a/cxmanage_api/cli/commands/tspackage.py +++ b/cxmanage_api/cli/commands/tspackage.py @@ -174,16 +174,10 @@ def write_version_info(args, nodes): 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, + lines = [ "[ Version Info for Node %d ]" % node.node_id, - add_newlines=False - ) - - lines.append("ECME IP Address : %s" % node.ip_address) + "ECME IP Address : %s" % node.ip_address + ] if node in info_results: info_result = info_results[node] @@ -440,9 +434,7 @@ def write_to_file(node, to_write, add_newlines=True): """ 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)) + node_file.write("%s\n" % "\n".join(to_write)) else: node_file.write("".join(to_write)) -- cgit v1.2.1 From 4ce4f00adfb39923773f85e35ad04c6fb01183de Mon Sep 17 00:00:00 2001 From: George Kraft Date: Tue, 5 Nov 2013 17:01:07 -0600 Subject: CXMAN-231: Add PMIC version to get_versions and tspackage --- cxmanage_api/cli/__init__.py | 1 + cxmanage_api/node.py | 5 +++++ cxmanage_test/node_test.py | 3 +++ 3 files changed, 9 insertions(+) diff --git a/cxmanage_api/cli/__init__.py b/cxmanage_api/cli/__init__.py index 0d72116..8e6e6f8 100644 --- a/cxmanage_api/cli/__init__.py +++ b/cxmanage_api/cli/__init__.py @@ -55,6 +55,7 @@ COMPONENTS = [ ("node_eeprom_config", "Node EEPROM config"), ("slot_eeprom_version", "Slot EEPROM version"), ("slot_eeprom_config", "Slot EEPROM config"), + ("pmic_version", "PMIC version") ] diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index c92bdc5..66f0d67 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1072,6 +1072,11 @@ communication. # to continue gracefully if the ECME is out of date. result.hardware_version = "Unknown" + try: + result.pmic_version = self.bmc.pmic_get_version() + except IpmiError: + pass + return result def get_versions_dict(self): diff --git a/cxmanage_test/node_test.py b/cxmanage_test/node_test.py index 2f0e83f..1853761 100644 --- a/cxmanage_test/node_test.py +++ b/cxmanage_test/node_test.py @@ -1011,6 +1011,9 @@ class DummyBMC(LanBMC): # Writes a fake FRU image with version "0.0" fru_image.write("x00" * 516 + "0.0" + "x00"*7673) + def pmic_get_version(self): + return "0" + # pylint: disable=R0913, R0903 class Partition(object): -- cgit v1.2.1 From e08354f9c7b26511eeb01209a9a64f5e4b31b6f5 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Tue, 5 Nov 2013 17:39:46 -0600 Subject: nojira: Bump to v0.10.0, bump pyipmi/ipmitool requirements --- scripts/cxmanage | 4 ++-- setup.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/cxmanage b/scripts/cxmanage index ea93997..9cb5d11 100755 --- a/scripts/cxmanage +++ b/scripts/cxmanage @@ -51,8 +51,8 @@ from cxmanage_api.cli.commands.tspackage import tspackage_command from cxmanage_api.cli.commands.eeprom import eepromupdate_command -PYIPMI_VERSION = '0.8.0' -IPMITOOL_VERSION = '1.8.11.0-cx7' +PYIPMI_VERSION = '0.9.0' +IPMITOOL_VERSION = '1.8.11.0-cx8' PARSER_EPILOG = """examples: diff --git a/setup.py b/setup.py index c44b83a..98aeaea 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ from setuptools import setup setup( name='cxmanage', - version='0.9.0', + version='0.10.0', packages=[ 'cxmanage_api', 'cxmanage_api.cli', @@ -50,7 +50,7 @@ setup( install_requires=[ 'tftpy', 'pexpect', - 'pyipmi>=0.8.0', + 'pyipmi>=0.9.0', 'argparse', 'unittest-xml-reporting<1.6.0' ], -- cgit v1.2.1 From 06768cad65ba1ae9c0b7e437ced811477cd4fbd7 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Thu, 7 Nov 2013 12:19:35 -0600 Subject: nojira: Updated copyright headers --- LICENSE | 2 +- cxmanage_api/__init__.py | 2 +- cxmanage_api/cli/__init__.py | 2 +- cxmanage_api/cli/commands/__init__.py | 2 +- cxmanage_api/cli/commands/config.py | 2 +- cxmanage_api/cli/commands/eeprom.py | 2 +- cxmanage_api/cli/commands/fabric.py | 2 +- cxmanage_api/cli/commands/fw.py | 2 +- cxmanage_api/cli/commands/info.py | 2 +- cxmanage_api/cli/commands/ipdiscover.py | 2 +- cxmanage_api/cli/commands/ipmitool.py | 2 +- cxmanage_api/cli/commands/mc.py | 2 +- cxmanage_api/cli/commands/power.py | 2 +- cxmanage_api/cli/commands/sensor.py | 2 +- cxmanage_api/cli/commands/tspackage.py | 2 +- cxmanage_api/crc32.py | 2 +- cxmanage_api/cx_exceptions.py | 2 +- cxmanage_api/docs/generate_api_rst.py | 2 +- cxmanage_api/fabric.py | 2 +- cxmanage_api/firmware_package.py | 2 +- cxmanage_api/image.py | 2 +- cxmanage_api/ip_retriever.py | 2 +- cxmanage_api/loggers.py | 2 +- cxmanage_api/node.py | 2 +- cxmanage_api/simg.py | 2 +- cxmanage_api/tasks.py | 2 +- cxmanage_api/tftp.py | 2 +- cxmanage_api/ubootenv.py | 2 +- cxmanage_test/__init__.py | 2 +- cxmanage_test/fabric_test.py | 2 +- cxmanage_test/image_test.py | 2 +- cxmanage_test/node_test.py | 2 +- cxmanage_test/tasks_test.py | 2 +- cxmanage_test/tftp_test.py | 2 +- run_tests | 2 +- scripts/cxmanage | 2 +- scripts/cxmux | 30 ++++++++++++++++++++++++++++++ scripts/sol_tabs | 2 +- setup.py | 2 +- 39 files changed, 68 insertions(+), 38 deletions(-) diff --git a/LICENSE b/LICENSE index affd7ab..89da6d8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012, Calxeda Inc. +Copyright (c) 2012-2013, Calxeda Inc. All rights reserved. diff --git a/cxmanage_api/__init__.py b/cxmanage_api/__init__.py index 4e7c0e4..9cff494 100644 --- a/cxmanage_api/__init__.py +++ b/cxmanage_api/__init__.py @@ -1,7 +1,7 @@ """Calxeda: __init__.py """ -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/cli/__init__.py b/cxmanage_api/cli/__init__.py index 8e6e6f8..f57a394 100644 --- a/cxmanage_api/cli/__init__.py +++ b/cxmanage_api/cli/__init__.py @@ -1,7 +1,7 @@ """Calxeda: __init__.py """ -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/cli/commands/__init__.py b/cxmanage_api/cli/commands/__init__.py index 571a3c5..b1cbfbb 100644 --- a/cxmanage_api/cli/commands/__init__.py +++ b/cxmanage_api/cli/commands/__init__.py @@ -1,7 +1,7 @@ """Calxeda: __init__.py""" -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/cli/commands/config.py b/cxmanage_api/cli/commands/config.py index bde21ca..23aa028 100644 --- a/cxmanage_api/cli/commands/config.py +++ b/cxmanage_api/cli/commands/config.py @@ -1,7 +1,7 @@ """Calxeda: config.py """ -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/cli/commands/eeprom.py b/cxmanage_api/cli/commands/eeprom.py index fa715ff..86ca1cd 100644 --- a/cxmanage_api/cli/commands/eeprom.py +++ b/cxmanage_api/cli/commands/eeprom.py @@ -1,6 +1,6 @@ """Calxeda: eeprom.py """ -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/cli/commands/fabric.py b/cxmanage_api/cli/commands/fabric.py index 9a410c1..0ee7805 100644 --- a/cxmanage_api/cli/commands/fabric.py +++ b/cxmanage_api/cli/commands/fabric.py @@ -1,7 +1,7 @@ """Calxeda: fabric.py""" -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/cli/commands/fw.py b/cxmanage_api/cli/commands/fw.py index b131bf9..6663152 100644 --- a/cxmanage_api/cli/commands/fw.py +++ b/cxmanage_api/cli/commands/fw.py @@ -1,6 +1,6 @@ """Calxeda: fw.py """ -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/cli/commands/info.py b/cxmanage_api/cli/commands/info.py index 9d92fa9..0968351 100644 --- a/cxmanage_api/cli/commands/info.py +++ b/cxmanage_api/cli/commands/info.py @@ -1,7 +1,7 @@ """Calxeda: info.py""" -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/cli/commands/ipdiscover.py b/cxmanage_api/cli/commands/ipdiscover.py index c6c3dee..fd21546 100644 --- a/cxmanage_api/cli/commands/ipdiscover.py +++ b/cxmanage_api/cli/commands/ipdiscover.py @@ -1,7 +1,7 @@ """Calxeda: ipdiscover.py""" -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/cli/commands/ipmitool.py b/cxmanage_api/cli/commands/ipmitool.py index 2c54b37..ab95bbf 100644 --- a/cxmanage_api/cli/commands/ipmitool.py +++ b/cxmanage_api/cli/commands/ipmitool.py @@ -1,7 +1,7 @@ """Calxeda: ipmitool.py""" -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/cli/commands/mc.py b/cxmanage_api/cli/commands/mc.py index ac258ab..86e0963 100644 --- a/cxmanage_api/cli/commands/mc.py +++ b/cxmanage_api/cli/commands/mc.py @@ -1,7 +1,7 @@ """Calxeda: mc.py""" -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/cli/commands/power.py b/cxmanage_api/cli/commands/power.py index 1255cbc..c14de70 100644 --- a/cxmanage_api/cli/commands/power.py +++ b/cxmanage_api/cli/commands/power.py @@ -1,7 +1,7 @@ """Calxeda: power.py """ -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/cli/commands/sensor.py b/cxmanage_api/cli/commands/sensor.py index acbad6e..97b4aba 100644 --- a/cxmanage_api/cli/commands/sensor.py +++ b/cxmanage_api/cli/commands/sensor.py @@ -1,7 +1,7 @@ """Calxeda: sensor.py""" -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/cli/commands/tspackage.py b/cxmanage_api/cli/commands/tspackage.py index e2663ba..2cbfef8 100644 --- a/cxmanage_api/cli/commands/tspackage.py +++ b/cxmanage_api/cli/commands/tspackage.py @@ -1,7 +1,7 @@ """Calxeda: tspackage.py""" -# Copyright 2013 Calxeda, Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/crc32.py b/cxmanage_api/crc32.py index aca7838..aa85179 100644 --- a/cxmanage_api/crc32.py +++ b/cxmanage_api/crc32.py @@ -1,4 +1,4 @@ -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/cx_exceptions.py b/cxmanage_api/cx_exceptions.py index 8f94955..5e0c931 100644 --- a/cxmanage_api/cx_exceptions.py +++ b/cxmanage_api/cx_exceptions.py @@ -1,7 +1,7 @@ """Calxeda: cx_exceptions.py""" -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/docs/generate_api_rst.py b/cxmanage_api/docs/generate_api_rst.py index 553d3c8..776dabe 100755 --- a/cxmanage_api/docs/generate_api_rst.py +++ b/cxmanage_api/docs/generate_api_rst.py @@ -2,7 +2,7 @@ :author: Eric Vasquez :contact: eric.vasquez@calxeda.com -:copyright: (c) 2012, Calxeda Inc. +:copyright: (c) 2012-2013, Calxeda Inc. """ diff --git a/cxmanage_api/fabric.py b/cxmanage_api/fabric.py index e143c32..7582aee 100644 --- a/cxmanage_api/fabric.py +++ b/cxmanage_api/fabric.py @@ -2,7 +2,7 @@ """Calxeda: fabric.py """ -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/firmware_package.py b/cxmanage_api/firmware_package.py index ce698c8..2822fd2 100644 --- a/cxmanage_api/firmware_package.py +++ b/cxmanage_api/firmware_package.py @@ -1,7 +1,7 @@ """Calxeda: firmware_package.py""" -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/image.py b/cxmanage_api/image.py index a6456d4..8f3011a 100644 --- a/cxmanage_api/image.py +++ b/cxmanage_api/image.py @@ -1,7 +1,7 @@ """Calxeda: image.py""" -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/ip_retriever.py b/cxmanage_api/ip_retriever.py index 39fbb30..ef443cb 100644 --- a/cxmanage_api/ip_retriever.py +++ b/cxmanage_api/ip_retriever.py @@ -1,7 +1,7 @@ """Calxeda: ip_retriever.py""" -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/loggers.py b/cxmanage_api/loggers.py index 9657ef9..1e4f4ab 100644 --- a/cxmanage_api/loggers.py +++ b/cxmanage_api/loggers.py @@ -1,4 +1,4 @@ -# Copyright (c) 2013, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 66f0d67..0977bf1 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -2,7 +2,7 @@ """Calxeda: node.py""" -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/simg.py b/cxmanage_api/simg.py index d8901b8..1870691 100644 --- a/cxmanage_api/simg.py +++ b/cxmanage_api/simg.py @@ -1,7 +1,7 @@ """Calxeda: simg.py""" -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/tasks.py b/cxmanage_api/tasks.py index 98fdd3e..d241c30 100644 --- a/cxmanage_api/tasks.py +++ b/cxmanage_api/tasks.py @@ -1,7 +1,7 @@ """Calxeda: tasks.py""" -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/tftp.py b/cxmanage_api/tftp.py index 59e9774..e3aaec3 100644 --- a/cxmanage_api/tftp.py +++ b/cxmanage_api/tftp.py @@ -1,7 +1,7 @@ """Calxeda: tftp.py""" -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_api/ubootenv.py b/cxmanage_api/ubootenv.py index da9b37c..14029bb 100644 --- a/cxmanage_api/ubootenv.py +++ b/cxmanage_api/ubootenv.py @@ -1,6 +1,6 @@ """Calxeda: ubootenv.py """ -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_test/__init__.py b/cxmanage_test/__init__.py index 61bf116..d8d5307 100644 --- a/cxmanage_test/__init__.py +++ b/cxmanage_test/__init__.py @@ -1,7 +1,7 @@ """Calxeda: __init__.py""" -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_test/fabric_test.py b/cxmanage_test/fabric_test.py index d2a6e28..69e7c03 100644 --- a/cxmanage_test/fabric_test.py +++ b/cxmanage_test/fabric_test.py @@ -1,6 +1,6 @@ """Calxeda: fabric_test.py """ -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_test/image_test.py b/cxmanage_test/image_test.py index fcbb011..71e8000 100644 --- a/cxmanage_test/image_test.py +++ b/cxmanage_test/image_test.py @@ -1,7 +1,7 @@ """Calxeda: image_test.py""" -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_test/node_test.py b/cxmanage_test/node_test.py index 1853761..dbe78a1 100644 --- a/cxmanage_test/node_test.py +++ b/cxmanage_test/node_test.py @@ -2,7 +2,7 @@ """Unit tests for the Node class.""" -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_test/tasks_test.py b/cxmanage_test/tasks_test.py index 5ede9e4..2d7b9a3 100644 --- a/cxmanage_test/tasks_test.py +++ b/cxmanage_test/tasks_test.py @@ -1,7 +1,7 @@ """Calxeda: task_test.py""" -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/cxmanage_test/tftp_test.py b/cxmanage_test/tftp_test.py index 94d9e38..afd258f 100644 --- a/cxmanage_test/tftp_test.py +++ b/cxmanage_test/tftp_test.py @@ -1,7 +1,7 @@ """Calxeda: tftp_test.py""" -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/run_tests b/run_tests index bfc64c4..dc9a7b3 100755 --- a/run_tests +++ b/run_tests @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/scripts/cxmanage b/scripts/cxmanage index 9cb5d11..3e6f8b8 100755 --- a/scripts/cxmanage +++ b/scripts/cxmanage @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/scripts/cxmux b/scripts/cxmux index 938ad04..0630efd 100755 --- a/scripts/cxmux +++ b/scripts/cxmux @@ -1,5 +1,35 @@ #!/usr/bin/env python +# Copyright (c) 2012-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. + import os import sys diff --git a/scripts/sol_tabs b/scripts/sol_tabs index c5cb9fe..d6626ca 100755 --- a/scripts/sol_tabs +++ b/scripts/sol_tabs @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # diff --git a/setup.py b/setup.py index 98aeaea..c0e4bbc 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ """Calxeda: setup.py""" -# Copyright (c) 2012, Calxeda Inc. +# Copyright (c) 2012-2013, Calxeda Inc. # # All rights reserved. # -- cgit v1.2.1 From 044eb7e045ff24cd951ddf697f06644d5085d2a8 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Thu, 7 Nov 2013 12:20:59 -0600 Subject: nojira: Bump to v0.10.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c0e4bbc..f1414a8 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ from setuptools import setup setup( name='cxmanage', - version='0.10.0', + version='0.10.1', packages=[ 'cxmanage_api', 'cxmanage_api.cli', -- cgit v1.2.1 From c14717e81182c31d66c31b061f71ffc658cf2e3f Mon Sep 17 00:00:00 2001 From: "matthew.hodgins" Date: Fri, 8 Nov 2013 15:34:59 -0600 Subject: AIT-528 raise an exception when ipmitool_command returns with non-zero Signed-off-by: matthew.hodgins --- cxmanage_api/node.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 0977bf1..834a900 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1123,6 +1123,8 @@ communication. :param ipmitool_args: Arguments to pass to the ipmitool. :type ipmitool_args: list + + :raises IpmiError: If the IPMI command fails. """ if ("IPMITOOL_PATH" in os.environ): @@ -1140,6 +1142,8 @@ communication. process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = process.communicate() + if(process.returncode != 0): + raise IpmiError('IPMI command returned with non-zero exit code') return (stdout + stderr).strip() def get_ubootenv(self): -- cgit v1.2.1 From 2d015edb052c28a7211b7d6be4f856e3d30614cb Mon Sep 17 00:00:00 2001 From: "matthew.hodgins" Date: Fri, 8 Nov 2013 16:10:06 -0600 Subject: nojira raise entire stderr in IpmiError Signed-off-by: matthew.hodgins --- cxmanage_api/node.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 834a900..133ebb1 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1123,7 +1123,7 @@ communication. :param ipmitool_args: Arguments to pass to the ipmitool. :type ipmitool_args: list - + :raises IpmiError: If the IPMI command fails. """ @@ -1143,7 +1143,7 @@ communication. stderr=subprocess.PIPE) stdout, stderr = process.communicate() if(process.returncode != 0): - raise IpmiError('IPMI command returned with non-zero exit code') + raise IpmiError(stderr.strip()) return (stdout + stderr).strip() def get_ubootenv(self): @@ -1187,7 +1187,7 @@ communication. elements = line.split() node_id = int(elements[1].rstrip(":")) ip_address = elements[2] - socket.inet_aton(ip_address) # IP validity check + socket.inet_aton(ip_address) # IP validity check results[node_id] = ip_address return results except (IndexError, ValueError, socket.error): -- cgit v1.2.1 From 545098d5d051e574297aa71ecf74026095f51dd3 Mon Sep 17 00:00:00 2001 From: evasquez Date: Mon, 11 Nov 2013 17:14:23 -0600 Subject: AIT-537: Added support for Chassis ID in dummy node. Signed-off-by: evasquez --- cxmanage_test/fabric_test.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cxmanage_test/fabric_test.py b/cxmanage_test/fabric_test.py index 69e7c03..96f76e6 100644 --- a/cxmanage_test/fabric_test.py +++ b/cxmanage_test/fabric_test.py @@ -454,7 +454,12 @@ class DummyNode(object): self.sel = [] self.bmc = make_bmc(DummyBMC, hostname=ip_address, username=username, password=password, verbose=False) - + + @property + def chassis_id(self): + """Returns 0 for chasis ID.""" + return 0 + def get_sel(self): """Simulate get_sel()""" self.executed.append('get_sel') -- cgit v1.2.1 From a68b90e5c412d414776177bb6c2fdc01d6223f84 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Tue, 12 Nov 2013 03:16:44 -0600 Subject: CXMAN-254: Move version definition from setup.py to __init__.py This makes the cxmanage package accessible via the cxmanage_api.__version__ variable. --- cxmanage_api/__init__.py | 3 +++ setup.py | 10 +++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/cxmanage_api/__init__.py b/cxmanage_api/__init__.py index 9cff494..6c2a52e 100644 --- a/cxmanage_api/__init__.py +++ b/cxmanage_api/__init__.py @@ -38,6 +38,9 @@ import shutil import tempfile +__version__ = "0.10.1" + + WORK_DIR = tempfile.mkdtemp(prefix="cxmanage_api-") atexit.register(lambda: shutil.rmtree(WORK_DIR)) diff --git a/setup.py b/setup.py index f1414a8..060f8fe 100644 --- a/setup.py +++ b/setup.py @@ -34,9 +34,17 @@ from setuptools import setup +def get_version(): + """ Parse __init__.py to find the package version """ + for line in open("cxmanage_api/__init__.py"): + key, delim, value = line.partition("=") + if key.strip() == "__version__" and delim == "=": + return value.strip().strip("'\"") + raise Exception("Failed to parse cxmanage package version from __init__.py") + setup( name='cxmanage', - version='0.10.1', + version=get_version(), packages=[ 'cxmanage_api', 'cxmanage_api.cli', -- cgit v1.2.1 From ee5d497e3fdde077b721279375e729d278c8c536 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Tue, 12 Nov 2013 03:32:55 -0600 Subject: CXMAN-254: Remove pkg_resources usage in tspackage.py --- cxmanage_api/cli/commands/tspackage.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cxmanage_api/cli/commands/tspackage.py b/cxmanage_api/cli/commands/tspackage.py index 2cbfef8..a5ebf15 100644 --- a/cxmanage_api/cli/commands/tspackage.py +++ b/cxmanage_api/cli/commands/tspackage.py @@ -41,7 +41,6 @@ import os -import pkg_resources import shutil import subprocess import sys @@ -49,6 +48,8 @@ import tarfile import tempfile import time +import pyipmi +import cxmanage_api from cxmanage_api.cli import get_tftp, get_nodes, run_command, COMPONENTS @@ -154,10 +155,8 @@ def write_client_info(): fout.write("\n[ Tool versions ]\n") fout.write("Python %s\n" % sys.version.replace("\n", "")) - cxmanage_version = pkg_resources.require("cxmanage")[0].version - fout.write("cxmanage version %s\n" % cxmanage_version) - pyipmi_version = pkg_resources.require("pyipmi")[0].version - fout.write("pyipmi version %s\n" % pyipmi_version) + fout.write("cxmanage version %s\n" % cxmanage_api.__version__) + fout.write("pyipmi version %s\n" % pyipmi.__version__) ipmitool_path = os.environ.get('IPMITOOL_PATH', 'ipmitool') write_command("%s -V" % ipmitool_path) -- cgit v1.2.1 From b6bbd565b2f4672ed0a71dc9972635535bfb68f5 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Tue, 12 Nov 2013 03:41:28 -0600 Subject: CXMAN-254: Remove pkg_resources.require usage in cxmanage script Still using pkg_resources.parse_version, that's ok for now. --- scripts/cxmanage | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/scripts/cxmanage b/scripts/cxmanage index 3e6f8b8..875afb7 100755 --- a/scripts/cxmanage +++ b/scripts/cxmanage @@ -36,6 +36,8 @@ import pkg_resources import subprocess import sys +import pyipmi +import cxmanage_api 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 @@ -335,29 +337,15 @@ def validate_args(args): sys.exit('Invalid argument --version when supplied with --skip-simg') -def print_version(): - """ Print the current version of cxmanage """ - version = pkg_resources.require('cxmanage')[0].version - print "cxmanage version %s" % version - - def check_versions(): """Check versions of dependencies""" # Check pyipmi version - try: - pkg_resources.require('pyipmi>=%s' % PYIPMI_VERSION) - except pkg_resources.DistributionNotFound: - print 'ERROR: cxmanage requires pyipmi version %s'\ - % PYIPMI_VERSION - print 'No existing version was found.' - sys.exit(1) - except pkg_resources.VersionConflict: - version = pkg_resources.require('pyipmi')[0].version + if (pkg_resources.parse_version(pyipmi.__version__) < + pkg_resources.parse_version(PYIPMI_VERSION)): print 'ERROR: cxmanage requires pyipmi version %s' % PYIPMI_VERSION - print 'Current pyipmi version is %s' % version + print 'Current pyipmi version is %s' % pyipmi.__version__ sys.exit(1) - # Check ipmitool version if 'IPMITOOL_PATH' in os.environ: args = [os.environ['IPMITOOL_PATH'], '-V'] @@ -384,7 +372,7 @@ def main(): """Get args and go""" for arg in sys.argv[1:]: if arg in ['-V', '--version']: - print_version() + print "cxmanage version %s" % cxmanage_api.__version__ sys.exit(0) elif arg[0] != '-': break -- cgit v1.2.1 From 082f03ee0d47bab30e9185866008da24ca8048b2 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Tue, 12 Nov 2013 03:46:21 -0600 Subject: CXMAN-254: Remove pkg_resources.require usage from firmware_package --- cxmanage_api/firmware_package.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/cxmanage_api/firmware_package.py b/cxmanage_api/firmware_package.py index 2822fd2..7f8e645 100644 --- a/cxmanage_api/firmware_package.py +++ b/cxmanage_api/firmware_package.py @@ -37,6 +37,7 @@ import tarfile import ConfigParser import pkg_resources +import cxmanage_api from cxmanage_api import temp_dir from cxmanage_api.image import Image @@ -81,15 +82,16 @@ class FirmwarePackage(object): % os.path.basename(filename)) if "package" in config.sections(): - cxmanage_ver = config.get("package", - "required_cxmanage_version") - try: - pkg_resources.require("cxmanage>=%s" % cxmanage_ver) - except pkg_resources.VersionConflict: + required_cxmanage_version = config.get( + "package", "required_cxmanage_version" + ) + if (pkg_resources.parse_version(cxmanage_api.__version__) < + pkg_resources.parse_version(required_cxmanage_version)): # @todo: CxmanageVersionError? raise ValueError( - "%s requires cxmanage version %s or later." - % (filename, cxmanage_ver)) + "%s requires cxmanage version %s or later." + % (filename, required_cxmanage_version) + ) if config.has_option("package", "required_socman_version"): self.required_socman_version = config.get("package", -- cgit v1.2.1 From 4da2f9c7eaa95ebf357eeca3a497b6a206675ef8 Mon Sep 17 00:00:00 2001 From: George Kraft Date: Tue, 12 Nov 2013 07:42:21 -0600 Subject: nojira: Bump to cxmanage v0.10.2, pyipmi requirement to v0.9.1 --- cxmanage_api/__init__.py | 2 +- scripts/cxmanage | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cxmanage_api/__init__.py b/cxmanage_api/__init__.py index 6c2a52e..c676a78 100644 --- a/cxmanage_api/__init__.py +++ b/cxmanage_api/__init__.py @@ -38,7 +38,7 @@ import shutil import tempfile -__version__ = "0.10.1" +__version__ = "0.10.2" WORK_DIR = tempfile.mkdtemp(prefix="cxmanage_api-") diff --git a/scripts/cxmanage b/scripts/cxmanage index 875afb7..66c6269 100755 --- a/scripts/cxmanage +++ b/scripts/cxmanage @@ -53,7 +53,7 @@ from cxmanage_api.cli.commands.tspackage import tspackage_command from cxmanage_api.cli.commands.eeprom import eepromupdate_command -PYIPMI_VERSION = '0.9.0' +PYIPMI_VERSION = '0.9.1' IPMITOOL_VERSION = '1.8.11.0-cx8' diff --git a/setup.py b/setup.py index 060f8fe..22e5533 100644 --- a/setup.py +++ b/setup.py @@ -58,7 +58,7 @@ setup( install_requires=[ 'tftpy', 'pexpect', - 'pyipmi>=0.9.0', + 'pyipmi>=0.9.1', 'argparse', 'unittest-xml-reporting<1.6.0' ], -- cgit v1.2.1