Skip to content

Commit

Permalink
Add cluster creation logic for local users (knative#569)
Browse files Browse the repository at this point in the history
* Add cluster creation logic for local users

* Add unit test

* Feedback updates
  • Loading branch information
chaodaiG authored and knative-prow-robot committed Aug 14, 2019
1 parent 11fec63 commit 057c0df
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 9 deletions.
2 changes: 1 addition & 1 deletion testutils/clustermanager/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package clustermanager

// Client is the entrypoint
type Client interface {
Setup(...interface{}) ClusterOperations
Setup(...interface{}) (ClusterOperations, error)
}

// ClusterOperations contains all provider specific logics
Expand Down
52 changes: 44 additions & 8 deletions testutils/clustermanager/gke.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package clustermanager

import (
"fmt"
"strings"

"knative.dev/pkg/testutils/clustermanager/boskos"
"knative.dev/pkg/testutils/common"
Expand All @@ -36,6 +37,8 @@ type GKECluster struct {
NeedCleanup bool
// TODO: evaluate returning "google.golang.org/api/container/v1.Cluster" when implementing the creation logic
Cluster *string
// Exec is the function used for execute standard shell functions, return stdout and stderr
Exec func(string, ...string) ([]byte, error)
}

// Setup sets up a GKECluster client.
Expand All @@ -44,21 +47,28 @@ type GKECluster struct {
// region: default to regional cluster if not provided, and use default backup regions
// zone: default is none, must be provided together with region
func (gs *GKEClient) Setup(numNodes *int, nodeType *string, region *string, zone *string, project *string) (ClusterOperations, error) {
var err error
gc := &GKECluster{}
if nil != project { // use provided project and create cluster
gc := &GKECluster{
Exec: standardExec,
}
// check for local run
if nil != project { // if project is supplied, use it and create cluster
gc.Project = project
gc.NeedCleanup = true
} else if err = gc.checkEnvironment(); nil != err {
} else if err := gc.checkEnvironment(); nil != err {
return nil, fmt.Errorf("failed checking existing cluster: '%v'", err)
}
if nil != gc.Cluster {
} else if nil != gc.Cluster { // return if Cluster was already set by kubeconfig
return gc, nil
}
// check for Prow
if common.IsProw() {
if *gc.Project, err = boskos.AcquireGKEProject(); nil != err {
project, err := boskos.AcquireGKEProject()
if nil != err {
return nil, fmt.Errorf("failed acquire boskos project: '%v'", err)
}
gc.Project = &project
}
if nil == gc.Project || "" == *gc.Project {
return nil, fmt.Errorf("gcp project must be set")
}
return gc, nil
}
Expand Down Expand Up @@ -91,6 +101,32 @@ func (gc *GKECluster) Delete() error {
// and sets up gc.Project and gc.Cluster properly, otherwise fail it.
// if project can be derived from gcloud, sets it up as well
func (gc *GKECluster) checkEnvironment() error {
// TODO: implement this
// if kubeconfig is configured, use it
output, err := gc.Exec("kubectl", "config", "current-context")
if nil == err {
// output should be in the form of gke_PROJECT_REGION_CLUSTER
parts := strings.Split(string(output), "_")
if len(parts) != 4 {
return fmt.Errorf("kubectl current-context is malformed: '%s'", string(output))
}
gc.Project = &parts[1]
gc.Cluster = &parts[3]
return nil
}
if string(output) != "" {
// this is unexpected error, should shout out directly
return fmt.Errorf("failed running kubectl config current-context: '%s'", string(output))
}

// if gcloud is pointing to a project, use it
output, err = gc.Exec("gcloud", "config", "get-value", "project")
if nil != err {
return fmt.Errorf("failed getting gcloud project: '%v'", err)
}
if string(output) != "" {
project := string(output)
gc.Project = &project
}

return nil
}
91 changes: 91 additions & 0 deletions testutils/clustermanager/gke_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
Copyright 2019 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 clustermanager

import (
"fmt"
"reflect"
"testing"
)

var (
fakeProj = "b"
fakeCluster = "d"
)

func TestGKECheckEnvironment(t *testing.T) {
datas := []struct {
kubectlOut string
kubectlErr error
gcloudOut string
gcloudErr error
expProj *string
expCluster *string
expErr error
}{
{
// Base condition, kubectl shouldn't return empty string if there is no error
"", nil, "", nil, nil, nil, fmt.Errorf("kubectl current-context is malformed: ''"),
}, {
// kubeconfig not set and gcloud not set
"", fmt.Errorf("kubectl not set"), "", nil, nil, nil, nil,
}, {
// kubeconfig failed
"failed", fmt.Errorf("kubectl other err"), "", nil, nil, nil, fmt.Errorf("failed running kubectl config current-context: 'failed'"),
}, {
// kubeconfig returned something other than "gke_PROJECT_REGION_CLUSTER"
"a_b_c", nil, "", nil, nil, nil, fmt.Errorf("kubectl current-context is malformed: 'a_b_c'"),
}, {
// kubeconfig returned something other than "gke_PROJECT_REGION_CLUSTER"
"a_b_c_d_e", nil, "", nil, nil, nil, fmt.Errorf("kubectl current-context is malformed: 'a_b_c_d_e'"),
}, {
// kubeconfig correctly set
"a_b_c_d", nil, "", nil, &fakeProj, &fakeCluster, nil,
}, {
// kubeconfig not set and gcloud failed
"", fmt.Errorf("kubectl not set"), "", fmt.Errorf("gcloud failed"), nil, nil, fmt.Errorf("failed getting gcloud project: 'gcloud failed'"),
}, {
// kubeconfig not set and gcloud not set
"", fmt.Errorf("kubectl not set"), "", nil, nil, nil, nil,
}, {
// kubeconfig not set and gcloud set
"", fmt.Errorf("kubectl not set"), "b", nil, &fakeProj, nil, nil,
},
}

for _, data := range datas {
gc := GKECluster{
Exec: func(name string, args ...string) ([]byte, error) {
var out []byte
var err error
if "gcloud" == name {
out = []byte(data.gcloudOut)
err = data.gcloudErr
} else if "kubectl" == name {
out = []byte(data.kubectlOut)
err = data.kubectlErr
}
return out, err
},
}
if err := gc.checkEnvironment(); !reflect.DeepEqual(err, data.expErr) || !reflect.DeepEqual(gc.Project, data.expProj) || !reflect.DeepEqual(gc.Cluster, data.expCluster) {
t.Errorf("check environment with:\n\tkubectl output: '%s'\n\t\terror: '%v'\n\tgcloud output: '%s'\n\t\t"+
"error: '%v'\nwant: project - '%v', cluster - '%v', err - '%v'\ngot: project - '%v', cluster - '%v', err - '%v'",
data.kubectlOut, data.kubectlErr, data.gcloudOut, data.gcloudErr, data.expProj, data.expCluster, data.expErr, gc.Project, gc.Cluster, err)
}
}
}
26 changes: 26 additions & 0 deletions testutils/clustermanager/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
Copyright 2019 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 clustermanager

import (
"os/exec"
)

// standardExec executes shell command and returns stdout and stderr
func standardExec(name string, args ...string) ([]byte, error) {
return exec.Command(name, args...).Output()
}
45 changes: 45 additions & 0 deletions testutils/clustermanager/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
Copyright 2019 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 clustermanager

import (
"fmt"
"reflect"
"testing"
)

func TestStandardExec(t *testing.T) {
datas := []struct {
cmd string
args []string
expOut string
expErr error
}{
{"bash", []string{"-c", "echo foo"}, "foo\n", nil},
{"cmd_not_exist", []string{"-c", "echo"}, "", fmt.Errorf("exec: \"cmd_not_exist\": executable file not found in $PATH")},
}

for _, data := range datas {
data := data
out, err := standardExec(data.cmd, data.args...)
if !reflect.DeepEqual(string(out), data.expOut) || (nil == err && nil != data.expErr) || (nil != err && nil == data.expErr) ||
(nil != err && nil != data.expErr && err.Error() != data.expErr.Error()) {
t.Errorf("running cmd: '%v', args: '%v'\nwant: out - '%v', err - '%v'\n got: out - '%s', err - '%v'",
data.cmd, data.args, data.expOut, data.expErr, string(out), err)
}
}
}

0 comments on commit 057c0df

Please sign in to comment.