Merge "Python-based installer for StarlingX"
This commit is contained in:
commit
14b5dbcd2f
398
deployment/virtualbox/pybox/Parser.py
Normal file
398
deployment/virtualbox/pybox/Parser.py
Normal 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
|
115
deployment/virtualbox/pybox/README.txt
Normal file
115
deployment/virtualbox/pybox/README.txt
Normal 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'
|
0
deployment/virtualbox/pybox/__init__.py
Normal file
0
deployment/virtualbox/pybox/__init__.py
Normal file
67
deployment/virtualbox/pybox/configs/aio-sx/lab_setup.conf
Normal file
67
deployment/virtualbox/pybox/configs/aio-sx/lab_setup.conf
Normal 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"
|
7132
deployment/virtualbox/pybox/configs/aio-sx/lab_setup.sh
Executable file
7132
deployment/virtualbox/pybox/configs/aio-sx/lab_setup.sh
Executable file
File diff suppressed because it is too large
Load Diff
@ -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
|
0
deployment/virtualbox/pybox/consts/__init__.py
Normal file
0
deployment/virtualbox/pybox/consts/__init__.py
Normal file
75
deployment/virtualbox/pybox/consts/config.ini
Normal file
75
deployment/virtualbox/pybox/consts/config.ini
Normal 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
|
27
deployment/virtualbox/pybox/consts/env.py
Normal file
27
deployment/virtualbox/pybox/consts/env.py
Normal 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*',
|
||||
}
|
78
deployment/virtualbox/pybox/consts/networking.py
Normal file
78
deployment/virtualbox/pybox/consts/networking.py
Normal 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/'
|
||||
}
|
79
deployment/virtualbox/pybox/consts/node.py
Normal file
79
deployment/virtualbox/pybox/consts/node.py
Normal 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]
|
||||
}
|
||||
}
|
15
deployment/virtualbox/pybox/consts/timeout.py
Normal file
15
deployment/virtualbox/pybox/consts/timeout.py
Normal 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
|
0
deployment/virtualbox/pybox/helper/__init__.py
Normal file
0
deployment/virtualbox/pybox/helper/__init__.py
Normal file
160
deployment/virtualbox/pybox/helper/host_helper.py
Normal file
160
deployment/virtualbox/pybox/helper/host_helper.py
Normal 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)
|
53
deployment/virtualbox/pybox/helper/install_lab.py
Normal file
53
deployment/virtualbox/pybox/helper/install_lab.py
Normal 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")
|
||||
|
490
deployment/virtualbox/pybox/helper/vboxmanage.py
Normal file
490
deployment/virtualbox/pybox/helper/vboxmanage.py
Normal 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)
|
||||
|
1608
deployment/virtualbox/pybox/install_vbox.py
Executable file
1608
deployment/virtualbox/pybox/install_vbox.py
Executable file
File diff suppressed because it is too large
Load Diff
6
deployment/virtualbox/pybox/requirements.txt
Normal file
6
deployment/virtualbox/pybox/requirements.txt
Normal file
@ -0,0 +1,6 @@
|
||||
# Install rsync, sshpass
|
||||
#
|
||||
configparser
|
||||
paramiko
|
||||
pytest
|
||||
streamexpect
|
0
deployment/virtualbox/pybox/utils/__init__.py
Normal file
0
deployment/virtualbox/pybox/utils/__init__.py
Normal file
52
deployment/virtualbox/pybox/utils/install_log.py
Normal file
52
deployment/virtualbox/pybox/utils/install_log.py
Normal 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
|
68
deployment/virtualbox/pybox/utils/kpi.py
Normal file
68
deployment/virtualbox/pybox/utils/kpi.py
Normal 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("==================================================")
|
||||
|
220
deployment/virtualbox/pybox/utils/serial.py
Normal file
220
deployment/virtualbox/pybox/utils/serial.py
Normal 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
|
123
deployment/virtualbox/pybox/utils/sftp.py
Normal file
123
deployment/virtualbox/pybox/utils/sftp.py
Normal 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)
|
||||
|
127
deployment/virtualbox/pybox/vbox-controlgrp.sh
Executable file
127
deployment/virtualbox/pybox/vbox-controlgrp.sh
Executable 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
|
||||
|
Loading…
Reference in New Issue
Block a user