mirror of
https://github.com/lxc/incus
synced 2026-08-02 05:26:46 +00:00
552 lines
15 KiB
Go
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{})
|
|
}
|