summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorge Kraft <george.kraft@calxeda.com>2013-08-29 12:58:10 -0500
committerGeorge Kraft <george.kraft@calxeda.com>2013-08-29 12:58:10 -0500
commit3988b859b0b874f0960662a8c216e0e8ee51e25d (patch)
tree9af7d2e41e1c17d0fb41f3373c3db6d82cb10113
parentbeb27f950677e5a21477446257dd33a2e9189840 (diff)
parent3a491410cb19b537bd525b402130152f05af3b6a (diff)
downloadcxmanage-3988b859b0b874f0960662a8c216e0e8ee51e25d.tar.gz
Merge branch 'master' into win32_support
Conflicts: cxmanage_api/image.py cxmanage_api/node.py
-rw-r--r--.gitignore2
-rw-r--r--cxmanage_api/__init__.py7
-rw-r--r--cxmanage_api/cli/__init__.py (renamed from cxmanage/__init__.py)69
-rw-r--r--cxmanage_api/cli/commands/__init__.py (renamed from cxmanage/commands/__init__.py)3
-rw-r--r--cxmanage_api/cli/commands/config.py (renamed from cxmanage/commands/config.py)16
-rw-r--r--cxmanage_api/cli/commands/fabric.py (renamed from cxmanage/commands/fabric.py)7
-rw-r--r--cxmanage_api/cli/commands/fru_version.py71
-rw-r--r--cxmanage_api/cli/commands/fw.py (renamed from cxmanage/commands/fw.py)27
-rw-r--r--cxmanage_api/cli/commands/info.py (renamed from cxmanage/commands/info.py)14
-rw-r--r--cxmanage_api/cli/commands/ipdiscover.py (renamed from cxmanage/commands/ipdiscover.py)5
-rw-r--r--cxmanage_api/cli/commands/ipmitool.py (renamed from cxmanage/commands/ipmitool.py)5
-rw-r--r--cxmanage_api/cli/commands/mc.py (renamed from cxmanage/commands/mc.py)8
-rw-r--r--cxmanage_api/cli/commands/power.py (renamed from cxmanage/commands/power.py)10
-rw-r--r--cxmanage_api/cli/commands/sensor.py (renamed from cxmanage/commands/sensor.py)6
-rw-r--r--cxmanage_api/cli/commands/tspackage.py (renamed from cxmanage/commands/tspackage.py)95
-rw-r--r--cxmanage_api/cx_exceptions.py36
-rwxr-xr-xcxmanage_api/docs/generate_api_rst.py2
-rw-r--r--cxmanage_api/docs/source/conf.py1
-rw-r--r--cxmanage_api/fabric.py99
-rw-r--r--cxmanage_api/firmware_package.py7
-rw-r--r--cxmanage_api/image.py10
-rw-r--r--cxmanage_api/ip_retriever.py46
-rw-r--r--cxmanage_api/loggers.py1
-rw-r--r--cxmanage_api/node.py250
-rw-r--r--cxmanage_api/simg.py6
-rw-r--r--cxmanage_api/tasks.py13
-rw-r--r--cxmanage_api/tftp.py88
-rw-r--r--cxmanage_api/ubootenv.py19
-rw-r--r--cxmanage_test/__init__.py18
-rw-r--r--cxmanage_test/fabric_test.py155
-rw-r--r--cxmanage_test/image_test.py10
-rw-r--r--cxmanage_test/node_test.py266
-rw-r--r--cxmanage_test/tasks_test.py12
-rw-r--r--cxmanage_test/tftp_test.py13
-rw-r--r--pylint.rc274
-rwxr-xr-xrun_tests3
-rwxr-xr-xscripts/cxmanage41
-rw-r--r--setup.py11
38 files changed, 1382 insertions, 344 deletions
diff --git a/.gitignore b/.gitignore
index 6e62db1..7a69fa9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
tags
*.pyc
cxmanage.egg-info
+.project
+.pydevproject
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
diff --git a/run_tests b/run_tests
index f0d7831..bfc64c4 100755
--- a/run_tests
+++ b/run_tests
@@ -32,6 +32,7 @@
import unittest
+import xmlrunner
from cxmanage_test import tftp_test, image_test, node_test, fabric_test, \
tasks_test
@@ -43,7 +44,7 @@ def main():
suite = unittest.TestSuite()
for module in test_modules:
suite.addTest(loader.loadTestsFromModule(module))
- unittest.TextTestRunner(verbosity=2).run(suite)
+ xmlrunner.XMLTestRunner(verbosity=2).run(suite)
if __name__ == "__main__":
main()
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
diff --git a/setup.py b/setup.py
index 4e2f47d..daba960 100644
--- a/setup.py
+++ b/setup.py
@@ -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'],