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