зеркало из https://github.com/golang/build.git
all: add buildlet reverse mode
In -reverse mode, the buildlet dials the coordinator. The connection is then turned around so the coordinator can control the buildlet. This lets us start buildlets on machines without APIs behind firewalls. Also add the -mode flag to the coordinator, which defaults to dev mode when running off GCE. In prod mode the coordinator attempts to become farmer.golang.org and pick up its real certificates. In the new mode, builtin certificates are used allowing you to start a local coordinator and buildlet for testing. A simple connection test will be in a followup CL, as soon as key checking is implemented. Change-Id: I2a7dcdfbb4efda71df31b571788945e9ce1f3365 Reviewed-on: https://go-review.googlesource.com/8490 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
Родитель
9d86d3da60
Коммит
581ddd17e2
|
@ -8,6 +8,7 @@ package buildlet // import "golang.org/x/build/buildlet"
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -62,6 +63,11 @@ func (c *Client) SetDescription(v string) {
|
|||
c.desc = v
|
||||
}
|
||||
|
||||
// SetHTTPClient replaces the underlying HTTP client.
|
||||
func (c *Client) SetHTTPClient(httpClient *http.Client) {
|
||||
c.httpClient = httpClient
|
||||
}
|
||||
|
||||
// defaultDialer returns the net/http package's default Dial function.
|
||||
// Notably, this sets TCP keep-alive values, so when we kill VMs
|
||||
// (whose TCP stacks stop replying, forever), we don't leak file
|
||||
|
@ -341,6 +347,40 @@ func (c *Client) DestroyVM(ts oauth2.TokenSource, proj, zone, instance string) e
|
|||
return retErr
|
||||
}
|
||||
|
||||
// Info provides configuration information about the buildlet.
|
||||
//
|
||||
// A coordinator can use the provided information to decide what, if anything,
|
||||
// to do with a buildlet.
|
||||
type Info struct {
|
||||
Version int // buildlet version, coordinator rejects any value less than 1.
|
||||
Targets []string // list of "goos-goarch[-extra]"
|
||||
}
|
||||
|
||||
// Info returns an Info value describing this buildlet.
|
||||
func (c *Client) Info() (Info, error) {
|
||||
req, err := http.NewRequest("GET", c.URL()+"/info", nil)
|
||||
if err != nil {
|
||||
return Info{}, err
|
||||
}
|
||||
resp, err := c.do(req)
|
||||
if err != nil {
|
||||
return Info{}, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return Info{}, errors.New(resp.Status)
|
||||
}
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
return Info{}, err
|
||||
}
|
||||
var info Info
|
||||
if err := json.Unmarshal(b, &info); err != nil {
|
||||
return Info{}, err
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// WorkDir returns the absolute path to the buildlet work directory.
|
||||
func (c *Client) WorkDir() (string, error) {
|
||||
req, err := http.NewRequest("GET", c.URL()+"/workdir", nil)
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"compress/gzip"
|
||||
"crypto/sha1"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
@ -36,13 +37,16 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/build/buildlet"
|
||||
"google.golang.org/cloud/compute/metadata"
|
||||
)
|
||||
|
||||
var (
|
||||
haltEntireOS = flag.Bool("halt", true, "halt OS in /halt handler. If false, the buildlet process just ends.")
|
||||
workDir = flag.String("workdir", "", "Temporary directory to use. The contents of this directory may be deleted at any time. If empty, TempDir is used to create one.")
|
||||
listenAddr = flag.String("listen", defaultListenAddr(), "address to listen on. Warning: this service is inherently insecure and offers no protection of its own. Do not expose this port to the world.")
|
||||
listenAddr = flag.String("listen", defaultListenAddr(), "address to listen on. Unused in reverse mode. Warning: this service is inherently insecure and offers no protection of its own. Do not expose this port to the world.")
|
||||
reverse = flag.Bool("reverse", false, "instead of listening for a coordinator, work in reverse. The buildlet dials the coordinator and awaits instructions.")
|
||||
coordinator = flag.String("coordinator", "localhost:8119", "address of coordinator, in production use farmer.golang.org. Only used in reverse mode.")
|
||||
)
|
||||
|
||||
func defaultListenAddr() string {
|
||||
|
@ -119,8 +123,17 @@ func main() {
|
|||
http.Handle("/tgz", requireAuth(handleGetTGZ))
|
||||
http.Handle("/removeall", requireAuth(handleRemoveAll))
|
||||
http.Handle("/workdir", requireAuth(handleWorkDir))
|
||||
http.Handle("/info", requireAuth(handleInfo))
|
||||
http.Handle("/ls", requireAuth(handleLs))
|
||||
|
||||
if *reverse {
|
||||
dialCoordinator()
|
||||
} else {
|
||||
listenForCoordinator()
|
||||
}
|
||||
}
|
||||
|
||||
func listenForCoordinator() {
|
||||
tlsCert, tlsKey := metadataValue("tls-cert"), metadataValue("tls-key")
|
||||
if (tlsCert == "") != (tlsKey == "") {
|
||||
log.Fatalf("tls-cert and tls-key must both be supplied, or neither.")
|
||||
|
@ -782,6 +795,27 @@ func handleWorkDir(w http.ResponseWriter, r *http.Request) {
|
|||
fmt.Fprint(w, *workDir)
|
||||
}
|
||||
|
||||
func handleInfo(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
http.Error(w, "requires GET method", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// TODO(crawshaw): make targets configurable
|
||||
info := buildlet.Info{
|
||||
Version: 1,
|
||||
Targets: []string{
|
||||
runtime.GOOS + "-" + runtime.GOARCH,
|
||||
},
|
||||
}
|
||||
b, err := json.Marshal(info)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
func handleLs(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
http.Error(w, "requires GET method", http.StatusBadRequest)
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func dialCoordinator() {
|
||||
caCert := testCoordinatorCA
|
||||
addr := *coordinator
|
||||
if strings.HasPrefix(addr, "farmer.golang.org") {
|
||||
if addr == "farmer.golang.org" {
|
||||
addr = "farmer.golang.org:443"
|
||||
}
|
||||
caCert = coordinatorCA
|
||||
}
|
||||
|
||||
caPool := x509.NewCertPool()
|
||||
if !caPool.AppendCertsFromPEM([]byte(caCert)) {
|
||||
log.Fatal("failed to append coordinator CA certificate")
|
||||
}
|
||||
|
||||
log.Printf("Dialing coordinator %s...", addr)
|
||||
tcpConn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not dial %s: %v", addr, err)
|
||||
}
|
||||
config := &tls.Config{
|
||||
ServerName: "go",
|
||||
RootCAs: caPool,
|
||||
}
|
||||
conn := tls.Client(tcpConn, config)
|
||||
if err := conn.Handshake(); err != nil {
|
||||
log.Fatalf("failed to handshake with coordinator: %v", err)
|
||||
}
|
||||
bufr := bufio.NewReader(conn)
|
||||
|
||||
// TODO(crawshaw): include build key as part of initial request.
|
||||
req, err := http.NewRequest("GET", "/reverse", nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := req.Write(conn); err != nil {
|
||||
log.Fatalf("coordinator /reverse request failed: %v", err)
|
||||
}
|
||||
if _, err := http.ReadResponse(bufr, req); err != nil {
|
||||
log.Fatalf("coordinator /reverse response failed: %v", err)
|
||||
}
|
||||
|
||||
// The client becomes the simple http server.
|
||||
log.Printf("Connected to coordinator, serving HTTP back at them.")
|
||||
srv := &http.Server{}
|
||||
srv.Serve(newReverseListener(conn))
|
||||
}
|
||||
|
||||
func newReverseListener(c net.Conn) net.Listener {
|
||||
rl := &reverseListener{c: c}
|
||||
return rl
|
||||
}
|
||||
|
||||
// reverseListener serves out a single underlying conn, once.
|
||||
// After that it blocks. A one-connection, boring net.Listener.
|
||||
type reverseListener struct {
|
||||
c net.Conn
|
||||
}
|
||||
|
||||
func (rl *reverseListener) Accept() (net.Conn, error) {
|
||||
if rl.c != nil {
|
||||
c := rl.c
|
||||
rl.c = nil
|
||||
return c, nil
|
||||
}
|
||||
// TODO(crawshaw): return error when the connection is closed and
|
||||
// make sure the function calling srv.Serve redials the communicator.
|
||||
select {}
|
||||
}
|
||||
|
||||
func (rl *reverseListener) Close() error { return nil }
|
||||
func (rl *reverseListener) Addr() net.Addr { return reverseAddr("buildlet") }
|
||||
|
||||
// reverseAddr implements net.Addr for reverseListener.
|
||||
type reverseAddr string
|
||||
|
||||
func (a reverseAddr) Network() string { return "reverse" }
|
||||
func (a reverseAddr) String() string { return "reverse:" + string(a) }
|
||||
|
||||
/*
|
||||
Certificate authority and the coordinator SSL key were created with:
|
||||
|
||||
openssl genrsa -out ca_key.pem 2048
|
||||
openssl req -x509 -new -key ca_key.pem -out ca_cert.pem -days 1068 -subj /CN="go"
|
||||
openssl genrsa -out key.pem 2048
|
||||
openssl req -new -out cert_req.pem -key key.pem -subj /CN="go"
|
||||
openssl x509 -req -in cert_req.pem -out cert.pem -CAkey ca_key.pem -CA ca_cert.pem -days 730 -CAcreateserial -CAserial serial
|
||||
*/
|
||||
|
||||
// coordinatorCA is the production CA cert for farmer.golang.org.
|
||||
const coordinatorCA = `-----BEGIN CERTIFICATE-----
|
||||
MIIDCzCCAfOgAwIBAgIJANl4KOv9Cj4UMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNV
|
||||
BAMTAmdvMB4XDTE1MDQwNTIwMTE0OFoXDTE4MDMwODIwMTE0OFowDTELMAkGA1UE
|
||||
AxMCZ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJ/oLb+ksvNScl
|
||||
zIweMGv2ZWRdWW3o9vWIMpOhkiYuBOZjp7zvs89OuKNdC1ylJs3ENnNtD8QOG1Ze
|
||||
kM3s6MTjCLVZUX4218HAenGifaunTNfbW1/q/tTnZh4Kri00vgq9jFtYnlqFLYhT
|
||||
PlmDMdpgOY4ligc/1bSPWVsI7CKCbh3fAz67m++opVE0M7LFp8bhkyFv/dnhZFxo
|
||||
s9ei3ZKFLjYJdZUNRMZ+HcqBzXMQR7HeCOD2pZ1yoHJw1b3Ebe4YOcQCHq4moW7W
|
||||
DavISKSXl7DKZYX1QlFUmEMkl5aMIEHUJ0oI2wnL9+u5s1NU2/k8sSxbH7Y/cKio
|
||||
cFPwuMt7AgMBAAGjbjBsMB0GA1UdDgQWBBS5f/j+8YL9B8THnoAXIhQty3vDZjA9
|
||||
BgNVHSMENjA0gBS5f/j+8YL9B8THnoAXIhQty3vDZqERpA8wDTELMAkGA1UEAxMC
|
||||
Z2+CCQDZeCjr/Qo+FDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBU
|
||||
EOOl2ChJyxFg8b4OrG/EC0HMxic2CakRsj6GWQlAwNU8+3o2u2+zYqKhuREDazsZ
|
||||
1+0f54iU4TXPgPLiOVLQT8AOM6BDDeZfugAopAf0QaIXW5AmM5hnkhW035aXZgx9
|
||||
rYageMGnnkK2H7E7WlcFbGcPjZtbpZyFnGoAvxcUfOzdnm/LLuvFg6YWf1ynXsNI
|
||||
aOx5LNVDhzcQlHZ26ueOLoyIpTQxqvo+hwmIOVDLlZ9bz2BS6FevFjsciJmcDL8N
|
||||
cmY1/5cC/4NzpnN95cvZxp3FX8Ka7YFun03ubjXzXttoeyrxP2WFXuc2D2hkTJPE
|
||||
Co9z2+Nue1JHG9JcDaeW
|
||||
-----END CERTIFICATE-----`
|
||||
|
||||
// testCoordinatorCA is a dev mode cert, not used in production.
|
||||
const testCoordinatorCA = `-----BEGIN CERTIFICATE-----
|
||||
MIIDCzCCAfOgAwIBAgIJAPvaWgVSI9PaMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNV
|
||||
BAMTAmdvMB4XDTE1MDQwNjAzMTc0MVoXDTE4MDMwOTAzMTc0MVowDTELMAkGA1UE
|
||||
AxMCZ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJ6t6PGkTk5CnR
|
||||
+ZVkHq8w9VgDutnTIED3fWQLZLlc7oyexY4wLqmB/fYxINtmtWg7tUon8Y6SMPBF
|
||||
51bam7qc69iWYuSUVkhHcQSGYM/OUKXmtl5V2W9HqfHT+Kcqi8Vm2E946LPMCtKJ
|
||||
JUuzSYYLkXFl8JZw0bi8CROZ23LY7FTZTK/lGUun65bDCTB9AuB/BlclBBtT7pDg
|
||||
6hSc73tMDWRZZ2c4rY0LXYgqbW9Zs0E8ePrKjHGFKxwQlDu0EKhjN/v6HWwq4qXD
|
||||
Zlcx8tiPdFIpUOPN5SkpJq80XiDLy1Cqxxc0gdbM1uxIxYwNzlJqwybVqx8E9H/E
|
||||
y4NAdg0xAgMBAAGjbjBsMB0GA1UdDgQWBBSXjKSDNj0jnlgUsb7lQU6K7CvUGjA9
|
||||
BgNVHSMENjA0gBSXjKSDNj0jnlgUsb7lQU6K7CvUGqERpA8wDTELMAkGA1UEAxMC
|
||||
Z2+CCQD72loFUiPT2jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCl
|
||||
YGLMKAAXgqr4Wj3sCOHfzeZR7fD0ngJ45eP08woXyc6Lg+2kcaOjNVIQ7k91XacP
|
||||
XeoWexeVnaNNxc0B3uWGqy54AF+6ZuJ8Ybtm3KiFrjYd4iuvQUS4wYYh8Iu83chX
|
||||
TjB7sEliFX8+KNSWONw3vULfggMugyTnRilW8qOWd0Xx729NlsvC+OFJc2RVkGoq
|
||||
bmE4LZKjOf0SAh32d1Ye4hH1lPjWkGnVtXiBZbtqk9Ctc1bn6Vq2UxsE/BbZHBlc
|
||||
0iKSFmwBiTqOyCs9q9Hpb012HqZYV+4CMBDsR21yAtecSuY8Rse9Vc+POuyRuY25
|
||||
oObGb36g+BHVuGJxjbFo
|
||||
-----END CERTIFICATE-----`
|
|
@ -21,6 +21,7 @@ import (
|
|||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"runtime"
|
||||
|
@ -52,6 +53,8 @@ var (
|
|||
cleanZones = flag.String("zones", "us-central1-a,us-central1-b,us-central1-f", "Comma-separated list of zones to periodically clean of stale build VMs (ones that failed to shut themselves down)")
|
||||
|
||||
buildLogBucket = flag.String("logbucket", "go-build-log", "GCS bucket to put trybot build failures in")
|
||||
|
||||
mode = flag.String("mode", "", "valid modes are 'dev', 'prod', or '' for auto-detect")
|
||||
)
|
||||
|
||||
// LOCK ORDER:
|
||||
|
@ -108,6 +111,18 @@ const (
|
|||
)
|
||||
|
||||
func readGCSFile(name string) ([]byte, error) {
|
||||
if *mode == "dev" {
|
||||
b, ok := testFiles[name]
|
||||
if !ok {
|
||||
return nil, &os.PathError{
|
||||
Op: "open",
|
||||
Path: name,
|
||||
Err: os.ErrNotExist,
|
||||
}
|
||||
}
|
||||
return []byte(b), nil
|
||||
}
|
||||
|
||||
r, err := storage.NewReader(serviceCtx, "go-builder-data", name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -116,6 +131,53 @@ func readGCSFile(name string) ([]byte, error) {
|
|||
return ioutil.ReadAll(r)
|
||||
}
|
||||
|
||||
// Fake keys signed by a fake CA.
|
||||
var testFiles = map[string]string{
|
||||
"farmer-cert.pem": `-----BEGIN CERTIFICATE-----
|
||||
MIICljCCAX4CCQCoS+/smvkG2TANBgkqhkiG9w0BAQUFADANMQswCQYDVQQDEwJn
|
||||
bzAeFw0xNTA0MDYwMzE3NDJaFw0xNzA0MDUwMzE3NDJaMA0xCzAJBgNVBAMTAmdv
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1NMaVxX8RfCMtQB18azV
|
||||
hL6/U7C8W2G+8WXYeFuOpgP2SHnMbsUeTiUYWS1xqAxUh3Vl/TT1HIASRDL7kBis
|
||||
yj+drspafnCr4Yp9oJx1xlIhVXGD/SyHk5oewkjkNEmrFtUT07mT2lmZqD3XJ+6V
|
||||
aQslRxhPEkLGsXIA/hCucPIplI9jgLY8TmOBhQ7RzXAnk/ayAzDkCgkWB4k/zaFy
|
||||
LiHjEkE7O7PIjjY51btCLep9QSts98zojY5oYNj2RdQOZa56MHAlh9hbdpm+P1vp
|
||||
2QBpsDbVpHYv2VPCPvkdOGU1/nzumsxHy17DcirKP8Tuf6zMf9obeuSlMvUUPptl
|
||||
hwIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBxvUMKsX+DEhZSmc164IuSVJ9ucZ97
|
||||
+KWn4nCwnVkI/RrsJpiTj3pZNRkAxq2vmZTpUdU0CgGHdZNXp/6s/GX4cSzFphSf
|
||||
WZQN0CG/O50SQ39m7fz/dZ2Xse6EH2grr6KN0QsDhK/RVxecQv57rY9nLFHnC60t
|
||||
vJBDC739lWlnsGDxylJNxEk2l5c2rJdn82yGw2G9pQ/LDVAtO1G2rxGkpi4FcpGk
|
||||
rNAa6MiwcyFHcAr3OsigLm4Q9bCS6YXfQDvCZGAR91ADXVWDFC1sgBgM3U3+1bGp
|
||||
tgXUVKymUvoVq0BiY4BCCYDluoErgZDytLmnUOxrykYi532VpRbbK2ja
|
||||
-----END CERTIFICATE-----`,
|
||||
"farmer-key.pem": `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEA1NMaVxX8RfCMtQB18azVhL6/U7C8W2G+8WXYeFuOpgP2SHnM
|
||||
bsUeTiUYWS1xqAxUh3Vl/TT1HIASRDL7kBisyj+drspafnCr4Yp9oJx1xlIhVXGD
|
||||
/SyHk5oewkjkNEmrFtUT07mT2lmZqD3XJ+6VaQslRxhPEkLGsXIA/hCucPIplI9j
|
||||
gLY8TmOBhQ7RzXAnk/ayAzDkCgkWB4k/zaFyLiHjEkE7O7PIjjY51btCLep9QSts
|
||||
98zojY5oYNj2RdQOZa56MHAlh9hbdpm+P1vp2QBpsDbVpHYv2VPCPvkdOGU1/nzu
|
||||
msxHy17DcirKP8Tuf6zMf9obeuSlMvUUPptlhwIDAQABAoIBAAJOPyzOWitPzdZw
|
||||
KNbzbmS/xEbd1UyQJIds+QlkxIjb5iEm4KYakJd8I2Vj7qVJbOkCxpYVqsoiQRBo
|
||||
FP2cptKSGd045/4SrmoFHBNPXp9FaIMKdcmaX+Wjd83XCFHgsm/O4yYaDpYA/n8q
|
||||
HFicZxX6Pu8kPkcOXiSx/XzDJYCnuec0GIfiJfbrQEwNLA+Ck2HnFfLy6LyrgCqi
|
||||
eqaxyBoLolzjW7guWV6e/ECsnLXx2n/Pj4l1aqIFKlYxOjBIKRqeUsqzMFpOCbrx
|
||||
z/scaBuH88hO96jbGZWUAm3R6ZslocQ6TaENYWNVKN1SeGISiE3hRoMAUIu1eHVu
|
||||
mEzOjvECgYEA9Ypu04NzVjAHdZRwrP7IiX3+CmbyNatdZXIoagp8boPBYWw7QeL8
|
||||
TPwvc3PCSIjxcT+Jv2hHTZ9Ofz9vAm/XJx6Ios9o/uAbytA+RAolQJWtLGuFLKv1
|
||||
wxq78iDFcIWq3iPwpl8FJaXeCb/bsNP9jruPhwWWbJVvD1eTif09ZzsCgYEA3ePo
|
||||
aQ5S0YrPtaf5r70eSBloe5vveG/kW3EW0QMrN6YlOhGSX+mjdAJk7XI/JW6vVPYS
|
||||
aK+g+ZnzV7HL421McuVH8mmwPHi48l5o2FewF54qYfOoTAJS1cjV08j8WtQsrEax
|
||||
HHom4m4joQEm0o4QEnTxJDS8/u7T/hhMALxeziUCgYANwevjvgHAWoCQffiyOLRT
|
||||
v9N0EcCQcUGSZYsOJfhC2O8E3mOTlXw9dAPUnC/OkJ22krDNILKeDsb/Kja2FD4h
|
||||
2vwc4zIm1be47WIPveHIdJp3Wq7jid8DR4QwVNW7MEIaoDjjmX9YVKrUMQPGLJqQ
|
||||
XMH19sIu41CNs4J4wM+n8QKBgBiIcFPdP47neBuvnM2vbT+vf3vbO9jnFip+EHW/
|
||||
kfGvLwKCmtp77JSRBzOxpAWxfTU5l8N3V6cBPIR/pflZRlCVxSSqRtAI0PoLMjBp
|
||||
UZDq7eiylfMBdsMoV2v5Ft28A8xwbHinkNEMOGg+xloVVvWTdG36XsMZCNtZOF4E
|
||||
db75AoGBAIk6IW5O2lk9Vc537TCyLpl2HYCP0jI3v6xIkFFolnfHPEgsXLJo9YU8
|
||||
crVtB0zy4jzjN/SClc/iaeOzk5Ot+iwSRFBZu2jdt0TRxbG+cd+6vKLs0Baw6kB1
|
||||
gpRUwP6i5yhi838rMgurGVFr3O/0Sv7wMx5UNEJ/RopbQ2K/bnwn
|
||||
-----END RSA PRIVATE KEY-----`,
|
||||
}
|
||||
|
||||
func listenAndServeTLS() {
|
||||
certPEM, err := readGCSFile("farmer-cert.pem")
|
||||
if err != nil {
|
||||
|
@ -134,6 +196,9 @@ func listenAndServeTLS() {
|
|||
}
|
||||
|
||||
server := &http.Server{Addr: ":443"}
|
||||
if *mode == "dev" {
|
||||
server.Addr = ":8119"
|
||||
}
|
||||
config := &tls.Config{
|
||||
NextProtos: []string{"http/1.1"},
|
||||
Certificates: []tls.Certificate{cert},
|
||||
|
@ -166,15 +231,33 @@ func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
|
|||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if err := initGCE(); err != nil {
|
||||
err := initGCE()
|
||||
if err != nil {
|
||||
if *mode == "" {
|
||||
*mode = "dev"
|
||||
}
|
||||
log.Printf("VM support disabled due to error initializing GCE: %v", err)
|
||||
} else {
|
||||
if *mode == "" {
|
||||
*mode = "prod"
|
||||
}
|
||||
}
|
||||
switch *mode {
|
||||
case "dev", "prod":
|
||||
log.Printf("Running in %s mode", *mode)
|
||||
default:
|
||||
log.Fatalf("Unknown mode: %q", *mode)
|
||||
}
|
||||
|
||||
http.HandleFunc("/", handleStatus)
|
||||
http.HandleFunc("/try", handleTryStatus)
|
||||
http.HandleFunc("/logs", handleLogs)
|
||||
http.HandleFunc("/debug/goroutines", handleDebugGoroutines)
|
||||
http.HandleFunc("/reverse", handleReverse)
|
||||
go func() {
|
||||
if *mode == "dev" {
|
||||
return
|
||||
}
|
||||
err := http.ListenAndServe(":80", nil)
|
||||
if err != nil {
|
||||
log.Fatalf("http.ListenAndServe:80: %v", err)
|
||||
|
@ -182,6 +265,11 @@ func main() {
|
|||
}()
|
||||
go listenAndServeTLS()
|
||||
|
||||
if *mode == "dev" {
|
||||
// TODO(crawshaw): do more in test mode
|
||||
select {}
|
||||
}
|
||||
|
||||
go gcePool.cleanUpOldVMs()
|
||||
|
||||
// Start the Docker processes on this host polling Gerrit and
|
||||
|
|
|
@ -225,6 +225,9 @@ func (p *gceBuildletPool) instanceUsed(instName string) bool {
|
|||
// "delete-at" metadata attribute on them when created to some time
|
||||
// that's well beyond their expected lifetime.
|
||||
func (p *gceBuildletPool) cleanUpOldVMs() {
|
||||
if *mode == "dev" {
|
||||
return
|
||||
}
|
||||
if computeService == nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
// 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 main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/build/buildlet"
|
||||
)
|
||||
|
||||
const minBuildletVersion = 1
|
||||
|
||||
var reversePool = &reverseBuildletPool{}
|
||||
|
||||
type reverseBuildletPool struct {
|
||||
mu sync.Mutex
|
||||
buildlets []reverseBuildlet
|
||||
}
|
||||
|
||||
type reverseBuildlet struct {
|
||||
info buildlet.Info
|
||||
client *buildlet.Client
|
||||
}
|
||||
|
||||
func handleReverse(w http.ResponseWriter, r *http.Request) {
|
||||
if r.TLS == nil {
|
||||
http.Error(w, "buildlet registration requires SSL", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// TODO(crawshaw): check key
|
||||
|
||||
conn, bufrw, err := w.(http.Hijacker).Hijack()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
log.Printf("Registering reverse buildlet %s", r.RemoteAddr)
|
||||
|
||||
// The server becomes a (very simple) http client.
|
||||
(&http.Response{Status: "200 OK", Proto: "HTTP/1.1"}).Write(conn)
|
||||
|
||||
client := buildlet.NewClient("none", buildlet.NoKeyPair)
|
||||
client.SetHTTPClient(&http.Client{
|
||||
Transport: newRoundTripper(bufrw),
|
||||
})
|
||||
info, err := client.Info()
|
||||
if err != nil {
|
||||
log.Printf("Reverse connection did not answer /info: %v", err)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
if info.Version < minBuildletVersion {
|
||||
log.Printf("Buildlet too old: %s, %+v", r.RemoteAddr, info)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
log.Printf("Buildlet %s: %+v", r.RemoteAddr, info)
|
||||
// TODO(crawshaw): register buildlet with pool, pass conn
|
||||
// TODO(crawshaw): add connection test
|
||||
select {}
|
||||
}
|
||||
|
||||
func newRoundTripper(bufrw *bufio.ReadWriter) *reverseRoundTripper {
|
||||
return &reverseRoundTripper{
|
||||
bufrw: bufrw,
|
||||
sema: make(chan bool, 1),
|
||||
}
|
||||
}
|
||||
|
||||
// reverseRoundTripper is an http client that serializes all requests
|
||||
// over a *bufio.ReadWriter.
|
||||
//
|
||||
// Attempts at concurrent requests return an error.
|
||||
type reverseRoundTripper struct {
|
||||
bufrw *bufio.ReadWriter
|
||||
sema chan bool
|
||||
}
|
||||
|
||||
func (c *reverseRoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) {
|
||||
select {
|
||||
case c.sema <- true:
|
||||
default:
|
||||
return nil, fmt.Errorf("reverseRoundTripper: line busy")
|
||||
}
|
||||
if err := req.Write(c.bufrw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.bufrw.Flush(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err = http.ReadResponse(c.bufrw.Reader, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Body = &reverseLockedBody{resp.Body, c.sema}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
type reverseLockedBody struct {
|
||||
body io.ReadCloser
|
||||
sema chan bool
|
||||
}
|
||||
|
||||
func (b *reverseLockedBody) Read(p []byte) (n int, err error) {
|
||||
return b.body.Read(p)
|
||||
}
|
||||
|
||||
func (b *reverseLockedBody) Close() error {
|
||||
err := b.body.Close()
|
||||
<-b.sema
|
||||
b.body = nil // prevent double close
|
||||
return err
|
||||
}
|
Загрузка…
Ссылка в новой задаче