Merge remote-tracking branch 'upstream/master' into update_aws-sdk

This commit is contained in:
Rodrigo Menezes 2017-11-24 17:13:56 -08:00
commit 498e3b29d7
44 changed files with 819 additions and 121 deletions

2
OWNERS
View File

@ -5,3 +5,5 @@ approvers:
- zmerlynn
- andrewsykim
- geojaz
- kashifsaadat
- gambol99

View File

@ -32,7 +32,7 @@ import (
var (
create_secret_dockerconfig_long = templates.LongDesc(i18n.T(`
Create a new docker config, and store it in the state store.
Create a new docker config, and store it in the state store.
Used to configure docker on each master or node (ie. for auth)
Use update to modify it, this command will only create a new entry.`))
@ -40,6 +40,9 @@ var (
# Create an new docker config.
kops create secret dockerconfig -f /path/to/docker/config.json \
--name k8s-cluster.example.com --state s3://example.com
# Replace an existing docker config secret.
kops create secret dockerconfig -f /path/to/docker/config.json --force \
--name k8s-cluster.example.com --state s3://example.com
`))
create_secret_dockerconfig_short = i18n.T(`Create a docker config.`)
@ -48,6 +51,7 @@ var (
type CreateSecretDockerConfigOptions struct {
ClusterName string
DockerConfigPath string
Force bool
}
func NewCmdCreateSecretDockerConfig(f *util.Factory, out io.Writer) *cobra.Command {
@ -78,6 +82,7 @@ func NewCmdCreateSecretDockerConfig(f *util.Factory, out io.Writer) *cobra.Comma
}
cmd.Flags().StringVarP(&options.DockerConfigPath, "", "f", "", "Path to docker config JSON file")
cmd.Flags().BoolVar(&options.Force, "force", options.Force, "Force replace the kops secret if it already exists")
return cmd
}
@ -119,9 +124,19 @@ func RunCreateSecretDockerConfig(f *util.Factory, out io.Writer, options *Create
secret.Data = data
_, _, err = secretStore.GetOrCreateSecret("dockerconfig", secret)
if err != nil {
return fmt.Errorf("error adding docker config secret: %v", err)
if !options.Force {
_, created, err := secretStore.GetOrCreateSecret("dockerconfig", secret)
if err != nil {
return fmt.Errorf("error adding dockerconfig secret: %v", err)
}
if !created {
return fmt.Errorf("failed to create the dockerconfig secret as it already exists. The `--force` flag can be passed to replace an existing secret.")
}
} else {
_, err := secretStore.ReplaceSecret("dockerconfig", secret)
if err != nil {
return fmt.Errorf("error updating dockerconfig secret: %v", err)
}
}
return nil

View File

@ -40,6 +40,9 @@ var (
# Create a new encryption config.
kops create secret encryptionconfig -f config.yaml \
--name k8s-cluster.example.com --state s3://example.com
# Replace an existing encryption config secret.
kops create secret encryptionconfig -f config.yaml --force \
--name k8s-cluster.example.com --state s3://example.com
`))
create_secret_encryptionconfig_short = i18n.T(`Create an encryption config.`)
@ -48,6 +51,7 @@ var (
type CreateSecretEncryptionConfigOptions struct {
ClusterName string
EncryptionConfigPath string
Force bool
}
func NewCmdCreateSecretEncryptionConfig(f *util.Factory, out io.Writer) *cobra.Command {
@ -78,6 +82,7 @@ func NewCmdCreateSecretEncryptionConfig(f *util.Factory, out io.Writer) *cobra.C
}
cmd.Flags().StringVarP(&options.EncryptionConfigPath, "", "f", "", "Path to encryption config yaml file")
cmd.Flags().BoolVar(&options.Force, "force", options.Force, "Force replace the kops secret if it already exists")
return cmd
}
@ -120,9 +125,19 @@ func RunCreateSecretEncryptionConfig(f *util.Factory, out io.Writer, options *Cr
secret.Data = data
_, _, err = secretStore.GetOrCreateSecret("encryptionconfig", secret)
if err != nil {
return fmt.Errorf("error adding encryption config secret: %v", err)
if !options.Force {
_, created, err := secretStore.GetOrCreateSecret("encryptionconfig", secret)
if err != nil {
return fmt.Errorf("error adding encryptionconfig secret: %v", err)
}
if !created {
return fmt.Errorf("failed to create the encryptionconfig secret as it already exists. The `--force` flag can be passed to replace an existing secret.")
}
} else {
_, err := secretStore.ReplaceSecret("encryptionconfig", secret)
if err != nil {
return fmt.Errorf("error updating encryptionconfig secret: %v", err)
}
}
return nil

View File

@ -20,6 +20,7 @@ import (
"fmt"
"io"
"os"
"runtime"
"strings"
"github.com/golang/glog"
@ -31,10 +32,18 @@ import (
"k8s.io/kops/cmd/kops/util"
api "k8s.io/kops/pkg/apis/kops"
apiutil "k8s.io/kops/pkg/apis/kops/util"
"k8s.io/kops/pkg/dns"
"k8s.io/kops/pkg/validation"
"k8s.io/kops/util/pkg/tables"
)
func init() {
if runtime.GOOS == "darwin" {
// In order for net.LookupHost(apiAddr.Host) to lookup our placeholder address on darwin, we have to
os.Setenv("GODEBUG", "netdns=go")
}
}
type ValidateClusterOptions struct {
// No options yet
}
@ -105,6 +114,25 @@ func RunValidateCluster(f *util.Factory, cmd *cobra.Command, args []string, out
return fmt.Errorf("Cannot build kube api client for %q: %v\n", contextName, err)
}
// Do not use if we are running gossip
if !dns.IsGossipHostname(cluster.ObjectMeta.Name) {
hasPlaceHolderIPAddress, err := validation.HasPlaceHolderIP(contextName)
if err != nil {
return err
}
if hasPlaceHolderIPAddress {
fmt.Println(
"Validation Failed\n\n" +
"The dns-controller Kubernetes deployment has not updated the Kubernetes cluster's API DNS entry to the correct IP address." +
" The API DNS IP address is the placeholder address that kops creates: 203.0.113.123." +
" Please wait about 5-10 minutes for a master to start, dns-controller to launch, and DNS to propagate." +
" The protokube container and dns-controller deployment logs may contain more diagnostic information." +
" Etcd and the API DNS entries must be updated for a kops Kubernetes cluster to start.")
return fmt.Errorf("\nCannot reach cluster's API server: unable to Validate Cluster: %s", cluster.ObjectMeta.Name)
}
}
validationCluster, validationFailed := validation.ValidateCluster(cluster.ObjectMeta.Name, list, k8sClient)
if validationCluster == nil || validationCluster.NodeList == nil || validationCluster.NodeList.Items == nil {

View File

@ -20,12 +20,16 @@ kops create secret dockerconfig
# Create an new docker config.
kops create secret dockerconfig -f /path/to/docker/config.json \
--name k8s-cluster.example.com --state s3://example.com
# Replace an existing docker config secret.
kops create secret dockerconfig -f /path/to/docker/config.json --force \
--name k8s-cluster.example.com --state s3://example.com
```
### Options
```
-f, -- string Path to docker config JSON file
--force Force replace the kops secret if it already exists
```
### Options inherited from parent commands

View File

@ -20,12 +20,16 @@ kops create secret encryptionconfig
# Create a new encryption config.
kops create secret encryptionconfig -f config.yaml \
--name k8s-cluster.example.com --state s3://example.com
# Replace an existing encryption config secret.
kops create secret encryptionconfig -f config.yaml --force \
--name k8s-cluster.example.com --state s3://example.com
```
### Options
```
-f, -- string Path to encryption config yaml file
--force Force replace the kops secret if it already exists
```
### Options inherited from parent commands

View File

@ -1,6 +1,6 @@
# Cluster Templating
The command `kops replace` can replace a cluster desired configuration from the config in a yaml file (see [/cli/kops_replace.md](/cli/kops_replace.md)).
The command `kops replace` can replace a cluster desired configuration from the config in a yaml file (see [cli/kops_replace.md](cli/kops_replace.md)).
It is possible to generate that yaml file from a template, using the command `kops toolbox template` (see [cli/kops_toolbox_template.md](cli/kops_toolbox_template.md)).
@ -45,7 +45,7 @@ Running `kops toolbox template` replaces the placeholders in the template by val
Note: when creating a cluster desired configuration template, you can
- use `kops get k8s-cluster.example.com -o yaml > cluster-desired-config.yaml` to create the cluster desired configuration file (see [cli/kops_get.md](cli/kops_get.md)). The values in this file are defined in [cli/cluster_spec.md](cli/cluster_spec.md).
- use `kops get k8s-cluster.example.com -o yaml > cluster-desired-config.yaml` to create the cluster desired configuration file (see [cli/kops_get.md](cli/kops_get.md)). The values in this file are defined in [cluster_spec.md](cluster_spec.md).
- replace values by placeholders in that file to create the template.
### Templates

View File

@ -1,3 +1,26 @@
# Installing Kops via Hombrew
Homebrew makes installing kops [very simple for MacOS.](../install.md)
```bash
brew update && brew install kops
```
Development Releases and master can also be installed via Homebrew very easily:
```bash
# Development Release
brew update && brew install kops --devel
# HEAD of master
brew update && brew install kops --HEAD
```
Note: if you already have kops installed, you need to substitute `upgrade` for `install`.
You can switch between development and stable releases with:
```bash
brew switch kops 1.7.1
brew switch kops 1.8.0-beta.1
```
# Releasing kops to Brew
Submitting a new release of kops to Homebrew is very simple.
@ -8,11 +31,20 @@ Submitting a new release of kops to Homebrew is very simple.
This will automatically update the provided fields and open a PR for you.
More details on this script are located [here.](https://github.com/Homebrew/brew/blob/master/Library/Homebrew/dev-cmd/bump-formula-pr.rb)
We now include both major and development releases in homebrew. A development version can be updated by adding the `--devel` flag.
Example usage:
```
```bash
# Major Version
brew bump-formula-pr \
--url=https://github.com/kubernetes/kops/archive/1.7.1.tar.gz \
--sha256=044c5c7a737ed3acf53517e64bb27d3da8f7517d2914df89efeeaf84bc8a722a
# Development Version
brew bump-formula-pr \
--devel \
--url=https://github.com/kubernetes/kops/archive/1.8.0-beta.1.tar.gz \
--sha256=81026d6c1cd7b3898a88275538a7842b4bd8387775937e0528ccb7b83948abf1
```
* Update the URL variable to the tar.gz of the new release source code

View File

@ -8,6 +8,8 @@ From Homebrew:
brew update && brew install kops
```
Developers can also easily install [development releases](development/homebrew.md).
From Github:
```bash

View File

@ -14,6 +14,13 @@ or `--networking flannel-udp` can be specified to explicitly choose a backend mo
See the *Changes to k8s-policy* section in the
[Calico release notes](https://github.com/projectcalico/calico/releases/tag/v2.4.0)
for help.
* Due to `ThirdPartyResources` becoming fully deprecated in Kubernetes v1.8 (replaced by `CustomResourceDefinitions`), existing Canal users upgrading their Clusters to Kubernetes v1.8 must follow the below TPR->CRD migration steps:
1. Run: `kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v2.6.2/upgrade/v2.5/manifests/upgrade-job.yaml`
2. Retrieve the pod name from describing the job: `kubectl describe job/calico-upgrade-v2.5`
3. Validate the last log line from the pod reports that it completed successfully: `kubectl logs calico-upgrade-v2.5-<random-id>`
4. Update the `KubernetesVersion` within your ClusterSpec to v1.8 (or above), performing an update & rolling-update to all nodes (will involve downtime)
5. Confirm cluster is back up and all canal pods are running successfully: `kops validate cluster` (this may take a few minutes for the cluster to fully validate)
6. Delete the upgrade job as it is no longer required: `kubectl delete job calico-upgrade-v2.5` (you can also safely delete the `clusterrole`, `clusterrolebinding` and `serviceaccount` resources that were created by the above manifest file)
# Full changelist

View File

@ -1,13 +1,33 @@
## How to update Kops - Kubernetes Ops
# Updating kops (Binaries)
Update the latest source code from kubernetes/kops
## MacOS
```
cd ${GOPATH}/src/k8s.io/kops/
git pull && make
```
From Homebrew:
Alternatively, if you installed from Homebrew
```
```bash
brew update && brew upgrade kops
```
From Github:
```bash
rm -rf /usr/local/bin/kops
wget -O kops https://github.com/kubernetes/kops/releases/download/$(curl -s https://api.github.com/repos/kubernetes/kops/releases/latest | grep tag_name | cut -d '"' -f 4)/kops-darwin-amd64
chmod +x ./kops
sudo mv ./kops /usr/local/bin/
```
You can also rerun rerun [these steps](development/building.md) if previously built from source.
## Linux
From Github:
```bash
rm -rf /usr/local/bin/kops
wget -O kops https://github.com/kubernetes/kops/releases/download/$(curl -s https://api.github.com/repos/kubernetes/kops/releases/latest | grep tag_name | cut -d '"' -f 4)/kops-linux-amd64
chmod +x ./kops
sudo mv ./kops /usr/local/bin/
```
You can also rerun rerun [these steps](development/building.md) if previously built from source.

View File

@ -123,6 +123,7 @@ k8s.io/kops/upup/pkg/fi/cloudup/dotasks
k8s.io/kops/upup/pkg/fi/cloudup/gce
k8s.io/kops/upup/pkg/fi/cloudup/gcetasks
k8s.io/kops/upup/pkg/fi/cloudup/openstack
k8s.io/kops/upup/pkg/fi/cloudup/openstacktasks
k8s.io/kops/upup/pkg/fi/cloudup/terraform
k8s.io/kops/upup/pkg/fi/cloudup/vsphere
k8s.io/kops/upup/pkg/fi/cloudup/vspheretasks

View File

@ -431,6 +431,57 @@ var dockerVersions = []dockerVersion{
Hash: "4659c937b66519c88ef2a82a906bb156db29d191",
Dependencies: []string{"policycoreutils-python"},
},
// 17.09.0 - k8s 1.8
// 17.09.0 - Jessie
{
DockerVersion: "17.09.0",
Name: "docker-ce",
Distros: []distros.Distribution{distros.DistributionJessie},
Architectures: []Architecture{ArchitectureAmd64},
Version: "17.09.0~ce-0~debian",
Source: "http://download.docker.com/linux/debian/dists/jessie/pool/stable/amd64/docker-ce_17.09.0~ce-0~debian_amd64.deb",
Hash: "430ba87f8aa36fedcac1a48e909cbe1830b53845",
Dependencies: []string{"bridge-utils", "libapparmor1", "libltdl7", "perl"},
},
// 17.09.0 - Jessie on ARM
{
DockerVersion: "17.09.0",
Name: "docker-ce",
Distros: []distros.Distribution{distros.DistributionJessie},
Architectures: []Architecture{ArchitectureArm},
Version: "17.09.0~ce-0~debian",
Source: "http://download.docker.com/linux/debian/dists/jessie/pool/stable/armhf/docker-ce_17.09.0~ce-0~debian_armhf.deb",
Hash: "5001a1defec7c33aa58ddebbd3eae6ebb5f36479",
Dependencies: []string{"bridge-utils", "libapparmor1", "libltdl7", "perl"},
},
// 17.09.0 - Xenial
{
DockerVersion: "17.09.0",
Name: "docker-ce",
Distros: []distros.Distribution{distros.DistributionXenial},
Architectures: []Architecture{ArchitectureAmd64},
Version: "17.09.0~ce-0~ubuntu",
Source: "http://download.docker.com/linux/ubuntu/dists/xenial/pool/stable/amd64/docker-ce_17.09.0~ce-0~ubuntu_amd64.deb",
Hash: "94f6e89be6d45d9988269a237eb27c7d6a844d7f",
Dependencies: []string{"bridge-utils", "iptables", "libapparmor1", "libltdl7", "perl"},
//Depends: iptables, init-system-helpers, lsb-base, libapparmor1, libc6, libdevmapper1.02.1, libltdl7, libeseccomp2, libsystemd0
//Recommends: aufs-tools, ca-certificates, cgroupfs-mount | cgroup-lite, git, xz-utils, apparmor
},
// 17.09.0 - Centos / Rhel7
{
DockerVersion: "17.09.0",
Name: "docker-ce",
Distros: []distros.Distribution{distros.DistributionRhel7, distros.DistributionCentos7},
Architectures: []Architecture{ArchitectureAmd64},
Version: "17.09.0.ce",
Source: "https://download.docker.com/linux/centos/7/x86_64/stable/Packages/docker-ce-17.09.0.ce-1.el7.centos.x86_64.rpm",
Hash: "b4ce72e80ff02926de943082821bbbe73958f87a",
Dependencies: []string{"libtool-ltdl", "libseccomp", "libgcroup"},
},
}
func (d *dockerVersion) matches(arch Architecture, dockerVersion string, distro distros.Distribution) bool {

View File

@ -322,7 +322,7 @@ type KubeControllerManagerConfig struct {
// HorizontalPodAutoscalerUpscaleDelay is a duration that specifies how
// long the autoscaler has to wait before another upscale operation can
// be performed after the current one has completed.
HorizontalPodAutoscalerUpscaleDelay *metav1.Duration `json:"horizontalPodAutoscalerUpscaleDelay,omitempty" flag:"horizontal-pod-autoscaler-downscale-delay"`
HorizontalPodAutoscalerUpscaleDelay *metav1.Duration `json:"horizontalPodAutoscalerUpscaleDelay,omitempty" flag:"horizontal-pod-autoscaler-upscale-delay"`
// FeatureGates is set of key=value pairs that describe feature gates for alpha/experimental features.
FeatureGates map[string]string `json:"featureGates,omitempty" flag:"feature-gates"`
}

View File

@ -322,7 +322,7 @@ type KubeControllerManagerConfig struct {
// HorizontalPodAutoscalerUpscaleDelay is a duration that specifies how
// long the autoscaler has to wait before another upscale operation can
// be performed after the current one has completed.
HorizontalPodAutoscalerUpscaleDelay *metav1.Duration `json:"horizontalPodAutoscalerUpscaleDelay,omitempty" flag:"horizontal-pod-autoscaler-downscale-delay"`
HorizontalPodAutoscalerUpscaleDelay *metav1.Duration `json:"horizontalPodAutoscalerUpscaleDelay,omitempty" flag:"horizontal-pod-autoscaler-upscale-delay"`
// FeatureGates is set of key=value pairs that describe feature gates for alpha/experimental features.
FeatureGates map[string]string `json:"featureGates,omitempty" flag:"feature-gates"`
}

View File

@ -469,9 +469,9 @@ func ValidateCluster(c *kops.Cluster, strict bool) *field.Error {
}
}
if kubernetesRelease.LT(semver.MustParse("1.6.0")) {
if kubernetesRelease.LT(semver.MustParse("1.7.0")) {
if c.Spec.Networking != nil && c.Spec.Networking.Romana != nil {
return field.Invalid(fieldSpec.Child("Networking"), "romana", "romana networking is not supported with kubernetes versions 1.5 or lower")
return field.Invalid(fieldSpec.Child("Networking"), "romana", "romana networking is not supported with kubernetes versions 1.6 or lower")
}
}

View File

@ -36,6 +36,8 @@ go_library(
"//upup/pkg/fi/cloudup/dotasks:go_default_library",
"//upup/pkg/fi/cloudup/gce:go_default_library",
"//upup/pkg/fi/cloudup/gcetasks:go_default_library",
"//upup/pkg/fi/cloudup/openstack:go_default_library",
"//upup/pkg/fi/cloudup/openstacktasks:go_default_library",
"//upup/pkg/fi/fitasks:go_default_library",
"//util/pkg/vfs:go_default_library",
"//vendor/github.com/blang/semver:go_default_library",

View File

@ -661,7 +661,7 @@ func addMasterASPolicies(p *Policy, resource stringorslice.StringOrSlice, legacy
Resource: resource,
Condition: Condition{
"StringEquals": map[string]string{
"ec2:ResourceTag/KubernetesCluster": clusterName,
"autoscaling:ResourceTag/KubernetesCluster": clusterName,
},
},
},

View File

@ -75,7 +75,7 @@
],
"Condition": {
"StringEquals": {
"ec2:ResourceTag/KubernetesCluster": "iam-builder-test.k8s.local"
"autoscaling:ResourceTag/KubernetesCluster": "iam-builder-test.k8s.local"
}
}
},

View File

@ -75,7 +75,7 @@
],
"Condition": {
"StringEquals": {
"ec2:ResourceTag/KubernetesCluster": "iam-builder-test.k8s.local"
"autoscaling:ResourceTag/KubernetesCluster": "iam-builder-test.k8s.local"
}
}
},

View File

@ -30,6 +30,8 @@ import (
"k8s.io/kops/upup/pkg/fi/cloudup/dotasks"
"k8s.io/kops/upup/pkg/fi/cloudup/gce"
"k8s.io/kops/upup/pkg/fi/cloudup/gcetasks"
"k8s.io/kops/upup/pkg/fi/cloudup/openstack"
"k8s.io/kops/upup/pkg/fi/cloudup/openstacktasks"
)
const (
@ -95,6 +97,11 @@ func (b *MasterVolumeBuilder) Build(c *fi.ModelBuilderContext) error {
b.addVSphereVolume(c, name, volumeSize, zone, etcd, m, allMembers)
case kops.CloudProviderBareMetal:
glog.Fatalf("BareMetal not implemented")
case kops.CloudProviderOpenstack:
err = b.addOpenstackVolume(c, name, volumeSize, zone, etcd, m, allMembers)
if err != nil {
return err
}
default:
return fmt.Errorf("unknown cloudprovider %q", b.Cluster.Spec.CloudProvider)
}
@ -205,3 +212,33 @@ func (b *MasterVolumeBuilder) addGCEVolume(c *fi.ModelBuilderContext, name strin
func (b *MasterVolumeBuilder) addVSphereVolume(c *fi.ModelBuilderContext, name string, volumeSize int32, zone string, etcd *kops.EtcdClusterSpec, m *kops.EtcdMemberSpec, allMembers []string) {
fmt.Print("addVSphereVolume to be implemented")
}
func (b *MasterVolumeBuilder) addOpenstackVolume(c *fi.ModelBuilderContext, name string, volumeSize int32, zone string, etcd *kops.EtcdClusterSpec, m *kops.EtcdMemberSpec, allMembers []string) error {
volumeType := fi.StringValue(m.VolumeType)
if volumeType == "" {
return fmt.Errorf("must set ETCDMemberSpec.VolumeType on Openstack platform")
}
// The tags are how protokube knows to mount the volume and use it for etcd
tags := make(map[string]string)
// Apply all user defined labels on the volumes
for k, v := range b.Cluster.Spec.CloudLabels {
tags[k] = v
}
// This is the configuration of the etcd cluster
tags[openstack.TagNameEtcdClusterPrefix+etcd.Name] = m.Name + "/" + strings.Join(allMembers, ",")
// This says "only mount on a master"
tags[openstack.TagNameRolePrefix+"master"] = "1"
t := &openstacktasks.Volume{
Name: s(name),
AvailabilityZone: s(zone),
VolumeType: s(volumeType),
SizeGB: fi.Int64(int64(volumeSize)),
Tags: tags,
Lifecycle: b.Lifecycle,
}
c.AddTask(t)
return nil
}

View File

@ -220,7 +220,7 @@ func addUntaggedRouteTables(cloud awsup.AWSCloud, clusterName string, resources
continue
}
t := buildTrackerForRouteTable(rt)
t := buildTrackerForRouteTable(rt, clusterName)
if resources[t.Type+":"+t.ID] == nil {
resources[t.Type+":"+t.ID] = t
}
@ -973,19 +973,20 @@ func ListRouteTables(cloud fi.Cloud, clusterName string) ([]*Resource, error) {
var resourceTrackers []*Resource
for _, rt := range routeTables {
resourceTracker := buildTrackerForRouteTable(rt)
resourceTracker := buildTrackerForRouteTable(rt, clusterName)
resourceTrackers = append(resourceTrackers, resourceTracker)
}
return resourceTrackers, nil
}
func buildTrackerForRouteTable(rt *ec2.RouteTable) *Resource {
func buildTrackerForRouteTable(rt *ec2.RouteTable, clusterName string) *Resource {
resourceTracker := &Resource{
Name: FindName(rt.Tags),
ID: aws.StringValue(rt.RouteTableId),
Type: ec2.ResourceTypeRouteTable,
Deleter: DeleteRouteTable,
Shared: HasSharedTag(ec2.ResourceTypeRouteTable+":"+*rt.RouteTableId, rt.Tags, clusterName),
}
var blocks []string

View File

@ -88,3 +88,55 @@ func TestAddUntaggedRouteTables(t *testing.T) {
t.Fatalf("expected=%q, actual=%q", expected, keys)
}
}
func TestListRouteTables(t *testing.T) {
cloud := awsup.BuildMockAWSCloud("us-east-1", "abc")
//resources := make(map[string]*Resource)
clusterName := "me.example.com"
ownershipTagKey := "kubernetes.io/cluster/" + clusterName
c := &mockec2.MockEC2{}
cloud.MockEC2 = c
c.RouteTables = append(c.RouteTables, &ec2.RouteTable{
VpcId: aws.String("vpc-1234"),
RouteTableId: aws.String("rt-shared"),
Tags: []*ec2.Tag{
{
Key: aws.String("KubernetesCluster"),
Value: aws.String(clusterName),
},
{
Key: aws.String(ownershipTagKey),
Value: aws.String("shared"),
},
},
})
c.RouteTables = append(c.RouteTables, &ec2.RouteTable{
VpcId: aws.String("vpc-1234"),
RouteTableId: aws.String("rt-owned"),
Tags: []*ec2.Tag{
{
Key: aws.String("KubernetesCluster"),
Value: aws.String(clusterName),
},
{
Key: aws.String(ownershipTagKey),
Value: aws.String("owned"),
},
},
})
resources, err := ListRouteTables(cloud, clusterName)
if err != nil {
t.Fatalf("error listing route tables: %v", err)
}
for _, rt := range resources {
if rt.ID == "rt-shared" && !rt.Shared {
t.Fatalf("expected Shared: true, got: %v", rt.Shared)
}
if rt.ID == "rt-owned" && rt.Shared {
t.Fatalf("expected Shared: false, got: %v", rt.Shared)
}
}
}

View File

@ -17,6 +17,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
],
)

View File

@ -18,11 +18,15 @@ package validation
import (
"fmt"
"net/url"
"time"
"net"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/apis/kops/util"
"k8s.io/kops/upup/pkg/fi"
@ -54,6 +58,32 @@ type ValidationNode struct {
Status v1.ConditionStatus `json:"status,omitempty"`
}
// HasPlaceHolderIP checks if the API DNS has been updated
func HasPlaceHolderIP(clusterName string) (bool, error) {
config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
clientcmd.NewDefaultClientConfigLoadingRules(),
&clientcmd.ConfigOverrides{CurrentContext: clusterName}).ClientConfig()
apiAddr, err := url.Parse(config.Host)
if err != nil {
return true, fmt.Errorf("unable to parse Kubernetes cluster API URL: %v", err)
}
hostAddrs, err := net.LookupHost(apiAddr.Host)
if err != nil {
return true, fmt.Errorf("unable to resolve Kubernetes cluster API URL dns: %v", err)
}
for _, h := range hostAddrs {
if h == "203.0.113.123" {
return true, nil
}
}
return false, nil
}
// ValidateCluster validate a k8s cluster with a provided instance group list
func ValidateCluster(clusterName string, instanceGroupList *kops.InstanceGroupList, clusterKubernetesClient kubernetes.Interface) (*ValidationCluster, error) {
var instanceGroups []*kops.InstanceGroup

View File

@ -99,7 +99,7 @@ spec:
- operator: Exists
initContainers:
- name: install-cni
image: quay.io/coreos/flannel:v0.9.0-amd64
image: quay.io/coreos/flannel:v0.9.1-amd64
command:
- cp
args:
@ -113,7 +113,7 @@ spec:
mountPath: /etc/kube-flannel/
containers:
- name: kube-flannel
image: quay.io/coreos/flannel:v0.9.0-amd64
image: quay.io/coreos/flannel:v0.9.1-amd64
command: [ "/opt/bin/flanneld", "--ip-masq", "--kube-subnet-mgr" ]
securityContext:
privileged: true

View File

@ -54,7 +54,7 @@ spec:
serviceAccountName: flannel
containers:
- name: kube-flannel
image: quay.io/coreos/flannel:v0.7.1
image: quay.io/coreos/flannel:v0.9.1
command: [ "/opt/bin/flanneld", "--ip-masq", "--kube-subnet-mgr" ]
securityContext:
privileged: true
@ -79,7 +79,7 @@ spec:
- name: flannel-cfg
mountPath: /etc/kube-flannel/
- name: install-cni
image: quay.io/coreos/flannel:v0.7.1
image: quay.io/coreos/flannel:v0.9.1
command: [ "/bin/sh", "-c", "set -e -x; cp -f /etc/kube-flannel/cni-conf.json /etc/cni/net.d/10-flannel.conf; while true; do sleep 3600; done" ]
resources:
limits:

View File

@ -171,7 +171,7 @@ spec:
# This container runs flannel using the kube-subnet-mgr backend
# for allocating subnets.
- name: kube-flannel
image: quay.io/coreos/flannel:v0.8.0
image: quay.io/coreos/flannel:v0.9.1
command: [ "/opt/bin/flanneld", "--ip-masq", "--kube-subnet-mgr" ]
securityContext:
privileged: true

View File

@ -3,7 +3,7 @@
# This manifest includes the following component versions:
# calico/node:v2.6.2
# calico/cni:v1.11.0
# coreos/flannel:v0.9.0
# coreos/flannel:v0.9.1
# This ConfigMap can be used to configure a self-hosted Canal installation.
kind: ConfigMap
@ -194,7 +194,7 @@ spec:
# This container runs flannel using the kube-subnet-mgr backend
# for allocating subnets.
- name: kube-flannel
image: quay.io/coreos/flannel:v0.9.0
image: quay.io/coreos/flannel:v0.9.1
command: [ "/opt/bin/flanneld", "--ip-masq", "--kube-subnet-mgr" ]
securityContext:
privileged: true

View File

@ -163,7 +163,7 @@ spec:
# This container runs flannel using the kube-subnet-mgr backend
# for allocating subnets.
- name: kube-flannel
image: quay.io/coreos/flannel:v0.8.0
image: quay.io/coreos/flannel:v0.9.1
command: [ "/opt/bin/flanneld", "--ip-masq", "--kube-subnet-mgr" ]
securityContext:
privileged: true

View File

@ -5,7 +5,7 @@ metadata:
name: calico-config
namespace: kube-system
data:
# The calico-etcd PetSet service IP:port
# etcd servers
etcd_endpoints: "{{ $cluster := index .EtcdClusters 0 -}}
{{- range $j, $member := $cluster.Members -}}
{{- if $j }},{{ end -}}
@ -18,33 +18,22 @@ data:
# The CNI network configuration to install on each node.
cni_network_config: |-
{
"name": "k8s-pod-network",
"cniVersion": "0.3.0",
"plugins": [
{
"type": "calico",
"etcd_endpoints": "__ETCD_ENDPOINTS__",
"log_level": "info",
"ipam": {
"name": "k8s-pod-network",
"type": "calico",
"etcd_endpoints": "__ETCD_ENDPOINTS__",
"log_level": "info",
"ipam": {
"type": "calico-ipam"
},
"policy": {
"type": "k8s",
"k8s_api_root": "https://__KUBERNETES_SERVICE_HOST__:__KUBERNETES_SERVICE_PORT__",
"k8s_auth_token": "__SERVICEACCOUNT_TOKEN__"
},
"kubernetes": {
"kubeconfig": "/etc/cni/net.d/__KUBECONFIG_FILENAME__"
}
},
{
"type": "portmap",
"snat": true,
"capabilities": {"portMappings": true}
"policy": {
"type": "k8s",
"k8s_api_root": "https://__KUBERNETES_SERVICE_HOST__:__KUBERNETES_SERVICE_PORT__",
"k8s_auth_token": "__SERVICEACCOUNT_TOKEN__"
},
"kubernetes": {
"kubeconfig": "/etc/cni/net.d/__KUBECONFIG_FILENAME__"
}
]
}
---
kind: ClusterRole
@ -133,12 +122,15 @@ spec:
operator: Exists
- effect: NoSchedule
operator: Exists
# Minimize downtime during a rolling upgrade or deletion; tell Kubernetes to do a "force
# deletion": https://kubernetes.io/docs/concepts/workloads/pods/pod/#termination-of-pods.
terminationGracePeriodSeconds: 0
containers:
# Runs calico/node container on each Kubernetes node. This
# container programs network policy and routes on each
# host.
- name: calico-node
image: quay.io/calico/node:v2.4.1
image: quay.io/calico/node:v2.6.2
resources:
requests:
cpu: 10m
@ -169,6 +161,14 @@ spec:
# Auto-detect the BGP IP address.
- name: IP
value: ""
# Disable IPv6 on Kubernetes.
- name: FELIX_IPV6SUPPORT
value: "false"
# Set Felix logging to "info"
- name: FELIX_LOGSEVERITYSCREEN
value: "info"
- name: FELIX_HEALTHENABLED
value: "true"
securityContext:
privileged: true
volumeMounts:
@ -185,7 +185,7 @@ spec:
# This container installs the Calico CNI binaries
# and CNI network config file on each node.
- name: install-cni
image: quay.io/calico/cni:v1.10.0
image: quay.io/calico/cni:v1.11.0
resources:
requests:
cpu: 10m
@ -194,7 +194,7 @@ spec:
env:
# The name of calico config file
- name: CNI_CONF_NAME
value: 10-calico.conflist
value: 10-calico.conf
# The location of the Calico etcd cluster.
- name: ETCD_ENDPOINTS
valueFrom:
@ -237,8 +237,8 @@ spec:
---
# This manifest deploys the Calico policy controller on Kubernetes.
# See https://github.com/projectcalico/k8s-policy
# This deployment turns off the old "policy-controller". It should remain at 0 replicas, and then
# be removed entirely once the new kube-controllers deployment has been deployed above.
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
@ -246,35 +246,23 @@ metadata:
namespace: kube-system
labels:
k8s-app: calico-policy
role.kubernetes.io/networking: "1"
spec:
# The policy controller can only have a single active instance.
replicas: 1
# Turn this deployment off in favor of the kube-controllers deployment above.
replicas: 0
strategy:
type: Recreate
template:
metadata:
name: calico-policy-controller
namespace: kube-system
labels:
k8s-app: calico-policy-controller
role.kubernetes.io/networking: "1"
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ''
k8s-app: calico-policy
spec:
# The policy controller must run in the host network namespace so that
# it isn't governed by policy that would prevent it from working.
hostNetwork: true
serviceAccountName: calico
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
- key: CriticalAddonsOnly
operator: Exists
containers:
- name: calico-policy-controller
image: quay.io/calico/kube-policy-controller:v0.7.0
resources:
requests:
cpu: 10m
image: quay.io/calico/kube-controllers:v1.0.0
env:
# The location of the Calico etcd cluster.
- name: ETCD_ENDPOINTS
@ -282,15 +270,6 @@ spec:
configMapKeyRef:
name: calico-config
key: etcd_endpoints
# The location of the Kubernetes API. Use the default Kubernetes
# service for API access.
- name: K8S_API
value: "https://kubernetes.default:443"
# Since we're running in the host namespace and might not have KubeDNS
# access, configure the container's /etc/hosts to resolve
# kubernetes.default to the correct service clusterIP.
- name: CONFIGURE_ETC_HOSTS
value: "true"
volumeMounts:
# Necessary for gossip based DNS
@ -301,6 +280,55 @@ spec:
- name: etc-hosts
hostPath:
path: /etc/hosts
---
# This manifest deploys the Calico Kubernetes controllers.
# See https://github.com/projectcalico/kube-controllers
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: calico-kube-controllers
namespace: kube-system
labels:
k8s-app: calico-kube-controllers
role.kubernetes.io/networking: "1"
spec:
# The controllers can only have a single active instance.
replicas: 1
template:
metadata:
name: calico-kube-controllers
namespace: kube-system
labels:
k8s-app: calico-kube-controllers
role.kubernetes.io/networking: "1"
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ''
spec:
# The controllers must run in the host network namespace so that
# it isn't governed by policy that would prevent it from working.
hostNetwork: true
serviceAccountName: calico
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
- key: CriticalAddonsOnly
operator: Exists
containers:
- name: calico-kube-controllers
image: quay.io/calico/kube-controllers:v1.0.0
resources:
requests:
cpu: 10m
env:
# The location of the Calico etcd cluster.
- name: ETCD_ENDPOINTS
valueFrom:
configMapKeyRef:
name: calico-config
key: etcd_endpoints
{{ if and (eq .CloudProvider "aws") (.Networking.Calico.CrossSubnet) -}}
# This manifest installs the k8s-ec2-srcdst container, which disables

View File

@ -16,11 +16,10 @@ data:
calico_backend: "bird"
# The CNI network configuration to install on each node.
# cniVersion should be 0.1.0 on k8s: https://github.com/projectcalico/calico/issues/742
cni_network_config: |-
{
"name": "k8s-pod-network",
"cniVersion": "0.1.0",
"cniVersion": "0.3.0",
"plugins": [
{
"type": "calico",

View File

@ -137,7 +137,7 @@ spec:
effect: NoSchedule
containers:
- name: romana-daemon
image: quay.io/romana/daemon:v2.0-preview.2
image: quay.io/romana/daemon:v2.0.0
imagePullPolicy: Always
resources:
requests:
@ -170,7 +170,7 @@ spec:
effect: NoSchedule
containers:
- name: romana-listener
image: quay.io/romana/listener:v2.0-preview.2
image: quay.io/romana/listener:v2.0.0
imagePullPolicy: Always
resources:
requests:
@ -185,6 +185,8 @@ metadata:
name: romana-agent
namespace: kube-system
spec:
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
@ -200,7 +202,7 @@ spec:
effect: NoSchedule
containers:
- name: romana-agent
image: quay.io/romana/agent:v2.0-preview.2
image: quay.io/romana/agent:v2.0.0
imagePullPolicy: Always
resources:
requests:
@ -213,6 +215,10 @@ spec:
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: NODEIP
valueFrom:
fieldRef:
fieldPath: status.hostIP
args:
- --service-cluster-ip-range={{ .ServiceClusterIPRange }}
securityContext:
@ -299,7 +305,7 @@ spec:
effect: NoSchedule
containers:
- name: romana-aws
image: quay.io/romana/aws:v2.0-preview.2
image: quay.io/romana/aws:v2.0.0
imagePullPolicy: Always
resources:
requests:
@ -328,7 +334,7 @@ spec:
effect: NoSchedule
containers:
- name: romana-vpcrouter
image: quay.io/romana/vpcrouter-romana-plugin
image: quay.io/romana/vpcrouter-romana-plugin:1.1.12
imagePullPolicy: Always
resources:
requests:

View File

@ -185,6 +185,14 @@ func NewAWSCloud(region string, tags map[string]string) (AWSCloud, error) {
config = config.WithCredentialsChainVerboseErrors(true)
config = request.WithRetryer(config, newLoggingRetryer(ClientMaxRetries))
// We have the updated aws sdk from 1.9, but don't have https://github.com/kubernetes/kubernetes/pull/55307
// Set the SleepDelay function to work around this
// TODO: Remove once we update to k8s >= 1.9 (or a version of the retry delayer than includes this)
config.SleepDelay = func(d time.Duration) {
glog.V(6).Infof("aws request sleeping for %v", d)
time.Sleep(d)
}
requestLogger := newRequestLogger(2)
sess, err := session.NewSession(config)

View File

@ -133,8 +133,11 @@ func (c *MockAWSCloud) BuildTags(name *string) map[string]string {
}
func (c *MockAWSCloud) Tags() map[string]string {
glog.Fatalf("MockAWSCloud Tags not implemented")
return nil
tags := make(map[string]string)
for k, v := range c.tags {
tags[k] = v
}
return tags
}
func (c *MockAWSCloud) CreateTags(resourceId string, tags map[string]string) error {

View File

@ -468,11 +468,10 @@ func (b *BootstrapChannelBuilder) buildManifest() (*channelsapi.Addons, map[stri
if b.cluster.Spec.Networking.Calico != nil {
key := "networking.projectcalico.org"
// 2.6.3-kops.1 = 2.6.2 with kops manifest tweaks. This should go away with the next version bump.
versions := map[string]string{
"pre-k8s-1.6": "2.4.1",
"k8s-1.6": "2.4.2-kops.1",
"k8s-1.8": "2.6.3-kops.1",
"k8s-1.6": "2.6.2",
"k8s-1.7": "2.6.2",
}
{
@ -499,14 +498,14 @@ func (b *BootstrapChannelBuilder) buildManifest() (*channelsapi.Addons, map[stri
Version: fi.String(versions[id]),
Selector: networkingSelector,
Manifest: fi.String(location),
KubernetesVersion: ">=1.6.0 <1.8.0",
KubernetesVersion: ">=1.6.0 <1.7.0",
Id: id,
})
manifests[key+"-"+id] = "addons/" + location
}
{
id := "k8s-1.8"
id := "k8s-1.7"
location := key + "/" + id + ".yaml"
addons.Spec.Addons = append(addons.Spec.Addons, &channelsapi.AddonSpec{
@ -514,7 +513,7 @@ func (b *BootstrapChannelBuilder) buildManifest() (*channelsapi.Addons, map[stri
Version: fi.String(versions[id]),
Selector: networkingSelector,
Manifest: fi.String(location),
KubernetesVersion: ">=1.8.0",
KubernetesVersion: ">=1.7.0",
Id: id,
})
manifests[key+"-"+id] = "addons/" + location
@ -598,18 +597,18 @@ func (b *BootstrapChannelBuilder) buildManifest() (*channelsapi.Addons, map[stri
if b.cluster.Spec.Networking.Romana != nil {
key := "networking.romana"
version := "v2.0-preview.3"
version := "v2.0.0"
{
location := key + "/k8s-1.6.yaml"
id := "k8s-1.6"
location := key + "/k8s-1.7.yaml"
id := "k8s-1.7"
addons.Spec.Addons = append(addons.Spec.Addons, &channelsapi.AddonSpec{
Name: fi.String(key),
Version: fi.String(version),
Selector: networkingSelector,
Manifest: fi.String(location),
KubernetesVersion: ">=1.6.0",
KubernetesVersion: ">=1.7.0",
Id: id,
})
manifests[key+"-"+id] = "addons/" + location

View File

@ -12,7 +12,13 @@ go_library(
"//pkg/apis/kops:go_default_library",
"//pkg/cloudinstances:go_default_library",
"//upup/pkg/fi:go_default_library",
"//util/pkg/vfs:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/gophercloud/gophercloud:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/kubernetes/federation/pkg/dnsprovider:go_default_library",
],
)

View File

@ -18,25 +18,90 @@ package openstack
import (
"fmt"
"time"
"github.com/golang/glog"
"github.com/gophercloud/gophercloud"
os "github.com/gophercloud/gophercloud/openstack"
cinder "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/cloudinstances"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/util/pkg/vfs"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
)
const TagNameEtcdClusterPrefix = "k8s.io/etcd/"
const TagNameRolePrefix = "k8s.io/role/"
const TagClusterName = "KubernetesCluster"
// readBackoff is the backoff strategy for openstack read retries.
var readBackoff = wait.Backoff{
Duration: time.Second,
Factor: 1.5,
Jitter: 0.1,
Steps: 4,
}
// writeBackoff is the backoff strategy for openstack write retries.
var writeBackoff = wait.Backoff{
Duration: time.Second,
Factor: 1.5,
Jitter: 0.1,
Steps: 5,
}
type OpenstackCloud interface {
fi.Cloud
// SetVolumeTags will set the tags for the Cinder volume
SetVolumeTags(id string, tags map[string]string) error
// GetCloudTags will return the tags attached on cloud
GetCloudTags() map[string]string
// ListVolumes will return the Cinder volumes which match the options
ListVolumes(opt cinder.ListOpts) ([]cinder.Volume, error)
// CreateVolume will create a new Cinder Volume
CreateVolume(opt cinder.CreateOpts) (*cinder.Volume, error)
}
type openstackCloud struct {
cinderClient *gophercloud.ServiceClient
tags map[string]string
}
var _ fi.Cloud = &openstackCloud{}
func NewOpenstackCloud() (OpenstackCloud, error) {
return &openstackCloud{}, nil
func NewOpenstackCloud(tags map[string]string) (OpenstackCloud, error) {
config := vfs.OpenstackConfig{}
authOption, err := config.GetCredential()
if err != nil {
return nil, err
}
provider, err := os.AuthenticatedClient(authOption)
if err != nil {
return nil, fmt.Errorf("error building openstack authenticated client: %v", err)
}
endpointOpt, err := config.GetServiceConfig("Cinder")
if err != nil {
return nil, err
}
cinderClient, err := os.NewBlockStorageV2(provider, endpointOpt)
if err != nil {
return nil, fmt.Errorf("error building swift client: %v", err)
}
c := &openstackCloud{
cinderClient: cinderClient,
tags: tags,
}
return c, nil
}
func (c *openstackCloud) ProviderID() kops.CloudProviderID {
@ -62,3 +127,78 @@ func (c *openstackCloud) DeleteGroup(g *cloudinstances.CloudInstanceGroup) error
func (c *openstackCloud) GetCloudGroups(cluster *kops.Cluster, instancegroups []*kops.InstanceGroup, warnUnmatched bool, nodes []v1.Node) (map[string]*cloudinstances.CloudInstanceGroup, error) {
return nil, fmt.Errorf("openstackCloud::GetCloudGroups not implemented")
}
func (c *openstackCloud) SetVolumeTags(id string, tags map[string]string) error {
if len(tags) == 0 {
return nil
}
if id == "" {
return fmt.Errorf("error setting tags to unknown volume")
}
glog.V(4).Infof("setting tags to cinder volume %q: %v", id, tags)
opt := cinder.UpdateOpts{Metadata: tags}
done, err := vfs.RetryWithBackoff(writeBackoff, func() (bool, error) {
_, err := cinder.Update(c.cinderClient, id, opt).Extract()
if err != nil {
return false, fmt.Errorf("error setting tags to cinder volume %q: %v", id, err)
}
return true, nil
})
if err != nil {
return err
} else if done {
return nil
} else {
return wait.ErrWaitTimeout
}
}
func (c *openstackCloud) GetCloudTags() map[string]string {
return c.tags
}
func (c *openstackCloud) ListVolumes(opt cinder.ListOpts) ([]cinder.Volume, error) {
var volumes []cinder.Volume
done, err := vfs.RetryWithBackoff(readBackoff, func() (bool, error) {
allPages, err := cinder.List(c.cinderClient, opt).AllPages()
if err != nil {
return false, fmt.Errorf("error listing volumes %v: %v", opt, err)
}
vs, err := cinder.ExtractVolumes(allPages)
if err != nil {
return false, fmt.Errorf("error extracting volumes from pages: %v", err)
}
volumes = vs
return true, nil
})
if err != nil {
return volumes, err
} else if done {
return volumes, nil
} else {
return volumes, wait.ErrWaitTimeout
}
}
func (c *openstackCloud) CreateVolume(opt cinder.CreateOpts) (*cinder.Volume, error) {
var volume *cinder.Volume
done, err := vfs.RetryWithBackoff(writeBackoff, func() (bool, error) {
v, err := cinder.Create(c.cinderClient, opt).Extract()
if err != nil {
return false, fmt.Errorf("error creating volume %v: %v", opt, err)
}
volume = v
return true, nil
})
if err != nil {
return volume, err
} else if done {
return volume, nil
} else {
return volume, wait.ErrWaitTimeout
}
}

View File

@ -0,0 +1,14 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["volume.go"],
importpath = "k8s.io/kops/upup/pkg/fi/cloudup/openstacktasks",
visibility = ["//visibility:public"],
deps = [
"//upup/pkg/fi:go_default_library",
"//upup/pkg/fi/cloudup/openstack:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes:go_default_library",
],
)

View File

@ -0,0 +1,145 @@
/*
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 openstacktasks
import (
"fmt"
"github.com/golang/glog"
cinder "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/openstack"
)
type Volume struct {
ID *string
Name *string
AvailabilityZone *string
VolumeType *string
SizeGB *int64
Tags map[string]string
Lifecycle *fi.Lifecycle
}
var _ fi.CompareWithID = &Volume{}
func (c *Volume) CompareWithID() *string {
return c.ID
}
func (c *Volume) Find(context *fi.Context) (*Volume, error) {
cloud := context.Cloud.(openstack.OpenstackCloud)
opt := cinder.ListOpts{
Name: fi.StringValue(c.Name),
Metadata: cloud.GetCloudTags(),
}
volumes, err := cloud.ListVolumes(opt)
if err != nil {
return nil, err
}
n := len(volumes)
if n == 0 {
return nil, nil
} else if n != 1 {
return nil, fmt.Errorf("found multiple Volumes with name: %s", fi.StringValue(c.Name))
}
v := volumes[0]
actual := &Volume{
ID: fi.String(v.ID),
Name: fi.String(v.Name),
AvailabilityZone: fi.String(v.AvailabilityZone),
VolumeType: fi.String(v.VolumeType),
SizeGB: fi.Int64(int64(v.Size)),
Tags: v.Metadata,
Lifecycle: c.Lifecycle,
}
return actual, nil
}
func (c *Volume) Run(context *fi.Context) error {
cloud := context.Cloud.(openstack.OpenstackCloud)
for k, v := range cloud.GetCloudTags() {
c.Tags[k] = v
}
return fi.DefaultDeltaRunMethod(c, context)
}
func (_ *Volume) CheckChanges(a, e, changes *Volume) error {
if a == nil {
if e.Name == nil {
return fi.RequiredField("Name")
}
if e.AvailabilityZone == nil {
return fi.RequiredField("AvailabilityZone")
}
if e.VolumeType == nil {
return fi.RequiredField("VolumeType")
}
if e.SizeGB == nil {
return fi.RequiredField("SizeGB")
}
} else {
if changes.ID != nil {
return fi.CannotChangeField("ID")
}
if changes.AvailabilityZone != nil {
return fi.CannotChangeField("AvailabilityZone")
}
if changes.VolumeType != nil {
return fi.CannotChangeField("VolumeType")
}
if changes.SizeGB != nil {
return fi.CannotChangeField("SizeGB")
}
}
return nil
}
func (_ *Volume) RenderOpenstack(t *openstack.OpenstackAPITarget, a, e, changes *Volume) error {
if a == nil {
glog.V(2).Infof("Creating PersistentVolume with Name:%q", fi.StringValue(e.Name))
opt := cinder.CreateOpts{
Size: int(*e.SizeGB),
AvailabilityZone: fi.StringValue(e.AvailabilityZone),
Metadata: e.Tags,
Name: fi.StringValue(e.Name),
VolumeType: fi.StringValue(e.VolumeType),
}
v, err := t.Cloud.CreateVolume(opt)
if err != nil {
return fmt.Errorf("error creating PersistentVolume: %v", err)
}
e.ID = fi.String(v.ID)
return nil
}
if changes != nil && changes.Tags != nil {
glog.V(2).Infof("Update the tags on volume %q: %v, the differences are %v", fi.StringValue(e.ID), e.Tags, changes.Tags)
err := t.Cloud.SetVolumeTags(fi.StringValue(e.ID), e.Tags)
if err != nil {
return fmt.Errorf("error updating the tags on volume %q: %v", fi.StringValue(e.ID), err)
}
}
glog.V(2).Infof("Openstack task Volume::RenderOpenstack did nothing")
return nil
}

View File

@ -133,7 +133,8 @@ func BuildCloud(cluster *kops.Cluster) (fi.Cloud, error) {
}
case kops.CloudProviderOpenstack:
{
osc, err := openstack.NewOpenstackCloud()
cloudTags := map[string]string{openstack.TagClusterName: cluster.ObjectMeta.Name}
osc, err := openstack.NewOpenstackCloud(cloudTags)
if err != nil {
return nil, err
}

View File

@ -32,8 +32,10 @@ type SecretStore interface {
DeleteSecret(item *KeystoreItem) error
// FindSecret finds a secret, if exists. Returns nil,nil if not found
FindSecret(id string) (*Secret, error)
// GetOrCreateSecret creates or replace a secret
// GetOrCreateSecret creates a secret
GetOrCreateSecret(id string, secret *Secret) (current *Secret, created bool, err error)
// ReplaceSecret will forcefully update an existing secret if it exists
ReplaceSecret(id string, secret *Secret) (current *Secret, err error)
// ListSecrets lists the ids of all known secrets
ListSecrets() ([]string, error)

View File

@ -157,7 +157,7 @@ func (c *ClientsetSecretStore) GetOrCreateSecret(name string, secret *fi.Secret)
return s, false, nil
}
_, err = c.createSecret(secret, name)
_, err = c.createSecret(secret, name, false)
if err != nil {
if errors.IsAlreadyExists(err) && i == 0 {
glog.Infof("Got already-exists error when writing secret; likely due to concurrent creation. Will retry")
@ -181,6 +181,21 @@ func (c *ClientsetSecretStore) GetOrCreateSecret(name string, secret *fi.Secret)
return s, true, nil
}
// ReplaceSecret implements fi.SecretStore::ReplaceSecret
func (c *ClientsetSecretStore) ReplaceSecret(name string, secret *fi.Secret) (*fi.Secret, error) {
_, err := c.createSecret(secret, name, true)
if err != nil {
return nil, fmt.Errorf("unable to write secret: %v", err)
}
// Confirm the secret exists
s, err := c.loadSecret(name)
if err != nil {
return nil, fmt.Errorf("unable to load secret immmediately after creation: %v", err)
}
return s, nil
}
// loadSecret returns the named secret, if it exists, otherwise returns nil
func (c *ClientsetSecretStore) loadSecret(name string) (*fi.Secret, error) {
name = NamePrefix + name
@ -207,8 +222,8 @@ func parseSecret(keyset *kops.Keyset) (*fi.Secret, error) {
return s, nil
}
// createSecret writes the secret, but only if it does not exist
func (c *ClientsetSecretStore) createSecret(s *fi.Secret, name string) (*kops.Keyset, error) {
// createSecret will create the Secret, overwriting an existing secret if replace is true
func (c *ClientsetSecretStore) createSecret(s *fi.Secret, name string, replace bool) (*kops.Keyset, error) {
keyset := &kops.Keyset{}
keyset.Name = NamePrefix + name
keyset.Spec.Type = kops.SecretTypeSecret
@ -221,5 +236,8 @@ func (c *ClientsetSecretStore) createSecret(s *fi.Secret, name string) (*kops.Ke
PrivateMaterial: s.Data,
})
if replace {
return c.clientset.Keysets(c.namespace).Update(keyset)
}
return c.clientset.Keysets(c.namespace).Create(keyset)
}

View File

@ -127,7 +127,7 @@ func (c *VFSSecretStore) GetOrCreateSecret(id string, secret *fi.Secret) (*fi.Se
return nil, false, err
}
err = c.createSecret(secret, p, acl)
err = c.createSecret(secret, p, acl, false)
if err != nil {
if os.IsExist(err) && i == 0 {
glog.Infof("Got already-exists error when writing secret; likely due to concurrent creation. Will retry")
@ -151,6 +151,27 @@ func (c *VFSSecretStore) GetOrCreateSecret(id string, secret *fi.Secret) (*fi.Se
return s, true, nil
}
func (c *VFSSecretStore) ReplaceSecret(id string, secret *fi.Secret) (*fi.Secret, error) {
p := c.buildSecretPath(id)
acl, err := acls.GetACL(p, c.cluster)
if err != nil {
return nil, err
}
err = c.createSecret(secret, p, acl, true)
if err != nil {
return nil, fmt.Errorf("unable to write secret: %v", err)
}
// Confirm the secret exists
s, err := c.loadSecret(p)
if err != nil {
return nil, fmt.Errorf("unable to load secret immmediately after creation %v: %v", p, err)
}
return s, nil
}
func (c *VFSSecretStore) loadSecret(p vfs.Path) (*fi.Secret, error) {
data, err := p.ReadFile()
if err != nil {
@ -166,11 +187,15 @@ func (c *VFSSecretStore) loadSecret(p vfs.Path) (*fi.Secret, error) {
return s, nil
}
// createSecret writes the secret, but only if it does not exists
func (c *VFSSecretStore) createSecret(s *fi.Secret, p vfs.Path, acl vfs.ACL) error {
// createSecret will create the Secret, overwriting an existing secret if replace is true
func (c *VFSSecretStore) createSecret(s *fi.Secret, p vfs.Path, acl vfs.ACL, replace bool) error {
data, err := json.Marshal(s)
if err != nil {
return fmt.Errorf("error serializing secret: %v", err)
}
if replace {
return p.WriteFile(data, acl)
}
return p.CreateFile(data, acl)
}