1
0
Fork 0
mirror of https://github.com/arangodb/kube-arangodb.git synced 2024-12-14 11:57:37 +00:00

[Feature] Early connections support (#1102)

This commit is contained in:
Tomasz Mielech 2022-09-19 17:21:13 +02:00 committed by GitHub
parent a4e26f3ce4
commit 92afe451eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 623 additions and 97 deletions

View file

@ -2,6 +2,7 @@
## [master](https://github.com/arangodb/kube-arangodb/tree/master) (N/A)
- (Feature) Add new field to DeploymentReplicationStatus with details on DC2DC sync status
- (Feature) Early connections support
## [1.2.16](https://github.com/arangodb/kube-arangodb/tree/1.2.16) (2022-09-14)
- (Feature) Add ArangoDeployment ServerGroupStatus

View file

@ -22,44 +22,80 @@ package cmd
import (
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"path"
"strconv"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/arangodb/go-driver"
"github.com/arangodb/go-driver/jwt"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/deployment/client"
"github.com/arangodb/kube-arangodb/pkg/deployment/features"
"github.com/arangodb/kube-arangodb/pkg/deployment/pod"
"github.com/arangodb/kube-arangodb/pkg/deployment/resources"
"github.com/arangodb/kube-arangodb/pkg/util/constants"
)
var (
cmdLifecycleProbe = &cobra.Command{
Use: "probe",
Run: cmdLifecycleProbeCheck,
Run: cmdLifecycleProbeRun,
}
cmdLifecycleProbeLiveness = &cobra.Command{
Use: "liveness",
Run: cmdLifecycleProbeRun,
}
cmdLifecycleProbeReadiness = &cobra.Command{
Use: "readiness",
Run: cmdLifecycleProbeRun,
}
cmdLifecycleProbeStartUp = &cobra.Command{
Use: "startup",
Run: cmdLifecycleProbeRun,
}
probeInput struct {
SSL bool
Auth bool
Endpoint string
JWTPath string
Endpoint string
JWTPath string
ArangoDBVersion string
ServerGroup string
DeploymentMode string
SSL bool
Auth bool
Enterprise bool
}
)
func init() {
f := cmdLifecycleProbe.PersistentFlags()
cmdLifecycleProbe.AddCommand(cmdLifecycleProbeLiveness)
cmdLifecycleProbe.AddCommand(cmdLifecycleProbeReadiness)
cmdLifecycleProbe.AddCommand(cmdLifecycleProbeStartUp)
f.BoolVarP(&probeInput.SSL, "ssl", "", false, "Determines if SSL is enabled")
f.BoolVarP(&probeInput.Auth, "auth", "", false, "Determines if authentication is enabled")
f.StringVarP(&probeInput.Endpoint, "endpoint", "", "/_api/version", "Endpoint (path) to call for lifecycle probe")
f.StringVarP(&probeInput.Endpoint, "endpoint", "", client.ServerApiVersionEndpoint, "Endpoint (path) to call for lifecycle probe")
f.MarkDeprecated("endpoint", "Endpoint is chosen automatically by the lifecycle process")
f.StringVarP(&probeInput.JWTPath, "jwt", "", shared.ClusterJWTSecretVolumeMountDir, "Path to the JWT tokens")
f.StringVar(&probeInput.ArangoDBVersion, "arangodb-version", os.Getenv(resources.ArangoDBOverrideVersionEnv),
"Version of the ArangoDB")
f.StringVar(&probeInput.ServerGroup, "serverGroup", os.Getenv(resources.ArangoDBOverrideServerGroupEnv),
"Name of the group where a server belongs to")
f.StringVar(&probeInput.DeploymentMode, "deploymentMode", os.Getenv(resources.ArangoDBOverrideDeploymentModeEnv),
"A deployment mode (Cluster, Single, ActiveFailover)")
enterprise, _ := strconv.ParseBool(os.Getenv(resources.ArangoDBOverrideEnterpriseEnv))
f.BoolVar(&probeInput.Enterprise, "enterprise", enterprise, "Determines if ArangoDB is enterprise")
}
func probeClient() *http.Client {
@ -147,10 +183,10 @@ func addAuthHeader(req *http.Request) error {
return nil
}
func doRequest() (*http.Response, error) {
func doRequest(endpoint string) (*http.Response, error) {
client := probeClient()
req, err := http.NewRequest(http.MethodGet, probeEndpoint(probeInput.Endpoint), nil)
req, err := http.NewRequest(http.MethodGet, probeEndpoint(endpoint), nil)
if err != nil {
return nil, err
}
@ -162,22 +198,25 @@ func doRequest() (*http.Response, error) {
return client.Do(req)
}
func cmdLifecycleProbeCheck(cmd *cobra.Command, args []string) {
if err := cmdLifecycleProbeCheckE(); err != nil {
func cmdLifecycleProbeRun(cmd *cobra.Command, _ []string) {
if err := cmdLifecycleProbeRunE(cmd); err != nil {
log.Error().Err(err).Msgf("Fatal")
os.Exit(1)
}
}
func cmdLifecycleProbeCheckE() error {
resp, err := doRequest()
func cmdLifecycleProbeRunE(cmd *cobra.Command) error {
endpoint := getEndpoint(api.ProbeType(cmd.Use))
resp, err := doRequest(endpoint)
if err != nil {
return err
}
if resp.Body != nil {
defer resp.Body.Close()
}
if resp.StatusCode != http.StatusOK {
if resp.Body != nil {
defer resp.Body.Close()
if data, err := ioutil.ReadAll(resp.Body); err == nil {
return errors.Errorf("Unexpected code: %d - %s", resp.StatusCode, string(data))
}
@ -186,7 +225,51 @@ func cmdLifecycleProbeCheckE() error {
return errors.Errorf("Unexpected code: %d", resp.StatusCode)
}
if endpoint == client.ServerStatusEndpoint {
// When server status endpoint is used then HTTP status code 200 is not enough.
// The progress should be also checked.
if resp.Body == nil {
return errors.Errorf("Expected body from the \"%s\" endpoint", endpoint)
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return errors.Errorf("Failed to read body from the \"%s\" endpoint", endpoint)
}
status := client.ServerStatus{}
if err = json.Unmarshal(data, &status); err != nil {
return errors.Errorf("Failed to unmarshal %s into server status", string(data))
}
if progress, ok := status.GetProgress(); !ok {
return errors.Errorf("server not ready: %s", progress)
}
}
log.Info().Msgf("Check passed")
return nil
}
// getEndpoint returns endpoint to the ArangoDB instance where readiness should be checked.
func getEndpoint(probeType api.ProbeType) string {
if probeType == api.ProbeTypeReadiness {
if probeInput.DeploymentMode == string(api.DeploymentModeActiveFailover) {
v := driver.Version(probeInput.ArangoDBVersion)
if features.FailoverLeadership().Supported(v, probeInput.Enterprise) {
return client.ServerApiVersionEndpoint
}
}
return client.ServerAvailabilityEndpoint
}
if probeInput.ServerGroup == api.ServerGroupDBServersString {
v := driver.Version(probeInput.ArangoDBVersion)
if features.Version310().Supported(v, probeInput.Enterprise) {
return client.ServerStatusEndpoint
}
}
return client.ServerApiVersionEndpoint
}

View file

@ -0,0 +1,29 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package v1
type ProbeType string
const (
ProbeTypeLiveness = "liveness"
ProbeTypeReadiness = "readiness"
ProbeTypeStartUp = "startup"
)

View file

@ -0,0 +1,29 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package v2alpha1
type ProbeType string
const (
ProbeTypeLiveness = "liveness"
ProbeTypeReadiness = "readiness"
ProbeTypeStartUp = "startup"
)

View file

@ -0,0 +1,76 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package client
import (
"fmt"
"strings"
)
const (
// ServerProgressPhaseInWait describes success progress state of a server.
ServerProgressPhaseInWait = "in wait"
// ServerStatusEndpoint describes endpoint of a server status.
ServerStatusEndpoint = "/_admin/status"
// ServerApiVersionEndpoint describes endpoint of a server version.
ServerApiVersionEndpoint = "/_api/version"
// ServerAvailabilityEndpoint describes endpoint of a server availability.
ServerAvailabilityEndpoint = "/_admin/server/availability"
)
// ServerProgress describes server progress.
type ServerProgress struct {
// Phase is a name of the lifecycle phase the instance is currently in.
Phase string `json:"phase,omitempty"`
// Feature is internal name of the feature that is currently being prepared
Feature string `json:"feature,omitempty"`
// Current recovery sequence number value, if the instance is currently recovering.
// If the instance is already past the recovery, this attribute contains the last handled recovery sequence number.
RecoveryTick int `json:"recoveryTick,omitempty"`
}
// ServerInfo describes server information.
type ServerInfo struct {
ServerProgress ServerProgress `json:"progress,omitempty"`
}
// ServerStatus describes server status.
type ServerStatus struct {
ServerInfo ServerInfo `json:"serverInfo,omitempty"`
}
// GetProgress returns human-readable progress status of the server, and true if server is ready.
func (s ServerStatus) GetProgress() (string, bool) {
p := s.ServerInfo.ServerProgress
var result strings.Builder
if len(p.Feature) > 0 {
result.WriteString("feature: " + p.Feature + ", ")
}
result.WriteString("phase: " + p.Phase)
if p.RecoveryTick > 0 {
result.WriteString(", recoveryTick: " + fmt.Sprintf("%d", p.RecoveryTick))
}
return result.String(), p.Phase == ServerProgressPhaseInWait
}

View file

@ -52,7 +52,7 @@ func TestEnsurePod_ArangoDB_Encryption(t *testing.T) {
firstAgentStatus,
},
},
Images: createTestImagesWithVersion(false, "3.7.0"),
Images: createTestImagesWithVersion(false, testVersion),
}
testCase.createTestPodData(deployment, api.ServerGroupAgents, firstAgentStatus)
@ -117,7 +117,7 @@ func TestEnsurePod_ArangoDB_Encryption(t *testing.T) {
firstDBServerStatus,
},
},
Images: createTestImagesWithVersion(false, "3.7.0"),
Images: createTestImagesWithVersion(false, testVersion),
}
testCase.createTestPodData(deployment, api.ServerGroupDBServers, firstDBServerStatus)
@ -200,7 +200,7 @@ func TestEnsurePod_ArangoDB_Encryption(t *testing.T) {
firstAgentStatus,
},
},
Images: createTestImagesWithVersion(true, "3.7.0"),
Images: createTestImagesWithVersion(true, testVersion),
}
testCase.createTestPodData(deployment, api.ServerGroupAgents, firstAgentStatus)
@ -263,7 +263,7 @@ func TestEnsurePod_ArangoDB_Encryption(t *testing.T) {
firstAgentStatus,
},
},
Images: createTestImagesWithVersion(true, "3.7.0"),
Images: createTestImagesWithVersion(true, testVersion),
}
testCase.createTestPodData(deployment, api.ServerGroupAgents, firstAgentStatus)

View file

@ -21,6 +21,7 @@
package deployment
import (
"math"
"testing"
core "k8s.io/api/core/v1"
@ -32,6 +33,13 @@ import (
)
func TestEnsurePod_ArangoDB_Probe(t *testing.T) {
addEarlyConnection := func() k8sutil.OptionPairs {
args := k8sutil.NewOptionPair()
args.Add("--server.early-connections", true)
return args
}
testCases := []testCaseStruct{
{
Name: "Agent Pod default probes",
@ -413,6 +421,278 @@ func TestEnsurePod_ArangoDB_Probe(t *testing.T) {
},
},
},
{
Name: "DBServer with early connections",
ArangoDeployment: &api.ArangoDeployment{
Spec: api.DeploymentSpec{
Image: util.NewString(createTestImageForVersion("3.10.0")),
Authentication: noAuthentication,
TLS: noTLS,
},
},
Helper: func(t *testing.T, deployment *Deployment, testCase *testCaseStruct) {
deployment.currentObjectStatus = &api.DeploymentStatus{
Members: api.DeploymentStatusMembers{
DBServers: api.MemberStatusList{
firstDBServerStatus,
},
},
Images: createTestImagesWithVersion(true, "3.10.0"),
}
testCase.createTestPodData(deployment, api.ServerGroupDBServers, firstDBServerStatus)
testCase.ExpectedPod.Spec.Containers[0].StartupProbe = createTestStartupProbe(cmdProbe, false, "",
math.MaxInt32)
testCase.ExpectedPod.Spec.Containers[0].LivenessProbe.FailureThreshold = math.MaxInt32
},
Features: testCaseFeatures{
Version310: true,
},
ExpectedEvent: "member dbserver is created",
ExpectedPod: core.Pod{
Spec: core.PodSpec{
Volumes: []core.Volume{
k8sutil.CreateVolumeEmptyDir(shared.ArangodVolumeName),
},
Containers: []core.Container{
{
Name: shared.ServerContainerName,
Image: createTestImageForVersion("3.10.0"),
Command: createTestCommandForDBServer(firstDBServerStatus.ID, false, false, false, addEarlyConnection),
Ports: createTestPorts(),
VolumeMounts: []core.VolumeMount{
k8sutil.ArangodVolumeMount(),
},
Resources: emptyResources,
LivenessProbe: createTestLivenessProbe(cmdProbe, false, "", shared.ArangoPort),
ImagePullPolicy: core.PullIfNotPresent,
SecurityContext: securityContext.NewSecurityContext(),
},
},
RestartPolicy: core.RestartPolicyNever,
TerminationGracePeriodSeconds: &defaultDBServerTerminationTimeout,
Hostname: testDeploymentName + "-" + api.ServerGroupDBServersString + "-" + firstDBServerStatus.ID,
Subdomain: testDeploymentName + "-int",
Affinity: k8sutil.CreateAffinity(testDeploymentName, api.ServerGroupDBServersString,
false, ""),
},
},
},
{
Name: "DBServer without early connections because version is lower than 3.10.0",
ArangoDeployment: &api.ArangoDeployment{
Spec: api.DeploymentSpec{
Image: util.NewString(createTestImageForVersion("3.9.2")),
Authentication: noAuthentication,
TLS: noTLS,
},
},
Helper: func(t *testing.T, deployment *Deployment, testCase *testCaseStruct) {
deployment.currentObjectStatus = &api.DeploymentStatus{
Members: api.DeploymentStatusMembers{
DBServers: api.MemberStatusList{
firstDBServerStatus,
},
},
Images: createTestImagesWithVersion(false, "3.9.2"),
}
testCase.createTestPodData(deployment, api.ServerGroupDBServers, firstDBServerStatus)
},
Features: testCaseFeatures{
Version310: true,
},
ExpectedEvent: "member dbserver is created",
ExpectedPod: core.Pod{
Spec: core.PodSpec{
Volumes: []core.Volume{
k8sutil.CreateVolumeEmptyDir(shared.ArangodVolumeName),
},
Containers: []core.Container{
{
Name: shared.ServerContainerName,
Image: createTestImageForVersion("3.9.2"),
Command: createTestCommandForDBServer(firstDBServerStatus.ID, false, false, false),
Ports: createTestPorts(),
VolumeMounts: []core.VolumeMount{
k8sutil.ArangodVolumeMount(),
},
Resources: emptyResources,
LivenessProbe: createTestLivenessProbe(httpProbe, false, "", shared.ArangoPort),
ImagePullPolicy: core.PullIfNotPresent,
SecurityContext: securityContext.NewSecurityContext(),
},
},
RestartPolicy: core.RestartPolicyNever,
TerminationGracePeriodSeconds: &defaultDBServerTerminationTimeout,
Hostname: testDeploymentName + "-" + api.ServerGroupDBServersString + "-" + firstDBServerStatus.ID,
Subdomain: testDeploymentName + "-int",
Affinity: k8sutil.CreateAffinity(testDeploymentName, api.ServerGroupDBServersString,
false, ""),
},
},
},
{
Name: "DBServer without early connections because 3.10 feature is disabled",
ArangoDeployment: &api.ArangoDeployment{
Spec: api.DeploymentSpec{
Image: util.NewString(createTestImageForVersion("3.10.0")),
Authentication: noAuthentication,
TLS: noTLS,
},
},
Helper: func(t *testing.T, deployment *Deployment, testCase *testCaseStruct) {
deployment.currentObjectStatus = &api.DeploymentStatus{
Members: api.DeploymentStatusMembers{
DBServers: api.MemberStatusList{
firstDBServerStatus,
},
},
Images: createTestImagesWithVersion(false, "3.10.0"),
}
testCase.createTestPodData(deployment, api.ServerGroupDBServers, firstDBServerStatus)
},
Features: testCaseFeatures{
Version310: false,
},
ExpectedEvent: "member dbserver is created",
ExpectedPod: core.Pod{
Spec: core.PodSpec{
Volumes: []core.Volume{
k8sutil.CreateVolumeEmptyDir(shared.ArangodVolumeName),
},
Containers: []core.Container{
{
Name: shared.ServerContainerName,
Image: createTestImageForVersion("3.10.0"),
Command: createTestCommandForDBServer(firstDBServerStatus.ID, false, false, false),
Ports: createTestPorts(),
VolumeMounts: []core.VolumeMount{
k8sutil.ArangodVolumeMount(),
},
Resources: emptyResources,
LivenessProbe: createTestLivenessProbe(httpProbe, false, "", shared.ArangoPort),
ImagePullPolicy: core.PullIfNotPresent,
SecurityContext: securityContext.NewSecurityContext(),
},
},
RestartPolicy: core.RestartPolicyNever,
TerminationGracePeriodSeconds: &defaultDBServerTerminationTimeout,
Hostname: testDeploymentName + "-" + api.ServerGroupDBServersString + "-" + firstDBServerStatus.ID,
Subdomain: testDeploymentName + "-int",
Affinity: k8sutil.CreateAffinity(testDeploymentName, api.ServerGroupDBServersString,
false, ""),
},
},
},
{
Name: "Coordinator should be without early connections",
ArangoDeployment: &api.ArangoDeployment{
Spec: api.DeploymentSpec{
Image: util.NewString(createTestImageForVersion("3.10.0")),
Authentication: noAuthentication,
TLS: noTLS,
},
},
Helper: func(t *testing.T, deployment *Deployment, testCase *testCaseStruct) {
deployment.currentObjectStatus = &api.DeploymentStatus{
Members: api.DeploymentStatusMembers{
Coordinators: api.MemberStatusList{
firstCoordinatorStatus,
},
},
Images: createTestImagesWithVersion(false, "3.10.0"),
}
testCase.createTestPodData(deployment, api.ServerGroupCoordinators, firstCoordinatorStatus)
},
Features: testCaseFeatures{
Version310: true,
},
ExpectedEvent: "member coordinator is created",
ExpectedPod: core.Pod{
Spec: core.PodSpec{
Volumes: []core.Volume{
k8sutil.CreateVolumeEmptyDir(shared.ArangodVolumeName),
},
Containers: []core.Container{
{
Name: shared.ServerContainerName,
Image: createTestImageForVersion("3.10.0"),
Command: createTestCommandForCoordinator(firstCoordinatorStatus.ID, false, false),
Ports: createTestPorts(),
VolumeMounts: []core.VolumeMount{
k8sutil.ArangodVolumeMount(),
},
Resources: emptyResources,
ReadinessProbe: createTestReadinessProbe(httpProbe, false, ""),
ImagePullPolicy: core.PullIfNotPresent,
SecurityContext: securityContext.NewSecurityContext(),
},
},
RestartPolicy: core.RestartPolicyNever,
TerminationGracePeriodSeconds: &defaultCoordinatorTerminationTimeout,
Hostname: testDeploymentName + "-" + api.ServerGroupCoordinatorsString + "-" + firstCoordinatorStatus.ID,
Subdomain: testDeploymentName + "-int",
Affinity: k8sutil.CreateAffinity(testDeploymentName, api.ServerGroupCoordinatorsString,
false, ""),
},
},
},
{
Name: "Agent should be without early connections",
ArangoDeployment: &api.ArangoDeployment{
Spec: api.DeploymentSpec{
Image: util.NewString(createTestImageForVersion("3.10.0")),
Authentication: noAuthentication,
TLS: noTLS,
},
},
Helper: func(t *testing.T, deployment *Deployment, testCase *testCaseStruct) {
deployment.currentObjectStatus = &api.DeploymentStatus{
Members: api.DeploymentStatusMembers{
Agents: api.MemberStatusList{
firstAgentStatus,
},
},
Images: createTestImagesWithVersion(false, "3.10.0"),
}
testCase.createTestPodData(deployment, api.ServerGroupAgents, firstAgentStatus)
},
Features: testCaseFeatures{
Version310: true,
},
ExpectedEvent: "member agent is created",
ExpectedPod: core.Pod{
Spec: core.PodSpec{
Volumes: []core.Volume{
k8sutil.CreateVolumeEmptyDir(shared.ArangodVolumeName),
},
Containers: []core.Container{
{
Name: shared.ServerContainerName,
Image: createTestImageForVersion("3.10.0"),
Command: createTestCommandForAgent(firstAgentStatus.ID, false, false, false),
Ports: createTestPorts(),
VolumeMounts: []core.VolumeMount{
k8sutil.ArangodVolumeMount(),
},
Resources: emptyResources,
LivenessProbe: createTestLivenessProbe(httpProbe, false, "", shared.ArangoPort),
ImagePullPolicy: core.PullIfNotPresent,
SecurityContext: securityContext.NewSecurityContext(),
},
},
RestartPolicy: core.RestartPolicyNever,
TerminationGracePeriodSeconds: &defaultAgentTerminationTimeout,
Hostname: testDeploymentName + "-" + api.ServerGroupAgentsString + "-" + firstAgentStatus.ID,
Subdomain: testDeploymentName + "-int",
Affinity: k8sutil.CreateAffinity(testDeploymentName, api.ServerGroupAgentsString,
false, ""),
},
},
},
}
runTestCases(t, testCases...)

View file

@ -134,7 +134,7 @@ func TestEnsurePod_ArangoDB_TLS_SNI(t *testing.T) {
Name: "Pod SNI Mounts - Enterprise - 3.6.0",
ArangoDeployment: &api.ArangoDeployment{
Spec: api.DeploymentSpec{
Image: util.NewString(testImage),
Image: util.NewString(createTestImageForVersion("3.6.0")),
Authentication: noAuthentication,
TLS: func() api.TLSSpec {
s := tlsSpec.DeepCopy()
@ -183,7 +183,7 @@ func TestEnsurePod_ArangoDB_TLS_SNI(t *testing.T) {
Containers: []core.Container{
{
Name: shared.ServerContainerName,
Image: testImage,
Image: createTestImageForVersion("3.6.0"),
Command: createTestCommandForCoordinator(firstCoordinatorStatus.ID, true, false),
Ports: createTestPorts(),
VolumeMounts: []core.VolumeMount{
@ -244,7 +244,7 @@ func TestEnsurePod_ArangoDB_TLS_SNI(t *testing.T) {
firstCoordinatorStatus,
},
},
Images: createTestImagesWithVersion(false, "3.7.0"),
Images: createTestImagesWithVersion(false, testVersion),
}
testCase.createTestPodData(deployment, api.ServerGroupCoordinators, firstCoordinatorStatus)
},
@ -319,7 +319,7 @@ func TestEnsurePod_ArangoDB_TLS_SNI(t *testing.T) {
firstCoordinatorStatus,
},
},
Images: createTestImagesWithVersion(true, "3.7.0"),
Images: createTestImagesWithVersion(true, testVersion),
}
testCase.createTestPodData(deployment, api.ServerGroupCoordinators, firstCoordinatorStatus)
},
@ -427,7 +427,7 @@ func TestEnsurePod_ArangoDB_TLS_SNI(t *testing.T) {
firstDBServerStatus,
},
},
Images: createTestImagesWithVersion(true, "3.7.0"),
Images: createTestImagesWithVersion(true, testVersion),
}
testCase.createTestPodData(deployment, api.ServerGroupDBServers, firstDBServerStatus)
},

View file

@ -112,6 +112,7 @@ func runTestCase(t *testing.T, testCase testCaseStruct) {
// Set features
{
*features.EncryptionRotation().EnabledPointer() = testCase.Features.EncryptionRotation
*features.Version310().EnabledPointer() = testCase.Features.Version310
require.Equal(t, testCase.Features.EncryptionRotation, *features.EncryptionRotation().EnabledPointer())
*features.JWTRotation().EnabledPointer() = testCase.Features.JWTRotation
*features.TLSSNI().EnabledPointer() = testCase.Features.TLSSNI

View file

@ -22,7 +22,6 @@ package deployment
import (
"context"
"fmt"
"os"
"path/filepath"
"sort"
@ -62,7 +61,8 @@ const (
testNamespace = "default"
testDeploymentName = "test"
testVersion = "3.7.0"
testImage = "arangodb/arangodb:" + testVersion
testImagePrefix = "arangodb/arangodb:"
testImage = testImagePrefix + testVersion
testCASecretName = "testCA"
testJWTSecretName = "testJWT"
testExporterToken = "testExporterToken"
@ -72,13 +72,13 @@ const (
testServiceAccountName = "testServiceAccountName"
testPriorityClassName = "testPriority"
testImageOperator = "arangodb/kube-arangodb:0.3.16"
testYes = "yes"
testProbeDBServerRetires = 4320 // 6 hours divide by 5.
testYes = "yes"
)
type testCaseFeatures struct {
TLSSNI, TLSRotation, JWTRotation, EncryptionRotation bool
Graceful *bool
TLSSNI, TLSRotation, JWTRotation, EncryptionRotation, Version310 bool
Graceful *bool
}
type testCaseStruct struct {
@ -95,6 +95,10 @@ type testCaseStruct struct {
DropInit bool
}
func createTestImageForVersion(version string) string {
return testImagePrefix + version
}
func createTestTLSVolume(serverGroupString, ID string) core.Volume {
return k8sutil.CreateVolumeWithSecret(shared.TlsKeyfileVolumeName,
k8sutil.CreateTLSKeyfileSecretName(testDeploymentName, serverGroupString, ID))
@ -138,11 +142,12 @@ func createTestReadinessSimpleProbe(mode string, secure bool, authorization stri
}
func createTestLivenessProbe(mode string, secure bool, authorization string, port int) *core.Probe {
return getProbeCreator(mode)(secure, authorization, "/_api/version", port).Create()
return getProbeCreator(mode)(secure, authorization, "/_api/version", port, api.ProbeTypeLiveness).Create()
}
func createTestReadinessProbe(mode string, secure bool, authorization string) *core.Probe {
p := getProbeCreator(mode)(secure, authorization, "/_admin/server/availability", shared.ArangoPort).Create()
p := getProbeCreator(mode)(secure, authorization, "/_admin/server/availability", shared.ArangoPort,
api.ProbeTypeReadiness).Create()
p.InitialDelaySeconds = 2
p.PeriodSeconds = 2
@ -151,7 +156,7 @@ func createTestReadinessProbe(mode string, secure bool, authorization string) *c
}
func createTestStartupProbe(mode string, secure bool, authorization string, failureThreshold int32) *core.Probe {
p := getProbeCreator(mode)(secure, authorization, "/_api/version", shared.ArangoPort).Create()
p := getProbeCreator(mode)(secure, authorization, "/_api/version", shared.ArangoPort, api.ProbeTypeStartUp).Create()
p.InitialDelaySeconds = 1
p.PeriodSeconds = 5
@ -160,7 +165,7 @@ func createTestStartupProbe(mode string, secure bool, authorization string, fail
return p
}
type probeCreator func(secure bool, authorization, endpoint string, port int) resources.Probe
type probeCreator func(secure bool, authorization, endpoint string, port int, probe api.ProbeType) resources.Probe
const (
cmdProbe = "cmdProbe"
@ -177,24 +182,24 @@ func getProbeCreator(t string) probeCreator {
}
func getHTTPProbeCreator() probeCreator {
return func(secure bool, authorization, endpoint string, port int) resources.Probe {
return func(secure bool, authorization, endpoint string, port int, _ api.ProbeType) resources.Probe {
return createHTTPTestProbe(secure, authorization, endpoint, port)
}
}
func getCMDProbeCreator() probeCreator {
return func(secure bool, authorization, endpoint string, port int) resources.Probe {
return createCMDTestProbe(secure, authorization != "", endpoint)
return func(secure bool, authorization, endpoint string, port int, probeType api.ProbeType) resources.Probe {
return createCMDTestProbe(secure, authorization != "", probeType)
}
}
func createCMDTestProbe(secure, authorization bool, endpoint string) resources.Probe {
func createCMDTestProbe(secure, authorization bool, probeType api.ProbeType) resources.Probe {
bin, _ := os.Executable()
args := []string{
filepath.Join(k8sutil.LifecycleVolumeMountDir, filepath.Base(bin)),
"lifecycle",
"probe",
fmt.Sprintf("--endpoint=%s", endpoint),
string(probeType),
}
if secure {
@ -270,7 +275,8 @@ func createTestCommandForDBServer(name string, tls, auth, encryptionRocksDB bool
args.Merge(mod())
}
return append(command, args.Unique().AsArgs()...)
sorted := args.Sort().Unique().AsArgs()
return append(command, sorted...)
}
func createTestCommandForCoordinator(name string, tls, auth bool, mods ...func() k8sutil.OptionPairs) []string {
@ -526,11 +532,12 @@ func createTestPorts() []core.ContainerPort {
}
func createTestImagesWithVersion(enterprise bool, version driver.Version) api.ImageInfoList {
i := createTestImageForVersion(string(version))
return api.ImageInfoList{
{
Image: testImage,
Image: i,
ArangoDBVersion: version,
ImageID: testImage,
ImageID: i,
Enterprise: enterprise,
},
}
@ -596,7 +603,7 @@ func (testCase *testCaseStruct) createTestPodData(deployment *Deployment, group
var auth string
var retries int32 = 720 // one hour divide by 5.
if group == api.ServerGroupDBServers {
retries = 4320 // 6 hours divide by 5.
retries = testProbeDBServerRetires
}
if deployment.GetSpec().IsAuthenticated() {
auth, _ = createTestToken(deployment, testCase, []string{"/_api/version"})

View file

@ -135,6 +135,13 @@ func createArangodArgs(cachedStatus interfaces.Inspector, input pod.Input, addit
options.Add("--cluster.my-role", "PRIMARY")
options.Add("--foxx.queues", false)
options.Add("--server.statistics", "true")
imageInfo := api.ImageInfo{
ArangoDBVersion: input.Version,
Enterprise: input.Enterprise,
}
if IsServerProgressAvailable(input.Group, imageInfo) {
options.Add("--server.early-connections", "true")
}
case api.ServerGroupCoordinators:
addAgentEndpoints = true
options.Add("--cluster.my-address", myTCPURL)

View file

@ -138,17 +138,17 @@ func (a *ArangoDContainer) GetSecurityContext() *core.SecurityContext {
func (a *ArangoDContainer) GetProbes() (*core.Probe, *core.Probe, *core.Probe, error) {
var liveness, readiness, startup *core.Probe
probeLivenessConfig, err := a.resources.getLivenessProbe(a.spec, a.group, a.imageInfo.ArangoDBVersion)
probeLivenessConfig, err := a.resources.getLivenessProbe(a.spec, a.group, a.imageInfo)
if err != nil {
return nil, nil, nil, err
}
probeReadinessConfig, err := a.resources.getReadinessProbe(a.spec, a.group, a.imageInfo.ArangoDBVersion)
probeReadinessConfig, err := a.resources.getReadinessProbe(a.spec, a.group, a.imageInfo)
if err != nil {
return nil, nil, nil, err
}
probeStartupConfig, err := a.resources.getStartupProbe(a.spec, a.group, a.imageInfo.ArangoDBVersion)
probeStartupConfig, err := a.resources.getStartupProbe(a.spec, a.group, a.imageInfo)
if err != nil {
return nil, nil, nil, err
}

View file

@ -21,14 +21,13 @@
package resources
import (
"fmt"
"math"
"os"
"path/filepath"
"time"
core "k8s.io/api/core/v1"
"github.com/arangodb/go-driver"
"github.com/arangodb/go-driver/jwt"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
@ -51,25 +50,25 @@ type probeCheckBuilder struct {
liveness, readiness, startup probeBuilder
}
type probeBuilder func(spec api.DeploymentSpec, group api.ServerGroup, version driver.Version) (Probe, error)
type probeBuilder func(spec api.DeploymentSpec, group api.ServerGroup, _ api.ImageInfo) (Probe, error)
func nilProbeBuilder(spec api.DeploymentSpec, group api.ServerGroup, version driver.Version) (Probe, error) {
func nilProbeBuilder(_ api.DeploymentSpec, _ api.ServerGroup, _ api.ImageInfo) (Probe, error) {
return nil, nil
}
func (r *Resources) getReadinessProbe(spec api.DeploymentSpec, group api.ServerGroup, version driver.Version) (Probe, error) {
func (r *Resources) getReadinessProbe(spec api.DeploymentSpec, group api.ServerGroup, imageInfo api.ImageInfo) (Probe, error) {
if !r.isReadinessProbeEnabled(spec, group) {
return nil, nil
}
builders := r.probeBuilders()
builders := r.probeBuilders(imageInfo)
builder, ok := builders[group]
if !ok {
return nil, nil
}
config, err := builder.readiness(spec, group, version)
config, err := builder.readiness(spec, group, imageInfo)
if err != nil {
return nil, err
}
@ -87,19 +86,19 @@ func (r *Resources) getReadinessProbe(spec api.DeploymentSpec, group api.ServerG
return config, nil
}
func (r *Resources) getLivenessProbe(spec api.DeploymentSpec, group api.ServerGroup, version driver.Version) (Probe, error) {
func (r *Resources) getLivenessProbe(spec api.DeploymentSpec, group api.ServerGroup, imageInfo api.ImageInfo) (Probe, error) {
if !r.isLivenessProbeEnabled(spec, group) {
return nil, nil
}
builders := r.probeBuilders()
builders := r.probeBuilders(imageInfo)
builder, ok := builders[group]
if !ok {
return nil, nil
}
config, err := builder.liveness(spec, group, version)
config, err := builder.liveness(spec, group, imageInfo)
if err != nil {
return nil, err
}
@ -117,19 +116,19 @@ func (r *Resources) getLivenessProbe(spec api.DeploymentSpec, group api.ServerGr
return config, nil
}
func (r *Resources) getStartupProbe(spec api.DeploymentSpec, group api.ServerGroup, version driver.Version) (Probe, error) {
func (r *Resources) getStartupProbe(spec api.DeploymentSpec, group api.ServerGroup, imageInfo api.ImageInfo) (Probe, error) {
if !r.isStartupProbeEnabled(spec, group) {
return nil, nil
}
builders := r.probeBuilders()
builders := r.probeBuilders(imageInfo)
builder, ok := builders[group]
if !ok {
return nil, nil
}
config, err := builder.startup(spec, group, version)
config, err := builder.startup(spec, group, imageInfo)
if err != nil {
return nil, err
}
@ -189,26 +188,26 @@ func (r *Resources) isStartupProbeEnabled(spec api.DeploymentSpec, group api.Ser
return probe.CanBeEnabled && probe.EnabledByDefault
}
func (r *Resources) probeBuilders() map[api.ServerGroup]probeCheckBuilder {
func (r *Resources) probeBuilders(imageInfo api.ImageInfo) map[api.ServerGroup]probeCheckBuilder {
return map[api.ServerGroup]probeCheckBuilder{
api.ServerGroupSingle: {
startup: r.probeBuilderStartupCoreSelect(),
liveness: r.probeBuilderLivenessCoreSelect(),
startup: r.probeBuilderStartupCoreSelect(api.ServerGroupSingle, imageInfo),
liveness: r.probeBuilderLivenessCoreSelect(api.ServerGroupSingle, imageInfo),
readiness: r.probeBuilderReadinessCoreSelect(),
},
api.ServerGroupAgents: {
startup: r.probeBuilderStartupCoreSelect(),
liveness: r.probeBuilderLivenessCoreSelect(),
startup: r.probeBuilderStartupCoreSelect(api.ServerGroupAgents, imageInfo),
liveness: r.probeBuilderLivenessCoreSelect(api.ServerGroupAgents, imageInfo),
readiness: r.probeBuilderReadinessSimpleCoreSelect(),
},
api.ServerGroupDBServers: {
startup: r.probeBuilderStartupCoreSelect(),
liveness: r.probeBuilderLivenessCoreSelect(),
startup: r.probeBuilderStartupCoreSelect(api.ServerGroupDBServers, imageInfo),
liveness: r.probeBuilderLivenessCoreSelect(api.ServerGroupDBServers, imageInfo),
readiness: r.probeBuilderReadinessSimpleCoreSelect(),
},
api.ServerGroupCoordinators: {
startup: r.probeBuilderStartupCoreSelect(),
liveness: r.probeBuilderLivenessCoreSelect(),
startup: r.probeBuilderStartupCoreSelect(api.ServerGroupCoordinators, imageInfo),
liveness: r.probeBuilderLivenessCoreSelect(api.ServerGroupCoordinators, imageInfo),
readiness: r.probeBuilderReadinessCoreSelect(),
},
api.ServerGroupSyncMasters: {
@ -224,7 +223,7 @@ func (r *Resources) probeBuilders() map[api.ServerGroup]probeCheckBuilder {
}
}
func (r *Resources) probeCommand(spec api.DeploymentSpec, endpoint string) ([]string, error) {
func (r *Resources) probeCommand(spec api.DeploymentSpec, probeType api.ProbeType) ([]string, error) {
binaryPath, err := os.Executable()
if err != nil {
return nil, err
@ -234,7 +233,8 @@ func (r *Resources) probeCommand(spec api.DeploymentSpec, endpoint string) ([]st
exePath,
"lifecycle",
"probe",
fmt.Sprintf("--endpoint=%s", endpoint),
string(probeType),
// TODO test rotation required when changed
}
if spec.IsSecure() {
@ -248,40 +248,50 @@ func (r *Resources) probeCommand(spec api.DeploymentSpec, endpoint string) ([]st
return args, nil
}
func (r *Resources) probeBuilderLivenessCoreSelect() probeBuilder {
if features.JWTRotation().Enabled() {
func (r *Resources) probeBuilderLivenessCoreSelect(group api.ServerGroup, imageInfo api.ImageInfo) probeBuilder {
if features.JWTRotation().Enabled() || IsServerProgressAvailable(group, imageInfo) {
return r.probeBuilderLivenessCoreOperator
}
return r.probeBuilderLivenessCore
}
func (r *Resources) probeBuilderStartupCoreSelect() probeBuilder {
if features.JWTRotation().Enabled() {
func (r *Resources) probeBuilderStartupCoreSelect(group api.ServerGroup, imageInfo api.ImageInfo) probeBuilder {
if features.JWTRotation().Enabled() || IsServerProgressAvailable(group, imageInfo) {
return r.probeBuilderStartupCoreOperator
}
return r.probeBuilderStartupCore
}
func (r *Resources) probeBuilderLivenessCoreOperator(spec api.DeploymentSpec, group api.ServerGroup, version driver.Version) (Probe, error) {
args, err := r.probeCommand(spec, "/_api/version")
func (r *Resources) probeBuilderLivenessCoreOperator(spec api.DeploymentSpec, group api.ServerGroup,
image api.ImageInfo) (Probe, error) {
args, err := r.probeCommand(spec, api.ProbeTypeLiveness)
if err != nil {
return nil, err
}
return &probes.CMDProbeConfig{
cmdProbeConfig := &probes.CMDProbeConfig{
Command: args,
}, nil
}
if IsServerProgressAvailable(group, image) {
cmdProbeConfig.FailureThreshold = math.MaxInt32
}
return cmdProbeConfig, nil
}
func (r *Resources) probeBuilderStartupCoreOperator(spec api.DeploymentSpec, group api.ServerGroup, version driver.Version) (Probe, error) {
args, err := r.probeCommand(spec, "/_api/version")
func (r *Resources) probeBuilderStartupCoreOperator(spec api.DeploymentSpec, group api.ServerGroup,
image api.ImageInfo) (Probe, error) {
args, err := r.probeCommand(spec, api.ProbeTypeStartUp)
if err != nil {
return nil, err
}
retries, periodSeconds := getProbeRetries(group)
if IsServerProgressAvailable(group, image) {
retries = math.MaxInt32
}
return &probes.CMDProbeConfig{
Command: args,
@ -291,7 +301,7 @@ func (r *Resources) probeBuilderStartupCoreOperator(spec api.DeploymentSpec, gro
}, nil
}
func (r *Resources) probeBuilderLivenessCore(spec api.DeploymentSpec, group api.ServerGroup, version driver.Version) (Probe, error) {
func (r *Resources) probeBuilderLivenessCore(spec api.DeploymentSpec, _ api.ServerGroup, _ api.ImageInfo) (Probe, error) {
authorization := ""
if spec.IsAuthenticated() {
secretData, err := r.getJWTSecret(spec)
@ -310,7 +320,7 @@ func (r *Resources) probeBuilderLivenessCore(spec api.DeploymentSpec, group api.
}, nil
}
func (r *Resources) probeBuilderStartupCore(spec api.DeploymentSpec, group api.ServerGroup, _ driver.Version) (Probe, error) {
func (r *Resources) probeBuilderStartupCore(spec api.DeploymentSpec, group api.ServerGroup, _ api.ImageInfo) (Probe, error) {
retries, periodSeconds := getProbeRetries(group)
authorization := ""
@ -342,8 +352,9 @@ func (r *Resources) probeBuilderReadinessSimpleCoreSelect() probeBuilder {
return r.probeBuilderReadinessSimpleCore
}
func (r *Resources) probeBuilderReadinessSimpleCoreOperator(spec api.DeploymentSpec, group api.ServerGroup, version driver.Version) (Probe, error) {
p, err := r.probeBuilderReadinessCoreOperator(spec, group, version)
func (r *Resources) probeBuilderReadinessSimpleCoreOperator(spec api.DeploymentSpec, group api.ServerGroup,
image api.ImageInfo) (Probe, error) {
p, err := r.probeBuilderReadinessCoreOperator(spec, group, image)
if err != nil {
return nil, err
}
@ -360,8 +371,9 @@ func (r *Resources) probeBuilderReadinessSimpleCoreOperator(spec api.DeploymentS
return p, nil
}
func (r *Resources) probeBuilderReadinessSimpleCore(spec api.DeploymentSpec, group api.ServerGroup, version driver.Version) (Probe, error) {
p, err := r.probeBuilderReadinessCore(spec, group, version)
func (r *Resources) probeBuilderReadinessSimpleCore(spec api.DeploymentSpec, group api.ServerGroup,
image api.ImageInfo) (Probe, error) {
p, err := r.probeBuilderReadinessCore(spec, group, image)
if err != nil {
return nil, err
}
@ -386,13 +398,8 @@ func (r *Resources) probeBuilderReadinessCoreSelect() probeBuilder {
return r.probeBuilderReadinessCore
}
func (r *Resources) probeBuilderReadinessCoreOperator(spec api.DeploymentSpec, _ api.ServerGroup, _ driver.Version) (Probe, error) {
// /_admin/server/availability is the way to go, it is available since 3.3.9
path := "/_admin/server/availability"
if features.FailoverLeadership().Enabled() && r.context.GetMode() == api.DeploymentModeActiveFailover {
path = "/_api/version"
}
args, err := r.probeCommand(spec, path)
func (r *Resources) probeBuilderReadinessCoreOperator(spec api.DeploymentSpec, _ api.ServerGroup, _ api.ImageInfo) (Probe, error) {
args, err := r.probeCommand(spec, api.ProbeTypeReadiness)
if err != nil {
return nil, err
}
@ -404,7 +411,7 @@ func (r *Resources) probeBuilderReadinessCoreOperator(spec api.DeploymentSpec, _
}, nil
}
func (r *Resources) probeBuilderReadinessCore(spec api.DeploymentSpec, _ api.ServerGroup, _ driver.Version) (Probe, error) {
func (r *Resources) probeBuilderReadinessCore(spec api.DeploymentSpec, _ api.ServerGroup, _ api.ImageInfo) (Probe, error) {
// /_admin/server/availability is the way to go, it is available since 3.3.9
localPath := "/_admin/server/availability"
if features.FailoverLeadership().Enabled() && r.context.GetMode() == api.DeploymentModeActiveFailover {
@ -433,7 +440,7 @@ func (r *Resources) probeBuilderReadinessCore(spec api.DeploymentSpec, _ api.Ser
return probeCfg, nil
}
func (r *Resources) probeBuilderLivenessSync(spec api.DeploymentSpec, group api.ServerGroup, version driver.Version) (Probe, error) {
func (r *Resources) probeBuilderLivenessSync(spec api.DeploymentSpec, group api.ServerGroup, _ api.ImageInfo) (Probe, error) {
authorization := ""
port := shared.ArangoSyncMasterPort
if group == api.ServerGroupSyncWorkers {
@ -468,7 +475,7 @@ func (r *Resources) probeBuilderLivenessSync(spec api.DeploymentSpec, group api.
}, nil
}
func (r *Resources) probeBuilderStartupSync(spec api.DeploymentSpec, group api.ServerGroup, _ driver.Version) (Probe, error) {
func (r *Resources) probeBuilderStartupSync(spec api.DeploymentSpec, group api.ServerGroup, _ api.ImageInfo) (Probe, error) {
authorization := ""
port := shared.ArangoSyncMasterPort
if group == api.ServerGroupSyncWorkers {
@ -518,3 +525,9 @@ func getProbeRetries(group api.ServerGroup) (int32, int32) {
return int32(howLong / period), int32(period / time.Second)
}
// IsServerProgressAvailable returns true if server progress is available.
func IsServerProgressAvailable(group api.ServerGroup, imageInfo api.ImageInfo) bool {
return group == api.ServerGroupDBServers &&
features.Version310().Supported(imageInfo.ArangoDBVersion, imageInfo.Enterprise)
}

View file

@ -105,17 +105,17 @@ func (a *ArangoSyncContainer) GetSecurityContext() *core.SecurityContext {
func (a *ArangoSyncContainer) GetProbes() (*core.Probe, *core.Probe, *core.Probe, error) {
var liveness, readiness, startup *core.Probe
probeLivenessConfig, err := a.resources.getLivenessProbe(a.spec, a.group, a.imageInfo.ArangoDBVersion)
probeLivenessConfig, err := a.resources.getLivenessProbe(a.spec, a.group, a.imageInfo)
if err != nil {
return nil, nil, nil, err
}
probeReadinessConfig, err := a.resources.getReadinessProbe(a.spec, a.group, a.imageInfo.ArangoDBVersion)
probeReadinessConfig, err := a.resources.getReadinessProbe(a.spec, a.group, a.imageInfo)
if err != nil {
return nil, nil, nil, err
}
probeStartupConfig, err := a.resources.getReadinessProbe(a.spec, a.group, a.imageInfo.ArangoDBVersion)
probeStartupConfig, err := a.resources.getReadinessProbe(a.spec, a.group, a.imageInfo)
if err != nil {
return nil, nil, nil, err
}