Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⚠ Bring in testing framework #749

Merged
merged 1 commit into from
Jan 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions OWNERS_ALIASES
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ aliases:
- shawn-hurley
- gerred
- joelanford
testing-integration-admins:
- apelisse
- hoegaarden
- totherme
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,5 @@ require (
k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655
k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90
k8s.io/utils v0.0.0-20190801114015-581e00157fb1
sigs.k8s.io/testing_frameworks v0.1.2
sigs.k8s.io/yaml v1.1.0
)
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,5 @@ modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs
modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
sigs.k8s.io/testing_frameworks v0.1.2 h1:vK0+tvjF0BZ/RYFeZ1E6BYBwHJJXhjuZ3TdsEKH+UQM=
sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
11 changes: 8 additions & 3 deletions hack/check-everything.sh
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,19 @@ SKIP_FETCH_TOOLS=${SKIP_FETCH_TOOLS:-""}

# fetch k8s API gen tools and make it available under kb_root_dir/bin.
function fetch_kb_tools {
header_text "fetching tools"
local dest_dir="${1}"

header_text "fetching tools (into '${dest_dir}')"
kb_tools_archive_name="kubebuilder-tools-$k8s_version-$goos-$goarch.tar.gz"
kb_tools_download_url="https://storage.googleapis.com/kubebuilder-tools/$kb_tools_archive_name"

kb_tools_archive_path="$tmp_root/$kb_tools_archive_name"
if [ ! -f $kb_tools_archive_path ]; then
curl -sL ${kb_tools_download_url} -o "$kb_tools_archive_path"
fi
tar -zvxf "$kb_tools_archive_path" -C "$tmp_root/"

mkdir -p "${dest_dir}"
tar -C "${dest_dir}" --strip-components=1 -zvxf "$kb_tools_archive_path"
}

function is_installed {
Expand All @@ -78,7 +82,8 @@ header_text "using tools"

if [ -z "$SKIP_FETCH_TOOLS" ]; then
fetch_go_tools
fetch_kb_tools
fetch_kb_tools "$kb_root_dir"
fetch_kb_tools "${hack_dir}/../pkg/internal/testing/integration/assets"
fi

setup_envs
Expand Down
2 changes: 1 addition & 1 deletion pkg/builder/builder_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/envtest"
"sigs.k8s.io/controller-runtime/pkg/internal/testing/integration/addr"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/metrics"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/testing_frameworks/integration/addr"
)

func TestBuilder(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/envtest/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/testing_frameworks/integration"
"sigs.k8s.io/controller-runtime/pkg/internal/testing/integration"

logf "sigs.k8s.io/controller-runtime/pkg/internal/log"
)
Expand Down
5 changes: 5 additions & 0 deletions pkg/internal/testing/OWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# See the OWNERS docs: https://git.k8s.io/community/contributors/devel/owners.md

approvers:
- controller-runtime-admins
- testing-integration-admins
1 change: 1 addition & 0 deletions pkg/internal/testing/integration/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
assets/bin
10 changes: 10 additions & 0 deletions pkg/internal/testing/integration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Integration Testing Framework

This package has been moved from [https://github.com/kubernetes-sigs/testing_frameworks/tree/master/integration](https://github.com/kubernetes-sigs/testing_frameworks/tree/master/integration).

A framework for integration testing components of kubernetes. This framework is
intended to work properly both in CI, and on a local dev machine. It therefore
explicitly supports both Linux and Darwin.

For detailed documentation see the
[![GoDoc](https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg/internal/testing/integration?status.svg)](https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg/internal/testing/integration).
14 changes: 14 additions & 0 deletions pkg/internal/testing/integration/addr/addr_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package addr_test

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"testing"
)

func TestAddr(t *testing.T) {
t.Parallel()
RegisterFailHandler(Fail)
RunSpecs(t, "Addr Suite")
}
74 changes: 74 additions & 0 deletions pkg/internal/testing/integration/addr/manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package addr

import (
"fmt"
"net"
"sync"
"time"
)

const (
portReserveTime = 1 * time.Minute
portConflictRetry = 100
)

type portCache struct {
lock sync.Mutex
ports map[int]time.Time
}

func (c *portCache) add(port int) bool {
c.lock.Lock()
defer c.lock.Unlock()
// remove outdated port
for p, t := range c.ports {
if time.Since(t) > portReserveTime {
delete(c.ports, p)
}
}
// try allocating new port
if _, ok := c.ports[port]; ok {
return false
}
c.ports[port] = time.Now()
return true
}

var cache = &portCache{
ports: make(map[int]time.Time),
}

func suggest() (port int, resolvedHost string, err error) {
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
if err != nil {
return
}
l, err := net.ListenTCP("tcp", addr)
if err != nil {
return
}
port = l.Addr().(*net.TCPAddr).Port
defer func() {
err = l.Close()
}()
resolvedHost = addr.IP.String()
return
}

// Suggest suggests an address a process can listen on. It returns
// a tuple consisting of a free port and the hostname resolved to its IP.
// It makes sure that new port allocated does not conflict with old ports
// allocated within 1 minute.
func Suggest() (port int, resolvedHost string, err error) {
for i := 0; i < portConflictRetry; i++ {
port, resolvedHost, err = suggest()
if err != nil {
return
}
if cache.add(port) {
return
}
}
err = fmt.Errorf("no free ports found after %d retries", portConflictRetry)
return
}
29 changes: 29 additions & 0 deletions pkg/internal/testing/integration/addr/manager_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package addr_test

import (
"sigs.k8s.io/controller-runtime/pkg/internal/testing/integration/addr"

"net"
"strconv"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("SuggestAddress", func() {
It("returns a free port and an address to bind to", func() {
port, host, err := addr.Suggest()

Expect(err).NotTo(HaveOccurred())
Expect(host).To(Or(Equal("127.0.0.1"), Equal("::1")))
Expect(port).NotTo(Equal(0))

addr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(host, strconv.Itoa(port)))
Expect(err).NotTo(HaveOccurred())
l, err := net.ListenTCP("tcp", addr)
defer func() {
Expect(l.Close()).To(Succeed())
}()
Expect(err).NotTo(HaveOccurred())
})
})
136 changes: 136 additions & 0 deletions pkg/internal/testing/integration/apiserver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package integration

import (
"fmt"
"io"
"net/url"
"time"

"sigs.k8s.io/controller-runtime/pkg/internal/testing/integration/addr"
"sigs.k8s.io/controller-runtime/pkg/internal/testing/integration/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

// SecurePort is the additional secure port that the APIServer should listen on.
SecurePort int

// 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. See the "Binaries" section above (in
// doc.go) for details.
Path string

// Args is a list of arguments which will passed to the APIServer binary.
// Before they are passed on, they will be evaluated as go-template strings.
// This means you can use fields which are defined and exported on this
// APIServer struct (e.g. "--cert-dir={{ .Dir }}").
// Those templates will be evaluated after the defaulting of the APIServer's
// fields has already happened and just before the binary actually gets
// started. Thus you have access to calculated fields like `URL` and others.
//
// If not specified, the minimal set of arguments to run the APIServer will
// be used.
Args []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

// Out, Err specify where APIServer should write its StdOut, StdErr to.
//
// If not specified, the output will be discarded.
Out io.Writer
Err io.Writer

processState *internal.ProcessState
}

// Start starts the apiserver, waits for it to come up, and returns an error,
// if occurred.
func (s *APIServer) Start() error {
if s.processState == nil {
if err := s.setProcessState(); err != nil {
return err
}
}
return s.processState.Start(s.Out, s.Err)
}

func (s *APIServer) setProcessState() error {
if s.EtcdURL == nil {
return fmt.Errorf("expected EtcdURL to be configured")
}

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
}

// Defaulting the secure port
if s.SecurePort == 0 {
s.SecurePort, _, err = addr.Suggest()
if err != nil {
return err
}
}

s.processState.HealthCheckEndpoint = "/healthz"

s.URL = &s.processState.URL
s.CertDir = s.processState.Dir
s.Path = s.processState.Path
s.StartTimeout = s.processState.StartTimeout
s.StopTimeout = s.processState.StopTimeout

s.processState.Args, err = internal.RenderTemplates(
internal.DoAPIServerArgDefaulting(s.Args), s,
)
return err
}

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

// APIServerDefaultArgs exposes the default args for the APIServer so that you
// can use those to append your own additional arguments.
//
// The internal default arguments are explicitly copied here, we don't want to
// allow users to change the internal ones.
var APIServerDefaultArgs = append([]string{}, internal.APIServerDefaultArgs...)
1 change: 1 addition & 0 deletions pkg/internal/testing/integration/assets/bin/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This directory will be the home of some binaries which are downloaded with `pkg/framework/test/scripts/download-binaries`.
Loading