Files
talos/internal/app/machined/pkg/controllers/k8s/control_plane_test.go
Andrey Smirnov badbc51e63 refactor: rewrite code to include preliminary support for multi-doc
`config.Container` implements a multi-doc container which implements
both `Container` interface (encoding, validation, etc.), and `Conifg`
interface (accessing parts of the config).

Refactor `generate` and `bundle` packages to support multi-doc, and
provide backwards compatibility.

Implement a first (mostly example) machine config document for
SideroLink API URL.

Many places don't properly support multi-doc yet (e.g. config patches).

Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
2023-05-31 18:38:05 +04:00

426 lines
14 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//nolint:dupl
package k8s_test
import (
"net/url"
"strings"
"testing"
"time"
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/resource/rtestutils"
"github.com/cosi-project/runtime/pkg/safe"
"github.com/siderolabs/go-pointer"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/ctest"
k8sctrl "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/k8s"
"github.com/siderolabs/talos/pkg/machinery/config/container"
"github.com/siderolabs/talos/pkg/machinery/config/machine"
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/resources/config"
"github.com/siderolabs/talos/pkg/machinery/resources/k8s"
)
type K8sControlPlaneSuite struct {
ctest.DefaultSuite
}
// setupMachine creates a machine with given configuration, waits for it to become ready,
// and returns API server's spec.
func (suite *K8sControlPlaneSuite) setupMachine(cfg *config.MachineConfig) {
machineType := config.NewMachineType()
machineType.SetMachineType(machine.TypeControlPlane)
suite.Require().NoError(suite.State().Create(suite.Ctx(), machineType))
suite.Require().NoError(suite.State().Create(suite.Ctx(), cfg))
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.AdmissionControlConfigID}, func(*k8s.AdmissionControlConfig, *assert.Assertions) {})
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.AuditPolicyConfigID}, func(*k8s.AuditPolicyConfig, *assert.Assertions) {})
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.APIServerConfigID}, func(*k8s.APIServerConfig, *assert.Assertions) {})
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.ControllerManagerConfigID}, func(*k8s.ControllerManagerConfig, *assert.Assertions) {})
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.SchedulerConfigID}, func(*k8s.SchedulerConfig, *assert.Assertions) {})
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.BootstrapManifestsConfigID}, func(*k8s.BootstrapManifestsConfig, *assert.Assertions) {})
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.ExtraManifestsConfigID}, func(*k8s.ExtraManifestsConfig, *assert.Assertions) {})
}
func (suite *K8sControlPlaneSuite) TestReconcileDefaults() {
u, err := url.Parse("https://foo:6443")
suite.Require().NoError(err)
cfg := config.NewMachineConfig(
container.NewV1Alpha1(
&v1alpha1.Config{
ConfigVersion: "v1alpha1",
MachineConfig: &v1alpha1.MachineConfig{},
ClusterConfig: &v1alpha1.ClusterConfig{
ControlPlane: &v1alpha1.ControlPlaneConfig{
Endpoint: &v1alpha1.Endpoint{
URL: u,
},
},
},
},
),
)
suite.setupMachine(cfg)
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.APIServerConfigID},
func(apiServer *k8s.APIServerConfig, assert *assert.Assertions) {
apiServerCfg := apiServer.TypedSpec()
assert.Empty(apiServerCfg.CloudProvider)
},
)
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.ControllerManagerConfigID},
func(controllerManager *k8s.ControllerManagerConfig, assert *assert.Assertions) {
assert.Empty(controllerManager.TypedSpec().CloudProvider)
},
)
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.BootstrapManifestsConfigID},
func(bootstrapConfig *k8s.BootstrapManifestsConfig, assert *assert.Assertions) {
assert.Equal("10.96.0.10", bootstrapConfig.TypedSpec().DNSServiceIP)
assert.Equal("", bootstrapConfig.TypedSpec().DNSServiceIPv6)
},
)
}
func (suite *K8sControlPlaneSuite) TestReconcileTransitionWorker() {
u, err := url.Parse("https://foo:6443")
suite.Require().NoError(err)
cfg := config.NewMachineConfig(
container.NewV1Alpha1(
&v1alpha1.Config{
ConfigVersion: "v1alpha1",
MachineConfig: &v1alpha1.MachineConfig{},
ClusterConfig: &v1alpha1.ClusterConfig{
ControlPlane: &v1alpha1.ControlPlaneConfig{
Endpoint: &v1alpha1.Endpoint{
URL: u,
},
},
},
},
),
)
suite.setupMachine(cfg)
machineType, err := safe.StateGet[*config.MachineType](suite.Ctx(), suite.State(), resource.NewMetadata(config.NamespaceName, config.MachineTypeType, config.MachineTypeID, resource.VersionUndefined))
suite.Require().NoError(err)
machineType.SetMachineType(machine.TypeWorker)
suite.Require().NoError(suite.State().Update(suite.Ctx(), machineType))
rtestutils.AssertNoResource[*k8s.AdmissionControlConfig](suite.Ctx(), suite.T(), suite.State(), k8s.AdmissionControlConfigID)
rtestutils.AssertNoResource[*k8s.AuditPolicyConfig](suite.Ctx(), suite.T(), suite.State(), k8s.AuditPolicyConfigID)
rtestutils.AssertNoResource[*k8s.APIServerConfig](suite.Ctx(), suite.T(), suite.State(), k8s.APIServerConfigID)
rtestutils.AssertNoResource[*k8s.ControllerManagerConfig](suite.Ctx(), suite.T(), suite.State(), k8s.ControllerManagerConfigID)
rtestutils.AssertNoResource[*k8s.SchedulerConfig](suite.Ctx(), suite.T(), suite.State(), k8s.SchedulerConfigID)
rtestutils.AssertNoResource[*k8s.BootstrapManifestsConfig](suite.Ctx(), suite.T(), suite.State(), k8s.BootstrapManifestsConfigID)
rtestutils.AssertNoResource[*k8s.ExtraManifestsConfig](suite.Ctx(), suite.T(), suite.State(), k8s.ExtraManifestsConfigID)
}
func (suite *K8sControlPlaneSuite) TestReconcileIPv6() {
u, err := url.Parse("https://foo:6443")
suite.Require().NoError(err)
cfg := config.NewMachineConfig(
container.NewV1Alpha1(
&v1alpha1.Config{
ConfigVersion: "v1alpha1",
MachineConfig: &v1alpha1.MachineConfig{},
ClusterConfig: &v1alpha1.ClusterConfig{
ControlPlane: &v1alpha1.ControlPlaneConfig{
Endpoint: &v1alpha1.Endpoint{
URL: u,
},
},
ClusterNetwork: &v1alpha1.ClusterNetworkConfig{
PodSubnet: []string{constants.DefaultIPv6PodNet},
ServiceSubnet: []string{constants.DefaultIPv6ServiceNet},
},
},
},
),
)
suite.setupMachine(cfg)
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.BootstrapManifestsConfigID},
func(bootstrapConfig *k8s.BootstrapManifestsConfig, assert *assert.Assertions) {
assert.Equal("", bootstrapConfig.TypedSpec().DNSServiceIP)
assert.Equal("fc00:db8:20::a", bootstrapConfig.TypedSpec().DNSServiceIPv6)
},
)
}
func (suite *K8sControlPlaneSuite) TestReconcileDualStack() {
u, err := url.Parse("https://foo:6443")
suite.Require().NoError(err)
cfg := config.NewMachineConfig(
container.NewV1Alpha1(
&v1alpha1.Config{
ConfigVersion: "v1alpha1",
MachineConfig: &v1alpha1.MachineConfig{},
ClusterConfig: &v1alpha1.ClusterConfig{
ControlPlane: &v1alpha1.ControlPlaneConfig{
Endpoint: &v1alpha1.Endpoint{
URL: u,
},
},
ClusterNetwork: &v1alpha1.ClusterNetworkConfig{
PodSubnet: []string{constants.DefaultIPv4PodNet, constants.DefaultIPv6PodNet},
ServiceSubnet: []string{constants.DefaultIPv4ServiceNet, constants.DefaultIPv6ServiceNet},
},
},
},
),
)
suite.setupMachine(cfg)
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.BootstrapManifestsConfigID},
func(bootstrapConfig *k8s.BootstrapManifestsConfig, assert *assert.Assertions) {
assert.Equal("10.96.0.10", bootstrapConfig.TypedSpec().DNSServiceIP)
assert.Equal("fc00:db8:20::a", bootstrapConfig.TypedSpec().DNSServiceIPv6)
},
)
}
func (suite *K8sControlPlaneSuite) TestReconcileExtraVolumes() {
u, err := url.Parse("https://foo:6443")
suite.Require().NoError(err)
cfg := config.NewMachineConfig(
container.NewV1Alpha1(
&v1alpha1.Config{
ConfigVersion: "v1alpha1",
MachineConfig: &v1alpha1.MachineConfig{},
ClusterConfig: &v1alpha1.ClusterConfig{
ControlPlane: &v1alpha1.ControlPlaneConfig{
Endpoint: &v1alpha1.Endpoint{
URL: u,
},
},
APIServerConfig: &v1alpha1.APIServerConfig{
ExtraVolumesConfig: []v1alpha1.VolumeMountConfig{
{
VolumeHostPath: "/var/lib",
VolumeMountPath: "/var/foo/",
},
{
VolumeHostPath: "/var/lib/a.foo",
VolumeMountPath: "/var/foo/b.foo",
},
},
},
},
},
),
)
suite.setupMachine(cfg)
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.APIServerConfigID},
func(apiServer *k8s.APIServerConfig, assert *assert.Assertions) {
apiServerCfg := apiServer.TypedSpec()
assert.Equal(
[]k8s.ExtraVolume{
{
Name: "var-foo",
HostPath: "/var/lib",
MountPath: "/var/foo/",
ReadOnly: false,
},
{
Name: "var-foo-b-foo",
HostPath: "/var/lib/a.foo",
MountPath: "/var/foo/b.foo",
ReadOnly: false,
},
}, apiServerCfg.ExtraVolumes,
)
},
)
}
func (suite *K8sControlPlaneSuite) TestReconcileEnvironment() {
u, err := url.Parse("https://foo:6443")
suite.Require().NoError(err)
cfg := config.NewMachineConfig(
container.NewV1Alpha1(
&v1alpha1.Config{
ConfigVersion: "v1alpha1",
MachineConfig: &v1alpha1.MachineConfig{},
ClusterConfig: &v1alpha1.ClusterConfig{
ControlPlane: &v1alpha1.ControlPlaneConfig{
Endpoint: &v1alpha1.Endpoint{
URL: u,
},
},
APIServerConfig: &v1alpha1.APIServerConfig{
EnvConfig: v1alpha1.Env{
"HTTP_PROXY": "foo",
},
},
},
},
),
)
suite.setupMachine(cfg)
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.APIServerConfigID},
func(apiServer *k8s.APIServerConfig, assert *assert.Assertions) {
apiServerCfg := apiServer.TypedSpec()
assert.Equal(
map[string]string{
"HTTP_PROXY": "foo",
}, apiServerCfg.EnvironmentVariables,
)
},
)
}
func (suite *K8sControlPlaneSuite) TestReconcileExternalCloudProvider() {
u, err := url.Parse("https://foo:6443")
suite.Require().NoError(err)
cfg := config.NewMachineConfig(
container.NewV1Alpha1(
&v1alpha1.Config{
ConfigVersion: "v1alpha1",
MachineConfig: &v1alpha1.MachineConfig{},
ClusterConfig: &v1alpha1.ClusterConfig{
ControlPlane: &v1alpha1.ControlPlaneConfig{
Endpoint: &v1alpha1.Endpoint{
URL: u,
},
},
ExternalCloudProviderConfig: &v1alpha1.ExternalCloudProviderConfig{
ExternalEnabled: pointer.To(true),
ExternalManifests: []string{
"https://raw.githubusercontent.com/kubernetes/cloud-provider-aws/v1.20.0-alpha.0/manifests/rbac.yaml",
"https://raw.githubusercontent.com/kubernetes/cloud-provider-aws/v1.20.0-alpha.0/manifests/aws-cloud-controller-manager-daemonset.yaml",
},
},
},
},
),
)
suite.setupMachine(cfg)
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.APIServerConfigID},
func(apiServer *k8s.APIServerConfig, assert *assert.Assertions) {
apiServerCfg := apiServer.TypedSpec()
assert.Equal("external", apiServerCfg.CloudProvider)
},
)
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.ControllerManagerConfigID},
func(controllerManager *k8s.ControllerManagerConfig, assert *assert.Assertions) {
assert.Equal("external", controllerManager.TypedSpec().CloudProvider)
},
)
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.ExtraManifestsConfigID},
func(extraManifests *k8s.ExtraManifestsConfig, assert *assert.Assertions) {
assert.Equal(
&k8s.ExtraManifestsConfigSpec{
ExtraManifests: []k8s.ExtraManifest{
{
Name: "https://raw.githubusercontent.com/kubernetes/cloud-provider-aws/v1.20.0-alpha.0/manifests/rbac.yaml",
URL: "https://raw.githubusercontent.com/kubernetes/cloud-provider-aws/v1.20.0-alpha.0/manifests/rbac.yaml",
Priority: "30",
},
{
Name: "https://raw.githubusercontent.com/kubernetes/cloud-provider-aws/v1.20.0-alpha.0/manifests/aws-cloud-controller-manager-daemonset.yaml",
URL: "https://raw.githubusercontent.com/kubernetes/cloud-provider-aws/v1.20.0-alpha.0/manifests/aws-cloud-controller-manager-daemonset.yaml",
Priority: "30",
},
},
}, extraManifests.TypedSpec())
},
)
}
func (suite *K8sControlPlaneSuite) TestReconcileInlineManifests() {
u, err := url.Parse("https://foo:6443")
suite.Require().NoError(err)
cfg := config.NewMachineConfig(
container.NewV1Alpha1(
&v1alpha1.Config{
ConfigVersion: "v1alpha1",
MachineConfig: &v1alpha1.MachineConfig{},
ClusterConfig: &v1alpha1.ClusterConfig{
ControlPlane: &v1alpha1.ControlPlaneConfig{
Endpoint: &v1alpha1.Endpoint{
URL: u,
},
},
ClusterInlineManifests: v1alpha1.ClusterInlineManifests{
{
InlineManifestName: "namespace-ci",
InlineManifestContents: strings.TrimSpace(
`
apiVersion: v1
kind: Namespace
metadata:
name: ci
`,
),
},
},
},
},
),
)
suite.setupMachine(cfg)
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.ExtraManifestsConfigID},
func(extraManifests *k8s.ExtraManifestsConfig, assert *assert.Assertions) {
assert.Equal(
&k8s.ExtraManifestsConfigSpec{
ExtraManifests: []k8s.ExtraManifest{
{
Name: "namespace-ci",
Priority: "99",
InlineManifest: "apiVersion: v1\nkind: Namespace\nmetadata:\n\tname: ci",
},
},
},
extraManifests.TypedSpec())
},
)
}
func TestK8sControlPlaneSuite(t *testing.T) {
suite.Run(t, &K8sControlPlaneSuite{
DefaultSuite: ctest.DefaultSuite{
Timeout: 10 * time.Second,
AfterSetup: func(suite *ctest.DefaultSuite) {
suite.Require().NoError(suite.Runtime().RegisterController(&k8sctrl.ControlPlaneController{}))
},
},
})
}