Python-based installer for StarlingX

Initial submission for a Python-based virtualbox installer for StarlingX

Co-Authored-By: Ovidiu.Poncea <ovidiu.poncea@windriver.com>
Co-Authored-By: Wei Zhou <wei.zhou@windriver.com>
Co-Authored-By: William Jia <william.jia@windriver.com>
Co-Authored-By: Sebastien Papineau <sebastien.papineau@windriver.com>
Co-Authored-By: Timothy Mather <timothy.mather@windriver.com>
Co-Authored-By: Paul Buzuloiu <paul.buzuloiu@windriver.com>

DocImpact
Story: 2005051
Task: 29552
Signed-off-by: Maria Yousaf <maria.yousaf@windriver.com>

Change-Id: Iad5b6d8103886d7eeeab7bf2782eca4adb95e3e6
This commit is contained in:
Maria Yousaf 2019-02-19 13:09:32 -05:00
parent 254fa6531b
commit 1846d3fcd6
24 changed files with 10912 additions and 0 deletions

View File

@ -0,0 +1,398 @@
#!/usr/bin/python3
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Parser to handle command line arguments
"""
import argparse
import getpass
def handle_args():
"""
Handle arguments supplied to the command line
"""
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
"""
**************************************
* Setup type & install configuration *
**************************************
"""
parser.add_argument("--setup-type", help=
"""
Type of setup:
AIO-SX
AIO-DX
STANDARD
STORAGE
""",
choices=['AIO-SX', 'AIO-DX', 'STANDARD', 'STORAGE'],
type=str)
parser.add_argument("--controllers", help=
"""
Number of controllers:
1 - single controller
2 - two controllers
""",
choices=[1, 2],
type=int,
default=2)
parser.add_argument("--workers", help=
"""
Number of workers:
1 - single worker
2 - two workers
etc.
""",
type=int,
default=2)
parser.add_argument("--storages", help=
"""
Number of storage nodes:
1 - single storage node
2 - two storage nodes
etc.\n
""",
type=int,
default=2)
parser.add_argument("--from-stage", help=
"""
Start stage.
For a list of stages run --list-stages
\n
""",
type=str)
parser.add_argument("--to-stage", help=
"""
End stage.
For a list of stages run --list-stages
\n
""",
type=str)
parser.add_argument("--custom-stages", help=
"""
Custom, comma separated list of stages.
For a list of stages run --list-stages
\n
""",
type=str,
default=None)
"""
******************************************
* Config folders and files configuration *
******************************************
"""
parser.add_argument("--iso-location", help=
"""
Location of ISO including the filename:
/folk/myousaf/bootimage.ISO
""",
type=str)
parser.add_argument("--config-files-dir", help=
"""
Directory with config files, scripts, images (i.e.
lab_setup.sh, lab_setup.conf, ...) that are needed
for the install. All files at this location are
transfered to controller-0 in /home/wrsroot. You
can add you own scripts that you need to be
present on the controller. Caution: rsync will
follow links and will fail if links are broken!
Use --config-files-dir-dont-follow-links
instead. Also, you can use both options for
different folders.
""",
type=str)
parser.add_argument("--config-files-dir-dont-follow-links", help=
"""
Same as --config-files-dir but keep symbolic link as is.
""",
type=str)
parser.add_argument("--config-controller-ini", help=
"""
Path to the local config_controller .ini. This
file is transfered to the controller. NOTE: OAM
configuration in this ini is updated dynamically
based on networking related args.
(e.g. stx_config.ini_centos,
~/stx_config.ini_centos, /home/myousaf ...).
""",
type=str)
parser.add_argument("--vbox-home-dir", help=
"""
This is the folder where vbox disks will be
placed. e.g. /home or /folk/cgts/users
The disks will be in /home/wzhou/vbox_disks/ or
/folk/cgts/users/wzhou/vbox_disks/
""",
type=str, default='/home')
parser.add_argument("--lab-setup-conf", help=
"""
Path to the config file to use
""",
action='append')
"""
**************************************
* Disk number and size configuration *
**************************************
"""
parser.add_argument("--controller-disks", help=
"""
Select the number of disks for a controller VM. default is 3
""",
type=int, default=3, choices=[1, 2, 3, 4, 5, 6, 7])
parser.add_argument("--storage-disks", help=
"""
Select the number of disks for storage VM. default is 3
""",
type=int, default=3, choices=[1, 2, 3, 4, 5, 6, 7])
parser.add_argument("--worker-disks", help=
"""
Select the number of disks for a worker VM. default is 2
""",
type=int, default=2, choices=[1, 2, 3, 4, 5, 6, 7])
parser.add_argument("--controller-disk-sizes", help=
"""
Configure size in MiB of controller disks as a comma separated list.
""",
type=str)
parser.add_argument("--storage-disk-sizes", help=
"""
Configure size in MiB of storage disks as a comma separated list.
""",
type=str)
parser.add_argument("--worker-disk-sizes", help=
"""
Configure size in MiB of worker disks as a comma separated list.
""",
type=str)
"""
**************
* Networking *
**************
"""
parser.add_argument("--vboxnet-name", help=
"""
Which host only network to use for setup.
""",
type=str)
parser.add_argument("--vboxnet-ip", help=
"""
The IP address of the host only adapter as it
is configured on the host (i.e. gateway). This is also used to
update GATEWAY_IP in [OAM_NETWORK] of config_controller config file.
""",
type=str)
parser.add_argument("--add-nat-interface", help=
"""
Add a new NAT interface to hosts.
""",
action='store_true')
parser.add_argument("--controller-floating-ip", help=
"""
OAM floating IP.
""",
type=str)
parser.add_argument("--controller0-ip", help=
"""
OAM IP of controller-0. This is also used to
update IP_ADDRESS in [OAM_NETWORK] of
config_controller config file of an AIO SX setup.
This should not be the floating IP.
""",
type=str)
parser.add_argument("--controller1-ip", help=
"""
OAM IP of controller-1.
This should not be the floating IP.
""",
type=str)
parser.add_argument("--vboxnet-type", help=
"""
Type of vbox network, either hostonly on nat
""",
choices=['hostonly', 'nat'],
type=str,
default='hostonly')
parser.add_argument("--nat-controller-floating-local-ssh-port", help=
"""
When oam network is configured as 'nat' a port on
the vbox host is used for connecting to ssh on
floating controller. No default value is
configured. This is mandatory if --vboxnet-type is
'nat' for non AIO-SX deployments.
""",
type=str)
parser.add_argument("--nat-controller0-local-ssh-port", help=
"""
When oam network is configured as 'nat' a port on
the vbox host is used for connecting to ssh on
controller-0. This is mandatory if --vboxnet-type
is 'nat'. No default value is configured.
""",
type=str)
parser.add_argument("--nat-controller1-local-ssh-port", help=
"""
When oam network is configured as 'nat' a port on
the vbox host is used for connecting to ssh on
controller-1. No default value is configued. This
is mandatory if --vboxnet-type is 'nat' for non
AIO-SX deployments or if second controller is
installed.
""",
type=str)
parser.add_argument("--ini-oam-cidr", help=
"""
The IP network and mask for the oam net, used to
update CIDR value in [OAM_NETWORK] of
config_controller config file. Default is
10.10.10.0/24
""",
type=str)
parser.add_argument("--ini-oam-ip-start-address", help=
"""
The start for the oam net allocation, used to
update IP_START_ADDRESS value in [OAM_NETWORK] of
config_controller config file. Not needed for AIO
SX setups.
""",
type=str)
parser.add_argument("--ini-oam-ip-end-address", help=
"""
The end for the oam net allocation, used to update
IP_END_ADDRESS value in [OAM_NETWORK] of
config_controller config file. Not needed for AIO
SX setups.
""",
type=str)
"""
******************
* Custom scripts *
******************
"""
parser.add_argument("--script1", help=
"""
Name of an executable script file plus options.
Has to be present in --config-files-dir.
It will be transfered to host in rsync-config
stage and executed as part of custom-script1
stage.
Example: --script1 'scripts/k8s_pv_cfg.sh,50,ssh,user'
Contains a comma separated value of:
<script_name>,<timeout>,<serial or ssh>,<user/root> Where:
script_name = name of the script, either .sh or .py;
timeout = how much to wait, in seconds, before considering failure;
serial/ssh = executed on the serial console;
user/root = as a user or as root (sudo <script_name);
Script executes successfully if return code is 0. Anything else
is considered error and further execution is aborted.
""",
default=None,
type=str)
parser.add_argument("--script2", help=
"""
See --script1
""",
default=None,
type=str)
parser.add_argument("--script3", help=
"""
See --script1
""",
default=None,
type=str)
parser.add_argument("--script4", help=
"""
See --script1
""",
default=None,
type=str)
parser.add_argument("--script5", help=
"""
See --script1
""",
default=None,
type=str)
"""
**************************************
* Other *
**************************************
"""
parser.add_argument("--list-stages", help=
"""
List stages that can be used by autoinstaller.
""",
action='store_true')
parser.add_argument("--logpath", help=
"""
Base directory to store logs.
""",
type=str)
parser.add_argument("--force-delete-lab", help=
"""
Don't ask for confirmation when deleting a lab.
""",
action='store_true')
parser.add_argument("--snapshot", help=
"""
Take snapshot at different stages when the lab is installed.
E.g. before and after config_controller, before and after lab_setup.
""",
action='store_true')
parser.add_argument("--securityprofile", help=
"""
Security profile to use:
standard
extended
Standard is the default
""",
type=str, choices=['standard', 'extended'],
default='standard')
parser.add_argument("--lowlatency", help=
"""
Whether to install an AIO system as low latency.
""",
action='store_true')
parser.add_argument("--install-mode", help=
"""
Lab will be installed using the mode specified. Serial mode by default
""",
type=str, choices=['serial', 'graphical'], default='serial')
parser.add_argument("--username", help=
"""
Username. default is 'wrsroot'
""",
type=str)
parser.add_argument("--password", help=
"""
Password. default is 'Li69nux*'
""",
type=str)
parser.add_argument("--labname", help=
"""
The name of the lab to be created.
""",
type=str)
parser.add_argument("--userid", help=
"""
Unique user id to differentiate vbox machine
unique names such as interface names or serial
ports even if setups have the same names for
different users. Default is your username on this
machine.
""",
type=str,
default=getpass.getuser())
parser.add_argument("--hostiocache", help=
"""
Turn on host i/o caching
""",
action='store_true')
return parser

View File

@ -0,0 +1,115 @@
Pybox
=====
The automated installer provides you with an easy tool to install
StarlingX AIO-SX, AIO-DX, Standard, and Storage setups on Linux hosts on
Virtualbox 5.1.x.
The main concepts of the autoinstaller is the stage and the chain. A stage
is an atomic set of actions taken by the autoinstaller. A chain is a set
of stages executed in a specific order. Stages can be executed
independently and repeated as many times the user needs. Chains can be
configured with the desired stages by the user. Or, the user can select a
specific chain from the available ones.
Example stages:
- create-lab # Create VMs in vbox: controller-0, controller-1...
- install-controller-0 # Install controller-0 from --iso-location
- config-controller # Run config controller using the
- config-controller-ini updated based on --ini-* options.
- rsync-config # Rsync all files from --config-files-dir and
--config-files-dir* to /home/wrsroot.
- lab-setup1 # Run lab_setup with one or more --lab-setup-conf
files from controller-0.
- unlock-controller-0 # Unlock controller-0 and wait for it to reboot.
- lab-setup2 # Run lab_setup with one or more --lab-setup-conf
files from controller-0.
Example chains: [create-lab, install-controller-0, config-controller,
rsync-config, lab-setup1, unlock-controller-0, lab-setup2]. This chain
will install an AIO-SX.
The autoinstaller has a predefined set of chains. The user can select from
these chains and choose from which stage to which stage to do the install.
For example, if the user already executed config_controller, they can choose
to continue from rsync-config to lab-setup2.
The user can also create a custom set of chains, as he sees fit by
specifying them in the desired order. This allows better customization of
the install process. For example, the user might want to execute his own
script after config_controller. In this case, he will have to specify a
chain like this: [create-lab, install-controller-0, config-controller,
rsync-config, custom-script1, lab-setup1, unlock-controller-0, lab-setup2]
The installer supports creating virtualbox snapshots after each stage so
the user does not need to reinstall from scratch. The user can restore the
snapshot of the previous stage, whether to retry or fix the issue
manually, then continue the process.
List of Features
----------------
Basic:
- Multi-user, and multiple lab installs can run at the same time.
- Uses config_controller ini and lab_setup.sh script to drive the
configuration [therefore their requirements have to be met prior to
execution].
- Specify setup (lab) name - this will group all nodes related to
this lab in a virtual box group
- Setup type - specify what you want to install (SX,DX,Standard,
Storage)
- Specify start and end stages or a custom list of stages
- Specify your custom ISO, config_controller ini file locations
- Updates config_controller ini automatically with your custom OAM
networking options so that you don't need to update the ini file for
each setup
- Rsync entire content from a couple of folders on your disk
directly on the controller /home/wrsroot thus allowing you easy access
to your scripts and files
- Take snapshots after each stage
Configuration:
- Specify the number of nodes you want for your setup (one or two controllers,
x storages, y workers)
- Specify the number of disks attached to each node. They use the
default sizes configured) or you can explicitly specify the sizes of the
disks
- Use either 'hostonly' adapter or 'NAT' interface with automated
port forwarding for SSH ports.
Advanced chains:
- Specify custom chain using any of the existing stages
- Ability to run your own custom scripts during the install process
- Ability to define what scripts are executed during custom script
stages, their timeout, are executed over ssh or serial, are executed as
normal user or as root.
Other features
- Log files per lab and date.
- Enable hostiocache option for virtualbox VMs storage controllers
to speed up install
- Basic support for Kubernetes (AIO-SX installable through a custom
chain)
- Support to install lowlatency and securityprofile
Installation
------------
Prerequisites:
- Install Virtualbox. It is recommend v5.1.x. Use v5.2 at your own risk
- Configure at least a vbox hostonly adapter network. If you want to
use NAT, you must also configue a NAT Network.
- Make sure you have rsync, ssh-keygen, and sshpass commands installed.
- Install python3 and pip3 if not already done.
Sample Usage
------------
./install_vbox.py --setup-type AIO-SX --iso-location
"/home/myousaf/bootimage.iso" --labname test --install-mode serial
--config-files-dir /home/myousaf/pybox/configs/aio-sx/
--config-controller-ini
/home/myousaf/pybox/configs/aio-sx/stx_config.ini_centos --vboxnet-name
vboxnet0 --controller0-ip 10.10.10.8 --ini-oam-cidr '10.10.10.0/24'

View File

View File

@ -0,0 +1,67 @@
VSWITCH_TYPE="ovs-dpdk"
## Lab specific configuration
SYSTEM_NAME="vbox"
MGMTSUBNETS=("192.168.151.0/27,192.168.151.32/27,192.168.151.64/27", "192.168.251.0/27,192.168.251.32/27,192.168.251.64/27")
MGMTDVR=("no" "no")
EXTERNALGWIP="192.168.51.1"
EXTERNALCIDR="192.168.51.0/24"
DATAMTU=1500
INFRAMTU=9000
MGMTMTU=1500
NAMESERVERS=("8.8.8.8,4.4.4.4")
NTPSERVERS=("0.pool.ntp.org,1.pool.ntp.org,2.pool.ntp.org")
CINDER_BACKENDS="ceph"
GLANCE_BACKENDS="ceph"
WHEN_TO_CONFIG_CEPH="early"
CEPH_TIER_DEFAULT="storage"
CONTROLLER0_OSD_DEVICES="/dev/disk/by-path/pci-0000:00:0d.0-ata-2.0|${CEPH_TIER_DEFAULT}"
## Provider network overrides
PROVIDERNETS="vlan|data0|${DATAMTU}|10-10|shared \
vlan|data0|${DATAMTU}|700-733|shared \
vlan|data0|${DATAMTU}|734-766|tenant1 \
vlan|data1|${DATAMTU}|767-799|tenant2"
## Manual tenant network assignments
EXTERNALPNET="vlan|data0|10"
INTERNALPNET="vlan|data0"
## Interface overrides
DATA_INTERFACES="ethernet|eth1000|${DATAMTU}|data0 \
ethernet|eth1001|${DATAMTU}|data1"
OAM_INTERFACES="ethernet|enp0s3|1500|none"
## IP address pools to support VXLAN provider networks. Each compute node will
## get an address allocated from within the specified pools
##
VLAN11_IPPOOLS="vlan11v4|192.168.59.0|24|random|192.168.59.239-192.168.59.239 vlan11v6|fd00:0:0:b::|64|sequential|fd00:0:0:b::ee-fd00:0:0:b::ee"
## Networking test mode
NETWORKING_TYPE="layer3"
## Network and VM instance parameters
VIRTIOAPPS=1
## Maximum number of networks physically possible in this lab
MAXNETWORKS=20
## Maximum number of VLANs per internal network
MAXVLANS=4
## Profile testing in this lab
TEST_PROFILES="no"
## Partitions.
CONTROLLER0_PARTITIONS="/dev/disk/by-path/pci-0000:00:0d.0-ata-1.0,[10,10]"
## Devices to extend cgts-vg
CONTROLLER0_CGTS_STORAGE="/dev/disk/by-path/pci-0000:00:0d.0-ata-1.0-part5"
## Local Storage override for this lab based on disks available
CONTROLLER0_LOCAL_STORAGE="local_image|/dev/disk/by-path/pci-0000:00:0d.0-ata-3.0|fixed|5"
## Kubernetes
K8S_ENABLED="yes"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
[LOGICAL_INTERFACE_2]
LAG_INTERFACE=N
INTERFACE_MTU=1500
INTERFACE_PORTS=enp0s3
[OAM_NETWORK]
IP_ADDRESS = 10.10.10.2
CIDR=10.10.10.0/24
GATEWAY=10.10.10.1
LOGICAL_INTERFACE=LOGICAL_INTERFACE_2
[AUTHENTICATION]
ADMIN_PASSWORD=Li69nux*
[VERSION]
RELEASE = 19.01
[SYSTEM]
SYSTEM_MODE=simplex

View File

@ -0,0 +1,75 @@
[General]
controllers=2
workers=2
storage=0
aio=False
deletevms=False
useexistinglab=True
securityprofile=standard
lowlatency=False
install_mode=graphical
[PhysicalTopology]
[ControllerCEPH]
memory=8192,
cpus=2,
disks=[80000]
[ControllerLVM]
memory=8192
cpus=2
disks=[100000, 10000]
[ControllerAIO]
memory=12288,
cpus=2
disks=[24000, 40000],
[Compute]
memory=4096
cpus=3
disks=[50000, 30000]
[Storage]
memory=3072
cpus=1
disks=[50000, 10000],
[NetworkTopology]
[Controller]
1={'nic': 'hostonly', 'intnet': 'none', 'nictype': '82540EM', 'nicpromisc': 'deny', 'hostonlyadapter': 'vboxnet0'}
2={'nic': 'intnet', 'intnet': 'intnet-management', 'nictype': '82540EM', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'}
3={'nic': 'intnet', 'intnet': 'intnet-infra', 'nictype': '82540EM', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'}
[Compute]
1={'nic': 'intnet', 'intnet': 'intnet-unused', 'nictype': '82540EM', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'}
2={'nic': 'intnet', 'intnet': 'intnet-management', 'nictype': '82540EM', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'}
3={'nic': 'intnet', 'intnet': 'intnet-data1', 'nictype': 'virtio', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'}
4={'nic': 'intnet', 'intnet': 'intnet-data2', 'nictype': 'virtio', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'}
[Storage]
1={'nic': 'internal', 'intnet': 'intnet-unused', 'nictype': '82540EM', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'}
2={'nic': 'internal', 'intnet': 'intnet-management', 'nictype': '82540EM', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'}
3={'nic': 'internal', 'intnet': 'intnet-infra', 'nictype': '82540EM', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'}
[OAMHostOnlyNetwork]
ip=10.10.10.254
netmask=255.255.255.0
[Lab]
name=vbox
floating_ip=10.10.10.7
controller-0_ip=10.10.10.8
controller-1_ip=10.10.10.9
username=wrsroot
password=Li69nux*
[Serial]
uartbase=0x3F8
uartport=4
uartmode=server
uartpath=/tmp
[ISO]
isohost=localhost
isopath=/tmp/bootimage.iso

View File

@ -0,0 +1,27 @@
#!/usr/bin/python3
#
# SPDX-License-Identifier: Apache-2.0
#
import getpass
from sys import platform
import os
user = getpass.getuser()
if platform == 'win32' or platform == 'win64':
LOGPATH = 'C:\\Temp\\pybox_logs'
PORT = 10000
else:
homedir = os.environ['HOME']
LOGPATH = '{}/vbox_installer_logs'.format(homedir)
class Lab:
VBOX = {
'floating_ip': '10.10.10.7',
'controller-0_ip': '10.10.10.8',
'controller-1_ip': '10.10.10.9',
'username': 'wrsroot',
'password': 'Li69nux*',
}

View File

@ -0,0 +1,78 @@
#!/usr/bin/python3
#
# SPDX-License-Identifier: Apache-2.0
#
from sys import platform
class Subnets:
IPV4 = {
'mgmt_subnet': '192.168.204.0/24',
'infra_subnet': '192.168.205.0/24',
'oam_subnet': '10.10.10.0/24'
}
IPV6 = {
'mgmt_subnet': 'aefd::/64',
'infra_subnet': 'aced::/64',
'oam_subnet': 'abcd::/64'
}
class NICs:
if platform == 'win32' or platform == 'win64':
CONTROLLER = {
'node_type': 'controller',
'1': {'nic': 'hostonly', 'intnet': '', 'nictype': '82540EM', 'nicpromisc': 'deny', 'hostonlyadapter': 'VirtualBox Host-Only Ethernet Adapter'},
'2': {'nic': 'intnet', 'intnet': 'intnet-management', 'nictype': '82540EM', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'},
'3': {'nic': 'intnet', 'intnet': 'intnet-data1', 'nictype': 'virtio', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'},
'4': {'nic': 'intnet', 'intnet': 'intnet-data2', 'nictype': 'virtio', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'},
}
else:
CONTROLLER = {
'node_type': 'controller',
'1': {'nic': 'hostonly', 'intnet': '', 'nictype': '82540EM', 'nicpromisc': 'deny', 'hostonlyadapter': 'vboxnet0'},
'2': {'nic': 'intnet', 'intnet': 'intnet-management', 'nictype': '82540EM', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'},
'3': {'nic': 'intnet', 'intnet': 'intnet-data1', 'nictype': 'virtio', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'},
'4': {'nic': 'intnet', 'intnet': 'intnet-data2', 'nictype': 'virtio', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'},
}
COMPUTE = {
'node_type': 'compute',
'1': {'nic': 'intnet', 'intnet': 'intnet-unused1', 'nictype': '82540EM', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'},
'2': {'nic': 'intnet', 'intnet': 'intnet-management', 'nictype': '82540EM', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'},
'3': {'nic': 'intnet', 'intnet': 'intnet-data1', 'nictype': 'virtio', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'},
'4': {'nic': 'intnet', 'intnet': 'intnet-data2', 'nictype': 'virtio', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'},
}
STORAGE = {
'node_type': 'storage',
'1': {'nic': 'intnet', 'intnet': 'intnet-unused', 'nictype': '82540EM', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'},
'2': {'nic': 'intnet', 'intnet': 'intnet-management', 'nictype': '82540EM', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'},
'3': {'nic': 'intnet', 'intnet': 'intnet-infra', 'nictype': '82540EM', 'nicpromisc': 'allow-all', 'hostonlyadapter': 'none'},
}
class OAM:
OAM = {
'ip': '10.10.10.254',
'netmask': '255.255.255.0',
}
class Serial:
if platform == 'win32' or platform == 'win64':
SERIAL = {
'uartbase': '0x3F8',
'uartport': '4',
'uartmode': 'tcpserver',
'uartpath': '10000'
}
else:
SERIAL = {
'uartbase': '0x3F8',
'uartport': '4',
'uartmode': 'server',
'uartpath': '/tmp/'
}

View File

@ -0,0 +1,79 @@
#!/usr/bin/python3
#
# SPDX-License-Identifier: Apache-2.0
#
class Nodes:
CONTROLLER_CEPH = {
'node_type': 'controller-STORAGE',
'memory': 12288,
'cpus': 5,
'disks': {
1:[240000],
2:[240000, 10000],
3:[240000, 10000, 10000],
4:[240000, 10000, 10000, 10000],
5:[240000, 10000, 10000, 10000, 10000],
6:[240000, 10000, 10000, 10000, 10000, 10000],
7:[240000, 10000, 10000, 10000, 10000, 10000, 10000]
}
}
CONTROLLER_LVM = {
'node_type': 'controller-STANDARD',
'memory': 12288,
'cpus': 5,
'disks': {
1:[240000],
2:[240000, 10000],
3:[240000, 10000, 10000],
4:[240000, 10000, 10000, 10000],
5:[240000, 10000, 10000, 10000, 10000],
6:[240000, 10000, 10000, 10000, 10000, 10000],
7:[240000, 10000, 10000, 10000, 10000, 10000, 10000]
}
}
CONTROLLER_AIO = {
'node_type': 'controller-AIO',
'memory': 20000,
'cpus': 8,
'disks': {
1:[240000],
2:[240000, 10000],
3:[240000, 10000, 10000],
4:[240000, 10000, 10000, 10000],
5:[240000, 10000, 10000, 10000, 10000],
6:[240000, 10000, 10000, 10000, 10000, 10000],
7:[240000, 10000, 10000, 10000, 10000, 10000, 10000]
}
}
COMPUTE = {
'node_type': 'compute',
'memory': 4096,
'cpus': 3,
'disks': {
1:[50000],
2:[50000, 10000],
3:[50000, 10000, 10000],
4:[50000, 10000, 10000, 10000],
5:[50000, 10000, 10000, 10000, 10000],
6:[50000, 10000, 10000, 10000, 10000, 10000],
7:[50000, 10000, 10000, 10000, 10000, 10000, 10000]
}
}
STORAGE = {
'node_type': 'storage',
'memory': 3072,
'cpus': 3,
'disks': {
1:[50000],
2:[50000, 10000],
3:[50000, 10000, 10000],
4:[50000, 10000, 10000, 10000],
5:[50000, 10000, 10000, 10000, 10000]
}
}

View File

@ -0,0 +1,15 @@
#!/usr/bin/python3
#
# SPDX-License-Identifier: Apache-2.0
#
class HostTimeout:
CONTROLLER_UNLOCK = 3600+1800
REBOOT = 900
INSTALL = 3600
LAB_INSTALL = 3600
HOST_INSTALL = 3600
LAB_CONFIG = 5400
INSTALL_PATCHES = 900
NETWORKING_OPERATIONAL = 60

View File

@ -0,0 +1,160 @@
#!/usr/bin/python3
#
# SPDX-License-Identifier: Apache-2.0
#
import time
import streamexpect
from consts.timeout import HostTimeout
from utils import serial
from utils.install_log import LOG
def unlock_host(stream, hostname):
"""
Unlocks given host
Args:
stream(stream): Stream to active controller
hostname(str): Name of host to unlock
Steps:
- Check that host is locked
- Unlock host
"""
LOG.info("#### Unlock %s", hostname)
serial.send_bytes(stream, "system host-list | grep {}".format(hostname), expect_prompt=False)
try:
serial.expect_bytes(stream, "locked")
except streamexpect.ExpectTimeout:
LOG.info("Host %s not locked", hostname)
return 1
serial.send_bytes(stream, "system host-unlock {}".format(hostname), expect_prompt=False)
LOG.info("Unlocking %s", hostname)
def lock_host(stream, hostname):
"""
Locks the specified host.
Args:
stream(stream): Stream to controller-0
hostname(str): Name of host to lock
Steps:
- Check that host is unlocked
- Lock host
"""
LOG.info("Lock %s", hostname)
serial.send_bytes(stream, "system host-list |grep {}".format(hostname), expect_prompt=False)
try:
serial.expect_bytes(stream, "unlocked")
except streamexpect.ExpectTimeout:
LOG.info("Host %s not unlocked", hostname)
return 1
serial.send_bytes(stream, "system host-lock {}".format(hostname), expect_prompt="keystone")
LOG.info("Locking %s", hostname)
def reboot_host(stream, hostname):
"""
Reboots host specified
Args:
stream():
hostname(str): Host to reboot
"""
LOG.info("Rebooting %s", hostname)
serial.send_bytes(stream, "system host-reboot {}".format(hostname), expect_prompt=False)
serial.expect_bytes(stream, "rebooting", HostTimeout.REBOOT)
def install_host(stream, hostname, host_type, host_id):
"""
Initiates install of specified host. Requires controller-0 to be installed already.
Args:
stream(stream): Stream to cont0
hostname(str): Name of host
host_type(str): Type of host being installed e.g. 'storage' or 'compute'
host_id(int): id to identify host
"""
time.sleep(10)
LOG.info("Installing %s with id %s", hostname, host_id)
if host_type is 'controller':
serial.send_bytes(stream,
"system host-update {} personality=controller".format(host_id),
expect_prompt=False)
elif host_type is 'storage':
serial.send_bytes(stream,
"system host-update {} personality=storage".format(host_id),
expect_prompt=False)
else:
serial.send_bytes(stream,
"system host-update {} personality=compute hostname={}".format(host_id,
hostname),
expect_prompt=False)
time.sleep(30)
def disable_logout(stream):
"""
Disables automatic logout of users.
Args:
stream(stream): stream to cont0
"""
LOG.info('Disabling automatic logout')
serial.send_bytes(stream, "export TMOUT=0")
def change_password(stream, username="wrsroot", password="Li69nux*"):
"""
changes the default password on initial login.
Args:
stream(stream): stream to cont0
"""
LOG.info('Changing password to Li69nux*')
serial.send_bytes(stream, username, expect_prompt=False)
serial.expect_bytes(stream, "Password:")
serial.send_bytes(stream, username, expect_prompt=False)
serial.expect_bytes(stream, "UNIX password:")
serial.send_bytes(stream, username, expect_prompt=False)
serial.expect_bytes(stream, "New password:")
serial.send_bytes(stream, password, expect_prompt=False)
serial.expect_bytes(stream, "Retype new")
serial.send_bytes(stream, password)
def login(stream, timeout=600, username="wrsroot", password="Li69nux*"):
"""
Logs into controller-0.
Args:
stream(stream): stream to cont0
timeout(int): Time before login fails in seconds.
"""
serial.send_bytes(stream, "\n", expect_prompt=False)
rc = serial.expect_bytes(stream, "ogin:", fail_ok=True, timeout=timeout)
if rc != 0:
serial.send_bytes(stream, "\n", expect_prompt=False)
if serial.expect_bytes(stream, "~$", timeout=10, fail_ok=True) == -1:
serial.send_bytes(stream, '\n', expect_prompt=False)
serial.expect_bytes(stream, "keystone", timeout=10)
else:
serial.send_bytes(stream, username, expect_prompt=False)
serial.expect_bytes(stream, "assword:")
serial.send_bytes(stream, password)
disable_logout(stream)
def logout(stream):
"""
Logs out of controller-0.
Args:
stream(stream): stream to cont0
"""
serial.send_bytes(stream, "exit", expect_prompt=False)
time.sleep(5)
def check_password(stream, password="Li69nux*"):
ret = serial.expect_bytes(stream, 'assword', fail_ok=True, timeout=5)
if ret == 0:
serial.send_bytes(stream, password, expect_prompt=False)

View File

@ -0,0 +1,53 @@
#!/usr/bin/python3
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Contains helper functions that will configure basic system settings.
"""
from consts.timeout import HostTimeout
from helper import host_helper
from utils import serial
from utils.install_log import LOG
def update_platform_cpus(stream, hostname, cpu_num=5):
"""
Update platform CPU allocation.
"""
LOG.info("Allocating %s CPUs for use by the %s platform.", cpu_num, hostname)
serial.send_bytes(stream, "\nsource /etc/platform/openrc; system host-cpu-modify "
"{} -f platform -p0 {}".format(hostname, cpu_num,
prompt='keystone', timeout=300))
def set_dns(stream, dns_ip):
"""
Perform DNS configuration on the system.
"""
LOG.info("Configuring DNS to %s.", dns_ip)
serial.send_bytes(stream, "source /etc/nova/openrc; system dns-modify "
"nameservers={}".format(dns_ip), prompt='keystone')
def config_controller(stream, config_file=None, password='Li69nux*'):
"""
Configure controller-0 using optional arguments
"""
args = ''
if config_file:
args += '--config-file ' + config_file + ' '
serial.send_bytes(stream, "sudo config_controller {}".format(args), expect_prompt=False)
host_helper.check_password(stream, password=password)
ret = serial.expect_bytes(stream, "unlock controller to proceed.",
timeout=HostTimeout.LAB_CONFIG)
if ret != 0:
LOG.info("Configuration failed. Exiting installer.")
raise Exception("Configcontroller failed")

View File

@ -0,0 +1,490 @@
#!/usr/bin/python3
#
# SPDX-License-Identifier: Apache-2.0
#
import os
import subprocess
import re
import getpass
import time
from sys import platform
from consts import env
from utils.install_log import LOG
def vboxmanage_version():
"""
Return version of vbox.
"""
version = subprocess.check_output(['vboxmanage', '--version'], stderr=subprocess.STDOUT)
return version
def vboxmanage_extpack(action="install"):
"""
This allows you to install, uninstall the vbox extensions"
"""
output = vboxmanage_version()
version = re.match(b'(.*)r', output)
version_path = version.group(1).decode('utf-8')
LOG.info("Downloading extension pack")
filename = 'Oracle_VM_VirtualBox_Extension_Pack-{}.vbox-extpack'.format(version_path)
cmd = 'http://download.virtualbox.org/virtualbox/{}/{}'.format(version_path, filename)
result = subprocess.check_output(['wget', cmd, '-P', '/tmp'], stderr=subprocess.STDOUT)
LOG.info(result)
LOG.info("Installing extension pack")
result = subprocess.check_output(['vboxmanage', 'extpack', 'install', '/tmp/' + filename,
'--replace'], stderr=subprocess.STDOUT)
LOG.info(result)
def get_all_vms(labname, option="vms"):
initial_node_list = []
vm_list = vboxmanage_list(option)
labname.encode('utf-8')
# Reduce the number of VMs we query
for item in vm_list:
if labname.encode('utf-8') in item and (b'controller-' in item or \
b'compute-' in item or b'storage-' in item):
initial_node_list.append(item.decode('utf-8'))
# Filter by group
node_list = []
group = bytearray('"/{}"'.format(labname), 'utf-8')
for item in initial_node_list:
info = vboxmanage_showinfo(item).splitlines()
for line in info:
try:
k, v = line.split(b'=')
except ValueError:
continue
if k == b'groups' and v == group:
node_list.append(item)
return node_list
def take_snapshot(labname, snapshot_name, socks=None):
vms = get_all_vms(labname, option="vms")
runningvms = get_all_vms(labname, option="runningvms")
LOG.info("#### Taking snapshot %s of lab %s", snapshot_name, labname)
LOG.info("VMs in lab %s: %s", labname, vms)
LOG.info("VMs running in lab %s: %s", labname, runningvms)
hosts = len(vms)
# Pause running VMs to take snapshot
if len(runningvms) > 1:
for node in runningvms:
newpid = os.fork()
if newpid == 0:
vboxmanage_controlvms([node], "pause")
os._exit(0)
for node in vms:
os.waitpid(0, 0)
time.sleep(2)
if hosts != 0:
vboxmanage_takesnapshot(vms, snapshot_name)
# Resume VMs after snapshot was taken
if len(runningvms) > 1:
for node in runningvms:
newpid = os.fork()
if newpid == 0:
vboxmanage_controlvms([node], "resume")
os._exit(0)
for node in runningvms:
os.waitpid(0, 0)
time.sleep(10) # Wait for VM serial port to stabilize, otherwise it may refuse to connect
if runningvms:
new_vms = get_all_vms(labname, option="runningvms")
retry = 0
while retry < 20:
LOG.info("Waiting for VMs to come up running after taking snapshot..."
"Up VMs are %s ", new_vms)
if len(runningvms) < len(new_vms):
time.sleep(1)
new_vms = get_all_vms(labname, option="runningvms")
retry += 1
else:
LOG.info("All VMs %s are up running after taking snapshot...", vms)
break
def restore_snapshot(node_list, name):
LOG.info("Restore snapshot of %s for hosts %s", name, node_list)
if len(node_list) != 0:
vboxmanage_controlvms(node_list, "poweroff")
time.sleep(5)
if len(node_list) != 0:
for host in node_list:
vboxmanage_restoresnapshot(host, name)
time.sleep(5)
for host in node_list:
if "controller-0" not in host:
vboxmanage_startvm(host)
time.sleep(10)
for host in node_list:
if "controller-0" in host:
vboxmanage_startvm(host)
time.sleep(10)
def vboxmanage_list(option="vms"):
"""
This returns a list of vm names.
"""
result = subprocess.check_output(['vboxmanage', 'list', option], stderr=subprocess.STDOUT)
vms_list = []
for item in result.splitlines():
vm_name = re.match(b'"(.*?)"', item)
vms_list.append(vm_name.group(1))
return vms_list
def vboxmanage_showinfo(host):
"""
This returns info about the host
"""
if not isinstance(host, str):
host.decode('utf-8')
result = subprocess.check_output(['vboxmanage', 'showvminfo', host, '--machinereadable'],
stderr=subprocess.STDOUT)
return result
def vboxmanage_createvm(hostname, labname):
"""
This creates a VM with the specified name.
"""
assert hostname, "Hostname is required"
assert labname, "Labname is required"
group = "/" + labname
LOG.info("Creating VM %s", hostname)
result = subprocess.check_output(['vboxmanage', 'createvm', '--name', hostname, '--register',
'--ostype', 'Linux_64', '--groups', group],
stderr=subprocess.STDOUT)
def vboxmanage_deletevms(hosts=None):
"""
Deletes a list of VMs
"""
assert hosts, "A list of hostname(s) is required"
if len(hosts) != 0:
for hostname in hosts:
LOG.info("Deleting VM %s", hostname)
result = subprocess.check_output(['vboxmanage', 'unregistervm', hostname, '--delete'],
stderr=subprocess.STDOUT)
time.sleep(10)
# in case medium is still present after delete
vboxmanage_deletemedium(hostname)
vms_list = vboxmanage_list("vms")
for items in hosts:
assert items not in vms_list, "The following vms are unexpectedly" \
"present {}".format(vms_list)
def vboxmanage_hostonlyifcreate(name="vboxnet0", ip=None, netmask=None):
"""
This creates a hostonly network for systems to communicate.
"""
assert name, "Must provide network name"
assert ip, "Must provide an OAM IP"
assert netmask, "Must provide an OAM Netmask"
LOG.info("Creating Host-only Network")
result = subprocess.check_output(['vboxmanage', 'hostonlyif', 'create'],
stderr=subprocess.STDOUT)
LOG.info("Provisioning %s with IP %s and Netmask %s", name, ip, netmask)
result = subprocess.check_output(['vboxmanage', 'hostonlyif', 'ipconfig', name, '--ip',
ip, '--netmask', netmask], stderr=subprocess.STDOUT)
def vboxmanage_hostonlyifdelete(name="vboxnet0"):
"""
Deletes hostonly network. This is used as a work around for creating too many hostonlyifs.
"""
assert name, "Must provide network name"
LOG.info("Removing Host-only Network")
result = subprocess.check_output(['vboxmanage', 'hostonlyif', 'remove', name],
stderr=subprocess.STDOUT)
def vboxmanage_modifyvm(hostname=None, cpus=None, memory=None, nic=None,
nictype=None, nicpromisc=None, nicnum=None,
intnet=None, hostonlyadapter=None,
natnetwork=None, uartbase=None, uartport=None,
uartmode=None, uartpath=None, nicbootprio2=1, prefix=""):
"""
This modifies a VM with a specified name.
"""
assert hostname, "Hostname is required"
# Add more semantic checks
cmd = ['vboxmanage', 'modifyvm', hostname]
if cpus:
cmd.extend(['--cpus', cpus])
if memory:
cmd.extend(['--memory', memory])
if nic and nictype and nicpromisc and nicnum:
cmd.extend(['--nic{}'.format(nicnum), nic])
cmd.extend(['--nictype{}'.format(nicnum), nictype])
cmd.extend(['--nicpromisc{}'.format(nicnum), nicpromisc])
if intnet:
if prefix:
intnet = "{}-{}".format(prefix, intnet)
else:
intnet = "{}".format(intnet)
cmd.extend(['--intnet{}'.format(nicnum), intnet])
if hostonlyadapter:
cmd.extend(['--hostonlyadapter{}'.format(nicnum), hostonlyadapter])
if natnetwork:
cmd.extend(['--nat-network{}'.format(nicnum), natnetwork])
elif nicnum and nictype == 'nat':
cmd.extend(['--nic{}'.format(nicnum), 'nat'])
if uartbase and uartport and uartmode and uartpath:
cmd.extend(['--uart1'])
cmd.extend(['{}'.format(uartbase)])
cmd.extend(['{}'.format(uartport)])
cmd.extend(['--uartmode1'])
cmd.extend(['{}'.format(uartmode)])
if platform == 'win32' or platform == 'win64':
cmd.extend(['{}'.format(env.PORT)])
env.PORT += 1
else:
if prefix:
prefix = "{}_".format(prefix)
if 'controller-0' in hostname:
cmd.extend(['{}{}{}_serial'.format(uartpath, prefix, hostname)])
else:
cmd.extend(['{}{}{}'.format(uartpath, prefix, hostname)])
if nicbootprio2:
cmd.extend(['--nicbootprio2'])
cmd.extend(['{}'.format(nicbootprio2)])
cmd.extend(['--boot4'])
cmd.extend(['net'])
LOG.info(cmd)
LOG.info("Updating VM %s configuration", hostname)
result = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
def vboxmanage_port_forward(hostname, network, local_port, guest_port, guest_ip):
# VBoxManage natnetwork modify --netname natnet1 --port-forward-4
# "ssh:tcp:[]:1022:[192.168.15.5]:22"
rule_name = "{}-{}".format(hostname, guest_port)
# Delete previous entry, if any
LOG.info("Removing previous forwarding rule '%s' from NAT network '%s'", rule_name, network)
cmd = ['vboxmanage', 'natnetwork', 'modify', '--netname', network,
'--port-forward-4', 'delete', rule_name]
try:
result = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError:
pass
# Add new rule
rule = "{}:tcp:[]:{}:[{}]:{}".format(rule_name, local_port, guest_ip, guest_port)
LOG.info("Updating port-forwarding rule to: %s", rule)
cmd = ['vboxmanage', 'natnetwork', 'modify', '--netname', network, '--port-forward-4', rule]
result = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
def vboxmanage_storagectl(hostname=None, storectl="sata", hostiocache="off"):
"""
This creates a storage controller on the host.
"""
assert hostname, "Hostname is required"
assert storectl, "Type of storage controller is required"
LOG.info("Creating %s storage controller on VM %s", storectl, hostname)
result = subprocess.check_output(['vboxmanage', 'storagectl',
hostname, '--name', storectl,
'--add', storectl, '--hostiocache',
hostiocache], stderr=subprocess.STDOUT)
def vboxmanage_storageattach(hostname=None, storectl="sata",
storetype="hdd", disk=None, port_num="0", device_num="0"):
"""
This attaches a disk to a controller.
"""
assert hostname, "Hostname is required"
assert disk, "Disk name is required"
assert storectl, "Name of storage controller is required"
assert storetype, "Type of storage controller is required"
LOG.info("Attaching %s storage to storage controller %s on VM %s",
storetype, storectl, hostname)
result = subprocess.check_output(['vboxmanage', 'storageattach',
hostname, '--storagectl', storectl,
'--medium', disk, '--type',
storetype, '--port', port_num,
'--device', device_num], stderr=subprocess.STDOUT)
return result
def vboxmanage_deletemedium(hostname, vbox_home_dir='/home'):
assert hostname, "Hostname is required"
if platform == 'win32' or platform == 'win64':
return
username = getpass.getuser()
vbox_home_dir = "{}/{}/vbox_disks/".format(vbox_home_dir, username)
disk_list = [f for f in os.listdir(vbox_home_dir) if
os.path.isfile(os.path.join(vbox_home_dir, f)) and hostname in f]
LOG.info("Disk mediums to delete: %s", disk_list)
for disk in disk_list:
LOG.info("Disconnecting disk %s from vbox.", disk)
try:
result = subprocess.check_output(['vboxmanage', 'closemedium', 'disk',
"{}{}".format(vbox_home_dir, disk), '--delete'],
stderr=subprocess.STDOUT)
LOG.info(result)
except subprocess.CalledProcessError as e:
# Continue if failures, disk may not be present
LOG.info("Error disconnecting disk, continuing. "
"Details: stdout: %s stderr: %s", e.stdout, e.stderr)
LOG.info("Removing backing file %s", disk)
try:
os.remove("{}{}".format(vbox_home_dir, disk))
except:
pass
def vboxmanage_createmedium(hostname=None, disk_list=None, vbox_home_dir='/home'):
"""
This creates the required disks.
"""
assert hostname, "Hostname is required"
assert disk_list, "A list of disk sizes is required"
username = getpass.getuser()
device_num = 0
port_num = 0
disk_count = 1
for disk in disk_list:
if platform == 'win32' or platform == 'win64':
file_name = "C:\\Users\\" + username + "\\vbox_disks\\" + \
hostname + "_disk_{}".format(disk_count)
else:
file_name = vbox_home_dir + '/' + username + "/vbox_disks/" \
+ hostname + "_disk_{}".format(disk_count)
LOG.info("Creating disk %s of size %s on VM %s on device %s port %s",
file_name, disk, hostname, device_num, port_num)
try:
result = subprocess.check_output(['vboxmanage', 'createmedium',
'disk', '--size', str(disk),
'--filename', file_name,
'--format', 'vdi',
'--variant', 'standard'],
stderr=subprocess.STDOUT)
LOG.info(result)
except subprocess.CalledProcessError as e:
LOG.info("Error stdout: %s stderr: %s", e.stdout, e.stderr)
raise
vboxmanage_storageattach(hostname, "sata", "hdd", file_name + \
".vdi", str(port_num), str(device_num))
disk_count += 1
port_num += 1
time.sleep(5)
def vboxmanage_startvm(hostname=None, force=False):
"""
This allows you to power on a VM.
"""
assert hostname, "Hostname is required"
if not force:
LOG.info("Check if VM is running")
running_vms = vboxmanage_list(option="runningvms")
else:
running_vms = []
if hostname.encode('utf-8') in running_vms:
LOG.info("Host %s is already started", hostname)
else:
LOG.info("Powering on VM %s", hostname)
result = subprocess.check_output(['vboxmanage', 'startvm',
hostname], stderr=subprocess.STDOUT)
LOG.info(result)
# Wait for VM to start
tmout = 20
while tmout:
tmout -= 1
running_vms = vboxmanage_list(option="runningvms")
if hostname.encode('utf-8') in running_vms:
break
time.sleep(1)
else:
raise "Failed to start VM: {}".format(hostname)
LOG.info("VM '%s' started.", hostname)
def vboxmanage_controlvms(hosts=None, action=None):
"""
This allows you to control a VM, e.g. pause, resume, etc.
"""
assert hosts, "Hostname is required"
assert action, "Need to provide an action to execute"
for host in hosts:
LOG.info("Executing %s action on VM %s", action, host)
result = subprocess.call(["vboxmanage", "controlvm", host,
action], stderr=subprocess.STDOUT)
time.sleep(1)
def vboxmanage_takesnapshot(hosts=None, name=None):
"""
This allows you to take snapshot of VMs.
"""
assert hosts, "Hostname is required"
assert name, "Need to provide a name for the snapshot"
for host in hosts:
LOG.info("Taking snapshot %s on VM %s", name, host)
result = subprocess.call(["vboxmanage", "snapshot", host, "take",
name], stderr=subprocess.STDOUT)
def vboxmanage_restoresnapshot(host=None, name=None):
"""
This allows you to restore snapshot of a VM.
"""
assert host, "Hostname is required"
assert name, "Need to provide the snapshot to restore"
LOG.info("Restoring snapshot %s on VM %s", name, host)
result = subprocess.call(["vboxmanage", "snapshot", host, "restore",
name], stderr=subprocess.STDOUT)
time.sleep(10)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
# Install rsync, sshpass
#
configparser
paramiko
pytest
streamexpect

View File

@ -0,0 +1,52 @@
#!/usr/bin/python3
#
# SPDX-License-Identifier: Apache-2.0
#
import os
import datetime
import logging
from consts.env import LOGPATH
log_dir = ""
LOG = logging.getLogger()
def init_logging(lab_name, log_path=None):
global LOG, log_dir
if not log_path:
log_path = LOGPATH
lab_log_path = log_path + "/" + lab_name
# Setup log sub-directory for current run
current_time = datetime.datetime.now()
log_dir = "{}/{}_{}_{}_{}_{}_{}".format(lab_log_path,
current_time.year,
current_time.month,
current_time.day,
current_time.hour,
current_time.minute,
current_time.second)
if not os.path.exists(log_dir):
os.makedirs(log_dir)
LOG.setLevel(logging.INFO)
formatter = logging.Formatter("%(asctime)s: %(message)s")
log_file = "{}/install.log".format(log_dir)
handler = logging.FileHandler(log_file)
handler.setFormatter(formatter)
handler.setLevel(logging.INFO)
LOG.addHandler(handler)
handler = logging.StreamHandler()
handler.setFormatter(formatter)
LOG.addHandler(handler)
# Create symbolic link to latest logs of this lab
try:
os.unlink(lab_log_path + "/latest")
except:
pass
os.symlink(log_dir, lab_log_path + "/latest")
def get_log_dir():
return log_dir

View File

@ -0,0 +1,68 @@
#!/usr/bin/python3
#
# SPDX-License-Identifier: Apache-2.0
#
import time
from utils.install_log import LOG
STAGES = []
METRICS = {}
start = 0
def init_kpi_metrics():
global start
start = time.time()
def get_formated_time(sec):
hours = sec // 3600
sec %= 3600
minutes = sec // 60
sec %= 60
seconds = sec
if hours:
return "{:.0f}h {:.0f}m {:.2f}s".format(hours, minutes, seconds)
elif minutes:
return "{:.0f}m {:.2f}s".format(minutes, seconds)
elif seconds:
return "{:.2f}s".format(seconds)
def set_kpi_metric(metric, duration):
global METRICS, STAGES
METRICS[metric] = duration
STAGES.append(metric)
def print_kpi(metric):
if metric in STAGES:
sec = METRICS[metric]
LOG.info(" Time in stage '%s': %s ", metric, get_formated_time(sec))
elif metric == 'total' and start:
duration = time.time() - start
LOG.info(" Total time: %s", get_formated_time(duration))
def get_kpi_str(metric):
msg = ""
if metric in STAGES:
sec = METRICS[metric]
msg += (" Time in stage '{}': {} \n".format(metric, get_formated_time(sec)))
elif metric == 'total' and start:
duration = time.time() - start
msg += (" Total time: {}\n".format(get_formated_time(duration)))
return msg
def get_kpi_metrics_str():
msg = "===================== Metrics ====================\n"
for stage in STAGES:
msg += get_kpi_str(stage)
msg += get_kpi_str('total')
msg += "===============================================\n"
return msg
def print_kpi_metrics():
LOG.info("===================== Metrics ====================")
for stage in STAGES:
print_kpi(stage)
print_kpi('total')
LOG.info("==================================================")

View File

@ -0,0 +1,220 @@
#!/usr/bin/python3
#
# SPDX-License-Identifier: Apache-2.0
#
import re
import socket
from sys import platform, stdout
import time
import streamexpect
from utils.install_log import LOG
def connect(hostname, port=10000, prefix=""):
"""
Connect to local domain socket and return the socket object.
Arguments:
- Requires the hostname of target, e.g. controller-0
- Requires TCP port if using Windows
"""
if prefix:
prefix = "{}_".format(prefix)
socketname = "/tmp/{}{}".format(prefix, hostname)
if 'controller-0'in hostname:
socketname += '_serial'
LOG.info("Connecting to %s at %s", hostname, socketname)
if platform == 'win32' or platform == 'win64':
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
else:
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
if platform == 'win32' or platform == 'win64':
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
sock.connect(('localhost', port))
else:
sock.connect(socketname)
except:
LOG.info("Connection failed")
pass
# disconnect(sock)
sock = None
# TODO (WEI): double check this
sock.setblocking(0)
return sock
def disconnect(sock):
"""
Disconnect a local doamin socket.
Arguments:
- Requires socket
"""
# Shutdown connection and release resources
LOG.info("Disconnecting from socket")
sock.shutdown(socket.SHUT_RDWR)
sock.close()
def get_output(stream, cmd, prompts=None, timeout=5, log=True, as_lines=True, flush=True):
#TODO: Not testested, will not work if kernel or other processes throw data on stdout or stderr
"""
Execute a command and get its output. Make sure no other command is executing.
And 'dmesg -D' was executed.
"""
POLL_PERIOD = 0.1
MAX_READ_BUFFER = 1024
data = ""
line_buf = ""
lines = []
if not prompts:
prompts = [':~$ ', ':~# ', ':/home/wrsroot# ', '(keystone_.*)]$ ', '(keystone_.*)]# ']
# Flush buffers
if flush:
try:
trash = stream.poll(1) # flush input buffers
if trash:
try:
LOG.info("Buffer has bytes before cmd execution: %s",
trash.decode('utf-8'))
except Exception:
pass
except streamexpect.ExpectTimeout:
pass
# Send command
stream.sendall("{}\n".format(cmd).encode('utf-8'))
# Get response
patterns = []
for prompt in prompts:
patterns.append(re.compile(prompt))
now = time.time()
end_time = now + float(timeout)
prev_timeout = stream.gettimeout()
stream.settimeout(POLL_PERIOD)
incoming = None
try:
while (end_time - now) >= 0:
try:
incoming = stream.recv(MAX_READ_BUFFER)
except socket.timeout:
pass
if incoming:
data += incoming
if log:
for c in incoming:
if c != '\n':
line_buf += c
else:
LOG.info(line_buf)
lines.append(line_buf)
line_buf = ""
for pattern in patterns:
if pattern.search(data):
if as_lines:
return lines
else:
return data
now = time.time()
raise streamexpect.ExpectTimeout()
finally:
stream.settimeout(prev_timeout)
def expect_bytes(stream, text, timeout=180, fail_ok=False, flush=True):
"""
Wait for user specified text from stream.
"""
time.sleep(1)
if timeout < 60:
LOG.info("Expecting text within %s seconds: %s\n", timeout, text)
else:
LOG.info("Expecting text within %s minutes: %s\n", timeout/60, text)
try:
stream.expect_bytes("{}".format(text).encode('utf-8'), timeout=timeout)
except streamexpect.ExpectTimeout:
if fail_ok:
return -1
else:
stdout.write('\n')
LOG.error("Did not find expected text")
# disconnect(stream)
raise
except Exception as e:
LOG.info("Connection failed with %s", e)
raise
stdout.write('\n')
LOG.info("Found expected text: %s", text)
time.sleep(1)
if flush:
try:
incoming = stream.poll(1) # flush input buffers
if incoming:
incoming += b'\n'
try:
LOG.info(">>> expect_bytes: Buffer has bytes!")
stdout.write(incoming.decode('utf-8')) # streamexpect hardcodes it
except Exception:
pass
except streamexpect.ExpectTimeout:
pass
return 0
def send_bytes(stream, text, fail_ok=False, expect_prompt=True,
prompt=None, timeout=180, send=True, flush=True):
"""
Send user specified text to stream.
"""
time.sleep(1)
if flush:
try:
incoming = stream.poll(1) # flush input buffers
if incoming:
incoming += b'\n'
try:
LOG.info(">>> send_bytes: Buffer has bytes!")
stdout.write(incoming.decode('utf-8')) # streamexpect hardcodes it
except Exception:
pass
except streamexpect.ExpectTimeout:
pass
LOG.info("Sending text: %s", text)
try:
if send:
stream.sendall("{}\n".format(text).encode('utf-8'))
else:
stream.sendall("{}".format(text).encode('utf-8'))
if expect_prompt:
time.sleep(1)
if prompt:
return expect_bytes(stream, prompt, timeout=timeout, fail_ok=fail_ok)
else:
rc = expect_bytes(stream, "~$", timeout=timeout, fail_ok=True)
if rc != 0:
send_bytes(stream, '\n', expect_prompt=False)
expect_bytes(stream, 'keystone', timeout=timeout)
return
except streamexpect.ExpectTimeout:
if fail_ok:
return -1
else:
LOG.error("Failed to send text, logging out.")
stream.sendall("exit".encode('utf-8'))
raise
except Exception as e:
LOG.info("Connection failed with %s.", e)
raise
return 0

View File

@ -0,0 +1,123 @@
#!/usr/bin/python3
#
# SPDX-License-Identifier: Apache-2.0
#
import getpass
import os
import time
import subprocess
import paramiko
from utils.install_log import LOG
def sftp_send(source, remote_host, remote_port, destination, username, password):
"""
Send files to remote server
"""
LOG.info("Connecting to server %s with username %s", remote_host, username)
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
## TODO(WEI): need to make this timeout handling better
retry = 0
while retry < 8:
try:
ssh_client.connect(remote_host, port=remote_port,
username=username, password=password,
look_for_keys=False, allow_agent=False)
sftp_client = ssh_client.open_sftp()
retry = 8
except Exception as e:
LOG.info("******* try again")
retry += 1
time.sleep(10)
LOG.info("Sending file from %s to %s", source, destination)
sftp_client.put(source, destination)
LOG.info("Done")
sftp_client.close()
ssh_client.close()
def send_dir(source, remote_host, remote_port, destination, username,
password, follow_links=True, clear_known_hosts=True):
# Only works from linux for now
if not source.endswith('/') or not source.endswith('\\'):
source = source + '/'
params = {
'source': source,
'remote_host': remote_host,
'destination': destination,
'port': remote_port,
'username': username,
'password': password,
'follow_links': "L" if follow_links else "",
}
if clear_known_hosts:
if remote_host == '127.0.0.1':
keygen_arg = "[127.0.0.1]:{}".format(remote_port)
else:
keygen_arg = remote_host
cmd = 'ssh-keygen -f "/home/%s/.ssh/known_hosts" -R' \
' %s', getpass.getuser(), keygen_arg
LOG.info("CMD: %s", cmd)
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
for line in iter(process.stdout.readline, b''):
LOG.info("%s", line.decode("utf-8").strip())
process.wait()
LOG.info("Running rsync of dir: {source} ->" \
"{username}@{remote_host}:{destination}".format(**params))
cmd = ("rsync -av{follow_links} "
"--rsh=\"/usr/bin/sshpass -p {password} ssh -p {port} -o StrictHostKeyChecking=no -l {username}\" "
"{source}* {username}@{remote_host}:{destination}".format(**params))
LOG.info("CMD: %s", cmd)
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
for line in iter(process.stdout.readline, b''):
LOG.info("%s", line.decode("utf-8").strip())
process.wait()
if process.returncode:
raise Exception("Error in rsync, return code:{}".format(process.returncode))
def send_dir_fallback(source, remote_host, destination, username, password):
"""
Send directory contents to remote server, usually controller-0
Note: does not send nested directories only files.
args:
- source: full path to directory
e.g. /localhost/loadbuild/jenkins/latest_build/
- Remote host: name of host to log into, controller-0 by default
e.g. myhost.com
- destination: where to store the file on host: /home/myuser/
"""
LOG.info("Connecting to server %s with username %s", remote_host, username)
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(remote_host, username=username, password=password, look_for_keys=False, allow_agent=False)
sftp_client = ssh_client.open_sftp()
path = ''
send_img = False
for items in os.listdir(source):
path = source+items
if os.path.isfile(path):
if items.endswith('.img'):
remote_path = destination+'images/'+items
LOG.info("Sending file from %s to %s", path, remote_path)
sftp_client.put(path, remote_path)
send_img = True
elif items.endswith('.iso'):
pass
else:
remote_path = destination+items
LOG.info("Sending file from %s to %s", path, remote_path)
sftp_client.put(path, remote_path)
LOG.info("Done")
sftp_client.close()
ssh_client.close()
if send_img:
time.sleep(10)

View File

@ -0,0 +1,127 @@
#!/bin/bash
#
# SPDX-License-Identifier: Apache-2.0
#
GROUP=$1
ACTION=$2
SNAP_NAME=$3
if [ $# -lt 2 ]; then
echo "Usage: $0 <group_name> <action> [<snap_name>]"
echo "Available cmds:"
echo " Instance actions: pause|resume|poweroff|poweron"
echo " Snapshot actions: take|delete|restore"
echo ""
echo "###### Available groups: "
groups=$(vboxmanage list groups)
for grp in $groups; do
grp_txt=${grp:2:-1}
if [ ! -z "$grp_txt" ]; then
echo "$grp_txt"
fi
done
exit 0
fi
echo "###### Params:"
echo "Group name: $GROUP"
echo "Action: $ACTION"
if [ ! -z "$SNAP_NAME" ]; then
echo "Snapshot name: $SNAP_NAME"
fi
BASIC_INST_ACTIONS="pause poweroff"
SNAP_ACTIONS="take delete restore"
get_vms_by_group () {
local group=$1
vms=$(VBoxManage list -l vms |
awk -v group="/$group" \
'/^Name:/ { name = $2; } '`
'/^Groups:/ { groups = $2; } '`
'/^UUID:/ { uuid = $2; if (groups == group) print name, uuid; }')
echo "###### VMs in group:" >&2
echo "$vms" >&2
echo "$vms"
}
if [[ "$SNAP_ACTIONS" = *"$ACTION"* ]]; then
if [ $# -lt 3 ]; then
echo "###### ERROR:"
echo "Action '$ACTION' requires a snapshot name."
fi
vms=$(get_vms_by_group "$GROUP")
echo "#### Executing action on vms"
while read -r vm; do
vm=(${vm})
echo "Executing '$ACTION' on ${vm[0]}..."
VBoxManage snapshot ${vm[1]} "${ACTION}" "${SNAP_NAME}"
done <<< "$vms"
elif [[ "$BASIC_INST_ACTIONS" = *"$ACTION"* ]]; then
vms=$(get_vms_by_group "$GROUP")
echo "#### Executing action on vms"
while read -r vm; do
vm=(${vm})
echo "Executing '$ACTION' on '${vm[0]}'..."
VBoxManage controlvm ${vm[1]} "${ACTION}"
done <<< "$vms"
wait
elif [[ "$ACTION" = "resume" ]]; then
echo "resume"
vms=$(get_vms_by_group "$GROUP")
# Getting vm's in saved state
saved_vms=""
while read -r vm; do
vmA=(${vm})
state=$(vboxmanage showvminfo ${vmA[1]} --machinereadable |
grep "VMState=")
if [[ "$state" = *"saved"* ]]; then
if [ -z "$saved_vms" ]; then
saved_vms="$vm"
else
saved_vms=$(printf '%s\n%s' "$saved_vms" "$vm")
fi
fi
done <<< "$vms"
echo "#### VMs in saved state:"
echo "$saved_vms"
# Powering on each VM
echo "#### Preparing vms for start"
if [ ! -z "$saved_vms" ]; then
while read -r vm; do
vm=(${vm})
echo "Powering on VM \"${vm[1]}\"."
(VBoxHeadless --start-paused --startvm ${vm[1]} \
--vrde config >/dev/null 2>&1) &
sleep 1
while true; do
state=$(vboxmanage showvminfo ${vm[1]} --machinereadable |
grep "VMState=")
if [[ "$state" = *"paused"* ]]; then
break
fi
done
done <<< "$saved_vms"
fi
elif [[ "$ACTION" = "poweron" ]]; then
vms=$(get_vms_by_group "$GROUP")
echo "#### Powering on vms"
while read -r vm; do
vm=(${vm})
(vboxmanage startvm ${vm[1]} --type headless) &
done <<< "$vms"
wait
elif [[ "$ACTION" = "poweroff" ]]; then
echo "poweroff"
else
echo "###### ERROR:"
echo "ERROR: Action '$ACTION' not supported"
fi