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