incus-mirror/cmd/incusd/instance_test.go
Stéphane Graber dc4f57c57d
global: Clean latest gofumpt
Signed-off-by: Stéphane Graber <stgraber@stgraber.org>
2026-06-05 01:56:18 -04:00

552 lines
15 KiB
Go

package main
import (
"context"
"errors"
"fmt"
"net"
"testing"
"time"
"github.com/stretchr/testify/suite"
"github.com/lxc/incus/v7/internal/server/db"
"github.com/lxc/incus/v7/internal/server/db/cluster"
deviceConfig "github.com/lxc/incus/v7/internal/server/device/config"
"github.com/lxc/incus/v7/internal/server/instance"
"github.com/lxc/incus/v7/internal/server/instance/instancetype"
storagePools "github.com/lxc/incus/v7/internal/server/storage"
internalUtil "github.com/lxc/incus/v7/internal/util"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v7/shared/idmap"
)
type containerTestSuite struct {
daemonTestSuite
}
func (s *containerTestSuite) TestContainer_ProfilesDefault() {
args := db.InstanceArgs{
Type: instancetype.Container,
Ephemeral: false,
Name: "testFoo",
}
c, op, _, err := instance.CreateInternal(s.d.State(), args, nil, true, true, false)
s.Req.Nil(err)
op.Done(nil)
defer func() { _ = c.Delete(true, true) }()
profiles := c.Profiles()
s.Len(
profiles,
1,
"No default profile created on instanceCreateInternal.",
)
s.Equal(
"default",
profiles[0].Name,
"First profile should be the default profile.",
)
}
func (s *containerTestSuite) TestContainer_ProfilesMulti() {
// Create an unprivileged profile
err := s.d.db.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
profile := cluster.Profile{
Name: "unprivileged",
Description: "unprivileged",
Project: "default",
}
id, err := cluster.CreateProfile(ctx, tx.Tx(), profile)
if err != nil {
return err
}
err = cluster.CreateProfileConfig(ctx, tx.Tx(), id, map[string]string{"security.privileged": "true"})
if err != nil {
return err
}
return err
})
s.Req.Nil(err, "Failed to create the unprivileged profile.")
defer func() {
_ = s.d.db.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
return cluster.DeleteProfile(ctx, tx.Tx(), "default", "unprivileged")
})
}()
var testProfiles []api.Profile
err = s.d.db.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
testProfiles, err = tx.GetProfiles(ctx, "default", []string{"default", "unprivileged"})
return err
})
s.Req.Nil(err)
args := db.InstanceArgs{
Type: instancetype.Container,
Ephemeral: false,
Profiles: testProfiles,
Name: "testFoo",
}
c, op, _, err := instance.CreateInternal(s.d.State(), args, nil, true, true, false)
s.Req.Nil(err)
op.Done(nil)
defer func() { _ = c.Delete(true, true) }()
profiles := c.Profiles()
s.Len(
profiles,
2,
"Didn't get both profiles in instanceCreateInternal.",
)
s.True(
c.IsPrivileged(),
"The container is not privileged (didn't apply the unprivileged profile?).",
)
}
func (s *containerTestSuite) TestContainer_ProfilesOverwriteDefaultNic() {
args := db.InstanceArgs{
Type: instancetype.Container,
Ephemeral: false,
Config: map[string]string{"security.privileged": "true"},
Devices: deviceConfig.Devices{
"eth0": deviceConfig.Device{
"type": "nic",
"nictype": "bridged",
"parent": "unknownbr0",
},
},
Name: "testFoo",
}
err := s.d.State().DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
_, err := tx.CreateNetwork(ctx, api.ProjectDefaultName, "unknownbr0", "", db.NetworkTypeBridge, nil)
return err
})
s.Req.Nil(err)
c, op, _, err := instance.CreateInternal(s.d.State(), args, nil, true, true, false)
s.Req.Nil(err)
op.Done(nil)
s.True(c.IsPrivileged(), "This container should be privileged.")
out, _, err := c.Render()
s.Req.Nil(err)
state, ok := out.(*api.Instance)
s.Req.True(ok)
defer func() { _ = c.Delete(true, true) }()
s.Equal(
"unknownbr0",
state.Devices["eth0"]["parent"],
"Container config doesn't overwrite profile config.",
)
}
func (s *containerTestSuite) TestContainer_LoadFromDB() {
args := db.InstanceArgs{
Type: instancetype.Container,
Ephemeral: false,
Config: map[string]string{"security.privileged": "true"},
Devices: deviceConfig.Devices{
"eth0": deviceConfig.Device{
"type": "nic",
"nictype": "bridged",
"parent": "unknownbr0",
},
},
Name: "testFoo",
}
state := s.d.State()
err := state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
_, err := tx.CreateNetwork(ctx, api.ProjectDefaultName, "unknownbr0", "", db.NetworkTypeBridge, nil)
return err
})
s.Req.Nil(err)
// Create the container
c, op, _, err := instance.CreateInternal(s.d.State(), args, nil, true, true, false)
s.Req.Nil(err)
op.Done(nil)
defer func() { _ = c.Delete(true, true) }()
poolName, err := c.StoragePool()
s.Req.Nil(err)
pool, err := storagePools.LoadByName(state, poolName)
s.Req.Nil(err)
err = state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
_, err = tx.CreateStoragePoolVolume(ctx, c.Project().Name, c.Name(), "", db.StoragePoolVolumeContentTypeFS, pool.ID(), nil, db.StoragePoolVolumeContentTypeFS, time.Now())
return err
})
s.Req.Nil(err)
// Load the container and trigger initLXC()
c2, err := instance.LoadByProjectAndName(state, "default", "testFoo")
c2.IsRunning()
s.Req.Nil(err)
hostInterfaces, _ := net.Interfaces()
apiC1, etagC1, err := c.RenderFull(hostInterfaces)
s.Req.Nil(err)
apiC2, etagC2, err := c2.RenderFull(hostInterfaces)
s.Req.Nil(err)
s.Equal(etagC1, etagC2)
s.Exactly(
apiC1,
apiC2,
"The loaded container isn't exactly the same as the created one.",
)
}
func (s *containerTestSuite) TestContainer_Path_Regular() {
// Regular
args := db.InstanceArgs{
Type: instancetype.Container,
Ephemeral: false,
Name: "testFoo",
}
c, op, _, err := instance.CreateInternal(s.d.State(), args, nil, true, true, false)
s.Req.Nil(err)
op.Done(nil)
defer func() { _ = c.Delete(true, true) }()
s.Req.False(c.IsSnapshot(), "Shouldn't be a snapshot.")
s.Req.Equal(internalUtil.VarPath("containers", "testFoo"), c.Path())
s.Req.Equal(internalUtil.VarPath("containers", "testFoo2"), storagePools.InstancePath(instancetype.Container, "default", "testFoo2", false))
}
func (s *containerTestSuite) TestContainer_LogPath() {
args := db.InstanceArgs{
Type: instancetype.Container,
Ephemeral: false,
Name: "testFoo",
}
c, op, _, err := instance.CreateInternal(s.d.State(), args, nil, true, true, false)
s.Req.Nil(err)
op.Done(nil)
defer func() { _ = c.Delete(true, true) }()
s.Req.Equal(internalUtil.VarPath("logs", "testFoo"), c.LogPath())
}
func (s *containerTestSuite) TestContainer_IsPrivileged_Privileged() {
args := db.InstanceArgs{
Type: instancetype.Container,
Ephemeral: false,
Config: map[string]string{"security.privileged": "true"},
Name: "testFoo",
}
c, op, _, err := instance.CreateInternal(s.d.State(), args, nil, true, true, false)
s.Req.Nil(err)
op.Done(nil)
s.Req.True(c.IsPrivileged(), "This container should be privileged.")
s.Req.Nil(c.Delete(true, true), "Failed to delete the container.")
}
func (s *containerTestSuite) TestContainer_AddRoutedNicValidation() {
eth0 := deviceConfig.Device{
"name": "eth0", "type": "nic", "ipv4.gateway": "none",
"ipv6.gateway": "none", "nictype": "routed", "parent": "unknownbr0",
}
eth1 := deviceConfig.Device{
"name": "eth1", "type": "nic", "ipv4.gateway": "none",
"ipv6.gateway": "none", "nictype": "routed", "parent": "unknownbr0",
}
eth2 := deviceConfig.Device{"name": "eth2", "type": "nic", "nictype": "bridged", "parent": "unknownbr0"}
var testProfiles []api.Profile
err := s.d.db.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
var err error
testProfiles, err = tx.GetProfiles(ctx, "default", []string{"default"})
return err
})
s.Req.Nil(err)
args := db.InstanceArgs{
Type: instancetype.Container,
Profiles: testProfiles,
Devices: deviceConfig.Devices{
"eth0": eth0,
},
Name: "testFoo",
}
c, op, _, err := instance.CreateInternal(s.d.State(), args, nil, true, true, false)
s.Req.NoError(err)
op.Done(nil)
err = c.Update(db.InstanceArgs{
Type: instancetype.Container,
Profiles: testProfiles,
Config: c.LocalConfig(),
Devices: deviceConfig.Devices{
"eth0": eth0,
"eth1": eth1,
},
Name: "testFoo",
}, true)
s.Req.NoError(err, errors.New("Adding multiple routed with gateway mode ['none'] should succeed. "))
eth0["ipv6.gateway"] = "auto"
eth1["ipv6.gateway"] = ""
err = c.Update(db.InstanceArgs{
Type: instancetype.Container,
Profiles: testProfiles,
Config: c.LocalConfig(),
Devices: deviceConfig.Devices{
"eth0": eth0,
"eth1": eth1,
},
Name: "testFoo",
}, true)
s.Req.Error(err,
errors.New("Adding multiple routed nic devices with any gateway mmode ['auto',''] should throw error. "))
err = c.Update(db.InstanceArgs{
Type: instancetype.Container,
Profiles: testProfiles,
Config: c.LocalConfig(),
Devices: deviceConfig.Devices{
"eth0": eth0,
"eth2": eth2,
},
Name: "testFoo",
}, true)
s.Req.NoError(err,
errors.New("Adding multiple nic devices with unicque nictype ['routed'] should throw error. "))
}
func (s *containerTestSuite) TestContainer_IsPrivileged_Unprivileged() {
args := db.InstanceArgs{
Type: instancetype.Container,
Ephemeral: false,
Config: map[string]string{"security.privileged": "false"},
Name: "testFoo",
}
c, op, _, err := instance.CreateInternal(s.d.State(), args, nil, true, true, false)
s.Req.Nil(err)
op.Done(nil)
s.Req.False(c.IsPrivileged(), "This container should be unprivileged.")
s.Req.Nil(c.Delete(true, true), "Failed to delete the container.")
}
func (s *containerTestSuite) TestContainer_Rename() {
args := db.InstanceArgs{
Type: instancetype.Container,
Ephemeral: false,
Name: "testFoo",
}
c, op, _, err := instance.CreateInternal(s.d.State(), args, nil, true, true, false)
s.Req.Nil(err)
op.Done(nil)
defer func() { _ = c.Delete(true, true) }()
s.Req.Nil(c.Rename("testFoo2", true), "Failed to rename the container.")
s.Req.Equal(internalUtil.VarPath("containers", "testFoo2"), c.Path())
}
func (s *containerTestSuite) TestContainer_findIdmap_isolated() {
c1, op, _, err := instance.CreateInternal(s.d.State(), db.InstanceArgs{
Type: instancetype.Container,
Name: "isol-1",
Config: map[string]string{
"security.idmap.isolated": "true",
},
}, nil, true, true, false)
s.Req.Nil(err)
op.Done(nil)
defer func() { _ = c1.Delete(true, true) }()
c2, op, _, err := instance.CreateInternal(s.d.State(), db.InstanceArgs{
Type: instancetype.Container,
Name: "isol-2",
Config: map[string]string{
"security.idmap.isolated": "true",
},
}, nil, true, true, false)
s.Req.Nil(err)
op.Done(nil)
defer func() { _ = c2.Delete(true, true) }()
map1, err := c1.(instance.Container).NextIdmap()
s.Req.Nil(err)
map2, err := c2.(instance.Container).NextIdmap()
s.Req.Nil(err)
host := s.d.os.IdmapSet.Entries[0]
for i := range 2 {
s.Req.Equal(host.HostID+65536, map1.Entries[i].HostID, "hostids don't match %d", i)
s.Req.Equal(int64(0), map1.Entries[i].NSID, "nsid nonzero")
s.Req.Equal(int64(65536), map1.Entries[i].MapRange, "incorrect maprange")
}
for i := range 2 {
s.Req.Equal(host.HostID+65536*2, map2.Entries[i].HostID, "hostids don't match")
s.Req.Equal(int64(0), map2.Entries[i].NSID, "nsid nonzero")
s.Req.Equal(int64(65536), map2.Entries[i].MapRange, "incorrect maprange")
}
}
func (s *containerTestSuite) TestContainer_findIdmap_mixed() {
c1, op, _, err := instance.CreateInternal(s.d.State(), db.InstanceArgs{
Type: instancetype.Container,
Name: "isol-1",
Config: map[string]string{
"security.idmap.isolated": "false",
},
}, nil, true, true, false)
s.Req.Nil(err)
op.Done(nil)
defer func() { _ = c1.Delete(true, true) }()
c2, op, _, err := instance.CreateInternal(s.d.State(), db.InstanceArgs{
Type: instancetype.Container,
Name: "isol-2",
Config: map[string]string{
"security.idmap.isolated": "true",
},
}, nil, true, true, false)
s.Req.Nil(err)
op.Done(nil)
defer func() { _ = c2.Delete(true, true) }()
map1, err := c1.(instance.Container).NextIdmap()
s.Req.Nil(err)
map2, err := c2.(instance.Container).NextIdmap()
s.Req.Nil(err)
host := s.d.os.IdmapSet.Entries[0]
for i := range 2 {
s.Req.Equal(host.HostID, map1.Entries[i].HostID, "hostids don't match %d", i)
s.Req.Equal(int64(0), map1.Entries[i].NSID, "nsid nonzero")
s.Req.Equal(host.MapRange, map1.Entries[i].MapRange, "incorrect maprange")
}
for i := range 2 {
s.Req.Equal(host.HostID+65536, map2.Entries[i].HostID, "hostids don't match")
s.Req.Equal(int64(0), map2.Entries[i].NSID, "nsid nonzero")
s.Req.Equal(int64(65536), map2.Entries[i].MapRange, "incorrect maprange")
}
}
func (s *containerTestSuite) TestContainer_findIdmap_raw() {
c1, op, _, err := instance.CreateInternal(s.d.State(), db.InstanceArgs{
Type: instancetype.Container,
Name: "isol-1",
Config: map[string]string{
"security.idmap.isolated": "false",
"raw.idmap": "both 1000 1000",
},
}, nil, true, true, false)
s.Req.Nil(err)
op.Done(nil)
defer func() { _ = c1.Delete(true, true) }()
map1, err := c1.(instance.Container).NextIdmap()
s.Req.Nil(err)
host := s.d.os.IdmapSet.Entries[0]
for _, i := range []int{0, 3} {
s.Req.Equal(host.HostID, map1.Entries[i].HostID, "hostids don't match")
s.Req.Equal(int64(0), map1.Entries[i].NSID, "nsid nonzero")
s.Req.Equal(int64(1000), map1.Entries[i].MapRange, "incorrect maprange")
}
s.Req.Equal(int64(1000), map1.Entries[1].HostID, "hostids don't match")
s.Req.Equal(int64(1000), map1.Entries[1].NSID, "invalid nsid")
s.Req.Equal(int64(1), map1.Entries[1].MapRange, "incorrect maprange")
for _, i := range []int{2, 4} {
s.Req.Equal(host.HostID+1001, map1.Entries[i].HostID, "hostids don't match")
s.Req.Equal(int64(1001), map1.Entries[i].NSID, "invalid nsid")
s.Req.Equal(host.MapRange-1000-1, map1.Entries[i].MapRange, "incorrect maprange")
}
}
func (s *containerTestSuite) TestContainer_findIdmap_maxed() {
maps := []*idmap.Set{}
instances := []instance.Instance{}
for i := range 7 {
c, op, _, err := instance.CreateInternal(s.d.State(), db.InstanceArgs{
Type: instancetype.Container,
Name: fmt.Sprintf("isol-%d", i),
Config: map[string]string{
"security.idmap.isolated": "true",
},
}, nil, true, true, false)
instances = append(instances, c)
/* we should fail if there are no ids left */
if i == 6 {
s.Req.NotNil(err)
return
}
s.Req.Nil(err)
op.Done(nil)
m, err := c.(instance.Container).NextIdmap()
s.Req.Nil(err)
maps = append(maps, m)
}
defer func() {
for _, c := range instances {
_ = c.Delete(true, true)
}
}()
for i, m1 := range maps {
for j, m2 := range maps {
if m1 == m2 {
continue
}
for _, e := range m2.Entries {
s.Req.False(m1.HostIDsIntersect(e), "%d and %d's idmaps intersect %v %v", i, j, m1, m2)
}
}
}
}
func TestContainerTestSuite(t *testing.T) {
suite.Run(t, &containerTestSuite{})
}