Remove pkg/framework/test that has moved to https://github.com/kubernetes-sig-testing/frameworks
This commit is contained in:
parent
4f3da7e305
commit
45193fb480
|
|
@ -1 +0,0 @@
|
||||||
assets/bin
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"k8s.io/kubectl/pkg/framework/test/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
// APIServer knows how to run a kubernetes apiserver.
|
|
||||||
type APIServer struct {
|
|
||||||
// URL is the address the ApiServer should listen on for client connections.
|
|
||||||
//
|
|
||||||
// If this is not specified, we default to a random free port on localhost.
|
|
||||||
URL *url.URL
|
|
||||||
|
|
||||||
// Path is the path to the apiserver binary.
|
|
||||||
//
|
|
||||||
// If this is left as the empty string, we will attempt to locate a binary,
|
|
||||||
// by checking for the TEST_ASSET_KUBE_APISERVER environment variable, and
|
|
||||||
// the default test assets directory.
|
|
||||||
Path string
|
|
||||||
|
|
||||||
// CertDir is a path to a directory containing whatever certificates the
|
|
||||||
// APIServer will need.
|
|
||||||
//
|
|
||||||
// If left unspecified, then the Start() method will create a fresh temporary
|
|
||||||
// directory, and the Stop() method will clean it up.
|
|
||||||
CertDir string
|
|
||||||
|
|
||||||
// EtcdURL is the URL of the Etcd the APIServer should use.
|
|
||||||
//
|
|
||||||
// If this is not specified, the Start() method will return an error.
|
|
||||||
EtcdURL *url.URL
|
|
||||||
|
|
||||||
// StartTimeout, StopTimeout specify the time the APIServer is allowed to
|
|
||||||
// take when starting and stoppping before an error is emitted.
|
|
||||||
//
|
|
||||||
// If not specified, these default to 20 seconds.
|
|
||||||
StartTimeout time.Duration
|
|
||||||
StopTimeout time.Duration
|
|
||||||
|
|
||||||
processState *internal.ProcessState
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start starts the apiserver, waits for it to come up, and returns an error,
|
|
||||||
// if occurred.
|
|
||||||
func (s *APIServer) Start() error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
s.processState = &internal.ProcessState{}
|
|
||||||
|
|
||||||
s.processState.DefaultedProcessInput, err = internal.DoDefaulting(
|
|
||||||
"kube-apiserver",
|
|
||||||
s.URL,
|
|
||||||
s.CertDir,
|
|
||||||
s.Path,
|
|
||||||
s.StartTimeout,
|
|
||||||
s.StopTimeout,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.processState.Args, err = internal.MakeAPIServerArgs(
|
|
||||||
s.processState.DefaultedProcessInput,
|
|
||||||
s.EtcdURL,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.processState.StartMessage = fmt.Sprintf(
|
|
||||||
"Serving insecurely on %s",
|
|
||||||
s.processState.URL.Host,
|
|
||||||
)
|
|
||||||
|
|
||||||
return s.processState.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stops this process gracefully, waits for its termination, and cleans up
|
|
||||||
// the CertDir if necessary.
|
|
||||||
func (s *APIServer) Stop() error {
|
|
||||||
return s.processState.Stop()
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
This directory will be the home of some binaries which are downloaded with `pkg/framework/test/scripts/download-binaries`.
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
|
|
||||||
jobs:
|
|
||||||
- name: test-dev-branch
|
|
||||||
public: true
|
|
||||||
serial: true
|
|
||||||
plan:
|
|
||||||
- get: git-kubectl-dev
|
|
||||||
trigger: true
|
|
||||||
- task: run-tests
|
|
||||||
config:
|
|
||||||
platform: linux
|
|
||||||
image_resource:
|
|
||||||
type: docker-image
|
|
||||||
source:
|
|
||||||
repository: golang
|
|
||||||
tag: 1.9
|
|
||||||
inputs:
|
|
||||||
- name: git-kubectl-dev
|
|
||||||
path: go/src/k8s.io/kubectl
|
|
||||||
run:
|
|
||||||
path: /bin/bash
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- |
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -eux
|
|
||||||
chown -R nobody:nogroup "${PWD}/go"
|
|
||||||
|
|
||||||
cat <<'EOS' | su -c bash -s /bin/bash nobody
|
|
||||||
set -eux
|
|
||||||
export GOPATH="${PWD}/go"
|
|
||||||
export PATH="${PATH}:/usr/local/go/bin:${GOPATH}/bin"
|
|
||||||
go get github.com/onsi/ginkgo/ginkgo
|
|
||||||
"${GOPATH}/src/k8s.io/kubectl/pkg/framework/test/scripts/download-binaries.sh"
|
|
||||||
GINKGO_PERFORMANCE=1 "${GOPATH}/src/k8s.io/kubectl/pkg/framework/test/scripts/run-tests.sh"
|
|
||||||
EOS
|
|
||||||
- name: push-to-prod-branch
|
|
||||||
serial: true
|
|
||||||
plan:
|
|
||||||
- get: git-kubectl-dev
|
|
||||||
trigger: true
|
|
||||||
passed:
|
|
||||||
- test-dev-branch
|
|
||||||
- put: git-kubectl-pair2
|
|
||||||
params:
|
|
||||||
repository: git-kubectl-dev
|
|
||||||
force: true
|
|
||||||
- put: git-kubectl-pair1
|
|
||||||
params:
|
|
||||||
repository: git-kubectl-dev
|
|
||||||
force: true
|
|
||||||
|
|
||||||
|
|
||||||
resources:
|
|
||||||
- name: git-kubectl-dev
|
|
||||||
type: git
|
|
||||||
source:
|
|
||||||
uri: {{git-dev-url}} # git@github.com:totherme/kubectl
|
|
||||||
branch: test-framework-dev
|
|
||||||
private_key: {{git-dev-private-key}}
|
|
||||||
ignore_paths: [pkg/framework/test/ci]
|
|
||||||
|
|
||||||
- name: git-kubectl-pair1
|
|
||||||
type: git
|
|
||||||
source:
|
|
||||||
uri: {{git-pair1-url}} #git@github.com:totherme/kubectl
|
|
||||||
branch: test-framework
|
|
||||||
private_key: {{git-pair1-private-key}}
|
|
||||||
|
|
||||||
- name: git-kubectl-pair2
|
|
||||||
type: git
|
|
||||||
source:
|
|
||||||
uri: {{git-pair2-url}} #git@github.com:hoegaarden/kubectl
|
|
||||||
branch: test-framework
|
|
||||||
private_key: {{git-pair2-private-key}}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
# Use DEBUG=1 ./set-pipeline.sh to get debug output
|
|
||||||
[[ -z "${DEBUG:-""}" ]] || set -x
|
|
||||||
|
|
||||||
# Use CONCOURSE_TARGET=my-concourse ./set-pipeline.sh to connect to your local concourse
|
|
||||||
: "${CONCOURSE_TARGET:="wings"}"
|
|
||||||
# Use PIPELINE_NAME=my-name ./set-pipeline.sh to give your pipeline a different name
|
|
||||||
: "${PIPELINE_NAME:="kubectl"}"
|
|
||||||
|
|
||||||
# Use PAIR1_LASTPASS=my-lastpass-key ./set-pipeline.sh to get your github keys and URL from your lastpass entry
|
|
||||||
: "${PAIR1_LASTPASS:="oss-k8s-github-gds-keypair"}"
|
|
||||||
: "${PAIR2_LASTPASS:="oss-k8s-github-hhorl-keypair"}"
|
|
||||||
|
|
||||||
github_pair1_key="$(lpass show "${PAIR1_LASTPASS}" --field "Private Key")"
|
|
||||||
github_pair2_key="$(lpass show "${PAIR2_LASTPASS}" --field "Private Key")"
|
|
||||||
github_pair1_url="$(lpass show "${PAIR1_LASTPASS}" --notes)"
|
|
||||||
github_pair2_url="$(lpass show "${PAIR2_LASTPASS}" --notes)"
|
|
||||||
|
|
||||||
script_dir="$(cd "$(dirname "$0")" ; pwd)"
|
|
||||||
|
|
||||||
# Create/Update the pipline
|
|
||||||
fly set-pipeline \
|
|
||||||
--target="${CONCOURSE_TARGET}" \
|
|
||||||
--pipeline="${PIPELINE_NAME}" \
|
|
||||||
--config="${script_dir}/pipeline.yml" \
|
|
||||||
--var=git-dev-url="${github_pair1_url}" \
|
|
||||||
--var=git-pair1-url="${github_pair1_url}" \
|
|
||||||
--var=git-pair2-url="${github_pair2_url}" \
|
|
||||||
--var=git-dev-private-key="${github_pair1_key}" \
|
|
||||||
--var=git-pair1-private-key="${github_pair1_key}" \
|
|
||||||
--var=git-pair2-private-key="${github_pair2_key}"
|
|
||||||
|
|
||||||
# Make the pipeline publicly available
|
|
||||||
fly expose-pipeline \
|
|
||||||
--target="${CONCOURSE_TARGET}" \
|
|
||||||
--pipeline="${PIPELINE_NAME}"
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ControlPlane is a struct that knows how to start your test control plane.
|
|
||||||
//
|
|
||||||
// Right now, that means Etcd and your APIServer. This is likely to increase in
|
|
||||||
// future.
|
|
||||||
type ControlPlane struct {
|
|
||||||
APIServer *APIServer
|
|
||||||
Etcd *Etcd
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewControlPlane will give you a ControlPlane struct that's properly wired
|
|
||||||
// together.
|
|
||||||
func NewControlPlane() *ControlPlane {
|
|
||||||
etcd := &Etcd{}
|
|
||||||
apiserver := &APIServer{}
|
|
||||||
|
|
||||||
return &ControlPlane{
|
|
||||||
APIServer: apiserver,
|
|
||||||
Etcd: etcd,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start will start your control plane processes. To stop them, call Stop().
|
|
||||||
func (f *ControlPlane) Start() error {
|
|
||||||
if err := f.Etcd.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
f.APIServer.EtcdURL = &f.Etcd.processState.URL
|
|
||||||
return f.APIServer.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop will stop your control plane processes, and clean up their data.
|
|
||||||
func (f *ControlPlane) Stop() error {
|
|
||||||
if err := f.APIServer.Stop(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return f.Etcd.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIURL returns the URL you should connect to to talk to your API.
|
|
||||||
func (f *ControlPlane) APIURL() url.URL {
|
|
||||||
return f.APIServer.processState.URL
|
|
||||||
}
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
// Copyright © 2017 NAME HERE <EMAIL ADDRESS>
|
|
||||||
//
|
|
||||||
// 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 cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// listPodsCmd represents the listPods command
|
|
||||||
var listPodsCmd = &cobra.Command{
|
|
||||||
Use: "listPods",
|
|
||||||
Short: "List all pods",
|
|
||||||
Long: `Give a list of all pods known by the system`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
apiURL, err := cmd.Flags().GetString("api-url")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
runGetPods(apiURL)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func runGetPods(apiURL string) {
|
|
||||||
config, err := clientcmd.BuildConfigFromFlags(apiURL, "")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the clientset
|
|
||||||
clientset, err := kubernetes.NewForConfig(config)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if len(pods.Items) > 0 {
|
|
||||||
} else {
|
|
||||||
fmt.Println("There are no pods.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RootCmd.AddCommand(listPodsCmd)
|
|
||||||
|
|
||||||
// Here you will define your flags and configuration settings.
|
|
||||||
|
|
||||||
listPodsCmd.Flags().String("api-url", "http://localhost:8080", "URL of the APIServer to connect to")
|
|
||||||
|
|
||||||
// Cobra supports Persistent Flags which will work for this command
|
|
||||||
// and all subcommands, e.g.:
|
|
||||||
// listPodsCmd.PersistentFlags().String("foo", "", "A help for foo")
|
|
||||||
|
|
||||||
// Cobra supports local flags which will only run when this command
|
|
||||||
// is called directly, e.g.:
|
|
||||||
// listPodsCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
|
||||||
}
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
// Copyright © 2017 NAME HERE <EMAIL ADDRESS>
|
|
||||||
//
|
|
||||||
// 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 cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
homedir "github.com/mitchellh/go-homedir"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
var cfgFile string
|
|
||||||
|
|
||||||
// RootCmd represents the base command when called without any subcommands
|
|
||||||
var RootCmd = &cobra.Command{
|
|
||||||
Use: "democli",
|
|
||||||
Short: "A demo CLI application",
|
|
||||||
Long: `This is a demo kubernetes CLI, which interacts with the kubernetes API.
|
|
||||||
|
|
||||||
The purpose of this CLI is to demo the testing framework that was used to develop it.`,
|
|
||||||
// Uncomment the following line if your bare application
|
|
||||||
// has an action associated with it:
|
|
||||||
// Run: func(cmd *cobra.Command, args []string) { },
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
|
||||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
|
||||||
func Execute() {
|
|
||||||
if err := RootCmd.Execute(); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
cobra.OnInitialize(initConfig)
|
|
||||||
|
|
||||||
// Here you will define your flags and configuration settings.
|
|
||||||
// Cobra supports persistent flags, which, if defined here,
|
|
||||||
// will be global for your application.
|
|
||||||
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.democli.yaml)")
|
|
||||||
|
|
||||||
// Cobra also supports local flags, which will only run
|
|
||||||
// when this action is called directly.
|
|
||||||
RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
|
||||||
}
|
|
||||||
|
|
||||||
// initConfig reads in config file and ENV variables if set.
|
|
||||||
func initConfig() {
|
|
||||||
if cfgFile != "" {
|
|
||||||
// Use config file from the flag.
|
|
||||||
viper.SetConfigFile(cfgFile)
|
|
||||||
} else {
|
|
||||||
// Find home directory.
|
|
||||||
home, err := homedir.Dir()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search config in home directory with name ".democli" (without extension).
|
|
||||||
viper.AddConfigPath(home)
|
|
||||||
viper.SetConfigName(".democli")
|
|
||||||
}
|
|
||||||
|
|
||||||
viper.AutomaticEnv() // read in environment variables that match
|
|
||||||
|
|
||||||
// If a config file is found, read it in.
|
|
||||||
if err := viper.ReadInConfig(); err == nil {
|
|
||||||
fmt.Println("Using config file:", viper.ConfigFileUsed())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
package integration_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/onsi/gomega/gexec"
|
|
||||||
"k8s.io/kubectl/pkg/framework/test"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIntegration(t *testing.T) {
|
|
||||||
RegisterFailHandler(Fail)
|
|
||||||
RunSpecs(t, "DemoCLI Integration Suite")
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
pathToDemoCommand string
|
|
||||||
controlPlane *test.ControlPlane
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = BeforeSuite(func() {
|
|
||||||
var err error
|
|
||||||
pathToDemoCommand, err = gexec.Build("k8s.io/kubectl/pkg/framework/test/democli/")
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
controlPlane = test.NewControlPlane()
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
err = controlPlane.Start()
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
})
|
|
||||||
|
|
||||||
var _ = AfterSuite(func() {
|
|
||||||
controlPlane.Stop()
|
|
||||||
gexec.CleanupBuildArtifacts()
|
|
||||||
})
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
package integration_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
"github.com/onsi/gomega/gbytes"
|
|
||||||
"github.com/onsi/gomega/gexec"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = Describe("DemoCLI Integration", func() {
|
|
||||||
It("can give us a helpful help message", func() {
|
|
||||||
helpfulMessage := `This is a demo kubernetes CLI, which interacts with the kubernetes API.`
|
|
||||||
|
|
||||||
command := exec.Command(pathToDemoCommand, "--help")
|
|
||||||
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Eventually(session).Should(gexec.Exit(0))
|
|
||||||
Expect(session.Out).To(gbytes.Say(helpfulMessage))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("can get a list of pods", func() {
|
|
||||||
apiURL := controlPlane.APIURL()
|
|
||||||
|
|
||||||
command := exec.Command(pathToDemoCommand, "listPods", "--api-url", apiURL.String())
|
|
||||||
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Eventually(session).Should(gexec.Exit(0))
|
|
||||||
Expect(session.Out).To(gbytes.Say("There are no pods."))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
// Copyright © 2017 NAME HERE <EMAIL ADDRESS>
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "k8s.io/kubectl/pkg/framework/test/democli/cmd"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cmd.Execute()
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
/*
|
|
||||||
|
|
||||||
Package test implements a integration testing framework for kubernetes.
|
|
||||||
|
|
||||||
It provides a kubernetes API you can connect to and test your
|
|
||||||
kubernetes client implementations against. The lifecycle of the components
|
|
||||||
needed to provide this API is managed by this framework.
|
|
||||||
|
|
||||||
Components
|
|
||||||
|
|
||||||
Currently the framework provides the following components:
|
|
||||||
|
|
||||||
ControlPlane: The ControlPlane is wrapping Etcd & APIServer and provides some
|
|
||||||
convenience as it creates instances of the components and wires them together
|
|
||||||
correctly. A ControlPlane can be new'd up, stopped & started and provide the
|
|
||||||
URL to connect to the API.
|
|
||||||
The ControlPlane is supposed to be the default entry point for you.
|
|
||||||
|
|
||||||
Etcd: Manages an Etcd binary, which can be started, stopped and connected to.
|
|
||||||
Etcd will listen on a random port for http connections and will create a
|
|
||||||
temporary directory for it'd data; unless configured differently.
|
|
||||||
|
|
||||||
APIServer: Manages an Kube-APIServer binary, which can be started, stopped and
|
|
||||||
connected to. APIServer will listen on a random port for http connections and
|
|
||||||
will create a temporary directory to store the (auto-generated) certificates;
|
|
||||||
unless configured differently.
|
|
||||||
|
|
||||||
Binaries
|
|
||||||
|
|
||||||
Both Etcd and APIServer use the same mechanism to determine which binaries to
|
|
||||||
use when they get started.
|
|
||||||
|
|
||||||
1. If the component is configured with a `Path` the framework tries to run that
|
|
||||||
binary. (Note: To overwrite the `Path` of a component you cannot use the
|
|
||||||
ControlPlane, but you need to create, wire and start & stop all the components
|
|
||||||
yourself.)
|
|
||||||
|
|
||||||
2. If a environment variable named
|
|
||||||
`TEST_ASSET_KUBE_APISERVER` or `TEST_ASSET_ETCD` is set, this value is used as a
|
|
||||||
path to the binary for the APIServer or Etcd.
|
|
||||||
|
|
||||||
3. If neither an environment variable is set nor the `Path` is overwritten
|
|
||||||
explicitly, the framework tries to use the binaries apiserver or etcd in the
|
|
||||||
directory ${FRAMEWORK_DIRECTORY}/assets/bin/ .
|
|
||||||
|
|
||||||
For convenience this framework ships with
|
|
||||||
${FRAMEWORK_DIR}/scripts/download-binaries.sh which can be used to download
|
|
||||||
pre-compiled versions of the needed binaries and place them in the default
|
|
||||||
location.
|
|
||||||
|
|
||||||
*/
|
|
||||||
package test
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"k8s.io/kubectl/pkg/framework/test/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Etcd knows how to run an etcd server.
|
|
||||||
type Etcd struct {
|
|
||||||
// URL is the address the Etcd should listen on for client connections.
|
|
||||||
//
|
|
||||||
// If this is not specified, we default to a random free port on localhost.
|
|
||||||
URL *url.URL
|
|
||||||
|
|
||||||
// Path is the path to the etcd binary.
|
|
||||||
//
|
|
||||||
// If this is left as the empty string, we will attempt to locate a binary,
|
|
||||||
// by checking for the TEST_ASSET_ETCD environment variable, and
|
|
||||||
// the default test assets directory.
|
|
||||||
Path string
|
|
||||||
|
|
||||||
// DataDir is a path to a directory in which etcd can store its state.
|
|
||||||
//
|
|
||||||
// If left unspecified, then the Start() method will create a fresh temporary
|
|
||||||
// directory, and the Stop() method will clean it up.
|
|
||||||
DataDir string
|
|
||||||
|
|
||||||
// StartTimeout, StopTimeout specify the time the Etcd is allowed to
|
|
||||||
// take when starting and stopping before an error is emitted.
|
|
||||||
//
|
|
||||||
// If not specified, these default to 20 seconds.
|
|
||||||
StartTimeout time.Duration
|
|
||||||
StopTimeout time.Duration
|
|
||||||
|
|
||||||
processState *internal.ProcessState
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start starts the etcd, waits for it to come up, and returns an error, if one
|
|
||||||
// occoured.
|
|
||||||
func (e *Etcd) Start() error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
e.processState = &internal.ProcessState{}
|
|
||||||
|
|
||||||
e.processState.DefaultedProcessInput, err = internal.DoDefaulting(
|
|
||||||
"etcd",
|
|
||||||
e.URL,
|
|
||||||
e.DataDir,
|
|
||||||
e.Path,
|
|
||||||
e.StartTimeout,
|
|
||||||
e.StopTimeout,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
e.processState.Args = internal.MakeEtcdArgs(e.processState.DefaultedProcessInput)
|
|
||||||
|
|
||||||
e.processState.StartMessage = fmt.Sprintf("serving insecure client requests on %s", e.processState.URL.Hostname())
|
|
||||||
|
|
||||||
return e.processState.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stops this process gracefully, waits for its termination, and cleans up
|
|
||||||
// the DataDir if necessary.
|
|
||||||
func (e *Etcd) Stop() error {
|
|
||||||
return e.processState.Stop()
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package integration_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/onsi/gomega/gexec"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIntegration(t *testing.T) {
|
|
||||||
RegisterFailHandler(Fail)
|
|
||||||
RunSpecs(t, "Framework Integration Suite")
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = AfterSuite(func() {
|
|
||||||
gexec.TerminateAndWait()
|
|
||||||
})
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
package integration_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
"k8s.io/kubectl/pkg/framework/test"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = Describe("The Testing Framework", func() {
|
|
||||||
It("Successfully manages the control plane lifecycle", func() {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
controlPlane := test.NewControlPlane()
|
|
||||||
|
|
||||||
By("Starting all the control plane processes")
|
|
||||||
err = controlPlane.Start()
|
|
||||||
Expect(err).NotTo(HaveOccurred(), "Expected controlPlane to start successfully")
|
|
||||||
|
|
||||||
apiServerURL := controlPlane.APIURL()
|
|
||||||
etcdClientURL := controlPlane.APIServer.EtcdURL
|
|
||||||
|
|
||||||
isEtcdListeningForClients := isSomethingListeningOnPort(etcdClientURL.Host)
|
|
||||||
isAPIServerListening := isSomethingListeningOnPort(apiServerURL.Host)
|
|
||||||
|
|
||||||
By("Ensuring Etcd is listening")
|
|
||||||
Expect(isEtcdListeningForClients()).To(BeTrue(),
|
|
||||||
fmt.Sprintf("Expected Etcd to listen for clients on %s,", etcdClientURL.Host))
|
|
||||||
|
|
||||||
By("Ensuring APIServer is listening")
|
|
||||||
Expect(isAPIServerListening()).To(BeTrue(),
|
|
||||||
fmt.Sprintf("Expected APIServer to listen on %s", apiServerURL.Host))
|
|
||||||
|
|
||||||
By("Stopping all the control plane processes")
|
|
||||||
err = controlPlane.Stop()
|
|
||||||
Expect(err).NotTo(HaveOccurred(), "Expected controlPlane to stop successfully")
|
|
||||||
|
|
||||||
By("Ensuring Etcd is not listening anymore")
|
|
||||||
Expect(isEtcdListeningForClients()).To(BeFalse(), "Expected Etcd not to listen for clients anymore")
|
|
||||||
|
|
||||||
By("Ensuring APIServer is not listening anymore")
|
|
||||||
Expect(isAPIServerListening()).To(BeFalse(), "Expected APIServer not to listen anymore")
|
|
||||||
})
|
|
||||||
|
|
||||||
Measure("It should be fast to bring up and tear down the control plane", func(b Benchmarker) {
|
|
||||||
b.Time("lifecycle", func() {
|
|
||||||
controlPlane := test.NewControlPlane()
|
|
||||||
|
|
||||||
controlPlane.Start()
|
|
||||||
controlPlane.Stop()
|
|
||||||
})
|
|
||||||
}, 10)
|
|
||||||
})
|
|
||||||
|
|
||||||
type portChecker func() bool
|
|
||||||
|
|
||||||
func isSomethingListeningOnPort(hostAndPort string) portChecker {
|
|
||||||
return func() bool {
|
|
||||||
conn, err := net.DialTimeout("tcp", hostAndPort, 1*time.Second)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
conn.Close()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AddressManager allocates a new address (interface & port) a process
|
|
||||||
// can bind and keeps track of that.
|
|
||||||
type AddressManager struct {
|
|
||||||
port int
|
|
||||||
host string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize returns a address a process can listen on. It returns
|
|
||||||
// a tuple consisting of a free port and the hostname resolved to its IP.
|
|
||||||
func (d *AddressManager) Initialize() (port int, resolvedHost string, err error) {
|
|
||||||
if d.port != 0 {
|
|
||||||
return 0, "", fmt.Errorf("this AddressManager is already initialized")
|
|
||||||
}
|
|
||||||
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
l, err := net.ListenTCP("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
d.port = l.Addr().(*net.TCPAddr).Port
|
|
||||||
defer func() {
|
|
||||||
err = l.Close()
|
|
||||||
}()
|
|
||||||
d.host = addr.IP.String()
|
|
||||||
return d.port, d.host, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Port returns the port that this AddressManager is managing. Port returns an
|
|
||||||
// error if this AddressManager has not yet been initialized.
|
|
||||||
func (d *AddressManager) Port() (int, error) {
|
|
||||||
if d.port == 0 {
|
|
||||||
return 0, fmt.Errorf("this AdressManager is not initialized yet")
|
|
||||||
}
|
|
||||||
return d.port, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Host returns the host that this AddressManager is managing. Host returns an
|
|
||||||
// error if this AddressManager has not yet been initialized.
|
|
||||||
func (d *AddressManager) Host() (string, error) {
|
|
||||||
if d.host == "" {
|
|
||||||
return "", fmt.Errorf("this AdressManager is not initialized yet")
|
|
||||||
}
|
|
||||||
return d.host, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
package internal_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "k8s.io/kubectl/pkg/framework/test/internal"
|
|
||||||
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = Describe("AddressManager", func() {
|
|
||||||
var addressManager *AddressManager
|
|
||||||
BeforeEach(func() {
|
|
||||||
addressManager = &AddressManager{}
|
|
||||||
})
|
|
||||||
|
|
||||||
Describe("Initialize", func() {
|
|
||||||
It("returns a free port and an address to bind to", func() {
|
|
||||||
port, host, err := addressManager.Initialize()
|
|
||||||
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(host).To(Equal("127.0.0.1"))
|
|
||||||
Expect(port).NotTo(Equal(0))
|
|
||||||
|
|
||||||
addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", host, port))
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
l, err := net.ListenTCP("tcp", addr)
|
|
||||||
defer func() {
|
|
||||||
Expect(l.Close()).To(Succeed())
|
|
||||||
}()
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
})
|
|
||||||
|
|
||||||
Context("initialized multiple times", func() {
|
|
||||||
It("fails", func() {
|
|
||||||
_, _, err := addressManager.Initialize()
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
_, _, err = addressManager.Initialize()
|
|
||||||
Expect(err).To(MatchError(ContainSubstring("already initialized")))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Describe("Port", func() {
|
|
||||||
It("returns an error if Initialize has not been called yet", func() {
|
|
||||||
_, err := addressManager.Port()
|
|
||||||
Expect(err).To(MatchError(ContainSubstring("not initialized yet")))
|
|
||||||
})
|
|
||||||
It("returns the same port as previously allocated by Initialize", func() {
|
|
||||||
expectedPort, _, err := addressManager.Initialize()
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
actualPort, err := addressManager.Port()
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(actualPort).To(Equal(expectedPort))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Describe("Host", func() {
|
|
||||||
It("returns an error if Initialize has not been called yet", func() {
|
|
||||||
_, err := addressManager.Host()
|
|
||||||
Expect(err).To(MatchError(ContainSubstring("not initialized yet")))
|
|
||||||
})
|
|
||||||
It("returns the same port as previously allocated by Initialize", func() {
|
|
||||||
_, expectedHost, err := addressManager.Initialize()
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
actualHost, err := addressManager.Host()
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(actualHost).To(Equal(expectedHost))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
func MakeAPIServerArgs(ps DefaultedProcessInput, etcdURL *url.URL) ([]string, error) {
|
|
||||||
if etcdURL == nil {
|
|
||||||
return []string{}, fmt.Errorf("must configure Etcd URL")
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{
|
|
||||||
"--authorization-mode=Node,RBAC",
|
|
||||||
"--runtime-config=admissionregistration.k8s.io/v1alpha1",
|
|
||||||
"--v=3", "--vmodule=",
|
|
||||||
"--admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,SecurityContextDeny,DefaultStorageClass,DefaultTolerationSeconds,GenericAdmissionWebhook,ResourceQuota",
|
|
||||||
"--admission-control-config-file=",
|
|
||||||
"--bind-address=0.0.0.0",
|
|
||||||
"--storage-backend=etcd3",
|
|
||||||
fmt.Sprintf("--etcd-servers=%s", etcdURL.String()),
|
|
||||||
fmt.Sprintf("--cert-dir=%s", ps.Dir),
|
|
||||||
fmt.Sprintf("--insecure-port=%s", ps.URL.Port()),
|
|
||||||
fmt.Sprintf("--insecure-bind-address=%s", ps.URL.Hostname()),
|
|
||||||
}
|
|
||||||
|
|
||||||
return args, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
package internal_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
. "k8s.io/kubectl/pkg/framework/test/internal"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = Describe("Apiserver", func() {
|
|
||||||
It("generates APIServer args", func() {
|
|
||||||
processInput := DefaultedProcessInput{
|
|
||||||
Dir: "/some/dir",
|
|
||||||
URL: url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: "some.apiserver.host:1234",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
etcdURL := &url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: "some.etcd.server",
|
|
||||||
}
|
|
||||||
|
|
||||||
args, err := MakeAPIServerArgs(processInput, etcdURL)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(len(args)).To(BeNumerically(">", 0))
|
|
||||||
Expect(args).To(ContainElement("--etcd-servers=http://some.etcd.server"))
|
|
||||||
Expect(args).To(ContainElement("--cert-dir=/some/dir"))
|
|
||||||
Expect(args).To(ContainElement("--insecure-port=1234"))
|
|
||||||
Expect(args).To(ContainElement("--insecure-bind-address=some.apiserver.host"))
|
|
||||||
})
|
|
||||||
|
|
||||||
Context("when URL is not configured", func() {
|
|
||||||
It("returns an error", func() {
|
|
||||||
var etcdURL *url.URL
|
|
||||||
processInput := DefaultedProcessInput{}
|
|
||||||
|
|
||||||
_, err := MakeAPIServerArgs(processInput, etcdURL)
|
|
||||||
Expect(err).To(MatchError("must configure Etcd URL"))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var assetsPath string
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
_, thisFile, _, ok := runtime.Caller(0)
|
|
||||||
if !ok {
|
|
||||||
panic("Could not determine the path of the BinPathFinder")
|
|
||||||
}
|
|
||||||
assetsPath = filepath.Join(filepath.Dir(thisFile), "..", "assets", "bin")
|
|
||||||
}
|
|
||||||
|
|
||||||
// BinPathFinder checks the an environment variable, derived from the symbolic name,
|
|
||||||
// and falls back to a default assets location when this variable is not set
|
|
||||||
func BinPathFinder(symbolicName string) (binPath string) {
|
|
||||||
punctuationPattern := regexp.MustCompile("[^A-Z0-9]+")
|
|
||||||
sanitizedName := punctuationPattern.ReplaceAllString(strings.ToUpper(symbolicName), "_")
|
|
||||||
leadingNumberPattern := regexp.MustCompile("^[0-9]+")
|
|
||||||
sanitizedName = leadingNumberPattern.ReplaceAllString(sanitizedName, "")
|
|
||||||
envVar := "TEST_ASSET_" + sanitizedName
|
|
||||||
|
|
||||||
if val, ok := os.LookupEnv(envVar); ok {
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
return filepath.Join(assetsPath, symbolicName)
|
|
||||||
}
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = Describe("BinPathFinder", func() {
|
|
||||||
Context("when relying on the default assets path", func() {
|
|
||||||
var (
|
|
||||||
previousAssetsPath string
|
|
||||||
)
|
|
||||||
BeforeEach(func() {
|
|
||||||
previousAssetsPath = assetsPath
|
|
||||||
assetsPath = "/some/path/assets/bin"
|
|
||||||
})
|
|
||||||
AfterEach(func() {
|
|
||||||
assetsPath = previousAssetsPath
|
|
||||||
})
|
|
||||||
It("returns the default path when no env var is configured", func() {
|
|
||||||
binPath := BinPathFinder("some_bin")
|
|
||||||
Expect(binPath).To(Equal("/some/path/assets/bin/some_bin"))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Context("when environment is configured", func() {
|
|
||||||
var (
|
|
||||||
previousValue string
|
|
||||||
wasSet bool
|
|
||||||
)
|
|
||||||
BeforeEach(func() {
|
|
||||||
envVarName := "TEST_ASSET_ANOTHER_SYMBOLIC_NAME"
|
|
||||||
if val, ok := os.LookupEnv(envVarName); ok {
|
|
||||||
previousValue = val
|
|
||||||
wasSet = true
|
|
||||||
}
|
|
||||||
os.Setenv(envVarName, "/path/to/some_bin.exe")
|
|
||||||
})
|
|
||||||
AfterEach(func() {
|
|
||||||
if wasSet {
|
|
||||||
os.Setenv("TEST_ASSET_ANOTHER_SYMBOLIC_NAME", previousValue)
|
|
||||||
} else {
|
|
||||||
os.Unsetenv("TEST_ASSET_ANOTHER_SYMBOLIC_NAME")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
It("returns the path from the env", func() {
|
|
||||||
binPath := BinPathFinder("another_symbolic_name")
|
|
||||||
Expect(binPath).To(Equal("/path/to/some_bin.exe"))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("sanitizes the environment variable name", func() {
|
|
||||||
By("cleaning all non-underscore punctuation")
|
|
||||||
binPath := BinPathFinder("another-symbolic name")
|
|
||||||
Expect(binPath).To(Equal("/path/to/some_bin.exe"))
|
|
||||||
binPath = BinPathFinder("another+symbolic\\name")
|
|
||||||
Expect(binPath).To(Equal("/path/to/some_bin.exe"))
|
|
||||||
binPath = BinPathFinder("another=symbolic.name")
|
|
||||||
Expect(binPath).To(Equal("/path/to/some_bin.exe"))
|
|
||||||
By("removing numbers from the beginning of the name")
|
|
||||||
binPath = BinPathFinder("12another_symbolic_name")
|
|
||||||
Expect(binPath).To(Equal("/path/to/some_bin.exe"))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
package internal
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func MakeEtcdArgs(input DefaultedProcessInput) []string {
|
|
||||||
args := []string{
|
|
||||||
"--debug",
|
|
||||||
"--listen-peer-urls=http://localhost:0",
|
|
||||||
fmt.Sprintf("--advertise-client-urls=%s", input.URL.String()),
|
|
||||||
fmt.Sprintf("--listen-client-urls=%s", input.URL.String()),
|
|
||||||
fmt.Sprintf("--data-dir=%s", input.Dir),
|
|
||||||
}
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
package internal_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
. "k8s.io/kubectl/pkg/framework/test/internal"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = Describe("Etcd", func() {
|
|
||||||
It("can create Etcd arguments", func() {
|
|
||||||
input := DefaultedProcessInput{
|
|
||||||
URL: url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: "some.etcd.service:5432",
|
|
||||||
},
|
|
||||||
Dir: "/some/data/dir",
|
|
||||||
}
|
|
||||||
|
|
||||||
args := MakeEtcdArgs(input)
|
|
||||||
Expect(args).To(ContainElement("--advertise-client-urls=http://some.etcd.service:5432"))
|
|
||||||
Expect(args).To(ContainElement("--listen-client-urls=http://some.etcd.service:5432"))
|
|
||||||
Expect(args).To(ContainElement("--data-dir=/some/data/dir"))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
package internal_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInternal(t *testing.T) {
|
|
||||||
RegisterFailHandler(Fail)
|
|
||||||
RunSpecs(t, "Internal Suite")
|
|
||||||
}
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"os/exec"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"github.com/onsi/gomega/gbytes"
|
|
||||||
"github.com/onsi/gomega/gexec"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ProcessState struct {
|
|
||||||
DefaultedProcessInput
|
|
||||||
Session *gexec.Session // TODO private?
|
|
||||||
StartMessage string
|
|
||||||
Args []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO explore ProcessInputs, Defaulter, ProcessState, ...
|
|
||||||
type DefaultedProcessInput struct {
|
|
||||||
URL url.URL
|
|
||||||
Dir string
|
|
||||||
DirNeedsCleaning bool
|
|
||||||
Path string
|
|
||||||
StopTimeout time.Duration
|
|
||||||
StartTimeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func DoDefaulting(
|
|
||||||
name string,
|
|
||||||
listenUrl *url.URL,
|
|
||||||
dir string,
|
|
||||||
path string,
|
|
||||||
startTimeout time.Duration,
|
|
||||||
stopTimeout time.Duration,
|
|
||||||
) (DefaultedProcessInput, error) {
|
|
||||||
defaults := DefaultedProcessInput{
|
|
||||||
Dir: dir,
|
|
||||||
Path: path,
|
|
||||||
StartTimeout: startTimeout,
|
|
||||||
StopTimeout: stopTimeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
if listenUrl == nil {
|
|
||||||
am := &AddressManager{}
|
|
||||||
port, host, err := am.Initialize()
|
|
||||||
if err != nil {
|
|
||||||
return DefaultedProcessInput{}, err
|
|
||||||
}
|
|
||||||
defaults.URL = url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: fmt.Sprintf("%s:%d", host, port),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
defaults.URL = *listenUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
if dir == "" {
|
|
||||||
newDir, err := ioutil.TempDir("", "k8s_test_framework_")
|
|
||||||
if err != nil {
|
|
||||||
return DefaultedProcessInput{}, err
|
|
||||||
}
|
|
||||||
defaults.Dir = newDir
|
|
||||||
defaults.DirNeedsCleaning = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if path == "" {
|
|
||||||
if name == "" {
|
|
||||||
return DefaultedProcessInput{}, fmt.Errorf("must have at least one of name or path")
|
|
||||||
}
|
|
||||||
defaults.Path = BinPathFinder(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if startTimeout == 0 {
|
|
||||||
defaults.StartTimeout = 20 * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
if stopTimeout == 0 {
|
|
||||||
defaults.StopTimeout = 20 * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaults, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *ProcessState) Start() (err error) {
|
|
||||||
command := exec.Command(ps.Path, ps.Args...)
|
|
||||||
|
|
||||||
stdErr := gbytes.NewBuffer()
|
|
||||||
detectedStart := stdErr.Detect(ps.StartMessage)
|
|
||||||
timedOut := time.After(ps.StartTimeout)
|
|
||||||
|
|
||||||
ps.Session, err = gexec.Start(command, nil, stdErr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-detectedStart:
|
|
||||||
return nil
|
|
||||||
case <-timedOut:
|
|
||||||
ps.Session.Terminate()
|
|
||||||
return fmt.Errorf("timeout waiting for process to start serving")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *ProcessState) Stop() error {
|
|
||||||
if ps.Session == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
detectedStop := ps.Session.Terminate().Exited
|
|
||||||
timedOut := time.After(ps.StopTimeout)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-detectedStop:
|
|
||||||
break
|
|
||||||
case <-timedOut:
|
|
||||||
return fmt.Errorf("timeout waiting for process to stop")
|
|
||||||
}
|
|
||||||
|
|
||||||
if ps.DirNeedsCleaning {
|
|
||||||
return os.RemoveAll(ps.Dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,181 +0,0 @@
|
||||||
package internal_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
"github.com/onsi/gomega/gexec"
|
|
||||||
. "k8s.io/kubectl/pkg/framework/test/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = Describe("Start method", func() {
|
|
||||||
It("can start a process", func() {
|
|
||||||
processState := &ProcessState{}
|
|
||||||
processState.Path = "bash"
|
|
||||||
processState.Args = simpleBashScript
|
|
||||||
processState.StartTimeout = 10 * time.Second
|
|
||||||
processState.StartMessage = "loop 5"
|
|
||||||
|
|
||||||
err := processState.Start()
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
Consistently(processState.Session.ExitCode).Should(BeNumerically("==", -1))
|
|
||||||
})
|
|
||||||
|
|
||||||
Context("when process takes too long to start", func() {
|
|
||||||
It("returns a timeout error", func() {
|
|
||||||
processState := &ProcessState{}
|
|
||||||
processState.Path = "bash"
|
|
||||||
processState.Args = simpleBashScript
|
|
||||||
processState.StartTimeout = 200 * time.Millisecond
|
|
||||||
processState.StartMessage = "loop 5000"
|
|
||||||
|
|
||||||
err := processState.Start()
|
|
||||||
Expect(err).To(MatchError(ContainSubstring("timeout")))
|
|
||||||
|
|
||||||
Eventually(processState.Session.ExitCode).Should(Equal(143))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Context("when the command cannot be started", func() {
|
|
||||||
It("propagates the error", func() {
|
|
||||||
processState := &ProcessState{}
|
|
||||||
processState.Path = "/nonexistent"
|
|
||||||
|
|
||||||
err := processState.Start()
|
|
||||||
|
|
||||||
Expect(os.IsNotExist(err)).To(BeTrue())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
var _ = Describe("Stop method", func() {
|
|
||||||
It("can stop a process", func() {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
processState := &ProcessState{}
|
|
||||||
processState.Session, err = gexec.Start(getSimpleCommand(), nil, nil)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
processState.StopTimeout = 10 * time.Second
|
|
||||||
|
|
||||||
Expect(processState.Stop()).To(Succeed())
|
|
||||||
})
|
|
||||||
|
|
||||||
Context("when the command cannot be stopped", func() {
|
|
||||||
It("returns a timeout error", func() {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
processState := &ProcessState{}
|
|
||||||
processState.Session, err = gexec.Start(getSimpleCommand(), nil, nil)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
processState.Session.Exited = make(chan struct{})
|
|
||||||
processState.StopTimeout = 200 * time.Millisecond
|
|
||||||
|
|
||||||
Expect(processState.Stop()).To(MatchError(ContainSubstring("timeout")))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Context("when the directory needs to be cleaned up", func() {
|
|
||||||
It("removes the directory", func() {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
processState := &ProcessState{}
|
|
||||||
processState.Session, err = gexec.Start(getSimpleCommand(), nil, nil)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
processState.Dir, err = ioutil.TempDir("", "k8s_test_framework_")
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
processState.DirNeedsCleaning = true
|
|
||||||
processState.StopTimeout = 200 * time.Millisecond
|
|
||||||
|
|
||||||
Expect(processState.Stop()).To(Succeed())
|
|
||||||
Expect(processState.Dir).NotTo(BeAnExistingFile())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
var _ = Describe("DoDefaulting", func() {
|
|
||||||
Context("when all inputs are provided", func() {
|
|
||||||
It("passes them through", func() {
|
|
||||||
defaults, err := DoDefaulting(
|
|
||||||
"some name",
|
|
||||||
&url.URL{Host: "some.host.to.listen.on"},
|
|
||||||
"/some/dir",
|
|
||||||
"/some/path/to/some/bin",
|
|
||||||
20*time.Hour,
|
|
||||||
65537*time.Millisecond,
|
|
||||||
)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
Expect(defaults.URL).To(Equal(url.URL{Host: "some.host.to.listen.on"}))
|
|
||||||
Expect(defaults.Dir).To(Equal("/some/dir"))
|
|
||||||
Expect(defaults.DirNeedsCleaning).To(BeFalse())
|
|
||||||
Expect(defaults.Path).To(Equal("/some/path/to/some/bin"))
|
|
||||||
Expect(defaults.StartTimeout).To(Equal(20 * time.Hour))
|
|
||||||
Expect(defaults.StopTimeout).To(Equal(65537 * time.Millisecond))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Context("when inputs are empty", func() {
|
|
||||||
It("defaults them", func() {
|
|
||||||
defaults, err := DoDefaulting(
|
|
||||||
"some name",
|
|
||||||
nil,
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
Expect(defaults.Dir).To(BeADirectory())
|
|
||||||
Expect(os.RemoveAll(defaults.Dir)).To(Succeed())
|
|
||||||
Expect(defaults.DirNeedsCleaning).To(BeTrue())
|
|
||||||
|
|
||||||
Expect(defaults.URL).NotTo(BeZero())
|
|
||||||
Expect(defaults.URL.Scheme).To(Equal("http"))
|
|
||||||
Expect(defaults.URL.Hostname()).NotTo(BeEmpty())
|
|
||||||
Expect(defaults.URL.Port()).NotTo(BeEmpty())
|
|
||||||
|
|
||||||
Expect(defaults.Path).NotTo(BeEmpty())
|
|
||||||
|
|
||||||
Expect(defaults.StartTimeout).NotTo(BeZero())
|
|
||||||
Expect(defaults.StopTimeout).NotTo(BeZero())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Context("when neither name nor path are provided", func() {
|
|
||||||
It("returns an error", func() {
|
|
||||||
_, err := DoDefaulting(
|
|
||||||
"",
|
|
||||||
nil,
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
Expect(err).To(MatchError("must have at least one of name or path"))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
var simpleBashScript = []string{
|
|
||||||
"-c",
|
|
||||||
`
|
|
||||||
i=0
|
|
||||||
while true
|
|
||||||
do
|
|
||||||
echo "loop $i" >&2
|
|
||||||
let 'i += 1'
|
|
||||||
sleep 0.2
|
|
||||||
done
|
|
||||||
`,
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSimpleCommand() *exec.Cmd {
|
|
||||||
return exec.Command("bash", simpleBashScript...)
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
# Use DEBUG=1 ./scripts/download-binaries.sh to get debug output
|
|
||||||
quiet="--quiet"
|
|
||||||
[[ -z "${DEBUG:-""}" ]] || {
|
|
||||||
set -x
|
|
||||||
quiet=""
|
|
||||||
}
|
|
||||||
|
|
||||||
# Use BASE_URL=https://my/binaries/url ./scripts/download-binaries to download
|
|
||||||
# from a different bucket
|
|
||||||
: "${BASE_URL:="https://storage.googleapis.com/k8s-c10s-test-binaries"}"
|
|
||||||
|
|
||||||
test_framework_dir="$(cd "$(dirname "$0")/.." ; pwd)"
|
|
||||||
os="$(uname -s)"
|
|
||||||
arch="$(uname -m)"
|
|
||||||
|
|
||||||
echo "About to download a couple of binaries. This might take a while..."
|
|
||||||
wget $quiet "${BASE_URL}/etcd-${os}-${arch}" -O "${test_framework_dir}/assets/bin/etcd"
|
|
||||||
wget $quiet "${BASE_URL}/kube-apiserver-${os}-${arch}" -O "${test_framework_dir}/assets/bin/kube-apiserver"
|
|
||||||
chmod +x "${test_framework_dir}/assets/bin/etcd"
|
|
||||||
chmod +x "${test_framework_dir}/assets/bin/kube-apiserver"
|
|
||||||
echo "Done!"
|
|
||||||
Loading…
Reference in New Issue