Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve networking options for libvirtd target #2

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions nix/libvirtd-image.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ let
config = (import <nixpkgs/nixos/lib/eval-config.nix> {
inherit system;
modules = [ {
imports = [
<nixpkgs/nixos/modules/profiles/qemu-guest.nix>
];
fileSystems."/".device = "/dev/disk/by-label/nixos";

boot.loader.grub.version = 2;
Expand All @@ -13,6 +16,8 @@ let
services.openssh.enable = true;
services.openssh.startWhenNeeded = false;
services.openssh.extraConfig = "UseDNS no";

services.qemuGuest.enable = true;
} ];
}).config;

Expand Down
10 changes: 7 additions & 3 deletions nix/libvirtd.nix
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,11 @@ in
};

deployment.libvirtd.networks = mkOption {
default = [ "default" ];
type = types.listOf types.str;
description = "Names of libvirt networks to attach the VM to.";
type = types.listOf (types.submodule (import ./network-options.nix {
inherit lib;
}));
default = [{ source = "default"; type= "virtual"; }];
description = "Describe network interfaces.";
};

deployment.libvirtd.extraDevicesXML = mkOption {
Expand Down Expand Up @@ -156,6 +158,8 @@ in
services.openssh.extraConfig = "UseDNS no";

deployment.hasFastConnection = true;

services.qemuGuest.enable = true;
};

}
23 changes: 23 additions & 0 deletions nix/network-options.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{ lib } :

with lib;
{
options = {

source = mkOption {
type = types.str;
default = "default";
description = ''
'';
};

type = mkOption {
type = types.enum [ "bridge" "virtual" ];
default = "virtual";
description = ''
'';
};

};

}
129 changes: 91 additions & 38 deletions nixopsvirtd/backends/libvirtd.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,29 @@
# to prevent libvirt errors from appearing on screen, see
# https://www.redhat.com/archives/libvirt-users/2017-August/msg00011.html


class LibvirtdNetwork:

INTERFACE_TYPES = {
'virtual': 'network',
'bridge': 'bridge',
}

def __init__(self, **kwargs):
self.type = kwargs['type']
self.source = kwargs['source']

@property
def interface_type(self):
return self.INTERFACE_TYPES[self.type]

@classmethod
def from_xml(cls, x):
type = x.find("attr[@name='type']/string").get("value")
source = x.find("attr[@name='source']/string").get("value")
return cls(type=type, source=source)


class LibvirtdDefinition(MachineDefinition):
"""Definition of a trivial machine."""

Expand All @@ -35,6 +58,9 @@ def __init__(self, xml, config):
self.extra_devices = x.find("attr[@name='extraDevicesXML']/string").get("value")
self.extra_domain = x.find("attr[@name='extraDomainXML']/string").get("value")
self.headless = x.find("attr[@name='headless']/bool").get("value") == 'true'
self.image_dir = x.find("attr[@name='imageDir']/string")
if self.image_dir:
self.image_dir = self.image_dir.get("value")
self.domain_type = x.find("attr[@name='domainType']/string").get("value")
self.kernel = x.find("attr[@name='kernel']/string").get("value")
self.initrd = x.find("attr[@name='initrd']/string").get("value")
Expand All @@ -43,17 +69,21 @@ def __init__(self, xml, config):
self.uri = x.find("attr[@name='URI']/string").get("value")

self.networks = [
k.get("value")
for k in x.findall("attr[@name='networks']/list/string")]
LibvirtdNetwork.from_xml(n)
for n in x.findall("attr[@name='networks']/list/*")
]

print("%r" % self.networks)
# print("%r" % self.networks[0])
# print("%r" % self.networks[1])
print("len=%d" % len(self.networks))
assert len(self.networks) > 0


class LibvirtdState(MachineState):
private_ipv4 = nixops.util.attr_property("privateIpv4", None)
client_public_key = nixops.util.attr_property("libvirtd.clientPublicKey", None)
client_private_key = nixops.util.attr_property("libvirtd.clientPrivateKey", None)
primary_net = nixops.util.attr_property("libvirtd.primaryNet", None)
primary_mac = nixops.util.attr_property("libvirtd.primaryMAC", None)
domain_xml = nixops.util.attr_property("libvirtd.domainXML", None)
disk_path = nixops.util.attr_property("libvirtd.diskPath", None)
storage_volume_name = nixops.util.attr_property("libvirtd.storageVolume", None)
Expand Down Expand Up @@ -134,17 +164,9 @@ def address_to(self, m):
def _vm_id(self):
return "nixops-{0}-{1}".format(self.depl.uuid, self.name)

def _generate_primary_mac(self):
mac = [0x52, 0x54, 0x00,
random.randint(0x00, 0x7f),
random.randint(0x00, 0xff),
random.randint(0x00, 0xff)]
self.primary_mac = ':'.join(map(lambda x: "%02x" % x, mac))

def create(self, defn, check, allow_reboot, allow_recreate):
assert isinstance(defn, LibvirtdDefinition)
self.set_common_state(defn)
self.primary_net = defn.networks[0]
self.storage_pool_name = defn.storage_pool_name
self.uri = defn.uri

Expand All @@ -153,14 +175,13 @@ def create(self, defn, check, allow_reboot, allow_recreate):
if self.conn.getLibVersion() < 1002007:
raise Exception('libvirt 1.2.7 or newer is required at the target host')

if not self.primary_mac:
self._generate_primary_mac()
self.storage_pool_name = defn.storage_pool_name

if not self.client_public_key:
(self.client_private_key, self.client_public_key) = nixops.util.create_key_pair()

if self.storage_volume_name is None:
self._prepare_storage_volume()
self._prepare_storage_volume(defn)
self.storage_volume_name = self.vol.name()

self.domain_xml = self._make_domain_xml(defn)
Expand All @@ -178,7 +199,7 @@ def create(self, defn, check, allow_reboot, allow_recreate):
self.start()
return True

def _prepare_storage_volume(self):
def _prepare_storage_volume(self, defn):
self.logger.log("preparing disk image...")
newEnv = copy.deepcopy(os.environ)
newEnv["NIXOPS_LIBVIRTD_PUBKEY"] = self.client_public_key
Expand All @@ -200,27 +221,40 @@ def _prepare_storage_volume(self):

self.logger.log("uploading disk image...")
image_info = self._get_image_info(temp_disk_path)
self._vol = self._create_volume(image_info['virtual-size'], image_info['actual-size'])
self._vol = self._create_volume(image_info['virtual-size'], image_info['actual-size'], defn.image_dir)
self._upload_volume(temp_disk_path, image_info['actual-size'])

def _get_image_info(self, filename):
output = self._logged_exec(["qemu-img", "info", "--output", "json", filename], capture_stdout=True)
return json.loads(output)

def _create_volume(self, virtual_size, actual_size):
def _create_volume(self, virtual_size, actual_size, path=None):
# according to https://libvirt.org/formatstorage.html#StoragePoolTarget
# files should be created with rights depending on parent folder but
# this doesn't seem true
# here I hardcode permission rights (BAD)
xml = '''
<volume>
<name>{name}</name>
<capacity>{virtual_size}</capacity>
<allocation>{actual_size}</allocation>
<target>
<format type="qcow2"/>
<permissions>
<owner>1000</owner>
<group>100</group>
<mode>0744</mode>
<label>virt_image_t</label>
</permissions>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs to be fixed before merge. Is there an upstream libvirt bug about the mismatch in docs and reality mentioned in the comment above?

Explicitly specifying owner, group and SELinux label will break many setups, including mine.

Copy link
Contributor Author

@teto teto Oct 17, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it might have become unnecessary with the other PR merged today. I will look into it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so if I understand the doc correctly, the pool is created with the permission of the folder but the volums are created by default with the user/group that libvirtd is running as. Seems like the libvirtd service is running as root without any nix option to change it so this looks a bit more complex than I have time for (for now) so feel free to look into it.

{eventual_path}
</target>
</volume>
'''.format(
name="{}.qcow2".format(self._vm_id()),
virtual_size=virtual_size,
actual_size=actual_size,
# eventual_path= "<path >%s</path>" % path if path else ""
eventual_path= ""
)
vol = self.pool.createXML(xml)
self._vol = vol
Expand All @@ -247,19 +281,15 @@ def _get_qemu_executable(self):
def _make_domain_xml(self, defn):
qemu = self._get_qemu_executable()

def maybe_mac(n):
if n == self.primary_net:
return '<mac address="' + self.primary_mac + '" />'
else:
return ""

def iface(n):
return "\n".join([
' <interface type="network">',
maybe_mac(n),
' <source network="{0}"/>',
' <interface type="{interface_type}">',
' <source {interface_type}="{source}"/>',
' </interface>',
]).format(n)
]).format(
interface_type=n.interface_type,
source=n.source,
)

def _make_os(defn):
return [
Expand Down Expand Up @@ -287,6 +317,10 @@ def _make_os(defn):
' <graphics type="vnc" port="-1" autoport="yes"/>' if not defn.headless else "",
' <input type="keyboard" bus="usb"/>',
' <input type="mouse" bus="usb"/>',
' <channel type="unix">',
' <target type="virtio" name="org.qemu.guest_agent.0"/>',
' <address type="virtio-serial" controller="0" bus="0" port="1"/>',
' </channel>',
defn.extra_devices,
' </devices>',
defn.extra_domain,
Expand All @@ -306,19 +340,39 @@ def _parse_ip(self):
"""
return an ip v4
"""
# alternative is VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE if qemu agent is available
ifaces = self.dom.interfaceAddresses(libvirt.VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE, 0)
if ifaces is None:
self.log("Failed to get domain interfaces")

try:
ifaces = self.dom.interfaceAddresses(libvirt.VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT, 0)
# if ifaces is None:
# self.log("Failed to get domain interfaces")
# return
# print("The interface IP addresses:")
from ipaddress import ip_address
for (name, val) in ifaces.iteritems():
self.log("Parsing interface %s..." % name)

if val['addrs']:
for ipaddr in val['addrs']:
curaddr = ip_address(unicode(ipaddr['addr']))

if ipaddr['type'] == libvirt.VIR_IP_ADDR_TYPE_IPV4:
print(ipaddr['addr'] + " VIR_IP_ADDR_TYPE_IPV4")
if curaddr.is_loopback:
continue
self.success("Found address")
return ipaddr['addr']

else:
pass

except libvirt.libvirtError as e:
self.log(str(e))
return

for (name, val) in ifaces.iteritems():
if val['addrs']:
for ipaddr in val['addrs']:
return ipaddr['addr']
return False


def _wait_for_ip(self, prev_time):
self.log_start("waiting for IP address to appear in DHCP leases...")
while True:
ip = self._parse_ip()
if ip:
Expand All @@ -338,7 +392,6 @@ def _is_running(self):
def start(self):
assert self.vm_id
assert self.domain_xml
assert self.primary_net
if self._is_running():
self.log("connecting...")
self.private_ipv4 = self._parse_ip()
Expand Down
2 changes: 1 addition & 1 deletion release.nix
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ in
done
'';
buildInputs = [ python2Packages.nose python2Packages.coverage ];
propagatedBuildInputs = [ python2Packages.libvirt ];
propagatedBuildInputs = [ python2Packages.ipaddress python2Packages.libvirt ];
doCheck = true;
postInstall = ''
mkdir -p $out/share/nix/nixops-libvirtd
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
url='https://github.com/AmineChikhaoui/nixops-libvirtd',
maintainer='Amine Chikhaoui',
maintainer_email='[email protected]',
install_requires=[ "ipaddress" ],
packages=['nixopsvirtd', 'nixopsvirtd.backends'],
entry_points={'nixops': ['virtd = nixopsvirtd.plugin']},
py_modules=['plugin']
Expand Down