From 44792d26796cf3314e4ca9c3f32f593ef3074486 Mon Sep 17 00:00:00 2001 From: Adam Janikowski <12255597+ajanikow@users.noreply.github.com> Date: Thu, 6 Jul 2023 14:33:39 +0200 Subject: [PATCH] [Feature] Version Check V2 (#1348) --- CHANGELOG.md | 1 + Makefile | 1 + README.md | 1 + cmd/cmd.go | 4 + cmd/cmd_exit_code.go | 39 ++++++++ cmd/cmd_exit_code_test.go | 34 +++++++ cmd/init_container_version_check.go | 99 +++++++++++++++++++ cmd/init_container_version_check_test.go | 88 +++++++++++++++++ cmd/init_containers.go | 37 +++++++ pkg/deployment/features/upgrade.go | 15 ++- .../resources/pod_creator_arangod.go | 32 +++--- pkg/handlers/backup/arango_client_impl.go | 2 +- pkg/handlers/backup/backup_suite_test.go | 2 +- pkg/handlers/backup/register.go | 2 +- pkg/util/k8sutil/version_check.go | 61 ++++++++++++ 15 files changed, 401 insertions(+), 17 deletions(-) create mode 100644 cmd/cmd_exit_code.go create mode 100644 cmd/cmd_exit_code_test.go create mode 100644 cmd/init_container_version_check.go create mode 100644 cmd/init_container_version_check_test.go create mode 100644 cmd/init_containers.go create mode 100644 pkg/util/k8sutil/version_check.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 700ca8591..db5d88183 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - (Improvement) Extract Agency Timeout - (Feature) Rebalancer V2 - (Bugfix) Fix for ContextExceeded error during backup upload +- (Feature) Version Check V2 ## [1.2.30](https://github.com/arangodb/kube-arangodb/tree/1.2.30) (2023-06-16) - (Feature) AgencyCache Interface diff --git a/Makefile b/Makefile index 367a90968..0154a29c6 100644 --- a/Makefile +++ b/Makefile @@ -506,6 +506,7 @@ run-unit-tests: $(SOURCES) $(REPOPATH)/pkg/storage \ $(REPOPATH)/pkg/crd/... \ $(REPOPATH)/pkg/util/... \ + $(REPOPATH)/cmd/... \ $(REPOPATH)/pkg/handlers/... # Release building diff --git a/README.md b/README.md index 5151e6cc3..5072294d2 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ covers individual newer features separately. | Encryption Key Rotation Support | 1.2.0 | > 3.7.0 | Enterprise | 1.0.3 | NotSupported | False | --deployment.feature.encryption-rotation | N/A | | Version Check | 1.1.4 | >= 3.6.0 | Community, Enterprise | 1.1.4 | Alpha | False | --deployment.feature.upgrade-version-check | N/A | | Version Check | 1.2.23 | >= 3.6.0 | Community, Enterprise | 1.1.4 | Production | True | --deployment.feature.upgrade-version-check | N/A | +| Version Check V2 | 1.2.31 | >= 3.6.0 | Community, Enterprise | 1.2.31 | Alpha | False | --deployment.feature.upgrade-version-check-v2 | N/A | | Operator Maintenance Management Support | 1.2.0 | >= 3.6.0 | Community, Enterprise | 1.0.7 | Production | True | --deployment.feature.maintenance | N/A | | Graceful Restart | 1.2.5 | >= 3.6.0 | Community, Enterprise | 1.0.7 | Production | True | --deployment.feature.graceful-shutdown | N/A | | Optional Graceful Restart | 1.2.25 | >= 3.6.0 | Community, Enterprise | 1.2.5 | Beta | True | --deployment.feature.optional-graceful-shutdown | N/A | diff --git a/cmd/cmd.go b/cmd/cmd.go index 0b9e75f76..b898c9a6d 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -242,6 +242,10 @@ func Execute() int { flag.CommandLine.AddGoFlagSet(goflag.CommandLine) if err := cmdMain.Execute(); err != nil { + if v, ok := err.(CommandExitCode); ok { + return v.ExitCode + } + return 1 } diff --git a/cmd/cmd_exit_code.go b/cmd/cmd_exit_code.go new file mode 100644 index 000000000..1a5492df1 --- /dev/null +++ b/cmd/cmd_exit_code.go @@ -0,0 +1,39 @@ +// +// DISCLAIMER +// +// Copyright 2023 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 cmd + +import ( + "fmt" +) + +type CommandExitCode struct { + ExitCode int +} + +func (c CommandExitCode) Error() string { + return fmt.Sprintf("Command exit: %d", c.ExitCode) +} + +func Exit(code int) error { + return CommandExitCode{ + ExitCode: code, + } +} diff --git a/cmd/cmd_exit_code_test.go b/cmd/cmd_exit_code_test.go new file mode 100644 index 000000000..7850b43c2 --- /dev/null +++ b/cmd/cmd_exit_code_test.go @@ -0,0 +1,34 @@ +// +// DISCLAIMER +// +// Copyright 2023 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 cmd + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func ensureExitCode(t *testing.T, err error, code int) { + require.Error(t, err) + v, ok := err.(CommandExitCode) + require.True(t, ok) + require.Equal(t, v.ExitCode, code) +} diff --git a/cmd/init_container_version_check.go b/cmd/init_container_version_check.go new file mode 100644 index 000000000..c98bb5f0c --- /dev/null +++ b/cmd/init_container_version_check.go @@ -0,0 +1,99 @@ +// +// DISCLAIMER +// +// Copyright 2016-2023 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 cmd + +import ( + "encoding/json" + "os" + + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" +) + +const cmdVersionCheckInitContainersInvalidVersionExitCode = 11 + +type cmdVersionCheckInitContainersInputStruct struct { + versionPath string + + major, minor int +} + +var ( + cmdVersionCheckInitContainers = &cobra.Command{ + Use: "version-check", + RunE: cmdVersionCheckInitContainersInput.Run, + } + + cmdVersionCheckInitContainersInput cmdVersionCheckInitContainersInputStruct +) + +type cmdVersionCheckInitContainersData struct { + Version int `json:"version,omitempty"` +} + +func init() { + cmdInitContainers.AddCommand(cmdVersionCheckInitContainers) + f := cmdVersionCheckInitContainers.Flags() + f.StringVar(&cmdVersionCheckInitContainersInput.versionPath, "path", "", "Path to the VERSION file") + f.IntVar(&cmdVersionCheckInitContainersInput.major, "major", 0, "Major version of the ArangoDB. 0 if check is disabled") + f.IntVar(&cmdVersionCheckInitContainersInput.minor, "minor", 0, "Minor version of the ArangoDB. 0 if check is disabled") +} + +func (c cmdVersionCheckInitContainersInputStruct) Run(cmd *cobra.Command, args []string) error { + if c.versionPath == "" { + return errors.Errorf("Path cannot be empty") + } + + if data, err := os.ReadFile(c.versionPath); err != nil { + log.Err(err).Msg("File is not readable, continue") + return nil + } else { + major, minor, _, ok := extractVersionFromData(data) + if !ok { + return nil + } + + if c.major != 0 { + if c.major != major { + return Exit(cmdVersionCheckInitContainersInvalidVersionExitCode) + } + if c.minor != 0 { + if c.minor != minor { + return Exit(cmdVersionCheckInitContainersInvalidVersionExitCode) + } + } + } + + return nil + } +} + +func extractVersionFromData(data []byte) (int, int, int, bool) { + var c cmdVersionCheckInitContainersData + + if err := json.Unmarshal(data, &c); err != nil { + log.Err(err).Msg("Invalid json, continue") + return 0, 0, 0, false + } + + return c.Version / 10000, c.Version % 10000 / 100, c.Version % 100, true +} diff --git a/cmd/init_container_version_check_test.go b/cmd/init_container_version_check_test.go new file mode 100644 index 000000000..0904a4e02 --- /dev/null +++ b/cmd/init_container_version_check_test.go @@ -0,0 +1,88 @@ +// +// DISCLAIMER +// +// Copyright 2023 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 cmd + +import ( + "encoding/json" + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func saveVersionFile(t *testing.T, v int, updates ...func(in *cmdVersionCheckInitContainersInputStruct)) *cmdVersionCheckInitContainersInputStruct { + var q cmdVersionCheckInitContainersData + + q.Version = v + + d, err := json.Marshal(q) + require.NoError(t, err) + + var n cmdVersionCheckInitContainersInputStruct + + n.versionPath = fmt.Sprintf("%s/VERSION", t.TempDir()) + + require.NoError(t, os.WriteFile(n.versionPath, d, 0644)) + + for _, u := range updates { + u(&n) + } + + return &n +} + +func Test_extractVersionFromData(t *testing.T) { + check := func(valid bool, name string, version int, updates ...func(in *cmdVersionCheckInitContainersInputStruct)) { + t.Run(name, func(t *testing.T) { + err := saveVersionFile(t, version, updates...).Run(nil, nil) + if valid { + require.NoError(t, err) + } else { + ensureExitCode(t, err, cmdVersionCheckInitContainersInvalidVersionExitCode) + } + }) + } + + check(true, "3.9.10_optional", 30910) + + check(true, "3.9.10_required_major", 30910, func(in *cmdVersionCheckInitContainersInputStruct) { + in.major = 3 + }) + + check(true, "3.9.10_required_minor", 30910, func(in *cmdVersionCheckInitContainersInputStruct) { + in.major = 3 + in.minor = 9 + }) + + check(false, "3.9.10_required_major_mismatch", 30910, func(in *cmdVersionCheckInitContainersInputStruct) { + in.major = 4 + }) + + check(false, "3.9.10_required_minor_mismatch", 30910, func(in *cmdVersionCheckInitContainersInputStruct) { + in.major = 3 + in.minor = 5 + }) + + check(true, "3.9.10_required_minor_only_mismatch", 30910, func(in *cmdVersionCheckInitContainersInputStruct) { + in.minor = 5 + }) +} diff --git a/cmd/init_containers.go b/cmd/init_containers.go new file mode 100644 index 000000000..7315502c1 --- /dev/null +++ b/cmd/init_containers.go @@ -0,0 +1,37 @@ +// +// DISCLAIMER +// +// Copyright 2023 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 cmd + +import "github.com/spf13/cobra" + +var ( + cmdInitContainers = &cobra.Command{ + Use: "init-containers", + Run: func(cmd *cobra.Command, args []string) { + + }, + Hidden: true, + } +) + +func init() { + cmdMain.AddCommand(cmdInitContainers) +} diff --git a/pkg/deployment/features/upgrade.go b/pkg/deployment/features/upgrade.go index f49fb04e5..293393932 100644 --- a/pkg/deployment/features/upgrade.go +++ b/pkg/deployment/features/upgrade.go @@ -1,7 +1,7 @@ // // DISCLAIMER // -// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany +// Copyright 2016-2023 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. @@ -22,6 +22,7 @@ package features func init() { registerFeature(upgradeVersionCheck) + registerFeature(upgradeVersionCheckV2) } var upgradeVersionCheck Feature = &feature{ @@ -32,6 +33,18 @@ var upgradeVersionCheck Feature = &feature{ enabledByDefault: true, } +var upgradeVersionCheckV2 Feature = &feature{ + name: "upgrade-version-check-v2", + description: "Enable initContainer with pre version check based by Operator", + version: "3.6.0", + enterpriseRequired: false, + enabledByDefault: true, +} + func UpgradeVersionCheck() Feature { return upgradeVersionCheck } + +func UpgradeVersionCheckV2() Feature { + return upgradeVersionCheckV2 +} diff --git a/pkg/deployment/resources/pod_creator_arangod.go b/pkg/deployment/resources/pod_creator_arangod.go index 09e8fb2d2..7fe7da560 100644 --- a/pkg/deployment/resources/pod_creator_arangod.go +++ b/pkg/deployment/resources/pod_creator_arangod.go @@ -470,21 +470,27 @@ func (m *MemberArangoDPod) GetInitContainers(cachedStatus interfaces.Inspector) // VersionCheck Container { - versionArgs := pod.UpgradeVersionCheck().Args(m.AsInput()) - if len(versionArgs) > 0 { - upgradeContainer := &ArangoVersionCheckContainer{ - m.GetContainerCreator(), - cachedStatus, - m.AsInput(), - versionArgs, - } + switch m.group { + case api.ServerGroupAgents, api.ServerGroupDBServers, api.ServerGroupSingle: + if features.UpgradeVersionCheckV2().Enabled() { + c := k8sutil.ArangodVersionCheckInitContainer(api.ServerGroupReservedInitContainerNameVersionCheck, executable, m.resources.context.GetOperatorImage(), + m.imageInfo.ArangoDBVersion, m.groupSpec.SecurityContext.NewSecurityContext()) + initContainers = append(initContainers, c) + } else if features.UpgradeVersionCheck().Enabled() { + upgradeContainer := &ArangoVersionCheckContainer{ + m.GetContainerCreator(), + cachedStatus, + m.AsInput(), + pod.UpgradeVersionCheck().Args(m.AsInput()), + } - c, err := k8sutil.NewContainer(upgradeContainer) - if err != nil { - return nil, err - } + c, err := k8sutil.NewContainer(upgradeContainer) + if err != nil { + return nil, err + } - initContainers = append(initContainers, c) + initContainers = append(initContainers, c) + } } } } diff --git a/pkg/handlers/backup/arango_client_impl.go b/pkg/handlers/backup/arango_client_impl.go index 451f787c1..2c8ab64ab 100644 --- a/pkg/handlers/backup/arango_client_impl.go +++ b/pkg/handlers/backup/arango_client_impl.go @@ -1,7 +1,7 @@ // // DISCLAIMER // -// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany +// Copyright 2016-2023 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. diff --git a/pkg/handlers/backup/backup_suite_test.go b/pkg/handlers/backup/backup_suite_test.go index be5335205..7e350c35e 100644 --- a/pkg/handlers/backup/backup_suite_test.go +++ b/pkg/handlers/backup/backup_suite_test.go @@ -1,7 +1,7 @@ // // DISCLAIMER // -// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany +// Copyright 2016-2023 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. diff --git a/pkg/handlers/backup/register.go b/pkg/handlers/backup/register.go index 831bf0fd7..1d17f4557 100644 --- a/pkg/handlers/backup/register.go +++ b/pkg/handlers/backup/register.go @@ -1,7 +1,7 @@ // // DISCLAIMER // -// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany +// Copyright 2016-2023 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. diff --git a/pkg/util/k8sutil/version_check.go b/pkg/util/k8sutil/version_check.go new file mode 100644 index 000000000..687d244db --- /dev/null +++ b/pkg/util/k8sutil/version_check.go @@ -0,0 +1,61 @@ +// +// DISCLAIMER +// +// Copyright 2023 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 k8sutil + +import ( + "fmt" + "path/filepath" + + core "k8s.io/api/core/v1" + + "github.com/arangodb/go-driver" + + "github.com/arangodb/kube-arangodb/pkg/apis/shared" +) + +// ArangodVersionCheckInitContainer creates a container configured to check version. +func ArangodVersionCheckInitContainer(name, executable, operatorImage string, version driver.Version, securityContext *core.SecurityContext) core.Container { + versionFile := filepath.Join(shared.ArangodVolumeMountDir, "VERSION-1") + var command = []string{ + executable, + "init-containers", + "version-check", + "--path", + versionFile, + } + + if v := version.Major(); v > 0 { + command = append(command, + "--major", + fmt.Sprintf("%d", v)) + + if v := version.Minor(); v > 0 { + command = append(command, + "--minor", + fmt.Sprintf("%d", v)) + } + } + + volumes := []core.VolumeMount{ + ArangodVolumeMount(), + } + return operatorInitContainer(name, operatorImage, command, securityContext, volumes) +}