summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorge Kraft <george.kraft@calxeda.com>2013-12-13 16:07:40 -0600
committerGeorge Kraft <george.kraft@calxeda.com>2013-12-13 16:07:40 -0600
commitb9aead6e0612e2fa41624d74d7328d00c052111c (patch)
treee34f14b0d6e7cd5a2b8eba207d0a478ce16b151a
parent1a68dce1ecccf5fd583b8ae4aacab4629608193a (diff)
parentb40d35cec775f50d96359846c343757cbc62c7d8 (diff)
downloadcxmanage-b9aead6e0612e2fa41624d74d7328d00c052111c.tar.gz
CXMAN-261: Merge branch 'credentials'
Conflicts: cxmanage_api/fabric.py
-rw-r--r--cxmanage_api/cli/__init__.py26
-rw-r--r--cxmanage_api/cli/commands/ipdiscover.py5
-rw-r--r--cxmanage_api/credentials.py56
-rw-r--r--cxmanage_api/fabric.py40
-rw-r--r--cxmanage_api/node.py45
-rw-r--r--cxmanage_api/tests/dummy_node.py3
-rw-r--r--cxmanage_api/tests/fabric_test.py9
-rw-r--r--cxmanage_api/tests/test_credentials.py69
-rw-r--r--pylint.rc2
-rwxr-xr-xrun_tests5
-rwxr-xr-xscripts/cxmanage9
-rwxr-xr-xscripts/cxmux5
12 files changed, 203 insertions, 71 deletions
diff --git a/cxmanage_api/cli/__init__.py b/cxmanage_api/cli/__init__.py
index 5acb0c9..033edec 100644
--- a/cxmanage_api/cli/__init__.py
+++ b/cxmanage_api/cli/__init__.py
@@ -100,9 +100,20 @@ def get_nodes(args, tftp, verify_prompt=False):
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]
+ credentials = {
+ "ecme_username": args.user,
+ "ecme_password": args.password,
+ "linux_username": args.linux_username,
+ "linux_password": args.linux_password
+ }
+
+ nodes = [
+ Node(
+ ip_address=x, credentials=credentials, tftp=tftp,
+ ecme_tftp_port=args.ecme_tftp_port, verbose=args.verbose
+ )
+ for x in hosts
+ ]
if args.all_nodes:
if not args.quiet:
@@ -116,10 +127,11 @@ def get_nodes(args, tftp, verify_prompt=False):
for node in nodes:
if node in results:
for node_id, ip_address in sorted(results[node].iteritems()):
- 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(
+ ip_address=ip_address, credentials=credentials,
+ 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)
diff --git a/cxmanage_api/cli/commands/ipdiscover.py b/cxmanage_api/cli/commands/ipdiscover.py
index fd21546..98a0155 100644
--- a/cxmanage_api/cli/commands/ipdiscover.py
+++ b/cxmanage_api/cli/commands/ipdiscover.py
@@ -42,8 +42,9 @@ def ipdiscover_command(args):
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)
+ results, errors = run_command(
+ args, nodes, 'get_server_ip', args.interface, args.ipv6, args.aggressive
+ )
if results:
node_strings = get_node_strings(args, results, justify=True)
diff --git a/cxmanage_api/credentials.py b/cxmanage_api/credentials.py
new file mode 100644
index 0000000..8709bf9
--- /dev/null
+++ b/cxmanage_api/credentials.py
@@ -0,0 +1,56 @@
+# Copyright (c) 2012-2013, Calxeda Inc.
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Calxeda Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+# DAMAGE.
+
+
+class Credentials(object):
+ """ Container for login credentials """
+ defaults = {
+ "ecme_username": "admin",
+ "ecme_password": "admin",
+ "linux_username": "user1",
+ "linux_password": "1Password"
+ }
+
+ def __init__(self, base=None, **kwargs):
+ self.__dict__.update(self.defaults)
+ if isinstance(base, Credentials):
+ self.__dict__.update(vars(base))
+ elif base != None:
+ self.__dict__.update(base)
+ self.__dict__.update(kwargs)
+
+ for key in self.__dict__:
+ if not key in self.defaults:
+ raise ValueError("Invalid credential key: %s" % key)
+
+ def __repr__(self):
+ return "Credentials(%s)" % (", ".join(
+ "%r: %r" % (key, value) for (key, value) in vars(self).items()
+ ))
diff --git a/cxmanage_api/fabric.py b/cxmanage_api/fabric.py
index bd05a10..4a84743 100644
--- a/cxmanage_api/fabric.py
+++ b/cxmanage_api/fabric.py
@@ -38,6 +38,7 @@ import re
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.credentials import Credentials
from cxmanage_api.cx_exceptions import CommandFailedError, TimeoutError, \
IpmiError, TftpException, ParseError
@@ -51,10 +52,8 @@ class Fabric(object):
:param ip_address: The ip_address of ANY known node for the Fabric.
:type ip_address: string
- :param username: The login username credential. [Default admin]
- :type username: string
- :param password: The login password credential. [Default admin]
- :type password: string
+ :param credentials: Login credentials for ECME/Linux
+ :type credentials: Credentials
:param tftp: Tftp server to facilitate IPMI command responses.
:type tftp: `Tftp <tftp.html>`_
:param task_queue: TaskQueue to use for sending commands.
@@ -113,13 +112,12 @@ class Fabric(object):
return function
# pylint: disable=R0913
- def __init__(self, ip_address, username="admin", password="admin",
- tftp=None, ecme_tftp_port=5001, task_queue=None,
- verbose=False, node=None):
+ def __init__(self, ip_address, credentials=None, tftp=None,
+ ecme_tftp_port=5001, task_queue=None, verbose=False,
+ node=None):
"""Default constructor for the Fabric class."""
self.ip_address = ip_address
- self.username = username
- self.password = password
+ self.credentials = Credentials(credentials)
self._tftp = tftp
self.ecme_tftp_port = ecme_tftp_port
self.task_queue = task_queue
@@ -218,16 +216,15 @@ class Fabric(object):
"""Returns a dictionary of nodes reported by the primary node IP"""
new_nodes = {}
root_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
+ ip_address=self.ip_address, credentials=self.credentials,
+ tftp=self.tftp, ecme_tftp_port=self.ecme_tftp_port,
+ verbose=self.verbose
)
ipinfo = root_node.get_fabric_ipinfo()
for node_id, node_address in ipinfo.items():
node = self.node(
- ip_address=node_address, username=self.username,
- password=self.password, tftp=self.tftp,
- ecme_tftp_port=self.ecme_tftp_port,
+ ip_address=node_address, credentials=self.credentials,
+ tftp=self.tftp, ecme_tftp_port=self.ecme_tftp_port,
verbose=self.verbose
)
node.node_id = node_id
@@ -934,8 +931,8 @@ 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):
+ def get_server_ip(self, interface=None, ipv6=False, aggressive=False,
+ async=False):
"""Get the server IP address from all nodes. The nodes must be powered
on for this to work.
@@ -951,10 +948,6 @@ class Fabric(object):
:type interface: string
:param ipv6: Return an IPv6 address instead of IPv4.
:type ipv6: boolean
- :param user: Linux username.
- :type user: string
- :param password: Linux password.
- :type password: string
:param aggressive: Discover the IP aggressively (may power cycle node).
:type aggressive: boolean
:param async: Flag that determines if the command result (dictionary)
@@ -965,8 +958,9 @@ class Fabric(object):
:rtype: dictionary or `Task <command.html>`_
"""
- return self._run_on_all_nodes(async, "get_server_ip", interface, ipv6,
- user, password, aggressive)
+ return self._run_on_all_nodes(
+ async, "get_server_ip", interface, ipv6, aggressive
+ )
def get_ipsrc(self):
"""Return the ipsrc for the fabric.
diff --git a/cxmanage_api/node.py b/cxmanage_api/node.py
index 784f7f5..f247495 100644
--- a/cxmanage_api/node.py
+++ b/cxmanage_api/node.py
@@ -51,6 +51,7 @@ from cxmanage_api.image import Image as IMAGE
from cxmanage_api.ubootenv import UbootEnv as UBOOTENV
from cxmanage_api.ip_retriever import IPRetriever as IPRETRIEVER
from cxmanage_api.decorators import retry
+from cxmanage_api.credentials import Credentials
from cxmanage_api.cx_exceptions import TimeoutError, NoSensorError, \
SocmanVersionError, FirmwareConfigError, PriorityIncrementError, \
NoPartitionError, TransferFailure, ImageSizeError, \
@@ -68,10 +69,8 @@ class Node(object):
:param ip_address: The ip_address of the Node.
:type ip_address: string
- :param username: The login username credential. [Default admin]
- :type username: string
- :param password: The login password credential. [Default admin]
- :type password: string
+ :param credentials: Login credentials for ECME/Linux
+ :type credentials: Credentials
:param tftp: The internal/external TFTP server to use for data xfer.
:type tftp: `Tftp <tftp.html>`_
:param verbose: Flag to turn on verbose output (cmd/response).
@@ -85,9 +84,9 @@ class Node(object):
"""
# pylint: disable=R0913
- def __init__(self, ip_address, username="admin", password="admin",
- tftp=None, ecme_tftp_port=5001, verbose=False, bmc=None,
- image=None, ubootenv=None, ipretriever=None):
+ def __init__(self, ip_address, credentials=None, tftp=None,
+ ecme_tftp_port=5001, verbose=False, bmc=None, image=None,
+ ubootenv=None, ipretriever=None):
"""Default constructor for the Node class."""
if (not tftp):
tftp = InternalTftp.default()
@@ -103,14 +102,15 @@ class Node(object):
ipretriever = IPRETRIEVER
self.ip_address = ip_address
- self.username = username
- self.password = password
+ self.credentials = Credentials(credentials)
self.tftp = tftp
self.ecme_tftp = ExternalTftp(ip_address, ecme_tftp_port)
self.verbose = verbose
- self.bmc = make_bmc(bmc, hostname=ip_address, username=username,
- password=password, verbose=verbose)
+ self.bmc = make_bmc(
+ bmc, hostname=ip_address, username=self.credentials.ecme_username,
+ password=self.credentials.ecme_password, verbose=verbose
+ )
self.image = image
self.ubootenv = ubootenv
self.ipretriever = ipretriever
@@ -1143,8 +1143,11 @@ communication.
else:
command = ["ipmitool"]
- command += ["-U", self.username, "-P", self.password, "-H",
- self.ip_address]
+ command += [
+ "-U", self.credentials.ecme_username,
+ "-P", self.credentials.ecme_password,
+ "-H", self.ip_address
+ ]
command += ipmitool_args
if (self.verbose):
@@ -1481,8 +1484,7 @@ communication.
return results
- def get_server_ip(self, interface=None, ipv6=False, user="user1",
- password="1Password", aggressive=False):
+ def get_server_ip(self, interface=None, ipv6=False, aggressive=False):
"""Get the IP address of the Linux server. The server must be powered
on for this to work.
@@ -1493,10 +1495,6 @@ communication.
:type interface: string
:param ipv6: Return an IPv6 address instead of IPv4.
:type ipv6: boolean
- :param user: Linux username.
- :type user: string
- :param password: Linux password.
- :type password: string
:param aggressive: Discover the IP aggressively (may power cycle node).
:type aggressive: boolean
@@ -1509,9 +1507,12 @@ obtained.
"""
verbosity = 2 if self.verbose else 0
- retriever = self.ipretriever(self.ip_address, aggressive=aggressive,
- verbosity=verbosity, server_user=user, server_password=password,
- interface=interface, ipv6=ipv6, bmc=self.bmc)
+ retriever = self.ipretriever(
+ self.ip_address, aggressive=aggressive, verbosity=verbosity,
+ server_user=self.credentials.linux_username,
+ server_password=self.credentials.linux_password,
+ interface=interface, ipv6=ipv6, bmc=self.bmc
+ )
retriever.run()
return retriever.server_ip
diff --git a/cxmanage_api/tests/dummy_node.py b/cxmanage_api/tests/dummy_node.py
index 02af2c2..6d54646 100644
--- a/cxmanage_api/tests/dummy_node.py
+++ b/cxmanage_api/tests/dummy_node.py
@@ -128,8 +128,7 @@ class DummyNode(Dummy):
return {}
# pylint: disable=R0913
- def get_server_ip(self, interface=None, ipv6=False, user="user1",
- password="1Password", aggressive=False):
+ def get_server_ip(self, interface=None, ipv6=False, aggressive=False):
"""Simulate get_server_ip(). """
return "192.168.200.1"
diff --git a/cxmanage_api/tests/fabric_test.py b/cxmanage_api/tests/fabric_test.py
index 3863186..757edc1 100644
--- a/cxmanage_api/tests/fabric_test.py
+++ b/cxmanage_api/tests/fabric_test.py
@@ -192,12 +192,11 @@ class FabricTest(unittest.TestCase):
def test_get_server_ip(self):
""" Test get_server_ip command """
- self.fabric.get_server_ip("interface", "ipv6", "user", "password",
- "aggressive")
+ self.fabric.get_server_ip("interface", "ipv6", "aggressive")
for node in self.nodes:
- self.assertEqual(node.method_calls, [call.get_server_ip(
- "interface", "ipv6", "user", "password", "aggressive"
- )])
+ self.assertEqual(node.method_calls,
+ [call.get_server_ip("interface", "ipv6", "aggressive")]
+ )
def test_failed_command(self):
""" Test a failed command """
diff --git a/cxmanage_api/tests/test_credentials.py b/cxmanage_api/tests/test_credentials.py
new file mode 100644
index 0000000..19de536
--- /dev/null
+++ b/cxmanage_api/tests/test_credentials.py
@@ -0,0 +1,69 @@
+# Copyright (c) 2012-2013, Calxeda Inc.
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Calxeda Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+# DAMAGE.
+
+import unittest
+
+from cxmanage_api.credentials import Credentials
+
+
+class TestCredentials(unittest.TestCase):
+ """ Unit tests for the Credentials class """
+ def test_default(self):
+ """ Test default Credentials object """
+ creds = Credentials()
+ self.assertEqual(vars(creds), Credentials.defaults)
+
+ def test_from_dict(self):
+ """ Test Credentials instantiated with a dict """
+ creds = Credentials({"linux_password": "foo"})
+ expected = dict(Credentials.defaults)
+ expected["linux_password"] = "foo"
+ self.assertEqual(vars(creds), expected)
+
+ def test_from_kwargs(self):
+ """ Test Credentials instantiated with kwargs """
+ creds = Credentials(linux_password="foo")
+ expected = dict(Credentials.defaults)
+ expected["linux_password"] = "foo"
+ self.assertEqual(vars(creds), expected)
+
+ def test_from_credentials(self):
+ """ Test Credentials instantiated with other Credentials """
+ creds = Credentials(Credentials(linux_password="foo"))
+ expected = dict(Credentials.defaults)
+ expected["linux_password"] = "foo"
+ self.assertEqual(vars(creds), expected)
+
+ def test_fails_on_invalid(self):
+ """ Test that we don't allow unrecognized credentials """
+ with self.assertRaises(ValueError):
+ Credentials({"desire_to_keep_going": "Very Low"})
+ with self.assertRaises(ValueError):
+ Credentials(magical_mystery_cure="Writing silly strings!")
diff --git a/pylint.rc b/pylint.rc
index 124425b..ea62fd7 100644
--- a/pylint.rc
+++ b/pylint.rc
@@ -150,7 +150,7 @@ ignore-mixin-members=yes
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set).
-ignored-classes=SQLObject
+ignored-classes=SQLObject,Credentials
# When zope mode is activated, add a predefined set of Zope acquired attributes
# to generated-members.
diff --git a/run_tests b/run_tests
index c121186..aae600f 100755
--- a/run_tests
+++ b/run_tests
@@ -35,9 +35,10 @@ import unittest
import xmlrunner
from cxmanage_api.tests import tftp_test, image_test, node_test, fabric_test, \
- tasks_test, dummy_test
+ tasks_test, dummy_test, test_credentials
test_modules = [
- tftp_test, image_test, node_test, fabric_test, tasks_test, dummy_test
+ tftp_test, image_test, node_test, fabric_test, tasks_test, dummy_test,
+ test_credentials
]
def main():
diff --git a/scripts/cxmanage b/scripts/cxmanage
index d87bb41..c9e64f8 100755
--- a/scripts/cxmanage
+++ b/scripts/cxmanage
@@ -111,6 +111,10 @@ def build_parser():
help='Username for login')
parser.add_argument('-p', '--password', default='admin',
help='Password for login')
+ parser.add_argument('-U', '--linux-username', type=str, default='user1',
+ metavar='USER', help='Server-side Linux username')
+ parser.add_argument('-P', '--linux-password', type=str, default='1Password',
+ metavar='PASSWORD', help='Server-side Linux password')
parser.add_argument('-a', '--all-nodes', action='store_true',
help='Send command to all nodes reported by fabric')
parser.add_argument('--threads', type=int, metavar='THREAD_COUNT',
@@ -304,11 +308,6 @@ def build_parser():
help='discover server-side IP addresses')
ipdiscover.add_argument('-A', '--aggressive', action='store_true',
help='discover IPs aggressively')
- ipdiscover.add_argument('-U', '--server-user', type=str, default='user1',
- metavar='USER', help='Server-side Linux username')
- ipdiscover.add_argument('-P', '--server-password', type=str,
- default='1Password', metavar='PASSWORD',
- help='Server-side Linux password')
ipdiscover.add_argument('-6', '--ipv6', action='store_true',
help='Discover IPv6 addresses')
ipdiscover.add_argument('-I', '--interface', type=str, default=None,
diff --git a/scripts/cxmux b/scripts/cxmux
index 2b595eb..67f2d51 100755
--- a/scripts/cxmux
+++ b/scripts/cxmux
@@ -85,8 +85,9 @@ def main():
fabric = cxmanage_api.fabric.Fabric(
ip_address=args.ecmeip,
- username=args.user,
- password=args.password
+ credentials={
+ "ecme_username": args.user, "ecme_password": args.password
+ }
)
ips = [node.ip_address for node in fabric.nodes.values()]
if args.ssh: