Add upstream code

This commit is contained in:
Jeroen 2023-05-28 10:49:27 +02:00
commit e270a62468
Signed by: jeroen
GPG Key ID: 7C7028F783798BAB
43 changed files with 9353 additions and 0 deletions

12
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: freebsd-vm-bhyve
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

24
LICENSE Normal file
View File

@ -0,0 +1,24 @@
Copyright (c) 2015-2016, churchers
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

50
Makefile Normal file
View File

@ -0,0 +1,50 @@
#
# vm-bhyve Makefile
#
PREFIX?=/usr/local
BINDIR=$(DESTDIR)$(PREFIX)/sbin
EXAMPLESDIR=$(DESTDIR)${PREFIX}/share/examples/vm-bhyve
LIBDIR=$(DESTDIR)$(PREFIX)/lib/vm-bhyve
MANDIR=$(DESTDIR)$(PREFIX)/man/man8
RCDIR=$(DESTDIR)$(PREFIX)/etc/rc.d
CP=/bin/cp
INSTALL=/usr/bin/install
LN=/bin/ln
MKDIR=/bin/mkdir
PROG=vm
MAN=$(PROG).8
install:
$(MKDIR) -p $(BINDIR)
$(INSTALL) -m 544 $(PROG) $(BINDIR)/
$(MKDIR) -p $(LIBDIR)
$(INSTALL) lib/* $(LIBDIR)/
$(MKDIR) -p $(EXAMPLESDIR)
$(INSTALL) sample-templates/* $(EXAMPLESDIR)/
$(MKDIR) -p $(RCDIR)
$(INSTALL) -m 555 rc.d/* $(RCDIR)/
$(MKDIR) -p $(MANDIR)
gzip -fk $(MAN)
$(INSTALL) $(MAN).gz $(MANDIR)/
rm -f -- $(MAN).gz
$(LN) -sf $(MANDIR)/$(MAN).gz $(MANDIR)/vm-bhyve.8.gz
vmdir:
@if [ -z "${PATH}" ]; then \
echo "Usage: make vmdir PATH=/path"; \
else \
${MKDIR} -p "${PATH}/.templates"; \
${MKDIR} -p "${PATH}/.iso"; \
${MKDIR} -p "${PATH}/.config"; \
${CP} sample-templates/* "${PATH}/.templates/"; \
fi;
.MAIN: clean
clean: ;

339
README.md Normal file
View File

@ -0,0 +1,339 @@
## vm-bhyve
Management system for FreeBSD bhyve virtual machines
Some of the main features include:
* Windows/UEFI support
* Simple commands to create/start/stop bhyve instances
* Simple configuration file format
* Virtual switches supporting vlans & automatic device creation
* ZFS support
* FreeBSD/MidnightBSD/NetBSD/OpenBSD/Linux guest support
* Automatic assignment of console devices to access guest console
* Integration with rc.d startup/shutdown
* Guest reboot handling
* Designed with multiple compute nodes + shared storage in mind (NFS/iSCSI/etc)
* Multiple datastores
* VNC graphics & tmux support (1.1+ only. See wiki for instructions)
* Dependency free**
** Some additional packages may be required in certain circumstances -
* The port has a dependancy on ca_root_nss added by the ports maintainers to help avoid any SSL errors when downloading FreeBSD ISO files using the `vm iso` command.
* `sysutils/grub2-bhyve` is required to run Linux or any other guests that need a Grub bootloader.
* `sysutils/bhyve-firmware` is required to run UEFI guests
* `sysutils/tmux` is needed to use tmux console access instead of cu/nmdm
##### See the GitHub wiki for more information and examples.
For most users, I recommend using the version in ports (1.1+).
Main development happens in the master branch on GitHub and it may contain broken or incomplete features.
## Quick-Start
A simple overview of the commands needed to install vm-bhyve and start a freebsd guest.
See the sections below for more in-depth details.
1. pkg install vm-bhyve
2. zfs create pool/vm
3. sysrc vm_enable="YES"
4. sysrc vm_dir="zfs:pool/vm"
5. vm init
6. cp /usr/local/share/examples/vm-bhyve/* /mountpoint/for/pool/vm/.templates/
7. vm switch create public
8. vm switch add public em0
9. vm iso https://download.freebsd.org/ftp/releases/ISO-IMAGES/11.2/FreeBSD-11.2-RELEASE-amd64-bootonly.iso
10. vm create myguest
11. vm install [-f] myguest FreeBSD-11.2-RELEASE-amd64-bootonly.iso
12. vm console myguest
- [ ] Line 1
Install vm-bhvye
- [ ] Line 2
Create a dataset for your virtual machines.
If you're not using ZFS, just create a normal directory.
- [ ] Lines 3-4
Enable vm-bhyve in /etc/rc.conf and set the dataset to use.
If not using ZFS, just set `$vm_dir="/my/vm/folder"`.
- [ ] Line 5
Run the `vm init` command to create the required directories under $vm_dir and load kernel modules.
- [ ] Line 6
Install the sample templates that come with vm-bhyve.
- [ ] Lines 7-8
Create a virtual switch called 'public' and attach your network interface to it.
Replace `em0` with whatever interface connects your machine to the network.
- [ ] Line 9
Download a copy of FreeBSD from the ftp site.
- [ ] Lines 10-12
Create a new guest using the `default.conf` template, run the installer and
then connect to its console. At this point proceed through the installation
as normal. By specifying the `-f` option before the install command, the guest
will run directly on your terminal so the `console` command is not required. (Bear
in mind that you won't get back to your terminal until the guest is fully shutdown)
## Install
Download the latest release from GitHub, or install `sysutils/vm-bhyve`
To install, just run the following command inside the vm-bhyve source directory
# make install
If you want to run guests other than FreeBSD, you will need the grub2-bhyve package;
# pkg install grub2-bhyve
## Initial configuration
First of all, you will need a directory to store all your virtual machines and vm-bhyve configuration.
If you are not using ZFS, just create a normal directory:
# mkdir /somefolder/vm
If you are using ZFS, create a dataset to hold vm-bhyve data
# zfs create pool/vm
Now update /etc/rc.conf to enable vm-bhyve, and tell it where your directory is
vm_enable="YES"
vm_dir="/somefolder/vm"
Or with ZFS:
vm_enable="YES"
vm_dir="zfs:pool/vm"
This directory will be referred to as $vm_dir in the rest of this readme.
Now run the following command to create the directories used to store vm-bhvye configuration and
load any necessary kernel modules. This needs to be run once after each host reboot, which is
normally handled by the rc.d script
# vm init
## Virtual machine templates
When creating a virtual machine, you use a template which defines how much memory to give the guest,
how many cpu cores, and networking/disk configuration. The templates are all stored inside $vm_dir/.templates.
To install the sample templates, run the following command:
# cp /usr/local/share/examples/vm-bhyve/* /my/vm/path/.templates/
If you look inside the template files with a text editor, you will see they are very simple. You
can create as many templates as you like. For example you could have web-server.conf, containing the setting
for your web servers, or freebsd-large.conf for large FreeBSD guests, and so on. This is the contents of
the default template:
guest="freebsd"
loader="bhyveload"
cpu=1
memory=256M
disk0_type="virtio-blk"
disk0_name="disk0.img"
network0_type="virtio-net"
network0_switch="public"
You will notice that each template is set to create one network interface. You can easily add more network
interfaces by duplicating the two network configuration options and incrementing the number. In general you
will not want to change the type from 'virtio-net', but you will notice the first interface is set to connect
to a switch called 'public'. See the next section for details on how to configure virtual switches.
I recommend reading the man page or `sample-templates/config.sample` for a full list of supported template
options and a description of their purpose. Almost all bhyve functionality is supported and a large variety
of network/storage configurations can be achieved.
## Virtual Switches
When a guest is started, each network interface is automatically connected to the virtual switch specified
in the configuration file. By default all the sample templates connect to a switch called 'public', although
you can use any name. The following section shows how to create a switch called 'public', and configure various
settings:
# vm switch create public
If you just want to bridge guests to your physical network, add the appropriate real interface to the switch.
Obviously you will need to replace em0 here with the correct interface name on your system:
# vm switch add public em0
If you want guest traffic to be on a specific VLAN when leaving the host, specify a vlan number. To turn
off vlans, just set the vlan number to 0:
# vm switch vlan public 10
# vm switch vlan public 0
You can view current switch configuration using the list command:
# vm switch list
## Creating virtual machines
Use one of the following command to create a new virtual machine:
# vm create testvm
# vm create -t templatename -s 50G testvm
The first example uses the default.conf template, and will create a 20GB disk image. The second
example specifies the templatename.conf template, and tells vm-bhyve to create a 50GB disk.
You will need an ISO to install the guest with, so download one using the iso command:
# vm iso https://download.freebsd.org/ftp/releases/ISO-IMAGES/11.2/FreeBSD-11.2-RELEASE-amd64-disc1.iso
To start a guest install, run the following command. vm-bhyve will run the machine in the background,
so use the console command to connect to it and finish installation.
# vm install testvm FreeBSD-11.2-RELEASE-amd64-disc1.iso
# vm console testvm
You can also specify the foreground option to run the guest directly on your terminal:
# vm install -f testvm FreeBSD-11.2-RELEASE-amd64-disc1.iso
Once installation has finished, you can reboot the guest from inside the console and it will boot up into
the new OS (assuming installation was successful). Further reboots will work as expected and
the guest can be shutdown in the normal way. As the console uses the cu command, type ~+Ctrl-D to exit
back to your host.
The following commands start and stop virtual machines:
# vm start testvm
# vm stop testvm
The basic configuration of each machine and state can be viewed using the list command:
# vm list
NAME GUEST LOADER CPU MEMORY AUTOSTART STATE
alpine linux default 1 512M No Stopped
c7 linux default 1 512M Yes [2] Stopped
centos linux default 1 512M No Stopped
debian linux default 1 512M No Stopped
fbsd freebsd default 1 256M No Stopped
netbsd generic grub 1 256M No Stopped
openbsd generic grub 1 256M No Stopped
pf freebsd default 1 256M Yes [1] Stopped
ubuntu linux default 1 512M No Stopped
wintest windows default 2 2G No Running (2796)
All running machines can be stopped using the stopall command
# vm stopall
On host boot, vm-bhyve will use the 'vm startall' command to start all machines. You can
control which guests start automatically using the following variables in /etc/rc.conf:
vm_list="vm1 vm2"
vm_delay="5"
The first defines the list of machines to start on boot, and the order to start them. The second
is the number of seconds to wait between starting each one. 5 seconds is the recommended setting,
although a longer delay is useful if you have disk intensive guests and don't want them all booting
at the same time.
There's also a command which opens a guest's configuration file in your default text editor, allowing
you to easily make changes to the configuration. Please note that changes only take effect after
a full shutdown and restart of the guest
# vm configure testvm
See the man page for a full description of all available commands.
# man vm
## Using cloud images
You can use cloud images to create virtual machines. The `vm img` command will download the image to datastore and
uncompress it if needed (.xz, .tar.gz, and .gz files are supported). The image should be in RAW or QCOW2 format.
To use this feature you'll need install qemu-tools package:
# pkg install qemu-tools
To launch FreeBSD using official cloud image:
# vm img https://download.freebsd.org/ftp/releases/VM-IMAGES/11.2-RELEASE/amd64/Latest/FreeBSD-11.2-RELEASE-amd64.raw.xz
# vm create -t freebsd-zvol -i FreeBSD-11.2-RELEASE-amd64.raw freebsd-cloud
# vm start freebsd-cloud
To list downloaded images:
# vm img
DATASTORE FILENAME
default CentOS-7-x86_64-GenericCloud-20180930_02.raw
default debian-9-openstack-amd64.qcow2
default Fedora-AtomicHost-28-1.1.x86_64.raw
default FreeBSD-11.2-RELEASE-amd64.raw
default xenial-server-cloudimg-amd64-uefi1.img
## Using cloud init
vm-bhyve has basic support for providing cloud-init configuration to the guest. You can enable it with `-C` option
to `vm create` command. You can also pass public SSH key to be injected into the guest with option `-k <file>`.
Example:
# vm create -t linux -i xenial-server-cloudimg-amd64-uefi1.img -C -k ~/.ssh/id_rsa.pub cloud-init-ubuntu
# vm start cloud-init-ubuntu
Starting cloud-init-ubuntu
* found guest in /zroot/vm/cloud-init-ubuntu
* booting...
# ssh ubuntu@192.168.0.91
The authenticity of host '192.168.0.91 (192.168.0.91)' can't be established.
ECDSA key fingerprint is SHA256:6s9uReyhsIXRv0dVRcBCKMHtY0kDYRV7zbM7ot6u604.
No matching host key fingerprint found in DNS.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.0.91' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 16.04.5 LTS (GNU/Linux 4.4.0-141-generic x86_64)
## Adding custom disks
Scenario: If you have a vm on one zpool and would like to add a new virtual disk to it that resides on a different zpool.
Manually create a sparse-zvol (in this case 50G in size).
# zfs create -sV 50G -o volmode=dev "zpool2/vm/yourvm/disk1"
Add it to your vm config file.
Please note, for Windows guests the type will need to be `ahci-hd`, as it does not have virtio-blk drivers.
# vm configure yourvm
disk1_name="/dev/zvol/zpool2/vm/yourvm/disk1"
disk1_type="virtio-blk"
disk1_dev="custom"
Restart your vm.
## Windows Support
Please see the Windows section in the [Wiki](https://github.com/churchers/vm-bhyve/wiki/Running-Windows)
## Autocomplete
If you are using the default csh/tcsh shell built into FreeBSD, running the following command should allow
autocomplete to work for all the currently supported functions. This is especially useful for viewing
and completing guest & ISO file names. Please note that there's three occurrences of '/path/to/vm' which
need to be changed to the directory containing your virtual machines.
To make the autocomplete features available permanently, add the following to your `$HOME/.cshrc` file. Then either
logout/login, or run `source ~/.cshrc` to cause the `.cshrc` file to be reloaded.
complete vm \
'p@1@(list create install start stop console configure reset poweroff destroy clone snapshot rollback add switch iso)@' \
'n@create@n@' \
'n@list@n@' \
'n@iso@n@' \
'n@switch@(list create add remove destroy vlan nat)@' \
'N@switch@`sysrc -inqf /path/to/vm/.config/switch switch_list`@' \
'N@install@`ls -1 /path/to/vm/.iso`@' \
'N@nat@(off on)@' \
'p@2@`ls -1 /path/to/vm | grep -v "^\." | grep -v "^images"`@'

61
lib/vm-base Normal file
View File

@ -0,0 +1,61 @@
#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (C) 2018 Matt Churchyard (churchers@gmail.com)
# All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
VERSION=1.6-devel
VERSION_INT=106001
VERSION_BSD=$(uname -K)
PATH=${PATH}:/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin:/usr/local/sbin
. /etc/rc.subr
load_rc_config "vm"
# check informational commands
cmd::parse_info "$@"
# we should be enabled in rc.conf
# or call it using forcestart
[ -z "$rc_force" ] && ! checkyesno vm_enable && util::err "\$vm_enable is not enabled in /etc/rc.conf!"
# check we can run bhyve
util::check_bhyve_support
# init for zfs
zfs::init
# create directories as needed
[ ! -d "${vm_dir}" ] && util::err "\$vm_dir has not been configured or is not a valid directory"
[ ! -d "${vm_dir}/.config" ] && mkdir "${vm_dir}/.config"
[ ! -e "${vm_dir}/.config/null.iso" ] && touch "${vm_dir}/.config/null.iso"
[ ! -d "${vm_dir}/.templates" ] && mkdir "${vm_dir}/.templates"
[ ! -d "${vm_dir}/.iso" ] && mkdir "${vm_dir}/.iso"
[ ! -d "${vm_dir}/.img" ] && mkdir "${vm_dir}/.img"
# load core configuration
config::core::load
datastore::load
# run the requested command
cmd::parse "$@"

237
lib/vm-cmd Normal file
View File

@ -0,0 +1,237 @@
#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (C) 2016 Matt Churchyard (churchers@gmail.com)
# All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
CMD_VALID_LIST="init,switch,datastore,image,get,set,list,create,destroy,rename,install,start,stop,restart"
CMD_VALID_LIST="${CMD_VALID_LIST},add,reset,poweroff,startall,stopall,console,iso,img,configure,passthru,_run"
CMD_VALID_LIST="${CMD_VALID_LIST},info,clone,snapshot,rollback,migrate,version,usage"
# cmd: vm ...
#
# handle simple information commands that don't need any
# priviledged access or bhyve support
#
# @param string _cmd the command right after 'vm '
#
cmd::parse_info(){
local _cmd
cmd::find "_cmd" "$1" "${CMD_VALID_LIST}"
case "${_cmd}" in
version) util::version && exit ;;
usage) util::usage ;;
esac
}
# cmd: vm ...
#
# process the vm command line to see which function is requested
#
# @param string _cmd the command right after 'vm '
#
cmd::parse(){
local _cmd
# try to find a matching command
cmd::find "_cmd" "$1" "${CMD_VALID_LIST}" || util::usage
shift
case "${_cmd}" in
init) util::setup
switch::init ;;
switch) cmd::parse_switch "$@" ;;
datastore) cmd::parse_datastore "$@" ;;
image) cmd::parse_image "$@" ;;
get) core::get "$@" ;;
set) core::set "$@" ;;
list) core::list "$@" ;;
create) core::create "$@" ;;
destroy) core::destroy "$@" ;;
rename) core::rename "$@" ;;
install) core::install "$@" ;;
start) core::start "$@" ;;
stop) core::stop "$@" ;;
restart) core::restart "$@" ;;
add) core::add "$@" ;;
reset) core::reset "$@" ;;
poweroff) core::poweroff "$@" ;;
startall) core::startall ;;
stopall) core::stopall ;;
console) core::console "$@" ;;
iso) core::iso "$@" ;;
img) core::img "$@" ;;
configure) core::configure "$@" ;;
passthru) core::passthru ;;
_run) vm::run "$@" ;;
info) info::guest "$@" ;;
clone) zfs::clone "$@" ;;
snapshot) zfs::snapshot "$@" ;;
rollback) zfs::rollback "$@" ;;
migrate) migration::run "$@" ;;
*) util::err "unknown command '${_user_cmd}'. please run 'vm usage' or view the manpage for help" ;;
esac
}
# cmd: vm switch ...
#
# parse switch command
# we've already shifted once, so $1 is the switch function
#
# @param string _cmd the command right after 'vm switch '
#
cmd::parse_switch(){
local _cmd
# try to find a matching command
cmd::find "_cmd" "$1" "create,list,destroy,add,remove,vlan,nat,address,private,info" || util::usage
shift
case "${_cmd}" in
create) switch::create "$@" ;;
list) switch::list ;;
destroy) switch::remove "$@" ;;
add) switch::add_member "$@" ;;
remove) switch::remove_member "$@" ;;
vlan) switch::vlan "$@" ;;
nat) switch::nat "$@" ;;
address) switch::address "$@" ;;
private) switch::private "$@" ;;
info) info::switch "$@" ;;
*) util::err "unknown command '${_user_cmd}'. please run 'vm usage' or view the manpage for help" ;;
esac
}
# cmd: vm datastore ...
#
# parse a datastore command
#
# @param string _cmd the command after 'vm datastore ...'
#
cmd::parse_datastore(){
local _cmd
# try to find a matching command
cmd::find "_cmd" "$1" "list,add,remove,iso,img" || util::usage
shift
case "${_cmd}" in
list) datastore::list ;;
add) datastore::add "$@" ;;
remove) datastore::remove "$@" ;;
iso) datastore::iso "$@" ;;
img) datastore::img "$@" ;;
*) util::err "unknown command '${_user_cmd}'. please run 'vm usage' or view the manpage for help" ;;
esac
}
# cmd 'vm image ...'
# parse the image command set
#
# @param string _cmd the command after 'vm image '
#
cmd::parse_image(){
local _cmd
[ -z "${VM_ZFS}" ] && util::err "\$vm_dir must be a ZFS datastore to use these functions"
# try to find a matching command
cmd::find "_cmd" "$1" "list,create,provision,destroy" || util::usage
shift
case "${_cmd}" in
list) zfs::image_list ;;
create) zfs::image_create "$@" ;;
provision) zfs::image_provision "$@" ;;
destroy) zfs::image_destroy "$@" ;;
*) util::err "unknown command '${_user_cmd}'. please run 'vm usage' or view the manpage for help" ;;
esac
}
# many commands accept the same arguments (force being the obvious one)
# provide a function to parse these so we don't have to keep
# repeating the same getopt code. the return value here is the number
# of arguments the caller needs to shift.
#
# note that start/install/_run use -f for foreground mode
#
# @param _arglist[multiple] the callers $@
# @return number of arguments caller should shift over
#
cmd::parse_args(){
local _opt _count
while getopts fitv _opt; do
case ${_opt} in
f) VM_OPT_FORCE="1"
VM_OPT_FOREGROUND="1" ;;
i) VM_OPT_INTERACTIVE="1" ;;
t) VM_OPT_TMUX="1" ;;
v) VM_OPT_VERBOSE="1" ;;
esac
done
[ -n "${VM_OPT_FOREGROUND}" ] && [ -n "${VM_OPT_INTERACTIVE}" ] && \
util::err "foreground and interactive mode are mutually exclusive"
return $((OPTIND - 1))
}
# try to match part of a command name against a list of valid commands
# if we find more than one match we return an error
# if we only get one match, return the full command name
#
# @param string _var variable to put full command name into
# @param string _user_cmd the value provided by the user
# @param string _valid comma-separated list of valid choices
# @return success if we find one match
#
cmd::find(){
local _var="$1"
local _user_cmd="$2"
local _valid="$3"
local _opt _choice _found=""
local IFS=","
[ -n "${_user_cmd}" ] || util::err "no command specified"
for _opt in ${_valid}; do
# exact match?
if [ "${_user_cmd}" = "${_opt}" ]; then
setvar "${_var}" "${_opt}"
return 0
fi
if echo "${_opt}" | grep -iqs "^${_user_cmd}"; then
[ -n "${_found}" ] && util::err "ambiguous command '${_user_cmd}'"
_found=1
_choice="${_opt}"
fi
done
[ -z "${_found}" ] && return 1
setvar "${_var}" "${_choice}"
}

199
lib/vm-config Normal file
View File

@ -0,0 +1,199 @@
#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (C) 2016 Matt Churchyard (churchers@gmail.com)
# All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# load a configuration file
# this reads the specfied file into the global VM_CONFIG variable.
# we have very basic parsing that uses # for comments and requires
# all variables to be at the beginning of the line in lowercase.
# Note also that a # within double quotes will still be treated
# as the start of a comment.
#
# @param string _file full path of the file to read
# @modifies VM_CONFIG
#
config::load(){
local _file="$1"
# read config file
# we kick out any lines that don't start with a letter,
# scrap anything after a # character, and remove double-quotes
VM_CONFIG=$(grep '^[a-z]' "${_file}" 2>/dev/null | awk -F# '{print $1}' | sed -e 's@ *$@@' | tr -d '"')
}
# get a configuration value from the current config file
#
# @param string _var the variable to put value into
# @param string _key the name of the config key to retrieve
# @param optional string _def default value to return if setting not found
# @return true if setting found
#
config::get(){
local _c_var="$1"
local _c_key="$2"
local _c_def="$3"
local _c_line
local IFS=$'\n'
for _c_line in ${VM_CONFIG}; do
if [ "${_c_key}" = "${_c_line%%=*}" ]; then
setvar "${_c_var}" "${_c_line#*=}"
return 0
fi
done
# not found
setvar "${_c_var}" "${_c_def}"
return 1
}
# simple wrapper to check a config setting to see if it's
# set to yes/no true/false etc
#
# @param string _key the config key to check
# @param string _def default value if config key doesn't exist
# @return true(0) if set to anything other than no/false/off/0
#
config::yesno(){
local _key="$1"
local _def="$2"
local _value
config::get "_value" "${_key}" "${_def}"
util::yesno "${_value}"
}
# set a value in guest configuration file
# we check for newline at the end as sysrc won't add it
# and that will mess up the new key and the existing one
# from the end of the file
#
# @param string _name guest name
# @param string _key config key to set
# @param string _value value
# @param int _skip_newline_check skip the check for newline
# @return true if sysrc successful
#
config::set(){
local _name="$1"
local _key="$2"
local _value="$3"
local _skip_newline_check="$4"
local _newline
if [ -z "${_skip_newline_check}" ]; then
_newline=$(tail -1 "${VM_DS_PATH}/${_name}/${_name}.conf" | wc -l | tr -d " ")
[ "${_newline}" -eq "0" ] && echo "" >> "${VM_DS_PATH}/${_name}/${_name}.conf"
fi
sysrc -inqf "${VM_DS_PATH}/${_name}/${_name}.conf" "${_key}=${_value}" >/dev/null 2>&1
}
# remove a value from guest config
config::remove(){
local _name="$1"
local _key="$2"
sysrc -inxqf "${VM_DS_PATH}/${_name}/${_name}.conf" "${_key}" >/dev/null 2>&1
}
# load core configuration file
#
# @modifies VM_CORE_CONFIG VM_CONFIG_USER
#
config::core::load(){
VM_CONFIG_USER="console;compress;decompress;"
# check config file exists
# this is mainly for upgrades to make sure switch/datastore config are migrated
# DEPRECATED 1.3, remove after
if [ ! -e "${vm_dir}/.config/system.conf" ]; then
cat "${vm_dir}/.config/switch" > "${vm_dir}/.config/system.conf" 2>/dev/null
cat "${vm_dir}/.config/datastore" >> "${vm_dir}/.config/system.conf" 2>/dev/null
fi
VM_CORE_CONFIG=$(grep '^[a-z]' "${vm_dir}/.config/system.conf" 2>/dev/null | awk -F# '{print $1}' | sed -e 's@ *$@@' | tr -d '"')
}
# get a value from core config
#
# @param string _c_var variable name to put value into
# @param string _c_key config key to look for
# @param string _c_def default value if not value
# @return 0 if found
#
config::core::get(){
local _c_var="$1"
local _c_key="$2"
local _c_def="$3"
local _c_line
local IFS=$'\n'
for _c_line in ${VM_CORE_CONFIG}; do
if [ "${_c_key}" = "${_c_line%%=*}" ]; then
setvar "${_c_var}" "${_c_line#*=}"
return 0
fi
done
# not found
setvar "${_c_var}" "${_c_def}"
return 1
}
# add a value to core configuration
#
# @param string _var variable to set
# @param string _value new value
# @param string _append non-empty to append to existing value
#
config::core::set(){
local _var="$1"
local _value="$2"
local _append="$3"
if [ -n "${_append}" ]; then
sysrc -inqf "${vm_dir}/.config/system.conf" "${_var}"+="${_value}" >/dev/null 2>&1
else
sysrc -inqf "${vm_dir}/.config/system.conf" "${_var}"="${_value}" >/dev/null 2>&1
fi
}
# remove a value from core configuration
#
# @param string _var variable to remove
# @param string _value if non-empty we will try to remove just this value from setting
# @return non-zero on error
#
config::core::remove(){
local _var="$1"
local _value="$2"
if [ -n "${_value}" ]; then
sysrc -inqf "${vm_dir}/.config/system.conf" "${_var}"-="${_value}" >/dev/null 2>&1
else
sysrc -inxqf "${vm_dir}/.config/system.conf" ${_var} >/dev/null 2>&1
fi
}

1080
lib/vm-core Normal file

File diff suppressed because it is too large Load Diff

500
lib/vm-datastore Normal file
View File

@ -0,0 +1,500 @@
#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (C) 2016 Matt Churchyard (churchers@gmail.com)
# All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# 'vm datastore list'
# show configured datastores
#
datastore::list(){
local _format="%-15s %-11s %-25s %s"
local _name _type _dataset _path _spec
# headings
printf "${_format}\n" "NAME" "TYPE" "PATH" "ZFS DATASET"
# add the default datastore
_name="default"
_path="${vm_dir}"
# get the type and path
if [ "${VM_ZFS}" ]; then
_type="zfs"
_dataset="${VM_ZFS_DATASET}"
else
_type="directory"
_dataset="-"
fi
printf "${_format}\n" "${_name}" "${_type}" "${_path}" "${_dataset}"
for _name in ${VM_DATASTORE_LIST}; do
[ "${_name}" = "default" ] && continue
config::core::get "_spec" "path_${_name}"
[ -z "${_spec}" ] && continue
if [ "${_spec%%:*}" = "zfs" ]; then
_type="zfs"
_dataset="${_spec#*:}"
datastore::__resolve_path "_path" "${_spec}"
elif [ "${_spec%%:*}" = "iso" ]; then
_type="iso"
_path="${_spec#*:}"
_dataset="-"
elif [ "${_spec%%:*}" = "img" ]; then
_type="img"
_path="${_spec#*:}"
_dataset="-"
else
_type="directory"
_path="${_spec}"
_dataset="-"
fi
printf "${_format}\n" "${_name}" "${_type}" "${_path}" "${_dataset}"
done
}
# 'vm datastore add'
# create a new datastore
#
# we don't try and create directories or datasets.
# the user should do that first
#
# @param string _name datastore name
# @param string _spec specification (either /path or zfs:dataset)
#
datastore::add(){
local _name="$1"
local _spec="$2"
local _mount _num=0 _curr
[ -z "${_name}" -o -z "${_spec}" ] && util::usage
util::check_name "${_name}" || util::err "invalid datastore name - '${_name}'"
# check name not in use
for _curr in ${VM_DATASTORE_LIST}; do
[ "${_curr}" = "${_name}" ] && util::err "datstore '${_name}' already exists!"
done
# look for zfs
if [ "${_spec%%:*}" = "zfs" ]; then
# try to find mountpoint
_mount=$(mount | grep "^${_spec#*:} " |cut -d' ' -f3)
[ -z "${_mount}" ] && util::err "${_spec} doesn't seem to be a valid, mounted dataset"
else
# make sure it's a directory
[ ! -d "${_spec}" ] && util::err "${_spec} doesn't seem to be a valid directory"
_mount="${_spec}"
fi
# see if this is already our default datastore
[ "${_mount}" = "${vm_dir}" ] && util::err "specified path already exists as default datastore"
# save
config::core::set "datastore_list" "${_name}" "1"
config::core::set "path_${_name}" "${_spec}"
[ $? -eq 0 ] || util::err "error saving settings to configuration file"
}
# remove a datastore
# we don't actually delete anything, just remove from config
#
# @param string _name name of dataset
#
datastore::remove(){
local _name="$1"
local _ds _found
[ "${_name}" = "default" ] && util::err "cannot remove default datastore"
for _ds in ${VM_DATASTORE_LIST}; do
[ "${_ds}" = "${_name}" ] && _found="1"
done
# found the dataset?
[ -z "${_found}" ] && util::err "unable to locate the specified dataset"
config::core::remove "datastore_list" "${_name}"
config::core::remove "path_${_name}"
[ $? -eq 0 ] || util::err "error removing settings from configuration file"
}
# get the filesystem path for the specified dataset spec
# this can either be just a path, or ZFS dataset
#
# @param string _var variable to put path into
# @param string _spec the path spec (either directory or zfs:dataset)
# @return non-zero on error
#
datastore::__resolve_path(){
local _var="$1"
local _spec="$2"
if [ "${_spec%%:*}" = "zfs" ]; then
_mount=$(mount | grep "^${_spec#*:} " |cut -d' ' -f3)
if [ -n "${_mount}" ]; then
setvar "${_var}" "${_mount}"
return 0
fi
elif [ "${_spec%%:*}" = "iso" ] || [ "${_spec%%:*}" = "img" ]; then
setvar "${_var}" "${_spec#*:}"
return 0
else
if [ -d "${_spec}" ]; then
setvar "${_var}" "${_spec}"
return 0
fi
fi
setvar "${_var}" ""
return 1
}
# load list of datastores into VM_DATASTORE_LIST
#
# @modifies VM_DATASTORE_LIST
#
datastore::load(){
config::core::get "VM_DATASTORE_LIST" "datastore_list"
VM_DATASTORE_LIST="default${VM_DATASTORE_LIST:+ }${VM_DATASTORE_LIST}"
}
# init global settings for a vm
# we take a guest name and try to find it in all
# datastores. we don't allow duplicate names, and
# if there is, we will just return the first found.
#
# @param string _guest guest name
# @return non-zero on error
# @modifies VM_DS_NAME VM_DS_PATH VM_DS_ZFS VM_DS_ZFS_DATASET
#
datastore::get_guest(){
local _guest="$1"
local _ds _spec _path _found _zfs _dataset
# look in default store
if [ -f "${vm_dir}/${_guest}/${_guest}.conf" ]; then
_found="1"
_ds="default"
_path="${vm_dir}"
_zfs="${VM_ZFS}"
_dataset="${VM_ZFS_DATASET}"
fi
# look on other datastores
if [ -z "${_found}" ]; then
for _ds in ${VM_DATASTORE_LIST}; do
[ "${_ds}" = "default" ] && continue
config::core::get "_spec" "path_${_ds}"
if [ "${_spec%%:*}" = "iso" ] || [ "${_spec%%:*}" = "img" ]; then
continue
fi
datastore::__resolve_path "_path" "${_spec}"
if [ -f "${_path}/${_guest}/${_guest}.conf" ]; then
[ "${_spec%%:*}" = "zfs" ] && _zfs="1" && _dataset="${_spec#*:}"
_found="1"
break
fi
done
fi
# make sure we have a path
[ -z "${_found}" ] && return 1
# set variables
VM_DS_NAME="${_ds}"
VM_DS_PATH="${_path}"
VM_DS_ZFS="${_zfs}"
VM_DS_ZFS_DATASET="${_dataset}"
}
# get the path and details for a datastore
# put into same variables as datastore_get_guest
#
# @param string _ds datastore name
# @return non-zero on error
# @modifies VM_DS_PATH VM_DS_ZFS VM_DS_ZFS_DATASET
#
datastore::get(){
local _ds="$1"
local _spec _path _zfs _dataset
# check for default
if [ "${_ds}" = "default" ]; then
VM_DS_NAME="default"
VM_DS_PATH="${vm_dir}"
VM_DS_ZFS="${VM_ZFS}"
VM_DS_ZFS_DATASET="${VM_ZFS_DATASET}"
return 0
fi
config::core::get "_spec" "path_${_ds}"
[ -z "${_spec}" ] && return 1
# skip iso and img stores
if [ "${_spec%%:*}" = "iso" ] || [ "${_spec%%:*}" = "img" ]; then
return 1
fi
datastore::__resolve_path "_path" "${_spec}" || return 1
[ "${_spec%%:*}" = "zfs" ] && _zfs="1" && _dataset="${_spec#*:}"
# set variables
VM_DS_NAME="${_ds}"
VM_DS_PATH="${_path}"
VM_DS_ZFS="${_zfs}"
VM_DS_ZFS_DATASET="${_dataset}"
}
# get the path for an iso datastore
#
# @param string _ds datastore name
#
datastore::get_iso(){
local _ds="$1"
# default?
# we use the .iso subdir in that case
if [ "${_ds}" = "default" ]; then
VM_DS_PATH="${vm_dir}/.iso"
return 0
fi
config::core::get "_spec" "path_${_ds}"
[ -z "${_spec}" ] && return 1
# should be an iso ds
[ "${_spec%%:*}" = "iso" ] || return 1
datastore::__resolve_path "_path" "${_spec}" || return 1
VM_DS_PATH="${_path}"
}
# get the path for an img datastore
#
# @param string _ds datastore name
#
datastore::get_img(){
local _ds="$1"
# default?
# we use the .img subdir in that case
if [ "${_ds}" = "default" ]; then
VM_DS_PATH="${vm_dir}/.img"
return 0
fi
config::core::get "_spec" "path_${_ds}"
[ -z "${_spec}" ] && return 1
# should be an img ds
[ "${_spec%%:*}" = "img" ] || return 1
datastore::__resolve_path "_path" "${_spec}" || return 1
VM_DS_PATH="${_path}"
}
# add a datastore for iso files
#
# @param string _name the name of the datastore
# @param string _path filesystem path
#
datastore::iso(){
local _name="$1"
local _path="$2"
[ -z "${_name}" -o -z "${_path}" ] && util::usage
util::check_name "${_name}" || util::err "invalid datastore name - '${_name}'"
# check name not in use
for _curr in ${VM_DATASTORE_LIST}; do
[ "${_curr}" = "${_name}" ] && util::err "datstore '${_name}' already exists!"
done
# make sure directory exists
[ ! -d "${_path}" ] && util::err "specified directory does not appear to be valid"
# save
config::core::set "datastore_list" "${_name}" "1"
config::core::set "path_${_name}" "iso:${_path}"
[ $? -eq 0 ] || util::err "error saving settings to configuration file"
}
# add a datastore for img files
#
# @param string _name the name of the datastore
# @param string _path filesystem path
#
datastore::img(){
local _name="$1"
local _path="$2"
[ -z "${_name}" -o -z "${_path}" ] && util::usage
util::check_name "${_name}" || util::err "invalid datastore name - '${_name}'"
# check name not in use
for _curr in ${VM_DATASTORE_LIST}; do
[ "${_curr}" = "${_name}" ] && util::err "datstore '${_name}' already exists!"
done
# make sure directory exists
[ ! -d "${_path}" ] && util::err "specified directory does not appear to be valid"
# save
config::core::set "datastore_list" "${_name}" "1"
config::core::set "path_${_name}" "img:${_path}"
[ $? -eq 0 ] || util::err "error saving settings to configuration file"
}
# find an iso file by looking in the default location
# and any "iso" datastores
#
# @param string _var variable name to put full iso path into
# @param string _file iso filename to look for
# @return int success if found
#
datastore::iso_find(){
local _var="$1"
local _file="$2"
local _ds _spec
# given a full path?
if [ -z "${_file%%/*}" ] && [ -r "${_file}" ]; then
setvar "${_var}" "${_file}"
return 0
fi
# file exists in current dir?
if [ -r "$(pwd)/${_file}" ]; then
setvar "${_var}" "$(pwd)/${_file}"
return 0
fi
# look in default store
if [ -r "${vm_dir}/.iso/${_file}" ]; then
setvar "${_var}" "${vm_dir}/.iso/${_file}"
return 0
fi
for _ds in ${VM_DATASTORE_LIST}; do
config::core::get "_spec" "path_${_ds}"
[ "${_spec%%:*}" != "iso" ] && continue
if [ -r "${_spec#*:}/${_file}" ]; then
setvar "${_var}" "${_spec#*:}/${_file}"
return 0
fi
done
return 1
}
# find an img file by looking in the default location
# and any "img" datastores
#
# @param string _var variable name to put full img path into
# @param string _file img filename to look for
# @return int success if found
#
datastore::img_find(){
local _var="$1"
local _file="$2"
local _ds _spec
# given a full path?
if [ -z "${_file%%/*}" ] && [ -r "${_file}" ]; then
setvar "${_var}" "${_file}"
return 0
fi
# file exists in current dir?
if [ -r "$(pwd)/${_file}" ]; then
setvar "${_var}" "$(pwd)/${_file}"
return 0
fi
# look in default store
if [ -r "${vm_dir}/.img/${_file}" ]; then
setvar "${_var}" "${vm_dir}/.img/${_file}"
return 0
fi
for _ds in ${VM_DATASTORE_LIST}; do
config::core::get "_spec" "path_${_ds}"
[ "${_spec%%:*}" != "img" ] && continue
if [ -r "${_spec#*:}/${_file}" ]; then
setvar "${_var}" "${_spec#*:}/${_file}"
return 0
fi
done
return 1
}
# list iso files
#
datastore::iso_list(){
local _ds _spec _format="%-20s%s\n"
printf "${_format}" "DATASTORE" "FILENAME"
# look for default iso location
[ -d "${vm_dir}/.iso" ] && ls -1 "${vm_dir}/.iso" | awk '{printf "'${_format}'","default",$1}'
# look for iso datastores
for _ds in ${VM_DATASTORE_LIST}; do
config::core::get "_spec" "path_${_ds}"
[ "${_spec%%:*}" != "iso" ] && continue
[ -d "${_spec#*:}" ] && ls -1 ${_spec#*:} | awk '{printf "'${_format}'","'${_ds}'",$1}'
done
}
# list img files
#
datastore::img_list(){
local _ds _spec _format="%-20s%s\n"
printf "${_format}" "DATASTORE" "FILENAME"
# look for default img location
[ -d "${vm_dir}/.img" ] && ls -1 "${vm_dir}/.img" | awk '{printf "'${_format}'","default",$1}'
# look for img datastores
for _ds in ${VM_DATASTORE_LIST}; do
config::core::get "_spec" "path_${_ds}"
[ "${_spec%%:*}" != "img" ] && continue
[ -d "${_spec#*:}" ] && ls -1 ${_spec#*:} | awk '{printf "'${_format}'","'${_ds}'",$1}'
done
}

196
lib/vm-guest Normal file
View File

@ -0,0 +1,196 @@
#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (C) 2016 Matt Churchyard (churchers@gmail.com)
# All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# guest::load
# this function is responsible for doing any pre-load tasks for a guest.
# for non uefi guests this normally means running bhyveload or grub-bhyve.
# this function should return a non-zero value if there's a problem
# or 0 on success.
# As this is called from within the scope of vm::run,
# the following variables are already set (among others)
#
# _name: guest name
# _loader: boot loader to use (grub|bhyveload)
# _com: com port - /dev/nmdmXA
# _conf: full path to guest config file
# _cpu: cpu count
# _memory: RAM
# _guest: guest type
# _bootdisk: full path to primary disk
#
# I've written append wrong as it just needs to be something other than 'write',
# and is much more readable when all the util::log* calls line up
#
# @param optional string _iso set to the boot iso on install, or not given for normal run
# @return int 0=success, 15=vm-bhyve error (see log), other=bhyveload|grub-bhyve error code
#
guest::load(){
local _iso="$1"
local _args _command _timeout _grub_opt _bsd_loader _custom_args
# require a boot disk
if [ -z "${_bootdisk}" ]; then
util::log "guest" "${_name}" "fatal; non-uefi loaders require a boot disk device"
return 15
fi
# all loaders have same console and wired memory options
[ -z "${VM_OPT_FOREGROUND}" ] && _args="-c ${_com}"
[ "${_wiredmem}" = "1" ] && _args="${_args}${_args:+ }-S"
# get timeout
config::get "_timeout" "loader_timeout" "3"
case "${_loader}" in
bhyveload)
_command="bhyveload"
_args="${_args}${_args:+ }-m ${_memory} -e smbios.system.uuid=${_uuid} -e autoboot_delay=${_timeout} -e bhyve_vm_name=${_name}"
# look for custom bhyveload arguments
config::get "_custom_args" "bhyveload_args"
[ -n "${_custom_args}" ] && _args="${_args} ${_custom_args}"
# have a custom guest loader specified?
config::get "_bsd_loader" "bhyveload_loader"
[ -n "${_bsd_loader}" ] && _args="${_args} -l ${_bsd_loader}"
if [ -n "${_iso}" ]; then
_args="${_args} -d ${_iso}"
else
_args="${_args} -d ${_bootdisk}"
fi
;;
grub)
_command=$(which grub-bhyve)
# check we have grub-bhyve
if [ $? -ne 0 ]; then
util::log "guest" "${_name}" "fatal; grub requested but sysutils/grub2-bhyve not installed?"
return 15
fi
# add device map path and memory
_args="${_args}${_args:+ }-m ${VM_DS_PATH}/${_name}/device.map -M ${_memory}"
if [ -n "${_iso}" ]; then
_root="cd0"
util::log_and_write "write" "${_name}" "device.map" "(cd0) ${_iso}"
util::log_and_write "appnd" "${_name}" "device.map" "(hd0) ${_bootdisk}"
guest::__map_all_disks
# if we have local grub config, we need to point grub-bhyve at the host.
# if not, just use defaults
if guest::__write_config "install"; then
_args="${_args} -r host -d ${VM_DS_PATH}/${_name}"
else
_args="${_args} -r ${_root}"
fi
else
_root="hd0,1"
util::log_and_write "write" "${_name}" "device.map" "(hd0) ${_bootdisk}"
guest::__map_all_disks
config::get "_grub_opt" "grub_run_partition"
[ -n "${_grub_opt}" ] && _root="hd0,${_grub_opt}"
# if we have local config, point grub-bhyve at it
# otherwise we use defaults, or directory and file specified by user
if guest::__write_config "run"; then
_args="${_args} -r host -d ${VM_DS_PATH}/${_name}"
else
_args="${_args} -r ${_root}"
config::get "_grub_opt" "grub_run_dir"
[ -n "${_grub_opt}" ] && _args="${_args} -d ${_grub_opt}"
config::get "_grub_opt" "grub_run_file"
[ -n "${_grub_opt}" ] && _args="${_args} -g ${_grub_opt}"
fi
fi
;;
*)
util::log "guest" "${_name}" "unsupported loader - '${_loader}'"
return 15
;;
esac
# run the command
util::log "guest" "${_name}" "${_command} ${_args} ${_name}"
${_command} ${_args} ${_name}
}
# Add all extra/non-boot disks to the device.map file
# Some users may need to access additional disks from the loader
#
guest::__map_all_disks(){
local _disk _dev _path _num=1
config::get "_disk" "disk${_num}_name"
while [ -n "${_disk}" ]; do
config::get "_dev" "disk${_num}_dev"
vm::get_disk_path "_path" "${_name}" "${_disk}" "${_dev}"
util::log_and_write "appnd" "${_name}" "device.map" "(hd${_num}) ${_path}"
_num=$((_num + 1))
config::get "_disk" "disk${_num}_name"
done
}
# See if the user has configured grub commands.
# If so we write them to a grub.cfg file and
# tell grub-bhyve to use it via (host) device
#
# @param string _type=install|run which commands to load
# @return int true (0) if commands were loaded
#
guest::__write_config(){
local _type="$1"
local _command _num=0
# make sure original boot command file grub.cmd is gone
# we've switched to grub.cfg now as this is the
# default for grub-bhyve and makes one less option needed
rm "${VM_DS_PATH}/${_name}/grub.*" >/dev/null 2>&1
config::get "_command" "grub_${_type}${_num}"
[ -z "${_command}" ] && return 1
util::log_and_write "write" "${_name}" "grub.cfg" "timeout=${_timeout}"
util::log_and_write "appnd" "${_name}" "grub.cfg" "menuentry '${_name} (bhyve ${_type})' {"
util::log_and_write "appnd" "${_name}" "grub.cfg" " root=${_root}"
while [ -n "${_command}" ]; do
# we don't need boot command anymore
[ "${_command}" != "boot" ] && util::log_and_write "appnd" "${_name}" "grub.cfg" " ${_command}"
_num=$((_num + 1))
config::get "_command" "grub_${_type}${_num}"
done
util::log_and_write "appnd" "${_name}" "grub.cfg" "}"
return 0
}

503
lib/vm-info Normal file
View File

@ -0,0 +1,503 @@
#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (C) 2016 Matt Churchyard (churchers@gmail.com)
# All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# 'vm info'
# display a wealth of information about all guests, or one specified
#
# @param optional string[multiple] _name name of the guest to display
#
info::guest(){
local _name="$1"
local _bridge_list=$(ifconfig -g vm-switch)
local _ds
vm::running_load
# see if guest name(s) provided
if [ -n "${_name}" ]; then
while [ -n "${_name}" ]; do
datastore::get_guest "${_name}" || util::err "unable to locate virtual machine '${_name}'"
info::guest_show "${_name}"
shift
_name="$1"
done
exit
fi
# show all guests from all datastores
for _ds in ${VM_DATASTORE_LIST}; do
datastore::get "${_ds}" || continue
ls -1 "${VM_DS_PATH}" 2>/dev/null | \
while read _name; do
[ -e "${VM_DS_PATH}/${_name}/${_name}.conf" ] && info::guest_show "${_name}"
done
done
}
# 'vm switch info'
# display config of each virtual switch as well as stats and connected guests
#
# @param optional string[multiple] _switch name of switch to display
#
info::switch(){
local _switch="$1"
local _list
# load config file manually using non-core function
# this means we can share the config_output function with guest
config::load "${vm_dir}/.config/system.conf"
config::get "_list" "switch_list"
if [ -n "${_switch}" ]; then
while [ -n "${_switch}" ]; do
info::switch_show "${_switch}"
shift
_switch="$1"
done
exit
fi
for _switch in ${_list}; do
info::switch_show "${_switch}"
done
}
# display info for one virtual switch
#
# @param string _switch the name of the switch
#
info::switch_show(){
local _switch="$1"
local _type _bridge _vale _netgraph _id
local _INDENT=" "
[ -z "${_switch}" ] && return 1
config::get "_type" "type_${_switch}" "standard"
config::get "_bridge" "bridge_${_switch}"
config::get "_vale" "vale_${_switch}"
config::get "_netgraph" "netgraph_${_switch}"
echo "------------------------"
echo "Virtual Switch: ${_switch}"
echo "------------------------"
echo "${_INDENT}type: ${_type}"
switch::id "_id" "${_switch}"
# we don't have a bridge for vale and netgraph switches
[ "${_type}" != "vale" ] && [ "${_type}" != "netgraph" ] && _bridge="${_id}"
echo "${_INDENT}ident: ${_id:--}"
info::__output_config "vlan_${_switch}" "vlan"
info::__output_config "ports_${_switch}" "physical-ports"
if [ -n "${_bridge}" ]; then
_stats=$(netstat -biI "${_bridge}" |grep '<Link#' | tail -n1 | awk '{ for (i=NF; i>1; i--) printf("%s ",$i); print $1; }' | awk '{print $5,$2}')
if [ -n "${_stats}" ]; then
_b_in=$(info::__bytes_human "${_stats%% *}")
_b_out=$(info::__bytes_human "${_stats##* }")
echo "${_INDENT}bytes-in: ${_stats%% *} (${_b_in})"
echo "${_INDENT}bytes-out: ${_stats##* } (${_b_out})"
fi
# show guest ports
info::switch_ports
fi
echo ""
}
# get all guest ports for current bridge
#
info::switch_ports(){
local _port_list=$(ifconfig "${_bridge}" |grep 'member: tap' |awk '{print $2}')
local _port _guest
for _port in ${_port_list}; do
_guest=$(ifconfig "${_port}" |grep 'description: vmnet' |awk '{print $2}' |cut -d'/' -f2)
echo ""
echo "${_INDENT}virtual-port"
echo "${_INDENT}${_INDENT}device: ${_port}"
echo "${_INDENT}${_INDENT}vm: ${_guest:--}"
done
}
# display the info for one guest
#
# @param string _name name of the guest to display
#
info::guest_show(){
local _name="$1"
local _conf="${VM_DS_PATH}/${_name}/${_name}.conf"
local _INDENT=" "
local _RUN="0"
local _res_mem _b_res_mem _global_run _port _opt _pid
[ -z "${_name}" ] && return 1
[ ! -f "${_conf}" ] && return 1
config::load "${_conf}"
# check local and global runstate
[ -e "/dev/vmm/${_name}" ] && _RUN="1"
vm::running_check "_global_run" "_pid" "${_name}"
_global_run=$(echo "${_global_run}" | tr '[:upper:]' '[:lower:]')
echo "------------------------"
echo "Virtual Machine: ${_name}"
echo "------------------------"
echo "${_INDENT}state: ${_global_run}"
echo "${_INDENT}datastore: ${VM_DS_NAME}"
# basic guest configuration
info::__output_config "loader" "" "none"
info::__output_config "uuid" "" "auto"
info::__output_config "cpu"
# check for a cpu topology
config::get "_opt" "cpu_sockets"
if [ -n "${_opt}" ]; then
echo -n "${_INDENT}cpu-topology: sockets=${_opt}"
config::get "_opt" "cpu_cores"
[ -n "${_opt}" ] && echo -n ", cores=${_opt}"
config::get "_opt" "cpu_threads"
[ -n "${_opt}" ] && echo -n ", threads=${_opt}"
echo ""
fi
info::__output_config "memory"
# running system details
if [ "${_RUN}" = "1" ]; then
_res_mem=$(bhyvectl --get-stats --vm="${_name}" |grep 'Resident memory' |awk '{print $3}')
if [ -n "${_res_mem}" ]; then
_b_res_mem=$(info::__bytes_human "${_res_mem}")
echo "${_INDENT}memory-resident: ${_res_mem} (${_b_res_mem})"
fi
# show com ports
if [ -e "${VM_DS_PATH}/${_name}/console" ]; then
echo ""
echo "${_INDENT}console-ports"
cat "${VM_DS_PATH}/${_name}/console" | \
while read _port; do
echo "${_INDENT}${_INDENT}${_port%%=*}: ${_port##*=}"
done
# virtio-console devices
info::guest_vtcon
fi
fi
# network interfaces
info::guest_networking
# disks
info::guest_disks
# zfs data
info::guest_zfs
echo ""
}
# display any virtio consoles
#
info::guest_vtcon(){
local _INDENT=" "
local _console _num=0
config::get "_console" "virt_console0"
[ -z "${_console}" ] && return 0
echo ""
while [ -n "${_console}" -a ${_num} -lt 16 ]; do
# if set to "yes/on/1", just use the console number as port name
case "${_console}" in
[Yy][Ee][Ss]|[Oo][Nn]|1) _console="${_num}" ;;
esac
echo "${_INDENT}vtcon${_num}: ${VM_DS_PATH}/${_name}/vtcon.${_console}"
_num=$((_num + 1))
config::get "_console" "virt_console${_num}"
done
}
# display zfs snapshot/origin data
#
info::guest_zfs(){
local _INDENT=" "
local _data
[ -z "${VM_DS_ZFS}" ] && return 1
_data=$(zfs list -o name,used,creation -s creation -rHt snapshot "${VM_DS_ZFS_DATASET}/${_name}" | sed "s/^/${_INDENT}/")
if [ -n "${_data}" ]; then
echo ""
echo " snapshots"
echo "${_data}"
fi
_data=$(zfs get -Ho value origin "${VM_DS_ZFS_DATASET}/${_name}")
[ "${_data}" = "-" ] && return 0
echo ""
echo " clone-origin"
echo " ${_data}"
}
# display disks
#
info::guest_disks(){
local _num=0
local _disk _type _dev _path _size _b_size _used _b_used
local _INDENT=" "
while true; do
config::get "_disk" "disk${_num}_name"
config::get "_type" "disk${_num}_type"
config::get "_dev" "disk${_num}_dev" "file"
[ -z "${_disk}" -o -z "${_type}" ] && break
vm::get_disk_path "_path" "${_name}" "${_disk}" "${_dev}"
echo ""
echo " virtual-disk"
echo "${_INDENT}number: ${_num}"
info::__output_config "disk${_num}_dev" "device-type" "file"
info::__output_config "disk${_num}_type" "emulation"
info::__output_config "disk${_num}_opts" "options"
echo "${_INDENT}system-path: ${_path:--}"
_size=""
_used=""
if [ -n "${_path}" ]; then
case "${_dev}" in
file)
_size=$(stat -f%z "${_path}")
_used=$(du "${_path}" | awk '{print $1}')
_used=$((_used * 1024))
;;
zvol|sparse-zvol)
_size=$(zfs get -Hp volsize "${_path#/dev/zvol/}" |cut -f3)
_used=$(zfs get -Hp refer "${_path#/dev/zvol/}" |cut -f3)
;;
iscsi)
_size=$(sysctl -b kern.geom.conftxt | awk "/ ${_path#/dev/} /{print \$4}")
_used=${_size}
esac
if [ -n "${_size}" -a -n "${_used}" ]; then
_b_size=$(info::__bytes_human "${_size}")
_b_used=$(info::__bytes_human "${_used}")
echo "${_INDENT}bytes-size: ${_size} (${_b_size})"
echo "${_INDENT}bytes-used: ${_used} (${_b_used})"
fi
fi
_num=$((_num + 1))
done
}
# display networking configuration
#
info::guest_networking(){
local _num=0
local _int _id _tag _switch _stats _b_in _b_out
local _INDENT=" "
while true; do
config::get "_int" "network${_num}_type"
[ -z "${_int}" ] && break
echo ""
echo " network-interface"
echo "${_INDENT}number: ${_num}"
# basic interface config
info::__output_config "network${_num}_type" "emulation"
info::__output_config "network${_num}_switch" "virtual-switch"
info::__output_config "network${_num}_mac" "fixed-mac-address"
info::__output_config "network${_num}_device" "fixed-device"
# if running, try to get some more interface details
if [ "${_RUN}" = "1" ]; then
config::get "_switch" "network${_num}_switch"
_int=$(ifconfig | grep -B1 "vmnet/${_name}/${_num}/" | head -n1 | cut -d' ' -f1,6)
_id=${_int%%:*}
_tag=$(ifconfig | grep "vmnet/${_name}/${_num}/" | cut -d' ' -f2)
info::__find_bridge "_bridge" "${_id}"
echo "${_INDENT}active-device: ${_id:--}"
echo "${_INDENT}desc: ${_tag:--}"
echo "${_INDENT}mtu: ${_int##* }"
echo "${_INDENT}bridge: ${_bridge:--}"
if [ -n "${_id}" ]; then
_stats=$(netstat -biI "${_id}" |grep '<Link#' |tail -n1 |awk '{ for (i=NF; i>1; i--) printf("%s ",$i); print $1; }' |awk '{print $2,$5}')
_b_in=$(info::__bytes_human "${_stats%% *}")
_b_out=$(info::__bytes_human "${_stats##* }")
echo "${_INDENT}bytes-in: ${_stats%% *} (${_b_in})"
echo "${_INDENT}bytes-out: ${_stats##* } (${_b_out})"
fi
fi
_num=$((_num + 1))
done
}
# output a single configuration variable
# alwasy called once guest configuration has been loaded
#
# @param string _option config option to display
# @param optional string _title title to display instead of using option name
# @param optional string _default default value to display if not -
#
info::__output_config(){
local _option="$1"
local _title="$2"
local _default="$3"
local _var
config::get "_var" "${_option}" "${_default:--}"
[ -z "${_title}" ] && _title="${_option}"
echo "${_INDENT}${_title}: ${_var}"
}
# try and find the bridge an interface is a member of.
# we do this rather than just use switch::id as
# this should be able to locate the bridge even for devices
# that have been bridged manually and have no switch name configured
#
# @param string _var variable to put value into
# @param string _interface interface to look for
#
info::__find_bridge(){
local _var="$1"
local _interface="$2"
local _br _found
for _br in ${_bridge_list}; do
_found=$(ifconfig "${_br}" |grep member: |awk '{print $2}' |tr "\n" "," | grep "${_interface},")
if [ -n "${_found}" ]; then
setvar "${_var}" "${_br}"
return 0
fi
done
setvar "${_var}" ""
return 1
}
# format bytes to human readable
# convert to k,m,g or t
# output rounded number
#
# @param int _val the value to convert
#
info::__bytes_human(){
local _val="$1" _int _ext
local _dec="$2"
local _num="$3"
: ${_dec:=3}
: ${_val:=0}
: ${_num:=1}
_int=${_val%%.*}
while [ ${_int} -ge 1024 -a ${_num} -lt 5 ]; do
_val=$(echo "scale=3; ${_val}/1024" | bc)
_int=${_val%%.*}
_num=$((_num + 1))
done
case "${_num}" in
1) _ext="B" ;;
2) _ext="K" ;;
3) _ext="M" ;;
4) _ext="G" ;;
5) _ext="T" ;;
esac
export LC_ALL="C"
printf "%.${_dec}f%s" "${_val}" "${_ext}"
}
info::__find_iscsi() {
local _var="$1"
# _target format: [iqn.*]unique_name[/N] where N is the lun# and defaults
# to 0. The address before the unique_name can be omitted so long as
# the unique_name is sufficient to select only one output of iscsictl -L.
local _target="$2"
local _lun _col _found
# If no lun is specified, assume /0
_lun=${_target##*/}
[ "${_lun}" = "${_target}" ] && _lun=0
_target=${_target%/*}
_found=$(iscsictl -L -w 10 | grep ${_target} | wc -l)
if [ "${_found}" -ne 1 ]; then
setvar "${_var}" ""
util::err "Unable to locate unique iSCSI device ${_target}"
fi
# _col to be the column of iscsictl -L we want
_col=$((_lun + 4))
_found=$(iscsictl -L | awk "/${_target}/{print \$${_col}}")
if echo "${_found}" | egrep -q '^da[0-9]+$'; then
setvar "${_var}" /dev/"${_found}"
return 0
fi
util::err "Unable to locate iSCSI device ${_target}/${_lun}"
}

278
lib/vm-migration Normal file
View File

@ -0,0 +1,278 @@
#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (C) 2021 Matt Churchyard (churchers@gmail.com)
# All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# vm migrate ...
#
# @param string name the guest to send
# @param string host host to send guest to
#
migration::run(){
local _name
local _ds="default"
local _start="1"
local _renaming="0"
local _config _opt _stage _inc _triple _rdataset _pid _exists _rname _running
local _snap1 _snap2 _snap3 _destroy
local _count=0
while getopts cn12txr:d:i: _opt; do
case $_opt in
c) _config="1" ;;
r) _rname="${OPTARG}" ;;
n) _start="" ;;
i) _inc="${OPTARG}" ;;
1) _stage="1" ;;
2) _stage="2" ;;
t) _triple="1" ;;
x) _destroy="1" ;;
d) _ds="${OPTARG}" ;;
esac
done
# get the name and host
shift $((OPTIND -1))
_name="$1"
_host="$2"
# do we want to output our config?
# sender uses the config option to pull config from the recieve end
if [ -n "${_config}" ]; then
migration::__check_config "${_ds}"
exit
fi
# basic checks
[ -z "${_name}" -o -z "${_host}" ] && util::usage
datastore::get_guest "${_name}" || util:err "unable to locate guest - '${_name}'"
[ -z "${VM_DS_ZFS}" ] && util:err "the source datastore must be ZFS to support migration"
[ -n "${_stage}" -a -n "${_triple}" ] && util::err "single stage and triple stage are mutually exclusive"
[ "${_stage}" = "2" -a -z "${_inc}" ] && util::err "source snapshot must be given when running stage 2"
if [ -n "${_rname}" ]; then
util::check_name "${_rname}" || util::err "invalid virtual machine name - '${_rname}'"
_renaming="1"
else
_rname="${_name}"
fi
# check guest can be sent
config::load "${VM_DS_PATH}/${_name}/${_name}.conf"
migration::__check_compat
# check running state
vm::confirm_stopped "${_name}" "1" >/dev/null 2>&1
_state=$?
[ ${_state} -eq 2 ] && util::err "guest is powered up on another host"
[ ${_state} -eq 1 ] && _running="1"
# try to get pid
if [ -n "${_running}" ]; then
_pid=$(pgrep -fx "bhyve: ${_name}")
[ -z "${_pid}" ] && util::err "guest seems to be running but can't find its pid"
fi
# try to get remote config
_rdataset=$(ssh "${_host}" vm migrate -cd "${_ds}" 2>/dev/null)
[ $? = "1" -o -z "${_rdataset}" ] && util::err "unable to get config from ${_host}"
echo "Attempting to send ${_name} to ${_host}"
echo " * remote dataset ${_rdataset}/${_rname}"
[ -n "${_running}" ] && echo " * source guest is powered on (#${_pid})"
# STAGE 1
# we send a full snapshot of the guest
if [ -z "${_stage}" -o "${_stage}" = "1" ]; then
_snap1="$(date +'%Y%m%d%H%M%S-s1')"
echo " * stage 1: taking snapshot ${_snap1}"
zfs snapshot -r "${VM_DS_ZFS_DATASET}/${_name}@${_snap1}" >/dev/null 2>&1
[ $? -eq 0 ] || util::err_inline "error taking local snapshot"
# send this snapshot
migrate::__send "1" "${_snap1}" "${_inc}"
fi
# STAGE 1B
# do it again in triple mode
# for a big guest, hopefully not too much changed during full send
# this will therefore complete fairly quick, leaving very few changes for stage 2
if [ -n "${_triple}" ]; then
_snap2="$(date +'%Y%m%d%H%M%S-s1b')"
echo " * stage 1b: taking snapshot ${_snap2}"
zfs snapshot -r "${VM_DS_ZFS_DATASET}/${_name}@${_snap2}" >/dev/null 2>&1
[ $? -eq 0 ] || util::err_inline "error taking local snapshot"
# send this snapshot
migrate::__send "1b" "${_snap2}" "${_snap1}"
fi
# only running first stage
if [ "${_stage}" = "1" ]; then
echo " * done"
exit
fi
# if it's running we now need to stop it
if [ -n "${_running}" ]; then
echo -n " * stage 2: attempting to stop guest"
kill ${_pid} >/dev/null 2>&1
while [ ${_count} -lt 60 ]; do
sleep 2
kill -0 ${_pid} >/dev/null 2>&1 || break
echo -n "."
_count=$((_count + 1))
done
echo ""
fi
# has it stopped?
kill -0 ${_pid} >/dev/null 2>&1 && util:err_inline "failed to stop guest"
echo " * stage 2: guest powered off"
# only needed if running or specifically doing a stage 2
if [ -n "${_running}" -o "${_stage}" = "2" ]; then
_snap3="$(date +'%Y%m%d%H%M%S-s2')"
echo " * stage 2: taking snapshot ${_snap3}"
zfs snapshot -r "${VM_DS_ZFS_DATASET}/${_name}@${_snap3}" >/dev/null 2>&1
[ $? -eq 0 ] || util::err_inline "error taking local snapshot"
# send this snapshot
if [ "${_triple}" = "1" ]; then
migrate::__send "2" "${_snap3}" "${_snap2}"
elif [ "${_stage}" = "2" ]; then
migrate::__send "2" "${_snap3}" "${_inc}"
else
migrate::__send "2" "${_snap3}" "${_snap1}"
fi
fi
# do we need to rename?
[ "${_renaming}" = "1" ] && migrate::__rename_config
# start
if [ -n "${_start}" -a -n "${_running}" ]; then
echo " * attempting to start ${_rname} on ${_host}"
ssh ${_host} vm start ${_rname}
fi
if [ -n "${_destroy}" ]; then
echo " * removing source guest"
zfs destroy -r "${VM_DS_ZFS_DATASET}/${_name}"
else
echo " * removing snapshots"
[ -n "${_snap1}" ] && zfs destroy "${VM_DS_ZFS_DATASET}/${_name}@${_snap1}" >/dev/null 2>&1
[ -n "${_snap2}" ] && zfs destroy "${VM_DS_ZFS_DATASET}/${_name}@${_snap2}" >/dev/null 2>&1
[ -n "${_snap3}" ] && zfs destroy "${VM_DS_ZFS_DATASET}/${_name}@${_snap3}" >/dev/null 2>&1
fi
echo " * done"
}
# updates the config file for a renamed guest
# god knows why I didn't just use "guest.conf"
#
migrate::__rename_config(){
local _path
# we need the mount path first
_path=$(ssh "${_host}" mount | grep "^${_rdataset} " | cut -wf3)
if [ $? -ne 0 -o -z "${_path}" ]; then
echo " ! failed to find remote datastore path. guest may not start"
return 1
fi
# make sure it's mounted on remote
ssh "${_host}" zfs mount "${_rdataset}/${_rname}" >/dev/null 2>&1
echo " * renaming configuration file to ${_rname}.conf"
ssh "${_host}" mv "${_path}/${_rname}/${_name}.conf" "${_path}/${_rname}/${_rname}.conf" >/dev/null 2>1
if [ $? -ne 0 ]; then
echo " ! failed to find rename remote configuration file. guest may not start"
return 1
fi
}
migrate::__send(){
local _stage="$1"
local _snap="$2"
local _inc="$3"
# are we sending incremental?
if [ -n "${_inc}" ]; then
echo " * stage ${_stage}: sending ${VM_DS_ZFS_DATASET}/${_name}@${_snap} (incremental source ${_inc})"
zfs send -Ri "${_inc}" "${VM_DS_ZFS_DATASET}/${_name}@${_snap}" | ssh ${_host} zfs recv "${_rdataset}/${_rname}"
else
echo " * stage ${_stage}: sending ${VM_DS_ZFS_DATASET}/${_name}@${_snap}"
zfs send -R "${VM_DS_ZFS_DATASET}/${_name}@${_snap}" | ssh ${_host} zfs recv "${_rdataset}/${_rname}"
fi
[ $? -eq 0 ] || util::err_inline "error detected while sending snapshot"
echo " * stage ${_stage}: snapshot sent"
}
# currently just outputs zfs path or error if datastore isn't zfs
# in future may also return some data we can use to verify compat, etc
#
# @param string _ds the datastore to get details of
#
migration::__check_config(){
local _ds="$1"
datastore::get "${_ds}"
[ -z "${VM_DS_ZFS}" ] && exit 1
# output the datastore dataset
# sender needs this to do a zfs recv
echo "${VM_DS_ZFS_DATASET}"
}
# see if a guest can be migrated.
# there are a few guest settings that are likely to
# cause the guest to break if it's moved to another host
#
migration::__check_compat(){
local _setting _err _num=0
# check pass through
config::get "_setting" "passthru0"
[ -n "${_setting}" ] && _err="pci pass-through enabled"
# check for custom disks
# file/zvol are under guest dataset and should go across ok
# custom disks could be anywhere
while true; do
config::get "_setting" "disk${_num}_type"
[ -z "${_setting}" ] && break
[ "${_setting}" = "custom" ] && _err="custom disk(s) configured" && break
_num=$((_num + 1))
done
[ -n "${_err}" ] && util::err "migration is not supported for this guest (${_err})"
}

88
lib/vm-rctl Normal file
View File

@ -0,0 +1,88 @@
#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (C) 2016 ramdaron (https://github.com/ramdaron)
# Copyright (C) 2016 Matt Churchyard (churchers@gmail.com)
# All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# set limits to virtual machine
# this is the background process
#
rctl::set(){
local _pcpu _rbps _wbps _riops _wiops
local _pid _pri
# get limit settings
config::get "_pri" "priority"
config::get "_pcpu" "limit_pcpu"
config::get "_rbps" "limit_rbps"
config::get "_wbps" "limit_wbps"
config::get "_riops" "limit_riops"
config::get "_wiops" "limit_wiops"
# wait till bhyve starts and get pid
sleep 1
_pid=$(pgrep -fx "bhyve[: ].* ${_name}")
[ -z "${_pid}" ] && return 1
# check for a priority
[ -n "${_pri}" ] && renice ${_pri} ${_pid} >/dev/null 2>&1
# return if there are no limits
[ -z "${_pcpu}${_rbps}${_wbps}${_riops}${_wiops}" ] && return 1
# see if rctl works
/usr/bin/rctl >/dev/null 2>&1
[ $? -ne 0 ] && \
util::log "guest" "${_name}" "RCTL support requested but RCTL not available" && return 1
util::log "guest" "${_name}" "applying rctl limits"
if [ -n "${_pcpu}" ]; then
/usr/bin/rctl -a process:${_pid}:pcpu:deny=${_pcpu} >/dev/null 2>&1
[ $? -eq 0 ] && util::log "guest" "${_name}" " pcpu=${_pcpu}"
fi
# at this point we can return if < FreeBSD 11
[ ${VERSION_BSD} -lt 1100000 ] && return 0
if [ -n "${_rbps}" ]; then
/usr/bin/rctl -a process:${_pid}:readbps:throttle=${_rbps} >/dev/null 2>&1
[ $? -eq 0 ] && util::log "guest" " readbps=${_rbps}"
fi
if [ -n "${_wbps}" ]; then
/usr/bin/rctl -a process:${_pid}:writebps:throttle=${_wbps} >/dev/null 2>&1
[ $? -eq 0 ] && util::log "guest" " writebps=${_wbps}"
fi
if [ -n "${_riops}" ]; then
/usr/bin/rctl -a process:${_pid}:readiops:throttle=${_riops} >/dev/null 2>&1
[ $? -eq 0 ] && util::log "guest" " readiops=${_riops}"
fi
if [ -n "${_wiops}" ]; then
/usr/bin/rctl -a process:${_pid}:writeiops:throttle=${_wiops} >/dev/null 2>&1
[ $? -eq 0 ] && util::log "guest" " writeiops=${_wiops}"
fi
}

1028
lib/vm-run Normal file

File diff suppressed because it is too large Load Diff

467
lib/vm-switch Normal file
View File

@ -0,0 +1,467 @@
#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (C) 2016 Matt Churchyard (churchers@gmail.com)
# All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# switch libraries
#
. "${LIB}/vm-switch-netgraph"
. "${LIB}/vm-switch-manual"
. "${LIB}/vm-switch-standard"
. "${LIB}/vm-switch-vale"
. "${LIB}/vm-switch-vxlan"
# create switches from rc list on init
# this should run once per boot to make sure switches from the
# configuration file have bridge interfaces. If any new switches are
# created, the create function takes care of setting them up
#
switch::init(){
local _switchlist _switch _type
config::core::get "_switchlist" "switch_list"
if [ -n "${_switchlist}" ]; then
for _switch in ${_switchlist}; do
# get the switch type
config::core::get "_type" "type_${_switch}"
case "${_type}" in
vxlan) switch::vxlan::init "${_switch}" ;;
manual) switch::manual::init "${_switch}" ;;
vale) ;;
netgraph) ;;
*) switch::standard::init "${_switch}" ;;
esac
done
fi
}
# list switches configured
#
switch::list(){
local _switchlist _switch _type
local _id _format="%s^%s^%s^%s^%s^%s^%s^%s\n"
config::core::get "_switchlist" "switch_list"
{
printf "${_format}" "NAME" "TYPE" "IFACE" "ADDRESS" "PRIVATE" "MTU" "VLAN" "PORTS"
if [ -n "${_switchlist}" ]; then
for _switch in ${_switchlist}; do
# get the switch type
config::core::get "_type" "type_${_switch}"
case "${_type}" in
netgraph) switch::netgraph::show "${_switch}" "${_format}" ;;
vale) switch::vale::show "${_switch}" "${_format}" ;;
vxlan) switch::vxlan::show "${_switch}" "${_format}" ;;
manual) switch::manual::show "${_switch}" "${_format}" ;;
*) switch::standard::show "${_switch}" "${_format}" ;;
esac
done
fi
} | column -ts^
}
# create a new virtual switch
#
# @param string _switch name of the switch to create
#
switch::create(){
local _switch
local _type="standard"
local _list _curr _vlan _if _bridge _addr _mtu _priv
# process options
while getopts t:i:n:b:a:m:p _opt; do
case ${_opt} in
t) _type="${OPTARG}" ;;
i) _if="${OPTARG}" ;;
n) _vlan="${OPTARG}" ;;
b) _bridge="${OPTARG}" ;;
a) _addr="${OPTARG}" ;;
m) _mtu="${OPTARG}" ;;
p) _priv="yes" ;;
*) util::usage ;;
esac
done
shift $((OPTIND - 1))
_switch="$1"
# check for a valid switch name
util::check_name "${_switch}" || util::err "invalid switch name - '${_switch}'"
# make sure it's not an existing name
config::core::get "_list" "switch_list"
for _curr in ${_list}; do
[ "${_switch}" = "${_curr}" ] && util::err "switch ${_switch} already exists"
done
# check vlan number
if [ -n "${_vlan}" ]; then
echo "${_vlan}" | egrep -qs '^[0-9]{1,4}$'
[ $? -eq 0 ] || util::err "invalid vlan number"
[ ${_vlan} -ge 4095 ] && util::err "invalid vlan number"
fi
# check address
if [ -n "${_addr}" ]; then
echo "${_addr}" | egrep -qs '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,2}$'
[ $? -eq 0 ] || util::err "address must be supplied in CIDR notation (a.b.c.d/prefix-len)"
fi
# check mtu
if [ -n "${_mtu}" ]; then
echo "${_mtu}" | egrep -qs '^[0-9]{3,4}$'
[ $? -eq 0 ] || util::err "invalid mtu"
[ ${_mtu} -gt 9000 ] && util::err "invalid mtu"
fi
# check switch type
case "${_type}" in
standard) switch::standard::create ;;
manual) switch::manual::create ;;
netgraph) switch::netgraph::create ;;
vale) switch::vale::create ;;
vxlan) switch::vxlan::create ;;
*) util::err "invalid switch type - '${_type}'" ;;
esac
}
# destroy a switch
# remove from configuration and unload any interfaces we created
#
# @param string _switch name of the switch to remove
#
switch::remove(){
local _switch="$1"
local _type
[ -z "${_switch}" ] && util::usage
# get the type of switch
config::core::get "_type" "type_${_switch}"
case "${_type}" in
standard) switch::standard::remove "${_switch}" ;;
manual) switch::manual::remove "${_switch}" ;;
netgraph) switch::netgraph::remove "${_switch}" ;;
vale) switch::vale::remove "${_switch}" ;;
vxlan) switch::vxlan::remove "${_switch}" ;;
*) util::err "unable to remove switch of unknown type" ;;
esac
# remove all configuration if there's no error
if [ $? -eq 0 ]; then
config::core::remove "switch_list" "${_switch}"
config::core::remove "ports_${_switch} vlan_${_switch} nat_${_switch} type_${_switch}"
config::core::remove "addr_${_switch} private_${_switch} mtu_${_switch}"
else
util::err "failed to remove virtual switch"
fi
# make sure the exit status indicates success,
# even if config::core::remove did not
return 0
}
# add a new interface to a switch
#
# @param string _switch name of the switch
# @param string _if the interface to add
#
switch::add_member(){
local _switch="$1"
local _if="$2"
local _type
[ -z "${_switch}" -o -z "${_if}" ] && util::usage
# get the type of switch
config::core::get "_type" "type_${_switch}"
case "${_type}" in
standard) switch::standard::add_member "${_switch}" "${_if}" ;;
manual) switch::manual::add_member "${_switch}" "${_if}" ;;
netgraph) switch::netgraph::add_member "${_switch}" "${_if}" ;;
vale) switch::vale::add_member "${_switch}" "${_if}" ;;
vxlan) switch::vxlan::add_member "${_switch}" "${_if}" ;;
*) util::err "unable to configure switch of unknown type" ;;
esac
}
# remove a member interface from a virtual switch
#
# @param string _switch name of the switch
# @param string _if the interface to remove
#
switch::remove_member(){
local _switch="$1"
local _if="$2"
local _type
[ -z "${_switch}" -o -z "${_if}" ] && util::usage
# get the type of switch
config::core::get "_type" "type_${_switch}"
case "${_type}" in
standard) switch::standard::remove_member "${_switch}" "${_if}" ;;
manual) switch::manual::remove_member "${_switch}" "${_if}" ;;
netgraph) switch::netgraph::remove_member "${_switch}" "${_if}" ;;
vale) switch::vale::remove_member "${_switch}" "${_if}" ;;
vxlan) switch::vxlan::remove_member "${_switch}" "${_if}" ;;
*) util::err "unable to configure switch of unknown type" ;;
esac
}
# change the vlan number on a virtual switch
#
# @param string _switch name of the switch
# @param int _vlan the vlan number (0 to turn vlan off)
#
switch::vlan(){
local _switch="$1"
local _vlan="$2"
local _id _type
[ -z "${_switch}" -o -z "${_vlan}" ] && util::usage
switch::id "_id" "${_switch}"
switch::type "_type" "${_switch}"
[ -z "${_id}" ] && util::err "unable to locate specified virtual switch"
echo "${_vlan}" | egrep -qs '^[0-9]{1,4}$'
[ $? -eq 0 ] || util::err "invalid vlan number"
[ ${_vlan} -ge 4095 ] && util::err "invalid vlan number"
case "${_type}" in
standard) switch::standard::vlan "${_switch}" "${_vlan}" ;;
manual) switch::manual::vlan "${_switch}" "${_vlan}" ;;
netgraph) switch::netgraph::vlan "${_switch}" "${_vlan}" ;;
vale) switch::vale::vlan "${_switch}" "${_vlan}" ;;
vxlan) switch::vxlan::vlan "${_switch}" "${_vlan}" ;;
*) util::err "unable to configure switch of unknown type" ;;
esac
}
# enable or diable private flag on a switch
# note that we don't update existing interfaces; this
# makes things easy for us and any guests booted after
# will get the new setting
#
# @param string _switch the switch to update
# @param string _priv on,yes|off,no
#
switch::private(){
local _switch="$1"
local _priv="$2"
local _type
# try to get switch type
[ -z "${_switch}" -o -z "${_priv}" ] && util::usage
switch::type "_type" "${_switch}" || util::err "specified switch does not appear to be valid"
case "${_type}" in
standard|manual|vxlan)
if util::yesno "${_priv}"; then
config::core::set "private_${_switch}" "yes"
else
config::core::set "private_${_switch}" "no"
fi
;;
netgraph)
util::err "unable to configure private mode on netgraph switches"
;;
vale)
util::err "unable to configure private mode on vale switches"
;;
*)
util::err "unable to configure switch of unknown type"
;;
esac
}
# enable or disable nat functionality on a virtual switch
#
# @param string _switch name of the switch
# @param string _nat on|off
#
switch::nat(){
util::warn "internal nat support is currently disabled"
util::warn "please add an address to the virtual switch and configure your firewall for NAT manually"
}
# set or remove ip address from a virtual switch
#
# @param string _switch name of the switch
# @param string _addr the ip address to add (or "none" to remove
#
switch::address(){
local _switch="$1"
local _addr="$2"
local _id _type
[ -z "${_switch}" ] && util::usage
switch::id "_id" "${_switch}"
switch::type "_type" "${_switch}"
[ -z "${_id}" ] && util::err "unable to locate specified virtual switch"
# check address
if [ -n "${_addr}" ] && [ "${_addr}" != "none" ]; then
echo "${_addr}" | egrep -qs '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,2}$'
[ $? -eq 0 ] || util::err "address must be supplied in CIDR notation (a.b.c.d/prefix-len)"
fi
case "${_type}" in
standard) switch::standard::address "${_switch}" "${_addr}" ;;
manual) ;&
netgraph) ;&
vale) ;&
vxlan) util::err "feature not currently supported on switches of this type" ;;
*) util::err "unable to configure switch of unknown type" ;;
esac
}
# return the type for a switch
#
# @param string _var variable to put type into
# @param string _switch the switch name
#
switch::type(){
local _var="$1"
local _switch="$2"
[ -z "${_switch}" ] && return 1
config::core::get "${_var}" "type_${_switch}" "standard"
}
# check if a switch is configured for private members
#
# @param string _switch switch name
# @return succes (0) if it's private
#
switch::is_private(){
local _switch="$1"
local _priv
config::core::get "_priv" "private_${_switch}"
util::yesno "${_priv}"
}
# get the bridge id for a virtual switch
#
# @param string _var variable to put name into
# @param string _switch the name of the switch
#
switch::id(){
local _var="$1"
local _switch="$2"
local _type
[ -z "${_switch}" ] && return 1
# get switch type
config::core::get "_type" "type_${_switch}"
case "${_type}" in
vale) switch::vale::id "${_var}" "${_switch}" ;;
netgraph) switch::netgraph::id "${_var}" "${_switch}" ;;
manual) switch::manual::id "${_var}" "${_switch}" ;;
*) switch::standard::id "${_var}" "${_switch}" ;;
esac
}
# get a virt interface id for a port/switch
#
# @param string _var variable name to put result into
# @param string _switch switch name to get id for
#
switch::__viid(){
local _hash=$(md5 -qs "${2}" | cut -c1-5)
setvar "$1" "viid-${_hash}@"
}
# retrieve interface name, given a switch name
# we convert to viid then look for the matching group
#
# @param string _var variable to put interface name into
# @param string _switch the switch name
#
switch::find(){
local _var="$1"
local _switch="$2"
local _viid _name
switch::__viid "_viid" "${_switch}"
_name=$(ifconfig -g "${_viid}" 2>/dev/null)
[ -n "${_name}" ] && setvar "${_var}" "${_name}"
}
# mark an interface with a unique viid
# i say unique, its 5 chars from an md5 hash which
# should be enough for half a dozen switches
#
# @parem string _switch switch name
# @param string _iface interface to mark
#
switch::set_viid(){
local _switch="$1"
local _iface="$2"
local _viid
switch::__viid "_viid" "${_switch}"
ifconfig "${_iface}" group "${_viid}" >/dev/null 2>&1
}
# create a network interface for a guest
# relies heavily on variables set in the main vm::run function
#
# @modifies _func _devices
#
switch::provision(){
local _switch _mac _type
config::get "_switch" "network${_num}_switch"
config::get "_mac" "network${_num}_mac"
# set a static mac if we don't have one
[ -z "${_mac}" ] && vm::generate_static_mac
switch::type "_type" "${_switch}"
case "${_type}" in
vale) switch::vale::provision ;;
netgraph) switch::netgraph::provision ;;
standard) ;&
manual) ;&
vxlan) switch::standard::provision ;;
*) util::warn "unable to configure interface ${_num}" ;;
esac
}

142
lib/vm-switch-manual Normal file
View File

@ -0,0 +1,142 @@
#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (C) 2016 Matt Churchyard (churchers@gmail.com)
# All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# for a manual switch the bridge interface should already exist
# we just assign our description so it's visible in ifconfig
#
# @param string _name name of the switch
#
switch::manual::init(){
local _name="$1"
local _bridge="$2"
# get bridge
if [ -z "${_bridge}" ]; then
config::core::get "_bridge" "bridge_${_name}"
[ -z "${_bridge}" ] && return 1
fi
# don't rename custom bridges nor set a description.
# manual interfaces are fully configured using rc.conf.
ifconfig "${_bridge}" group vm-switch up >/dev/null 2>&1
switch::set_viid "${_name}" "${_bridge}"
}
# show the configuration details for a manual switch
#
# @param string _name the switch name
# @param string _format output format
#
switch::manual::show(){
local _name="$1"
local _format="$2"
local _bridge _priv
config::core::get "_bridge" "bridge_${_name}"
config::core::get "_priv" "private_${_name}" "no"
printf "${_format}" "${_name}" "manual" "${_bridge}" "n/a" "${_priv}" "n/a" "n/a" "n/a"
}
# create a manual switch
# we just assign our switch name to the existing bridge
#
switch::manual::create(){
# we need to have a bridge
[ -z "${_bridge}" ] && util::err "you must specify a bridge to import when creating a manual switch"
# check we can find this bridge on the system
ifconfig "${_bridge}" >/dev/null 2>&1
[ $? -eq 0 ] || util::err "${_bridge} does not appear to be a valid existing bridge"
# store configuration
config::core::set "switch_list" "${_switch}" "1"
config::core::set "type_${_switch}" "manual"
config::core::set "bridge_${_switch}" "${_bridge}"
[ -n "${_priv}" ] && config::core::set "private_${_switch}" "${_priv}"
# import
switch::manual::init "${_switch}" "${_bridge}"
}
# remove a manual switch
#
# @param string _switch the name of the switch
#
switch::manual::remove(){
local _switch="$1"
local _id
switch::manual::id "_id" "${_switch}"
[ -z "${_id}" ] && return 0
# try to remove our description
# viid stays but it's not worth the extra hassle to remove that
ifconfig ${_id} -descr -group vm-switch >/dev/null 2>&1
}
# add a new interface to this switch
# manual switches should be managed by the user
# using rc.conf, hence "manual"
#
# @param string _switch name of the switch
# @param string _if the interface to add
#
switch::manual::add_member(){
util::err "manual switches and member interfaces should be configured using /etc/rc.conf"
}
# remove an interface
# manual switches should be managed by the user
# using rc.conf, hence "manual"
#
# @param string _switch name of the switch
# @param string _if the interface to remove
#
switch::manual::remove_member(){
util::err "manual switches and member interfaces should be configured using /etc/rc.conf"
}
# set vlan id
#
# @param string _switch name of switch
# @param int _vlan vlan id to set
#
switch::manual::vlan(){
util::err "manual switches and member interfaces should be configured using /etc/rc.conf"
}
# get id for a manual switch
# in this case we need to return the bridge name
#
# @param string _var variable to put id into
# @param string _switch switch name
# @return 0 if switch id found
#
switch::manual::id(){
config::core::get "$1" "bridge_$2"
}

119
lib/vm-switch-netgraph Normal file
View File

@ -0,0 +1,119 @@
#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (C) 2021 Benoit Chesneau (bchesneau@gmail.com)
# All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# show the configuration details for a netgraph switch
#
# @param string _name the switch name
# @param string _format output format
#
switch::netgraph::show(){
local _name="$1"
local _format="$2"
local _id
switch::netgraph::id "_id" "${_name}"
printf "${_format}" "${_name}" "netraph" "${_id}" "n/a" "n/a" "n/a" "n/a" "n/a"
}
# create a netgraph switch
#
# @param string _switch the name of the switch
#
switch::netgraph::create(){
config::core::set "switch_list" "${_switch}" "1"
config::core::set "type_${_switch}" "netgraph"
}
# remove a netgraph switch
#
switch::netgraph::remove(){ }
# add a new interface to this switch
# at the moment we require the user to manually
# set up any netgraph switches
#
# @param string _switch name of the switch
# @param string _if the interface to add
#
switch::netgraph::add_member(){
util::err "physical interfaces must be added to the netgraph switch manually"
}
# remove an interface
#
# @param string _switch name of the switch
# @param string _if the interface to remove
#
switch::netgraph::remove_member(){
util::err "physical interfaces must be removed from the netgraph switch manually"
}
# set vlan id
#
# @param string _switch name of switch
# @param int _vlan vlan id to set
#
switch::netgraph::vlan(){
util::err "vlan support is not currently implemented for netgraph switches"
}
# gets a unique linkname name for a ng_bridge interface
# we need to make sure the link is unique and the last one
#
# @param string _var name of variable to put port name into
# @param string _switch the name of the switch
#
switch::netgraph::id(){
local _var="$1"
local _switch="$2"
# Create a new interface to the bridge
num=2
while ngctl msg "${_switch}:" getstats $num > /dev/null 2>&1
do
num=$(( $num + 1 ))
done
setvar "${_var}" "netgraph,path=${_switch}:,peerhook=link$num"
}
# create a netgraph interface for a guest
# relies heavily on variables set in the main vm::run function
#
# @modifies _func _devices
# @return 1 if we don't get a tap device
#
switch::netgraph::provision(){
local _ngid
# create a netgraph peer
switch::netgraph::id "_ngid" "${_switch}"
util::log "guest" "${_name}" "adding netgraph interface ${_ngid} (${_switch})"
_devices="${_devices} -s ${_slot}:${_func},${_emulation},${_ngid}"
[ -n "${_mac}" ] && _devices="${_devices},mac=${_mac}"
_func=$((_func + 1))
}

405
lib/vm-switch-standard Normal file
View File

@ -0,0 +1,405 @@
#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (C) 2016 Matt Churchyard (churchers@gmail.com)
# All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# creaate bridge interface for a standard switch
#
# @param string _name name of the switch
#
switch::standard::init(){
local _name="$1"
local _id _addr _mtu _len _ifconf
# see if it already exists
switch::standard::id "_id" "${_name}" && return 0
# get the length of the switch name
# it's useful for other utilities to use switch name as interface name
# as it's static. can't do that if it's > 12 chars
_len=$(echo -n "${_name}" | wc -m)
if [ ${_len} -le 12 ]; then
_ifconf="name vm-${_name}"
else
_ifconf="descr vm/${_name}"
fi
# create a bridge for this switch
_id=$(ifconfig bridge create ${_ifconf} group vm-switch up 2>/dev/null)
[ $? -eq 0 ] || util::err "failed to create bridge interface for switch ${_name}"
switch::set_viid "${_name}" "${_id}"
# randomise mac if feature is available
[ ${VERSION_BSD} -ge 1102000 ] && ifconfig "${_id}" link random
# try to set ip address
config::core::get "_addr" "addr_${_name}"
[ -n "${_addr}" ] && ifconfig "${_id}" inet ${_addr} 2>/dev/null
config::core::get "_addr" "addr6_${_name}"
[ -n "${_addr}" ] && ifconfig "${_id}" inet6 ${_addr} 2>/dev/null
# custom mtu?
config::core::get "_mtu" "mtu_${_name}"
[ -n "${_mtu}" ] && ifconfig "${_id}" mtu ${_mtu}
# add member interfaces
switch::standard::__add_members "${_name}" "${_id}"
}
# show the configuration details for a switch
#
# @param string _name the switch name
# @param string _format output format
#
switch::standard::show(){
local _name="$1"
local _format="$2"
local _id _vlan _ports _addr _mtu _priv
switch::find "_id" "${_name}"
config::core::get "_ports" "ports_${_name}"
config::core::get "_vlan" "vlan_${_name}"
config::core::get "_addr" "addr_${_name}"
config::core::get "_mtu" "mtu_${_name}"
config::core::get "_priv" "private_${_name}" "no"
printf "${_format}" "${_name}" "standard" "${_id:--}" "${_addr:--}" "${_priv}" "${_mtu:--}" \
"${_vlan:--}" "${_ports:--}"
}
# create a standard virtual switch
#
switch::standard::create(){
# store configuration
config::core::set "switch_list" "${_switch}" "1"
config::core::set "type_${_switch}" "standard"
[ -n "${_if}" ] && config::core::set "ports_${_switch}" "${_if}"
[ -n "${_vlan}" ] && config::core::set "vlan_${_switch}" "${_vlan}"
[ -n "${_addr}" ] && config::core::set "addr_${_switch}" "${_addr}"
[ -n "${_priv}" ] && config::core::set "private_${_switch}" "${_priv}"
[ -n "${_mtu}" ] && config::core::set "mtu_${_switch}" "${_mtu}"
config::core::load
switch::standard::init "${_switch}"
}
# destroy a standard switch
#
# @param string _switch name of the switch to destroy
#
switch::standard::remove(){
local _switch="$1"
local _id
# get the bridge id
switch::standard::id "_id" "${_switch}"
[ $? -eq 0 ] || return 1
# remove all member interfaces
switch::standard::__remove_members "${_switch}" "${_id}"
# destroy the bridge
ifconfig "${_id}" destroy >/dev/null 2>&1
}
# add a new interface to this switch
#
# @param string _switch name of the switch
# @param string _if the interface to add
#
switch::standard::add_member(){
local _switch="$1"
local _if="$2"
local _id _vlan _mtu
switch::standard::id "_id" "${_switch}" || util::err "unable to locate switch id"
config::core::get "_vlan" "vlan_${_switch}"
config::core::get "_mtu" "mtu_${_switch}"
switch::standard::__configure_port "${_switch}" "${_id}" "${_if}" "${_vlan}" "${_mtu}"
config::core::set "ports_${_switch}" "${_if}" "1"
}
# remove a member interface from this switch
#
# @param string _switch name of the switch
# @param string _if the interface to remove
#
switch::standard::remove_member(){
local _switch="$1"
local _if="$2"
local _id _vlan
switch::standard::id "_id" "${_switch}" || util::err "unable to locate switch id"
config::core::remove "ports_${_switch}" "${_if}"
config::core::get "_vlan" "vlan_${_switch}"
switch::standard::__unconfigure_port "${_switch}" "${_id}" "${_if}" "${_vlan}"
}
# set vlan id
#
# @param string _switch name of switch
# @param int _vlan vlan id to set
#
switch::standard::vlan(){
local _switch="$1"
local _vlan="$2"
local _id
switch::standard::id "_id" "${_switch}" || util::err "unable to locate switch id"
switch::standard::__remove_members "${_switch}" "${_id}"
# update configuration
if [ "${_vlan}" = "0" ]; then
config::core::remove "vlan_${_switch}"
else
config::core::set "vlan_${_switch}" "${_vlan}"
fi
config::core::load
switch::standard::__add_members "${_switch}" "${_id}"
}
# set or remove ip address
#
# @param string _swtich name of the switch
# @param string _addr address or "none"
# @scope _id switch if from parent switch::address
#
switch::standard::address(){
local _switch="$1"
local _addr="$2"
local _curr
if [ "${_addr}" = "none" ]; then
config::core::get "_curr" "addr_${_switch}"
[ $? -eq 0 ] || util::err "unable to locate an existing address for this switch"
config::core::remove "addr_${_switch}"
ifconfig "${_id}" "${_curr}" delete
else
config::core::set "addr_${_switch}" "${_addr}"
ifconfig "${_id}" "${_addr}"
fi
}
# add all member interfaces to a switch
#
# @param string _switch the name of the switch
# @param string _id interface id for the switch
#
switch::standard::__add_members(){
local _switch="$1"
local _id="$2"
local _ports _vlan _port _mtu
# get the id if not provided
if [ -z "${_id}" ]; then
switch::standard::id "_id" "${_switch}" || util:err "failed to get switch id while adding members"
fi
config::core::get "_ports" "ports_${_switch}"
config::core::get "_vlan" "vlan_${_switch}"
config::core::get "_mtu" "mtu_${_switch}"
if [ -n "${_ports}" ]; then
for _port in ${_ports}; do
switch::standard::__configure_port "${_switch}" "${_id}" "${_port}" "${_vlan}" "${_mtu}"
done
fi
}
# remove member interfaces from a switch
#
# @param string _switch the name of the switch
# @param string _id bridge id if already known
#
switch::standard::__remove_members(){
local _switch="$1"
local _id="$2"
local _ports _port _vlan
# get id if not given to us
if [ -z "${_id}" ]; then
switch::standard::id "_id" "${_switch}"
[ $? -eq 0 ] || util::err "failed to get switch id while removing members"
fi
# get full port list
config::core::get "_ports" "ports_${_switch}"
config::core::get "_vlan" "vlan_${_switch}"
if [ -n "${_ports}" ]; then
for _port in ${_ports}; do
switch::standard::__unconfigure_port "${_switch}" "${_id}" "${_port}" "${_vlan}"
done
fi
}
# configure a local port for our bridge
#
# @param string _switch the switch to add port to
# @param string _id the bridge id of the switch
# @param string _port the interface to add
# @param int _vlan vlan number if assigned to this switch
# @param int _mtu custom mtu to use for this port
#
switch::standard::__configure_port(){
local _switch="$1"
local _id="$2"
local _port="$3"
local _vlan="$4"
local _mtu="$5"
local _viface _vname
# try to set mtu of port?
[ -n "${_mtu}" ] && ifconfig "${_port}" mtu ${_mtu} >/dev/null 2>&1
# vlan enabled?
if [ -n "${_vlan}" ]; then
# see if vlan interface already exists
_vname="${_port}.${_vlan}"
switch::standard::id "_viface" "${_vname}"
# create if needed
if [ $? -ne 0 ]; then
# use our id as the interface name here.
# it should always be a valid name and interface.vlan-id is much easier to understand in ifconfig
# than a bunch of vlanX interfaces
_viface=$(ifconfig vlan create vlandev "${_port}" vlan "${_vlan}" descr "vm-vlan/${_switch}/${_vname}" name "${_vname}" group vm-vlan up 2>/dev/null)
[ $? -eq 0 ] || util::err "failed to create vlan interface for port ${_port} on switch ${_switch}"
fi
switch::set_viid "${_vname}" "${_viface}"
ifconfig ${_id} addm ${_viface} >/dev/null 2>&1
else
# add to bridge, nice and simple :)
ifconfig ${_id} addm ${_port} >/dev/null 2>&1
fi
[ $? -eq 0 ] || util::err "failed to add member ${_port} to the virtual switch ${_switch}"
}
# unconfigure a local port
#
# @param string _switch the switch to remove port from
# @param string _id the bridge id of the switch
# @param string _port the interface to remove
# @param string _vlan vlan number if assigned to this switch
#
switch::standard::__unconfigure_port(){
local _switch="$1"
local _id="$2"
local _port="$3"
local _vlan="$4"
local _vid
if [ -n "${_vlan}" ]; then
# get vlan interface
switch::standard::id "_vid" "${_port}.${_vlan}"
# remove the vlan interface, it will be removed from bridge automatically
[ $? -eq 0 ] && ifconfig ${_vid} destroy >/dev/null 2>&1
else
ifconfig ${_id} deletem ${_port} >/dev/null 2>&1
fi
}
# get the id for a standard switch
# this returns the associated bridge name
#
# @param string _var variable to put id into
# @param string _switch the switch to look for
# @return 0 on success
#
switch::standard::id(){
switch::find "$1" "$2"
}
# creates a standard tap interface for a guest
# relies heavily on variables set in the main vm::run function
#
# @modifies _func _devices
# @return 1 if we don't get a tap device
#
switch::standard::provision(){
local _tap _custom_tap _sid _mtu _member_type _iname
config::get "_custom_tap" "network${_num}_device"
config::get "_iname" "network${_num}_name"
# create interface
if [ -n "${_custom_tap}" ]; then
_tap="${_custom_tap}"
elif [ -n "${_iname}" ]; then
_tap=$(ifconfig tap create name "${_iname}")
else
_tap=$(ifconfig tap create)
fi
[ -z "${_tap}" ] && return 1;
util::log "guest" "${_name}" "initialising network device ${_tap}"
ifconfig "${_tap}" descr "vmnet/${_name}/${_num}/${_switch:-custom}" group vm-port >/dev/null 2>&1
if [ -n "${_switch}" ]; then
switch::id "_sid" "${_switch}"
# should this be a span member?
_member_type="addm"
config::yesno "network${_num}_span" && _member_type="span"
if [ -n "${_sid}" ]; then
_mtu=$(ifconfig "${_sid}" | head -n1 | awk '{print $NF}')
if [ "${_mtu}" != "1500" ]; then
util::log "guest" "${_name}" "setting mtu of ${_tap} to ${_mtu}"
ifconfig "${_tap}" mtu "${_mtu}" >/dev/null 2>&1
fi
util::log "guest" "${_name}" "adding ${_tap} -> ${_sid} (${_switch} ${_member_type})"
ifconfig "${_sid}" "${_member_type}" "${_tap}" >/dev/null 2>&1 || util::log "guest" "${_name}" "failed to add ${_tap} to ${_sid}"
util::log "guest" "${_name}" "bring up ${_tap} -> ${_sid} (${_switch} ${_member_type})"
ifconfig "${_tap}" up >/dev/null 2>&1 || util::log "guest" "${_name}" "failed to bring up ${_tap} in ${_sid}"
# set private if configured
switch::is_private "${_switch}" && ifconfig "${_sid}" "private" "${_tap}" >/dev/null 2>&1
else
util::log "guest" "${_name}" "failed to find virtual switch '${_switch}'"
fi
fi
_devices="${_devices} -s ${_slot}:${_func},${_emulation},${_tap}"
[ -n "${_mac}" ] && _devices="${_devices},mac=${_mac}"
_func=$((_func + 1))
[ -z "${_custom_tap}" ] && _taplist="${_taplist}${_taplist:+ }${_tap}"
}

128
lib/vm-switch-vale Normal file
View File

@ -0,0 +1,128 @@
#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (C) 2016 Matt Churchyard (churchers@gmail.com)
# All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# show the configuration details for a vale switch
#
# @param string _name the switch name
# @param string _format output format
#
switch::vale::show(){
local _name="$1"
local _format="$2"
local _id
switch::vale::id "_id" "${_name}"
printf "${_format}" "${_name}" "vale" "${_id}" "n/a" "n/a" "n/a" "n/a" "n/a"
}
# create a vale switch
#
# @param string _switch the name of the switch
#
switch::vale::create(){
config::core::set "switch_list" "${_switch}" "1"
config::core::set "type_${_switch}" "vale"
}
# remove a vale switch
#
switch::vale::remove(){ }
# add a new interface to this switch
# at the moment we require the user to manually
# set up any vale switches
#
# @param string _switch name of the switch
# @param string _if the interface to add
#
switch::vale::add_member(){
util::err "physical interfaces must be added to the vale switch manually"
}
# remove an interface
#
# @param string _switch name of the switch
# @param string _if the interface to remove
#
switch::vale::remove_member(){
util::err "physical interfaces must be removed from the vale switch manually"
}
# set vlan id
#
# @param string _switch name of switch
# @param int _vlan vlan id to set
#
switch::vale::vlan(){
util::err "vlan support is not currently implemented for vale switches"
}
# gets a unique port name for a vale interface
# we need to make sure vale switch name is the same
# for all interfaces on the same switch, but port is
# different
#
# @param string _var name of variable to put port name into
# @param string _switch the name of the switch
# @param string _port unique port identifier (usually mac address)
#
switch::vale::id(){
local _var="$1"
local _switch="$2"
local _port="$3"
local _id_s _id_p
# get a switch id
_id_s=$(md5 -qs "${_switch}" | cut -c1-5)
# given port?
if [ -n "${_port}" ]; then
_id_p=$(md5 -qs "${_port}" | cut -c1-5)
setvar "${_var}" "vale${_id_s}:${_id_p}"
else
setvar "${_var}" "vale${_id_s}"
fi
}
# create a vale interface for a guest
# relies heavily on variables set in the main vm::run function
#
# @modifies _func _devices
# @return 1 if we don't get a tap device
#
switch::vale::provision(){
local _vale_id
# create a vale port id
switch::vale::id "_vale_id" "${_switch}" "${_mac}"
util::log "guest" "${_name}" "adding vale interface ${_tap} (${_switch})"
_devices="${_devices} -s ${_slot}:${_func},${_emulation},${_vale_id}"
[ -n "${_mac}" ] && _devices="${_devices},mac=${_mac}"
_func=$((_func + 1))
}

204
lib/vm-switch-vxlan Normal file
View File

@ -0,0 +1,204 @@
#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (C) 2016 Matt Churchyard (churchers@gmail.com)
# All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# create a vxlan switch
# we create a bridge and then add the vxlan interface to it
#
# @param string _name name of the switch
#
switch::vxlan::init(){
local _name="$1"
local _id _vlan _if _maddr _addr _ifconf
# see if the bridge already exists
switch::standard::id "_id" "${_name}" && return 0
# need a vlan id and interface
config::core::get "_vlan" "vlan_${_name}"
config::core::get "_if" "ports_${_name}"
[ -z "${_vlan}" -o -z "${_if}" ] && return 1
# get local address for this interface
_local=$(ifconfig ${_if} | grep "inet " | cut -d" " -f 2)
[ -z "${_local}" ] && return 1
# come up with an ip address for multicast data
switch::vxlan::__multicast "_maddr" "${_name}"
# create the vxlan interface
ifconfig "vxlan${_vlan}" create vxlanid "${_vlan}" vxlanlocal "${_local}" vxlangroup "${_maddr}" \
vxlandev "${_if}" descr "vm-vxlan/${_switch}" group vm-vlan up >/dev/null 2>&1
[ $? -eq 0 ] || return 1
# get the length of the switch name
# it's useful for other utilities to use switch name as interface name
# as it's static. can't do that if it's > 12 chars
_len=$(echo -n "${_name}" | wc -m)
if [ ${_len} -le 12 ]; then
_ifconf="name vm-${_name}"
else
_ifconf="descr vm/${_name}"
fi
# create a bridge for this switch
_id=$(ifconfig bridge create ${_ifconf} group vm-switch up 2>/dev/null)
[ $? -eq 0 ] || util::err "failed to create bridge interface for switch ${_name}"
switch::set_viid "${_name}" "${_id}"
# randomise mac if feature is available
[ ${VERSION_BSD} -ge 1102000 ] && ifconfig "${_id}" link random
# bridge vxlan to our guest switch
# static route traffic for this multicast address via our specified interface
ifconfig "${_id}" addm "vxlan${_vlan}"
route add -net ${_maddr}/32 -iface ${_if} >/dev/null 2>&1
# custom address for bridge?
config::core::get "_addr" "addr_${_name}"
[ -n "${_addr}" ] && ifconfig "${_id}" inet ${_addr}
}
# show the configuration details for a vxlan switch
#
# @param string _name the switch name
# @param string _format output format
#
switch::vxlan::show(){
local _name="$1"
local _format="$2"
local _id _vlan _port _addr _priv
switch::standard::id "_id" "${_name}"
config::core::get "_vlan" "vlan_${_name}"
config::core::get "_port" "ports_${_name}"
config::core::get "_addr" "addr_${_name}"
config::core::get "_priv" "private_${_name}" "no"
printf "${_format}" "${_name}" "vxlan" "${_id:--}" "${_addr:--}" "${_priv}" "n/a" "${_vlan}" "${_port}"
}
# create a vxlan switch
#
switch::vxlan::create(){
# we must have an interface and vlan to use
[ -z "${_if}" -o -z "${_vlan}" ] && util::err "vxlan switches must be created with an interface and vlan id specified"
# store configuration
config::core::set "switch_list" "${_switch}" "1"
config::core::set "type_${_switch}" "vxlan"
config::core::set "vlan_${_switch}" "${_vlan}"
config::core::set "ports_${_switch}" "${_if}"
[ -n "${_addr}" ] && config::core::set "addr_${_switch}" "${_addr}"
[ -n "${_priv}" ] && config::core::set "private_${_switch}" "${_priv}"
config::core::load
switch::vxlan::init "${_switch}"
}
# destroy a vxlan switch
#
# @param string _switch the switch to remove
#
switch::vxlan::remove(){
local _switch="$1"
local _id _vlan _maddr
# try to get guest bridge and vxlan id
switch::standard::id "_id" "${_switch}"
[ $? -eq 0 ] || return 1
config::core::get "_vlan" "vlan_${_switch}"
[ -z "${_vlan}" ] && return 1
# get the multicast address we used for this switch
# and try to remove any route we may have added
switch::vxlan::__multicast "_maddr" "${_switch}"
route del -net "${_maddr}/32" >/dev/null 2>&1
# destroy the guest bridge
ifconfig ${_id} destroy >/dev/null 2>&1
[ $? -eq 0 ] || return 1
# destroy the vxlan
ifconfig "vxlan${_vlan}" destroy >/dev/null 2>&1
}
# add a new interface to this switch
# we only allow a single physical interface for
# vxlan switches. this must be set at creation time
# so this just reports an error
#
# @param string _switch name of the vxlan switch
# @param string _if the interface to add
#
switch::vxlan::add_member(){
util::err "vxlan interface must be configured at creation time"
}
# remove an interface from a switch
# we don't support this here
#
# @param string _switch name of the switch
# @param string _if the interface to remove
#
switch::vxlan::remove_member(){
util::err "vxlan interface must be configured at creation time"
}
# set vlan id
#
# @param string _switch name of switch
# @param int _vlan vlan id to set
#
switch::vxlan::vlan(){
util::err "vxlan id can only be set at creation time"
}
# get the multicast address for a vxlan switch
#
# @param string _var variable to put address into
# @param string _switch the switch name
#
switch::vxlan::__multicast(){
local _var="$1"
local _switch="$2"
local _hash _l_addr _octet _pos
# come up with an ip address for multicast data
_hash=$(md5 -qs "${_switch}")
_l_addr="239"
for _pos in 1-2 3-4 5-6; do
_octet=$(printf ".%d" "0x`echo "${_hash}"| cut -c ${_pos}`")
_l_addr="${_l_addr}${_octet}"
done
setvar "${_var}" "${_l_addr}"
}

405
lib/vm-util Normal file
View File

@ -0,0 +1,405 @@
#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (C) 2016 Matt Churchyard (churchers@gmail.com)
# All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# make sure we have the right environment
#
util::setup(){
util::load_module "nmdm"
util::load_module "if_bridge"
# tap(4) & tun(4) were unified in r347241, this is closest ABI bump
if [ `uname -K` -ge 1300029 ]; then
util::load_module "if_tuntap"
else
util::load_module "if_tap"
fi
sysctl net.link.tap.up_on_open=1 >/dev/null 2>&1
# do we have the default template example, but no default in our .templates?
# if so, get a copy, this at least allows a simple "vm create" to work out of the box
if [ -e "/usr/local/share/examples/vm-bhyve/default.conf" -a ! -e "${vm_dir}/.templates/default.conf" ]; then
cp "/usr/local/share/examples/vm-bhyve/default.conf" "${vm_dir}/.templates/" >/dev/null 2>&1
fi
}
# load a kernel module
#
# @param string _mod the module name
#
util::load_module(){
local _mod="$1"
kldstat -qm ${_mod} >/dev/null 2>&1
if [ $? -ne 0 ]; then
kldload ${_mod} >/dev/null 2>&1
[ $? -eq 0 ] || util::err "unable to load ${_mod}.ko!"
fi
}
# check if system have bhyve support
# look for sysctls set by the vmm module. I can't get confirmation that this
# is a valid way to check vmm is working, even though it seems reasonable.
# the vm_disable_host_checks="yes" rc settings allows bypassing all this
# if your system should be supported but these checks break.
#
# @modifies VM_NO_UG
#
util::check_bhyve_support(){
# almost all our functionality requires access to things only root can do
[ `id -u` -ne 0 ] && util::err "virtual machines can only be managed by root"
# try to load the vmm module
# we do this here to make sure the sysctls exist, and before disable_host_checks
# as we want this loaded anyway. FreeBSD version check removed as the
# module won't exist on systems too old for bhyve
util::load_module "vmm"
# don't check if user wants to bypass host checks
util::yesno "$vm_disable_host_checks" && return 0
# check sysctls
# these only work for intel
# for AMD we give up trying to check for the time being
sysctl hw.model |grep Intel >/dev/null 2>&1
if [ $? -eq 0 ]; then
[ "`sysctl -n hw.vmm.vmx.initialized 2>/dev/null`" != "1" ] && util::err "kernel vmm not initialised (no VT-x / AMD SVM cpu support?)"
[ "`sysctl -n hw.vmm.vmx.cap.unrestricted_guest 2>/dev/null`" != "1" ] && VM_NO_UG="1"
fi
}
# check for passthru support
# following neel@ wiki we search for DMAR acpi table for vt-d
# and we check sysctl if amdvi is present and enabled
#
# @return success if host has vt-d or amdvi
#
util::check_bhyve_iommu(){
local _vtd
local _amdvi
# don't check if user wants to bypass host checks
# think this check is fairly solid but there's probably someone somewhere
# with iommu support that our tests fail for.
util::yesno "$vm_disable_host_checks" && return 0
_vtd=$(acpidump -t |grep DMAR)
_amdvi=$(sysctl hw |grep 'vmm.amdvi.enable: 1')
[ -z "${_vtd}" -a -z "${_amdvi}" ] && return 1
return 0
}
# restart a local service
# checks if service is running and either starts or restarts
#
# @param string _serv the name of the service
#
util::restart_service(){
local _serv="$1"
local _cmd="restart"
# see if it's actually running
service ${_serv} status >/dev/null 2>&1
[ $? -ne 0 ] && _cmd="start"
service ${_serv} ${_cmd} >/dev/null 2>&1
[ $? -ne 0 ] && util::warn "failed to ${_cmd} service ${_serv}"
}
# show version
#
util::version(){
echo "vm-bhyve: Bhyve virtual machine management v${VERSION} (rev. ${VERSION_INT})"
}
# show version & usage information
# we exit after running this
#
util::usage(){
util::version
cat << EOT
Usage: vm ...
version
init
set [setting=value] [...]
get [all|setting] [...]
switch list
switch info [name] [...]
switch create [-t type] [-i interface] [-n vlan-id] [-m mtu] [-a address/prefix-len] [-b bridge] [-p] <name>
switch vlan <name> <vlan|0>
switch nat <name> <on|off>
switch private <name> <on|off>
switch add <name> <interface>
switch remove <name> <interface>
switch destroy <name>
datastore list
datastore add <name> <spec>
datastore remove <name>
datastore add <name> <path>
list
info [name] [...]
create [-d datastore] [-t template] [-s size] [-m memory] [-c vCPUs] <name>
install [-fi] <name> <iso>
start [-fi] <name> [...]
stop <name> [...]
restart <name>
console <name> [com1|com2]
configure <name>
rename <name> <new-name>
add [-d device] [-t type] [-s size|switch] <name>
startall
stopall
reset [-f] <name>
poweroff [-f] <name>
destroy [-f] <name>
passthru
clone <name[@snapshot]> <new-name>
snapshot [-f] <name[@snapshot]>
rollback [-r] <name@snapshot>
iso [url]
img [url]
image list
image create [-d description] [-u] <name>
image destroy <uuid>
image provision [-d datastore] <uuid> <newname>
EOT
exit 1
}
# err
# display an error message and exit immediately
#
# @param string - the message to display
#
util::err(){
echo "${0}: ERROR: $1" >&2
exit 1
}
# err_inline
# display an error inline with informational output
#
# @param string - message to display
#
util::err_inline(){
echo " ! $1"
exit 1
}
# warn
# display warning, but do not exit
#
# @param string - the message to display
#
util::warn(){
echo "${0}: WARNING: $1" >&2
}
# log_rotate
# simple rotation of log files
# if we hit 1MB, which should cover a fair amount of history,
# we move existing log and and create a new one.
# one keep 1 previous file, as that should be enough
#
# @param string _type whether to rotate guest or main log
#
util::log_rotate(){
local _type="$1"
local _lf="vm-bhyve.log"
local _file _size _guest
case "${_type}" in
guest)
_guest="$2"
_file="${VM_DS_PATH}/${_guest}/${_lf}"
;;
system)
_file="${vm_dir}/${_lf}"
;;
esac
if [ -e "${_file}" ]; then
_size=$(stat -f %z "${_file}")
if [ -n "${_size}" -a "${_size}" -ge 1048576 ]; then
unlink "${_file}.0.gz" >/dev/null 2>&1
mv "${_file}" "${_file}.0"
gzip "${_file}.0"
fi
fi
}
# log to file
# writes the date and a message to the specified log
# the global log is in $vm_dir/vm-bhyve.log
# guest logs are $vm_dir/{guest}/vm-bhyve.log
#
# @param string _type=guest|system log to global vm-bhyve log or guest
# @param optional string _guest if _type=guest, the guest name, otherwise do not provide at all
# @param string _message the message to log
#
util::log(){
local _type="$1"
local _lf="vm-bhyve.log"
local _guest _message _file _date
case "${_type}" in
guest)
_guest="$2"
_file="${VM_DS_PATH}/${_guest}/${_lf}"
shift 2
;;
system)
_file="${vm_dir}/${_lf}"
shift 1
;;
esac
while [ -n "$1" ]; do
echo "$(date +'%b %d %T'): $1" >> "${_file}"
shift
done
}
# write content to a file, and log what we
# did to the guest log file
# it's useful to be able to see what files vm-bhyve is creating
# and the contents so we write that to the log.
# The file is created in $vm_dir/{guest}
#
# @param string _type=write|appnd create file or append to it
# @param string _guest the guest name
# @param string _file the file name to write to
# @param string _message the data to write
#
util::log_and_write(){
local _type="$1"
local _guest="$2"
local _file="${VM_DS_PATH}/${_guest}/$3"
local _message="$4"
if [ "${_type}" = "write" ]; then
util::log "guest" "${_guest}" "create file ${_file}"
echo "${_message}" > "${_file}"
else
echo "${_message}" >> "${_file}"
fi
util::log "guest" "${_guest}" " -> ${_message}"
}
# confirm yes or no
#
# @param string _msh message to display
# @return int success if confirmed
#
util::confirm(){
local _msg="$1"
local _resp
while read -p "${_msg} (y/n)? " _resp; do
case "${_resp}" in
y*) return 0 ;;
n*) return 1 ;;
esac
done
}
# our own checkyesno copy
# doesn't warn for unsupported values
# also returns as 'yes' unless value is specifically no/off/false/0
#
# @param _value the value to test
# @return int 1 if set to "off/false/no/0", 0 otherwise
#
util::yesno(){
local _value="$1"
[ -z "${_value}" ] && return 1
case "$_value" in
[Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0)
return 1 ;;
*) return 0 ;;
esac
}
# 'vm check name'
# check name of virtual machine
#
# @param _name name to check
# @param _maxlen=30(229 on 13+) maximum name length (NOTE should be 2 less than desired)
# @return int 0 if name is valid
#
util::check_name(){
local _name="$1"
local _maxlen="$2"
if [ -z "${_maxlen}" ]; then
if [ ${VERSION_BSD} -ge 1300000 ]; then
: ${_maxlen:=229}
else
: ${_maxlen:=30}
fi
fi
echo "${_name}" | egrep -iqs "^[a-z0-9][.a-z0-9_-]{0,${_maxlen}}[a-z0-9]\$"
}
# check if the specified string is a valid core configuration
# setting that the user can change
#
# @param string the setting name to look for
#
util::valid_config_setting(){
echo "${VM_CONFIG_USER}" | grep -iqs "${1};"
}
# __getpid
# get a process id
#
# @param string _var variable to put pid into
# @param string _proc process to look for
#
util::getpid(){
local _var="$1"
local _proc="$2"
local _ret
_ret=$(pgrep -f "${_proc}")
[ $? -eq 0 ] || return 1
setvar "${_var}" "${_ret}"
}
util::get_part(){
local _var="$1"
local _data="$2"
local _num="$3"
setvar "${_var}" $(echo "${_data}" |cut -w -f${_num})
}

519
lib/vm-zfs Normal file
View File

@ -0,0 +1,519 @@
#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (C) 2016 Matt Churchyard (churchers@gmail.com)
# All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# set us up for zfs use
# we need the zfs dataset name for zfs commands, and the file system path
# for bhyve. It's highly possible these will be different.
# We ask user to specify the dataset name as "zfs:pool/dataset"
# This can then be used for zfs commands, and we can retrieve the mountpoint
# to find out the file path for bhyve
#
# This then overwrites $vm_dir with the file system path, so that the
# rest of vm-bhyve can work normally, regardless of whether we are in zfs mode
# or not
#
# If zfs is enabled, the following global variables are set
# VM_ZFS=1
# VM_ZFS_DATASET={pool/dataset}
#
# @modifies VM_ZFS VM_ZFS_DATASET vm_dir
#
zfs::init(){
# check for zfs storage location
# user should specify "zfs:pool/dataset" if they want ZFS support
if [ "${vm_dir%%:*}" = "zfs" ]; then
# check zfs running
kldstat -qm zfs >/dev/null 2>&1
[ $? -eq 0 ] || util::err "ZFS support requested but ZFS not available"
# global zfs details
VM_ZFS="1"
VM_ZFS_DATASET="${vm_dir#*:}"
# update vm_dir
# this makes sure it exists, confirms it's mounted & gets correct path in one go
vm_dir=$(mount | grep "^${VM_ZFS_DATASET} " |cut -d' ' -f3)
[ -z "${vm_dir}" ] && util::err "unable to locate mountpoint for ZFS dataset ${VM_ZFS_DATASET}"
fi
}
# make a new dataset
# this is always called when creating a new vm, but will do nothing
#
# @param string _name name of the dataset to create
#
zfs::make_dataset(){
local _name="$1"
local _opts="$2"
if [ -n "${_name}" -a "${VM_DS_ZFS}" = "1" ]; then
zfs::__format_options "_opts" "${_opts}"
zfs create ${_opts} "${_name}"
[ $? -eq 0 ] || util::err "failed to create new ZFS dataset ${_name}"
fi
}
# destroy a dataset
#
# @param string _name name of the dataset to destroy
#
zfs::destroy_dataset(){
local _name="$1"
if [ -n "${_name}" -a "${VM_DS_ZFS}" = "1" ]; then
zfs destroy -rf "${_name}" >/dev/null 2>&1
[ $? -eq 0 ] || util::err "failed to destroy ZFS dataset ${_name}"
fi
}
# rename a dataset
# as with other zfs functions, the arguments should just be the name
# of the dataset under $VM_ZFS_DATASET (in this case just guest name)
#
# @param string _old the name of the dataset to rename
# @param string _new the new name
#
zfs::rename_dataset(){
local _old="$1"
local _new="$2"
if [ -n "${_old}" -a -n "${_new}" -a "${VM_DS_ZFS}" = "1" ]; then
zfs rename "${VM_DS_ZFS_DATASET}/${_old}" "${VM_DS_ZFS_DATASET}/${_new}" >/dev/null 2>&1
[ $? -eq 0 ] || util::err "failed to rename ZFS dataset ${VM_DS_ZFS_DATASET}/${_old}"
fi
}
# make a zvol for a guest disk image
#
# @param string _name name of the zvol to create
# @param string _size how big to create the dataset
# @param int _sparse=0 set to 1 for a sparse zvol
#
zfs::make_zvol(){
local _name="$1"
local _size="$2"
local _sparse="$3"
local _user_opts="$4"
local _opt="-V"
[ ! "${VM_DS_ZFS}" = "1" ] && util::err "cannot use ZVOL storage unless ZFS support is enabled"
[ "${_sparse}" = "1" ] && _opt="-sV"
zfs::__format_options "_user_opts" "${_user_opts}"
zfs create ${_opt} ${_size} -o volmode=dev ${_user_opts} "${_name}"
[ $? -eq 0 ] || util::err "failed to create new ZVOL ${_name}"
}
# format options for zfs commands
# options are stored in configuration separated by a space
# we need to replace that with -o
#
# @modifies $_val
#
zfs::__format_options(){
local _val="$1"
local _c_opts="$2"
if [ -n "${_c_opts}" ]; then
_c_opts=$(echo "${_c_opts}" |sed -e 's/\ / -o /')
_c_opts="-o ${_c_opts}"
setvar "${_val}" "${_c_opts}"
return 0
fi
setvar "${_val}" ""
}
# 'vm snapshot'
# create a snapshot of a guest
# specify the snapshot name in zfs format guest@snap
# if no snapshot name is specified, Y-m-d-H:M:S will be used
#
# @param flag (-f) force snapshot if guest is running
# @param string _name the name of the guest to snapshot
#
zfs::snapshot(){
local _name _snap
cmd::parse_args "$@"
shift $?
_name="$1"
# try to get snapshot name
# we support normal zfs syntax for this
echo "${_name}" | grep -qs "@"
if [ $? -eq 0 ]; then
_snap=${_name##*@}
_name=${_name%%@*}
fi
[ -z "${_name}" ] && util::usage
datastore::get_guest "${_name}" || util::err "${_name} does not appear to be an existing virtual machine"
[ ! "${VM_DS_ZFS}" = "1" ] && util::err "cannot snapshot guests on non-zfs datastores"
[ -z "${_snap}" ] && _snap=$(date +"%Y-%m-%d-%H:%M:%S")
if ! vm::confirm_stopped "${_name}" >/dev/null; then
[ -z "${VM_OPT_FORCE}" ] && util::err "${_name} must be powered off first (use -f to override)"
fi
zfs snapshot -r ${VM_DS_ZFS_DATASET}/${_name}@${_snap}
[ $? -eq 0 ] || util::err "failed to create recursive snapshot of virtual machine"
}
# try to remove a snapshot
#
# @param string _name the guest name and snapshot (guest@snap)
# @return true if successful
#
zfs::remove_snapshot(){
local _name="$1"
local _snap
# split name and snapshot
_snap=${_name##*@}
_name=${_name%%@*}
# try to load guest
datastore::get_guest "${_name}" || util::err "${_name} does not appear to be an existing virtual machine"
[ ! "${VM_DS_ZFS}" = "1" ] && util::err "cannot snapshot guests on non-zfs datastores"
# remove
zfs destroy -r ${VM_DS_ZFS_DATASET}/${_name}@${_snap}
[ $? -eq 0 ] || util::err "failed to remove snapshot ${VM_DS_ZFS_DATASET}/${_name}@${_snap}"
}
# 'vm rollback'
# roll a guest back to a previous snapshot
# we show zfs errors here as it will fail if the snapshot is not the most recent.
# zfs will output an error mentioning to use '-r', and listing the snapshots
# that will be deleted. makes sense to let user see this and just support
# that option directly
#
# @param flag (-r) force deletion of more recent snapshots
# @param string _name name of the guest
#
zfs::rollback(){
local _name _snap _opt _force _fs _snap_exists
while getopts r _opt; do
case $_opt in
r) _force="-r" ;;
*) util::usage ;;
esac
done
shift $((OPTIND - 1))
_snap_exists=$(echo "${1}" | grep "@")
[ -z "${_snap_exists}" ] && util::err "a snapshot name must be provided in guest@snapshot format"
_name="${1%%@*}"
_snap="${1##*@}"
[ -z "${_name}" -o -z "${_snap}" ] && util::usage
datastore::get_guest "${_name}" || util::err "${_name} does not appear to be an existing virtual machine"
[ ! "${VM_DS_ZFS}" = "1" ] && util::err "cannot rollback guests on non-zfs datastores"
vm::confirm_stopped "${_name}" || exit 1
# list all datasets and zvols under guest
zfs list -o name -rHt filesystem,volume ${VM_DS_ZFS_DATASET}/${_name} | \
while read _fs; do
zfs rollback ${_force} ${_fs}@${_snap}
[ $? -ne 0 ] && exit $?
done
}
# 'vm clone'
# clone a vm
# this makes a true zfs clone of the specifies guest
#
# @param string _old the guest to clone
# @param string _new name of the new guest
#
zfs::clone(){
local _old="$1"
local _name="$2"
local _fs _newfs _snap _snap_exists _fs_list _entry
local _num=0 _error=0
local _uuid=$(uuidgen)
# check args and make sure new guest doesn't already exist
[ -z "${_old}" -o -z "${_name}" ] && util::usage
datastore::get_guest "${_name}" && util::err "new guest already exists in ${VM_DS_PATH}/${_name}"
# try to get snapshot name
# we support normal zfs syntax for this
_snap_exists=$(echo "${_old}" | grep "@")
if [ -n "${_snap_exists}" ]; then
_snap=${_old##*@}
_old=${_old%%@*}
fi
# make sure old guest exists
datastore::get_guest "${_old}" || util::err "${_old} does not appear to be an existing virtual machine"
[ ! "${VM_DS_ZFS}" = "1" ] && util::err "cannot clone guests on non-zfs datastores"
# get list of datasets to copy
_fs_list=$(zfs list -rHo name -t filesystem,volume "${VM_DS_ZFS_DATASET}/${_old}")
[ $? -eq 0 ] || util::err "unable to list datasets for ${VM_DS_ZFS_DATASET}/${_old}"
# generate a short uuid and create snapshot if no custom snap given
if [ -z "${_snap}" ]; then
vm::confirm_stopped "${_old}" || exit 1
_snap=$(echo "${_uuid}" |awk -F- '{print $1}')
zfs snapshot -r "${VM_DS_ZFS_DATASET}/${_old}@${_snap}"
[ $? -eq 0 ] || util::err "failed to create snapshot ${VM_DS_ZFS_DATASET}/${_old}@${_snap}"
else
for _fs in ${_fs_list}; do
zfs get creation "${_fs}@${_snap}" >/dev/null 2>&1
[ $? -eq 0 ] || util::err "snapshot ${_fs}@${_snap} doesn't seem to exist"
done
fi
# clone
for _fs in ${_fs_list}; do
_newfs=$(echo "${_fs}" | sed "s@${VM_DS_ZFS_DATASET}/${_old}@${VM_DS_ZFS_DATASET}/${_name}@")
zfs clone "${_fs}@${_snap}" "${_newfs}"
[ $? -eq 0 ] || util::err "error while cloning dataset ${_fs}@${_snap}"
done
# update new guest files
unlink "${VM_DS_PATH}/${_name}/vm-bhyve.log" >/dev/null 2>&1
mv "${VM_DS_PATH}/${_name}/${_old}.conf" "${VM_DS_PATH}/${_name}/${_name}.conf" >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo "Unable to rename configuration file to ${_name}.conf"
echo "This will need to be renamed manually"
echo "Please also remove any uuid or mac address settings, these will be regenerated automatically"
exit 1
fi
# generalise the clone
vm::generalise "${_name}"
}
# 'vm image create'
# create an image of a vm
# this creates an archive of the specified guest, stored in $vm_dir/images
# we use a uuid just in case we want to provide the ability to share images at any point
#
# @param optional string (-d) description of the image
# @param string _name name of guest to take image of
#
zfs::image_create(){
local _name _opt _desc
local _uuid _snap _date _no_compress _filename
local _compress _decompress
while getopts d:u _opt ; do
case $_opt in
d) _desc=${OPTARG} ;;
u) _no_compress="1" ;;
*) util::usage ;;
esac
done
shift $((OPTIND - 1))
_name=$1
_uuid=$(uuidgen)
_snap=${_uuid%%-*}
_date=$(date)
[ -z "${_desc}" ] && _desc="No description provided"
datastore::get_guest "${_name}" || util::err "${_name} does not appear to be a valid virtual machine"
[ -z "${VM_ZFS}" -o -z "${VM_DS_ZFS}" ] && util::err "this command is only supported on zfs datastores"
# create the image dataset if we don't have it
if [ ! -e "${vm_dir}/images" ]; then
zfs create "${VM_ZFS_DATASET}/images" >/dev/null 2>&1
[ $? -eq 0 ] || util::err "failed to create image store ${VM_ZFS_DATASET}/images"
fi
# try to snapshot
zfs snapshot -r "${VM_DS_ZFS_DATASET}/${_name}@${_snap}" >/dev/null 2>&1
[ $? -eq 0 ] || util::err "failed to create snapshot of source dataset ${VM_DS_ZFS_DATASET}/${_name}@${_snap}"
# copy source
if [ -n "${_no_compress}" ]; then
_filename="${_uuid}.zfs"
echo "Creating guest image, this may take some time..."
zfs send -R "${VM_DS_ZFS_DATASET}/${_name}@${_snap}" > "${vm_dir}/images/${_filename}"
else
_filename="${_uuid}.zfs.z"
config::core::get "_compress" "compress"
config::core::get "_decompress" "decompress"
# use defaults if either of these settings are missing
# no point using user defined compress if we don't know how to decompress
if [ "${_compress}" = "" -o "${_decompress}" = "" ]; then
_compress="xz -T0"
_decompress="xz -d"
fi
echo "Creating a compressed image, this may take some time..."
zfs send -R "${VM_DS_ZFS_DATASET}/${_name}@${_snap}" | ${_compress} > "${vm_dir}/images/${_filename}"
fi
[ $? -ne 0 ] && exit 1
# done with the source snapshot
zfs destroy -r ${VM_DS_ZFS_DATASET}/${_name}@${_snap}
# create a description file
sysrc -inqf "${vm_dir}/images/${_uuid}.manifest" "description=${_desc}" >/dev/null 2>&1
sysrc -inqf "${vm_dir}/images/${_uuid}.manifest" "created=${_date}" >/dev/null 2>&1
sysrc -inqf "${vm_dir}/images/${_uuid}.manifest" "name=${_name}" >/dev/null 2>&1
sysrc -inqf "${vm_dir}/images/${_uuid}.manifest" "filename=${_filename}" >/dev/null 2>&1
sysrc -inqf "${vm_dir}/images/${_uuid}.manifest" "decompress=${_decompress}" >/dev/null 2>&1
echo "Image of ${_name} created with UUID ${_uuid}"
}
# 'vm image provision'
# create a new vm from an image
#
# @param string _uuid the uuid of the image to use
# @param string _name name of the new guest
#
zfs::image_provision(){
local _uuid _name _file _oldname _entry _num=0 _type
local _datastore="default" _decompress
while getopts d: _opt ; do
case $_opt in
d) _datastore="${OPTARG}" ;;
*) util::usage ;;
esac
done
shift $((OPTIND - 1))
_uuid="$1"
_name="$2"
[ -z "${_uuid}" -o -z "${_name}" ] && util::usage
[ ! -e "${vm_dir}/images/${_uuid}.manifest" ] && util::err "unable to locate image with uuid ${_uuid}"
datastore::get_guest "${_name}" && util::err "new guest already exists in ${VM_DS_PATH}/${_name}"
# get the data filename
_file=$(sysrc -inqf "${vm_dir}/images/${_uuid}.manifest" filename)
_type=${_file##*.}
_oldname=$(sysrc -inqf "${vm_dir}/images/${_uuid}.manifest" name)
[ -z "${_file}" -o -z "${_oldname}" ] && util::err "unable to locate required details from the specified image manifest"
[ ! -e "${vm_dir}/images/${_file}" ] && util::err "image data file does not exist: ${vm_dir}/images/${_file}"
# get the datastore to create on
datastore::get "${_datastore}" || util::err "unable to locate datastore '${_datastore}'"
# try to recieve
echo "Unpacking guest image, this may take some time..."
# check format of image
case ${_type} in
zfs) cat "${vm_dir}/images/${_file}" | zfs recv "${VM_DS_ZFS_DATASET}/${_name}" ;;
xz) xz -dc "${vm_dir}/images/${_file}" 2>/dev/null | zfs recv "${VM_DS_ZFS_DATASET}/${_name}" ;;
z) _decompress=$(sysrc -inqf "${vm_dir}/images/${_uuid}.manifest" decompress)
[ -z "${_decompress}" ] && util::err "unable to locate decompression configuration"
${_decompress} <"${vm_dir}/images/${_file}" 2>/dev/null | zfs recv "${VM_DS_ZFS_DATASET}/${_name}" ;;
*) util::err "unsupported guest image type - '${_type}'" ;;
esac
# error unpacking?
[ $? -eq 0 ] || util::err "errors occured while trying to unpackage the image file"
# remove the original snapshot
zfs destroy -r "${VM_DS_ZFS_DATASET}/${_name}@${_uuid%%-*}" >/dev/null 2>&1
# rename the guest configuration file
mv "${VM_DS_PATH}/${_name}/${_oldname}.conf" "${VM_DS_PATH}/${_name}/${_name}.conf" >/dev/null 2>&1
[ $? -eq 0 ] || util::err "unpackaged image but unable to update guest configuration file"
# update mac addresses and create a new uuid
_uuid=$(uuidgen)
# remove unique settings from new image
vm::generalise "${_name}"
# vm may be started when 'vm image create' is executed
rm -f "${VM_DS_PATH}/${_name}/run.lock" >/dev/null 2>&1
rm -f "${VM_DS_PATH}/${_name}/vm-bhyve.log*" >/dev/null 2>&1
}
# 'vm image list'
# list available images
#
zfs::image_list(){
local _file _uuid _ext
local _format="%s^%s^%s^%s\n"
{
printf "${_format}" "UUID" "NAME" "CREATED" "DESCRIPTION"
[ ! -e "${vm_dir}/images" ] && exit
ls -1 ${vm_dir}/images/ | \
while read _file; do
if [ "${_file##*.}" = "manifest" ]; then
_uuid=${_file%.*}
_desc=$(sysrc -inqf "${vm_dir}/images/${_uuid}.manifest" description)
_created=$(sysrc -inqf "${vm_dir}/images/${_uuid}.manifest" created)
_name=$(sysrc -inqf "${vm_dir}/images/${_uuid}.manifest" name)
printf "${_format}" "${_uuid}" "${_name}" "${_created}" "${_desc}"
fi
done
} | column -ts^
}
# 'vm image destroy'
# destroy an image
#
# @param string _uuid the uuid of the image
#
zfs::image_destroy(){
local _uuid="$1"
local _file
[ -z "${_uuid}" ] && util::usage
[ ! -e "${vm_dir}/images/${_uuid}.manifest" ] && util::err "unable to locate image with uuid ${_uuid}"
# get the image filename
_file=$(sysrc -inqf "${vm_dir}/images/${_uuid}.manifest" filename)
[ -z "${_file}" ] && util::err "unable to locate filename for the specified image"
unlink "${vm_dir}/images/${_uuid}.manifest"
unlink "${vm_dir}/images/${_file}"
}

30
rc.d/vm Normal file
View File

@ -0,0 +1,30 @@
#!/bin/sh
#
# $FreeBSD$
# PROVIDE: vm
# REQUIRE: NETWORKING SERVERS dmesg
# BEFORE: dnsmasq ipfw pf
# KEYWORD: shutdown nojail
. /etc/rc.subr
: ${vm_enable="NO"}
name=vm
desc="Start and stop vm-bhyve guests on boot/shutdown"
rcvar=vm_enable
load_rc_config $name
command="/usr/local/sbin/${name}"
start_cmd="${name}_start"
stop_cmd="${command} stopall -f"
vm_start()
{
env rc_force="$rc_force" ${command} init
env rc_force="$rc_force" ${command} startall >/dev/null &
}
run_rc_command "$1"

View File

@ -0,0 +1,11 @@
loader="grub"
cpu=1
memory=512M
network0_type="virtio-net"
network0_switch="public"
disk0_type="virtio-blk"
disk0_name="disk0.img"
grub_install0="linux /boot/vmlinuz-vanilla initrd=/boot/initramfs-vanilla alpine_dev=cdrom:iso9660 modules=loop,squashfs,sd-mod,usb-storage,sr-mod"
grub_install1="initrd /boot/initramfs-vanilla"
grub_run0="linux /boot/vmlinuz-vanilla root=/dev/vda3 modules=ext4"
grub_run1="initrd /boot/initramfs-vanilla"

View File

@ -0,0 +1,9 @@
loader="grub"
cpu=1
memory=512M
network0_type="virtio-net"
network0_switch="public"
disk0_type="virtio-blk"
disk0_name="disk0.img"
grub_install0="linux /arch/boot/x86_64/vmlinuz archisobasedir=arch archisolabel=ARCH_201611 ro"
grub_install1="initrd /arch/boot/x86_64/archiso.img"

View File

@ -0,0 +1,11 @@
loader="grub"
cpu=1
memory=512M
network0_type="virtio-net"
network0_switch="public"
disk0_type="virtio-blk"
disk0_name="disk0.img"
grub_install0="linux /isolinux/vmlinuz"
grub_install1="initrd /isolinux/initrd.img"
grub_run0="linux /vmlinuz-2.6.32-573.el6.x86_64 root=/dev/mapper/VolGroup-lv_root"
grub_run1="initrd /initramfs-2.6.32-573.el6.x86_64.img"

View File

@ -0,0 +1,9 @@
loader="uefi"
graphics="yes"
xhci_mouse="yes"
cpu=1
memory=512M
network0_type="virtio-net"
network0_switch="public"
disk0_type="virtio-blk"
disk0_name="disk0.img"

View File

@ -0,0 +1,503 @@
# This is a sample configuration file containing all supported options
# Please do not try and use this file itself for a guest
# For any option that contains a number in the name, such as "network0_type",
# you can add additional devices of that type by creating a new set of
# variables using the next number in sequence, e.g "network1_type"
#
# Please make sure all option names are specified in lowercase and
# at the beginning of the line. If there is any whitespace before
# the option name, the line will be ignored.
# The '#' character signifies the start of a comment, even within
# double-quotes, and so cannot be used inside any values.
# loader
# Specify the loader to use for the guest. This can either be
# one of the original bhyve loaders (bhyveload/grub), or
# you can specify to use uefi firmware to load the guest
#
# Valid Options: bhyveload,grub,uefi,uefi-csm
#
loader=""
# bhyveload_loader
# If using bhyveload, this option can be used to specify the path
# to the loader inside the guest to use
#
# the default is /boot/userboot.so
#
bhyveload_loader=""
# bhyveload_args
# If using bhyveload, this option can be used to pass command line
# arguments to the loader
#
bhyveload_args="-e machdep.hyperthreading_allowed=0"
# loader_timeout
# By default bhyveload & grub-bhyve will wait 3 seconds before booting the default
# option. This setting allows you to either reduce the timeout to
# make boot faster, or increase it so that it's easier to access
# the grub console before it starts booting
#
loader_timeout="3"
# uefi_vars
# set to a true value to support persistent UEFI vars
# this relies on a version of uefi-firmware that comes with the BHYVE_UEFI_VARS.fd template,
# and support in bhyve
#
uefi_vars="no"
# cpu (required)
# specify the number of cpu cores to give to the guest
#
cpu="1"
# cpu_sockets
# manually configure the number of sockets that bhyve should
# expose to the guest.
# note that sockets*cores*threads should equal the above cpu count
#
cpu_sockets="1"
# cpu_cores
# the number of cores to create per physical processor
#
cpu_cores="1"
# cpu_threads
# the number of cpu threads per core
#
cpu_threads="1"
# memory (required)
# specify the amount of ram to give to the guest. This can be
# followed by M or G.
#
memory="512M"
# wired_memory
# All requested memory should be wired to the guest
#
wired_memory="no"
# hostbridge
# Allows you to specify the type of hostbridge to use for the
# guest hardware. This can usually be left as default. The
# additional options are 'amd', for a hostbridge that advertises
# itself as AMD hardware and 'none' for no hostbridge. Note
# that there is no requirement to use the 'amd' hostbridge if
# you host has an AMD processor
#
# Default: standard
# Valid Options: standard,amd,none
#
hostbridge=""
# ignore_bad_msr
# Instruct bhyve to ignore accesses to model specific registers
# that are not implemented in the current CPU.
# This appears to be required for AMD processors when using
# some guest operating systems. Note that this is enabled
# by default when running a UEFI guest
#
ignore_bad_msr="no"
# bhyve_options
# any additional bhyve command line options
#
bhyve_options="-p 1:1"
# comports
# This allows you to define the com ports which should be available.
# By default only com1 is connected, and can be accessed using the
# 'vm console' command. If more than one com port is specified, you
# can choose the port to connect to by running 'vm console guest com1|com2'.
# When using the 'vm console' command, if no com port is specified,
# you are connected to the first port listed in this string.
#
# Default: com1
# Valid Options: com1,com2,com1 com2,com2 com1
#
comports=""
# utctime
# bhyve normally sets the guests RTC to the host's localtime. The utctime
# option causes bhyve to try and configure the guests RTC to UTC.
#
# As of vm-bhyve 1.2, this setting defaults to yes, giving the guest a
# UTC realtime clock. I consider this more consistent, and is actually
# expected by some guests. The guest should show correct time as long as
# its timezone is configured correctly. Note that the following command
# is useful to verify the time of a guest's "hardware" RTC:
# bhyvectl --vm={guestname} --get-rtc-time
#
# To revert to the default bhyve behaviour, explicitly set this to off/no/false/0
#
# Additionally it is generally advised to run a time sync daemon, such as ntpd
# in the guest, as each OS will have its own clock that will inevitably drift.
#
# Default: yes
#
utctime="no"
# debug
# Set to a value other than [empty]/no/off/false/0 to run vm-bhyve in debug mode.
# In this mode, all output from the bhyve process is written to
# $vm_dir/{guest}/bhyve.log. This is useful if the guest is crashing or
# exiting abnormally as the log will contain any output from bhyve.
#
# Default: no
#
debug=""
# uuid
# This is set automatically by vm-bhyve when creating a new guest. Normally
# bhyve assigns a UUID at runtime based on host and guest name. This
# option allows you to specify a fixed UUID that will always be used. Remove
# this or leave blank to return to the normal bhyve behaviour.
#
uuid=""
# ahci_device_limit
# By default all ahci devices (ahci-hd/ahci-cd) are configured on independent
# slots with their own controller. In FreeBSD 12 it's possible to put up
# to 32 devices on each controller. This setting allows you to configure
# the number of devices vm-bhyve will allocate on each controller.
#
# Valid Options: 2-32
# Default: 1
#
ahci_device_limit="8"
# disk0_type (required)
# This specifies the emulation type for disk0. Please note that each disk requires
# at least a type and name.
#
# Valid Options: virtio-blk,ahci-hd,ahci-cd,nvme
#
disk0_type="virtio-blk"
# disk0_dev
# The type of device used as the backing store for this disk. The default is 'file',
# which means a sparse file is used. This file is stored in the guest's directory.
# For the zvol options, the zvol must be directly under the guest dataset.
# There is also a 'custom' option, in which case the disk name should be the full path
# to the file or device you want to use.
# For 'iscsi', the disk name must be set to a unique target and lun combination
# when matched against iscsictl -L output.
#
# Default: file
# Valid Options: file,zvol,sparse-zvol,custom,iscsi
#
disk0_dev=""
# disk0_name (required)
# The name of the file or zvol for this disk. If the device type is 'custom', it should
# be the full path to whichever device or file you want to use
#
# This value is translated to a path as follows, based on disk0_dev
#
# DEVICE TYPE DISK NAME BHYVE PATH USED
# file 'disk0.img' -> '$vm_dir/$name/disk0.img'
# zvol|sparse-zvol 'disk0' -> '/dev/zvol/pool/dataset/path/guest/disk0'
# custom '/dev/da10' -> '/dev/da10'
# iscsi 'tgt[/lun]' -> '/dev/daNN' (lun defaults to 0 if omitted)
#
disk0_name="disk0.img"
# disk0_opts
# A comma separated list of additional options for the specified disk.
# The available options are listed below. See the bhyve(8) man page for
# more details
#
# Valid Options: direct,nocache,ro,sectorsize=logical[/physical]
#
disk0_opts=""
# disk0_size
# When a new guest is created, vm will create a 20G disk image by
# default. This option can be used to specify a different size
# for this disk. Make sure to include a human readable suffix
# compatible with 'zfs create' (G for gigabytes, T terabytes,
# etc)
#
# The size of the first disk (disk0) can also be overridden
# using the -s option to 'vm create'.
#
# NOTE: This option is only supported in template files. This
# setting serves no purpose in real guests and would become
# misleading if a disk were resized manually. When provisioning
# a new guest, all 'diskX_size' options are stripped from
# its configuration file.
#
disk0_size="50G"
# network0_type
# This specifies the emulation type to use for the first network interface.
# Networking is not required, although this field is mandatory if you do want
# to add an interface
#
# Valid Options: virtio-net,e1000
#
network0_type="virtio-net"
# network0_switch
# The name of the virtual switch to connect this interface to. When starting the
# guest, if this switch cannot be found, or no switch is specified, the interface
# is still created but will not be connected to anything.
#
# All default templates use a switch called 'public', although it's perfectly
# reasonable to use other switch names that make sense in your environment
#
network0_switch="public"
# network0_device
# If you do not want vm-bhyve to create a new interface, but use an existing
# one, enter the interface name here. This allows you to preconfigure the network
# device in a custom configuration, then instruct vm-bhyve to use that rather
# than create all interfaces dynamically at run time.
#
network0_device=""
# network0_name
# if specified, the interface will be given this name
#
network0_name="web1"
# network0_mac
# This allows you to specify a fixed mac address for this interface inside the guest.
# When a guest is run, vm-bhyve will automatically assign a mac address for each
# interface if one is not specified. This mac address is then written to the
# configuration file using this option. If we didn't do this guests might get
# a different mac if the tap device changes (very possible in vm-bhyve as all
# tap devices are dynamic by default). Guests like Windows treat an interface
# with a different mac as a new interface, with a new set of default settings.
#
network0_mac=""
# network0_span
# Set to any value other than [empty]/off/false/no/0 to create the specified
# port as a SPAN port rather than as an ordinary bridge member.
#
# NOTE: Does not work with VALE switches yet.
#
network0_span="no"
# passthru0
# Add a pass-through PCI device to the virtual machine. This allows the guest
# to access a hardware device no differently than if it was running on bare
# metal. The value of this option is the B/S/F of the appropriate device.
# e.g "3/0/0"
#
# The slot to use in bhyve can be specified as below. This example will
# force the host device 6/0/0 to use slot 2:0 in the guest
#
# passthru0="6/0/0=2:0"
#
# Please note that in order to stop the bhyve host from attaching to the device,
# there are some steps required to reserve the device in /boot/loader.conf.
#
# The 'vm passthru' command provides a convinient way of seeing the BSF of each
# device in your system, and whether any have been reserved ready for use
# in bhyve
#
# More details can be found in the FreeBSD bhyve wiki pages
#
passthru0=""
# start_slot
# The slot to start creating devices at inside the guest. Note that
# we create disk devices first, and some UEFI guests require disks to
# be in slots 3-6. The default is 4, with 3 being left available for
# an installation ISO
#
start_slot="4"
# install_slot
# The slot to use for an installation ISO. By default this is 3,
# which is the first available slot with the original UEFI firmware.
# Using this makes sure the ISO is the first device, and leaves
# 4-6 available for hd devices. Being able to change this may
# be useful for non-UEFI guests, especially if a passthru device
# requires this slot.
#
install_slot="3"
# virt_random
# Set to any value other than [empty]/off/false/no/0 to create
# a virtio-rnd device for the guest
#
virt_random=""
# graphics
# Set to a value other than [empty]/off/false/no/0 to enable
# the bhyve frame buffer device. This creates a graphics console
# in the guest, which is accessible using vnc
#
# By default this is set at 800x600, and we find an available vnc
# port starting at 5900. The port can be seen in vm list|info output.
#
graphics="yes"
# graphics_port
# Use this option to specify a fixed network port that the vnc service
# should listen on. If specifying port numbers manually, please make
# sure all guests have a unique port.
#
graphics_port="5999"
# graphics_listen
# By default, the vnc service will listen on 0.0.0.0, so you can connect by
# using any IP address assigned to the bhyve host. Use this option if you
# want to specify a specific IP address that the service should bind to
#
# Default: 0.0.0.0
#
graphics_listen="10.0.0.1"
# graphics_res
# This allows you to specify a resolution for the graphical console.
# Pleas note only the below options are supported
#
# Default: 800x600
# Valid Options: 1920x1200,1920x1080,1600x1200,1600x900,1280x1024,1280x720,1024x768,800x600,640x480
#
graphics_res="800x600"
# graphics_wait
# Set to yes in order to make guest boot wait for the VNC console
# to be opened. This can help when installing operating systems
# that require immediate keyboard input (such as a timed 'enter setup'
# screen). The default setting of auto will add the wait option
# if the guest is run in install mode. Note that in auto mode
# the wait option will only be present on the first boot. If you
# need the guest to wait on every boot during install, the yes
# option should be used.
#
# Valid Options: no,yes,auto
#
graphics_wait="auto"
# graphics_vga
# valid options for this are on/off/io. io is the default
# please see the bhyve man page for details on this option
#
graphics_vga="io"
# xhci_mouse
# When graphics are enabled, a PS2 mouse is created by default. This
# doesn't track very well, and can be replaced with an XHCI mouse
# by setting this option to yes. Please note only some guests support
# this mouse
#
xhci_mouse="yes"
# virt_console0
# create up to 16 virtual console devices
#
# the value can be yes|on|1 to create a numbered port. FreeBSD < 12
# only supports virtio consoles configured in this way
#
# For guests with named console support (FreeBSD 12+, Linux?), the
# value can be the name of the port to create. The name "org.freenas.byhve-agent"
# can be useful as it ties in with tools written to make use of the
# FreeNAS bhyve-agent interface.
#
virt_console0="org.freenas.byhve-agent"
# grub_install0
# use this to specify grub commands that should be run inside the
# guest when installing.
#
# If more than one command is needed, you can specify this option
# multiple times, incrementing the number on the end each time
#
grub_install0="..."
grub_install1="..."
# grub_run0
# use this to specify grub commands to run when starting the guest
# normally
#
grub_run0="linux ..."
grub_run1="initrd ..."
# grub_run_partition
# by default 'hd0,1' is specified as the root when running grub-bhyve.
# to force 'hd0,X' instead, set this to 'X'.
# in most cases the default of partition 1 is correct, although
# this settings allows you to force grub-bhyve to look on a different
# partition if required.
#
grub_run_partition="msdos1"
# grub_run_dir
# by default grub-bhyve will look in /boot/grub for the guests
# grub config file. use this to specify an alterate path
#
# Default: /boot/grub (set by grub-bhyve)
#
grub_run_dir="/grub"
# grub_run_file
# by default grub-bhyve will look for a file called grub.cfg containing
# the guests grub configuration. use this to specify an alternate filename
#
# Default: grub.cfg (set by grub-bhyve)
#
grub_run_file="grub.conf"
# zfs_dataset_opts
# A list of ZFS properties to set on any new dataset created for this
# guest. Multiple properties can be specified, separated by a space.
# As a dataset is created while provisioning a new guest, this option
# makes most sense when specified inside a template.
#
# Please note that spaces are currently not supported in the field values
#
zfs_dataset_opts=""
# zfs_zvol_opts
# A list of ZFS properties to set on any new ZVOL created for this guest.
# As with dataset_opts, this should to be set inside the guest template
# if you need the properties to apply to the guest as it is created.
# Some options such as volblocksize cannot be changed once the guest
# disk has been created.
#
zfs_zvol_opts=""
# prestart
# specify a script to run when the guest starts
# if just a name rather than full path is provided, we look in the guest directory
# the script must be executable and is run in the following way -
#
# {scriptname} <guest-name> [zfs-dataset?]
#
# we also change directory to <guest-path> before running the script
# note that if taking guest snapshots, the -f option must be used as although
# the guest is technically stopped when this script runs, vm-bhyve still has it
# locked
#
prestart="myscript.pl"
# priority
# set a priority (nice value) for a guest
# valid range is -20 (highest) to 20 (only run when system idle), with
# 0 being the default system priority
#
priority="10"
# limit_pcpu
# use rctl to limit guest to the specified cpu percentage
#
limit_pcpu=""
# limit_rbps, limit_wbps, limit_riops, limit_wiops
# Configure additional rctl limits available on 11+
# These limit read/write throughput and iops
#
limit_rbps=""
limit_wbps=""
limit_riops=""
limit_wiops=""

View File

@ -0,0 +1,14 @@
# Use GRUB when booting from an installation medium
#loader="grub"
# Use UEFI when booting from a disk
loader="uefi"
cpu=1
memory=1024M
network0_type="virtio-net"
network0_switch="public"
disk0_type="virtio-blk"
disk0_name="disk0.img"
grub_install0="linux /coreos/vmlinuz coreos.autologin"
grub_install1="initrd /coreos/cpio.gz"

View File

@ -0,0 +1,9 @@
loader="grub"
cpu=1
memory=512M
network0_type="virtio-net"
network0_switch="public"
disk0_type="ahci-hd"
disk0_name="disk0.img"
grub_run_partition="1"
grub_run_dir="/boot/grub"

View File

@ -0,0 +1,7 @@
loader="bhyveload"
cpu=1
memory=256M
network0_type="virtio-net"
network0_switch="public"
disk0_type="virtio-blk"
disk0_name="disk0.img"

View File

@ -0,0 +1,14 @@
# DragonFly 4.6 and later can boot with UEFI. The Dragonfly installer works
# with UEFI beginning at 4.8
loader="uefi"
# The live CD has a serial console, but the installer requires graphics
graphics="yes"
graphics_wait="no"
cpu=1
# 4GB of RAM is the minimum when using HAMMER.
memory=4G
network0_type="virtio-net"
network0_switch="public"
# The installer requires ahci. It can't correctly partition a virtio-blk
disk0_type="ahci-hd"
disk0_name="disk0.img"

View File

@ -0,0 +1,8 @@
loader="bhyveload"
cpu=1
memory=256M
network0_type="virtio-net"
network0_switch="public"
disk0_type="virtio-blk"
disk0_name="disk0"
disk0_dev="sparse-zvol"

View File

@ -0,0 +1,12 @@
loader="grub"
cpu=1
memory=512M
network0_type="virtio-net"
network0_switch="public"
disk0_type="virtio-blk"
disk0_name="disk0.img"
grub_install0="linux /isolinux/vmlinuz vga=normal ramdisk_size=32768 ks=cdrom:/kickstart-simple-asterisk13.cfg asknetwork LANG=en_US.UTF-8 KEYTABLE=us SYSFONT=latarcyrheb-sun16 console=ttyS0"
grub_install1="initrd /isolinux/initrd.img"
grub_run0="linux /vmlinuz-2.6.32-642.6.2.el6.x86_64 root=/dev/mapper/VolGroup-lv_root LANG=en_US.UTF-8 KEYTABLE=us SYSFONT=latarcyrheb-sun16 rd_NO_LUKS LANG=en_US.UTF-8 rd_NO_MD rd_LVM_LV=VolGroup/lv_swap SYSFONT=latarcyrheb-sun16 console=ttyS0 crashkernel=auto rd_LVM_LV=VolGroup/lv_root KEYBOARDTYPE=pc KEYTABLE=us rd_NO_DM"
grub_run1="initrd /initramfs-2.6.32-642.6.2.el6.x86_64.img"

View File

@ -0,0 +1,13 @@
loader="grub"
cpu=1
memory=512MB
network0_type="virtio-net"
network0_switch="public"
disk0_type="virtio-blk"
disk0_name="disk0.img"
grub_install0="linux /boot/gentoo root=/dev/ram0 init=/linuxrc dokeymap looptype=squashfs loop=/image.squashfs cdroot"
grub_install1="initrd /boot/gentoo.igz"
# Make sure to modify the "root" variable according to your partitioning scheme.
grub_run0="set root=(hd0,gpt2)"
grub_run1="set timeout=1"
grub_run2="configfile /grub/grub.cfg"

View File

@ -0,0 +1,8 @@
loader="grub"
cpu=1
memory=512M
network0_type="virtio-net"
network0_switch="public"
disk0_name="disk0"
disk0_dev="sparse-zvol"
disk0_type="virtio-blk"

View File

@ -0,0 +1,9 @@
loader="grub"
cpu=1
memory=256M
network0_type="virtio-net"
network0_switch="public"
disk0_type="virtio-blk"
disk0_name="disk0.img"
grub_install0="knetbsd -h -r cd0a /netbsd"
grub_run0="knetbsd -h -r dk0 /netbsd"

View File

@ -0,0 +1,10 @@
loader="grub"
cpu=1
memory=256M
network0_type="virtio-net"
network0_switch="public"
disk0_type="virtio-blk"
disk0_name="disk0.img"
grub_install0="kopenbsd -h com0 /6.2/amd64/bsd.rd"
grub_run0="kopenbsd -h com0 -r sd0a /bsd"
bhyve_options="-w"

View File

@ -0,0 +1,10 @@
loader="grub"
cpu=1
memory=256M
network0_type="virtio-net"
network0_switch="public"
disk0_type="virtio-blk"
disk0_name="disk0.img"
grub_run_partition="openbsd4"
grub_run0="kopenbsd -h com0 -r sd0d /bsd"
bhyve_options="-w"

View File

@ -0,0 +1,7 @@
loader="grub"
cpu=1
memory=512M
network0_type="virtio-net"
network0_switch="public"
disk0_type="virtio-blk"
disk0_name="disk0.img"

View File

@ -0,0 +1,21 @@
loader="uefi"
graphics="yes"
xhci_mouse="yes"
cpu=2
memory=2G
# put up to 8 disks on a single ahci controller.
# without this, adding a disk pushes the following network devices onto higher slot numbers,
# which causes windows to see them as a new interface
ahci_device_limit="8"
# ideally this should be changed to virtio-net and drivers installed in the guest
# e1000 works out-of-the-box
network0_type="e1000"
network0_switch="public"
disk0_type="ahci-hd"
disk0_name="disk0.img"
# windows expects the host to expose localtime by default, not UTC
utctime="no"

49
vm Normal file
View File

@ -0,0 +1,49 @@
#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (C) 2016 Matt Churchyard (churchers@gmail.com)
# All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# get libs
if [ -e "/usr/local/lib/vm-bhyve" ]; then
LIB="/usr/local/lib/vm-bhyve"
else
echo "unable to locate vm-bhyve libraries"
exit 1
fi
# load libs
. "${LIB}/vm-cmd"
. "${LIB}/vm-config"
. "${LIB}/vm-core"
. "${LIB}/vm-datastore"
. "${LIB}/vm-guest"
. "${LIB}/vm-info"
. "${LIB}/vm-migration"
. "${LIB}/vm-rctl"
. "${LIB}/vm-run"
. "${LIB}/vm-switch"
. "${LIB}/vm-util"
. "${LIB}/vm-zfs"
. "${LIB}/vm-base"

1605
vm.8 Normal file

File diff suppressed because it is too large Load Diff