Browse Source

Merge "Python-based installer for StarlingX"

changes/23/677623/1
Zuul 2 years ago
committed by Gerrit Code Review
parent
commit
14b5dbcd2f
  1. 398
      deployment/virtualbox/pybox/Parser.py
  2. 115
      deployment/virtualbox/pybox/README.txt
  3. 0
      deployment/virtualbox/pybox/__init__.py
  4. 67
      deployment/virtualbox/pybox/configs/aio-sx/lab_setup.conf
  5. 7132
      deployment/virtualbox/pybox/configs/aio-sx/lab_setup.sh
  6. 19
      deployment/virtualbox/pybox/configs/aio-sx/stx_config.ini_centos
  7. 0
      deployment/virtualbox/pybox/consts/__init__.py
  8. 75
      deployment/virtualbox/pybox/consts/config.ini
  9. 27
      deployment/virtualbox/pybox/consts/env.py
  10. 78
      deployment/virtualbox/pybox/consts/networking.py
  11. 79
      deployment/virtualbox/pybox/consts/node.py
  12. 15
      deployment/virtualbox/pybox/consts/timeout.py
  13. 0
      deployment/virtualbox/pybox/helper/__init__.py
  14. 160
      deployment/virtualbox/pybox/helper/host_helper.py
  15. 53
      deployment/virtualbox/pybox/helper/install_lab.py
  16. 490
      deployment/virtualbox/pybox/helper/vboxmanage.py
  17. 1608
      deployment/virtualbox/pybox/install_vbox.py
  18. 6
      deployment/virtualbox/pybox/requirements.txt
  19. 0
      deployment/virtualbox/pybox/utils/__init__.py
  20. 52
      deployment/virtualbox/pybox/utils/install_log.py
  21. 68
      deployment/virtualbox/pybox/utils/kpi.py
  22. 220
      deployment/virtualbox/pybox/utils/serial.py
  23. 123
      deployment/virtualbox/pybox/utils/sftp.py
  24. 127
      deployment/virtualbox/pybox/vbox-controlgrp.sh

398
deployment/virtualbox/pybox/Parser.py

@ -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

@ -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

67
deployment/virtualbox/pybox/configs/aio-sx/lab_setup.conf

@ -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

File diff suppressed because it is too large

19
deployment/virtualbox/pybox/configs/aio-sx/stx_config.ini_centos

@ -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

75
deployment/virtualbox/pybox/consts/config.ini

@ -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

@ -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

@ -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

@ -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

@ -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

160
deployment/virtualbox/pybox/helper/host_helper.py

@ -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

@ -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

@ -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',