diff options
38 files changed, 1382 insertions, 344 deletions
@@ -1,3 +1,5 @@ tags *.pyc cxmanage.egg-info +.project +.pydevproject diff --git a/cxmanage_api/__init__.py b/cxmanage_api/__init__.py index 8c83888..2bb8907 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. @@ -47,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(): diff --git a/cxmanage/__init__.py b/cxmanage_api/cli/__init__.py index e2d416a..438d568 100644 --- a/cxmanage/__init__.py +++ b/cxmanage_api/cli/__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 @@ -71,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 = [] @@ -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") @@ -92,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, @@ -104,13 +106,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 +121,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) @@ -135,6 +140,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: @@ -147,7 +153,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: @@ -182,11 +190,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 +216,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: @@ -220,6 +230,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: @@ -232,8 +243,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: @@ -243,8 +257,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='): @@ -275,12 +292,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])) @@ -300,17 +318,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): @@ -331,7 +352,9 @@ COMPONENTS = [ ("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") + ("dtb_version", "DTB version"), ] + diff --git a/cxmanage/commands/__init__.py b/cxmanage_api/cli/commands/__init__.py index 2160043..571a3c5 100644 --- a/cxmanage/commands/__init__.py +++ b/cxmanage_api/cli/commands/__init__.py @@ -1,3 +1,6 @@ +"""Calxeda: __init__.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. diff --git a/cxmanage/commands/config.py b/cxmanage_api/cli/commands/config.py index 3d5b060..bde21ca 100644 --- a/cxmanage/commands/config.py +++ b/cxmanage_api/cli/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.cli import get_tftp, get_nodes, get_node_strings, run_command + +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) diff --git a/cxmanage/commands/fabric.py b/cxmanage_api/cli/commands/fabric.py index 3bf84c2..9a410c1 100644 --- a/cxmanage/commands/fabric.py +++ b/cxmanage_api/cli/commands/fabric.py @@ -1,3 +1,6 @@ +"""Calxeda: fabric.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -28,7 +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 +from cxmanage_api.cli import get_tftp, get_nodes, run_command def ipinfo_command(args): @@ -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_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/commands/fw.py b/cxmanage_api/cli/commands/fw.py index 99ed4fe..b131bf9 100644 --- a/cxmanage/commands/fw.py +++ b/cxmanage_api/cli/commands/fw.py @@ -1,3 +1,5 @@ +"""Calxeda: fw.py """ + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -31,13 +33,13 @@ from pkg_resources import parse_version -from cxmanage import get_tftp, get_nodes, get_node_strings, run_command, \ - prompt_yes +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(): @@ -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) diff --git a/cxmanage/commands/info.py b/cxmanage_api/cli/commands/info.py index b1a03c0..0f0b2ca 100644 --- a/cxmanage/commands/info.py +++ b/cxmanage_api/cli/commands/info.py @@ -1,3 +1,6 @@ +"""Calxeda: info.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -28,8 +31,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 + +from cxmanage_api.cli import get_tftp, get_nodes, get_node_strings, \ + run_command, COMPONENTS def info_command(args): @@ -42,7 +46,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,9 +59,13 @@ def info_basic_command(args): 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) @@ -72,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_api/cli/commands/ipdiscover.py index f619d16..c6c3dee 100644 --- a/cxmanage/commands/ipdiscover.py +++ b/cxmanage_api/cli/commands/ipdiscover.py @@ -1,3 +1,6 @@ +"""Calxeda: ipdiscover.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -28,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 ipdiscover_command(args): diff --git a/cxmanage/commands/ipmitool.py b/cxmanage_api/cli/commands/ipmitool.py index f8baf80..2c54b37 100644 --- a/cxmanage/commands/ipmitool.py +++ b/cxmanage_api/cli/commands/ipmitool.py @@ -1,3 +1,6 @@ +"""Calxeda: ipmitool.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -28,8 +31,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_api.cli import get_tftp, get_nodes, get_node_strings, run_command def ipmitool_command(args): """run arbitrary ipmitool command""" diff --git a/cxmanage/commands/mc.py b/cxmanage_api/cli/commands/mc.py index 2573540..ac258ab 100644 --- a/cxmanage/commands/mc.py +++ b/cxmanage_api/cli/commands/mc.py @@ -1,3 +1,6 @@ +"""Calxeda: mc.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -28,7 +31,8 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. -from cxmanage import get_tftp, get_nodes, run_command + +from cxmanage_api.cli import get_tftp, get_nodes, run_command def mcreset_command(args): @@ -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/power.py b/cxmanage_api/cli/commands/power.py index b5b6015..623c38d 100644 --- a/cxmanage/commands/power.py +++ b/cxmanage_api/cli/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) diff --git a/cxmanage/commands/sensor.py b/cxmanage_api/cli/commands/sensor.py index c3fed32..3a27143 100644 --- a/cxmanage/commands/sensor.py +++ b/cxmanage_api/cli/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_api/cli/commands/tspackage.py index 1a37e6e..d6ee198 100644 --- a/cxmanage/commands/tspackage.py +++ b/cxmanage_api/cli/commands/tspackage.py @@ -1,6 +1,43 @@ -#!/usr/bin/env python - -# Copyright 2013 Calxeda, Inc. All Rights Reserved. +"""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 @@ -9,8 +46,7 @@ import shutil import tarfile import tempfile -from cxmanage import get_tftp, get_nodes, run_command -from cxmanage import COMPONENTS +from cxmanage import get_tftp, get_nodes, run_command, COMPONENTS def tspackage_command(args): @@ -52,6 +88,14 @@ def tspackage_command(args): 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) @@ -93,8 +137,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 +160,9 @@ def write_version_info(args, nodes): "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) @@ -127,6 +172,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.""" @@ -153,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 = "" @@ -258,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: diff --git a/cxmanage_api/cx_exceptions.py b/cxmanage_api/cx_exceptions.py index f390392..df2dcc7 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,12 +31,22 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. -"""Defines the custom exceptions used by the cxmanage_api project.""" +# +# 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. +# + + class TimeoutError(Exception): """Raised when a timeout has been reached. @@ -304,6 +317,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 @@ -363,5 +377,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 "<stdin>", line 1, in <module> + 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/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/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. diff --git a/cxmanage_api/fabric.py b/cxmanage_api/fabric.py index bd8d6a7..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): @@ -237,16 +243,30 @@ 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.). + :type async: boolean + + :return: The uplink info for each node. + :rtype: dictionary + + """ + 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. + + >>> 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.). @@ -256,7 +276,7 @@ class Fabric(object): :rtype: dictionary """ - return self.primary_node.get_fabric_uplink_info() + return self._run_on_all_nodes(async, "get_uplink_speed") def get_power(self, async=False): """Returns the power status for all nodes. @@ -274,7 +294,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 ... @@ -290,9 +310,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. @@ -585,7 +609,8 @@ class Fabric(object): } .. seealso:: - `Node.get_versions() <node.html#cxmanage_api.node.Node.get_versions>`_ + `Node.get_versions() \ +<node.html#cxmanage_api.node.Node.get_versions>`_ :param async: Flag that determines if the command result (dictionary) is returned or a Command object (can get status, etc.). @@ -624,7 +649,8 @@ class Fabric(object): } .. seealso:: - `Node.get_versions_dict() <node.html#cxmanage_api.node.Node.get_versions_dict>`_ + `Node.get_versions_dict() \ +<node.html#cxmanage_api.node.Node.get_versions_dict>`_ :param async: Flag that determines if the command result (dictionary) is returned or a Task object (can get status, etc.). @@ -683,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 @@ -813,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 @@ -863,6 +890,7 @@ class Fabric(object): :return: mac address mask :rtype: string + """ return self.primary_node.bmc.fabric_config_get_macaddr_mask() @@ -975,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. >>> # @@ -1054,11 +1082,50 @@ class Fabric(object): """ return self._run_on_all_nodes(async, "get_depth_chart") - def _run_on_all_nodes(self, async, name, *args): + 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 = {} 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 diff --git a/cxmanage_api/firmware_package.py b/cxmanage_api/firmware_package.py index 0ca5d04..7145861 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. @@ -39,7 +42,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:: @@ -55,6 +59,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 7cbd59f..7be88fb 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, "wb") as f: - f.write(simg) + with open(filename, "wb") as file_: + file_.write(simg) # Make sure the simg was built correctly if (not valid_simg(open(filename, "rb").read())): diff --git a/cxmanage_api/ip_retriever.py b/cxmanage_api/ip_retriever.py index 0bf7d5c..d750f12 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 @@ -46,9 +48,10 @@ 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 + """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 @@ -56,7 +59,7 @@ class IPRetriever(threading.Thread): retry = None timeout = None interface = None - + ecme_ip = None ecme_user = None ecme_password = None @@ -64,14 +67,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: @@ -79,7 +82,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) @@ -96,20 +99,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): @@ -120,11 +123,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) @@ -138,14 +143,14 @@ 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: 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: @@ -161,7 +166,7 @@ class IPRetriever(threading.Thread): """ server = Server(self._bmc) - if cycle: + if cycle: self._log('Powering server off') server.power_off() sleep(5) @@ -202,15 +207,16 @@ 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 - + + # 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 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) @@ -304,7 +310,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 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. diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py index 5408555..f0239ba 100644 --- a/cxmanage_api/node.py +++ b/cxmanage_api/node.py @@ -1,3 +1,7 @@ +# pylint: disable=C0302 +"""Calxeda: node.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -28,11 +32,9 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. - import os import re import time -import shutil import tempfile import subprocess @@ -50,9 +52,10 @@ 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 +# pylint: disable=R0902, R0904 class Node(object): """A node is a single instance of an ECME. @@ -78,7 +81,7 @@ class Node(object): :type ubootenv: `UbootEnv <ubootenv.html>`_ """ - + # 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): @@ -186,7 +189,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) @@ -201,7 +205,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) @@ -222,7 +227,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' @@ -236,8 +241,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): @@ -249,7 +263,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 @@ -305,9 +320,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 |', ... ] @@ -518,7 +536,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() @@ -568,7 +587,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()] @@ -590,10 +610,12 @@ class Node(object): 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 + # pylint: disable=R0914, R0912, R0915 def update_firmware(self, package, partition_arg="INACTIVE", priority=None): """ Update firmware on this target. @@ -728,8 +750,9 @@ class Node(object): ) filename = temp_file() - with open(filename, "wb") as f: - f.write(ubootenv.get_contents()) + with open(filename, "wb") as file_: + file_.write(ubootenv.get_contents()) + ubootenv_image = self.image(filename, image.type, False, image.daddr, image.skip_crc32, image.version) @@ -821,11 +844,12 @@ 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 - result = self.bmc.reset_firmware() + self.bmc.reset_firmware() # Reset ubootenv fwinfo = self.get_firmware_info() @@ -860,8 +884,8 @@ class Node(object): priority = max(int(x.priority, 16) for x in [first_part, active_part]) filename = temp_file() - with open(filename, "wb") as f: - f.write(ubootenv.get_contents()) + with open(filename, "wb") as file_: + file_.write(ubootenv.get_contents()) ubootenv_image = self.image(filename, image.type, False, image.daddr, image.skip_crc32, image.version) @@ -896,8 +920,8 @@ class Node(object): 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) @@ -925,20 +949,38 @@ 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. """ 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")] + fw_version = result.firmware_version + + # components maps variables to firmware partition types + components = [ + ("cdb_version", "CDB"), + ("stage2_version", "S2_ELF"), + ("bootlog_version", "BOOT_LOG") + ] + # 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")) + 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")) + for var, ptype in components: try: partition = self._get_partition(fwinfo, ptype, "ACTIVE") @@ -984,7 +1026,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. """ @@ -1146,8 +1189,8 @@ class Node(object): 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 @@ -1310,13 +1353,16 @@ class Node(object): 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: # pylint: disable=W0703 pass results[target] = dchrt_entries @@ -1348,8 +1394,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 @@ -1419,6 +1467,82 @@ 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_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 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.""" @@ -1428,7 +1552,7 @@ class Node(object): 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, @@ -1448,7 +1572,8 @@ class Node(object): 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 @@ -1507,9 +1632,13 @@ class Node(object): 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, 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): @@ -1532,9 +1661,13 @@ class Node(object): 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, 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): @@ -1595,13 +1728,9 @@ class Node(object): % (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( @@ -1631,13 +1760,16 @@ 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") 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] @@ -1651,4 +1783,16 @@ class Node(object): "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 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/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() diff --git a/cxmanage_api/tftp.py b/cxmanage_api/tftp.py index 02b7c49..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. @@ -29,9 +32,6 @@ # DAMAGE. -import os -import sys -import atexit import shutil import socket import logging @@ -43,8 +43,9 @@ from cxmanage_api import temp_dir from tftpy.TftpShared import TftpException -class InternalTftp(object): - """Internally serves files using the `Trivial File Transfer Protocol <http://en.wikipedia.org/wiki/Trivial_File_Transfer_Protocol>`_. +class InternalTftp(Thread): + """Internally serves files using the `Trivial File Transfer Protocol \ +<http://en.wikipedia.org/wiki/Trivial_File_Transfer_Protocol>`_. >>> # Typical instantiation ... >>> from cxmanage_api.tftp import InternalTftp @@ -62,54 +63,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 +110,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. @@ -153,7 +117,7 @@ class InternalTftp(object): :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 """ @@ -222,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_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: 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 diff --git a/cxmanage_test/fabric_test.py b/cxmanage_test/fabric_test.py index f2720c9..fe1a80c 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. @@ -44,12 +46,15 @@ from pyipmi import make_bmc 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)) @@ -75,11 +80,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 """ @@ -169,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 """ @@ -181,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() @@ -218,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() @@ -286,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): @@ -407,10 +422,22 @@ 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): - self.fabric.cbmc.fake_method + self.fabric.cbmc.fake_method() self.fabric.cbmc.set_chassis_power("off") results = self.fabric.cbmc.get_chassis_status() @@ -428,43 +455,55 @@ 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 = [] + self.power_state = False self.ip_address = ip_address self.tftp = tftp self.bmc = make_bmc(DummyBMC, hostname=ip_address, username=username, password=password, verbose=False) 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(). """ 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) @@ -472,29 +511,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" @@ -503,13 +550,16 @@ 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): + """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() @@ -517,15 +567,21 @@ 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 {} - def get_server_ip(self, interface, ipv6, user, password, aggressive): + # pylint: disable=R0913 + 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)) 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): @@ -536,13 +592,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', @@ -567,50 +635,71 @@ 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" + 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 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/node_test.py b/cxmanage_test/node_test.py index b6f860b..3c0e4a0 100644 --- a/cxmanage_test/node_test.py +++ b/cxmanage_test/node_test.py @@ -1,3 +1,7 @@ +# pylint: disable=C0302 +"""Unit tests for the Node class.""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -27,7 +31,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 @@ -49,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 """ @@ -61,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: @@ -117,8 +128,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 """ @@ -336,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)])) @@ -347,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): @@ -360,39 +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 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: + node.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: + node.get_uplink_speed() + + for found in node.bmc.executed: + self.assertEqual(found, "get_uplink_speed") - for x in node.bmc.executed: - self.assertEqual(x, "fabric_config_get_uplink_info") 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): @@ -419,9 +451,26 @@ 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 """ + + + GUID_UNIQUE = 0 + + def __init__(self, **kwargs): super(DummyBMC, self).__init__(**kwargs) self.executed = [] @@ -435,6 +484,20 @@ 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") + + # pylint: disable=R0903 + class Result(object): + """Results class.""" + 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 """ @@ -444,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" @@ -490,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() @@ -509,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() @@ -527,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 @@ -566,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" @@ -577,12 +656,21 @@ 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" 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""" self.executed.append('fabric_info_get_link_map') @@ -594,7 +682,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 +712,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: @@ -647,23 +735,68 @@ 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-") with open('%s/%s' % (work_dir, filename), 'w') as dc_file: for dchart in depth_chart: @@ -678,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""" @@ -867,22 +1001,46 @@ 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 + + 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, + +# 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 = { @@ -890,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 @@ -911,7 +1069,7 @@ class DummyUbootEnv(UbootEnv): """ Do nothing """ pass - +# pylint: disable=R0903 class DummyIPRetriever(object): """ Dummy IP retriever """ 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): 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: 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*(# )?<?https?://\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 @@ -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() diff --git a/scripts/cxmanage b/scripts/cxmanage index ccde835..ce7748c 100755 --- a/scripts/cxmanage +++ b/scripts/cxmanage @@ -36,18 +36,20 @@ 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_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 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 @@ -1,3 +1,6 @@ +"""Calxeda: setup.py""" + + # Copyright (c) 2012, Calxeda Inc. # # All rights reserved. @@ -34,7 +37,12 @@ from setuptools import setup setup( name='cxmanage', version='0.9.0', - packages=['cxmanage', 'cxmanage.commands', 'cxmanage_api'], + 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 @@ -44,6 +52,7 @@ setup( 'pexpect', 'pyipmi>=0.8.0', 'argparse', + 'unittest-xml-reporting<1.6.0' ], extras_require={ 'docs': ['sphinx', 'cloud_sptheme'], |