Skip to content

Commit

Permalink
Support network resource management
Browse files Browse the repository at this point in the history
  • Loading branch information
onixie committed Feb 13, 2020
1 parent bff6054 commit 81c21f3
Show file tree
Hide file tree
Showing 11 changed files with 381 additions and 11 deletions.
4 changes: 3 additions & 1 deletion nix/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,7 @@
options = [
./virtualbox.nix
];
resources = { ... }: {};
resources = { evalResources, zipAttrs, resourcesByType, ...}: {
vboxNetworks = evalResources ./virtualbox-network.nix (zipAttrs resourcesByType.vboxNetworks or []);
};
}
55 changes: 55 additions & 0 deletions nix/virtualbox-network.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{ config, lib, pkgs, uuid, name, ... }:

with lib;
with import <nixops/lib.nix> lib;

rec {
options = {
type = mkOption {
default = "hostonly";
description = ''
The type of the VirtualBox network.
Either NAT network or Host-only network can be specified. Defaults to Host-only Network.
'';
type = types.enum [ "natnet" "hostonly" ];
};

cidrBlock = mkOption {
example = "192.168.56.0/24";
description = ''
The IPv4 CIDR block for the VirtualBox network. The following IP addresses are reserved for the network:
Network - The first address in the IP range, e.g. 192.168.56.0 in 192.168.56.0/24
Gateway - The second address in the IP range, e.g. 192.168.56.1 in 192.168.56.0/24
DHCP Server - The third address in the IP range, e.g. 192.168.56.2 in 192.168.56.0/24
Broadcast - The last address in the IP range, e.g. 192.168.56.255 in 192.168.56.0/24
'';
type = types.str;
};

staticIPs = mkOption {
default = [];
description = "The list of machine to IPv4 address bindings for fixing IP address of the machine in the network";
type = with types; listOf (submodule {
options = {
machine = mkOption {
type = either str (resource "machine");
apply = x: if builtins.isString x then x else x._name;
description = "The name of the machine in the network";
};
address = mkOption {
example = "192.168.56.3";
type = str;
description = ''
The IPv4 address assigned to the machine as static IP.
The static IP must be a non-reserved IP address.
'';
};
};
});
};
};

config = {
_type = "vbox-network";
};
}
32 changes: 31 additions & 1 deletion nix/virtualbox.nix
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{ config, pkgs, lib, ... }:
{ config, pkgs, lib, resources, ... }:

with lib;
with import <nixops/lib.nix> lib;

let

Expand Down Expand Up @@ -91,6 +92,35 @@ in
});
};

deployment.virtualbox.networks = mkOption {
default = [ { type = "nat"; } { type = "hostonly"; name = "vboxnet0"; } ];
description = ''
The list of networks to which the instance is attached. The network can be either
a vbox-network resource or a network not managed by NixOps.
For the sake of backward compatibility, the default list contains the following networks:
- NAT
- Host-only network vboxnet0
Note: NixOps requires at least one Host-only network to access the instance for management purposes,
When multiple Host-only networks exist, the first one in the list will be used for machine connection.
'';
type = with types; listOf (either (resource "vbox-network")
(submodule { options = {
name = mkOption {
default = "";
description = "The name of the network not managed by NixOps";
type = str;
};
type = mkOption {
description = "The type of the network";
type = enum [ "nat" "natnet" "bridge" "hostonly" "intnet" "generic" ];
};
};
})
);
};

deployment.virtualbox.sharedFolders = mkOption {
default = {};

Expand Down
64 changes: 56 additions & 8 deletions nixopsvbox/backends/virtualbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
import time
import shutil
import stat
import re
from collections import defaultdict
from nixops.backends import MachineDefinition, MachineState
from nixops.nix_expr import RawValue
import nixops.known_hosts
import nixopsvbox.resources.virtualbox_network
from distutils import spawn

sata_ports = 8
Expand Down Expand Up @@ -49,6 +52,7 @@ def get_type(cls):
def __init__(self, depl, name, id):
MachineState.__init__(self, depl, name, id)
self._disk_attached = False
self._hooks = defaultdict(list)

@property
def resource_id(self):
Expand Down Expand Up @@ -123,8 +127,10 @@ def _get_vm_state(self, can_fail=False):


def _start(self):
nic_num, _ = next(self._get_nic_info())

self._logged_exec(
["VBoxManage", "guestproperty", "set", self.vm_id, "/VirtualBox/GuestInfo/Net/1/V4/IP", ''])
["VBoxManage", "guestproperty", "set", self.vm_id, "/VirtualBox/GuestInfo/Net/{}/V4/IP".format(nic_num - 1), ''])

self._logged_exec(
["VBoxManage", "guestproperty", "set", self.vm_id, "/VirtualBox/GuestInfo/Charon/ClientPublicKey", self._client_public_key])
Expand All @@ -134,10 +140,11 @@ def _start(self):

self.state = self.STARTING


def _update_ip(self):
nic_num, _ = next(self._get_nic_info())

res = self._logged_exec(
["VBoxManage", "guestproperty", "get", self.vm_id, "/VirtualBox/GuestInfo/Net/1/V4/IP"],
["VBoxManage", "guestproperty", "get", self.vm_id, "/VirtualBox/GuestInfo/Net/{}/V4/IP".format(nic_num - 1)],
capture_stdout=True).rstrip()
if res[0:7] != "Value: ": return
new_address = res[7:]
Expand Down Expand Up @@ -173,6 +180,9 @@ def _wait_for_ip(self):
self.log_end(" " + self.private_ipv4)


def create_after(self, resources, defn):
return {r for r in resources if isinstance(r, nixopsvbox.resources.virtualbox_network.VirtualBoxNetworkState)}

def create(self, defn, check, allow_reboot, allow_recreate):
assert isinstance(defn, VirtualBoxDefinition)

Expand All @@ -191,6 +201,9 @@ def create(self, defn, check, allow_reboot, allow_recreate):
self.vm_id = vm_id
self.state = self.STOPPED

for hook in self._hooks.get("after_createvm", []):
hook()

# Generate a public/private host key.
if not self.public_host_key:
(private, public) = nixops.util.create_key_pair()
Expand Down Expand Up @@ -350,13 +363,10 @@ def create(self, defn, check, allow_reboot, allow_recreate):
modifyvm_args = [
"--memory", str(defn.config["virtualbox"]["memorySize"]),
"--vram", "10",
"--nictype1", "virtio",
"--nictype2", "virtio",
"--nic2", "hostonly",
"--hostonlyadapter2", "vboxnet0",
"--nestedpaging", "off",
"--paravirtprovider", "kvm"
]
] + [ f for fs in [ nic["flags"] for nic in self.parse_nic_spec(defn, flags=True).values() ] for f in fs if f ]

vcpus = defn.config["virtualbox"]["vcpu"] # None or integer
if vcpus is not None:
modifyvm_args.extend(["--cpus", str(vcpus)])
Expand Down Expand Up @@ -470,3 +480,41 @@ def _check(self, res):
MachineState._check(self, res)
else:
self.state = self.UNKNOWN

def _get_nic_info(self, network_type="hostonly"):
res = re.findall(r"nic(\d+)=\"({})\"".format(network_type), self._logged_exec(
["VBoxManage", "showvminfo", self.vm_id, "--machinereadable"],
capture_stdout=True
))
return ((int(num), type) for num, type in res)

def parse_nic_spec(self, defn, num=True, flags=False):
def to_arg(type):
if type == "bridge": return "bridged"
elif type == "natnet": return "natnetwork"
else:
return type

def to_adp(type):
if type == "bridge" : return "--bridgeadapter{}"
elif type == "hostonly": return "--hostonlyadapter{}"
elif type == "intnet" : return "--intnet{}"
elif type == "natnet" : return "--nat-network{}"
elif type == "generic" : return "--nicgenericdrv{}"
elif type == "nat" : return ""
else:
raise Exception("unknown NIC type is specified on VirtualBox VM ‘{0}’".format(self.name))

res = defaultdict(lambda: {})
for i, net in enumerate(defn.config["virtualbox"]["networks"], start=1):
k = "{0}:{1}".format(net.get("name", net.get("_name")), net.get("type"))
if num : res[k]["num"] = i
if flags: res[k]["flags"] = [
"--nic{0}".format(i) , to_arg(net.get("type")),
"--nictype{0}".format(i) , "virtio",
to_adp(net.get("type")).format(i), self.depl.resources[net.get("_name")].network_name if net.get("_name") else net.get("name", "")
]
return res

def add_hook(self, k, handler):
self._hooks[k].append(handler)
1 change: 1 addition & 0 deletions nixopsvbox/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ def nixexprs():
@nixops.plugins.hookimpl
def load():
return [
"nixopsvbox.resources",
"nixopsvbox.backends.virtualbox",
]
2 changes: 2 additions & 0 deletions nixopsvbox/resources/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import virtualbox_network
import __init__
Binary file added nixopsvbox/resources/__init__.pyc
Binary file not shown.
Loading

0 comments on commit 81c21f3

Please sign in to comment.