Federation build and e2e test integration

Federation components are now buildable and e2e-testable via e2e.go.
pull/6/head
Colin Hom 2016-05-10 14:44:45 -07:00
parent 025b017277
commit a092fd2dd4
34 changed files with 926 additions and 48 deletions

View File

@ -98,6 +98,7 @@ kube::build::get_docker_wrapped_binaries() {
kube-controller-manager,busybox
kube-scheduler,busybox
kube-proxy,gcr.io/google_containers/debian-iptables-amd64:v3
federation-apiserver,busybox
);;
"arm")
local targets=(
@ -105,6 +106,7 @@ kube::build::get_docker_wrapped_binaries() {
kube-controller-manager,armel/busybox
kube-scheduler,armel/busybox
kube-proxy,gcr.io/google_containers/debian-iptables-arm:v3
federation-apiserver,armel/busybox
);;
"arm64")
local targets=(
@ -112,6 +114,7 @@ kube::build::get_docker_wrapped_binaries() {
kube-controller-manager,aarch64/busybox
kube-scheduler,aarch64/busybox
kube-proxy,gcr.io/google_containers/debian-iptables-arm64:v3
federation-apiserver,aarch64/busybox
);;
"ppc64le")
local targets=(
@ -119,6 +122,7 @@ kube::build::get_docker_wrapped_binaries() {
kube-controller-manager,ppc64le/busybox
kube-scheduler,ppc64le/busybox
kube-proxy,gcr.io/google_containers/debian-iptables-ppc64le:v3
federation-apiserver,ppc64le/busybox
);;
esac
@ -1002,6 +1006,11 @@ function kube::release::package_full_tarball() {
mkdir -p "${release_stage}/third_party"
cp -R "${KUBE_ROOT}/third_party/htpasswd" "${release_stage}/third_party/htpasswd"
# Include only federation/cluster and federation/manifests
mkdir "${release_stage}/federation"
cp -R "${KUBE_ROOT}/federation/cluster" "${release_stage}/federation/"
cp -R "${KUBE_ROOT}/federation/manifests" "${release_stage}/federation/"
cp -R "${KUBE_ROOT}/examples" "${release_stage}/"
cp -R "${KUBE_ROOT}/docs" "${release_stage}/"
cp "${KUBE_ROOT}/README.md" "${release_stage}/"

View File

@ -20,20 +20,10 @@ set -o errexit
set -o nounset
set -o pipefail
# TODO(zmerlynn): Blech, this belongs in build/common.sh, probably,
# but common.sh sets up its readonly variables when its sourced, so
# there's a chicken/egg issue getting it there and using it for
# KUBE_GCE_RELEASE_PREFIX.
function kube::release::semantic_version() {
# This takes:
# Client Version: version.Info{Major:"1", Minor:"1+", GitVersion:"v1.1.0-alpha.0.2328+3c0a05de4a38e3", GitCommit:"3c0a05de4a38e355d147dbfb4d85bad6d2d73bb9", GitTreeState:"clean"}
# and spits back the GitVersion piece in a way that is somewhat
# resilient to the other fields changing (we hope)
${KUBE_ROOT}/cluster/kubectl.sh version -c | sed "s/, */\\
/g" | egrep "^GitVersion:" | cut -f2 -d: | cut -f2 -d\"
}
KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
source "${KUBE_ROOT}/build/util.sh"
LATEST=$(kube::release::semantic_version)
KUBE_GCS_NO_CACHING='n'
@ -44,7 +34,7 @@ KUBE_GCS_DELETE_EXISTING='y'
KUBE_GCS_RELEASE_PREFIX="ci/${LATEST}"
KUBE_GCS_PUBLISH_VERSION="${LATEST}"
source "$KUBE_ROOT/build/common.sh"
source "${KUBE_ROOT}/build/common.sh"
MAX_ATTEMPTS=3
attempt=0
@ -53,4 +43,13 @@ while [[ ${attempt} -lt ${MAX_ATTEMPTS} ]]; do
attempt=$((attempt + 1))
sleep 5
done
[[ ${attempt} -lt ${MAX_ATTEMPTS} ]] || exit 1
if [[ ! ${attempt} -lt ${MAX_ATTEMPTS} ]];then
kube::log::error "Max attempts reached. Will exit."
exit 1
fi
if [[ "${FEDERATION:-}" == "true" ]];then
source "${KUBE_ROOT}/federation/cluster/common.sh"
# Docker compatiblity
FEDERATION_IMAGE_TAG="$(kube::release::semantic_image_tag_version)" push-federated-images
fi

View File

@ -37,6 +37,14 @@ if [[ $KUBE_RELEASE_RUN_TESTS =~ ^[yY]$ ]]; then
kube::build::run_build_command hack/test-integration.sh
fi
if [[ "${FEDERATION:-}" == "true" ]];then
(
source "${KUBE_ROOT}/build/util.sh"
# Write federated docker image tag to workspace
kube::release::semantic_image_tag_version > "${KUBE_ROOT}/federation/manifests/federated-image.tag"
)
fi
kube::build::copy_output
kube::release::package_tarballs
kube::release::package_hyperkube

32
build/util.sh Normal file
View File

@ -0,0 +1,32 @@
#!/bin/bash
# Copyright 2016 The Kubernetes Authors All rights reserved.
#
# 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.
# Common utility functions for build scripts
KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
function kube::release::semantic_version() {
# This takes:
# Client Version: version.Info{Major:"1", Minor:"1+", GitVersion:"v1.1.0-alpha.0.2328+3c0a05de4a38e3", GitCommit:"3c0a05de4a38e355d147dbfb4d85bad6d2d73bb9", GitTreeState:"clean"}
# and spits back the GitVersion piece in a way that is somewhat
# resilient to the other fields changing (we hope)
${KUBE_ROOT}/cluster/kubectl.sh version -c | sed "s/, */\\
/g" | egrep "^GitVersion:" | cut -f2 -d: | cut -f2 -d\"
}
function kube::release::semantic_image_tag_version() {
printf "$(kube::release::semantic_version)" | tr + _
}

View File

@ -38,7 +38,6 @@ KUBE_RELEASE_VERSION_REGEX="^v(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*
# kube::release::parse_and_validate_ci_version()
KUBE_CI_VERSION_REGEX="^v(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)-(beta|alpha)\\.(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*)\\+[-0-9a-z]*)?$"
# Generate kubeconfig data for the created cluster.
# Assumed vars:
# KUBE_USER
@ -50,12 +49,23 @@ KUBE_CI_VERSION_REGEX="^v(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)-(be
# If the apiserver supports bearer auth, also provide:
# KUBE_BEARER_TOKEN
#
# If the kubeconfig context being created should NOT be set as the current context
# SECONDARY_KUBECONFIG=true
#
# To explicitly name the context being created, use OVERRIDE_CONTEXT
#
# The following can be omitted for --insecure-skip-tls-verify
# KUBE_CERT
# KUBE_KEY
# CA_CERT
function create-kubeconfig() {
local kubectl="${KUBE_ROOT}/cluster/kubectl.sh"
SECONDARY_KUBECONFIG=${SECONDARY_KUBECONFIG:-}
OVERRIDE_CONTEXT=${OVERRIDE_CONTEXT:-}
if [[ "$OVERRIDE_CONTEXT" != "" ]];then
CONTEXT=$OVERRIDE_CONTEXT
fi
export KUBECONFIG=${KUBECONFIG:-$DEFAULT_KUBECONFIG}
# KUBECONFIG determines the file we write to, but it may not exist yet
@ -99,7 +109,10 @@ function create-kubeconfig() {
"${kubectl}" config set-credentials "${CONTEXT}" "${user_args[@]}"
fi
"${kubectl}" config set-context "${CONTEXT}" --cluster="${CONTEXT}" --user="${CONTEXT}"
if [[ "${SECONDARY_KUBECONFIG}" != "true" ]];then
"${kubectl}" config use-context "${CONTEXT}" --cluster="${CONTEXT}"
fi
# If we have a bearer token, also create a credential entry with basic auth
# so that it is easy to discover the basic auth password for your cluster

View File

@ -99,3 +99,33 @@ PROVIDER_UTILS="${KUBE_ROOT}/cluster/${KUBERNETES_PROVIDER}/util.sh"
if [ -f ${PROVIDER_UTILS} ]; then
source "${PROVIDER_UTILS}"
fi
# Federation utils
# Should NOT be called within the global scope, unless setting the desired global zone vars
# This function is currently NOT USED in the global scope
function set-federated-zone-vars {
zone="$1"
export OVERRIDE_CONTEXT="federation-e2e-${KUBERNETES_PROVIDER}-$zone"
echo "Setting zone vars to: $OVERRIDE_CONTEXT"
if [[ "$KUBERNETES_PROVIDER" == "gce" ]];then
export KUBE_GCE_ZONE="$zone"
# gcloud has a 61 character limit, and for firewall rules this
# prefix gets appended to itslef, with some extra information
# need tot keep it short
export KUBE_GCE_INSTANCE_PREFIX="${USER}-${zone}"
elif [[ "$KUBERNETES_PROVIDER" == "gke" ]];then
export CLUSTER_NAME="${USER}-${zone}"
elif [[ "$KUBERNETES_PROVIDER" == "aws" ]];then
export KUBE_AWS_ZONE="$zone"
export KUBE_AWS_INSTANCE_PREFIX="${USER}-${zone}"
else
echo "Provider \"${KUBERNETES_PROVIDER}\" is not supported"
exit 1
fi
}

View File

@ -118,7 +118,7 @@ if ! which /usr/libexec/cockpit-ws &>/dev/null; then
pushd /etc/yum.repos.d
curl -OL https://copr.fedorainfracloud.org/coprs/g/cockpit/cockpit-preview/repo/fedora-23/msuchy-cockpit-preview-fedora-23.repo
dnf install -y cockpit cockpit-kubernetes
dnf install -y cockpit cockpit-kubernetes socat ethtool
dnf update -y docker
popd

View File

@ -83,3 +83,5 @@ add-volume-support
run-salt
dnf install -y socat ethtool
dnf update -y docker

View File

@ -0,0 +1,189 @@
# Copyright 2014 The Kubernetes Authors All rights reserved.
#
# 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.
# required:
# KUBE_ROOT: path of the root of the Kubernetes reposiitory
# optional override
# FEDERATION_IMAGE_REPO_BASE: repo which federated images are tagged under (default gcr.io/google_containers)
# FEDERATION_NAMESPACE: name of the namespace will created for the federated components in the underlying cluster.
# KUBE_PLATFORM
# KUBE_ARCH
# KUBE_BUILD_STAGE
: "${KUBE_ROOT?Must set KUBE_ROOT env var}"
FEDERATION_IMAGE_REPO_BASE=${FEDERATION_IMAGE_REPO_BASE:-'gcr.io/google_containers'}
FEDERATION_NAMESPACE=${FEDERATION_NAMESPACE:-federation-e2e}
KUBE_PLATFORM=${KUBE_PLATFORM:-linux}
KUBE_ARCH=${KUBE_ARCH:-amd64}
KUBE_BUILD_STAGE=${KUBE_BUILD_STAGE:-release-stage}
source "${KUBE_ROOT}/cluster/common.sh"
host_kubectl="${KUBE_ROOT}/cluster/kubectl.sh --namespace=${FEDERATION_NAMESPACE}"
# required:
# FEDERATION_PUSH_REPO_BASE: repo to which federated container images will be pushed
# Optional
# FEDERATION_IMAGE_TAG: reference and pull all federated images with this tag. Used for ci testing
function create-federated-api-objects {
(
: "${FEDERATION_PUSH_REPO_BASE?Must set FEDERATION_PUSH_REPO_BASE env var}"
export FEDERATION_APISERVER_DEPLOYMENT_NAME="federation-apiserver"
export FEDERATION_APISERVER_IMAGE_REPO="${FEDERATION_PUSH_REPO_BASE}/federation-apiserver"
export FEDERATION_APISERVER_IMAGE_TAG="${FEDERATION_IMAGE_TAG:-$(cat ${KUBE_ROOT}/_output/${KUBE_BUILD_STAGE}/server/${KUBE_PLATFORM}-${KUBE_ARCH}/kubernetes/server/bin/federation-apiserver.docker_tag)}"
export FEDERATION_SERVICE_CIDR=${FEDERATION_SERVICE_CIDR:-"10.10.0.0/24"}
#Only used for providers that require a nodeport service (vagrant for now)
#We will use loadbalancer services where we can
export FEDERATION_API_NODEPORT=32111
export FEDERATION_NAMESPACE
template="go run ${KUBE_ROOT}/federation/cluster/template.go"
FEDERATION_KUBECONFIG_PATH="${KUBE_ROOT}/federation/cluster/kubeconfig"
federation_kubectl="${KUBE_ROOT}/cluster/kubectl.sh --context=federated-cluster --namespace=default"
manifests_root="${KUBE_ROOT}/federation/manifests/"
$template "${manifests_root}/federation-ns.yaml" | $host_kubectl apply -f -
cleanup-federated-api-objects
export FEDERATION_API_HOST=""
export KUBE_MASTER_IP=""
if [[ "$KUBERNETES_PROVIDER" == "vagrant" ]];then
# The vagrant approach is to use a nodeport service, and point kubectl at one of the nodes
$template "${manifests_root}/federation-apiserver-nodeport-service.yaml" | $host_kubectl create -f -
node_addresses=`$host_kubectl get nodes -o=jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}'`
FEDERATION_API_HOST=`printf "$node_addresses" | cut -d " " -f1`
KUBE_MASTER_IP="${FEDERATION_API_HOST}:${FEDERATION_API_NODEPORT}"
elif [[ "$KUBERNETES_PROVIDER" == "gce" || "$KUBERNETES_PROVIDER" == "gke" || "$KUBERNETES_PROVIDER" == "aws" ]];then
# any capable providers should use a loadbalancer service
# we check for ingress.ip and ingress.hostname, so should work for any loadbalancer-providing provider
# allows 30x5 = 150 seconds for loadbalancer creation
$template "${manifests_root}/federation-apiserver-lb-service.yaml" | $host_kubectl create -f -
for i in {1..30};do
echo "attempting to get federation-apiserver loadbalancer hostname ($i / 30)"
for field in ip hostname;do
FEDERATION_API_HOST=`${host_kubectl} get -o=jsonpath svc/${FEDERATION_APISERVER_DEPLOYMENT_NAME} --template '{.status.loadBalancer.ingress[*].'"${field}}"`
if [[ ! -z "${FEDERATION_API_HOST// }" ]];then
break 2
fi
done
if [[ $i -eq 30 ]];then
echo "Could not find ingress hostname for federation-apiserver loadbalancer service"
exit 1
fi
sleep 5
done
KUBE_MASTER_IP="${FEDERATION_API_HOST}:443"
else
echo "provider ${KUBERNETES_PROVIDER} is not (yet) supported for e2e testing"
exit 1
fi
echo "Found federation-apiserver host at $FEDERATION_API_HOST"
FEDERATION_API_TOKEN="$(dd if=/dev/urandom bs=128 count=1 2>/dev/null | base64 | tr -d "=+/" | dd bs=32 count=1 2>/dev/null)"
export FEDERATION_API_KNOWN_TOKENS="${FEDERATION_API_TOKEN},admin,admin"
$template "${manifests_root}/federation-apiserver-"{deployment,secrets}".yaml" | $host_kubectl create -f -
# Don't finish provisioning until federation-apiserver pod is running
for i in {1..30};do
#TODO(colhom): in the future this needs to scale out for N pods. This assumes just one pod
phase="$($host_kubectl get -o=jsonpath pods -lapp=federated-cluster,module=federation-apiserver --template '{.items[*].status.phase}')"
echo "Waiting for federation-apiserver to be running...(phase= $phase)"
if [[ "$phase" == "Running" ]];then
echo "federation-apiserver pod is running!"
break
fi
if [[ $i -eq 30 ]];then
echo "federation-apiserver pod is not running! giving up."
exit 1
fi
sleep 4
done
CONTEXT=federated-cluster \
KUBE_BEARER_TOKEN="$FEDERATION_API_TOKEN" \
SECONDARY_KUBECONFIG=true \
create-kubeconfig
)
}
# Required
# FEDERATION_PUSH_REPO_BASE: the docker repo where federated images will be pushed
# Optional
# FEDERATION_IMAGE_TAG: push all federated images with this tag. Used for ci testing
function push-federated-images {
: "${FEDERATION_PUSH_REPO_BASE?Must set FEDERATION_PUSH_REPO_BASE env var}"
local FEDERATION_BINARIES=${FEDERATION_BINARIES:-'federation-apiserver'}
local imageFolder="${KUBE_ROOT}/_output/${KUBE_BUILD_STAGE}/server/${KUBE_PLATFORM}-${KUBE_ARCH}/kubernetes/server/bin"
if [[ ! -d "$imageFolder" ]];then
echo "${imageFolder} does not exist! Run make quick-release or make release"
exit 1
fi
for binary in $FEDERATION_BINARIES;do
local imageFile="${imageFolder}/${binary}.tar"
if [[ ! -f "$imageFile" ]];then
echo "${imageFile} does not exist!"
exit 1
fi
echo "Load: ${imageFile}"
# Load the image. Trust we know what it's called, as docker load provides no help there :(
docker load -q < "${imageFile}"
local srcImageTag="$(cat ${imageFolder}/${binary}.docker_tag)"
local dstImageTag="${FEDERATION_IMAGE_TAG:-$srcImageTag}"
local srcImageName="${FEDERATION_IMAGE_REPO_BASE}/${binary}:${srcImageTag}"
local dstImageName="${FEDERATION_PUSH_REPO_BASE}/${binary}:${dstImageTag}"
echo "Tag: ${srcImageName} --> ${dstImageName}"
docker tag "$srcImageName" "$dstImageName"
echo "Push: $dstImageName"
if [[ "${FEDERATION_PUSH_REPO_BASE}" == "gcr.io/"* ]];then
echo " -> GCR repository detected. Using gcloud"
gcloud docker push "$dstImageName"
else
docker push "$dstImageName"
fi
echo "Remove: $srcImageName"
docker rmi "$srcImageName"
if [[ "$srcImageName" != "dstImageName" ]];then
echo "Remove: $dstImageName"
docker rmi "$dstImageName"
fi
done
}
function cleanup-federated-api-objects {
$host_kubectl delete pods,svc,rc,deployment,secret -lapp=federated-cluster
}

View File

@ -0,0 +1,27 @@
#!/bin/bash
# Copyright 2014 The Kubernetes Authors All rights reserved.
#
# 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.
set -o errexit
set -o nounset
set -o pipefail
KUBE_ROOT=$(readlink -m $(dirname "${BASH_SOURCE}")/../../)
. ${KUBE_ROOT}/federation/cluster/common.sh
cleanup-federated-api-objects
$host_kubectl delete ns/${FEDERATION_NAMESPACE}

View File

@ -0,0 +1,32 @@
#!/bin/bash
# Copyright 2014 The Kubernetes Authors All rights reserved.
#
# 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.
# Bring up a Kubernetes cluster.
#
# If the full release name (gs://<bucket>/<release>) is passed in then we take
# that directly. If not then we assume we are doing development stuff and take
# the defaults in the release config.
set -o errexit
set -o nounset
set -o pipefail
KUBE_ROOT=$(readlink -m $(dirname "${BASH_SOURCE}")/../../)
. ${KUBE_ROOT}/federation/cluster/common.sh
push-federated-images

View File

@ -0,0 +1,25 @@
#!/bin/bash
# Copyright 2014 The Kubernetes Authors All rights reserved.
#
# 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.
set -o errexit
set -o nounset
set -o pipefail
KUBE_ROOT=$(readlink -m $(dirname "${BASH_SOURCE}")/../../)
. ${KUBE_ROOT}/federation/cluster/common.sh
create-federated-api-objects

View File

@ -0,0 +1,76 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
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.
*/
/*
This is a simple script that makes *every* environment variable available
as a go template field of the same name
$ echo "hello world, MYVAR={{.MYVAR}}" > test.txt
$ MYVAR=foobar go run template.go test.txt
> hello world, MYVAR=foobar
If you want the base64 version of any MYVAR, simple use {{.MYVAR_BASE64}}
*/
package main
import (
"encoding/base64"
"flag"
"fmt"
"io"
"os"
"path"
"strings"
"text/template"
)
func main() {
flag.Parse()
env := make(map[string]string)
envList := os.Environ()
for i := range envList {
pieces := strings.SplitN(envList[i], "=", 2)
if len(pieces) == 2 {
env[pieces[0]] = pieces[1]
env[pieces[0]+"_BASE64"] = base64.StdEncoding.EncodeToString([]byte(pieces[1]))
} else {
fmt.Fprintf(os.Stderr, "Invalid environ found: %s\n", envList[i])
os.Exit(2)
}
}
for i := 0; i < flag.NArg(); i++ {
inpath := flag.Arg(i)
if err := templateYamlFile(env, inpath, os.Stdout); err != nil {
panic(err)
}
}
}
func templateYamlFile(params map[string]string, inpath string, out io.Writer) error {
if tmpl, err := template.New(path.Base(inpath)).ParseFiles(inpath); err != nil {
return err
} else {
if err := tmpl.Execute(out, params); err != nil {
return err
}
}
_, err := out.Write([]byte("\n---\n"))
return err
}

1
federation/manifests/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/federated-image.tag

View File

@ -0,0 +1,42 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{.FEDERATION_APISERVER_DEPLOYMENT_NAME}}
namespace: {{.FEDERATION_NAMESPACE}}
labels:
app: federated-cluster
spec:
template:
metadata:
name: federation-apiserver
labels:
app: federated-cluster
module: federation-apiserver
spec:
containers:
- name: apiserver
image: {{.FEDERATION_APISERVER_IMAGE_REPO}}:{{.FEDERATION_APISERVER_IMAGE_TAG}}
command:
- /usr/local/bin/federation-apiserver
- --bind-address=0.0.0.0
- --etcd-servers=http://localhost:2379
- --service-cluster-ip-range={{.FEDERATION_SERVICE_CIDR}}
- --secure-port=443
- --advertise-address={{.FEDERATION_API_HOST}}
- --admission-control=NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota
- --token-auth-file=/srv/kubernetes/known-tokens.csv
ports:
- containerPort: 443
name: https
- containerPort: 8080
name: local
volumeMounts:
- name: federation-apiserver-secrets
mountPath: /srv/kubernetes/
readOnly: true
- name: etcd
image: quay.io/coreos/etcd:v2.3.3
volumes:
- name: federation-apiserver-secrets
secret:
secretName: federation-apiserver-secrets

View File

@ -0,0 +1,17 @@
apiVersion: v1
kind: Service
metadata:
name: {{.FEDERATION_APISERVER_DEPLOYMENT_NAME}}
namespace: {{.FEDERATION_NAMESPACE}}
labels:
app: federated-cluster
spec:
type: LoadBalancer
selector:
app: federated-cluster
module: federation-apiserver
ports:
- name: https
protocol: TCP
port: 443
targetPort: 443

View File

@ -0,0 +1,17 @@
apiVersion: v1
kind: Service
metadata:
name: {{.FEDERATION_APISERVER_DEPLOYMENT_NAME}}
namespace: {{.FEDERATION_NAMESPACE}}
labels:
app: federated-cluster
spec:
type: NodePort
selector:
app: federated-cluster
module: federation-apiserver
ports:
- name: https
protocol: TCP
nodePort: {{.FEDERATION_API_NODEPORT}}
port: 443

View File

@ -0,0 +1,9 @@
apiVersion: v1
kind: Secret
metadata:
name: federation-apiserver-secrets
labels:
app: federated-cluster
type: Opaque
data:
known-tokens.csv: {{.FEDERATION_API_KNOWN_TOKENS_BASE64}}

View File

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: {{.FEDERATION_NAMESPACE}}

View File

@ -29,4 +29,5 @@ source "${KUBE_ROOT}/cluster/kube-util.sh"
prepare-e2e
#TODO(colhom): spec and implement federated version of this
${KUBECTL} get nodes --no-headers | wc -l

View File

@ -29,4 +29,16 @@ source "${KUBE_ROOT}/cluster/kube-util.sh"
prepare-e2e
if [[ "${FEDERATION:-}" == "true" ]];then
source "${KUBE_ROOT}/federation/cluster/common.sh"
for zone in ${E2E_ZONES};do
# bring up e2e cluster
(
set-federated-zone-vars "$zone"
cleanup-federated-api-objects || echo "Couldn't cleanup federated api objects"
test-teardown
)
done
else
test-teardown
fi

View File

@ -29,4 +29,19 @@ source "${KUBE_ROOT}/cluster/kube-util.sh"
prepare-e2e
if [[ "${FEDERATION:-}" == "true" ]];then
FEDERATION_NAMESPACE=${FEDERATION_NAMESPACE:-federation-e2e}
#TODO(colhom): the last cluster that was created in the loop above is the current context.
# Hence, it will be the cluster that hosts the federated components.
# In the future, we will want to loop through the all the federated contexts,
# select each one and call federated-up
for zone in ${E2E_ZONES};do
(
set-federated-zone-vars "$zone"
printf "\n\tChecking version for $OVERRIDE_CONTEXT\n"
${KUBECTL} --context="$OVERRIDE_CONTEXT" version
)
done
else
${KUBECTL} version
fi

View File

@ -29,4 +29,21 @@ source "${KUBE_ROOT}/cluster/kube-util.sh"
prepare-e2e
if [[ "${FEDERATION:-}" == "true" ]];then
#TODO(colhom): the last cluster that was created in the loop above is the current context.
# Hence, it will be the cluster that hosts the federated components.
# In the future, we will want to loop through the all the federated contexts,
# select each one and call federated-up
for zone in ${E2E_ZONES};do
(
set-federated-zone-vars "$zone"
test-setup
)
done
if [[ -f "${KUBE_ROOT}/federation/manifests/federated-image.tag" ]];then
export FEDERATION_IMAGE_TAG="$(cat "${KUBE_ROOT}/federation/manifests/federated-image.tag")"
fi
"${KUBE_ROOT}/federation/cluster/federated-up.sh"
else
test-setup
fi

View File

@ -155,6 +155,10 @@ func Up() bool {
// Ensure that the cluster is large engough to run the e2e tests.
func ValidateClusterSize() {
if os.Getenv("FEDERATION") == "true" {
//TODO(colhom): federated equivalent of ValidateClusterSize
return
}
// Check that there are at least minNodeCount nodes running
cmd := exec.Command(path.Join(*root, "hack/e2e-internal/e2e-cluster-size.sh"))
if *verbose {
@ -189,7 +193,15 @@ func Test() bool {
ValidateClusterSize()
}
if os.Getenv("FEDERATION") == "" {
return finishRunning("Ginkgo tests", exec.Command(filepath.Join(*root, "hack/ginkgo-e2e.sh"), strings.Fields(*testArgs)...))
} else {
if *testArgs == "" {
*testArgs = "--ginkgo.focus=\\[Feature:Federation\\]"
}
return finishRunning("Federated Ginkgo tests", exec.Command(filepath.Join(*root, "hack/federated-ginkgo-e2e.sh"), strings.Fields(*testArgs)...))
}
}
func finishRunning(stepName string, cmd *exec.Cmd) bool {
@ -212,8 +224,9 @@ func finishRunning(stepName string, cmd *exec.Cmd) bool {
// returns either "", or a list of args intended for appending with the
// kubectl command (beginning with a space).
func kubectlArgs() string {
args := []string{""}
if *checkVersionSkew {
return " --match-server-version"
args = append(args, "--match-server-version")
}
return ""
return strings.Join(args, " ")
}

34
hack/federated-ginkgo-e2e.sh Executable file
View File

@ -0,0 +1,34 @@
#!/bin/bash
# Copyright 2014 The Kubernetes Authors All rights reserved.
#
# 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.
set -o errexit
set -o nounset
set -o pipefail
KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
source "${KUBE_ROOT}/cluster/kube-util.sh"
#A little hack to get the last zone. we always deploy federated cluster to the last zone.
#TODO(colhom): deploy federated control plane to multiple underlying clusters in robust way
lastZone=""
for zone in ${E2E_ZONES};do
lastZone="$zone"
done
(
set-federated-zone-vars "$zone"
"${KUBE_ROOT}/hack/ginkgo-e2e.sh" $@
)

View File

@ -136,6 +136,7 @@ readonly KUBE_TEST_PORTABLE=(
hack/e2e-internal
hack/get-build.sh
hack/ginkgo-e2e.sh
hack/federated-ginkgo-e2e.sh
hack/lib
)
@ -164,6 +165,7 @@ readonly KUBE_STATIC_LIBRARIES=(
kube-scheduler
kube-proxy
kubectl
federation-apiserver
)
kube::golang::is_statically_linked_library() {

View File

@ -143,6 +143,7 @@ failure-domains
fake-clientset
federated-api-burst
federated-api-qps
federated-kube-context
file-check-frequency
file-suffix
file_content_in_loop

View File

@ -17,11 +17,12 @@ limitations under the License.
package main
import (
flag "github.com/spf13/pflag"
"log"
"os"
"strings"
flag "github.com/spf13/pflag"
"k8s.io/kubernetes/test/e2e"
)

View File

@ -20,7 +20,10 @@ import (
"fmt"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/annotations"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions"
@ -35,10 +38,6 @@ import (
"k8s.io/kubernetes/pkg/util/wait"
"k8s.io/kubernetes/pkg/watch"
"k8s.io/kubernetes/test/e2e/framework"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/kubernetes/pkg/api/annotations"
)
const (

View File

@ -0,0 +1,64 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
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 e2e
import (
"fmt"
. "github.com/onsi/ginkgo"
federationapi "k8s.io/kubernetes/federation/apis/federation"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/test/e2e/framework"
)
// Create/delete cluster api objects
var _ = framework.KubeDescribe("Federation apiserver [Feature:Federation]", func() {
f := framework.NewDefaultFederatedFramework("federated-cluster")
It("should allow creation of cluster api objects", func() {
framework.SkipUnlessFederated()
contexts := f.GetUnderlyingFederatedContexts()
for _, context := range contexts {
framework.Logf("Creating cluster object: %s (%s)", context.Name, context.Cluster.Cluster.Server)
cluster := federationapi.Cluster{
ObjectMeta: api.ObjectMeta{
Name: context.Name,
},
Spec: federationapi.ClusterSpec{
ServerAddressByClientCIDRs: []federationapi.ServerAddressByClientCIDR{
{
ClientCIDR: "0.0.0.0/0",
ServerAddress: context.Cluster.Cluster.Server,
},
},
//TODO(colhom): add SecretRef when #26132 lands
},
}
_, err := f.FederationClient.Clusters().Create(&cluster)
framework.ExpectNoError(err, fmt.Sprintf("creating cluster: %+v", err))
}
for _, context := range contexts {
c, err := f.FederationClient.Clusters().Get(context.Name)
framework.ExpectNoError(err, fmt.Sprintf("get cluster: %+v", err))
if c.ObjectMeta.Name != context.Name {
framework.Failf("cluster name does not match input context: actual=%+v, expected=%+v", c, context)
}
}
})
})

View File

@ -19,11 +19,13 @@ package framework
import (
"bytes"
"fmt"
"io/ioutil"
"reflect"
"strings"
"sync"
"time"
unversionedfederation "k8s.io/kubernetes/federation/client/clientset_generated/federation_internalclientset/typed/federation/unversioned"
"k8s.io/kubernetes/pkg/api"
apierrs "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2"
@ -39,6 +41,7 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
yaml "gopkg.in/yaml.v2"
)
const (
@ -54,6 +57,8 @@ type Framework struct {
Clientset_1_2 *release_1_2.Clientset
Clientset_1_3 *release_1_3.Clientset
FederationClient *unversionedfederation.FederationClient
Namespace *api.Namespace // Every test has at least one namespace
namespacesToDelete []*api.Namespace // Some tests have more than one.
NamespaceDeletionTimeout time.Duration
@ -75,6 +80,9 @@ type Framework struct {
// configuration for framework's client
options FrameworkOptions
// will this framework exercise a federated cluster as well
federated bool
}
type TestDataSummary interface {
@ -97,6 +105,12 @@ func NewDefaultFramework(baseName string) *Framework {
return NewFramework(baseName, options, nil)
}
func NewDefaultFederatedFramework(baseName string) *Framework {
f := NewDefaultFramework(baseName)
f.federated = true
return f
}
func NewFramework(baseName string, options FrameworkOptions, client *client.Client) *Framework {
f := &Framework{
BaseName: baseName,
@ -130,9 +144,17 @@ func (f *Framework) BeforeEach() {
Expect(err).NotTo(HaveOccurred())
f.Client = c
}
f.Clientset_1_2 = adapter_1_2.FromUnversionedClient(f.Client)
f.Clientset_1_3 = adapter_1_3.FromUnversionedClient(f.Client)
if f.federated && f.FederationClient == nil {
By("Creating a federated kubernetes client")
var err error
f.FederationClient, err = LoadFederationClient()
Expect(err).NotTo(HaveOccurred())
}
By("Building a namespace api object")
namespace, err := f.CreateNamespace(f.BaseName, map[string]string{
"e2e-framework": f.BaseName,
@ -206,6 +228,18 @@ func (f *Framework) AfterEach() {
f.Client = nil
}()
if f.federated {
defer func() {
if f.FederationClient == nil {
Logf("Warning: framework is marked federated, but has no FederationClient")
return
}
if err := f.FederationClient.Clusters().DeleteCollection(nil, api.ListOptions{}); err != nil {
Logf("Error: failed to delete Clusters: %+v", err)
}
}()
}
// Print events if the test failed.
if CurrentGinkgoTestDescription().Failed && TestContext.DumpLogsOnFailure {
DumpAllNamespaceInfo(f.Client, f.Namespace.Name)
@ -465,6 +499,97 @@ func (f *Framework) CreatePodsPerNodeForSimpleApp(appName string, podSpec func(n
return labels
}
type KubeUser struct {
Name string `yaml:"name"`
User struct {
Username string `yaml:"username"`
Password string `yaml:"password"`
Token string `yaml:"token"`
} `yaml:"user"`
}
type KubeCluster struct {
Name string `yaml:"name"`
Cluster struct {
CertificateAuthorityData string `yaml:"certificate-authority-data"`
Server string `yaml:"server"`
} `yaml:"cluster"`
}
type KubeConfig struct {
Contexts []struct {
Name string `yaml:"name"`
Context struct {
Cluster string `yaml:"cluster"`
User string
} `yaml:"context"`
} `yaml:"contexts"`
Clusters []KubeCluster `yaml:"clusters"`
Users []KubeUser `yaml:"users"`
}
func (kc *KubeConfig) findUser(name string) *KubeUser {
for _, user := range kc.Users {
if user.Name == name {
return &user
}
}
return nil
}
func (kc *KubeConfig) findCluster(name string) *KubeCluster {
for _, cluster := range kc.Clusters {
if cluster.Name == name {
return &cluster
}
}
return nil
}
type E2EContext struct {
Name string `yaml:"name"`
Cluster *KubeCluster `yaml:"cluster"`
User *KubeUser `yaml:"user"`
}
func (f *Framework) GetUnderlyingFederatedContexts() []E2EContext {
if !f.federated {
Failf("geUnderlyingFederatedContexts called on non-federated framework")
}
kubeconfig := KubeConfig{}
configBytes, err := ioutil.ReadFile(TestContext.KubeConfig)
ExpectNoError(err)
err = yaml.Unmarshal(configBytes, &kubeconfig)
ExpectNoError(err)
e2eContexts := []E2EContext{}
for _, context := range kubeconfig.Contexts {
if strings.HasPrefix(context.Name, "federation-e2e") {
user := kubeconfig.findUser(context.Context.User)
if user == nil {
Failf("Could not find user for context %+v", context)
}
cluster := kubeconfig.findCluster(context.Context.Cluster)
if cluster == nil {
Failf("Could not find cluster for context %+v", context)
}
e2eContexts = append(e2eContexts, E2EContext{
Name: context.Name,
Cluster: cluster,
User: user,
})
}
}
return e2eContexts
}
func kubectlExecWithRetry(namespace string, podName, containerName string, args ...string) ([]byte, []byte, error) {
for numRetries := 0; numRetries < maxKubectlExecRetries; numRetries++ {
if numRetries > 0 {

View File

@ -77,10 +77,7 @@ type CloudConfig struct {
}
var TestContext TestContextType
func SetTestContext(t TestContextType) {
TestContext = t
}
var federatedKubeContext string
func RegisterFlags() {
// Turn on verbose by default to get spec names
@ -95,6 +92,8 @@ func RegisterFlags() {
flag.StringVar(&TestContext.KubeConfig, clientcmd.RecommendedConfigPathFlag, os.Getenv(clientcmd.RecommendedConfigPathEnvVar), "Path to kubeconfig containing embedded authinfo.")
flag.StringVar(&TestContext.KubeContext, clientcmd.FlagContext, "", "kubeconfig context to use/override. If unset, will use value from 'current-context'")
flag.StringVar(&TestContext.KubeAPIContentType, "kube-api-content-type", "", "ContentType used to communicate with apiserver")
flag.StringVar(&federatedKubeContext, "federated-kube-context", "federated-cluster", "kubeconfig context for federated-cluster.")
flag.StringVar(&TestContext.KubeVolumeDir, "volume-dir", "/var/lib/kubelet", "Path to the directory containing the kubelet volumes.")
flag.StringVar(&TestContext.CertDir, "cert-dir", "", "Path to the directory containing the certs. Default is empty, which doesn't use certs.")
flag.StringVar(&TestContext.Host, "host", "", "The host, or apiserver, to connect to")

View File

@ -36,6 +36,7 @@ import (
"sync"
"time"
unversionedfederation "k8s.io/kubernetes/federation/client/clientset_generated/federation_internalclientset/typed/federation/unversioned"
"k8s.io/kubernetes/pkg/api"
apierrs "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/resource"
@ -325,6 +326,28 @@ func SkipUnlessServerVersionGTE(v semver.Version, c discovery.ServerVersionInter
}
}
// Detects whether the federation namespace exists in the underlying cluster
func SkipUnlessFederated() {
c, err := LoadClient()
if err != nil {
Failf("Unable to load client: %v", err)
}
federationNS := os.Getenv("FEDERATION_NAMESPACE")
if federationNS == "" {
federationNS = "federation-e2e"
}
_, err = c.Namespaces().Get(federationNS)
if err != nil {
if apierrs.IsNotFound(err) {
Skipf("Could not find federation namespace %s: skipping federated test", federationNS)
} else {
Failf("Unexpected error getting namespace: %v", err)
}
}
}
// ProvidersWithSSH are those providers where each node is accessible with SSH
var ProvidersWithSSH = []string{"gce", "gke", "aws"}
@ -1521,22 +1544,42 @@ func ServiceResponding(c *client.Client, ns, name string) error {
})
}
func LoadConfig() (*restclient.Config, error) {
switch {
case TestContext.KubeConfig != "":
Logf(">>> TestContext.KubeConfig: %s\n", TestContext.KubeConfig)
func restclientConfig(kubeContext string) (*clientcmdapi.Config, error) {
Logf(">>> kubeConfig: %s\n", TestContext.KubeConfig)
if TestContext.KubeConfig == "" {
return nil, fmt.Errorf("KubeConfig must be specified to load client config")
}
c, err := clientcmd.LoadFromFile(TestContext.KubeConfig)
if err != nil {
return nil, fmt.Errorf("error loading KubeConfig: %v", err.Error())
}
if TestContext.KubeContext != "" {
Logf(">>> TestContext.KubeContext: %s\n", TestContext.KubeContext)
c.CurrentContext = TestContext.KubeContext
if kubeContext != "" {
Logf(">>> kubeContext: %s\n", kubeContext)
c.CurrentContext = kubeContext
}
return c, nil
}
func LoadConfig() (*restclient.Config, error) {
c, err := restclientConfig(TestContext.KubeContext)
if err != nil {
return nil, err
}
return clientcmd.NewDefaultClientConfig(*c, &clientcmd.ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: TestContext.Host}}).ClientConfig()
default:
return nil, fmt.Errorf("KubeConfig must be specified to load client config")
}
func LoadFederatedConfig() (*restclient.Config, error) {
c, err := restclientConfig(federatedKubeContext)
if err != nil {
return nil, err
}
cfg, err := clientcmd.NewDefaultClientConfig(*c, &clientcmd.ConfigOverrides{}).ClientConfig()
if cfg != nil {
//TODO(colhom): this is only here because https://github.com/kubernetes/kubernetes/issues/25422
cfg.NegotiatedSerializer = api.Codecs
}
return cfg, err
}
func loadClientFromConfig(config *restclient.Config) (*client.Client, error) {
@ -1550,6 +1593,25 @@ func loadClientFromConfig(config *restclient.Config) (*client.Client, error) {
return c, nil
}
func loadFederationClientFromConfig(config *restclient.Config) (*unversionedfederation.FederationClient, error) {
c, err := unversionedfederation.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("error creating client: %v", err.Error())
}
if c.Client.Timeout == 0 {
c.Client.Timeout = SingleCallTimeout
}
return c, nil
}
func LoadFederationClient() (*unversionedfederation.FederationClient, error) {
config, err := LoadFederatedConfig()
if err != nil {
return nil, fmt.Errorf("error creating client: %v", err.Error())
}
return loadFederationClientFromConfig(config)
}
func LoadClient() (*client.Client, error) {
config, err := LoadConfig()
if err != nil {

View File

@ -22,11 +22,12 @@ import (
"log"
"fmt"
"net/http"
"k8s.io/kubernetes/pkg/api"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"net/http"
)
func main() {