summaryrefslogtreecommitdiff
path: root/cxmanage_api/ip_retriever.py
diff options
context:
space:
mode:
Diffstat (limited to 'cxmanage_api/ip_retriever.py')
-rw-r--r--cxmanage_api/ip_retriever.py382
1 files changed, 382 insertions, 0 deletions
diff --git a/cxmanage_api/ip_retriever.py b/cxmanage_api/ip_retriever.py
new file mode 100644
index 0000000..411465b
--- /dev/null
+++ b/cxmanage_api/ip_retriever.py
@@ -0,0 +1,382 @@
+#!/usr/bin/env python
+
+# 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 re
+import json
+
+import threading
+from time import sleep
+
+from cxmanage_api.cx_exceptions import IPDiscoveryError
+
+from pexpect import TIMEOUT, EOF
+from pyipmi import make_bmc
+from pyipmi.server import Server
+from pyipmi.bmc import LanBMC
+
+
+class IPRetriever(threading.Thread):
+ """The IPRetriever class takes an ECME address and when run will
+ connect to the Linux Server from the ECME over SOL and use
+ ifconfig to determine the IP address.
+ """
+ verbosity = None
+ aggressive = None
+ retry = None
+ timeout = None
+ interface = None
+
+ ecme_ip = None
+ ecme_user = None
+ ecme_password = None
+
+ server_ip = None
+ server_user = None
+ server_password = None
+
+ def __init__(self, ecme_ip, aggressive=False, verbosity=0, **kwargs):
+ """Initializes the IPRetriever class. The IPRetriever needs the
+ only the first node to know where to start.
+ """
+ super(IPRetriever, self).__init__()
+ self.daemon = True
+
+ if hasattr(ecme_ip, 'ip_address'):
+ self.ecme_ip = ecme_ip.ip_address
+ else:
+ self.ecme_ip = ecme_ip
+
+ self.aggressive = aggressive
+ self.verbosity = verbosity
+
+ # Everything here is optional
+ self.timeout = kwargs.get('timeout', 120)
+ self.retry = kwargs.get('retry', 0)
+
+ self.ecme_user = kwargs.get('ecme_user', 'admin')
+ self.ecme_password = kwargs.get('ecme_password', 'admin')
+
+ self.server_user = kwargs.get('server_user', 'user1')
+ self.server_password = kwargs.get('server_password', '1Password')
+
+ if '_inet_pattern' in kwargs and '_ip_pattern' in kwargs:
+ self.interface = kwargs.get('interface', None)
+ self._inet_pattern = kwargs['_inet_pattern']
+ self._ip_pattern = kwargs['_ip_pattern']
+
+ else:
+ self.set_interface(kwargs.get('interface', None),
+ kwargs.get('ipv6', False))
+
+ if 'bmc' in kwargs:
+ self._bmc = kwargs['bmc']
+ else:
+ self._bmc = make_bmc(LanBMC, verbose=(self.verbosity>1),
+ hostname=self.ecme_ip,
+ username=self.ecme_user,
+ password=self.ecme_password)
+
+ if 'config_path' in kwargs:
+ self.read_config(kwargs['config_path'])
+
+
+
+ def set_interface(self, interface=None, ipv6=False):
+ """Sets the interface and IP Version that is looked for on the server.
+ The interface must be acceptable by ifconfig. By default the first
+ interface given by ifconfig will be used.
+ """
+ self.interface = interface
+
+ if not ipv6:
+ self._ip_pattern = re.compile('\d+\.'*3 + '\d+')
+ self._inet_pattern = re.compile('inet addr:(%s)' %
+ self._ip_pattern.pattern)
+ else:
+ self._ip_pattern = re.compile('[0-9a-fA-F:]*:'*2 + '[0-9a-fA-F:]+')
+ self._inet_pattern = re.compile('inet6 addr: ?(%s)' %
+ self._ip_pattern.pattern)
+
+
+ def _log(self, msg, error=False):
+ """Print message with the ECME IP if verbosity is normal."""
+ if error:
+ sys.stderr.write('Error %s: %s\n' % (self.ecme_ip, msg))
+ elif self.verbosity > 0:
+ sys.stdout.write('%s: %s\n' % (self.ecme_ip, msg))
+
+
+ def run(self):
+ """Attempts to finds the server IP address associated with the
+ ECME IP. If successful, server_ip will contain the IP address.
+ """
+ if self.server_ip is not None:
+ self._log('Using stored IP %s' % self.server_ip)
+ return
+
+ for attempt in range(self.retry + 1):
+ self.server_ip = self.sol_try_command(self.sol_find_ip)
+
+ if self.server_ip is not None:
+ self._log('The server IP is %s' % self.server_ip)
+ return
+
+ self._log('The server IP could not be found')
+
+
+ def _power_server(self, cycle=False):
+ """Puts the server in a powered state with conditions that should
+ result in a successful SOL activation. Returns True if successful.
+ """
+ server = Server(self._bmc)
+
+ if cycle:
+ self._log('Powering server off')
+ server.power_off()
+ sleep(5)
+
+ if not server.is_powered:
+ self._log('Powering server on')
+ server.power_on()
+ sleep(10)
+
+ return server.is_powered
+
+
+ def sol_find_ip(self, session):
+ """Uses ifconfig to get the IP address in an SOL session.
+ Returns the ip address if it is found or None on failure.
+ """
+ if self.interface:
+ session.sendline('ifconfig %s' % self.interface)
+ else:
+ session.sendline('ifconfig')
+
+ index = session.expect(['Link encap', 'error fetching interface',
+ TIMEOUT, EOF], timeout=2)
+
+ # ifconfig found the interface
+ if index == 0:
+ output = ''.join(session.readline() for line in range(3))
+ found_ip = self._inet_pattern.findall(output)
+
+ if found_ip:
+ return found_ip[0]
+ else:
+ self._bmc.deactivate_payload()
+ raise IPDiscoveryError('Interface %s does not have '
+ 'given address' % self.interface)
+ elif index == 1:
+ self._bmc.deactivate_payload()
+ raise IPDiscoveryError('Could not find interface %s'
+ % self.interface)
+
+ else: # Failed to find interface. Returning None
+ return None
+
+
+ def sol_try_command(self, command):
+ """Connects to the server over a SOL connection. Attempts
+ to run the given command on the server without knowing
+ the state of the server. The command must return None if
+ it fails. If aggresive is True, then the server may be
+ restarted or power cycled to try and reset the state.
+ """
+ server = Server(self._bmc)
+ if not server.is_powered:
+ self._log("Server is powered off. Can't proceed.")
+ raise IPDiscoveryError("Server is powered off. Can't proceed.")
+
+ self._log('Activating SOL')
+ session = self._bmc.activate_payload()
+ sleep(2)
+
+ timeout = self.timeout
+ attempt = 0
+ login_attempted = False
+
+ options = [TIMEOUT, EOF,
+ 'Highbank #', 'Invalid boot device',
+ '[lL]ogin:', '[pP]assword:',
+ 'network configuration',
+ 'going down for reboot', 'Stopped',
+ 'SOL payload already active',
+ 'SOL Session operational']
+
+ while attempt < 7:
+ index = session.expect(options, timeout)
+
+ # Catchable errors
+
+ # May need to boot
+ if index == 2:
+ session.sendline('run bootcmd_sata')
+ timeout = self.timeout
+
+ # An invalid boot device can occur if bootcmd_sata fails
+ elif index == 3:
+ self._bmc.deactivate_payload()
+ raise IPDiscoveryError('Unable to boot linux due to '
+ 'an invalid boot device')
+
+ # Enter username or report incorrect login
+ elif index == 4:
+ if not login_attempted:
+ self._log('Logging into Linux')
+ session.sendline(self.server_user)
+
+ # now check for failed login
+ options[index] = 'incorrect'
+ login_attempted = True
+ timeout = 4
+ else:
+ self._bmc.deactivate_payload()
+ raise IPDiscoveryError('Incorrect username or password')
+
+ # Enter password
+ elif index == 5:
+ session.sendline(self.server_password)
+ timeout = 4
+
+ # Warn about the network configuration
+ elif index == 6:
+ self._log('Waiting for network configuration')
+ timeout = self.timeout
+
+ # Inform of reboot
+ elif index == 7:
+ self._log('Linux is rebooting')
+ timeout = self.timeout
+
+ # Inform of zombied processes
+ elif index == 8:
+ self._log('Suspended the current process')
+ timeout = 2
+
+ # Try restarting SOL connection
+ elif index == 9:
+ self._log('Restarting SOL session')
+ self._bmc.deactivate_payload()
+ sleep(2)
+ session = self._bmc.activate_payload()
+ sleep(2)
+ session.sendline()
+ timeout = 8
+
+ # Successful SOL connection
+ elif index == 10:
+ self._log('SOL Activated')
+ session.sendline()
+ session.sendcontrol('z')
+ timeout = 2
+
+ else:
+ # Assume where are at a prompt and able to run the command
+ value = command(session)
+
+ if value is not None:
+ self._bmc.deactivate_payload()
+ return value
+
+ # Non catchable errors
+
+ # Try to zombie the current process
+ if attempt == 0:
+ session.sendcontrol('z')
+ timeout = 2
+
+ elif not self.aggressive:
+ sleep(2)
+ self._bmc.deactivate_payload()
+ raise IPDiscoveryError('Unable to obtain the server\'s '
+ 'IP address unintrusively')
+
+ # Try sending kill signals
+ elif attempt == 1:
+ self._log('Sending interrupt signals')
+ session.sendcontrol('c')
+ timeout = 2
+
+
+ elif attempt == 2:
+ session.sendcontrol('\\')
+ timeout = 2
+
+ # Try exiting. Will put us in login if we were another user
+ elif attempt == 3:
+ session.sendline('exit')
+ timeout = 4
+
+ # Attempt to reboot the Linux server
+ elif attempt == 4:
+ self._log('Attempting reboot')
+ session.sendline('sudo reboot')
+ sleep(1)
+ timeout = 4
+ login_attempted = False
+
+ # If all else fails: power cycle the server
+ elif attempt == 5:
+ self._power_server(cycle=True)
+ timeout = self.timeout
+ login_attempted = False
+
+ attempt += 1
+
+ # Reaches here if nothing succeeds
+ self._bmc.deactivate_payload()
+ raise IPDiscoveryError('Unable to properly connect over SOL')
+
+
+ def read_config(self, path):
+ """Loads the address information from a json configuration
+ file written by write_config
+ """
+ with open(path, 'r') as json_file:
+ json_data = json_file.read()
+ config_data = json.loads(json_data)
+
+ self.ecme_ip = config_data['ecme_host']
+ self.server_ip = config_data['server_host']
+
+ def write_config(self, path):
+ """Saves the address information in a json configuration file"""
+ config_data = {'ecme_host': self.ecme_ip,
+ 'server_host': self.server_ip}
+
+ json_data = json.dumps(config_data, indent=4)
+ with open(path, 'w') as json_file:
+ json_file.write(json_data)
+
+
+