Skip to content

Commit

Permalink
Merge pull request dexidp#1072 from ericchiang/k8s-test
Browse files Browse the repository at this point in the history
*: run kubernetes tests in travis
  • Loading branch information
rithujohn191 committed Oct 31, 2017
2 parents 68d9026 + 8940b56 commit e868221
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 9 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ go:

services:
- postgresql
- docker

env:
- DEX_POSTGRES_DATABASE=postgres DEX_POSTGRES_USER=postgres DEX_POSTGRES_HOST="localhost" DEX_LDAP_TESTS=1 DEBIAN_FRONTEND=noninteractive
Expand All @@ -21,6 +22,7 @@ install:

script:
- make testall
- ./scripts/test-k8s.sh

notifications:
email: false
8 changes: 6 additions & 2 deletions Documentation/dev-integration-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@

## Kubernetes

Kubernetes tests will only run if the `DEX_KUBECONFIG` environment variable is set.
Kubernetes tests run against a Kubernetes API server, and are enabled by the `DEX_KUBECONFIG` environment variable:

```
$ export DEX_KUBECONFIG=~/.kube/config
$ go test -v -i ./storage/kubernetes
$ go test -v ./storage/kubernetes
```

Because third party resources creation isn't synchronized it's expected that the tests fail the first time. Fear not, and just run them again.
These tests can be executed locally using docker by running the following script:

```
$ ./scripts/test-k8s.sh
```

## Postgres

Expand Down
56 changes: 56 additions & 0 deletions scripts/test-k8s.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/bin/bash -e

TEMPDIR=$( mktemp -d )

cat << EOF > $TEMPDIR/kubeconfig
apiVersion: v1
kind: Config
clusters:
- name: local
cluster:
server: http://localhost:8080
users:
- name: local
user:
contexts:
- context:
cluster: local
user: local
EOF

cleanup () {
docker rm -f $( cat $TEMPDIR/etcd )
docker rm -f $( cat $TEMPDIR/kube-apiserver )
rm -rf $TEMPDIR
}

trap "{ CODE=$?; cleanup ; exit $CODE; }" EXIT

docker run \
--cidfile=$TEMPDIR/etcd \
-d \
--net=host \
gcr.io/google_containers/etcd:3.1.10 \
etcd

docker run \
--cidfile=$TEMPDIR/kube-apiserver \
-d \
-v $TEMPDIR:/var/run/kube-test:ro \
--net=host \
gcr.io/google_containers/kube-apiserver-amd64:v1.7.4 \
kube-apiserver \
--etcd-servers=http://localhost:2379 \
--service-cluster-ip-range=10.0.0.1/16 \
--insecure-bind-address=0.0.0.0 \
--insecure-port=8080

until $(curl --output /dev/null --silent --head --fail http://localhost:8080/healthz); do
printf '.'
sleep 1
done
echo "API server ready"

export DEX_KUBECONFIG=$TEMPDIR/kubeconfig
go test -v -i ./storage/kubernetes
go test -v ./storage/kubernetes
6 changes: 5 additions & 1 deletion storage/kubernetes/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,11 @@ func closeResp(r *http.Response) {
}

func (c *client) get(resource, name string, v interface{}) error {
url := c.urlFor(c.apiVersion, c.namespace, resource, name)
return c.getResource(c.apiVersion, c.namespace, resource, name, v)
}

func (c *client) getResource(apiVersion, namespace, resource, name string, v interface{}) error {
url := c.urlFor(apiVersion, namespace, resource, name)
resp, err := c.client.Get(url)
if err != nil {
return err
Expand Down
59 changes: 53 additions & 6 deletions storage/kubernetes/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ func (c *Config) Open(logger logrus.FieldLogger) (storage.Storage, error) {
// open returns a kubernetes client, initializing the third party resources used
// by dex.
//
// errOnResources controls if errors creating the resources cause this method to return
// waitForResources controls if errors creating the resources cause this method to return
// immediately (used during testing), or if the client will asynchronously retry.
func (c *Config) open(logger logrus.FieldLogger, errOnResources bool) (*client, error) {
func (c *Config) open(logger logrus.FieldLogger, waitForResources bool) (*client, error) {
if c.InCluster && (c.KubeConfigFile != "") {
return nil, errors.New("cannot specify both 'inCluster' and 'kubeConfigFile'")
}
Expand Down Expand Up @@ -87,7 +87,7 @@ func (c *Config) open(logger logrus.FieldLogger, errOnResources bool) (*client,

logger.Info("creating custom Kubernetes resources")
if !cli.registerCustomResources(c.UseTPR) {
if errOnResources {
if waitForResources {
cancel()
return nil, fmt.Errorf("failed creating custom resources")
}
Expand All @@ -111,6 +111,13 @@ func (c *Config) open(logger logrus.FieldLogger, errOnResources bool) (*client,
}()
}

if waitForResources {
if err := cli.waitForCRDs(ctx); err != nil {
cancel()
return nil, err
}
}

// If the client is closed, stop trying to create resources.
cli.cancel = cancel
return cli, nil
Expand All @@ -123,9 +130,6 @@ func (c *Config) open(logger logrus.FieldLogger, errOnResources bool) (*client,
// It logs all errors, returning true if the resources were created successfully.
//
// Creating a custom resource does not mean that they'll be immediately available.
//
// TODO(ericchiang): Provide an option to wait for the resources to actually
// be available.
func (cli *client) registerCustomResources(useTPR bool) (ok bool) {
ok = true
length := len(customResourceDefinitions)
Expand Down Expand Up @@ -165,6 +169,49 @@ func (cli *client) registerCustomResources(useTPR bool) (ok bool) {
return ok
}

// waitForCRDs waits for all CRDs to be in a ready state, and is used
// by the tests to synchronize before running conformance.
func (cli *client) waitForCRDs(ctx context.Context) error {
ctx, cancel := context.WithTimeout(ctx, time.Second*30)
defer cancel()

for _, crd := range customResourceDefinitions {
for {
err := cli.isCRDReady(crd.Name)
if err == nil {
break
}

cli.logger.Errorf("checking CRD: %v", err)

select {
case <-ctx.Done():
return errors.New("timed out waiting for CRDs to be available")
case <-time.After(time.Millisecond * 100):
}
}
}
return nil
}

// isCRDReady determines if a CRD is ready by inspecting its conditions.
func (cli *client) isCRDReady(name string) error {
var r k8sapi.CustomResourceDefinition
err := cli.getResource("apiextensions.k8s.io/v1beta1", "", "customresourcedefinitions", name, &r)
if err != nil {
return fmt.Errorf("get crd %s: %v", name, err)
}

conds := make(map[string]string) // For debugging, keep the conditions around.
for _, c := range r.Status.Conditions {
if c.Type == k8sapi.Established && c.Status == k8sapi.ConditionTrue {
return nil
}
conds[string(c.Type)] = string(c.Status)
}
return fmt.Errorf("crd %s not ready %#v", name, conds)
}

func (cli *client) Close() error {
if cli.cancel != nil {
cli.cancel()
Expand Down

0 comments on commit e868221

Please sign in to comment.