Libvirt

Hook scripts

Libvirt can run hook scripts when certain events happen, such as a guest starting, stopping or migrating. You can define these scripts with libvirt_helm_values. Each entry under conf.hooks.scripts is mounted as an executable file at /etc/libvirt/hooks/<name> inside the libvirt pods, where <name> is one of daemon, qemu, lxc, libxl, bhyve or network.

For example, guests started by a libvirt with AppArmor enabled carry an AppArmor security label. The libvirt pods don’t use AppArmor, so live migrating these guests into Atmosphere fails with Security driver model 'apparmor' is not available. The following qemu hook removes those labels from the incoming guest definition when the migration begins on the destination host, so the guest arrives with only the DAC label:

libvirt_helm_values:
  conf:
    hooks:
      enabled: true
      scripts:
        qemu: |
          #!/usr/bin/env python3
          # Called as: /etc/libvirt/hooks/qemu <guest> <operation> <sub-op> <extra>
          #
          # For "migrate begin", libvirt passes the incoming domain XML on
          # stdin and uses whatever this script prints in its place. Printing
          # nothing keeps the original XML, which this script does for every
          # other operation, when there is nothing to strip, and on errors, so
          # the hook is never the reason a migration fails.

          import sys
          import xml.etree.ElementTree as ET
          from io import StringIO


          def strip_apparmor(xml):
              # Keep the original namespace prefixes (such as the "nova:"
              # metadata) instead of generated ns0/ns1 ones.
              for _, (prefix, uri) in ET.iterparse(StringIO(xml), events=("start-ns",)):
                  ET.register_namespace(prefix, uri)

              root = ET.fromstring(xml)
              removed = 0
              # Domain level <seclabel> and per-device <source><seclabel>.
              for parent in root.iter():
                  for child in list(parent):
                      if child.tag == "seclabel" and child.get("model") == "apparmor":
                          parent.remove(child)
                          removed += 1

              if not removed:
                  return None
              return ET.tostring(root, encoding="unicode")


          def main():
              if len(sys.argv) < 3 or sys.argv[2:4] != ["migrate", "begin"]:
                  return 0

              xml = sys.stdin.read()
              try:
                  out = strip_apparmor(xml)
              except Exception as exc:
                  sys.stderr.write("qemu hook: leaving XML unchanged: %s\n" % exc)
                  return 0

              if out:
                  sys.stdout.write(out)
              return 0


          if __name__ == "__main__":
              sys.exit(main())

The chart renders each script as a Helm template with the chart context, so scripts can reference values such as {{ .Release.Namespace }}. Escape any literal {{ sequences in your scripts.

Note

Libvirt only looks for hook scripts when it starts. Adding, changing or removing a script triggers a rolling restart of the libvirt pods, one node at a time.