From 3a491410cb19b537bd525b402130152f05af3b6a Mon Sep 17 00:00:00 2001 From: George Kraft Date: Thu, 29 Aug 2013 10:24:53 -0500 Subject: CXMAN-221: Rename "cxmanage" python package to "cxmanage_api.cli" Groups up all the cxmanage functionality under one package. Yay --- cxmanage/__init__.py | 360 ------------------------- cxmanage/commands/__init__.py | 32 --- cxmanage/commands/config.py | 146 ---------- cxmanage/commands/fabric.py | 83 ------ cxmanage/commands/fru_version.py | 71 ----- cxmanage/commands/fw.py | 172 ------------ cxmanage/commands/info.py | 103 ------- cxmanage/commands/ipdiscover.py | 59 ---- cxmanage/commands/ipmitool.py | 64 ----- cxmanage/commands/mc.py | 51 ---- cxmanage/commands/power.py | 116 -------- cxmanage/commands/sensor.py | 87 ------ cxmanage/commands/tspackage.py | 448 ------------------------------- cxmanage_api/cli/__init__.py | 360 +++++++++++++++++++++++++ cxmanage_api/cli/commands/__init__.py | 32 +++ cxmanage_api/cli/commands/config.py | 146 ++++++++++ cxmanage_api/cli/commands/fabric.py | 83 ++++++ cxmanage_api/cli/commands/fru_version.py | 71 +++++ cxmanage_api/cli/commands/fw.py | 172 ++++++++++++ cxmanage_api/cli/commands/info.py | 103 +++++++ cxmanage_api/cli/commands/ipdiscover.py | 59 ++++ cxmanage_api/cli/commands/ipmitool.py | 63 +++++ cxmanage_api/cli/commands/mc.py | 51 ++++ cxmanage_api/cli/commands/power.py | 116 ++++++++ cxmanage_api/cli/commands/sensor.py | 87 ++++++ cxmanage_api/cli/commands/tspackage.py | 448 +++++++++++++++++++++++++++++++ scripts/cxmanage | 26 +- setup.py | 7 +- 28 files changed, 1810 insertions(+), 1806 deletions(-) delete mode 100644 cxmanage/__init__.py delete mode 100644 cxmanage/commands/__init__.py delete mode 100644 cxmanage/commands/config.py delete mode 100644 cxmanage/commands/fabric.py delete mode 100644 cxmanage/commands/fru_version.py delete mode 100644 cxmanage/commands/fw.py delete mode 100644 cxmanage/commands/info.py delete mode 100644 cxmanage/commands/ipdiscover.py delete mode 100644 cxmanage/commands/ipmitool.py delete mode 100644 cxmanage/commands/mc.py delete mode 100644 cxmanage/commands/power.py delete mode 100644 cxmanage/commands/sensor.py delete mode 100644 cxmanage/commands/tspackage.py create mode 100644 cxmanage_api/cli/__init__.py create mode 100644 cxmanage_api/cli/commands/__init__.py create mode 100644 cxmanage_api/cli/commands/config.py create mode 100644 cxmanage_api/cli/commands/fabric.py create mode 100644 cxmanage_api/cli/commands/fru_version.py create mode 100644 cxmanage_api/cli/commands/fw.py create mode 100644 cxmanage_api/cli/commands/info.py create mode 100644 cxmanage_api/cli/commands/ipdiscover.py create mode 100644 cxmanage_api/cli/commands/ipmitool.py create mode 100644 cxmanage_api/cli/commands/mc.py create mode 100644 cxmanage_api/cli/commands/power.py create mode 100644 cxmanage_api/cli/commands/sensor.py create mode 100644 cxmanage_api/cli/commands/tspackage.py diff --git a/cxmanage/__init__.py b/cxmanage/__init__.py deleted file mode 100644 index 438d568..0000000 --- a/cxmanage/__init__.py +++ /dev/null @@ -1,360 +0,0 @@ -"""Calxeda: __init__.py """ - - -# Copyright (c) 2012, Calxeda Inc. -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of Calxeda Inc. nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -# DAMAGE. - - -import sys -import time - -from cxmanage_api.tftp import InternalTftp, ExternalTftp -from cxmanage_api.node import Node -from cxmanage_api.tasks import TaskQueue -from cxmanage_api.cx_exceptions import TftpException - - -def get_tftp(args): - """Get a TFTP server""" - if args.internal_tftp: - tftp_args = args.internal_tftp.split(':') - if len(tftp_args) == 1: - ip_address = tftp_args[0] - port = 0 - elif len(tftp_args) == 2: - ip_address = tftp_args[0] - port = int(tftp_args[1]) - else: - print ('ERROR: %s is not a valid argument for --internal-tftp' - % args.internal_tftp) - sys.exit(1) - return InternalTftp(ip_address=ip_address, port=port, - verbose=args.verbose) - - elif args.external_tftp: - tftp_args = args.external_tftp.split(':') - if len(tftp_args) == 1: - ip_address = tftp_args[0] - port = 69 - elif len(tftp_args) == 2: - ip_address = tftp_args[0] - port = int(tftp_args[1]) - else: - print ('ERROR: %s is not a valid argument for --external-tftp' - % args.external_tftp) - sys.exit(1) - return ExternalTftp(ip_address=ip_address, port=port, - verbose=args.verbose) - - return InternalTftp(verbose=args.verbose) - -# pylint: disable=R0912 -def get_nodes(args, tftp, verify_prompt=False): - """Get nodes""" - hosts = [] - for entry in args.hostname.split(','): - hosts.extend(parse_host_entry(entry)) - - nodes = [Node(ip_address=x, username=args.user, password=args.password, - tftp=tftp, ecme_tftp_port=args.ecme_tftp_port, - verbose=args.verbose) for x in hosts] - - if args.all_nodes: - if not args.quiet: - print("Getting IP addresses...") - - results, errors = run_command(args, nodes, "get_fabric_ipinfo") - - all_nodes = [] - for node in nodes: - if node in results: - for node_id, ip_address in sorted(results[node].iteritems()): - new_node = Node(ip_address=ip_address, username=args.user, - password=args.password, tftp=tftp, - ecme_tftp_port=args.ecme_tftp_port, - verbose=args.verbose) - new_node.node_id = node_id - if not new_node in all_nodes: - all_nodes.append(new_node) - - node_strings = get_node_strings(args, all_nodes, justify=False) - if not args.quiet and all_nodes: - print("Discovered the following IP addresses:") - for node in all_nodes: - print node_strings[node] - print - - if errors: - print("ERROR: Failed to get IP addresses. Aborting.\n") - sys.exit(1) - - if args.nodes: - if len(all_nodes) != args.nodes: - print ("ERROR: Discovered %i nodes, expected %i. Aborting.\n" - % (len(all_nodes), args.nodes)) - sys.exit(1) - elif verify_prompt and not args.force: - print( - "NOTE: Please check node count! Ensure discovery of all " + - "nodes in the cluster. Power cycle your system if the " + - "discovered node count does not equal nodes in" + - "your system.\n" - ) - if not prompt_yes("Discovered %i nodes. Continue?" - % len(all_nodes)): - sys.exit(1) - - return all_nodes - - return nodes - - -def get_node_strings(args, nodes, justify=False): - """ Get string representations for the nodes. """ - # Use the private _node_id instead of node_id. Strange choice, - # but we want to avoid accidentally polling the BMC. - # pylint: disable=W0212 - if args.ids and all(x._node_id != None for x in nodes): - strings = ["Node %i (%s)" % (x._node_id, x.ip_address) for x in nodes] - else: - strings = [x.ip_address for x in nodes] - - if justify: - just_size = max(16, max(len(x) for x in strings) + 1) - strings = [x.ljust(just_size) for x in strings] - - return dict(zip(nodes, strings)) - - -# pylint: disable=R0915 -def run_command(args, nodes, name, *method_args): - """Runs a command on nodes.""" - if args.threads != None: - task_queue = TaskQueue(threads=args.threads, delay=args.command_delay) - else: - task_queue = TaskQueue(delay=args.command_delay) - - tasks = {} - for node in nodes: - tasks[node] = task_queue.put(getattr(node, name), *method_args) - - results = {} - errors = {} - try: - counter = 0 - while any(x.is_alive() for x in tasks.values()): - if not args.quiet: - _print_command_status(tasks, counter) - counter += 1 - time.sleep(0.25) - - for node, task in tasks.iteritems(): - if task.status == "Completed": - results[node] = task.result - else: - errors[node] = task.error - - except KeyboardInterrupt: - args.retry = 0 - - for node, task in tasks.iteritems(): - if task.status == "Completed": - results[node] = task.result - elif task.status == "Failed": - errors[node] = task.error - else: - errors[node] = KeyboardInterrupt( - "Aborted by keyboard interrupt" - ) - - if not args.quiet: - _print_command_status(tasks, counter) - print("\n") - - # Handle errors - should_retry = False - if errors: - _print_errors(args, nodes, errors) - if args.retry == None: - sys.stdout.write("Retry command on failed hosts? (y/n): ") - sys.stdout.flush() - while True: - command = raw_input().strip().lower() - if command in ['y', 'yes']: - should_retry = True - break - elif command in ['n', 'no']: - print - break - elif args.retry >= 1: - should_retry = True - if args.retry == 1: - print("Retrying command 1 more time...") - elif args.retry > 1: - print("Retrying command %i more times..." % args.retry) - args.retry -= 1 - - if should_retry: - nodes = [x for x in nodes if x in errors] - new_results, errors = run_command(args, nodes, name, *method_args) - results.update(new_results) - - return results, errors - - -def prompt_yes(prompt): - """Prompts the user. """ - sys.stdout.write("%s (y/n) " % prompt) - sys.stdout.flush() - while True: - command = raw_input().strip().lower() - if command in ['y', 'yes']: - print - return True - elif command in ['n', 'no']: - print - return False - - -def parse_host_entry(entry, hostfiles=None): - """parse a host entry""" - if not(hostfiles): - hostfiles = set() - - try: - return parse_hostfile_entry(entry, hostfiles) - except ValueError: - try: - return parse_ip_range_entry(entry) - except ValueError: - return [entry] - - -def parse_hostfile_entry(entry, hostfiles=None): - """parse a hostfile entry, returning a list of hosts""" - if not(hostfiles): - hostfiles = set() - - if entry.startswith('file='): - filename = entry[5:] - elif entry.startswith('hostfile='): - filename = entry[9:] - else: - raise ValueError('%s is not a hostfile entry' % entry) - - if filename in hostfiles: - return [] - hostfiles.add(filename) - - entries = [] - try: - for line in open(filename): - for element in line.partition('#')[0].split(): - for hostfile_entry in element.split(','): - entries.extend(parse_host_entry(hostfile_entry, hostfiles)) - except IOError: - print 'ERROR: %s is not a valid hostfile entry' % entry - sys.exit(1) - - return entries - - -def parse_ip_range_entry(entry): - """ Get a list of ip addresses in a given range""" - try: - start, end = entry.split('-') - - # Convert start address to int - start_bytes = [int(x) for x in start.split('.')] - - start_i = ((start_bytes[0] << 24) | (start_bytes[1] << 16) - | (start_bytes[2] << 8) | (start_bytes[3])) - - # Convert end address to int - end_bytes = [int(x) for x in end.split('.')] - end_i = ((end_bytes[0] << 24) | (end_bytes[1] << 16) - | (end_bytes[2] << 8) | (end_bytes[3])) - - # Get ip addresses in range - addresses = [] - for i in range(start_i, end_i + 1): - address_bytes = [(i >> (24 - 8 * x)) & 0xff for x in range(4)] - addresses.append('%i.%i.%i.%i' % tuple(address_bytes)) - - except (ValueError, IndexError): - raise ValueError('%s is not an IP range' % entry) - - return addresses - - -def _print_errors(args, nodes, errors): - """ Print errors if they occured """ - if errors: - node_strings = get_node_strings(args, nodes, justify=True) - print("Command failed on these hosts") - for node in nodes: - if node in errors: - print("%s: %s" % (node_strings[node], errors[node])) - print - - # Print a special message for TFTP errors - if all(isinstance(x, TftpException) for x in errors.itervalues()): - print( - "There may be networking issues (when behind NAT) between " + - "the host (where cxmanage is running) and the Calxeda node " + - "when establishing a TFTP session. Please refer to the " + - "documentation for more information.\n" - ) - - -def _print_command_status(tasks, counter): - """ Print the status of a command """ - message = "\r%i successes | %i errors | %i nodes left | %s" - successes = len([x for x in tasks.values() if x.status == "Completed"]) - errors = len([x for x in tasks.values() if x.status == "Failed"]) - nodes_left = len(tasks) - successes - errors - dots = "".join(["." for x in range(counter % 4)]).ljust(3) - sys.stdout.write(message % (successes, errors, nodes_left, dots)) - sys.stdout.flush() - - -# These are needed for ipinfo and whenever version information is printed -COMPONENTS = [ - ("ecme_version", "ECME version"), - ("cdb_version", "CDB version"), - ("stage2_version", "Stage2boot version"), - ("bootlog_version", "Bootlog version"), - ("a9boot_version", "A9boot version"), - ("a15boot_version", "A15boot version"), - ("uboot_version", "Uboot version"), - ("ubootenv_version", "Ubootenv version"), - ("dtb_version", "DTB version"), -] - diff --git a/cxmanage/commands/__init__.py b/cxmanage/commands/__init__.py deleted file mode 100644 index 571a3c5..0000000 --- a/cxmanage/commands/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -"""Calxeda: __init__.py""" - - -# Copyright (c) 2012, Calxeda Inc. -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of Calxeda Inc. nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -# DAMAGE. diff --git a/cxmanage/commands/config.py b/cxmanage/commands/config.py deleted file mode 100644 index bbe5256..0000000 --- a/cxmanage/commands/config.py +++ /dev/null @@ -1,146 +0,0 @@ -"""Calxeda: config.py """ - - -# Copyright (c) 2012, Calxeda Inc. -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of Calxeda Inc. nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -# DAMAGE. - - -from cxmanage import get_tftp, get_nodes, get_node_strings, run_command - -from cxmanage_api.ubootenv import validate_boot_args, \ - validate_pxe_interface - - -def config_reset_command(args): - """reset to factory default settings""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp, verify_prompt=True) - - if not args.quiet: - print "Sending config reset command..." - - _, errors = run_command(args, nodes, "config_reset") - - if not args.quiet and not errors: - print "Command completed successfully.\n" - - return len(errors) > 0 - - -def config_boot_command(args): - """set A9 boot order""" - if args.boot_order == ['status']: - return config_boot_status_command(args) - - validate_boot_args(args.boot_order) - - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Setting boot order..." - - _, errors = run_command(args, nodes, "set_boot_order", - args.boot_order) - - if not args.quiet and not errors: - print "Command completed successfully.\n" - - return len(errors) > 0 - - -def config_boot_status_command(args): - """Get boot status command.""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Getting boot order..." - results, errors = run_command(args, nodes, "get_boot_order") - - # Print results - if results: - node_strings = get_node_strings(args, results, justify=True) - print "Boot order" - for node in nodes: - if node in results: - print "%s: %s" % (node_strings[node], ",".join(results[node])) - print - - if not args.quiet and errors: - print "Some errors occured during the command.\n" - - return len(errors) > 0 - - -def config_pxe_command(args): - """set the PXE boot interface""" - if args.interface == "status": - return config_pxe_status_command(args) - - validate_pxe_interface(args.interface) - - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Setting pxe interface..." - - _, errors = run_command(args, nodes, "set_pxe_interface", - args.interface) - - if not args.quiet and not errors: - print "Command completed successfully.\n" - - return len(errors) > 0 - - -def config_pxe_status_command(args): - """Gets pxe status.""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Getting pxe interface..." - results, errors = run_command(args, nodes, "get_pxe_interface") - - # Print results - if results: - node_strings = get_node_strings(args, results, justify=True) - print "PXE interface" - for node in nodes: - if node in results: - print "%s: %s" % (node_strings[node], results[node]) - print - - if not args.quiet and errors: - print "Some errors occured during the command.\n" - - return len(errors) > 0 diff --git a/cxmanage/commands/fabric.py b/cxmanage/commands/fabric.py deleted file mode 100644 index 8bc3f65..0000000 --- a/cxmanage/commands/fabric.py +++ /dev/null @@ -1,83 +0,0 @@ -"""Calxeda: fabric.py""" - - -# Copyright (c) 2012, Calxeda Inc. -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of Calxeda Inc. nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -# DAMAGE. - -from cxmanage import get_tftp, get_nodes, run_command - - -def ipinfo_command(args): - """get ip info from a cluster or host""" - args.all_nodes = False - - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Getting IP addresses..." - - results, _ = run_command(args, nodes, "get_fabric_ipinfo") - - for node in nodes: - if node in results: - print 'IP info from %s' % node.ip_address - for node_id, node_address in results[node].iteritems(): - print 'Node %i: %s' % (node_id, node_address) - print - - return 0 - - -def macaddrs_command(args): - """get mac addresses from a cluster or host""" - args.all_nodes = False - - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Getting MAC addresses..." - results, errors = run_command(args, nodes, "get_fabric_macaddrs") - - for node in nodes: - if node in results: - print "MAC addresses from %s" % node.ip_address - for node_id in results[node]: - for port in results[node][node_id]: - for mac_address in results[node][node_id][port]: - print "Node %i, Port %i: %s" % (node_id, port, - mac_address) - print - - if not args.quiet and errors: - print "Some errors occured during the command.\n" - - return len(errors) == 0 diff --git a/cxmanage/commands/fru_version.py b/cxmanage/commands/fru_version.py deleted file mode 100644 index 1dd318c..0000000 --- a/cxmanage/commands/fru_version.py +++ /dev/null @@ -1,71 +0,0 @@ -"""Calxeda: fru_version.py """ - - -# Copyright (c) 2013, Calxeda Inc. -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of Calxeda Inc. nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -# DAMAGE. - - -from cxmanage import get_tftp, get_nodes, get_node_strings, run_command - - -def node_fru_version_command(args): - """Get the node FRU version for each node. """ - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - results, errors = run_command(args, nodes, 'get_node_fru_version') - - # Print results if we were successful - if results: - node_strings = get_node_strings(args, results, justify=True) - for node in nodes: - print("%s: %s" % (node_strings[node], results[node])) - - print("") # For readability - - if not args.quiet and errors: - print('Some errors occured during the command.\n') - - -def slot_fru_version_command(args): - """Get the slot FRU version for each node. """ - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - results, errors = run_command(args, nodes, 'get_slot_fru_version') - - # Print results if we were successful - if results: - node_strings = get_node_strings(args, results, justify=True) - for node in nodes: - print("%s: %s" % (node_strings[node], results[node])) - - print("") # For readability - - if not args.quiet and errors: - print('Some errors occured during the command.\n') diff --git a/cxmanage/commands/fw.py b/cxmanage/commands/fw.py deleted file mode 100644 index 611315e..0000000 --- a/cxmanage/commands/fw.py +++ /dev/null @@ -1,172 +0,0 @@ -"""Calxeda: fw.py """ - -# Copyright (c) 2012, Calxeda Inc. -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of Calxeda Inc. nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -# DAMAGE. - - -from pkg_resources import parse_version - -from cxmanage import get_tftp, get_nodes, get_node_strings, run_command, \ - prompt_yes - -from cxmanage_api.image import Image -from cxmanage_api.firmware_package import FirmwarePackage - -# pylint: disable=R0912 -def fwupdate_command(args): - """update firmware on a cluster or host""" - def do_update(): - """ Do a single firmware check+update. Returns True on failure. """ - if not args.force: - if not args.quiet: - print "Checking hosts..." - - _, errors = run_command(args, nodes, "_check_firmware", - package, args.partition, args.priority) - if errors: - print "ERROR: Firmware update aborted." - return True - - if not args.quiet: - print "Updating firmware..." - - _, errors = run_command(args, nodes, "update_firmware", package, - args.partition, args.priority) - if errors: - print "ERROR: Firmware update failed." - return True - - return False - - def do_reset(): - """ Reset and wait. Returns True on failure. """ - if not args.quiet: - print "Checking ECME versions..." - - results, errors = run_command(args, nodes, "get_versions") - if errors: - print "ERROR: MC reset aborted. Backup partitions not updated." - return True - - for result in results.values(): - version = result.ecme_version.lstrip("v") - if parse_version(version) < parse_version("1.2.0"): - print "ERROR: MC reset is unsafe on ECME version v%s" % version - print "Please power cycle the system and start a new fwupdate." - return True - - if not args.quiet: - print "Resetting nodes..." - - results, errors = run_command(args, nodes, "mc_reset", True) - if errors: - print "ERROR: MC reset failed. Backup partitions not updated." - return True - - return False - - if args.image_type == "PACKAGE": - package = FirmwarePackage(args.filename) - else: - try: - simg = None - if args.force_simg: - simg = False - elif args.skip_simg: - simg = True - - image = Image(args.filename, args.image_type, simg, args.daddr, - args.skip_crc32, args.fw_version) - package = FirmwarePackage() - package.images.append(image) - except ValueError as err: - print "ERROR: %s" % err - return True - - if not args.all_nodes: - if args.force: - print( - 'WARNING: Updating firmware without --all-nodes' + - ' is dangerous.' - ) - else: - if not prompt_yes( - 'WARNING: Updating firmware without ' + - '--all-nodes is dangerous. Continue?' - ): - return 1 - - tftp = get_tftp(args) - nodes = get_nodes(args, tftp, verify_prompt=True) - - errors = do_update() - - if args.full and not errors: - errors = do_reset() - if not errors: - errors = do_update() - - if not args.quiet and not errors: - print "Command completed successfully.\n" - - return errors - - -def fwinfo_command(args): - """print firmware info""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Getting firmware info..." - - results, errors = run_command(args, nodes, "get_firmware_info") - - node_strings = get_node_strings(args, results, justify=False) - for node in nodes: - if node in results: - print "[ Firmware info for %s ]" % node_strings[node] - - for partition in results[node]: - print "Partition : %s" % partition.partition - print "Type : %s" % partition.type - print "Offset : %s" % partition.offset - print "Size : %s" % partition.size - print "Priority : %s" % partition.priority - print "Daddr : %s" % partition.daddr - print "Flags : %s" % partition.flags - print "Version : %s" % partition.version - print "In Use : %s" % partition.in_use - print - - if not args.quiet and errors: - print "Some errors occured during the command.\n" - - return len(errors) > 0 diff --git a/cxmanage/commands/info.py b/cxmanage/commands/info.py deleted file mode 100644 index b15d2c6..0000000 --- a/cxmanage/commands/info.py +++ /dev/null @@ -1,103 +0,0 @@ -"""Calxeda: info.py""" - - -# Copyright (c) 2012, Calxeda Inc. -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of Calxeda Inc. nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -# DAMAGE. - - -from cxmanage import get_tftp, get_nodes, get_node_strings, run_command -from cxmanage import COMPONENTS - - -def info_command(args): - """print info from a cluster or host""" - if args.info_type in [None, 'basic']: - return info_basic_command(args) - elif args.info_type == 'ubootenv': - return info_ubootenv_command(args) - - -def info_basic_command(args): - """Print basic info""" - - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Getting info..." - results, errors = run_command(args, nodes, "get_versions") - - # Print results - node_strings = get_node_strings(args, results, justify=False) - for node in nodes: - if node in results: - result = results[node] - # Get mappings between attributes and formatted strings - components = COMPONENTS - - print "[ Info from %s ]" % node_strings[node] - print "Hardware version : %s" % result.hardware_version - print "Firmware version : %s" % result.firmware_version - # var is the variable, string is the printable string of var - for var, string in components: - if hasattr(result, var): - version = getattr(result, var) - print "%s: %s" % (string.ljust(19), version) - print - - if not args.quiet and errors: - print "Some errors occured during the command.\n" - - return len(errors) > 0 - - -def info_ubootenv_command(args): - """Print uboot info""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Getting u-boot environment..." - results, errors = run_command(args, nodes, "get_ubootenv") - - # Print results - node_strings = get_node_strings(args, results, justify=False) - for node in nodes: - if node in results: - ubootenv = results[node] - print "[ U-Boot Environment from %s ]" % node_strings[node] - for variable in ubootenv.variables: - print "%s=%s" % (variable, ubootenv.variables[variable]) - print - - if not args.quiet and errors: - print "Some errors occured during the command.\n" - - return len(errors) > 0 diff --git a/cxmanage/commands/ipdiscover.py b/cxmanage/commands/ipdiscover.py deleted file mode 100644 index bd09413..0000000 --- a/cxmanage/commands/ipdiscover.py +++ /dev/null @@ -1,59 +0,0 @@ -"""Calxeda: ipdiscover.py""" - - -# Copyright (c) 2012, Calxeda Inc. -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of Calxeda Inc. nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -# DAMAGE. - -from cxmanage import get_tftp, get_nodes, get_node_strings, run_command - - -def ipdiscover_command(args): - """discover server IP addresses""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print 'Getting server-side IP addresses...' - - results, errors = run_command(args, nodes, 'get_server_ip', args.interface, - args.ipv6, args.server_user, args.server_password, args.aggressive) - - if results: - node_strings = get_node_strings(args, results, justify=True) - print 'IP addresses (ECME, Server)' - for node in nodes: - if node in results: - print '%s: %s' % (node_strings[node], results[node]) - print - - if not args.quiet and errors: - print 'Some errors occurred during the command.' - - return len(errors) > 0 diff --git a/cxmanage/commands/ipmitool.py b/cxmanage/commands/ipmitool.py deleted file mode 100644 index ac21e00..0000000 --- a/cxmanage/commands/ipmitool.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Calxeda: ipmitool.py""" - - -# Copyright (c) 2012, Calxeda Inc. -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of Calxeda Inc. nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -# DAMAGE. - - -from cxmanage import get_tftp, get_nodes, get_node_strings, run_command - - -def ipmitool_command(args): - """run arbitrary ipmitool command""" - if args.lanplus: - ipmitool_args = ['-I', 'lanplus'] + args.ipmitool_args - else: - ipmitool_args = args.ipmitool_args - - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Running IPMItool command..." - results, errors = run_command(args, nodes, "ipmitool_command", - ipmitool_args) - - # Print results - node_strings = get_node_strings(args, results, justify=False) - for node in nodes: - if node in results and results[node] != "": - print "[ IPMItool output from %s ]" % node_strings[node] - print results[node] - print - - if not args.quiet and errors: - print "Some errors occured during the command.\n" - - return len(errors) > 0 diff --git a/cxmanage/commands/mc.py b/cxmanage/commands/mc.py deleted file mode 100644 index 6c42615..0000000 --- a/cxmanage/commands/mc.py +++ /dev/null @@ -1,51 +0,0 @@ -"""Calxeda: mc.py""" - - -# Copyright (c) 2012, Calxeda Inc. -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of Calxeda Inc. nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -# DAMAGE. - - -from cxmanage import get_tftp, get_nodes, run_command - - -def mcreset_command(args): - """reset the management controllers of a cluster or host""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print 'Sending MC reset command...' - - _, errors = run_command(args, nodes, 'mc_reset') - - if not args.quiet and not errors: - print 'Command completed successfully.\n' - - return len(errors) > 0 diff --git a/cxmanage/commands/power.py b/cxmanage/commands/power.py deleted file mode 100644 index 623c38d..0000000 --- a/cxmanage/commands/power.py +++ /dev/null @@ -1,116 +0,0 @@ -"""Calxeda: power.py """ - - -# Copyright (c) 2012, Calxeda Inc. -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of Calxeda Inc. nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -# DAMAGE. - -from cxmanage import get_tftp, get_nodes, get_node_strings, run_command - - -def power_command(args): - """change the power state of a cluster or host""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print 'Sending power %s command...' % args.power_mode - - _, errors = run_command(args, nodes, 'set_power', args.power_mode) - - if not args.quiet and not errors: - print 'Command completed successfully.\n' - - return len(errors) > 0 - - -def power_status_command(args): - """Executes the power status command with args.""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print 'Getting power status...' - results, errors = run_command(args, nodes, 'get_power') - - # Print results - if results: - node_strings = get_node_strings(args, results, justify=True) - print 'Power status' - for node in nodes: - if node in results: - result = 'on' if results[node] else 'off' - print '%s: %s' % (node_strings[node], result) - print - - if not args.quiet and errors: - print 'Some errors occured during the command.\n' - - return len(errors) > 0 - - -def power_policy_command(args): - """Executes power policy command with args.""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print 'Setting power policy to %s...' % args.policy - - _, errors = run_command(args, nodes, 'set_power_policy', - args.policy) - - if not args.quiet and not errors: - print 'Command completed successfully.\n' - - return len(errors) > 0 - - -def power_policy_status_command(args): - """Executes the power policy status command with args.""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print 'Getting power policy status...' - results, errors = run_command(args, nodes, 'get_power_policy') - - # Print results - if results: - node_strings = get_node_strings(args, results, justify=True) - print 'Power policy status' - for node in nodes: - if node in results: - print '%s: %s' % (node_strings[node], results[node]) - print - - if not args.quiet and errors: - print 'Some errors occured during the command.\n' - - return len(errors) > 0 diff --git a/cxmanage/commands/sensor.py b/cxmanage/commands/sensor.py deleted file mode 100644 index 3a27143..0000000 --- a/cxmanage/commands/sensor.py +++ /dev/null @@ -1,87 +0,0 @@ -"""Calxeda: sensor.py""" - - -# Copyright (c) 2012, Calxeda Inc. -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of Calxeda Inc. nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -# DAMAGE. - - -from cxmanage import get_tftp, get_nodes, get_node_strings, run_command - -# pylint: disable=R0914 -def sensor_command(args): - """read sensor values from a cluster or host""" - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - if not args.quiet: - print "Getting sensor readings..." - results, errors = run_command(args, nodes, "get_sensors", - args.sensor_name) - - sensors = {} - for node in nodes: - if node in results: - for sensor_name, sensor in results[node].iteritems(): - if not sensor_name in sensors: - sensors[sensor_name] = [] - - reading = sensor.sensor_reading.replace("(+/- 0) ", "") - try: - value = float(reading.split()[0]) - suffix = reading.lstrip("%f " % value) - sensors[sensor_name].append((node, value, suffix)) - except ValueError: - sensors[sensor_name].append((node, reading, "")) - - node_strings = get_node_strings(args, results, justify=True) - jsize = len(node_strings.itervalues().next()) - for sensor_name, readings in sensors.iteritems(): - print sensor_name - - for node, reading, suffix in readings: - print "%s: %.2f %s" % (node_strings[node], reading, suffix) - - try: - if all(suffix == x[2] for x in readings): - minimum = min(x[1] for x in readings) - maximum = max(x[1] for x in readings) - average = sum(x[1] for x in readings) / len(readings) - print "%s: %.2f %s" % ("Minimum".ljust(jsize), minimum, suffix) - print "%s: %.2f %s" % ("Maximum".ljust(jsize), maximum, suffix) - print "%s: %.2f %s" % ("Average".ljust(jsize), average, suffix) - except ValueError: - pass - - print - - if not args.quiet and errors: - print "Some errors occured during the command.\n" - - return len(errors) > 0 diff --git a/cxmanage/commands/tspackage.py b/cxmanage/commands/tspackage.py deleted file mode 100644 index d6ee198..0000000 --- a/cxmanage/commands/tspackage.py +++ /dev/null @@ -1,448 +0,0 @@ -"""Calxeda: tspackage.py""" - - -# Copyright 2013 Calxeda, Inc. -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of Calxeda Inc. nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -# DAMAGE. - - -# -# A cxmanage command to collect information about a node and archive it. -# -# Example: -# cxmanage tspackage 10.10.10.10 -# - - -import os -import time -import shutil -import tarfile -import tempfile - -from cxmanage import get_tftp, get_nodes, run_command, COMPONENTS - - -def tspackage_command(args): - """Get information pertaining to each node. - This includes: - Version info (like cxmanage info) - MAC addresses - Sensor readings - Sensor data records - Firmware info - Boot order - SELs (System Event Logs), - Depth charts - Routing Tables - - This data will be written to a set of files. Each node will get its own - file. All of these files will be archived and saved to the user's current - directory. - - Internally, this command is called from: - ~/virtual_testenv/workspace/cx_manage_util/scripts/cxmanage - - """ - tftp = get_tftp(args) - nodes = get_nodes(args, tftp) - - # Make a temporary directory to store the node information files - original_dir = os.getcwd() - temp_dir = tempfile.mkdtemp() - os.chdir(temp_dir) - tspackage_dir = "tspackage.%s" % time.strftime("%Y%m%d%H%M%S") - os.mkdir(tspackage_dir) - os.chdir(tspackage_dir) - - quiet = args.quiet - - if not quiet: - print("Getting version information...") - write_version_info(args, nodes) - - if not quiet: - print("Getting node FRU version...") - write_node_fru_version(args, nodes) - - if not quiet: - print("Getting slot FRU version...") - write_slot_fru_version(args, nodes) - - if not quiet: - print("Getting boot order...") - write_boot_order(args, nodes) - - if not quiet: - print("Getting MAC addresses...") - write_mac_addrs(args, nodes) - - if not quiet: - print("Getting sensor information...") - write_sensor_info(args, nodes) - - if not quiet: - print("Getting firmware information...") - write_fwinfo(args, nodes) - - if not quiet: - print("Getting system event logs...") - write_sel(args, nodes) - - if not quiet: - print("Getting depth charts...") - write_depth_chart(args, nodes) - - if not quiet: - print("Getting routing tables...") - write_routing_table(args, nodes) - - # Archive the files - archive(os.getcwd(), original_dir) - - # The original files are already archived, so we can delete them. - shutil.rmtree(temp_dir) - - -def write_version_info(args, nodes): - """Write the version info (like cxmanage info) for each node - to their respective files. - - """ - info_results, _ = run_command(args, nodes, "get_versions") - - - for node in nodes: - lines = [] # The lines of text to write to file - - # Since this is the first line of the file, we don't need a \n - write_to_file( - node, - "[ Version Info for Node %d ]" % node.node_id, - add_newlines=False - ) - - lines.append("ECME IP Address : %s" % node.ip_address) - - if node in info_results: - info_result = info_results[node] - lines.append( - "Hardware version : %s" % - info_result.hardware_version - ) - lines.append( - "Firmware version : %s" % - info_result.firmware_version - ) - - # Get mappings between attributes and formatted strings - components = COMPONENTS - for var, description in components: - if hasattr(info_result, var): - version = getattr(info_result, var) - lines.append("%s: %s" % (description.ljust(19), version)) - else: - lines.append("No version information could be found.") - - write_to_file(node, lines) - -def write_node_fru_version(args, nodes): - """Write the node and slot FRU versions for each node to their - respective files. - - """ - node_fru_results, _ = run_command(args, nodes, "get_node_fru_version") - - for node in nodes: - lines = [] # Lines of text to write to file - if node in node_fru_results: - lines.append("%s: %s" % \ - ("Node FRU Version".ljust(19), node_fru_results[node])) - else: - lines.append("\nWARNING: No node FRU found!") - write_to_file(node, lines) - -def write_slot_fru_version(args, nodes): - """Write the node and slot FRU versions for each node to their - respective files. - - """ - slot_fru_results, _ = run_command(args, nodes, "get_slot_fru_version") - - for node in nodes: - lines = [] # Lines of text to write to file - if node in slot_fru_results: - lines.append("%s: %s" % \ - ("Slot FRU Version".ljust(19), slot_fru_results[node])) - else: - lines.append("Error reading slot FRU. Perhaps the system board " + - "does not have slot FRUs?") - - write_to_file(node, lines) - -def write_mac_addrs(args, nodes): - """Write the MAC addresses for each node to their respective files.""" - mac_addr_results, _ = run_command( - args, - nodes, - "get_fabric_macaddrs" - ) - - for node in nodes: - lines = [] # Lines of text to write to file - # \n is used here to give a blank line before this section - lines.append("\n[ MAC Addresses for Node %d ]" % node.node_id) - - if node in mac_addr_results: - for port in mac_addr_results[node][node.node_id]: - for mac_address in mac_addr_results[node][node.node_id][port]: - lines.append( - "Node %i, Port %i: %s" % - (node.node_id, port, mac_address) - ) - else: - lines.append("\nWARNING: No MAC addresses found!") - - write_to_file(node, lines) - -# pylint: disable=R0914 -def write_sensor_info(args, nodes): - """Write sensor information for each node to their respective files.""" - args.sensor_name = "" - - results, _ = run_command(args, nodes, "get_sensors", - args.sensor_name) - - sensors = {} - for node in nodes: - lines = [] # Lines of text to write to file - # \n is used here to give a blank line before this section - lines.append("\n[ Sensors for Node %d ]" % node.node_id) - - if node in results: - for sensor_name, sensor in results[node].iteritems(): - if not sensor_name in sensors: - sensors[sensor_name] = [] - - reading = sensor.sensor_reading.replace("(+/- 0) ", "") - try: - value = float(reading.split()[0]) - suffix = reading.lstrip("%f " % value) - sensors[sensor_name].append((node, value, suffix)) - except ValueError: - sensors[sensor_name].append((node, reading, "")) - else: - print("Could not get sensor info!") - lines.append("Could not get sensor info!") - - for sensor_name, readings in sensors.iteritems(): - for reading_node, reading, suffix in readings: - if reading_node.ip_address == node.ip_address: - left_side = "{:<18}".format(sensor_name) - right_side = ": %.2f %s" % (reading, suffix) - lines.append(left_side + right_side) - - write_to_file(node, lines) - - -def write_fwinfo(args, nodes): - """Write information about each node's firware partitions - to its respective file. - - """ - results, _ = run_command(args, nodes, "get_firmware_info") - - for node in nodes: - lines = [] # Lines of text to write to file - # \n is used here to give a blank line before this section - lines.append("\n[ Firmware Info for Node %d ]" % node.node_id) - - if node in results: - first_partition = True # The first partiton doesn't need \n - - for partition in results[node]: - if first_partition: - lines.append("Partition : %s" % partition.partition) - first_partition = False - else: - lines.append("\nPartition : %s" % partition.partition) - lines.append("Type : %s" % partition.type) - lines.append("Offset : %s" % partition.offset) - lines.append("Size : %s" % partition.size) - lines.append("Priority : %s" % partition.priority) - lines.append("Daddr : %s" % partition.daddr) - lines.append("Flags : %s" % partition.flags) - lines.append("Version : %s" % partition.version) - lines.append("In Use : %s" % partition.in_use) - else: - lines.append("Could not get firmware info!") - write_to_file(node, lines) - - -def write_boot_order(args, nodes): - """Write the boot order of each node to their respective files.""" - results, _ = run_command(args, nodes, "get_boot_order") - - for node in nodes: - lines = [] # Lines of text to write to file - # \n is used here to give a blank line before this section - lines.append("\n[ Boot Order for Node %d ]" % node.node_id) - - if node in results: - lines.append(", ".join(results[node])) - else: - lines.append("Could not get boot order!") - - write_to_file(node, lines) - - -def write_sel(args, nodes): - """Write the SEL for each node to their respective files.""" - results, _ = run_command(args, nodes, "get_sel") - - for node in nodes: - lines = [] # Lines of text to write to file - # \n is used here to give a blank line before this section - lines.append("\n[ System Event Log for Node %d ]" % node.node_id) - - try: - if node in results: - for event in results[node]: - lines.append(event) - - # pylint: disable=W0703 - except Exception as error: - lines.append("Could not get SEL! " + str(error)) - if not args.quiet: - print("Failed to get system event log for " + node.ip_address) - - write_to_file(node, lines) - - -def write_depth_chart(args, nodes): - """Write the depth chart for each node to their respective files.""" - depth_results, _ = run_command(args, nodes, "get_depth_chart") - - for node in nodes: - lines = [] # Lines of text to write to file - # \n is used here to give a blank line before this section - lines.append("\n[ Depth Chart for Node %d ]" % node.node_id) - - if node in depth_results: - depth_chart = depth_results[node] - for key in depth_chart: - subchart = depth_chart[key] - lines.append("To node " + str(key)) - - # The 'shortest' entry is one tuple, but - # the 'others' are a list. - for subkey in subchart: - if str(subkey) == "shortest": - lines.append( - " " + str(subkey) + - " : " + str(subchart[subkey]) - ) - else: - for entry in subchart[subkey]: - lines.append( - " " + str(subkey) + - " : " + str(entry) - ) - - else: - lines.append("Could not get depth chart!") - - write_to_file(node, lines) - - -def write_routing_table(args, nodes): - """Write the routing table for each node to their respective files.""" - routing_results, _ = run_command(args, nodes, "get_routing_table") - - for node in nodes: - lines = [] # Lines of text to write to file - # \n is used here to give a blank line before this section - lines.append("\n[ Routing Table for Node %d ]" % node.node_id) - - if node in routing_results: - table = routing_results[node] - for node_to in table: - lines.append(str(node_to) + " : " + str(table[node_to])) - else: - lines.append("Could not get routing table!") - - write_to_file(node, lines) - - -def write_to_file(node, to_write, add_newlines=True): - """Append to_write to an info file for every node in nodes. - - :param node: Node object to write about - :type node: Node object - :param to_write: Text to write to the files - :type to_write: List of strings - :param add_newlines: Whether to add newline characters before - every item in to_write. True by default. True will add newline - characters. - :type add_newlines: bool - - """ - with open("node" + str(node.node_id) + ".txt", 'a') as node_file: - if add_newlines: - # join() doesn't add a newline before the first item - to_write[0] = "\n" + to_write[0] - node_file.write("\n".join(to_write)) - else: - node_file.write("".join(to_write)) - - -def archive(directory_to_archive, destination): - """Creates a .tar containing everything in the directory_to_archive. - The .tar is saved to destination with the same name as the original - directory_to_archive, but with .tar appended. - - :param directory_to_archive: A path to the directory to be archived. - :type directory_to_archive: string - - :param destination: A path to the location the .tar should be saved - :type destination: string - - """ - os.chdir(os.path.dirname(directory_to_archive)) - - tar_name = os.path.basename(directory_to_archive) + ".tar" - tar_name = os.path.join(destination, tar_name) - - with tarfile.open(tar_name, "w") as tar: - tar.add(os.path.basename(directory_to_archive)) - - print( - "Finished! One archive created:\n" + - os.path.join(destination, tar_name) - ) diff --git a/cxmanage_api/cli/__init__.py b/cxmanage_api/cli/__init__.py new file mode 100644 index 0000000..438d568 --- /dev/null +++ b/cxmanage_api/cli/__init__.py @@ -0,0 +1,360 @@ +"""Calxeda: __init__.py """ + + +# Copyright (c) 2012, Calxeda Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Calxeda Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + + +import sys +import time + +from cxmanage_api.tftp import InternalTftp, ExternalTftp +from cxmanage_api.node import Node +from cxmanage_api.tasks import TaskQueue +from cxmanage_api.cx_exceptions import TftpException + + +def get_tftp(args): + """Get a TFTP server""" + if args.internal_tftp: + tftp_args = args.internal_tftp.split(':') + if len(tftp_args) == 1: + ip_address = tftp_args[0] + port = 0 + elif len(tftp_args) == 2: + ip_address = tftp_args[0] + port = int(tftp_args[1]) + else: + print ('ERROR: %s is not a valid argument for --internal-tftp' + % args.internal_tftp) + sys.exit(1) + return InternalTftp(ip_address=ip_address, port=port, + verbose=args.verbose) + + elif args.external_tftp: + tftp_args = args.external_tftp.split(':') + if len(tftp_args) == 1: + ip_address = tftp_args[0] + port = 69 + elif len(tftp_args) == 2: + ip_address = tftp_args[0] + port = int(tftp_args[1]) + else: + print ('ERROR: %s is not a valid argument for --external-tftp' + % args.external_tftp) + sys.exit(1) + return ExternalTftp(ip_address=ip_address, port=port, + verbose=args.verbose) + + return InternalTftp(verbose=args.verbose) + +# pylint: disable=R0912 +def get_nodes(args, tftp, verify_prompt=False): + """Get nodes""" + hosts = [] + for entry in args.hostname.split(','): + hosts.extend(parse_host_entry(entry)) + + nodes = [Node(ip_address=x, username=args.user, password=args.password, + tftp=tftp, ecme_tftp_port=args.ecme_tftp_port, + verbose=args.verbose) for x in hosts] + + if args.all_nodes: + if not args.quiet: + print("Getting IP addresses...") + + results, errors = run_command(args, nodes, "get_fabric_ipinfo") + + all_nodes = [] + for node in nodes: + if node in results: + for node_id, ip_address in sorted(results[node].iteritems()): + new_node = Node(ip_address=ip_address, username=args.user, + password=args.password, tftp=tftp, + ecme_tftp_port=args.ecme_tftp_port, + verbose=args.verbose) + new_node.node_id = node_id + if not new_node in all_nodes: + all_nodes.append(new_node) + + node_strings = get_node_strings(args, all_nodes, justify=False) + if not args.quiet and all_nodes: + print("Discovered the following IP addresses:") + for node in all_nodes: + print node_strings[node] + print + + if errors: + print("ERROR: Failed to get IP addresses. Aborting.\n") + sys.exit(1) + + if args.nodes: + if len(all_nodes) != args.nodes: + print ("ERROR: Discovered %i nodes, expected %i. Aborting.\n" + % (len(all_nodes), args.nodes)) + sys.exit(1) + elif verify_prompt and not args.force: + print( + "NOTE: Please check node count! Ensure discovery of all " + + "nodes in the cluster. Power cycle your system if the " + + "discovered node count does not equal nodes in" + + "your system.\n" + ) + if not prompt_yes("Discovered %i nodes. Continue?" + % len(all_nodes)): + sys.exit(1) + + return all_nodes + + return nodes + + +def get_node_strings(args, nodes, justify=False): + """ Get string representations for the nodes. """ + # Use the private _node_id instead of node_id. Strange choice, + # but we want to avoid accidentally polling the BMC. + # pylint: disable=W0212 + if args.ids and all(x._node_id != None for x in nodes): + strings = ["Node %i (%s)" % (x._node_id, x.ip_address) for x in nodes] + else: + strings = [x.ip_address for x in nodes] + + if justify: + just_size = max(16, max(len(x) for x in strings) + 1) + strings = [x.ljust(just_size) for x in strings] + + return dict(zip(nodes, strings)) + + +# pylint: disable=R0915 +def run_command(args, nodes, name, *method_args): + """Runs a command on nodes.""" + if args.threads != None: + task_queue = TaskQueue(threads=args.threads, delay=args.command_delay) + else: + task_queue = TaskQueue(delay=args.command_delay) + + tasks = {} + for node in nodes: + tasks[node] = task_queue.put(getattr(node, name), *method_args) + + results = {} + errors = {} + try: + counter = 0 + while any(x.is_alive() for x in tasks.values()): + if not args.quiet: + _print_command_status(tasks, counter) + counter += 1 + time.sleep(0.25) + + for node, task in tasks.iteritems(): + if task.status == "Completed": + results[node] = task.result + else: + errors[node] = task.error + + except KeyboardInterrupt: + args.retry = 0 + + for node, task in tasks.iteritems(): + if task.status == "Completed": + results[node] = task.result + elif task.status == "Failed": + errors[node] = task.error + else: + errors[node] = KeyboardInterrupt( + "Aborted by keyboard interrupt" + ) + + if not args.quiet: + _print_command_status(tasks, counter) + print("\n") + + # Handle errors + should_retry = False + if errors: + _print_errors(args, nodes, errors) + if args.retry == None: + sys.stdout.write("Retry command on failed hosts? (y/n): ") + sys.stdout.flush() + while True: + command = raw_input().strip().lower() + if command in ['y', 'yes']: + should_retry = True + break + elif command in ['n', 'no']: + print + break + elif args.retry >= 1: + should_retry = True + if args.retry == 1: + print("Retrying command 1 more time...") + elif args.retry > 1: + print("Retrying command %i more times..." % args.retry) + args.retry -= 1 + + if should_retry: + nodes = [x for x in nodes if x in errors] + new_results, errors = run_command(args, nodes, name, *method_args) + results.update(new_results) + + return results, errors + + +def prompt_yes(prompt): + """Prompts the user. """ + sys.stdout.write("%s (y/n) " % prompt) + sys.stdout.flush() + while True: + command = raw_input().strip().lower() + if command in ['y', 'yes']: + print + return True + elif command in ['n', 'no']: + print + return False + + +def parse_host_entry(entry, hostfiles=None): + """parse a host entry""" + if not(hostfiles): + hostfiles = set() + + try: + return parse_hostfile_entry(entry, hostfiles) + except ValueError: + try: + return parse_ip_range_entry(entry) + except ValueError: + return [entry] + + +def parse_hostfile_entry(entry, hostfiles=None): + """parse a hostfile entry, returning a list of hosts""" + if not(hostfiles): + hostfiles = set() + + if entry.startswith('file='): + filename = entry[5:] + elif entry.startswith('hostfile='): + filename = entry[9:] + else: + raise ValueError('%s is not a hostfile entry' % entry) + + if filename in hostfiles: + return [] + hostfiles.add(filename) + + entries = [] + try: + for line in open(filename): + for element in line.partition('#')[0].split(): + for hostfile_entry in element.split(','): + entries.extend(parse_host_entry(hostfile_entry, hostfiles)) + except IOError: + print 'ERROR: %s is not a valid hostfile entry' % entry + sys.exit(1) + + return entries + + +def parse_ip_range_entry(entry): + """ Get a list of ip addresses in a given range""" + try: + start, end = entry.split('-') + + # Convert start address to int + start_bytes = [int(x) for x in start.split('.')] + + start_i = ((start_bytes[0] << 24) | (start_bytes[1] << 16) + | (start_bytes[2] << 8) | (start_bytes[3])) + + # Convert end address to int + end_bytes = [int(x) for x in end.split('.')] + end_i = ((end_bytes[0] << 24) | (end_bytes[1] << 16) + | (end_bytes[2] << 8) | (end_bytes[3])) + + # Get ip addresses in range + addresses = [] + for i in range(start_i, end_i + 1): + address_bytes = [(i >> (24 - 8 * x)) & 0xff for x in range(4)] + addresses.append('%i.%i.%i.%i' % tuple(address_bytes)) + + except (ValueError, IndexError): + raise ValueError('%s is not an IP range' % entry) + + return addresses + + +def _print_errors(args, nodes, errors): + """ Print errors if they occured """ + if errors: + node_strings = get_node_strings(args, nodes, justify=True) + print("Command failed on these hosts") + for node in nodes: + if node in errors: + print("%s: %s" % (node_strings[node], errors[node])) + print + + # Print a special message for TFTP errors + if all(isinstance(x, TftpException) for x in errors.itervalues()): + print( + "There may be networking issues (when behind NAT) between " + + "the host (where cxmanage is running) and the Calxeda node " + + "when establishing a TFTP session. Please refer to the " + + "documentation for more information.\n" + ) + + +def _print_command_status(tasks, counter): + """ Print the status of a command """ + message = "\r%i successes | %i errors | %i nodes left | %s" + successes = len([x for x in tasks.values() if x.status == "Completed"]) + errors = len([x for x in tasks.values() if x.status == "Failed"]) + nodes_left = len(tasks) - successes - errors + dots = "".join(["." for x in range(counter % 4)]).ljust(3) + sys.stdout.write(message % (successes, errors, nodes_left, dots)) + sys.stdout.flush() + + +# These are needed for ipinfo and whenever version information is printed +COMPONENTS = [ + ("ecme_version", "ECME version"), + ("cdb_version", "CDB version"), + ("stage2_version", "Stage2boot version"), + ("bootlog_version", "Bootlog version"), + ("a9boot_version", "A9boot version"), + ("a15boot_version", "A15boot version"), + ("uboot_version", "Uboot version"), + ("ubootenv_version", "Ubootenv version"), + ("dtb_version", "DTB version"), +] + diff --git a/cxmanage_api/cli/commands/__init__.py b/cxmanage_api/cli/commands/__init__.py new file mode 100644 index 0000000..571a3c5 --- /dev/null +++ b/cxmanage_api/cli/commands/__init__.py @@ -0,0 +1,32 @@ +"""Calxeda: __init__.py""" + + +# Copyright (c) 2012, Calxeda Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Calxeda Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. diff --git a/cxmanage_api/cli/commands/config.py b/cxmanage_api/cli/commands/config.py new file mode 100644 index 0000000..bde21ca --- /dev/null +++ b/cxmanage_api/cli/commands/config.py @@ -0,0 +1,146 @@ +"""Calxeda: config.py """ + + +# Copyright (c) 2012, Calxeda Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Calxeda Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + + +from cxmanage_api.cli import get_tftp, get_nodes, get_node_strings, run_command + +from cxmanage_api.ubootenv import validate_boot_args, \ + validate_pxe_interface + + +def config_reset_command(args): + """reset to factory default settings""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp, verify_prompt=True) + + if not args.quiet: + print "Sending config reset command..." + + _, errors = run_command(args, nodes, "config_reset") + + if not args.quiet and not errors: + print "Command completed successfully.\n" + + return len(errors) > 0 + + +def config_boot_command(args): + """set A9 boot order""" + if args.boot_order == ['status']: + return config_boot_status_command(args) + + validate_boot_args(args.boot_order) + + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Setting boot order..." + + _, errors = run_command(args, nodes, "set_boot_order", + args.boot_order) + + if not args.quiet and not errors: + print "Command completed successfully.\n" + + return len(errors) > 0 + + +def config_boot_status_command(args): + """Get boot status command.""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Getting boot order..." + results, errors = run_command(args, nodes, "get_boot_order") + + # Print results + if results: + node_strings = get_node_strings(args, results, justify=True) + print "Boot order" + for node in nodes: + if node in results: + print "%s: %s" % (node_strings[node], ",".join(results[node])) + print + + if not args.quiet and errors: + print "Some errors occured during the command.\n" + + return len(errors) > 0 + + +def config_pxe_command(args): + """set the PXE boot interface""" + if args.interface == "status": + return config_pxe_status_command(args) + + validate_pxe_interface(args.interface) + + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Setting pxe interface..." + + _, errors = run_command(args, nodes, "set_pxe_interface", + args.interface) + + if not args.quiet and not errors: + print "Command completed successfully.\n" + + return len(errors) > 0 + + +def config_pxe_status_command(args): + """Gets pxe status.""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Getting pxe interface..." + results, errors = run_command(args, nodes, "get_pxe_interface") + + # Print results + if results: + node_strings = get_node_strings(args, results, justify=True) + print "PXE interface" + for node in nodes: + if node in results: + print "%s: %s" % (node_strings[node], results[node]) + print + + if not args.quiet and errors: + print "Some errors occured during the command.\n" + + return len(errors) > 0 diff --git a/cxmanage_api/cli/commands/fabric.py b/cxmanage_api/cli/commands/fabric.py new file mode 100644 index 0000000..9a410c1 --- /dev/null +++ b/cxmanage_api/cli/commands/fabric.py @@ -0,0 +1,83 @@ +"""Calxeda: fabric.py""" + + +# Copyright (c) 2012, Calxeda Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Calxeda Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + +from cxmanage_api.cli import get_tftp, get_nodes, run_command + + +def ipinfo_command(args): + """get ip info from a cluster or host""" + args.all_nodes = False + + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Getting IP addresses..." + + results, _ = run_command(args, nodes, "get_fabric_ipinfo") + + for node in nodes: + if node in results: + print 'IP info from %s' % node.ip_address + for node_id, node_address in results[node].iteritems(): + print 'Node %i: %s' % (node_id, node_address) + print + + return 0 + + +def macaddrs_command(args): + """get mac addresses from a cluster or host""" + args.all_nodes = False + + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Getting MAC addresses..." + results, errors = run_command(args, nodes, "get_fabric_macaddrs") + + for node in nodes: + if node in results: + print "MAC addresses from %s" % node.ip_address + for node_id in results[node]: + for port in results[node][node_id]: + for mac_address in results[node][node_id][port]: + print "Node %i, Port %i: %s" % (node_id, port, + mac_address) + print + + if not args.quiet and errors: + print "Some errors occured during the command.\n" + + return len(errors) == 0 diff --git a/cxmanage_api/cli/commands/fru_version.py b/cxmanage_api/cli/commands/fru_version.py new file mode 100644 index 0000000..65d0418 --- /dev/null +++ b/cxmanage_api/cli/commands/fru_version.py @@ -0,0 +1,71 @@ +"""Calxeda: fru_version.py """ + + +# Copyright (c) 2013, Calxeda Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Calxeda Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + + +from cxmanage_api.cli import get_tftp, get_nodes, get_node_strings, run_command + + +def node_fru_version_command(args): + """Get the node FRU version for each node. """ + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + results, errors = run_command(args, nodes, 'get_node_fru_version') + + # Print results if we were successful + if results: + node_strings = get_node_strings(args, results, justify=True) + for node in nodes: + print("%s: %s" % (node_strings[node], results[node])) + + print("") # For readability + + if not args.quiet and errors: + print('Some errors occured during the command.\n') + + +def slot_fru_version_command(args): + """Get the slot FRU version for each node. """ + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + results, errors = run_command(args, nodes, 'get_slot_fru_version') + + # Print results if we were successful + if results: + node_strings = get_node_strings(args, results, justify=True) + for node in nodes: + print("%s: %s" % (node_strings[node], results[node])) + + print("") # For readability + + if not args.quiet and errors: + print('Some errors occured during the command.\n') diff --git a/cxmanage_api/cli/commands/fw.py b/cxmanage_api/cli/commands/fw.py new file mode 100644 index 0000000..b131bf9 --- /dev/null +++ b/cxmanage_api/cli/commands/fw.py @@ -0,0 +1,172 @@ +"""Calxeda: fw.py """ + +# Copyright (c) 2012, Calxeda Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Calxeda Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + + +from pkg_resources import parse_version + +from cxmanage_api.cli import get_tftp, get_nodes, get_node_strings, \ + run_command, prompt_yes + +from cxmanage_api.image import Image +from cxmanage_api.firmware_package import FirmwarePackage + +# pylint: disable=R0912 +def fwupdate_command(args): + """update firmware on a cluster or host""" + def do_update(): + """ Do a single firmware check+update. Returns True on failure. """ + if not args.force: + if not args.quiet: + print "Checking hosts..." + + _, errors = run_command(args, nodes, "_check_firmware", + package, args.partition, args.priority) + if errors: + print "ERROR: Firmware update aborted." + return True + + if not args.quiet: + print "Updating firmware..." + + _, errors = run_command(args, nodes, "update_firmware", package, + args.partition, args.priority) + if errors: + print "ERROR: Firmware update failed." + return True + + return False + + def do_reset(): + """ Reset and wait. Returns True on failure. """ + if not args.quiet: + print "Checking ECME versions..." + + results, errors = run_command(args, nodes, "get_versions") + if errors: + print "ERROR: MC reset aborted. Backup partitions not updated." + return True + + for result in results.values(): + version = result.ecme_version.lstrip("v") + if parse_version(version) < parse_version("1.2.0"): + print "ERROR: MC reset is unsafe on ECME version v%s" % version + print "Please power cycle the system and start a new fwupdate." + return True + + if not args.quiet: + print "Resetting nodes..." + + results, errors = run_command(args, nodes, "mc_reset", True) + if errors: + print "ERROR: MC reset failed. Backup partitions not updated." + return True + + return False + + if args.image_type == "PACKAGE": + package = FirmwarePackage(args.filename) + else: + try: + simg = None + if args.force_simg: + simg = False + elif args.skip_simg: + simg = True + + image = Image(args.filename, args.image_type, simg, args.daddr, + args.skip_crc32, args.fw_version) + package = FirmwarePackage() + package.images.append(image) + except ValueError as err: + print "ERROR: %s" % err + return True + + if not args.all_nodes: + if args.force: + print( + 'WARNING: Updating firmware without --all-nodes' + + ' is dangerous.' + ) + else: + if not prompt_yes( + 'WARNING: Updating firmware without ' + + '--all-nodes is dangerous. Continue?' + ): + return 1 + + tftp = get_tftp(args) + nodes = get_nodes(args, tftp, verify_prompt=True) + + errors = do_update() + + if args.full and not errors: + errors = do_reset() + if not errors: + errors = do_update() + + if not args.quiet and not errors: + print "Command completed successfully.\n" + + return errors + + +def fwinfo_command(args): + """print firmware info""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Getting firmware info..." + + results, errors = run_command(args, nodes, "get_firmware_info") + + node_strings = get_node_strings(args, results, justify=False) + for node in nodes: + if node in results: + print "[ Firmware info for %s ]" % node_strings[node] + + for partition in results[node]: + print "Partition : %s" % partition.partition + print "Type : %s" % partition.type + print "Offset : %s" % partition.offset + print "Size : %s" % partition.size + print "Priority : %s" % partition.priority + print "Daddr : %s" % partition.daddr + print "Flags : %s" % partition.flags + print "Version : %s" % partition.version + print "In Use : %s" % partition.in_use + print + + if not args.quiet and errors: + print "Some errors occured during the command.\n" + + return len(errors) > 0 diff --git a/cxmanage_api/cli/commands/info.py b/cxmanage_api/cli/commands/info.py new file mode 100644 index 0000000..0f0b2ca --- /dev/null +++ b/cxmanage_api/cli/commands/info.py @@ -0,0 +1,103 @@ +"""Calxeda: info.py""" + + +# Copyright (c) 2012, Calxeda Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Calxeda Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + + +from cxmanage_api.cli import get_tftp, get_nodes, get_node_strings, \ + run_command, COMPONENTS + + +def info_command(args): + """print info from a cluster or host""" + if args.info_type in [None, 'basic']: + return info_basic_command(args) + elif args.info_type == 'ubootenv': + return info_ubootenv_command(args) + + +def info_basic_command(args): + """Print basic info""" + + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Getting info..." + results, errors = run_command(args, nodes, "get_versions") + + # Print results + node_strings = get_node_strings(args, results, justify=False) + for node in nodes: + if node in results: + result = results[node] + # Get mappings between attributes and formatted strings + components = COMPONENTS + + print "[ Info from %s ]" % node_strings[node] + print "Hardware version : %s" % result.hardware_version + print "Firmware version : %s" % result.firmware_version + # var is the variable, string is the printable string of var + for var, string in components: + if hasattr(result, var): + version = getattr(result, var) + print "%s: %s" % (string.ljust(19), version) + print + + if not args.quiet and errors: + print "Some errors occured during the command.\n" + + return len(errors) > 0 + + +def info_ubootenv_command(args): + """Print uboot info""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Getting u-boot environment..." + results, errors = run_command(args, nodes, "get_ubootenv") + + # Print results + node_strings = get_node_strings(args, results, justify=False) + for node in nodes: + if node in results: + ubootenv = results[node] + print "[ U-Boot Environment from %s ]" % node_strings[node] + for variable in ubootenv.variables: + print "%s=%s" % (variable, ubootenv.variables[variable]) + print + + if not args.quiet and errors: + print "Some errors occured during the command.\n" + + return len(errors) > 0 diff --git a/cxmanage_api/cli/commands/ipdiscover.py b/cxmanage_api/cli/commands/ipdiscover.py new file mode 100644 index 0000000..c6c3dee --- /dev/null +++ b/cxmanage_api/cli/commands/ipdiscover.py @@ -0,0 +1,59 @@ +"""Calxeda: ipdiscover.py""" + + +# Copyright (c) 2012, Calxeda Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Calxeda Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + +from cxmanage_api.cli import get_tftp, get_nodes, get_node_strings, run_command + + +def ipdiscover_command(args): + """discover server IP addresses""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print 'Getting server-side IP addresses...' + + results, errors = run_command(args, nodes, 'get_server_ip', args.interface, + args.ipv6, args.server_user, args.server_password, args.aggressive) + + if results: + node_strings = get_node_strings(args, results, justify=True) + print 'IP addresses (ECME, Server)' + for node in nodes: + if node in results: + print '%s: %s' % (node_strings[node], results[node]) + print + + if not args.quiet and errors: + print 'Some errors occurred during the command.' + + return len(errors) > 0 diff --git a/cxmanage_api/cli/commands/ipmitool.py b/cxmanage_api/cli/commands/ipmitool.py new file mode 100644 index 0000000..2c54b37 --- /dev/null +++ b/cxmanage_api/cli/commands/ipmitool.py @@ -0,0 +1,63 @@ +"""Calxeda: ipmitool.py""" + + +# Copyright (c) 2012, Calxeda Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Calxeda Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + + +from cxmanage_api.cli import get_tftp, get_nodes, get_node_strings, run_command + +def ipmitool_command(args): + """run arbitrary ipmitool command""" + if args.lanplus: + ipmitool_args = ['-I', 'lanplus'] + args.ipmitool_args + else: + ipmitool_args = args.ipmitool_args + + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Running IPMItool command..." + results, errors = run_command(args, nodes, "ipmitool_command", + ipmitool_args) + + # Print results + node_strings = get_node_strings(args, results, justify=False) + for node in nodes: + if node in results and results[node] != "": + print "[ IPMItool output from %s ]" % node_strings[node] + print results[node] + print + + if not args.quiet and errors: + print "Some errors occured during the command.\n" + + return len(errors) > 0 diff --git a/cxmanage_api/cli/commands/mc.py b/cxmanage_api/cli/commands/mc.py new file mode 100644 index 0000000..ac258ab --- /dev/null +++ b/cxmanage_api/cli/commands/mc.py @@ -0,0 +1,51 @@ +"""Calxeda: mc.py""" + + +# Copyright (c) 2012, Calxeda Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Calxeda Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + + +from cxmanage_api.cli import get_tftp, get_nodes, run_command + + +def mcreset_command(args): + """reset the management controllers of a cluster or host""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print 'Sending MC reset command...' + + _, errors = run_command(args, nodes, 'mc_reset') + + if not args.quiet and not errors: + print 'Command completed successfully.\n' + + return len(errors) > 0 diff --git a/cxmanage_api/cli/commands/power.py b/cxmanage_api/cli/commands/power.py new file mode 100644 index 0000000..623c38d --- /dev/null +++ b/cxmanage_api/cli/commands/power.py @@ -0,0 +1,116 @@ +"""Calxeda: power.py """ + + +# Copyright (c) 2012, Calxeda Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Calxeda Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + +from cxmanage import get_tftp, get_nodes, get_node_strings, run_command + + +def power_command(args): + """change the power state of a cluster or host""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print 'Sending power %s command...' % args.power_mode + + _, errors = run_command(args, nodes, 'set_power', args.power_mode) + + if not args.quiet and not errors: + print 'Command completed successfully.\n' + + return len(errors) > 0 + + +def power_status_command(args): + """Executes the power status command with args.""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print 'Getting power status...' + results, errors = run_command(args, nodes, 'get_power') + + # Print results + if results: + node_strings = get_node_strings(args, results, justify=True) + print 'Power status' + for node in nodes: + if node in results: + result = 'on' if results[node] else 'off' + print '%s: %s' % (node_strings[node], result) + print + + if not args.quiet and errors: + print 'Some errors occured during the command.\n' + + return len(errors) > 0 + + +def power_policy_command(args): + """Executes power policy command with args.""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print 'Setting power policy to %s...' % args.policy + + _, errors = run_command(args, nodes, 'set_power_policy', + args.policy) + + if not args.quiet and not errors: + print 'Command completed successfully.\n' + + return len(errors) > 0 + + +def power_policy_status_command(args): + """Executes the power policy status command with args.""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print 'Getting power policy status...' + results, errors = run_command(args, nodes, 'get_power_policy') + + # Print results + if results: + node_strings = get_node_strings(args, results, justify=True) + print 'Power policy status' + for node in nodes: + if node in results: + print '%s: %s' % (node_strings[node], results[node]) + print + + if not args.quiet and errors: + print 'Some errors occured during the command.\n' + + return len(errors) > 0 diff --git a/cxmanage_api/cli/commands/sensor.py b/cxmanage_api/cli/commands/sensor.py new file mode 100644 index 0000000..3a27143 --- /dev/null +++ b/cxmanage_api/cli/commands/sensor.py @@ -0,0 +1,87 @@ +"""Calxeda: sensor.py""" + + +# Copyright (c) 2012, Calxeda Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Calxeda Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + + +from cxmanage import get_tftp, get_nodes, get_node_strings, run_command + +# pylint: disable=R0914 +def sensor_command(args): + """read sensor values from a cluster or host""" + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + if not args.quiet: + print "Getting sensor readings..." + results, errors = run_command(args, nodes, "get_sensors", + args.sensor_name) + + sensors = {} + for node in nodes: + if node in results: + for sensor_name, sensor in results[node].iteritems(): + if not sensor_name in sensors: + sensors[sensor_name] = [] + + reading = sensor.sensor_reading.replace("(+/- 0) ", "") + try: + value = float(reading.split()[0]) + suffix = reading.lstrip("%f " % value) + sensors[sensor_name].append((node, value, suffix)) + except ValueError: + sensors[sensor_name].append((node, reading, "")) + + node_strings = get_node_strings(args, results, justify=True) + jsize = len(node_strings.itervalues().next()) + for sensor_name, readings in sensors.iteritems(): + print sensor_name + + for node, reading, suffix in readings: + print "%s: %.2f %s" % (node_strings[node], reading, suffix) + + try: + if all(suffix == x[2] for x in readings): + minimum = min(x[1] for x in readings) + maximum = max(x[1] for x in readings) + average = sum(x[1] for x in readings) / len(readings) + print "%s: %.2f %s" % ("Minimum".ljust(jsize), minimum, suffix) + print "%s: %.2f %s" % ("Maximum".ljust(jsize), maximum, suffix) + print "%s: %.2f %s" % ("Average".ljust(jsize), average, suffix) + except ValueError: + pass + + print + + if not args.quiet and errors: + print "Some errors occured during the command.\n" + + return len(errors) > 0 diff --git a/cxmanage_api/cli/commands/tspackage.py b/cxmanage_api/cli/commands/tspackage.py new file mode 100644 index 0000000..d6ee198 --- /dev/null +++ b/cxmanage_api/cli/commands/tspackage.py @@ -0,0 +1,448 @@ +"""Calxeda: tspackage.py""" + + +# Copyright 2013 Calxeda, Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Calxeda Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + + +# +# A cxmanage command to collect information about a node and archive it. +# +# Example: +# cxmanage tspackage 10.10.10.10 +# + + +import os +import time +import shutil +import tarfile +import tempfile + +from cxmanage import get_tftp, get_nodes, run_command, COMPONENTS + + +def tspackage_command(args): + """Get information pertaining to each node. + This includes: + Version info (like cxmanage info) + MAC addresses + Sensor readings + Sensor data records + Firmware info + Boot order + SELs (System Event Logs), + Depth charts + Routing Tables + + This data will be written to a set of files. Each node will get its own + file. All of these files will be archived and saved to the user's current + directory. + + Internally, this command is called from: + ~/virtual_testenv/workspace/cx_manage_util/scripts/cxmanage + + """ + tftp = get_tftp(args) + nodes = get_nodes(args, tftp) + + # Make a temporary directory to store the node information files + original_dir = os.getcwd() + temp_dir = tempfile.mkdtemp() + os.chdir(temp_dir) + tspackage_dir = "tspackage.%s" % time.strftime("%Y%m%d%H%M%S") + os.mkdir(tspackage_dir) + os.chdir(tspackage_dir) + + quiet = args.quiet + + if not quiet: + print("Getting version information...") + write_version_info(args, nodes) + + if not quiet: + print("Getting node FRU version...") + write_node_fru_version(args, nodes) + + if not quiet: + print("Getting slot FRU version...") + write_slot_fru_version(args, nodes) + + if not quiet: + print("Getting boot order...") + write_boot_order(args, nodes) + + if not quiet: + print("Getting MAC addresses...") + write_mac_addrs(args, nodes) + + if not quiet: + print("Getting sensor information...") + write_sensor_info(args, nodes) + + if not quiet: + print("Getting firmware information...") + write_fwinfo(args, nodes) + + if not quiet: + print("Getting system event logs...") + write_sel(args, nodes) + + if not quiet: + print("Getting depth charts...") + write_depth_chart(args, nodes) + + if not quiet: + print("Getting routing tables...") + write_routing_table(args, nodes) + + # Archive the files + archive(os.getcwd(), original_dir) + + # The original files are already archived, so we can delete them. + shutil.rmtree(temp_dir) + + +def write_version_info(args, nodes): + """Write the version info (like cxmanage info) for each node + to their respective files. + + """ + info_results, _ = run_command(args, nodes, "get_versions") + + + for node in nodes: + lines = [] # The lines of text to write to file + + # Since this is the first line of the file, we don't need a \n + write_to_file( + node, + "[ Version Info for Node %d ]" % node.node_id, + add_newlines=False + ) + + lines.append("ECME IP Address : %s" % node.ip_address) + + if node in info_results: + info_result = info_results[node] + lines.append( + "Hardware version : %s" % + info_result.hardware_version + ) + lines.append( + "Firmware version : %s" % + info_result.firmware_version + ) + + # Get mappings between attributes and formatted strings + components = COMPONENTS + for var, description in components: + if hasattr(info_result, var): + version = getattr(info_result, var) + lines.append("%s: %s" % (description.ljust(19), version)) + else: + lines.append("No version information could be found.") + + write_to_file(node, lines) + +def write_node_fru_version(args, nodes): + """Write the node and slot FRU versions for each node to their + respective files. + + """ + node_fru_results, _ = run_command(args, nodes, "get_node_fru_version") + + for node in nodes: + lines = [] # Lines of text to write to file + if node in node_fru_results: + lines.append("%s: %s" % \ + ("Node FRU Version".ljust(19), node_fru_results[node])) + else: + lines.append("\nWARNING: No node FRU found!") + write_to_file(node, lines) + +def write_slot_fru_version(args, nodes): + """Write the node and slot FRU versions for each node to their + respective files. + + """ + slot_fru_results, _ = run_command(args, nodes, "get_slot_fru_version") + + for node in nodes: + lines = [] # Lines of text to write to file + if node in slot_fru_results: + lines.append("%s: %s" % \ + ("Slot FRU Version".ljust(19), slot_fru_results[node])) + else: + lines.append("Error reading slot FRU. Perhaps the system board " + + "does not have slot FRUs?") + + write_to_file(node, lines) + +def write_mac_addrs(args, nodes): + """Write the MAC addresses for each node to their respective files.""" + mac_addr_results, _ = run_command( + args, + nodes, + "get_fabric_macaddrs" + ) + + for node in nodes: + lines = [] # Lines of text to write to file + # \n is used here to give a blank line before this section + lines.append("\n[ MAC Addresses for Node %d ]" % node.node_id) + + if node in mac_addr_results: + for port in mac_addr_results[node][node.node_id]: + for mac_address in mac_addr_results[node][node.node_id][port]: + lines.append( + "Node %i, Port %i: %s" % + (node.node_id, port, mac_address) + ) + else: + lines.append("\nWARNING: No MAC addresses found!") + + write_to_file(node, lines) + +# pylint: disable=R0914 +def write_sensor_info(args, nodes): + """Write sensor information for each node to their respective files.""" + args.sensor_name = "" + + results, _ = run_command(args, nodes, "get_sensors", + args.sensor_name) + + sensors = {} + for node in nodes: + lines = [] # Lines of text to write to file + # \n is used here to give a blank line before this section + lines.append("\n[ Sensors for Node %d ]" % node.node_id) + + if node in results: + for sensor_name, sensor in results[node].iteritems(): + if not sensor_name in sensors: + sensors[sensor_name] = [] + + reading = sensor.sensor_reading.replace("(+/- 0) ", "") + try: + value = float(reading.split()[0]) + suffix = reading.lstrip("%f " % value) + sensors[sensor_name].append((node, value, suffix)) + except ValueError: + sensors[sensor_name].append((node, reading, "")) + else: + print("Could not get sensor info!") + lines.append("Could not get sensor info!") + + for sensor_name, readings in sensors.iteritems(): + for reading_node, reading, suffix in readings: + if reading_node.ip_address == node.ip_address: + left_side = "{:<18}".format(sensor_name) + right_side = ": %.2f %s" % (reading, suffix) + lines.append(left_side + right_side) + + write_to_file(node, lines) + + +def write_fwinfo(args, nodes): + """Write information about each node's firware partitions + to its respective file. + + """ + results, _ = run_command(args, nodes, "get_firmware_info") + + for node in nodes: + lines = [] # Lines of text to write to file + # \n is used here to give a blank line before this section + lines.append("\n[ Firmware Info for Node %d ]" % node.node_id) + + if node in results: + first_partition = True # The first partiton doesn't need \n + + for partition in results[node]: + if first_partition: + lines.append("Partition : %s" % partition.partition) + first_partition = False + else: + lines.append("\nPartition : %s" % partition.partition) + lines.append("Type : %s" % partition.type) + lines.append("Offset : %s" % partition.offset) + lines.append("Size : %s" % partition.size) + lines.append("Priority : %s" % partition.priority) + lines.append("Daddr : %s" % partition.daddr) + lines.append("Flags : %s" % partition.flags) + lines.append("Version : %s" % partition.version) + lines.append("In Use : %s" % partition.in_use) + else: + lines.append("Could not get firmware info!") + write_to_file(node, lines) + + +def write_boot_order(args, nodes): + """Write the boot order of each node to their respective files.""" + results, _ = run_command(args, nodes, "get_boot_order") + + for node in nodes: + lines = [] # Lines of text to write to file + # \n is used here to give a blank line before this section + lines.append("\n[ Boot Order for Node %d ]" % node.node_id) + + if node in results: + lines.append(", ".join(results[node])) + else: + lines.append("Could not get boot order!") + + write_to_file(node, lines) + + +def write_sel(args, nodes): + """Write the SEL for each node to their respective files.""" + results, _ = run_command(args, nodes, "get_sel") + + for node in nodes: + lines = [] # Lines of text to write to file + # \n is used here to give a blank line before this section + lines.append("\n[ System Event Log for Node %d ]" % node.node_id) + + try: + if node in results: + for event in results[node]: + lines.append(event) + + # pylint: disable=W0703 + except Exception as error: + lines.append("Could not get SEL! " + str(error)) + if not args.quiet: + print("Failed to get system event log for " + node.ip_address) + + write_to_file(node, lines) + + +def write_depth_chart(args, nodes): + """Write the depth chart for each node to their respective files.""" + depth_results, _ = run_command(args, nodes, "get_depth_chart") + + for node in nodes: + lines = [] # Lines of text to write to file + # \n is used here to give a blank line before this section + lines.append("\n[ Depth Chart for Node %d ]" % node.node_id) + + if node in depth_results: + depth_chart = depth_results[node] + for key in depth_chart: + subchart = depth_chart[key] + lines.append("To node " + str(key)) + + # The 'shortest' entry is one tuple, but + # the 'others' are a list. + for subkey in subchart: + if str(subkey) == "shortest": + lines.append( + " " + str(subkey) + + " : " + str(subchart[subkey]) + ) + else: + for entry in subchart[subkey]: + lines.append( + " " + str(subkey) + + " : " + str(entry) + ) + + else: + lines.append("Could not get depth chart!") + + write_to_file(node, lines) + + +def write_routing_table(args, nodes): + """Write the routing table for each node to their respective files.""" + routing_results, _ = run_command(args, nodes, "get_routing_table") + + for node in nodes: + lines = [] # Lines of text to write to file + # \n is used here to give a blank line before this section + lines.append("\n[ Routing Table for Node %d ]" % node.node_id) + + if node in routing_results: + table = routing_results[node] + for node_to in table: + lines.append(str(node_to) + " : " + str(table[node_to])) + else: + lines.append("Could not get routing table!") + + write_to_file(node, lines) + + +def write_to_file(node, to_write, add_newlines=True): + """Append to_write to an info file for every node in nodes. + + :param node: Node object to write about + :type node: Node object + :param to_write: Text to write to the files + :type to_write: List of strings + :param add_newlines: Whether to add newline characters before + every item in to_write. True by default. True will add newline + characters. + :type add_newlines: bool + + """ + with open("node" + str(node.node_id) + ".txt", 'a') as node_file: + if add_newlines: + # join() doesn't add a newline before the first item + to_write[0] = "\n" + to_write[0] + node_file.write("\n".join(to_write)) + else: + node_file.write("".join(to_write)) + + +def archive(directory_to_archive, destination): + """Creates a .tar containing everything in the directory_to_archive. + The .tar is saved to destination with the same name as the original + directory_to_archive, but with .tar appended. + + :param directory_to_archive: A path to the directory to be archived. + :type directory_to_archive: string + + :param destination: A path to the location the .tar should be saved + :type destination: string + + """ + os.chdir(os.path.dirname(directory_to_archive)) + + tar_name = os.path.basename(directory_to_archive) + ".tar" + tar_name = os.path.join(destination, tar_name) + + with tarfile.open(tar_name, "w") as tar: + tar.add(os.path.basename(directory_to_archive)) + + print( + "Finished! One archive created:\n" + + os.path.join(destination, tar_name) + ) diff --git a/scripts/cxmanage b/scripts/cxmanage index a3edb37..ce7748c 100755 --- a/scripts/cxmanage +++ b/scripts/cxmanage @@ -36,19 +36,19 @@ import pkg_resources import subprocess import sys -from cxmanage.commands.power import power_command, power_status_command, \ - power_policy_command, power_policy_status_command -from cxmanage.commands.mc import mcreset_command -from cxmanage.commands.fw import fwupdate_command, fwinfo_command -from cxmanage.commands.sensor import sensor_command -from cxmanage.commands.fabric import ipinfo_command, macaddrs_command -from cxmanage.commands.config import config_reset_command, config_boot_command, \ - config_pxe_command -from cxmanage.commands.info import info_command -from cxmanage.commands.ipmitool import ipmitool_command -from cxmanage.commands.ipdiscover import ipdiscover_command -from cxmanage.commands.tspackage import tspackage_command -from cxmanage.commands.fru_version import node_fru_version_command, \ +from cxmanage_api.cli.commands.power import power_command, \ + power_status_command, power_policy_command, power_policy_status_command +from cxmanage_api.cli.commands.mc import mcreset_command +from cxmanage_api.cli.commands.fw import fwupdate_command, fwinfo_command +from cxmanage_api.cli.commands.sensor import sensor_command +from cxmanage_api.cli.commands.fabric import ipinfo_command, macaddrs_command +from cxmanage_api.cli.commands.config import config_reset_command, \ + config_boot_command, config_pxe_command +from cxmanage_api.cli.commands.info import info_command +from cxmanage_api.cli.commands.ipmitool import ipmitool_command +from cxmanage_api.cli.commands.ipdiscover import ipdiscover_command +from cxmanage_api.cli.commands.tspackage import tspackage_command +from cxmanage_api.cli.commands.fru_version import node_fru_version_command, \ slot_fru_version_command diff --git a/setup.py b/setup.py index c2b5bb5..daba960 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,12 @@ from setuptools import setup setup( name='cxmanage', version='0.9.0', - packages=['cxmanage', 'cxmanage.commands', 'cxmanage_api', 'cxmanage_test'], + packages=[ + 'cxmanage_api', + 'cxmanage_api.cli', + 'cxmanage_api.cli.commands', + 'cxmanage_test' + ], scripts=['scripts/cxmanage', 'scripts/sol_tabs'], description='Calxeda Management Utility', # NOTE: As of right now, the pyipmi version requirement needs to be updated -- cgit v1.2.1