Skip to content

Commit f62fdf2

Browse files
authored
[Feature] ArangoMember template propagation (#773)
1 parent d9aa13c commit f62fdf2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1525
-493
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- Add Pending Member phase
77
- Add Ephemeral Volumes for apps feature
88
- Check if the DB server is cleaned out.
9+
- Render Pod Template in ArangoMember Spec and Status
910

1011
## [1.2.1](https://github.com/arangodb/kube-arangodb/tree/1.2.1) (2021-07-28)
1112
- Fix ArangoMember race with multiple ArangoDeployments within single namespace

main.go

Lines changed: 107 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,15 @@ import (
2828
goflag "flag"
2929
"fmt"
3030
"net"
31+
"net/http"
3132
"os"
3233
"strconv"
3334
"strings"
3435
"time"
3536

37+
operatorHTTP "github.com/arangodb/kube-arangodb/pkg/util/http"
38+
"github.com/gin-gonic/gin"
39+
3640
"github.com/arangodb/kube-arangodb/pkg/version"
3741

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

114119
alpineImage, metricsExporterImage, arangoImage string
115120

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

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

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

210220
// Check environment
211-
if len(namespace) == 0 {
212-
cliLog.Fatal().Msgf("%s environment variable missing", constants.EnvOperatorPodNamespace)
213-
}
214-
if len(name) == 0 {
215-
cliLog.Fatal().Msgf("%s environment variable missing", constants.EnvOperatorPodName)
216-
}
217-
if len(ip) == 0 {
218-
cliLog.Fatal().Msgf("%s environment variable missing", constants.EnvOperatorPodIP)
219-
}
221+
if !operatorOptions.versionOnly {
222+
if len(namespace) == 0 {
223+
cliLog.Fatal().Msgf("%s environment variable missing", constants.EnvOperatorPodNamespace)
224+
}
225+
if len(name) == 0 {
226+
cliLog.Fatal().Msgf("%s environment variable missing", constants.EnvOperatorPodName)
227+
}
228+
if len(ip) == 0 {
229+
cliLog.Fatal().Msgf("%s environment variable missing", constants.EnvOperatorPodIP)
230+
}
220231

221-
// Get host name
222-
id, err := os.Hostname()
223-
if err != nil {
224-
cliLog.Fatal().Err(err).Msg("Failed to get hostname")
225-
}
232+
// Get host name
233+
id, err := os.Hostname()
234+
if err != nil {
235+
cliLog.Fatal().Err(err).Msg("Failed to get hostname")
236+
}
226237

227-
// Create kubernetes client
228-
kubecli, err := k8sutil.NewKubeClient()
229-
if err != nil {
230-
cliLog.Fatal().Err(err).Msg("Failed to create Kubernetes client")
231-
}
232-
secrets := kubecli.CoreV1().Secrets(namespace)
238+
// Create kubernetes client
239+
kubecli, err := k8sutil.NewKubeClient()
240+
if err != nil {
241+
cliLog.Fatal().Err(err).Msg("Failed to create Kubernetes client")
242+
}
243+
secrets := kubecli.CoreV1().Secrets(namespace)
233244

234-
// Create operator
235-
cfg, deps, err := newOperatorConfigAndDeps(id+"-"+name, namespace, name)
236-
if err != nil {
237-
cliLog.Fatal().Err(err).Msg("Failed to create operator config & deps")
238-
}
239-
o, err := operator.NewOperator(cfg, deps)
240-
if err != nil {
241-
cliLog.Fatal().Err(err).Msg("Failed to create operator")
245+
// Create operator
246+
cfg, deps, err := newOperatorConfigAndDeps(id+"-"+name, namespace, name)
247+
if err != nil {
248+
cliLog.Fatal().Err(err).Msg("Failed to create operator config & deps")
249+
}
250+
o, err := operator.NewOperator(cfg, deps)
251+
if err != nil {
252+
cliLog.Fatal().Err(err).Msg("Failed to create operator")
253+
}
254+
255+
listenAddr := net.JoinHostPort(serverOptions.host, strconv.Itoa(serverOptions.port))
256+
if svr, err := server.NewServer(kubecli.CoreV1(), server.Config{
257+
Namespace: namespace,
258+
Address: listenAddr,
259+
TLSSecretName: serverOptions.tlsSecretName,
260+
TLSSecretNamespace: namespace,
261+
PodName: name,
262+
PodIP: ip,
263+
AdminSecretName: serverOptions.adminSecretName,
264+
AllowAnonymous: serverOptions.allowAnonymous,
265+
}, server.Dependencies{
266+
Log: logService.MustGetLogger(logging.LoggerNameServer),
267+
LivenessProbe: &livenessProbe,
268+
Deployment: server.OperatorDependency{
269+
Enabled: cfg.EnableDeployment,
270+
Probe: &deploymentProbe,
271+
},
272+
DeploymentReplication: server.OperatorDependency{
273+
Enabled: cfg.EnableDeploymentReplication,
274+
Probe: &deploymentReplicationProbe,
275+
},
276+
Storage: server.OperatorDependency{
277+
Enabled: cfg.EnableStorage,
278+
Probe: &storageProbe,
279+
},
280+
Backup: server.OperatorDependency{
281+
Enabled: cfg.EnableBackup,
282+
Probe: &backupProbe,
283+
},
284+
Operators: o,
285+
286+
Secrets: secrets,
287+
}); err != nil {
288+
cliLog.Fatal().Err(err).Msg("Failed to create HTTP server")
289+
} else {
290+
go utilsError.LogError(cliLog, "error while starting service", svr.Run)
291+
}
292+
293+
// startChaos(context.Background(), cfg.KubeCli, cfg.Namespace, chaosLevel)
294+
295+
// Start operator
296+
o.Run()
297+
} else {
298+
if err := startVersionProcess(); err != nil {
299+
cliLog.Fatal().Err(err).Msg("Failed to create HTTP server")
300+
}
242301
}
302+
}
243303

304+
func startVersionProcess() error {
305+
// Just expose version
244306
listenAddr := net.JoinHostPort(serverOptions.host, strconv.Itoa(serverOptions.port))
245-
if svr, err := server.NewServer(kubecli.CoreV1(), server.Config{
246-
Namespace: namespace,
247-
Address: listenAddr,
248-
TLSSecretName: serverOptions.tlsSecretName,
249-
TLSSecretNamespace: namespace,
250-
PodName: name,
251-
PodIP: ip,
252-
AdminSecretName: serverOptions.adminSecretName,
253-
AllowAnonymous: serverOptions.allowAnonymous,
254-
}, server.Dependencies{
255-
Log: logService.MustGetLogger(logging.LoggerNameServer),
256-
LivenessProbe: &livenessProbe,
257-
Deployment: server.OperatorDependency{
258-
Enabled: cfg.EnableDeployment,
259-
Probe: &deploymentProbe,
260-
},
261-
DeploymentReplication: server.OperatorDependency{
262-
Enabled: cfg.EnableDeploymentReplication,
263-
Probe: &deploymentReplicationProbe,
264-
},
265-
Storage: server.OperatorDependency{
266-
Enabled: cfg.EnableStorage,
267-
Probe: &storageProbe,
268-
},
269-
Backup: server.OperatorDependency{
270-
Enabled: cfg.EnableBackup,
271-
Probe: &backupProbe,
272-
},
273-
Operators: o,
274-
275-
Secrets: secrets,
276-
}); err != nil {
277-
cliLog.Fatal().Err(err).Msg("Failed to create HTTP server")
278-
} else {
279-
go utilsError.LogError(cliLog, "error while starting service", svr.Run)
307+
cliLog.Info().Str("addr", listenAddr).Msgf("Starting version endpoint")
308+
309+
gin.SetMode(gin.ReleaseMode)
310+
r := gin.New()
311+
r.Use(gin.Recovery())
312+
313+
versionV1Responser, err := operatorHTTP.NewSimpleJSONResponse(version.GetVersionV1())
314+
if err != nil {
315+
return errors.WithStack(err)
280316
}
317+
r.GET("/_api/version", gin.WrapF(versionV1Responser.ServeHTTP))
318+
r.GET("/api/v1/version", gin.WrapF(versionV1Responser.ServeHTTP))
281319

282-
// startChaos(context.Background(), cfg.KubeCli, cfg.Namespace, chaosLevel)
320+
s := http.Server{
321+
Addr: listenAddr,
322+
Handler: r,
323+
}
283324

284-
// Start operator
285-
o.Run()
325+
return s.ListenAndServe()
286326
}
287327

288328
// newOperatorConfigAndDeps creates operator config & dependencies.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2021 ArangoDB GmbH, Cologne, Germany
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
//
20+
// Author Adam Janikowski
21+
//
22+
23+
package v1
24+
25+
import (
26+
"crypto/sha256"
27+
"fmt"
28+
29+
core "k8s.io/api/core/v1"
30+
"k8s.io/apimachinery/pkg/util/json"
31+
)
32+
33+
func GetArangoMemberPodTemplate(pod *core.PodTemplateSpec, podSpecChecksum string) (*ArangoMemberPodTemplate, error) {
34+
data, err := json.Marshal(pod.Spec)
35+
if err != nil {
36+
return nil, err
37+
}
38+
39+
return &ArangoMemberPodTemplate{
40+
PodSpec: pod,
41+
PodSpecChecksum: podSpecChecksum,
42+
Checksum: fmt.Sprintf("%0x", sha256.Sum256(data)),
43+
}, nil
44+
}
45+
46+
type ArangoMemberPodTemplate struct {
47+
PodSpec *core.PodTemplateSpec `json:"podSpec,omitempty"`
48+
PodSpecChecksum string `json:"podSpecChecksum,omitempty"`
49+
Checksum string `json:"checksum,omitempty"`
50+
}
51+
52+
func (a *ArangoMemberPodTemplate) Equals(b *ArangoMemberPodTemplate) bool {
53+
if a == nil && b == nil {
54+
return true
55+
}
56+
57+
if a == nil || b == nil {
58+
return false
59+
}
60+
61+
return a.Checksum == b.Checksum && a.PodSpecChecksum == b.PodSpecChecksum
62+
}
63+
64+
func (a *ArangoMemberPodTemplate) RotationNeeded(b *ArangoMemberPodTemplate) bool {
65+
if a == nil && b == nil {
66+
return true
67+
}
68+
69+
if a == nil || b == nil {
70+
return true
71+
}
72+
73+
return a.PodSpecChecksum != b.PodSpecChecksum
74+
}
75+
76+
func (a *ArangoMemberPodTemplate) EqualPodSpecChecksum(checksum string) bool {
77+
if a == nil {
78+
return false
79+
}
80+
return checksum == a.PodSpecChecksum
81+
}

pkg/apis/deployment/v1/arango_member_spec.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
package v1
2424

2525
import (
26-
core "k8s.io/api/core/v1"
2726
"k8s.io/apimachinery/pkg/types"
2827
)
2928

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

35-
Template *core.PodTemplate `json:"template,omitempty"`
36-
TemplateChecksum string `json:"templateChecksum,omitempty"`
34+
Template *ArangoMemberPodTemplate `json:"template,omitempty"`
3735
}

pkg/apis/deployment/v1/arango_member_status.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,16 @@
2222

2323
package v1
2424

25+
const (
26+
ArangoMemberConditionPendingRestart ConditionType = "pending-restart"
27+
)
28+
2529
type ArangoMemberStatus struct {
30+
Conditions ConditionList `json:"conditions,omitempty"`
31+
32+
Template *ArangoMemberPodTemplate `json:"template,omitempty"`
33+
}
34+
35+
func (a ArangoMemberStatus) IsPendingRestart() bool {
36+
return a.Conditions.IsTrue(ArangoMemberConditionPendingRestart)
2637
}

pkg/apis/deployment/v1/conditions.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ import (
3131
// ConditionType is a strongly typed condition name
3232
type ConditionType string
3333

34+
func (c ConditionType) String() string {
35+
return string(c)
36+
}
37+
3438
const (
3539
// ConditionTypeReady indicates that the member or entire deployment is ready and running normally.
3640
ConditionTypeReady ConditionType = "Ready"
@@ -67,6 +71,10 @@ const (
6771
ConditionTypeUpgradeFailed ConditionType = "UpgradeFailed"
6872
// ConditionTypeMaintenanceMode indicates that Maintenance is enabled
6973
ConditionTypeMaintenanceMode ConditionType = "MaintenanceMode"
74+
// ConditionTypePendingRestart indicates that restart is required
75+
ConditionTypePendingRestart ConditionType = "PendingRestart"
76+
// ConditionTypePendingTLSRotation indicates that TLS rotation is pending
77+
ConditionTypePendingTLSRotation ConditionType = "PendingTLSRotation"
7078
)
7179

7280
// Condition represents one current condition of a deployment or deployment member.

pkg/apis/deployment/v1/deployment_status.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,7 @@ func (ds *DeploymentStatus) Equal(other DeploymentStatus) bool {
102102
func (ds *DeploymentStatus) IsForceReload() bool {
103103
return util.BoolOrDefault(ds.ForceStatusReload, false)
104104
}
105+
106+
func (ds *DeploymentStatus) IsPlanEmpty() bool {
107+
return ds.Plan.IsEmpty() && ds.HighPriorityPlan.IsEmpty()
108+
}

pkg/apis/deployment/v1/plan.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ const (
161161
ActionTypeSetMemberCondition ActionType = "SetMemberCondition"
162162
// ActionTypeMemberRIDUpdate updated member Run ID (UID). High priority
163163
ActionTypeMemberRIDUpdate ActionType = "MemberRIDUpdate"
164+
// ActionTypeArangoMemberUpdatePodSpec updates pod spec
165+
ActionTypeArangoMemberUpdatePodSpec ActionType = "ArangoMemberUpdatePodSpec"
166+
// ActionTypeArangoMemberUpdatePodStatus updates pod spec
167+
ActionTypeArangoMemberUpdatePodStatus ActionType = "ArangoMemberUpdatePodStatus"
164168
)
165169

166170
const (

0 commit comments

Comments
 (0)