[robot] Add libraries and resources used by the suite
Add a series of libraries and resources that are used by the suite setup and test cases functionality. - Libraries - Libraries written in python mostly to serve the installation and deployment of StarlingX from robot test cases. - Resources – Libraries in robot format that are used as a pool of keywords to be used by the entire set of test cases. - Utils – Libraries written in python that expose functionality to configure the framework at host machine level. - Variables – Global variables that are used to setup the framework as well as test cases. Story: 2004828 Task: 29004 Depends-On: I6ead335412150fb8d64a6abf7909cf702d0d248c Change-Id: I796dcaf71089424dd37a050691fd0ee003ad3176 Signed-off-by: Jose Perez Carranza <jose.perez.carranza@intel.com>changes/25/676225/6
parent
3b98a48102
commit
2d3d047a8c
|
@ -0,0 +1,29 @@
|
|||
# Table of contents
|
||||
|
||||
- [iso_setup python module](#iso_setup-python-module)
|
||||
|
||||
# iso_setup python module
|
||||
|
||||
The iso_setup.py in this folder provides the capability to setup a StarlingX
|
||||
iso with specific configuration, this configuration comes from the `config.ini`
|
||||
file.
|
||||
|
||||
The **config.ini** file in the section `iso_installer` contains the
|
||||
variable `KERNEL_OPTION` which can have the following values.
|
||||
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ------------------------------------------------------------ |
|
||||
| 0 | Standard Controller Configuration > Serial Console > Standard Security Boot Profile |
|
||||
| S0 | Standard Controller Configuration > Serial Console > Extended Security Boot Profile |
|
||||
| 1 | Standard Controller Configuration > Graphical Console > Standard Security Boot Profile |
|
||||
| S1 | Standard Controller Configuration > Graphical Console > Extended Security Boot Profile |
|
||||
| 2 | All-in-one Controller Configuration > Serial Console > Standard Security Boot Profile |
|
||||
| S2 | All-in-one Controller Configuration > Serial Console > Extended Security Boot Profile |
|
||||
| 3 | All-in-one Controller Configuration > Graphical Console > Standard Security Boot Profile |
|
||||
| S3 | All-in-one Controller Configuration > Graphical Console > Extended Security Boot Profile |
|
||||
| 4 | All-in-one (lowlatency) Controller Configuration > Serial Console > Standard Security Boot Profile |
|
||||
| S4 | All-in-one (lowlatency) Controller Configuration > Serial Console > Extended Security Boot Profile |
|
||||
| 5 | All-in-one (lowlatency) Controller Configuration > Graphical Console > Standard Security Boot Profile |
|
||||
| S5 | All-in-one (lowlatency) Controller Configuration > Graphical Console > Extended Security Boot Profile |
|
||||
|
|
@ -0,0 +1,430 @@
|
|||
"""Provides a library of useful utilities for Robot Framework"""
|
||||
|
||||
import os
|
||||
import configparser
|
||||
import logging
|
||||
import yaml
|
||||
from Utils import bash_utils as bash
|
||||
from Config import config
|
||||
|
||||
# create the logger object
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def update_config_ini(**kwargs):
|
||||
"""Update a specific config.ini
|
||||
|
||||
This function update a the values from a specific config.ini file
|
||||
|
||||
:param kwargs: this is a dict that will contains the following values:
|
||||
- config_ini: which is absolute path to the config.ini (obligatory
|
||||
variable)
|
||||
- config_section: which is the section to modify the variables
|
||||
(optional variable)
|
||||
- all others variables are dynamic and they are directly dependent of
|
||||
the existing values in the config.ini, e.g:
|
||||
|
||||
*** How to use this function ***
|
||||
|
||||
- Example 1:
|
||||
|
||||
scenario : the config.ini has a unique variable in all sections
|
||||
|
||||
update_config_ini(
|
||||
config_ini='path_to_config.init', LOG_PATH='some_value')
|
||||
|
||||
where LOG_PATH is an existing value in the config.ini.
|
||||
You can use as many values you want, the only limitation is that
|
||||
these must exist in the config.init file.
|
||||
|
||||
- Example 2:
|
||||
|
||||
scenario: the config.ini has duplicates variables in several sections
|
||||
|
||||
update_config_ini(
|
||||
config_ini='path_to_config.init',
|
||||
config_section='LOGICAL_INTERFACE_2', LOG_PATH='some_value')
|
||||
|
||||
where LOGICAL_INTERFACE_2 is an existing section in the config.ini,
|
||||
please notice that the variables here must exists in the section
|
||||
specified.
|
||||
|
||||
:return
|
||||
This function returns a tuple with the following values:
|
||||
- status: this can be the following values:
|
||||
1. True, if some values from config.ini were modified
|
||||
2. False, if there were no modifications
|
||||
- message: a message with descriptive information about the
|
||||
success/error
|
||||
"""
|
||||
|
||||
status = False
|
||||
message = None
|
||||
|
||||
if len(kwargs) < 2:
|
||||
raise RuntimeError('a minimum of two variables are expected')
|
||||
|
||||
# obligatory variable
|
||||
config_ini = kwargs['config_ini']
|
||||
# optional variable
|
||||
config_section = kwargs.get('config_section', False)
|
||||
|
||||
if not os.path.exists(config_ini):
|
||||
raise IOError('{}: does not exists'.format(config_ini))
|
||||
|
||||
configurations = configparser.ConfigParser()
|
||||
# preserve the variables from config.ini in upper case
|
||||
configurations.optionxform = lambda option: option.upper()
|
||||
configurations.read(config_ini)
|
||||
|
||||
# ------------------------ validation section ---------------------------
|
||||
# checking if the section given is valid (if any)
|
||||
if config_section and config_section not in configurations.sections():
|
||||
message = '{}: section does not exists'.format(config_section)
|
||||
return status, message
|
||||
|
||||
elif not config_section:
|
||||
# checking if the values are in more than one section
|
||||
duplicates = 0
|
||||
|
||||
for key, value in kwargs.items():
|
||||
for section in configurations.sections():
|
||||
if configurations.has_option(section, key):
|
||||
duplicates += 1
|
||||
if duplicates > 1:
|
||||
status = False
|
||||
message = ('{}: is in more than one section, please '
|
||||
'set config_section'.format(key))
|
||||
return status, message
|
||||
duplicates = 0
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
blacklist_keys = ['config_ini', 'config_section']
|
||||
count = 0
|
||||
|
||||
# ------------------- update config values section ----------------------
|
||||
if config_section:
|
||||
# (the user provides a config_section)
|
||||
# get a list of tuples without the values in the blacklist list
|
||||
values = [x for x in kwargs.items() if x[0] not in blacklist_keys]
|
||||
|
||||
for _key, _value in values:
|
||||
try:
|
||||
_ = configurations[config_section][_key]
|
||||
except KeyError:
|
||||
message = '{}: key does not exists in the section :{}'.format(
|
||||
_key, config_section)
|
||||
return status, message
|
||||
else:
|
||||
configurations[config_section][_key] = _value
|
||||
count += 1
|
||||
status = True
|
||||
|
||||
else:
|
||||
# (the user does not provides a config_section only values)
|
||||
# modifying configurations according to the values
|
||||
for section in configurations.sections():
|
||||
for item in configurations.items(section):
|
||||
for key, value in kwargs.items():
|
||||
if key == item[0]:
|
||||
configurations[section][item[0]] = value
|
||||
count += 1
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
if count != 0:
|
||||
with open(config_ini, 'w') as configfile:
|
||||
configurations.write(configfile)
|
||||
status = True
|
||||
message = '{}: was updated successfully'.format(os.path.basename(
|
||||
config_ini))
|
||||
|
||||
return status, message
|
||||
|
||||
|
||||
def string_to_dict(string_table):
|
||||
"""Convert string table to dictionary
|
||||
|
||||
This function convert a string table output from a command executed in the
|
||||
controller node into a dictionary.
|
||||
Useful to parse the output in keys/values for Robot Framework.
|
||||
|
||||
@param string_table: the string table to convert into a dictionary, it
|
||||
comes from the controller node through Robot Framework.
|
||||
:return:
|
||||
a dictionary with all the string table entries.
|
||||
"""
|
||||
# string_table variable comes from Robot Framework into a dictionary list
|
||||
# in unicode format, so we need the following
|
||||
# 1. converting string_table variable from unicode to ascii code
|
||||
# 2. split in a list with line breaks
|
||||
line_breaks_list = string_table['stdout'].encode('utf-8').split('\n')
|
||||
|
||||
robot_dictionary = {}
|
||||
|
||||
try:
|
||||
# getting the table headers without empty spaces
|
||||
table_headers = [
|
||||
header.strip() for header in line_breaks_list[1].split('|')[1:-1]
|
||||
]
|
||||
except IndexError:
|
||||
err_dict = {
|
||||
'summary': {
|
||||
'err': 'IndexError',
|
||||
'cause': 'the command did not return a table'
|
||||
}
|
||||
}
|
||||
robot_dictionary.update(err_dict)
|
||||
return robot_dictionary
|
||||
|
||||
# the blacklist is used for build the body variable without the index on it
|
||||
blacklist = [0, 1, 2, len(line_breaks_list) - 1]
|
||||
body = list(filter(
|
||||
lambda item: line_breaks_list.index(item) not in blacklist,
|
||||
line_breaks_list))
|
||||
|
||||
table_data = [[v.strip() for v in i.strip('|').split('|')] for i in body]
|
||||
robot_dictionary = {
|
||||
table_headers[0]: {
|
||||
i[0]: {
|
||||
k: v for k, v in zip(table_headers[1:], i[1:])
|
||||
} for i in table_data
|
||||
}
|
||||
}
|
||||
|
||||
return robot_dictionary
|
||||
|
||||
|
||||
def get_cmd_boot_line():
|
||||
"""Get a cmd boot line.
|
||||
|
||||
This function build a custom cmd line in order to boot the startlingx iso
|
||||
:return:
|
||||
cmd: the cmd line for boot the iso.
|
||||
"""
|
||||
|
||||
kernel_option = config.get('iso_installer', 'KERNEL_OPTION')
|
||||
vmlinuz = config.get('iso_installer', 'VMLINUZ')
|
||||
consoles = config.get('iso_installer', 'CONSOLES')
|
||||
serial = config.get('iso_installer', 'SERIAL')
|
||||
opts_1 = config.get('iso_installer', 'OPTS_1')
|
||||
sys_type_1 = config.get('iso_installer', 'SYS_TYPE_1')
|
||||
sys_type_2 = config.get('iso_installer', 'SYS_TYPE_2')
|
||||
sys_type_3 = config.get('iso_installer', 'SYS_TYPE_3')
|
||||
opts_2 = config.get('iso_installer', 'OPTS_2')
|
||||
sec_prof_1 = config.get('iso_installer', 'SEC_PROF_1')
|
||||
sec_prof_2 = config.get('iso_installer', 'SEC_PROF_2')
|
||||
initrd = config.get('iso_installer', 'INITRD')
|
||||
|
||||
cmd = False
|
||||
|
||||
serial = '{vmlinuz} {consoles} {ser} {opts}'.format(
|
||||
vmlinuz=vmlinuz, consoles=consoles, ser=serial, opts=opts_1)
|
||||
no_serial = '{vmlinuz} {consoles} {opts}'.format(
|
||||
vmlinuz=vmlinuz, consoles=consoles, opts=opts_1)
|
||||
|
||||
if kernel_option == '0':
|
||||
cmd = ('{serial} {sys_type} {opts} {sec_prof} {initrd}'.format(
|
||||
serial=serial, sys_type=sys_type_1, opts=opts_2,
|
||||
sec_prof=sec_prof_1, initrd=initrd))
|
||||
elif kernel_option == 'S0':
|
||||
cmd = ('{serial} {sys_type} {opts} {sec_prof} {initrd}'.format(
|
||||
serial=serial, sys_type=sys_type_1, opts=opts_2,
|
||||
sec_prof=sec_prof_2, initrd=initrd))
|
||||
elif kernel_option == '1':
|
||||
cmd = ('{no_serial} {sys_type} {opts} {sec_prof} {initrd}'.format(
|
||||
no_serial=no_serial, sys_type=sys_type_1, opts=opts_2,
|
||||
sec_prof=sec_prof_1, initrd=initrd))
|
||||
elif kernel_option == 'S1':
|
||||
cmd = ('{no_serial} {sys_type} {opts} {sec_prof} {initrd}'.format(
|
||||
no_serial=no_serial, sys_type=sys_type_1, opts=opts_2,
|
||||
sec_prof=sec_prof_2, initrd=initrd))
|
||||
elif kernel_option == '2':
|
||||
cmd = ('{serial} {sys_type} {opts} {sec_prof} {initrd}'.format(
|
||||
serial=serial, sys_type=sys_type_2, opts=opts_2,
|
||||
sec_prof=sec_prof_1, initrd=initrd))
|
||||
elif kernel_option == 'S2':
|
||||
cmd = ('{serial} {sys_type} {opts} {sec_prof} {initrd}'.format(
|
||||
serial=serial, sys_type=sys_type_2, opts=opts_2,
|
||||
sec_prof=sec_prof_2, initrd=initrd))
|
||||
elif kernel_option == '3':
|
||||
cmd = ('{no_serial} {sys_type} {opts} {sec_prof} {initrd}'.format(
|
||||
no_serial=no_serial, sys_type=sys_type_2, opts=opts_2,
|
||||
sec_prof=sec_prof_1, initrd=initrd))
|
||||
elif kernel_option == 'S3':
|
||||
cmd = ('{no_serial} {sys_type} {opts} {sec_prof} {initrd}'.format(
|
||||
no_serial=no_serial, sys_type=sys_type_2, opts=opts_2,
|
||||
sec_prof=sec_prof_2, initrd=initrd))
|
||||
elif kernel_option == '4':
|
||||
cmd = ('{serial} {sys_type} {opts} {sec_prof} {initrd}'.format(
|
||||
serial=serial, sys_type=sys_type_3, opts=opts_2,
|
||||
sec_prof=sec_prof_1, initrd=initrd))
|
||||
elif kernel_option == 'S4':
|
||||
cmd = ('{serial} {sys_type} {opts} {sec_prof} {initrd}'.format(
|
||||
serial=serial, sys_type=sys_type_3, opts=opts_2,
|
||||
sec_prof=sec_prof_2, initrd=initrd))
|
||||
elif kernel_option == '5':
|
||||
cmd = ('{no_serial} {sys_type} {opts} {sec_prof} {initrd}'.format(
|
||||
no_serial=no_serial, sys_type=sys_type_3, opts=opts_2,
|
||||
sec_prof=sec_prof_1, initrd=initrd))
|
||||
elif kernel_option == 'S5':
|
||||
cmd = ('{no_serial} {sys_type} {opts} {sec_prof} {initrd}'.format(
|
||||
no_serial=no_serial, sys_type=sys_type_3, opts=opts_2,
|
||||
sec_prof=sec_prof_2, initrd=initrd))
|
||||
|
||||
return cmd
|
||||
|
||||
|
||||
def grub_checker(iso, mode, grub_option, grub_cmd):
|
||||
"""Check a grub cmd boot line against the ones in the StarlingX ISO file
|
||||
|
||||
This function compare the grub cmd boot line built from get_cmd_boot_line
|
||||
function against a StarlingX ISO file in order to check if this is still
|
||||
valid.
|
||||
Basically check if all the arguments from the ISO contains them in the
|
||||
built one from the get_cmd_boot_line function.
|
||||
|
||||
:param iso: the iso to mount.
|
||||
:param mode: the mode to check the grub cmd line, this can be vbios/uefi.
|
||||
:param grub_option: the boot line to compare which could have the
|
||||
following values:
|
||||
- 0: Standard Controller Configuration > Serial Console >
|
||||
Standard Security Boot Profile.
|
||||
- S0: Standard Controller Configuration > Serial Console > Extended
|
||||
Security Boot Profile
|
||||
- 1: Standard Controller Configuration > Graphical Console >
|
||||
Standard Security Boot Profile
|
||||
- S1: Standard Controller Configuration > Graphical Console >
|
||||
Extended Security Boot Profile
|
||||
- 2: All-in-one Controller Configuration > Serial Console >
|
||||
Standard Security Boot Profile
|
||||
- S2: All-in-one Controller Configuration > Serial Console >
|
||||
Extended Security Boot Profile
|
||||
- 3: All-in-one Controller Configuration > Graphical Console >
|
||||
Standard Security Boot Profile
|
||||
- S3 All-in-one Controller Configuration > Graphical Console >
|
||||
Extended Security Boot Profile
|
||||
- 4: All-in-one (lowlatency) Controller Configuration >
|
||||
Serial Console > Standard Security Boot Profile
|
||||
- S4: All-in-one (lowlatency) Controller Configuration >
|
||||
Serial Console > Extended Security Boot Profile
|
||||
- 5: All-in-one (lowlatency) Controller Configuration >
|
||||
Graphical Console > Standard Security Boot Profile
|
||||
- S5: All-in-one (lowlatency) Controller Configuration >
|
||||
Graphical Console > Extended Security Boot Profile
|
||||
:param grub_cmd: the cmd line built from get_cmd_boot_line function
|
||||
:return
|
||||
- match: if the grub_cmd has all the elements from the iso
|
||||
- mismatch: if the grub_cmd does not have all the elements from the iso
|
||||
"""
|
||||
allowed_grub_options = [
|
||||
'0', 'S0', '1', 'S1', '2', 'S2', '3', 'S3', '4', 'S4', '5', 'S5']
|
||||
|
||||
if grub_option not in allowed_grub_options:
|
||||
raise KeyError('grub boot number does not exists')
|
||||
|
||||
mount_point = '/tmp/cdrom'
|
||||
|
||||
if os.path.exists(mount_point) and os.path.ismount(mount_point):
|
||||
bash.run_command('sudo umount -l {}'.format(mount_point),
|
||||
raise_exception=True)
|
||||
elif not os.path.exists(mount_point):
|
||||
os.makedirs(mount_point)
|
||||
|
||||
# mounting the iso file
|
||||
bash.run_command('sudo mount -o loop {} {}'.format(iso, mount_point),
|
||||
raise_exception=True)
|
||||
|
||||
if mode == 'vbios':
|
||||
grub = '{}/syslinux.cfg'.format(mount_point)
|
||||
regex = '-e "label [0-9]" -e "label [A-Z][0-9]" -e append'
|
||||
grub_extracted_lines = bash.run_command('grep {} {}'.format(
|
||||
regex, grub))
|
||||
grub_option_list = grub_extracted_lines[1].split('\n')
|
||||
|
||||
key_dict = []
|
||||
values_dict = []
|
||||
|
||||
# Filling the lists
|
||||
for line in grub_option_list:
|
||||
current_line = line.strip()
|
||||
if current_line.startswith('label'):
|
||||
key_dict.append(current_line.replace('label ', ''))
|
||||
elif current_line.startswith('append'):
|
||||
values_dict.append(current_line)
|
||||
|
||||
# zipping the list in only one as a list of tuples
|
||||
grub_list = zip(key_dict, values_dict)
|
||||
grub_dict = dict()
|
||||
|
||||
# creating a dict with the grub entries
|
||||
for key, value in grub_list:
|
||||
grub_dict[key] = value
|
||||
|
||||
# comparing the grub boot line from the ISO with the one obtained from
|
||||
# get_cmd_boot_line function
|
||||
iso_boot_line = grub_dict[grub_option].split()
|
||||
|
||||
# removing blacklist elements from iso_boot_line
|
||||
blacklist = [
|
||||
i for i, word in enumerate(iso_boot_line)
|
||||
if word.startswith('console')
|
||||
]
|
||||
|
||||
for index in blacklist:
|
||||
del iso_boot_line[index]
|
||||
|
||||
if set(grub_cmd.split()).issuperset(set(iso_boot_line)):
|
||||
status = 'match'
|
||||
else:
|
||||
status = 'mismatch'
|
||||
diff = [
|
||||
element for element in iso_boot_line
|
||||
if element not in grub_cmd.split()]
|
||||
LOG.warn('missed params from cmd grub line')
|
||||
for element in diff:
|
||||
LOG.warn(element)
|
||||
|
||||
elif mode == 'uefi':
|
||||
raise NotImplementedError
|
||||
else:
|
||||
raise IndexError('{}: not allowed'.format(mode))
|
||||
|
||||
# dismount the mount_point
|
||||
bash.run_command('sudo umount -l {}'.format(mount_point),
|
||||
raise_exception=True)
|
||||
|
||||
return status
|
||||
|
||||
def get_controllers_ip(env, config_file, config_type, lab_file):
|
||||
"""Get IPs of the controllers from the specific stx configuration file
|
||||
|
||||
Args:
|
||||
- config_file: The stx-configuration.ini file
|
||||
- config_type: The type of configuration selected from the command
|
||||
line.
|
||||
|
||||
Return:
|
||||
- controller_data: Dictionary with the key name and the IP of the
|
||||
controllers
|
||||
"""
|
||||
|
||||
# Read Configurtion File
|
||||
conf = yaml.safe_load(open(config_file))
|
||||
|
||||
cont_data = {}
|
||||
# Get Controllers IP's
|
||||
if config_type == 'simplex':
|
||||
cont_data['IP_UNIT_0_ADDRESS'] = conf['external_oam_floating_address']
|
||||
cont_data['IP_UNIT_1_ADDRESS'] = ''
|
||||
else:
|
||||
cont_data['IP_UNIT_0_ADDRESS'] = conf['external_oam_node_0_address']
|
||||
cont_data['IP_UNIT_1_ADDRESS'] = conf['external_oam_node_1_address']
|
||||
|
||||
if env == 'baremetal':
|
||||
# Get phyisical interfaces
|
||||
conf_lab = yaml.safe_load(open(lab_file))
|
||||
|
||||
cont_data['OAM_IF'] = conf_lab['nodes']['controller-0']['oam_if']
|
||||
cont_data['MGMT_IF'] = conf_lab['nodes']['controller-0']['mgmt_if']
|
||||
|
||||
return cont_data
|
|
@ -0,0 +1,220 @@
|
|||
"""Provides the capability to setup a StarlingX iso with specific
|
||||
configuration"""
|
||||
|
||||
from imp import reload
|
||||
import os
|
||||
import getpass
|
||||
import subprocess
|
||||
import pexpect
|
||||
|
||||
import psutil
|
||||
|
||||
from Config import config
|
||||
from Libraries import common
|
||||
from Utils import logger
|
||||
from Utils import network
|
||||
|
||||
# reloading config.ini
|
||||
reload(config)
|
||||
|
||||
# Global variables
|
||||
THIS_PATH = os.path.dirname(os.path.abspath(__file__))
|
||||
PROJECT_PATH = os.path.dirname(THIS_PATH)
|
||||
CURRENT_USER = getpass.getuser()
|
||||
PASSWORD = config.get('credentials', 'STX_DEPLOY_USER_PSWD')
|
||||
PROMPT = '$'
|
||||
|
||||
# setup the logger
|
||||
LOG_FILENAME = 'iso_setup.log'
|
||||
LOG_PATH = config.get('general', 'LOG_PATH')
|
||||
LOG = logger.setup_logging(
|
||||
'iso_setup', log_file='{path}/{filename}'.format(
|
||||
path=LOG_PATH, filename=LOG_FILENAME), console_log=False)
|
||||
|
||||
|
||||
class Installer(object):
|
||||
"""Install a StarlingX ISO though serial console"""
|
||||
|
||||
def __init__(self):
|
||||
self.child = pexpect.spawn(config.get('iso_installer', 'VIRSH_CMD'))
|
||||
self.child.logfile = open('{}/iso_setup_console.txt'.format(
|
||||
LOG_PATH), 'wb')
|
||||
|
||||
@staticmethod
|
||||
def open_xterm_console():
|
||||
"""Open a xterm console to visualize logs from serial connection"""
|
||||
|
||||
suite_path = os.path.dirname(THIS_PATH)
|
||||
terminal = 'xterm'
|
||||
terminal_title = '"controller-0 boot console"'
|
||||
geometry = '-0+0' # upper right hand corner
|
||||
os.environ['DISPLAY'] = ':0'
|
||||
command = 'python {suite}/Utils/watcher.py {log_path}'.format(
|
||||
suite=suite_path, log_path=LOG_PATH)
|
||||
|
||||
try:
|
||||
pid_list = subprocess.check_output(['pidof', terminal]).split()
|
||||
|
||||
# killing all xterm active sessions
|
||||
for pid in pid_list:
|
||||
_pid = psutil.Process(int(pid))
|
||||
# terminate the process
|
||||
_pid.terminate()
|
||||
|
||||
if _pid.is_running():
|
||||
# forces the process to terminate
|
||||
_pid.suspend()
|
||||
_pid.resume()
|
||||
except subprocess.CalledProcessError:
|
||||
LOG.info('There is not process for : {}'.format(terminal))
|
||||
|
||||
os.system('{term} -geometry {geo} -T {title} -e {cmd} &'.format(
|
||||
term=terminal, geo=geometry, title=terminal_title, cmd=command))
|
||||
|
||||
def boot_installer(self):
|
||||
"""Interact with the installation process at boot time
|
||||
|
||||
The aim of this function is send the appropriate arguments in order to
|
||||
boot the ISO
|
||||
"""
|
||||
boot_timeout = int(config.get('iso_installer', 'BOOT_TIMEOUT'))
|
||||
self.child.expect('Escape character')
|
||||
LOG.info('connected to the VM (controller-0)')
|
||||
# send a escape character
|
||||
self.child.sendline('\x1b')
|
||||
self.child.expect('boot:')
|
||||
cmd_boot_line = common.get_cmd_boot_line()
|
||||
self.child.sendline(cmd_boot_line)
|
||||
LOG.info('kernel command line sent: {}'.format(cmd_boot_line))
|
||||
# send a enter character
|
||||
self.child.sendline('\r')
|
||||
# setting a boot timeout
|
||||
self.child.timeout = boot_timeout
|
||||
self.child.expect('Loading vmlinuz')
|
||||
LOG.info('Loading vmlinuz')
|
||||
self.child.expect('Loading initrd.img')
|
||||
LOG.info('Loading initrd.img')
|
||||
self.child.expect('Starting installer, one moment...')
|
||||
LOG.info('Starting installer ...')
|
||||
self.child.expect('Performing post-installation setup tasks')
|
||||
LOG.info('Performing post-installation setup tasks')
|
||||
|
||||
def first_login(self):
|
||||
"""Change the password at first login"""
|
||||
|
||||
user_name = config.get('credentials', 'STX_DEPLOY_USER_NAME')
|
||||
self.child.expect('localhost login:')
|
||||
LOG.info('the system boot up correctly')
|
||||
LOG.info('logging into the system')
|
||||
self.child.sendline(user_name)
|
||||
self.child.expect('Password:')
|
||||
self.child.sendline(user_name)
|
||||
LOG.info('setting a new password')
|
||||
self.child.expect('UNIX password:')
|
||||
self.child.sendline(user_name)
|
||||
self.child.expect('New password:')
|
||||
self.child.sendline(PASSWORD)
|
||||
self.child.expect('Retype new password:')
|
||||
self.child.sendline(PASSWORD)
|
||||
self.child.expect('$')
|
||||
LOG.info('the password was changed successfully')
|
||||
|
||||
def configure_temp_network(self):
|
||||
"""Setup a temporal controller IP"""
|
||||
|
||||
controller_tmp_ip = config.get('iso_installer', 'CONTROLLER_TMP_IP')
|
||||
controller_tmp_gateway = config.get(
|
||||
'iso_installer', 'CONTROLLER_TMP_GATEWAY')
|
||||
LOG.info('Configuring temporal network')
|
||||
|
||||
self.child.expect(PROMPT)
|
||||
|
||||
# -----------------------------
|
||||
# getting OS network interfaces
|
||||
timeout_before = self.child.timeout
|
||||
self.child.timeout = 10
|
||||
self.child.sendline('ls /sys/class/net')
|
||||
cmd_stdout = []
|
||||
|
||||
try:
|
||||
for stdout in self.child:
|
||||
cmd_stdout.append(stdout.strip())
|
||||
except pexpect.exceptions.TIMEOUT:
|
||||
LOG.info('custom timeout reached')
|
||||
|
||||
network_interfaces = []
|
||||
network_interfaces.extend(''.join(cmd_stdout[-1:]).split())
|
||||
# returning to the original timeout value
|
||||
self.child.timeout = timeout_before
|
||||
controller_tmp_interface = network_interfaces[0]
|
||||
# -----------------------------
|
||||
|
||||
self.child.sendline('sudo ip addr add {0}/24 dev {1}'.format(
|
||||
controller_tmp_ip, controller_tmp_interface))
|
||||
self.child.expect('Password:')
|
||||
self.child.sendline(PASSWORD)
|
||||
|
||||
self.child.expect(PROMPT)
|
||||
|
||||
self.child.sendline('sudo ip link set {} up'.format(
|
||||
controller_tmp_interface))
|
||||
|
||||
self.child.expect(PROMPT)
|
||||
self.child.sendline('sudo ip route add default via {}'.format(
|
||||
controller_tmp_gateway))
|
||||
|
||||
LOG.info('Network configured, testing ping')
|
||||
self.child.sendline('ping -c 1 127.0.0.1')
|
||||
self.child.expect('1 packets transmitted')
|
||||
LOG.info('Ping successful')
|
||||
|
||||
# updating networks in the config.ini
|
||||
configuration_file = os.path.join(
|
||||
PROJECT_PATH, 'Config', 'config.ini')
|
||||
configuration_type = config.get('general', 'CONFIGURATION_TYPE')
|
||||
network.update_networks_config(
|
||||
network_interfaces, configuration_file, configuration_type)
|
||||
|
||||
def config_controller(self, config_file):
|
||||
"""Configure controller with provided configuration file
|
||||
|
||||
:param config_file: which is the configuration file for
|
||||
config_controller
|
||||
"""
|
||||
config_controller_timeout = int(config.get(
|
||||
'iso_installer', 'CONFIG_CONTROLLER_TIMEOUT'))
|
||||
self.child.expect(PROMPT)
|
||||
LOG.info('Applying configuration (this will take several minutes)')
|
||||
self.child.sendline(
|
||||
'sudo config_controller --force --config-file {}'
|
||||
.format(config_file))
|
||||
self.child.timeout = config_controller_timeout
|
||||
self.child.expect('Configuration was applied')
|
||||
LOG.info(self.child.before)
|
||||
|
||||
def finish_logging(self):
|
||||
"""Stop logging and close log file"""
|
||||
self.child.logfile.close()
|
||||
LOG.info('Closing the log')
|
||||
|
||||
|
||||
def install_iso():
|
||||
"""Start the process of installing a StarlingX ISO"""
|
||||
|
||||
install_obj = Installer()
|
||||
install_obj.open_xterm_console()
|
||||
install_obj.boot_installer()
|
||||
install_obj.first_login()
|
||||
install_obj.configure_temp_network()
|
||||
return install_obj
|
||||
|
||||
|
||||
def config_controller(controller_connection, config_file):
|
||||
"""Start controller configuration with specified configuration file
|
||||
|
||||
:param controller_connection: which is the connection stabilised through
|
||||
to the controller
|
||||
:param config_file: which is the configuration file for config_controller
|
||||
"""
|
||||
controller_connection.config_controller(config_file)
|
||||
controller_connection.finish_logging()
|
|
@ -0,0 +1,528 @@
|
|||
"""Provides the capability to setup a StarlingX iso with specific
|
||||
configuration"""
|
||||
from ast import literal_eval
|
||||
from imp import reload
|
||||
import os
|
||||
import re
|
||||
from shutil import copyfile
|
||||
from shutil import copytree
|
||||
from shutil import rmtree
|
||||
import sys
|
||||
import threading
|
||||
import pexpect
|
||||
import yaml
|
||||
|
||||
from bash import bash
|
||||
|
||||
from Config import config
|
||||
from Utils import logger
|
||||
from Utils import network
|
||||
from Utils.utils import isdir
|
||||
|
||||
# reloading config.ini
|
||||
reload(config)
|
||||
|
||||
# Global variables
|
||||
THIS_PATH = os.path.dirname(os.path.abspath(__file__))
|
||||
PROJECT_PATH = os.path.dirname(THIS_PATH)
|
||||
PROMPT = '$'
|
||||
|
||||
# Setup the logger
|
||||
LOG_FILENAME = 'iso_setup_baremetal.log'
|
||||
LOG_PATH = config.get('general', 'LOG_PATH')
|
||||
LOG = logger.setup_logging('iso_setup_baremetal', log_file='{path}/{filename}'
|
||||
.format(path=LOG_PATH, filename=LOG_FILENAME),
|
||||
console_log=False)
|
||||
|
||||
|
||||
class PxeServer(object):
|
||||
"""Handle PXE services and mount ISO for Installation"""
|
||||
|
||||
def __init__(self, iso_path):
|
||||
self.iso_path = iso_path
|
||||
self.iso_name = os.path.basename(self.iso_path).replace('.iso', '')
|
||||
self.tftp_dir = '/var/lib/tftpboot/uefi'
|
||||
|
||||
def mount_iso(self):
|
||||
"""Mounting ISO and grabbing shim, grub.efi and grub.cfg files"""
|
||||
|
||||
# Mounting ISO on /mnt and on http server
|
||||
mount_point = '/mnt'
|
||||
http_mnt_point = '/var/www/html/stx'
|
||||
tmp_mnt_point = '/tmp'
|
||||
|
||||
if os.listdir(mount_point):
|
||||
LOG.info('{} is busy umounting'.format(mount_point))
|
||||
umounting_attempts = 3
|
||||
|
||||
while umounting_attempts > 0:
|
||||
umounting = bash('sudo umount -l {}'.format(mount_point))
|
||||
|
||||
if umounting.stderr and umounting_attempts:
|
||||
LOG.info('Failed to umount {}, retrying...'.format(
|
||||
mount_point))
|
||||
elif umounting.stderr and not umounting_attempts:
|
||||
LOG.info('Max umounting attempts reached, leaving '
|
||||
'installation')
|
||||
sys.exit(1)
|
||||
else:
|
||||
break
|
||||
|
||||
umounting_attempts -= 1
|
||||
|
||||
bash('sudo mount {0} {1}'.format(self.iso_path, mount_point))
|
||||
LOG.info('Mounting ISO on {}'.format(mount_point))
|
||||
|
||||
if isdir(os.path.join(http_mnt_point, self.iso_name)):
|
||||
LOG.info('Folder {0}/{1} already exists in http server, deleting '
|
||||
'it.'.format(http_mnt_point, self.iso_name))
|
||||
rmtree(os.path.join(http_mnt_point, self.iso_name))
|
||||
copytree(mount_point, os.path.join(http_mnt_point, self.iso_name))
|
||||
|
||||
if isdir(os.path.join(tmp_mnt_point, self.iso_name)):
|
||||
LOG.info('Folder {0}/{1} already exists in http server, deleting '
|
||||
'it.'.format(tmp_mnt_point, self.iso_name))
|
||||
rmtree(os.path.join(tmp_mnt_point, self.iso_name))
|
||||
|
||||
# Changing from RPM to CPIO format
|
||||
LOG.info('Uncompressing RPM necessary files')
|
||||
copytree(os.path.join(http_mnt_point, self.iso_name, 'Packages'),
|
||||
os.path.join(tmp_mnt_point, self.iso_name, 'Packages'))
|
||||
grub2_regex = re.compile('grub2-efi-x64-[0-9]')
|
||||
os.chdir(os.path.join(tmp_mnt_point, self.iso_name, 'Packages'))
|
||||
|
||||
for package in os.listdir(
|
||||
os.path.join(tmp_mnt_point, self.iso_name, 'Packages')):
|
||||
|
||||
if 'shim' in package or grub2_regex.search(package):
|
||||
LOG.info('Found grub/shim file uncompressing it')
|
||||
bash('rpm2cpio {} | cpio -dimv'.format(package))
|
||||
|
||||
# Copying shim, and grub files to tftpboot folder
|
||||
# fixme: handle condition to make sure tftp_dir exists
|
||||
if not os.path.isdir(self.tftp_dir):
|
||||
os.makedirs(self.tftp_dir)
|
||||
LOG.info('Copying grub and shim files to TFTP server')
|
||||
|
||||
for root, _, files in os.walk('/tmp/{}/Packages'.format(
|
||||
self.iso_name)):
|
||||
|
||||
for package in files:
|
||||
if any(boot_file in package for boot_file in ('shim.efi',
|
||||
'grubx64.efi')):
|
||||
copyfile(os.path.join(root, package),
|
||||
os.path.join(self.tftp_dir, package))
|
||||
|
||||
if 'grub.cfg' in package:
|
||||
copyfile(os.path.join(root, package),
|
||||
os.path.join(self.tftp_dir, package))
|
||||
copyfile(os.path.join(http_mnt_point, self.iso_name,
|
||||
'EFI/BOOT/grub.cfg'),
|
||||
os.path.join(self.tftp_dir, 'grub.cfg'))
|
||||
|
||||
# Copying vmlinuz and initrd
|
||||
images_dir = os.path.join(self.tftp_dir, 'images')
|
||||
|
||||
if isdir(images_dir):
|
||||
LOG.info('{} already exists, deleting directory.'.format(
|
||||
images_dir))
|
||||
rmtree(images_dir)
|
||||
|
||||
LOG.info('Copying vmlinuz and initrd files.')
|
||||
copytree(os.path.join(mount_point, 'images/pxeboot'),
|
||||
os.path.join(self.tftp_dir, 'images'))
|
||||
|
||||
@staticmethod
|
||||
def check_pxe_services():
|
||||
"""This function is intended to restart DHCP service
|
||||
|
||||
DHCP service needs to be restarted in order to grab the changes on the
|
||||
dhcp config file"""
|
||||
LOG.info('Checking PXE needed services')
|
||||
services = ['dhcpd', 'tftp', 'httpd']
|
||||
|
||||
for service in services:
|
||||
active_service = bash('sudo systemctl is-active {}'
|
||||
.format(service))
|
||||
if 'active' in active_service.stdout:
|
||||
LOG.info('{} service is active'.format(service))
|
||||
continue
|
||||
else:
|
||||
LOG.info('{} service is not active, restarting'
|
||||
.format(service))
|
||||
bash('sudo systemctl restart {}'.format(service))
|
||||
|
||||
def get_efi_boot_line(self, grub_dict):
|
||||
"""Get linuxefi command and initrdefi command from grub_dict
|
||||
|
||||
Get linuxefi command and initrdefi command from grub_dict according to
|
||||
specified option on configuration argument while running runner.py
|
||||
"""
|
||||
configuration_type = config.get('general', 'CONFIGURATION_TYPE')
|
||||
http_server_ip = config.get('baremetal', 'HTTP_SERVER')
|
||||
LOG.info('config_type')
|
||||
LOG.info(configuration_type)
|
||||
boot_lines = dict()
|
||||
|
||||
if configuration_type == 'simplex':
|
||||
boot_lines = grub_dict['aio']['serial']['standard']
|
||||
elif configuration_type == 'duplex':
|
||||
boot_lines = grub_dict['aio']['serial']['standard']
|
||||
elif configuration_type == 'multinode_controller_storage':
|
||||
boot_lines = grub_dict['standard']['serial']['standard']
|
||||
elif configuration_type == 'multinode_dedicated_storage':
|
||||
boot_lines = grub_dict['standard']['serial']['standard']
|
||||
|
||||
prefix = 'uefi/images'
|
||||
linuxefi_cmd = boot_lines['linuxefi']
|
||||
linuxefi_http_cmd = list()
|
||||
|
||||
for parameter in linuxefi_cmd.split(' '):
|
||||
if 'inst.ks' in parameter:
|
||||
ks_file = parameter.split('/')[-1]
|
||||
parameter = 'inst.ks=http://{server}/stx/{iso}/{ks_file}' \
|
||||
.format(server=http_server_ip, iso=self.iso_name,
|
||||
ks_file=ks_file)
|
||||
linuxefi_http_cmd.append(parameter)
|
||||
elif 'inst.stage2' in parameter:
|
||||
parameter = 'inst.stage2=http://{server}/stx/{iso}' \
|
||||
.format(server=http_server_ip, iso=self.iso_name)
|
||||
linuxefi_http_cmd.append(parameter)
|
||||
elif 'vmlinuz' in parameter:
|
||||
parameter = '{prefix}{parameter}'.format(prefix=prefix,
|
||||
parameter=parameter)
|
||||
linuxefi_http_cmd.append(parameter)
|
||||
else:
|
||||
linuxefi_http_cmd.append(parameter)
|
||||
inst_repo = 'inst.repo=http://{server}/stx/{iso}'\
|
||||
.format(server=http_server_ip, iso=self.iso_name)
|
||||
linuxefi_http_cmd.append(inst_repo)
|
||||
boot_lines['linuxefi'] = ' '.join(linuxefi_http_cmd)
|
||||
|
||||
initrd_cmd = boot_lines['initrdefi']
|
||||
initrd_prefix_cmd = list()
|
||||
|
||||
for parameter in initrd_cmd.split(' '):
|
||||
if 'initrd.img' in parameter:
|
||||
parameter = '{prefix}{parameter}'.format(prefix=prefix,
|
||||
parameter=parameter)
|
||||
initrd_prefix_cmd.append(parameter)
|
||||
else:
|
||||
initrd_prefix_cmd.append(parameter)
|
||||
boot_lines['initrdefi'] = ' '.join(initrd_prefix_cmd)
|
||||
return boot_lines
|
||||
|
||||
def handle_grub(self):
|
||||
"""Pointing source files to http server on grub"""
|
||||
|
||||
installation_type = config.get('general', 'CONFIGURATION_TYPE')
|
||||
grub_dict = analyze_grub(
|
||||
os.path.join(self.tftp_dir, 'grub.cfg'))
|
||||
grub_lines = self.get_efi_boot_line(grub_dict)
|
||||
grub_entry = ("menuentry '{config}'{{\n{linuxefi}\n{initrdefi}\n}}"
|
||||
.format(config=installation_type,
|
||||
linuxefi=grub_lines['linuxefi'],
|
||||
initrdefi=grub_lines['initrdefi']))
|
||||
grub_timeout = 'timeout=5\n'
|
||||
with open(os.path.join(self.tftp_dir, 'grub.cfg'), 'w') as grub_file:
|
||||
grub_file.writelines(grub_timeout)
|
||||
grub_file.write(grub_entry)
|
||||
|
||||
|
||||
class Node(object):
|
||||
"""Constructs a Node server that can be booted to pxe and also follow the
|
||||
installation of STX system
|
||||
"""
|
||||
|
||||
def __init__(self, node):
|
||||
self.name = node['name']
|
||||
self.personality = node['personality']
|
||||
self.pxe_nic_mac = node['pxe_nic_mac']
|
||||
self.bmc_ip = node['bmc_ip']
|
||||
self.bmc_user = node['bmc_user']
|
||||
self.bmc_pswd = node['bmc_pswd']
|
||||
if self.name == 'controller-0':
|
||||
self.installation_ip = node['installation_ip']
|
||||
|
||||
def boot_server_to_pxe(self):
|
||||
"""Boot the installation target server using PXE server"""
|
||||
|
||||
LOG.info('Booting {} To PXE'.format(self.name))
|
||||
LOG.info('Node {}: Setting PXE as first boot option'
|
||||
.format(self.name))
|
||||
set_pxe = bash('ipmitool -I lanplus -H {node_bmc_ip} '
|
||||
'-U {node_bmc_user} -P {node_bmc_pswd} '
|
||||
'chassis bootdev pxe'.format(
|
||||
node_bmc_ip=self.bmc_ip,
|
||||
node_bmc_user=self.bmc_user,
|
||||
node_bmc_pswd=self.bmc_pswd))
|
||||
if set_pxe.stderr:
|
||||
LOG.info(set_pxe.stderr)
|
||||
|
||||
LOG.info('Node {}: Resetting target.'.format(self.name))
|
||||
power_status = bash('ipmitool -I lanplus -H {node_bmc_ip} '
|
||||
'-U {node_bmc_user} -P {node_bmc_pswd} '
|
||||
'chassis power status'.format(
|
||||
node_bmc_ip=self.bmc_ip,
|
||||
node_bmc_user=self.bmc_user,
|
||||
node_bmc_pswd=self.bmc_pswd))
|
||||
if power_status.stderr:
|
||||
LOG.info(set_pxe.stderr)
|
||||
|
||||
if "Chassis Power is on" in str(power_status):
|
||||
power = bash('ipmitool -I lanplus -H {node_bmc_ip} '
|
||||
'-U {node_bmc_user} -P {node_bmc_pswd} '
|
||||
'chassis power reset'.format(
|
||||
node_bmc_ip=self.bmc_ip,
|
||||
node_bmc_user=self.bmc_user,
|
||||
node_bmc_pswd=self.bmc_pswd))
|
||||
else:
|
||||
power = bash('ipmitool -I lanplus -H {node_bmc_ip} '
|
||||
'-U {node_bmc_user} -P {node_bmc_pswd} '
|
||||
'chassis power on'.format(
|
||||
node_bmc_ip=self.bmc_ip,
|
||||
node_bmc_user=self.bmc_user,
|
||||
node_bmc_pswd=self.bmc_pswd))
|
||||
if power.stderr:
|
||||
LOG.info(power.stderr)
|
||||
|
||||
LOG.info('Node {}: Deactivating sol sessions.'.format(self.name))
|
||||
kill_sol = bash('ipmitool -I lanplus -H {node_bmc_ip} '
|
||||
'-U {node_bmc_user} -P {node_bmc_pswd} sol '
|
||||
'deactivate'.format(node_bmc_ip=self.bmc_ip,
|
||||
node_bmc_user=self.bmc_user,
|
||||
node_bmc_pswd=self.bmc_pswd))
|
||||
if kill_sol.stderr:
|
||||
LOG.info(kill_sol.stderr)
|
||||
|
||||
def follow_node_installation(self):
|
||||
"""This function is intended to follow nodes installation"""
|
||||
|
||||
user_name = config.get('credentials', 'STX_DEPLOY_USER_NAME')
|
||||
password = config.get('credentials', 'STX_DEPLOY_USER_PSWD')
|
||||
|
||||
LOG.info('Node {}: Following node installation.'.format(self.name))
|
||||
installation = pexpect.spawn(('ipmitool -I lanplus -H {node_bmc_ip} '
|
||||
'-U {node_bmc_user} -P {node_bmc_pswd} '
|
||||
'sol activate')
|
||||
.format(node_bmc_ip=self.bmc_ip,
|
||||
node_bmc_user=self.bmc_user,
|
||||
node_bmc_pswd=self.bmc_pswd))
|
||||
installation.logfile = open('{}/iso_setup_installation.txt'.format(
|
||||
LOG_PATH), 'wb')
|
||||
installation.timeout = int(config.get('iso_installer', 'BOOT_TIMEOUT'))
|
||||
installation.expect('Start PXE over IPv4.')
|
||||
LOG.info('Node {}: Trying to boot using PXE'.format(self.name))
|
||||
installation.expect('Linux version')
|
||||
LOG.info('Node {}: Loading Linux Kernel'.format(self.name))
|
||||
installation.expect('Welcome to')
|
||||
LOG.info('Node {}: CentOS have been loaded'.format(self.name))
|
||||
installation.expect('Starting installer, one moment...')
|
||||
LOG.info('Node {}: Starting installer ...'.format(self.name))
|
||||
installation.expect('Performing post-installation setup tasks')
|
||||
LOG.info('Node {}: Performing post-installation setup tasks'
|
||||
.format(self.name))
|
||||
installation.expect('login:')
|
||||
LOG.info('Node {}: the system boot up correctly'.format(self.name))
|
||||
LOG.info('Node {}: logging into the system'.format(self.name))
|
||||
installation.sendline(user_name)
|
||||
installation.expect('Password:')
|
||||
installation.sendline(user_name)
|
||||
LOG.info('Node {}: setting a new password'.format(self.name))
|
||||
installation.expect('UNIX password:')
|
||||
installation.sendline(user_name)
|
||||
installation.expect('New password:')
|
||||
installation.sendline(password)
|
||||
installation.expect('Retype new password:')
|
||||
installation.sendline(password)
|
||||
installation.expect('$')
|
||||
LOG.info('Node {}: the password was changed successfully'
|
||||
.format(self.name))
|
||||
installation.close()
|
||||
LOG.info('Node {}: Closing SOL session after successfully installation'
|
||||
.format(self.name))
|
||||
deactivate_sol = bash(('ipmitool -I lanplus -H {node_bmc_ip} '
|
||||
'-U {node_bmc_user} -P {node_bmc_pswd} '
|
||||
'sol deactivate')
|
||||
.format(node_bmc_ip=self.bmc_ip,
|
||||
node_bmc_user=self.bmc_user,
|
||||
node_bmc_pswd=self.bmc_pswd))
|
||||
if not deactivate_sol.stderr:
|
||||
LOG.info('Node {}: SOL session closed successfully'
|
||||
.format(self.name))
|
||||
|
||||
|
||||
def analyze_grub(grub_cfg_file):
|
||||
"""Get linuxefi command and initrdefi command from grub_dict
|
||||
|
||||
Get linuxefi command and initrdefi command from grub_dict according to
|
||||
selected option in config file
|
||||
"""
|
||||
with open(grub_cfg_file, 'r') as grub:
|
||||
lines = grub.readlines()
|
||||
cmd_lines = list()
|
||||
|
||||
for line in lines:
|
||||
|
||||
if 'linuxefi' in line:
|
||||
line = line.strip()
|
||||
cmd_line = "'linuxefi': '{line}',".format(line=line)
|
||||
cmd_lines.append(cmd_line)
|
||||
elif 'initrdefi' in line:
|
||||
line = line.strip()
|
||||
cmd_line = "'initrdefi': '{line}'".format(line=line)
|
||||
cmd_lines.append(cmd_line)
|
||||
elif 'submenu' in line or 'menuentry' in line:
|
||||
if re.search('--id=(.*) {', line):
|
||||
menu_name = re.search('--id=(.*) {', line)
|
||||
else:
|
||||
menu_name = re.search("'(.*)'", line)
|
||||
menu_name = menu_name.group(1)
|
||||
line = "'{}': {{".format(menu_name)
|
||||
cmd_lines.append(line)
|
||||
elif '}' in line:
|
||||
cmd_lines.append('},')
|
||||
|
||||
grub_menu = ''.join(cmd_lines) # type: str
|
||||
grub_menu = '{{ {} }}'.format(grub_menu)
|
||||
grub_dict = literal_eval(grub_menu)
|
||||
return grub_dict
|
||||
|
||||
|
||||
def mount_iso_on_pxe(iso):
|
||||
""""Manage and enable PXE services"""
|
||||
|
||||
pxe_server = PxeServer(iso)
|
||||
pxe_server.mount_iso()
|
||||
pxe_server.handle_grub()
|
||||
pxe_server.check_pxe_services()
|
||||
|
||||
|
||||
def install_iso_master_controller():
|
||||
"""Launch ISO installation on controller-0"""
|
||||
|
||||
nodes_file = os.path.join(os.environ['PYTHONPATH'], 'baremetal',
|
||||
'baremetal_setup.yaml')
|
||||
nodes = yaml.safe_load(open(nodes_file))
|
||||
|
||||
# Update config.ini with OAM and MGMT interfaces
|
||||
network_interfaces = []
|
||||
network_interfaces.insert(0, nodes['nodes']['controller-0']['oam_if'])
|
||||
network_interfaces.insert(1, nodes['nodes']['controller-0']['mgmt_if'])
|
||||
configuration_file = os.path.join(
|
||||
PROJECT_PATH, 'Config', 'config.ini')
|
||||
configuration_type = config.get('general', 'CONFIGURATION_TYPE')
|
||||
network.update_networks_config(
|
||||
network_interfaces, configuration_file, configuration_type)
|
||||
|
||||
# Installing STX on main controller
|
||||
controller_0 = nodes['nodes']['controller-0']
|
||||
master_controller = Node(controller_0)
|
||||
master_controller.boot_server_to_pxe()
|
||||
master_controller.follow_node_installation()
|
||||
|
||||
return master_controller
|
||||
|
||||
|
||||
def get_controller0_ip():
|
||||
"""Returns master controller IP"""
|
||||
|
||||
nodes_file = os.path.join(THIS_PATH, '..', 'BareMetal',
|
||||
'installation_setup.yaml')
|
||||
nodes = yaml.load(open(nodes_file))
|
||||
controller_0 = nodes['controller-0']
|
||||
master_controller = Node(controller_0)
|
||||
|
||||
return master_controller.installation_ip
|
||||
|
||||
|
||||
def config_controller(config_file):
|
||||
"""Configures master controller using its corresponding init file"""
|
||||
|
||||
config_controller_timeout = int(config.get(
|
||||
'iso_installer', 'CONFIG_CONTROLLER_TIMEOUT'))
|
||||
nodes_file = os.path.join(os.environ['PYTHONPATH'], 'baremetal',
|
||||
'baremetal_setup.yaml')
|
||||
nodes = yaml.safe_load(open(nodes_file))
|
||||
controller_0 = nodes['nodes']['controller-0']
|
||||
master_controller = Node(controller_0)
|
||||
serial_cmd = ('ipmitool -I lanplus -H {node_bmc_ip} -U {node_bmc_user} '
|
||||
'-P {node_bmc_pswd} sol activate'
|
||||
.format(node_bmc_ip=master_controller.bmc_ip,
|
||||
node_bmc_user=master_controller.bmc_user,
|
||||
node_bmc_pswd=master_controller.bmc_pswd))
|
||||
|
||||
configuring_controller = pexpect.spawn(serial_cmd)
|
||||
configuring_controller.logfile = open('{}/iso_setup_installation.txt'
|
||||
.format(LOG_PATH), 'wb')
|
||||
configuring_controller.sendline('\r')
|
||||
configuring_controller.expect(PROMPT)
|
||||
LOG.info('Applying configuration (this will take several minutes)')
|
||||
configuring_controller.sendline('sudo config_controller --force --config-file {}'
|
||||
.format(config_file))
|
||||
configuring_controller.timeout = config_controller_timeout
|
||||
configuring_controller.expect('Configuration was applied')
|
||||
LOG.info(configuring_controller.before)
|
||||
configuring_controller.logfile.close()
|
||||
LOG.info('Closing the log')
|
||||
configuring_controller.close()
|
||||
closing_serial_connection = (
|
||||
bash('ipmitool -I lanplus -H {node_bmc_ip} -U {node_bmc_user} '
|
||||
'-P {node_bmc_pswd} sol deactivate'
|
||||
.format(node_bmc_ip=master_controller.bmc_ip,
|
||||
node_bmc_user=master_controller.bmc_user,
|
||||
node_bmc_pswd=master_controller.bmc_pswd)))
|
||||
if closing_serial_connection.stderr:
|
||||
LOG.info(closing_serial_connection.stderr)
|
||||
|
||||
|
||||
def install_secondary_nodes():
|
||||
"""Installs STX on controller-1 and computes"""
|
||||
|
||||
nodes_file = os.path.join(THIS_PATH, '..', 'BareMetal',
|
||||
'installation_setup.yml')
|
||||
nodes = yaml.load(open(nodes_file))
|
||||
|
||||
# Removing controller-0 from Nodes
|
||||
controller_0 = nodes.pop('controller-0')
|
||||
master_controller = Node(controller_0)
|
||||
serial_cmd = ('ipmitool -I lanplus -H {node_bmc_ip} -U {node_bmc_user} '
|
||||
'-P {node_bmc_pswd} sol activate'
|
||||
.format(node_bmc_ip=master_controller.bmc_ip,
|
||||
node_bmc_user=master_controller.bmc_user,
|
||||
node_bmc_pswd=master_controller.bmc_pswd))
|
||||
controller_0_serial = pexpect.spawn(serial_cmd)
|
||||
|
||||
# Loading openrc
|
||||
controller_0_serial.sendline('source /etc/nova/openrc')
|
||||
|
||||
# Adding nodes to master controller
|
||||
nodes_instances = list()
|
||||
node_names = nodes.keys()
|
||||
|
||||
for node_name in node_names:
|
||||
node = Node(nodes[node_name])
|
||||
nodes_instances.append(node)
|
||||
controller_0_serial.sendline('system host-add -n {name} '
|
||||
'-p {personality} -m {mac_address}'
|
||||
.format(name=node.name,
|
||||
personality=node.personality,
|
||||
mac_address=node.pxe_nic_mac))
|
||||
node.boot_server_to_pxe()
|
||||
|
||||
node_installation_threads = list()
|
||||
|
||||
for nodes_instance in nodes_instances:
|
||||
thread = threading.Thread(
|
||||
target=nodes_instance.follow_node_installation())
|
||||
LOG.info('Starting installation on {}'.format(nodes_instance.name))
|
||||
thread.start()
|
||||
node_installation_threads.append(thread)
|
||||
|
||||
# Waiting for nodes to be installed
|
||||
LOG.info('Waiting for nodes to be installed')
|
||||
|
||||
for node_installation_thread in node_installation_threads:
|
||||
node_installation_thread.join()
|
||||
|
||||
LOG.info('All nodes have been installed successfully!')
|
|
@ -0,0 +1,62 @@
|
|||
*** Settings ***
|
||||
Documentation Lock and Unlock compute and storage hosts. Swact a controller.
|
||||
... Author(s):
|
||||
... - Jose Perez Carranza <jose.perez.carranza@intel.com>
|
||||
... - Juan Carlos Alonso <juan.carlos.alonso@intel.com>
|
||||
|
||||
Library SSHLibrary
|
||||
Library Collections
|
||||
Library OperatingSystem
|
||||
Library Libraries/common.py
|
||||
Library String
|
||||
Variables Variables/Global.py
|
||||
Variables Variables/config_init.py Config
|
||||
... %{PYTHONPATH}/Config/config.ini
|
||||
|
||||
*** Keywords ***
|
||||
Unlock Controller
|
||||
[Arguments] ${controller_name}
|
||||
[Documentation] Unlocks specified controller.
|
||||
Wait Until Keyword Succeeds 15 min 10 sec Check Property Value
|
||||
... ${controller_name} availability online
|
||||
${result} Run Command system host-unlock ${controller_name} True
|
||||
... 60
|
||||
Wait Until Keyword Succeeds 20 min 5 sec Check Property Value
|
||||
... ${controller_name} administrative unlocked
|
||||
[Return] ${result}
|
||||
|
||||
Unlock Compute
|
||||
[Arguments] ${compute}
|
||||
[Documentation] Unlock specified compute.
|
||||
Run Command system host-unlock ${compute} True 60 sec
|
||||
Check Host Readiness ${compute}
|
||||
|
||||
Lock Node
|
||||
[Documentation] Locks specified node.
|
||||
[Arguments] ${controller_name}
|
||||
Wait Until Keyword Succeeds 5 min 10 sec Check Property Value
|
||||
... ${controller_name} availability available
|
||||
${result} Run Command system host-lock ${controller_name} True
|
||||
Wait Until Keyword Succeeds 5 min 10 sec Check Property Value
|
||||
... ${controller_name} administrative locked
|
||||
[Return] ${result}
|
||||
|
||||
Swact Controller
|
||||
[Arguments] ${controller}
|
||||
[Documentation] Swact the active controller and activates the SSH
|
||||
... connection with the new active controller
|
||||
${result} Run Command system host-swact ${controller} True
|
||||
${new_act_cont} Set Variable If
|
||||
... '${controller}'=='controller -0' controller-1 controller-0
|
||||
Wait Until Keyword Succeeds 10 min 2 sec Check Host Task
|
||||
... ${controller} Swact: Complete
|
||||
Check Host Readiness ${new_act_cont} 1
|
||||
# - Switch SSH connection to the Active Controller
|
||||
Switch Controller Connection ${secondary_controller_connection}
|
||||
... ${master_controller_connection}
|
||||
|
||||
Unlock Storage
|
||||
[Arguments] ${storage}
|
||||
[Documentation] Unlock specified storage node.
|
||||
Run Command system host-unlock ${storage} True 60 sec
|
||||
Check Host Readiness ${storage}
|
|
@ -0,0 +1,93 @@
|
|||
*** Settings ***
|
||||
Documentation Checks the health of the PODs, kube system services and
|
||||
... perform a helm override to openstack application.
|
||||
... Author(s):
|
||||
... - Jose Perez Carranza <jose.perez.carranza@intel.com>
|
||||
... - Juan Carlos Alonso <juan.carlos.alonso@intel.com>
|
||||
|
||||
Library SSHLibrary
|
||||
Library Collections
|
||||
Library OperatingSystem
|
||||
Library Libraries/common.py
|
||||
Library String
|
||||
Variables Variables/Global.py
|
||||
Variables Variables/config_init.py Config
|
||||
... %{PYTHONPATH}/Config/config.ini
|
||||
|
||||
*** Keywords ***
|
||||
Check PODs Health
|
||||
[Documentation] Check all OpenStack pods are healthy
|
||||
${kubectl_cmd} Set Variable kubectl get pods --all-namespaces -o wide
|
||||
${cmd} Catenate SEPARATOR=| ${kubectl_cmd} grep -v NAMESPACE
|
||||
... grep -v Running grep -v Completed
|
||||
&{result} Run Command ${cmd}
|
||||
${value} Get From Dictionary ${result} stdout
|
||||
Should Be Empty ${value}
|
||||
|
||||
Helm Override OpenStack
|
||||
[Arguments] ${app_name} ${char_name} ${namespace}
|
||||
[Documentation] Helm override for OpenStack nova chart and reset.
|
||||
${kubectl_cmd} Set Variable system helm-override-update
|
||||
${cmd} Catenate ${kubectl_cmd} --set conf.nova.DEFAULT.foo=bar
|
||||
... ${app_name} ${char_name} ${namespace}
|
||||
Run Command ${cmd} True
|
||||
|
||||
Check Helm Override OpenStack
|
||||
[Documentation] Check nova-compute.conf is updated in all nova-compute
|
||||
... containers.
|
||||
${kubectl_cmd} Set Variable kubectl get pods --all-namespaces -o wide
|
||||
${cmd} Catenate SEPARATOR=| ${kubectl_cmd} grep nova-compute
|
||||
... awk '{print $2}'
|
||||
&{result} Run Command ${cmd}
|
||||
@{nova_pod_list} Convert Response To List ${result}
|
||||
${kubectl_cmd} Set Variable kubectl exec -n openstack -it
|
||||
: FOR ${nova_pod} IN @{nova_pod_list}
|
||||
\ ${cmd} Catenate ${kubectl_cmd} ${nova_pod}
|
||||
... -- grep foo /etc/nova/nova.conf
|
||||
\ &{result} Run Command ${cmd}
|
||||
\ Should Contain ${result.stdout} foo = bar
|
||||
|
||||
Check Kube System Services
|
||||
[Documentation] Check pods status and kube-system services are
|
||||
... displayed.
|
||||
${kubectl_cmd} Set Variable kubectl get services -n kube-system
|
||||
${cmd} Catenate SEPARATOR=| ${kubectl_cmd} grep -v NAME
|
||||
... awk '{print $1}'
|
||||
&{result} Run Command ${cmd}
|
||||
${kubeb_systems} Get From Dictionary ${result} stdout
|
||||
Should Contain ${kubeb_systems} ingress
|
||||
Should Contain ${kubeb_systems} ingress-error-pages
|
||||
Should Contain ${kubeb_systems} ingress-exporter
|
||||
Should Contain ${kubeb_systems} kube-dns
|
||||
Should Contain ${kubeb_systems} tiller-deploy
|
||||
&{result} Run Command kubectl get deployments.apps -n kube-system
|
||||
${kubeb_systems} Get From Dictionary ${result} stdout
|
||||
Should Contain ${kubeb_systems} calico-kube-controllers
|
||||
Should Contain ${kubeb_systems} coredns
|
||||
Should Contain ${kubeb_systems} ingress-error-pages
|
||||
Should Contain ${kubeb_systems} rbd-provisioner
|
||||
Should Contain ${kubeb_systems} tiller-deploy
|
||||
|
||||
Create POD
|
||||
[Arguments] ${pod_yml} ${pod_name}
|
||||
[Documentation] Create a POD.
|
||||
&{result} Run Command kubectl create -f ${pod_yml}
|
||||
${value} Get From Dictionary ${result} stdout
|
||||
Should Be Equal As Strings ${value} pod/${pod_name} created
|
||||
|
||||
Delete POD
|
||||
[Arguments] ${pod_name}
|
||||
[Documentation] Delete a POD.
|
||||
&{result} Run Command kubectl delete pods ${pod_name} timeout=60
|
||||
${value} Get From Dictionary ${result} stdout
|
||||
Should Be Equal As Strings ${value} pod "${pod_name}" deleted
|
||||
|
||||
Check POD
|
||||
[Arguments] ${pod_name}
|
||||
[Documentation] Check if a POD is running.
|
||||
${kubectl_cmd} Set Variable kubectl get pods -n default
|
||||
${cmd} Catenate SEPARATOR=| ${kubectl_cmd} grep ${pod_name}
|
||||
... awk '{print $3}'
|
||||
&{result} Run Command ${cmd}
|
||||
${status} Get From Dictionary ${result} stdout
|
||||
Should Be Equal As Strings ${status} Running
|
|
@ -0,0 +1,477 @@
|
|||
*** Settings ***
|
||||
Documentation Establish a SSH connection with the master controller to
|
||||
... execute openstack commands to create networks, subnetworks, flavors,
|
||||
... images, volumes, snapshots, instances, etc.
|
||||
... Author(s):
|
||||
... - Jose Perez Carranza <jose.perez.carranza@intel.com>
|
||||
... - Juan Carlos Alonso <juan.carlos.alonso@intel.com>
|
||||
|
||||
Library Collections
|
||||
Library SSHLibrary
|
||||
Library String
|
||||
Resource Resources/Utils.robot
|
||||
Variables Variables/Global.py
|
||||
|
||||
*** Keywords ***
|
||||
Run OS Command
|
||||
[Arguments] ${cmd} ${fail_if_error}=False ${timeout}=${TIMEOUT+20}
|
||||
[Documentation] Keyword to execute exclusively commands for OpenStack as
|
||||
... it uses the proper token for OS authentication.
|
||||
${load_os_token} Set Variable export OS_CLOUD=openstack_helm
|
||||
${stdout} ${stderr} ${rc} Execute Command
|
||||
... ${load_os_token} && ${cmd} return_stdout=True
|
||||
... return_stderr=True return_rc=True timeout=${timeout}
|
||||
${res} Create dictionary stdout=${stdout} stderr=${stderr}
|
||||
... rc=${rc}
|
||||
Run Keyword If ${rc} != 0 and ${fail_if_error} == True FAIL
|
||||
... ${stderr}
|
||||
[Return] ${res}
|
||||
|
||||
Create Network
|
||||
[Arguments] ${network_name} ${additional_args}=${EMPTY}
|
||||
... ${verbose}=TRUE
|
||||
[Documentation] Create Network with openstack request.
|
||||
${openstack_cmd} Set Variable openstack network create
|
||||
${cmd} Catenate ${openstack_cmd} ${network_name}
|
||||
... ${additional_args}
|
||||
Run OS Command ${cmd} True 30 sec
|
||||
|
||||
Create Subnet
|
||||
[Arguments] ${network_name} ${range_ip}
|
||||
... ${additional_args}=${EMPTY}
|
||||
[Documentation] Create SubNet for the Network with neutron request.
|
||||
${openstack_cmd} Set Variable openstack subnet create
|
||||
${cmd} Catenate ${openstack_cmd} --network ${network_name}
|
||||
... --subnet-range ${range_ip} ${additional_args}
|
||||
Run OS Command ${cmd} True 30 sec
|
||||
|
||||
Create Flavor
|
||||
[Arguments] ${ram} ${vcpus} ${disk} ${name}
|
||||
... ${extra_args}=${EMPTY}
|
||||
[Documentation] Create a flavor with specified values.
|
||||
${openstack_cmd} Set Variable openstack flavor create
|
||||
${cmd} Catenate ${openstack_cmd} --ram ${ram} --disk ${disk}
|
||||
... --vcpus ${vcpus} --public --id auto ${extra_args}
|
||||
... ${name}
|
||||
Run OS Command ${cmd} True 3 min
|
||||
|
||||
Create Image
|
||||
[Arguments] ${file_path} ${disk_format} ${name}
|
||||
[Documentation] Create image from a given .img file.
|
||||
SSHLibrary.File Should Exist ${file_path}
|
||||
${openstack_cmd} Set Variable openstack image create
|
||||
${cmd} Catenate ${openstack_cmd} --file ${file_path}
|
||||
... --disk-format ${disk_format} --public ${name}
|
||||
Run OS Command ${cmd} True 3 min
|
||||
Wait Until Keyword Succeeds 5 min 10 sec Check Field Value
|
||||
... image ${name} status active
|
||||
|
||||
Create Volume
|
||||
[Arguments] ${size} ${image} ${bootable} ${name}
|
||||
[Documentation] Create Volume.
|
||||
${openstack_cmd} Set Variable openstack volume create
|
||||
${cmd} Catenate ${openstack_cmd} --size ${size}
|
||||
... --image ${image} ${bootable} ${name}
|
||||
Run OS Command ${cmd} True 30 sec
|
||||
Wait Until Keyword Succeeds 10 min 10 sec Check Field Value
|
||||
... volume ${name} status available
|
||||
|
||||
Create Snapshot
|
||||
[Arguments] ${volume} ${name}
|
||||
[Documentation] Create Snapshot.
|
||||
Run OS Command
|
||||
... openstack volume snapshot create --volume ${volume} ${name}
|
||||
... True 30 sec
|
||||
Wait Until Keyword Succeeds 5 min 10 sec Check Field Value
|
||||
... volume snapshot ${name} status available
|
||||
|
||||
Create Stack
|
||||
[Arguments] ${stack_name} ${stack_template} ${net_id}
|
||||
[Documentation] Create Stack
|
||||
${openstack_cmd} Set Variable openstack stack create --template
|
||||
${cmd} Catenate ${openstack_cmd} ${stack_template}
|
||||
... ${stack_name} --parameter "NetID=${net_id}"
|
||||
${output} Run OS Command ${cmd}
|
||||
Wait Until Keyword Succeeds 5 min 10 sec Check Field Value
|
||||
... stack ${stack_name} stack_status CREATE_COMPLETE
|
||||
${openstack_cmd} Set Variable openstack server list
|
||||
${cmd} Catenate SEPARATOR=| ${openstack_cmd} awk '{print$4}'
|
||||
... grep -v "Name"
|
||||
&{result} Run OS Command ${cmd} True 30 sec
|
||||
@{vm_list} Convert Response To List ${result}
|
||||
: FOR ${vm} IN @{vm_list}
|
||||
\ Wait Until Keyword Succeeds 5 min 10 sec Check Field Value
|
||||
... server ${vm} status ACTIVE
|
||||
\ Wait Until Keyword Succeeds 5 min 10 sec Check Field Value
|
||||
... server ${vm} power_state Running
|
||||
|
||||
Create Instance
|
||||
[Arguments] ${net_name} ${vm_name} ${image} ${flavor}
|
||||
[Documentation] Create a VM Instances with the net id of the Netowrk
|
||||
... flavor and image
|
||||