Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kubeadm upgrade same version #62568

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion cmd/kubeadm/app/apis/kubeadm/v1alpha1/BUILD
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "go_default_library",
Expand All @@ -7,6 +7,7 @@ go_library(
"doc.go",
"register.go",
"types.go",
"upgrade.go",
"zz_generated.conversion.go",
"zz_generated.deepcopy.go",
"zz_generated.defaults.go",
Expand Down Expand Up @@ -52,12 +53,17 @@ go_library(
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/features:go_default_library",
"//pkg/api/legacyscheme:go_default_library",
"//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library",
"//pkg/kubelet/apis/kubeletconfig/v1beta1:go_default_library",
"//pkg/proxy/apis/kubeproxyconfig/scheme:go_default_library",
"//pkg/proxy/apis/kubeproxyconfig/v1alpha1:go_default_library",
"//vendor/github.com/json-iterator/go:go_default_library",
"//vendor/github.com/ugorji/go/codec:go_default_library",
"//vendor/gopkg.in/yaml.v2:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
Expand All @@ -77,3 +83,11 @@ filegroup(
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

go_test(
name = "go_default_test",
srcs = ["upgrade_test.go"],
data = glob(["testdata/**"]),
embed = [":go_default_library"],
deps = ["//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library"],
)
62 changes: 62 additions & 0 deletions cmd/kubeadm/app/apis/kubeadm/v1alpha1/testdata/kubeadm196.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
api:
advertiseAddress: 172.31.93.180
bindPort: 6443
authorizationModes:
- Node
- RBAC
certificatesDir: /etc/kubernetes/pki
cloudProvider: aws
etcd:
caFile: ""
certFile: ""
dataDir: /var/lib/etcd
endpoints: null
image: ""
keyFile: ""
imageRepository: gcr.io/google_containers
kubeProxy:
config:
bindAddress: 0.0.0.0
clientConnection:
acceptContentTypes: ""
burst: 10
contentType: application/vnd.kubernetes.protobuf
kubeconfig: /var/lib/kube-proxy/kubeconfig.conf
qps: 5
clusterCIDR: 192.168.0.0/16
configSyncPeriod: 15m0s
conntrack:
max: null
maxPerCore: 32768
min: 131072
tcpCloseWaitTimeout: 1h0m0s
tcpEstablishedTimeout: 24h0m0s
enableProfiling: false
featureGates: ""
healthzBindAddress: 0.0.0.0:10256
hostnameOverride: ""
iptables:
masqueradeAll: false
masqueradeBit: 14
minSyncPeriod: 0s
syncPeriod: 30s
ipvs:
minSyncPeriod: 0s
scheduler: ""
syncPeriod: 30s
metricsBindAddress: 127.0.0.1:10249
mode: ""
oomScoreAdj: -999
portRange: ""
resourceContainer: /kube-proxy
udpTimeoutMilliseconds: 250ms
kubeletConfiguration: {}
kubernetesVersion: v1.9.6
networking:
dnsDomain: cluster.local
podSubnet: 192.168.0.0/16
serviceSubnet: 10.96.0.0/12
nodeName: ip-172-31-93-180.ec2.internal
token: 8d69af.cd3e1c58f6228dfc
tokenTTL: 24h0m0s
unifiedControlPlaneImage: ""
135 changes: 135 additions & 0 deletions cmd/kubeadm/app/apis/kubeadm/v1alpha1/upgrade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
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 v1alpha1

import (
"bytes"
"errors"
"fmt"
"reflect"
"strconv"
"strings"

"github.com/json-iterator/go"
"github.com/ugorji/go/codec"
yaml "gopkg.in/yaml.v2"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
runtime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api/legacyscheme"
)

var json = jsoniter.ConfigCompatibleWithStandardLibrary

type configMutationFunc func(map[string]interface{}) error

// These migrations are a stop-gap until we get a properly-versioned configuration file for MasterConfiguration.
// https://github.com/kubernetes/kubeadm/issues/750
var migrations = map[string][]configMutationFunc{
"MasterConfiguration": {
proxyFeatureListToMap,
},
}

// Migrate takes a map representing a config file and an object to decode into.
// The map is transformed into a format suitable for encoding into the supplied object, then serialised and decoded.
func Migrate(in map[string]interface{}, obj runtime.Object) error {
kind := reflect.TypeOf(obj).Elem().Name()
migrationsForKind := migrations[kind]

for _, m := range migrationsForKind {
err := m(in)
if err != nil {
return err
}
}

// Use codec instead of encoding/json to handle map[interface{}]interface{}
handle := &codec.JsonHandle{}
buf := new(bytes.Buffer)
if err := codec.NewEncoder(buf, handle).Encode(in); err != nil {
return fmt.Errorf("couldn't json encode object: %v", err)
}

return runtime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), buf.Bytes(), obj)
}

func proxyFeatureListToMap(m map[string]interface{}) error {
featureGatePath := []string{"kubeProxy", "config", "featureGates"}

// If featureGatePath is already a map, we don't need to do anything.
_, _, err := unstructured.NestedMap(m, featureGatePath...)
if err == nil {
return nil
}

gates, _, err := unstructured.NestedString(m, featureGatePath...)
if err != nil {
return fmt.Errorf("couldn't get featureGates: %v", err)
}

gateMap := make(map[string]interface{})
for _, gate := range strings.Split(gates, ",") {
if gate == "" {
continue
}
parts := strings.SplitN(gate, "=", 2)
if len(parts) != 2 {
return fmt.Errorf("unparsable kubeproxy feature gate %q", gate)
}
val, err := strconv.ParseBool(parts[1])
if err != nil {
return fmt.Errorf("unparsable kubeproxy feature gate %q: %v", gate, err)
}
gateMap[parts[0]] = val
}

unstructured.SetNestedMap(m, gateMap, featureGatePath...)
return nil
}

// LoadYAML is a small wrapper around go-yaml that ensures all nested structs are map[string]interface{} instead of map[interface{}]interface{}.
func LoadYAML(bytes []byte) (map[string]interface{}, error) {
var decoded map[interface{}]interface{}
if err := yaml.Unmarshal(bytes, &decoded); err != nil {
return map[string]interface{}{}, fmt.Errorf("couldn't unmarshal YAML: %v", err)
}

converted, ok := convert(decoded).(map[string]interface{})
if !ok {
return map[string]interface{}{}, errors.New("yaml is not a map")
}

return converted, nil
}

// https://stackoverflow.com/questions/40737122/convert-yaml-to-json-without-struct-golang
func convert(i interface{}) interface{} {
switch x := i.(type) {
case map[interface{}]interface{}:
m2 := map[string]interface{}{}
for k, v := range x {
m2[k.(string)] = convert(v)
}
return m2
case []interface{}:
for i, v := range x {
x[i] = convert(v)
}
}
return i
}
137 changes: 137 additions & 0 deletions cmd/kubeadm/app/apis/kubeadm/v1alpha1/upgrade_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
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 v1alpha1

import (
"io/ioutil"
"testing"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

const test196 = "testdata/kubeadm196.yaml"

func TestUpgrade(t *testing.T) {
testYAML, err := ioutil.ReadFile(test196)
if err != nil {
t.Fatalf("couldn't read test data: %v", err)
}

decoded, err := LoadYAML(testYAML)
if err != nil {
t.Fatalf("couldn't unmarshal test yaml: %v", err)
}

var obj MasterConfiguration
if err := Migrate(decoded, &obj); err != nil {
t.Fatalf("couldn't decode migrated object: %v", err)
}
}

func TestProxyFeatureListToMap(t *testing.T) {

cases := []struct {
name string
featureGates interface{}
expected map[string]interface{}
shouldError bool
}{
{
name: "multiple features",
featureGates: "feature1=true,feature2=false",
expected: map[string]interface{}{
"feature1": true,
"feature2": false,
},
},
{
name: "single feature",
featureGates: "feature1=true",
expected: map[string]interface{}{
"feature1": true,
},
},
{
name: "already a map",
featureGates: map[string]interface{}{
"feature1": true,
},
expected: map[string]interface{}{
"feature1": true,
},
},
{
name: "single feature",
featureGates: "",
expected: map[string]interface{}{},
},
{
name: "malformed string",
featureGates: "test,",
shouldError: true,
},
}

for _, testCase := range cases {
t.Run(testCase.name, func(t *testing.T) {

cfg := map[string]interface{}{
"kubeProxy": map[string]interface{}{
"config": map[string]interface{}{
"featureGates": testCase.featureGates,
},
},
}

err := proxyFeatureListToMap(cfg)
if testCase.shouldError {
if err == nil {
t.Error("expected error, got nil")
}
return
}

if err != nil {
t.Errorf("unexpected error: %v", err)
}

gates, ok, err := unstructured.NestedMap(cfg, "kubeProxy", "config", "featureGates")
if !ok {
t.Errorf("missing map keys in nested map")
}
if err != nil {
t.Errorf("unexpected error in map: %v", err)
}

if len(testCase.expected) != len(gates) {
t.Errorf("expected feature gate size %d, got %d", len(testCase.expected), len(gates))
}

for k, v := range testCase.expected {
gateVal, ok := gates[k]
if !ok {
t.Errorf("featureGates missing key %q", k)
continue
}

if v != gateVal {
t.Errorf("expected value %v, got %v", v, gateVal)
}
}
})
}
}
1 change: 0 additions & 1 deletion cmd/kubeadm/app/phases/upgrade/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
Expand Down
Loading