mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 12:15:52 +00:00
Merge pull request #92286 from wojtek-t/migrate_if_needed_golang
Merge migrate-if-needed etcd bash script with golang binary
This commit is contained in:
commit
dc0122ca6a
@ -65,8 +65,7 @@ dependencies:
|
||||
refPaths:
|
||||
- path: cluster/images/etcd/Makefile
|
||||
match: BUNDLED_ETCD_VERSIONS\?|LATEST_ETCD_VERSION\?
|
||||
- path: cluster/images/etcd/migrate-if-needed.sh
|
||||
match: BUNDLED_VERSIONS=
|
||||
- path: cluster/images/etcd/migrate/options.go
|
||||
|
||||
- name: "golang"
|
||||
version: 1.14.4
|
||||
|
@ -14,95 +14,13 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# NOTES
|
||||
# This script performs etcd upgrade based on the following environmental
|
||||
# variables:
|
||||
# TARGET_STORAGE - API of etcd to be used (supported: 'etcd3')
|
||||
# TARGET_VERSION - etcd release to be used (supported: '3.0.17', '3.1.12',
|
||||
# '3.2.24', "3.3.17", "3.4.9")
|
||||
# DATA_DIRECTORY - directory with etcd data
|
||||
#
|
||||
# The current etcd version and storage format is detected based on the
|
||||
# contents of "${DATA_DIRECTORY}/version.txt" file (if the file doesn't
|
||||
# exist, we default it to "3.0.17/etcd2".
|
||||
#
|
||||
# The update workflow support the following upgrade steps:
|
||||
# - 3.0.17/etcd3 -> 3.1.12/etcd3
|
||||
# - 3.1.12/etcd3 -> 3.2.24/etcd3
|
||||
# - 3.2.24/etcd3 -> 3.3.17/etcd3
|
||||
# - 3.3.17/etcd3 -> 3.4.9/etcd3
|
||||
#
|
||||
# NOTE: The releases supported in this script has to match release binaries
|
||||
# present in the etcd image (to make this script work correctly).
|
||||
#
|
||||
# Based on the current etcd version and storage format we detect what
|
||||
# upgrade step from this should be done to get reach target configuration
|
||||
|
||||
# DEPRECATED:
|
||||
# The functionality has been moved to migrate binary and this script
|
||||
# if left for backward compatibility with previous manifests. It will be
|
||||
# removed in the future.
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
|
||||
# NOTE: BUNDLED_VERSION has to match release binaries present in the
|
||||
# etcd image (to make this script work correctly).
|
||||
BUNDLED_VERSIONS="3.0.17, 3.1.12, 3.2.24, 3.3.17, 3.4.9"
|
||||
|
||||
# shellcheck disable=SC2039
|
||||
# NOTE: Make sure the resulted ETCD_NAME agrees with --name in etcd.manifest: https://github.com/kubernetes/kubernetes/blob/e7ca64fbe16d0c4b6c7b36aecde9cd75042b2828/cluster/gce/manifests/etcd.manifest#L27
|
||||
ETCD_NAME="${ETCD_NAME:-etcd-${ETCD_HOSTNAME:-$HOSTNAME}}"
|
||||
if [ -z "${DATA_DIRECTORY:-}" ]; then
|
||||
echo "DATA_DIRECTORY variable unset - unexpected failure"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "${DATA_DIRECTORY}" in
|
||||
*event*)
|
||||
ETCD_PEER_PORT=2381
|
||||
ETCD_CLIENT_PORT=18631
|
||||
;;
|
||||
*)
|
||||
ETCD_PEER_PORT=2380
|
||||
ETCD_CLIENT_PORT=18629
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "${INITIAL_CLUSTER:-}" ]; then
|
||||
echo "Warn: INITIAL_CLUSTER variable unset - defaulting to ${ETCD_NAME}=http://localhost:${ETCD_PEER_PORT}"
|
||||
INITIAL_CLUSTER="${ETCD_NAME}=http://localhost:${ETCD_PEER_PORT}"
|
||||
fi
|
||||
if [ -z "${LISTEN_PEER_URLS:-}" ]; then
|
||||
echo "Warn: LISTEN_PEER_URLS variable unset - defaulting to http://localhost:${ETCD_PEER_PORT}"
|
||||
LISTEN_PEER_URLS="http://localhost:${ETCD_PEER_PORT}"
|
||||
fi
|
||||
if [ -z "${INITIAL_ADVERTISE_PEER_URLS:-}" ]; then
|
||||
echo "Warn: INITIAL_ADVERTISE_PEER_URLS variable unset - defaulting to http://localhost:${ETCD_PEER_PORT}"
|
||||
INITIAL_ADVERTISE_PEER_URLS="http://localhost:${ETCD_PEER_PORT}"
|
||||
fi
|
||||
if [ -z "${TARGET_VERSION:-}" ]; then
|
||||
echo "TARGET_VERSION variable unset - unexpected failure"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "${TARGET_STORAGE:-}" ]; then
|
||||
echo "TARGET_STORAGE variable unset - unexpected failure"
|
||||
exit 1
|
||||
fi
|
||||
ETCD_DATA_PREFIX="${ETCD_DATA_PREFIX:-/registry}"
|
||||
ETCD_CREDS="${ETCD_CREDS:-}"
|
||||
|
||||
# Correctly support upgrade and rollback to non-default version.
|
||||
if [ "${DO_NOT_MOVE_BINARIES:-}" != "true" ]; then
|
||||
/bin/cp "/usr/local/bin/etcd-${TARGET_VERSION}" "/usr/local/bin/etcd"
|
||||
/bin/cp "/usr/local/bin/etcdctl-${TARGET_VERSION}" "/usr/local/bin/etcdctl"
|
||||
fi
|
||||
|
||||
/usr/local/bin/migrate \
|
||||
--name "${ETCD_NAME}" \
|
||||
--port "${ETCD_CLIENT_PORT}" \
|
||||
--listen-peer-urls "${LISTEN_PEER_URLS}" \
|
||||
--initial-advertise-peer-urls "${INITIAL_ADVERTISE_PEER_URLS}" \
|
||||
--data-dir "${DATA_DIRECTORY}" \
|
||||
--bundled-versions "${BUNDLED_VERSIONS}" \
|
||||
--initial-cluster "${INITIAL_CLUSTER}" \
|
||||
--target-version "${TARGET_VERSION}" \
|
||||
--target-storage "${TARGET_STORAGE}" \
|
||||
--etcd-data-prefix "${ETCD_DATA_PREFIX}" \
|
||||
--ttl-keys-directory "${TTL_KEYS_DIRECTORY:-${ETCD_DATA_PREFIX}/events}" \
|
||||
--etcd-server-extra-args "${ETCD_CREDS}"
|
||||
/usr/local/bin/migrate
|
||||
|
@ -3,11 +3,13 @@ load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"copy_file.go",
|
||||
"data_dir.go",
|
||||
"migrate.go",
|
||||
"migrate_client.go",
|
||||
"migrate_server.go",
|
||||
"migrator.go",
|
||||
"options.go",
|
||||
"util_others.go",
|
||||
"utils_windows.go",
|
||||
"versions.go",
|
||||
@ -17,6 +19,7 @@ go_library(
|
||||
deps = [
|
||||
"//vendor/github.com/blang/semver:go_default_library",
|
||||
"//vendor/github.com/spf13/cobra:go_default_library",
|
||||
"//vendor/github.com/spf13/pflag:go_default_library",
|
||||
"//vendor/go.etcd.io/etcd/client:go_default_library",
|
||||
"//vendor/go.etcd.io/etcd/clientv3:go_default_library",
|
||||
"//vendor/google.golang.org/grpc:go_default_library",
|
||||
@ -69,6 +72,7 @@ go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"data_dir_test.go",
|
||||
"options_test.go",
|
||||
"versions_test.go",
|
||||
],
|
||||
data = glob(["testdata/**"]),
|
||||
|
56
cluster/images/etcd/migrate/copy_file.go
Normal file
56
cluster/images/etcd/migrate/copy_file.go
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func copyFile(source, dest string) error {
|
||||
sf, err := os.Open(source)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to open source file [%s]: %q", source, err)
|
||||
}
|
||||
defer sf.Close()
|
||||
fi, err := sf.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to stat source file [%s]: %q", source, err)
|
||||
}
|
||||
|
||||
dir := filepath.Dir(dest)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("unable to create directory [%s]: %q", dir, err)
|
||||
}
|
||||
df, err := os.Create(dest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create destination file [%s]: %q", dest, err)
|
||||
}
|
||||
defer df.Close()
|
||||
|
||||
_, err = io.Copy(df, sf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to copy [%s] to [%s]: %q", source, dest, err)
|
||||
}
|
||||
|
||||
if err := os.Chmod(dest, fi.Mode()); err != nil {
|
||||
return fmt.Errorf("unable to close destination file: %q", err)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -42,7 +42,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
testSupportedVersions = MustParseSupportedVersions("3.0.17, 3.1.12")
|
||||
testSupportedVersions = MustParseSupportedVersions([]string{"3.0.17", "3.1.12"})
|
||||
testVersionPrevious = &EtcdVersion{semver.MustParse("3.0.17")}
|
||||
testVersionLatest = &EtcdVersion{semver.MustParse("3.1.12")}
|
||||
)
|
||||
|
@ -18,7 +18,6 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@ -26,8 +25,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
versionFilename = "version.txt"
|
||||
defaultPort uint64 = 18629
|
||||
versionFilename = "version.txt"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -46,94 +44,43 @@ a target etcd version, this tool will upgrade or downgrade the etcd data from th
|
||||
opts = migrateOpts{}
|
||||
)
|
||||
|
||||
type migrateOpts struct {
|
||||
name string
|
||||
port uint64
|
||||
peerListenUrls string
|
||||
peerAdvertiseUrls string
|
||||
binDir string
|
||||
dataDir string
|
||||
bundledVersionString string
|
||||
etcdDataPrefix string
|
||||
ttlKeysDirectory string
|
||||
initialCluster string
|
||||
targetVersion string
|
||||
targetStorage string
|
||||
etcdServerArgs string
|
||||
}
|
||||
|
||||
func main() {
|
||||
flags := migrateCmd.Flags()
|
||||
flags.StringVar(&opts.name, "name", "", "etcd cluster member name. Defaults to etcd-{hostname}")
|
||||
flags.Uint64Var(&opts.port, "port", defaultPort, "etcd client port to use during migration operations. This should be a different port than typically used by etcd to avoid clients accidentally connecting during upgrade/downgrade operations.")
|
||||
flags.StringVar(&opts.peerListenUrls, "listen-peer-urls", "", "etcd --listen-peer-urls flag, required for HA clusters")
|
||||
flags.StringVar(&opts.peerAdvertiseUrls, "initial-advertise-peer-urls", "", "etcd --initial-advertise-peer-urls flag, required for HA clusters")
|
||||
flags.StringVar(&opts.binDir, "bin-dir", "/usr/local/bin", "directory of etcd and etcdctl binaries, must contain etcd-<version> and etcdctl-<version> for each version listed in bindled-versions")
|
||||
flags.StringVar(&opts.dataDir, "data-dir", "", "etcd data directory of etcd server to migrate")
|
||||
flags.StringVar(&opts.bundledVersionString, "bundled-versions", "", "comma separated list of etcd binary versions present under the bin-dir")
|
||||
flags.StringVar(&opts.etcdDataPrefix, "etcd-data-prefix", "/registry", "etcd key prefix under which all objects are kept")
|
||||
flags.StringVar(&opts.ttlKeysDirectory, "ttl-keys-directory", "", "etcd key prefix under which all keys with TTLs are kept. Defaults to {etcd-data-prefix}/events")
|
||||
flags.StringVar(&opts.initialCluster, "initial-cluster", "", "comma separated list of name=endpoint pairs. Defaults to etcd-{hostname}=http://localhost:2380")
|
||||
flags.StringVar(&opts.targetVersion, "target-version", "", "version of etcd to migrate to. Format must be '<major>.<minor>.<patch>'")
|
||||
flags.StringVar(&opts.targetStorage, "target-storage", "", "storage version of etcd to migrate to, one of: etcd2, etcd3")
|
||||
flags.StringVar(&opts.etcdServerArgs, "etcd-server-extra-args", "", "additional etcd server args for starting etcd servers during migration steps, --peer-* TLS cert flags should be added for etcd clusters with more than 1 member that use mutual TLS for peer communication.")
|
||||
registerFlags(migrateCmd.Flags(), &opts)
|
||||
err := migrateCmd.Execute()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to execute migratecmd: %s", err)
|
||||
klog.Errorf("Failed to execute migratecmd: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// runMigrate validates the command line flags and starts the migration.
|
||||
// runMigrate starts the migration.
|
||||
func runMigrate() {
|
||||
if opts.name == "" {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
klog.Errorf("Error while getting hostname to supply default --name: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
opts.name = fmt.Sprintf("etcd-%s", hostname)
|
||||
}
|
||||
|
||||
if opts.ttlKeysDirectory == "" {
|
||||
opts.ttlKeysDirectory = fmt.Sprintf("%s/events", opts.etcdDataPrefix)
|
||||
}
|
||||
if opts.initialCluster == "" {
|
||||
opts.initialCluster = fmt.Sprintf("%s=http://localhost:2380", opts.name)
|
||||
}
|
||||
if opts.targetStorage == "" {
|
||||
klog.Errorf("--target-storage is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
if opts.targetVersion == "" {
|
||||
klog.Errorf("--target-version is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
if opts.dataDir == "" {
|
||||
klog.Errorf("--data-dir is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
if opts.bundledVersionString == "" {
|
||||
klog.Errorf("--bundled-versions is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
bundledVersions, err := ParseSupportedVersions(opts.bundledVersionString)
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to parse --supported-versions: %v", err)
|
||||
}
|
||||
err = validateBundledVersions(bundledVersions, opts.binDir)
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to validate that 'etcd-<version>' and 'etcdctl-<version>' binaries exist in --bin-dir '%s' for all --bundled-versions '%s': %v",
|
||||
opts.binDir, opts.bundledVersionString, err)
|
||||
os.Exit(1)
|
||||
if err := opts.validateAndDefault(); err != nil {
|
||||
klog.Fatalf("%v", err)
|
||||
}
|
||||
copyBinaries()
|
||||
|
||||
target := &EtcdVersionPair{
|
||||
version: MustParseEtcdVersion(opts.targetVersion),
|
||||
storageVersion: MustParseEtcdStorageVersion(opts.targetStorage),
|
||||
}
|
||||
|
||||
migrate(opts.name, opts.port, opts.peerListenUrls, opts.peerAdvertiseUrls, opts.binDir, opts.dataDir, opts.etcdDataPrefix, opts.ttlKeysDirectory, opts.initialCluster, target, bundledVersions, opts.etcdServerArgs)
|
||||
migrate(
|
||||
opts.name, opts.port, opts.peerListenUrls, opts.peerAdvertiseUrls, opts.binDir,
|
||||
opts.dataDir, opts.etcdDataPrefix, opts.ttlKeysDirectory, opts.initialCluster,
|
||||
target, opts.supportedVersions, opts.etcdServerArgs)
|
||||
}
|
||||
|
||||
func copyBinaries() {
|
||||
if val, err := lookupEnv("DO_NOT_MOVE_BINARIES"); err != nil || val != "true" {
|
||||
etcdVersioned := fmt.Sprintf("etcd-%s", opts.targetVersion)
|
||||
etcdctlVersioned := fmt.Sprintf("etcdctl-%s", opts.targetVersion)
|
||||
if err := copyFile(filepath.Join(opts.binDir, etcdVersioned), filepath.Join(opts.binDir, "etcd")); err != nil {
|
||||
klog.Fatalf("Failed to copy %s: %v", etcdVersioned, err)
|
||||
}
|
||||
if err := copyFile(filepath.Join(opts.binDir, etcdctlVersioned), filepath.Join(opts.binDir, "etcdctl")); err != nil {
|
||||
klog.Fatalf("Failed to copy %s: %v", etcdctlVersioned, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// migrate opens or initializes the etcd data directory, configures the migrator, and starts the migration.
|
||||
@ -142,8 +89,7 @@ func migrate(name string, port uint64, peerListenUrls string, peerAdvertiseUrls
|
||||
|
||||
dataDir, err := OpenOrCreateDataDirectory(dataDirPath)
|
||||
if err != nil {
|
||||
klog.Errorf("Error opening or creating data directory %s: %v", dataDirPath, err)
|
||||
os.Exit(1)
|
||||
klog.Fatalf("Error opening or creating data directory %s: %v", dataDirPath, err)
|
||||
}
|
||||
|
||||
cfg := &EtcdMigrateCfg{
|
||||
@ -161,8 +107,7 @@ func migrate(name string, port uint64, peerListenUrls string, peerAdvertiseUrls
|
||||
}
|
||||
client, err := NewEtcdMigrateClient(cfg)
|
||||
if err != nil {
|
||||
klog.Errorf("Migration failed: %v", err)
|
||||
os.Exit(1)
|
||||
klog.Fatalf("Migration failed: %v", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
@ -170,22 +115,6 @@ func migrate(name string, port uint64, peerListenUrls string, peerAdvertiseUrls
|
||||
|
||||
err = migrator.MigrateIfNeeded(target)
|
||||
if err != nil {
|
||||
klog.Errorf("Migration failed: %v", err)
|
||||
os.Exit(1)
|
||||
klog.Fatalf("Migration failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// validateBundledVersions checks that 'etcd-<version>' and 'etcdctl-<version>' binaries exist in the binDir
|
||||
// for each version in the bundledVersions list.
|
||||
func validateBundledVersions(bundledVersions SupportedVersions, binDir string) error {
|
||||
for _, v := range bundledVersions {
|
||||
for _, binaryName := range []string{"etcd", "etcdctl"} {
|
||||
fn := filepath.Join(binDir, fmt.Sprintf("%s-%s", binaryName, v))
|
||||
if _, err := os.Stat(fn); err != nil {
|
||||
return fmt.Errorf("failed to validate '%s' binary exists for bundled-version '%s': %v", fn, v, err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
239
cluster/images/etcd/migrate/options.go
Normal file
239
cluster/images/etcd/migrate/options.go
Normal file
@ -0,0 +1,239 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
flag "github.com/spf13/pflag"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
supportedEtcdVersions = []string{"3.0.17", "3.1.12", "3.2.24", "3.3.17", "3.4.9"}
|
||||
)
|
||||
|
||||
const (
|
||||
etcdNameEnv = "ETCD_NAME"
|
||||
etcdHostnameEnv = "ETCD_HOSTNAME"
|
||||
hostnameEnv = "HOSTNAME"
|
||||
dataDirEnv = "DATA_DIRECTORY"
|
||||
initialClusterEnv = "INITIAL_CLUSTER"
|
||||
initialClusterFmt = "%s=http://localhost:%d"
|
||||
peerListenUrlsEnv = "LISTEN_PEER_URLS"
|
||||
peerListenUrlsFmt = "http://localhost:%d"
|
||||
peerAdvertiseUrlsEnv = "INITIAL_ADVERTISE_PEER_URLS"
|
||||
peerAdvertiseUrlsFmt = "http://localhost:%d"
|
||||
targetVersionEnv = "TARGET_VERSION"
|
||||
targetStorageEnv = "TARGET_STORAGE"
|
||||
etcdDataPrefixEnv = "ETCD_DATA_PREFIX"
|
||||
etcdDataPrefixDefault = "/registry"
|
||||
ttlKeysDirectoryFmt = "%s/events"
|
||||
etcdServerArgsEnv = "ETCD_CREDS"
|
||||
)
|
||||
|
||||
type migrateOpts struct {
|
||||
name string
|
||||
port uint64
|
||||
peerPort uint64
|
||||
peerListenUrls string
|
||||
peerAdvertiseUrls string
|
||||
binDir string
|
||||
dataDir string
|
||||
bundledVersions []string
|
||||
supportedVersions SupportedVersions
|
||||
etcdDataPrefix string
|
||||
ttlKeysDirectory string
|
||||
initialCluster string
|
||||
targetVersion string
|
||||
targetStorage string
|
||||
etcdServerArgs string
|
||||
}
|
||||
|
||||
func registerFlags(flags *flag.FlagSet, opt *migrateOpts) {
|
||||
flags.StringVar(&opts.name, "name", "",
|
||||
"etcd cluster member name. If unset fallbacks to defaults to ETCD_NAME env, if unset defaults to etcd-<ETCD_HOSTNAME> env, if unset defaults to etcd-<HOSTNAME> env.")
|
||||
flags.Uint64Var(&opts.port, "port", 0,
|
||||
"etcd client port to use during migration operations. "+
|
||||
"This should be a different port than typically used by etcd to avoid clients accidentally connecting during upgrade/downgrade operations. "+
|
||||
"If unset default to 18629 or 18631 depenging on <data-dir>.")
|
||||
flags.Uint64Var(&opts.peerPort, "peer-port", 0,
|
||||
"etcd peer port to use during migration operations. If unset defaults to 2380 or 2381 depending on <data-dir>.")
|
||||
flags.StringVar(&opts.peerListenUrls, "listen-peer-urls", "",
|
||||
"etcd --listen-peer-urls flag. If unset, fallbacks to LISTEN_PEER_URLS env and if unset defaults to http://localhost:<peer-port>.")
|
||||
flags.StringVar(&opts.peerAdvertiseUrls, "initial-advertise-peer-urls", "",
|
||||
"etcd --initial-advertise-peer-urls flag. If unset fallbacks to INITIAL_ADVERTISE_PEER_URLS env and if unset defaults to http://localhost:<peer-port>.")
|
||||
flags.StringVar(&opts.binDir, "bin-dir", "/usr/local/bin",
|
||||
"directory of etcd and etcdctl binaries, must contain etcd-<version> and etcdctl-<version> for each version listed in <bundled-versions>.")
|
||||
flags.StringVar(&opts.dataDir, "data-dir", "",
|
||||
"etcd data directory of etcd server to migrate. If unset fallbacks to DATA_DIRECTORY env.")
|
||||
flags.StringSliceVar(&opts.bundledVersions, "bundled-versions", supportedEtcdVersions,
|
||||
"comma separated list of etcd binary versions present under the bin-dir.")
|
||||
flags.StringVar(&opts.etcdDataPrefix, "etcd-data-prefix", "",
|
||||
"etcd key prefix under which all objects are kept. If unset fallbacks to ETCD_DATA_PREFIX env and if unset defaults to /registry.")
|
||||
flags.StringVar(&opts.ttlKeysDirectory, "ttl-keys-directory", "",
|
||||
"etcd key prefix under which all keys with TTLs are kept. Defaults to <etcd-data-prefix>/events")
|
||||
flags.StringVar(&opts.initialCluster, "initial-cluster", "",
|
||||
"comma separated list of name=endpoint pairs. If unset fallbacks to INITIAL_CLUSTER and if unset defaults to <etcd-name>=https://localhost:<peer-port>.")
|
||||
flags.StringVar(&opts.targetVersion, "target-version", "",
|
||||
"version of etcd to migrate to. Format must be <major>.<minor>.<patch>. If unset fallbacks to TARGET_VERSION env.")
|
||||
flags.StringVar(&opts.targetStorage, "target-storage", "",
|
||||
"storage version of etcd to migrate to, one of: etcd2, etcd3. If unset fallbacks to TARGET_STORAGE env.")
|
||||
flags.StringVar(&opts.etcdServerArgs, "etcd-server-extra-args", "",
|
||||
"additional etcd server args for starting etcd servers during migration steps, need to set TLS certs flags for multi-member clusters using mTLS for communication. "+
|
||||
"If unset fallbacks to ETCD_CREDS env.")
|
||||
}
|
||||
|
||||
func lookupEnv(env string) (string, error) {
|
||||
result, ok := os.LookupEnv(env)
|
||||
if !ok || len(result) == 0 {
|
||||
return result, fmt.Errorf("%s variable unset - expected failure", env)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func fallbackToEnv(flag, env string) (string, error) {
|
||||
klog.Infof("--%s unset - falling back to %s variable", flag, env)
|
||||
return lookupEnv(env)
|
||||
}
|
||||
|
||||
func fallbackToEnvWithDefault(flag, env, def string) string {
|
||||
if value, err := lookupEnv(env); err == nil {
|
||||
return value
|
||||
}
|
||||
klog.Warningf("%s variable unset - defaulting to %s", env, def)
|
||||
return def
|
||||
}
|
||||
|
||||
func defaultName() (string, error) {
|
||||
if etcdName, err := lookupEnv(etcdNameEnv); err == nil {
|
||||
return etcdName, nil
|
||||
}
|
||||
klog.Warningf("%s variable unset - falling back to etcd-%s variable", etcdNameEnv, etcdHostnameEnv)
|
||||
if etcdHostname, err := lookupEnv(etcdHostnameEnv); err == nil {
|
||||
return fmt.Sprintf("etcd-%s", etcdHostname), nil
|
||||
}
|
||||
klog.Warningf("%s variable unset - falling back to etcd-%s variable", etcdHostnameEnv, hostnameEnv)
|
||||
if hostname, err := lookupEnv(hostnameEnv); err == nil {
|
||||
return fmt.Sprintf("etcd-%s", hostname), nil
|
||||
}
|
||||
return "", fmt.Errorf("defaulting --name failed due to all ETCD_NAME, ETCD_HOSTNAME and HOSTNAME unset")
|
||||
}
|
||||
|
||||
func (opts *migrateOpts) validateAndDefault() error {
|
||||
var err error
|
||||
|
||||
if opts.name == "" {
|
||||
klog.Infof("--name unset - falling back to %s variable", etcdNameEnv)
|
||||
if opts.name, err = defaultName(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.dataDir == "" {
|
||||
if opts.dataDir, err = fallbackToEnv("data-dir", dataDirEnv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
etcdEventsRE := regexp.MustCompile("event")
|
||||
if opts.port == 0 {
|
||||
if etcdEventsRE.MatchString(opts.dataDir) {
|
||||
opts.port = 18631
|
||||
} else {
|
||||
opts.port = 18629
|
||||
}
|
||||
klog.Infof("--port unset - defaulting to %d", opts.port)
|
||||
}
|
||||
if opts.peerPort == 0 {
|
||||
if etcdEventsRE.MatchString(opts.dataDir) {
|
||||
opts.peerPort = 2381
|
||||
} else {
|
||||
opts.peerPort = 2380
|
||||
}
|
||||
klog.Infof("--peer-port unset - defaulting to %d", opts.peerPort)
|
||||
}
|
||||
|
||||
if opts.initialCluster == "" {
|
||||
def := fmt.Sprintf(initialClusterFmt, opts.name, opts.peerPort)
|
||||
opts.initialCluster = fallbackToEnvWithDefault("initial-cluster", initialClusterEnv, def)
|
||||
}
|
||||
|
||||
if opts.peerListenUrls == "" {
|
||||
def := fmt.Sprintf(peerListenUrlsFmt, opts.peerPort)
|
||||
opts.peerListenUrls = fallbackToEnvWithDefault("listen-peer-urls", peerListenUrlsEnv, def)
|
||||
}
|
||||
|
||||
if opts.peerAdvertiseUrls == "" {
|
||||
def := fmt.Sprintf(peerAdvertiseUrlsFmt, opts.peerPort)
|
||||
opts.peerAdvertiseUrls = fallbackToEnvWithDefault("initial-advertise-peer-urls", peerAdvertiseUrlsEnv, def)
|
||||
}
|
||||
|
||||
if opts.targetVersion == "" {
|
||||
if opts.targetVersion, err = fallbackToEnv("target-version", targetVersionEnv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.targetStorage == "" {
|
||||
if opts.targetStorage, err = fallbackToEnv("target-storage", targetStorageEnv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.etcdDataPrefix == "" {
|
||||
opts.etcdDataPrefix = fallbackToEnvWithDefault("etcd-data-prefix", etcdDataPrefixEnv, etcdDataPrefixDefault)
|
||||
}
|
||||
|
||||
if opts.ttlKeysDirectory == "" {
|
||||
opts.ttlKeysDirectory = fmt.Sprintf(ttlKeysDirectoryFmt, opts.etcdDataPrefix)
|
||||
klog.Infof("--ttl-keys-directory unset - defaulting to %s", opts.ttlKeysDirectory)
|
||||
}
|
||||
|
||||
if opts.etcdServerArgs == "" {
|
||||
opts.etcdServerArgs = fallbackToEnvWithDefault("etcd-server-extra-args", etcdServerArgsEnv, "")
|
||||
}
|
||||
|
||||
if opts.supportedVersions, err = ParseSupportedVersions(opts.bundledVersions); err != nil {
|
||||
return fmt.Errorf("failed to parse --bundled-versions: %v", err)
|
||||
}
|
||||
|
||||
if err := validateBundledVersions(opts.supportedVersions, opts.binDir); err != nil {
|
||||
return fmt.Errorf("failed to validate that 'etcd-<version>' and 'etcdctl-<version>' binaries exist in --bin-dir '%s' for all --bundled-versions '%s': %v",
|
||||
opts.binDir, strings.Join(opts.bundledVersions, ","), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateBundledVersions checks that 'etcd-<version>' and 'etcdctl-<version>' binaries exist in the binDir
|
||||
// for each version in the bundledVersions list.
|
||||
func validateBundledVersions(bundledVersions SupportedVersions, binDir string) error {
|
||||
for _, v := range bundledVersions {
|
||||
for _, binaryName := range []string{"etcd", "etcdctl"} {
|
||||
fn := filepath.Join(binDir, fmt.Sprintf("%s-%s", binaryName, v))
|
||||
if _, err := os.Stat(fn); err != nil {
|
||||
return fmt.Errorf("failed to validate '%s' binary exists for bundled-version '%s': %v", fn, v, err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
146
cluster/images/etcd/migrate/options_test.go
Normal file
146
cluster/images/etcd/migrate/options_test.go
Normal file
@ -0,0 +1,146 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func setEnvVar(t *testing.T, env, val string, exists bool) {
|
||||
if exists {
|
||||
if err := os.Setenv(env, val); err != nil {
|
||||
t.Errorf("could't set env %s: %v", env, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := os.Unsetenv(env); err != nil {
|
||||
t.Errorf("couldn't unset env %s: %v", env, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFallbackToEnv(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
env string
|
||||
value string
|
||||
valueSet bool
|
||||
expectedValue string
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
desc: "value unset",
|
||||
env: "FOO",
|
||||
valueSet: false,
|
||||
expectedValue: "",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
desc: "value set empty",
|
||||
env: "FOO",
|
||||
value: "",
|
||||
valueSet: true,
|
||||
expectedValue: "",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
desc: "value set",
|
||||
env: "FOO",
|
||||
value: "foo",
|
||||
valueSet: true,
|
||||
expectedValue: "foo",
|
||||
expectedError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
prevVal, prevOk := os.LookupEnv(test.env)
|
||||
defer func() {
|
||||
// preserve the original behavior
|
||||
setEnvVar(t, test.env, prevVal, prevOk)
|
||||
}()
|
||||
|
||||
setEnvVar(t, test.env, test.value, test.valueSet)
|
||||
value, err := fallbackToEnv("some-flag", test.env)
|
||||
if test.expectedError {
|
||||
if err == nil {
|
||||
t.Errorf("expected error, got: %v", err)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if value != test.expectedValue {
|
||||
t.Errorf("unexpected result: %s, expected: %s", value, test.expectedValue)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFallbackToEnvWithDefault(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
env string
|
||||
value string
|
||||
valueSet bool
|
||||
defaultValue string
|
||||
expectedValue string
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
desc: "value unset",
|
||||
env: "FOO",
|
||||
valueSet: false,
|
||||
defaultValue: "default",
|
||||
expectedValue: "default",
|
||||
},
|
||||
{
|
||||
desc: "value set empty",
|
||||
env: "FOO",
|
||||
value: "",
|
||||
valueSet: true,
|
||||
defaultValue: "default",
|
||||
expectedValue: "default",
|
||||
},
|
||||
{
|
||||
desc: "value set",
|
||||
env: "FOO",
|
||||
value: "foo",
|
||||
valueSet: true,
|
||||
defaultValue: "default",
|
||||
expectedValue: "foo",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
prevVal, prevOk := os.LookupEnv(test.env)
|
||||
defer func() {
|
||||
// preserve the original behavior
|
||||
setEnvVar(t, test.env, prevVal, prevOk)
|
||||
}()
|
||||
|
||||
setEnvVar(t, test.env, test.value, test.valueSet)
|
||||
value := fallbackToEnvWithDefault("some-flag", test.env, test.defaultValue)
|
||||
if value != test.expectedValue {
|
||||
t.Errorf("unexpected result: %s, expected: %s", value, test.expectedValue)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -174,10 +174,9 @@ func (sv SupportedVersions) NextVersionPair(current *EtcdVersionPair) *EtcdVersi
|
||||
return &EtcdVersionPair{version: nextVersion, storageVersion: storageVersion}
|
||||
}
|
||||
|
||||
// ParseSupportedVersions parses a comma separated list of etcd versions.
|
||||
func ParseSupportedVersions(s string) (SupportedVersions, error) {
|
||||
// ParseSupportedVersions parses a list of etcd versions.
|
||||
func ParseSupportedVersions(list []string) (SupportedVersions, error) {
|
||||
var err error
|
||||
list := strings.Split(s, ",")
|
||||
versions := make(SupportedVersions, len(list))
|
||||
for i, v := range list {
|
||||
versions[i], err = ParseEtcdVersion(strings.TrimSpace(v))
|
||||
@ -189,8 +188,8 @@ func ParseSupportedVersions(s string) (SupportedVersions, error) {
|
||||
}
|
||||
|
||||
// MustParseSupportedVersions parses a comma separated list of etcd versions or panics if the parse fails.
|
||||
func MustParseSupportedVersions(s string) SupportedVersions {
|
||||
versions, err := ParseSupportedVersions(s)
|
||||
func MustParseSupportedVersions(list []string) SupportedVersions {
|
||||
versions, err := ParseSupportedVersions(list)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user