summaryrefslogtreecommitdiff
path: root/cxmanage_api/fabric.py
diff options
context:
space:
mode:
Diffstat (limited to 'cxmanage_api/fabric.py')
-rw-r--r--cxmanage_api/fabric.py315
1 files changed, 273 insertions, 42 deletions
diff --git a/cxmanage_api/fabric.py b/cxmanage_api/fabric.py
index 34f435e..7582aee 100644
--- a/cxmanage_api/fabric.py
+++ b/cxmanage_api/fabric.py
@@ -1,4 +1,8 @@
-# Copyright (c) 2012, Calxeda Inc.
+# pylint: disable=C0302
+"""Calxeda: fabric.py """
+
+
+# Copyright (c) 2012-2013, Calxeda Inc.
#
# All rights reserved.
#
@@ -28,12 +32,16 @@
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.
+import time
+
from cxmanage_api.tasks import DEFAULT_TASK_QUEUE
from cxmanage_api.tftp import InternalTftp
from cxmanage_api.node import Node as NODE
-from cxmanage_api.cx_exceptions import CommandFailedError
+from cxmanage_api.cx_exceptions import CommandFailedError, TimeoutError, \
+ IpmiError, TftpException
+# pylint: disable=R0902,R0903, R0904
class Fabric(object):
""" The Fabric class provides management of multiple nodes.
@@ -56,6 +64,54 @@ class Fabric(object):
:type node: `Node <node.html>`_
"""
+ class CompositeBMC(object):
+ """ Composite BMC object. Provides a mechanism to run BMC
+ commands in parallel across all nodes.
+ """
+
+ def __init__(self, fabric):
+ self.fabric = fabric
+
+ def __getattr__(self, name):
+ """ If the underlying BMCs have a method by this name, then return
+ a callable function that does it in parallel across all nodes.
+ """
+ nodes = self.fabric.nodes
+ task_queue = self.fabric.task_queue
+
+ for node in nodes.values():
+ if ((not hasattr(node.bmc, name)) or
+ (not hasattr(getattr(node.bmc, name), "__call__"))):
+ raise AttributeError(
+ "'CompositeBMC' object has no attribute '%s'"
+ % name
+ )
+
+ def function(*args, **kwargs):
+ """ Run the named BMC command in parallel across all nodes. """
+ tasks = {}
+ for node_id, node in nodes.iteritems():
+ tasks[node_id] = task_queue.put(
+ getattr(node.bmc, name),
+ *args,
+ **kwargs
+ )
+
+ results = {}
+ errors = {}
+ for node_id, task in tasks.items():
+ task.join()
+ if task.status == "Completed":
+ results[node_id] = task.result
+ else:
+ errors[node_id] = task.error
+ if errors:
+ raise CommandFailedError(results, errors)
+ return results
+
+ return function
+
+ # pylint: disable=R0913
def __init__(self, ip_address, username="admin", password="admin",
tftp=None, ecme_tftp_port=5001, task_queue=None,
verbose=False, node=None):
@@ -68,6 +124,7 @@ class Fabric(object):
self.task_queue = task_queue
self.verbose = verbose
self.node = node
+ self.cbmc = Fabric.CompositeBMC(self)
self._nodes = {}
@@ -77,9 +134,6 @@ class Fabric(object):
if (not self.task_queue):
self.task_queue = DEFAULT_TASK_QUEUE
- if (not self._tftp):
- self._tftp = InternalTftp()
-
def __eq__(self, other):
"""__eq__() override."""
return (isinstance(other, Fabric) and self.nodes == other.nodes)
@@ -104,6 +158,9 @@ class Fabric(object):
:rtype: `Tftp <tftp.html>`_
"""
+ if (not self._tftp):
+ self._tftp = InternalTftp.default()
+
return self._tftp
@tftp.setter
@@ -137,7 +194,8 @@ class Fabric(object):
"""
if not self._nodes:
- self._discover_nodes(self.ip_address)
+ self.refresh()
+
return self._nodes
@property
@@ -154,6 +212,47 @@ class Fabric(object):
"""
return self.nodes[0]
+ def refresh(self, wait=False, timeout=600):
+ """Gets the nodes of this fabric by pulling IP info from a BMC."""
+ def get_nodes():
+ """Returns a dictionary of nodes reported by the primary node IP"""
+ new_nodes = {}
+ node = self.node(
+ ip_address=self.ip_address, username=self.username,
+ password=self.password, tftp=self.tftp,
+ ecme_tftp_port=self.ecme_tftp_port, verbose=self.verbose
+ )
+ ipinfo = node.get_fabric_ipinfo()
+ for node_id, node_address in ipinfo.iteritems():
+ new_nodes[node_id] = self.node(
+ ip_address=node_address, username=self.username,
+ password=self.password, tftp=self.tftp,
+ ecme_tftp_port=self.ecme_tftp_port,
+ verbose=self.verbose
+ )
+ new_nodes[node_id].node_id = node_id
+ return new_nodes
+
+ initial_node_count = len(self._nodes)
+ self._nodes = {}
+
+ if wait:
+ deadline = time.time() + timeout
+ while time.time() < deadline:
+ try:
+ self._nodes = get_nodes()
+ if len(self._nodes) >= initial_node_count:
+ break
+ except (IpmiError, TftpException):
+ pass
+ else:
+ raise TimeoutError(
+ "Fabric refresh timed out. Rediscovered %i of %i nodes"
+ % (len(self._nodes), initial_node_count)
+ )
+ else:
+ self._nodes = get_nodes()
+
def get_mac_addresses(self):
"""Gets MAC addresses from all nodes.
@@ -165,26 +264,20 @@ class Fabric(object):
3: ['fc:2f:40:88:b3:6c', 'fc:2f:40:88:b3:6d', 'fc:2f:40:88:b3:6e']
}
- :param async: Flag that determines if the command result (dictionary)
- is returned or a Task object (can get status, etc.).
- :type async: boolean
-
:return: The MAC addresses for each node.
:rtype: dictionary
"""
return self.primary_node.get_fabric_macaddrs()
- def get_uplink_info(self):
+ def get_uplink_info(self, async=False):
"""Gets the fabric uplink info.
>>> fabric.get_uplink_info()
- {
- 0: {0: 0, 1: 0, 2: 0}
- 1: {0: 0, 1: 0, 2: 0}
- 2: {0: 0, 1: 0, 2: 0}
- 3: {0: 0, 1: 0, 2: 0}
- }
+ {0: 'Node 0: eth0 0, eth1 0, mgmt 0',
+ 1: 'Node 1: eth0 0, eth1 0, mgmt 0',
+ 2: 'Node 2: eth0 0, eth1 0, mgmt 0',
+ 3: 'Node 3: eth0 0, eth1 0, mgmt 0'}
:param async: Flag that determines if the command result (dictionary)
is returned or a Task object (can get status, etc.).
@@ -194,7 +287,23 @@ class Fabric(object):
:rtype: dictionary
"""
- return self.primary_node.get_fabric_uplink_info()
+ return self._run_on_all_nodes(async, "get_uplink_info")
+
+ def get_uplink_speed(self, async=False):
+ """Gets the uplink speed of every node in the fabric.
+
+ >>> fabric.get_uplink_speed()
+ {0: 1, 1: 0, 2: 0, 3: 0}
+
+ :param async: Flag that determines if the command result (dictionary)
+ is returned or a Task object (can get status, etc.).
+ :type async: boolean
+
+ :return: The uplink info for each node.
+ :rtype: dictionary
+
+ """
+ return self._run_on_all_nodes(async, "get_uplink_speed")
def get_power(self, async=False):
"""Returns the power status for all nodes.
@@ -212,7 +321,7 @@ class Fabric(object):
"""
return self._run_on_all_nodes(async, "get_power")
- def set_power(self, mode, async=False):
+ def set_power(self, mode, async=False, ignore_existing_state=False):
"""Send an IPMI power command to all nodes.
>>> # On ...
@@ -228,9 +337,13 @@ class Fabric(object):
:param async: Flag that determines if the command result (dictionary)
is returned or a Command object (can get status, etc.).
:type async: boolean
+ :param ignore_existing_state: Flag that allows the caller to only try
+ to turn on or off nodes that are not
+ turned on or off, respectively.
+ :type ignore_existing_state: boolean
"""
- self._run_on_all_nodes(async, "set_power", mode)
+ self._run_on_all_nodes(async, "set_power", mode, ignore_existing_state)
def get_power_policy(self, async=False):
"""Gets the power policy from all nodes.
@@ -476,6 +589,41 @@ class Fabric(object):
"""
return self._run_on_all_nodes(async, "get_boot_order")
+ def set_pxe_interface(self, interface, async=False):
+ """Sets the pxe interface on all nodes.
+
+ >>> fabric.set_pxe_interface(interface='eth0')
+
+ :param interface: Inteface for pxe requests
+ :type interface: string
+ :param async: Flag that determines if the command result (dictionary)
+ is returned or a Command object (can get status, etc.).
+ :type async: boolean
+
+ """
+ self._run_on_all_nodes(async, "set_pxe_interface", interface)
+
+ def get_pxe_interface(self, async=False):
+ """Gets the pxe interface from all nodes.
+
+ >>> fabric.get_pxe_interface()
+ {
+ 0: 'eth0',
+ 1: 'eth0',
+ 2: 'eth0',
+ 3: 'eth0'
+ }
+
+ :param async: Flag that determines if the command result (dictionary)
+ is returned or a Command object (can get status, etc.).
+ :type async: boolean
+
+ :returns: The boot order of each node on this fabric.
+ :rtype: dictionary or `Task <tasks.html>`__
+
+ """
+ return self._run_on_all_nodes(async, "get_pxe_interface")
+
def get_versions(self, async=False):
"""Gets the version info from all nodes.
@@ -488,7 +636,8 @@ class Fabric(object):
}
.. seealso::
- `Node.get_versions() <node.html#cxmanage_api.node.Node.get_versions>`_
+ `Node.get_versions() \
+<node.html#cxmanage_api.node.Node.get_versions>`_
:param async: Flag that determines if the command result (dictionary)
is returned or a Command object (can get status, etc.).
@@ -527,7 +676,8 @@ class Fabric(object):
}
.. seealso::
- `Node.get_versions_dict() <node.html#cxmanage_api.node.Node.get_versions_dict>`_
+ `Node.get_versions_dict() \
+<node.html#cxmanage_api.node.Node.get_versions_dict>`_
:param async: Flag that determines if the command result (dictionary)
is returned or a Task object (can get status, etc.).
@@ -586,6 +736,7 @@ class Fabric(object):
"""
return self._run_on_all_nodes(async, "get_ubootenv")
+ # pylint: disable=R0913
def get_server_ip(self, interface=None, ipv6=False, user="user1",
password="1Password", aggressive=False, async=False):
"""Get the server IP address from all nodes. The nodes must be powered
@@ -716,7 +867,7 @@ class Fabric(object):
:param nodeid: Node id from which the macaddr is to be remove
:type nodeid: integer
- :param iface: interface on the node from which the macaddr is to be removed
+ :param iface: interface on the node which the macaddr is to be removed
:type iface: integer
:param macaddr: mac address to be removed
:type macaddr: string
@@ -725,6 +876,51 @@ class Fabric(object):
self.primary_node.bmc.fabric_rm_macaddr(nodeid=nodeid, iface=iface,
macaddr=macaddr)
+ def set_macaddr_base(self, macaddr):
+ """ Set a base MAC address for a custom range.
+
+ >>> fabric.set_macaddr_base("66:55:44:33:22:11")
+
+ :param macaddr: mac address base to use
+ :type macaddr: string
+
+ """
+ self.primary_node.bmc.fabric_config_set_macaddr_base(macaddr=macaddr)
+
+ def get_macaddr_base(self):
+ """ Get the base MAC address for custom ranges.
+
+ >>> fabric.get_macaddr_base()
+ '08:00:00:00:08:5c'
+
+ :return: mac address base
+ :rtype: string
+ """
+ return self.primary_node.bmc.fabric_config_get_macaddr_base()
+
+ def set_macaddr_mask(self, mask):
+ """ Set MAC address mask for a custom range.
+
+ >>> fabric.set_macaddr_mask("ff:ff:ff:ff:ff:00")
+
+ :param macaddr: mac address mask to use
+ :type macaddr: string
+
+ """
+ self.primary_node.bmc.fabric_config_set_macaddr_mask(mask=mask)
+
+ def get_macaddr_mask(self):
+ """ Get the MAC address mask for custom ranges.
+
+ >>> fabric.get_macaddr_mask()
+ '08:00:00:00:08:5c'
+
+ :return: mac address mask
+ :rtype: string
+
+ """
+ return self.primary_node.bmc.fabric_config_get_macaddr_mask()
+
def get_linkspeed_policy(self):
"""Get the global linkspeed policy for the fabric. In the partition
world this means the linkspeed for Configuration 0, Partition 0,
@@ -797,7 +993,7 @@ class Fabric(object):
def set_uplink(self, uplink=0, iface=0):
"""Set the uplink for an interface to xmit a packet out of the cluster.
- >>> fabric.set_uplink(0,0)
+ >>> fabric.set_uplink(uplink=0,iface=0)
:param uplink: The uplink to set.
:type uplink: integer
@@ -811,6 +1007,33 @@ class Fabric(object):
def get_link_stats(self, link=0, async=False):
"""Get the link_stats for each node in the fabric.
+ >>> fabric.get_link_stats()
+ {0: {'FS_LC0_BYTE_CNT_0': '0x0',
+ 'FS_LC0_BYTE_CNT_1': '0x0',
+ 'FS_LC0_CFG_0': '0x1000d07f',
+ 'FS_LC0_CFG_1': '0x105f',
+ 'FS_LC0_CM_RXDATA_0': '0x0',
+ 'FS_LC0_CM_RXDATA_1': '0x0',
+ 'FS_LC0_CM_TXDATA_0': '0x82000002',
+ 'FS_LC0_CM_TXDATA_1': '0x0',
+ 'FS_LC0_PKT_CNT_0': '0x0',
+ 'FS_LC0_PKT_CNT_1': '0x0',
+ 'FS_LC0_RDRPSCNT': '0x3e89f',
+ 'FS_LC0_RERRSCNT': '0x0',
+ 'FS_LC0_RMCSCNT': '0x174b9bf',
+ 'FS_LC0_RPKTSCNT': '0x0',
+ 'FS_LC0_RUCSCNT': '0x43e9b',
+ 'FS_LC0_SC_STAT': '0x0',
+ 'FS_LC0_STATE': '0x1033',
+ 'FS_LC0_TDRPSCNT': '0x0',
+ 'FS_LC0_TPKTSCNT': '0x1'},
+ }}
+ >>> #
+ >>> # Output trimmed for brevity ...
+ >>> # The data shown for node 0 is the same type of data for each
+ >>> # node in the fabric.
+ >>> #
+
:param link: The link to get stats for (0-4).
:type link: integer
@@ -827,6 +1050,9 @@ class Fabric(object):
def get_linkmap(self, async=False):
"""Get the linkmap for each node in the fabric.
+ >>> fabric.get_linkmap()
+ {0: {1: 2, 3: 1, 4: 3}, 1: {3: 0}, 2: {3: 0, 4: 3}, 3: {3: 0, 4: 2}}
+
:param async: Flag that determines if the command result (dictionary)
is returned or a Task object (can get status, etc.).
:type async: boolean
@@ -840,6 +1066,12 @@ class Fabric(object):
def get_routing_table(self, async=False):
"""Get the routing_table for the fabric.
+ >>> fabric.get_routing_table()
+ {0: {1: [0, 0, 0, 3, 0], 2: [0, 3, 0, 0, 2], 3: [0, 2, 0, 0, 3]},
+ 1: {0: [0, 0, 0, 3, 0], 2: [0, 0, 0, 2, 0], 3: [0, 0, 0, 2, 0]},
+ 2: {0: [0, 0, 0, 3, 2], 1: [0, 0, 0, 2, 0], 3: [0, 0, 0, 2, 3]},
+ 3: {0: [0, 0, 0, 3, 2], 1: [0, 0, 0, 2, 0], 2: [0, 0, 0, 2, 3]}}
+
:param async: Flag that determines if the command result (dictionary)
is returned or a Task object (can get status, etc.).
:type async: boolean
@@ -853,6 +1085,20 @@ class Fabric(object):
def get_depth_chart(self, async=False):
"""Get the depth_chart for the fabric.
+ >>> fabric.get_depth_chart()
+ {0: {1: {'shortest': (0, 0)},
+ 2: {'others': [(3, 1)], 'shortest': (0, 0)},
+ 3: {'others': [(2, 1)], 'shortest': (0, 0)}},
+ 1: {0: {'shortest': (1, 0)},
+ 2: {'others': [(3, 2)], 'shortest': (0, 1)},
+ 3: {'others': [(2, 2)], 'shortest': (0, 1)}},
+ 2: {0: {'others': [(3, 1)], 'shortest': (2, 0)},
+ 1: {'shortest': (0, 1)},
+ 3: {'others': [(0, 1)], 'shortest': (2, 0)}},
+ 3: {0: {'others': [(2, 1)], 'shortest': (3, 0)},
+ 1: {'shortest': (0, 1)},
+ 2: {'others': [(0, 1)], 'shortest': (3, 0)}}}
+
:param async: Flag that determines if the command result (dictionary)
is returned or a Task object (can get status, etc.).
:type async: boolean
@@ -863,11 +1109,12 @@ class Fabric(object):
"""
return self._run_on_all_nodes(async, "get_depth_chart")
- def _run_on_all_nodes(self, async, name, *args):
+ def _run_on_all_nodes(self, async, name, *args, **kwargs):
"""Start a command on all nodes."""
tasks = {}
for node_id, node in self.nodes.iteritems():
- tasks[node_id] = self.task_queue.put(getattr(node, name), *args)
+ tasks[node_id] = self.task_queue.put(getattr(node, name), *args,
+ **kwargs)
if async:
return tasks
@@ -884,21 +1131,5 @@ class Fabric(object):
raise CommandFailedError(results, errors)
return results
- def _discover_nodes(self, ip_address, username="admin", password="admin"):
- """Gets the nodes of this fabric by pulling IP info from a BMC."""
- node = self.node(ip_address=ip_address, username=username,
- password=password, tftp=self.tftp,
- ecme_tftp_port=self.ecme_tftp_port,
- verbose=self.verbose)
- ipinfo = node.get_fabric_ipinfo()
- for node_id, node_address in ipinfo.iteritems():
- self._nodes[node_id] = self.node(ip_address=node_address,
- username=username,
- password=password,
- tftp=self.tftp,
- ecme_tftp_port=self.ecme_tftp_port,
- verbose=self.verbose)
- self._nodes[node_id].node_id = node_id
-
# End of file: ./fabric.py