From db6b22f7f9008a3ee66892f9a13fb17acd783d5a Mon Sep 17 00:00:00 2001 From: Thomas Parrott Date: Tue, 16 Jul 2024 16:08:17 +0100 Subject: [PATCH 01/99] lxd/instance/drivers/driver/common: Fix crash when device doesn't return run config when being live updated Fixes #13774 Signed-off-by: Thomas Parrott --- lxd/instance/drivers/driver_common.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lxd/instance/drivers/driver_common.go b/lxd/instance/drivers/driver_common.go index 7626c1bdca36..013ba308b6d4 100644 --- a/lxd/instance/drivers/driver_common.go +++ b/lxd/instance/drivers/driver_common.go @@ -1523,7 +1523,7 @@ func (d *common) devicesUpdate(inst instance.Instance, removeDevices deviceConfi "config": entry.Config, } - if len(runConf.Mounts) > 0 { + if runConf != nil && len(runConf.Mounts) > 0 { for _, opt := range runConf.Mounts[0].Opts { if strings.HasPrefix(opt, "mountTag=") { parts := strings.SplitN(opt, "=", 2) From 5bd86a7ffd1ab7ecadf9c7f472b593dabb18729a Mon Sep 17 00:00:00 2001 From: Wesley Hershberger Date: Fri, 5 Apr 2024 16:37:00 -0500 Subject: [PATCH 02/99] lxd/network: Pass bridge ips to `Firewall.NetworkSetup` Enables creation of firewall rules targeting the managed bridge's ip(s). Signed-off-by: Wesley Hershberger --- lxd/network/driver_bridge.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/lxd/network/driver_bridge.go b/lxd/network/driver_bridge.go index c8e46f5508a4..9716ab3e17d1 100644 --- a/lxd/network/driver_bridge.go +++ b/lxd/network/driver_bridge.go @@ -1357,16 +1357,20 @@ func (n *bridge) setup(oldConfig map[string]string) error { } } + var ipv4Address net.IP + // Configure IPv4. if !shared.ValueInSlice(n.config["ipv4.address"], []string{"", "none"}) { + var subnet *net.IPNet + // Parse the subnet. - ipAddress, subnet, err := net.ParseCIDR(n.config["ipv4.address"]) + ipv4Address, subnet, err = net.ParseCIDR(n.config["ipv4.address"]) if err != nil { return fmt.Errorf("Failed parsing ipv4.address: %w", err) } // Update the dnsmasq config. - dnsmasqCmd = append(dnsmasqCmd, fmt.Sprintf("--listen-address=%s", ipAddress.String())) + dnsmasqCmd = append(dnsmasqCmd, fmt.Sprintf("--listen-address=%s", ipv4Address.String())) if n.DHCPv4Subnet() != nil { if !shared.ValueInSlice("--dhcp-no-override", dnsmasqCmd) { dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-no-override", "--dhcp-authoritative", fmt.Sprintf("--dhcp-leasefile=%s", shared.VarPath("networks", n.name, "dnsmasq.leases")), fmt.Sprintf("--dhcp-hostsfile=%s", shared.VarPath("networks", n.name, "dnsmasq.hosts"))}...) @@ -1482,6 +1486,8 @@ func (n *bridge) setup(oldConfig map[string]string) error { return err } + var ipv6Address net.IP + // Configure IPv6. if !shared.ValueInSlice(n.config["ipv6.address"], []string{"", "none"}) { // Enable IPv6 for the subnet. @@ -1490,8 +1496,10 @@ func (n *bridge) setup(oldConfig map[string]string) error { return err } + var subnet *net.IPNet + // Parse the subnet. - ipAddress, subnet, err := net.ParseCIDR(n.config["ipv6.address"]) + ipv6Address, subnet, err = net.ParseCIDR(n.config["ipv6.address"]) if err != nil { return fmt.Errorf("Failed parsing ipv6.address: %w", err) } @@ -1514,7 +1522,7 @@ func (n *bridge) setup(oldConfig map[string]string) error { } // Update the dnsmasq config. - dnsmasqCmd = append(dnsmasqCmd, []string{fmt.Sprintf("--listen-address=%s", ipAddress.String()), "--enable-ra"}...) + dnsmasqCmd = append(dnsmasqCmd, []string{fmt.Sprintf("--listen-address=%s", ipv6Address.String()), "--enable-ra"}...) if n.DHCPv6Subnet() != nil { if n.hasIPv6Firewall() { fwOpts.FeaturesV6.ICMPDHCPDNSAccess = true @@ -1736,6 +1744,9 @@ func (n *bridge) setup(oldConfig map[string]string) error { fmt.Sprintf("--dhcp-hostsfile=%s", shared.VarPath("networks", n.name, "dnsmasq.hosts")), "--dhcp-range", fmt.Sprintf("%s,%s,%s", dhcpalloc.GetIP(hostSubnet, 2).String(), dhcpalloc.GetIP(hostSubnet, -2).String(), expiry)}...) + // Save the dnsmasq listen address so that firewall rules can be added later + ipv4Address = net.ParseIP(addr[0]) + // Setup the tunnel. if n.config["fan.type"] == "ipip" { r := &ip.Route{ @@ -2118,7 +2129,7 @@ func (n *bridge) setup(oldConfig map[string]string) error { // Setup firewall. n.logger.Debug("Setting up firewall") - err = n.state.Firewall.NetworkSetup(n.name, fwOpts) + err = n.state.Firewall.NetworkSetup(n.name, ipv4Address, ipv6Address, fwOpts) if err != nil { return fmt.Errorf("Failed to setup firewall: %w", err) } From 6993efe76273f3912849b147c370371262fed325 Mon Sep 17 00:00:00 2001 From: Wesley Hershberger Date: Fri, 5 Apr 2024 16:40:28 -0500 Subject: [PATCH 03/99] lxd/firewall: Drop DNS traffic to dnsmasq originating outside the bridge Fixes #13081. I went ahead and matched on the bridge IP since we don't want to prevent users from exposing port 53 in their instances. Signed-off-by: Wesley Hershberger --- lxd/firewall/drivers/drivers_nftables.go | 29 +++++++------------ .../drivers/drivers_nftables_templates.go | 16 +++++++--- lxd/firewall/drivers/drivers_xtables.go | 18 +++++++++--- lxd/firewall/firewall_interface.go | 2 +- 4 files changed, 37 insertions(+), 28 deletions(-) diff --git a/lxd/firewall/drivers/drivers_nftables.go b/lxd/firewall/drivers/drivers_nftables.go index df1e14ec32ff..86d4ff5afccf 100644 --- a/lxd/firewall/drivers/drivers_nftables.go +++ b/lxd/firewall/drivers/drivers_nftables.go @@ -265,23 +265,15 @@ func (d Nftables) networkSetupOutboundNAT(networkName string, SNATV4 *SNATOpts, } // networkSetupICMPDHCPDNSAccess sets up basic nftables overrides for ICMP, DHCP and DNS. -func (d Nftables) networkSetupICMPDHCPDNSAccess(networkName string, ipVersions []uint) error { - ipFamilies := []string{} - for _, ipVersion := range ipVersions { - switch ipVersion { - case 4: - ipFamilies = append(ipFamilies, "ip") - case 6: - ipFamilies = append(ipFamilies, "ip6") - } - } - +// This should be called with at least one of (ip4Address, ip6Address) != nil. +func (d Nftables) networkSetupICMPDHCPDNSAccess(networkName string, ip4Address net.IP, ip6Address net.IP) error { tplFields := map[string]any{ "namespace": nftablesNamespace, "chainSeparator": nftablesChainSeparator, "networkName": networkName, + "ip4Address": ip4Address.String(), + "ip6Address": ip6Address.String(), "family": "inet", - "ipFamilies": ipFamilies, } err := d.applyNftConfig(nftablesNetICMPDHCPDNS, tplFields) @@ -315,7 +307,7 @@ func (d Nftables) networkSetupACLChainAndJumpRules(networkName string) error { } // NetworkSetup configure network firewall. -func (d Nftables) NetworkSetup(networkName string, opts Opts) error { +func (d Nftables) NetworkSetup(networkName string, ip4Address net.IP, ip6Address net.IP, opts Opts) error { // Do this first before adding other network rules, so jump to ACL rules come first. if opts.ACL { err := d.networkSetupACLChainAndJumpRules(networkName) @@ -331,21 +323,20 @@ func (d Nftables) NetworkSetup(networkName string, opts Opts) error { } } - dhcpDNSAccess := []uint{} var ip4ForwardingAllow, ip6ForwardingAllow *bool if opts.FeaturesV4 != nil || opts.FeaturesV6 != nil { if opts.FeaturesV4 != nil { - if opts.FeaturesV4.ICMPDHCPDNSAccess { - dhcpDNSAccess = append(dhcpDNSAccess, 4) + if !opts.FeaturesV4.ICMPDHCPDNSAccess { + ip4Address = nil } ip4ForwardingAllow = &opts.FeaturesV4.ForwardingAllow } if opts.FeaturesV6 != nil { - if opts.FeaturesV6.ICMPDHCPDNSAccess { - dhcpDNSAccess = append(dhcpDNSAccess, 6) + if !opts.FeaturesV6.ICMPDHCPDNSAccess { + ip6Address = nil } ip6ForwardingAllow = &opts.FeaturesV6.ForwardingAllow @@ -356,7 +347,7 @@ func (d Nftables) NetworkSetup(networkName string, opts Opts) error { return err } - err = d.networkSetupICMPDHCPDNSAccess(networkName, dhcpDNSAccess) + err = d.networkSetupICMPDHCPDNSAccess(networkName, ip4Address, ip6Address) if err != nil { return err } diff --git a/lxd/firewall/drivers/drivers_nftables_templates.go b/lxd/firewall/drivers/drivers_nftables_templates.go index b08577070e5e..be76d4366ff1 100644 --- a/lxd/firewall/drivers/drivers_nftables_templates.go +++ b/lxd/firewall/drivers/drivers_nftables_templates.go @@ -46,16 +46,24 @@ chain in{{.chainSeparator}}{{.networkName}} { iifname "{{.networkName}}" tcp dport 53 accept iifname "{{.networkName}}" udp dport 53 accept + iifname "lo" tcp dport 53 accept + iifname "lo" udp dport 53 accept + + {{if ne .ip4Address "" -}} + ip daddr == "{{.ip4Address}}" tcp dport 53 drop + ip daddr == "{{.ip4Address}}" udp dport 53 drop - {{- range .ipFamilies}} - {{if eq . "ip" -}} iifname "{{$.networkName}}" icmp type {3, 11, 12} accept iifname "{{$.networkName}}" udp dport 67 accept - {{else -}} + {{- end}} + + {{if ne .ip6Address "" -}} + ip6 daddr == "{{.ip6Address}}" tcp dport 53 drop + ip6 daddr == "{{.ip6Address}}" udp dport 53 drop + iifname "{{$.networkName}}" icmpv6 type {1, 2, 3, 4, 133, 135, 136, 143} accept iifname "{{$.networkName}}" udp dport 547 accept {{- end}} - {{- end}} } chain out{{.chainSeparator}}{{.networkName}} { diff --git a/lxd/firewall/drivers/drivers_xtables.go b/lxd/firewall/drivers/drivers_xtables.go index 2c063b2808e7..e83552dfb2c6 100644 --- a/lxd/firewall/drivers/drivers_xtables.go +++ b/lxd/firewall/drivers/drivers_xtables.go @@ -379,11 +379,17 @@ func (d Xtables) networkSetupOutboundNAT(networkName string, subnet *net.IPNet, } // networkSetupICMPDHCPDNSAccess sets up basic iptables overrides for ICMP, DHCP and DNS. -func (d Xtables) networkSetupICMPDHCPDNSAccess(networkName string, ipVersion uint) error { +func (d Xtables) networkSetupICMPDHCPDNSAccess(networkName string, networkAddress net.IP, ipVersion uint) error { var rules [][]string if ipVersion == 4 { rules = [][]string{ {"4", networkName, "filter", "INPUT", "-i", networkName, "-p", "udp", "--dport", "67", "-j", "ACCEPT"}, + // Prevent DNS requests to the bridge's dnsmasq except from lo and the bridge + // `rules` is reversed when applied (iptablesPrepend(...)), so the drop rules come first + {"4", networkName, "filter", "INPUT", "-d", networkAddress.String(), "-p", "udp", "--dport", "53", "-j", "DROP"}, + {"4", networkName, "filter", "INPUT", "-d", networkAddress.String(), "-p", "tcp", "--dport", "53", "-j", "DROP"}, + {"4", networkName, "filter", "INPUT", "-i", "lo", "-p", "udp", "--dport", "53", "-j", "ACCEPT"}, + {"4", networkName, "filter", "INPUT", "-i", "lo", "-p", "tcp", "--dport", "53", "-j", "ACCEPT"}, {"4", networkName, "filter", "INPUT", "-i", networkName, "-p", "udp", "--dport", "53", "-j", "ACCEPT"}, {"4", networkName, "filter", "INPUT", "-i", networkName, "-p", "tcp", "--dport", "53", "-j", "ACCEPT"}, {"4", networkName, "filter", "OUTPUT", "-o", networkName, "-p", "udp", "--sport", "67", "-j", "ACCEPT"}, @@ -398,6 +404,10 @@ func (d Xtables) networkSetupICMPDHCPDNSAccess(networkName string, ipVersion uin } else if ipVersion == 6 { rules = [][]string{ {"6", networkName, "filter", "INPUT", "-i", networkName, "-p", "udp", "--dport", "547", "-j", "ACCEPT"}, + {"6", networkName, "filter", "INPUT", "-d", networkAddress.String(), "-p", "udp", "--dport", "53", "-j", "DROP"}, + {"6", networkName, "filter", "INPUT", "-d", networkAddress.String(), "-p", "tcp", "--dport", "53", "-j", "DROP"}, + {"6", networkName, "filter", "INPUT", "-i", "lo", "-p", "udp", "--dport", "53", "-j", "ACCEPT"}, + {"6", networkName, "filter", "INPUT", "-i", "lo", "-p", "tcp", "--dport", "53", "-j", "ACCEPT"}, {"6", networkName, "filter", "INPUT", "-i", networkName, "-p", "udp", "--dport", "53", "-j", "ACCEPT"}, {"6", networkName, "filter", "INPUT", "-i", networkName, "-p", "tcp", "--dport", "53", "-j", "ACCEPT"}, {"6", networkName, "filter", "OUTPUT", "-o", networkName, "-p", "udp", "--sport", "547", "-j", "ACCEPT"}, @@ -441,7 +451,7 @@ func (d Xtables) networkSetupDHCPv4Checksum(networkName string) error { } // NetworkSetup configure network firewall. -func (d Xtables) NetworkSetup(networkName string, opts Opts) error { +func (d Xtables) NetworkSetup(networkName string, ipv4Address net.IP, ipv6Address net.IP, opts Opts) error { if opts.SNATV4 != nil { err := d.networkSetupOutboundNAT(networkName, opts.SNATV4.Subnet, opts.SNATV4.SNATAddress, opts.SNATV4.Append) if err != nil { @@ -458,7 +468,7 @@ func (d Xtables) NetworkSetup(networkName string, opts Opts) error { if opts.FeaturesV4 != nil { if opts.FeaturesV4.ICMPDHCPDNSAccess { - err := d.networkSetupICMPDHCPDNSAccess(networkName, 4) + err := d.networkSetupICMPDHCPDNSAccess(networkName, ipv4Address, 4) if err != nil { return err } @@ -477,7 +487,7 @@ func (d Xtables) NetworkSetup(networkName string, opts Opts) error { if opts.FeaturesV6 != nil { if opts.FeaturesV6.ICMPDHCPDNSAccess { - err := d.networkSetupICMPDHCPDNSAccess(networkName, 6) + err := d.networkSetupICMPDHCPDNSAccess(networkName, ipv6Address, 6) if err != nil { return err } diff --git a/lxd/firewall/firewall_interface.go b/lxd/firewall/firewall_interface.go index 0510ccfb9cbf..f850ab429a54 100644 --- a/lxd/firewall/firewall_interface.go +++ b/lxd/firewall/firewall_interface.go @@ -11,7 +11,7 @@ type Firewall interface { String() string Compat() (bool, error) - NetworkSetup(networkName string, opts drivers.Opts) error + NetworkSetup(networkName string, ip4Address net.IP, ip6Address net.IP, opts drivers.Opts) error NetworkClear(networkName string, delete bool, ipVersions []uint) error NetworkApplyACLRules(networkName string, rules []drivers.ACLRule) error NetworkApplyForwards(networkName string, rules []drivers.AddressForward) error From 40576f3b29f32cb2c7cb996e71fb2f087bf8be9f Mon Sep 17 00:00:00 2001 From: Wesley Hershberger Date: Fri, 12 Apr 2024 17:13:46 -0500 Subject: [PATCH 04/99] test: Ensure dns traffic from external source is dropped Signed-off-by: Wesley Hershberger --- test/suites/container_devices_nic_bridged.sh | 83 +++++++++++++++++++- 1 file changed, 81 insertions(+), 2 deletions(-) diff --git a/test/suites/container_devices_nic_bridged.sh b/test/suites/container_devices_nic_bridged.sh index 78d1c40fc3ba..201618aff2ba 100644 --- a/test/suites/container_devices_nic_bridged.sh +++ b/test/suites/container_devices_nic_bridged.sh @@ -14,20 +14,23 @@ test_container_devices_nic_bridged() { ctMAC="0a:92:a7:0d:b7:d9" ipRand=$(shuf -i 0-9 -n 1) brName="lxdt$$" + dnsDomain="blah" # Standard bridge with random subnet and a bunch of options lxc network create "${brName}" lxc network set "${brName}" dns.mode managed - lxc network set "${brName}" dns.domain blah + lxc network set "${brName}" dns.domain "${dnsDomain}" lxc network set "${brName}" ipv4.nat true lxc network set "${brName}" ipv4.routing false lxc network set "${brName}" ipv6.routing false + lxc network set "${brName}" ipv4.dhcp.ranges 192.0.2.100-192.0.2.200 + lxc network set "${brName}" ipv6.dhcp.ranges 2001:db8::100-2001:db8::f00 lxc network set "${brName}" ipv6.dhcp.stateful true lxc network set "${brName}" bridge.hwaddr 00:11:22:33:44:55 lxc network set "${brName}" ipv4.address 192.0.2.1/24 lxc network set "${brName}" ipv6.address 2001:db8::1/64 lxc network set "${brName}" ipv4.routes 192.0.3.0/24 - lxc network set "${brName}" ipv6.routes 2001:db7::/64 + lxc network set "${brName}" ipv6.routes 2001:db8::/64 [ "$(cat /sys/class/net/${brName}/address)" = "00:11:22:33:44:55" ] # Record how many nics we started with. @@ -429,6 +432,82 @@ test_container_devices_nic_bridged() { lxc exec "${ctName}" -- udhcpc6 -f -i eth0 -n -q -t5 2>&1 | grep 'IPv6 obtained' fi + # Check that dnsmasq will resolve on the lo + # If testImage can't request a dhcp6 lease, it won't have an ip6 addr, so just + # check the A record; we only care about access to dnsmasq here, not the + # record itself. + dig -4 +retry=0 +notcp @192.0.2.1 A "${ctName}.${dnsDomain}" | grep "${ctName}.${dnsDomain}.\\+0.\\+IN.\\+A.\\+192.0.2." + dig -6 +retry=0 +notcp @2001:db8::1 A "${ctName}.${dnsDomain}" | grep "${ctName}.${dnsDomain}.\\+0.\\+IN.\\+A.\\+192.0.2." + dig -4 +retry=0 +tcp @192.0.2.1 A "${ctName}.${dnsDomain}" | grep "${ctName}.${dnsDomain}.\\+0.\\+IN.\\+A.\\+192.0.2." + dig -6 +retry=0 +tcp @2001:db8::1 A "${ctName}.${dnsDomain}" | grep "${ctName}.${dnsDomain}.\\+0.\\+IN.\\+A.\\+192.0.2." + + # Check that dnsmasq will resolve from the bridge + # testImage doesn't have dig, so we create a netns with eth0 connected to the + # bridge instead + ip link add veth_left type veth peer veth_right + ip link set veth_left master "${brName}" up + + ip netns add testdns + ip link set dev veth_right netns testdns + + ip netns exec testdns ip link set veth_right name eth0 + ip netns exec testdns ip link set dev eth0 up + ip netns exec testdns ip addr add 192.0.2.20/24 dev eth0 + ip netns exec testdns ip addr add 2001:db8::20/64 dev eth0 + + ip addr + ip netns exec testdns ip addr + + # Give eth0 a chance to finish duplicate addr detection (ipv6) + while ip netns exec testdns ip a | grep "tentative"; do + sleep 0.5 + done + + ip netns exec testdns dig -4 +retry=0 +notcp @192.0.2.1 A "${ctName}.${dnsDomain}" | grep "${ctName}.${dnsDomain}.\\+0.\\+IN.\\+A.\\+192.0.2." + ip netns exec testdns dig -6 +retry=0 +notcp @2001:db8::1 A "${ctName}.${dnsDomain}" | grep "${ctName}.${dnsDomain}.\\+0.\\+IN.\\+A.\\+192.0.2." + ip netns exec testdns dig -4 +retry=0 +tcp @192.0.2.1 A "${ctName}.${dnsDomain}" | grep "${ctName}.${dnsDomain}.\\+0.\\+IN.\\+A.\\+192.0.2." + ip netns exec testdns dig -6 +retry=0 +tcp @2001:db8::1 A "${ctName}.${dnsDomain}" | grep "${ctName}.${dnsDomain}.\\+0.\\+IN.\\+A.\\+192.0.2." + + ip netns exec testdns ip link delete eth0 + ip netns delete testdns + + # Ensure that dnsmasq is inaccessible from outside its managed bridge and the host lo + # This creates a new net namespace `testdns`, a bridge `testbr0`, and veths + # between; we need dns requests to come from an interface that isn't the + # lxd-managed bridge or the host's loopback, and `dig` doesn't let you specify + # the interface to use, only the source ip + testbr0Addr4=10.10.10.1 + testbr0Addr6=fc00:feed:beef::1 + + ip link add veth_left type veth peer veth_right + ip link add testbr0 type bridge + ip link set testbr0 up + ip addr add "${testbr0Addr4}/24" dev testbr0 + ip addr add "${testbr0Addr6}/64" dev testbr0 + ip link set veth_left master testbr0 up + + ip netns add testdns + ip link set dev veth_right netns testdns + + ip netns exec testdns ip link set veth_right name eth0 + ip netns exec testdns ip link set dev eth0 up + ip netns exec testdns ip addr add 10.10.10.2/24 dev eth0 + ip netns exec testdns ip addr add fc00:feed:beef::2/64 dev eth0 + ip netns exec testdns ip route add default via "${testbr0Addr4}" dev eth0 + ip netns exec testdns ip -6 route add default via "${testbr0Addr6}" dev eth0 + + ip addr + ip netns exec testdns ip addr + + ! ip netns exec testdns dig -4 +retry=0 +notcp @192.0.2.1 A "${ctName}.${dnsDomain}" | grep "${ctName}.${dnsDomain}.\\+0.\\+IN.\\+A.\\+192.0.2." + ! ip netns exec testdns dig -6 +retry=0 +notcp @2001:db8::1 A "${ctName}.${dnsDomain}" | grep "${ctName}.${dnsDomain}.\\+0.\\+IN.\\+A.\\+192.0.2." + ! ip netns exec testdns dig -4 +retry=0 +tcp @192.0.2.1 A "${ctName}.${dnsDomain}" | grep "${ctName}.${dnsDomain}.\\+0.\\+IN.\\+A.\\+192.0.2." + ! ip netns exec testdns dig -6 +retry=0 +tcp @2001:db8::1 A "${ctName}.${dnsDomain}" | grep "${ctName}.${dnsDomain}.\\+0.\\+IN.\\+A.\\+192.0.2." + + ip netns exec testdns ip link delete eth0 + ip netns delete testdns + ip link delete testbr0 + # Delete container, check LXD releases lease. lxc delete "${ctName}" -f From d48a17350e9bbd88812d1c878d9e1465db4c9ce5 Mon Sep 17 00:00:00 2001 From: Thomas Parrott Date: Fri, 14 Jun 2024 10:11:52 +0100 Subject: [PATCH 05/99] lxd/instance/drivers/driver/qemu: Use consistent host drive share device name when booting and hotplugging Fixes hot removal of boot time drive. Signed-off-by: Thomas Parrott --- lxd/instance/drivers/driver_qemu.go | 6 ++---- lxd/instance/drivers/driver_qemu_templates.go | 13 ++++++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index e6e2a10c0e13..da45ab08e317 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -2183,8 +2183,7 @@ func (d *qemu) deviceStart(dev device.Device, instanceRunning bool) (*deviceConf } func (d *qemu) deviceAttachPath(deviceName string) (mountTag string, err error) { - escapedDeviceName := filesystem.PathNameEncode(deviceName) - deviceID := fmt.Sprintf("%s%s", qemuDeviceIDPrefix, escapedDeviceName) + deviceID := qemuHostDriveDeviceID(deviceName, "virtio-fs") mountTag = d.generateQemuDeviceName(deviceName) // Detect virtiofsd path. @@ -2311,8 +2310,7 @@ func (d *qemu) deviceAttachBlockDevice(mount deviceConfig.MountEntryItem) error } func (d *qemu) deviceDetachPath(deviceName string) error { - escapedDeviceName := filesystem.PathNameEncode(deviceName) - deviceID := fmt.Sprintf("%s%s", qemuDeviceIDPrefix, escapedDeviceName) + deviceID := qemuHostDriveDeviceID(deviceName, "virtio-fs") mountTag := d.generateQemuDeviceName(deviceName) // Check if the agent is running. diff --git a/lxd/instance/drivers/driver_qemu_templates.go b/lxd/instance/drivers/driver_qemu_templates.go index c6b789266352..0e641afb440d 100644 --- a/lxd/instance/drivers/driver_qemu_templates.go +++ b/lxd/instance/drivers/driver_qemu_templates.go @@ -5,9 +5,15 @@ import ( "strings" "github.com/canonical/lxd/lxd/resources" + "github.com/canonical/lxd/lxd/storage/filesystem" "github.com/canonical/lxd/shared/osarch" ) +// qemuHostDriveDeviceID returns the device ID to use for a host drive share. +func qemuHostDriveDeviceID(deviceName string, protocol string) string { + return fmt.Sprintf("%s%s-%s", qemuDeviceIDPrefix, filesystem.PathNameEncode(deviceName), protocol) +} + type cfgEntry struct { key string value string @@ -630,8 +636,8 @@ func qemuDriveFirmware(opts *qemuDriveFirmwareOpts) []cfgSection { type qemuHostDriveOpts struct { dev qemuDevOpts + id string name string - nameSuffix string comment string fsdriver string mountTag string @@ -698,7 +704,7 @@ func qemuHostDrive(opts *qemuHostDriveOpts) []cfgSection { return []cfgSection{ driveSection, { - name: fmt.Sprintf(`device "dev-%s%s-%s"`, opts.name, opts.nameSuffix, opts.protocol), + name: fmt.Sprintf(`device "%s"`, opts.id), entries: append(qemuDeviceEntries(&deviceOpts), extraDeviceEntries...), }, } @@ -713,9 +719,9 @@ type qemuDriveConfigOpts struct { func qemuDriveConfig(opts *qemuDriveConfigOpts) []cfgSection { return qemuHostDrive(&qemuHostDriveOpts{ dev: opts.dev, + id: fmt.Sprintf("dev-qemu_config-drive-%s", opts.protocol), // Devices use "qemu_" prefix indicating that this is a internally named device. name: "qemu_config", - nameSuffix: "-drive", comment: fmt.Sprintf("Config drive (%s)", opts.protocol), mountTag: "config", protocol: opts.protocol, @@ -739,6 +745,7 @@ type qemuDriveDirOpts struct { func qemuDriveDir(opts *qemuDriveDirOpts) []cfgSection { return qemuHostDrive(&qemuHostDriveOpts{ dev: opts.dev, + id: qemuHostDriveDeviceID(opts.devName, opts.protocol), // Devices use "lxd_" prefix indicating that this is a user named device. name: fmt.Sprintf("lxd_%s", opts.devName), comment: fmt.Sprintf("%s drive (%s)", opts.devName, opts.protocol), From 1ce1646cd65f98b2176eb6fbf97d64a5d706e708 Mon Sep 17 00:00:00 2001 From: Thomas Parrott Date: Fri, 14 Jun 2024 10:26:22 +0100 Subject: [PATCH 06/99] lxd/instance/drivers/driver/qemu/templates: Fix qemuHostDriveDeviceID to support long device names Signed-off-by: Thomas Parrott --- lxd/instance/drivers/driver_qemu_templates.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lxd/instance/drivers/driver_qemu_templates.go b/lxd/instance/drivers/driver_qemu_templates.go index 0e641afb440d..d24de357332b 100644 --- a/lxd/instance/drivers/driver_qemu_templates.go +++ b/lxd/instance/drivers/driver_qemu_templates.go @@ -11,7 +11,9 @@ import ( // qemuHostDriveDeviceID returns the device ID to use for a host drive share. func qemuHostDriveDeviceID(deviceName string, protocol string) string { - return fmt.Sprintf("%s%s-%s", qemuDeviceIDPrefix, filesystem.PathNameEncode(deviceName), protocol) + suffix := "-" + protocol + maxNameLength := qemuDeviceIDMaxLength - (len(qemuDeviceIDPrefix) + len(suffix)) + return fmt.Sprintf("%s%s%s", qemuDeviceIDPrefix, hashIfLonger(filesystem.PathNameEncode(deviceName), maxNameLength), suffix) } type cfgEntry struct { From 859e64aa674dfc5b82e1acd05ac20b5d8152a634 Mon Sep 17 00:00:00 2001 From: Thomas Parrott Date: Fri, 14 Jun 2024 10:26:54 +0100 Subject: [PATCH 07/99] lxd/instance/drivers/driver/qemu: Align fsdev and chardev names for host drive to that used when hotplugging Signed-off-by: Thomas Parrott --- lxd/instance/drivers/driver_qemu.go | 9 ++++----- lxd/instance/drivers/driver_qemu_templates.go | 8 ++++---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index da45ab08e317..f33f9b5ed4f6 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -2234,7 +2234,7 @@ func (d *qemu) deviceAttachPath(deviceName string) (mountTag string, err error) reverter.Add(func() { _ = monitor.CloseFile(virtiofsdSockPath) }) err = monitor.AddCharDevice(map[string]any{ - "id": mountTag, + "id": deviceID, "backend": map[string]any{ "type": "socket", "data": map[string]any{ @@ -2252,7 +2252,7 @@ func (d *qemu) deviceAttachPath(deviceName string) (mountTag string, err error) return "", fmt.Errorf("Failed to add the character device: %w", err) } - reverter.Add(func() { _ = monitor.RemoveCharDevice(mountTag) }) + reverter.Add(func() { _ = monitor.RemoveCharDevice(deviceID) }) // Figure out a hotplug slot. pciDevID := qemuPCIDeviceIDStart @@ -2276,7 +2276,7 @@ func (d *qemu) deviceAttachPath(deviceName string) (mountTag string, err error) "bus": pciDeviceName, "addr": "00.0", "tag": mountTag, - "chardev": mountTag, + "chardev": deviceID, "id": deviceID, } @@ -2311,7 +2311,6 @@ func (d *qemu) deviceAttachBlockDevice(mount deviceConfig.MountEntryItem) error func (d *qemu) deviceDetachPath(deviceName string) error { deviceID := qemuHostDriveDeviceID(deviceName, "virtio-fs") - mountTag := d.generateQemuDeviceName(deviceName) // Check if the agent is running. monitor, err := qmp.Connect(d.monitorPath(), qemuSerialChardevName, d.getMonitorEventHandler()) @@ -2327,7 +2326,7 @@ func (d *qemu) deviceDetachPath(deviceName string) error { waitDuration := time.Duration(time.Second * time.Duration(10)) waitUntil := time.Now().Add(waitDuration) for { - err = monitor.RemoveCharDevice(mountTag) + err = monitor.RemoveCharDevice(deviceID) if err == nil { break } diff --git a/lxd/instance/drivers/driver_qemu_templates.go b/lxd/instance/drivers/driver_qemu_templates.go index d24de357332b..a143e36ae03f 100644 --- a/lxd/instance/drivers/driver_qemu_templates.go +++ b/lxd/instance/drivers/driver_qemu_templates.go @@ -664,7 +664,7 @@ func qemuHostDrive(opts *qemuHostDriveOpts) []cfgSection { } driveSection = cfgSection{ - name: fmt.Sprintf(`fsdev "%s"`, opts.name), + name: fmt.Sprintf(`fsdev "%s"`, opts.id), comment: opts.comment, entries: []cfgEntry{ {key: "fsdriver", value: opts.fsdriver}, @@ -680,11 +680,11 @@ func qemuHostDrive(opts *qemuHostDriveOpts) []cfgSection { extraDeviceEntries = []cfgEntry{ {key: "mount_tag", value: opts.mountTag}, - {key: "fsdev", value: opts.name}, + {key: "fsdev", value: opts.id}, } } else if opts.protocol == "virtio-fs" { driveSection = cfgSection{ - name: fmt.Sprintf(`chardev "%s"`, opts.name), + name: fmt.Sprintf(`chardev "%s"`, opts.id), comment: opts.comment, entries: []cfgEntry{ {key: "backend", value: "socket"}, @@ -697,7 +697,7 @@ func qemuHostDrive(opts *qemuHostDriveOpts) []cfgSection { extraDeviceEntries = []cfgEntry{ {key: "tag", value: opts.mountTag}, - {key: "chardev", value: opts.name}, + {key: "chardev", value: opts.id}, } } else { return []cfgSection{} From 65c49c17f61ab9212dab858cd6b24a8518a52528 Mon Sep 17 00:00:00 2001 From: Thomas Parrott Date: Fri, 14 Jun 2024 11:00:11 +0100 Subject: [PATCH 08/99] lxd/device/disk: Escape device name when using it as part of a path for drive share daemons Allows use of supported "/" char in device names. Signed-off-by: Thomas Parrott --- lxd/device/disk.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lxd/device/disk.go b/lxd/device/disk.go index 255d6ca0e156..b174a2d999bd 100644 --- a/lxd/device/disk.go +++ b/lxd/device/disk.go @@ -851,15 +851,15 @@ func (d *disk) startContainer() (*deviceConfig.RunConfig, error) { // vmVirtfsProxyHelperPaths returns the path for PID file to use with virtfs-proxy-helper process. func (d *disk) vmVirtfsProxyHelperPaths() string { - pidPath := filepath.Join(d.inst.DevicesPath(), fmt.Sprintf("%s.pid", d.name)) + pidPath := filepath.Join(d.inst.DevicesPath(), fmt.Sprintf("%s.pid", filesystem.PathNameEncode(d.name))) return pidPath } // vmVirtiofsdPaths returns the path for the socket and PID file to use with virtiofsd process. func (d *disk) vmVirtiofsdPaths() (sockPath string, pidPath string) { - sockPath = filepath.Join(d.inst.DevicesPath(), fmt.Sprintf("virtio-fs.%s.sock", d.name)) - pidPath = filepath.Join(d.inst.DevicesPath(), fmt.Sprintf("virtio-fs.%s.pid", d.name)) + sockPath = filepath.Join(d.inst.DevicesPath(), fmt.Sprintf("virtio-fs.%s.sock", filesystem.PathNameEncode(d.name))) + pidPath = filepath.Join(d.inst.DevicesPath(), fmt.Sprintf("virtio-fs.%s.pid", filesystem.PathNameEncode(d.name))) return sockPath, pidPath } @@ -1115,7 +1115,7 @@ func (d *disk) startVM() (*deviceConfig.RunConfig, error) { // virtfs-proxy-helper 9p share. The 9p share will only be used as a fallback. err = func() error { sockPath, pidPath := d.vmVirtiofsdPaths() - logPath := filepath.Join(d.inst.LogPath(), fmt.Sprintf("disk.%s.log", d.name)) + logPath := filepath.Join(d.inst.LogPath(), fmt.Sprintf("disk.%s.log", filesystem.PathNameEncode(d.name))) _ = os.Remove(logPath) // Remove old log if needed. revertFunc, unixListener, err := DiskVMVirtiofsdStart(d.state.OS.ExecPath, d.inst, sockPath, pidPath, logPath, mount.DevPath, rawIDMaps) From 303d3f09b970e7ac0a1963c548a117e57e235210 Mon Sep 17 00:00:00 2001 From: Thomas Parrott Date: Fri, 14 Jun 2024 11:01:12 +0100 Subject: [PATCH 09/99] lxd/instance/drivers/driver/qemu: Update path of virtiofsd in deviceAttachPath Signed-off-by: Thomas Parrott --- lxd/instance/drivers/driver_qemu.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index f33f9b5ed4f6..e2e72abd42a1 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -2187,7 +2187,7 @@ func (d *qemu) deviceAttachPath(deviceName string) (mountTag string, err error) mountTag = d.generateQemuDeviceName(deviceName) // Detect virtiofsd path. - virtiofsdSockPath := filepath.Join(d.DevicesPath(), fmt.Sprintf("virtio-fs.%s.sock", deviceName)) + virtiofsdSockPath := filepath.Join(d.DevicesPath(), fmt.Sprintf("virtio-fs.%s.sock", filesystem.PathNameEncode(deviceName))) if !shared.PathExists(virtiofsdSockPath) { return "", fmt.Errorf("Virtiofsd isn't running") } From 402b21a559310df17bfe0db5ec60ffa372ea4fd1 Mon Sep 17 00:00:00 2001 From: Thomas Parrott Date: Fri, 14 Jun 2024 11:01:42 +0100 Subject: [PATCH 10/99] lxd/instance/drivers/driver/qemu: Updates generateQemuDeviceName to also do escaping So device names can container supported "/" chars. Signed-off-by: Thomas Parrott --- lxd/instance/drivers/driver_qemu.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index e2e72abd42a1..7e0c987ef0f0 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -8914,9 +8914,10 @@ func hashIfLonger(name string, maxLength int) string { } // Block node names and device tags may only be up to 31 characters long, so use a hash if longer. +// Also escapes / to -, and - to --. func (d *qemu) generateQemuDeviceName(name string) string { maxNameLength := qemuDeviceNameMaxLength - len(qemuDeviceNamePrefix) - name = hashIfLonger(name, maxNameLength) + name = hashIfLonger(filesystem.PathNameEncode(name), maxNameLength) // Apply the lxd_ prefix. return fmt.Sprintf("%s%s", qemuDeviceNamePrefix, name) From 573672e71ee710c5f6b03a183da7de3acf686cae Mon Sep 17 00:00:00 2001 From: Thomas Parrott Date: Fri, 14 Jun 2024 11:03:00 +0100 Subject: [PATCH 11/99] lxd/instance/drivers/driver/qemu: Update usage of d.generateQemuDeviceName Where escaping was already taking place it has been removed. Signed-off-by: Thomas Parrott --- lxd/instance/drivers/driver_qemu.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index 7e0c987ef0f0..a7e8da1434a4 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -2351,9 +2351,8 @@ func (d *qemu) deviceDetachBlockDevice(deviceName string) error { return err } - escapedDeviceName := filesystem.PathNameEncode(deviceName) - deviceID := fmt.Sprintf("%s%s", qemuDeviceIDPrefix, escapedDeviceName) - blockDevName := d.generateQemuDeviceName(escapedDeviceName) + deviceID := fmt.Sprintf("%s%s", qemuDeviceIDPrefix, filesystem.PathNameEncode(deviceName)) + blockDevName := d.generateQemuDeviceName(deviceName) err = monitor.RemoveFDFromFDSet(blockDevName) if err != nil { @@ -3954,8 +3953,6 @@ func (d *qemu) addDriveConfig(qemuDev map[string]string, bootIndexes map[string] directCache = false } - escapedDeviceName := filesystem.PathNameEncode(driveConf.DevName) - blockDev := map[string]any{ "aio": aioMode, "cache": map[string]any{ @@ -3964,7 +3961,7 @@ func (d *qemu) addDriveConfig(qemuDev map[string]string, bootIndexes map[string] }, "discard": "unmap", // Forward as an unmap request. This is the same as `discard=on` in the qemu config file. "driver": "file", - "node-name": d.generateQemuDeviceName(escapedDeviceName), + "node-name": d.generateQemuDeviceName(driveConf.DevName), "read-only": false, } @@ -4072,6 +4069,8 @@ func (d *qemu) addDriveConfig(qemuDev map[string]string, bootIndexes map[string] qemuDev = map[string]string{} } + escapedDeviceName := filesystem.PathNameEncode(driveConf.DevName) + qemuDev["id"] = fmt.Sprintf("%s%s", qemuDeviceIDPrefix, escapedDeviceName) qemuDevDrive, ok := blockDev["node-name"].(string) if !ok { From 4f68437c33e781e33bee351a5aca261597e5ecbd Mon Sep 17 00:00:00 2001 From: Thomas Parrott Date: Fri, 14 Jun 2024 13:08:16 +0100 Subject: [PATCH 12/99] lxd/instance/drivers/driver/qemu/config/test: Update host drive tests to reflect new consistent fsdev and chardev naming Signed-off-by: Thomas Parrott --- .../drivers/driver_qemu_config_test.go | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lxd/instance/drivers/driver_qemu_config_test.go b/lxd/instance/drivers/driver_qemu_config_test.go index 309909bf5b7c..2811ae893c50 100644 --- a/lxd/instance/drivers/driver_qemu_config_test.go +++ b/lxd/instance/drivers/driver_qemu_config_test.go @@ -754,7 +754,7 @@ func TestQemuConfigTemplates(t *testing.T) { protocol: "9p", }, `# Config drive (9p) - [fsdev "qemu_config"] + [fsdev "dev-qemu_config-drive-9p"] fsdriver = "local" security_model = "none" readonly = "on" @@ -765,7 +765,7 @@ func TestQemuConfigTemplates(t *testing.T) { bus = "qemu_pcie0" addr = "00.5" mount_tag = "config" - fsdev = "qemu_config"`, + fsdev = "dev-qemu_config-drive-9p"`, }, { qemuDriveConfigOpts{ dev: qemuDevOpts{"pcie", "qemu_pcie1", "10.2", true}, @@ -773,7 +773,7 @@ func TestQemuConfigTemplates(t *testing.T) { protocol: "virtio-fs", }, `# Config drive (virtio-fs) - [chardev "qemu_config"] + [chardev "dev-qemu_config-drive-virtio-fs"] backend = "socket" path = "/dev/virtio-fs" @@ -783,7 +783,7 @@ func TestQemuConfigTemplates(t *testing.T) { addr = "10.2" multifunction = "on" tag = "config" - chardev = "qemu_config"`, + chardev = "dev-qemu_config-drive-virtio-fs"`, }, { qemuDriveConfigOpts{ dev: qemuDevOpts{"ccw", "devBus", "busAddr", false}, @@ -791,14 +791,14 @@ func TestQemuConfigTemplates(t *testing.T) { protocol: "virtio-fs", }, `# Config drive (virtio-fs) - [chardev "qemu_config"] + [chardev "dev-qemu_config-drive-virtio-fs"] backend = "socket" path = "/var/virtio-fs" [device "dev-qemu_config-drive-virtio-fs"] driver = "vhost-user-fs-ccw" tag = "config" - chardev = "qemu_config"`, + chardev = "dev-qemu_config-drive-virtio-fs"`, }, { qemuDriveConfigOpts{ dev: qemuDevOpts{"ccw", "devBus", "busAddr", true}, @@ -806,7 +806,7 @@ func TestQemuConfigTemplates(t *testing.T) { protocol: "9p", }, `# Config drive (9p) - [fsdev "qemu_config"] + [fsdev "dev-qemu_config-drive-9p"] fsdriver = "local" security_model = "none" readonly = "on" @@ -816,7 +816,7 @@ func TestQemuConfigTemplates(t *testing.T) { driver = "virtio-9p-ccw" multifunction = "on" mount_tag = "config" - fsdev = "qemu_config"`, + fsdev = "dev-qemu_config-drive-9p"`, }, { qemuDriveConfigOpts{ dev: qemuDevOpts{"ccw", "devBus", "busAddr", true}, @@ -844,7 +844,7 @@ func TestQemuConfigTemplates(t *testing.T) { proxyFD: 5, }, `# stub drive (9p) - [fsdev "lxd_stub"] + [fsdev "dev-lxd_stub-9p"] fsdriver = "proxy" sock_fd = "5" readonly = "off" @@ -855,7 +855,7 @@ func TestQemuConfigTemplates(t *testing.T) { addr = "00.5" multifunction = "on" mount_tag = "mtag" - fsdev = "lxd_stub"`, + fsdev = "dev-lxd_stub-9p"`, }, { qemuDriveDirOpts{ dev: qemuDevOpts{"pcie", "qemu_pcie1", "10.2", false}, @@ -865,7 +865,7 @@ func TestQemuConfigTemplates(t *testing.T) { protocol: "virtio-fs", }, `# vfs drive (virtio-fs) - [chardev "lxd_vfs"] + [chardev "dev-lxd_vfs-virtio-fs"] backend = "socket" path = "/dev/virtio" @@ -874,7 +874,7 @@ func TestQemuConfigTemplates(t *testing.T) { bus = "qemu_pcie1" addr = "10.2" tag = "vtag" - chardev = "lxd_vfs"`, + chardev = "dev-lxd_vfs-virtio-fs"`, }, { qemuDriveDirOpts{ dev: qemuDevOpts{"ccw", "devBus", "busAddr", true}, @@ -884,7 +884,7 @@ func TestQemuConfigTemplates(t *testing.T) { protocol: "virtio-fs", }, `# vfs drive (virtio-fs) - [chardev "lxd_vfs"] + [chardev "dev-lxd_vfs-virtio-fs"] backend = "socket" path = "/dev/vio" @@ -892,7 +892,7 @@ func TestQemuConfigTemplates(t *testing.T) { driver = "vhost-user-fs-ccw" multifunction = "on" tag = "vtag" - chardev = "lxd_vfs"`, + chardev = "dev-lxd_vfs-virtio-fs"`, }, { qemuDriveDirOpts{ dev: qemuDevOpts{"ccw", "devBus", "busAddr", false}, @@ -903,7 +903,7 @@ func TestQemuConfigTemplates(t *testing.T) { proxyFD: 3, }, `# stub2 drive (9p) - [fsdev "lxd_stub2"] + [fsdev "dev-lxd_stub2-9p"] fsdriver = "proxy" sock_fd = "3" readonly = "on" @@ -911,7 +911,7 @@ func TestQemuConfigTemplates(t *testing.T) { [device "dev-lxd_stub2-9p"] driver = "virtio-9p-ccw" mount_tag = "mtag2" - fsdev = "lxd_stub2"`, + fsdev = "dev-lxd_stub2-9p"`, }, { qemuDriveDirOpts{ dev: qemuDevOpts{"ccw", "devBus", "busAddr", true}, From 94cf1687ef8063b1b8353fbe4b28898a002ae2ab Mon Sep 17 00:00:00 2001 From: hamistao Date: Tue, 25 Jun 2024 02:58:25 -0300 Subject: [PATCH 13/99] lxd/instance/device: Correct `qemuDeviceIDMaxLength` Signed-off-by: hamistao --- lxd/instance/drivers/driver_qemu.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index a7e8da1434a4..16a4e874285a 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -109,7 +109,7 @@ const qemuPCIDeviceIDStart = 4 const qemuDeviceIDPrefix = "dev-lxd_" // qemuDeviceNameMaxLength used to indicate the maximum length of a qemu device ID. -const qemuDeviceIDMaxLength = 64 +const qemuDeviceIDMaxLength = 63 // qemuDeviceNamePrefix used as part of the name given QEMU blockdevs, netdevs and device tags generated from user added devices. const qemuDeviceNamePrefix = "lxd_" From 4127b723bede637e70e59eae7385e1b2f06a91b7 Mon Sep 17 00:00:00 2001 From: hamistao Date: Tue, 25 Jun 2024 18:45:27 -0300 Subject: [PATCH 14/99] lxd/instance/drivers: Switch `qemuHostDriveDeviceID` for `qemuDeviceNameOrID` Use a more general function so it is possible to reuse it for TPM devices that have different prefixes. The name change and the additional lenght parameter were introduced so it could also be used for device names. Signed-off-by: hamistao --- lxd/instance/drivers/driver_qemu.go | 4 +-- lxd/instance/drivers/driver_qemu_templates.go | 30 +++++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index 16a4e874285a..78b63203ff92 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -2183,7 +2183,7 @@ func (d *qemu) deviceStart(dev device.Device, instanceRunning bool) (*deviceConf } func (d *qemu) deviceAttachPath(deviceName string) (mountTag string, err error) { - deviceID := qemuHostDriveDeviceID(deviceName, "virtio-fs") + deviceID := qemuDeviceNameOrID(qemuDeviceIDPrefix, deviceName, "-virtio-fs", qemuDeviceIDMaxLength) mountTag = d.generateQemuDeviceName(deviceName) // Detect virtiofsd path. @@ -2310,7 +2310,7 @@ func (d *qemu) deviceAttachBlockDevice(mount deviceConfig.MountEntryItem) error } func (d *qemu) deviceDetachPath(deviceName string) error { - deviceID := qemuHostDriveDeviceID(deviceName, "virtio-fs") + deviceID := qemuDeviceNameOrID(qemuDeviceIDPrefix, deviceName, "-virtio-fs", qemuDeviceIDMaxLength) // Check if the agent is running. monitor, err := qmp.Connect(d.monitorPath(), qemuSerialChardevName, d.getMonitorEventHandler()) diff --git a/lxd/instance/drivers/driver_qemu_templates.go b/lxd/instance/drivers/driver_qemu_templates.go index a143e36ae03f..a9c4ddc86b64 100644 --- a/lxd/instance/drivers/driver_qemu_templates.go +++ b/lxd/instance/drivers/driver_qemu_templates.go @@ -1,6 +1,8 @@ package drivers import ( + "crypto/sha256" + "encoding/base64" "fmt" "strings" @@ -9,11 +11,27 @@ import ( "github.com/canonical/lxd/shared/osarch" ) -// qemuHostDriveDeviceID returns the device ID to use for a host drive share. -func qemuHostDriveDeviceID(deviceName string, protocol string) string { - suffix := "-" + protocol - maxNameLength := qemuDeviceIDMaxLength - (len(qemuDeviceIDPrefix) + len(suffix)) - return fmt.Sprintf("%s%s%s", qemuDeviceIDPrefix, hashIfLonger(filesystem.PathNameEncode(deviceName), maxNameLength), suffix) +// qemuDeviceNameOrID generates a QEMU device name or ID. +// Respects the property length limit by hashing the device name when necessary. Also escapes / to -, and - to --. +func qemuDeviceNameOrID(prefix string, deviceName string, suffix string, maxLength int) string { + baseName := filesystem.PathNameEncode(deviceName) + maxNameLength := maxLength - (len(prefix) + len(suffix)) + + if len(baseName) > maxNameLength { + // If the name is too long, hash it as SHA-256 (32 bytes). + // Then encode the SHA-256 binary hash as Base64 Raw URL format and trim down if needed. + hash := sha256.New() + hash.Write([]byte(baseName)) + binaryHash := hash.Sum(nil) + + // Raw URL avoids the use of "+" character and the padding "=" character which QEMU doesn't allow. + baseName = base64.RawURLEncoding.EncodeToString(binaryHash) + if len(baseName) > maxNameLength { + baseName = baseName[0:maxNameLength] + } + } + + return fmt.Sprintf("%s%s%s", prefix, baseName, suffix) } type cfgEntry struct { @@ -747,7 +765,7 @@ type qemuDriveDirOpts struct { func qemuDriveDir(opts *qemuDriveDirOpts) []cfgSection { return qemuHostDrive(&qemuHostDriveOpts{ dev: opts.dev, - id: qemuHostDriveDeviceID(opts.devName, opts.protocol), + id: qemuDeviceNameOrID(qemuDeviceIDPrefix, opts.devName, "-"+opts.protocol, qemuDeviceIDMaxLength), // Devices use "lxd_" prefix indicating that this is a user named device. name: fmt.Sprintf("lxd_%s", opts.devName), comment: fmt.Sprintf("%s drive (%s)", opts.devName, opts.protocol), From b495e8bb918a7ae5211d99538a637a8b89e90a41 Mon Sep 17 00:00:00 2001 From: hamistao Date: Tue, 25 Jun 2024 18:53:38 -0300 Subject: [PATCH 15/99] lxd/instance/drivers: Use `qemuDeviceNameOrID` for node names and mount tags Signed-off-by: hamistao --- lxd/instance/drivers/driver_qemu.go | 43 ++++------------------------- 1 file changed, 5 insertions(+), 38 deletions(-) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index 78b63203ff92..e3f587a3a123 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -17,11 +17,9 @@ import ( "bytes" "compress/gzip" "context" - "crypto/sha256" "crypto/tls" "crypto/x509" "database/sql" - "encoding/base64" "encoding/json" "errors" "fmt" @@ -2184,7 +2182,7 @@ func (d *qemu) deviceStart(dev device.Device, instanceRunning bool) (*deviceConf func (d *qemu) deviceAttachPath(deviceName string) (mountTag string, err error) { deviceID := qemuDeviceNameOrID(qemuDeviceIDPrefix, deviceName, "-virtio-fs", qemuDeviceIDMaxLength) - mountTag = d.generateQemuDeviceName(deviceName) + mountTag = qemuDeviceNameOrID(qemuDeviceNamePrefix, deviceName, "", qemuDeviceNameMaxLength) // Detect virtiofsd path. virtiofsdSockPath := filepath.Join(d.DevicesPath(), fmt.Sprintf("virtio-fs.%s.sock", filesystem.PathNameEncode(deviceName))) @@ -2352,7 +2350,7 @@ func (d *qemu) deviceDetachBlockDevice(deviceName string) error { } deviceID := fmt.Sprintf("%s%s", qemuDeviceIDPrefix, filesystem.PathNameEncode(deviceName)) - blockDevName := d.generateQemuDeviceName(deviceName) + blockDevName := qemuDeviceNameOrID(qemuDeviceNamePrefix, deviceName, "", qemuDeviceNameMaxLength) err = monitor.RemoveFDFromFDSet(blockDevName) if err != nil { @@ -3730,7 +3728,7 @@ func (d *qemu) addRootDriveConfig(qemuDev map[string]string, mountInfo *storageP // addDriveDirConfig adds the qemu config required for adding a supplementary drive directory share. func (d *qemu) addDriveDirConfig(cfg *[]cfgSection, bus *qemuBus, fdFiles *[]*os.File, agentMounts *[]instancetype.VMAgentMount, driveConf deviceConfig.MountEntryItem) error { - mountTag := d.generateQemuDeviceName(driveConf.DevName) + mountTag := qemuDeviceNameOrID(qemuDeviceNamePrefix, driveConf.DevName, "", qemuDeviceNameMaxLength) agentMount := instancetype.VMAgentMount{ Source: mountTag, @@ -3961,7 +3959,7 @@ func (d *qemu) addDriveConfig(qemuDev map[string]string, bootIndexes map[string] }, "discard": "unmap", // Forward as an unmap request. This is the same as `discard=on` in the qemu config file. "driver": "file", - "node-name": d.generateQemuDeviceName(driveConf.DevName), + "node-name": qemuDeviceNameOrID(qemuDeviceNamePrefix, driveConf.DevName, "", qemuDeviceNameMaxLength), "read-only": false, } @@ -8670,7 +8668,7 @@ func (d *qemu) checkFeatures(hostArch int, qemuPath string) (map[string]any, err // Check io_uring feature. blockDev := map[string]any{ - "node-name": d.generateQemuDeviceName("feature-check"), + "node-name": fmt.Sprintf("%s%s", qemuDeviceNamePrefix, "feature-check"), "driver": "file", "filename": blockDevPath.Name(), "aio": "io_uring", @@ -8891,37 +8889,6 @@ func (d *qemu) deviceDetachUSB(usbDev deviceConfig.USBDeviceItem) error { return nil } -// hashIfLonger returns a full or partial hash of a name as to fit it within a size limit. -func hashIfLonger(name string, maxLength int) string { - if len(name) <= maxLength { - return name - } - - // If the name is too long, hash it as SHA-256 (32 bytes). - // Then encode the SHA-256 binary hash as Base64 Raw URL format and trim down if needed. - hash := sha256.New() - hash.Write([]byte(name)) - binaryHash := hash.Sum(nil) - - // Raw URL avoids the use of "+" character and the padding "=" character which QEMU doesn't allow. - hashedName := base64.RawURLEncoding.EncodeToString(binaryHash) - if len(hashedName) > maxLength { - hashedName = hashedName[0:maxLength] - } - - return hashedName -} - -// Block node names and device tags may only be up to 31 characters long, so use a hash if longer. -// Also escapes / to -, and - to --. -func (d *qemu) generateQemuDeviceName(name string) string { - maxNameLength := qemuDeviceNameMaxLength - len(qemuDeviceNamePrefix) - name = hashIfLonger(filesystem.PathNameEncode(name), maxNameLength) - - // Apply the lxd_ prefix. - return fmt.Sprintf("%s%s", qemuDeviceNamePrefix, name) -} - func (d *qemu) setCPUs(count int) error { if count == 0 { return nil From 90ad5fd3cd507efe9836fcdf0e5f97611a2ad81a Mon Sep 17 00:00:00 2001 From: hamistao Date: Tue, 25 Jun 2024 18:57:43 -0300 Subject: [PATCH 16/99] lxd/instance/drivers: Use `qemuDeviceNameOrID` for TPM config section IDs This contains the actual fix for the bug by avoiding trimming the device ID and resulting in mismatches between the device ID and references to it. Signed-off-by: hamistao --- lxd/instance/drivers/driver_qemu_templates.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lxd/instance/drivers/driver_qemu_templates.go b/lxd/instance/drivers/driver_qemu_templates.go index a9c4ddc86b64..dd491bcad188 100644 --- a/lxd/instance/drivers/driver_qemu_templates.go +++ b/lxd/instance/drivers/driver_qemu_templates.go @@ -892,8 +892,9 @@ type qemuTPMOpts struct { } func qemuTPM(opts *qemuTPMOpts) []cfgSection { - chardev := fmt.Sprintf("qemu_tpm-chardev_%s", opts.devName) - tpmdev := fmt.Sprintf("qemu_tpm-tpmdev_%s", opts.devName) + chardev := qemuDeviceNameOrID("qemu_tpm-chardev_", opts.devName, "", qemuDeviceIDMaxLength) + tpmdev := qemuDeviceNameOrID("qemu_tpm-tpmdev_", opts.devName, "", qemuDeviceIDMaxLength) + device := qemuDeviceNameOrID(qemuDeviceIDPrefix, opts.devName, "", qemuDeviceIDMaxLength) return []cfgSection{{ name: fmt.Sprintf(`chardev "%s"`, chardev), @@ -908,7 +909,7 @@ func qemuTPM(opts *qemuTPMOpts) []cfgSection { {key: "chardev", value: chardev}, }, }, { - name: fmt.Sprintf(`device "dev-lxd_%s"`, opts.devName), + name: fmt.Sprintf(`device "%s"`, device), entries: []cfgEntry{ {key: "driver", value: "tpm-crb"}, {key: "tpmdev", value: tpmdev}, From 69faae699b8f342be9ca78547e318f08201e6b30 Mon Sep 17 00:00:00 2001 From: hamistao Date: Wed, 26 Jun 2024 13:12:40 -0300 Subject: [PATCH 17/99] lxd/device: Allow `/` in VM TPM device name Signed-off-by: hamistao --- lxd/device/tpm.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lxd/device/tpm.go b/lxd/device/tpm.go index 477a520cd753..c340f109c536 100644 --- a/lxd/device/tpm.go +++ b/lxd/device/tpm.go @@ -13,6 +13,7 @@ import ( deviceConfig "github.com/canonical/lxd/lxd/device/config" "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/instance/instancetype" + "github.com/canonical/lxd/lxd/storage/filesystem" "github.com/canonical/lxd/lxd/subprocess" "github.com/canonical/lxd/lxd/util" "github.com/canonical/lxd/shared" @@ -97,7 +98,7 @@ func (d *tpm) Start() (*deviceConfig.RunConfig, error) { return nil, fmt.Errorf("Failed to validate environment: %w", err) } - tpmDevPath := filepath.Join(d.inst.Path(), fmt.Sprintf("tpm.%s", d.name)) + tpmDevPath := filepath.Join(d.inst.Path(), fmt.Sprintf("tpm.%s", filesystem.PathNameEncode(d.name))) if !shared.PathExists(tpmDevPath) { err := os.Mkdir(tpmDevPath, 0700) @@ -212,8 +213,9 @@ func (d *tpm) startVM() (*deviceConfig.RunConfig, error) { revert := revert.New() defer revert.Fail() - tpmDevPath := filepath.Join(d.inst.Path(), fmt.Sprintf("tpm.%s", d.name)) - socketPath := filepath.Join(tpmDevPath, fmt.Sprintf("swtpm-%s.sock", d.name)) + escapedDeviceName := filesystem.PathNameEncode(d.name) + tpmDevPath := filepath.Join(d.inst.Path(), fmt.Sprintf("tpm.%s", escapedDeviceName)) + socketPath := filepath.Join(tpmDevPath, fmt.Sprintf("swtpm-%s.sock", escapedDeviceName)) runConf := deviceConfig.RunConfig{ TPMDevice: []deviceConfig.RunConfigItem{ {Key: "devName", Value: d.name}, @@ -280,7 +282,7 @@ func (d *tpm) startVM() (*deviceConfig.RunConfig, error) { revert.Add(func() { _ = proc.Stop() }) - pidPath := filepath.Join(d.inst.DevicesPath(), fmt.Sprintf("%s.pid", d.name)) + pidPath := filepath.Join(d.inst.DevicesPath(), fmt.Sprintf("%s.pid", escapedDeviceName)) err = proc.Save(pidPath) if err != nil { From 99bd0c2c21ae867f72671f8afe719452d98fcacf Mon Sep 17 00:00:00 2001 From: hamistao Date: Wed, 26 Jun 2024 13:24:38 -0300 Subject: [PATCH 18/99] lxd/device: Allow `/` in container TPM device name Signed-off-by: hamistao --- lxd/device/tpm.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lxd/device/tpm.go b/lxd/device/tpm.go index c340f109c536..b2e59f5fd1e9 100644 --- a/lxd/device/tpm.go +++ b/lxd/device/tpm.go @@ -115,8 +115,9 @@ func (d *tpm) Start() (*deviceConfig.RunConfig, error) { } func (d *tpm) startContainer() (*deviceConfig.RunConfig, error) { - tpmDevPath := filepath.Join(d.inst.Path(), fmt.Sprintf("tpm.%s", d.name)) - logFileName := fmt.Sprintf("tpm.%s.log", d.name) + escapedDeviceName := filesystem.PathNameEncode(d.name) + tpmDevPath := filepath.Join(d.inst.Path(), fmt.Sprintf("tpm.%s", escapedDeviceName)) + logFileName := fmt.Sprintf("tpm.%s.log", escapedDeviceName) logPath := filepath.Join(d.inst.LogPath(), logFileName) proc, err := subprocess.NewProcess("swtpm", []string{"chardev", "--tpm2", "--tpmstate", fmt.Sprintf("dir=%s", tpmDevPath), "--vtpm-proxy"}, logPath, "") @@ -135,7 +136,7 @@ func (d *tpm) startContainer() (*deviceConfig.RunConfig, error) { // Stop the TPM emulator if anything goes wrong. revert.Add(func() { _ = proc.Stop() }) - pidPath := filepath.Join(d.inst.DevicesPath(), fmt.Sprintf("%s.pid", d.name)) + pidPath := filepath.Join(d.inst.DevicesPath(), fmt.Sprintf("%s.pid", escapedDeviceName)) err = proc.Save(pidPath) if err != nil { From 79b83bba26795d98e1cf37eb53242962e1d849a0 Mon Sep 17 00:00:00 2001 From: Alexander Mikhalitsyn Date: Fri, 28 Jun 2024 14:32:29 +0200 Subject: [PATCH 19/99] lxd/apparmor/feature_check: add infastructure to check AppArmor features Add some infastructure to check AppArmor features availability, as version checks can not be reliable because of backports of different features between branches. Signed-off-by: Alexander Mikhalitsyn --- lxd/apparmor/feature_check.go | 71 +++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 lxd/apparmor/feature_check.go diff --git a/lxd/apparmor/feature_check.go b/lxd/apparmor/feature_check.go new file mode 100644 index 000000000000..eae698d81c9f --- /dev/null +++ b/lxd/apparmor/feature_check.go @@ -0,0 +1,71 @@ +package apparmor + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "text/template" + + "github.com/google/uuid" + + "github.com/canonical/lxd/lxd/sys" + "github.com/canonical/lxd/shared" +) + +var featureCheckProfileTpl = template.Must(template.New("featureCheckProfile").Parse(` +profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) { + +} +`)) + +// FeatureCheck tries to generate feature check profile and process it with apparmor_parser. +func FeatureCheck(sysOS *sys.OS, feature string) (bool, error) { + randomUUID := uuid.New().String() + name := fmt.Sprintf("<%s-%s>", randomUUID, feature) + profileName := profileName("featurecheck", name) + profilePath := filepath.Join(aaPath, "profiles", profileName) + content, err := os.ReadFile(profilePath) + if err != nil && !os.IsNotExist(err) { + return false, err + } + + updated, err := featureCheckProfile(profileName, feature) + if err != nil { + return false, err + } + + if string(content) != string(updated) { + err = os.WriteFile(profilePath, []byte(updated), 0600) + if err != nil { + return false, err + } + } + + defer func() { + _ = deleteProfile(sysOS, profileName, profileName) + }() + + err = parseProfile(sysOS, profileName) + if err != nil { + return false, nil + } + + return true, nil +} + +// featureCheckProfile generates the AppArmor profile. +func featureCheckProfile(profileName string, feature string) (string, error) { + // Render the profile. + sb := &strings.Builder{} + err := featureCheckProfileTpl.Execute(sb, map[string]any{ + "name": profileName, + "snap": shared.InSnap(), + "feature": feature, + }) + if err != nil { + return "", err + } + + return sb.String(), nil +} From bd2e4ed14e580eaea7d34fb72063ea1efdf04d84 Mon Sep 17 00:00:00 2001 From: Alexander Mikhalitsyn Date: Fri, 28 Jun 2024 14:34:04 +0200 Subject: [PATCH 20/99] lxd/apparmor/instance_lxc: allow nosymfollow mount flag See also: https://github.com/canonical/lxd/pull/12698 Thanks-to: Nick Rosbrook Signed-off-by: Alexander Mikhalitsyn --- lxd/apparmor/apparmor.go | 16 ++++++++++++++++ lxd/apparmor/feature_check.go | 4 ++++ lxd/apparmor/instance.go | 26 ++++++++++++++++---------- lxd/apparmor/instance_lxc.go | 20 ++++++++++++++++++++ lxd/sys/apparmor.go | 2 ++ lxd/sys/os.go | 7 +++++++ 6 files changed, 65 insertions(+), 10 deletions(-) diff --git a/lxd/apparmor/apparmor.go b/lxd/apparmor/apparmor.go index fac585699e21..2f33f200f31a 100644 --- a/lxd/apparmor/apparmor.go +++ b/lxd/apparmor/apparmor.go @@ -185,6 +185,22 @@ func parserSupports(sysOS *sys.OS, feature string) (bool, error) { return ver.Compare(minVer) >= 0, nil } + if feature == "mount_nosymfollow" { + sysOS.AppArmorFeatures.Lock() + defer sysOS.AppArmorFeatures.Unlock() + supported, ok := sysOS.AppArmorFeatures.Map[feature] + if !ok { + supported, err = FeatureCheck(sysOS, feature) + if err != nil { + return false, nil + } + + sysOS.AppArmorFeatures.Map[feature] = supported + } + + return supported, nil + } + return false, nil } diff --git a/lxd/apparmor/feature_check.go b/lxd/apparmor/feature_check.go index eae698d81c9f..3043ec20c738 100644 --- a/lxd/apparmor/feature_check.go +++ b/lxd/apparmor/feature_check.go @@ -16,6 +16,10 @@ import ( var featureCheckProfileTpl = template.Must(template.New("featureCheckProfile").Parse(` profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) { +{{- if eq .feature "mount_nosymfollow" }} + mount options=(nosymfollow) /, +{{- end }} + } `)) diff --git a/lxd/apparmor/instance.go b/lxd/apparmor/instance.go index fbef81c2b3aa..895836ce5665 100644 --- a/lxd/apparmor/instance.go +++ b/lxd/apparmor/instance.go @@ -154,18 +154,24 @@ func instanceProfile(sysOS *sys.OS, inst instance) (string, error) { } // Render the profile. - var sb *strings.Builder = &strings.Builder{} + sb := &strings.Builder{} if inst.Type() == instancetype.Container { + mountNosymfollowSupported, err := parserSupports(sysOS, "mount_nosymfollow") + if err != nil { + return "", err + } + err = lxcProfileTpl.Execute(sb, map[string]any{ - "feature_cgns": sysOS.CGInfo.Namespacing, - "feature_cgroup2": sysOS.CGInfo.Layout == cgroup.CgroupsUnified || sysOS.CGInfo.Layout == cgroup.CgroupsHybrid, - "feature_stacking": sysOS.AppArmorStacking && !sysOS.AppArmorStacked, - "feature_unix": unixSupported, - "name": InstanceProfileName(inst), - "namespace": InstanceNamespaceName(inst), - "nesting": shared.IsTrue(inst.ExpandedConfig()["security.nesting"]), - "raw": rawContent, - "unprivileged": shared.IsFalseOrEmpty(inst.ExpandedConfig()["security.privileged"]) || sysOS.RunningInUserNS, + "feature_cgns": sysOS.CGInfo.Namespacing, + "feature_cgroup2": sysOS.CGInfo.Layout == cgroup.CgroupsUnified || sysOS.CGInfo.Layout == cgroup.CgroupsHybrid, + "feature_stacking": sysOS.AppArmorStacking && !sysOS.AppArmorStacked, + "feature_unix": unixSupported, + "feature_mount_nosymfollow": mountNosymfollowSupported, + "name": InstanceProfileName(inst), + "namespace": InstanceNamespaceName(inst), + "nesting": shared.IsTrue(inst.ExpandedConfig()["security.nesting"]), + "raw": rawContent, + "unprivileged": shared.IsFalseOrEmpty(inst.ExpandedConfig()["security.privileged"]) || sysOS.RunningInUserNS, }) if err != nil { return "", err diff --git a/lxd/apparmor/instance_lxc.go b/lxd/apparmor/instance_lxc.go index a41fd7f09358..3a49b5f5c744 100644 --- a/lxd/apparmor/instance_lxc.go +++ b/lxd/apparmor/instance_lxc.go @@ -255,6 +255,26 @@ profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) { mount options=(ro,remount,bind,nosuid,noexec,strictatime) /sy[^s]*{,/**}, mount options=(ro,remount,bind,nosuid,noexec,strictatime) /sys?*{,/**}, +{{- if .feature_mount_nosymfollow }} + # see https://github.com/canonical/lxd/pull/12698 + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /[^spd]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /d[^e]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /de[^v]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /dev/.[^l]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /dev/.l[^x]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /dev/.lx[^c]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /dev/.lxc?*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /dev/[^.]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /dev?*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /p[^r]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /pr[^o]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /pro[^c]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /proc?*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /s[^y]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /sy[^s]*{,/**}, + mount options=(ro,remount,bind,nosuid,noexec,nodev,nosymfollow) /sys?*{,/**}, +{{- end }} + # Allow bind-mounts of anything except /proc, /sys and /dev/.lxc mount options=(rw,bind) /[^spd]*{,/**}, mount options=(rw,bind) /d[^e]*{,/**}, diff --git a/lxd/sys/apparmor.go b/lxd/sys/apparmor.go index 0c82fa7f5a14..e54e6a44adba 100644 --- a/lxd/sys/apparmor.go +++ b/lxd/sys/apparmor.go @@ -86,6 +86,8 @@ func (s *OS) initAppArmor() []cluster.Warning { s.AppArmorConfined = true } + s.AppArmorFeatures.Map = map[string]bool{} + return dbWarnings } diff --git a/lxd/sys/os.go b/lxd/sys/os.go index 12588a97a5d2..3c18e5bbf6f0 100644 --- a/lxd/sys/os.go +++ b/lxd/sys/os.go @@ -38,6 +38,12 @@ type InotifyInfo struct { Targets map[string]*InotifyTargetInfo } +// AppArmorFeaturesInfo records the AppArmor features availability. +type AppArmorFeaturesInfo struct { + sync.Mutex + Map map[string]bool +} + // OS is a high-level facade for accessing all operating-system // level functionality that LXD uses. type OS struct { @@ -69,6 +75,7 @@ type OS struct { AppArmorConfined bool AppArmorStacked bool AppArmorStacking bool + AppArmorFeatures AppArmorFeaturesInfo // Cgroup features CGInfo cgroup.Info From 06701fa14bdc71dac94c167fb4dfe73c3760949b Mon Sep 17 00:00:00 2001 From: Simon Deziel Date: Thu, 11 Jul 2024 16:21:39 -0400 Subject: [PATCH 21/99] test/includes/lxd: optimize spawn_lxd() Signed-off-by: Simon Deziel --- test/includes/lxd.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/includes/lxd.sh b/test/includes/lxd.sh index 752e22830854..1a8924ee0514 100644 --- a/test/includes/lxd.sh +++ b/test/includes/lxd.sh @@ -37,7 +37,7 @@ spawn_lxd() { LXD_DIR="${lxddir}" lxd --logfile "${lxddir}/lxd.log" "${DEBUG-}" "$@" 2>&1 & else # shellcheck disable=SC2153 - pid="$(cat "${TEST_DIR}/ns/${LXD_NETNS}/PID")" + read -r pid < "${TEST_DIR}/ns/${LXD_NETNS}/PID" LXD_DIR="${lxddir}" nsenter -n -m -t "${pid}" lxd --logfile "${lxddir}/lxd.log" "${DEBUG-}" "$@" 2>&1 & fi LXD_PID=$! @@ -96,7 +96,7 @@ respawn_lxd() { if [ "${LXD_NETNS}" = "" ]; then LXD_DIR="${lxddir}" lxd --logfile "${lxddir}/lxd.log" "${DEBUG-}" "$@" 2>&1 & else - pid="$(cat "${TEST_DIR}/ns/${LXD_NETNS}/PID")" + read -r pid < "${TEST_DIR}/ns/${LXD_NETNS}/PID" LXD_DIR="${lxddir}" nsenter -n -m -t "${pid}" lxd --logfile "${lxddir}/lxd.log" "${DEBUG-}" "$@" 2>&1 & fi LXD_PID=$! From 0a6313bf763a609845139aa623ed66a5d6a6afd5 Mon Sep 17 00:00:00 2001 From: Simon Deziel Date: Thu, 11 Jul 2024 16:21:53 -0400 Subject: [PATCH 22/99] test/includes/lxd: simplify kill_lxd() Even `apparmor_parser` on 20.04 supports `--print-cache-dir` which is probably the oldest distro we care about. Signed-off-by: Simon Deziel --- test/includes/lxd.sh | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/includes/lxd.sh b/test/includes/lxd.sh index 1a8924ee0514..ea67ee05a745 100644 --- a/test/includes/lxd.sh +++ b/test/includes/lxd.sh @@ -210,11 +210,7 @@ kill_lxd() { rm -f "${daemon_dir}/containers/lxc-monitord.log" # Support AppArmor policy cache directory - if apparmor_parser --help | grep -q -- '--print-cache.dir'; then - apparmor_cache_dir="$(apparmor_parser -L "${daemon_dir}"/security/apparmor/cache --print-cache-dir)" - else - apparmor_cache_dir="${daemon_dir}/security/apparmor/cache" - fi + apparmor_cache_dir="$(apparmor_parser -L "${daemon_dir}"/security/apparmor/cache --print-cache-dir)" rm -f "${apparmor_cache_dir}/.features" check_empty "${daemon_dir}/containers/" check_empty "${daemon_dir}/devices/" From f7dd438d9a22337e608fe11402f70b0792dffbf7 Mon Sep 17 00:00:00 2001 From: Simon Deziel Date: Thu, 11 Jul 2024 15:54:04 -0400 Subject: [PATCH 23/99] test/includes/storage: optimize storage_backend() Signed-off-by: Simon Deziel --- test/includes/storage.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/includes/storage.sh b/test/includes/storage.sh index 5189223c1d56..cd65a7327c22 100644 --- a/test/includes/storage.sh +++ b/test/includes/storage.sh @@ -24,7 +24,7 @@ random_storage_backend() { # Return the storage backend being used by a LXD instance storage_backend() { - cat "$1/lxd.backend" + read -r backend < "$1/lxd.backend" && echo "${backend}" } # Return a list of available storage backends From df582a39c4985b5503378e780eaa4800deb3d88e Mon Sep 17 00:00:00 2001 From: Simon Deziel Date: Fri, 12 Jul 2024 16:29:13 -0400 Subject: [PATCH 24/99] test/includes/storage: add storage_backend_optimized_backup() helper Signed-off-by: Simon Deziel --- test/includes/storage.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/includes/storage.sh b/test/includes/storage.sh index cd65a7327c22..b768a3eb985c 100644 --- a/test/includes/storage.sh +++ b/test/includes/storage.sh @@ -16,6 +16,13 @@ storage_backend_available() { false } +# Returns 0 if --optimized-storage works for backups (export/import) +storage_backend_optimized_backup() { + [ "${1}" = "btrfs" ] && return 0 + [ "${1}" = "zfs" ] && return 0 + return 1 +} + # Choose a random available backend, excluding LXD_BACKEND random_storage_backend() { # shellcheck disable=2046 From 005d0807a5f892d3c4acceb07d7a01eb99c44c25 Mon Sep 17 00:00:00 2001 From: Simon Deziel Date: Fri, 12 Jul 2024 16:32:33 -0400 Subject: [PATCH 25/99] test/suites/backup: use storage_backend_optimized_backup() helper Signed-off-by: Simon Deziel --- test/suites/backup.sh | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/test/suites/backup.sh b/test/suites/backup.sh index cef45ef4f4e4..83a89d101d0c 100644 --- a/test/suites/backup.sh +++ b/test/suites/backup.sh @@ -371,7 +371,7 @@ _backup_import_with_project() { # container only # create backup - if [ "$lxd_backend" = "btrfs" ] || [ "$lxd_backend" = "zfs" ]; then + if storage_backend_optimized_backup "$lxd_backend"; then lxc export c1 "${LXD_DIR}/c1-optimized.tar.gz" --optimized-storage --instance-only fi @@ -384,7 +384,7 @@ _backup_import_with_project() { lxc start c1 lxc delete --force c1 - if [ "$lxd_backend" = "btrfs" ] || [ "$lxd_backend" = "zfs" ]; then + if storage_backend_optimized_backup "$lxd_backend"; then lxc import "${LXD_DIR}/c1-optimized.tar.gz" lxc info c1 lxc start c1 @@ -393,7 +393,7 @@ _backup_import_with_project() { # with snapshots - if [ "$lxd_backend" = "btrfs" ] || [ "$lxd_backend" = "zfs" ]; then + if storage_backend_optimized_backup "$lxd_backend"; then lxc export c2 "${LXD_DIR}/c2-optimized.tar.gz" --optimized-storage fi @@ -442,7 +442,7 @@ _backup_import_with_project() { lxc delete --force c3 - if [ "$lxd_backend" = "btrfs" ] || [ "$lxd_backend" = "zfs" ]; then + if storage_backend_optimized_backup "$lxd_backend"; then lxc import "${LXD_DIR}/c2-optimized.tar.gz" lxc import "${LXD_DIR}/c2-optimized.tar.gz" c3 lxc info c2 | grep snap0 @@ -557,7 +557,7 @@ _backup_export_with_project() { # container only - if [ "$lxd_backend" = "btrfs" ] || [ "$lxd_backend" = "zfs" ]; then + if storage_backend_optimized_backup "$lxd_backend"; then lxc export c1 "${LXD_DIR}/c1-optimized.tar.gz" --optimized-storage --instance-only tar -xzf "${LXD_DIR}/c1-optimized.tar.gz" -C "${LXD_DIR}/optimized" @@ -577,8 +577,7 @@ _backup_export_with_project() { rm -rf "${LXD_DIR}/non-optimized/"* "${LXD_DIR}/optimized/"* # with snapshots - - if [ "$lxd_backend" = "btrfs" ] || [ "$lxd_backend" = "zfs" ]; then + if storage_backend_optimized_backup "$lxd_backend"; then lxc export c1 "${LXD_DIR}/c1-optimized.tar.gz" --optimized-storage tar -xzf "${LXD_DIR}/c1-optimized.tar.gz" -C "${LXD_DIR}/optimized" @@ -716,7 +715,7 @@ _backup_volume_export_with_project() { lxc storage volume snapshot "${custom_vol_pool}" testvol test-snap1 lxc storage volume set "${custom_vol_pool}" testvol user.foo=post-test-snap1 - if [ "$lxd_backend" = "btrfs" ] || [ "$lxd_backend" = "zfs" ]; then + if storage_backend_optimized_backup "$lxd_backend"; then # Create optimized backup without snapshots. lxc storage volume export "${custom_vol_pool}" testvol "${LXD_DIR}/testvol-optimized.tar.gz" --volume-only --optimized-storage @@ -749,7 +748,7 @@ _backup_volume_export_with_project() { rm -rf "${LXD_DIR}/non-optimized/"* rm "${LXD_DIR}/testvol.tar.gz" - if [ "$lxd_backend" = "btrfs" ] || [ "$lxd_backend" = "zfs" ]; then + if storage_backend_optimized_backup "$lxd_backend"; then # Create optimized backup with snapshots. lxc storage volume export "${custom_vol_pool}" testvol "${LXD_DIR}/testvol-optimized.tar.gz" --optimized-storage @@ -822,7 +821,7 @@ _backup_volume_export_with_project() { fi # Test optimized import. - if [ "$lxd_backend" = "btrfs" ] || [ "$lxd_backend" = "zfs" ]; then + if storage_backend_optimized_backup "$lxd_backend"; then lxc storage volume detach "${custom_vol_pool}" testvol c1 lxc storage volume detach "${custom_vol_pool}" testvol2 c1 lxc storage volume delete "${custom_vol_pool}" testvol From e0a55f3ca3292cb116c9889058fc7788dae6f407 Mon Sep 17 00:00:00 2001 From: Simon Deziel Date: Thu, 11 Jul 2024 14:30:22 -0400 Subject: [PATCH 26/99] test/suites/backup: cleanup exported tarballs Signed-off-by: Simon Deziel --- test/suites/backup.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/suites/backup.sh b/test/suites/backup.sh index 83a89d101d0c..1c51611f51ed 100644 --- a/test/suites/backup.sh +++ b/test/suites/backup.sh @@ -516,6 +516,9 @@ _backup_import_with_project() { lxc storage delete pool_2 + # Cleanup exported tarballs + rm -f "${LXD_DIR}"/c*.tar.gz + if [ "$#" -ne 0 ]; then lxc image rm testimage lxc image rm testimage --project "$project-b" @@ -605,6 +608,9 @@ _backup_export_with_project() { lxc delete --force c1-foo + # Cleanup exported tarballs + rm -f "${LXD_DIR}"/c*.tar.gz + if [ "$#" -ne 0 ]; then lxc image rm testimage lxc project switch default @@ -950,6 +956,9 @@ test_backup_different_instance_uuid() { fi lxc delete -f c1 + + # Cleanup exported tarballs + rm -f "${LXD_DIR}"/c*.tar.gz } test_backup_volume_expiry() { From cf7f8bc9cd3c18380f657282d23b938d5e093ec1 Mon Sep 17 00:00:00 2001 From: Simon Deziel Date: Thu, 11 Jul 2024 14:34:48 -0400 Subject: [PATCH 27/99] test/suites/backup: replace `lxc rm` by `lxc delete` Signed-off-by: Simon Deziel --- test/suites/backup.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/suites/backup.sh b/test/suites/backup.sh index 1c51611f51ed..9ca069ad808c 100644 --- a/test/suites/backup.sh +++ b/test/suites/backup.sh @@ -489,13 +489,13 @@ _backup_import_with_project() { lxc export c3 "${LXD_DIR}/c3.tar.gz" # Remove container and storage pool - lxc rm -f c3 + lxc delete -f c3 lxc storage delete pool_1 # This should succeed as it will fall back on the default pool lxc import "${LXD_DIR}/c3.tar.gz" - lxc rm -f c3 + lxc delete -f c3 # Remove root device lxc profile device remove default root @@ -509,7 +509,7 @@ _backup_import_with_project() { # Specify pool explicitly lxc import "${LXD_DIR}/c3.tar.gz" -s pool_2 - lxc rm -f c3 + lxc delete -f c3 # Reset default storage pool lxc profile device add default root disk path=/ pool="${default_pool}" @@ -861,7 +861,7 @@ _backup_volume_export_with_project() { lxc storage volume detach "${custom_vol_pool}" testvol2 c1 lxc storage volume rm "${custom_vol_pool}" testvol lxc storage volume rm "${custom_vol_pool}" testvol2 - lxc rm -f c1 + lxc delete -f c1 rmdir "${LXD_DIR}/optimized" rmdir "${LXD_DIR}/non-optimized" @@ -1016,7 +1016,7 @@ yes EOF # Remove recovered instance. - lxc rm -f c2 + lxc delete -f c2 ) } From 68b4e0adf79a758966477734bf1836227289650e Mon Sep 17 00:00:00 2001 From: Simon Deziel Date: Thu, 11 Jul 2024 15:59:44 -0400 Subject: [PATCH 28/99] test/suites/backup: list content of untar'ed directory Signed-off-by: Simon Deziel --- test/suites/backup.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/suites/backup.sh b/test/suites/backup.sh index 9ca069ad808c..fb23360d8dd5 100644 --- a/test/suites/backup.sh +++ b/test/suites/backup.sh @@ -564,6 +564,7 @@ _backup_export_with_project() { lxc export c1 "${LXD_DIR}/c1-optimized.tar.gz" --optimized-storage --instance-only tar -xzf "${LXD_DIR}/c1-optimized.tar.gz" -C "${LXD_DIR}/optimized" + ls -l "${LXD_DIR}/optimized/backup/" [ -f "${LXD_DIR}/optimized/backup/index.yaml" ] [ -f "${LXD_DIR}/optimized/backup/container.bin" ] [ ! -d "${LXD_DIR}/optimized/backup/snapshots" ] @@ -573,6 +574,7 @@ _backup_export_with_project() { tar -xzf "${LXD_DIR}/c1.tar.gz" -C "${LXD_DIR}/non-optimized" # check tarball content + ls -l "${LXD_DIR}/non-optimized/backup/" [ -f "${LXD_DIR}/non-optimized/backup/index.yaml" ] [ -d "${LXD_DIR}/non-optimized/backup/container" ] [ ! -d "${LXD_DIR}/non-optimized/backup/snapshots" ] @@ -584,6 +586,7 @@ _backup_export_with_project() { lxc export c1 "${LXD_DIR}/c1-optimized.tar.gz" --optimized-storage tar -xzf "${LXD_DIR}/c1-optimized.tar.gz" -C "${LXD_DIR}/optimized" + ls -l "${LXD_DIR}/optimized/backup/" [ -f "${LXD_DIR}/optimized/backup/index.yaml" ] [ -f "${LXD_DIR}/optimized/backup/container.bin" ] [ -f "${LXD_DIR}/optimized/backup/snapshots/snap0.bin" ] @@ -593,6 +596,7 @@ _backup_export_with_project() { tar -xzf "${LXD_DIR}/c1.tar.gz" -C "${LXD_DIR}/non-optimized" # check tarball content + ls -l "${LXD_DIR}/non-optimized/backup/" [ -f "${LXD_DIR}/non-optimized/backup/index.yaml" ] [ -d "${LXD_DIR}/non-optimized/backup/container" ] [ -d "${LXD_DIR}/non-optimized/backup/snapshots/snap0" ] @@ -730,6 +734,7 @@ _backup_volume_export_with_project() { # Extract backup tarball. tar -xzf "${LXD_DIR}/testvol-optimized.tar.gz" -C "${LXD_DIR}/optimized" + ls -l "${LXD_DIR}/optimized/backup/" [ -f "${LXD_DIR}/optimized/backup/index.yaml" ] [ -f "${LXD_DIR}/optimized/backup/volume.bin" ] [ ! -d "${LXD_DIR}/optimized/backup/volume-snapshots" ] @@ -744,6 +749,7 @@ _backup_volume_export_with_project() { tar -xzf "${LXD_DIR}/testvol.tar.gz" -C "${LXD_DIR}/non-optimized" # Check tarball content. + ls -l "${LXD_DIR}/non-optimized/backup/" [ -f "${LXD_DIR}/non-optimized/backup/index.yaml" ] [ -d "${LXD_DIR}/non-optimized/backup/volume" ] [ "$(cat "${LXD_DIR}/non-optimized/backup/volume/test")" = "bar" ] @@ -763,6 +769,7 @@ _backup_volume_export_with_project() { # Extract backup tarball. tar -xzf "${LXD_DIR}/testvol-optimized.tar.gz" -C "${LXD_DIR}/optimized" + ls -l "${LXD_DIR}/optimized/backup/" [ -f "${LXD_DIR}/optimized/backup/index.yaml" ] [ -f "${LXD_DIR}/optimized/backup/volume.bin" ] [ -f "${LXD_DIR}/optimized/backup/volume-snapshots/test-snap0.bin" ] @@ -777,6 +784,7 @@ _backup_volume_export_with_project() { tar -xzf "${LXD_DIR}/testvol.tar.gz" -C "${LXD_DIR}/non-optimized" # Check tarball content. + ls -l "${LXD_DIR}/non-optimized/backup/" [ -f "${LXD_DIR}/non-optimized/backup/index.yaml" ] [ -d "${LXD_DIR}/non-optimized/backup/volume" ] [ "$(cat "${LXD_DIR}/non-optimized/backup/volume/test")" = "bar" ] From e593393be5a17f1e54541447177e55a61caf5895 Mon Sep 17 00:00:00 2001 From: Simon Deziel Date: Fri, 12 Jul 2024 16:38:28 -0400 Subject: [PATCH 29/99] doc/reference/storage_drivers: use emojies to ease reading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Emojies have long been displayable even on terminal( ✅ ❌ ➖ ) and they make pattern recognition way easier. Signed-off-by: Simon Deziel --- doc/reference/storage_drivers.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/doc/reference/storage_drivers.md b/doc/reference/storage_drivers.md index 07d8f0fa32af..cdfdbbec2082 100644 --- a/doc/reference/storage_drivers.md +++ b/doc/reference/storage_drivers.md @@ -27,22 +27,22 @@ See the corresponding pages for driver-specific information and configuration op Where possible, LXD uses the advanced features of each storage system to optimize operations. -Feature | Directory | Btrfs | LVM | ZFS | Ceph RBD | CephFS | Ceph Object | Dell PowerFlex -:--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- -{ref}`storage-optimized-image-storage` | no | yes | yes | yes | yes | n/a | n/a | no -Optimized instance creation | no | yes | yes | yes | yes | n/a | n/a | no -Optimized snapshot creation | no | yes | yes | yes | yes | yes | n/a | yes -Optimized image transfer | no | yes | no | yes | yes | n/a | n/a | no -{ref}`storage-optimized-volume-transfer` | no | yes | no | yes | yes[^1] | n/a | n/a | no -{ref}`storage-optimized-volume-refresh` | no | yes | yes[^2] | yes | yes[^3] | n/a | n/a | no -Copy on write | no | yes | yes | yes | yes | yes | n/a | yes -Block based | no | no | yes | no | yes | no | n/a | yes -Instant cloning | no | yes | yes | yes | yes | yes | n/a | no -Storage driver usable inside a container | yes | yes | no | yes[^4] | no | n/a | n/a | no -Restore from older snapshots (not latest) | yes | yes | yes | no | yes | yes | n/a | yes -Storage quotas | yes[^5] | yes | yes | yes | yes | yes | yes | yes -Available on `lxd init` | yes | yes | yes | yes | yes | no | no | no -Object storage | yes | yes | yes | yes | no | no | yes | no +Feature | Directory | Btrfs | LVM | ZFS | Ceph RBD | CephFS | Ceph Object | Dell PowerFlex +:--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- +{ref}`storage-optimized-image-storage` | ❌ | ✅ | ✅ | ✅ | ✅ | ➖ | ➖ | ❌ +Optimized instance creation | ❌ | ✅ | ✅ | ✅ | ✅ | ➖ | ➖ | ❌ +Optimized snapshot creation | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ➖ | ✅ +Optimized image transfer | ❌ | ✅ | ❌ | ✅ | ✅ | ➖ | ➖ | ❌ +{ref}`storage-optimized-volume-transfer` | ❌ | ✅ | ❌ | ✅ | ✅[^1] | ➖ | ➖ | ❌ +{ref}`storage-optimized-volume-refresh` | ❌ | ✅ | ✅[^2] | ✅ | ✅[^3] | ➖ | ➖ | ❌ +Copy on write | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ➖ | ✅ +Block based | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ➖ | ✅ +Instant cloning | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ➖ | ❌ +Storage driver usable inside a container | ✅ | ✅ | ❌ | ✅[^4] | ❌ | ➖ | ➖ | ❌ +Restore from older snapshots (not latest) | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ➖ | ✅ +Storage quotas | ✅[^5] | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ +Available on `lxd init` | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ +Object storage | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ [^1]: Volumes of type `block` will fall back to non-optimized transfer when migrating to an older LXD server that doesn't yet support the `RBD_AND_RSYNC` migration type. [^2]: Requires {config:option}`storage-lvm-pool-conf:lvm.use_thinpool` to be enabled. Only when refreshing local volumes. From 58a4213e3067925602a05292b7cb3cb827c61983 Mon Sep 17 00:00:00 2001 From: Simon Deziel Date: Fri, 12 Jul 2024 16:42:30 -0400 Subject: [PATCH 30/99] doc/reference/storage_drivers: add column for optimized backup support (Btrfs/ZFS only) Signed-off-by: Simon Deziel --- doc/reference/storage_drivers.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/reference/storage_drivers.md b/doc/reference/storage_drivers.md index cdfdbbec2082..195d60534d16 100644 --- a/doc/reference/storage_drivers.md +++ b/doc/reference/storage_drivers.md @@ -33,6 +33,7 @@ Feature | Directory | Btrfs | LVM | ZFS Optimized instance creation | ❌ | ✅ | ✅ | ✅ | ✅ | ➖ | ➖ | ❌ Optimized snapshot creation | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ➖ | ✅ Optimized image transfer | ❌ | ✅ | ❌ | ✅ | ✅ | ➖ | ➖ | ❌ +Optimized backup (import/export) | ❌ | ✅ | ❌ | ✅ | ❌ | ➖ | ➖ | ❌ {ref}`storage-optimized-volume-transfer` | ❌ | ✅ | ❌ | ✅ | ✅[^1] | ➖ | ➖ | ❌ {ref}`storage-optimized-volume-refresh` | ❌ | ✅ | ✅[^2] | ✅ | ✅[^3] | ➖ | ➖ | ❌ Copy on write | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ➖ | ✅ From be239aa4e095f36092a3baf13de7c414c93d1ca8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 01:15:59 +0000 Subject: [PATCH 31/99] fix(deps): update k8s.io/utils digest to 18e509b --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index f474d5782d93..ef4bd1db98c2 100644 --- a/go.mod +++ b/go.mod @@ -63,7 +63,7 @@ require ( google.golang.org/protobuf v1.34.2 gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 gopkg.in/yaml.v2 v2.4.0 - k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 ) require ( diff --git a/go.sum b/go.sum index 1d4b2c73c036..3ec3f44afe7d 100644 --- a/go.sum +++ b/go.sum @@ -1144,6 +1144,8 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= launchpad.net/xmlpath v0.0.0-20130614043138-000000000004/go.mod h1:vqyExLOM3qBx7mvYRkoxjSCF945s0mbe7YynlKYXtsA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= From c5fbd82cc5f278d65e2215a4a90c3a37d194c895 Mon Sep 17 00:00:00 2001 From: Din Music Date: Mon, 15 Jul 2024 11:02:49 +0000 Subject: [PATCH 32/99] shared/simplestreams/products: Try to extract version creation time beside the date Signed-off-by: Din Music --- shared/simplestreams/products.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/shared/simplestreams/products.go b/shared/simplestreams/products.go index 4013f59b9bb7..7939bef787f9 100644 --- a/shared/simplestreams/products.go +++ b/shared/simplestreams/products.go @@ -70,7 +70,8 @@ func (s *Products) ToLXD() ([]api.Image, map[string][][]string) { downloads := map[string][][]string{} images := []api.Image{} - nameLayout := "20060102" + nameLayoutLong := "20060102_0304" // Date and time. + nameLayoutShort := "20060102" // Date only. eolLayout := "2006-01-02" for _, product := range s.Products { @@ -91,9 +92,15 @@ func (s *Products) ToLXD() ([]api.Image, map[string][][]string) { continue } - creationDate, err := time.Parse(nameLayout, name[0:8]) + // Parse the creation date from the version name. First, check if the version contains + // both date and upload time. If it doesn't, try parsing just the date part. If the date + // cannot be fetched, skip that version instead of erroring out. + creationDate, err := time.Parse(nameLayoutLong, name) if err != nil { - continue + creationDate, err = time.Parse(nameLayoutShort, name[0:8]) + if err != nil { + continue + } } // Image processing function From badf73f3e1422958ac0d7d3e8a9347a6d9bb0f3c Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Mon, 15 Jul 2024 11:12:24 +0100 Subject: [PATCH 33/99] lxd: Use constant for devlxd remote address. Signed-off-by: Mark Laing --- lxd/daemon.go | 2 +- lxd/devlxd.go | 4 +++- lxd/images.go | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lxd/daemon.go b/lxd/daemon.go index 5d62a22d786b..4ecdac10bf4b 100644 --- a/lxd/daemon.go +++ b/lxd/daemon.go @@ -388,7 +388,7 @@ func (d *Daemon) Authenticate(w http.ResponseWriter, r *http.Request) (trusted b } // Devlxd unix socket credentials on main API. - if r.RemoteAddr == "@devlxd" { + if r.RemoteAddr == devlxdRemoteAddress { return false, "", "", nil, fmt.Errorf("Main API query can't come from /dev/lxd socket") } diff --git a/lxd/devlxd.go b/lxd/devlxd.go index 82902ea70997..1a62f31aff22 100644 --- a/lxd/devlxd.go +++ b/lxd/devlxd.go @@ -30,6 +30,8 @@ import ( "github.com/canonical/lxd/shared/ws" ) +const devlxdRemoteAddress = "@devlxd" + type hoistFunc func(f func(*Daemon, instance.Instance, http.ResponseWriter, *http.Request) response.Response, d *Daemon) func(http.ResponseWriter, *http.Request) // DevLxdServer creates an http.Server capable of handling requests against the @@ -101,7 +103,7 @@ var devlxdImageExport = devLxdHandler{"/1.0/images/{fingerprint}/export", func(d } // Use by security checks to distinguish devlxd vs lxd APIs - r.RemoteAddr = "@devlxd" + r.RemoteAddr = devlxdRemoteAddress resp := imageExport(d, r) diff --git a/lxd/images.go b/lxd/images.go index ff35a2696095..8291c63651e3 100644 --- a/lxd/images.go +++ b/lxd/images.go @@ -4033,7 +4033,7 @@ func imageExport(d *Daemon, r *http.Request) response.Response { secret := r.FormValue("secret") - if r.RemoteAddr == "@devlxd" { + if r.RemoteAddr == devlxdRemoteAddress { if !imgInfo.Public && !imgInfo.Cached { return response.NotFound(fmt.Errorf("Image %q not found", fingerprint)) } From 3a9711f45c36089692b6565a84cb912ed8ac4bfa Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Tue, 9 Jul 2024 16:41:50 +0100 Subject: [PATCH 34/99] lxd: Clarify authentication/authorization for viewing/exporting images. Signed-off-by: Mark Laing --- lxd/images.go | 160 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 116 insertions(+), 44 deletions(-) diff --git a/lxd/images.go b/lxd/images.go index 8291c63651e3..db5d45e846a4 100644 --- a/lxd/images.go +++ b/lxd/images.go @@ -973,6 +973,7 @@ func imagesPost(d *Daemon, r *http.Request) response.Response { projectName := request.ProjectParam(r) + // If the client is not authenticated, CheckPermission will return a http.StatusForbidden api.StatusError. var userCanCreateImages bool err := s.Authorizer.CheckPermission(r.Context(), entity.ProjectURL(projectName), auth.EntitlementCanCreateImages) if err != nil && !auth.IsDeniedError(err) { @@ -1631,14 +1632,12 @@ func imagesGet(d *Daemon, r *http.Request) response.Response { request.SetCtxValue(r, request.CtxEffectiveProjectName, effectiveProjectName) - // Check if the caller is authenticated via the request context. - trusted, err := request.GetCtxValue[bool](r.Context(), request.CtxTrusted) - if err != nil { - return response.InternalError(fmt.Errorf("Failed getting authentication status: %w", err)) - } + // If the caller is not trusted, we only want to list public images. + publicOnly := !auth.IsTrusted(r.Context()) // Get a permission checker. If the caller is not authenticated, the permission checker will deny all. - // However, the permission checker will not be called for public images. + // However, the permission checker is only called when an image is private. Both trusted and untrusted clients will + // still see public images. canViewImage, err := s.Authorizer.GetPermissionChecker(r.Context(), auth.EntitlementCanView, entity.TypeImage) if err != nil { return response.SmartError(err) @@ -1651,7 +1650,7 @@ func imagesGet(d *Daemon, r *http.Request) response.Response { var result any err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error { - result, err = doImagesGet(ctx, tx, util.IsRecursionRequest(r), projectName, !trusted, clauses, canViewImage) + result, err = doImagesGet(ctx, tx, util.IsRecursionRequest(r), projectName, publicOnly, clauses, canViewImage) if err != nil { return err } @@ -2993,37 +2992,65 @@ func imageGet(d *Daemon, r *http.Request) response.Response { return response.SmartError(err) } - // Get the image (expand partial fingerprints). + trusted := auth.IsTrusted(r.Context()) + secret := r.FormValue("secret") + + // Unauthenticated clients that do not provide a secret may only view public images. + publicOnly := !trusted && secret == "" + + // Get the image. We need to do this before the permission check because the URL in the permission check will not + // work with partial fingerprints. var info *api.Image err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error { - info, err = doImageGet(ctx, tx, projectName, fingerprint, false) + info, err = doImageGet(ctx, tx, projectName, fingerprint, publicOnly) if err != nil { return err } return nil }) - if err != nil { + if err != nil && api.StatusErrorCheck(err, http.StatusNotFound) { + // Return a generic not found. This is so that the caller cannot determine the existence of an image by the + // contents of the error message. + return response.NotFound(nil) + } else if err != nil { return response.SmartError(err) } + // Access check. var userCanViewImage bool - err = s.Authorizer.CheckPermission(r.Context(), entity.ImageURL(projectName, info.Fingerprint), auth.EntitlementCanView) - if err != nil && !auth.IsDeniedError(err) { - return response.SmartError(err) - } else if err == nil { - userCanViewImage = true - } + if secret != "" { + // If a secret was provided, validate it regardless of whether the image is public or the caller has sufficient + // privilege. This is to ensure the image token operation is cancelled. + op, err := imageValidSecret(s, r, projectName, info.Fingerprint, secret) + if err != nil { + return response.SmartError(err) + } - secret := r.FormValue("secret") + // If an operation was found the caller has access, otherwise continue to other access checks. + if op != nil { + userCanViewImage = true + } + } - op, err := imageValidSecret(s, r, projectName, info.Fingerprint, secret) - if err != nil { - return response.SmartError(err) + // No operation found for the secret. Perform other access checks. + if !userCanViewImage { + if info.Public { + // If the image is public any client can view it. + userCanViewImage = true + } else { + // Otherwise perform an access check with the full image fingerprint. + err = s.Authorizer.CheckPermission(r.Context(), entity.ImageURL(projectName, info.Fingerprint), auth.EntitlementCanView) + if err != nil && !auth.IsDeniedError(err) { + return response.SmartError(err) + } else if err == nil { + userCanViewImage = true + } + } } - // If the caller does not have permission to view the image and the secret was invalid, return generic not found. - if !info.Public && !userCanViewImage && op == nil { + // If the client still cannot view the image, return a generic not found error. + if !userCanViewImage { return response.NotFound(nil) } @@ -3597,6 +3624,8 @@ func imageAliasGet(d *Daemon, r *http.Request) response.Response { s := d.State() + // Set `userCanViewImageAlias` to true only when the caller is authenticated and can view the alias. + // We don't abort the request if this is false because the image alias may be for a public image. var userCanViewImageAlias bool err = s.Authorizer.CheckPermission(r.Context(), entity.ImageAliasURL(projectName, name), auth.EntitlementCanView) if err != nil && !auth.IsDeniedError(err) { @@ -3607,12 +3636,16 @@ func imageAliasGet(d *Daemon, r *http.Request) response.Response { var alias api.ImageAliasesEntry err = d.State().DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error { + // If `userCanViewImageAlias` is false, the query will be restricted to public images only. _, alias, err = tx.GetImageAlias(ctx, projectName, name, userCanViewImageAlias) return err }) - if err != nil { + if err != nil && !api.StatusErrorCheck(err, http.StatusNotFound) { return response.SmartError(err) + } else if err != nil { + // Return a generic not found error. + return response.NotFound(nil) } return response.SyncResponseETag(true, alias, alias) @@ -4010,43 +4043,82 @@ func imageExport(d *Daemon, r *http.Request) response.Response { return response.SmartError(err) } - // Get the image (expand the fingerprint). + isDevLXDQuery := r.RemoteAddr == devlxdRemoteAddress + secret := r.FormValue("secret") + trusted := auth.IsTrusted(r.Context()) + + // Unauthenticated remote clients that do not provide a secret may only view public images. + // For devlxd, we allow querying for private images. We'll subsequently perform additional access checks. + publicOnly := !trusted && secret == "" && !isDevLXDQuery + + // Get the image. We need to do this before the permission check because the URL in the permission check will not + // work with partial fingerprints. var imgInfo *api.Image - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { - // Get the image (expand the fingerprint). - _, imgInfo, err = tx.GetImage(ctx, fingerprint, dbCluster.ImageFilter{Project: &projectName}) + err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error { + filter := dbCluster.ImageFilter{Project: &projectName} + if publicOnly { + filter.Public = &publicOnly + } + _, imgInfo, err = tx.GetImage(ctx, fingerprint, filter) return err }) - if err != nil { + if err != nil && api.StatusErrorCheck(err, http.StatusNotFound) { + // Return a generic not found. This is so that the caller cannot determine the existence of an image by the + // contents of the error message. + return response.NotFound(nil) + } else if err != nil { return response.SmartError(err) } // Access control. var userCanViewImage bool - err = s.Authorizer.CheckPermission(r.Context(), entity.ImageURL(projectName, imgInfo.Fingerprint), auth.EntitlementCanView) - if err != nil && !auth.IsDeniedError(err) { - return response.SmartError(err) - } else if err == nil { - userCanViewImage = true - } - - secret := r.FormValue("secret") - - if r.RemoteAddr == devlxdRemoteAddress { - if !imgInfo.Public && !imgInfo.Cached { - return response.NotFound(fmt.Errorf("Image %q not found", fingerprint)) - } - } else { + if secret != "" { + // If a secret was provided, validate it regardless of whether the image is public or the caller has sufficient + // privilege. This is to ensure the image token operation is cancelled. op, err := imageValidSecret(s, r, projectName, imgInfo.Fingerprint, secret) if err != nil { return response.SmartError(err) } - // If the image is not public and the caller cannot view it, return a generic not found error. - if !imgInfo.Public && !userCanViewImage && op == nil { + // If an operation was found the caller has access, otherwise continue to other access checks. + if op != nil { + userCanViewImage = true + } + } + + if isDevLXDQuery { + // A devlxd query must contain the full fingerprint of the image (no partials). + if fingerprint != imgInfo.Fingerprint { + return response.NotFound(nil) + } + + // A devlxd query must be for a public or cached image. + if !(imgInfo.Public || imgInfo.Cached) { return response.NotFound(nil) } + + userCanViewImage = true + } + + if !userCanViewImage { + if imgInfo.Public { + // If the image is public any client can view it. + userCanViewImage = true + } else { + // Otherwise perform an access check with the full image fingerprint. + err = s.Authorizer.CheckPermission(r.Context(), entity.ImageURL(projectName, imgInfo.Fingerprint), auth.EntitlementCanView) + if err != nil && !auth.IsDeniedError(err) { + return response.SmartError(err) + } else if err == nil { + userCanViewImage = true + } + } + } + + // If the client still cannot view the image, return a generic not found error. + if !userCanViewImage { + return response.NotFound(nil) } var address string From 17349e01933eb887a28f23ff5d91f8d639b6ce90 Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Thu, 11 Jul 2024 12:12:17 +0100 Subject: [PATCH 35/99] test/suites: Test public image behaviour for trusted, restricted clients. Signed-off-by: Mark Laing --- test/suites/tls_restrictions.sh | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/test/suites/tls_restrictions.sh b/test/suites/tls_restrictions.sh index 5f807b83e9ed..d898d864b078 100644 --- a/test/suites/tls_restrictions.sh +++ b/test/suites/tls_restrictions.sh @@ -60,17 +60,34 @@ test_tls_restrictions() { ! lxc_remote storage volume list "localhost:${pool_name}" --project default || false ! lxc_remote storage bucket list "localhost:${pool_name}" --project default || false - # Can still list images as some may be public. There are no public images in the default project now, - # so the list should be empty - [ "$(lxc_remote image list localhost --project default --format csv)" = "" ] + ### Validate images. + test_image_fingerprint="$(lxc image info testimage --project default | awk '/^Fingerprint/ {print $2}')" - # Set up the test image in the blah project (ensure_import_testimage imports the image into the current project). - lxc project switch blah && ensure_import_testimage && lxc project switch default + # We can always list images, but there are no public images in the default project now, so the list should be empty. + [ "$(lxc_remote image list localhost: --project default --format csv)" = "" ] + ! lxc_remote image show localhost:testimage --project default || false + + # Set the image to public and ensure we can view it. + lxc image show testimage --project default | sed -e "s/public: false/public: true/" | lxc image edit testimage --project default + [ "$(lxc_remote image list localhost: --project default --format csv | wc -l)" = 1 ] + lxc_remote image show localhost:testimage --project default + + # Check we can export the public image: + lxc image export localhost:testimage "${LXD_DIR}/" --project default + [ "${test_image_fingerprint}" = "$(sha256sum "${LXD_DIR}/${test_image_fingerprint}.tar.xz" | cut -d' ' -f1)" ] + rm "${LXD_DIR}/${test_image_fingerprint}.tar.xz" + + # While the image is public, copy it to the blah project and create an alias for it. + lxc_remote image copy localhost:testimage localhost: --project default --target-project blah + lxc_remote image alias create localhost:testimage "${test_image_fingerprint}" --project blah + + # Restore privacy on the test image in the default project. + lxc image show testimage --project default | sed -e "s/public: true/public: false/" | lxc image edit testimage --project default # Set up a profile in the blah project. Additionally ensures restricted TLS clients can edit profiles in projects they have access to. lxc profile show default | lxc_remote profile edit localhost:default --project blah - # Create an instance. + # Create an instance (using the test image copied from the default project while it was public). lxc_remote init testimage localhost:blah-instance --project blah # Create a custom volume. From 373d1d8e109002941cbe60ac7ec0e541aeb7a793 Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Fri, 12 Jul 2024 13:30:15 +0100 Subject: [PATCH 36/99] lxd: Send image-retrieved lifecycle event when rootfs file is present. Signed-off-by: Mark Laing --- lxd/images.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lxd/images.go b/lxd/images.go index db5d45e846a4..341ae033a6d6 100644 --- a/lxd/images.go +++ b/lxd/images.go @@ -4178,6 +4178,9 @@ func imageExport(d *Daemon, r *http.Request) response.Response { files[1].Path = rootfsPath files[1].Filename = filename + requestor := request.CreateRequestor(r) + s.Events.SendLifecycle(projectName, lifecycle.ImageRetrieved.Event(imgInfo.Fingerprint, projectName, requestor, nil)) + return response.FileResponse(r, files, nil) } From 3da82873972159bcca5d9c3980ff887d3ac251bd Mon Sep 17 00:00:00 2001 From: Alexander Mikhalitsyn Date: Thu, 11 Jul 2024 13:27:19 +0200 Subject: [PATCH 37/99] lxd/apparmor: fix linter errors Signed-off-by: Alexander Mikhalitsyn --- lxd/apparmor/archive.go | 2 +- lxd/apparmor/rsync.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lxd/apparmor/archive.go b/lxd/apparmor/archive.go index e4a691722382..ee9789293dd0 100644 --- a/lxd/apparmor/archive.go +++ b/lxd/apparmor/archive.go @@ -122,7 +122,7 @@ func archiveProfile(outputPath string, allowedCommandPaths []string) (string, er } // Render the profile. - var sb *strings.Builder = &strings.Builder{} + sb := &strings.Builder{} err = archiveProfileTpl.Execute(sb, map[string]any{ "name": ArchiveProfileName(outputPath), // Use non-deferenced outputPath for name. "outputPath": outputPathFull, // Use deferenced path in AppArmor profile. diff --git a/lxd/apparmor/rsync.go b/lxd/apparmor/rsync.go index a12a153a950f..346551dfee31 100644 --- a/lxd/apparmor/rsync.go +++ b/lxd/apparmor/rsync.go @@ -175,7 +175,7 @@ func rsyncProfile(sysOS *sys.OS, name string, sourcePath string, dstPath string) execPath = fullPath } - var sb *strings.Builder = &strings.Builder{} + sb := &strings.Builder{} err = rsyncProfileTpl.Execute(sb, map[string]any{ "name": name, "execPath": execPath, From f5a367de25e3101d6db3292e19fcc8b8fec711cb Mon Sep 17 00:00:00 2001 From: Thomas Parrott Date: Tue, 16 Jul 2024 11:47:26 +0100 Subject: [PATCH 38/99] Removes CODEOWNERS file No longer needed as we use GH's own push protections. Signed-off-by: Thomas Parrott --- .github/CODEOWNERS | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 30e9030d905c..000000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @tomponline From d1864391821cc9ecb88b738a044dfe43cb6ddca6 Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Tue, 16 Jul 2024 12:16:39 +0100 Subject: [PATCH 39/99] lxd: Define devlxd handler functions. Signed-off-by: Mark Laing --- lxd/devlxd.go | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/lxd/devlxd.go b/lxd/devlxd.go index 1a62f31aff22..d943364f75a9 100644 --- a/lxd/devlxd.go +++ b/lxd/devlxd.go @@ -56,7 +56,9 @@ type devLxdHandler struct { f func(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response } -var devlxdConfigGet = devLxdHandler{"/1.0/config", func(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { +var devlxdConfigGet = devLxdHandler{"/1.0/config", devlxdConfigGetHandler} + +func devlxdConfigGetHandler(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { if shared.IsFalse(c.ExpandedConfig()["security.devlxd"]) { return response.DevLxdErrorResponse(api.StatusErrorf(http.StatusForbidden, "not authorized"), c.Type() == instancetype.VM) } @@ -69,9 +71,11 @@ var devlxdConfigGet = devLxdHandler{"/1.0/config", func(d *Daemon, c instance.In } return response.DevLxdResponse(http.StatusOK, filtered, "json", c.Type() == instancetype.VM) -}} +} -var devlxdConfigKeyGet = devLxdHandler{"/1.0/config/{key}", func(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { +var devlxdConfigKeyGet = devLxdHandler{"/1.0/config/{key}", devlxdConfigKeyGetHandler} + +func devlxdConfigKeyGetHandler(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { if shared.IsFalse(c.ExpandedConfig()["security.devlxd"]) { return response.DevLxdErrorResponse(api.StatusErrorf(http.StatusForbidden, "not authorized"), c.Type() == instancetype.VM) } @@ -91,9 +95,11 @@ var devlxdConfigKeyGet = devLxdHandler{"/1.0/config/{key}", func(d *Daemon, c in } return response.DevLxdResponse(http.StatusOK, value, "raw", c.Type() == instancetype.VM) -}} +} -var devlxdImageExport = devLxdHandler{"/1.0/images/{fingerprint}/export", func(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { +var devlxdImageExport = devLxdHandler{"/1.0/images/{fingerprint}/export", devlxdImageExportHandler} + +func devlxdImageExportHandler(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { if shared.IsFalse(c.ExpandedConfig()["security.devlxd"]) { return response.DevLxdErrorResponse(api.StatusErrorf(http.StatusForbidden, "not authorized"), c.Type() == instancetype.VM) } @@ -113,9 +119,11 @@ var devlxdImageExport = devLxdHandler{"/1.0/images/{fingerprint}/export", func(d } return response.DevLxdResponse(http.StatusOK, "", "raw", c.Type() == instancetype.VM) -}} +} + +var devlxdMetadataGet = devLxdHandler{"/1.0/meta-data", devlxdMetadataGetHandler} -var devlxdMetadataGet = devLxdHandler{"/1.0/meta-data", func(d *Daemon, inst instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { +func devlxdMetadataGetHandler(d *Daemon, inst instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { if shared.IsFalse(inst.ExpandedConfig()["security.devlxd"]) { return response.DevLxdErrorResponse(api.StatusErrorf(http.StatusForbidden, "not authorized"), inst.Type() == instancetype.VM) } @@ -123,9 +131,11 @@ var devlxdMetadataGet = devLxdHandler{"/1.0/meta-data", func(d *Daemon, inst ins value := inst.ExpandedConfig()["user.meta-data"] return response.DevLxdResponse(http.StatusOK, fmt.Sprintf("#cloud-config\ninstance-id: %s\nlocal-hostname: %s\n%s", inst.CloudInitID(), inst.Name(), value), "raw", inst.Type() == instancetype.VM) -}} +} + +var devlxdEventsGet = devLxdHandler{"/1.0/events", devlxdEventsGetHandler} -var devlxdEventsGet = devLxdHandler{"/1.0/events", func(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { +func devlxdEventsGetHandler(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { if shared.IsFalse(c.ExpandedConfig()["security.devlxd"]) { return response.DevLxdErrorResponse(api.StatusErrorf(http.StatusForbidden, "not authorized"), c.Type() == instancetype.VM) } @@ -180,9 +190,11 @@ var devlxdEventsGet = devLxdHandler{"/1.0/events", func(d *Daemon, c instance.In listener.Wait(r.Context()) return resp -}} +} -var devlxdAPIHandler = devLxdHandler{"/1.0", func(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { +var devlxdAPIHandler = devLxdHandler{"/1.0", devlxdAPIHandlerFunc} + +func devlxdAPIHandlerFunc(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { s := d.State() if r.Method == "GET" { @@ -238,10 +250,11 @@ var devlxdAPIHandler = devLxdHandler{"/1.0", func(d *Daemon, c instance.Instance } return response.DevLxdErrorResponse(api.StatusErrorf(http.StatusMethodNotAllowed, fmt.Sprintf("method %q not allowed", r.Method)), c.Type() == instancetype.VM) +} -}} +var devlxdDevicesGet = devLxdHandler{"/1.0/devices", devlxdDevicesGetHandler} -var devlxdDevicesGet = devLxdHandler{"/1.0/devices", func(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { +func devlxdDevicesGetHandler(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { if shared.IsFalse(c.ExpandedConfig()["security.devlxd"]) { return response.DevLxdErrorResponse(api.StatusErrorf(http.StatusForbidden, "not authorized"), c.Type() == instancetype.VM) } @@ -258,7 +271,7 @@ var devlxdDevicesGet = devLxdHandler{"/1.0/devices", func(d *Daemon, c instance. } return response.DevLxdResponse(http.StatusOK, c.ExpandedDevices(), "json", c.Type() == instancetype.VM) -}} +} var handlers = []devLxdHandler{ {"/", func(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { From 8a46c1ed675a56ff821f56645d2ee8fcfebc8be9 Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Tue, 16 Jul 2024 12:17:43 +0100 Subject: [PATCH 40/99] lxd: Define a type for a devlxd handler function. Signed-off-by: Mark Laing --- lxd/devlxd.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lxd/devlxd.go b/lxd/devlxd.go index d943364f75a9..71169a01b02d 100644 --- a/lxd/devlxd.go +++ b/lxd/devlxd.go @@ -34,6 +34,8 @@ const devlxdRemoteAddress = "@devlxd" type hoistFunc func(f func(*Daemon, instance.Instance, http.ResponseWriter, *http.Request) response.Response, d *Daemon) func(http.ResponseWriter, *http.Request) +type devlxdHandlerFunc func(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response + // DevLxdServer creates an http.Server capable of handling requests against the // /dev/lxd Unix socket endpoint created inside containers. func devLxdServer(d *Daemon) *http.Server { From 74a4672d3ac915d3f6b2618a4586b5c3b3810f8c Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Tue, 16 Jul 2024 12:18:26 +0100 Subject: [PATCH 41/99] lxd: Rename the `f` field of the devlxdHandler type. Go conventions state that single letter variable names should be used for receivers, for familiar types (e.g. r for io.Reader or *http.Request) or in very short scopes such as loops. They shouldn't be used for field names. See https://google.github.io/styleguide/go/decisions.html#single-letter-variable-names. Signed-off-by: Mark Laing --- lxd/devlxd.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lxd/devlxd.go b/lxd/devlxd.go index 71169a01b02d..ea91ba1379da 100644 --- a/lxd/devlxd.go +++ b/lxd/devlxd.go @@ -55,7 +55,7 @@ type devLxdHandler struct { * server side right now either, I went the simple route to avoid * needless noise. */ - f func(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response + handlerFunc devlxdHandlerFunc } var devlxdConfigGet = devLxdHandler{"/1.0/config", devlxdConfigGetHandler} @@ -329,7 +329,7 @@ func devLxdAPI(d *Daemon, f hoistFunc) http.Handler { m.UseEncodedPath() // Allow encoded values in path segments. for _, handler := range handlers { - m.HandleFunc(handler.path, f(handler.f, d)) + m.HandleFunc(handler.path, f(handler.handlerFunc, d)) } return m From 1c4ac717f2fc18efe2fb69176e4e4bd5b9be024d Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Tue, 16 Jul 2024 12:29:06 +0100 Subject: [PATCH 42/99] lxd: Specify field names in devlxd handler definitions. Field names are optional if private according to the Go style guide (https://google.github.io/styleguide/go/decisions.html#field-names), but for consistency in our code-base we should include the field names. Signed-off-by: Mark Laing --- lxd/devlxd.go | 44 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/lxd/devlxd.go b/lxd/devlxd.go index ea91ba1379da..140f1153903b 100644 --- a/lxd/devlxd.go +++ b/lxd/devlxd.go @@ -58,7 +58,10 @@ type devLxdHandler struct { handlerFunc devlxdHandlerFunc } -var devlxdConfigGet = devLxdHandler{"/1.0/config", devlxdConfigGetHandler} +var devlxdConfigGet = devLxdHandler{ + path: "/1.0/config", + handlerFunc: devlxdConfigGetHandler, +} func devlxdConfigGetHandler(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { if shared.IsFalse(c.ExpandedConfig()["security.devlxd"]) { @@ -75,7 +78,10 @@ func devlxdConfigGetHandler(d *Daemon, c instance.Instance, w http.ResponseWrite return response.DevLxdResponse(http.StatusOK, filtered, "json", c.Type() == instancetype.VM) } -var devlxdConfigKeyGet = devLxdHandler{"/1.0/config/{key}", devlxdConfigKeyGetHandler} +var devlxdConfigKeyGet = devLxdHandler{ + path: "/1.0/config/{key}", + handlerFunc: devlxdConfigKeyGetHandler, +} func devlxdConfigKeyGetHandler(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { if shared.IsFalse(c.ExpandedConfig()["security.devlxd"]) { @@ -99,7 +105,10 @@ func devlxdConfigKeyGetHandler(d *Daemon, c instance.Instance, w http.ResponseWr return response.DevLxdResponse(http.StatusOK, value, "raw", c.Type() == instancetype.VM) } -var devlxdImageExport = devLxdHandler{"/1.0/images/{fingerprint}/export", devlxdImageExportHandler} +var devlxdImageExport = devLxdHandler{ + path: "/1.0/images/{fingerprint}/export", + handlerFunc: devlxdImageExportHandler, +} func devlxdImageExportHandler(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { if shared.IsFalse(c.ExpandedConfig()["security.devlxd"]) { @@ -123,7 +132,10 @@ func devlxdImageExportHandler(d *Daemon, c instance.Instance, w http.ResponseWri return response.DevLxdResponse(http.StatusOK, "", "raw", c.Type() == instancetype.VM) } -var devlxdMetadataGet = devLxdHandler{"/1.0/meta-data", devlxdMetadataGetHandler} +var devlxdMetadataGet = devLxdHandler{ + path: "/1.0/meta-data", + handlerFunc: devlxdMetadataGetHandler, +} func devlxdMetadataGetHandler(d *Daemon, inst instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { if shared.IsFalse(inst.ExpandedConfig()["security.devlxd"]) { @@ -135,7 +147,10 @@ func devlxdMetadataGetHandler(d *Daemon, inst instance.Instance, w http.Response return response.DevLxdResponse(http.StatusOK, fmt.Sprintf("#cloud-config\ninstance-id: %s\nlocal-hostname: %s\n%s", inst.CloudInitID(), inst.Name(), value), "raw", inst.Type() == instancetype.VM) } -var devlxdEventsGet = devLxdHandler{"/1.0/events", devlxdEventsGetHandler} +var devlxdEventsGet = devLxdHandler{ + path: "/1.0/events", + handlerFunc: devlxdEventsGetHandler, +} func devlxdEventsGetHandler(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { if shared.IsFalse(c.ExpandedConfig()["security.devlxd"]) { @@ -194,7 +209,10 @@ func devlxdEventsGetHandler(d *Daemon, c instance.Instance, w http.ResponseWrite return resp } -var devlxdAPIHandler = devLxdHandler{"/1.0", devlxdAPIHandlerFunc} +var devlxdAPIHandler = devLxdHandler{ + path: "/1.0", + handlerFunc: devlxdAPIHandlerFunc, +} func devlxdAPIHandlerFunc(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { s := d.State() @@ -254,7 +272,10 @@ func devlxdAPIHandlerFunc(d *Daemon, c instance.Instance, w http.ResponseWriter, return response.DevLxdErrorResponse(api.StatusErrorf(http.StatusMethodNotAllowed, fmt.Sprintf("method %q not allowed", r.Method)), c.Type() == instancetype.VM) } -var devlxdDevicesGet = devLxdHandler{"/1.0/devices", devlxdDevicesGetHandler} +var devlxdDevicesGet = devLxdHandler{ + path: "/1.0/devices", + handlerFunc: devlxdDevicesGetHandler, +} func devlxdDevicesGetHandler(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { if shared.IsFalse(c.ExpandedConfig()["security.devlxd"]) { @@ -276,9 +297,12 @@ func devlxdDevicesGetHandler(d *Daemon, c instance.Instance, w http.ResponseWrit } var handlers = []devLxdHandler{ - {"/", func(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { - return response.DevLxdResponse(http.StatusOK, []string{"/1.0"}, "json", c.Type() == instancetype.VM) - }}, + { + path: "/", + handlerFunc: func(d *Daemon, c instance.Instance, w http.ResponseWriter, r *http.Request) response.Response { + return response.DevLxdResponse(http.StatusOK, []string{"/1.0"}, "json", c.Type() == instancetype.VM) + }, + }, devlxdAPIHandler, devlxdConfigGet, devlxdConfigKeyGet, From 249264d3376f07b2e97dbe424514982b35fb3a22 Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Mon, 15 Jul 2024 13:29:53 +0100 Subject: [PATCH 43/99] lxd: Fix lint errors (revive: unchecked-type-assertion). Additionally, if the `net.Conn` in `(*ConnPidMapper).ConnStateHandler` is not a (*net.UnixConn), return early to avoid a panic. Signed-off-by: Mark Laing --- lxd/devlxd.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lxd/devlxd.go b/lxd/devlxd.go index 140f1153903b..8f21c77aa1bb 100644 --- a/lxd/devlxd.go +++ b/lxd/devlxd.go @@ -392,7 +392,12 @@ type ConnPidMapper struct { } func (m *ConnPidMapper) ConnStateHandler(conn net.Conn, state http.ConnState) { - unixConn := conn.(*net.UnixConn) + unixConn, _ := conn.(*net.UnixConn) + if unixConn == nil { + logger.Error("Invalid type for devlxd connection", logger.Ctx{"conn_type": fmt.Sprintf("%T", conn)}) + return + } + switch state { case http.StateNew: cred, err := ucred.GetCred(unixConn) @@ -478,7 +483,9 @@ func findContainerForPid(pid int32, s *state.State) (instance.Container, error) return nil, fmt.Errorf("Instance is not container type") } - return inst.(instance.Container), nil + // Explicitly ignore type assertion check. We've just checked that it's a container. + c, _ := inst.(instance.Container) + return c, nil } status, err := os.ReadFile(fmt.Sprintf("/proc/%d/status", pid)) @@ -531,7 +538,9 @@ func findContainerForPid(pid int32, s *state.State) (instance.Container, error) } if origPidNs == pidNs { - return inst.(instance.Container), nil + // Explicitly ignore type assertion check. The instance must be a container if we've found it via the process ID. + c, _ := inst.(instance.Container) + return c, nil } } From 56d8da2e0322ad6828c301aa8ecda96dfead2b61 Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Mon, 15 Jul 2024 13:33:21 +0100 Subject: [PATCH 44/99] lxd: Remove log formatting and use log context instead. Signed-off-by: Mark Laing --- lxd/devlxd.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lxd/devlxd.go b/lxd/devlxd.go index 8f21c77aa1bb..017bcb47b78c 100644 --- a/lxd/devlxd.go +++ b/lxd/devlxd.go @@ -402,7 +402,7 @@ func (m *ConnPidMapper) ConnStateHandler(conn net.Conn, state http.ConnState) { case http.StateNew: cred, err := ucred.GetCred(unixConn) if err != nil { - logger.Debugf("Error getting ucred for conn %s", err) + logger.Debug("Error getting ucred for devlxd connection", logger.Ctx{"error": err}) } else { m.mLock.Lock() m.m[unixConn] = cred @@ -430,7 +430,7 @@ func (m *ConnPidMapper) ConnStateHandler(conn net.Conn, state http.ConnState) { delete(m.m, unixConn) m.mLock.Unlock() default: - logger.Debugf("Unknown state for connection %s", state) + logger.Debug("Unknown state for devlxd connection", logger.Ctx{"state": state.String()}) } } From d1ba10c6c7560e17241a06fb5f76c2ddee3fbe2f Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Mon, 15 Jul 2024 13:36:06 +0100 Subject: [PATCH 45/99] lxd: Rename pidNotInContainerErr (revive: error-naming). Additionally standardise the error message. Signed-off-by: Mark Laing --- lxd/devlxd.go | 7 ++++--- lxd/devlxd_test.go | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lxd/devlxd.go b/lxd/devlxd.go index 017bcb47b78c..692324785885 100644 --- a/lxd/devlxd.go +++ b/lxd/devlxd.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "errors" "fmt" "net" "net/http" @@ -317,7 +318,7 @@ func hoistReq(f func(*Daemon, instance.Instance, http.ResponseWriter, *http.Requ conn := ucred.GetConnFromContext(r.Context()) cred, ok := pidMapper.m[conn.(*net.UnixConn)] if !ok { - http.Error(w, pidNotInContainerErr.Error(), http.StatusInternalServerError) + http.Error(w, errPIDNotInContainer.Error(), http.StatusInternalServerError) return } @@ -434,7 +435,7 @@ func (m *ConnPidMapper) ConnStateHandler(conn net.Conn, state http.ConnState) { } } -var pidNotInContainerErr = fmt.Errorf("pid not in container?") +var errPIDNotInContainer = errors.New("Process ID not found in container") func findContainerForPid(pid int32, s *state.State) (instance.Container, error) { /* @@ -544,5 +545,5 @@ func findContainerForPid(pid int32, s *state.State) (instance.Container, error) } } - return nil, pidNotInContainerErr + return nil, errPIDNotInContainer } diff --git a/lxd/devlxd_test.go b/lxd/devlxd_test.go index 1ad667c4ac8a..6c21bae7c03f 100644 --- a/lxd/devlxd_test.go +++ b/lxd/devlxd_test.go @@ -169,7 +169,7 @@ func TestHttpRequest(t *testing.T) { t.Fatal(err) } - if !strings.Contains(string(resp), pidNotInContainerErr.Error()) { + if !strings.Contains(string(resp), errPIDNotInContainer.Error()) { t.Fatal("resp error not expected: ", string(resp)) } } From c9f7ec208c506286197bd1d5af51c4adaac634f7 Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Mon, 15 Jul 2024 13:46:39 +0100 Subject: [PATCH 46/99] lxd: Add comments to exported types/methods (revive: exported). Signed-off-by: Mark Laing --- lxd/devlxd.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lxd/devlxd.go b/lxd/devlxd.go index 692324785885..45322a2b2196 100644 --- a/lxd/devlxd.go +++ b/lxd/devlxd.go @@ -387,11 +387,15 @@ func devLxdAPI(d *Daemon, f hoistFunc) http.Handler { */ var pidMapper = ConnPidMapper{m: map[*net.UnixConn]*unix.Ucred{}} +// ConnPidMapper is threadsafe cache of unix connections to process IDs. We use this in hoistReq to determine +// the instance that the connection has been made from. type ConnPidMapper struct { m map[*net.UnixConn]*unix.Ucred mLock sync.Mutex } +// ConnStateHandler is used in the `ConnState` field of the devlxd http.Server so that we can cache the process ID of the +// caller when a new connection is made and delete it when the connection is closed. func (m *ConnPidMapper) ConnStateHandler(conn net.Conn, state http.ConnState) { unixConn, _ := conn.(*net.UnixConn) if unixConn == nil { From 20c9b55c6f24c1249add2ba579800e79dfafe5d7 Mon Sep 17 00:00:00 2001 From: Thomas Parrott Date: Tue, 16 Jul 2024 16:15:51 +0100 Subject: [PATCH 47/99] test: Adds a test for the none device Useful to catch missing runConf != nil issues. Signed-off-by: Thomas Parrott --- test/main.sh | 1 + test/suites/container_devices_none.sh | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 test/suites/container_devices_none.sh diff --git a/test/main.sh b/test/main.sh index 97b8525b4ed6..1c205965d27e 100755 --- a/test/main.sh +++ b/test/main.sh @@ -288,6 +288,7 @@ if [ "${1:-"all"}" != "cluster" ]; then run_test test_container_devices_nic_ipvlan "container devices - nic - ipvlan" run_test test_container_devices_nic_sriov "container devices - nic - sriov" run_test test_container_devices_nic_routed "container devices - nic - routed" + run_test test_container_devices_none "container devices - none" run_test test_container_devices_infiniband_physical "container devices - infiniband - physical" run_test test_container_devices_infiniband_sriov "container devices - infiniband - sriov" run_test test_container_devices_proxy "container devices - proxy" diff --git a/test/suites/container_devices_none.sh b/test/suites/container_devices_none.sh new file mode 100644 index 000000000000..2325a016a265 --- /dev/null +++ b/test/suites/container_devices_none.sh @@ -0,0 +1,20 @@ +test_container_devices_none() { + ensure_import_testimage + ensure_has_localhost_remote "${LXD_ADDR}" + ctName="ct$$" + lxc launch testimage "${ctName}" + + # Check eth0 interface exists. + lxc exec "${ctName}" -- stat /sys/class/net/eth0 + + # Add none device to remove eth0 interface (simulating network disruption). + lxc config device add "${ctName}" eth0 none + ! lxc exec "${ctName}" -- stat /sys/class/net/eth0 || false + + # Remove device and check eth0 interface is added back. + lxc config device rm "${ctName}" eth0 + lxc exec "${ctName}" -- stat /sys/class/net/eth0 + + # Clean up + lxc rm -f "${ctName}" +} From a1dcd28284940c1a20199763a51bf8cb6da79179 Mon Sep 17 00:00:00 2001 From: Din Music Date: Tue, 16 Jul 2024 15:29:14 +0000 Subject: [PATCH 48/99] lxd/api_1.0: Sort drivers in response Signed-off-by: Din Music --- lxd/api_1.0.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go index a5791f173dca..e5b21ae54fe3 100644 --- a/lxd/api_1.0.go +++ b/lxd/api_1.0.go @@ -8,6 +8,7 @@ import ( "net" "net/http" "os" + "slices" "github.com/canonical/lxd/client" "github.com/canonical/lxd/lxd/auth" @@ -17,6 +18,7 @@ import ( "github.com/canonical/lxd/lxd/config" "github.com/canonical/lxd/lxd/db" instanceDrivers "github.com/canonical/lxd/lxd/instance/drivers" + "github.com/canonical/lxd/lxd/instance/instancetype" "github.com/canonical/lxd/lxd/lifecycle" "github.com/canonical/lxd/lxd/node" "github.com/canonical/lxd/lxd/request" @@ -329,7 +331,18 @@ func api10Get(d *Daemon, r *http.Request) response.Response { } drivers := instanceDrivers.DriverStatuses() - for _, driver := range drivers { + + // Sort drivers map keys in order to produce consistent results. + driverKeys := make([]instancetype.Type, 0, len(drivers)) + for k := range drivers { + driverKeys = append(driverKeys, k) + } + + slices.Sort(driverKeys) + + for _, key := range driverKeys { + driver := drivers[key] + // Only report the supported drivers. if !driver.Supported { continue From 72fdaa1aea5ae168e32f4b694834fd0941b107d1 Mon Sep 17 00:00:00 2001 From: Simon Deziel Date: Tue, 16 Jul 2024 11:34:49 -0400 Subject: [PATCH 49/99] test: use bash and separately set options bash brings interesting features without being an unreasonable minimum requirement. Besides, `make static-analysis` already uses `shellcheck --shell bash`. Signed-off-by: Simon Deziel --- test/lint/auth-up-to-date.sh | 3 ++- test/lint/golangci.sh | 3 ++- test/lint/i18n-up-to-date.sh | 3 ++- test/lint/licenses.sh | 3 ++- test/lint/metadata-up-to-date.sh | 3 ++- test/lint/mixed-whitespace.sh | 3 ++- test/lint/negated-is-bool.sh | 3 ++- test/lint/newline-after-block.sh | 3 ++- test/lint/no-oneline-assign-and-test.sh | 3 ++- test/lint/no-short-form-imports.sh | 3 ++- test/lint/rest-api-up-to-date.sh | 3 ++- test/lint/trailing-space.sh | 3 ++- test/main.sh | 3 ++- test/perf.sh | 3 ++- test/suites/storage_driver_dir.sh | 2 +- 15 files changed, 29 insertions(+), 15 deletions(-) diff --git a/test/lint/auth-up-to-date.sh b/test/lint/auth-up-to-date.sh index 972b23650e82..be13938222f0 100755 --- a/test/lint/auth-up-to-date.sh +++ b/test/lint/auth-up-to-date.sh @@ -1,4 +1,5 @@ -#!/bin/sh -eu +#!/bin/bash +set -eu f="lxd/auth/entitlements_generated.go" diff --git a/test/lint/golangci.sh b/test/lint/golangci.sh index 06df8ff78c28..1b183d69aa55 100755 --- a/test/lint/golangci.sh +++ b/test/lint/golangci.sh @@ -1,4 +1,5 @@ -#!/bin/sh -eu +#!/bin/bash +set -eu # Default target branch. target_branch="main" diff --git a/test/lint/i18n-up-to-date.sh b/test/lint/i18n-up-to-date.sh index 4af412c953d3..d45c8d7cc4da 100755 --- a/test/lint/i18n-up-to-date.sh +++ b/test/lint/i18n-up-to-date.sh @@ -1,4 +1,5 @@ -#!/bin/sh -eu +#!/bin/bash +set -eu safe_pot_hash() { sed -e "/Project-Id-Version/,/Content-Transfer-Encoding/d" -e "/^#/d" "po/lxd.pot" | md5sum | cut -f1 -d" " diff --git a/test/lint/licenses.sh b/test/lint/licenses.sh index e79aed1a31e9..fe06a022d925 100755 --- a/test/lint/licenses.sh +++ b/test/lint/licenses.sh @@ -1,4 +1,5 @@ -#!/bin/sh -eu +#!/bin/bash +set -eu # Check LXD doesn't include non-permissive licenses (except for itself). mv COPYING COPYING.tmp diff --git a/test/lint/metadata-up-to-date.sh b/test/lint/metadata-up-to-date.sh index 700a0552c7da..6744d0d571d9 100755 --- a/test/lint/metadata-up-to-date.sh +++ b/test/lint/metadata-up-to-date.sh @@ -1,4 +1,5 @@ -#!/bin/sh -eu +#!/bin/bash +set -eu hash_before="lxd/metadata-before.txt" hash_after="lxd/metadata-after.txt" diff --git a/test/lint/mixed-whitespace.sh b/test/lint/mixed-whitespace.sh index 8d329561d949..1f950bf32d29 100755 --- a/test/lint/mixed-whitespace.sh +++ b/test/lint/mixed-whitespace.sh @@ -1,4 +1,5 @@ -#!/bin/sh -eu +#!/bin/bash +set -eu echo "Checking for mixed tabs and spaces in shell scripts..." diff --git a/test/lint/negated-is-bool.sh b/test/lint/negated-is-bool.sh index f70d27e1063b..8aeec354966b 100755 --- a/test/lint/negated-is-bool.sh +++ b/test/lint/negated-is-bool.sh @@ -1,4 +1,5 @@ -#!/bin/sh -eu +#!/bin/bash +set -eu echo "Checking usage of negated shared.Is(True|False)*() functions..." diff --git a/test/lint/newline-after-block.sh b/test/lint/newline-after-block.sh index 21972bc50d6c..f37020a79a73 100755 --- a/test/lint/newline-after-block.sh +++ b/test/lint/newline-after-block.sh @@ -1,4 +1,5 @@ -#!/bin/sh -eu +#!/bin/bash +set -eu echo "Checking that functional blocks are followed by newlines..." diff --git a/test/lint/no-oneline-assign-and-test.sh b/test/lint/no-oneline-assign-and-test.sh index c30937d09aec..d180a955a4d2 100755 --- a/test/lint/no-oneline-assign-and-test.sh +++ b/test/lint/no-oneline-assign-and-test.sh @@ -1,4 +1,5 @@ -#!/bin/sh -eu +#!/bin/bash +set -eu echo "Checking for oneline assign & test..." diff --git a/test/lint/no-short-form-imports.sh b/test/lint/no-short-form-imports.sh index 5bf4688d8d8c..9cfbca56f375 100755 --- a/test/lint/no-short-form-imports.sh +++ b/test/lint/no-short-form-imports.sh @@ -1,4 +1,5 @@ -#!/bin/sh -eu +#!/bin/bash +set -eu echo "Checking for short form imports..." diff --git a/test/lint/rest-api-up-to-date.sh b/test/lint/rest-api-up-to-date.sh index f52fd54a5b10..bf3c5874b0e2 100755 --- a/test/lint/rest-api-up-to-date.sh +++ b/test/lint/rest-api-up-to-date.sh @@ -1,4 +1,5 @@ -#!/bin/sh -eu +#!/bin/bash +set -eu f="doc/rest-api.yaml" diff --git a/test/lint/trailing-space.sh b/test/lint/trailing-space.sh index fa44e1c06955..b902fbb645c7 100755 --- a/test/lint/trailing-space.sh +++ b/test/lint/trailing-space.sh @@ -1,4 +1,5 @@ -#!/bin/sh -eu +#!/bin/bash +set -eu echo "Checking that there are no trailing spaces in shell scripts..." diff --git a/test/main.sh b/test/main.sh index 1c205965d27e..9e9f1fc13ac3 100755 --- a/test/main.sh +++ b/test/main.sh @@ -1,4 +1,5 @@ -#!/bin/sh -eu +#!/bin/bash +set -eu [ -n "${GOPATH:-}" ] && export "PATH=${GOPATH}/bin:${PATH}" # Don't translate lxc output for parsing in it in tests. diff --git a/test/perf.sh b/test/perf.sh index 2aaa4764a924..a0511c2a3e04 100755 --- a/test/perf.sh +++ b/test/perf.sh @@ -1,4 +1,5 @@ -#!/bin/sh -eu +#!/bin/bash +set -eu # # Performance tests runner # diff --git a/test/suites/storage_driver_dir.sh b/test/suites/storage_driver_dir.sh index a4a4440ffbdb..a54939aa088f 100644 --- a/test/suites/storage_driver_dir.sh +++ b/test/suites/storage_driver_dir.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash test_storage_driver_dir() { do_dir_on_empty_fs From 4d43dd5fb160b7f5f563d14992159b15f33b77f4 Mon Sep 17 00:00:00 2001 From: Simon Deziel Date: Tue, 16 Jul 2024 12:42:53 -0400 Subject: [PATCH 50/99] Add a shellcheck RC to default to bash flavor Signed-off-by: Simon Deziel --- .shellcheckrc | 2 ++ Makefile | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 .shellcheckrc diff --git a/.shellcheckrc b/.shellcheckrc new file mode 100644 index 000000000000..97d91bae52a7 --- /dev/null +++ b/.shellcheckrc @@ -0,0 +1,2 @@ +# +shell=bash diff --git a/Makefile b/Makefile index 61196f4b53d1..74f0bd41341e 100644 --- a/Makefile +++ b/Makefile @@ -247,8 +247,7 @@ ifeq ($(shell command -v flake8),) exit 1 endif flake8 test/deps/import-busybox - shellcheck --shell bash test/*.sh test/includes/*.sh test/suites/*.sh test/backends/*.sh test/lint/*.sh - shellcheck test/extras/*.sh + shellcheck test/*.sh test/includes/*.sh test/suites/*.sh test/backends/*.sh test/lint/*.sh test/extras/*.sh NOT_EXEC="$(shell find test/lint -type f -not -executable)"; \ if [ -n "$$NOT_EXEC" ]; then \ echo "lint scripts not executable: $$NOT_EXEC"; \ From 74c7e31e57315d2844bc1a14c4daf70904ccbaf1 Mon Sep 17 00:00:00 2001 From: Simon Deziel Date: Tue, 16 Jul 2024 16:51:18 -0400 Subject: [PATCH 51/99] test: stop silencing `local` is not supported by `sh` Those are shellcheck: SC2039 and SC3043 This is supported now that we use `bash`. Change done mechanically with: ``` find test/ -type f -name '*.sh' -exec sed -i '/# shellcheck disable=\(SC\)\?2039,\(SC\)\?3043$/d' {} + ``` With manual fix-up. Signed-off-by: Simon Deziel --- test/backends/btrfs.sh | 3 -- test/backends/ceph.sh | 3 -- test/backends/dir.sh | 3 -- test/backends/lvm.sh | 3 -- test/backends/zfs.sh | 3 -- test/includes/check.sh | 1 - test/includes/clustering.sh | 4 +-- test/includes/lxc.sh | 1 - test/includes/lxd.sh | 7 ---- test/includes/net.sh | 1 - test/includes/setup.sh | 1 - test/includes/storage.sh | 10 ------ test/main.sh | 2 -- test/perf.sh | 2 -- test/suites/basic.sh | 1 - test/suites/clustering.sh | 36 +++---------------- ...clustering_instance_placement_scriptlet.sh | 2 +- test/suites/clustering_move.sh | 2 +- test/suites/config.sh | 5 ++- test/suites/container_devices_disk.sh | 3 -- .../container_local_cross_pool_handling.sh | 1 - test/suites/database.sh | 1 - test/suites/filtering.sh | 1 - test/suites/image.sh | 18 +++++----- test/suites/image_auto_update.sh | 1 - test/suites/image_prefer_cached.sh | 1 - test/suites/incremental_copy.sh | 3 -- test/suites/migration.sh | 4 --- test/suites/projects.sh | 1 - test/suites/remote.sh | 1 - test/suites/security.sh | 1 - test/suites/serverconfig.sh | 1 - test/suites/snapshots.sh | 5 --- test/suites/storage.sh | 3 -- test/suites/storage_buckets.sh | 2 -- test/suites/storage_driver_btrfs.sh | 1 - test/suites/storage_driver_ceph.sh | 1 - test/suites/storage_driver_cephfs.sh | 1 - test/suites/storage_driver_dir.sh | 1 - test/suites/storage_driver_zfs.sh | 3 -- test/suites/storage_local_volume_handling.sh | 1 - test/suites/storage_snapshots.sh | 2 -- test/suites/template.sh | 1 - 43 files changed, 17 insertions(+), 131 deletions(-) diff --git a/test/backends/btrfs.sh b/test/backends/btrfs.sh index bbc40e61a135..829728094034 100644 --- a/test/backends/btrfs.sh +++ b/test/backends/btrfs.sh @@ -1,5 +1,4 @@ btrfs_setup() { - # shellcheck disable=2039,3043 local LXD_DIR LXD_DIR=$1 @@ -8,7 +7,6 @@ btrfs_setup() { } btrfs_configure() { - # shellcheck disable=2039,3043 local LXD_DIR LXD_DIR=$1 @@ -20,7 +18,6 @@ btrfs_configure() { } btrfs_teardown() { - # shellcheck disable=2039,3043 local LXD_DIR LXD_DIR=$1 diff --git a/test/backends/ceph.sh b/test/backends/ceph.sh index 4d5511f33e93..2874693a180c 100644 --- a/test/backends/ceph.sh +++ b/test/backends/ceph.sh @@ -1,5 +1,4 @@ ceph_setup() { - # shellcheck disable=2039,3043 local LXD_DIR LXD_DIR=$1 @@ -8,7 +7,6 @@ ceph_setup() { } ceph_configure() { - # shellcheck disable=2039,3043 local LXD_DIR LXD_DIR=$1 @@ -20,7 +18,6 @@ ceph_configure() { } ceph_teardown() { - # shellcheck disable=2039,3043 local LXD_DIR LXD_DIR=$1 diff --git a/test/backends/dir.sh b/test/backends/dir.sh index 4a8dfffe2568..c0d4a4d21c02 100644 --- a/test/backends/dir.sh +++ b/test/backends/dir.sh @@ -4,7 +4,6 @@ # Any necessary backend-specific setup dir_setup() { - # shellcheck disable=2039,3043 local LXD_DIR LXD_DIR=$1 @@ -14,7 +13,6 @@ dir_setup() { # Do the API voodoo necessary to configure LXD to use this backend dir_configure() { - # shellcheck disable=2039,3043 local LXD_DIR LXD_DIR=$1 @@ -26,7 +24,6 @@ dir_configure() { } dir_teardown() { - # shellcheck disable=2039,3043 local LXD_DIR LXD_DIR=$1 diff --git a/test/backends/lvm.sh b/test/backends/lvm.sh index 9ff4231855c6..3001547ff194 100644 --- a/test/backends/lvm.sh +++ b/test/backends/lvm.sh @@ -1,5 +1,4 @@ lvm_setup() { - # shellcheck disable=2039,3043 local LXD_DIR LXD_DIR=$1 @@ -8,7 +7,6 @@ lvm_setup() { } lvm_configure() { - # shellcheck disable=2039,3043 local LXD_DIR LXD_DIR=$1 @@ -20,7 +18,6 @@ lvm_configure() { } lvm_teardown() { - # shellcheck disable=2039,3043 local LXD_DIR LXD_DIR=$1 diff --git a/test/backends/zfs.sh b/test/backends/zfs.sh index 5e55f0160c13..a9d9c25730df 100644 --- a/test/backends/zfs.sh +++ b/test/backends/zfs.sh @@ -1,5 +1,4 @@ zfs_setup() { - # shellcheck disable=2039,3043 local LXD_DIR LXD_DIR=$1 @@ -8,7 +7,6 @@ zfs_setup() { } zfs_configure() { - # shellcheck disable=2039,3043 local LXD_DIR LXD_DIR=$1 @@ -20,7 +18,6 @@ zfs_configure() { } zfs_teardown() { - # shellcheck disable=2039,3043 local LXD_DIR LXD_DIR=$1 diff --git a/test/includes/check.sh b/test/includes/check.sh index ac3d0b33a7b5..3e2f4d1ce6a0 100644 --- a/test/includes/check.sh +++ b/test/includes/check.sh @@ -1,7 +1,6 @@ # Miscellaneous test checks. check_dependencies() { - # shellcheck disable=SC2039,3043 local dep missing missing="" diff --git a/test/includes/clustering.sh b/test/includes/clustering.sh index cd2fda907d06..15f8a8fad4d8 100644 --- a/test/includes/clustering.sh +++ b/test/includes/clustering.sh @@ -109,7 +109,6 @@ teardown_clustering_netns() { } spawn_lxd_and_bootstrap_cluster() { - # shellcheck disable=SC2039,SC3043 local LXD_NETNS set -e @@ -199,7 +198,6 @@ EOF } spawn_lxd_and_join_cluster() { - # shellcheck disable=SC2039,SC3043 local LXD_NETNS set -e @@ -386,7 +384,7 @@ EOF } respawn_lxd_cluster_member() { - # shellcheck disable=SC2039,SC2034,SC3043 + # shellcheck disable=SC2034 local LXD_NETNS set -e diff --git a/test/includes/lxc.sh b/test/includes/lxc.sh index 3fa0f8c34adb..901b82e68184 100644 --- a/test/includes/lxc.sh +++ b/test/includes/lxc.sh @@ -7,7 +7,6 @@ lxc() { lxc_remote() { set +x - # shellcheck disable=SC2039,3043 local injected cmd arg injected=0 diff --git a/test/includes/lxd.sh b/test/includes/lxd.sh index ea67ee05a745..91f5c7ca459f 100644 --- a/test/includes/lxd.sh +++ b/test/includes/lxd.sh @@ -5,7 +5,6 @@ spawn_lxd() { # LXD_DIR is local here because since $(lxc) is actually a function, it # overwrites the environment and we would lose LXD_DIR's value otherwise. - # shellcheck disable=2039,3043 local LXD_DIR lxddir lxd_backend lxddir=${1} @@ -82,7 +81,6 @@ respawn_lxd() { # LXD_DIR is local here because since $(lxc) is actually a function, it # overwrites the environment and we would lose LXD_DIR's value otherwise. - # shellcheck disable=2039,3043 local LXD_DIR lxddir=${1} @@ -117,7 +115,6 @@ kill_lxd() { # LXD_DIR is local here because since $(lxc) is actually a function, it # overwrites the environment and we would lose LXD_DIR's value otherwise. - # shellcheck disable=2039,3043 local LXD_DIR daemon_dir daemon_pid check_leftovers lxd_backend daemon_dir=${1} @@ -261,7 +258,6 @@ shutdown_lxd() { # LXD_DIR is local here because since $(lxc) is actually a function, it # overwrites the environment and we would lose LXD_DIR's value otherwise. - # shellcheck disable=2039,3043 local LXD_DIR daemon_dir=${1} @@ -279,7 +275,6 @@ shutdown_lxd() { } wait_for() { - # shellcheck disable=SC2039,3043 local addr op addr=${1} @@ -296,7 +291,6 @@ wipe() { fi fi - # shellcheck disable=SC2039,3043 local pid # shellcheck disable=SC2009 ps aux | grep lxc-monitord | grep "${1}" | awk '{print $2}' | while read -r pid; do @@ -312,7 +306,6 @@ wipe() { # Kill and cleanup LXD instances and related resources cleanup_lxds() { - # shellcheck disable=SC2039,3043 local test_dir daemon_dir test_dir="$1" diff --git a/test/includes/net.sh b/test/includes/net.sh index 448655c419fd..4f9ad546297d 100644 --- a/test/includes/net.sh +++ b/test/includes/net.sh @@ -13,7 +13,6 @@ EOF return fi - # shellcheck disable=SC2039,3043 local port pid while true; do diff --git a/test/includes/setup.sh b/test/includes/setup.sh index e3430e5b58c9..83c90d902bb4 100644 --- a/test/includes/setup.sh +++ b/test/includes/setup.sh @@ -1,7 +1,6 @@ # Test setup helper functions. ensure_has_localhost_remote() { - # shellcheck disable=SC2039,3043 local addr="${1}" if ! lxc remote list | grep -q "localhost"; then lxc remote add localhost "https://${addr}" --accept-certificate --password foo diff --git a/test/includes/storage.sh b/test/includes/storage.sh index b768a3eb985c..9cb4fa98e5ab 100644 --- a/test/includes/storage.sh +++ b/test/includes/storage.sh @@ -2,7 +2,6 @@ # Whether a storage backend is available storage_backend_available() { - # shellcheck disable=2039,3043 local backends backends="$(available_storage_backends)" if [ "${backends#*"$1"}" != "$backends" ]; then @@ -36,7 +35,6 @@ storage_backend() { # Return a list of available storage backends available_storage_backends() { - # shellcheck disable=2039,3043 local backend backends storage_backends backends="dir" # always available @@ -56,7 +54,6 @@ available_storage_backends() { } import_storage_backends() { - # shellcheck disable=SC2039,3043 local backend for backend in $(available_storage_backends); do # shellcheck disable=SC1090 @@ -65,7 +62,6 @@ import_storage_backends() { } configure_loop_device() { - # shellcheck disable=SC2039,3043 local lv_loop_file pvloopdev # shellcheck disable=SC2153 @@ -82,17 +78,13 @@ configure_loop_device() { # The following code enables to return a value from a shell function by # calling the function as: fun VAR1 - # shellcheck disable=2039,3043 local __tmp1="${1}" - # shellcheck disable=2039,3043 local res1="${lv_loop_file}" if [ "${__tmp1}" ]; then eval "${__tmp1}='${res1}'" fi - # shellcheck disable=2039,3043 local __tmp2="${2}" - # shellcheck disable=2039,3043 local res2="${pvloopdev}" if [ "${__tmp2}" ]; then eval "${__tmp2}='${res2}'" @@ -100,7 +92,6 @@ configure_loop_device() { } deconfigure_loop_device() { - # shellcheck disable=SC2039,3043 local lv_loop_file loopdev success lv_loop_file="${1}" loopdev="${2}" @@ -129,7 +120,6 @@ deconfigure_loop_device() { } umount_loops() { - # shellcheck disable=SC2039,3043 local line test_dir test_dir="$1" diff --git a/test/main.sh b/test/main.sh index 9e9f1fc13ac3..52209d29c54e 100755 --- a/test/main.sh +++ b/test/main.sh @@ -30,7 +30,6 @@ LXD_NETNS="" import_subdir_files() { test "$1" - # shellcheck disable=SC2039,3043 local file for file in "$1"/*.sh; do # shellcheck disable=SC1090 @@ -159,7 +158,6 @@ run_test() { echo "==> TEST BEGIN: ${TEST_CURRENT_DESCRIPTION}" START_TIME=$(date +%s) - # shellcheck disable=SC2039,3043 local skip=false # Skip test if requested. diff --git a/test/perf.sh b/test/perf.sh index a0511c2a3e04..ee7944874c9e 100755 --- a/test/perf.sh +++ b/test/perf.sh @@ -13,7 +13,6 @@ LXD_NETNS="" import_subdir_files() { test "$1" - # shellcheck disable=SC2039,3043 local file for file in "$1"/*.sh; do # shellcheck disable=SC1090 @@ -28,7 +27,6 @@ log_message() { } run_benchmark() { - # shellcheck disable=SC2039,3043 local label description label="$1" description="$2" diff --git a/test/suites/basic.sh b/test/suites/basic.sh index 5aa9a5c5f4ab..a7c3b22e4f06 100644 --- a/test/suites/basic.sh +++ b/test/suites/basic.sh @@ -1,5 +1,4 @@ test_basic_usage() { - # shellcheck disable=2039,3043 local lxd_backend lxd_backend=$(storage_backend "$LXD_DIR") diff --git a/test/suites/clustering.sh b/test/suites/clustering.sh index d892ab4b89b2..3748e5a08f70 100644 --- a/test/suites/clustering.sh +++ b/test/suites/clustering.sh @@ -1,5 +1,4 @@ test_clustering_enable() { - # shellcheck disable=2039,3043 local LXD_DIR LXD_INIT_DIR=$(mktemp -d -p "${TEST_DIR}" XXX) @@ -179,7 +178,6 @@ test_clustering_enable() { } test_clustering_membership() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -384,7 +382,6 @@ test_clustering_membership() { } test_clustering_containers() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -570,7 +567,6 @@ test_clustering_containers() { } test_clustering_storage() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -977,7 +973,6 @@ test_clustering_storage() { # two-stage process required multi-node clusters, or directly with the normal # procedure for non-clustered daemons. test_clustering_storage_single_node() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -1049,7 +1044,6 @@ test_clustering_storage_single_node() { } test_clustering_network() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -1249,7 +1243,6 @@ test_clustering_network() { # Perform an upgrade of a 2-member cluster, then a join a third member and # perform one more upgrade test_clustering_upgrade() { - # shellcheck disable=2039,3043 local LXD_DIR LXD_NETNS setup_clustering_bridge @@ -1345,7 +1338,6 @@ test_clustering_upgrade() { # Perform an upgrade of an 8-member cluster. test_clustering_upgrade_large() { - # shellcheck disable=2039,3043 local LXD_DIR LXD_NETNS N setup_clustering_bridge @@ -1401,7 +1393,6 @@ test_clustering_upgrade_large() { } test_clustering_publish() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -1453,7 +1444,6 @@ test_clustering_publish() { } test_clustering_profiles() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -1527,7 +1517,6 @@ EOF } test_clustering_update_cert() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -1606,7 +1595,6 @@ test_clustering_update_cert() { } test_clustering_update_cert_reversion() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -1700,7 +1688,7 @@ test_clustering_update_cert_reversion() { } test_clustering_join_api() { - # shellcheck disable=2039,2034,3043 + # shellcheck disable=SC2034 local LXD_DIR LXD_NETNS setup_clustering_bridge @@ -1740,7 +1728,6 @@ test_clustering_join_api() { } test_clustering_shutdown_nodes() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -1812,7 +1799,6 @@ test_clustering_shutdown_nodes() { } test_clustering_projects() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -1877,7 +1863,6 @@ test_clustering_projects() { } test_clustering_address() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -1955,7 +1940,6 @@ test_clustering_address() { } test_clustering_image_replication() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -2118,7 +2102,6 @@ test_clustering_image_replication() { } test_clustering_dns() { - # shellcheck disable=2039,3043 local LXD_DIR # Because we do not want tests to only run on Ubuntu (due to cluster's fan network dependency) @@ -2203,7 +2186,7 @@ test_clustering_dns() { } test_clustering_recover() { - # shellcheck disable=2039,2034,3043 + # shellcheck disable=SC2034 local LXD_DIR setup_clustering_bridge @@ -2285,7 +2268,7 @@ test_clustering_recover() { # When a voter cluster member is shutdown, its role gets transferred to a spare # node. test_clustering_handover() { - # shellcheck disable=2039,2034,3043 + # shellcheck disable=SC2034 local LXD_DIR setup_clustering_bridge @@ -2403,7 +2386,7 @@ test_clustering_handover() { # If a voter node crashes and is detected as offline, its role is migrated to a # stand-by. test_clustering_rebalance() { - # shellcheck disable=2039,2034,3043 + # shellcheck disable=SC2034 local LXD_DIR setup_clustering_bridge @@ -2492,7 +2475,6 @@ test_clustering_rebalance() { # Recover a cluster where a raft node was removed from the nodes table but not # from the raft configuration. test_clustering_remove_raft_node() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -2613,7 +2595,6 @@ test_clustering_remove_raft_node() { } test_clustering_failure_domains() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -2717,7 +2698,6 @@ test_clustering_failure_domains() { } test_clustering_image_refresh() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -2947,7 +2927,6 @@ test_clustering_image_refresh() { } test_clustering_evacuation() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -3096,7 +3075,6 @@ test_clustering_evacuation() { } test_clustering_edit_configuration() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -3245,7 +3223,6 @@ test_clustering_edit_configuration() { } test_clustering_remove_members() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -3383,7 +3360,6 @@ test_clustering_remove_members() { } test_clustering_autotarget() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -3435,7 +3411,6 @@ test_clustering_autotarget() { } test_clustering_groups() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -3624,7 +3599,6 @@ test_clustering_groups() { } test_clustering_events() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -3826,7 +3800,6 @@ test_clustering_events() { } test_clustering_uuid() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge @@ -3883,7 +3856,6 @@ test_clustering_uuid() { } test_clustering_trust_add() { - # shellcheck disable=2039,3043 local LXD_DIR setup_clustering_bridge diff --git a/test/suites/clustering_instance_placement_scriptlet.sh b/test/suites/clustering_instance_placement_scriptlet.sh index 337c7964e254..28fedcaf7101 100644 --- a/test/suites/clustering_instance_placement_scriptlet.sh +++ b/test/suites/clustering_instance_placement_scriptlet.sh @@ -1,5 +1,5 @@ test_clustering_instance_placement_scriptlet() { - # shellcheck disable=2039,3043,SC2034 + # shellcheck disable=SC2034 local LXD_DIR setup_clustering_bridge diff --git a/test/suites/clustering_move.sh b/test/suites/clustering_move.sh index 3ac88099ff8e..7781b2800853 100644 --- a/test/suites/clustering_move.sh +++ b/test/suites/clustering_move.sh @@ -1,5 +1,5 @@ test_clustering_move() { - # shellcheck disable=2039,3043,SC2034 + # shellcheck disable=SC2034 local LXD_DIR setup_clustering_bridge diff --git a/test/suites/config.sh b/test/suites/config.sh index b3fd6251231b..1c98339bdcc4 100644 --- a/test/suites/config.sh +++ b/test/suites/config.sh @@ -325,7 +325,6 @@ test_property() { # Create a storage volume, create a volume snapshot and set its expiration timestamp - # shellcheck disable=2039,3043 local storage_pool storage_pool="lxdtest-$(basename "${LXD_DIR}")" storage_volume="${storage_pool}-vol" @@ -347,8 +346,8 @@ test_property() { } test_config_edit_container_snapshot_pool_config() { - # shellcheck disable=2034,2039,2155,3043 - local storage_pool="lxdtest-$(basename "${LXD_DIR}")" + local storage_pool + storage_pool="lxdtest-$(basename "${LXD_DIR}")" ensure_import_testimage diff --git a/test/suites/container_devices_disk.sh b/test/suites/container_devices_disk.sh index f09343744aab..ab1b89f629de 100644 --- a/test/suites/container_devices_disk.sh +++ b/test/suites/container_devices_disk.sh @@ -15,7 +15,6 @@ test_container_devices_disk() { } _container_devices_disk_shift() { - # shellcheck disable=2039,3043 local lxd_backend lxd_backend=$(storage_backend "$LXD_DIR") @@ -122,7 +121,6 @@ _container_devices_raw_mount_options() { } _container_devices_disk_ceph() { - # shellcheck disable=SC2039,3043 local LXD_BACKEND LXD_BACKEND=$(storage_backend "$LXD_DIR") @@ -147,7 +145,6 @@ _container_devices_disk_ceph() { } _container_devices_disk_cephfs() { - # shellcheck disable=SC2039,3043 local LXD_BACKEND LXD_BACKEND=$(storage_backend "$LXD_DIR") diff --git a/test/suites/container_local_cross_pool_handling.sh b/test/suites/container_local_cross_pool_handling.sh index b0443733f58b..abce8ba0c9a4 100644 --- a/test/suites/container_local_cross_pool_handling.sh +++ b/test/suites/container_local_cross_pool_handling.sh @@ -1,7 +1,6 @@ test_container_local_cross_pool_handling() { ensure_import_testimage - # shellcheck disable=2039,3043 local LXD_STORAGE_DIR lxd_backend lxd_backend=$(storage_backend "$LXD_DIR") LXD_STORAGE_DIR=$(mktemp -d -p "${TEST_DIR}" XXXXXXXXX) diff --git a/test/suites/database.sh b/test/suites/database.sh index 97a9664517a1..21270b4f449a 100644 --- a/test/suites/database.sh +++ b/test/suites/database.sh @@ -43,7 +43,6 @@ EOF } test_database_no_disk_space() { - # shellcheck disable=2039,3043 local LXD_DIR LXD_NOSPACE_DIR=$(mktemp -d -p "${TEST_DIR}" XXX) diff --git a/test/suites/filtering.sh b/test/suites/filtering.sh index ac9560f70155..d011d1993fe5 100644 --- a/test/suites/filtering.sh +++ b/test/suites/filtering.sh @@ -1,6 +1,5 @@ # Test API filtering. test_filtering() { - # shellcheck disable=2039,3043 local LXD_DIR LXD_FILTERING_DIR=$(mktemp -d -p "${TEST_DIR}" XXX) diff --git a/test/suites/image.sh b/test/suites/image.sh index 644d3ccbb998..eb30848f081a 100644 --- a/test/suites/image.sh +++ b/test/suites/image.sh @@ -1,5 +1,4 @@ test_image_expiry() { - # shellcheck disable=2039,3043 local LXD2_DIR LXD2_ADDR LXD2_DIR=$(mktemp -d -p "${TEST_DIR}" XXX) chmod +x "${LXD2_DIR}" @@ -78,8 +77,8 @@ test_image_expiry() { test_image_list_all_aliases() { ensure_import_testimage - # shellcheck disable=2039,2034,2155,3043 - local sum="$(lxc image info testimage | awk '/^Fingerprint/ {print $2}')" + local sum + sum="$(lxc image info testimage | awk '/^Fingerprint/ {print $2}')" lxc image alias create zzz "$sum" lxc image list | grep -vq zzz # both aliases are listed if the "aliases" column is included in output @@ -91,17 +90,17 @@ test_image_list_all_aliases() { test_image_import_dir() { ensure_import_testimage lxc image export testimage - # shellcheck disable=2039,2034,2155,3043 - local image="$(ls -1 -- *.tar.xz)" + local image + image="$(ls -1 -- *.tar.xz)" mkdir -p unpacked tar -C unpacked -xf "$image" - # shellcheck disable=2039,2034,2155,3043 - local fingerprint="$(lxc image import unpacked | awk '{print $NF;}')" + local fingerprint + fingerprint="$(lxc image import unpacked | awk '{print $NF;}')" rm -rf "$image" unpacked lxc image export "$fingerprint" - # shellcheck disable=2039,2034,2155,3043 - local exported="${fingerprint}.tar.xz" + local exported + exported="${fingerprint}.tar.xz" tar tvf "$exported" | grep -Fq metadata.yaml rm "$exported" @@ -122,7 +121,6 @@ test_image_import_existing_alias() { } test_image_refresh() { - # shellcheck disable=2039,3043 local LXD2_DIR LXD2_ADDR LXD2_DIR=$(mktemp -d -p "${TEST_DIR}" XXX) chmod +x "${LXD2_DIR}" diff --git a/test/suites/image_auto_update.sh b/test/suites/image_auto_update.sh index e5c29b8af1ec..92e6d0aaf7ae 100644 --- a/test/suites/image_auto_update.sh +++ b/test/suites/image_auto_update.sh @@ -3,7 +3,6 @@ test_image_auto_update() { lxc image delete testimage fi - # shellcheck disable=2039,3043 local LXD2_DIR LXD2_ADDR LXD2_DIR=$(mktemp -d -p "${TEST_DIR}" XXX) chmod +x "${LXD2_DIR}" diff --git a/test/suites/image_prefer_cached.sh b/test/suites/image_prefer_cached.sh index 90c1fe090b94..126f4326c1f0 100644 --- a/test/suites/image_prefer_cached.sh +++ b/test/suites/image_prefer_cached.sh @@ -6,7 +6,6 @@ test_image_prefer_cached() { lxc image delete testimage fi - # shellcheck disable=2039,3043 local LXD2_DIR LXD2_ADDR LXD2_DIR=$(mktemp -d -p "${TEST_DIR}" XXX) chmod +x "${LXD2_DIR}" diff --git a/test/suites/incremental_copy.sh b/test/suites/incremental_copy.sh index 7a654412c1c5..fd3836b5c380 100644 --- a/test/suites/incremental_copy.sh +++ b/test/suites/incremental_copy.sh @@ -5,7 +5,6 @@ test_incremental_copy() { do_copy "" "" # cross-pool copy - # shellcheck disable=2039,3043 local source_pool source_pool="lxdtest-$(basename "${LXD_DIR}")-dir-pool" lxc storage create "${source_pool}" dir @@ -14,9 +13,7 @@ test_incremental_copy() { } do_copy() { - # shellcheck disable=2039,3043 local source_pool="${1}" - # shellcheck disable=2039,3043 local target_pool="${2}" # Make sure the containers don't exist diff --git a/test/suites/migration.sh b/test/suites/migration.sh index 9c8bd74f5386..96e271ff3a73 100644 --- a/test/suites/migration.sh +++ b/test/suites/migration.sh @@ -1,6 +1,5 @@ test_migration() { # setup a second LXD - # shellcheck disable=2039,3043 local LXD2_DIR LXD2_ADDR lxd_backend # shellcheck disable=2153 lxd_backend=$(storage_backend "$LXD_DIR") @@ -26,7 +25,6 @@ test_migration() { if [ "${LXD_BACKEND}" = "lvm" ]; then # Test that non-thinpool lvm backends work fine with migration. - # shellcheck disable=2039,3043 local storage_pool1 storage_pool2 # shellcheck disable=2153 storage_pool1="lxdtest-$(basename "${LXD_DIR}")-non-thinpool-lvm-migration" @@ -54,7 +52,6 @@ test_migration() { continue fi - # shellcheck disable=2039,3043 local storage_pool1 storage_pool2 # shellcheck disable=2153 storage_pool1="lxdtest-$(basename "${LXD_DIR}")-block-mode" @@ -81,7 +78,6 @@ test_migration() { } migration() { - # shellcheck disable=2039,3043 local lxd2_dir lxd_backend lxd2_backend lxd2_dir="$1" lxd_backend=$(storage_backend "$LXD_DIR") diff --git a/test/suites/projects.sh b/test/suites/projects.sh index c7b211d65790..2460ba14e280 100644 --- a/test/suites/projects.sh +++ b/test/suites/projects.sh @@ -741,7 +741,6 @@ test_projects_limits() { # too small for resize2fs. if [ "${LXD_BACKEND}" = "dir" ] || [ "${LXD_BACKEND}" = "zfs" ]; then # Add a remote LXD to be used as image server. - # shellcheck disable=2039,3043 local LXD_REMOTE_DIR LXD_REMOTE_DIR=$(mktemp -d -p "${TEST_DIR}" XXX) chmod +x "${LXD_REMOTE_DIR}" diff --git a/test/suites/remote.sh b/test/suites/remote.sh index 5240a5405a76..805b69987612 100644 --- a/test/suites/remote.sh +++ b/test/suites/remote.sh @@ -194,7 +194,6 @@ test_remote_admin() { } test_remote_usage() { - # shellcheck disable=2039,3043 local LXD2_DIR LXD2_ADDR LXD2_DIR=$(mktemp -d -p "${TEST_DIR}" XXX) chmod +x "${LXD2_DIR}" diff --git a/test/suites/security.sh b/test/suites/security.sh index 7829f24bd817..3face753ede9 100644 --- a/test/suites/security.sh +++ b/test/suites/security.sh @@ -87,7 +87,6 @@ test_security() { lxc delete test-unpriv --force - # shellcheck disable=2039,3043 local LXD_STORAGE_DIR LXD_STORAGE_DIR=$(mktemp -d -p "${TEST_DIR}" XXXXXXXXX) diff --git a/test/suites/serverconfig.sh b/test/suites/serverconfig.sh index e36b5876ff89..161b879bc851 100644 --- a/test/suites/serverconfig.sh +++ b/test/suites/serverconfig.sh @@ -33,7 +33,6 @@ _server_config_access() { } _server_config_storage() { - # shellcheck disable=2039,3043 local lxd_backend lxd_backend=$(storage_backend "$LXD_DIR") diff --git a/test/suites/snapshots.sh b/test/suites/snapshots.sh index 0f3ff0686f18..9056f198f7db 100644 --- a/test/suites/snapshots.sh +++ b/test/suites/snapshots.sh @@ -17,7 +17,6 @@ test_snapshots() { } snapshots() { - # shellcheck disable=2039,3043 local lxd_backend lxd_backend=$(storage_backend "$LXD_DIR") pool="$1" @@ -126,7 +125,6 @@ test_snap_restore() { } snap_restore() { - # shellcheck disable=2039,3043 local lxd_backend lxd_backend=$(storage_backend "$LXD_DIR") pool="$1" @@ -343,7 +341,6 @@ restore_and_compare_fs() { } test_snap_expiry() { - # shellcheck disable=2039,3043 local lxd_backend lxd_backend=$(storage_backend "$LXD_DIR") @@ -369,7 +366,6 @@ test_snap_expiry() { } test_snap_schedule() { - # shellcheck disable=2039,3043 local lxd_backend lxd_backend=$(storage_backend "$LXD_DIR") @@ -396,7 +392,6 @@ test_snap_schedule() { } test_snap_volume_db_recovery() { - # shellcheck disable=2039,3043 local lxd_backend lxd_backend=$(storage_backend "$LXD_DIR") diff --git a/test/suites/storage.sh b/test/suites/storage.sh index 5b60ae841a07..2788dfe118c9 100644 --- a/test/suites/storage.sh +++ b/test/suites/storage.sh @@ -1,7 +1,6 @@ test_storage() { ensure_import_testimage - # shellcheck disable=2039,3043 local LXD_STORAGE_DIR lxd_backend lxd_backend=$(storage_backend "$LXD_DIR") @@ -10,7 +9,6 @@ test_storage() { spawn_lxd "${LXD_STORAGE_DIR}" false # edit storage and pool description - # shellcheck disable=2039,3043 local storage_pool storage_volume storage_pool="lxdtest-$(basename "${LXD_DIR}")-pool" storage_volume="${storage_pool}-vol" @@ -49,7 +47,6 @@ test_storage() { # Test btrfs resize if [ "$lxd_backend" = "lvm" ] || [ "$lxd_backend" = "ceph" ]; then - # shellcheck disable=2039,3043 local btrfs_storage_pool btrfs_storage_volume btrfs_storage_pool="lxdtest-$(basename "${LXD_DIR}")-pool-btrfs" btrfs_storage_volume="${storage_pool}-vol" diff --git a/test/suites/storage_buckets.sh b/test/suites/storage_buckets.sh index 2ed9daca89c1..483bae4661c2 100644 --- a/test/suites/storage_buckets.sh +++ b/test/suites/storage_buckets.sh @@ -1,5 +1,4 @@ s3cmdrun () { - # shellcheck disable=2039,3043 local backend accessKey secreyKey backend="${1}" accessKey="${2}" @@ -27,7 +26,6 @@ s3cmdrun () { } test_storage_buckets() { - # shellcheck disable=2039,3043 local lxd_backend lxd_backend=$(storage_backend "$LXD_DIR") diff --git a/test/suites/storage_driver_btrfs.sh b/test/suites/storage_driver_btrfs.sh index 935be125f487..cbe670bea06a 100644 --- a/test/suites/storage_driver_btrfs.sh +++ b/test/suites/storage_driver_btrfs.sh @@ -1,5 +1,4 @@ test_storage_driver_btrfs() { - # shellcheck disable=2039,3043 local LXD_STORAGE_DIR lxd_backend lxd_backend=$(storage_backend "$LXD_DIR") diff --git a/test/suites/storage_driver_ceph.sh b/test/suites/storage_driver_ceph.sh index da7394230755..8825f4d38743 100644 --- a/test/suites/storage_driver_ceph.sh +++ b/test/suites/storage_driver_ceph.sh @@ -1,5 +1,4 @@ test_storage_driver_ceph() { - # shellcheck disable=2039,3043 local LXD_STORAGE_DIR lxd_backend lxd_backend=$(storage_backend "$LXD_DIR") diff --git a/test/suites/storage_driver_cephfs.sh b/test/suites/storage_driver_cephfs.sh index e7468aed4577..29d0418779bc 100644 --- a/test/suites/storage_driver_cephfs.sh +++ b/test/suites/storage_driver_cephfs.sh @@ -1,5 +1,4 @@ test_storage_driver_cephfs() { - # shellcheck disable=2039,3043 local lxd_backend lxd_backend=$(storage_backend "$LXD_DIR") diff --git a/test/suites/storage_driver_dir.sh b/test/suites/storage_driver_dir.sh index a54939aa088f..2a61bc3c9f9a 100644 --- a/test/suites/storage_driver_dir.sh +++ b/test/suites/storage_driver_dir.sh @@ -5,7 +5,6 @@ test_storage_driver_dir() { } do_dir_on_empty_fs() { - # shellcheck disable=2039,3043 local lxd_backend lxd_backend=$(storage_backend "${LXD_DIR}") diff --git a/test/suites/storage_driver_zfs.sh b/test/suites/storage_driver_zfs.sh index cef162a1ba83..b8357ba8a616 100644 --- a/test/suites/storage_driver_zfs.sh +++ b/test/suites/storage_driver_zfs.sh @@ -8,7 +8,6 @@ test_storage_driver_zfs() { } do_zfs_delegate() { - # shellcheck disable=2039,3043 local lxd_backend lxd_backend=$(storage_backend "$LXD_DIR") @@ -46,7 +45,6 @@ do_zfs_delegate() { } do_zfs_cross_pool_copy() { - # shellcheck disable=2039,3043 local LXD_STORAGE_DIR lxd_backend lxd_backend=$(storage_backend "$LXD_DIR") @@ -118,7 +116,6 @@ do_storage_driver_zfs() { return fi - # shellcheck disable=2039,3043 local LXD_STORAGE_DIR lxd_backend lxd_backend=$(storage_backend "$LXD_DIR") diff --git a/test/suites/storage_local_volume_handling.sh b/test/suites/storage_local_volume_handling.sh index b3012f88c01d..5631ebd518f8 100755 --- a/test/suites/storage_local_volume_handling.sh +++ b/test/suites/storage_local_volume_handling.sh @@ -1,7 +1,6 @@ test_storage_local_volume_handling() { ensure_import_testimage - # shellcheck disable=2039,3043 local LXD_STORAGE_DIR lxd_backend lxd_backend=$(storage_backend "$LXD_DIR") LXD_STORAGE_DIR=$(mktemp -d -p "${TEST_DIR}" XXXXXXXXX) diff --git a/test/suites/storage_snapshots.sh b/test/suites/storage_snapshots.sh index adcacbe51a9b..5446ee861a35 100644 --- a/test/suites/storage_snapshots.sh +++ b/test/suites/storage_snapshots.sh @@ -1,7 +1,6 @@ test_storage_volume_snapshots() { ensure_import_testimage - # shellcheck disable=2039,3043 local LXD_STORAGE_DIR lxd_backend lxd_backend=$(storage_backend "$LXD_DIR") @@ -10,7 +9,6 @@ test_storage_volume_snapshots() { spawn_lxd "${LXD_STORAGE_DIR}" false lxc remote add test "${LXD_ADDR}" --accept-certificate --password foo - # shellcheck disable=2039,3043 local storage_pool storage_volume storage_pool="lxdtest-$(basename "${LXD_STORAGE_DIR}")-pool" storage_pool2="${storage_pool}2" diff --git a/test/suites/template.sh b/test/suites/template.sh index 490bf9ec199a..1fdb20971a3d 100644 --- a/test/suites/template.sh +++ b/test/suites/template.sh @@ -1,5 +1,4 @@ test_template() { - # shellcheck disable=2039,3043 local lxd_backend lxd_backend=$(storage_backend "$LXD_DIR") From d04f2de3a27c418b711c30d669f74634686e8ad2 Mon Sep 17 00:00:00 2001 From: Simon Deziel Date: Tue, 16 Jul 2024 17:34:28 -0400 Subject: [PATCH 52/99] test/suites/security: avoid reusing standard UID shell variable Signed-off-by: Simon Deziel --- test/suites/security.sh | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/suites/security.sh b/test/suites/security.sh index 3face753ede9..7a09a46e8401 100644 --- a/test/suites/security.sh +++ b/test/suites/security.sh @@ -24,14 +24,14 @@ test_security() { lxc launch testimage test-priv -c security.privileged=true PERM=$(stat -L -c %a "${LXD_DIR}/containers/test-priv") - UID=$(stat -L -c %u "${LXD_DIR}/containers/test-priv") + FUID=$(stat -L -c %u "${LXD_DIR}/containers/test-priv") if [ "${PERM}" != "100" ]; then echo "Bad container permissions: ${PERM}" false fi - if [ "${UID}" != "0" ]; then - echo "Bad container owner: ${UID}" + if [ "${FUID}" != "0" ]; then + echo "Bad container owner: ${FUID}" false fi @@ -41,14 +41,14 @@ test_security() { lxc restart test-priv --force PERM=$(stat -L -c %a "${LXD_DIR}/containers/test-priv") - UID=$(stat -L -c %u "${LXD_DIR}/containers/test-priv") + FUID=$(stat -L -c %u "${LXD_DIR}/containers/test-priv") if [ "${PERM}" != "100" ]; then echo "Bad container permissions: ${PERM}" false fi - if [ "${UID}" != "0" ]; then - echo "Bad container owner: ${UID}" + if [ "${FUID}" != "0" ]; then + echo "Bad container owner: ${FUID}" false fi @@ -59,14 +59,14 @@ test_security() { lxc restart test-unpriv --force PERM=$(stat -L -c %a "${LXD_DIR}/containers/test-unpriv") - UID=$(stat -L -c %u "${LXD_DIR}/containers/test-unpriv") + FUID=$(stat -L -c %u "${LXD_DIR}/containers/test-unpriv") if [ "${PERM}" != "100" ]; then echo "Bad container permissions: ${PERM}" false fi - if [ "${UID}" != "0" ]; then - echo "Bad container owner: ${UID}" + if [ "${FUID}" != "0" ]; then + echo "Bad container owner: ${FUID}" false fi @@ -74,14 +74,14 @@ test_security() { lxc restart test-unpriv --force PERM=$(stat -L -c %a "${LXD_DIR}/containers/test-unpriv") - UID=$(stat -L -c %u "${LXD_DIR}/containers/test-unpriv") + FUID=$(stat -L -c %u "${LXD_DIR}/containers/test-unpriv") if [ "${PERM}" != "100" ]; then echo "Bad container permissions: ${PERM}" false fi - if [ "${UID}" = "0" ]; then - echo "Bad container owner: ${UID}" + if [ "${FUID}" = "0" ]; then + echo "Bad container owner: ${FUID}" false fi From f71835dd3e215b34cd22bdec253f23fc236d68a4 Mon Sep 17 00:00:00 2001 From: Simon Deziel Date: Tue, 16 Jul 2024 21:44:04 -0400 Subject: [PATCH 53/99] test/suites/clustering: fix variable shadowing/reuse/uninit Signed-off-by: Simon Deziel --- test/suites/clustering.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/suites/clustering.sh b/test/suites/clustering.sh index 3748e5a08f70..b780f6d46f7e 100644 --- a/test/suites/clustering.sh +++ b/test/suites/clustering.sh @@ -2102,11 +2102,12 @@ test_clustering_image_replication() { } test_clustering_dns() { - local LXD_DIR + local lxdDir # Because we do not want tests to only run on Ubuntu (due to cluster's fan network dependency) # instead we will just spawn forkdns directly and check DNS resolution. + # XXX: make a copy of the global LXD_DIR # shellcheck disable=SC2031 lxdDir="${LXD_DIR}" prefix="lxd$$" From e3ebe419b40a751094bdb39051901fa1b86e462e Mon Sep 17 00:00:00 2001 From: Ruth Fuchss Date: Tue, 16 Jul 2024 15:05:31 +0200 Subject: [PATCH 54/99] doc/authentication: clean up PKI instructions Move the information from the note about using PKI with ACME to the main instructions, and update the docs to cover a cluster setup as well. Signed-off-by: Ruth Fuchss --- doc/authentication.md | 51 ++++++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/doc/authentication.md b/doc/authentication.md index e5f8e768a955..6e6045ce4f4a 100644 --- a/doc/authentication.md +++ b/doc/authentication.md @@ -114,26 +114,59 @@ Alternatively, the clients can provide the token directly when adding the remote In a {abbr}`PKI (Public key infrastructure)` setup, a system administrator manages a central PKI that issues client certificates for all the LXD clients and server certificates for all the LXD daemons. -In PKI mode, TLS authentication requires that client certificates are signed be the CA. +In PKI mode, TLS authentication requires that client certificates are signed be the {abbr}`CA (Certificate authority)`. This requirement does not apply to clients that authenticate via [OIDC](authentication-openid). -To enable PKI mode, complete the following steps: +The steps for enabling PKI mode differ slightly depending on whether you use an ACME provider in addition (see {ref}`authentication-server-certificate`). -1. Add the {abbr}`CA (Certificate authority)` certificate to all machines: +`````{tabs} +````{group-tab} Only PKI +If you use a PKI system, both the server certificates and the client certificates are issued by a secondary CA. + +1. Add the CA certificate to all machines: - Place the `client.ca` file in the clients' configuration directories (`~/.config/lxc` or `~/snap/lxd/common/config` for snap users). - Place the `server.ca` file in the server's configuration directory (`/var/lib/lxd` or `/var/snap/lxd/common/lxd` for snap users). + + ```{note} + In a cluster setup, the CA certificate must be named `cluster.ca`, and the same file must be added to all cluster members. + ``` + 1. Place the certificates issued by the CA in the clients' configuration directories, replacing the automatically generated `client.crt` and `client.key` files. 1. If you want clients to automatically trust the server, place the certificates issued by the CA in the server's configuration directory, replacing the automatically generated `server.crt` and `server.key` files. - When a client adds a PKI-enabled server as a remote, it checks the server certificate and prompts the user to trust the server certificate only if the certificate has not been signed by the CA. + ```{note} + In a cluster setup, the certificate files must be named `cluster.crt` and `cluster.key`. + They must be identical on all cluster members. + ``` + + When a client adds a PKI-enabled server or cluster as a remote, it checks the server certificate and prompts the user to trust the server certificate only if the certificate has not been signed by the CA. 1. Restart the LXD daemon. +```` +````{group-tab} PKI and ACME +If you use a PKI system alongside an ACME provider, the server certificates are issued by the ACME provider, and the client certificates are issued by a secondary CA. -Note that CA-signed client certificates are not automatically trusted. +1. Place the CA certificate for the server (`server.ca`) in the server's configuration directory (`/var/lib/lxd` or `/var/snap/lxd/common/lxd` for snap users), so that the server can authenticate the clients. + + ```{note} + In a cluster setup, the CA certificate must be named `cluster.ca`, and the same file must be added to all cluster members. + ``` + +1. Place the certificates issued by the CA in the clients' configuration directories, replacing the automatically generated `client.crt` and `client.key` files. +1. Restart the LXD daemon. + +```` +````` + +#### Trusting certificates + +CA-signed client certificates are not automatically trusted. You must still add them to the server in one of the ways described in {ref}`authentication-trusted-clients`. To automatically trust CA-signed client certificates, set the {config:option}`server-core:core.trust_ca_certificates` server configuration to true. When `core.trust_ca_certificates` is enabled, any new clients with a CA-signed certificate will have full access to LXD. +#### Revoking certificates + To revoke certificates via the PKI, place a certificate revocation list in the server's configuration directory as `ca.crl` and restart the LXD daemon. A client with a CA-signed certificate that has been revoked, and is present in `ca.crl`, will not be able to authenticate with LXD, nor add LXD as a remote via [mutual TLS](authentication-trusted-clients). @@ -172,14 +205,6 @@ This can be achieved by using a reverse proxy such as [HAProxy](http://www.hapro Here's a minimal HAProxy configuration that uses `lxd.example.net` as the domain. After the certificate has been issued, LXD will be reachable from `https://lxd.example.net/`. -```{note} -A [PKI system](authentication-pki) can be used alongside an ACME provider to verify client certificates. -In this case, a secondary CA issues certificates to clients. -A `server.ca` and `ca.crl` file should still be added to the server's configuration directory, but server certificates must not be issued by the secondary CA, as they are managed by the ACME provider. -Client certificates issued by the secondary CA should be added to the clients' configuration directories, but the `client.ca` file *must not* be added to the clients' configuration directories. -This is because the server certificates were issued by the ACME provider and not the secondary CA. -``` - ``` # Global configuration global From ed9a46423ea80fc507fa03784073c407a268cdaa Mon Sep 17 00:00:00 2001 From: Thomas Parrott Date: Thu, 18 Jul 2024 08:47:41 +0100 Subject: [PATCH 55/99] lxd: Standardise on "err" field in contextual logging for error Signed-off-by: Thomas Parrott --- lxd/devlxd.go | 2 +- lxd/util/http.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lxd/devlxd.go b/lxd/devlxd.go index 45322a2b2196..5940d729cbb6 100644 --- a/lxd/devlxd.go +++ b/lxd/devlxd.go @@ -407,7 +407,7 @@ func (m *ConnPidMapper) ConnStateHandler(conn net.Conn, state http.ConnState) { case http.StateNew: cred, err := ucred.GetCred(unixConn) if err != nil { - logger.Debug("Error getting ucred for devlxd connection", logger.Ctx{"error": err}) + logger.Debug("Error getting ucred for devlxd connection", logger.Ctx{"err": err}) } else { m.mLock.Lock() m.m[unixConn] = cred diff --git a/lxd/util/http.go b/lxd/util/http.go index b5ded988a21f..5b33f04907cf 100644 --- a/lxd/util/http.go +++ b/lxd/util/http.go @@ -200,7 +200,7 @@ func CheckCASignature(cert x509.Certificate, networkCert *shared.CertInfo) (trus err = crl.CheckSignatureFrom(ca) if err != nil { - logger.Error("Certificate revokation list has not been signed by server CA", logger.Ctx{"error": err}) + logger.Error("Certificate revokation list has not been signed by server CA", logger.Ctx{"err": err}) return false, false, "" } From edf5d35cd31fb7fd303481491b6d4bd42074a153 Mon Sep 17 00:00:00 2001 From: Din Music Date: Thu, 18 Jul 2024 07:46:48 +0000 Subject: [PATCH 56/99] lxd/migrate_storage_volume: Add comments to public functions Signed-off-by: Din Music --- lxd/migrate_storage_volumes.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lxd/migrate_storage_volumes.go b/lxd/migrate_storage_volumes.go index 918e83b88aeb..f09316ddf45d 100644 --- a/lxd/migrate_storage_volumes.go +++ b/lxd/migrate_storage_volumes.go @@ -65,6 +65,9 @@ func newStorageMigrationSource(volumeOnly bool, pushTarget *api.StorageVolumePos return &ret, nil } +// DoStorage handles the migration of a storage volume from the source to the target. +// It waits for migration connections, negotiates migration types, and initiates +// the volume transfer. func (s *migrationSourceWs) DoStorage(state *state.State, projectName string, poolName string, volName string, migrateOp *operations.Operation) error { l := logger.AddContext(logger.Ctx{"project": projectName, "pool": poolName, "volume": volName, "push": s.pushOperationURL != ""}) @@ -238,6 +241,8 @@ func newStorageMigrationSink(args *migrationSinkArgs) (*migrationSink, error) { return &sink, nil } +// DoStorage handles the storage volume migration on the target side. It waits for +// migration connections, negotiates migration types, and initiates the volume reception. func (c *migrationSink) DoStorage(state *state.State, projectName string, poolName string, req *api.StorageVolumesPost, op *operations.Operation) error { l := logger.AddContext(logger.Ctx{"project": projectName, "pool": poolName, "volume": req.Name, "push": c.push}) From c1556de20ed4404abbc01614be946500ba1b8265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Tue, 4 Jun 2024 10:54:17 -0400 Subject: [PATCH 57/99] internal/linux: Define some IOCTLs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber (cherry picked from commit 4220910b7fbabbdf836fa10f3a300d6959217806) Signed-off-by: Thomas Parrott License: Apache-2.0 --- lxd/linux/ioctls.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 lxd/linux/ioctls.go diff --git a/lxd/linux/ioctls.go b/lxd/linux/ioctls.go new file mode 100644 index 000000000000..4e10a5945584 --- /dev/null +++ b/lxd/linux/ioctls.go @@ -0,0 +1,17 @@ +package linux + +/* +#include +#include +#include +*/ +import "C" + +// IoctlBtrfsSetReceivedSubvol is used to set information about a received subvolume. +const IoctlBtrfsSetReceivedSubvol = C.BTRFS_IOC_SET_RECEIVED_SUBVOL + +// IoctlHIDIOCGrawInfo contains the bus type, the vendor ID (VID), and product ID (PID) of the device. +const IoctlHIDIOCGrawInfo = C.HIDIOCGRAWINFO + +// IoctlVhostVsockSetGuestCid is used to set the vsock guest context ID. +const IoctlVhostVsockSetGuestCid = C.VHOST_VSOCK_SET_GUEST_CID From 42c218dd73ad72d78c0d2d28659e902cf6f5e4ca Mon Sep 17 00:00:00 2001 From: Thomas Parrott Date: Thu, 18 Jul 2024 08:55:12 +0100 Subject: [PATCH 58/99] Revert "lxd/instance/drivers/driver_qemu: properly calculate VHOST_VSOCK_SET_GUEST_CID" This reverts commit ecc020159adb880112826d0c54fefd45a72dad59. Signed-off-by: Thomas Parrott --- lxd/instance/drivers/driver_qemu.go | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index e3f587a3a123..f7cc0b6c071c 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -1,17 +1,5 @@ package drivers -/* - -#include -#include -#include - -#define VHOST_VIRTIO 0xAF -#define VHOST_VSOCK_SET_GUEST_CID _IOW(VHOST_VIRTIO, 0x60, __u64) - -*/ -import "C" - import ( "bufio" "bytes" @@ -8087,7 +8075,8 @@ func (d *qemu) acquireVsockID(vsockID uint32) (*os.File, error) { // The vsock Context ID cannot be supplied as type uint32. vsockIDInt := uint64(vsockID) - _, _, errno := unix.Syscall(unix.SYS_IOCTL, vsockF.Fd(), C.VHOST_VSOCK_SET_GUEST_CID, uintptr(unsafe.Pointer(&vsockIDInt))) + // 0x4008AF60 = VHOST_VSOCK_SET_GUEST_CID = _IOW(VHOST_VIRTIO, 0x60, __u64) + _, _, errno := unix.Syscall(unix.SYS_IOCTL, vsockF.Fd(), 0x4008AF60, uintptr(unsafe.Pointer(&vsockIDInt))) if errno != 0 { if !errors.Is(errno, unix.EADDRINUSE) { return nil, fmt.Errorf("Failed ioctl syscall to vhost socket: %q", errno.Error()) From 844c52b8bfb65941c9109531e0ebcf0566bcf63c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Tue, 4 Jun 2024 10:54:38 -0400 Subject: [PATCH 59/99] incusd/instance/qemu: Don't use hardcoded ioctl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber (cherry picked from commit f3f18d80e8f037754f2eb3658b6394aec013bda9) Signed-off-by: Thomas Parrott License: Apache-2.0 --- lxd/instance/drivers/driver_qemu.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index f7cc0b6c071c..019b0bbb4e60 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -8075,8 +8075,8 @@ func (d *qemu) acquireVsockID(vsockID uint32) (*os.File, error) { // The vsock Context ID cannot be supplied as type uint32. vsockIDInt := uint64(vsockID) - // 0x4008AF60 = VHOST_VSOCK_SET_GUEST_CID = _IOW(VHOST_VIRTIO, 0x60, __u64) - _, _, errno := unix.Syscall(unix.SYS_IOCTL, vsockF.Fd(), 0x4008AF60, uintptr(unsafe.Pointer(&vsockIDInt))) + // Call the ioctl to set the context ID. + _, _, errno := unix.Syscall(unix.SYS_IOCTL, vsockF.Fd(), linux.IoctlVhostVsockSetGuestCid, uintptr(unsafe.Pointer(&vsockIDInt))) if errno != 0 { if !errors.Is(errno, unix.EADDRINUSE) { return nil, fmt.Errorf("Failed ioctl syscall to vhost socket: %q", errno.Error()) From 89c0b4699a187e5c6e7c4f1bcc702547ab3e989a Mon Sep 17 00:00:00 2001 From: Thomas Parrott Date: Thu, 18 Jul 2024 08:58:48 +0100 Subject: [PATCH 60/99] Revert "storage/drivers/driver_btrfs_utils: properly calculate BTRFS_IOC_SET_RECEIVED_SUBVOL" This reverts commit 8e4d7cb1baf39ee5ad7dc0e40967d5959ef1b13a. Signed-off-by: Thomas Parrott --- lxd/storage/drivers/driver_btrfs_utils.go | 35 ++--------------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/lxd/storage/drivers/driver_btrfs_utils.go b/lxd/storage/drivers/driver_btrfs_utils.go index 018e812d52fc..7d57f2fdf33a 100644 --- a/lxd/storage/drivers/driver_btrfs_utils.go +++ b/lxd/storage/drivers/driver_btrfs_utils.go @@ -1,37 +1,5 @@ package drivers -/* - -#include -#include -#include - -// definitions are borrowed from include/uapi/linux/btrfs.h - -#define BTRFS_IOCTL_MAGIC 0x94 -#define BTRFS_UUID_SIZE 16 - -struct btrfs_ioctl_timespec { - __u64 sec; - __u32 nsec; -}; - -struct btrfs_ioctl_received_subvol_args { - char uuid[BTRFS_UUID_SIZE]; - __u64 stransid; - __u64 rtransid; - struct btrfs_ioctl_timespec stime; - struct btrfs_ioctl_timespec rtime; - __u64 flags; - __u64 reserved[16]; -}; - -#define BTRFS_IOC_SET_RECEIVED_SUBVOL _IOWR(BTRFS_IOCTL_MAGIC, 37, \ - struct btrfs_ioctl_received_subvol_args) - -*/ -import "C" - import ( "bufio" "bytes" @@ -93,7 +61,8 @@ func setReceivedUUID(path string, UUID string) error { copy(args.uuid[:], binUUID) - _, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(), C.BTRFS_IOC_SET_RECEIVED_SUBVOL, uintptr(unsafe.Pointer(&args))) + // 0xC0C09425 = _IOWR(BTRFS_IOCTL_MAGIC, 37, struct btrfs_ioctl_received_subvol_args) + _, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(), 0xC0C09425, uintptr(unsafe.Pointer(&args))) if errno != 0 { return fmt.Errorf("Failed setting received UUID: %w", unix.Errno(errno)) } From 7f48e3134f6adffac5956f5c38c846e4a5fa63d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Tue, 4 Jun 2024 10:54:50 -0400 Subject: [PATCH 61/99] incusd/storage/btrfs: Don't use hardcoded ioctl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber (cherry picked from commit 58d21750f64965415af8ceb526f9b2b0e5cce04e) Signed-off-by: Thomas Parrott License: Apache-2.0 --- lxd/instance/drivers/driver_qemu.go | 1 + lxd/storage/drivers/driver_btrfs_utils.go | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index 019b0bbb4e60..27d2c08d0799 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -53,6 +53,7 @@ import ( "github.com/canonical/lxd/lxd/instance/operationlock" "github.com/canonical/lxd/lxd/instancewriter" "github.com/canonical/lxd/lxd/lifecycle" + "github.com/canonical/lxd/lxd/linux" "github.com/canonical/lxd/lxd/metrics" "github.com/canonical/lxd/lxd/migration" "github.com/canonical/lxd/lxd/network" diff --git a/lxd/storage/drivers/driver_btrfs_utils.go b/lxd/storage/drivers/driver_btrfs_utils.go index 7d57f2fdf33a..2b91af112b8d 100644 --- a/lxd/storage/drivers/driver_btrfs_utils.go +++ b/lxd/storage/drivers/driver_btrfs_utils.go @@ -19,6 +19,7 @@ import ( "gopkg.in/yaml.v2" "github.com/canonical/lxd/lxd/backup" + "github.com/canonical/lxd/lxd/linux" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" "github.com/canonical/lxd/shared/ioprogress" @@ -61,8 +62,7 @@ func setReceivedUUID(path string, UUID string) error { copy(args.uuid[:], binUUID) - // 0xC0C09425 = _IOWR(BTRFS_IOCTL_MAGIC, 37, struct btrfs_ioctl_received_subvol_args) - _, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(), 0xC0C09425, uintptr(unsafe.Pointer(&args))) + _, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(), linux.IoctlBtrfsSetReceivedSubvol, uintptr(unsafe.Pointer(&args))) if errno != 0 { return fmt.Errorf("Failed setting received UUID: %w", unix.Errno(errno)) } From be7599297a11e8628d3a088a0b07014118580d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Tue, 4 Jun 2024 10:55:11 -0400 Subject: [PATCH 62/99] incusd/devices: Simplify ioctl logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber (cherry picked from commit 870014c4f2d7bc4748fc1adf1defa1cc61c4e310) Signed-off-by: Thomas Parrott License: Apache-2.0 --- lxd/devices.go | 49 ++++++++++++------------------------------------- 1 file changed, 12 insertions(+), 37 deletions(-) diff --git a/lxd/devices.go b/lxd/devices.go index 65a7340f7b6f..d6e50b3c473d 100644 --- a/lxd/devices.go +++ b/lxd/devices.go @@ -1,37 +1,5 @@ package main -/* -#ifndef _GNU_SOURCE -#define _GNU_SOURCE 1 -#endif -#include -#include - -#include "include/memory_utils.h" - -#ifndef HIDIOCGRAWINFO -#define HIDIOCGRAWINFO _IOR('H', 0x03, struct hidraw_devinfo) -struct hidraw_devinfo { - __u32 bustype; - __s16 vendor; - __s16 product; -}; -#endif - -static int get_hidraw_devinfo(int fd, struct hidraw_devinfo *info) -{ - int ret; - - ret = ioctl(fd, HIDIOCGRAWINFO, info); - if (ret) - return -1; - - return 0; -} - -*/ -import "C" - import ( "fmt" "os" @@ -40,14 +8,15 @@ import ( "sort" "strconv" "strings" + "unsafe" "golang.org/x/sys/unix" "github.com/canonical/lxd/lxd/cgroup" "github.com/canonical/lxd/lxd/device" - _ "github.com/canonical/lxd/lxd/include" // Used by cgo "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/instance/instancetype" + "github.com/canonical/lxd/lxd/linux" "github.com/canonical/lxd/lxd/resources" "github.com/canonical/lxd/lxd/state" "github.com/canonical/lxd/shared" @@ -710,10 +679,16 @@ func devicesRegister(instances []instance.Instance) { } func getHidrawDevInfo(fd int) (vendor string, product string, err error) { - info := C.struct_hidraw_devinfo{} - ret, err := C.get_hidraw_devinfo(C.int(fd), &info) - if ret != 0 { - return "", "", err + type hidInfo struct { + busType uint32 + vendor int16 + product int16 + } + + var info hidInfo + _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), linux.IoctlHIDIOCGrawInfo, uintptr(unsafe.Pointer(&info))) + if errno != 0 { + return "", "", fmt.Errorf("Failed setting received UUID: %w", unix.Errno(errno)) } return fmt.Sprintf("%04x", info.vendor), fmt.Sprintf("%04x", info.product), nil From d5ea12a6fa7c436ba91b23429c6fe83b17490bdd Mon Sep 17 00:00:00 2001 From: Sally Date: Thu, 18 Jul 2024 11:52:09 +0100 Subject: [PATCH 63/99] fix typo in index.md Signed-off-by: Sally --- doc/howto/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/howto/index.md b/doc/howto/index.md index 2263fd6cbd06..ca3517582302 100644 --- a/doc/howto/index.md +++ b/doc/howto/index.md @@ -39,7 +39,7 @@ You'll also need to set up and configure other entities. ## Get ready for production Once you are ready for production, consider setting up a LXD cluster to support the required load. -You should also monitor you server or servers and configure them for the expected load. +You should also monitor your server or servers and configure them for the expected load. ```{toctree} :titlesonly: From e76a7b276942c3e28872ad8100878bbaa2dcbbea Mon Sep 17 00:00:00 2001 From: Ruth Fuchss Date: Thu, 18 Jul 2024 17:03:52 +0200 Subject: [PATCH 64/99] doc/contributing: add section on how-tos Add a section on how to add instructions for CLI, API, and UI. Signed-off-by: Ruth Fuchss --- doc/.custom_wordlist.txt | 1 + doc/.sphinx/.markdownlint/exceptions.txt | 12 +++++++ doc/contributing.md | 46 ++++++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/doc/.custom_wordlist.txt b/doc/.custom_wordlist.txt index 5989f235383c..3714e581f865 100644 --- a/doc/.custom_wordlist.txt +++ b/doc/.custom_wordlist.txt @@ -13,6 +13,7 @@ ASN AXFR backend backends +backticks balancers benchmarking BGP diff --git a/doc/.sphinx/.markdownlint/exceptions.txt b/doc/.sphinx/.markdownlint/exceptions.txt index 902dfbdf5738..dff8d656e4f0 100644 --- a/doc/.sphinx/.markdownlint/exceptions.txt +++ b/doc/.sphinx/.markdownlint/exceptions.txt @@ -9,3 +9,15 @@ .tmp/doc/howto/network_forwards.md:67: MD032 Lists should be surrounded by blank lines .tmp/doc/howto/network_forwards.md:72: MD032 Lists should be surrounded by blank lines .tmp/doc/doc-cheat-sheet-myst.md:171: MD034 Bare URL used +.tmp/doc/contributing.md:124: MD004 Unordered list style +.tmp/doc/contributing.md:129: MD004 Unordered list style +.tmp/doc/contributing.md:136: MD004 Unordered list style +.tmp/doc/contributing.md:124: MD005 Inconsistent indentation for list items at the same level +.tmp/doc/contributing.md:125: MD005 Inconsistent indentation for list items at the same level +.tmp/doc/contributing.md:126: MD005 Inconsistent indentation for list items at the same level +.tmp/doc/contributing.md:129: MD005 Inconsistent indentation for list items at the same level +.tmp/doc/contributing.md:131: MD005 Inconsistent indentation for list items at the same level +.tmp/doc/contributing.md:133: MD005 Inconsistent indentation for list items at the same level +.tmp/doc/contributing.md:136: MD005 Inconsistent indentation for list items at the same level +.tmp/doc/contributing.md:138: MD005 Inconsistent indentation for list items at the same level +.tmp/doc/contributing.md:125: MD032 Lists should be surrounded by blank lines diff --git a/doc/contributing.md b/doc/contributing.md index ae54f8c328a1..b3d88b3525ef 100644 --- a/doc/contributing.md +++ b/doc/contributing.md @@ -93,6 +93,52 @@ You can (and should!) run these tests locally as well with the following command - Check the Markdown formatting: `make doc-lint` - Check for inclusive language: `make doc-woke` +### Document instructions (how-to guides) + +LXD can be used with different clients, most importantly the command-line interface (CLI), the API, and the UI. +The documentation contains instructions for all of these. +Therefore, when adding or updating how-to guides, remember to update the documentation for all clients. + +Information that is different for each client should be put on tabs: + +````` +````{tabs} +```{group-tab} CLI +[...] +``` +```{group-tab} API +[...] +``` +```{group-tab} UI +[...] +``` +```` +````` + +```{tip} +You might need to increase the number of backticks (`) if there are code blocks or other directives in the tab content. +``` + +Adhere to the following guidelines when adding instructions: + +CLI instructions +: - Add a link to the command reference of the `lxc` command (syntax example: ``[`lxc init`](lxc_init.md)``). + - You don't need to document all available flags of a command, but you should mention any that are especially relevant. + - Examples are very helpful, so add a few if it makes sense. + +API instructions +: - If possible, use [`lxc query`](lxc_query.md) to demonstrate the API calls. + For more complicated calls, use curl or other widely available tools. + - In the request data, include all fields that are required for the request to succeed. + Keep it as simple as possible though - no need to include all available fields. + - Add a link to the API call reference (syntax example: ``[`POST /1.0/instances`](swagger:/instances/instances_post)``). + +UI instructions +: - You can include screenshots to illustrate the instructions, but use them sparingly. + Screenshots are difficult to maintain and keep up-to-date. + - When referring to labels in the UI, use the `{guilabel}` role. + For example: ``To create an instance, go to the {guilabel}`Instances` section and click {guilabel}`Create instance`.`` + ### Document configuration options The documentation of configuration options is extracted from comments in the Go code. From 36e640a85c5dea9f660e3d3079b78b667e315e3d Mon Sep 17 00:00:00 2001 From: Mark Bolton Date: Fri, 19 Jul 2024 09:08:04 -0700 Subject: [PATCH 65/99] lxd: Update logic for project config patch Signed-off-by: Mark Bolton --- lxd/api_project.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lxd/api_project.go b/lxd/api_project.go index 5e75f1ca5d41..693ec8954844 100644 --- a/lxd/api_project.go +++ b/lxd/api_project.go @@ -599,14 +599,14 @@ func projectPatch(d *Daemon, r *http.Request) response.Response { req.Description = project.Description } - config, err := reqRaw.GetMap("config") - if err != nil { - req.Config = project.Config - } else { - for k, v := range project.Config { - _, ok := config[k] - if !ok { - config[k] = v + // Perform config patch + req.Config = util.CopyConfig(project.Config) + patches, err := reqRaw.GetMap("config") + if err == nil { + for k, v := range patches { + strVal, ok := v.(string) + if ok { + req.Config[k] = strVal } } } From a0c670176cf4e74134d7f54ccbf068e39d70b013 Mon Sep 17 00:00:00 2001 From: Mark Bolton Date: Fri, 19 Jul 2024 12:48:14 -0700 Subject: [PATCH 66/99] test/suites: Add testing for project PATCH Signed-off-by: Mark Bolton --- test/suites/projects.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/suites/projects.sh b/test/suites/projects.sh index 2460ba14e280..0fa40405dfe3 100644 --- a/test/suites/projects.sh +++ b/test/suites/projects.sh @@ -20,6 +20,10 @@ test_projects_crud() { lxc project show foo | grep -q 'features.images: "true"' lxc project get foo "features.profiles" | grep -q 'true' + # Set a limit + lxc project set foo limits.containers 10 + lxc project show foo | grep -q 'limits.containers: "10"' + # Trying to create a project with the same name fails ! lxc project create foo || false @@ -37,6 +41,13 @@ test_projects_crud() { lxc project show bar| sed 's/^description:.*/description: "Bar project"/' | lxc project edit bar lxc project show bar | grep -q "description: Bar project" + # Edit the project config via PATCH. Existing key/value pairs should remain or be updated. + lxc query -X PATCH -d '{\"config\" : {\"limits.memory\":\"5GiB\",\"features.images\":\"false\"}}' /1.0/projects/bar + lxc project show bar | grep -q 'limits.memory: 5GiB' + lxc project show bar | grep -q 'features.images: "false"' + lxc project show bar | grep -q 'features.profiles: "true"' + lxc project show bar | grep -q 'limits.containers: "10"' + # Create a second project lxc project create foo From 61cfd2811479914cd01465de7ec9076c8752b732 Mon Sep 17 00:00:00 2001 From: hamistao Date: Wed, 17 Jul 2024 19:32:40 -0300 Subject: [PATCH 67/99] Makefile: bump Go min to 1.22.5 (needed by OpenFGA) Signed-off-by: hamistao --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 74f0bd41341e..957995e7c994 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ GOPATH ?= $(shell go env GOPATH) CGO_LDFLAGS_ALLOW ?= (-Wl,-wrap,pthread_create)|(-Wl,-z,now) SPHINXENV=doc/.sphinx/venv/bin/activate SPHINXPIPPATH=doc/.sphinx/venv/bin/pip -GOMIN=1.22.4 +GOMIN=1.22.5 ifneq "$(wildcard vendor)" "" DQLITE_PATH=$(CURDIR)/vendor/dqlite From 401d28e6f9dc435d7ce55e920418b6f4c2ea283c Mon Sep 17 00:00:00 2001 From: hamistao Date: Wed, 17 Jul 2024 19:44:47 -0300 Subject: [PATCH 68/99] doc/requirements: bump min Go version to 1.22.5 Signed-off-by: hamistao --- doc/requirements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.md b/doc/requirements.md index fd46e0b1ccef..6a08460851d7 100644 --- a/doc/requirements.md +++ b/doc/requirements.md @@ -4,7 +4,7 @@ (requirements-go)= ## Go -LXD requires Go 1.22.4 or higher and is only tested with the Golang compiler. +LXD requires Go 1.22.5 or higher and is only tested with the Golang compiler. We recommend having at least 2GiB of RAM to allow the build to complete. From aebde581cfd171bad302df8002a23a317eca40a3 Mon Sep 17 00:00:00 2001 From: Alexander Mikhalitsyn Date: Wed, 17 Jul 2024 11:39:37 +0200 Subject: [PATCH 69/99] lxd/apparmor: allow userns for security.nesting=true case Right now this patch does not change anything, because user namespaces are always allowed. But after we merge https://github.com/canonical/lxd-pkg-snap/pull/277 user namespaces become restricted by default and we need to explicitly allow it when needed. Signed-off-by: Alexander Mikhalitsyn --- lxd/apparmor/apparmor.go | 2 +- lxd/apparmor/feature_check.go | 4 ++++ lxd/apparmor/instance.go | 6 ++++++ lxd/apparmor/instance_lxc.go | 5 +++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lxd/apparmor/apparmor.go b/lxd/apparmor/apparmor.go index 2f33f200f31a..34be5481a76c 100644 --- a/lxd/apparmor/apparmor.go +++ b/lxd/apparmor/apparmor.go @@ -185,7 +185,7 @@ func parserSupports(sysOS *sys.OS, feature string) (bool, error) { return ver.Compare(minVer) >= 0, nil } - if feature == "mount_nosymfollow" { + if feature == "mount_nosymfollow" || feature == "userns_rule" { sysOS.AppArmorFeatures.Lock() defer sysOS.AppArmorFeatures.Unlock() supported, ok := sysOS.AppArmorFeatures.Map[feature] diff --git a/lxd/apparmor/feature_check.go b/lxd/apparmor/feature_check.go index 3043ec20c738..1d0f3a08f9b2 100644 --- a/lxd/apparmor/feature_check.go +++ b/lxd/apparmor/feature_check.go @@ -20,6 +20,10 @@ profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) { mount options=(nosymfollow) /, {{- end }} +{{- if eq .feature "userns_rule" }} + userns, +{{- end }} + } `)) diff --git a/lxd/apparmor/instance.go b/lxd/apparmor/instance.go index 895836ce5665..2595ecc428bc 100644 --- a/lxd/apparmor/instance.go +++ b/lxd/apparmor/instance.go @@ -161,12 +161,18 @@ func instanceProfile(sysOS *sys.OS, inst instance) (string, error) { return "", err } + usernsRuleSupported, err := parserSupports(sysOS, "userns_rule") + if err != nil { + return "", err + } + err = lxcProfileTpl.Execute(sb, map[string]any{ "feature_cgns": sysOS.CGInfo.Namespacing, "feature_cgroup2": sysOS.CGInfo.Layout == cgroup.CgroupsUnified || sysOS.CGInfo.Layout == cgroup.CgroupsHybrid, "feature_stacking": sysOS.AppArmorStacking && !sysOS.AppArmorStacked, "feature_unix": unixSupported, "feature_mount_nosymfollow": mountNosymfollowSupported, + "feature_userns_rule": usernsRuleSupported, "name": InstanceProfileName(inst), "namespace": InstanceNamespaceName(inst), "nesting": shared.IsTrue(inst.ExpandedConfig()["security.nesting"]), diff --git a/lxd/apparmor/instance_lxc.go b/lxd/apparmor/instance_lxc.go index 3a49b5f5c744..e61f306bf575 100644 --- a/lxd/apparmor/instance_lxc.go +++ b/lxd/apparmor/instance_lxc.go @@ -466,6 +466,11 @@ profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) { ### Configuration: nesting pivot_root, + # Allow user namespaces to be created +{{- if .feature_userns_rule }} + userns, +{{- end }} + # Allow sending signals and tracing children namespaces ptrace, signal, From 9727a23f6d572027c1e1c4be72b0a3fbf187fc65 Mon Sep 17 00:00:00 2001 From: Din Music Date: Fri, 12 Jul 2024 12:15:49 +0000 Subject: [PATCH 70/99] shared/api/instance: Name return arguments Signed-off-by: Din Music --- shared/api/instance.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/api/instance.go b/shared/api/instance.go index e7855198055b..efb92359d942 100644 --- a/shared/api/instance.go +++ b/shared/api/instance.go @@ -6,7 +6,7 @@ import ( ) // GetParentAndSnapshotName returns the parent name, snapshot name, and whether it actually was a snapshot name. -func GetParentAndSnapshotName(name string) (string, string, bool) { +func GetParentAndSnapshotName(name string) (parentName string, snapshotName string, isSnapshot bool) { fields := strings.SplitN(name, "/", 2) if len(fields) == 1 { return name, "", false From ecfe5ddb1983538bae4d0cd5db9f37f369d6e8c7 Mon Sep 17 00:00:00 2001 From: Din Music Date: Mon, 15 Jul 2024 16:26:27 +0000 Subject: [PATCH 71/99] shared/util_linux: Fix linting issues Signed-off-by: Din Music --- shared/util_linux.go | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/shared/util_linux.go b/shared/util_linux.go index f1b8ac40d433..17288ac5e353 100644 --- a/shared/util_linux.go +++ b/shared/util_linux.go @@ -25,11 +25,13 @@ import ( // --- pure Go functions --- +// GetFileStat retrieves the UID, GID, major and minor device numbers, inode, and number of hard links for +// the given file path. func GetFileStat(p string) (uid int, gid int, major uint32, minor uint32, inode uint64, nlink int, err error) { var stat unix.Stat_t err = unix.Lstat(p, &stat) if err != nil { - return + return 0, 0, 0, 0, 0, 0, err } uid = int(stat.Uid) @@ -41,7 +43,7 @@ func GetFileStat(p string) (uid int, gid int, major uint32, minor uint32, inode minor = unix.Minor(uint64(stat.Rdev)) } - return + return uid, gid, major, minor, inode, nlink, nil } // GetPathMode returns a os.FileMode for the provided path. @@ -55,6 +57,7 @@ func GetPathMode(path string) (os.FileMode, error) { return mode, nil } +// SetSize sets the terminal size to the specified width and height for the given file descriptor. func SetSize(fd int, width int, height int) (err error) { var dimensions [4]uint16 dimensions[0] = uint16(height) @@ -94,8 +97,10 @@ func GetAllXattr(path string) (map[string]string, error) { return xattrs, nil } -var ObjectFound = fmt.Errorf("Found requested object") +// ErrObjectFound indicates that the requested object was found. +var ErrObjectFound = fmt.Errorf("Found requested object") +// LookupUUIDByBlockDevPath finds and returns the UUID of a block device by its path. func LookupUUIDByBlockDevPath(diskDevice string) (string, error) { uuid := "" readUUID := func(path string, info os.FileInfo, err error) error { @@ -117,14 +122,14 @@ func LookupUUIDByBlockDevPath(diskDevice string) (string, error) { uuid = path // Will allows us to avoid needlessly travers // the whole directory. - return ObjectFound + return ErrObjectFound } } return nil } err := filepath.Walk("/dev/disk/by-uuid", readUUID) - if err != nil && err != ObjectFound { + if err != nil && err != ErrObjectFound { return "", fmt.Errorf("Failed to detect UUID: %s", err) } @@ -136,7 +141,9 @@ func LookupUUIDByBlockDevPath(diskDevice string) (string, error) { return uuid[lastSlash+1:], nil } -// Detect whether err is an errno. +// GetErrno detects whether the error is an errno. +// +//revive:disable:error-return Error is returned first because this is similar to assertion. func GetErrno(err error) (errno error, iserrno bool) { sysErr, ok := err.(*os.SyscallError) if ok { @@ -220,10 +227,12 @@ func intArrayToString(arr any) string { return s } +// DeviceTotalMemory returns the total memory of the device by reading /proc/meminfo. func DeviceTotalMemory() (int64, error) { return GetMeminfo("MemTotal") } +// GetMeminfo retrieves the memory information for the specified field from /proc/meminfo. func GetMeminfo(field string) (int64, error) { // Open /proc/meminfo f, err := os.Open("/proc/meminfo") @@ -260,7 +269,7 @@ func GetMeminfo(field string) (int64, error) { } // OpenPtyInDevpts creates a new PTS pair, configures them and returns them. -func OpenPtyInDevpts(devpts_fd int, uid, gid int64) (*os.File, *os.File, error) { +func OpenPtyInDevpts(devptsFD int, uid, gid int64) (*os.File, *os.File, error) { revert := revert.New() defer revert.Fail() var fd int @@ -268,8 +277,8 @@ func OpenPtyInDevpts(devpts_fd int, uid, gid int64) (*os.File, *os.File, error) var err error // Create a PTS pair. - if devpts_fd >= 0 { - fd, err = unix.Openat(devpts_fd, "ptmx", unix.O_RDWR|unix.O_CLOEXEC|unix.O_NOCTTY, 0) + if devptsFD >= 0 { + fd, err = unix.Openat(devptsFD, "ptmx", unix.O_RDWR|unix.O_CLOEXEC|unix.O_NOCTTY, 0) } else { fd, err = unix.Openat(-1, "/dev/ptmx", unix.O_RDWR|unix.O_CLOEXEC|unix.O_NOCTTY, 0) } @@ -301,7 +310,7 @@ func OpenPtyInDevpts(devpts_fd int, uid, gid int64) (*os.File, *os.File, error) pty = os.NewFile(ptyFd, fmt.Sprintf("/dev/pts/%d", id)) } else { - if devpts_fd >= 0 { + if devptsFD >= 0 { return nil, nil, fmt.Errorf("TIOCGPTPEER required but not available") } @@ -401,7 +410,7 @@ func ExitStatus(err error) (int, error) { } // GetPollRevents poll for events on provided fd. -func GetPollRevents(fd int, timeout int, flags int) (int, int, error) { +func GetPollRevents(fd int, timeout int, flags int) (n int, revents int, err error) { pollFd := unix.PollFd{ Fd: int32(fd), Events: int16(flags), @@ -411,7 +420,7 @@ func GetPollRevents(fd int, timeout int, flags int) (int, int, error) { pollFds := []unix.PollFd{pollFd} again: - n, err := unix.Poll(pollFds, timeout) + n, err = unix.Poll(pollFds, timeout) if err != nil { if err == unix.EAGAIN || err == unix.EINTR { goto again @@ -498,10 +507,12 @@ func (w *execWrapper) Read(p []byte) (int, error) { return n, opErr } +// Write writes data to the underlying os.File. func (w *execWrapper) Write(p []byte) (int, error) { return w.f.Write(p) } +// Close closes the underlying os.File. func (w *execWrapper) Close() error { return w.f.Close() } From 6f1c9e18e1880c6b7462a56da26d3a187d686ba8 Mon Sep 17 00:00:00 2001 From: Din Music Date: Mon, 15 Jul 2024 16:13:45 +0000 Subject: [PATCH 72/99] lxd/storage: Move BlockDiskSizeBytes function into separate package Signed-off-by: Din Music --- lxd/storage/backend_lxd.go | 5 ++- lxd/storage/block/utils.go | 39 +++++++++++++++++++ lxd/storage/drivers/driver_btrfs_volumes.go | 4 +- lxd/storage/drivers/driver_ceph_volumes.go | 3 +- lxd/storage/drivers/driver_dir_volumes.go | 3 +- .../drivers/driver_powerflex_volumes.go | 3 +- lxd/storage/drivers/generic_vfs.go | 3 +- lxd/storage/drivers/utils.go | 30 -------------- lxd/storage/utils.go | 5 ++- 9 files changed, 56 insertions(+), 39 deletions(-) create mode 100644 lxd/storage/block/utils.go diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go index 9e60069ba930..1222e78dc48f 100644 --- a/lxd/storage/backend_lxd.go +++ b/lxd/storage/backend_lxd.go @@ -37,6 +37,7 @@ import ( "github.com/canonical/lxd/lxd/project" "github.com/canonical/lxd/lxd/response" "github.com/canonical/lxd/lxd/state" + "github.com/canonical/lxd/lxd/storage/block" "github.com/canonical/lxd/lxd/storage/drivers" "github.com/canonical/lxd/lxd/storage/filesystem" "github.com/canonical/lxd/lxd/storage/memorypipe" @@ -1453,7 +1454,7 @@ func (b *lxdBackend) RefreshCustomVolume(projectName string, srcProjectName stri return err } - volSize, err = drivers.BlockDiskSizeBytes(volDiskPath) + volSize, err = block.DiskSizeBytes(volDiskPath) if err != nil { return err } @@ -4981,7 +4982,7 @@ func (b *lxdBackend) CreateCustomVolumeFromCopy(projectName string, srcProjectNa return err } - volSize, err = drivers.BlockDiskSizeBytes(volDiskPath) + volSize, err = block.DiskSizeBytes(volDiskPath) if err != nil { return err } diff --git a/lxd/storage/block/utils.go b/lxd/storage/block/utils.go new file mode 100644 index 000000000000..ba5e113ee2ec --- /dev/null +++ b/lxd/storage/block/utils.go @@ -0,0 +1,39 @@ +package block + +import ( + "os" + + "golang.org/x/sys/unix" + + "github.com/canonical/lxd/shared" +) + +// DiskSizeBytes returns the size of a block disk (path can be either block device or raw file). +func DiskSizeBytes(blockDiskPath string) (int64, error) { + if shared.IsBlockdevPath(blockDiskPath) { + // Attempt to open the device path. + f, err := os.Open(blockDiskPath) + if err != nil { + return -1, err + } + + defer func() { _ = f.Close() }() + fd := int(f.Fd()) + + // Retrieve the block device size. + res, err := unix.IoctlGetInt(fd, unix.BLKGETSIZE64) + if err != nil { + return -1, err + } + + return int64(res), nil + } + + // Block device is assumed to be a raw file. + fi, err := os.Lstat(blockDiskPath) + if err != nil { + return -1, err + } + + return fi.Size(), nil +} diff --git a/lxd/storage/drivers/driver_btrfs_volumes.go b/lxd/storage/drivers/driver_btrfs_volumes.go index 45eb02394dd2..0aef7f4b1209 100644 --- a/lxd/storage/drivers/driver_btrfs_volumes.go +++ b/lxd/storage/drivers/driver_btrfs_volumes.go @@ -20,6 +20,7 @@ import ( "github.com/canonical/lxd/lxd/instancewriter" "github.com/canonical/lxd/lxd/migration" "github.com/canonical/lxd/lxd/operations" + "github.com/canonical/lxd/lxd/storage/block" "github.com/canonical/lxd/lxd/storage/filesystem" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" @@ -1010,7 +1011,7 @@ func (d *btrfs) SetVolumeQuota(vol Volume, size string, allowUnsafeResize bool, // Custom handling for filesystem volume associated with a VM. if vol.volType == VolumeTypeVM && shared.PathExists(filepath.Join(volPath, genericVolumeDiskFile)) { // Get the size of the VM image. - blockSize, err := BlockDiskSizeBytes(filepath.Join(volPath, genericVolumeDiskFile)) + blockSize, err := block.DiskSizeBytes(filepath.Join(volPath, genericVolumeDiskFile)) if err != nil { return err } @@ -1272,6 +1273,7 @@ func (d *btrfs) migrateVolumeOptimized(vol Volume, conn io.ReadWriteCloser, volS sentVols := 0 // Send volume (and any subvolumes if supported) to target. + //revive:disable:defer Allow defer inside a loop. for _, subVolume := range subvolumes { if subVolume.Snapshot != snapName { continue // Only sending subvolumes related to snapshot name (empty for main vol). diff --git a/lxd/storage/drivers/driver_ceph_volumes.go b/lxd/storage/drivers/driver_ceph_volumes.go index db146c015d29..d94b2b33773b 100644 --- a/lxd/storage/drivers/driver_ceph_volumes.go +++ b/lxd/storage/drivers/driver_ceph_volumes.go @@ -19,6 +19,7 @@ import ( "github.com/canonical/lxd/lxd/migration" "github.com/canonical/lxd/lxd/operations" "github.com/canonical/lxd/lxd/response" + "github.com/canonical/lxd/lxd/storage/block" "github.com/canonical/lxd/lxd/storage/filesystem" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" @@ -1318,7 +1319,7 @@ func (d *ceph) SetVolumeQuota(vol Volume, size string, allowUnsafeResize bool, o defer func() { _ = d.rbdUnmapVolume(vol, true) }() } - oldSizeBytes, err := BlockDiskSizeBytes(devPath) + oldSizeBytes, err := block.DiskSizeBytes(devPath) if err != nil { return fmt.Errorf("Error getting current size: %w", err) } diff --git a/lxd/storage/drivers/driver_dir_volumes.go b/lxd/storage/drivers/driver_dir_volumes.go index a9e09dfff98b..a824286e36e1 100644 --- a/lxd/storage/drivers/driver_dir_volumes.go +++ b/lxd/storage/drivers/driver_dir_volumes.go @@ -12,6 +12,7 @@ import ( "github.com/canonical/lxd/lxd/migration" "github.com/canonical/lxd/lxd/operations" "github.com/canonical/lxd/lxd/rsync" + "github.com/canonical/lxd/lxd/storage/block" "github.com/canonical/lxd/lxd/storage/filesystem" "github.com/canonical/lxd/lxd/storage/quota" "github.com/canonical/lxd/shared" @@ -347,7 +348,7 @@ func (d *dir) SetVolumeQuota(vol Volume, size string, allowUnsafeResize bool, op volPath := vol.MountPath() if sizeBytes > 0 && vol.volType == VolumeTypeVM && shared.PathExists(filepath.Join(volPath, genericVolumeDiskFile)) { // Get the size of the VM image. - blockSize, err := BlockDiskSizeBytes(filepath.Join(volPath, genericVolumeDiskFile)) + blockSize, err := block.DiskSizeBytes(filepath.Join(volPath, genericVolumeDiskFile)) if err != nil { return err } diff --git a/lxd/storage/drivers/driver_powerflex_volumes.go b/lxd/storage/drivers/driver_powerflex_volumes.go index d3a4a5466572..571b1b890d1f 100644 --- a/lxd/storage/drivers/driver_powerflex_volumes.go +++ b/lxd/storage/drivers/driver_powerflex_volumes.go @@ -15,6 +15,7 @@ import ( "github.com/canonical/lxd/lxd/instancewriter" "github.com/canonical/lxd/lxd/migration" "github.com/canonical/lxd/lxd/operations" + "github.com/canonical/lxd/lxd/storage/block" "github.com/canonical/lxd/lxd/storage/filesystem" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" @@ -552,7 +553,7 @@ func (d *powerflex) SetVolumeQuota(vol Volume, size string, allowUnsafeResize bo defer cleanup() - oldSizeBytes, err := BlockDiskSizeBytes(devPath) + oldSizeBytes, err := block.DiskSizeBytes(devPath) if err != nil { return fmt.Errorf("Error getting current size: %w", err) } diff --git a/lxd/storage/drivers/generic_vfs.go b/lxd/storage/drivers/generic_vfs.go index a4ea67d91f28..e86c9946fbcc 100644 --- a/lxd/storage/drivers/generic_vfs.go +++ b/lxd/storage/drivers/generic_vfs.go @@ -15,6 +15,7 @@ import ( "github.com/canonical/lxd/lxd/operations" "github.com/canonical/lxd/lxd/rsync" "github.com/canonical/lxd/lxd/state" + "github.com/canonical/lxd/lxd/storage/block" "github.com/canonical/lxd/lxd/storage/filesystem" "github.com/canonical/lxd/lxd/sys" "github.com/canonical/lxd/shared" @@ -569,7 +570,7 @@ func genericVFSBackupVolume(d Driver, vol VolumeCopy, tarWriter *instancewriter. } // Get size of disk block device for tarball header. - blockDiskSize, err := BlockDiskSizeBytes(blockPath) + blockDiskSize, err := block.DiskSizeBytes(blockPath) if err != nil { return fmt.Errorf("Error getting block device size %q: %w", blockPath, err) } diff --git a/lxd/storage/drivers/utils.go b/lxd/storage/drivers/utils.go index 6460a9547dc6..5ca347b6c425 100644 --- a/lxd/storage/drivers/utils.go +++ b/lxd/storage/drivers/utils.go @@ -787,36 +787,6 @@ func ShiftZFSSkipper(dir string, absPath string, fi os.FileInfo) bool { return false } -// BlockDiskSizeBytes returns the size of a block disk (path can be either block device or raw file). -func BlockDiskSizeBytes(blockDiskPath string) (int64, error) { - if shared.IsBlockdevPath(blockDiskPath) { - // Attempt to open the device path. - f, err := os.Open(blockDiskPath) - if err != nil { - return -1, err - } - - defer func() { _ = f.Close() }() - fd := int(f.Fd()) - - // Retrieve the block device size. - res, err := unix.IoctlGetInt(fd, unix.BLKGETSIZE64) - if err != nil { - return -1, err - } - - return int64(res), nil - } - - // Block device is assumed to be a raw file. - fi, err := os.Lstat(blockDiskPath) - if err != nil { - return -1, err - } - - return fi.Size(), nil -} - // OperationLockName returns the storage specific lock name to use with locking package. func OperationLockName(operationName string, poolName string, volType VolumeType, contentType ContentType, volName string) string { return fmt.Sprintf("%s/%s/%s/%s/%s", operationName, poolName, volType, contentType, volName) diff --git a/lxd/storage/utils.go b/lxd/storage/utils.go index 668ad8a1c92c..5b264e178a9c 100644 --- a/lxd/storage/utils.go +++ b/lxd/storage/utils.go @@ -24,6 +24,7 @@ import ( "github.com/canonical/lxd/lxd/response" "github.com/canonical/lxd/lxd/rsync" "github.com/canonical/lxd/lxd/state" + "github.com/canonical/lxd/lxd/storage/block" "github.com/canonical/lxd/lxd/storage/drivers" "github.com/canonical/lxd/lxd/sys" "github.com/canonical/lxd/shared" @@ -762,7 +763,7 @@ func ImageUnpack(imageFile string, vol drivers.Volume, destBlockFile string, sys } if shared.PathExists(dstPath) { - volSizeBytes, err := drivers.BlockDiskSizeBytes(dstPath) + volSizeBytes, err := block.DiskSizeBytes(dstPath) if err != nil { return -1, fmt.Errorf("Error getting current size of %q: %w", dstPath, err) } @@ -1188,7 +1189,7 @@ func InstanceDiskBlockSize(pool Pool, inst instance.Instance, op *operations.Ope return -1, fmt.Errorf("No disk path available from mount") } - blockDiskSize, err := drivers.BlockDiskSizeBytes(mountInfo.DiskPath) + blockDiskSize, err := block.DiskSizeBytes(mountInfo.DiskPath) if err != nil { return -1, fmt.Errorf("Error getting block disk size %q: %w", mountInfo.DiskPath, err) } From 54328182bc73ba071070ceaf572c2294496536ad Mon Sep 17 00:00:00 2001 From: Din Music Date: Wed, 5 Jun 2024 19:07:51 +0000 Subject: [PATCH 73/99] lxd/storage/backend_mock: Satisfy linter by adding empty comments to all functions Signed-off-by: Din Music --- lxd/storage/backend_mock.go | 80 +++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/lxd/storage/backend_mock.go b/lxd/storage/backend_mock.go index 1424892de51e..860556bca112 100644 --- a/lxd/storage/backend_mock.go +++ b/lxd/storage/backend_mock.go @@ -27,42 +27,52 @@ type mockBackend struct { driver drivers.Driver } +// ID ... func (b *mockBackend) ID() int64 { return 1 // The tests expect the storage pool ID to be 1. } +// Name ... func (b *mockBackend) Name() string { return b.name } +// Description ... func (b *mockBackend) Description() string { return "" } +// ValidateName ... func (b *mockBackend) ValidateName(value string) error { return nil } +// Validate ... func (b *mockBackend) Validate(config map[string]string) error { return nil } +// Status ... func (b *mockBackend) Status() string { return api.NetworkStatusUnknown } +// LocalStatus ... func (b *mockBackend) LocalStatus() string { return api.NetworkStatusUnknown } +// ToAPI ... func (b *mockBackend) ToAPI() api.StoragePool { return api.StoragePool{} } +// Driver ... func (b *mockBackend) Driver() drivers.Driver { return b.driver } +// MigrationTypes ... func (b *mockBackend) MigrationTypes(contentType drivers.ContentType, refresh bool, copySnapshots bool) []migration.Type { return []migration.Type{ { @@ -72,82 +82,102 @@ func (b *mockBackend) MigrationTypes(contentType drivers.ContentType, refresh bo } } +// GetResources ... func (b *mockBackend) GetResources() (*api.ResourcesStoragePool, error) { return nil, nil } +// IsUsed ... func (b *mockBackend) IsUsed() (bool, error) { return false, nil } +// Delete ... func (b *mockBackend) Delete(clientType request.ClientType, op *operations.Operation) error { return nil } +// Update ... func (b *mockBackend) Update(clientType request.ClientType, newDescription string, newConfig map[string]string, op *operations.Operation) error { return nil } +// Create ... func (b *mockBackend) Create(clientType request.ClientType, op *operations.Operation) error { return nil } +// Mount ... func (b *mockBackend) Mount() (bool, error) { return true, nil } +// Unmount ... func (b *mockBackend) Unmount() (bool, error) { return true, nil } +// ApplyPatch ... func (b *mockBackend) ApplyPatch(name string) error { return nil } +// GetVolume ... func (b *mockBackend) GetVolume(volType drivers.VolumeType, contentType drivers.ContentType, volName string, volConfig map[string]string) drivers.Volume { return drivers.Volume{} } +// CreateInstance ... func (b *mockBackend) CreateInstance(inst instance.Instance, op *operations.Operation) error { return nil } +// CreateInstanceFromBackup ... func (b *mockBackend) CreateInstanceFromBackup(srcBackup backup.Info, srcData io.ReadSeeker, op *operations.Operation) (func(instance.Instance) error, revert.Hook, error) { return nil, nil, nil } +// CreateInstanceFromCopy ... func (b *mockBackend) CreateInstanceFromCopy(inst instance.Instance, src instance.Instance, snapshots bool, allowInconsistent bool, op *operations.Operation) error { return nil } +// CreateInstanceFromImage ... func (b *mockBackend) CreateInstanceFromImage(inst instance.Instance, fingerprint string, op *operations.Operation) error { return nil } +// CreateInstanceFromMigration ... func (b *mockBackend) CreateInstanceFromMigration(inst instance.Instance, conn io.ReadWriteCloser, args migration.VolumeTargetArgs, op *operations.Operation) error { return nil } +// RenameInstance ... func (b *mockBackend) RenameInstance(inst instance.Instance, newName string, op *operations.Operation) error { return nil } +// DeleteInstance ... func (b *mockBackend) DeleteInstance(inst instance.Instance, op *operations.Operation) error { return nil } +// UpdateInstance ... func (b *mockBackend) UpdateInstance(inst instance.Instance, newDesc string, newConfig map[string]string, op *operations.Operation) error { return nil } +// GenerateCustomVolumeBackupConfig ... func (b *mockBackend) GenerateCustomVolumeBackupConfig(projectName string, volName string, snapshots bool, op *operations.Operation) (*backupConfig.Config, error) { return nil, nil } +// GenerateInstanceBackupConfig ... func (b *mockBackend) GenerateInstanceBackupConfig(inst instance.Instance, snapshots bool, op *operations.Operation) (*backupConfig.Config, error) { return nil, nil } +// UpdateInstanceBackupFile ... func (b *mockBackend) UpdateInstanceBackupFile(inst instance.Instance, snapshot bool, op *operations.Operation) error { return nil } @@ -157,202 +187,252 @@ func (b *mockBackend) CheckInstanceBackupFileSnapshots(backupConf *backupConfig. return nil, nil } +// ListUnknownVolumes ... func (b *mockBackend) ListUnknownVolumes(op *operations.Operation) (map[string][]*backupConfig.Config, error) { return nil, nil } +// ImportInstance ... func (b *mockBackend) ImportInstance(inst instance.Instance, poolVol *backupConfig.Config, op *operations.Operation) (revert.Hook, error) { return nil, nil } +// MigrateInstance ... func (b *mockBackend) MigrateInstance(inst instance.Instance, conn io.ReadWriteCloser, args *migration.VolumeSourceArgs, op *operations.Operation) error { return nil } +// CleanupInstancePaths ... func (b *mockBackend) CleanupInstancePaths(inst instance.Instance, op *operations.Operation) error { return nil } +// RefreshCustomVolume ... func (b *mockBackend) RefreshCustomVolume(projectName string, srcProjectName string, volName string, desc string, config map[string]string, srcPoolName, srcVolName string, srcVolOnly bool, op *operations.Operation) error { return nil } +// RefreshInstance ... func (b *mockBackend) RefreshInstance(inst instance.Instance, src instance.Instance, srcSnapshots []instance.Instance, allowInconsistent bool, op *operations.Operation) error { return nil } +// BackupInstance ... func (b *mockBackend) BackupInstance(inst instance.Instance, tarWriter *instancewriter.InstanceTarWriter, optimized bool, snapshots bool, op *operations.Operation) error { return nil } +// GetInstanceUsage ... func (b *mockBackend) GetInstanceUsage(inst instance.Instance) (*VolumeUsage, error) { return nil, nil } +// SetInstanceQuota ... func (b *mockBackend) SetInstanceQuota(inst instance.Instance, size string, vmStateSize string, op *operations.Operation) error { return nil } +// MountInstance ... func (b *mockBackend) MountInstance(inst instance.Instance, op *operations.Operation) (*MountInfo, error) { return &MountInfo{}, nil } +// UnmountInstance ... func (b *mockBackend) UnmountInstance(inst instance.Instance, op *operations.Operation) error { return nil } +// CreateInstanceSnapshot ... func (b *mockBackend) CreateInstanceSnapshot(i instance.Instance, src instance.Instance, op *operations.Operation) error { return nil } +// RenameInstanceSnapshot ... func (b *mockBackend) RenameInstanceSnapshot(inst instance.Instance, newName string, op *operations.Operation) error { return nil } +// DeleteInstanceSnapshot ... func (b *mockBackend) DeleteInstanceSnapshot(inst instance.Instance, op *operations.Operation) error { return nil } +// RestoreInstanceSnapshot ... func (b *mockBackend) RestoreInstanceSnapshot(inst instance.Instance, src instance.Instance, op *operations.Operation) error { return nil } +// MountInstanceSnapshot ... func (b *mockBackend) MountInstanceSnapshot(inst instance.Instance, op *operations.Operation) (*MountInfo, error) { return &MountInfo{}, nil } +// UnmountInstanceSnapshot ... func (b *mockBackend) UnmountInstanceSnapshot(inst instance.Instance, op *operations.Operation) error { return nil } +// UpdateInstanceSnapshot ... func (b *mockBackend) UpdateInstanceSnapshot(inst instance.Instance, newDesc string, newConfig map[string]string, op *operations.Operation) error { return nil } +// EnsureImage ... func (b *mockBackend) EnsureImage(fingerprint string, op *operations.Operation) error { return nil } +// DeleteImage ... func (b *mockBackend) DeleteImage(fingerprint string, op *operations.Operation) error { return nil } +// UpdateImage ... func (b *mockBackend) UpdateImage(fingerprint, newDesc string, newConfig map[string]string, op *operations.Operation) error { return nil } +// CreateBucket ... func (b *mockBackend) CreateBucket(projectName string, bucket api.StorageBucketsPost, op *operations.Operation) error { return nil } +// UpdateBucket ... func (b *mockBackend) UpdateBucket(projectName string, bucketName string, bucket api.StorageBucketPut, op *operations.Operation) error { return nil } +// DeleteBucket ... func (b *mockBackend) DeleteBucket(projectName string, bucketName string, op *operations.Operation) error { return nil } +// ImportBucket ... func (b *mockBackend) ImportBucket(projectName string, poolVol *backupConfig.Config, op *operations.Operation) (revert.Hook, error) { return nil, nil } +// CreateBucketKey ... func (b *mockBackend) CreateBucketKey(projectName string, bucketName string, key api.StorageBucketKeysPost, op *operations.Operation) (*api.StorageBucketKey, error) { return nil, nil } +// UpdateBucketKey ... func (b *mockBackend) UpdateBucketKey(projectName string, bucketName string, keyName string, key api.StorageBucketKeyPut, op *operations.Operation) error { return nil } +// DeleteBucketKey ... func (b *mockBackend) DeleteBucketKey(projectName string, bucketName string, keyName string, op *operations.Operation) error { return nil } +// ActivateBucket ... func (b *mockBackend) ActivateBucket(projectName string, bucketName string, op *operations.Operation) (*miniod.Process, error) { return nil, nil } +// GetBucketURL ... func (b *mockBackend) GetBucketURL(bucketName string) *url.URL { return nil } +// CreateCustomVolume ... func (b *mockBackend) CreateCustomVolume(projectName string, volName string, desc string, config map[string]string, contentType drivers.ContentType, op *operations.Operation) error { return nil } +// CreateCustomVolumeFromCopy ... func (b *mockBackend) CreateCustomVolumeFromCopy(projectName string, srcProjectName string, volName string, desc string, config map[string]string, srcPoolName string, srcVolName string, srcVolOnly bool, op *operations.Operation) error { return nil } +// RenameCustomVolume ... func (b *mockBackend) RenameCustomVolume(projectName string, volName string, newName string, op *operations.Operation) error { return nil } +// UpdateCustomVolume ... func (b *mockBackend) UpdateCustomVolume(projectName string, volName string, newDesc string, newConfig map[string]string, op *operations.Operation) error { return nil } +// DeleteCustomVolume ... func (b *mockBackend) DeleteCustomVolume(projectName string, volName string, op *operations.Operation) error { return nil } +// MigrateCustomVolume ... func (b *mockBackend) MigrateCustomVolume(projectName string, conn io.ReadWriteCloser, args *migration.VolumeSourceArgs, op *operations.Operation) error { return nil } +// CreateCustomVolumeFromMigration ... func (b *mockBackend) CreateCustomVolumeFromMigration(projectName string, conn io.ReadWriteCloser, args migration.VolumeTargetArgs, op *operations.Operation) error { return nil } +// GetCustomVolumeDisk ... func (b *mockBackend) GetCustomVolumeDisk(projectName string, volName string) (string, error) { return "", nil } +// GetCustomVolumeUsage ... func (b *mockBackend) GetCustomVolumeUsage(projectName string, volName string) (*VolumeUsage, error) { return nil, nil } +// MountCustomVolume ... func (b *mockBackend) MountCustomVolume(projectName string, volName string, op *operations.Operation) (*MountInfo, error) { return nil, nil } +// UnmountCustomVolume ... func (b *mockBackend) UnmountCustomVolume(projectName string, volName string, op *operations.Operation) (bool, error) { return true, nil } +// ImportCustomVolume ... func (b *mockBackend) ImportCustomVolume(projectName string, poolVol *backupConfig.Config, op *operations.Operation) (revert.Hook, error) { return nil, nil } +// CreateCustomVolumeSnapshot ... func (b *mockBackend) CreateCustomVolumeSnapshot(projectName string, volName string, newSnapshotName string, expiryDate time.Time, op *operations.Operation) error { return nil } +// RenameCustomVolumeSnapshot ... func (b *mockBackend) RenameCustomVolumeSnapshot(projectName string, volName string, newName string, op *operations.Operation) error { return nil } +// DeleteCustomVolumeSnapshot ... func (b *mockBackend) DeleteCustomVolumeSnapshot(projectName string, volName string, op *operations.Operation) error { return nil } +// UpdateCustomVolumeSnapshot ... func (b *mockBackend) UpdateCustomVolumeSnapshot(projectName string, volName string, newDesc string, newConfig map[string]string, expiryDate time.Time, op *operations.Operation) error { return nil } +// RestoreCustomVolume ... func (b *mockBackend) RestoreCustomVolume(projectName string, volName string, snapshotName string, op *operations.Operation) error { return nil } +// BackupCustomVolume ... func (b *mockBackend) BackupCustomVolume(projectName string, volName string, tarWriter *instancewriter.InstanceTarWriter, optimized bool, snapshots bool, op *operations.Operation) error { return nil } +// CreateCustomVolumeFromBackup ... func (b *mockBackend) CreateCustomVolumeFromBackup(srcBackup backup.Info, srcData io.ReadSeeker, op *operations.Operation) error { return nil } +// CreateCustomVolumeFromISO ... func (b *mockBackend) CreateCustomVolumeFromISO(projectName string, volName string, srcData io.ReadSeeker, size int64, op *operations.Operation) error { return nil } From 3c1352ccaffe7de42e4b698f40dceeee15d1675c Mon Sep 17 00:00:00 2001 From: Din Music Date: Thu, 6 Jun 2024 08:58:04 +0000 Subject: [PATCH 74/99] lxd/storage/backend_lxd: Use contexctual logger in CreateInstanceFromMigration Signed-off-by: Din Music --- lxd/storage/backend_lxd.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go index 1222e78dc48f..ebafe4447918 100644 --- a/lxd/storage/backend_lxd.go +++ b/lxd/storage/backend_lxd.go @@ -2126,10 +2126,10 @@ func (b *lxdBackend) CreateInstanceFromMigration(inst instance.Instance, conn io // This way if the volume being received is larger than the pool default size, the block volume created // will still be able to accommodate it. if args.VolumeSize > 0 && contentType == drivers.ContentTypeBlock { - b.logger.Debug("Setting volume size from offer header", logger.Ctx{"size": args.VolumeSize}) + l.Debug("Setting volume size from offer header", logger.Ctx{"size": args.VolumeSize}) args.Config["size"] = fmt.Sprintf("%d", args.VolumeSize) } else if args.Config["size"] != "" { - b.logger.Debug("Using volume size from root disk config", logger.Ctx{"size": args.Config["size"]}) + l.Debug("Using volume size from root disk config", logger.Ctx{"size": args.Config["size"]}) } var preFiller drivers.VolumeFiller From 25581820fbe2f4cbcfb963edff03dbfcf779d0e2 Mon Sep 17 00:00:00 2001 From: Din Music Date: Wed, 22 May 2024 11:28:24 +0000 Subject: [PATCH 75/99] lxd/migrate*: Add missing comments to exported functions Signed-off-by: Din Music --- lxd/migrate.go | 5 +++++ lxd/migrate_instance.go | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/lxd/migrate.go b/lxd/migrate.go index cd76b9ee3fee..0421a11c0345 100644 --- a/lxd/migrate.go +++ b/lxd/migrate.go @@ -144,6 +144,8 @@ type migrationSourceWs struct { pushSecrets map[string]string } +// Metadata returns a map where each key is a connection name and each value is +// the secret of the corresponding websocket connection. func (s *migrationSourceWs) Metadata() any { secrets := make(shared.Jmap, len(s.conns)) for connName, conn := range s.conns { @@ -153,6 +155,9 @@ func (s *migrationSourceWs) Metadata() any { return secrets } +// Connect handles an incoming HTTP request to establish a websocket connection for migration. +// It verifies the provided secret and matches it to the appropriate connection. If the secret +// is valid, it accepts the incoming connection. Otherwise, it returns an error. func (s *migrationSourceWs) Connect(op *operations.Operation, r *http.Request, w http.ResponseWriter) error { incomingSecret := r.FormValue("secret") if incomingSecret == "" { diff --git a/lxd/migrate_instance.go b/lxd/migrate_instance.go index 48b8fcc0a5c3..37ff44d035d8 100644 --- a/lxd/migrate_instance.go +++ b/lxd/migrate_instance.go @@ -81,6 +81,9 @@ func newMigrationSource(inst instance.Instance, stateful bool, instanceOnly bool return &ret, nil } +// Do performs the migration operation on the source side for the given state and +// operation. It sets up the necessary websocket connections for control, state, +// and filesystem, and then initiates the migration process. func (s *migrationSourceWs) Do(state *state.State, migrateOp *operations.Operation) error { l := logger.AddContext(logger.Ctx{"project": s.instance.Project().Name, "instance": s.instance.Name(), "live": s.live, "clusterMoveSourceName": s.clusterMoveSourceName, "push": s.pushOperationURL != ""}) @@ -206,6 +209,9 @@ func newMigrationSink(args *migrationSinkArgs) (*migrationSink, error) { return &sink, nil } +// Do performs the migration operation on the target side (sink) for the given +// state and instance operation. It sets up the necessary websocket connections +// for control, state, and filesystem, and then receives the migration data. func (c *migrationSink) Do(state *state.State, instOp *operationlock.InstanceOperation) error { l := logger.AddContext(logger.Ctx{"project": c.instance.Project().Name, "instance": c.instance.Name(), "live": c.live, "clusterMoveSourceName": c.clusterMoveSourceName, "push": c.push}) From 20e8c709cce59f29cd6e2a1b7680c39b108a24fc Mon Sep 17 00:00:00 2001 From: Din Music Date: Fri, 12 Jul 2024 16:10:51 +0000 Subject: [PATCH 76/99] lxd-migrate/main_migrate: Fix typo in volume size question Signed-off-by: Din Music --- lxd-migrate/main_migrate.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lxd-migrate/main_migrate.go b/lxd-migrate/main_migrate.go index 21d212c2f02a..76e3e2c433f4 100644 --- a/lxd-migrate/main_migrate.go +++ b/lxd-migrate/main_migrate.go @@ -71,7 +71,7 @@ func (c *cmdMigrateData) render() string { Mounts []string `yaml:"Mounts,omitempty"` Profiles []string `yaml:"Profiles,omitempty"` StoragePool string `yaml:"Storage pool,omitempty"` - StorageSize string `yaml:"Storage pool size,omitempty"` + StorageSize string `yaml:"Storage volume size,omitempty"` Network string `yaml:"Network name,omitempty"` Config map[string]string `yaml:"Config,omitempty"` }{ @@ -677,13 +677,13 @@ func (c *cmdMigrate) askStorage(server lxd.InstanceServer, config *cmdMigrateDat "path": "/", } - changeStorageSize, err := c.global.asker.AskBool("Do you want to change the storage size? [default=no]: ", "no") + changeStorageSize, err := c.global.asker.AskBool("Do you want to change the storage volume size? [default=no]: ", "no") if err != nil { return err } if changeStorageSize { - size, err := c.global.asker.AskString("Please specify the storage size: ", "", func(s string) error { + size, err := c.global.asker.AskString("Please specify the storage volume size: ", "", func(s string) error { _, err := units.ParseByteSizeString(s) return err }) From e3ac88a29de0a2f1f07f77610dc64e45f83c1b25 Mon Sep 17 00:00:00 2001 From: Din Music Date: Wed, 12 Jun 2024 08:39:49 +0000 Subject: [PATCH 77/99] lxd-migrate: Remove unused input argument Signed-off-by: Din Music --- lxd-migrate/main_migrate.go | 2 +- lxd-migrate/utils.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lxd-migrate/main_migrate.go b/lxd-migrate/main_migrate.go index 76e3e2c433f4..5413c763c227 100644 --- a/lxd-migrate/main_migrate.go +++ b/lxd-migrate/main_migrate.go @@ -586,7 +586,7 @@ func (c *cmdMigrate) run(cmd *cobra.Command, args []string) error { return err } - err = transferRootfs(ctx, server, op, fullPath, c.flagRsyncArgs, config.InstanceArgs.Type) + err = transferRootfs(ctx, op, fullPath, c.flagRsyncArgs, config.InstanceArgs.Type) if err != nil { return err } diff --git a/lxd-migrate/utils.go b/lxd-migrate/utils.go index 21be44156d75..353601b85626 100644 --- a/lxd-migrate/utils.go +++ b/lxd-migrate/utils.go @@ -24,7 +24,7 @@ import ( "github.com/canonical/lxd/shared/ws" ) -func transferRootfs(ctx context.Context, dst lxd.InstanceServer, op lxd.Operation, rootfs string, rsyncArgs string, instanceType api.InstanceType) error { +func transferRootfs(ctx context.Context, op lxd.Operation, rootfs string, rsyncArgs string, instanceType api.InstanceType) error { opAPI := op.Get() // Connect to the websockets From 981b838a409cbfb64e847e6e3f04fcc3e93d2bdc Mon Sep 17 00:00:00 2001 From: Din Music Date: Fri, 12 Jul 2024 12:49:40 +0000 Subject: [PATCH 78/99] lxd-migrate: Rename util function to transferRootDiskForMigration Signed-off-by: Din Music --- lxd-migrate/main_migrate.go | 2 +- lxd-migrate/utils.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lxd-migrate/main_migrate.go b/lxd-migrate/main_migrate.go index 5413c763c227..832b9ef1aa2a 100644 --- a/lxd-migrate/main_migrate.go +++ b/lxd-migrate/main_migrate.go @@ -586,7 +586,7 @@ func (c *cmdMigrate) run(cmd *cobra.Command, args []string) error { return err } - err = transferRootfs(ctx, op, fullPath, c.flagRsyncArgs, config.InstanceArgs.Type) + err = transferRootDiskForMigration(ctx, op, fullPath, c.flagRsyncArgs, config.InstanceArgs.Type) if err != nil { return err } diff --git a/lxd-migrate/utils.go b/lxd-migrate/utils.go index 353601b85626..9a6d10ad6abd 100644 --- a/lxd-migrate/utils.go +++ b/lxd-migrate/utils.go @@ -24,7 +24,7 @@ import ( "github.com/canonical/lxd/shared/ws" ) -func transferRootfs(ctx context.Context, op lxd.Operation, rootfs string, rsyncArgs string, instanceType api.InstanceType) error { +func transferRootDiskForMigration(ctx context.Context, op lxd.Operation, rootfs string, rsyncArgs string, instanceType api.InstanceType) error { opAPI := op.Get() // Connect to the websockets From 22bdb6398781f8ac8d7646bac1f98fa8420a00aa Mon Sep 17 00:00:00 2001 From: Din Music Date: Wed, 10 Jul 2024 12:23:44 +0000 Subject: [PATCH 79/99] lxd-migrate: Extract helper function for sending block volume Signed-off-by: Din Music --- lxd-migrate/transfer.go | 22 ++++++++++++++++++++++ lxd-migrate/utils.go | 23 +---------------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/lxd-migrate/transfer.go b/lxd-migrate/transfer.go index 4a0dbc245e05..ff05a7672fb3 100644 --- a/lxd-migrate/transfer.go +++ b/lxd-migrate/transfer.go @@ -131,6 +131,28 @@ func rsyncSendSetup(ctx context.Context, path string, rsyncArgs string, instance return cmd, conn, stderr, nil } +func sendBlockVol(ctx context.Context, conn io.WriteCloser, path string) error { + f, err := os.Open(path) + if err != nil { + return err + } + + defer func() { _ = f.Close() }() + + go func() { + <-ctx.Done() + _ = conn.Close() + _ = f.Close() + }() + + _, err = io.Copy(conn, f) + if err != nil { + return err + } + + return conn.Close() +} + func protoSendError(ws *websocket.Conn, err error) { migration.ProtoSendControl(ws, err) diff --git a/lxd-migrate/utils.go b/lxd-migrate/utils.go index 9a6d10ad6abd..9acaa154c417 100644 --- a/lxd-migrate/utils.go +++ b/lxd-migrate/utils.go @@ -6,7 +6,6 @@ import ( "crypto/x509" "encoding/pem" "fmt" - "io" "net/url" "os" "path/filepath" @@ -101,27 +100,7 @@ func transferRootDiskForMigration(ctx context.Context, op lxd.Operation, rootfs // Send block volume if instanceType == api.InstanceTypeVM { - f, err := os.Open(filepath.Join(rootfs, "root.img")) - if err != nil { - return abort(err) - } - - defer func() { _ = f.Close() }() - - conn := ws.NewWrapper(wsFs) - - go func() { - <-ctx.Done() - _ = conn.Close() - _ = f.Close() - }() - - _, err = io.Copy(conn, f) - if err != nil { - return abort(fmt.Errorf("Failed sending block volume: %w", err)) - } - - err = conn.Close() + err := sendBlockVol(ctx, ws.NewWrapper(wsFs), filepath.Join(rootfs, "root.img")) if err != nil { return abort(err) } From 1ad9f67b383dd5f5919eb96cae44fc0701323f66 Mon Sep 17 00:00:00 2001 From: Din Music Date: Thu, 4 Jul 2024 15:01:37 +0000 Subject: [PATCH 80/99] lxd-migrate/utils: Add helper function to determine whether the image is in raw format Signed-off-by: Din Music --- lxd-migrate/utils.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lxd-migrate/utils.go b/lxd-migrate/utils.go index 9acaa154c417..6c74ed4913cb 100644 --- a/lxd-migrate/utils.go +++ b/lxd-migrate/utils.go @@ -8,6 +8,7 @@ import ( "fmt" "net/url" "os" + "os/exec" "path/filepath" "reflect" "strings" @@ -331,3 +332,16 @@ func parseURL(URL string) (string, error) { return u.String(), nil } + +// isImageTypeRaw checks whether the file on a given path represents a disk, +// partition, or image in raw format. +func isImageTypeRaw(path string) (bool, error) { + cmd := exec.Command("file", "--brief", path) + out, err := cmd.Output() + if err != nil { + return false, fmt.Errorf("Failed to extract image file type: %v", err) + } + + isRaw := strings.HasPrefix(string(out), "DOS/MBR boot sector") || strings.HasPrefix(string(out), "block special") + return isRaw, nil +} From d02123dbc3a374d920c12b445ae111a64dad2a79 Mon Sep 17 00:00:00 2001 From: Din Music Date: Thu, 4 Jul 2024 15:03:44 +0000 Subject: [PATCH 81/99] lxd-migrate/main_migrate: Allow only raw VM image in migration mode Signed-off-by: Din Music --- lxd-migrate/main_migrate.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lxd-migrate/main_migrate.go b/lxd-migrate/main_migrate.go index 832b9ef1aa2a..9e0d56bbb36a 100644 --- a/lxd-migrate/main_migrate.go +++ b/lxd-migrate/main_migrate.go @@ -333,6 +333,17 @@ func (c *cmdMigrate) runInteractive(server lxd.InstanceServer) (cmdMigrateData, return errors.New("Path does not exist") } + if config.InstanceArgs.Type == api.InstanceTypeVM && config.InstanceArgs.Source.Type == "migration" { + isImageTypeRaw, err := isImageTypeRaw(config.SourcePath) + if err != nil { + return err + } + + if !isImageTypeRaw { + return fmt.Errorf(`Source disk format cannot be converted by server. Source disk should be in raw format`) + } + } + _, err := os.Stat(s) if err != nil { return err From 0b54540f2428b3e3ee591cbab4a5da1266c0f0ba Mon Sep 17 00:00:00 2001 From: Din Music Date: Fri, 12 Jul 2024 13:01:01 +0000 Subject: [PATCH 82/99] doc: Remove misleading tip in instance import doc Signed-off-by: Din Music --- doc/howto/import_machines_to_instances.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/doc/howto/import_machines_to_instances.md b/doc/howto/import_machines_to_instances.md index cbc83bfa52b3..52a3eb6beb67 100644 --- a/doc/howto/import_machines_to_instances.md +++ b/doc/howto/import_machines_to_instances.md @@ -69,11 +69,6 @@ Complete the following steps to migrate an existing machine to a LXD instance: The tool then asks you to provide the information required for the migration. - ```{tip} - As an alternative to running the tool interactively, you can provide the configuration as parameters to the command. - See `./bin.linux.lxd-migrate --help` for more information. - ``` - 1. Specify the LXD server URL, either as an IP address or as a DNS name. ```{note} From 0f9aed13740f2064ec3f54225c45030ac07a5552 Mon Sep 17 00:00:00 2001 From: Din Music Date: Fri, 12 Jul 2024 16:15:12 +0000 Subject: [PATCH 83/99] doc: Fix typo in questions within instance import doc Signed-off-by: Din Music --- doc/howto/import_machines_to_instances.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/howto/import_machines_to_instances.md b/doc/howto/import_machines_to_instances.md index 52a3eb6beb67..39046e25bc95 100644 --- a/doc/howto/import_machines_to_instances.md +++ b/doc/howto/import_machines_to_instances.md @@ -152,8 +152,8 @@ Complete the following steps to migrate an existing machine to a LXD instance: Please pick one of the options above [default=1]: 4 Please provide the storage pool to use: default - Do you want to change the storage size? [default=no]: yes - Please specify the storage size: 20GiB + Do you want to change the storage volume size? [default=no]: yes + Please specify the storage volume size: 20GiB Instance to be created: Name: foo @@ -161,7 +161,7 @@ Complete the following steps to migrate an existing machine to a LXD instance: Type: container Source: / Storage pool: default - Storage pool size: 20GiB + Storage volume size: 20GiB Config: limits.cpu: "2" @@ -181,7 +181,7 @@ Complete the following steps to migrate an existing machine to a LXD instance: Type: container Source: / Storage pool: default - Storage pool size: 20GiB + Storage volume size: 20GiB Network name: lxdbr0 Config: limits.cpu: "2" @@ -259,8 +259,8 @@ Complete the following steps to migrate an existing machine to a LXD instance: Please pick one of the options above [default=1]: 4 Please provide the storage pool to use: default - Do you want to change the storage size? [default=no]: yes - Please specify the storage size: 20GiB + Do you want to change the storage volume size? [default=no]: yes + Please specify the storage volume size: 20GiB Instance to be created: Name: foo @@ -268,7 +268,7 @@ Complete the following steps to migrate an existing machine to a LXD instance: Type: virtual-machine Source: ./virtual-machine.img Storage pool: default - Storage pool size: 20GiB + Storage volume size: 20GiB Config: limits.cpu: "2" security.secureboot: "false" @@ -289,7 +289,7 @@ Complete the following steps to migrate an existing machine to a LXD instance: Type: virtual-machine Source: ./virtual-machine.img Storage pool: default - Storage pool size: 20GiB + Storage volume size: 20GiB Network name: lxdbr0 Config: limits.cpu: "2" From c03d7f4f5ca713b11f2598bde140bcba5c238a24 Mon Sep 17 00:00:00 2001 From: Din Music Date: Thu, 13 Jun 2024 08:31:58 +0000 Subject: [PATCH 84/99] lxd/migrate: Fix migrationSinkArgs comment Signed-off-by: Din Music --- lxd/migrate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lxd/migrate.go b/lxd/migrate.go index 0421a11c0345..4c32ee876afa 100644 --- a/lxd/migrate.go +++ b/lxd/migrate.go @@ -191,7 +191,7 @@ type migrationSink struct { refresh bool } -// MigrationSinkArgs arguments to configure migration sink. +// migrationSinkArgs arguments to configure migration sink. type migrationSinkArgs struct { // General migration fields Dialer *websocket.Dialer From ce343143a3a6770471951293e534062cfdc3ea46 Mon Sep 17 00:00:00 2001 From: Din Music Date: Thu, 18 Jul 2024 07:49:20 +0000 Subject: [PATCH 85/99] lxd/migrate: Remove unused migrations sink args Signed-off-by: Din Music --- lxd/migrate.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/lxd/migrate.go b/lxd/migrate.go index 4c32ee876afa..ec2a7e189ec5 100644 --- a/lxd/migrate.go +++ b/lxd/migrate.go @@ -15,7 +15,6 @@ import ( "github.com/gorilla/websocket" "google.golang.org/protobuf/proto" - "github.com/canonical/lxd/lxd/idmap" "github.com/canonical/lxd/lxd/instance" "github.com/canonical/lxd/lxd/migration" "github.com/canonical/lxd/lxd/operations" @@ -202,7 +201,6 @@ type migrationSinkArgs struct { // Instance specific fields Instance instance.Instance InstanceOnly bool - Idmap *idmap.IdmapSet Live bool Refresh bool ClusterMoveSourceName string @@ -210,7 +208,6 @@ type migrationSinkArgs struct { // Storage specific fields VolumeOnly bool - VolumeSize int64 // Transport specific fields RsyncFeatures []string From a85d22685b82a50dbf2933e07f7e0a99dcdd166f Mon Sep 17 00:00:00 2001 From: Din Music Date: Thu, 13 Jun 2024 08:34:03 +0000 Subject: [PATCH 86/99] lxd/*: Make fields of migrationSinkArgs structure private Signed-off-by: Din Music --- lxd/instances_post.go | 18 +++++++++--------- lxd/migrate.go | 28 ++++++++++++++-------------- lxd/migrate_instance.go | 20 ++++++++++---------- lxd/migrate_storage_volumes.go | 32 ++++++++++++++++---------------- lxd/storage_volumes.go | 12 ++++++------ 5 files changed, 55 insertions(+), 55 deletions(-) diff --git a/lxd/instances_post.go b/lxd/instances_post.go index ec1c97c723c0..67b0dad1b371 100644 --- a/lxd/instances_post.go +++ b/lxd/instances_post.go @@ -354,15 +354,15 @@ func createFromMigration(s *state.State, r *http.Request, projectName string, pr } migrationArgs := migrationSinkArgs{ - URL: req.Source.Operation, - Dialer: dialer, - Instance: inst, - Secrets: req.Source.Websockets, - Push: push, - Live: req.Source.Live, - InstanceOnly: instanceOnly, - ClusterMoveSourceName: clusterMoveSourceName, - Refresh: req.Source.Refresh, + url: req.Source.Operation, + dialer: dialer, + instance: inst, + secrets: req.Source.Websockets, + push: push, + live: req.Source.Live, + instanceOnly: instanceOnly, + clusterMoveSourceName: clusterMoveSourceName, + refresh: req.Source.Refresh, } sink, err := newMigrationSink(&migrationArgs) diff --git a/lxd/migrate.go b/lxd/migrate.go index ec2a7e189ec5..e85936e86b9b 100644 --- a/lxd/migrate.go +++ b/lxd/migrate.go @@ -193,24 +193,24 @@ type migrationSink struct { // migrationSinkArgs arguments to configure migration sink. type migrationSinkArgs struct { // General migration fields - Dialer *websocket.Dialer - Push bool - Secrets map[string]string - URL string - - // Instance specific fields - Instance instance.Instance - InstanceOnly bool - Live bool - Refresh bool - ClusterMoveSourceName string - Snapshots []*migration.Snapshot + dialer *websocket.Dialer + push bool + secrets map[string]string + url string + + // instance specific fields + instance instance.Instance + instanceOnly bool + live bool + refresh bool + clusterMoveSourceName string + snapshots []*migration.Snapshot // Storage specific fields - VolumeOnly bool + volumeOnly bool // Transport specific fields - RsyncFeatures []string + rsyncFeatures []string } // Metadata returns metadata for the migration sink. diff --git a/lxd/migrate_instance.go b/lxd/migrate_instance.go index 37ff44d035d8..6a197ffd079f 100644 --- a/lxd/migrate_instance.go +++ b/lxd/migrate_instance.go @@ -161,14 +161,14 @@ func (s *migrationSourceWs) Do(state *state.State, migrateOp *operations.Operati func newMigrationSink(args *migrationSinkArgs) (*migrationSink, error) { sink := migrationSink{ migrationFields: migrationFields{ - instance: args.Instance, - instanceOnly: args.InstanceOnly, - live: args.Live, + instance: args.instance, + instanceOnly: args.instanceOnly, + live: args.live, }, - url: args.URL, - clusterMoveSourceName: args.ClusterMoveSourceName, - push: args.Push, - refresh: args.Refresh, + url: args.url, + clusterMoveSourceName: args.clusterMoveSourceName, + push: args.push, + refresh: args.refresh, } secretNames := []string{api.SecretNameControl, api.SecretNameFilesystem} @@ -186,16 +186,16 @@ func newMigrationSink(args *migrationSinkArgs) (*migrationSink, error) { sink.conns = make(map[string]*migrationConn, len(secretNames)) for _, connName := range secretNames { if !sink.push { - if args.Secrets[connName] == "" { + if args.secrets[connName] == "" { return nil, fmt.Errorf("Expected %q connection secret missing from migration sink target request", connName) } - u, err := url.Parse(fmt.Sprintf("wss://%s/websocket", strings.TrimPrefix(args.URL, "https://"))) + u, err := url.Parse(fmt.Sprintf("wss://%s/websocket", strings.TrimPrefix(args.url, "https://"))) if err != nil { return nil, fmt.Errorf("Failed parsing websocket URL for migration sink %q connection: %w", connName, err) } - sink.conns[connName] = newMigrationConn(args.Secrets[connName], args.Dialer, u) + sink.conns[connName] = newMigrationConn(args.secrets[connName], args.dialer, u) } else { secret, err := shared.RandomCryptoString() if err != nil { diff --git a/lxd/migrate_storage_volumes.go b/lxd/migrate_storage_volumes.go index f09316ddf45d..7f1f8ed36483 100644 --- a/lxd/migrate_storage_volumes.go +++ b/lxd/migrate_storage_volumes.go @@ -207,27 +207,27 @@ func (s *migrationSourceWs) DoStorage(state *state.State, projectName string, po func newStorageMigrationSink(args *migrationSinkArgs) (*migrationSink, error) { sink := migrationSink{ migrationFields: migrationFields{ - volumeOnly: args.VolumeOnly, + volumeOnly: args.volumeOnly, }, - url: args.URL, - push: args.Push, - refresh: args.Refresh, + url: args.url, + push: args.push, + refresh: args.refresh, } secretNames := []string{api.SecretNameControl, api.SecretNameFilesystem} sink.conns = make(map[string]*migrationConn, len(secretNames)) for _, connName := range secretNames { if !sink.push { - if args.Secrets[connName] == "" { + if args.secrets[connName] == "" { return nil, fmt.Errorf("Expected %q connection secret missing from migration sink target request", connName) } - u, err := url.Parse(fmt.Sprintf("wss://%s/websocket", strings.TrimPrefix(args.URL, "https://"))) + u, err := url.Parse(fmt.Sprintf("wss://%s/websocket", strings.TrimPrefix(args.url, "https://"))) if err != nil { return nil, fmt.Errorf("Failed parsing websocket URL for migration sink %q connection: %w", connName, err) } - sink.conns[connName] = newMigrationConn(args.Secrets[connName], args.Dialer, u) + sink.conns[connName] = newMigrationConn(args.secrets[connName], args.dialer, u) } else { secret, err := shared.RandomCryptoString() if err != nil { @@ -331,15 +331,15 @@ func (c *migrationSink) DoStorage(state *state.State, projectName string, poolNa MigrationType: respTypes[0], TrackProgress: true, ContentType: req.ContentType, - Refresh: args.Refresh, - VolumeOnly: args.VolumeOnly, + Refresh: args.refresh, + VolumeOnly: args.volumeOnly, } // A zero length Snapshots slice indicates volume only migration in // VolumeTargetArgs. So if VoluneOnly was requested, do not populate them. - if !args.VolumeOnly { - volTargetArgs.Snapshots = make([]string, 0, len(args.Snapshots)) - for _, snap := range args.Snapshots { + if !args.volumeOnly { + volTargetArgs.Snapshots = make([]string, 0, len(args.snapshots)) + for _, snap := range args.snapshots { volTargetArgs.Snapshots = append(volTargetArgs.Snapshots, *snap.Name) } } @@ -428,10 +428,10 @@ func (c *migrationSink) DoStorage(state *state.State, projectName string, poolNa // as part of MigrationSinkArgs below. rsyncFeatures := respHeader.GetRsyncFeaturesSlice() args := migrationSinkArgs{ - RsyncFeatures: rsyncFeatures, - Snapshots: respHeader.Snapshots, - VolumeOnly: c.volumeOnly, - Refresh: c.refresh, + rsyncFeatures: rsyncFeatures, + snapshots: respHeader.Snapshots, + volumeOnly: c.volumeOnly, + refresh: c.refresh, } fsConn, err := c.conns[api.SecretNameFilesystem].WebsocketIO(context.TODO()) diff --git a/lxd/storage_volumes.go b/lxd/storage_volumes.go index 37dcf965c771..57cfd91705a2 100644 --- a/lxd/storage_volumes.go +++ b/lxd/storage_volumes.go @@ -1221,16 +1221,16 @@ func doVolumeMigration(s *state.State, r *http.Request, requestProjectName strin // Initialise migrationArgs, don't set the Storage property yet, this is done in DoStorage, // to avoid this function relying on the legacy storage layer. migrationArgs := migrationSinkArgs{ - URL: req.Source.Operation, - Dialer: &websocket.Dialer{ + url: req.Source.Operation, + dialer: &websocket.Dialer{ TLSClientConfig: config, NetDialContext: shared.RFC3493Dialer, HandshakeTimeout: time.Second * 5, }, - Secrets: req.Source.Websockets, - Push: push, - VolumeOnly: req.Source.VolumeOnly, - Refresh: req.Source.Refresh, + secrets: req.Source.Websockets, + push: push, + volumeOnly: req.Source.VolumeOnly, + refresh: req.Source.Refresh, } sink, err := newStorageMigrationSink(&migrationArgs) From c64ff251750466ead8e48a321f9a24666426cec4 Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Thu, 18 Jul 2024 17:33:16 +0100 Subject: [PATCH 87/99] lxd/db/openfga: Add an exception for type-bound public access in `ReadStartingWithUser` Previously this method expected there to be only one user filter. OpenFGA is now also passing in a filter for a type-bound public access where applicable in the model. Here I have performed the same optimisation is in `ReadUsersetTuples` to handle the case. Signed-off-by: Mark Laing Signed-off-by: hamistao --- lxd/db/openfga/openfga.go | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/lxd/db/openfga/openfga.go b/lxd/db/openfga/openfga.go index eeada05d3b01..7dc604a40cf3 100644 --- a/lxd/db/openfga/openfga.go +++ b/lxd/db/openfga/openfga.go @@ -310,7 +310,9 @@ WHERE auth_groups_permissions.entitlement = ? AND auth_groups_permissions.entity // 3. Listing objects that a group is related to via an entitlement. // 4. Listing objects that an identity is related to via an entitlement. // -// - The UserFilter field of storage.ReadStartingWithUserFilter always has length 1. +// - The UserFilter field of storage.ReadStartingWithUserFilter usually has length 1. Sometimes there is another user +// when a type-bound public access is used (e.g. `identity:*`). We return early if this is the case, as we don't need +// to call the database. // // Implementation: // - For the first two cases we can perform a simple lookup for entities of the requested type (with project name if project relation). @@ -356,16 +358,30 @@ func (o *openfgaStore) ReadStartingWithUser(ctx context.Context, store string, f return nil, api.StatusErrorf(http.StatusBadRequest, "ReadStartingWithUser: Must provide relation") } - // Expect that there will be exactly one user filter. - if len(filter.UserFilter) != 1 { - return nil, fmt.Errorf("ReadStartingWithUser: Unexpected user filter list length") - } - - // Validate the entity type. entityType := entity.Type(filter.ObjectType) err := entityType.Validate() if err != nil { - return nil, fmt.Errorf("ReadStartingWithUser: Invalid entity type %q: %w", entityType, err) + return nil, fmt.Errorf("ReadUsersetTuples: Invalid object filter %q: %w", entityType, err) + } + + // Check for type-bound public access exception. + if entityType == entity.TypeServer && filter.Relation == "can_view" { + return storage.NewStaticTupleIterator([]*openfgav1.Tuple{ + // Only returning one tuple here for the identity. When adding service accounts, we'll need + // to add another tuple to account for them. + { + Key: &openfgav1.TupleKey{ + Object: fmt.Sprintf("%s:%s", entity.TypeServer, entity.ServerURL().String()), + Relation: string(auth.EntitlementCanView), + User: fmt.Sprintf("%s:*", entity.TypeIdentity), + }, + }, + }), nil + } + + // Expect that there will be exactly one user filter when not dealing with a type-bound public access. + if len(filter.UserFilter) != 1 { + return nil, fmt.Errorf("ReadStartingWithUser: Unexpected user filter list length") } // Expect that the user filter object has an entity type and a URL. From 6809de87b67d60f9f9a195e48e5ccf87325aa74c Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Thu, 18 Jul 2024 17:33:16 +0100 Subject: [PATCH 88/99] lxd/db/openfga: Switch raw string for `auth.EntitlementCanView` Previously this method expected there to be only one user filter. OpenFGA is now also passing in a filter for a type-bound public access where applicable in the model. Here I have performed the same optimisation is in `ReadUsersetTuples` to handle the case. Signed-off-by: Mark Laing Signed-off-by: hamistao --- lxd/db/openfga/openfga.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lxd/db/openfga/openfga.go b/lxd/db/openfga/openfga.go index 7dc604a40cf3..3e223fe07e5b 100644 --- a/lxd/db/openfga/openfga.go +++ b/lxd/db/openfga/openfga.go @@ -365,7 +365,7 @@ func (o *openfgaStore) ReadStartingWithUser(ctx context.Context, store string, f } // Check for type-bound public access exception. - if entityType == entity.TypeServer && filter.Relation == "can_view" { + if entityType == entity.TypeServer && filter.Relation == string(auth.EntitlementCanView) { return storage.NewStaticTupleIterator([]*openfgav1.Tuple{ // Only returning one tuple here for the identity. When adding service accounts, we'll need // to add another tuple to account for them. From 9533cc5b7207d1f9ed1360e2102928ef62c7b94c Mon Sep 17 00:00:00 2001 From: hamistao Date: Fri, 19 Jul 2024 10:57:38 -0300 Subject: [PATCH 89/99] lxd/db/openfga: Lint fix Signed-off-by: hamistao --- lxd/db/openfga/openfga.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lxd/db/openfga/openfga.go b/lxd/db/openfga/openfga.go index 3e223fe07e5b..5227a8cca03b 100644 --- a/lxd/db/openfga/openfga.go +++ b/lxd/db/openfga/openfga.go @@ -305,9 +305,13 @@ WHERE auth_groups_permissions.entitlement = ? AND auth_groups_permissions.entity // Observations: // // - This method appears to be called in four scenarios: +// // 1. Listing objects related to the server object via `server`. +// // 2. Listing objects related to project objects via `project`. +// // 3. Listing objects that a group is related to via an entitlement. +// // 4. Listing objects that an identity is related to via an entitlement. // // - The UserFilter field of storage.ReadStartingWithUserFilter usually has length 1. Sometimes there is another user From 93beb535fabb974cb3c7ff154e8421c0b6c74883 Mon Sep 17 00:00:00 2001 From: hamistao Date: Fri, 19 Jul 2024 18:12:06 -0300 Subject: [PATCH 90/99] test/godeps: Update lxd-agent dependency list Signed-off-by: hamistao --- test/godeps/lxd-agent.list | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/godeps/lxd-agent.list b/test/godeps/lxd-agent.list index 795beb686a30..137b7543a2e3 100644 --- a/test/godeps/lxd-agent.list +++ b/test/godeps/lxd-agent.list @@ -241,6 +241,7 @@ google.golang.org/grpc/backoff google.golang.org/grpc/balancer google.golang.org/grpc/balancer/base google.golang.org/grpc/balancer/grpclb/state +google.golang.org/grpc/balancer/pickfirst google.golang.org/grpc/balancer/roundrobin google.golang.org/grpc/binarylog/grpc_binarylog_v1 google.golang.org/grpc/channelz @@ -261,7 +262,6 @@ google.golang.org/grpc/internal/channelz google.golang.org/grpc/internal/credentials google.golang.org/grpc/internal/envconfig google.golang.org/grpc/internal/grpclog -google.golang.org/grpc/internal/grpcrand google.golang.org/grpc/internal/grpcsync google.golang.org/grpc/internal/grpcutil google.golang.org/grpc/internal/idle From 26502d4dbd8dc23ef16c75c20364f5b214d23648 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 02:24:23 +0000 Subject: [PATCH 91/99] fix(deps): update golang.org/x/exp digest to 8a7402a --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index ef4bd1db98c2..65a4627d7053 100644 --- a/go.mod +++ b/go.mod @@ -54,7 +54,7 @@ require ( go.starlark.net v0.0.0-20240520160348-046347dcd104 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.25.0 - golang.org/x/exp v0.0.0-20240707233637-46b078467d37 + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/oauth2 v0.21.0 golang.org/x/sync v0.7.0 golang.org/x/sys v0.22.0 diff --git a/go.sum b/go.sum index 3ec3f44afe7d..e6067ca6d030 100644 --- a/go.sum +++ b/go.sum @@ -753,6 +753,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= From fce1dda6c780ad5ca118b172c02edbcb78c15cb6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 02:24:32 +0000 Subject: [PATCH 92/99] fix(deps): update module github.com/minio/minio-go/v7 to v7.0.74 --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 65a4627d7053..c1b23860a1a6 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/mdlayher/netx v0.0.0-20230430222610-7e21880baee8 github.com/mdlayher/vsock v1.2.1 github.com/miekg/dns v1.1.61 - github.com/minio/minio-go/v7 v7.0.73 + github.com/minio/minio-go/v7 v7.0.74 github.com/mitchellh/mapstructure v1.5.0 github.com/oklog/ulid/v2 v2.1.0 github.com/olekukonko/tablewriter v0.0.5 diff --git a/go.sum b/go.sum index e6067ca6d030..4dabac2f8484 100644 --- a/go.sum +++ b/go.sum @@ -494,6 +494,8 @@ github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.0.73 h1:qr2vi96Qm7kZ4v7LLebjte+MQh621fFWnv93p12htEo= github.com/minio/minio-go/v7 v7.0.73/go.mod h1:qydcVzV8Hqtj1VtEocfxbmVFa2siu6HGa+LDEPogjD8= +github.com/minio/minio-go/v7 v7.0.74 h1:fTo/XlPBTSpo3BAMshlwKL5RspXRv9us5UeHEGYCFe0= +github.com/minio/minio-go/v7 v7.0.74/go.mod h1:qydcVzV8Hqtj1VtEocfxbmVFa2siu6HGa+LDEPogjD8= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= From 5a82f572f6fe0d544b62891a635884f06040548b Mon Sep 17 00:00:00 2001 From: JohnHammell Date: Sun, 21 Jul 2024 22:28:49 -0400 Subject: [PATCH 93/99] Update metrics.md to remove symlink trailing slash Tiny fix. Trailing slash in the symbolic link probably should not be there. When I run the command: "ln -s /var/snap/prometheus/common/tls/ /var/snap/prometheus/current/tls/" The result is: "ln: failed to create symbolic link '/var/snap/prometheus/current/tls/': No such file or directory" Removing the trailing slash from the symbolic link command corrects this and creates the sym link. Signed-off-by: JohnHammell --- doc/metrics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/metrics.md b/doc/metrics.md index 0462030d59cb..3f0dfb9a98cc 100644 --- a/doc/metrics.md +++ b/doc/metrics.md @@ -124,7 +124,7 @@ cp /var/snap/lxd/common/lxd/server.crt /var/snap/prometheus/common/tls/ # Create a symbolic link pointing to tls directory that you created # https://bugs.launchpad.net/prometheus-snap/+bug/2066910 -ln -s /var/snap/prometheus/common/tls/ /var/snap/prometheus/current/tls/ +ln -s /var/snap/prometheus/common/tls/ /var/snap/prometheus/current/tls ``` If you are not using the snap, you must also make sure that Prometheus can read these files (usually, Prometheus is run as user `prometheus`): From 94750cb20a97e60efbb6e72c4868b653e80ba9d2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 03:25:58 +0000 Subject: [PATCH 94/99] fix(deps): update module github.com/pkg/xattr to v0.4.10 --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c1b23860a1a6..3b246d9fdb8a 100644 --- a/go.mod +++ b/go.mod @@ -43,7 +43,7 @@ require ( github.com/openfga/openfga v1.5.5 github.com/osrg/gobgp/v3 v3.28.0 github.com/pkg/sftp v1.13.6 - github.com/pkg/xattr v0.4.9 + github.com/pkg/xattr v0.4.10 github.com/robfig/cron/v3 v3.0.1 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.1 diff --git a/go.sum b/go.sum index 4dabac2f8484..8f866243f8d5 100644 --- a/go.sum +++ b/go.sum @@ -562,6 +562,8 @@ github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE= github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= +github.com/pkg/xattr v0.4.10 h1:Qe0mtiNFHQZ296vRgUjRCoPHPqH7VdTOrZx3g0T+pGA= +github.com/pkg/xattr v0.4.10/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= From 4d0c3b87cdfe0548c40afcb50ee38acf2d59ba94 Mon Sep 17 00:00:00 2001 From: Thomas Parrott Date: Mon, 22 Jul 2024 10:29:55 +0100 Subject: [PATCH 95/99] lxd/device/device/utils/disk: Update DiskVMVirtiofsdStart to use chroot sandbox mode on pre pidfd_open kernels Otherwise virtiofsd won't start on Ubuntu 18.04 generic kernels (4.15). Signed-off-by: Thomas Parrott --- lxd/device/device_utils_disk.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lxd/device/device_utils_disk.go b/lxd/device/device_utils_disk.go index 2b70ad05b995..c088f68f6585 100644 --- a/lxd/device/device_utils_disk.go +++ b/lxd/device/device_utils_disk.go @@ -21,6 +21,7 @@ import ( "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/osarch" "github.com/canonical/lxd/shared/revert" + "github.com/canonical/lxd/shared/version" ) // RBDFormatPrefix is the prefix used in disk paths to identify RBD. @@ -424,7 +425,7 @@ func DiskVMVirtfsProxyStop(pidPath string) error { // Returns UnsupportedError error if the host system or instance does not support virtiosfd, returns normal error // type if process cannot be started for other reasons. // Returns revert function and listener file handle on success. -func DiskVMVirtiofsdStart(execPath string, inst instance.Instance, socketPath string, pidPath string, logPath string, sharePath string, idmaps []idmap.IdmapEntry) (func(), net.Listener, error) { +func DiskVMVirtiofsdStart(kernelVersion version.DottedVersion, execPath string, inst instance.Instance, socketPath string, pidPath string, logPath string, sharePath string, idmaps []idmap.IdmapEntry) (func(), net.Listener, error) { revert := revert.New() defer revert.Fail() @@ -502,7 +503,18 @@ func DiskVMVirtiofsdStart(execPath string, inst instance.Instance, socketPath st defer func() { _ = unixFile.Close() }() // Start the virtiofsd process in non-daemon mode. - args := []string{"--fd=3", "-o", fmt.Sprintf("source=%s", sharePath)} + args := []string{ + "--fd=3", + "-o", fmt.Sprintf("source=%s", sharePath), + } + + // Virtiofsd defaults to namespace sandbox mode which requires pidfd_open support. + // This was added in Linux 5.3, so if running an earlier kernel fallback to chroot sandbox mode. + minVer, _ := version.NewDottedVersion("5.3.0") + if kernelVersion.Compare(minVer) < 0 { + args = append(args, "--sandbox=chroot") + } + proc, err := subprocess.NewProcess(cmd, args, logPath, logPath) if err != nil { return nil, nil, err From 7a68c44000642b5819ad56c3980c6257dec83cca Mon Sep 17 00:00:00 2001 From: Thomas Parrott Date: Mon, 22 Jul 2024 10:30:49 +0100 Subject: [PATCH 96/99] lxd: DiskVMVirtiofsdStart usage Signed-off-by: Thomas Parrott --- lxd/device/disk.go | 2 +- lxd/instance/drivers/driver_qemu.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lxd/device/disk.go b/lxd/device/disk.go index b174a2d999bd..a5b6503330eb 100644 --- a/lxd/device/disk.go +++ b/lxd/device/disk.go @@ -1118,7 +1118,7 @@ func (d *disk) startVM() (*deviceConfig.RunConfig, error) { logPath := filepath.Join(d.inst.LogPath(), fmt.Sprintf("disk.%s.log", filesystem.PathNameEncode(d.name))) _ = os.Remove(logPath) // Remove old log if needed. - revertFunc, unixListener, err := DiskVMVirtiofsdStart(d.state.OS.ExecPath, d.inst, sockPath, pidPath, logPath, mount.DevPath, rawIDMaps) + revertFunc, unixListener, err := DiskVMVirtiofsdStart(d.state.OS.KernelVersion, d.state.OS.ExecPath, d.inst, sockPath, pidPath, logPath, mount.DevPath, rawIDMaps) if err != nil { var errUnsupported UnsupportedError if errors.As(err, &errUnsupported) { diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index 27d2c08d0799..294a2371db13 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -1388,7 +1388,7 @@ func (d *qemu) start(stateful bool, op *operationlock.InstanceOperation) error { // This is used by the lxd-agent in preference to 9p (due to its improved performance) and in scenarios // where 9p isn't available in the VM guest OS. configSockPath, configPIDPath := d.configVirtiofsdPaths() - revertFunc, unixListener, err := device.DiskVMVirtiofsdStart(d.state.OS.ExecPath, d, configSockPath, configPIDPath, "", configMntPath, nil) + revertFunc, unixListener, err := device.DiskVMVirtiofsdStart(d.state.OS.KernelVersion, d.state.OS.ExecPath, d, configSockPath, configPIDPath, "", configMntPath, nil) if err != nil { var errUnsupported device.UnsupportedError if !errors.As(err, &errUnsupported) { From c1d82436a1168f1243f992f5ff40941bd5d7c35b Mon Sep 17 00:00:00 2001 From: Thomas Parrott Date: Mon, 22 Jul 2024 10:32:24 +0100 Subject: [PATCH 97/99] lxd/device/device/utils/disk: Removes unused execPath arg from DiskVMVirtiofsdStart Signed-off-by: Thomas Parrott --- lxd/device/device_utils_disk.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lxd/device/device_utils_disk.go b/lxd/device/device_utils_disk.go index c088f68f6585..c9bdd53712d7 100644 --- a/lxd/device/device_utils_disk.go +++ b/lxd/device/device_utils_disk.go @@ -425,7 +425,7 @@ func DiskVMVirtfsProxyStop(pidPath string) error { // Returns UnsupportedError error if the host system or instance does not support virtiosfd, returns normal error // type if process cannot be started for other reasons. // Returns revert function and listener file handle on success. -func DiskVMVirtiofsdStart(kernelVersion version.DottedVersion, execPath string, inst instance.Instance, socketPath string, pidPath string, logPath string, sharePath string, idmaps []idmap.IdmapEntry) (func(), net.Listener, error) { +func DiskVMVirtiofsdStart(kernelVersion version.DottedVersion, inst instance.Instance, socketPath string, pidPath string, logPath string, sharePath string, idmaps []idmap.IdmapEntry) (func(), net.Listener, error) { revert := revert.New() defer revert.Fail() From b5b39070130085be44180dca938c1e536eafd849 Mon Sep 17 00:00:00 2001 From: Thomas Parrott Date: Mon, 22 Jul 2024 10:32:43 +0100 Subject: [PATCH 98/99] lxd: DiskVMVirtiofsdStart usage Signed-off-by: Thomas Parrott --- lxd/device/disk.go | 2 +- lxd/instance/drivers/driver_qemu.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lxd/device/disk.go b/lxd/device/disk.go index a5b6503330eb..da61a882acf4 100644 --- a/lxd/device/disk.go +++ b/lxd/device/disk.go @@ -1118,7 +1118,7 @@ func (d *disk) startVM() (*deviceConfig.RunConfig, error) { logPath := filepath.Join(d.inst.LogPath(), fmt.Sprintf("disk.%s.log", filesystem.PathNameEncode(d.name))) _ = os.Remove(logPath) // Remove old log if needed. - revertFunc, unixListener, err := DiskVMVirtiofsdStart(d.state.OS.KernelVersion, d.state.OS.ExecPath, d.inst, sockPath, pidPath, logPath, mount.DevPath, rawIDMaps) + revertFunc, unixListener, err := DiskVMVirtiofsdStart(d.state.OS.KernelVersion, d.inst, sockPath, pidPath, logPath, mount.DevPath, rawIDMaps) if err != nil { var errUnsupported UnsupportedError if errors.As(err, &errUnsupported) { diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index 294a2371db13..ca56d180fc96 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -1388,7 +1388,7 @@ func (d *qemu) start(stateful bool, op *operationlock.InstanceOperation) error { // This is used by the lxd-agent in preference to 9p (due to its improved performance) and in scenarios // where 9p isn't available in the VM guest OS. configSockPath, configPIDPath := d.configVirtiofsdPaths() - revertFunc, unixListener, err := device.DiskVMVirtiofsdStart(d.state.OS.KernelVersion, d.state.OS.ExecPath, d, configSockPath, configPIDPath, "", configMntPath, nil) + revertFunc, unixListener, err := device.DiskVMVirtiofsdStart(d.state.OS.KernelVersion, d, configSockPath, configPIDPath, "", configMntPath, nil) if err != nil { var errUnsupported device.UnsupportedError if !errors.As(err, &errUnsupported) { From ea9b2e6af71d5db67ecf68544a83bca1eae3b268 Mon Sep 17 00:00:00 2001 From: Thomas Parrott Date: Mon, 22 Jul 2024 11:16:55 +0100 Subject: [PATCH 99/99] gomod: Dependency updates Signed-off-by: Thomas Parrott --- go.mod | 27 +++++++++++++------------- go.sum | 60 +++++++++++++++++++++++++++------------------------------- 2 files changed, 42 insertions(+), 45 deletions(-) diff --git a/go.mod b/go.mod index 3b246d9fdb8a..6d965231be35 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/canonical/lxd -go 1.22.4 +go 1.22.5 require ( github.com/Rican7/retry v0.3.1 @@ -38,9 +38,9 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/oklog/ulid/v2 v2.1.0 github.com/olekukonko/tablewriter v0.0.5 - github.com/openfga/api/proto v0.0.0-20240612172407-db6f98774490 + github.com/openfga/api/proto v0.0.0-20240722084519-a9261bb50796 github.com/openfga/language/pkg/go v0.2.0-beta.0 - github.com/openfga/openfga v1.5.5 + github.com/openfga/openfga v1.5.6 github.com/osrg/gobgp/v3 v3.28.0 github.com/pkg/sftp v1.13.6 github.com/pkg/xattr v0.4.10 @@ -51,7 +51,7 @@ require ( github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 github.com/vishvananda/netlink v1.2.1-beta.2 github.com/zitadel/oidc/v3 v3.26.0 - go.starlark.net v0.0.0-20240520160348-046347dcd104 + go.starlark.net v0.0.0-20240705175910-70002002b310 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.25.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 @@ -75,10 +75,11 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect - github.com/digitalocean/go-libvirt v0.0.0-20240610184155-f66fb3c0f6d7 // indirect + github.com/digitalocean/go-libvirt v0.0.0-20240709142323-d8406205c752 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/eapache/channels v1.1.0 // indirect github.com/eapache/queue v1.1.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-ini/ini v1.67.0 // indirect @@ -114,13 +115,14 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/muhlemmer/gu v0.3.1 // indirect github.com/muhlemmer/httpforwarded v0.1.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/natefinch/wrap v0.2.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.19.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.54.0 // indirect + github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rs/cors v1.11.0 // indirect @@ -139,20 +141,19 @@ require ( github.com/zitadel/logging v0.6.0 // indirect github.com/zitadel/schema v1.3.0 // indirect go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/sdk v1.27.0 // indirect + go.opentelemetry.io/otel/sdk v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect - go.uber.org/mock v0.4.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.19.0 // indirect golang.org/x/net v0.27.0 // indirect golang.org/x/tools v0.23.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect - google.golang.org/grpc v1.64.1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d // indirect + google.golang.org/grpc v1.65.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 8f866243f8d5..900f9427ef76 100644 --- a/go.sum +++ b/go.sum @@ -108,8 +108,8 @@ github.com/dell/goscaleio v1.15.0 h1:DzI1ZlQhdIR+V4AKGOMwz1Viu2bAtj3N6kTyixB0Qg8 github.com/dell/goscaleio v1.15.0/go.mod h1:h7SCmReARG/szFWBMQGETGkZObknhS45lQipQbtdmJ8= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/digitalocean/go-libvirt v0.0.0-20240610184155-f66fb3c0f6d7 h1:KMOLn19gbh7KbPEgu76ZIf/b2CnnYhC2GFLgLiN/YkA= -github.com/digitalocean/go-libvirt v0.0.0-20240610184155-f66fb3c0f6d7/go.mod h1:DMUPOdO9OHCbF88MGmFNUnCBH9GLjeHl2RaA49Vy3vo= +github.com/digitalocean/go-libvirt v0.0.0-20240709142323-d8406205c752 h1:NI7XEcHzWVvBfVjSVK6Qk4wmrUfoyQxCNpBjrHelZFk= +github.com/digitalocean/go-libvirt v0.0.0-20240709142323-d8406205c752/go.mod h1:/Ok8PA2qi/ve0Py38+oL+VxoYmlowigYRyLEODRYdgc= github.com/digitalocean/go-qemu v0.0.0-20230711162256-2e3d0186973e h1:x5PInTuXLddHWHlePCNAcM8QtUfOGx44f3UmYPMtDcI= github.com/digitalocean/go-qemu v0.0.0-20230711162256-2e3d0186973e/go.mod h1:K4+o74YGNjOb9N6yyG+LPj1NjHtk+Qz0IYQPvirbaLs= github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q= @@ -130,6 +130,8 @@ github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4 github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -183,8 +185,8 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -492,8 +494,6 @@ github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs= github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.0.73 h1:qr2vi96Qm7kZ4v7LLebjte+MQh621fFWnv93p12htEo= -github.com/minio/minio-go/v7 v7.0.73/go.mod h1:qydcVzV8Hqtj1VtEocfxbmVFa2siu6HGa+LDEPogjD8= github.com/minio/minio-go/v7 v7.0.74 h1:fTo/XlPBTSpo3BAMshlwKL5RspXRv9us5UeHEGYCFe0= github.com/minio/minio-go/v7 v7.0.74/go.mod h1:qydcVzV8Hqtj1VtEocfxbmVFa2siu6HGa+LDEPogjD8= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -527,6 +527,8 @@ github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM= github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM= github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/vWUetyzY= github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/natefinch/wrap v0.2.0 h1:IXzc/pw5KqxJv55gV0lSOcKHYuEZPGbQrOOXr/bamRk= github.com/natefinch/wrap v0.2.0/go.mod h1:6gMHlAl12DwYEfKP3TkuykYUfLSEAvHw67itm4/KAS8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= @@ -539,12 +541,12 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/openfga/api/proto v0.0.0-20240612172407-db6f98774490 h1:Osd/j+jTWlZi/aXwKjzRPfsFKmdhB8nYMlidpKST8f8= -github.com/openfga/api/proto v0.0.0-20240612172407-db6f98774490/go.mod h1:gil5LBD8tSdFQbUkCQdnXsoeU9kDJdJgbGdHkgJfcd0= +github.com/openfga/api/proto v0.0.0-20240722084519-a9261bb50796 h1:4z3o/S0mxuoB3n1p4Xrg72cLY1V6Zj4z+I6sEFHcPpk= +github.com/openfga/api/proto v0.0.0-20240722084519-a9261bb50796/go.mod h1:gil5LBD8tSdFQbUkCQdnXsoeU9kDJdJgbGdHkgJfcd0= github.com/openfga/language/pkg/go v0.2.0-beta.0 h1:dTvgDkQImfNnH1iDvxnUIbz4INvKr4kS46dI12oAEzM= github.com/openfga/language/pkg/go v0.2.0-beta.0/go.mod h1:mCwEY2IQvyNgfEwbfH0C0ERUwtL8z6UjSAF8zgn5Xbg= -github.com/openfga/openfga v1.5.5 h1:KPVX176JuHOCX9iARSacbS06VqxL5+jZ3WvIGws/xQw= -github.com/openfga/openfga v1.5.5/go.mod h1:9R4YjXJZZsd7x+oV2qCVFZNbEOck8DCnXwkaJ3zowwY= +github.com/openfga/openfga v1.5.6 h1:V5VPXbDnThXHORJaP0Hv0kdw0gtS62eV4H0IQk0EqfE= +github.com/openfga/openfga v1.5.6/go.mod h1:Iv2BfL2b6ANYrqWIANSoEveZPh51LV2YnoexrUI8bvU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/osrg/gobgp/v3 v3.28.0 h1:Oy96v6TUiCxMq32b2cmfcREhPFwBoNK+JtBKwjhGQgw= github.com/osrg/gobgp/v3 v3.28.0/go.mod h1:ZGeSti9mURR/o5hf5R6T1FM5g1yiEBZbhP+TuqYJUpI= @@ -560,8 +562,6 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= -github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE= -github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= github.com/pkg/xattr v0.4.10 h1:Qe0mtiNFHQZ296vRgUjRCoPHPqH7VdTOrZx3g0T+pGA= github.com/pkg/xattr v0.4.10/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -577,8 +577,8 @@ github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJL github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.54.0 h1:ZlZy0BgJhTwVZUn7dLOkwCZHUkrAqd3WYtcFCWnM1D8= -github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -702,20 +702,20 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc= go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw= go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= -go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= -go.starlark.net v0.0.0-20240520160348-046347dcd104 h1:3qhteRISupnJvaWshOmeqEUs2y9oc/+/ePPvDh3Eygg= -go.starlark.net v0.0.0-20240520160348-046347dcd104/go.mod h1:YKMCv9b1WrfWmeqdV5MAuEHWsu5iC+fe6kYl2sQjdI8= +go.starlark.net v0.0.0-20240705175910-70002002b310 h1:tEAOMoNmN2MqVNi0MMEWpTtPI4YNCXgxmAGtuv3mST0= +go.starlark.net v0.0.0-20240705175910-70002002b310/go.mod h1:YKMCv9b1WrfWmeqdV5MAuEHWsu5iC+fe6kYl2sQjdI8= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -755,8 +755,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= -golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= @@ -1071,10 +1069,10 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 h1:MuYw1wJzT+ZkybKfaOXKp5hJiZDn2iHaXRw0mRYdHSc= -google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4/go.mod h1:px9SlOOZBg1wM1zdnr8jEL4CNGUBZ+ZKYtNPApNQc4c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 h1:Di6ANFilr+S60a4S61ZM00vLdw0IrQOSMS2/6mrnOU0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY= +google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d h1:JU0iKnSg02Gmb5ZdV8nYsKEKsP6o/FGVWTrw4i1DA9A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1095,8 +1093,8 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= -google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1148,8 +1146,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= -k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM=