Add generated file
This PR adds generated files under pkg/client and vendor folder.
This commit is contained in:
25
vendor/k8s.io/kubernetes/cluster/images/etcd-empty-dir-cleanup/Dockerfile
generated
vendored
Normal file
25
vendor/k8s.io/kubernetes/cluster/images/etcd-empty-dir-cleanup/Dockerfile
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# 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.
|
||||
|
||||
FROM busybox
|
||||
|
||||
COPY etcdctl etcd-empty-dir-cleanup.sh /
|
||||
RUN chmod a+rx /etcdctl /etcd-empty-dir-cleanup.sh
|
||||
|
||||
ENV ETCDCTL /etcdctl
|
||||
ENV SLEEP_SECOND 3600
|
||||
|
||||
USER nobody:nogroup
|
||||
|
||||
ENTRYPOINT ["/etcd-empty-dir-cleanup.sh"]
|
37
vendor/k8s.io/kubernetes/cluster/images/etcd-empty-dir-cleanup/Makefile
generated
vendored
Normal file
37
vendor/k8s.io/kubernetes/cluster/images/etcd-empty-dir-cleanup/Makefile
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# 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.
|
||||
|
||||
.PHONY: build push
|
||||
|
||||
ETCD_VERSION = 3.2.18
|
||||
# Image should be pulled from k8s.gcr.io, which will auto-detect
|
||||
# region (us, eu, asia, ...) and pull from the closest.
|
||||
REGISTRY = k8s.gcr.io
|
||||
# Images should be pushed to staging-k8s.gcr.io.
|
||||
PUSH_REGISTRY = staging-k8s.gcr.io
|
||||
TAG = 3.2.18.0
|
||||
|
||||
clean:
|
||||
rm -rf etcdctl etcd-v$(ETCD_VERSION)-linux-amd64 etcd-v$(ETCD_VERSION)-linux-amd64.tar.gz
|
||||
|
||||
build: clean
|
||||
curl -L -O https://github.com/coreos/etcd/releases/download/v$(ETCD_VERSION)/etcd-v$(ETCD_VERSION)-linux-amd64.tar.gz
|
||||
tar xzvf etcd-v$(ETCD_VERSION)-linux-amd64.tar.gz
|
||||
cp etcd-v$(ETCD_VERSION)-linux-amd64/etcdctl .
|
||||
docker build --pull -t $(REGISTRY)/etcd-empty-dir-cleanup:$(TAG) .
|
||||
rm -rf etcdctl etcd-v$(ETCD_VERSION)-linux-amd64 etcd-v$(ETCD_VERSION)-linux-amd64.tar.gz
|
||||
|
||||
push: build
|
||||
docker tag $(REGISTRY)/etcd-empty-dir-cleanup:$(TAG) $(PUSH_REGISTRY)/etcd-empty-dir-cleanup:$(TAG)
|
||||
docker push $(PUSH_REGISTRY)/etcd-empty-dir-cleanup:$(TAG)
|
37
vendor/k8s.io/kubernetes/cluster/images/etcd-empty-dir-cleanup/etcd-empty-dir-cleanup.sh
generated
vendored
Normal file
37
vendor/k8s.io/kubernetes/cluster/images/etcd-empty-dir-cleanup/etcd-empty-dir-cleanup.sh
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
#!/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.
|
||||
|
||||
echo "Removing empty directories from etcd..."
|
||||
|
||||
cleanup_empty_dirs () {
|
||||
if [ "$(${ETCDCTL} ls $1)" ]; then
|
||||
for SUBDIR in $(${ETCDCTL} ls -p $1 | grep "/$")
|
||||
do
|
||||
cleanup_empty_dirs ${SUBDIR}
|
||||
done
|
||||
else
|
||||
echo "Removing empty key $1 ..."
|
||||
${ETCDCTL} rmdir $1
|
||||
fi
|
||||
}
|
||||
|
||||
while true
|
||||
do
|
||||
echo "Starting cleanup..."
|
||||
cleanup_empty_dirs "/registry"
|
||||
echo "Done with cleanup."
|
||||
sleep ${SLEEP_SECOND}
|
||||
done
|
40
vendor/k8s.io/kubernetes/cluster/images/etcd-version-monitor/BUILD
generated
vendored
Normal file
40
vendor/k8s.io/kubernetes/cluster/images/etcd-version-monitor/BUILD
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_binary(
|
||||
name = "etcd-version-monitor",
|
||||
embed = [":go_default_library"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["etcd-version-monitor.go"],
|
||||
importpath = "k8s.io/kubernetes/cluster/images/etcd-version-monitor",
|
||||
deps = [
|
||||
"//vendor/github.com/gogo/protobuf/proto:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
|
||||
"//vendor/github.com/prometheus/client_golang/prometheus/promhttp:go_default_library",
|
||||
"//vendor/github.com/prometheus/client_model/go:go_default_library",
|
||||
"//vendor/github.com/prometheus/common/expfmt:go_default_library",
|
||||
"//vendor/github.com/spf13/pflag:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
20
vendor/k8s.io/kubernetes/cluster/images/etcd-version-monitor/Dockerfile
generated
vendored
Normal file
20
vendor/k8s.io/kubernetes/cluster/images/etcd-version-monitor/Dockerfile
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# Copyright 2017 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.
|
||||
|
||||
FROM scratch
|
||||
LABEL maintainer "Shyam JVS <shyamjvs@google.com>"
|
||||
|
||||
COPY etcd-version-monitor /etcd-version-monitor
|
||||
|
||||
EXPOSE 9101
|
47
vendor/k8s.io/kubernetes/cluster/images/etcd-version-monitor/Makefile
generated
vendored
Normal file
47
vendor/k8s.io/kubernetes/cluster/images/etcd-version-monitor/Makefile
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
# Copyright 2017 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-version-monitor image
|
||||
#
|
||||
# Usage:
|
||||
# [GOLANG_VERSION=1.8.3] [REGISTRY=staging-k8s.gcr.io] [TAG=test] make (build|push)
|
||||
# TODO(shyamjvs): Support architectures other than amd64 if needed.
|
||||
ARCH:=amd64
|
||||
GOLANG_VERSION?=1.8.3
|
||||
REGISTRY?=staging-k8s.gcr.io
|
||||
TAG?=0.1.2
|
||||
IMAGE:=$(REGISTRY)/etcd-version-monitor:$(TAG)
|
||||
CURRENT_DIR:=$(pwd)
|
||||
TEMP_DIR:=$(shell mktemp -d)
|
||||
|
||||
build:
|
||||
# Copy the necessary files for building the image to TEMP_DIR.
|
||||
cp etcd-version-monitor.go Dockerfile $(TEMP_DIR)
|
||||
|
||||
# Compile etcd-version-monitor.
|
||||
docker run -it \
|
||||
-v $(shell pwd)/../../../:/go/src/k8s.io/kubernetes \
|
||||
-v $(TEMP_DIR):/build \
|
||||
-e GOARCH=$(ARCH) \
|
||||
golang:$(GOLANG_VERSION) \
|
||||
/bin/bash -c "CGO_ENABLED=0 go build -o /build/etcd-version-monitor k8s.io/kubernetes/cluster/images/etcd-version-monitor"
|
||||
|
||||
docker build -t $(IMAGE) $(TEMP_DIR)
|
||||
|
||||
push: build
|
||||
docker push $(IMAGE)
|
||||
|
||||
all: build
|
||||
|
||||
.PHONY: build push
|
33
vendor/k8s.io/kubernetes/cluster/images/etcd-version-monitor/README.md
generated
vendored
Normal file
33
vendor/k8s.io/kubernetes/cluster/images/etcd-version-monitor/README.md
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# etcd-version-monitor
|
||||
|
||||
This is a tool for exporting etcd metrics and supplementing them with etcd
|
||||
server binary version and cluster version. These metrics are in
|
||||
prometheus format and can be scraped by a prometheus server.
|
||||
The metrics are exposed at the http://localhost:9101/metrics endpoint.
|
||||
|
||||
For etcd 3.1+, the
|
||||
[go-grpc-prometheus](https://github.com/grpc-ecosystem/go-grpc-prometheus)
|
||||
metrics format, which backward incompatibly replaces the 3.0 legacy grpc metric
|
||||
format, is exposed in both the 3.1 format and in the 3.0. This preserves
|
||||
backward compatibility.
|
||||
|
||||
For etcd 3.1+, the `--metrics=extensive` must be set on etcd for grpc request
|
||||
latency metrics (`etcd_grpc_unary_requests_duration_seconds`) to be exposed.
|
||||
|
||||
**RUNNING THE TOOL**
|
||||
|
||||
To run this tool as a docker container:
|
||||
- make build
|
||||
- docker run --net=host -i -t k8s.gcr.io/etcd-version-monitor:test /etcd-version-monitor --logtostderr
|
||||
|
||||
To run this as a pod on the kubernetes cluster:
|
||||
- Place the 'etcd-version-monitor.yaml' in the manifests directory of
|
||||
kubelet on the master machine.
|
||||
|
||||
*Note*: This tool has to run on the same machine as etcd, as communication
|
||||
with etcd is over localhost.
|
||||
|
||||
**VERIFYING THE TOOL**
|
||||
|
||||
- Goto [http://localhost:9101/metrics](http://localhost:9101/metrics) in order to view the exported metrics.
|
||||
- The metrics prefixed with "etcd_" are the ones of interest to us.
|
405
vendor/k8s.io/kubernetes/cluster/images/etcd-version-monitor/etcd-version-monitor.go
generated
vendored
Normal file
405
vendor/k8s.io/kubernetes/cluster/images/etcd-version-monitor/etcd-version-monitor.go
generated
vendored
Normal file
@@ -0,0 +1,405 @@
|
||||
/*
|
||||
Copyright 2017 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"
|
||||
"encoding/json"
|
||||
goflag "flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/golang/glog"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"github.com/prometheus/common/expfmt"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// Initialize the prometheus instrumentation and client related flags.
|
||||
var (
|
||||
listenAddress string
|
||||
metricsPath string
|
||||
etcdVersionScrapeURI string
|
||||
etcdMetricsScrapeURI string
|
||||
scrapeTimeout time.Duration
|
||||
)
|
||||
|
||||
func registerFlags(fs *pflag.FlagSet) {
|
||||
fs.StringVar(&listenAddress, "listen-address", "localhost:9101", "Address to listen on for serving prometheus metrics")
|
||||
fs.StringVar(&metricsPath, "metrics-path", "/metrics", "Path under which prometheus metrics are to be served")
|
||||
fs.StringVar(&etcdVersionScrapeURI, "etcd-version-scrape-uri", "http://localhost:2379/version", "URI to scrape etcd version info")
|
||||
fs.StringVar(&etcdMetricsScrapeURI, "etcd-metrics-scrape-uri", "http://localhost:2379/metrics", "URI to scrape etcd metrics")
|
||||
fs.DurationVar(&scrapeTimeout, "scrape-timeout", 15*time.Second, "Timeout for trying to get stats from etcd")
|
||||
}
|
||||
|
||||
const (
|
||||
namespace = "etcd" // For prefixing prometheus metrics
|
||||
)
|
||||
|
||||
// Initialize prometheus metrics to be exported.
|
||||
var (
|
||||
// Register all custom metrics with a dedicated registry to keep them separate.
|
||||
customMetricRegistry = prometheus.NewRegistry()
|
||||
|
||||
// Custom etcd version metric since etcd 3.2- does not export one.
|
||||
// This will be replaced by https://github.com/coreos/etcd/pull/8960 in etcd 3.3.
|
||||
etcdVersion = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Name: "version_info",
|
||||
Help: "Etcd server's binary version",
|
||||
},
|
||||
[]string{"binary_version"})
|
||||
|
||||
gatherer = &monitorGatherer{
|
||||
// Rewrite rules for etcd metrics that are exported by default.
|
||||
exported: map[string]*exportedMetric{
|
||||
// etcd 3.0 metric format for total grpc requests with renamed method and service labels.
|
||||
"etcd_grpc_requests_total": {
|
||||
rewriters: []rewriteFunc{
|
||||
func(mf *dto.MetricFamily) (*dto.MetricFamily, error) {
|
||||
mf = deepCopyMetricFamily(mf)
|
||||
renameLabels(mf, map[string]string{
|
||||
"grpc_method": "method",
|
||||
"grpc_service": "service",
|
||||
})
|
||||
return mf, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
// etcd 3.1+ metric format for total grpc requests.
|
||||
"grpc_server_handled_total": {
|
||||
rewriters: []rewriteFunc{
|
||||
// Export the metric exactly as-is. For 3.1+ metrics, we will
|
||||
// pass all metrics directly through.
|
||||
identity,
|
||||
// Write to the etcd 3.0 metric format for backward compatibility.
|
||||
func(mf *dto.MetricFamily) (*dto.MetricFamily, error) {
|
||||
mf = deepCopyMetricFamily(mf)
|
||||
renameMetric(mf, "etcd_grpc_requests_total")
|
||||
renameLabels(mf, map[string]string{
|
||||
"grpc_method": "method",
|
||||
"grpc_service": "service",
|
||||
})
|
||||
filterMetricsByLabels(mf, map[string]string{
|
||||
"grpc_type": "unary",
|
||||
})
|
||||
groupCounterMetricsByLabels(mf, map[string]bool{
|
||||
"grpc_type": true,
|
||||
"grpc_code": true,
|
||||
})
|
||||
return mf, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// etcd 3.0 metric format for grpc request latencies,
|
||||
// rewritten to the etcd 3.1+ format.
|
||||
"etcd_grpc_unary_requests_duration_seconds": {
|
||||
rewriters: []rewriteFunc{
|
||||
func(mf *dto.MetricFamily) (*dto.MetricFamily, error) {
|
||||
mf = deepCopyMetricFamily(mf)
|
||||
renameMetric(mf, "grpc_server_handling_seconds")
|
||||
tpeName := "grpc_type"
|
||||
tpeVal := "unary"
|
||||
for _, m := range mf.Metric {
|
||||
m.Label = append(m.Label, &dto.LabelPair{Name: &tpeName, Value: &tpeVal})
|
||||
}
|
||||
return mf, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
// etcd 3.1+ metric format for total grpc requests.
|
||||
"grpc_server_handling_seconds": {},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// monitorGatherer is a custom metric gatherer for prometheus that exports custom metrics
|
||||
// defined by this monitor as well as rewritten etcd metrics.
|
||||
type monitorGatherer struct {
|
||||
exported map[string]*exportedMetric
|
||||
}
|
||||
|
||||
// exportedMetric identifies a metric that is exported and defines how it is rewritten before
|
||||
// it is exported.
|
||||
type exportedMetric struct {
|
||||
rewriters []rewriteFunc
|
||||
}
|
||||
|
||||
// rewriteFunc rewrites metrics before they are exported.
|
||||
type rewriteFunc func(mf *dto.MetricFamily) (*dto.MetricFamily, error)
|
||||
|
||||
func (m *monitorGatherer) Gather() ([]*dto.MetricFamily, error) {
|
||||
etcdMetrics, err := scrapeMetrics()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exported, err := m.rewriteExportedMetrics(etcdMetrics)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
custom, err := customMetricRegistry.Gather()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]*dto.MetricFamily, 0, len(exported)+len(custom))
|
||||
result = append(result, exported...)
|
||||
result = append(result, custom...)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (m *monitorGatherer) rewriteExportedMetrics(metrics map[string]*dto.MetricFamily) ([]*dto.MetricFamily, error) {
|
||||
results := make([]*dto.MetricFamily, 0, len(metrics))
|
||||
for n, mf := range metrics {
|
||||
if e, ok := m.exported[n]; ok {
|
||||
// Apply rewrite rules for metrics that have them.
|
||||
if e.rewriters == nil {
|
||||
results = append(results, mf)
|
||||
} else {
|
||||
for _, rewriter := range e.rewriters {
|
||||
new, err := rewriter(mf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, new)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Proxy all metrics without any rewrite rules directly.
|
||||
results = append(results, mf)
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// Struct for unmarshalling the json response from etcd's /version endpoint.
|
||||
type EtcdVersion struct {
|
||||
BinaryVersion string `json:"etcdserver"`
|
||||
ClusterVersion string `json:"etcdcluster"`
|
||||
}
|
||||
|
||||
// Function for fetching etcd version info and feeding it to the prometheus metric.
|
||||
func getVersion(lastSeenBinaryVersion *string) error {
|
||||
// Create the get request for the etcd version endpoint.
|
||||
req, err := http.NewRequest("GET", etcdVersionScrapeURI, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create GET request for etcd version: %v", err)
|
||||
}
|
||||
|
||||
// Send the get request and receive a response.
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to receive GET response for etcd version: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Obtain EtcdVersion from the JSON response.
|
||||
var version EtcdVersion
|
||||
if err := json.NewDecoder(resp.Body).Decode(&version); err != nil {
|
||||
return fmt.Errorf("Failed to decode etcd version JSON: %v", err)
|
||||
}
|
||||
|
||||
// Return without updating the version if it stayed the same since last time.
|
||||
if *lastSeenBinaryVersion == version.BinaryVersion {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete the metric for the previous version.
|
||||
if *lastSeenBinaryVersion != "" {
|
||||
deleted := etcdVersion.Delete(prometheus.Labels{"binary_version": *lastSeenBinaryVersion})
|
||||
if !deleted {
|
||||
return fmt.Errorf("Failed to delete previous version's metric")
|
||||
}
|
||||
}
|
||||
|
||||
// Record the new version in a metric.
|
||||
etcdVersion.With(prometheus.Labels{
|
||||
"binary_version": version.BinaryVersion,
|
||||
}).Set(0)
|
||||
*lastSeenBinaryVersion = version.BinaryVersion
|
||||
return nil
|
||||
}
|
||||
|
||||
// Periodically fetches etcd version info.
|
||||
func getVersionPeriodically(stopCh <-chan struct{}) {
|
||||
lastSeenBinaryVersion := ""
|
||||
for {
|
||||
if err := getVersion(&lastSeenBinaryVersion); err != nil {
|
||||
glog.Errorf("Failed to fetch etcd version: %v", err)
|
||||
}
|
||||
select {
|
||||
case <-stopCh:
|
||||
break
|
||||
case <-time.After(scrapeTimeout):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scrapeMetrics scrapes the prometheus metrics from the etcd metrics URI.
|
||||
func scrapeMetrics() (map[string]*dto.MetricFamily, error) {
|
||||
req, err := http.NewRequest("GET", etcdMetricsScrapeURI, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to create GET request for etcd metrics: %v", err)
|
||||
}
|
||||
|
||||
// Send the get request and receive a response.
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to receive GET response for etcd metrics: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Parse the metrics in text format to a MetricFamily struct.
|
||||
var textParser expfmt.TextParser
|
||||
return textParser.TextToMetricFamilies(resp.Body)
|
||||
}
|
||||
|
||||
func renameMetric(mf *dto.MetricFamily, name string) {
|
||||
mf.Name = &name
|
||||
}
|
||||
|
||||
func renameLabels(mf *dto.MetricFamily, nameMapping map[string]string) {
|
||||
for _, m := range mf.Metric {
|
||||
for _, lbl := range m.Label {
|
||||
if alias, ok := nameMapping[*lbl.Name]; ok {
|
||||
lbl.Name = &alias
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func filterMetricsByLabels(mf *dto.MetricFamily, labelValues map[string]string) {
|
||||
buf := mf.Metric[:0]
|
||||
for _, m := range mf.Metric {
|
||||
shouldRemove := false
|
||||
for _, lbl := range m.Label {
|
||||
if val, ok := labelValues[*lbl.Name]; ok && val != *lbl.Value {
|
||||
shouldRemove = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !shouldRemove {
|
||||
buf = append(buf, m)
|
||||
}
|
||||
}
|
||||
mf.Metric = buf
|
||||
}
|
||||
|
||||
func groupCounterMetricsByLabels(mf *dto.MetricFamily, names map[string]bool) {
|
||||
buf := mf.Metric[:0]
|
||||
deleteLabels(mf, names)
|
||||
byLabels := map[string]*dto.Metric{}
|
||||
for _, m := range mf.Metric {
|
||||
if metric, ok := byLabels[labelsKey(m.Label)]; ok {
|
||||
metric.Counter.Value = proto.Float64(*metric.Counter.Value + *m.Counter.Value)
|
||||
} else {
|
||||
byLabels[labelsKey(m.Label)] = m
|
||||
buf = append(buf, m)
|
||||
}
|
||||
}
|
||||
mf.Metric = buf
|
||||
}
|
||||
|
||||
func labelsKey(lbls []*dto.LabelPair) string {
|
||||
var buf bytes.Buffer
|
||||
for i, lbl := range lbls {
|
||||
buf.WriteString(lbl.String())
|
||||
if i < len(lbls)-1 {
|
||||
buf.WriteString(",")
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func deleteLabels(mf *dto.MetricFamily, names map[string]bool) {
|
||||
for _, m := range mf.Metric {
|
||||
buf := m.Label[:0]
|
||||
for _, lbl := range m.Label {
|
||||
shouldRemove := names[*lbl.Name]
|
||||
if !shouldRemove {
|
||||
buf = append(buf, lbl)
|
||||
}
|
||||
}
|
||||
m.Label = buf
|
||||
}
|
||||
}
|
||||
|
||||
func identity(mf *dto.MetricFamily) (*dto.MetricFamily, error) {
|
||||
return mf, nil
|
||||
}
|
||||
|
||||
func deepCopyMetricFamily(mf *dto.MetricFamily) *dto.MetricFamily {
|
||||
r := &dto.MetricFamily{}
|
||||
r.Name = mf.Name
|
||||
r.Help = mf.Help
|
||||
r.Type = mf.Type
|
||||
r.Metric = make([]*dto.Metric, len(mf.Metric))
|
||||
for i, m := range mf.Metric {
|
||||
r.Metric[i] = deepCopyMetric(m)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func deepCopyMetric(m *dto.Metric) *dto.Metric {
|
||||
r := &dto.Metric{}
|
||||
r.Label = make([]*dto.LabelPair, len(m.Label))
|
||||
for i, lp := range m.Label {
|
||||
r.Label[i] = deepCopyLabelPair(lp)
|
||||
}
|
||||
r.Gauge = m.Gauge
|
||||
r.Counter = m.Counter
|
||||
r.Summary = m.Summary
|
||||
r.Untyped = m.Untyped
|
||||
r.Histogram = m.Histogram
|
||||
r.TimestampMs = m.TimestampMs
|
||||
return r
|
||||
}
|
||||
|
||||
func deepCopyLabelPair(lp *dto.LabelPair) *dto.LabelPair {
|
||||
r := &dto.LabelPair{}
|
||||
r.Name = lp.Name
|
||||
r.Value = lp.Value
|
||||
return r
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Register the commandline flags passed to the tool.
|
||||
registerFlags(pflag.CommandLine)
|
||||
pflag.CommandLine.AddGoFlagSet(goflag.CommandLine)
|
||||
pflag.Parse()
|
||||
|
||||
// Register the metrics we defined above with prometheus.
|
||||
customMetricRegistry.MustRegister(etcdVersion)
|
||||
customMetricRegistry.Unregister(prometheus.NewGoCollector())
|
||||
|
||||
// Spawn threads for periodically scraping etcd version metrics.
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
go getVersionPeriodically(stopCh)
|
||||
|
||||
// Serve our metrics on listenAddress/metricsPath.
|
||||
glog.Infof("Listening on: %v", listenAddress)
|
||||
http.Handle(metricsPath, promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{}))
|
||||
glog.Errorf("Stopped listening/serving metrics: %v", http.ListenAndServe(listenAddress, nil))
|
||||
}
|
13
vendor/k8s.io/kubernetes/cluster/images/etcd-version-monitor/etcd-version-monitor.yaml
generated
vendored
Normal file
13
vendor/k8s.io/kubernetes/cluster/images/etcd-version-monitor/etcd-version-monitor.yaml
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: etcd-version-monitor
|
||||
namespace: kube-system
|
||||
spec:
|
||||
hostNetwork: true
|
||||
containers:
|
||||
- name: etcd-version-monitor
|
||||
image: k8s.gcr.io/etcd-version-monitor:0.1.2
|
||||
command:
|
||||
- /etcd-version-monitor
|
||||
- --logtostderr
|
19
vendor/k8s.io/kubernetes/cluster/images/etcd/Dockerfile
generated
vendored
Normal file
19
vendor/k8s.io/kubernetes/cluster/images/etcd/Dockerfile
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# 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.
|
||||
|
||||
FROM BASEIMAGE
|
||||
|
||||
EXPOSE 2379 2380 4001 7001
|
||||
COPY etcd* etcdctl* /usr/local/bin/
|
||||
COPY migrate-if-needed.sh migrate /usr/local/bin/
|
150
vendor/k8s.io/kubernetes/cluster/images/etcd/Makefile
generated
vendored
Normal file
150
vendor/k8s.io/kubernetes/cluster/images/etcd/Makefile
generated
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
# 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=2.2.1 2.3.7 3.0.17 3.1.12 3.2.18] [REGISTRY=k8s.gcr.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?=2.2.1 2.3.7 3.0.17 3.1.12 3.2.18
|
||||
|
||||
# LATEST_ETCD_VERSION identifies the most recent etcd version available.
|
||||
LATEST_ETCD_VERSION?=3.2.18
|
||||
|
||||
# REVISION provides a version number fo 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 k8s.gcr.io/etcd docker image with a tag of the form "<etcd-version>-<revision>".
|
||||
IMAGE_TAG=$(LATEST_ETCD_VERSION)-$(REVISION)
|
||||
|
||||
ARCH?=amd64
|
||||
# Image should be pulled from k8s.gcr.io, which will auto-detect
|
||||
# region (us, eu, asia, ...) and pull from the closest.
|
||||
REGISTRY?=k8s.gcr.io
|
||||
# Images should be pushed to staging-k8s.gcr.io.
|
||||
PUSH_REGISTRY?=staging-k8s.gcr.io
|
||||
# golang version should match the golang version from https://github.com/coreos/etcd/releases for the current ETCD_VERSION.
|
||||
GOLANG_VERSION?=1.8.7
|
||||
GOARM=7
|
||||
TEMP_DIR:=$(shell mktemp -d)
|
||||
|
||||
ifeq ($(ARCH),amd64)
|
||||
BASEIMAGE?=busybox
|
||||
endif
|
||||
ifeq ($(ARCH),arm)
|
||||
BASEIMAGE?=arm32v7/busybox
|
||||
endif
|
||||
ifeq ($(ARCH),arm64)
|
||||
BASEIMAGE?=arm64v8/busybox
|
||||
endif
|
||||
ifeq ($(ARCH),ppc64le)
|
||||
BASEIMAGE?=ppc64le/busybox
|
||||
endif
|
||||
ifeq ($(ARCH),s390x)
|
||||
BASEIMAGE?=s390x/busybox
|
||||
endif
|
||||
|
||||
build:
|
||||
# Copy the content in this dir to the temp dir,
|
||||
# without copying the subdirectories.
|
||||
find ./ -maxdepth 1 -type f | xargs -I {} cp {} $(TEMP_DIR)
|
||||
|
||||
# Compile migrate
|
||||
docker run --interactive -v $(shell pwd)/../../../:/go/src/k8s.io/kubernetes -v $(TEMP_DIR):/build -e GOARCH=$(ARCH) golang:$(GOLANG_VERSION) \
|
||||
/bin/bash -c "CGO_ENABLED=0 go build -o /build/migrate k8s.io/kubernetes/cluster/images/etcd/migrate"
|
||||
|
||||
|
||||
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.
|
||||
for version in $(BUNDLED_ETCD_VERSIONS); do \
|
||||
etcd_release_tmp_dir=$(shell mktemp -d); \
|
||||
curl -sSL --retry 5 https://github.com/coreos/etcd/releases/download/v$$version/etcd-v$$version-linux-amd64.tar.gz | tar -xz -C $$etcd_release_tmp_dir --strip-components=1; \
|
||||
cp $$etcd_release_tmp_dir/etcd $$etcd_release_tmp_dir/etcdctl $(TEMP_DIR)/; \
|
||||
cp $(TEMP_DIR)/etcd $(TEMP_DIR)/etcd-$$version; \
|
||||
cp $(TEMP_DIR)/etcdctl $(TEMP_DIR)/etcdctl-$$version; \
|
||||
done
|
||||
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.
|
||||
for version in $(BUNDLED_ETCD_VERSIONS); do \
|
||||
etcd_release_tmp_dir=$(shell mktemp -d); \
|
||||
docker run --interactive -v $${etcd_release_tmp_dir}:/etcdbin golang:$(GOLANG_VERSION) /bin/bash -c \
|
||||
"git clone https://github.com/coreos/etcd /go/src/github.com/coreos/etcd \
|
||||
&& cd /go/src/github.com/coreos/etcd \
|
||||
&& git checkout v$${version} \
|
||||
&& GOARM=$(GOARM) GOARCH=$(ARCH) ./build \
|
||||
&& cp -f bin/$(ARCH)/etcd* bin/etcd* /etcdbin; echo 'done'"; \
|
||||
cp $$etcd_release_tmp_dir/etcd $$etcd_release_tmp_dir/etcdctl $(TEMP_DIR)/; \
|
||||
cp $(TEMP_DIR)/etcd $(TEMP_DIR)/etcd-$$version; \
|
||||
cp $(TEMP_DIR)/etcdctl $(TEMP_DIR)/etcdctl-$$version; \
|
||||
done
|
||||
|
||||
# Add this ENV variable in order to workaround an unsupported arch blocker
|
||||
# The multiarch feature is in an limited and experimental state right now, and etcd should work fine on arm64
|
||||
# On arm (which is 32-bit), it can't handle >1GB data in-memory, but it is very unlikely someone tinkering with their limited arm devices would reach such a high usage
|
||||
# ppc64le is still quite untested, but compiles and is probably in the process of being validated by IBM.
|
||||
cd $(TEMP_DIR) && echo "ENV ETCD_UNSUPPORTED_ARCH=$(ARCH)" >> Dockerfile
|
||||
endif
|
||||
|
||||
# Replace BASEIMAGE with the real base image
|
||||
cd $(TEMP_DIR) && sed -i.bak 's|BASEIMAGE|$(BASEIMAGE)|g' Dockerfile
|
||||
|
||||
# And build the image
|
||||
docker build --pull -t $(REGISTRY)/etcd-$(ARCH):$(IMAGE_TAG) $(TEMP_DIR)
|
||||
|
||||
push: build
|
||||
docker tag $(REGISTRY)/etcd-$(ARCH):$(IMAGE_TAG) $(PUSH_REGISTRY)/etcd-$(ARCH):$(IMAGE_TAG)
|
||||
docker push $(PUSH_REGISTRY)/etcd-$(ARCH):$(IMAGE_TAG)
|
||||
|
||||
ifeq ($(ARCH),amd64)
|
||||
# Backward compatibility. TODO: deprecate this image tag
|
||||
docker tag $(REGISTRY)/etcd-$(ARCH):$(IMAGE_TAG) $(PUSH_REGISTRY)/etcd:$(IMAGE_TAG)
|
||||
docker push $(PUSH_REGISTRY)/etcd:$(IMAGE_TAG)
|
||||
endif
|
||||
|
||||
unit-test:
|
||||
docker run --interactive -v $(shell pwd)/../../../:/go/src/k8s.io/kubernetes -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 `k8s.gcr.io/etcd` image (`/usr/local/bin/etcd-<version>`, ...).
|
||||
# Since the `k8s.gcr.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
|
||||
cd $(TEMP_DIR)_integration_test && sed -i.bak 's|BASEIMAGE|golang:$(GOLANG_VERSION)|g' Dockerfile
|
||||
docker build --pull -t etcd-integration-test $(TEMP_DIR)_integration_test
|
||||
|
||||
integration-test:
|
||||
docker run --interactive -v $(shell pwd)/../../../:/go/src/k8s.io/kubernetes -e GOARCH=$(ARCH) etcd-integration-test \
|
||||
/bin/bash -c "CGO_ENABLED=0 go test -tags=integration -v 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: build test
|
||||
.PHONY: build push unit-test build-integration-test-image integration-test integration-build-test test
|
86
vendor/k8s.io/kubernetes/cluster/images/etcd/README.md
generated
vendored
Normal file
86
vendor/k8s.io/kubernetes/cluster/images/etcd/README.md
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
### k8s.gcr.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 `k8s.gcr.io/etcd` docker image is tagged with an version string of the form
|
||||
`<etcd-version>-<image-revision>`, 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 `k8s.gcr.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.
|
||||
|
||||
`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.2.11" )
|
||||
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 and from 3.0 to 2.3.7 are 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
|
||||
`k8s.gcr.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 `busybox` images serve as base images.
|
||||
|
||||
#### 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 for linux/amd64 (default)
|
||||
$ make push ARCH=amd64
|
||||
# ---> staging-k8s.gcr.io/etcd-amd64:TAG
|
||||
# ---> staging-k8s.gcr.io/etcd:TAG
|
||||
|
||||
$ make push ARCH=arm
|
||||
# ---> staging-k8s.gcr.io/etcd-arm:TAG
|
||||
|
||||
$ make push ARCH=arm64
|
||||
# ---> staging-k8s.gcr.io/etcd-arm64:TAG
|
||||
|
||||
$ make push ARCH=ppc64le
|
||||
# ---> staging-k8s.gcr.io/etcd-ppc64le:TAG
|
||||
|
||||
$ make push ARCH=s390x
|
||||
# ---> staging-k8s.gcr.io/etcd-s390x:TAG
|
||||
```
|
||||
|
||||
If you don't want to push the images, run `make` or `make build` instead
|
||||
|
||||
|
||||
[]()
|
105
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate-if-needed.sh
generated
vendored
Executable file
105
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate-if-needed.sh
generated
vendored
Executable file
@@ -0,0 +1,105 @@
|
||||
#!/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.
|
||||
|
||||
# NOTES
|
||||
# This script performs etcd upgrade based on the following environmental
|
||||
# variables:
|
||||
# TARGET_STORAGE - API of etcd to be used (supported: 'etcd2', 'etcd3')
|
||||
# TARGET_VERSION - etcd release to be used (supported: '2.2.1', '2.3.7', '3.0.17', '3.1.12', '3.2.18')
|
||||
# DATA_DIRECTORY - directory with etcd data
|
||||
#
|
||||
# The current etcd version and storage format is detected based on the
|
||||
# contents of "${DATA_DIRECTORY}/version.txt" file (if the file doesn't
|
||||
# exist, we default it to "2.2.1/etcd2".
|
||||
#
|
||||
# The update workflow support the following upgrade steps:
|
||||
# - 2.2.1/etcd2 -> 2.3.7/etcd2
|
||||
# - 2.3.7/etcd2 -> 3.0.17/etcd2
|
||||
# - 3.0.17/etcd3 -> 3.1.12/etcd3
|
||||
# - 3.1.12/etcd3 -> 3.2.18/etcd3
|
||||
#
|
||||
# NOTE: The releases supported in this script has to match release binaries
|
||||
# present in the etcd image (to make this script work correctly).
|
||||
#
|
||||
# Based on the current etcd version and storage format we detect what
|
||||
# upgrade step from this should be done to get reach target configuration
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
|
||||
# NOTE: BUNDLED_VERSION has to match release binaries present in the
|
||||
# etcd image (to make this script work correctly).
|
||||
BUNDLED_VERSIONS="2.2.1, 2.3.7, 3.0.17, 3.1.12, 3.2.18"
|
||||
|
||||
ETCD_NAME="${ETCD_NAME:-etcd-$(hostname)}"
|
||||
if [ -z "${DATA_DIRECTORY:-}" ]; then
|
||||
echo "DATA_DIRECTORY variable unset - unexpected failure"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "${DATA_DIRECTORY}" in
|
||||
*event*)
|
||||
ETCD_PEER_PORT=2381
|
||||
ETCD_CLIENT_PORT=18631
|
||||
;;
|
||||
*)
|
||||
ETCD_PEER_PORT=2380
|
||||
ETCD_CLIENT_PORT=18629
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "${INITIAL_CLUSTER:-}" ]; then
|
||||
echo "Warn: INITIAL_CLUSTER variable unset - defaulting to ${ETCD_NAME}=http://localhost:${ETCD_PEER_PORT}"
|
||||
INITIAL_CLUSTER="${ETCD_NAME}=http://localhost:${ETCD_PEER_PORT}"
|
||||
fi
|
||||
if [ -z "${LISTEN_PEER_URLS:-}" ]; then
|
||||
echo "Warn: LISTEN_PEER_URLS variable unset - defaulting to http://localhost:${ETCD_PEER_PORT}"
|
||||
LISTEN_PEER_URLS="http://localhost:${ETCD_PEER_PORT}"
|
||||
fi
|
||||
if [ -z "${INITIAL_ADVERTISE_PEER_URLS:-}" ]; then
|
||||
echo "Warn: INITIAL_ADVERTISE_PEER_URLS variable unset - defaulting to http://localhost:${ETCD_PEER_PORT}"
|
||||
INITIAL_ADVERTISE_PEER_URLS="http://localhost:${ETCD_PEER_PORT}"
|
||||
fi
|
||||
if [ -z "${TARGET_VERSION:-}" ]; then
|
||||
echo "TARGET_VERSION variable unset - unexpected failure"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "${TARGET_STORAGE:-}" ]; then
|
||||
echo "TARGET_STORAGE variable unset - unexpected failure"
|
||||
exit 1
|
||||
fi
|
||||
ETCD_DATA_PREFIX="${ETCD_DATA_PREFIX:-/registry}"
|
||||
ETCD_CREDS="${ETCD_CREDS:-}"
|
||||
|
||||
# Correctly support upgrade and rollback to non-default version.
|
||||
if [ "${DO_NOT_MOVE_BINARIES:-}" != "true" ]; then
|
||||
cp "/usr/local/bin/etcd-${TARGET_VERSION}" "/usr/local/bin/etcd"
|
||||
cp "/usr/local/bin/etcdctl-${TARGET_VERSION}" "/usr/local/bin/etcdctl"
|
||||
fi
|
||||
|
||||
/usr/local/bin/migrate \
|
||||
--name "${ETCD_NAME}" \
|
||||
--port "${ETCD_CLIENT_PORT}" \
|
||||
--listen-peer-urls "${LISTEN_PEER_URLS}" \
|
||||
--initial-advertise-peer-urls "${INITIAL_ADVERTISE_PEER_URLS}" \
|
||||
--data-dir "${DATA_DIRECTORY}" \
|
||||
--bundled-versions "${BUNDLED_VERSIONS}" \
|
||||
--initial-cluster "${INITIAL_CLUSTER}" \
|
||||
--target-version "${TARGET_VERSION}" \
|
||||
--target-storage "${TARGET_STORAGE}" \
|
||||
--etcd-data-prefix "${ETCD_DATA_PREFIX}" \
|
||||
--ttl-keys-directory "${TTL_KEYS_DIRECTORY:-${ETCD_DATA_PREFIX}/events}" \
|
||||
--etcd-server-extra-args "${ETCD_CREDS}"
|
72
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/BUILD
generated
vendored
Normal file
72
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/BUILD
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_binary(
|
||||
name = "migrate",
|
||||
embed = [":go_default_library"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"data_dir.go",
|
||||
"migrate.go",
|
||||
"migrate_client.go",
|
||||
"migrate_server.go",
|
||||
"migrator.go",
|
||||
"rollback_v2.go",
|
||||
"versions.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/cluster/images/etcd/migrate",
|
||||
deps = [
|
||||
"//third_party/forked/etcd221/wal:go_default_library",
|
||||
"//vendor/github.com/blang/semver:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/client:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/clientv3:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/etcdserver:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/etcdserver/etcdserverpb:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/etcdserver/membership:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/mvcc/backend:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/mvcc/mvccpb:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/pkg/pbutil:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/pkg/types:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/raft/raftpb:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/snap:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/store:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/wal:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/wal/walpb:go_default_library",
|
||||
"//vendor/github.com/coreos/go-semver/semver:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/github.com/spf13/cobra:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"data_dir_test.go",
|
||||
"versions_test.go",
|
||||
],
|
||||
data = glob(["testdata/**"]),
|
||||
embed = [":go_default_library"],
|
||||
deps = ["//vendor/github.com/blang/semver:go_default_library"],
|
||||
)
|
157
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/data_dir.go
generated
vendored
Normal file
157
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/data_dir.go
generated
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
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"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// 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 verion 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 {
|
||||
glog.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 {
|
||||
glog.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 = exec.Command("cp", "-r", d.path, backupDir).Run()
|
||||
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 verions
|
||||
// of the data in the directory.
|
||||
type VersionFile struct {
|
||||
path string
|
||||
}
|
||||
|
||||
// Exists returns true if a version.txt file exists on the filesystem.
|
||||
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 := ioutil.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 '<version>/<storage-version>' string from version.txt file contents '%s': %v", txt, err)
|
||||
}
|
||||
return vp, nil
|
||||
}
|
||||
|
||||
// Write creates or overwrites the contents of the version.txt file with the given EtcdVersionPair.
|
||||
func (v *VersionFile) Write(vp *EtcdVersionPair) error {
|
||||
data := []byte(fmt.Sprintf("%s/%s", vp.version, vp.storageVersion))
|
||||
return ioutil.WriteFile(v.path, data, 0666)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
159
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/data_dir_test.go
generated
vendored
Normal file
159
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/data_dir_test.go
generated
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
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 (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/blang/semver"
|
||||
)
|
||||
|
||||
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("Data directory is non-empty")
|
||||
}
|
||||
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)
|
||||
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("Data directory is empty")
|
||||
}
|
||||
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("Data directory is non-empty")
|
||||
}
|
||||
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)
|
||||
d, err := OpenOrCreateDataDirectory(filepath.Join(path, "data-dir"))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open data dir: %v", 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 afer Backup()")
|
||||
}
|
||||
}
|
||||
|
||||
func newTestPath(t *testing.T) string {
|
||||
path, err := ioutil.TempDir("", "etcd-migrate-test-")
|
||||
os.Chmod(path, 0777)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create tmp dir for test: %v", err)
|
||||
}
|
||||
return path
|
||||
}
|
356
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/integration_test.go
generated
vendored
Normal file
356
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/integration_test.go
generated
vendored
Normal file
@@ -0,0 +1,356 @@
|
||||
// +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"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/blang/semver"
|
||||
)
|
||||
|
||||
var (
|
||||
testSupportedVersions = MustParseSupportedVersions("2.2.1, 2.3.7, 3.0.17, 3.1.12")
|
||||
testVersionOldest = &EtcdVersion{semver.MustParse("2.2.1")}
|
||||
testVersionPrevious = &EtcdVersion{semver.MustParse("3.0.17")}
|
||||
testVersionLatest = &EtcdVersion{semver.MustParse("3.1.12")}
|
||||
)
|
||||
|
||||
func TestMigrate(t *testing.T) {
|
||||
migrations := []struct {
|
||||
title string
|
||||
memberCount int
|
||||
startVersion string
|
||||
endVersion string
|
||||
protocol string
|
||||
}{
|
||||
// upgrades
|
||||
{"v2-v3-up", 1, "2.2.1/etcd2", "3.0.17/etcd3", "https"},
|
||||
{"v3-v3-up", 1, "3.0.17/etcd3", "3.1.12/etcd3", "https"},
|
||||
{"oldest-newest-up", 1, "2.2.1/etcd2", "3.1.12/etcd3", "https"},
|
||||
|
||||
// warning: v2->v3 ha upgrades not currently supported.
|
||||
{"ha-v3-v3-up", 3, "3.0.17/etcd3", "3.1.12/etcd3", "https"},
|
||||
|
||||
// downgrades
|
||||
{"v3-v2-down", 1, "3.0.17/etcd3", "2.2.1/etcd2", "https"},
|
||||
{"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)
|
||||
|
||||
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) []*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,
|
||||
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 = ioutil.WriteFile(certFileName, certPem, os.FileMode(0777))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
os.MkdirAll(filepath.Dir(keyFileName), os.FileMode(0777))
|
||||
err = ioutil.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, net.ParseIP(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 := net.ParseIP(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
|
||||
}
|
188
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/migrate.go
generated
vendored
Normal file
188
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/migrate.go
generated
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
versionFilename = "version.txt"
|
||||
defaultPort uint64 = 18629
|
||||
)
|
||||
|
||||
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{}
|
||||
)
|
||||
|
||||
type migrateOpts struct {
|
||||
name string
|
||||
port uint64
|
||||
peerListenUrls string
|
||||
peerAdvertiseUrls string
|
||||
binDir string
|
||||
dataDir string
|
||||
bundledVersionString string
|
||||
etcdDataPrefix string
|
||||
ttlKeysDirectory string
|
||||
initialCluster string
|
||||
targetVersion string
|
||||
targetStorage string
|
||||
etcdServerArgs string
|
||||
}
|
||||
|
||||
func main() {
|
||||
flags := migrateCmd.Flags()
|
||||
flags.StringVar(&opts.name, "name", "", "etcd cluster member name. Defaults to etcd-{hostname}")
|
||||
flags.Uint64Var(&opts.port, "port", defaultPort, "etcd client port to use during migration operations. This should be a different port than typically used by etcd to avoid clients accidentally connecting during upgrade/downgrade operations.")
|
||||
flags.StringVar(&opts.peerListenUrls, "listen-peer-urls", "", "etcd --listen-peer-urls flag, required for HA clusters")
|
||||
flags.StringVar(&opts.peerAdvertiseUrls, "initial-advertise-peer-urls", "", "etcd --initial-advertise-peer-urls flag, required for HA clusters")
|
||||
flags.StringVar(&opts.binDir, "bin-dir", "/usr/local/bin", "directory of etcd and etcdctl binaries, must contain etcd-<version> and etcdctl-<version> for each version listed in bindled-versions")
|
||||
flags.StringVar(&opts.dataDir, "data-dir", "", "etcd data directory of etcd server to migrate")
|
||||
flags.StringVar(&opts.bundledVersionString, "bundled-versions", "", "comma separated list of etcd binary versions present under the bin-dir")
|
||||
flags.StringVar(&opts.etcdDataPrefix, "etcd-data-prefix", "/registry", "etcd key prefix under which all objects are kept")
|
||||
flags.StringVar(&opts.ttlKeysDirectory, "ttl-keys-directory", "", "etcd key prefix under which all keys with TTLs are kept. Defaults to {etcd-data-prefix}/events")
|
||||
flags.StringVar(&opts.initialCluster, "initial-cluster", "", "comma separated list of name=endpoint pairs. Defaults to etcd-{hostname}=http://localhost:2380")
|
||||
flags.StringVar(&opts.targetVersion, "target-version", "", "version of etcd to migrate to. Format must be '<major>.<minor>.<patch>'")
|
||||
flags.StringVar(&opts.targetStorage, "target-storage", "", "storage version of etcd to migrate to, one of: etcd2, etcd3")
|
||||
flags.StringVar(&opts.etcdServerArgs, "etcd-server-extra-args", "", "additional etcd server args for starting etcd servers during migration steps, --peer-* TLS cert flags should be added for etcd clusters with more than 1 member that use mutual TLS for peer communication.")
|
||||
migrateCmd.Execute()
|
||||
}
|
||||
|
||||
// runMigrate validates the command line flags and starts the migration.
|
||||
func runMigrate() {
|
||||
if opts.name == "" {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
glog.Errorf("Error while getting hostname to supply default --name: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
opts.name = fmt.Sprintf("etcd-%s", hostname)
|
||||
}
|
||||
|
||||
if opts.ttlKeysDirectory == "" {
|
||||
opts.ttlKeysDirectory = fmt.Sprintf("%s/events", opts.etcdDataPrefix)
|
||||
}
|
||||
if opts.initialCluster == "" {
|
||||
opts.initialCluster = fmt.Sprintf("%s=http://localhost:2380", opts.name)
|
||||
}
|
||||
if opts.targetStorage == "" {
|
||||
glog.Errorf("--target-storage is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
if opts.targetVersion == "" {
|
||||
glog.Errorf("--target-version is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
if opts.dataDir == "" {
|
||||
glog.Errorf("--data-dir is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
if opts.bundledVersionString == "" {
|
||||
glog.Errorf("--bundled-versions is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
bundledVersions, err := ParseSupportedVersions(opts.bundledVersionString)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to parse --supported-versions: %v", err)
|
||||
}
|
||||
err = validateBundledVersions(bundledVersions, opts.binDir)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to validate that 'etcd-<version>' and 'etcdctl-<version>' binaries exist in --bin-dir '%s' for all --bundled-verions '%s': %v",
|
||||
opts.binDir, opts.bundledVersionString, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
target := &EtcdVersionPair{
|
||||
version: MustParseEtcdVersion(opts.targetVersion),
|
||||
storageVersion: MustParseEtcdStorageVersion(opts.targetStorage),
|
||||
}
|
||||
|
||||
migrate(opts.name, opts.port, opts.peerListenUrls, opts.peerAdvertiseUrls, opts.binDir, opts.dataDir, opts.etcdDataPrefix, opts.ttlKeysDirectory, opts.initialCluster, target, bundledVersions, opts.etcdServerArgs)
|
||||
}
|
||||
|
||||
// migrate opens or initializes the etcd data directory, configures the migrator, and starts the migration.
|
||||
func migrate(name string, port uint64, peerListenUrls string, peerAdvertiseUrls string, binPath string, dataDirPath string, etcdDataPrefix string, ttlKeysDirectory string,
|
||||
initialCluster string, target *EtcdVersionPair, bundledVersions SupportedVersions, etcdServerArgs string) {
|
||||
|
||||
dataDir, err := OpenOrCreateDataDirectory(dataDirPath)
|
||||
if err != nil {
|
||||
glog.Errorf("Error opening or creating data directory %s: %v", dataDirPath, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg := &EtcdMigrateCfg{
|
||||
binPath: binPath,
|
||||
name: name,
|
||||
port: port,
|
||||
peerListenUrls: peerListenUrls,
|
||||
peerAdvertiseUrls: peerAdvertiseUrls,
|
||||
etcdDataPrefix: etcdDataPrefix,
|
||||
ttlKeysDirectory: ttlKeysDirectory,
|
||||
initialCluster: initialCluster,
|
||||
supportedVersions: bundledVersions,
|
||||
dataDirectory: dataDirPath,
|
||||
etcdServerArgs: etcdServerArgs,
|
||||
}
|
||||
client, err := NewEtcdMigrateClient(cfg)
|
||||
if err != nil {
|
||||
glog.Errorf("Migration failed: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
migrator := &Migrator{cfg, dataDir, client}
|
||||
|
||||
err = migrator.MigrateIfNeeded(target)
|
||||
if err != nil {
|
||||
glog.Errorf("Migration failed: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// validateBundledVersions checks that 'etcd-<version>' and 'etcdctl-<version>' binaries exist in the binDir
|
||||
// for each version in the bundledVersions list.
|
||||
func validateBundledVersions(bundledVersions SupportedVersions, binDir string) error {
|
||||
for _, v := range bundledVersions {
|
||||
for _, binaryName := range []string{"etcd", "etcdctl"} {
|
||||
fn := filepath.Join(binDir, fmt.Sprintf("%s-%s", binaryName, v))
|
||||
if _, err := os.Stat(fn); err != nil {
|
||||
return fmt.Errorf("failed to validate '%s' binary exists for bundled-version '%s': %v", fn, v, err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
223
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/migrate_client.go
generated
vendored
Normal file
223
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/migrate_client.go
generated
vendored
Normal file
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
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"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"context"
|
||||
|
||||
clientv2 "github.com/coreos/etcd/client"
|
||||
"github.com/coreos/etcd/clientv3"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
if version.Major == 2 {
|
||||
v2client, err := e.clientV2()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = v2client.Set(context.Background(), key, value, nil)
|
||||
return err
|
||||
}
|
||||
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) {
|
||||
if version.Major == 2 {
|
||||
v2client, err := e.clientV2()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp, err := v2client.Get(context.Background(), key, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.Node.Value, nil
|
||||
}
|
||||
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) clientV2() (clientv2.KeysAPI, error) {
|
||||
v2client, err := clientv2.New(clientv2.Config{Endpoints: []string{e.endpoint()}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return clientv2.NewKeysAPI(v2client), nil
|
||||
}
|
||||
|
||||
func (e *CombinedEtcdClient) clientV3() (*clientv3.Client, error) {
|
||||
return clientv3.New(clientv3.Config{Endpoints: []string{e.endpoint()}})
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
glog.Infof("Lease with TTL: %v created", lease.TTL)
|
||||
|
||||
glog.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 {
|
||||
glog.Errorf("Error while attaching lease to: %s", string(kv.Key))
|
||||
}
|
||||
if bytes.Compare(putResp.PrevKv.Value, kv.Value) != 0 {
|
||||
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)
|
||||
}
|
132
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/migrate_server.go
generated
vendored
Normal file
132
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/migrate_server.go
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// 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.
|
||||
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", fmt.Sprintf("http://127.0.0.1:%d", r.cfg.port),
|
||||
"--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)
|
||||
done <- true
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-interval.C:
|
||||
err := r.client.SetEtcdVersionKeyValue(version)
|
||||
if err != nil {
|
||||
glog.Infof("Still waiting for etcd to start, current error: %v", err)
|
||||
// keep waiting
|
||||
} else {
|
||||
glog.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)
|
||||
timedout <- true
|
||||
}()
|
||||
go func() {
|
||||
select {
|
||||
case <-stopped:
|
||||
return
|
||||
case <-timedout:
|
||||
glog.Infof("etcd server has not terminated gracefully after %s, killing it.", gracefulWait)
|
||||
r.cmd.Process.Kill()
|
||||
return
|
||||
}
|
||||
}()
|
||||
err = r.cmd.Wait()
|
||||
stopped <- true
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
glog.Infof("etcd server stopped (signal: %s)", exiterr.Error())
|
||||
// stopped
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("error waiting for etcd to stop: %v", err)
|
||||
}
|
||||
glog.Infof("Stopped etcd server %s", r.cfg.name)
|
||||
return nil
|
||||
}
|
258
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/migrator.go
generated
vendored
Normal file
258
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/migrator.go
generated
vendored
Normal file
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
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"
|
||||
"time"
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// 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
|
||||
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 {
|
||||
glog.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 {
|
||||
glog.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):
|
||||
glog.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:
|
||||
glog.Infof("upgrading from etcd2 storage to etcd3 storage")
|
||||
current, err = m.etcd2ToEtcd3Upgrade(current, target)
|
||||
case current.version.Major == 3 && target.version.Major == 2:
|
||||
glog.Infof("downgrading from etcd 3.x to 2.x")
|
||||
current, err = m.rollbackToEtcd2(current, target)
|
||||
case current.version.Major == target.version.Major && current.version.Minor < target.version.Minor:
|
||||
stepVersion := m.cfg.supportedVersions.NextVersionPair(current)
|
||||
glog.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:
|
||||
glog.Infof("rolling etcd back from %s to %s", current, target)
|
||||
current, err = m.rollbackEtcd3MinorVersion(current, target)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Migrator) backupEtcd2(current *EtcdVersion) error {
|
||||
backupDir := fmt.Sprintf("%s/%s", m.dataDirectory, "migration-backup")
|
||||
glog.Infof("Backup etcd before starting migration")
|
||||
err := os.Mkdir(backupDir, 0666)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create backup directory before starting migration: %v", err)
|
||||
}
|
||||
m.client.Backup(current, backupDir)
|
||||
glog.Infof("Backup done in %s", backupDir)
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
glog.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()
|
||||
glog.Infof("Starting etcd version %s to capture rollback snapshot.", current.version)
|
||||
err = runner.Start(current.version)
|
||||
if err != nil {
|
||||
glog.Fatalf("Unable to automatically downgrade etcd: starting etcd version %s to capture rollback snapshot failed: %v", current.version, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
glog.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
|
||||
}
|
||||
|
||||
glog.Infof("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 = exec.Command("mv", m.dataDirectory.path, backupDir).Run()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
glog.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) rollbackToEtcd2(current *EtcdVersionPair, target *EtcdVersionPair) (*EtcdVersionPair, error) {
|
||||
if !(current.version.Major == 3 && current.version.Minor == 0 && target.version.Major == 2 && target.version.Minor == 2) {
|
||||
return nil, fmt.Errorf("etcd3 -> etcd2 downgrade is supported only between 3.0.x and 2.2.x, got current %s target %s", current, target)
|
||||
}
|
||||
glog.Infof("Backup and remove all existing v2 data")
|
||||
err := m.dataDirectory.Backup()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = RollbackV3ToV2(m.dataDirectory.path, time.Hour)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rollback to etcd 2.x failed: %v", err)
|
||||
}
|
||||
return target, nil
|
||||
|
||||
}
|
||||
|
||||
func (m *Migrator) etcd2ToEtcd3Upgrade(current *EtcdVersionPair, target *EtcdVersionPair) (*EtcdVersionPair, error) {
|
||||
if current.storageVersion != storageEtcd2 || target.version.Major != 3 || target.storageVersion != storageEtcd3 {
|
||||
return nil, fmt.Errorf("etcd2 to etcd3 upgrade is supported only for x.x.x/etcd2 to 3.0.x/etcd3, got current %s target %s", current, target)
|
||||
}
|
||||
runner := m.newServer()
|
||||
|
||||
glog.Infof("Performing etcd2 -> etcd3 migration")
|
||||
err := m.client.Migrate(target.version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
glog.Infof("Attaching leases to TTL entries")
|
||||
|
||||
// Now attach lease to all keys.
|
||||
// To do it, we temporarily start etcd on a random port (so that
|
||||
// apiserver actually cannot access it).
|
||||
err = runner.Start(target.version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
err = runner.Stop()
|
||||
}()
|
||||
|
||||
// Create a lease and attach all keys to it.
|
||||
err = m.client.AttachLease(1 * time.Hour)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return target, err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
328
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/rollback_v2.go
generated
vendored
Normal file
328
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/rollback_v2.go
generated
vendored
Normal file
@@ -0,0 +1,328 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
// Uncomment when you want to rollback to 2.2.1 version.
|
||||
oldwal "k8s.io/kubernetes/third_party/forked/etcd221/wal"
|
||||
// Uncomment when you want to rollback to 2.3.7 version.
|
||||
// oldwal "k8s.io/kubernetes/third_party/forked/etcd237/wal"
|
||||
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/etcdserver/membership"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
"github.com/coreos/etcd/pkg/pbutil"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/etcd/raft/raftpb"
|
||||
"github.com/coreos/etcd/snap"
|
||||
"github.com/coreos/etcd/store"
|
||||
"github.com/coreos/etcd/wal"
|
||||
"github.com/coreos/etcd/wal/walpb"
|
||||
"github.com/coreos/go-semver/semver"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
const rollbackVersion = "2.2.0"
|
||||
|
||||
// RollbackV3ToV2 rolls back an etcd 3.0.x data directory to the 2.x.x version specified by rollbackVersion.
|
||||
func RollbackV3ToV2(migrateDatadir string, ttl time.Duration) error {
|
||||
dbpath := path.Join(migrateDatadir, "member", "snap", "db")
|
||||
glog.Infof("Rolling db file %s back to etcd 2.x", dbpath)
|
||||
|
||||
// etcd3 store backend. We will use it to parse v3 data files and extract information.
|
||||
be := backend.NewDefaultBackend(dbpath)
|
||||
tx := be.BatchTx()
|
||||
|
||||
// etcd2 store backend. We will use v3 data to update this and then save snapshot to disk.
|
||||
st := store.New(etcdserver.StoreClusterPrefix, etcdserver.StoreKeysPrefix)
|
||||
expireTime := time.Now().Add(ttl)
|
||||
|
||||
tx.Lock()
|
||||
err := tx.UnsafeForEach([]byte("key"), func(k, v []byte) error {
|
||||
kv := &mvccpb.KeyValue{}
|
||||
kv.Unmarshal(v)
|
||||
|
||||
// This is compact key.
|
||||
if !strings.HasPrefix(string(kv.Key), "/") {
|
||||
return nil
|
||||
}
|
||||
|
||||
ttlOpt := store.TTLOptionSet{}
|
||||
if kv.Lease != 0 {
|
||||
ttlOpt = store.TTLOptionSet{ExpireTime: expireTime}
|
||||
}
|
||||
|
||||
if !isTombstone(k) {
|
||||
sk := path.Join(strings.Trim(etcdserver.StoreKeysPrefix, "/"), string(kv.Key))
|
||||
_, err := st.Set(sk, false, string(kv.Value), ttlOpt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
st.Delete(string(kv.Key), false, false)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tx.Unlock()
|
||||
|
||||
if err := traverseAndDeleteEmptyDir(st, "/"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// rebuild cluster state.
|
||||
metadata, hardstate, oldSt, err := rebuild(migrateDatadir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// In the following, it's low level logic that saves metadata and data into v2 snapshot.
|
||||
backupPath := migrateDatadir + ".rollback.backup"
|
||||
if err := os.Rename(migrateDatadir, backupPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(path.Join(migrateDatadir, "member", "snap"), 0777); err != nil {
|
||||
return err
|
||||
}
|
||||
walDir := path.Join(migrateDatadir, "member", "wal")
|
||||
|
||||
w, err := oldwal.Create(walDir, metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = w.SaveSnapshot(walpb.Snapshot{Index: hardstate.Commit, Term: hardstate.Term})
|
||||
w.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
event, err := oldSt.Get(etcdserver.StoreClusterPrefix, true, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// nodes (members info) for ConfState
|
||||
nodes := []uint64{}
|
||||
traverseMetadata(event.Node, func(n *store.NodeExtern) {
|
||||
if n.Key != etcdserver.StoreClusterPrefix {
|
||||
// update store metadata
|
||||
v := ""
|
||||
if !n.Dir {
|
||||
v = *n.Value
|
||||
}
|
||||
if n.Key == path.Join(etcdserver.StoreClusterPrefix, "version") {
|
||||
v = rollbackVersion
|
||||
}
|
||||
if _, err := st.Set(n.Key, n.Dir, v, store.TTLOptionSet{}); err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
|
||||
// update nodes
|
||||
fields := strings.Split(n.Key, "/")
|
||||
if len(fields) == 4 && fields[2] == "members" {
|
||||
nodeID, err := strconv.ParseUint(fields[3], 16, 64)
|
||||
if err != nil {
|
||||
glog.Fatalf("failed to parse member ID (%s): %v", fields[3], err)
|
||||
}
|
||||
nodes = append(nodes, nodeID)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
data, err := st.Save()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
raftSnap := raftpb.Snapshot{
|
||||
Data: data,
|
||||
Metadata: raftpb.SnapshotMetadata{
|
||||
Index: hardstate.Commit,
|
||||
Term: hardstate.Term,
|
||||
ConfState: raftpb.ConfState{
|
||||
Nodes: nodes,
|
||||
},
|
||||
},
|
||||
}
|
||||
snapshotter := snap.New(path.Join(migrateDatadir, "member", "snap"))
|
||||
if err := snapshotter.SaveSnap(raftSnap); err != nil {
|
||||
return err
|
||||
}
|
||||
glog.Infof("Finished successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
func traverseMetadata(head *store.NodeExtern, handleFunc func(*store.NodeExtern)) {
|
||||
q := []*store.NodeExtern{head}
|
||||
|
||||
for len(q) > 0 {
|
||||
n := q[0]
|
||||
q = q[1:]
|
||||
|
||||
handleFunc(n)
|
||||
|
||||
for _, next := range n.Nodes {
|
||||
q = append(q, next)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
revBytesLen = 8 + 1 + 8
|
||||
markedRevBytesLen = revBytesLen + 1
|
||||
markBytePosition = markedRevBytesLen - 1
|
||||
|
||||
markTombstone byte = 't'
|
||||
)
|
||||
|
||||
func isTombstone(b []byte) bool {
|
||||
return len(b) == markedRevBytesLen && b[markBytePosition] == markTombstone
|
||||
}
|
||||
|
||||
func traverseAndDeleteEmptyDir(st store.Store, dir string) error {
|
||||
e, err := st.Get(dir, true, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(e.Node.Nodes) == 0 {
|
||||
st.Delete(dir, true, true)
|
||||
return nil
|
||||
}
|
||||
for _, node := range e.Node.Nodes {
|
||||
if !node.Dir {
|
||||
glog.V(2).Infof("key: %s", node.Key[len(etcdserver.StoreKeysPrefix):])
|
||||
} else {
|
||||
err := traverseAndDeleteEmptyDir(st, node.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func rebuild(datadir string) ([]byte, *raftpb.HardState, store.Store, error) {
|
||||
waldir := path.Join(datadir, "member", "wal")
|
||||
snapdir := path.Join(datadir, "member", "snap")
|
||||
|
||||
ss := snap.New(snapdir)
|
||||
snapshot, err := ss.Load()
|
||||
if err != nil && err != snap.ErrNoSnapshot {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
var walsnap walpb.Snapshot
|
||||
if snapshot != nil {
|
||||
walsnap.Index, walsnap.Term = snapshot.Metadata.Index, snapshot.Metadata.Term
|
||||
}
|
||||
|
||||
w, err := wal.OpenForRead(waldir, walsnap)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
meta, hardstate, ents, err := w.ReadAll()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
st := store.New(etcdserver.StoreClusterPrefix, etcdserver.StoreKeysPrefix)
|
||||
if snapshot != nil {
|
||||
err := st.Recovery(snapshot.Data)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
cluster := membership.NewCluster("")
|
||||
cluster.SetStore(st)
|
||||
cluster.Recover(func(*semver.Version) {})
|
||||
|
||||
applier := etcdserver.NewApplierV2(st, cluster)
|
||||
for _, ent := range ents {
|
||||
if ent.Type == raftpb.EntryConfChange {
|
||||
var cc raftpb.ConfChange
|
||||
pbutil.MustUnmarshal(&cc, ent.Data)
|
||||
switch cc.Type {
|
||||
case raftpb.ConfChangeAddNode:
|
||||
m := new(membership.Member)
|
||||
if err := json.Unmarshal(cc.Context, m); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
cluster.AddMember(m)
|
||||
case raftpb.ConfChangeRemoveNode:
|
||||
id := types.ID(cc.NodeID)
|
||||
cluster.RemoveMember(id)
|
||||
case raftpb.ConfChangeUpdateNode:
|
||||
m := new(membership.Member)
|
||||
if err := json.Unmarshal(cc.Context, m); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
cluster.UpdateRaftAttributes(m.ID, m.RaftAttributes)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
var raftReq pb.InternalRaftRequest
|
||||
if !pbutil.MaybeUnmarshal(&raftReq, ent.Data) { // backward compatible
|
||||
var r pb.Request
|
||||
pbutil.MustUnmarshal(&r, ent.Data)
|
||||
applyRequest(&r, applier)
|
||||
} else {
|
||||
if raftReq.V2 != nil {
|
||||
req := raftReq.V2
|
||||
applyRequest(req, applier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return meta, &hardstate, st, nil
|
||||
}
|
||||
|
||||
func toTTLOptions(r *pb.Request) store.TTLOptionSet {
|
||||
refresh, _ := pbutil.GetBool(r.Refresh)
|
||||
ttlOptions := store.TTLOptionSet{Refresh: refresh}
|
||||
if r.Expiration != 0 {
|
||||
ttlOptions.ExpireTime = time.Unix(0, r.Expiration)
|
||||
}
|
||||
return ttlOptions
|
||||
}
|
||||
|
||||
func applyRequest(r *pb.Request, applyV2 etcdserver.ApplierV2) {
|
||||
toTTLOptions(r)
|
||||
switch r.Method {
|
||||
case "PUT":
|
||||
applyV2.Put(r)
|
||||
case "DELETE":
|
||||
applyV2.Delete(r)
|
||||
case "POST", "QGET", "SYNC":
|
||||
return
|
||||
default:
|
||||
glog.Fatal("unknown command")
|
||||
}
|
||||
}
|
1
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/testdata/datadir_with_version/version.txt
generated
vendored
Normal file
1
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/testdata/datadir_with_version/version.txt
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
3.1.12/etcd3
|
0
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/testdata/datadir_without_version/.placeholder
generated
vendored
Normal file
0
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/testdata/datadir_without_version/.placeholder
generated
vendored
Normal file
198
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/versions.go
generated
vendored
Normal file
198
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/versions.go
generated
vendored
Normal file
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
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"
|
||||
)
|
||||
|
||||
// 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 "<version>/<storage-version>" 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 <major>.<minor>.<patch>/<storage> 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
|
||||
}
|
||||
|
||||
// MustParseEtcdVersionPair parses a "<version>/<storage-version>" 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
|
||||
}
|
||||
|
||||
// String returns "<version>/<storage-version>" 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 comma separated list of etcd versions.
|
||||
func ParseSupportedVersions(s string) (SupportedVersions, error) {
|
||||
var err error
|
||||
list := strings.Split(s, ",")
|
||||
versions := make(SupportedVersions, len(list))
|
||||
for i, v := range list {
|
||||
versions[i], err = ParseEtcdVersion(strings.TrimSpace(v))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return versions, nil
|
||||
}
|
||||
|
||||
// MustParseSupportedVersions parses a comma separated list of etcd versions or panics if the parse fails.
|
||||
func MustParseSupportedVersions(s string) SupportedVersions {
|
||||
versions, err := ParseSupportedVersions(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return versions
|
||||
}
|
84
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/versions_test.go
generated
vendored
Normal file
84
vendor/k8s.io/kubernetes/cluster/images/etcd/migrate/versions_test.go
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
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"
|
||||
)
|
||||
|
||||
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},
|
||||
{"2.2.1/etcd2", &EtcdVersionPair{&EtcdVersion{semver.MustParse("2.2.1")}, storageEtcd2}, 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},
|
||||
|
||||
{"2.2.2/etcd2", &EtcdVersionPair{&EtcdVersion{semver.MustParse("2.2.1")}, storageEtcd2}, false},
|
||||
{"2.2.1/etcd3", &EtcdVersionPair{&EtcdVersion{semver.MustParse("2.2.1")}, storageEtcd2}, false},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
46
vendor/k8s.io/kubernetes/cluster/images/hyperkube/BUILD
generated
vendored
Normal file
46
vendor/k8s.io/kubernetes/cluster/images/hyperkube/BUILD
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
load("@io_bazel_rules_docker//docker:docker.bzl", "docker_build", "docker_bundle")
|
||||
|
||||
docker_build(
|
||||
name = "hyperkube-internal",
|
||||
base = "@debian-hyperkube-base-amd64//image",
|
||||
files = [
|
||||
"//cmd/hyperkube",
|
||||
],
|
||||
symlinks = {
|
||||
"/%s" % path: "/hyperkube"
|
||||
for path in [
|
||||
"/apiserver",
|
||||
"/controller-manager",
|
||||
"/kubectl",
|
||||
"/kubelet",
|
||||
"/proxy",
|
||||
"/scheduler",
|
||||
"/usr/local/bin/kube-apiserver",
|
||||
"/usr/local/bin/kube-controller-manager",
|
||||
"/usr/local/bin/kubectl",
|
||||
"/usr/local/bin/kubelet",
|
||||
"/usr/local/bin/kube-proxy",
|
||||
"/usr/local/bin/kube-scheduler",
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
docker_bundle(
|
||||
name = "hyperkube",
|
||||
images = {"k8s.gcr.io/hyperkube-amd64:{STABLE_DOCKER_TAG}": "hyperkube-internal"},
|
||||
stamp = True,
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
38
vendor/k8s.io/kubernetes/cluster/images/hyperkube/Dockerfile
generated
vendored
Normal file
38
vendor/k8s.io/kubernetes/cluster/images/hyperkube/Dockerfile
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# 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.
|
||||
|
||||
FROM BASEIMAGE
|
||||
|
||||
# Create symlinks for each hyperkube server
|
||||
# Also create symlinks to /usr/local/bin/ where the server image binaries live, so the hyperkube image may be
|
||||
# used instead of k8s.gcr.io/kube-* without any modifications.
|
||||
# TODO: replace manual symlink creation with --make-symlink command once
|
||||
# cross-building with qemu supports go binaries. See #28702
|
||||
# RUN /hyperkube --make-symlinks
|
||||
RUN ln -s /hyperkube /apiserver \
|
||||
&& ln -s /hyperkube /controller-manager \
|
||||
&& ln -s /hyperkube /kubectl \
|
||||
&& ln -s /hyperkube /kubelet \
|
||||
&& ln -s /hyperkube /proxy \
|
||||
&& ln -s /hyperkube /scheduler \
|
||||
&& ln -s /hyperkube /aggregator \
|
||||
&& ln -s /hyperkube /usr/local/bin/kube-apiserver \
|
||||
&& ln -s /hyperkube /usr/local/bin/kube-controller-manager \
|
||||
&& ln -s /hyperkube /usr/local/bin/kubectl \
|
||||
&& ln -s /hyperkube /usr/local/bin/kubelet \
|
||||
&& ln -s /hyperkube /usr/local/bin/kube-proxy \
|
||||
&& ln -s /hyperkube /usr/local/bin/kube-scheduler
|
||||
|
||||
# Copy the hyperkube binary
|
||||
COPY hyperkube /hyperkube
|
55
vendor/k8s.io/kubernetes/cluster/images/hyperkube/Makefile
generated
vendored
Normal file
55
vendor/k8s.io/kubernetes/cluster/images/hyperkube/Makefile
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
# 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 hyperkube image.
|
||||
#
|
||||
# Usage:
|
||||
# [ARCH=amd64] [REGISTRY="staging-k8s.gcr.io"] make (build|push) VERSION={some_released_version_of_kubernetes}
|
||||
|
||||
REGISTRY?=staging-k8s.gcr.io
|
||||
ARCH?=amd64
|
||||
OUT_DIR?=_output
|
||||
HYPERKUBE_BIN?=$(shell pwd)/../../../$(OUT_DIR)/dockerized/bin/linux/$(ARCH)/hyperkube
|
||||
|
||||
BASEIMAGE=k8s.gcr.io/debian-hyperkube-base-$(ARCH):0.10
|
||||
TEMP_DIR:=$(shell mktemp -d -t hyperkubeXXXXXX)
|
||||
|
||||
all: build
|
||||
|
||||
build:
|
||||
|
||||
ifndef VERSION
|
||||
$(error VERSION is undefined)
|
||||
endif
|
||||
cp -r ./* ${TEMP_DIR}
|
||||
cp ${HYPERKUBE_BIN} ${TEMP_DIR}
|
||||
|
||||
chmod a+rx ${TEMP_DIR}/hyperkube
|
||||
|
||||
cd ${TEMP_DIR} && sed -i.back "s|BASEIMAGE|${BASEIMAGE}|g" Dockerfile
|
||||
|
||||
# Register /usr/bin/qemu-ARCH-static as the handler for non-x86 binaries in the kernel
|
||||
docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
docker build --pull -t ${REGISTRY}/hyperkube-${ARCH}:${VERSION} ${TEMP_DIR}
|
||||
rm -rf "${TEMP_DIR}"
|
||||
|
||||
push: build
|
||||
docker push ${REGISTRY}/hyperkube-${ARCH}:${VERSION}
|
||||
ifeq ($(ARCH),amd64)
|
||||
docker rmi ${REGISTRY}/hyperkube:${VERSION} 2>/dev/null || true
|
||||
docker tag ${REGISTRY}/hyperkube-${ARCH}:${VERSION} ${REGISTRY}/hyperkube:${VERSION}
|
||||
docker push ${REGISTRY}/hyperkube:${VERSION}
|
||||
endif
|
||||
|
||||
.PHONY: build push all
|
8
vendor/k8s.io/kubernetes/cluster/images/hyperkube/OWNERS
generated
vendored
Normal file
8
vendor/k8s.io/kubernetes/cluster/images/hyperkube/OWNERS
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
reviewers:
|
||||
- ixdy
|
||||
- luxas
|
||||
- mikedanese
|
||||
approvers:
|
||||
- ixdy
|
||||
- luxas
|
||||
- mikedanese
|
35
vendor/k8s.io/kubernetes/cluster/images/hyperkube/README.md
generated
vendored
Normal file
35
vendor/k8s.io/kubernetes/cluster/images/hyperkube/README.md
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
### hyperkube
|
||||
|
||||
`hyperkube` is an all-in-one binary for the Kubernetes server components
|
||||
`hyperkube` is built for multiple architectures and _the image is pushed automatically on every release._
|
||||
|
||||
#### How to release by hand
|
||||
|
||||
```console
|
||||
# First, build the binaries
|
||||
$ build/run.sh make cross
|
||||
|
||||
# Build for linux/amd64 (default)
|
||||
# export REGISTRY=$HOST/$ORG to switch from staging-k8s.gcr.io
|
||||
|
||||
$ make push VERSION={target_version} ARCH=amd64
|
||||
# ---> staging-k8s.gcr.io/hyperkube-amd64:VERSION
|
||||
# ---> staging-k8s.gcr.io/hyperkube:VERSION (image with backwards-compatible naming)
|
||||
|
||||
$ make push VERSION={target_version} ARCH=arm
|
||||
# ---> staging-k8s.gcr.io/hyperkube-arm:VERSION
|
||||
|
||||
$ make push VERSION={target_version} ARCH=arm64
|
||||
# ---> staging-k8s.gcr.io/hyperkube-arm64:VERSION
|
||||
|
||||
$ make push VERSION={target_version} ARCH=ppc64le
|
||||
# ---> staging-k8s.gcr.io/hyperkube-ppc64le:VERSION
|
||||
|
||||
$ make push VERSION={target_version} ARCH=s390x
|
||||
# ---> staging-k8s.gcr.io/hyperkube-s390x:VERSION
|
||||
```
|
||||
|
||||
If you don't want to push the images, run `make` or `make build` instead
|
||||
|
||||
|
||||
[]()
|
34
vendor/k8s.io/kubernetes/cluster/images/kubemark/BUILD
generated
vendored
Normal file
34
vendor/k8s.io/kubernetes/cluster/images/kubemark/BUILD
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("@io_bazel_rules_docker//docker:docker.bzl", "docker_build", "docker_push")
|
||||
|
||||
docker_build(
|
||||
name = "image",
|
||||
base = "@official_busybox//image",
|
||||
entrypoint = ["/kubemark"],
|
||||
files = ["//cmd/kubemark"],
|
||||
)
|
||||
|
||||
docker_push(
|
||||
name = "push",
|
||||
image = ":image",
|
||||
registry = "$(REGISTRY)",
|
||||
repository = "kubemark",
|
||||
stamp = True,
|
||||
tag = "$(IMAGE_TAG)",
|
||||
tags = ["manual"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
17
vendor/k8s.io/kubernetes/cluster/images/kubemark/Dockerfile
generated
vendored
Normal file
17
vendor/k8s.io/kubernetes/cluster/images/kubemark/Dockerfile
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# 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.
|
||||
|
||||
FROM debian:jessie
|
||||
|
||||
COPY kubemark /kubemark
|
36
vendor/k8s.io/kubernetes/cluster/images/kubemark/Makefile
generated
vendored
Normal file
36
vendor/k8s.io/kubernetes/cluster/images/kubemark/Makefile
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# 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 Kubemark image from currently built binaries containing both 'real' master and Hollow Node.
|
||||
# This makefile assumes that the kubemark binary is present in this directory.
|
||||
|
||||
# Allow the caller to override this. Beware make's precedence. This:
|
||||
# REGISTRY=$VAR make
|
||||
# .. is not the same as:
|
||||
# make REGISTRY=$VAR
|
||||
REGISTRY := $(if $(REGISTRY),$(REGISTRY),staging-k8s.gcr.io)
|
||||
IMAGE_TAG := $(if $(IMAGE_TAG),$(IMAGE_TAG),latest)
|
||||
|
||||
all: gcloudpush
|
||||
|
||||
build:
|
||||
docker build --pull -t $(REGISTRY)/kubemark:$(IMAGE_TAG) .
|
||||
|
||||
gcloudpush: build
|
||||
docker push $(REGISTRY)/kubemark:$(IMAGE_TAG)
|
||||
|
||||
push: build
|
||||
docker -- push $(REGISTRY)/kubemark:$(IMAGE_TAG)
|
||||
|
||||
.PHONY: all build gcloudpush push
|
8
vendor/k8s.io/kubernetes/cluster/images/kubemark/OWNERS
generated
vendored
Normal file
8
vendor/k8s.io/kubernetes/cluster/images/kubemark/OWNERS
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
reviewers:
|
||||
- gmarek
|
||||
- shyamjvs
|
||||
- wojtek-t
|
||||
approvers:
|
||||
- gmarek
|
||||
- shyamjvs
|
||||
- wojtek-t
|
Reference in New Issue
Block a user