Skip to content

Commit

Permalink
Add initial helloworld integration test
Browse files Browse the repository at this point in the history
This is a WIP of adding an integration test to cover #59, which is a
simple TaskRun which has no inputs or outputs but just runs a container
that echoes helloworld.

At this point we have the logic to:
- Create the Task and TaskRun
- Wait for the TaskRun to have a condition (TODO: actually check the
  conditions, waiting to rebase onto #86)
- Get the Build associated (not yet created, will be via #86)
- Get the Pod for the build
- TODO: get the logs for that Pod (we've been looking at
  https://github.com/knative/build/blob/e8c2cb6eb5cb09d9737ca9e6da4a1c68af3247b2/pkg/logs/logs.go#L36:6
  for inspiration)

This also changes namespaces to be test specific, so each test can
create a namespace and tear it down, without needing to keep track of
resources created and delete them individually.
  • Loading branch information
bobcatfish committed Oct 5, 2018
1 parent 859ac08 commit 0465433
Show file tree
Hide file tree
Showing 9 changed files with 336 additions and 101 deletions.
18 changes: 5 additions & 13 deletions pkg/apis/pipeline/v1alpha1/task_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package v1alpha1

import (
buildv1alpha1 "github.com/knative/build/pkg/apis/build/v1alpha1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

Expand All @@ -27,8 +26,8 @@ type TaskSpec struct {
// +optional
Inputs *Inputs `json:"inputs,omitempty"`
// +optional
Outputs *Outputs `json:"outputs,omitempty"`
BuildSpec BuildSpec `json:"buildSpec"`
Outputs *Outputs `json:"outputs,omitempty"`
BuildSpec *buildv1alpha1.BuildSpec `json:"buildSpec"`
}

// TaskStatus defines the observed state of Task
Expand Down Expand Up @@ -70,6 +69,9 @@ type Inputs struct {
// used as the name of the volume containing this context which will be mounted
// into the container executed by the Build/Task, e.g. a Source with the
// name "workspace" would be mounted into "/workspace".
//
// TODO(#62): Something is wrong here, this should be a reference to a resource,
// could just be that the names and comments are out of date.
type Source struct {
// name of the source should match the name of the SourceBinding in the pipeline
Name string `json:"name"`
Expand Down Expand Up @@ -101,16 +103,6 @@ type TestResult struct {
Path string `json:"path"`
}

// BuildSpec describes how to create a Build for this Task.
// A BuildSpec will contain either a Template or a series of Steps.
type BuildSpec struct {
// Trying to emulate https://github.com/knative/build/blob/master/pkg/apis/build/v1alpha1/build_types.go
// +optional
Steps []corev1.Container `json:"steps,omitempty"`
// +optional
Template buildv1alpha1.TemplateInstantiationSpec `json:"template,omitempty"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// TaskList contains a list of Task
Expand Down
12 changes: 5 additions & 7 deletions pkg/apis/pipeline/v1alpha1/taskrun_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,23 @@ package v1alpha1
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

buildv1alpha1 "github.com/knative/build/pkg/apis/build/v1alpha1"
)

// TaskRunSpec defines the desired state of TaskRun
type TaskRunSpec struct {
TaskRef TaskRef `json:"taskRef"`
Trigger TaskTrigger `json:"trigger"`
// +optional
Inputs TaskRunInputs `json:"inputs,omitempty"`
Inputs *TaskRunInputs `json:"inputs,omitempty"`
// +optional
Outputs Outputs `json:"outputs,omitempty"`
Results Results `json:"results"`
}

// TaskRunInputs holds the input values that this task was invoked with.
type TaskRunInputs struct {
Resources []PipelineResourceVersion `json:"resourcesVersion"`
// +optional
Resources []PipelineResourceVersion `json:"resourcesVersion,omitempty"`
// +optional
Params []Param `json:"params,omitempty"`
}
Expand Down Expand Up @@ -125,10 +124,9 @@ type TaskRun struct {
metav1.ObjectMeta `json:"metadata,omitempty"`

// +optional
Spec TaskRunSpec `json:"spec,omitempty"`
Spec *TaskRunSpec `json:"spec,omitempty"`
// +optional
//TODO(aaron-prindle) change back to TaskRunStatus
Status buildv1alpha1.BuildStatus `json:"status,omitempty"`
Status *TaskRunStatus `json:"status,omitempty"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
Expand Down
66 changes: 37 additions & 29 deletions pkg/apis/pipeline/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 41 additions & 0 deletions test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,47 @@ namespace := test.AppendRandomString('arendelle')

_See [randstring.go](./randstring.go)._

#### Check Pipeline resources

After creating Pipeline resources or making changes to them, you will need to wait for the system
to realize those changes. You can use polling methods to check the resources reach the desired state.

The `WaitFor*` functions use the kubernetes [`wait` package](https://godoc.org/k8s.io/apimachinery/pkg/util/wait).
To poll they use [`PollImmediate`](https://godoc.org/k8s.io/apimachinery/pkg/util/wait#PollImmediate)
and the return values of the function you provide behave the same as
[`ConditionFunc`](https://godoc.org/k8s.io/apimachinery/pkg/util/wait#ConditionFunc):
a `bool` to indicate if the function should stop or continue polling, and an `error` to indicate if
there has been an error.

For example, you can poll a `TaskRun` object to wait for it to have a `Status.Condition`:

```go
// Verify status of TaskRun (wait for it)
err = WaitForTaskRunState(c, hwTaskRunName, func(tr *v1alpha1.TaskRun) (bool, error) {
if tr.Status != nil && len(tr.Status.Conditions) > 0 {
return true, nil
}
return false, nil
}, "TaskRunHasCondition")
```

_[Metrics will be emitted](#emit-metrics) for these `Wait` method tracking how long test poll for._

We also have `Check*` variants of many of these methods with identical signatures, same example:

```go
var revisionName string
err := test.CheckConfigurationState(clients.ServingClient, configName, func(c *v1alpha1.Configuration) (bool, error) {
if c.Status.LatestCreatedRevisionName != "" {
revisionName = c.Status.LatestCreatedRevisionName
return true, nil
}
return false, nil
})
```

_See [crd_checks.go](./crd_checks.go) and [kube_checks.go](./kube_checks.go)._

## Presubmit tests

[`presubmit-tests.sh`](./presubmit-tests.sh) is the entry point for all tests
Expand Down
33 changes: 24 additions & 9 deletions test/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,30 @@ import (

"github.com/knative/build-pipeline/pkg/client/clientset/versioned"
"github.com/knative/build-pipeline/pkg/client/clientset/versioned/typed/pipeline/v1alpha1"
buildversioned "github.com/knative/build/pkg/client/clientset/versioned"
buildv1alpha1 "github.com/knative/build/pkg/client/clientset/versioned/typed/build/v1alpha1"
knativetest "github.com/knative/pkg/test"
)

// Clients holds instances of interfaces for making requests to the Pipeline controllers.
type Clients struct {
KubeClient *knativetest.KubeClient
// clients holds instances of interfaces for making requests to the Pipeline controllers.
type clients struct {
KubeClient *knativetest.KubeClient

PipelineClient v1alpha1.PipelineInterface
TaskClient v1alpha1.TaskInterface
TaskRunClient v1alpha1.TaskRunInterface

BuildClient buildv1alpha1.BuildInterface
}

// NewClients instantiates and returns several clientsets required for making requests to the
// newClients instantiates and returns several clientsets required for making requests to the
// Pipeline cluster specified by the combination of clusterName and configPath. Clients can
// make requests within namespace.
func NewClients(configPath, clusterName, namespace string) (*Clients, error) {
func newClients(configPath, clusterName, namespace string) (*clients, error) {
var err error
clients := &Clients{}
c := &clients{}

clients.KubeClient, err = knativetest.NewKubeClient(configPath, clusterName)
c.KubeClient, err = knativetest.NewKubeClient(configPath, clusterName)
if err != nil {
return nil, fmt.Errorf("failed to create kubeclient from config file at %s: %s", configPath, err)
}
Expand All @@ -50,7 +57,15 @@ func NewClients(configPath, clusterName, namespace string) (*Clients, error) {
if err != nil {
return nil, fmt.Errorf("failed to create pipeline clientset from config file at %s: %s", configPath, err)
}
clients.PipelineClient = cs.PipelineV1alpha1().Pipelines(namespace)
c.PipelineClient = cs.PipelineV1alpha1().Pipelines(namespace)
c.TaskClient = cs.PipelineV1alpha1().Tasks(namespace)
c.TaskRunClient = cs.PipelineV1alpha1().TaskRuns(namespace)

bcs, err := buildversioned.NewForConfig(cfg)
if err != nil {
return nil, fmt.Errorf("failed to create build clientset from config file at %s: %s", configPath, err)
}
c.BuildClient = bcs.BuildV1alpha1().Builds(namespace)

return clients, nil
return c, nil
}
52 changes: 52 additions & 0 deletions test/crd_checks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
Copyright 2018 The Knative Authors.
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 test

import (
"context"
"fmt"
"time"

"github.com/knative/build-pipeline/pkg/apis/pipeline/v1alpha1"
"go.opencensus.io/trace"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
)

const (
interval = 1 * time.Second
timeout = 10 * time.Second
//timeout = 6 * time.Minute
)

// WaitForTaskRunState polls the status of the TaskRun called name from client every
// interval until inState returns `true` indicating it is done, returns an
// error or timeout. desc will be used to name the metric that is emitted to
// track how long it took for name to get into the state checked by inState.
func WaitForTaskRunState(c *clients, name string, inState func(r *v1alpha1.TaskRun) (bool, error), desc string) error {
metricName := fmt.Sprintf("WaitForTaskRunState/%s/%s", name, desc)
_, span := trace.StartSpan(context.Background(), metricName)
defer span.End()

return wait.PollImmediate(interval, timeout, func() (bool, error) {
r, err := c.TaskRunClient.Get(name, metav1.GetOptions{})
if err != nil {
return true, err
}
return inState(r)
})
}
Loading

0 comments on commit 0465433

Please sign in to comment.