mirror of https://github.com/linkerd/linkerd2.git
532 lines
18 KiB
Go
532 lines
18 KiB
Go
package cmd
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/linkerd/linkerd2/controller/gen/config"
|
|
charts "github.com/linkerd/linkerd2/pkg/charts/linkerd2"
|
|
)
|
|
|
|
const (
|
|
installProxyVersion = "install-proxy-version"
|
|
installControlPlaneVersion = "install-control-plane-version"
|
|
installDebugVersion = "install-debug-version"
|
|
)
|
|
|
|
func TestRender(t *testing.T) {
|
|
ctx := context.Background()
|
|
defaultOptions, err := testInstallOptions()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
defaultValues, _, err := defaultOptions.validateAndBuild(ctx, "", nil)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error validating options: %v", err)
|
|
}
|
|
addFakeTLSSecrets(defaultValues)
|
|
|
|
configValues, _, err := defaultOptions.validateAndBuild(ctx, configStage, nil)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error validating options: %v", err)
|
|
}
|
|
addFakeTLSSecrets(configValues)
|
|
|
|
controlPlaneValues, _, err := defaultOptions.validateAndBuild(ctx, controlPlaneStage, nil)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error validating options: %v", err)
|
|
}
|
|
|
|
// A configuration that shows that all config setting strings are honored
|
|
// by `render()`.
|
|
metaOptions, err := testInstallOptions()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
|
|
identityContext := toIdentityContext(&identityWithAnchorsAndTrustDomain{
|
|
TrustAnchorsPEM: "test-trust-anchor",
|
|
Identity: &charts.Identity{
|
|
Issuer: &charts.Issuer{
|
|
ClockSkewAllowance: "20s",
|
|
IssuanceLifetime: "86400s",
|
|
},
|
|
},
|
|
})
|
|
metaConfig := metaOptions.configs(identityContext)
|
|
metaConfig.Global.LinkerdNamespace = "Namespace"
|
|
metaValues := &charts.Values{
|
|
ControllerImage: "ControllerImage",
|
|
WebImage: "WebImage",
|
|
ControllerUID: 2103,
|
|
EnableH2Upgrade: true,
|
|
WebhookFailurePolicy: "WebhookFailurePolicy",
|
|
OmitWebhookSideEffects: false,
|
|
RestrictDashboardPrivileges: false,
|
|
InstallNamespace: true,
|
|
Identity: defaultValues.Identity,
|
|
NodeSelector: defaultValues.NodeSelector,
|
|
Tolerations: defaultValues.Tolerations,
|
|
Global: &charts.Global{
|
|
Namespace: "Namespace",
|
|
ClusterDomain: "cluster.local",
|
|
ImagePullPolicy: "ImagePullPolicy",
|
|
CliVersion: "CliVersion",
|
|
ControllerComponentLabel: "ControllerComponentLabel",
|
|
ControllerLogLevel: "ControllerLogLevel",
|
|
ControllerImageVersion: "ControllerImageVersion",
|
|
ControllerNamespaceLabel: "ControllerNamespaceLabel",
|
|
WorkloadNamespaceLabel: "WorkloadNamespaceLabel",
|
|
CreatedByAnnotation: "CreatedByAnnotation",
|
|
ProxyInjectAnnotation: "ProxyInjectAnnotation",
|
|
ProxyInjectDisabled: "ProxyInjectDisabled",
|
|
LinkerdNamespaceLabel: "LinkerdNamespaceLabel",
|
|
ProxyContainerName: "ProxyContainerName",
|
|
CNIEnabled: false,
|
|
IdentityTrustDomain: defaultValues.Global.IdentityTrustDomain,
|
|
IdentityTrustAnchorsPEM: defaultValues.Global.IdentityTrustAnchorsPEM,
|
|
Proxy: &charts.Proxy{
|
|
DestinationGetNetworks: "DestinationGetNetworks",
|
|
Image: &charts.Image{
|
|
Name: "ProxyImageName",
|
|
PullPolicy: "ImagePullPolicy",
|
|
Version: "ProxyVersion",
|
|
},
|
|
LogLevel: "warn,linkerd=info",
|
|
LogFormat: "plain",
|
|
Ports: &charts.Ports{
|
|
Admin: 4191,
|
|
Control: 4190,
|
|
Inbound: 4143,
|
|
Outbound: 4140,
|
|
},
|
|
UID: 2102,
|
|
Trace: &charts.Trace{},
|
|
},
|
|
ProxyInit: &charts.ProxyInit{
|
|
Image: &charts.Image{
|
|
Name: "ProxyInitImageName",
|
|
PullPolicy: "ImagePullPolicy",
|
|
Version: "ProxyInitVersion",
|
|
},
|
|
Resources: &charts.Resources{
|
|
CPU: charts.Constraints{
|
|
Limit: "100m",
|
|
Request: "10m",
|
|
},
|
|
Memory: charts.Constraints{
|
|
Limit: "50Mi",
|
|
Request: "10Mi",
|
|
},
|
|
},
|
|
XTMountPath: &charts.VolumeMountPath{
|
|
MountPath: "/run",
|
|
Name: "linkerd-proxy-init-xtables-lock",
|
|
},
|
|
},
|
|
},
|
|
Configs: charts.ConfigJSONs{
|
|
Global: "GlobalConfig",
|
|
Proxy: "ProxyConfig",
|
|
Install: "InstallConfig",
|
|
},
|
|
ControllerReplicas: 1,
|
|
ProxyInjector: defaultValues.ProxyInjector,
|
|
ProfileValidator: defaultValues.ProfileValidator,
|
|
Tap: defaultValues.Tap,
|
|
Dashboard: &charts.Dashboard{
|
|
Replicas: 1,
|
|
},
|
|
Prometheus: charts.Prometheus{
|
|
"enabled": true,
|
|
"image": "PrometheusImage",
|
|
},
|
|
Tracing: map[string]interface{}{
|
|
"enabled": false,
|
|
},
|
|
Grafana: defaultValues.Grafana,
|
|
}
|
|
|
|
haOptions, err := testInstallOptions()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
|
|
haOptions.recordedFlags = []*config.Install_Flag{{Name: "ha", Value: "true"}}
|
|
haOptions.highAvailability = true
|
|
haValues, _, _ := haOptions.validateAndBuild(ctx, "", nil)
|
|
addFakeTLSSecrets(haValues)
|
|
|
|
haWithOverridesOptions, err := testInstallOptions()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
|
|
haWithOverridesOptions.recordedFlags = []*config.Install_Flag{
|
|
{Name: "ha", Value: "true"},
|
|
{Name: "controller-replicas", Value: "2"},
|
|
{Name: "proxy-cpu-request", Value: "400m"},
|
|
{Name: "proxy-memory-request", Value: "300Mi"},
|
|
}
|
|
haWithOverridesOptions.highAvailability = true
|
|
haWithOverridesOptions.controllerReplicas = 2
|
|
haWithOverridesOptions.proxyCPURequest = "400m"
|
|
haWithOverridesOptions.proxyMemoryRequest = "300Mi"
|
|
haWithOverridesValues, _, _ := haWithOverridesOptions.validateAndBuild(ctx, "", nil)
|
|
addFakeTLSSecrets(haWithOverridesValues)
|
|
|
|
cniEnabledOptions, err := testInstallOptions()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
|
|
cniEnabledOptions.recordedFlags = []*config.Install_Flag{{Name: "linkerd-cni-enabled", Value: "true"}}
|
|
cniEnabledOptions.cniEnabled = true
|
|
cniEnabledValues, _, _ := cniEnabledOptions.validateAndBuild(ctx, "", nil)
|
|
addFakeTLSSecrets(cniEnabledValues)
|
|
|
|
withProxyIgnoresOptions, err := testInstallOptions()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
withProxyIgnoresOptions.ignoreInboundPorts = []string{"22", "8100-8102"}
|
|
withProxyIgnoresOptions.ignoreOutboundPorts = []string{"5432"}
|
|
withProxyIgnoresValues, _, _ := withProxyIgnoresOptions.validateAndBuild(ctx, "", nil)
|
|
addFakeTLSSecrets(withProxyIgnoresValues)
|
|
|
|
withHeartBeatDisabled, err := testInstallOptions()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
withHeartBeatDisabled.disableHeartbeat = true
|
|
withHeartBeatDisabledValues, _, _ := withHeartBeatDisabled.validateAndBuild(ctx, "", nil)
|
|
addFakeTLSSecrets(withHeartBeatDisabledValues)
|
|
|
|
withRestrictedDashboardPrivileges, err := testInstallOptions()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
withRestrictedDashboardPrivileges.restrictDashboardPrivileges = true
|
|
withRestrictedDashboardPrivilegesValues, _, _ := withRestrictedDashboardPrivileges.validateAndBuild(ctx, "", nil)
|
|
addFakeTLSSecrets(withRestrictedDashboardPrivilegesValues)
|
|
|
|
withControlPlaneTracing, err := testInstallOptions()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
withControlPlaneTracing.controlPlaneTracing = true
|
|
withControlPlaneTracingValues, _, _ := withControlPlaneTracing.validateAndBuild(ctx, "", nil)
|
|
addFakeTLSSecrets(withControlPlaneTracingValues)
|
|
|
|
customRegistryOverride := "my.custom.registry/linkerd-io"
|
|
withCustomRegistryOptions, err := testInstallOptions()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
withCustomRegistryOptions.dockerRegistry = customRegistryOverride
|
|
withCustomRegistryOptions.recordedFlags = []*config.Install_Flag{
|
|
{Name: "registry", Value: customRegistryOverride},
|
|
}
|
|
withCustomRegistryValues, _, _ := withCustomRegistryOptions.validateAndBuild(ctx, "", nil)
|
|
addFakeTLSSecrets(withCustomRegistryValues)
|
|
|
|
withAddOnConfigStage, err := testInstallOptions()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
withAddOnConfigStageValues, _, _ := withAddOnConfigStage.validateAndBuild(ctx, configStage, nil)
|
|
withAddOnConfigStageValues.Tracing["enabled"] = true
|
|
addFakeTLSSecrets(withAddOnConfigStageValues)
|
|
|
|
withAddOnControlPlaneStage, err := testInstallOptions()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
withAddOnControlPlaneStageValues, _, _ := withAddOnControlPlaneStage.validateAndBuild(ctx, controlPlaneStage, nil)
|
|
withAddOnControlPlaneStageValues.Tracing["enabled"] = true
|
|
addFakeTLSSecrets(withAddOnControlPlaneStageValues)
|
|
|
|
withCustomDestinationGetNets, err := testInstallOptions()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
withCustomDestinationGetNets.destinationGetNetworks = []string{"10.0.0.0/8", "172.0.0.0/8"}
|
|
withCustomDestinationGetNetsValues, _, _ := withCustomDestinationGetNets.validateAndBuild(ctx, "", nil)
|
|
addFakeTLSSecrets(withCustomDestinationGetNetsValues)
|
|
|
|
testCases := []struct {
|
|
values *charts.Values
|
|
goldenFileName string
|
|
}{
|
|
{defaultValues, "install_default.golden"},
|
|
{configValues, "install_config.golden"},
|
|
{controlPlaneValues, "install_control-plane.golden"},
|
|
{metaValues, "install_output.golden"},
|
|
{haValues, "install_ha_output.golden"},
|
|
{haWithOverridesValues, "install_ha_with_overrides_output.golden"},
|
|
{cniEnabledValues, "install_no_init_container.golden"},
|
|
{withProxyIgnoresValues, "install_proxy_ignores.golden"},
|
|
{withHeartBeatDisabledValues, "install_heartbeat_disabled_output.golden"},
|
|
{withRestrictedDashboardPrivilegesValues, "install_restricted_dashboard.golden"},
|
|
{withControlPlaneTracingValues, "install_controlplane_tracing_output.golden"},
|
|
{withCustomRegistryValues, "install_custom_registry.golden"},
|
|
{withAddOnConfigStageValues, "install_addon_config.golden"},
|
|
{withAddOnControlPlaneStageValues, "install_addon_control-plane.golden"},
|
|
{withCustomDestinationGetNetsValues, "install_default_override_dst_get_nets.golden"},
|
|
}
|
|
|
|
for i, tc := range testCases {
|
|
tc := tc // pin
|
|
t.Run(fmt.Sprintf("%d: %s", i, tc.goldenFileName), func(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
if err := render(&buf, tc.values); err != nil {
|
|
t.Fatalf("Failed to render templates: %v", err)
|
|
}
|
|
diffTestdata(t, tc.goldenFileName, buf.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateAndBuild_Errors(t *testing.T) {
|
|
ctx := context.Background()
|
|
t.Run("Fails validation for invalid ignoreInboundPorts", func(t *testing.T) {
|
|
installOptions, err := testInstallOptions()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
installOptions.ignoreInboundPorts = []string{"-25"}
|
|
_, _, err = installOptions.validateAndBuild(ctx, "", nil)
|
|
if err == nil {
|
|
t.Fatal("expected error but got nothing")
|
|
}
|
|
})
|
|
|
|
t.Run("Fails validation for invalid ignoreOutboundPorts", func(t *testing.T) {
|
|
installOptions, err := testInstallOptions()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
installOptions.ignoreOutboundPorts = []string{"-25"}
|
|
_, _, err = installOptions.validateAndBuild(ctx, "", nil)
|
|
if err == nil {
|
|
t.Fatal("expected error but got nothing")
|
|
}
|
|
})
|
|
}
|
|
|
|
func testInstallOptions() (*installOptions, error) {
|
|
o, err := newInstallOptionsWithDefaults()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
o.ignoreCluster = true
|
|
o.proxyVersion = installProxyVersion
|
|
o.debugImageVersion = installDebugVersion
|
|
o.controlPlaneVersion = installControlPlaneVersion
|
|
o.heartbeatSchedule = fakeHeartbeatSchedule
|
|
o.identityOptions.crtPEMFile = filepath.Join("testdata", "valid-crt.pem")
|
|
o.identityOptions.keyPEMFile = filepath.Join("testdata", "valid-key.pem")
|
|
o.identityOptions.trustPEMFile = filepath.Join("testdata", "valid-trust-anchors.pem")
|
|
return o, nil
|
|
}
|
|
|
|
func TestValidate(t *testing.T) {
|
|
t.Run("Accepts the default options as valid", func(t *testing.T) {
|
|
opts, err := testInstallOptions()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
|
|
if err := opts.validate(); err != nil {
|
|
t.Fatalf("Unexpected error: %s", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Rejects invalid destination networks", func(t *testing.T) {
|
|
options, err := testInstallOptions()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
|
|
options.destinationGetNetworks = []string{"wrong"}
|
|
expected := "cannot parse destination get networks: invalid CIDR address: wrong"
|
|
|
|
err = options.validate()
|
|
if err == nil {
|
|
t.Fatal("Expected error, got nothing")
|
|
}
|
|
if err.Error() != expected {
|
|
t.Fatalf("Expected error string\"%s\", got \"%s\"", expected, err)
|
|
}
|
|
})
|
|
|
|
t.Run("Rejects invalid controller log level", func(t *testing.T) {
|
|
options, err := testInstallOptions()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
|
|
options.controllerLogLevel = "super"
|
|
expected := "--controller-log-level must be one of: panic, fatal, error, warn, info, debug"
|
|
|
|
err = options.validate()
|
|
if err == nil {
|
|
t.Fatal("Expected error, got nothing")
|
|
}
|
|
if err.Error() != expected {
|
|
t.Fatalf("Expected error string\"%s\", got \"%s\"", expected, err)
|
|
}
|
|
})
|
|
|
|
t.Run("Properly validates proxy log level", func(t *testing.T) {
|
|
testCases := []struct {
|
|
input string
|
|
valid bool
|
|
}{
|
|
{"", false},
|
|
{"info", true},
|
|
{"somemodule", true},
|
|
{"bad%name", false},
|
|
{"linkerd2_proxy=debug", true},
|
|
{"linkerd2%proxy=debug", false},
|
|
{"linkerd2_proxy=foobar", false},
|
|
{"linker2d_proxy,std::option", true},
|
|
{"warn,linkerd=info", true},
|
|
{"warn,linkerd=foobar", false},
|
|
}
|
|
|
|
options, err := testInstallOptions()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
options.proxyLogLevel = tc.input
|
|
err := options.validate()
|
|
if tc.valid && err != nil {
|
|
t.Fatalf("Error not expected: %s", err)
|
|
}
|
|
if !tc.valid && err == nil {
|
|
t.Fatalf("Expected error string \"%s is not a valid proxy log level\", got nothing", tc.input)
|
|
}
|
|
expectedErr := fmt.Sprintf("\"%s\" is not a valid proxy log level - for allowed syntax check https://docs.rs/env_logger/0.6.0/env_logger/#enabling-logging", tc.input)
|
|
if tc.input == "" {
|
|
expectedErr = "--proxy-log-level must not be empty"
|
|
}
|
|
if !tc.valid && err.Error() != expectedErr {
|
|
t.Fatalf("Expected error string \"%s\", got \"%s\"; input=\"%s\"", expectedErr, err, tc.input)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("Validates the issuer certs upon install", func(t *testing.T) {
|
|
|
|
testCases := []struct {
|
|
crtFilePrefix string
|
|
expectedError string
|
|
}{
|
|
{"valid", ""},
|
|
{"expired", "failed to verify issuer certs stored on disk: not valid anymore. Expired on 1990-01-01T01:01:11Z"},
|
|
{"not-valid-yet", "failed to verify issuer certs stored on disk: not valid before: 2100-01-01T01:00:51Z"},
|
|
{"wrong-domain", "failed to verify issuer certs stored on disk: x509: certificate is valid for wrong.linkerd.cluster.local, not identity.linkerd.cluster.local"},
|
|
{"wrong-algo", "failed to verify issuer certs stored on disk: must use P-256 curve for public key, instead P-521 was used"},
|
|
}
|
|
for _, tc := range testCases {
|
|
|
|
options, err := testInstallOptions()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
|
|
options.identityOptions.crtPEMFile = filepath.Join("testdata", tc.crtFilePrefix+"-crt.pem")
|
|
options.identityOptions.keyPEMFile = filepath.Join("testdata", tc.crtFilePrefix+"-key.pem")
|
|
options.identityOptions.trustPEMFile = filepath.Join("testdata", tc.crtFilePrefix+"-trust-anchors.pem")
|
|
|
|
_, err = options.identityOptions.validateAndBuild(context.Background())
|
|
|
|
if tc.expectedError != "" {
|
|
if err == nil {
|
|
t.Fatal("Expected error, got nothing")
|
|
}
|
|
if err.Error() != tc.expectedError {
|
|
t.Fatalf("Expected error string\"%s\", got \"%s\"", tc.expectedError, err)
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Fatalf("Expected no error bu got \"%s\"", err)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("Rejects identity cert files data when external issuer is set", func(t *testing.T) {
|
|
|
|
options, err := testInstallOptions()
|
|
options.identityOptions.crtPEMFile = ""
|
|
options.identityOptions.keyPEMFile = ""
|
|
options.identityOptions.trustPEMFile = ""
|
|
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v\n", err)
|
|
}
|
|
|
|
withoutCertDataOptions := options.identityOptions
|
|
withoutCertDataOptions.identityExternalIssuer = true
|
|
withCrtFile := *withoutCertDataOptions
|
|
withCrtFile.crtPEMFile = "crt-file"
|
|
withTrustAnchorsFile := *withoutCertDataOptions
|
|
withTrustAnchorsFile.trustPEMFile = "ta-file"
|
|
withKeyFile := *withoutCertDataOptions
|
|
withKeyFile.keyPEMFile = "key-file"
|
|
|
|
testCases := []struct {
|
|
input *installIdentityOptions
|
|
expectedError string
|
|
}{
|
|
{withoutCertDataOptions, ""},
|
|
{&withCrtFile, "--identity-issuer-certificate-file must not be specified if --identity-external-issuer=true"},
|
|
{&withTrustAnchorsFile, "--identity-trust-anchors-file must not be specified if --identity-external-issuer=true"},
|
|
{&withKeyFile, "--identity-issuer-key-file must not be specified if --identity-external-issuer=true"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
err = tc.input.validate()
|
|
|
|
if tc.expectedError != "" {
|
|
if err == nil {
|
|
t.Fatal("Expected error, got nothing")
|
|
}
|
|
if err.Error() != tc.expectedError {
|
|
t.Fatalf("Expected error string\"%s\", got \"%s\"", tc.expectedError, err)
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Fatalf("Expected no error bu got \"%s\"", err)
|
|
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func fakeHeartbeatSchedule() string {
|
|
return "1 2 3 4 5"
|
|
}
|
|
|
|
func addFakeTLSSecrets(values *charts.Values) {
|
|
values.ProxyInjector.CrtPEM = "proxy injector crt"
|
|
values.ProxyInjector.KeyPEM = "proxy injector key"
|
|
values.ProxyInjector.CaBundle = "proxy injector CA bundle"
|
|
values.ProfileValidator.CrtPEM = "profile validator crt"
|
|
values.ProfileValidator.KeyPEM = "profile validator key"
|
|
values.ProfileValidator.CaBundle = "profile validator CA bundle"
|
|
values.Tap.CrtPEM = "tap crt"
|
|
values.Tap.KeyPEM = "tap key"
|
|
values.Tap.CaBundle = "tap CA bundle"
|
|
}
|