Skip to content

Commit

Permalink
Merge pull request #151 from sawsa307/add-release-project
Browse files Browse the repository at this point in the history
Release boskos project after run.
  • Loading branch information
bowei authored Aug 18, 2023
2 parents aeeb616 + dc52026 commit bc732d0
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 56 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.

PROJECT_ID ?= $(shell gcloud config get-value project 2>&1 | head -n 1)
BOSKOS_RESOURCE_TYPE ?= gke-internal-project
RUN_IN_PROW ?= false
LOCATION ?= us-central1-c
NUM_NODES ?= 3
TEST_TO_RUN ?= .*
JOB_NAME ?= gke-networking-recipe-e2e

all: bin/recipes-test

Expand Down
12 changes: 10 additions & 2 deletions test/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"strings"
"testing"

"github.com/GoogleCloudPlatform/gke-networking-recipes/test/utils"
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud"
"k8s.io/client-go/tools/clientcmd"
backendconfigclient "k8s.io/ingress-gce/pkg/backendconfig/client/clientset/versioned"
Expand Down Expand Up @@ -85,10 +86,17 @@ func TestMain(m *testing.M) {
}

project := flags.testProjectID

// If running in Prow, then acquire and set up a project through Boskos.
if flags.inProw {
project = setupProwConfig(flags.boskosResourceType)
ph, err := utils.NewProjectHolder()
if err != nil {
klog.Fatalf("NewProjectHolder()=%v, want nil", err)
}
project = ph.AcquireOrDie(flags.boskosResourceType)
defer func() {
ph.Release()
}()

if _, ok := os.LookupEnv("USER"); !ok {
if err := os.Setenv("USER", "prow"); err != nil {
klog.Fatalf("failed to set user in prow to prow: %v", err)
Expand Down
54 changes: 0 additions & 54 deletions test/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,69 +22,15 @@ import (
"os"
"os/exec"
"strconv"
"time"

"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud"
"golang.org/x/oauth2/google"
alpha "google.golang.org/api/compute/v0.alpha"
beta "google.golang.org/api/compute/v0.beta"
compute "google.golang.org/api/compute/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/util/retry"
"k8s.io/klog/v2"
boskosclient "sigs.k8s.io/boskos/client"
"sigs.k8s.io/boskos/common"
)

var boskos, _ = boskosclient.NewClient(os.Getenv("JOB_NAME"), "http://boskos", "", "")

// getBoskosProject retries acquiring a boskos project until success or timeout.
func getBoskosProject(resourceType string) *common.Resource {
var project *common.Resource
err := retry.OnError(
wait.Backoff{
Duration: 10 * time.Second,
Factor: 1.0,
Steps: 30,
},
func(err error) bool { return true },
func() error {
klog.Info("Trying to acquire boskos project...")
project, err := boskos.Acquire(resourceType, "free", "busy")
if err != nil {
return fmt.Errorf("boskos failed to acquire project: %w", err)
}
if project != nil {
return fmt.Errorf("boskos does not have a free %s at the moment", resourceType)
}
return nil
},
)
if err != nil {
klog.Fatalf("Error trying to acquire boskos project: %v", err)
}
return project
}

func setupProwConfig(resourceType string) string {
// Try to get a Boskos project
klog.Info("Running in PROW")
klog.Info("Fetching a Boskos loaned project")

p := getBoskosProject(resourceType)
project := p.Name

go func(c *boskosclient.Client) {
for range time.Tick(time.Minute * 5) {
if err := c.UpdateOne(p.Name, "busy", nil); err != nil {
klog.Warningf("[Boskos] Update %s failed with %v", p.Name, err)
}
}
}(boskos)

return project
}

func setEnvProject(project string) error {
if out, err := exec.Command("gcloud", "config", "set", "project", project).CombinedOutput(); err != nil {
return fmt.Errorf("failed to set gcloud project to %s: %s, err: %w", project, out, err)
Expand Down
129 changes: 129 additions & 0 deletions test/utils/project_holder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright 2023 Google LLC
//
// 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 utils

import (
"fmt"
"os"
"time"

"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/util/retry"
"k8s.io/klog/v2"
boskosclient "sigs.k8s.io/boskos/client"
"sigs.k8s.io/boskos/common"
)

const (
retryDuration = 10 * time.Second
retryFactor = 1.0
retryStep = 30

// How often we send update to boskos client to refresh the resource.
updateInterval = 5 * time.Minute
)

type ProjectHolder struct {
c *boskosclient.Client
// quit channel signals when we need to stop refresh the resource and release the boskos project.
quit chan struct{}
// Name of the project to use.
project string
}

func NewProjectHolder() (*ProjectHolder, error) {
jobName := os.Getenv("JOB_NAME")
if jobName == "" {
return nil, fmt.Errorf("JOB_NAME is required but not provided")
}
c, err := boskosclient.NewClient(jobName, "http://boskos", "", "")
if err != nil {
return nil, err
}
return &ProjectHolder{
c: c,
quit: make(chan struct{}),
}, nil
}

// AcquireOrDie tries to get a boskso project. If succeeded, it spawns a
// goroutine to refresh it and returns the name of the project to use.
// If failed, it will terminate.
func (ph *ProjectHolder) AcquireOrDie(resourceType string) string {
// Try to get a Boskos project
klog.Infof("Running in Prow, getting project resourceType = %q", resourceType)

p := ph.getBoskosProjectOrDie(resourceType)
ph.project = p.Name
go ph.refresh()

return p.Name
}

// Release stops the refresh goroutine, and releases the boskso project.
func (ph *ProjectHolder) Release() {
ph.quit <- struct{}{}

// Wait until project cleanup is finished.
<-ph.quit
}

// Periodically refresh the resource to avoid the resource being cleaned
// up accidentally.
// Boskos Reaper component looks for resources that are owned but not
// updated for a period of time, and resets stale resources to dirty state,
// and Boskos Janitor component cleans up all dirty resources.
func (ph *ProjectHolder) refresh() {
for {
select {
case <-time.Tick(updateInterval):
if err := ph.c.UpdateOne(ph.project, common.Busy, nil); err != nil {
klog.Warningf("[Boskos] Update %s failed with %v", ph.project, err)
}
case <-ph.quit:
if err := ph.c.ReleaseOne(ph.project, common.Dirty); err != nil {
klog.Warningf("[Boskos] ReleaseOne %s failed with %v", ph.project, err)
}
ph.quit <- struct{}{}
return
}
}
}

// getBoskosProjectOrDie retries acquiring a boskos project until success or timeout and terminate.
func (ph *ProjectHolder) getBoskosProjectOrDie(resourceType string) *common.Resource {
var project *common.Resource
err := retry.OnError(
wait.Backoff{
Duration: retryDuration,
Factor: retryFactor,
Steps: retryStep,
},
func(err error) bool { return err != nil },
func() error {
klog.Infof("Trying to acquire boskos project of type %s...", resourceType)
var err error
project, err = ph.c.Acquire(resourceType, common.Free, common.Busy)
if err != nil {
return err
}
return nil
},
)
if err != nil || project == nil {
klog.Fatalf("Error trying to acquire boskos project: %v", err)
}
return project
}

0 comments on commit bc732d0

Please sign in to comment.