Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Add Pending Member phase
- Add Ephemeral Volumes for apps feature
- Check if the DB server is cleaned out.
- Render Pod Template in ArangoMember Spec and Status

## [1.2.1](https://github.com/arangodb/kube-arangodb/tree/1.2.1) (2021-07-28)
- Fix ArangoMember race with multiple ArangoDeployments within single namespace
Expand Down
174 changes: 107 additions & 67 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@ import (
goflag "flag"
"fmt"
"net"
"net/http"
"os"
"strconv"
"strings"
"time"

operatorHTTP "github.com/arangodb/kube-arangodb/pkg/util/http"
"github.com/gin-gonic/gin"

"github.com/arangodb/kube-arangodb/pkg/version"

"github.com/arangodb/kube-arangodb/pkg/util/arangod"
Expand Down Expand Up @@ -110,6 +114,7 @@ var (
enableDeploymentReplication bool // Run deployment-replication operator
enableStorage bool // Run local-storage operator
enableBackup bool // Run backup operator
versionOnly bool // Run only version endpoint, explicitly disabled with other

alpineImage, metricsExporterImage, arangoImage string

Expand Down Expand Up @@ -143,6 +148,7 @@ func init() {
f.BoolVar(&operatorOptions.enableDeploymentReplication, "operator.deployment-replication", false, "Enable to run the ArangoDeploymentReplication operator")
f.BoolVar(&operatorOptions.enableStorage, "operator.storage", false, "Enable to run the ArangoLocalStorage operator")
f.BoolVar(&operatorOptions.enableBackup, "operator.backup", false, "Enable to run the ArangoBackup operator")
f.BoolVar(&operatorOptions.versionOnly, "operator.version", false, "Enable only version endpoint in Operator")
f.StringVar(&operatorOptions.alpineImage, "operator.alpine-image", UBIImageEnv.GetOrDefault(defaultAlpineImage), "Docker image used for alpine containers")
f.MarkDeprecated("operator.alpine-image", "Value is not used anymore")
f.StringVar(&operatorOptions.metricsExporterImage, "operator.metrics-exporter-image", MetricsExporterImageEnv.GetOrDefault(defaultMetricsExporterImage), "Docker image used for metrics containers by default")
Expand Down Expand Up @@ -198,7 +204,11 @@ func cmdMainRun(cmd *cobra.Command, args []string) {

// Check operating mode
if !operatorOptions.enableDeployment && !operatorOptions.enableDeploymentReplication && !operatorOptions.enableStorage && !operatorOptions.enableBackup {
cliLog.Fatal().Err(err).Msg("Turn on --operator.deployment, --operator.deployment-replication, --operator.storage, --operator.backup or any combination of these")
if !operatorOptions.versionOnly {
cliLog.Fatal().Err(err).Msg("Turn on --operator.deployment, --operator.deployment-replication, --operator.storage, --operator.backup or any combination of these")
}
} else if operatorOptions.versionOnly {
cliLog.Fatal().Err(err).Msg("Options --operator.deployment, --operator.deployment-replication, --operator.storage, --operator.backup cannot be enabled together with --operator.version")
}

// Log version
Expand All @@ -208,81 +218,111 @@ func cmdMainRun(cmd *cobra.Command, args []string) {
Msgf("Starting arangodb-operator (%s), version %s build %s", version.GetVersionV1().Edition.Title(), version.GetVersionV1().Version, version.GetVersionV1().Build)

// Check environment
if len(namespace) == 0 {
cliLog.Fatal().Msgf("%s environment variable missing", constants.EnvOperatorPodNamespace)
}
if len(name) == 0 {
cliLog.Fatal().Msgf("%s environment variable missing", constants.EnvOperatorPodName)
}
if len(ip) == 0 {
cliLog.Fatal().Msgf("%s environment variable missing", constants.EnvOperatorPodIP)
}
if !operatorOptions.versionOnly {
if len(namespace) == 0 {
cliLog.Fatal().Msgf("%s environment variable missing", constants.EnvOperatorPodNamespace)
}
if len(name) == 0 {
cliLog.Fatal().Msgf("%s environment variable missing", constants.EnvOperatorPodName)
}
if len(ip) == 0 {
cliLog.Fatal().Msgf("%s environment variable missing", constants.EnvOperatorPodIP)
}

// Get host name
id, err := os.Hostname()
if err != nil {
cliLog.Fatal().Err(err).Msg("Failed to get hostname")
}
// Get host name
id, err := os.Hostname()
if err != nil {
cliLog.Fatal().Err(err).Msg("Failed to get hostname")
}

// Create kubernetes client
kubecli, err := k8sutil.NewKubeClient()
if err != nil {
cliLog.Fatal().Err(err).Msg("Failed to create Kubernetes client")
}
secrets := kubecli.CoreV1().Secrets(namespace)
// Create kubernetes client
kubecli, err := k8sutil.NewKubeClient()
if err != nil {
cliLog.Fatal().Err(err).Msg("Failed to create Kubernetes client")
}
secrets := kubecli.CoreV1().Secrets(namespace)

// Create operator
cfg, deps, err := newOperatorConfigAndDeps(id+"-"+name, namespace, name)
if err != nil {
cliLog.Fatal().Err(err).Msg("Failed to create operator config & deps")
}
o, err := operator.NewOperator(cfg, deps)
if err != nil {
cliLog.Fatal().Err(err).Msg("Failed to create operator")
// Create operator
cfg, deps, err := newOperatorConfigAndDeps(id+"-"+name, namespace, name)
if err != nil {
cliLog.Fatal().Err(err).Msg("Failed to create operator config & deps")
}
o, err := operator.NewOperator(cfg, deps)
if err != nil {
cliLog.Fatal().Err(err).Msg("Failed to create operator")
}

listenAddr := net.JoinHostPort(serverOptions.host, strconv.Itoa(serverOptions.port))
if svr, err := server.NewServer(kubecli.CoreV1(), server.Config{
Namespace: namespace,
Address: listenAddr,
TLSSecretName: serverOptions.tlsSecretName,
TLSSecretNamespace: namespace,
PodName: name,
PodIP: ip,
AdminSecretName: serverOptions.adminSecretName,
AllowAnonymous: serverOptions.allowAnonymous,
}, server.Dependencies{
Log: logService.MustGetLogger(logging.LoggerNameServer),
LivenessProbe: &livenessProbe,
Deployment: server.OperatorDependency{
Enabled: cfg.EnableDeployment,
Probe: &deploymentProbe,
},
DeploymentReplication: server.OperatorDependency{
Enabled: cfg.EnableDeploymentReplication,
Probe: &deploymentReplicationProbe,
},
Storage: server.OperatorDependency{
Enabled: cfg.EnableStorage,
Probe: &storageProbe,
},
Backup: server.OperatorDependency{
Enabled: cfg.EnableBackup,
Probe: &backupProbe,
},
Operators: o,

Secrets: secrets,
}); err != nil {
cliLog.Fatal().Err(err).Msg("Failed to create HTTP server")
} else {
go utilsError.LogError(cliLog, "error while starting service", svr.Run)
}

// startChaos(context.Background(), cfg.KubeCli, cfg.Namespace, chaosLevel)

// Start operator
o.Run()
} else {
if err := startVersionProcess(); err != nil {
cliLog.Fatal().Err(err).Msg("Failed to create HTTP server")
}
}
}

func startVersionProcess() error {
// Just expose version
listenAddr := net.JoinHostPort(serverOptions.host, strconv.Itoa(serverOptions.port))
if svr, err := server.NewServer(kubecli.CoreV1(), server.Config{
Namespace: namespace,
Address: listenAddr,
TLSSecretName: serverOptions.tlsSecretName,
TLSSecretNamespace: namespace,
PodName: name,
PodIP: ip,
AdminSecretName: serverOptions.adminSecretName,
AllowAnonymous: serverOptions.allowAnonymous,
}, server.Dependencies{
Log: logService.MustGetLogger(logging.LoggerNameServer),
LivenessProbe: &livenessProbe,
Deployment: server.OperatorDependency{
Enabled: cfg.EnableDeployment,
Probe: &deploymentProbe,
},
DeploymentReplication: server.OperatorDependency{
Enabled: cfg.EnableDeploymentReplication,
Probe: &deploymentReplicationProbe,
},
Storage: server.OperatorDependency{
Enabled: cfg.EnableStorage,
Probe: &storageProbe,
},
Backup: server.OperatorDependency{
Enabled: cfg.EnableBackup,
Probe: &backupProbe,
},
Operators: o,

Secrets: secrets,
}); err != nil {
cliLog.Fatal().Err(err).Msg("Failed to create HTTP server")
} else {
go utilsError.LogError(cliLog, "error while starting service", svr.Run)
cliLog.Info().Str("addr", listenAddr).Msgf("Starting version endpoint")

gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(gin.Recovery())

versionV1Responser, err := operatorHTTP.NewSimpleJSONResponse(version.GetVersionV1())
if err != nil {
return errors.WithStack(err)
}
r.GET("/_api/version", gin.WrapF(versionV1Responser.ServeHTTP))
r.GET("/api/v1/version", gin.WrapF(versionV1Responser.ServeHTTP))

// startChaos(context.Background(), cfg.KubeCli, cfg.Namespace, chaosLevel)
s := http.Server{
Addr: listenAddr,
Handler: r,
}

// Start operator
o.Run()
return s.ListenAndServe()
}

// newOperatorConfigAndDeps creates operator config & dependencies.
Expand Down
81 changes: 81 additions & 0 deletions pkg/apis/deployment/v1/arango_member_pod_template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//
// DISCLAIMER
//
// Copyright 2021 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
//
// Author Adam Janikowski
//

package v1

import (
"crypto/sha256"
"fmt"

core "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/json"
)

func GetArangoMemberPodTemplate(pod *core.PodTemplateSpec, podSpecChecksum string) (*ArangoMemberPodTemplate, error) {
data, err := json.Marshal(pod.Spec)
if err != nil {
return nil, err
}

return &ArangoMemberPodTemplate{
PodSpec: pod,
PodSpecChecksum: podSpecChecksum,
Checksum: fmt.Sprintf("%0x", sha256.Sum256(data)),
}, nil
}

type ArangoMemberPodTemplate struct {
PodSpec *core.PodTemplateSpec `json:"podSpec,omitempty"`
PodSpecChecksum string `json:"podSpecChecksum,omitempty"`
Checksum string `json:"checksum,omitempty"`
}

func (a *ArangoMemberPodTemplate) Equals(b *ArangoMemberPodTemplate) bool {
if a == nil && b == nil {
return true
}

if a == nil || b == nil {
return false
}

return a.Checksum == b.Checksum && a.PodSpecChecksum == b.PodSpecChecksum
}

func (a *ArangoMemberPodTemplate) RotationNeeded(b *ArangoMemberPodTemplate) bool {
if a == nil && b == nil {
return true
}

if a == nil || b == nil {
return true
}

return a.PodSpecChecksum != b.PodSpecChecksum
}

func (a *ArangoMemberPodTemplate) EqualPodSpecChecksum(checksum string) bool {
if a == nil {
return false
}
return checksum == a.PodSpecChecksum
}
4 changes: 1 addition & 3 deletions pkg/apis/deployment/v1/arango_member_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
package v1

import (
core "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
)

Expand All @@ -32,6 +31,5 @@ type ArangoMemberSpec struct {
ID string `json:"id,omitempty"`
DeploymentUID types.UID `json:"deploymentUID,omitempty"`

Template *core.PodTemplate `json:"template,omitempty"`
TemplateChecksum string `json:"templateChecksum,omitempty"`
Template *ArangoMemberPodTemplate `json:"template,omitempty"`
}
11 changes: 11 additions & 0 deletions pkg/apis/deployment/v1/arango_member_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,16 @@

package v1

const (
ArangoMemberConditionPendingRestart ConditionType = "pending-restart"
)

type ArangoMemberStatus struct {
Conditions ConditionList `json:"conditions,omitempty"`

Template *ArangoMemberPodTemplate `json:"template,omitempty"`
}

func (a ArangoMemberStatus) IsPendingRestart() bool {
return a.Conditions.IsTrue(ArangoMemberConditionPendingRestart)
}
8 changes: 8 additions & 0 deletions pkg/apis/deployment/v1/conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ import (
// ConditionType is a strongly typed condition name
type ConditionType string

func (c ConditionType) String() string {
return string(c)
}

const (
// ConditionTypeReady indicates that the member or entire deployment is ready and running normally.
ConditionTypeReady ConditionType = "Ready"
Expand Down Expand Up @@ -67,6 +71,10 @@ const (
ConditionTypeUpgradeFailed ConditionType = "UpgradeFailed"
// ConditionTypeMaintenanceMode indicates that Maintenance is enabled
ConditionTypeMaintenanceMode ConditionType = "MaintenanceMode"
// ConditionTypePendingRestart indicates that restart is required
ConditionTypePendingRestart ConditionType = "PendingRestart"
// ConditionTypePendingTLSRotation indicates that TLS rotation is pending
ConditionTypePendingTLSRotation ConditionType = "PendingTLSRotation"
)

// Condition represents one current condition of a deployment or deployment member.
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/deployment/v1/deployment_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,7 @@ func (ds *DeploymentStatus) Equal(other DeploymentStatus) bool {
func (ds *DeploymentStatus) IsForceReload() bool {
return util.BoolOrDefault(ds.ForceStatusReload, false)
}

func (ds *DeploymentStatus) IsPlanEmpty() bool {
return ds.Plan.IsEmpty() && ds.HighPriorityPlan.IsEmpty()
}
4 changes: 4 additions & 0 deletions pkg/apis/deployment/v1/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ const (
ActionTypeSetMemberCondition ActionType = "SetMemberCondition"
// ActionTypeMemberRIDUpdate updated member Run ID (UID). High priority
ActionTypeMemberRIDUpdate ActionType = "MemberRIDUpdate"
// ActionTypeArangoMemberUpdatePodSpec updates pod spec
ActionTypeArangoMemberUpdatePodSpec ActionType = "ArangoMemberUpdatePodSpec"
// ActionTypeArangoMemberUpdatePodStatus updates pod spec
ActionTypeArangoMemberUpdatePodStatus ActionType = "ArangoMemberUpdatePodStatus"
)

const (
Expand Down
Loading