2015-09-12 08:44:06 +03:00
|
|
|
// Copyright 2015 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package buildlet
|
|
|
|
|
|
|
|
import (
|
2015-09-26 07:06:19 +03:00
|
|
|
"crypto/tls"
|
2015-09-12 08:44:06 +03:00
|
|
|
"fmt"
|
2015-10-03 01:38:22 +03:00
|
|
|
"log"
|
2015-09-26 07:06:19 +03:00
|
|
|
"net/http"
|
|
|
|
"strings"
|
2015-09-12 08:44:06 +03:00
|
|
|
"time"
|
|
|
|
|
2016-02-15 02:59:59 +03:00
|
|
|
"golang.org/x/build/buildenv"
|
2015-09-12 08:44:06 +03:00
|
|
|
"golang.org/x/build/dashboard"
|
|
|
|
"golang.org/x/build/kubernetes"
|
|
|
|
"golang.org/x/build/kubernetes/api"
|
2015-10-03 01:38:22 +03:00
|
|
|
"golang.org/x/net/context"
|
|
|
|
"golang.org/x/net/context/ctxhttp"
|
2015-09-12 08:44:06 +03:00
|
|
|
)
|
|
|
|
|
2015-10-18 06:35:55 +03:00
|
|
|
var (
|
|
|
|
// TODO(evanbrown): resource requirements should be
|
|
|
|
// defined per-builder in dashboard/builders.go
|
2016-11-23 08:58:31 +03:00
|
|
|
BuildletCPU = api.MustParse("2") // 2 Cores
|
|
|
|
BuildletCPULimit = api.MustParse("8") // 8 Cores
|
|
|
|
BuildletMemory = api.MustParse("4000000Ki") // 4,000,000Ki RAM
|
2015-10-18 06:35:55 +03:00
|
|
|
)
|
|
|
|
|
2015-09-12 08:44:06 +03:00
|
|
|
// PodOpts control how new pods are started.
|
|
|
|
type PodOpts struct {
|
2016-02-15 02:59:59 +03:00
|
|
|
// ProjectID is the GCE project ID. Required.
|
|
|
|
ProjectID string
|
|
|
|
|
2015-09-12 08:44:06 +03:00
|
|
|
// ImageRegistry specifies the Docker registry Kubernetes
|
|
|
|
// will use to create the pod. Required.
|
|
|
|
ImageRegistry string
|
|
|
|
|
|
|
|
// TLS optionally specifies the TLS keypair to use.
|
|
|
|
// If zero, http without auth is used.
|
|
|
|
TLS KeyPair
|
|
|
|
|
|
|
|
// Description optionally describes the pod.
|
|
|
|
Description string
|
|
|
|
|
|
|
|
// Labels optionally specify key=value strings that Kubernetes
|
|
|
|
// can use to filter and group pods.
|
|
|
|
Labels map[string]string
|
|
|
|
|
|
|
|
// DeleteIn optionally specifies a duration at which
|
|
|
|
// to delete the pod.
|
|
|
|
DeleteIn time.Duration
|
|
|
|
|
2016-02-17 09:39:27 +03:00
|
|
|
// OnPodCreating optionally specifies a hook to run synchronously
|
|
|
|
// after the pod create request has been made, but before the create
|
2017-01-07 16:56:01 +03:00
|
|
|
// has succeeded.
|
2016-02-17 09:39:27 +03:00
|
|
|
OnPodCreating func()
|
|
|
|
|
2015-09-12 08:44:06 +03:00
|
|
|
// OnPodCreated optionally specifies a hook to run synchronously
|
2016-02-17 09:39:27 +03:00
|
|
|
// after the pod create request succeeds.
|
2015-09-12 08:44:06 +03:00
|
|
|
OnPodCreated func()
|
|
|
|
|
2016-02-17 09:39:27 +03:00
|
|
|
// OnGotPodInfo optionally specifies a hook to run synchronously
|
2015-09-12 08:44:06 +03:00
|
|
|
// after the pod Get call.
|
|
|
|
OnGotPodInfo func()
|
|
|
|
}
|
|
|
|
|
|
|
|
// StartPod creates a new pod on a Kubernetes cluster and returns a buildlet client
|
|
|
|
// configured to speak to it.
|
all: split builder config into builder & host configs
Our builders are named of the form "GOOS-GOARCH" or
"GOOS-GOARCH-suffix".
Over time we've grown many builders. This CL doesn't change
that. Builders continue to be named and operate as before.
Previously the build configuration file (dashboard/builders.go) made
each builder type ("linux-amd64-race", etc) define how to create a
host running a buildlet of that type, even though many builders had
identical host configs. For example, these builders all share the same
host type (a Kubernetes container):
linux-amd64
linux-amd64-race
linux-386
linux-386-387
And these are the same host type (a GCE VM):
windows-amd64-gce
windows-amd64-race
windows-386-gce
This CL creates a new concept of a "hostType" which defines how
the buildlet is created (Kube, GCE, Reverse, and how), and then each
builder itself references a host type.
Users never see the hostType. (except perhaps in gomote list output)
But they at least never need to care about them.
Reverse buildlets now can only be one hostType at a time, which
simplifies things. We were no longer using multiple roles per machine
once moving to VMs for OS X.
gomote continues to operate as it did previously but its underlying
protocol changed and clients will need to be updated. As a new
feature, gomote now has a new flag to let you reuse a buildlet host
connection for different builder rules if they share the same
underlying host type. But users can ignore that.
This CL is a long-standing TODO (previously attempted and aborted) and
will make many things easier and faster, including the linux-arm
cross-compilation effort, and keeping pre-warmed buildlets of VM types
ready to go.
Updates golang/go#17104
Change-Id: Iad8387f48680424a8441e878a2f4762bf79ea4d2
Reviewed-on: https://go-review.googlesource.com/29551
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
2016-09-22 00:27:37 +03:00
|
|
|
func StartPod(ctx context.Context, kubeClient *kubernetes.Client, podName, hostType string, opts PodOpts) (*Client, error) {
|
|
|
|
conf, ok := dashboard.Hosts[hostType]
|
2015-09-12 08:44:06 +03:00
|
|
|
if !ok || conf.KubeImage == "" {
|
all: split builder config into builder & host configs
Our builders are named of the form "GOOS-GOARCH" or
"GOOS-GOARCH-suffix".
Over time we've grown many builders. This CL doesn't change
that. Builders continue to be named and operate as before.
Previously the build configuration file (dashboard/builders.go) made
each builder type ("linux-amd64-race", etc) define how to create a
host running a buildlet of that type, even though many builders had
identical host configs. For example, these builders all share the same
host type (a Kubernetes container):
linux-amd64
linux-amd64-race
linux-386
linux-386-387
And these are the same host type (a GCE VM):
windows-amd64-gce
windows-amd64-race
windows-386-gce
This CL creates a new concept of a "hostType" which defines how
the buildlet is created (Kube, GCE, Reverse, and how), and then each
builder itself references a host type.
Users never see the hostType. (except perhaps in gomote list output)
But they at least never need to care about them.
Reverse buildlets now can only be one hostType at a time, which
simplifies things. We were no longer using multiple roles per machine
once moving to VMs for OS X.
gomote continues to operate as it did previously but its underlying
protocol changed and clients will need to be updated. As a new
feature, gomote now has a new flag to let you reuse a buildlet host
connection for different builder rules if they share the same
underlying host type. But users can ignore that.
This CL is a long-standing TODO (previously attempted and aborted) and
will make many things easier and faster, including the linux-arm
cross-compilation effort, and keeping pre-warmed buildlets of VM types
ready to go.
Updates golang/go#17104
Change-Id: Iad8387f48680424a8441e878a2f4762bf79ea4d2
Reviewed-on: https://go-review.googlesource.com/29551
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
2016-09-22 00:27:37 +03:00
|
|
|
return nil, fmt.Errorf("invalid builder type %q", hostType)
|
2015-09-12 08:44:06 +03:00
|
|
|
}
|
2015-09-26 07:06:19 +03:00
|
|
|
pod := &api.Pod{
|
2015-09-12 08:44:06 +03:00
|
|
|
TypeMeta: api.TypeMeta{
|
|
|
|
APIVersion: "v1",
|
|
|
|
Kind: "Pod",
|
|
|
|
},
|
|
|
|
ObjectMeta: api.ObjectMeta{
|
|
|
|
Name: podName,
|
|
|
|
Labels: map[string]string{
|
|
|
|
"name": podName,
|
all: split builder config into builder & host configs
Our builders are named of the form "GOOS-GOARCH" or
"GOOS-GOARCH-suffix".
Over time we've grown many builders. This CL doesn't change
that. Builders continue to be named and operate as before.
Previously the build configuration file (dashboard/builders.go) made
each builder type ("linux-amd64-race", etc) define how to create a
host running a buildlet of that type, even though many builders had
identical host configs. For example, these builders all share the same
host type (a Kubernetes container):
linux-amd64
linux-amd64-race
linux-386
linux-386-387
And these are the same host type (a GCE VM):
windows-amd64-gce
windows-amd64-race
windows-386-gce
This CL creates a new concept of a "hostType" which defines how
the buildlet is created (Kube, GCE, Reverse, and how), and then each
builder itself references a host type.
Users never see the hostType. (except perhaps in gomote list output)
But they at least never need to care about them.
Reverse buildlets now can only be one hostType at a time, which
simplifies things. We were no longer using multiple roles per machine
once moving to VMs for OS X.
gomote continues to operate as it did previously but its underlying
protocol changed and clients will need to be updated. As a new
feature, gomote now has a new flag to let you reuse a buildlet host
connection for different builder rules if they share the same
underlying host type. But users can ignore that.
This CL is a long-standing TODO (previously attempted and aborted) and
will make many things easier and faster, including the linux-arm
cross-compilation effort, and keeping pre-warmed buildlets of VM types
ready to go.
Updates golang/go#17104
Change-Id: Iad8387f48680424a8441e878a2f4762bf79ea4d2
Reviewed-on: https://go-review.googlesource.com/29551
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
2016-09-22 00:27:37 +03:00
|
|
|
"type": hostType,
|
2015-09-12 08:44:06 +03:00
|
|
|
"role": "buildlet",
|
|
|
|
},
|
2015-10-18 06:35:55 +03:00
|
|
|
Annotations: map[string]string{},
|
2015-09-12 08:44:06 +03:00
|
|
|
},
|
|
|
|
Spec: api.PodSpec{
|
2015-10-03 01:38:22 +03:00
|
|
|
RestartPolicy: api.RestartPolicyNever,
|
2015-09-12 08:44:06 +03:00
|
|
|
Containers: []api.Container{
|
|
|
|
{
|
|
|
|
Name: "buildlet",
|
2015-09-26 07:06:19 +03:00
|
|
|
Image: imageID(opts.ImageRegistry, conf.KubeImage),
|
2015-09-12 08:44:06 +03:00
|
|
|
ImagePullPolicy: api.PullAlways,
|
2015-10-18 06:35:55 +03:00
|
|
|
Resources: api.ResourceRequirements{
|
2016-01-08 10:02:05 +03:00
|
|
|
Requests: api.ResourceList{
|
|
|
|
api.ResourceCPU: BuildletCPU,
|
|
|
|
api.ResourceMemory: BuildletMemory,
|
|
|
|
},
|
2015-10-18 06:35:55 +03:00
|
|
|
Limits: api.ResourceList{
|
2016-11-23 08:58:31 +03:00
|
|
|
api.ResourceCPU: BuildletCPULimit,
|
2015-10-18 06:35:55 +03:00
|
|
|
api.ResourceMemory: BuildletMemory,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Command: []string{"/usr/local/bin/stage0"},
|
2015-09-12 08:44:06 +03:00
|
|
|
Ports: []api.ContainerPort{
|
|
|
|
{
|
|
|
|
ContainerPort: 80,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Env: []api.EnvVar{
|
|
|
|
{
|
|
|
|
Name: "IN_KUBERNETES",
|
|
|
|
Value: "1",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2015-09-26 07:06:19 +03:00
|
|
|
addEnv := func(name, value string) {
|
|
|
|
for i, _ := range pod.Spec.Containers {
|
|
|
|
pod.Spec.Containers[i].Env = append(pod.Spec.Containers[i].Env, api.EnvVar{
|
|
|
|
Name: name,
|
|
|
|
Value: value,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// The buildlet-binary-url is the URL of the buildlet binary
|
|
|
|
// which the pods are configured to download at boot and run.
|
|
|
|
// This lets us/ update the buildlet more easily than
|
|
|
|
// rebuilding the whole pod image.
|
2016-02-15 02:59:59 +03:00
|
|
|
addEnv("META_BUILDLET_BINARY_URL", conf.BuildletBinaryURL(buildenv.ByProjectID(opts.ProjectID)))
|
all: split builder config into builder & host configs
Our builders are named of the form "GOOS-GOARCH" or
"GOOS-GOARCH-suffix".
Over time we've grown many builders. This CL doesn't change
that. Builders continue to be named and operate as before.
Previously the build configuration file (dashboard/builders.go) made
each builder type ("linux-amd64-race", etc) define how to create a
host running a buildlet of that type, even though many builders had
identical host configs. For example, these builders all share the same
host type (a Kubernetes container):
linux-amd64
linux-amd64-race
linux-386
linux-386-387
And these are the same host type (a GCE VM):
windows-amd64-gce
windows-amd64-race
windows-386-gce
This CL creates a new concept of a "hostType" which defines how
the buildlet is created (Kube, GCE, Reverse, and how), and then each
builder itself references a host type.
Users never see the hostType. (except perhaps in gomote list output)
But they at least never need to care about them.
Reverse buildlets now can only be one hostType at a time, which
simplifies things. We were no longer using multiple roles per machine
once moving to VMs for OS X.
gomote continues to operate as it did previously but its underlying
protocol changed and clients will need to be updated. As a new
feature, gomote now has a new flag to let you reuse a buildlet host
connection for different builder rules if they share the same
underlying host type. But users can ignore that.
This CL is a long-standing TODO (previously attempted and aborted) and
will make many things easier and faster, including the linux-arm
cross-compilation effort, and keeping pre-warmed buildlets of VM types
ready to go.
Updates golang/go#17104
Change-Id: Iad8387f48680424a8441e878a2f4762bf79ea4d2
Reviewed-on: https://go-review.googlesource.com/29551
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
2016-09-22 00:27:37 +03:00
|
|
|
addEnv("META_BUILDLET_HOST_TYPE", hostType)
|
2015-09-26 07:06:19 +03:00
|
|
|
if !opts.TLS.IsZero() {
|
|
|
|
addEnv("META_TLS_CERT", opts.TLS.CertPEM)
|
|
|
|
addEnv("META_TLS_KEY", opts.TLS.KeyPEM)
|
|
|
|
addEnv("META_PASSWORD", opts.TLS.Password())
|
|
|
|
}
|
2015-09-12 08:44:06 +03:00
|
|
|
|
2015-09-26 07:06:19 +03:00
|
|
|
if opts.DeleteIn != 0 {
|
|
|
|
// In case the pod gets away from us (generally: if the
|
|
|
|
// coordinator dies while a build is running), then we
|
2015-10-18 06:35:55 +03:00
|
|
|
// set this annotation of when it should be killed so
|
2015-09-26 07:06:19 +03:00
|
|
|
// we can kill it later when the coordinator is
|
|
|
|
// restarted. The cleanUpOldPods goroutine loop handles
|
|
|
|
// that killing.
|
2015-10-18 06:35:55 +03:00
|
|
|
pod.ObjectMeta.Annotations["delete-at"] = fmt.Sprint(time.Now().Add(opts.DeleteIn).Unix())
|
2015-09-26 07:06:19 +03:00
|
|
|
}
|
|
|
|
|
2016-02-17 09:39:27 +03:00
|
|
|
condRun(opts.OnPodCreating)
|
2016-05-05 21:29:41 +03:00
|
|
|
podStatus, err := kubeClient.RunLongLivedPod(ctx, pod)
|
2015-09-26 07:06:19 +03:00
|
|
|
if err != nil {
|
2016-02-17 09:39:27 +03:00
|
|
|
return nil, err
|
2015-09-12 08:44:06 +03:00
|
|
|
}
|
2015-10-18 06:35:55 +03:00
|
|
|
|
2015-09-26 07:06:19 +03:00
|
|
|
// The new pod must be in Running phase. Possible phases are described at
|
|
|
|
// http://releases.k8s.io/HEAD/docs/user-guide/pod-states.md#pod-phase
|
2016-05-05 21:29:41 +03:00
|
|
|
if podStatus.Phase != api.PodRunning {
|
|
|
|
return nil, fmt.Errorf("pod is in invalid state %q: %v", podStatus.Phase, podStatus.Message)
|
2015-09-26 07:06:19 +03:00
|
|
|
}
|
2016-02-17 09:39:27 +03:00
|
|
|
condRun(opts.OnPodCreated)
|
2015-09-26 07:06:19 +03:00
|
|
|
|
|
|
|
// Wait for the pod to boot and its buildlet to come up.
|
|
|
|
var buildletURL string
|
|
|
|
var ipPort string
|
|
|
|
if !opts.TLS.IsZero() {
|
2016-05-05 21:29:41 +03:00
|
|
|
buildletURL = "https://" + podStatus.PodIP
|
|
|
|
ipPort = podStatus.PodIP + ":443"
|
2015-09-26 07:06:19 +03:00
|
|
|
} else {
|
2016-05-05 21:29:41 +03:00
|
|
|
buildletURL = "http://" + podStatus.PodIP
|
|
|
|
ipPort = podStatus.PodIP + ":80"
|
2015-09-26 07:06:19 +03:00
|
|
|
}
|
|
|
|
condRun(opts.OnGotPodInfo)
|
|
|
|
|
|
|
|
impatientClient := &http.Client{
|
|
|
|
Timeout: 5 * time.Second,
|
|
|
|
Transport: &http.Transport{
|
|
|
|
Dial: defaultDialer(),
|
|
|
|
DisableKeepAlives: true,
|
|
|
|
TLSClientConfig: &tls.Config{
|
|
|
|
InsecureSkipVerify: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2015-10-03 01:38:22 +03:00
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, 3*time.Minute)
|
|
|
|
defer cancel()
|
|
|
|
c := make(chan error, 1)
|
|
|
|
go func() {
|
|
|
|
defer close(c)
|
|
|
|
try := 0
|
|
|
|
for {
|
|
|
|
try++
|
|
|
|
// Make sure pod is still running
|
|
|
|
podStatus, err := kubeClient.PodStatus(ctx, pod.Name)
|
|
|
|
if err != nil {
|
|
|
|
c <- fmt.Errorf("polling the buildlet pod for its status failed: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if podStatus.Phase != api.PodRunning {
|
|
|
|
podLog, err := kubeClient.PodLog(ctx, pod.Name)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("failed to retrieve log for pod %q: %v", pod.Name, err)
|
|
|
|
c <- fmt.Errorf("buildlet pod left the Running phase and entered phase %q", podStatus.Phase)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
log.Printf("log from pod %q: %v", pod.Name, podLog)
|
|
|
|
c <- fmt.Errorf("buildlet pod left the Running phase and entered phase %q", podStatus.Phase)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
res, err := ctxhttp.Get(ctx, impatientClient, buildletURL)
|
|
|
|
if err != nil {
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
res.Body.Close()
|
|
|
|
if res.StatusCode != 200 {
|
|
|
|
c <- fmt.Errorf("buildlet returned HTTP status code %d on try number %d", res.StatusCode, try)
|
|
|
|
}
|
|
|
|
return
|
2015-09-26 07:06:19 +03:00
|
|
|
}
|
2015-10-03 01:38:22 +03:00
|
|
|
}()
|
|
|
|
|
|
|
|
// Wait for the buildlet to respond to an HTTP request. If the timeout happens first, or
|
|
|
|
// if the buildlet pod leaves the running state, return an error.
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return nil, ctx.Err()
|
|
|
|
case err = <-c:
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return NewClient(ipPort, opts.TLS), nil
|
2015-09-26 07:06:19 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func imageID(registry, image string) string {
|
|
|
|
// Sanitize the registry and image names
|
|
|
|
registry = strings.TrimRight(registry, "/")
|
|
|
|
image = strings.TrimLeft(image, "/")
|
|
|
|
return registry + "/" + image
|
2015-09-12 08:44:06 +03:00
|
|
|
}
|