From c1b4b41c1f81850ef7d289ca90b31d1a8b7739da Mon Sep 17 00:00:00 2001 From: Ciprian Hacman Date: Thu, 18 Dec 2025 14:35:19 +0200 Subject: [PATCH] etcd: Remove unused legacy image build code --- build/dependencies.yaml | 26 -- cluster/images/etcd/Dockerfile | 37 -- cluster/images/etcd/Dockerfile.windows | 28 -- cluster/images/etcd/Makefile | 266 ------------ cluster/images/etcd/OWNERS | 10 - cluster/images/etcd/README.md | 79 ---- cluster/images/etcd/cloudbuild.yaml | 25 -- cluster/images/etcd/migrate-if-needed.bat | 21 - cluster/images/etcd/migrate-if-needed.sh | 26 -- cluster/images/etcd/migrate/copy_file.go | 56 --- cluster/images/etcd/migrate/data_dir.go | 189 --------- cluster/images/etcd/migrate/data_dir_test.go | 167 -------- .../images/etcd/migrate/integration_test.go | 384 ------------------ cluster/images/etcd/migrate/migrate.go | 122 ------ cluster/images/etcd/migrate/migrate_client.go | 201 --------- cluster/images/etcd/migrate/migrate_server.go | 133 ------ cluster/images/etcd/migrate/migrator.go | 194 --------- cluster/images/etcd/migrate/options.go | 249 ------------ cluster/images/etcd/migrate/options_test.go | 134 ------ .../testdata/datadir_with_version/version.txt | 1 - .../datadir_without_version/.placeholder | 0 cluster/images/etcd/migrate/util_others.go | 27 -- cluster/images/etcd/migrate/utils_windows.go | 25 -- cluster/images/etcd/migrate/versions.go | 178 -------- cluster/images/etcd/migrate/versions_test.go | 80 ---- 25 files changed, 2658 deletions(-) delete mode 100644 cluster/images/etcd/Dockerfile delete mode 100644 cluster/images/etcd/Dockerfile.windows delete mode 100644 cluster/images/etcd/Makefile delete mode 100644 cluster/images/etcd/OWNERS delete mode 100644 cluster/images/etcd/README.md delete mode 100644 cluster/images/etcd/cloudbuild.yaml delete mode 100755 cluster/images/etcd/migrate-if-needed.bat delete mode 100755 cluster/images/etcd/migrate-if-needed.sh delete mode 100644 cluster/images/etcd/migrate/copy_file.go delete mode 100644 cluster/images/etcd/migrate/data_dir.go delete mode 100644 cluster/images/etcd/migrate/data_dir_test.go delete mode 100644 cluster/images/etcd/migrate/integration_test.go delete mode 100644 cluster/images/etcd/migrate/migrate.go delete mode 100644 cluster/images/etcd/migrate/migrate_client.go delete mode 100644 cluster/images/etcd/migrate/migrate_server.go delete mode 100644 cluster/images/etcd/migrate/migrator.go delete mode 100644 cluster/images/etcd/migrate/options.go delete mode 100644 cluster/images/etcd/migrate/options_test.go delete mode 100644 cluster/images/etcd/migrate/testdata/datadir_with_version/version.txt delete mode 100644 cluster/images/etcd/migrate/testdata/datadir_without_version/.placeholder delete mode 100644 cluster/images/etcd/migrate/util_others.go delete mode 100644 cluster/images/etcd/migrate/utils_windows.go delete mode 100644 cluster/images/etcd/migrate/versions.go delete mode 100644 cluster/images/etcd/migrate/versions_test.go diff --git a/build/dependencies.yaml b/build/dependencies.yaml index 695e1c2f3d4..5e64aa79b71 100644 --- a/build/dependencies.yaml +++ b/build/dependencies.yaml @@ -79,13 +79,6 @@ dependencies: - path: test/utils/image/manifest.go match: configs\[Etcd\] = Config{list\.GcEtcdRegistry, "etcd", "\d+\.\d+.\d+(-(alpha|beta|rc).\d+)?(-\d+)?"} - - name: "etcd-image" - version: 3.6.4 - refPaths: - - path: cluster/images/etcd/Makefile - match: BUNDLED_ETCD_VERSIONS\?|LATEST_ETCD_VERSION\? - - path: cluster/images/etcd/migrate/options.go - - name: "node-problem-detector" version: 1.34.0 refPaths: @@ -107,13 +100,6 @@ dependencies: #- path: cluster/gce/windows/k8s-node-setup.psm1 # match: DEFAULT_NPD_VERSION - # From https://github.com/etcd-io/etcd/blob/main/Makefile - - name: "golang: etcd release version" - version: 1.23.11 # https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.6.md - refPaths: - - path: cluster/images/etcd/Makefile - match: 'GOLANG_VERSION := \d+.\d+(alpha|beta|rc)?\.?(\d+)?' - # Golang # TODO: this should really be eliminated and controlled by .go-version - name: "golang: upstream version" @@ -151,16 +137,6 @@ dependencies: - name: "registry.k8s.io/debian-base: dependents" version: bookworm-v1.0.6 refPaths: - - path: cluster/images/etcd/Makefile - match: BASEIMAGE\?\=registry\.k8s\.io\/build-image\/debian-base:[a-zA-Z]+\-v((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?) - - path: cluster/images/etcd/Makefile - match: BASEIMAGE\?\=registry\.k8s\.io\/build-image\/debian-base-arm:[a-zA-Z]+\-v((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?) - - path: cluster/images/etcd/Makefile - match: BASEIMAGE\?\=registry\.k8s\.io\/build-image\/debian-base-arm64:[a-zA-Z]+\-v((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?) - - path: cluster/images/etcd/Makefile - match: BASEIMAGE\?\=registry\.k8s\.io\/build-image\/debian-base-ppc64le:[a-zA-Z]+\-v((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?) - - path: cluster/images/etcd/Makefile - match: BASEIMAGE\?\=registry\.k8s\.io\/build-image\/debian-base-s390x:[a-zA-Z]+\-v((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?) - path: test/conformance/image/Makefile match: BASE_IMAGE_VERSION\?= - path: test/images/pets/peer-finder/BASEIMAGE @@ -267,7 +243,5 @@ dependencies: refPaths: - path: build/pause/cloudbuild.yaml match: gcr.io/k8s-staging-test-infra/gcb-docker-gcloud - - path: cluster/images/etcd/cloudbuild.yaml - match: gcr.io/k8s-staging-test-infra/gcb-docker-gcloud - path: test/images/cloudbuild.yaml match: gcr.io/k8s-staging-test-infra/gcb-docker-gcloud diff --git a/cluster/images/etcd/Dockerfile b/cluster/images/etcd/Dockerfile deleted file mode 100644 index 36dc86256d4..00000000000 --- a/cluster/images/etcd/Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright 2016 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. - -ARG BASEIMAGE -ARG RUNNERIMAGE - -FROM ${BASEIMAGE} as builder - -# This image needs bash for running "migrate-if-needed.sh". Instead of a full debian image -# we use just the bash-static and we wrap bash-static into a distroless image instead of -# a full debian image -RUN apt-get update -y \ - && apt-get -yy -q install --no-install-recommends --no-install-suggests --fix-missing \ - bash-static - -RUN cp /bin/bash-static /sh - -FROM ${RUNNERIMAGE} -WORKDIR / - -COPY --from=builder /sh /bin/ - -EXPOSE 2379 2380 4001 7001 -# etcdctl is used by etcd.manifest for livenessProbe. -COPY etcd* etcdctl* /usr/local/bin/ -COPY migrate-if-needed.sh migrate /usr/local/bin/ diff --git a/cluster/images/etcd/Dockerfile.windows b/cluster/images/etcd/Dockerfile.windows deleted file mode 100644 index c88131f8130..00000000000 --- a/cluster/images/etcd/Dockerfile.windows +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright 2021 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. - -ARG RUNNERIMAGE -FROM ${RUNNERIMAGE} - -EXPOSE 2379 2380 4001 7001 - -WORKDIR C:/usr/local/bin -COPY etcd* etcdctl* /usr/local/bin/ -COPY migrate-if-needed.bat /usr/local/bin/ -COPY migrate /usr/local/bin/migrate.exe - -# NOTE(claudiub): docker buildx sets the PATH env variable to a Linux-like PATH, -# # which is not desirable. See: https://github.com/moby/buildkit/issues/1560 -# # TODO(claudiub): remove this once the issue has been resolved. -ENV PATH="C:\usr\local\bin;C:\Windows\system32;C:\Windows;" diff --git a/cluster/images/etcd/Makefile b/cluster/images/etcd/Makefile deleted file mode 100644 index 7178ef7333e..00000000000 --- a/cluster/images/etcd/Makefile +++ /dev/null @@ -1,266 +0,0 @@ -# Copyright 2016 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. - -# Build the etcd image -# -# Usage: -# [BUNDLED_ETCD_VERSIONS=3.4.18 3.5.21 3.6.4] [REGISTRY=registry.k8s.io] [ARCH=amd64] [BASEIMAGE=busybox] make (build|push) -# -# The image contains different etcd versions to simplify -# upgrades. Thus be careful when removing any versions from here. -# -# NOTE: The etcd upgrade rules are that you can upgrade only 1 minor -# version at a time, and patch release don't matter. -# -# Except from etcd-$(version) and etcdctl-$(version) binaries, we also -# need etcd and etcdctl binaries for backward compatibility reasons. -# That binary will be set to the last version from $(BUNDLED_ETCD_VERSIONS). -BUNDLED_ETCD_VERSIONS?=3.4.18 3.5.21 3.6.4 - -# LATEST_ETCD_VERSION identifies the most recent etcd version available. -LATEST_ETCD_VERSION?=3.6.4 - -# REVISION provides a version number for this image and all it's bundled -# artifacts. It should start at zero for each LATEST_ETCD_VERSION and increment -# for each revision of this image at that etcd version. -REVISION?=0 - -# IMAGE_TAG Uniquely identifies registry.k8s.io/etcd docker image with a tag of the form "-". -IMAGE_TAG=$(LATEST_ETCD_VERSION)-$(REVISION) - -ARCH?=amd64 - -# Operating systems supported: linux, windows -OS ?= linux -# OS Version for the Windows images: 1809, ltsc2022 -OSVERSION ?= 1809 - -# The output type could either be docker (local), or registry. -# If it is registry, it will also allow us to push the Windows images. -OUTPUT_TYPE ?= docker - -ALL_OS = linux windows -ALL_ARCH.linux = amd64 arm arm64 ppc64le s390x -ALL_OS_ARCH.linux = $(foreach arch, ${ALL_ARCH.linux}, linux-$(arch)) -ALL_ARCH.windows = amd64 -ALL_OSVERSIONS.windows := 1809 ltsc2022 -ALL_OS_ARCH.windows = $(foreach arch, $(ALL_ARCH.windows), $(foreach osversion, ${ALL_OSVERSIONS.windows}, windows-$(arch)-${osversion})) -ALL_OS_ARCH = $(foreach os, $(ALL_OS), ${ALL_OS_ARCH.${os}}) - -IMAGE_SUFFIX.linux = $(OS)-$(ARCH) -IMAGE_SUFFIX.windows = $(OS)-$(ARCH)-$(OSVERSION) -IMAGE_SUFFIX := ${IMAGE_SUFFIX.${OS}} - -# Image should be pulled from registry.k8s.io, which will auto-detect -# region (us, eu, asia, ...) and pull from the closest. -REGISTRY?=registry.k8s.io -# Images should be pushed to staging-k8s.gcr.io. -PUSH_REGISTRY?=staging-k8s.gcr.io - -MANIFEST_IMAGE := $(PUSH_REGISTRY)/etcd - -# Install binaries matching base distro permissions -BIN_INSTALL := install -m 0555 - -# Hosts running SELinux need :z added to volume mounts -SELINUX_ENABLED := $(shell cat /sys/fs/selinux/enforce 2> /dev/null || echo 0) - -ifeq ($(SELINUX_ENABLED),1) - DOCKER_VOL_OPTS?=:z -endif - -# This option is for running docker manifest command -export DOCKER_CLI_EXPERIMENTAL := enabled -# golang version should match the golang version of the official build from https://github.com/etcd-io/etcd/releases. -GOLANG_VERSION := 1.23.11 # https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.6.md -GOARM?=7 -TEMP_DIR:=$(shell mktemp -d) - -DOCKERFILE.linux = Dockerfile -DOCKERFILE.windows = Dockerfile.windows -DOCKERFILE := ${DOCKERFILE.${OS}} - -ifeq ($(ARCH),amd64) - BASEIMAGE?=registry.k8s.io/build-image/debian-base:bookworm-v1.0.6 -endif -ifeq ($(ARCH),arm) - BASEIMAGE?=registry.k8s.io/build-image/debian-base-arm:bookworm-v1.0.6 -endif -ifeq ($(ARCH),arm64) - BASEIMAGE?=registry.k8s.io/build-image/debian-base-arm64:bookworm-v1.0.6 -endif -ifeq ($(ARCH),ppc64le) - BASEIMAGE?=registry.k8s.io/build-image/debian-base-ppc64le:bookworm-v1.0.6 -endif -ifeq ($(ARCH),s390x) - BASEIMAGE?=registry.k8s.io/build-image/debian-base-s390x:bookworm-v1.0.6 -endif - -BASE.windows = mcr.microsoft.com/windows/nanoserver - -RUNNERIMAGE.windows?=$(BASE.windows):$(OSVERSION) -RUNNERIMAGE.linux?=gcr.io/distroless/static:latest -RUNNERIMAGE := ${RUNNERIMAGE.${OS}} - -QEMUVERSION?=5.2.0-2 - -build: - # Explicitly copy files to the temp directory - $(BIN_INSTALL) migrate-if-needed.sh $(TEMP_DIR) - $(BIN_INSTALL) migrate-if-needed.bat $(TEMP_DIR) - install $(DOCKERFILE) $(TEMP_DIR) - - # Compile migrate - migrate_tmp_dir=$(shell mktemp -d); \ - docker run --rm --interactive -v $(shell pwd)/../../../:/go/src/k8s.io/kubernetes$(DOCKER_VOL_OPTS) -v $${migrate_tmp_dir}:/build$(DOCKER_VOL_OPTS) -e GOOS=$(OS) -e GOARCH=$(ARCH) golang:$(GOLANG_VERSION) \ - /bin/bash -c "CGO_ENABLED=0 GO111MODULE=off go build -o /build/migrate k8s.io/kubernetes/cluster/images/etcd/migrate"; \ - $(BIN_INSTALL) $${migrate_tmp_dir}/migrate $(TEMP_DIR); - -ifeq ($(ARCH),amd64) - - # Do not compile if we should make an image for amd64, use the official etcd binaries instead - # For each release create a tmp dir 'etcd_release_tmp_dir' and unpack the release tar there. -ifeq ($(OS),windows) - for version in $(BUNDLED_ETCD_VERSIONS); do \ - etcd_release_tmp_dir=$(shell mktemp -d); \ - curl -sSL --retry 5 https://github.com/etcd-io/etcd/releases/download/v$$version/etcd-v$$version-windows-amd64.zip -o etcd-v$$version-windows-amd64.zip; \ - unzip -q -d $$etcd_release_tmp_dir etcd-v$$version-windows-amd64.zip; \ - rm etcd-v$$version-windows-amd64.zip; \ - $(BIN_INSTALL) $$etcd_release_tmp_dir/etcd-v$$version-windows-amd64/etcd.exe $$etcd_release_tmp_dir/etcd-v$$version-windows-amd64/etcdctl.exe $(TEMP_DIR)/; \ - $(BIN_INSTALL) $(TEMP_DIR)/etcd.exe $(TEMP_DIR)/etcd-$$version.exe; \ - $(BIN_INSTALL) $(TEMP_DIR)/etcdctl.exe $(TEMP_DIR)/etcdctl-$$version.exe; \ - done -else - for version in $(BUNDLED_ETCD_VERSIONS); do \ - etcd_release_tmp_dir=$(shell mktemp -d); \ - curl -sSL --retry 5 https://github.com/etcd-io/etcd/releases/download/v$$version/etcd-v$$version-linux-amd64.tar.gz | tar -xz -C $$etcd_release_tmp_dir --strip-components=1; \ - $(BIN_INSTALL) $$etcd_release_tmp_dir/etcd $$etcd_release_tmp_dir/etcdctl $(TEMP_DIR)/; \ - $(BIN_INSTALL) $(TEMP_DIR)/etcd $(TEMP_DIR)/etcd-$$version; \ - $(BIN_INSTALL) $(TEMP_DIR)/etcdctl $(TEMP_DIR)/etcdctl-$$version; \ - done -endif - -else - - # Download etcd in a golang container and cross-compile it statically - # For each release create a tmp dir 'etcd_release_tmp_dir' and unpack the release tar there. - arch_prefix="" - ifeq ($(ARCH),arm) - arch_prefix="GOARM=$(GOARM)" - endif - - # use '/go/src/go.etcd.io/etcd' to build etcd 3.4 and later. - for version in $(BUNDLED_ETCD_VERSIONS); do \ - etcd_release_tmp_dir=$(shell mktemp -d); \ - etcd_build_dir="/go/src/github.com/coreos/etcd"; \ - etcd_build_script="./build.sh"; \ - if [ $$(echo $$version | cut -d. -f2) -gt 3 ]; then \ - etcd_build_dir="/go/src/go.etcd.io/etcd"; \ - fi; \ - if [ $$(echo $$version | cut -d. -f2) -gt 5 ]; then \ - etcd_build_script="./scripts/build.sh"; \ - fi; \ - docker run --rm --interactive -v $${etcd_release_tmp_dir}:/etcdbin golang:$(GOLANG_VERSION)$(DOCKER_VOL_OPTS) /bin/bash -c \ - "git clone https://github.com/etcd-io/etcd $$etcd_build_dir \ - && cd $$etcd_build_dir \ - && git checkout v$${version} \ - && $(arch_prefix) GOARCH=$(ARCH) $$etcd_build_script \ - && cp -f bin/$(ARCH)/etcd* bin/etcd* /etcdbin; echo 'done'"; \ - $(BIN_INSTALL) $$etcd_release_tmp_dir/etcd $$etcd_release_tmp_dir/etcdctl $(TEMP_DIR)/; \ - $(BIN_INSTALL) $(TEMP_DIR)/etcd $(TEMP_DIR)/etcd-$$version; \ - $(BIN_INSTALL) $(TEMP_DIR)/etcdctl $(TEMP_DIR)/etcdctl-$$version; \ - done - - # Add this ENV variable in order to workaround an unsupported arch blocker - # On arm (which is 32-bit), it can't handle >1GB data in-memory - ifeq ($(ARCH),arm) - cd $(TEMP_DIR) && echo "ENV ETCD_UNSUPPORTED_ARCH=$(ARCH)" >> $(DOCKERFILE) - endif -endif - - docker run --rm --privileged multiarch/qemu-user-static:$(QEMUVERSION) --reset -p yes - docker buildx version - BUILDER=$(shell docker buildx create --use) - - # And build the image - docker buildx build \ - --pull \ - --provenance=false \ - --sbom=false \ - --output=type=$(OUTPUT_TYPE) \ - --platform "$(OS)/$(ARCH)" \ - -t $(REGISTRY)/etcd:$(IMAGE_TAG)-$(IMAGE_SUFFIX) \ - --build-arg BASEIMAGE=$(BASEIMAGE) \ - --build-arg RUNNERIMAGE=$(RUNNERIMAGE) \ - -f $(TEMP_DIR)/$(DOCKERFILE) \ - $(TEMP_DIR) - docker buildx rm $$BUILDER - -push: build - -# split words on hyphen, access by 1-index -word-hyphen = $(word $2,$(subst -, ,$1)) - -sub-build-%: - $(MAKE) OUTPUT_TYPE=docker OS=$(call word-hyphen,$*,1) ARCH=$(call word-hyphen,$*,2) build - -all-build: $(addprefix sub-build-,$(ALL_OS_ARCH)) - -sub-push-image-%: - $(MAKE) OUTPUT_TYPE=registry OS=$(call word-hyphen,$*,1) ARCH=$(call word-hyphen,$*,2) OSVERSION=$(call word-hyphen,$*,3) REGISTRY=$(PUSH_REGISTRY) push - -all-push-images: $(addprefix sub-push-image-,$(ALL_OS_ARCH)) - -# NOTE(claudiub): A non-default builder instance is needed in order to build Windows images. -all-push: all-push-images push-manifest - -push-manifest: - docker manifest create --amend $(MANIFEST_IMAGE):$(IMAGE_TAG) $(shell echo $(ALL_OS_ARCH) | sed -e "s~[^ ]*~$(MANIFEST_IMAGE):$(IMAGE_TAG)\-&~g") - set -x; for arch in $(ALL_ARCH.linux); do docker manifest annotate --os linux --arch $${arch} ${MANIFEST_IMAGE}:${IMAGE_TAG} ${MANIFEST_IMAGE}:${IMAGE_TAG}-linux-$${arch}; done - # For Windows images, we also need to include the "os.version" in the manifest list, so the Windows node can pull the proper image it needs. - # we use awk to also trim the quotes around the OS version string. - set -x; \ - for arch in $(ALL_ARCH.windows); do \ - for osversion in ${ALL_OSVERSIONS.windows}; do \ - full_version=`docker manifest inspect ${BASE.windows}:$${osversion} | grep "os.version" | head -n 1 | awk -F\" '{print $$4}'` || true; \ - docker manifest annotate --os windows --arch $${arch} --os-version $${full_version} ${MANIFEST_IMAGE}:${IMAGE_TAG} ${MANIFEST_IMAGE}:${IMAGE_TAG}-windows-$${arch}-$${osversion}; \ - done; \ - done - docker manifest push --purge ${MANIFEST_IMAGE}:${IMAGE_TAG} - -unit-test: - docker run --rm --interactive -v $(shell pwd)/../../../:/go/src/k8s.io/kubernetes$(DOCKER_VOL_OPTS) -e GOARCH=$(ARCH) golang:$(GOLANG_VERSION) \ - /bin/bash -c "CGO_ENABLED=0 go test -v k8s.io/kubernetes/cluster/images/etcd/migrate" - -# Integration tests require both a golang build environment and all the etcd binaries from a `registry.k8s.io/etcd` image (`/usr/local/bin/etcd-`, ...). -# Since the `registry.k8s.io/etcd` image is for runtime only and does not have a build golang environment, we create a new docker image to run integration tests -# with. -build-integration-test-image: build - cp -r $(TEMP_DIR) $(TEMP_DIR)_integration_test - cp Dockerfile $(TEMP_DIR)_integration_test/Dockerfile - docker build \ - --pull \ - -t etcd-integration-test \ - --build-arg BASEIMAGE=golang:$(GOLANG_VERSION) \ - --build-arg RUNNERIMAGE=$(RUNNERIMAGE) \ - $(TEMP_DIR)_integration_test - -integration-test: - docker run --rm --interactive -v $(shell pwd)/../../../:/go/src/k8s.io/kubernetes$(DOCKER_VOL_OPTS) -e GOARCH=$(ARCH) etcd-integration-test \ - /bin/bash -c "CGO_ENABLED=0 go test -tags=integration k8s.io/kubernetes/cluster/images/etcd/migrate -args -v 10 -logtostderr true" - -integration-build-test: build-integration-test-image integration-test -test: unit-test integration-build-test -all: all-build test -.PHONY: build push push-manifest all-push all-push-images all-build unit-test build-integration-test-image integration-test integration-build-test test diff --git a/cluster/images/etcd/OWNERS b/cluster/images/etcd/OWNERS deleted file mode 100644 index 30affa99d38..00000000000 --- a/cluster/images/etcd/OWNERS +++ /dev/null @@ -1,10 +0,0 @@ -# See the OWNERS docs at https://go.k8s.io/owners - -reviewers: - - jpbetz - - serathius -approvers: - - jpbetz - - serathius -labels: - - sig/etcd diff --git a/cluster/images/etcd/README.md b/cluster/images/etcd/README.md deleted file mode 100644 index 4e26caa06a6..00000000000 --- a/cluster/images/etcd/README.md +++ /dev/null @@ -1,79 +0,0 @@ -### registry.k8s.io/etcd docker image - -Provides docker images containing etcd and etcdctl binaries for multiple etcd -version as well as a migration operator utility for upgrading and downgrading -etcd--it's data directory in particular--to a target version. - -#### Versioning - -Each `registry.k8s.io/etcd` docker image is tagged with an version string of the form -`-`, e.g. `3.0.17-0`. The etcd version is the -SemVer of latest etcd version available in the image. The image revision -distinguishes between docker images with the same lastest etcd version but -changes (bug fixes and backward compatible improvements) to the migration -utility bundled with the image. - -In addition to the latest etcd version, each `registry.k8s.io/etcd` image contains -etcd and etcdctl binaries for older versions of etcd. These are used by the -migration operator utility when performing downgrades and multi-step upgrades, -but can also be used as the etcd target version. - -#### Usage - -Always run `/usr/local/bin/migrate` (or the -`/usr/local/bin/migrate-if-needed.sh` wrapper script) before starting the etcd -server. On Windows, run `C:\bin\migrate.exe` (or the `C:\bin\migrate-if-needed.bat -wrapper script`). - -`migrate` writes a `version.txt` file to track the "current" version -of etcd that was used to persist data to disk. A "target" version may also be provided -by the `TARGET_STORAGE` (e.g. "etcd3") and `TARGET_VERSION` (e.g. "3.4.13" ) -environment variables. If the persisted version differs from the target version, -`migrate-if-needed.sh` will migrate the data from the current to the target -version. - -Upgrades to any target version are supported. The data will be automatically upgraded -in steps to each minor version until the target version is reached. - -Downgrades to the previous minor version of the 3.x series is supported. - -#### Permissions - -By default, `migrate` will write data directory files with default permissions -according to the umask it is run with. When run in the published -`registry.k8s.io/etcd` images the default umask is 0022 which will result in 0755 -directory permissions and 0644 file permissions. - -#### Cross building - -For `amd64`, official `etcd` and `etcdctl` binaries are downloaded from Github -to maintain official support. For other architectures, `etcd` is cross-compiled -from source. Arch-specific `debian` images serve as base images. - -Windows images can be built on Linux nodes due to `docker buildx`, but they will -only be created and pushed when using the `all-push` make target. - -#### How to release - -First, update `ETCD_VERSION` and `REVSION` in the `Makefile`. - -Next, build and test the image: - -```console -$ make build test -``` - -Last, build and push the docker images for all supported architectures. - -```console -# Build images for all the architecture and push the manifest image as well -$ make all-push - -# Build images for all the architecture -$ make all-build - -# Build image for target architecture(default=amd64) -$ make build ARCH=ppc64le -``` - -If you don't want to push the images, run `make` or `make build` instead diff --git a/cluster/images/etcd/cloudbuild.yaml b/cluster/images/etcd/cloudbuild.yaml deleted file mode 100644 index 520fb8e74df..00000000000 --- a/cluster/images/etcd/cloudbuild.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# See https://cloud.google.com/cloud-build/docs/build-config -timeout: 1200s -options: - substitution_option: ALLOW_LOOSE - machineType: 'N1_HIGHCPU_8' -steps: - - name: 'gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20240523-a15ad90fc9@sha256:bb04162508c2c61637eae700a0d8e8c8be8f2d4c831d2b75e59db2d4dd6cf75d' - entrypoint: 'bash' - dir: ./cluster/images/etcd - env: - - DOCKER_CLI_EXPERIMENTAL=enabled - - REGISTRY=gcr.io/$PROJECT_ID - - PUSH_REGISTRY=gcr.io/$PROJECT_ID - - IMAGE=gcr.io/$PROJECT_ID/etcd - - BUILD_IMAGE=debian-build - - TMPDIR=/workspace - - HOME=/root # for docker buildx - args: - - '-c' - - | - gcloud auth configure-docker \ - && docker buildx create --name img-builder --use \ - && docker buildx inspect --bootstrap \ - && docker run --rm --privileged linuxkit/binfmt:4ea3b9b0938cbd19834c096aa31ff475cc75d281 \ - && make all-push diff --git a/cluster/images/etcd/migrate-if-needed.bat b/cluster/images/etcd/migrate-if-needed.bat deleted file mode 100755 index a264f82a883..00000000000 --- a/cluster/images/etcd/migrate-if-needed.bat +++ /dev/null @@ -1,21 +0,0 @@ -@echo off -REM Copyright 2021 The Kubernetes Authors. -REM -REM Licensed under the Apache License, Version 2.0 (the "License"); -REM you may not use this file except in compliance with the License. -REM You may obtain a copy of the License at -REM -REM http://www.apache.org/licenses/LICENSE-2.0 -REM -REM Unless required by applicable law or agreed to in writing, software -REM distributed under the License is distributed on an "AS IS" BASIS, -REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -REM See the License for the specific language governing permissions and -REM limitations under the License. - -REM DEPRECATED: -REM The functionality has been moved to migrate binary and this script -REM if left for backward compatibility with previous manifests. It will be -REM removed in the future. - -C:\bin\migrate.exe diff --git a/cluster/images/etcd/migrate-if-needed.sh b/cluster/images/etcd/migrate-if-needed.sh deleted file mode 100755 index 75002700c31..00000000000 --- a/cluster/images/etcd/migrate-if-needed.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh - -# Copyright 2016 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. - - -# 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 - -/usr/local/bin/migrate diff --git a/cluster/images/etcd/migrate/copy_file.go b/cluster/images/etcd/migrate/copy_file.go deleted file mode 100644 index 7a8421237b4..00000000000 --- a/cluster/images/etcd/migrate/copy_file.go +++ /dev/null @@ -1,56 +0,0 @@ -/* -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 -} diff --git a/cluster/images/etcd/migrate/data_dir.go b/cluster/images/etcd/migrate/data_dir.go deleted file mode 100644 index f13c555ec61..00000000000 --- a/cluster/images/etcd/migrate/data_dir.go +++ /dev/null @@ -1,189 +0,0 @@ -/* -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" - "io" - "os" - "path/filepath" - "strings" - - "k8s.io/klog/v2" -) - -// DataDirectory provides utilities for initializing and backing up an -// etcd "data-dir" as well as managing a version.txt file to track the -// etcd server version and storage version of the etcd data in the -// directory. -type DataDirectory struct { - path string - versionFile *VersionFile -} - -// OpenOrCreateDataDirectory opens a data directory, creating the directory -// if it doesn't not already exist. -func OpenOrCreateDataDirectory(path string) (*DataDirectory, error) { - exists, err := exists(path) - if err != nil { - return nil, err - } - if !exists { - klog.Infof("data directory '%s' does not exist, creating it", path) - err := os.MkdirAll(path, 0777) - if err != nil { - return nil, fmt.Errorf("failed to create data directory %s: %v", path, err) - } - } - versionFile := &VersionFile{ - path: filepath.Join(path, versionFilename), - } - return &DataDirectory{path, versionFile}, nil -} - -// Initialize set the version.txt to the target version if the data -// directory is empty. If the data directory is non-empty, no -// version.txt file will be written since the actual version of etcd -// used to create the data is unknown. -func (d *DataDirectory) Initialize(target *EtcdVersionPair) error { - isEmpty, err := d.IsEmpty() - if err != nil { - return err - } - if isEmpty { - klog.Infof("data directory '%s' is empty, writing target version '%s' to version.txt", d.path, target) - err = d.versionFile.Write(target) - if err != nil { - return fmt.Errorf("failed to write version.txt to '%s': %v", d.path, err) - } - return nil - } - return nil -} - -// Backup creates a backup copy of data directory. -func (d *DataDirectory) Backup() error { - backupDir := fmt.Sprintf("%s.bak", d.path) - err := os.RemoveAll(backupDir) - if err != nil { - return err - } - err = os.MkdirAll(backupDir, 0777) - if err != nil { - return err - } - err = copyDirectory(d.path, backupDir) - if err != nil { - return err - } - - return nil -} - -// IsEmpty returns true if the data directory is entirely empty. -func (d *DataDirectory) IsEmpty() (bool, error) { - dir, err := os.Open(d.path) - if err != nil { - return false, fmt.Errorf("failed to open data directory %s: %v", d.path, err) - } - defer dir.Close() - _, err = dir.Readdirnames(1) - if err == io.EOF { - return true, nil - } - return false, err -} - -// String returns the data directory path. -func (d *DataDirectory) String() string { - return d.path -} - -// VersionFile provides utilities for reading and writing version.txt files -// to etcd "data-dir" for tracking the etcd server and storage versions -// of the data in the directory. -type VersionFile struct { - path string -} - -func (v *VersionFile) nextPath() string { - return fmt.Sprintf("%s-next", v.path) -} - -// Exists returns true if a version.txt file exists on the file system. -func (v *VersionFile) Exists() (bool, error) { - return exists(v.path) -} - -// Read parses the version.txt file and returns it's contents. -func (v *VersionFile) Read() (*EtcdVersionPair, error) { - data, err := os.ReadFile(v.path) - if err != nil { - return nil, fmt.Errorf("failed to read version file %s: %v", v.path, err) - } - txt := strings.TrimSpace(string(data)) - vp, err := ParseEtcdVersionPair(txt) - if err != nil { - return nil, fmt.Errorf("failed to parse etcd '/' string from version.txt file contents '%s': %v", txt, err) - } - return vp, nil -} - -// equals returns true iff VersionFile exists and contains given EtcdVersionPair. -func (v *VersionFile) equals(vp *EtcdVersionPair) (bool, error) { - exists, err := v.Exists() - if err != nil { - return false, err - } - if !exists { - return false, nil - } - cvp, err := v.Read() - if err != nil { - return false, err - } - return vp.Equals(cvp), nil -} - -// Write creates or overwrites the contents of the version.txt file with the given EtcdVersionPair. -func (v *VersionFile) Write(vp *EtcdVersionPair) error { - // We do write only if file content differs from given EtcdVersionPair. - isUpToDate, err := v.equals(vp) - if err != nil { - return fmt.Errorf("failed to to check if version file %s should be changed: %v", v.path, err) - } - if isUpToDate { - return nil - } - // We do write + rename instead of just write to protect from version.txt - // corruption under full disk condition. - // See https://github.com/kubernetes/kubernetes/issues/98989. - err = os.WriteFile(v.nextPath(), []byte(vp.String()), 0666) - if err != nil { - return fmt.Errorf("failed to write new version file %s: %v", v.nextPath(), err) - } - return os.Rename(v.nextPath(), v.path) -} - -func exists(path string) (bool, error) { - if _, err := os.Stat(path); os.IsNotExist(err) { - return false, nil - } else if err != nil { - return false, err - } - return true, nil -} diff --git a/cluster/images/etcd/migrate/data_dir_test.go b/cluster/images/etcd/migrate/data_dir_test.go deleted file mode 100644 index 41b7279cad6..00000000000 --- a/cluster/images/etcd/migrate/data_dir_test.go +++ /dev/null @@ -1,167 +0,0 @@ -/* -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" - "path/filepath" - "testing" - - "github.com/blang/semver/v4" -) - -var ( - latestVersion = semver.MustParse("3.1.12") -) - -func TestExistingDataDirWithVersionFile(t *testing.T) { - d, err := OpenOrCreateDataDirectory("testdata/datadir_with_version") - if err != nil { - t.Fatalf("Failed to open data dir: %v", err) - } - isEmpty, err := d.IsEmpty() - if err != nil { - t.Fatalf("Failed to check if data dir is empty: %v", err) - } - if isEmpty { - t.Errorf("Expected non-empty data directory to exist") - } - exists, err := d.versionFile.Exists() - if err != nil { - t.Fatal(err) - } - if !exists { - t.Fatalf("Expected version file %s to exist", d.versionFile.path) - } - vp, err := d.versionFile.Read() - if err != nil { - t.Fatalf("Failed to read version file %s: %v", d.versionFile.path, err) - } - expectedVersion := &EtcdVersionPair{&EtcdVersion{latestVersion}, storageEtcd3} - if !vp.Equals(expectedVersion) { - t.Errorf("Expected version file to contain %s, but got %s", expectedVersion, vp) - } -} - -func TestExistingDataDirWithoutVersionFile(t *testing.T) { - targetVersion := &EtcdVersionPair{&EtcdVersion{latestVersion}, storageEtcd3} - - d, err := OpenOrCreateDataDirectory("testdata/datadir_without_version") - if err != nil { - t.Fatalf("Failed to open data dir: %v", err) - } - exists, err := d.versionFile.Exists() - if err != nil { - t.Fatal(err) - } - if exists { - t.Errorf("Expected version file %s not to exist", d.versionFile.path) - } - err = d.Initialize(targetVersion) - if err != nil { - t.Fatalf("Failed initialize data directory %s: %v", d.path, err) - } - exists, err = d.versionFile.Exists() - if err != nil { - t.Fatal(err) - } - if exists { - t.Fatalf("Expected version file %s not to exist after initializing non-empty data-dir", d.versionFile.path) - } -} - -func TestNonexistingDataDir(t *testing.T) { - targetVersion := &EtcdVersionPair{&EtcdVersion{latestVersion}, storageEtcd3} - path := newTestPath(t) - defer os.RemoveAll(path) - d, err := OpenOrCreateDataDirectory(filepath.Join(path, "data-dir")) - if err != nil { - t.Fatalf("Failed to open data dir: %v", err) - } - isEmpty, err := d.IsEmpty() - if err != nil { - t.Fatalf("Failed to check if data dir is empty: %v", err) - } - if !isEmpty { - t.Errorf("Expected empty data directory to exist") - } - err = d.Initialize(targetVersion) - if err != nil { - t.Fatalf("Failed initialize data directory %s: %v", d.path, err) - } - exists, err := d.versionFile.Exists() - if err != nil { - t.Fatal(err) - } - if !exists { - t.Fatalf("Expected version file %s to exist", d.versionFile.path) - } - isEmpty, err = d.IsEmpty() - if err != nil { - t.Fatalf("Failed to check if data dir is empty: %v", err) - } - if isEmpty { - t.Errorf("Expected non-empty data directory to exist after Initialize()") - } - vp, err := d.versionFile.Read() - if err != nil { - t.Fatalf("Failed to read version file %s: %v", d.versionFile.path, err) - } - if !vp.Equals(targetVersion) { - t.Errorf("Expected version file to contain %s, but got %s", targetVersion, vp) - } -} - -func TestBackup(t *testing.T) { - path := newTestPath(t) - defer os.RemoveAll(path) - d, err := OpenOrCreateDataDirectory(filepath.Join(path, "data-dir")) - if err != nil { - t.Fatalf("Failed to open data dir: %v", err) - } - _, err = os.Create(filepath.Join(path, "data-dir", "empty.txt")) - if err != nil { - t.Fatal(err) - } - err = d.Backup() - if err != nil { - t.Fatalf("Failed to backup data directory %s: %v", d.path, err) - } - bak, err := OpenOrCreateDataDirectory(filepath.Join(path, "data-dir.bak")) - if err != nil { - t.Fatalf("Failed to open backup data dir: %v", err) - } - isEmpty, err := bak.IsEmpty() - if err != nil { - t.Fatal(err) - } - if isEmpty { - t.Errorf("Expected non-empty backup directory to exist after Backup()") - } -} - -func newTestPath(t *testing.T) string { - path, err := os.MkdirTemp("", "etcd-migrate-test-") - if err != nil { - t.Fatalf("Failed to create tmp dir for test: %v", err) - } - err = os.Chmod(path, 0777) - if err != nil { - t.Fatalf("Failed to granting permission to tmp dir for test: %v", err) - } - return path -} diff --git a/cluster/images/etcd/migrate/integration_test.go b/cluster/images/etcd/migrate/integration_test.go deleted file mode 100644 index 6f7b255f93d..00000000000 --- a/cluster/images/etcd/migrate/integration_test.go +++ /dev/null @@ -1,384 +0,0 @@ -//go:build integration - -/* -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 ( - "bytes" - cryptorand "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "flag" - "fmt" - "math/big" - "net" - "os" - "path/filepath" - "strings" - "sync" - "testing" - "time" - - "github.com/blang/semver/v4" - "k8s.io/klog/v2" - netutils "k8s.io/utils/net" -) - -var ( - testSupportedVersions = mustParseSupportedVersions([]string{"3.0.17", "3.1.12"}) - testVersionPrevious = &EtcdVersion{semver.MustParse("3.0.17")} - testVersionLatest = &EtcdVersion{semver.MustParse("3.1.12")} -) - -func init() { - // Enable klog which is used in dependencies - klog.InitFlags(nil) - flag.Set("logtostderr", "true") - flag.Set("v", "9") -} - -func TestMigrate(t *testing.T) { - migrations := []struct { - title string - memberCount int - startVersion string - endVersion string - protocol string - clientListenUrls string - }{ - // upgrades - {"v3-v3-up", 1, "3.0.17/etcd3", "3.1.12/etcd3", "https", ""}, - {"oldest-newest-up", 1, "3.0.17/etcd3", "3.1.12/etcd3", "https", ""}, - {"v3-v3-up-with-additional-client-url", 1, "3.0.17/etcd3", "3.1.12/etcd3", "https", "http://127.0.0.1:2379,http://10.128.0.1:2379"}, - - // warning: v2->v3 ha upgrades not currently supported. - {"ha-v3-v3-up", 3, "3.0.17/etcd3", "3.1.12/etcd3", "https", ""}, - - // downgrades - {"v3-v3-down", 1, "3.1.12/etcd3", "3.0.17/etcd3", "https", ""}, - - // warning: ha downgrades not yet supported. - } - - for _, m := range migrations { - t.Run(m.title, func(t *testing.T) { - start := mustParseEtcdVersionPair(m.startVersion) - end := mustParseEtcdVersionPair(m.endVersion) - - testCfgs := clusterConfig(t, m.title, m.memberCount, m.protocol, m.clientListenUrls) - - servers := []*EtcdMigrateServer{} - for _, cfg := range testCfgs { - client, err := NewEtcdMigrateClient(cfg) - if err != nil { - t.Fatalf("Failed to create client: %v", err) - } - server := NewEtcdMigrateServer(cfg, client) - servers = append(servers, server) - } - - // Start the servers. - parallel(servers, func(server *EtcdMigrateServer) { - dataDir, err := OpenOrCreateDataDirectory(server.cfg.dataDirectory) - if err != nil { - t.Fatalf("Error opening or creating data directory %s: %v", server.cfg.dataDirectory, err) - } - migrator := &Migrator{server.cfg, dataDir, server.client} - err = migrator.MigrateIfNeeded(start) - if err != nil { - t.Fatalf("Migration failed: %v", err) - } - err = server.Start(start.version) - if err != nil { - t.Fatalf("Failed to start server: %v", err) - } - }) - - // Write a value to each server, read it back. - parallel(servers, func(server *EtcdMigrateServer) { - key := fmt.Sprintf("/registry/%s", server.cfg.name) - value := fmt.Sprintf("value-%s", server.cfg.name) - err := server.client.Put(start.version, key, value) - if err != nil { - t.Fatalf("failed to write text value: %v", err) - } - - checkVal, err := server.client.Get(start.version, key) - if err != nil { - t.Errorf("Error getting %s for validation: %v", key, err) - } - if checkVal != value { - t.Errorf("Expected %s from %s but got %s", value, key, checkVal) - } - }) - - // Migrate the servers in series. - serial(servers, func(server *EtcdMigrateServer) { - err := server.Stop() - if err != nil { - t.Fatalf("Stop server failed: %v", err) - } - dataDir, err := OpenOrCreateDataDirectory(server.cfg.dataDirectory) - if err != nil { - t.Fatalf("Error opening or creating data directory %s: %v", server.cfg.dataDirectory, err) - } - migrator := &Migrator{server.cfg, dataDir, server.client} - err = migrator.MigrateIfNeeded(end) - if err != nil { - t.Fatalf("Migration failed: %v", err) - } - err = server.Start(end.version) - if err != nil { - t.Fatalf("Start server failed: %v", err) - } - }) - - // Check that all test values can be read back from all the servers. - parallel(servers, func(server *EtcdMigrateServer) { - for _, s := range servers { - key := fmt.Sprintf("/registry/%s", s.cfg.name) - value := fmt.Sprintf("value-%s", s.cfg.name) - checkVal, err := server.client.Get(end.version, key) - if err != nil { - t.Errorf("Error getting %s from etcd 2.x after rollback from 3.x: %v", key, err) - } - if checkVal != value { - t.Errorf("Expected %s from %s but got %s when reading after rollback from %s to %s", value, key, checkVal, start, end) - } - } - }) - - // Stop the servers. - parallel(servers, func(server *EtcdMigrateServer) { - err := server.Stop() - if err != nil { - t.Fatalf("Failed to stop server: %v", err) - } - }) - - // Check that version.txt contains the correct end version. - parallel(servers, func(server *EtcdMigrateServer) { - dataDir, err := OpenOrCreateDataDirectory(server.cfg.dataDirectory) - v, err := dataDir.versionFile.Read() - if err != nil { - t.Fatalf("Failed to read version.txt file: %v", err) - } - if !v.Equals(end) { - t.Errorf("Expected version.txt to contain %s but got %s", end, v) - } - // Integration tests are run in a docker container with umask of 0022. - checkPermissions(t, server.cfg.dataDirectory, 0755|os.ModeDir) - checkPermissions(t, dataDir.versionFile.path, 0644) - }) - }) - } -} - -func parallel(servers []*EtcdMigrateServer, fn func(server *EtcdMigrateServer)) { - var wg sync.WaitGroup - wg.Add(len(servers)) - for _, server := range servers { - go func(s *EtcdMigrateServer) { - defer wg.Done() - fn(s) - }(server) - } - wg.Wait() -} - -func serial(servers []*EtcdMigrateServer, fn func(server *EtcdMigrateServer)) { - for _, server := range servers { - fn(server) - } -} - -func checkPermissions(t *testing.T, path string, expected os.FileMode) { - info, err := os.Stat(path) - if err != nil { - t.Fatalf("Failed to stat file %s: %v", path, err) - } - if info.Mode() != expected { - t.Errorf("Expected permissions for file %s of %s, but got %s", path, expected, info.Mode()) - } -} - -func clusterConfig(t *testing.T, name string, memberCount int, protocol string, clientListenUrls string) []*EtcdMigrateCfg { - peers := []string{} - for i := 0; i < memberCount; i++ { - memberName := fmt.Sprintf("%s-%d", name, i) - peerPort := uint64(2380 + i*10000) - peer := fmt.Sprintf("%s=%s://127.0.0.1:%d", memberName, protocol, peerPort) - peers = append(peers, peer) - } - initialCluster := strings.Join(peers, ",") - - extraArgs := "" - if protocol == "https" { - extraArgs = getOrCreateTLSPeerCertArgs(t) - } - - cfgs := []*EtcdMigrateCfg{} - for i := 0; i < memberCount; i++ { - memberName := fmt.Sprintf("%s-%d", name, i) - peerURL := fmt.Sprintf("%s://127.0.0.1:%d", protocol, uint64(2380+i*10000)) - cfg := &EtcdMigrateCfg{ - binPath: "/usr/local/bin", - name: memberName, - initialCluster: initialCluster, - port: uint64(2379 + i*10000), - peerListenUrls: peerURL, - peerAdvertiseUrls: peerURL, - clientListenUrls: clientListenUrls, - etcdDataPrefix: "/registry", - ttlKeysDirectory: "/registry/events", - supportedVersions: testSupportedVersions, - dataDirectory: fmt.Sprintf("/tmp/etcd-data-dir-%s", memberName), - etcdServerArgs: extraArgs, - } - cfgs = append(cfgs, cfg) - } - return cfgs -} - -func getOrCreateTLSPeerCertArgs(t *testing.T) string { - spec := TestCertSpec{ - host: "localhost", - ips: []string{"127.0.0.1"}, - } - certDir := "/tmp/certs" - certFile := filepath.Join(certDir, "test.crt") - keyFile := filepath.Join(certDir, "test.key") - err := getOrCreateTestCertFiles(certFile, keyFile, spec) - if err != nil { - t.Fatalf("failed to create server cert: %v", err) - } - return fmt.Sprintf("--peer-client-cert-auth --peer-trusted-ca-file=%s --peer-cert-file=%s --peer-key-file=%s", certFile, certFile, keyFile) -} - -type TestCertSpec struct { - host string - names, ips []string // in certificate -} - -func getOrCreateTestCertFiles(certFileName, keyFileName string, spec TestCertSpec) (err error) { - if _, err := os.Stat(certFileName); err == nil { - if _, err := os.Stat(keyFileName); err == nil { - return nil - } - } - - certPem, keyPem, err := generateSelfSignedCertKey(spec.host, parseIPList(spec.ips), spec.names) - if err != nil { - return err - } - - os.MkdirAll(filepath.Dir(certFileName), os.FileMode(0777)) - err = os.WriteFile(certFileName, certPem, os.FileMode(0777)) - if err != nil { - return err - } - - os.MkdirAll(filepath.Dir(keyFileName), os.FileMode(0777)) - err = os.WriteFile(keyFileName, keyPem, os.FileMode(0777)) - if err != nil { - return err - } - - return nil -} - -func parseIPList(ips []string) []net.IP { - var netIPs []net.IP - for _, ip := range ips { - netIPs = append(netIPs, netutils.ParseIPSloppy(ip)) - } - return netIPs -} - -// generateSelfSignedCertKey creates a self-signed certificate and key for the given host. -// Host may be an IP or a DNS name -// You may also specify additional subject alt names (either ip or dns names) for the certificate -func generateSelfSignedCertKey(host string, alternateIPs []net.IP, alternateDNS []string) ([]byte, []byte, error) { - priv, err := rsa.GenerateKey(cryptorand.Reader, 2048) - if err != nil { - return nil, nil, err - } - - template := x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - CommonName: fmt.Sprintf("%s@%d", host, time.Now().Unix()), - }, - NotBefore: time.Unix(0, 0), - NotAfter: time.Now().Add(time.Hour * 24 * 365 * 100), - - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, - BasicConstraintsValid: true, - IsCA: true, - } - - if ip := netutils.ParseIPSloppy(host); ip != nil { - template.IPAddresses = append(template.IPAddresses, ip) - } else { - template.DNSNames = append(template.DNSNames, host) - } - - template.IPAddresses = append(template.IPAddresses, alternateIPs...) - template.DNSNames = append(template.DNSNames, alternateDNS...) - - derBytes, err := x509.CreateCertificate(cryptorand.Reader, &template, &template, &priv.PublicKey, priv) - if err != nil { - return nil, nil, err - } - - // Generate cert - certBuffer := bytes.Buffer{} - if err := pem.Encode(&certBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { - return nil, nil, err - } - - // Generate key - keyBuffer := bytes.Buffer{} - if err := pem.Encode(&keyBuffer, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil { - return nil, nil, err - } - - return certBuffer.Bytes(), keyBuffer.Bytes(), nil -} - -// mustParseEtcdVersionPair parses a "/" string to an EtcdVersionPair -// or panics if the parse fails. -func mustParseEtcdVersionPair(s string) *EtcdVersionPair { - pair, err := ParseEtcdVersionPair(s) - if err != nil { - panic(err) - } - return pair -} - -// mustParseSupportedVersions parses a comma separated list of etcd versions or panics if the parse fails. -func mustParseSupportedVersions(list []string) SupportedVersions { - versions, err := ParseSupportedVersions(list) - if err != nil { - panic(err) - } - return versions -} diff --git a/cluster/images/etcd/migrate/migrate.go b/cluster/images/etcd/migrate/migrate.go deleted file mode 100644 index e089786faf5..00000000000 --- a/cluster/images/etcd/migrate/migrate.go +++ /dev/null @@ -1,122 +0,0 @@ -/* -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" - "path/filepath" - - "github.com/spf13/cobra" - "k8s.io/klog/v2" -) - -const ( - versionFilename = "version.txt" -) - -var ( - migrateCmd = &cobra.Command{ - Short: "Upgrade/downgrade etcd data across multiple versions", - Long: `Upgrade or downgrade etcd data across multiple versions to the target version - -Given a 'bin-dir' directory of etcd and etcdctl binaries, an etcd 'data-dir' with a 'version.txt' file and -a target etcd version, this tool will upgrade or downgrade the etcd data from the version specified in -'version.txt' to the target version. -`, - Run: func(cmd *cobra.Command, args []string) { - runMigrate() - }, - } - opts = migrateOpts{} -) - -func main() { - registerFlags(migrateCmd.Flags(), &opts) - err := migrateCmd.Execute() - if err != nil { - klog.Errorf("Failed to execute migratecmd: %s", err) - } -} - -// runMigrate starts the migration. -func runMigrate() { - 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.clientListenUrls, - 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. -func migrate(name string, port uint64, peerListenUrls string, peerAdvertiseUrls string, clientListenUrls string, - binPath string, dataDirPath string, etcdDataPrefix string, ttlKeysDirectory string, - initialCluster string, target *EtcdVersionPair, bundledVersions SupportedVersions, etcdServerArgs string) { - - dataDir, err := OpenOrCreateDataDirectory(dataDirPath) - if err != nil { - klog.Fatalf("Error opening or creating data directory %s: %v", dataDirPath, err) - } - - cfg := &EtcdMigrateCfg{ - binPath: binPath, - name: name, - port: port, - peerListenUrls: peerListenUrls, - peerAdvertiseUrls: peerAdvertiseUrls, - clientListenUrls: clientListenUrls, - etcdDataPrefix: etcdDataPrefix, - ttlKeysDirectory: ttlKeysDirectory, - initialCluster: initialCluster, - supportedVersions: bundledVersions, - dataDirectory: dataDirPath, - etcdServerArgs: etcdServerArgs, - } - client, err := NewEtcdMigrateClient(cfg) - if err != nil { - klog.Fatalf("Migration failed: %v", err) - } - defer client.Close() - - migrator := &Migrator{cfg, dataDir, client} - - err = migrator.MigrateIfNeeded(target) - if err != nil { - klog.Fatalf("Migration failed: %v", err) - } -} diff --git a/cluster/images/etcd/migrate/migrate_client.go b/cluster/images/etcd/migrate/migrate_client.go deleted file mode 100644 index 229356b2715..00000000000 --- a/cluster/images/etcd/migrate/migrate_client.go +++ /dev/null @@ -1,201 +0,0 @@ -/* -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 ( - "bytes" - "context" - "fmt" - "os" - "os/exec" - "path/filepath" - "strings" - "time" - - clientv3 "go.etcd.io/etcd/client/v3" - "google.golang.org/grpc" - "k8s.io/klog/v2" -) - -// CombinedEtcdClient provides an implementation of EtcdMigrateClient using a combination of the etcd v2 client, v3 client -// and etcdctl commands called via the shell. -type CombinedEtcdClient struct { - cfg *EtcdMigrateCfg -} - -// NewEtcdMigrateClient creates a new EtcdMigrateClient from the given EtcdMigrateCfg. -func NewEtcdMigrateClient(cfg *EtcdMigrateCfg) (EtcdMigrateClient, error) { - return &CombinedEtcdClient{cfg}, nil -} - -// Close closes the client and releases any resources it holds. -func (e *CombinedEtcdClient) Close() error { - return nil -} - -// SetEtcdVersionKeyValue writes the given version to the etcd 'etcd_version' key. -// If no error is returned, the write was successful, indicating the etcd server is available -// and able to perform consensus writes. -func (e *CombinedEtcdClient) SetEtcdVersionKeyValue(version *EtcdVersion) error { - return e.Put(version, "etcd_version", version.String()) -} - -// Put write a single key value pair to etcd. -func (e *CombinedEtcdClient) Put(version *EtcdVersion, key, value string) error { - v3client, err := e.clientV3() - if err != nil { - return err - } - defer v3client.Close() - _, err = v3client.KV.Put(context.Background(), key, value) - return err -} - -// Get reads a single value for a given key. -func (e *CombinedEtcdClient) Get(version *EtcdVersion, key string) (string, error) { - v3client, err := e.clientV3() - if err != nil { - return "", err - } - defer v3client.Close() - resp, err := v3client.KV.Get(context.Background(), key) - if err != nil { - return "", err - } - kvs := resp.Kvs - if len(kvs) != 1 { - return "", fmt.Errorf("expected exactly one value for key %s but got %d", key, len(kvs)) - } - - return string(kvs[0].Value), nil -} - -func (e *CombinedEtcdClient) clientV3() (*clientv3.Client, error) { - return clientv3.New(clientv3.Config{ - Endpoints: []string{e.endpoint()}, - DialTimeout: 20 * time.Second, - DialOptions: []grpc.DialOption{ - grpc.WithBlock(), // block until the underlying connection is up - }, - }) -} - -// Backup creates a backup of an etcd2 data directory at the given backupDir. -func (e *CombinedEtcdClient) Backup(version *EtcdVersion, backupDir string) error { - // We cannot use etcd/client (v2) to make this call. It is implemented in the etcdctl client code. - if version.Major != 2 { - return fmt.Errorf("etcd 2.x required but got version '%s'", version) - } - return e.runEtcdctlCommand(version, - "--debug", - "backup", - "--data-dir", e.cfg.dataDirectory, - "--backup-dir", backupDir, - ) -} - -// Snapshot captures a snapshot from a running etcd3 server and saves it to the given snapshotFile. -// We cannot use etcd/clientv3 to make this call. It is implemented in the etcdctl client code. -func (e *CombinedEtcdClient) Snapshot(version *EtcdVersion, snapshotFile string) error { - if version.Major != 3 { - return fmt.Errorf("etcd 3.x required but got version '%s'", version) - } - return e.runEtcdctlCommand(version, - "--endpoints", e.endpoint(), - "snapshot", "save", snapshotFile, - ) -} - -// Restore restores a given snapshotFile into the data directory specified this clients config. -func (e *CombinedEtcdClient) Restore(version *EtcdVersion, snapshotFile string) error { - // We cannot use etcd/clientv3 to make this call. It is implemented in the etcdctl client code. - if version.Major != 3 { - return fmt.Errorf("etcd 3.x required but got version '%s'", version) - } - return e.runEtcdctlCommand(version, - "snapshot", "restore", snapshotFile, - "--data-dir", e.cfg.dataDirectory, - "--name", e.cfg.name, - "--initial-advertise-peer-urls", e.cfg.peerAdvertiseUrls, - "--initial-cluster", e.cfg.initialCluster, - ) -} - -// Migrate upgrades a 'etcd2' storage version data directory to a 'etcd3' storage version -// data directory. -func (e *CombinedEtcdClient) Migrate(version *EtcdVersion) error { - // We cannot use etcd/clientv3 to make this call as it is implemented in etcd/etcdctl. - if version.Major != 3 { - return fmt.Errorf("etcd 3.x required but got version '%s'", version) - } - return e.runEtcdctlCommand(version, - "migrate", - "--data-dir", e.cfg.dataDirectory, - ) -} - -func (e *CombinedEtcdClient) runEtcdctlCommand(version *EtcdVersion, args ...string) error { - etcdctlCmd := exec.Command(filepath.Join(e.cfg.binPath, fmt.Sprintf("etcdctl-%s", version)), args...) - etcdctlCmd.Env = []string{fmt.Sprintf("ETCDCTL_API=%d", version.Major)} - etcdctlCmd.Stdout = os.Stdout - etcdctlCmd.Stderr = os.Stderr - return etcdctlCmd.Run() -} - -// AttachLease attaches leases of the given leaseDuration to all the etcd objects under -// ttlKeysDirectory specified in this client's config. -func (e *CombinedEtcdClient) AttachLease(leaseDuration time.Duration) error { - ttlKeysPrefix := e.cfg.ttlKeysDirectory - // Make sure that ttlKeysPrefix is ended with "/" so that we only get children "directories". - if !strings.HasSuffix(ttlKeysPrefix, "/") { - ttlKeysPrefix += "/" - } - ctx := context.Background() - - v3client, err := e.clientV3() - if err != nil { - return err - } - defer v3client.Close() - objectsResp, err := v3client.KV.Get(ctx, ttlKeysPrefix, clientv3.WithPrefix()) - if err != nil { - return fmt.Errorf("error while getting objects to attach to the lease") - } - - lease, err := v3client.Lease.Grant(ctx, int64(leaseDuration/time.Second)) - if err != nil { - return fmt.Errorf("error while creating lease: %v", err) - } - klog.Infof("Lease with TTL: %v created", lease.TTL) - - klog.Infof("Attaching lease to %d entries", len(objectsResp.Kvs)) - for _, kv := range objectsResp.Kvs { - putResp, err := v3client.KV.Put(ctx, string(kv.Key), string(kv.Value), clientv3.WithLease(lease.ID), clientv3.WithPrevKV()) - if err != nil { - klog.Errorf("Error while attaching lease to: %s", string(kv.Key)) - } - if !bytes.Equal(putResp.PrevKv.Value, kv.Value) { - return fmt.Errorf("concurrent access to key detected when setting lease on %s, expected previous value of %s but got %s", - kv.Key, kv.Value, putResp.PrevKv.Value) - } - } - return nil -} - -func (e *CombinedEtcdClient) endpoint() string { - return fmt.Sprintf("http://127.0.0.1:%d", e.cfg.port) -} diff --git a/cluster/images/etcd/migrate/migrate_server.go b/cluster/images/etcd/migrate/migrate_server.go deleted file mode 100644 index da9c659cec3..00000000000 --- a/cluster/images/etcd/migrate/migrate_server.go +++ /dev/null @@ -1,133 +0,0 @@ -/* -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" - "os/exec" - "strings" - "time" - - "k8s.io/klog/v2" -) - -// EtcdMigrateServer manages starting and stopping a versioned etcd server binary. -type EtcdMigrateServer struct { - cfg *EtcdMigrateCfg - client EtcdMigrateClient - cmd *exec.Cmd -} - -// NewEtcdMigrateServer creates a EtcdMigrateServer for starting and stopping a etcd server at the given version. -func NewEtcdMigrateServer(cfg *EtcdMigrateCfg, client EtcdMigrateClient) *EtcdMigrateServer { - return &EtcdMigrateServer{cfg: cfg, client: client} -} - -// Start starts an etcd server as a separate process, waits until it has started, and returns a exec.Cmd. -// TODO: Add support for listening to client via TLS. -func (r *EtcdMigrateServer) Start(version *EtcdVersion) error { - etcdCmd := exec.Command( - fmt.Sprintf("%s/etcd-%s", r.cfg.binPath, version), - "--name", r.cfg.name, - "--initial-cluster", r.cfg.initialCluster, - "--debug", - "--data-dir", r.cfg.dataDirectory, - "--listen-client-urls", r.cfg.clientListenUrls, - "--advertise-client-urls", fmt.Sprintf("http://127.0.0.1:%d", r.cfg.port), - "--listen-peer-urls", r.cfg.peerListenUrls, - "--initial-advertise-peer-urls", r.cfg.peerAdvertiseUrls, - ) - if r.cfg.etcdServerArgs != "" { - extraArgs := strings.Fields(r.cfg.etcdServerArgs) - etcdCmd.Args = append(etcdCmd.Args, extraArgs...) - } - fmt.Printf("Starting server %s: %+v\n", r.cfg.name, etcdCmd.Args) - - etcdCmd.Stdout = os.Stdout - etcdCmd.Stderr = os.Stderr - err := etcdCmd.Start() - if err != nil { - return err - } - interval := time.NewTicker(time.Millisecond * 500) - defer interval.Stop() - done := make(chan bool) - go func() { - time.Sleep(time.Minute * 2) - close(done) - }() - for { - select { - case <-interval.C: - err := r.client.SetEtcdVersionKeyValue(version) - if err != nil { - klog.Infof("Still waiting for etcd to start, current error: %v", err) - // keep waiting - } else { - klog.Infof("Etcd on port %d is up.", r.cfg.port) - r.cmd = etcdCmd - return nil - } - case <-done: - err = etcdCmd.Process.Kill() - if err != nil { - return fmt.Errorf("error killing etcd: %v", err) - } - return fmt.Errorf("timed out waiting for etcd on port %d", r.cfg.port) - } - } -} - -// Stop terminates the etcd server process. If the etcd server process has not been started -// or is not still running, this returns an error. -func (r *EtcdMigrateServer) Stop() error { - if r.cmd == nil { - return fmt.Errorf("cannot stop EtcdMigrateServer that has not been started") - } - err := r.cmd.Process.Signal(os.Interrupt) - if err != nil { - return fmt.Errorf("error sending SIGINT to etcd for graceful shutdown: %v", err) - } - gracefulWait := time.Minute * 2 - stopped := make(chan bool) - timedout := make(chan bool) - go func() { - time.Sleep(gracefulWait) - close(timedout) - }() - go func() { - select { - case <-stopped: - return - case <-timedout: - klog.Infof("etcd server has not terminated gracefully after %s, killing it.", gracefulWait) - r.cmd.Process.Kill() - return - } - }() - err = r.cmd.Wait() - close(stopped) - if exiterr, ok := err.(*exec.ExitError); ok { - klog.Infof("etcd server stopped (signal: %s)", exiterr.Error()) - // stopped - } else if err != nil { - return fmt.Errorf("error waiting for etcd to stop: %v", err) - } - klog.Infof("Stopped etcd server %s", r.cfg.name) - return nil -} diff --git a/cluster/images/etcd/migrate/migrator.go b/cluster/images/etcd/migrate/migrator.go deleted file mode 100644 index 2ecc87c29fe..00000000000 --- a/cluster/images/etcd/migrate/migrator.go +++ /dev/null @@ -1,194 +0,0 @@ -/* -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" - "time" - - "github.com/blang/semver/v4" - "k8s.io/klog/v2" -) - -// EtcdMigrateCfg provides all configuration required to perform etcd data upgrade/downgrade migrations. -type EtcdMigrateCfg struct { - binPath string - name string - initialCluster string - port uint64 - peerListenUrls string - peerAdvertiseUrls string - clientListenUrls string - etcdDataPrefix string - ttlKeysDirectory string - supportedVersions SupportedVersions - dataDirectory string - etcdServerArgs string -} - -// EtcdMigrateClient defines the etcd client operations required to perform migrations. -type EtcdMigrateClient interface { - SetEtcdVersionKeyValue(version *EtcdVersion) error - Get(version *EtcdVersion, key string) (string, error) - Put(version *EtcdVersion, key, value string) error - Backup(version *EtcdVersion, backupDir string) error - Snapshot(version *EtcdVersion, snapshotFile string) error - Restore(version *EtcdVersion, snapshotFile string) error - Migrate(version *EtcdVersion) error - AttachLease(leaseDuration time.Duration) error - Close() error -} - -// Migrator manages etcd data migrations. -type Migrator struct { - cfg *EtcdMigrateCfg // TODO: don't wire this directly in - dataDirectory *DataDirectory - client EtcdMigrateClient -} - -// MigrateIfNeeded upgrades or downgrades the etcd data directory to the given target version. -func (m *Migrator) MigrateIfNeeded(target *EtcdVersionPair) error { - klog.Infof("Starting migration to %s", target) - err := m.dataDirectory.Initialize(target) - if err != nil { - return fmt.Errorf("failed to initialize data directory %s: %v", m.dataDirectory.path, err) - } - - var current *EtcdVersionPair - vfExists, err := m.dataDirectory.versionFile.Exists() - if err != nil { - return err - } - if vfExists { - current, err = m.dataDirectory.versionFile.Read() - if err != nil { - return err - } - } else { - return fmt.Errorf("existing data directory '%s' is missing version.txt file, unable to migrate", m.dataDirectory.path) - } - - for { - klog.Infof("Converging current version '%s' to target version '%s'", current, target) - currentNextMinorVersion := &EtcdVersion{Version: semver.Version{Major: current.version.Major, Minor: current.version.Minor + 1}} - switch { - case current.version.MajorMinorEquals(target.version) || currentNextMinorVersion.MajorMinorEquals(target.version): - klog.Infof("current version '%s' equals or is one minor version previous of target version '%s' - migration complete", current, target) - err = m.dataDirectory.versionFile.Write(target) - if err != nil { - return fmt.Errorf("failed to write version.txt to '%s': %v", m.dataDirectory.path, err) - } - return nil - case current.storageVersion == storageEtcd2 && target.storageVersion == storageEtcd3: - return fmt.Errorf("upgrading from etcd2 storage to etcd3 storage is not supported") - case current.version.Major == 3 && target.version.Major == 2: - return fmt.Errorf("downgrading from etcd 3.x to 2.x is not supported") - case current.version.Major == target.version.Major && current.version.Minor < target.version.Minor: - stepVersion := m.cfg.supportedVersions.NextVersionPair(current) - klog.Infof("upgrading etcd from %s to %s", current, stepVersion) - current, err = m.minorVersionUpgrade(current, stepVersion) - case current.version.Major == 3 && target.version.Major == 3 && current.version.Minor > target.version.Minor: - klog.Infof("rolling etcd back from %s to %s", current, target) - current, err = m.rollbackEtcd3MinorVersion(current, target) - } - if err != nil { - return err - } - } -} - -func (m *Migrator) rollbackEtcd3MinorVersion(current *EtcdVersionPair, target *EtcdVersionPair) (*EtcdVersionPair, error) { - if target.version.Minor != current.version.Minor-1 { - return nil, fmt.Errorf("rollback from %s to %s not supported, only rollbacks to the previous minor version are supported", current.version, target.version) - } - - klog.Infof("Performing etcd %s -> %s rollback", current.version, target.version) - err := m.dataDirectory.Backup() - if err != nil { - return nil, err - } - - snapshotFilename := fmt.Sprintf("%s.snapshot.db", m.dataDirectory.path) - err = os.Remove(snapshotFilename) - if err != nil && !os.IsNotExist(err) { - return nil, fmt.Errorf("failed to clean snapshot file before rollback: %v", err) - } - - // Start current version of etcd. - runner := m.newServer() - klog.Infof("Starting etcd version %s to capture rollback snapshot.", current.version) - err = runner.Start(current.version) - if err != nil { - klog.Fatalf("Unable to automatically downgrade etcd: starting etcd version %s to capture rollback snapshot failed: %v", current.version, err) - return nil, err - } - - klog.Infof("Snapshotting etcd %s to %s", current.version, snapshotFilename) - err = m.client.Snapshot(current.version, snapshotFilename) - if err != nil { - return nil, err - } - - err = runner.Stop() - if err != nil { - return nil, err - } - - klog.Info("Backing up data before rolling back") - backupDir := fmt.Sprintf("%s.bak", m.dataDirectory) - err = os.RemoveAll(backupDir) - if err != nil { - return nil, err - } - origInfo, err := os.Stat(m.dataDirectory.path) - if err != nil { - return nil, err - } - err = os.Rename(m.dataDirectory.path, backupDir) - if err != nil { - return nil, err - } - - klog.Infof("Restoring etcd %s from %s", target.version, snapshotFilename) - err = m.client.Restore(target.version, snapshotFilename) - if err != nil { - return nil, err - } - err = os.Chmod(m.dataDirectory.path, origInfo.Mode()) - if err != nil { - return nil, err - } - - return target, nil -} - -func (m *Migrator) minorVersionUpgrade(current *EtcdVersionPair, target *EtcdVersionPair) (*EtcdVersionPair, error) { - runner := m.newServer() - - // Do the migration step, by just starting etcd in the target version. - err := runner.Start(target.version) - if err != nil { - return nil, err - } - err = runner.Stop() - return target, err -} - -func (m *Migrator) newServer() *EtcdMigrateServer { - return NewEtcdMigrateServer(m.cfg, m.client) -} diff --git a/cluster/images/etcd/migrate/options.go b/cluster/images/etcd/migrate/options.go deleted file mode 100644 index 42fbb375898..00000000000 --- a/cluster/images/etcd/migrate/options.go +++ /dev/null @@ -1,249 +0,0 @@ -/* -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.4.18", "3.5.21", "3.6.4"} -) - -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" - clientListenURLsEnv = "LISTEN_CLIENT_URLS" - clientListenURLFmt = "http://127.0.0.1:%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 - clientListenUrls 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- env, if unset defaults to etcd- 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 depending on .") - flags.Uint64Var(&opts.peerPort, "peer-port", 0, - "etcd peer port to use during migration operations. If unset defaults to 2380 or 2381 depending on .") - 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:.") - 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:.") - flags.StringVar(&opts.clientListenUrls, "listen-client-urls", "", - "etcd --listen-client-urls flag. If unset, fallbacks to LISTEN_CLIENT_URLS env, and if unset defaults to http://127.0.0.1:.") - flags.StringVar(&opts.binDir, "bin-dir", "/usr/local/bin", - "directory of etcd and etcdctl binaries, must contain etcd- and etcdctl- for each version listed in .") - 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 /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 =https://localhost:.") - flags.StringVar(&opts.targetVersion, "target-version", "", - "version of etcd to migrate to. Format must be ... 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 for %s flag unset - defaulting to %s", env, flag, 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.clientListenUrls == "" { - def := fmt.Sprintf(clientListenURLFmt, opts.port) - opts.clientListenUrls = fallbackToEnvWithDefault("listen-client-urls", clientListenURLsEnv, 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-' and 'etcdctl-' 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-' and 'etcdctl-' 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 -} diff --git a/cluster/images/etcd/migrate/options_test.go b/cluster/images/etcd/migrate/options_test.go deleted file mode 100644 index 6b1f3e95245..00000000000 --- a/cluster/images/etcd/migrate/options_test.go +++ /dev/null @@ -1,134 +0,0 @@ -/* -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 { - t.Setenv(env, val) - } else if prev, ok := os.LookupEnv(env); ok { - t.Cleanup(func() { os.Setenv(env, prev) }) - - 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) { - 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) { - 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) - } - }) - } -} diff --git a/cluster/images/etcd/migrate/testdata/datadir_with_version/version.txt b/cluster/images/etcd/migrate/testdata/datadir_with_version/version.txt deleted file mode 100644 index d557c0269dc..00000000000 --- a/cluster/images/etcd/migrate/testdata/datadir_with_version/version.txt +++ /dev/null @@ -1 +0,0 @@ -3.1.12/etcd3 diff --git a/cluster/images/etcd/migrate/testdata/datadir_without_version/.placeholder b/cluster/images/etcd/migrate/testdata/datadir_without_version/.placeholder deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/cluster/images/etcd/migrate/util_others.go b/cluster/images/etcd/migrate/util_others.go deleted file mode 100644 index 7640bb722f8..00000000000 --- a/cluster/images/etcd/migrate/util_others.go +++ /dev/null @@ -1,27 +0,0 @@ -//go:build !windows - -/* -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 ( - "github.com/mrunalp/fileutils" -) - -func copyDirectory(source string, dest string) error { - return fileutils.CopyDirectory(source, dest) -} diff --git a/cluster/images/etcd/migrate/utils_windows.go b/cluster/images/etcd/migrate/utils_windows.go deleted file mode 100644 index 47a9663019e..00000000000 --- a/cluster/images/etcd/migrate/utils_windows.go +++ /dev/null @@ -1,25 +0,0 @@ -//go:build windows - -/* -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" - -func copyDirectory(source string, dest string) error { - return fmt.Errorf("no support for windows") -} diff --git a/cluster/images/etcd/migrate/versions.go b/cluster/images/etcd/migrate/versions.go deleted file mode 100644 index 4982ea88f53..00000000000 --- a/cluster/images/etcd/migrate/versions.go +++ /dev/null @@ -1,178 +0,0 @@ -/* -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" - "strings" - - "github.com/blang/semver/v4" -) - -// EtcdVersion specifies an etcd server binaries SemVer. -type EtcdVersion struct { - semver.Version -} - -// ParseEtcdVersion parses a SemVer string to an EtcdVersion. -func ParseEtcdVersion(s string) (*EtcdVersion, error) { - v, err := semver.Make(s) - if err != nil { - return nil, err - } - return &EtcdVersion{v}, nil -} - -// MustParseEtcdVersion parses a SemVer string to an EtcdVersion and panics if the parse fails. -func MustParseEtcdVersion(s string) *EtcdVersion { - return &EtcdVersion{semver.MustParse(s)} -} - -// String returns the version in SemVer string format. -func (v *EtcdVersion) String() string { - return v.Version.String() -} - -// Equals returns true if the versions are exactly equal. -func (v *EtcdVersion) Equals(o *EtcdVersion) bool { - return v.Version.Equals(o.Version) -} - -// MajorMinorEquals returns true if the major and minor parts of the versions are equal; -// if only patch versions differ, this returns true. -func (v *EtcdVersion) MajorMinorEquals(o *EtcdVersion) bool { - return v.Major == o.Major && v.Minor == o.Minor -} - -// EtcdStorageVersion identifies the storage version of an etcd data directory. -type EtcdStorageVersion int - -const ( - storageUnknown EtcdStorageVersion = iota - storageEtcd2 - storageEtcd3 -) - -// ParseEtcdStorageVersion parses an etcd storage version string to an EtcdStorageVersion. -func ParseEtcdStorageVersion(s string) (EtcdStorageVersion, error) { - switch s { - case "etcd2": - return storageEtcd2, nil - case "etcd3": - return storageEtcd3, nil - default: - return storageUnknown, fmt.Errorf("unrecognized storage version: %s", s) - } -} - -// MustParseEtcdStorageVersion parses an etcd storage version string to an EtcdStorageVersion and -// panics if the parse fails. -func MustParseEtcdStorageVersion(s string) EtcdStorageVersion { - version, err := ParseEtcdStorageVersion(s) - if err != nil { - panic(err) - } - return version -} - -// String returns the text representation of the EtcdStorageVersion, 'etcd2' or 'etcd3'. -func (v EtcdStorageVersion) String() string { - switch v { - case storageEtcd2: - return "etcd2" - case storageEtcd3: - return "etcd3" - default: - panic(fmt.Sprintf("enum value %d missing from EtcdStorageVersion String() function", v)) - } -} - -// EtcdVersionPair is composed of an etcd version and storage version. -type EtcdVersionPair struct { - version *EtcdVersion - storageVersion EtcdStorageVersion -} - -// ParseEtcdVersionPair parses a "/" string to an EtcdVersionPair. -func ParseEtcdVersionPair(s string) (*EtcdVersionPair, error) { - parts := strings.Split(s, "/") - if len(parts) != 2 { - return nil, fmt.Errorf("malformed version file, expected ../ but got %s", s) - } - version, err := ParseEtcdVersion(parts[0]) - if err != nil { - return nil, err - } - storageVersion, err := ParseEtcdStorageVersion(parts[1]) - if err != nil { - return nil, err - } - return &EtcdVersionPair{version, storageVersion}, nil -} - -// String returns "/" string of the EtcdVersionPair. -func (vp *EtcdVersionPair) String() string { - return fmt.Sprintf("%s/%s", vp.version, vp.storageVersion) -} - -// Equals returns true if both the versions and storage versions are exactly equal. -func (vp *EtcdVersionPair) Equals(o *EtcdVersionPair) bool { - return vp.version.Equals(o.version) && vp.storageVersion == o.storageVersion -} - -// SupportedVersions provides a list of etcd versions that are "supported" for some purpose. -// The list must be sorted from lowest semantic version to high. -type SupportedVersions []*EtcdVersion - -// NextVersion returns the next supported version after the given current version, or nil if no -// next version exists. -func (sv SupportedVersions) NextVersion(current *EtcdVersion) *EtcdVersion { - var nextVersion *EtcdVersion - for i, supportedVersion := range sv { - if current.MajorMinorEquals(supportedVersion) && len(sv) > i+1 { - nextVersion = sv[i+1] - } - } - return nextVersion -} - -// NextVersionPair returns the next supported version after the given current version and infers -// the storage version from the major version part of the next version. -func (sv SupportedVersions) NextVersionPair(current *EtcdVersionPair) *EtcdVersionPair { - nextVersion := sv.NextVersion(current.version) - if nextVersion == nil { - return nil - } - storageVersion := storageEtcd3 - if nextVersion.Major == 2 { - storageVersion = storageEtcd2 - } - return &EtcdVersionPair{version: nextVersion, storageVersion: storageVersion} -} - -// ParseSupportedVersions parses a list of etcd versions. -func ParseSupportedVersions(list []string) (SupportedVersions, error) { - var err error - versions := make(SupportedVersions, len(list)) - for i, v := range list { - versions[i], err = ParseEtcdVersion(strings.TrimSpace(v)) - if err != nil { - return nil, err - } - } - return versions, nil -} diff --git a/cluster/images/etcd/migrate/versions_test.go b/cluster/images/etcd/migrate/versions_test.go deleted file mode 100644 index 3e4114fd418..00000000000 --- a/cluster/images/etcd/migrate/versions_test.go +++ /dev/null @@ -1,80 +0,0 @@ -/* -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 ( - "testing" - - "github.com/blang/semver/v4" -) - -func TestSerializeEtcdVersionPair(t *testing.T) { - cases := []struct { - versionTxt string - version *EtcdVersionPair - match bool - }{ - {"3.1.2/etcd3", &EtcdVersionPair{&EtcdVersion{semver.MustParse("3.1.2")}, storageEtcd3}, true}, - {"1.1.1-rc.0/etcd3", &EtcdVersionPair{&EtcdVersion{semver.MustParse("1.1.1-rc.0")}, storageEtcd3}, true}, - {"10.100.1000/etcd3", &EtcdVersionPair{&EtcdVersion{semver.MustParse("10.100.1000")}, storageEtcd3}, true}, - } - - for _, c := range cases { - vp, err := ParseEtcdVersionPair(c.versionTxt) - if err != nil { - t.Errorf("Failed to parse '%s': %v", c.versionTxt, err) - } - if vp.Equals(c.version) != c.match { - t.Errorf("Expected '%s' to be parsed as '%+v', got '%+v'", c.versionTxt, c.version, vp) - } - if vp.String() != c.versionTxt { - t.Errorf("Expected round trip serialization back to '%s', got '%s'", c.versionTxt, vp.String()) - } - } - - unparsables := []string{ - "1.1/etcd3", - "1.1.1.1/etcd3", - "1.1.1/etcd4", - } - for _, unparsable := range unparsables { - vp, err := ParseEtcdVersionPair(unparsable) - if err == nil { - t.Errorf("Should have failed to parse '%s' but got '%s'", unparsable, vp) - } - } -} - -func TestMajorMinorEquals(t *testing.T) { - cases := []struct { - first *EtcdVersion - second *EtcdVersion - match bool - }{ - {&EtcdVersion{semver.Version{Major: 3, Minor: 1, Patch: 2}}, &EtcdVersion{semver.Version{Major: 3, Minor: 1, Patch: 0}}, true}, - {&EtcdVersion{semver.Version{Major: 3, Minor: 1, Patch: 2}}, &EtcdVersion{semver.Version{Major: 3, Minor: 1, Patch: 2}}, true}, - - {&EtcdVersion{semver.Version{Major: 3, Minor: 0, Patch: 0}}, &EtcdVersion{semver.Version{Major: 3, Minor: 1, Patch: 0}}, false}, - {&EtcdVersion{semver.Version{Major: 2, Minor: 0, Patch: 0}}, &EtcdVersion{semver.Version{Major: 3, Minor: 0, Patch: 0}}, false}, - } - - for _, c := range cases { - if c.first.MajorMinorEquals(c.second) != c.match { - t.Errorf("Expected (%+v == %+v) == %t, got %t", c.first, c.second, c.match, !c.match) - } - } -}