Remove pkg/framework/test that has moved to https://github.com/kubernetes-sig-testing/frameworks

This commit is contained in:
Antoine Pelisse 2018-01-24 09:19:12 -08:00
parent 4f3da7e305
commit 45193fb480
27 changed files with 0 additions and 1404 deletions

View File

@ -1 +0,0 @@
assets/bin

View File

@ -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()
}

View File

@ -1 +0,0 @@
This directory will be the home of some binaries which are downloaded with `pkg/framework/test/scripts/download-binaries`.

View File

@ -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}}

View File

@ -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}"

View File

@ -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
}

View File

@ -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")
}

View File

@ -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())
}
}

View File

@ -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()
})

View File

@ -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."))
})
})

View File

@ -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()
}

View File

@ -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

View File

@ -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()
}

View File

@ -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()
})

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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))
})
})
})

View File

@ -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
}

View File

@ -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"))
})
})
})

View File

@ -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)
}

View File

@ -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"))
})
})
})

View File

@ -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
}

View File

@ -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"))
})
})

View File

@ -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")
}

View File

@ -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
}

View File

@ -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...)
}

View File

@ -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!"