<commit_before>// 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.

// The stage0 command looks up the buildlet's URL from its environment
// (GCE metadata service, scaleway, etc), downloads it, and runs
// it. If not on GCE, such as when in a Linux Docker container being
// developed and tested locally, the stage0 instead looks for the
// META_BUILDLET_BINARY_URL environment to have a URL to the buildlet
// binary.
package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"time"

	"cloud.google.com/go/compute/metadata"
	"golang.org/x/build/internal/httpdl"
	"golang.org/x/build/internal/untar"
)

// This lets us be lazy and put the stage0 start-up in rc.local where
// it might race with the network coming up, rather than write proper
// upstart+systemd+init scripts:
var networkWait = flag.Duration("network-wait", 0, "if zero, a default is used if needed")

const osArch = runtime.GOOS + "/" + runtime.GOARCH

const attr = "buildlet-binary-url"

// untar helper, for the Windows image prep script.
var (
	untarFile    = flag.String("untar-file", "", "if non-empty, tar.gz to untar to --untar-dest-dir")
	untarDestDir = flag.String("untar-dest-dir", "", "destination directory to untar --untar-file to")
)

// configureSerialLogOutput and closeSerialLogOutput are set non-nil
// on some platforms to configure log output to go to the serial
// console and to close the serial port, respectively.
var (
	configureSerialLogOutput func()
	closeSerialLogOutput     func()
)

func main() {
	if configureSerialLogOutput != nil {
		configureSerialLogOutput()
	}
	log.SetPrefix("stage0: ")
	flag.Parse()

	if *untarFile != "" {
		log.Printf("running in untar mode, untarring %q to %q", *untarFile, *untarDestDir)
		untarMode()
		log.Printf("done untarring; exiting")
		return
	}
	log.Printf("bootstrap binary running")

	switch osArch {
	case "linux/arm":
		switch env := os.Getenv("GO_BUILDER_ENV"); env {
		case "linux-arm-arm5spacemonkey", "host-linux-arm-scaleway":
			// No setup currently.
		default:
			panic(fmt.Sprintf("unknown/unspecified $GO_BUILDER_ENV value %q", env))
		}
	case "linux/arm64":
		switch env := os.Getenv("GO_BUILDER_ENV"); env {
		case "host-linux-arm64-packet", "host-linux-arm64-linaro":
			// No special setup.
		default:
			panic(fmt.Sprintf("unknown/unspecified $GO_BUILDER_ENV value %q", env))
		}
	case "linux/ppc64":
		initOregonStatePPC64()
	case "linux/ppc64le":
		initOregonStatePPC64le()
	}

	if !awaitNetwork() {
		sleepFatalf("network didn't become reachable")
	}

	// Note: we name it ".exe" for Windows, but the name also
	// works fine on Linux, etc.
	target := filepath.FromSlash("./buildlet.exe")
	if err := download(target, buildletURL()); err != nil {
		sleepFatalf("Downloading %s: %v", buildletURL, err)
	}

	if runtime.GOOS != "windows" {
		if err := os.Chmod(target, 0755); err != nil {
			log.Fatal(err)
		}
	}

	env := os.Environ()
	if isUnix() && os.Getuid() == 0 {
		if os.Getenv("USER") == "" {
			env = append(env, "USER=root")
		}
		if os.Getenv("HOME") == "" {
			env = append(env, "HOME=/root")
		}
	}

	cmd := exec.Command(target)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Env = env
	switch buildenv := os.Getenv("GO_BUILDER_ENV"); buildenv {
	case "linux-arm-arm5spacemonkey":
		cmd.Args = append(cmd.Args, legacyReverseBuildletArgs(buildenv)...)
	case "host-linux-arm-scaleway":
		scalewayArgs := append(
			legacyReverseBuildletArgs(buildenv),
			"--hostname="+os.Getenv("HOSTNAME"),
		)
		cmd.Args = append(cmd.Args,
			scalewayArgs...,
		)
	}
	switch osArch {
	case "linux/s390x":
		cmd.Args = append(cmd.Args, "--workdir=/data/golang/workdir")
		cmd.Args = append(cmd.Args, legacyReverseBuildletArgs("linux-s390x-ibm")...)
	case "linux/arm64":
		switch v := os.Getenv("GO_BUILDER_ENV"); v {
		case "host-linux-arm64-packet", "host-linux-arm64-linaro":
			hostname := os.Getenv("HOSTNAME") // if empty, docker container name is used
			cmd.Args = append(cmd.Args,
				"--reverse-type="+v,
				"--workdir=/workdir",
				"--hostname="+hostname,
				"--halt=false",
				"--reboot=false",
				"--coordinator=farmer.golang.org:443",
			)
		default:
			panic(fmt.Sprintf("unknown/unspecified $GO_BUILDER_ENV value %q", env))
		}
	case "linux/ppc64":
		cmd.Args = append(cmd.Args, legacyReverseBuildletArgs("linux-ppc64-buildlet")...)
	case "linux/ppc64le":
		cmd.Args = append(cmd.Args, legacyReverseBuildletArgs("linux-ppc64le-buildlet")...)
	case "solaris/amd64":
		cmd.Args = append(cmd.Args, legacyReverseBuildletArgs("solaris-amd64-smartosbuildlet")...)
	}
	// Release the serial port (if we opened it) so the buildlet
	// process can open & write to it. At least on Windows, only
	// one process can have it open.
	if closeSerialLogOutput != nil {
		closeSerialLogOutput()
	}
	if err := cmd.Run(); err != nil {
		if configureSerialLogOutput != nil {
			configureSerialLogOutput()
		}
		sleepFatalf("Error running buildlet: %v", err)
	}
}

// legacyReverseBuildletArgs passes builder as the deprecated --reverse flag.
// New code should use --reverse-type instead.
func legacyReverseBuildletArgs(builder string) []string {
	return []string{
		"--halt=false",
		"--reverse=" + builder,
		"--coordinator=farmer.golang.org:443",
	}
}

// awaitNetwork reports whether the network came up within 30 seconds,
// determined somewhat arbitrarily via a DNS lookup for google.com.
func awaitNetwork() bool {
	timeout := 30 * time.Second
	if runtime.GOOS == "windows" {
		timeout = 5 * time.Minute // empirically slower sometimes?
	}
	if *networkWait != 0 {
		timeout = *networkWait
	}
	deadline := time.Now().Add(timeout)
	var lastSpam time.Time
	log.Printf("waiting for network.")
	for time.Now().Before(deadline) {
		t0 := time.Now()
		if isNetworkUp() {
			log.Printf("network is up.")
			return true
		}
		failAfter := time.Since(t0)
		if now := time.Now(); now.After(lastSpam.Add(5 * time.Second)) {
			const round = time.Second / 10
			log.Printf("network still down; probe failure took %v", failAfter/round*round)
			lastSpam = now
		}
		time.Sleep(1 * time.Second)
	}
	log.Printf("gave up waiting for network")
	return false
}

func isNetworkUp() bool {
	const probeURL = "http://farmer.golang.org/netcheck" // 404 is fine.
	c := &http.Client{
		Timeout: 5 * time.Second,
		Transport: &http.Transport{
			DisableKeepAlives: true,
		},
	}
	res, err := c.Get(probeURL)
	if err != nil {
		return false
	}
	res.Body.Close()
	return true
}

func buildletURL() string {
	if os.Getenv("GO_BUILDER_ENV") == "linux-arm-arm5spacemonkey" {
		return "https://storage.googleapis.com/go-builder-data/buildlet.linux-arm-arm5"
	}
	switch osArch {
	case "linux/s390x":
		return "https://storage.googleapis.com/go-builder-data/buildlet.linux-s390x"
	case "linux/arm64":
		return "https://storage.googleapis.com/go-builder-data/buildlet.linux-arm64"
	case "linux/ppc64":
		return "https://storage.googleapis.com/go-builder-data/buildlet.linux-ppc64"
	case "linux/ppc64le":
		return "https://storage.googleapis.com/go-builder-data/buildlet.linux-ppc64le"
	case "solaris/amd64":
		return "https://storage.googleapis.com/go-builder-data/buildlet.solaris-amd64"
	}
	// The buildlet download URL is located in an env var
	// when the buildlet is not running on GCE, or is running
	// on Kubernetes.
	if !metadata.OnGCE() || os.Getenv("IN_KUBERNETES") == "1" {
		if v := os.Getenv("META_BUILDLET_BINARY_URL"); v != "" {
			return v
		}
		sleepFatalf("Not on GCE, and no META_BUILDLET_BINARY_URL specified.")
	}
	v, err := metadata.InstanceAttributeValue(attr)
	if err != nil {
		sleepFatalf("Failed to look up %q attribute value: %v", attr, err)
	}
	return v
}

func sleepFatalf(format string, args ...interface{}) {
	log.Printf(format, args...)
	if runtime.GOOS == "windows" {
		log.Printf("(sleeping for 1 minute before failing)")
		time.Sleep(time.Minute) // so user has time to see it in cmd.exe, maybe
	}
	os.Exit(1)
}

func download(file, url string) error {
	log.Printf("downloading %s to %s ...\n", url, file)
	const maxTry = 3
	var lastErr error
	for try := 1; try <= maxTry; try++ {
		if try > 1 {
			// network should be up by now per awaitNetwork, so just retry
			// shortly a few time on errors.
			time.Sleep(2)
		}
		err := httpdl.Download(file, url)
		if err == nil {
			fi, err := os.Stat(file)
			if err != nil {
				return err
			}
			log.Printf("downloaded %s (%d bytes)", file, fi.Size())
			return nil
		}
		lastErr = err
		log.Printf("try %d/%d download failure: %v", try, maxTry, err)
	}
	return lastErr
}

func aptGetInstall(pkgs ...string) {
	args := append([]string{"--yes", "install"}, pkgs...)
	cmd := exec.Command("apt-get", args...)
	if out, err := cmd.CombinedOutput(); err != nil {
		log.Fatalf("error running apt-get install: %s", out)
	}
}

func initBootstrapDir(destDir, tgzCache string) {
	if err := os.MkdirAll(destDir, 0755); err != nil {
		log.Fatal(err)
	}
	// TODO(bradfitz): rewrite this to use Go instead of curl+tar
	// if this ever gets used on platforms besides Unix. For
	// Windows and Plan 9 we bake in the bootstrap tarball into
	// the image anyway. So this works for now. Solaris might require
	// tweaking to use gtar instead or something.
	latestURL := fmt.Sprintf("https://storage.googleapis.com/go-builder-data/gobootstrap-%s-%s.tar.gz",
		runtime.GOOS, runtime.GOARCH)
	curl := exec.Command("/usr/bin/curl", "-R", "-o", tgzCache, "-z", tgzCache, latestURL)
	out, err := curl.CombinedOutput()
	if err != nil {
		log.Fatalf("curl error fetching %s to %s: %s", latestURL, out, err)
	}
	tar := exec.Command("tar", "zxf", tgzCache)
	tar.Dir = destDir
	out, err = tar.CombinedOutput()
	if err != nil {
		log.Fatalf("error untarring %s to %s: %s", tgzCache, destDir, out)
	}
}

func initOregonStatePPC64() {
	aptGetInstall("gcc", "strace", "libc6-dev", "gdb")
	initBootstrapDir("/usr/local/go-bootstrap", "/usr/local/go-bootstrap.tar.gz")
}

func initOregonStatePPC64le() {
	aptGetInstall("gcc", "strace", "libc6-dev", "gdb")
	initBootstrapDir("/usr/local/go-bootstrap", "/usr/local/go-bootstrap.tar.gz")
}

func isUnix() bool {
	switch runtime.GOOS {
	case "plan9", "windows":
		return false
	}
	return true
}

func untarMode() {
	if *untarDestDir == "" {
		log.Fatal("--untar-dest-dir must not be empty")
	}
	if fi, err := os.Stat(*untarDestDir); err != nil || !fi.IsDir() {
		if err != nil {
			log.Fatalf("--untar-dest-dir %q: %v", *untarDestDir, err)
		}
		log.Fatalf("--untar-dest-dir %q not a directory.", *untarDestDir)
	}
	f, err := os.Open(*untarFile)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
	if err := untar.Untar(f, *untarDestDir); err != nil {
		log.Fatalf("Untarring %q to %q: %v", *untarFile, *untarDestDir, err)
	}
}
<commit_msg>cmd/buildlet/stage0: more logging about various delays, pass in env to child<commit_after>// 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.

// The stage0 command looks up the buildlet's URL from its environment
// (GCE metadata service, scaleway, etc), downloads it, and runs
// it. If not on GCE, such as when in a Linux Docker container being
// developed and tested locally, the stage0 instead looks for the
// META_BUILDLET_BINARY_URL environment to have a URL to the buildlet
// binary.
package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"time"

	"cloud.google.com/go/compute/metadata"
	"golang.org/x/build/internal/httpdl"
	"golang.org/x/build/internal/untar"
)

// This lets us be lazy and put the stage0 start-up in rc.local where
// it might race with the network coming up, rather than write proper
// upstart+systemd+init scripts:
var networkWait = flag.Duration("network-wait", 0, "if zero, a default is used if needed")

const osArch = runtime.GOOS + "/" + runtime.GOARCH

const attr = "buildlet-binary-url"

// untar helper, for the Windows image prep script.
var (
	untarFile    = flag.String("untar-file", "", "if non-empty, tar.gz to untar to --untar-dest-dir")
	untarDestDir = flag.String("untar-dest-dir", "", "destination directory to untar --untar-file to")
)

// configureSerialLogOutput and closeSerialLogOutput are set non-nil
// on some platforms to configure log output to go to the serial
// console and to close the serial port, respectively.
var (
	configureSerialLogOutput func()
	closeSerialLogOutput     func()
)

var timeStart = time.Now()

func main() {
	if configureSerialLogOutput != nil {
		configureSerialLogOutput()
	}
	log.SetPrefix("stage0: ")
	flag.Parse()

	if *untarFile != "" {
		log.Printf("running in untar mode, untarring %q to %q", *untarFile, *untarDestDir)
		untarMode()
		log.Printf("done untarring; exiting")
		return
	}
	log.Printf("bootstrap binary running")

	switch osArch {
	case "linux/arm":
		switch env := os.Getenv("GO_BUILDER_ENV"); env {
		case "linux-arm-arm5spacemonkey", "host-linux-arm-scaleway":
			// No setup currently.
		default:
			panic(fmt.Sprintf("unknown/unspecified $GO_BUILDER_ENV value %q", env))
		}
	case "linux/arm64":
		switch env := os.Getenv("GO_BUILDER_ENV"); env {
		case "host-linux-arm64-packet", "host-linux-arm64-linaro":
			// No special setup.
		default:
			panic(fmt.Sprintf("unknown/unspecified $GO_BUILDER_ENV value %q", env))
		}
	case "linux/ppc64":
		initOregonStatePPC64()
	case "linux/ppc64le":
		initOregonStatePPC64le()
	}

	if !awaitNetwork() {
		sleepFatalf("network didn't become reachable")
	}
	timeNetwork := time.Now()
	netDelay := prettyDuration(timeNetwork.Sub(timeStart))
	log.Printf("network up after %v", netDelay)

	// Note: we name it ".exe" for Windows, but the name also
	// works fine on Linux, etc.
	target := filepath.FromSlash("./buildlet.exe")
	if err := download(target, buildletURL()); err != nil {
		sleepFatalf("Downloading %s: %v", buildletURL, err)
	}

	if runtime.GOOS != "windows" {
		if err := os.Chmod(target, 0755); err != nil {
			log.Fatal(err)
		}
	}
	downloadDelay := prettyDuration(time.Since(timeNetwork))
	log.Printf("downloaded buildlet in %v", downloadDelay)

	env := os.Environ()
	if isUnix() && os.Getuid() == 0 {
		if os.Getenv("USER") == "" {
			env = append(env, "USER=root")
		}
		if os.Getenv("HOME") == "" {
			env = append(env, "HOME=/root")
		}
	}
	env = append(env, fmt.Sprintf("GO_STAGE0_NET_DELAY=%v", netDelay))
	env = append(env, fmt.Sprintf("GO_STAGE0_DL_DELAY=%v", downloadDelay))

	cmd := exec.Command(target)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Env = env
	switch buildenv := os.Getenv("GO_BUILDER_ENV"); buildenv {
	case "linux-arm-arm5spacemonkey":
		cmd.Args = append(cmd.Args, legacyReverseBuildletArgs(buildenv)...)
	case "host-linux-arm-scaleway":
		scalewayArgs := append(
			legacyReverseBuildletArgs(buildenv),
			"--hostname="+os.Getenv("HOSTNAME"),
		)
		cmd.Args = append(cmd.Args,
			scalewayArgs...,
		)
	}
	switch osArch {
	case "linux/s390x":
		cmd.Args = append(cmd.Args, "--workdir=/data/golang/workdir")
		cmd.Args = append(cmd.Args, legacyReverseBuildletArgs("linux-s390x-ibm")...)
	case "linux/arm64":
		switch v := os.Getenv("GO_BUILDER_ENV"); v {
		case "host-linux-arm64-packet", "host-linux-arm64-linaro":
			hostname := os.Getenv("HOSTNAME") // if empty, docker container name is used
			cmd.Args = append(cmd.Args,
				"--reverse-type="+v,
				"--workdir=/workdir",
				"--hostname="+hostname,
				"--halt=false",
				"--reboot=false",
				"--coordinator=farmer.golang.org:443",
			)
		default:
			panic(fmt.Sprintf("unknown/unspecified $GO_BUILDER_ENV value %q", env))
		}
	case "linux/ppc64":
		cmd.Args = append(cmd.Args, legacyReverseBuildletArgs("linux-ppc64-buildlet")...)
	case "linux/ppc64le":
		cmd.Args = append(cmd.Args, legacyReverseBuildletArgs("linux-ppc64le-buildlet")...)
	case "solaris/amd64":
		cmd.Args = append(cmd.Args, legacyReverseBuildletArgs("solaris-amd64-smartosbuildlet")...)
	}
	// Release the serial port (if we opened it) so the buildlet
	// process can open & write to it. At least on Windows, only
	// one process can have it open.
	if closeSerialLogOutput != nil {
		closeSerialLogOutput()
	}
	if err := cmd.Run(); err != nil {
		if configureSerialLogOutput != nil {
			configureSerialLogOutput()
		}
		sleepFatalf("Error running buildlet: %v", err)
	}
}

// legacyReverseBuildletArgs passes builder as the deprecated --reverse flag.
// New code should use --reverse-type instead.
func legacyReverseBuildletArgs(builder string) []string {
	return []string{
		"--halt=false",
		"--reverse=" + builder,
		"--coordinator=farmer.golang.org:443",
	}
}

// awaitNetwork reports whether the network came up within 30 seconds,
// determined somewhat arbitrarily via a DNS lookup for google.com.
func awaitNetwork() bool {
	timeout := 30 * time.Second
	if runtime.GOOS == "windows" {
		timeout = 5 * time.Minute // empirically slower sometimes?
	}
	if *networkWait != 0 {
		timeout = *networkWait
	}
	deadline := time.Now().Add(timeout)
	var lastSpam time.Time
	log.Printf("waiting for network.")
	for time.Now().Before(deadline) {
		t0 := time.Now()
		if isNetworkUp() {
			return true
		}
		failAfter := time.Since(t0)
		if now := time.Now(); now.After(lastSpam.Add(5 * time.Second)) {
			log.Printf("network still down for %v; probe failure took %v",
				prettyDuration(time.Since(timeStart)),
				prettyDuration(failAfter))
			lastSpam = now
		}
		time.Sleep(1 * time.Second)
	}
	log.Printf("gave up waiting for network")
	return false
}

// isNetworkUp reports whether the network is up by hitting an
// known-up HTTP server. It might block for a few seconds before
// returning an answer.
func isNetworkUp() bool {
	const probeURL = "http://farmer.golang.org/netcheck" // 404 is fine.
	c := &http.Client{
		Timeout: 5 * time.Second,
		Transport: &http.Transport{
			DisableKeepAlives: true,
		},
	}
	res, err := c.Get(probeURL)
	if err != nil {
		return false
	}
	res.Body.Close()
	return true
}

func buildletURL() string {
	if os.Getenv("GO_BUILDER_ENV") == "linux-arm-arm5spacemonkey" {
		return "https://storage.googleapis.com/go-builder-data/buildlet.linux-arm-arm5"
	}
	switch osArch {
	case "linux/s390x":
		return "https://storage.googleapis.com/go-builder-data/buildlet.linux-s390x"
	case "linux/arm64":
		return "https://storage.googleapis.com/go-builder-data/buildlet.linux-arm64"
	case "linux/ppc64":
		return "https://storage.googleapis.com/go-builder-data/buildlet.linux-ppc64"
	case "linux/ppc64le":
		return "https://storage.googleapis.com/go-builder-data/buildlet.linux-ppc64le"
	case "solaris/amd64":
		return "https://storage.googleapis.com/go-builder-data/buildlet.solaris-amd64"
	}
	// The buildlet download URL is located in an env var
	// when the buildlet is not running on GCE, or is running
	// on Kubernetes.
	if !metadata.OnGCE() || os.Getenv("IN_KUBERNETES") == "1" {
		if v := os.Getenv("META_BUILDLET_BINARY_URL"); v != "" {
			return v
		}
		sleepFatalf("Not on GCE, and no META_BUILDLET_BINARY_URL specified.")
	}
	v, err := metadata.InstanceAttributeValue(attr)
	if err != nil {
		sleepFatalf("Failed to look up %q attribute value: %v", attr, err)
	}
	return v
}

func sleepFatalf(format string, args ...interface{}) {
	log.Printf(format, args...)
	if runtime.GOOS == "windows" {
		log.Printf("(sleeping for 1 minute before failing)")
		time.Sleep(time.Minute) // so user has time to see it in cmd.exe, maybe
	}
	os.Exit(1)
}

func download(file, url string) error {
	log.Printf("downloading %s to %s ...\n", url, file)
	const maxTry = 3
	var lastErr error
	for try := 1; try <= maxTry; try++ {
		if try > 1 {
			// network should be up by now per awaitNetwork, so just retry
			// shortly a few time on errors.
			time.Sleep(2)
		}
		err := httpdl.Download(file, url)
		if err == nil {
			fi, err := os.Stat(file)
			if err != nil {
				return err
			}
			log.Printf("downloaded %s (%d bytes)", file, fi.Size())
			return nil
		}
		lastErr = err
		log.Printf("try %d/%d download failure: %v", try, maxTry, err)
	}
	return lastErr
}

func aptGetInstall(pkgs ...string) {
	args := append([]string{"--yes", "install"}, pkgs...)
	cmd := exec.Command("apt-get", args...)
	if out, err := cmd.CombinedOutput(); err != nil {
		log.Fatalf("error running apt-get install: %s", out)
	}
}

func initBootstrapDir(destDir, tgzCache string) {
	if err := os.MkdirAll(destDir, 0755); err != nil {
		log.Fatal(err)
	}
	// TODO(bradfitz): rewrite this to use Go instead of curl+tar
	// if this ever gets used on platforms besides Unix. For
	// Windows and Plan 9 we bake in the bootstrap tarball into
	// the image anyway. So this works for now. Solaris might require
	// tweaking to use gtar instead or something.
	latestURL := fmt.Sprintf("https://storage.googleapis.com/go-builder-data/gobootstrap-%s-%s.tar.gz",
		runtime.GOOS, runtime.GOARCH)
	curl := exec.Command("/usr/bin/curl", "-R", "-o", tgzCache, "-z", tgzCache, latestURL)
	out, err := curl.CombinedOutput()
	if err != nil {
		log.Fatalf("curl error fetching %s to %s: %s", latestURL, out, err)
	}
	tar := exec.Command("tar", "zxf", tgzCache)
	tar.Dir = destDir
	out, err = tar.CombinedOutput()
	if err != nil {
		log.Fatalf("error untarring %s to %s: %s", tgzCache, destDir, out)
	}
}

func initOregonStatePPC64() {
	aptGetInstall("gcc", "strace", "libc6-dev", "gdb")
	initBootstrapDir("/usr/local/go-bootstrap", "/usr/local/go-bootstrap.tar.gz")
}

func initOregonStatePPC64le() {
	aptGetInstall("gcc", "strace", "libc6-dev", "gdb")
	initBootstrapDir("/usr/local/go-bootstrap", "/usr/local/go-bootstrap.tar.gz")
}

func isUnix() bool {
	switch runtime.GOOS {
	case "plan9", "windows":
		return false
	}
	return true
}

func untarMode() {
	if *untarDestDir == "" {
		log.Fatal("--untar-dest-dir must not be empty")
	}
	if fi, err := os.Stat(*untarDestDir); err != nil || !fi.IsDir() {
		if err != nil {
			log.Fatalf("--untar-dest-dir %q: %v", *untarDestDir, err)
		}
		log.Fatalf("--untar-dest-dir %q not a directory.", *untarDestDir)
	}
	f, err := os.Open(*untarFile)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
	if err := untar.Untar(f, *untarDestDir); err != nil {
		log.Fatalf("Untarring %q to %q: %v", *untarFile, *untarDestDir, err)
	}
}

func prettyDuration(d time.Duration) time.Duration {
	const round = time.Second / 10
	return d / round * round
}
<|endoftext|>


<commit_before>// Copyright 2015 ISRG.  All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package main

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"fmt"
	"math/big"
	"sync"
	"testing"
	"time"

	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"

	"github.com/letsencrypt/boulder/core"
	"github.com/letsencrypt/boulder/sa"
	"github.com/letsencrypt/boulder/sa/satest"
	"github.com/letsencrypt/boulder/test"
)

var dbConnStr = "mysql+tcp://boulder@localhost:3306/boulder_sa_test"

func BenchmarkCheckCert(b *testing.B) {
	dbMap, err := sa.NewDbMap(dbConnStr)
	if err != nil {
		fmt.Println("Couldn't connect to database")
		return
	}

	checker := newChecker(dbMap)
	testKey, _ := rsa.GenerateKey(rand.Reader, 1024)
	expiry := time.Now().AddDate(0, 0, 1)
	serial := big.NewInt(1337)
	rawCert := x509.Certificate{
		Subject: pkix.Name{
			CommonName: "example.com",
		},
		NotAfter:     expiry,
		DNSNames:     []string{"example-a.com"},
		SerialNumber: serial,
	}
	certDer, _ := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey)
	cert := core.Certificate{
		Status:  core.StatusValid,
		Serial:  core.SerialToString(serial),
		Digest:  core.Fingerprint256(certDer),
		DER:     certDer,
		Issued:  time.Now(),
		Expires: expiry,
	}
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		checker.checkCert(cert)
	}
}

func TestCheckCert(t *testing.T) {
	testKey, _ := rsa.GenerateKey(rand.Reader, 1024)
	checker := newChecker(nil)
	fc := clock.NewFake()
	fc.Add(time.Hour * 24 * 90)
	checker.clock = fc

	issued := checker.clock.Now().Add(-time.Hour * 24 * 45)
	goodExpiry := issued.Add(checkPeriod)
	serial := big.NewInt(1337)
	// Problems
	//   Blacklsited common name
	//   Expiry period is too long
	//   Basic Constraints aren't set
	//   Wrong key usage (none)
	rawCert := x509.Certificate{
		Subject: pkix.Name{
			CommonName: "example.com",
		},
		NotAfter:              goodExpiry.AddDate(0, 0, 1), // Period too long
		DNSNames:              []string{"example-a.com"},
		SerialNumber:          serial,
		BasicConstraintsValid: false,
	}
	brokenCertDer, err := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey)
	test.AssertNotError(t, err, "Couldn't create certificate")
	// Problems
	//   Digest doesn't match
	//   Serial doesn't match
	//   Expiry doesn't match
	cert := core.Certificate{
		Status:  core.StatusValid,
		DER:     brokenCertDer,
		Issued:  issued,
		Expires: goodExpiry.AddDate(0, 0, 2), // Expiration doesn't match
	}

	problems := checker.checkCert(cert)
	test.AssertEquals(t, len(problems), 7)

	// Fix the problems
	rawCert.Subject.CommonName = "example-a.com"
	rawCert.NotAfter = goodExpiry
	rawCert.BasicConstraintsValid = true
	rawCert.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
	goodCertDer, err := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey)
	test.AssertNotError(t, err, "Couldn't create certificate")
	parsed, err := x509.ParseCertificate(goodCertDer)
	test.AssertNotError(t, err, "Couldn't parse created certificate")
	cert.Serial = core.SerialToString(serial)
	cert.Digest = core.Fingerprint256(goodCertDer)
	cert.DER = goodCertDer
	cert.Expires = parsed.NotAfter
	problems = checker.checkCert(cert)
	test.AssertEquals(t, len(problems), 0)
}

func TestGetAndProcessCerts(t *testing.T) {
	dbMap, err := sa.NewDbMap(dbConnStr)
	test.AssertNotError(t, err, "Couldn't connect to database")
	checker := newChecker(dbMap)
	sa, err := sa.NewSQLStorageAuthority(dbMap)
	test.AssertNotError(t, err, "Couldn't create SA to insert certificates")
	defer func() {
		dbMap.TruncateTables()
		test.AssertNotError(t, err, "Failed to truncate tables")
	}()

	testKey, _ := rsa.GenerateKey(rand.Reader, 1024)
	// Problems
	//   Expiry period is too long
	rawCert := x509.Certificate{
		Subject: pkix.Name{
			CommonName: "not-blacklisted.com",
		},
		BasicConstraintsValid: true,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
	}
	reg, err := sa.NewRegistration(core.Registration{
		Key: satest.GoodJWK(),
	})
	fmt.Println(err)
	test.AssertNotError(t, err, "Couldn't create registration")
	for i := int64(0); i < 5; i++ {
		rawCert.SerialNumber = big.NewInt(i)
		certDER, err := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey)
		test.AssertNotError(t, err, "Couldn't create certificate")
		_, err = sa.AddCertificate(certDER, reg.ID)
		test.AssertNotError(t, err, "Couldn't add certificate")
	}

	err = checker.getCerts()
	test.AssertNotError(t, err, "Failed to retrieve certificates")
	test.AssertEquals(t, len(checker.certs), 5)
	wg := new(sync.WaitGroup)
	wg.Add(1)
	checker.processCerts(wg)
	test.AssertEquals(t, checker.issuedReport.BadCerts, int64(5))
	test.AssertEquals(t, len(checker.issuedReport.Entries), 5)
}
<commit_msg>Remove debug println<commit_after>// Copyright 2015 ISRG.  All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package main

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"fmt"
	"math/big"
	"sync"
	"testing"
	"time"

	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"

	"github.com/letsencrypt/boulder/core"
	"github.com/letsencrypt/boulder/sa"
	"github.com/letsencrypt/boulder/sa/satest"
	"github.com/letsencrypt/boulder/test"
)

var dbConnStr = "mysql+tcp://boulder@localhost:3306/boulder_sa_test"

func BenchmarkCheckCert(b *testing.B) {
	dbMap, err := sa.NewDbMap(dbConnStr)
	if err != nil {
		fmt.Println("Couldn't connect to database")
		return
	}

	checker := newChecker(dbMap)
	testKey, _ := rsa.GenerateKey(rand.Reader, 1024)
	expiry := time.Now().AddDate(0, 0, 1)
	serial := big.NewInt(1337)
	rawCert := x509.Certificate{
		Subject: pkix.Name{
			CommonName: "example.com",
		},
		NotAfter:     expiry,
		DNSNames:     []string{"example-a.com"},
		SerialNumber: serial,
	}
	certDer, _ := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey)
	cert := core.Certificate{
		Status:  core.StatusValid,
		Serial:  core.SerialToString(serial),
		Digest:  core.Fingerprint256(certDer),
		DER:     certDer,
		Issued:  time.Now(),
		Expires: expiry,
	}
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		checker.checkCert(cert)
	}
}

func TestCheckCert(t *testing.T) {
	testKey, _ := rsa.GenerateKey(rand.Reader, 1024)
	checker := newChecker(nil)
	fc := clock.NewFake()
	fc.Add(time.Hour * 24 * 90)
	checker.clock = fc

	issued := checker.clock.Now().Add(-time.Hour * 24 * 45)
	goodExpiry := issued.Add(checkPeriod)
	serial := big.NewInt(1337)
	// Problems
	//   Blacklsited common name
	//   Expiry period is too long
	//   Basic Constraints aren't set
	//   Wrong key usage (none)
	rawCert := x509.Certificate{
		Subject: pkix.Name{
			CommonName: "example.com",
		},
		NotAfter:              goodExpiry.AddDate(0, 0, 1), // Period too long
		DNSNames:              []string{"example-a.com"},
		SerialNumber:          serial,
		BasicConstraintsValid: false,
	}
	brokenCertDer, err := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey)
	test.AssertNotError(t, err, "Couldn't create certificate")
	// Problems
	//   Digest doesn't match
	//   Serial doesn't match
	//   Expiry doesn't match
	cert := core.Certificate{
		Status:  core.StatusValid,
		DER:     brokenCertDer,
		Issued:  issued,
		Expires: goodExpiry.AddDate(0, 0, 2), // Expiration doesn't match
	}

	problems := checker.checkCert(cert)
	test.AssertEquals(t, len(problems), 7)

	// Fix the problems
	rawCert.Subject.CommonName = "example-a.com"
	rawCert.NotAfter = goodExpiry
	rawCert.BasicConstraintsValid = true
	rawCert.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
	goodCertDer, err := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey)
	test.AssertNotError(t, err, "Couldn't create certificate")
	parsed, err := x509.ParseCertificate(goodCertDer)
	test.AssertNotError(t, err, "Couldn't parse created certificate")
	cert.Serial = core.SerialToString(serial)
	cert.Digest = core.Fingerprint256(goodCertDer)
	cert.DER = goodCertDer
	cert.Expires = parsed.NotAfter
	problems = checker.checkCert(cert)
	test.AssertEquals(t, len(problems), 0)
}

func TestGetAndProcessCerts(t *testing.T) {
	dbMap, err := sa.NewDbMap(dbConnStr)
	test.AssertNotError(t, err, "Couldn't connect to database")
	checker := newChecker(dbMap)
	sa, err := sa.NewSQLStorageAuthority(dbMap)
	test.AssertNotError(t, err, "Couldn't create SA to insert certificates")
	defer func() {
		dbMap.TruncateTables()
		test.AssertNotError(t, err, "Failed to truncate tables")
	}()

	testKey, _ := rsa.GenerateKey(rand.Reader, 1024)
	// Problems
	//   Expiry period is too long
	rawCert := x509.Certificate{
		Subject: pkix.Name{
			CommonName: "not-blacklisted.com",
		},
		BasicConstraintsValid: true,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
	}
	reg, err := sa.NewRegistration(core.Registration{
		Key: satest.GoodJWK(),
	})
	test.AssertNotError(t, err, "Couldn't create registration")
	for i := int64(0); i < 5; i++ {
		rawCert.SerialNumber = big.NewInt(i)
		certDER, err := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey)
		test.AssertNotError(t, err, "Couldn't create certificate")
		_, err = sa.AddCertificate(certDER, reg.ID)
		test.AssertNotError(t, err, "Couldn't add certificate")
	}

	err = checker.getCerts()
	test.AssertNotError(t, err, "Failed to retrieve certificates")
	test.AssertEquals(t, len(checker.certs), 5)
	wg := new(sync.WaitGroup)
	wg.Add(1)
	checker.processCerts(wg)
	test.AssertEquals(t, checker.issuedReport.BadCerts, int64(5))
	test.AssertEquals(t, len(checker.issuedReport.Entries), 5)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"code.cloudfoundry.org/guardian/guardiancmd"
	"code.cloudfoundry.org/lager"
	"github.com/concourse/atc"
	"github.com/concourse/baggageclaim/baggageclaimcmd"
	"github.com/concourse/baggageclaim/fs"
	"github.com/concourse/bin/bindata"
	"github.com/jessevdk/go-flags"
	"github.com/tedsuo/ifrit"
)

const btrfsFSType = 0x9123683e

type GardenBackend guardiancmd.GuardianCommand

func (cmd WorkerCommand) lessenRequirements(command *flags.Command) {
	command.FindOptionByLongName("garden-depot").Required = false
	command.FindOptionByLongName("garden-graph").Required = false
	command.FindOptionByLongName("garden-runc-bin").Required = false
	command.FindOptionByLongName("garden-dadoo-bin").Required = false
	command.FindOptionByLongName("garden-init-bin").Required = false
	command.FindOptionByLongName("garden-nstar-bin").Required = false
	command.FindOptionByLongName("garden-tar-bin").Required = false

	command.FindOptionByLongName("baggageclaim-volumes").Required = false
	command.FindOptionByLongName("baggageclaim-driver").Default = []string{"btrfs"}
}

func (cmd *WorkerCommand) gardenRunner(logger lager.Logger, args []string) (atc.Worker, ifrit.Runner, error) {
	err := cmd.checkRoot()
	if err != nil {
		return atc.Worker{}, nil, err
	}

	assetsDir, err := cmd.restoreVersionedAssets(logger.Session("unpack-assets"))
	if err != nil {
		return atc.Worker{}, nil, err
	}

	depotDir := filepath.Join(cmd.WorkDir, "depot")

	// must be readable by other users so unprivileged containers can run their
	// own `initc' process
	err = os.MkdirAll(depotDir, 0755)
	if err != nil {
		return atc.Worker{}, nil, err
	}

	cmd.Garden.Server.BindIP = guardiancmd.IPFlag(cmd.BindIP)

	cmd.Garden.Containers.Dir = guardiancmd.DirFlag(depotDir)

	cmd.Garden.Bin.Runc = filepath.Join(assetsDir, "bin", "runc")
	cmd.Garden.Bin.Dadoo = guardiancmd.FileFlag(filepath.Join(assetsDir, "bin", "dadoo"))
	cmd.Garden.Bin.Init = guardiancmd.FileFlag(filepath.Join(assetsDir, "bin", "init"))
	cmd.Garden.Bin.NSTar = guardiancmd.FileFlag(filepath.Join(assetsDir, "bin", "nstar"))
	cmd.Garden.Bin.Tar = guardiancmd.FileFlag(filepath.Join(assetsDir, "bin", "tar"))

	iptablesDir := filepath.Join(assetsDir, "iptables")
	cmd.Garden.Bin.IPTables = guardiancmd.FileFlag(filepath.Join(iptablesDir, "sbin", "iptables"))

	cmd.Garden.Network.AllowHostAccess = true

	worker := atc.Worker{
		Platform: "linux",
		Tags:     cmd.Tags,
		Team:     cmd.TeamName,

		HTTPProxyURL:  cmd.HTTPProxy.String(),
		HTTPSProxyURL: cmd.HTTPSProxy.String(),
		NoProxy:       strings.Join(cmd.NoProxy, ","),
		StartTime:     time.Now().Unix(),
	}

	worker.ResourceTypes, err = cmd.extractResources(
		logger.Session("extract-resources"),
		assetsDir,
	)
	if err != nil {
		return atc.Worker{}, nil, err
	}

	worker.Name, err = cmd.workerName()
	if err != nil {
		return atc.Worker{}, nil, err
	}

	runner := guardiancmd.GuardianCommand(cmd.Garden)
	return worker, &runner, nil
}

func (cmd *WorkerCommand) restoreVersionedAssets(logger lager.Logger) (string, error) {
	assetsDir := filepath.Join(cmd.WorkDir, Version)

	restoredDir := filepath.Join(assetsDir, "linux")

	okMarker := filepath.Join(assetsDir, "ok")

	_, err := os.Stat(okMarker)
	if err == nil {
		logger.Info("already-done")
		return restoredDir, nil
	}

	logger.Info("unpacking")

	err = bindata.RestoreAssets(assetsDir, "linux")
	if err != nil {
		logger.Error("failed-to-unpack", err)
		return "", err
	}

	ok, err := os.Create(okMarker)
	if err != nil {
		logger.Error("failed-to-create-ok-marker", err)
		return "", err
	}

	err = ok.Close()
	if err != nil {
		logger.Error("failed-to-close-ok-marker", err)
		return "", err
	}

	logger.Info("done")

	return restoredDir, nil
}

func (cmd *WorkerCommand) baggageclaimRunner(logger lager.Logger) (ifrit.Runner, error) {
	volumesImage := filepath.Join(cmd.WorkDir, "volumes.img")
	volumesDir := filepath.Join(cmd.WorkDir, "volumes")

	assetsDir, err := cmd.restoreVersionedAssets(logger.Session("unpack-assets"))
	if err != nil {
		return nil, err
	}
	btrfsToolsDir := filepath.Join(assetsDir, "btrfs")

	err = os.MkdirAll(volumesDir, 0755)
	if err != nil {
		return nil, err
	}

	var fsStat syscall.Statfs_t
	err = syscall.Statfs(volumesDir, &fsStat)
	if err != nil {
		return nil, fmt.Errorf("failed to stat volumes filesystem: %s", err)
	}

	if fsStat.Type != btrfsFSType {
		filesystem := fs.New(logger.Session("fs"), volumesImage, volumesDir, filepath.Join(btrfsToolsDir, "mkfs.btrfs"))

		err = filesystem.Create(fsStat.Blocks * uint64(fsStat.Bsize))
		if err != nil {
			logger.Error("falling-back-on-naive-driver", err)
			return cmd.naiveBaggageclaimRunner(logger)
		}
	}

	cmd.Baggageclaim.Metrics = cmd.Metrics
	cmd.Baggageclaim.VolumesDir = baggageclaimcmd.DirFlag(volumesDir)

	cmd.Baggageclaim.MkfsBin = filepath.Join(btrfsToolsDir, "mkfs.btrfs")
	cmd.Baggageclaim.BtrfsBin = filepath.Join(btrfsToolsDir, "btrfs")

	return cmd.Baggageclaim.Runner(nil)
}

func (cmd *WorkerCommand) extractResources(logger lager.Logger, assetsDir string) ([]atc.WorkerResourceType, error) {
	var resourceTypes []atc.WorkerResourceType

	resourcesDir := filepath.Join(assetsDir, "resources")

	infos, err := ioutil.ReadDir(resourcesDir)
	if err != nil {
		logger.Error("failed-to-list-resource-assets", err)
		return nil, err
	}

	for _, info := range infos {
		resourceType := info.Name()

		workerResourceType, err := cmd.extractResource(
			logger.Session("extract", lager.Data{"resource-type": resourceType}),
			assetsDir,
			resourcesDir,
			resourceType,
		)
		if err != nil {
			logger.Error("failed-to-extract-resource", err)
			return nil, err
		}

		resourceTypes = append(resourceTypes, workerResourceType)
	}

	return resourceTypes, nil
}

func (cmd *WorkerCommand) extractResource(
	logger lager.Logger,
	assetsDir string,
	resourcesDir string,
	resourceType string,
) (atc.WorkerResourceType, error) {
	resourceImagesDir := filepath.Join(assetsDir, "resource-images")
	tarBin := filepath.Join(assetsDir, "bin", "tar")

	archive := filepath.Join(resourcesDir, resourceType, "rootfs.tar.gz")

	extractedDir := filepath.Join(resourceImagesDir, resourceType)

	rootfsDir := filepath.Join(extractedDir, "rootfs")
	okMarker := filepath.Join(extractedDir, "ok")

	var version string
	versionFile, err := os.Open(filepath.Join(resourcesDir, resourceType, "version"))
	if err != nil {
		logger.Error("failed-to-read-version", err)
		return atc.WorkerResourceType{}, err
	}

	_, err = fmt.Fscanf(versionFile, "%s", &version)
	if err != nil {
		logger.Error("failed-to-parse-version", err)
		return atc.WorkerResourceType{}, err
	}

	defer versionFile.Close()

	_, err = os.Stat(okMarker)
	if err == nil {
		logger.Info("already-extracted")
	} else {
		logger.Info("extracting")

		err := os.RemoveAll(rootfsDir)
		if err != nil {
			logger.Error("failed-to-clear-out-existing-rootfs", err)
			return atc.WorkerResourceType{}, err
		}

		err = os.MkdirAll(rootfsDir, 0755)
		if err != nil {
			logger.Error("failed-to-create-rootfs-dir", err)
			return atc.WorkerResourceType{}, err
		}

		tar := exec.Command(tarBin, "-zxf", archive, "-C", rootfsDir)

		output, err := tar.CombinedOutput()
		if err != nil {
			logger.Error("failed-to-extract-resource", err, lager.Data{
				"output": string(output),
			})
			return atc.WorkerResourceType{}, err
		}

		ok, err := os.Create(okMarker)
		if err != nil {
			logger.Error("failed-to-create-ok-marker", err)
			return atc.WorkerResourceType{}, err
		}

		err = ok.Close()
		if err != nil {
			logger.Error("failed-to-close-ok-marker", err)
			return atc.WorkerResourceType{}, err
		}
	}

	return atc.WorkerResourceType{
		Type:    resourceType,
		Image:   rootfsDir,
		Version: version,
	}, nil
}
<commit_msg>tweaks for latest guardian<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"code.cloudfoundry.org/guardian/guardiancmd"
	"code.cloudfoundry.org/lager"
	"github.com/concourse/atc"
	"github.com/concourse/baggageclaim/baggageclaimcmd"
	"github.com/concourse/baggageclaim/fs"
	"github.com/concourse/bin/bindata"
	"github.com/jessevdk/go-flags"
	"github.com/tedsuo/ifrit"
)

const btrfsFSType = 0x9123683e

type GardenBackend guardiancmd.ServerCommand

func (cmd WorkerCommand) lessenRequirements(command *flags.Command) {
	command.FindOptionByLongName("garden-bind-ip").Default = []string{"7777"}

	command.FindOptionByLongName("garden-depot").Required = false
	command.FindOptionByLongName("garden-graph").Required = false
	command.FindOptionByLongName("garden-runc-bin").Required = false
	command.FindOptionByLongName("garden-dadoo-bin").Required = false
	command.FindOptionByLongName("garden-init-bin").Required = false
	command.FindOptionByLongName("garden-nstar-bin").Required = false
	command.FindOptionByLongName("garden-tar-bin").Required = false

	command.FindOptionByLongName("baggageclaim-volumes").Required = false
	command.FindOptionByLongName("baggageclaim-driver").Default = []string{"btrfs"}
}

func (cmd *WorkerCommand) gardenRunner(logger lager.Logger, args []string) (atc.Worker, ifrit.Runner, error) {
	err := cmd.checkRoot()
	if err != nil {
		return atc.Worker{}, nil, err
	}

	assetsDir, err := cmd.restoreVersionedAssets(logger.Session("unpack-assets"))
	if err != nil {
		return atc.Worker{}, nil, err
	}

	depotDir := filepath.Join(cmd.WorkDir, "depot")

	// must be readable by other users so unprivileged containers can run their
	// own `initc' process
	err = os.MkdirAll(depotDir, 0755)
	if err != nil {
		return atc.Worker{}, nil, err
	}

	cmd.Garden.Server.BindIP = guardiancmd.IPFlag(cmd.BindIP)

	cmd.Garden.Containers.Dir = depotDir

	cmd.Garden.Bin.Runc = filepath.Join(assetsDir, "bin", "runc")
	cmd.Garden.Bin.Dadoo = guardiancmd.FileFlag(filepath.Join(assetsDir, "bin", "dadoo"))
	cmd.Garden.Bin.Init = guardiancmd.FileFlag(filepath.Join(assetsDir, "bin", "init"))
	cmd.Garden.Bin.NSTar = guardiancmd.FileFlag(filepath.Join(assetsDir, "bin", "nstar"))
	cmd.Garden.Bin.Tar = guardiancmd.FileFlag(filepath.Join(assetsDir, "bin", "tar"))

	iptablesDir := filepath.Join(assetsDir, "iptables")
	cmd.Garden.Bin.IPTables = guardiancmd.FileFlag(filepath.Join(iptablesDir, "sbin", "iptables"))

	cmd.Garden.Network.AllowHostAccess = true

	worker := atc.Worker{
		Platform: "linux",
		Tags:     cmd.Tags,
		Team:     cmd.TeamName,

		HTTPProxyURL:  cmd.HTTPProxy.String(),
		HTTPSProxyURL: cmd.HTTPSProxy.String(),
		NoProxy:       strings.Join(cmd.NoProxy, ","),
		StartTime:     time.Now().Unix(),
	}

	worker.ResourceTypes, err = cmd.extractResources(
		logger.Session("extract-resources"),
		assetsDir,
	)
	if err != nil {
		return atc.Worker{}, nil, err
	}

	worker.Name, err = cmd.workerName()
	if err != nil {
		return atc.Worker{}, nil, err
	}

	runner := guardiancmd.ServerCommand(cmd.Garden)
	return worker, &runner, nil
}

func (cmd *WorkerCommand) restoreVersionedAssets(logger lager.Logger) (string, error) {
	assetsDir := filepath.Join(cmd.WorkDir, Version)

	restoredDir := filepath.Join(assetsDir, "linux")

	okMarker := filepath.Join(assetsDir, "ok")

	_, err := os.Stat(okMarker)
	if err == nil {
		logger.Info("already-done")
		return restoredDir, nil
	}

	logger.Info("unpacking")

	err = bindata.RestoreAssets(assetsDir, "linux")
	if err != nil {
		logger.Error("failed-to-unpack", err)
		return "", err
	}

	ok, err := os.Create(okMarker)
	if err != nil {
		logger.Error("failed-to-create-ok-marker", err)
		return "", err
	}

	err = ok.Close()
	if err != nil {
		logger.Error("failed-to-close-ok-marker", err)
		return "", err
	}

	logger.Info("done")

	return restoredDir, nil
}

func (cmd *WorkerCommand) baggageclaimRunner(logger lager.Logger) (ifrit.Runner, error) {
	volumesImage := filepath.Join(cmd.WorkDir, "volumes.img")
	volumesDir := filepath.Join(cmd.WorkDir, "volumes")

	assetsDir, err := cmd.restoreVersionedAssets(logger.Session("unpack-assets"))
	if err != nil {
		return nil, err
	}
	btrfsToolsDir := filepath.Join(assetsDir, "btrfs")

	err = os.MkdirAll(volumesDir, 0755)
	if err != nil {
		return nil, err
	}

	var fsStat syscall.Statfs_t
	err = syscall.Statfs(volumesDir, &fsStat)
	if err != nil {
		return nil, fmt.Errorf("failed to stat volumes filesystem: %s", err)
	}

	if fsStat.Type != btrfsFSType {
		filesystem := fs.New(logger.Session("fs"), volumesImage, volumesDir, filepath.Join(btrfsToolsDir, "mkfs.btrfs"))

		err = filesystem.Create(fsStat.Blocks * uint64(fsStat.Bsize))
		if err != nil {
			logger.Error("falling-back-on-naive-driver", err)
			return cmd.naiveBaggageclaimRunner(logger)
		}
	}

	cmd.Baggageclaim.Metrics = cmd.Metrics
	cmd.Baggageclaim.VolumesDir = baggageclaimcmd.DirFlag(volumesDir)

	cmd.Baggageclaim.MkfsBin = filepath.Join(btrfsToolsDir, "mkfs.btrfs")
	cmd.Baggageclaim.BtrfsBin = filepath.Join(btrfsToolsDir, "btrfs")

	return cmd.Baggageclaim.Runner(nil)
}

func (cmd *WorkerCommand) extractResources(logger lager.Logger, assetsDir string) ([]atc.WorkerResourceType, error) {
	var resourceTypes []atc.WorkerResourceType

	resourcesDir := filepath.Join(assetsDir, "resources")

	infos, err := ioutil.ReadDir(resourcesDir)
	if err != nil {
		logger.Error("failed-to-list-resource-assets", err)
		return nil, err
	}

	for _, info := range infos {
		resourceType := info.Name()

		workerResourceType, err := cmd.extractResource(
			logger.Session("extract", lager.Data{"resource-type": resourceType}),
			assetsDir,
			resourcesDir,
			resourceType,
		)
		if err != nil {
			logger.Error("failed-to-extract-resource", err)
			return nil, err
		}

		resourceTypes = append(resourceTypes, workerResourceType)
	}

	return resourceTypes, nil
}

func (cmd *WorkerCommand) extractResource(
	logger lager.Logger,
	assetsDir string,
	resourcesDir string,
	resourceType string,
) (atc.WorkerResourceType, error) {
	resourceImagesDir := filepath.Join(assetsDir, "resource-images")
	tarBin := filepath.Join(assetsDir, "bin", "tar")

	archive := filepath.Join(resourcesDir, resourceType, "rootfs.tar.gz")

	extractedDir := filepath.Join(resourceImagesDir, resourceType)

	rootfsDir := filepath.Join(extractedDir, "rootfs")
	okMarker := filepath.Join(extractedDir, "ok")

	var version string
	versionFile, err := os.Open(filepath.Join(resourcesDir, resourceType, "version"))
	if err != nil {
		logger.Error("failed-to-read-version", err)
		return atc.WorkerResourceType{}, err
	}

	_, err = fmt.Fscanf(versionFile, "%s", &version)
	if err != nil {
		logger.Error("failed-to-parse-version", err)
		return atc.WorkerResourceType{}, err
	}

	defer versionFile.Close()

	_, err = os.Stat(okMarker)
	if err == nil {
		logger.Info("already-extracted")
	} else {
		logger.Info("extracting")

		err := os.RemoveAll(rootfsDir)
		if err != nil {
			logger.Error("failed-to-clear-out-existing-rootfs", err)
			return atc.WorkerResourceType{}, err
		}

		err = os.MkdirAll(rootfsDir, 0755)
		if err != nil {
			logger.Error("failed-to-create-rootfs-dir", err)
			return atc.WorkerResourceType{}, err
		}

		tar := exec.Command(tarBin, "-zxf", archive, "-C", rootfsDir)

		output, err := tar.CombinedOutput()
		if err != nil {
			logger.Error("failed-to-extract-resource", err, lager.Data{
				"output": string(output),
			})
			return atc.WorkerResourceType{}, err
		}

		ok, err := os.Create(okMarker)
		if err != nil {
			logger.Error("failed-to-create-ok-marker", err)
			return atc.WorkerResourceType{}, err
		}

		err = ok.Close()
		if err != nil {
			logger.Error("failed-to-close-ok-marker", err)
			return atc.WorkerResourceType{}, err
		}
	}

	return atc.WorkerResourceType{
		Type:    resourceType,
		Image:   rootfsDir,
		Version: version,
	}, nil
}
<|endoftext|>


<commit_before>/*
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 main

import (
	"flag"
	"github.com/golang/glog"
	"github.com/spf13/pflag"
	"net/http"
	"os"

	"k8s.io/apiserver/pkg/authentication/authenticator"
	"k8s.io/apiserver/pkg/authorization/authorizer"
	kflag "k8s.io/apiserver/pkg/util/flag"
	"k8s.io/apiserver/pkg/util/logs"
	"k8s.io/cloud-provider-openstack/pkg/identity/keystone"
	"k8s.io/cloud-provider-openstack/pkg/identity/webhook"
)

func webhookServer(authenticator authenticator.Token, authorizer authorizer.Authorizer) http.Handler {
	return &webhook.WebhookHandler{
		Authenticator: authenticator,
		Authorizer:    authorizer,
	}
}

var (
	listenAddr     string
	tlsCertFile    string
	tlsPrivateKey  string
	keystoneURL    string
	keystoneCaFile string
	policyFile     string
	version        string
)

func main() {
	flag.CommandLine.Parse([]string{})
	pflag.StringVar(&listenAddr, "listen", "0.0.0.0:8443", "<address>:<port> to listen on")
	pflag.StringVar(&tlsCertFile, "tls-cert-file", "", "File containing the default x509 Certificate for HTTPS.")
	pflag.StringVar(&tlsPrivateKey, "tls-private-key-file", "", "File containing the default x509 private key matching --tls-cert-file.")
	pflag.StringVar(&keystoneURL, "keystone-url", os.Getenv("OS_AUTH_URL"), "URL for the OpenStack Keystone API")
	pflag.StringVar(&keystoneCaFile, "keystone-ca-file", "", "File containing the certificate authority for Keystone Service.")
	pflag.StringVar(&policyFile, "keystone-policy-file", "", "File containing the policy.")

	kflag.InitFlags()
	logs.InitLogs()
	defer logs.FlushLogs()

	glog.V(1).Infof("k8s-keystone-auth version: %s", version)

	if keystoneURL == "" {
		glog.Fatal("please specify --keystone-url or set the OS_AUTH_URL environment variable.")
	}
	if tlsCertFile == "" || tlsPrivateKey == "" {
		glog.Fatal("Please specify --tls-cert-file and --tls-private-key-file arguments.")
	}
	if policyFile == "" {
		glog.Infof("Argument --keystone-policy-file missing. Only keystone authentication will work. Use RBAC for authorization.")
	}

	authentication_handler, err := keystone.NewKeystoneAuthenticator(keystoneURL, keystoneCaFile)
	if err != nil {
		glog.Fatal(err.Error())
	}

	authorization_handler, err := keystone.NewKeystoneAuthorizer(keystoneURL, keystoneCaFile, policyFile)
	if err != nil {
		glog.Fatal(err.Error())
	}

	http.Handle("/webhook", webhookServer(authentication_handler, authorization_handler))
	glog.Infof("Starting webhook...")
	glog.Fatal(
		http.ListenAndServeTLS(listenAddr,
			tlsCertFile,
			tlsPrivateKey,
			nil))
}
<commit_msg>Support env variables for more arguments<commit_after>/*
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 main

import (
	"flag"
	"github.com/golang/glog"
	"github.com/spf13/pflag"
	"net/http"
	"os"

	"k8s.io/apiserver/pkg/authentication/authenticator"
	"k8s.io/apiserver/pkg/authorization/authorizer"
	kflag "k8s.io/apiserver/pkg/util/flag"
	"k8s.io/apiserver/pkg/util/logs"
	"k8s.io/cloud-provider-openstack/pkg/identity/keystone"
	"k8s.io/cloud-provider-openstack/pkg/identity/webhook"
)

func webhookServer(authenticator authenticator.Token, authorizer authorizer.Authorizer) http.Handler {
	return &webhook.WebhookHandler{
		Authenticator: authenticator,
		Authorizer:    authorizer,
	}
}

var (
	listenAddr     string
	tlsCertFile    string
	tlsPrivateKey  string
	keystoneURL    string
	keystoneCaFile string
	policyFile     string
	version        string
)

func main() {
	flag.CommandLine.Parse([]string{})
	pflag.StringVar(&listenAddr, "listen", "0.0.0.0:8443", "<address>:<port> to listen on")
	pflag.StringVar(&tlsCertFile, "tls-cert-file", os.Getenv("TLS_CERT_FILE"), "File containing the default x509 Certificate for HTTPS.")
	pflag.StringVar(&tlsPrivateKey, "tls-private-key-file", os.Getenv("TLS_PRIVATE_KEY_FILE"), "File containing the default x509 private key matching --tls-cert-file.")
	pflag.StringVar(&keystoneURL, "keystone-url", os.Getenv("OS_AUTH_URL"), "URL for the OpenStack Keystone API")
	pflag.StringVar(&keystoneCaFile, "keystone-ca-file", os.Getenv("KEYSTONE_CA_FILE"), "File containing the certificate authority for Keystone Service.")
	pflag.StringVar(&policyFile, "keystone-policy-file", os.Getenv("KEYSTONE_POLICY_FILE"), "File containing the policy.")

	kflag.InitFlags()
	logs.InitLogs()
	defer logs.FlushLogs()

	glog.V(1).Infof("k8s-keystone-auth version: %s", version)

	if keystoneURL == "" {
		glog.Fatal("please specify --keystone-url or set the OS_AUTH_URL environment variable.")
	}
	if tlsCertFile == "" || tlsPrivateKey == "" {
		glog.Fatal("Please specify --tls-cert-file and --tls-private-key-file arguments.")
	}
	if policyFile == "" {
		glog.Infof("Argument --keystone-policy-file missing. Only keystone authentication will work. Use RBAC for authorization.")
	}

	authentication_handler, err := keystone.NewKeystoneAuthenticator(keystoneURL, keystoneCaFile)
	if err != nil {
		glog.Fatal(err.Error())
	}

	authorization_handler, err := keystone.NewKeystoneAuthorizer(keystoneURL, keystoneCaFile, policyFile)
	if err != nil {
		glog.Fatal(err.Error())
	}

	http.Handle("/webhook", webhookServer(authentication_handler, authorization_handler))
	glog.Infof("Starting webhook...")
	glog.Fatal(
		http.ListenAndServeTLS(listenAddr,
			tlsCertFile,
			tlsPrivateKey,
			nil))
}
<|endoftext|>


<commit_before>package monitor

import (
	"flag"
	"fmt"
	"net"
	"net/http"
	"os"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/docker/leadership"
	"github.com/docker/libkv/store"
	"github.com/docker/libkv/store/zookeeper"
	"github.com/funkygao/gafka"
	"github.com/funkygao/gafka/ctx"
	"github.com/funkygao/gafka/telemetry"
	"github.com/funkygao/gafka/telemetry/influxdb"
	"github.com/funkygao/gafka/zk"
	"github.com/funkygao/go-metrics"
	"github.com/funkygao/golib/signal"
	log "github.com/funkygao/log4go"
	"github.com/julienschmidt/httprouter"
)

// Monitor is the engine that will start/stop plugin watchers.
// It itself is an implementation of Context.
type Monitor struct {
	influxdbAddr   string
	influxdbDbName string
	apiAddr        string
	externalDir    string

	startedAt time.Time
	leadAt    time.Time

	router *httprouter.Router
	zkzone *zk.ZkZone

	candidate *leadership.Candidate

	watchers []Watcher

	inflight *sync.WaitGroup
	stop     chan struct{} // broadcast to all watchers to stop, but might restart again
	quit     chan struct{}
	leader   bool
}

func (this *Monitor) Init() {
	var logFile, zone string
	flag.StringVar(&logFile, "log", "stdout", "log filename")
	flag.StringVar(&zone, "z", "", "zone, required")
	flag.StringVar(&this.apiAddr, "http", ":10025", "api http server addr")
	flag.StringVar(&this.influxdbAddr, "influxAddr", "", "influxdb addr, required")
	flag.StringVar(&this.influxdbDbName, "db", "", "influxdb db name, required")
	flag.StringVar(&this.externalDir, "confd", "", "external script config dir")
	flag.Parse()

	if zone == "" || this.influxdbDbName == "" || this.influxdbAddr == "" {
		panic("zone or influxdb empty, run help ")
	}

	ctx.LoadFromHome()
	this.zkzone = zk.NewZkZone(zk.DefaultConfig(zone, ctx.ZoneZkAddrs(zone)))
	this.watchers = make([]Watcher, 0, 10)
	this.quit = make(chan struct{})

	// export RESTful api
	this.setupRoutes()

	if logFile == "stdout" {
		log.AddFilter("stdout", log.TRACE, log.NewConsoleLogWriter())
	} else {
		log.DeleteFilter("stdout")

		filer := log.NewFileLogWriter(logFile, true, false, 0644)
		filer.SetRotateDaily(true)
		filer.SetFormat("[%d %T] [%L] (%S) %M")
		log.AddFilter("file", log.TRACE, filer)
	}

	rc, err := influxdb.NewConfig(this.influxdbAddr, this.influxdbDbName, "", "", time.Minute)
	if err != nil {
		panic(err)
	}
	telemetry.Default = influxdb.New(metrics.DefaultRegistry, rc)
}

func (this *Monitor) Stop() {
	if this.leader {
		this.leader = false

		log.Info("stopping all watchers ...")
		close(this.stop)

		log.Info("stopping telemetry and flush all metrics...")
		telemetry.Default.Stop()

		this.candidate.Stop()
		log.Info("election stopped")

		// because of github.com/docker/leadership problem, /_kguard/leader is left
		// even when we stop election.
		// so we have to manually clean it here
		if _, err := this.zkzone.Conn().Set("/"+zk.KguardLeaderPath, []byte{}, -1); err != nil {
			log.Error("cleanup: %v", err)
		} else {
			log.Info("cleanup leader zk node done")
		}
	}
}

func (this *Monitor) Start() {
	this.leader = true
	this.leadAt = time.Now()
	this.stop = make(chan struct{})

	go func() {
		log.Info("telemetry started: %s", telemetry.Default.Name())

		if err := telemetry.Default.Start(); err != nil {
			log.Error("telemetry: %v", err)
		}
	}()

	this.inflight = new(sync.WaitGroup)
	this.watchers = this.watchers[:0]
	for name, watcherFactory := range registeredWatchers {
		watcher := watcherFactory()
		this.watchers = append(this.watchers, watcher)

		watcher.Init(this)

		log.Info("created and starting watcher: %s", name)

		this.inflight.Add(1)
		go watcher.Run()
	}

	log.Info("all watchers ready!")

	<-this.stop
	this.inflight.Wait()

	log.Info("all watchers stopped")
}

func (this *Monitor) ServeForever() {
	defer this.zkzone.Close()

	this.startedAt = time.Now()
	log.Info("kguard[%s@%s] starting...", gafka.BuildId, gafka.BuiltAt)

	signal.RegisterHandler(func(sig os.Signal) {
		log.Info("kguard[%s@%s] received signal: %s", gafka.BuildId, gafka.BuiltAt, strings.ToUpper(sig.String()))

		this.Stop()
		close(this.quit)
	}, syscall.SIGINT, syscall.SIGTERM)

	// start the api server
	apiServer := &http.Server{
		Addr:    this.apiAddr,
		Handler: this.router,
	}
	apiListener, err := net.Listen("tcp4", this.apiAddr)
	if err == nil {
		log.Info("api http server ready on %s", this.apiAddr)
		go apiServer.Serve(apiListener)
	} else {
		panic(fmt.Sprintf("api http server: %v", err))
	}

	backend, err := zookeeper.New(this.zkzone.ZkAddrList(), &store.Config{})
	if err != nil {
		panic(err)
	}

	ip, _ := ctx.LocalIP()
	this.candidate = leadership.NewCandidate(backend, zk.KguardLeaderPath, ip.String(), 25*time.Second)
	electedCh, errCh := this.candidate.RunForElection()
	if err != nil {
		panic("Cannot run for election, store is probably down")
	}

	for {
		select {
		case isElected := <-electedCh:
			if isElected {
				log.Info("Won the election, starting all watchers")

				this.Start()
			} else {
				log.Warn("Fails the election, watching election events...")
				this.Stop()
			}

		case err := <-errCh:
			if err != nil {
				log.Error("Error during election: %v", err)
			}

		case <-this.quit:
			apiListener.Close()
			log.Info("api http server closed")
			log.Info("kguard[%s@%s] bye!", gafka.BuildId, gafka.BuiltAt)
			log.Close()
			return
		}
	}

}

func (this *Monitor) ZkZone() *zk.ZkZone {
	return this.zkzone
}

func (this *Monitor) StopChan() <-chan struct{} {
	return this.stop
}

func (this *Monitor) Inflight() *sync.WaitGroup {
	return this.inflight
}

func (this *Monitor) InfluxAddr() string {
	return this.influxdbAddr
}

func (this *Monitor) InfluxDB() string {
	return this.influxdbDbName
}

func (this *Monitor) ExternalDir() string {
	return this.externalDir
}
<commit_msg>IPV6 enabled<commit_after>package monitor

import (
	"flag"
	"fmt"
	"net"
	"net/http"
	"os"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/docker/leadership"
	"github.com/docker/libkv/store"
	"github.com/docker/libkv/store/zookeeper"
	"github.com/funkygao/gafka"
	"github.com/funkygao/gafka/ctx"
	"github.com/funkygao/gafka/telemetry"
	"github.com/funkygao/gafka/telemetry/influxdb"
	"github.com/funkygao/gafka/zk"
	"github.com/funkygao/go-metrics"
	"github.com/funkygao/golib/signal"
	log "github.com/funkygao/log4go"
	"github.com/julienschmidt/httprouter"
)

// Monitor is the engine that will start/stop plugin watchers.
// It itself is an implementation of Context.
type Monitor struct {
	influxdbAddr   string
	influxdbDbName string
	apiAddr        string
	externalDir    string

	startedAt time.Time
	leadAt    time.Time

	router *httprouter.Router
	zkzone *zk.ZkZone

	candidate *leadership.Candidate

	watchers []Watcher

	inflight *sync.WaitGroup
	stop     chan struct{} // broadcast to all watchers to stop, but might restart again
	quit     chan struct{}
	leader   bool
}

func (this *Monitor) Init() {
	var logFile, zone string
	flag.StringVar(&logFile, "log", "stdout", "log filename")
	flag.StringVar(&zone, "z", "", "zone, required")
	flag.StringVar(&this.apiAddr, "http", ":10025", "api http server addr")
	flag.StringVar(&this.influxdbAddr, "influxAddr", "", "influxdb addr, required")
	flag.StringVar(&this.influxdbDbName, "db", "", "influxdb db name, required")
	flag.StringVar(&this.externalDir, "confd", "", "external script config dir")
	flag.Parse()

	if zone == "" || this.influxdbDbName == "" || this.influxdbAddr == "" {
		panic("zone or influxdb empty, run help ")
	}

	ctx.LoadFromHome()
	this.zkzone = zk.NewZkZone(zk.DefaultConfig(zone, ctx.ZoneZkAddrs(zone)))
	this.watchers = make([]Watcher, 0, 10)
	this.quit = make(chan struct{})

	// export RESTful api
	this.setupRoutes()

	if logFile == "stdout" {
		log.AddFilter("stdout", log.TRACE, log.NewConsoleLogWriter())
	} else {
		log.DeleteFilter("stdout")

		filer := log.NewFileLogWriter(logFile, true, false, 0644)
		filer.SetRotateDaily(true)
		filer.SetFormat("[%d %T] [%L] (%S) %M")
		log.AddFilter("file", log.TRACE, filer)
	}

	rc, err := influxdb.NewConfig(this.influxdbAddr, this.influxdbDbName, "", "", time.Minute)
	if err != nil {
		panic(err)
	}
	telemetry.Default = influxdb.New(metrics.DefaultRegistry, rc)
}

func (this *Monitor) Stop() {
	if this.leader {
		this.leader = false

		log.Info("stopping all watchers ...")
		close(this.stop)

		log.Info("stopping telemetry and flush all metrics...")
		telemetry.Default.Stop()

		this.candidate.Stop()
		log.Info("election stopped")

		// because of github.com/docker/leadership problem, /_kguard/leader is left
		// even when we stop election.
		// so we have to manually clean it here
		if _, err := this.zkzone.Conn().Set("/"+zk.KguardLeaderPath, []byte{}, -1); err != nil {
			log.Error("cleanup: %v", err)
		} else {
			log.Info("cleanup leader zk node done")
		}
	}
}

func (this *Monitor) Start() {
	this.leader = true
	this.leadAt = time.Now()
	this.stop = make(chan struct{})

	go func() {
		log.Info("telemetry started: %s", telemetry.Default.Name())

		if err := telemetry.Default.Start(); err != nil {
			log.Error("telemetry: %v", err)
		}
	}()

	this.inflight = new(sync.WaitGroup)
	this.watchers = this.watchers[:0]
	for name, watcherFactory := range registeredWatchers {
		watcher := watcherFactory()
		this.watchers = append(this.watchers, watcher)

		watcher.Init(this)

		log.Info("created and starting watcher: %s", name)

		this.inflight.Add(1)
		go watcher.Run()
	}

	log.Info("all watchers ready!")

	<-this.stop
	this.inflight.Wait()

	log.Info("all watchers stopped")
}

func (this *Monitor) ServeForever() {
	defer this.zkzone.Close()

	this.startedAt = time.Now()
	log.Info("kguard[%s@%s] starting...", gafka.BuildId, gafka.BuiltAt)

	signal.RegisterHandler(func(sig os.Signal) {
		log.Info("kguard[%s@%s] received signal: %s", gafka.BuildId, gafka.BuiltAt, strings.ToUpper(sig.String()))

		this.Stop()
		close(this.quit)
	}, syscall.SIGINT, syscall.SIGTERM)

	// start the api server
	apiServer := &http.Server{
		Addr:    this.apiAddr,
		Handler: this.router,
	}
	apiListener, err := net.Listen("tcp", this.apiAddr)
	if err == nil {
		log.Info("api http server ready on %s", this.apiAddr)
		go apiServer.Serve(apiListener)
	} else {
		panic(fmt.Sprintf("api http server: %v", err))
	}

	backend, err := zookeeper.New(this.zkzone.ZkAddrList(), &store.Config{})
	if err != nil {
		panic(err)
	}

	ip, _ := ctx.LocalIP()
	this.candidate = leadership.NewCandidate(backend, zk.KguardLeaderPath, ip.String(), 25*time.Second)
	electedCh, errCh := this.candidate.RunForElection()
	if err != nil {
		panic("Cannot run for election, store is probably down")
	}

	for {
		select {
		case isElected := <-electedCh:
			if isElected {
				log.Info("Won the election, starting all watchers")

				this.Start()
			} else {
				log.Warn("Fails the election, watching election events...")
				this.Stop()
			}

		case err := <-errCh:
			if err != nil {
				log.Error("Error during election: %v", err)
			}

		case <-this.quit:
			apiListener.Close()
			log.Info("api http server closed")
			log.Info("kguard[%s@%s] bye!", gafka.BuildId, gafka.BuiltAt)
			log.Close()
			return
		}
	}

}

func (this *Monitor) ZkZone() *zk.ZkZone {
	return this.zkzone
}

func (this *Monitor) StopChan() <-chan struct{} {
	return this.stop
}

func (this *Monitor) Inflight() *sync.WaitGroup {
	return this.inflight
}

func (this *Monitor) InfluxAddr() string {
	return this.influxdbAddr
}

func (this *Monitor) InfluxDB() string {
	return this.influxdbDbName
}

func (this *Monitor) ExternalDir() string {
	return this.externalDir
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

package main

import (
	"encoding/json"
	"math"
	"strings"
	"sync"

	"github.com/aristanetworks/glog"
	"github.com/aristanetworks/goarista/gnmi"
	"github.com/golang/protobuf/proto"
	"github.com/golang/protobuf/ptypes/any"
	pb "github.com/openconfig/gnmi/proto/gnmi"
	"github.com/prometheus/client_golang/prometheus"
)

// A metric source.
type source struct {
	addr string
	path string
}

// Since the labels are fixed per-path and per-device we can cache them here,
// to avoid recomputing them.
type labelledMetric struct {
	metric       prometheus.Metric
	labels       []string
	defaultValue float64
	stringMetric bool
}

type collector struct {
	// Protects access to metrics map
	m       sync.Mutex
	metrics map[source]*labelledMetric

	config *Config
}

func newCollector(config *Config) *collector {
	return &collector{
		metrics: make(map[source]*labelledMetric),
		config:  config,
	}
}

// Process a notification and update or create the corresponding metrics.
func (c *collector) update(addr string, message proto.Message) {
	resp, ok := message.(*pb.SubscribeResponse)
	if !ok {
		glog.Errorf("Unexpected type of message: %T", message)
		return
	}

	notif := resp.GetUpdate()
	if notif == nil {
		return
	}

	device := strings.Split(addr, ":")[0]
	prefix := gnmi.StrPath(notif.Prefix)
	// Process deletes first
	for _, del := range notif.Delete {
		path := prefix + gnmi.StrPath(del)
		key := source{addr: device, path: path}
		c.m.Lock()
		if _, ok := c.metrics[key]; ok {
			delete(c.metrics, key)
		} else {
			// TODO: replace this with a prefix tree
			p := path + "/"
			for k := range c.metrics {
				if k.addr == device && strings.HasPrefix(k.path, p) {
					delete(c.metrics, k)
				}
			}
		}
		c.m.Unlock()
	}

	// Process updates next
	for _, update := range notif.Update {
		path := prefix + gnmi.StrPath(update.Path)
		value, suffix, ok := parseValue(update)
		if !ok {
			continue
		}

		var strUpdate bool
		var floatVal float64
		var strVal string

		switch v := value.(type) {
		case float64:
			strUpdate = false
			floatVal = v
		case string:
			strUpdate = true
			strVal = v
		}

		if suffix != "" {
			path += "/" + suffix
		}

		src := source{addr: device, path: path}
		c.m.Lock()
		// Use the cached labels and descriptor if available
		if m, ok := c.metrics[src]; ok {
			if strUpdate {
				// Skip string updates for non string metrics
				if !m.stringMetric {
					c.m.Unlock()
					continue
				}
				// Display a default value and replace the value label with the string value
				floatVal = m.defaultValue
				m.labels[len(m.labels)-1] = strVal
			}

			m.metric = prometheus.MustNewConstMetric(m.metric.Desc(), prometheus.GaugeValue,
				floatVal, m.labels...)
			c.m.Unlock()
			continue
		}

		c.m.Unlock()
		// Get the descriptor and labels for this source
		metric := c.config.getMetricValues(src)
		if metric == nil || metric.desc == nil {
			glog.V(8).Infof("Ignoring unmatched update %v at %s:%s with value %+v",
				update, device, path, value)
			continue
		}

		if strUpdate {
			if !metric.stringMetric {
				// Skip string updates for non string metrics
				continue
			}
			// Display a default value and replace the value label with the string value
			floatVal = metric.defaultValue
			metric.labels[len(metric.labels)-1] = strVal
		}

		// Save the metric and labels in the cache
		c.m.Lock()
		lm := prometheus.MustNewConstMetric(metric.desc, prometheus.GaugeValue,
			floatVal, metric.labels...)
		c.metrics[src] = &labelledMetric{
			metric:       lm,
			labels:       metric.labels,
			defaultValue: metric.defaultValue,
			stringMetric: metric.stringMetric,
		}
		c.m.Unlock()
	}
}

// parseValue takes in an update and parses a value and suffix
// Returns an interface that contains either a string or a float64 as well as a suffix
// Unparseable updates return (0, empty string, false)
func parseValue(update *pb.Update) (interface{}, string, bool) {
	intf, err := gnmi.ExtractValue(update)
	if err != nil {
		return 0, "", false
	}

	switch value := intf.(type) {
	// float64 or string expected as the return value
	case int64:
		return float64(value), "", true
	case uint64:
		return float64(value), "", true
	case float32:
		return float64(value), "", true
	case *pb.Decimal64:
		val := gnmi.DecimalToFloat(value)
		if math.IsInf(val, 0) || math.IsNaN(val) {
			return 0, "", false
		}
		return val, "", true
	case json.Number:
		valFloat, err := value.Float64()
		if err != nil {
			return value, "", true
		}
		return valFloat, "", true
	case *any.Any:
		return value.String(), "", true
	case []interface{}:
		// extract string represetation for now
		return gnmi.StrVal(update.Val), "", false
	case map[string]interface{}:
		if vIntf, ok := value["value"]; ok {
			if num, ok := vIntf.(json.Number); ok {
				valFloat, err := num.Float64()
				if err != nil {
					return num, "value", true
				}
				return valFloat, "value", true
			}
		}
	case bool:
		if value {
			return float64(1), "", true
		}
		return float64(0), "", true
	case string:
		return value, "", true
	default:
		glog.V(9).Infof("Ignoring update with unexpected type: %T", value)
	}

	return 0, "", false
}

// Describe implements prometheus.Collector interface
func (c *collector) Describe(ch chan<- *prometheus.Desc) {
	c.config.getAllDescs(ch)
}

// Collect implements prometheus.Collector interface
func (c *collector) Collect(ch chan<- prometheus.Metric) {
	c.m.Lock()
	for _, m := range c.metrics {
		ch <- m.metric
	}
	c.m.Unlock()
}
<commit_msg>cmd/ocprometheus: patch to handle updates with nil values<commit_after>// Copyright (c) 2017 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

package main

import (
	"encoding/json"
	"math"
	"strings"
	"sync"

	"github.com/aristanetworks/glog"
	"github.com/aristanetworks/goarista/gnmi"
	"github.com/golang/protobuf/proto"
	"github.com/golang/protobuf/ptypes/any"
	pb "github.com/openconfig/gnmi/proto/gnmi"
	"github.com/prometheus/client_golang/prometheus"
)

// A metric source.
type source struct {
	addr string
	path string
}

// Since the labels are fixed per-path and per-device we can cache them here,
// to avoid recomputing them.
type labelledMetric struct {
	metric       prometheus.Metric
	labels       []string
	defaultValue float64
	stringMetric bool
}

type collector struct {
	// Protects access to metrics map
	m       sync.Mutex
	metrics map[source]*labelledMetric

	config *Config
}

func newCollector(config *Config) *collector {
	return &collector{
		metrics: make(map[source]*labelledMetric),
		config:  config,
	}
}

// Process a notification and update or create the corresponding metrics.
func (c *collector) update(addr string, message proto.Message) {
	resp, ok := message.(*pb.SubscribeResponse)
	if !ok {
		glog.Errorf("Unexpected type of message: %T", message)
		return
	}

	notif := resp.GetUpdate()
	if notif == nil {
		return
	}

	device := strings.Split(addr, ":")[0]
	prefix := gnmi.StrPath(notif.Prefix)
	// Process deletes first
	for _, del := range notif.Delete {
		path := prefix + gnmi.StrPath(del)
		key := source{addr: device, path: path}
		c.m.Lock()
		if _, ok := c.metrics[key]; ok {
			delete(c.metrics, key)
		} else {
			// TODO: replace this with a prefix tree
			p := path + "/"
			for k := range c.metrics {
				if k.addr == device && strings.HasPrefix(k.path, p) {
					delete(c.metrics, k)
				}
			}
		}
		c.m.Unlock()
	}

	// Process updates next
	for _, update := range notif.Update {
		path := prefix + gnmi.StrPath(update.Path)
		value, suffix, ok := parseValue(update)
		if !ok {
			continue
		}

		var strUpdate bool
		var floatVal float64
		var strVal string

		switch v := value.(type) {
		case float64:
			strUpdate = false
			floatVal = v
		case string:
			strUpdate = true
			strVal = v
		}

		if suffix != "" {
			path += "/" + suffix
		}

		src := source{addr: device, path: path}
		c.m.Lock()
		// Use the cached labels and descriptor if available
		if m, ok := c.metrics[src]; ok {
			if strUpdate {
				// Skip string updates for non string metrics
				if !m.stringMetric {
					c.m.Unlock()
					continue
				}
				// Display a default value and replace the value label with the string value
				floatVal = m.defaultValue
				m.labels[len(m.labels)-1] = strVal
			}

			m.metric = prometheus.MustNewConstMetric(m.metric.Desc(), prometheus.GaugeValue,
				floatVal, m.labels...)
			c.m.Unlock()
			continue
		}

		c.m.Unlock()
		// Get the descriptor and labels for this source
		metric := c.config.getMetricValues(src)
		if metric == nil || metric.desc == nil {
			glog.V(8).Infof("Ignoring unmatched update %v at %s:%s with value %+v",
				update, device, path, value)
			continue
		}

		if strUpdate {
			if !metric.stringMetric {
				// Skip string updates for non string metrics
				continue
			}
			// Display a default value and replace the value label with the string value
			floatVal = metric.defaultValue
			metric.labels[len(metric.labels)-1] = strVal
		}

		// Save the metric and labels in the cache
		c.m.Lock()
		lm := prometheus.MustNewConstMetric(metric.desc, prometheus.GaugeValue,
			floatVal, metric.labels...)
		c.metrics[src] = &labelledMetric{
			metric:       lm,
			labels:       metric.labels,
			defaultValue: metric.defaultValue,
			stringMetric: metric.stringMetric,
		}
		c.m.Unlock()
	}
}

// parseValue takes in an update and parses a value and suffix
// Returns an interface that contains either a string or a float64 as well as a suffix
// Unparseable updates return (0, empty string, false)
func parseValue(update *pb.Update) (interface{}, string, bool) {
	intf, err := gnmi.ExtractValue(update)
	if err != nil {
		return 0, "", false
	}

	switch value := intf.(type) {
	// float64 or string expected as the return value
	case int64:
		return float64(value), "", true
	case uint64:
		return float64(value), "", true
	case float32:
		return float64(value), "", true
	case *pb.Decimal64:
		val := gnmi.DecimalToFloat(value)
		if math.IsInf(val, 0) || math.IsNaN(val) {
			return 0, "", false
		}
		return val, "", true
	case json.Number:
		valFloat, err := value.Float64()
		if err != nil {
			return value, "", true
		}
		return valFloat, "", true
	case *any.Any:
		return value.String(), "", true
	case []interface{}:
		glog.V(9).Infof("skipping array value")
	case map[string]interface{}:
		if vIntf, ok := value["value"]; ok {
			if num, ok := vIntf.(json.Number); ok {
				valFloat, err := num.Float64()
				if err != nil {
					return num, "value", true
				}
				return valFloat, "value", true
			}
		}
	case bool:
		if value {
			return float64(1), "", true
		}
		return float64(0), "", true
	case string:
		return value, "", true
	default:
		glog.V(9).Infof("Ignoring update with unexpected type: %T", value)
	}

	return 0, "", false
}

// Describe implements prometheus.Collector interface
func (c *collector) Describe(ch chan<- *prometheus.Desc) {
	c.config.getAllDescs(ch)
}

// Collect implements prometheus.Collector interface
func (c *collector) Collect(ch chan<- prometheus.Metric) {
	c.m.Lock()
	for _, m := range c.metrics {
		ch <- m.metric
	}
	c.m.Unlock()
}
<|endoftext|>


<commit_before>package config_test

import (
	"io/ioutil"
	"os"
	"time"

	"code.cloudfoundry.org/bbs/test_helpers"
	"code.cloudfoundry.org/debugserver"
	loggingclient "code.cloudfoundry.org/diego-logging-client"
	"code.cloudfoundry.org/durationjson"
	executorinit "code.cloudfoundry.org/executor/initializer"
	"code.cloudfoundry.org/lager/lagerflags"
	"code.cloudfoundry.org/locket"
	"code.cloudfoundry.org/rep/cmd/rep/config"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("RepConfig", func() {
	var configFilePath, configData string

	BeforeEach(func() {
		configData = `{
			"proxy_memory_allocation_mb": 6,
			"advertise_domain": "test-domain",
			"bbs_address": "1.1.1.1:9091",
			"bbs_client_session_cache_size": 100,
			"bbs_max_idle_conns_per_host": 10,
			"ca_cert_file": "/tmp/ca_cert",
			"cache_path": "/tmp/cache",
			"cell_id" : "cell_z1/10",
			"communication_timeout": "11s",
			"consul_ca_cert": "/tmp/consul_ca_cert",
			"consul_client_cert": "/tmp/consul_client_cert",
			"consul_client_key": "/tmp/consul_client_key",
			"consul_cluster": "test cluster",
			"container_inode_limit": 1000,
			"container_max_cpu_shares": 4,
			"container_metrics_report_interval": "16s",
			"container_owner_name": "vcap",
			"container_reap_interval": "11s",
			"create_work_pool_size": 15,
			"csi_paths": ["/var/vcap/data/csiplugins"],
			"csi_mount_root_dir": "/var/vcap/data/csimountroot",
			"debug_address": "5.5.5.5:9090",
			"delete_work_pool_size": 10,
			"disk_mb": "20000",
			"enable_declarative_healthcheck": true,
			"declarative_healthcheck_path": "/var/vcap/packages/healthcheck",
			"enable_consul_service_registration": true,
			"enable_legacy_api_endpoints": true,
			"evacuation_polling_interval" : "13s",
			"evacuation_timeout" : "12s",
			"enable_container_proxy": true,
			"container_proxy_ads_servers": ["10.0.0.2:15010", "10.0.0.3:15010"],
			"enable_unproxied_port_mappings": true,
			"envoy_config_refresh_delay": "1s",
			"envoy_config_reload_duration": "5s",
			"envoy_drain_timeout": "15m",
			"garden_addr": "100.0.0.1",
			"garden_healthcheck_command_retry_pause": "15s",
			"garden_healthcheck_emission_interval": "13s",
			"garden_healthcheck_interval": "12s",
			"garden_healthcheck_process_args": ["arg1", "arg2"],
			"garden_healthcheck_process_dir": "/tmp/process",
			"garden_healthcheck_process_env": ["env1", "env2"],
			"garden_healthcheck_process_path": "/tmp/healthcheck-process",
			"garden_healthcheck_process_user": "vcap_health",
			"garden_healthcheck_timeout": "14s",
			"garden_network": "test-network",
			"graceful_shutdown_interval": "10s",
			"healthcheck_container_owner_name": "vcap_health",
			"healthcheck_work_pool_size": 10,
			"healthy_monitoring_interval": "5s",
			"healthy_monitoring_interval": "5s",
			"listen_addr": "0.0.0.0:8080",
			"listen_addr_admin": "0.0.0.1:8081",
			"listen_addr_securable": "0.0.0.0:8081",
			"lock_retry_interval": "5s",
			"lock_ttl": "5s",
			"cell_registrations_locket_enabled": true,
			"locket_address": "0.0.0.0:909090909",
			"locket_ca_cert_file": "locket-ca-cert",
			"locket_client_cert_file": "locket-client-cert",
			"locket_client_key_file": "locket-client-key",
			"log_level": "debug",
			"loggregator": {
				"loggregator_use_v2_api": true,
				"loggregator_api_port": 1234,
				"loggregator_ca_path": "ca-path",
				"loggregator_cert_path": "cert-path",
				"loggregator_key_path": "key-path",
				"loggregator_job_deployment": "job-deployment",
				"loggregator_job_name": "job-name",
				"loggregator_job_index": "job-index",
				"loggregator_job_ip": "job-ip",
				"loggregator_job_origin": "job-origin"
			},
			"max_cache_size_in_bytes": 101,
			"max_concurrent_downloads": 11,
			"memory_mb": "1000",
			"metrics_work_pool_size": 5,
			"optional_placement_tags": ["otag1", "otag2"],
			"path_to_ca_certs_for_downloads": "/tmp/ca-certs",
			"placement_tags": ["tag1", "tag2"],
			"polling_interval": "10s",
			"post_setup_hook": "post_setup_hook",
			"post_setup_user": "post_setup_user",
			"preloaded_root_fs": ["test:value", "test2:value2"],
			"read_work_pool_size": 15,
			"reserved_expiration_time": "10s",
			"cert_file": "/tmp/server_cert",
			"key_file": "/tmp/server_key",
			"session_name": "test",
			"skip_cert_verify": true,
			"supported_providers": ["provider1", "provider2"],
			"temp_dir": "/tmp/test",
			"trusted_system_certificates_path": "/tmp/trusted",
			"unhealthy_monitoring_interval": "10s",
			"volman_driver_paths": "/tmp/volman1:/tmp/volman2",
			"zone": "test-zone"
		}`
	})

	JustBeforeEach(func() {
		configFile, err := ioutil.TempFile("", "config-file")
		Expect(err).NotTo(HaveOccurred())

		defer configFile.Close()

		n, err := configFile.WriteString(configData)
		Expect(err).NotTo(HaveOccurred())

		Expect(n).To(Equal(len(configData)))

		configFilePath = configFile.Name()
	})

	AfterEach(func() {
		err := os.RemoveAll(configFilePath)
		Expect(err).NotTo(HaveOccurred())
	})

	It("correctly parses the config file", func() {
		repConfig, err := config.NewRepConfig(configFilePath)
		Expect(err).NotTo(HaveOccurred())

		Expect(repConfig).To(test_helpers.DeepEqual(config.RepConfig{
			AdvertiseDomain:           "test-domain",
			BBSAddress:                "1.1.1.1:9091",
			BBSClientSessionCacheSize: 100,
			BBSMaxIdleConnsPerHost:    10,
			CaCertFile:                "/tmp/ca_cert",
			CellID:                    "cell_z1/10",
			CellRegistrationsLocketEnabled: true,
			ClientLocketConfig: locket.ClientLocketConfig{
				LocketAddress:        "0.0.0.0:909090909",
				LocketCACertFile:     "locket-ca-cert",
				LocketClientCertFile: "locket-client-cert",
				LocketClientKeyFile:  "locket-client-key",
			},
			CommunicationTimeout: durationjson.Duration(11 * time.Second),
			ConsulCACert:         "/tmp/consul_ca_cert",
			ConsulClientCert:     "/tmp/consul_client_cert",
			ConsulClientKey:      "/tmp/consul_client_key",
			ConsulCluster:        "test cluster",
			DebugServerConfig: debugserver.DebugServerConfig{
				DebugAddress: "5.5.5.5:9090",
			},
			EnableConsulServiceRegistration: true,
			EvacuationPollingInterval:       durationjson.Duration(13 * time.Second),
			EvacuationTimeout:               durationjson.Duration(12 * time.Second),
			ExecutorConfig: executorinit.ExecutorConfig{
				ProxyMemoryAllocationMB:        6,
				CachePath:                      "/tmp/cache",
				ContainerInodeLimit:            1000,
				ContainerMaxCpuShares:          4,
				ContainerMetricsReportInterval: 16000000000,
				ContainerOwnerName:             "vcap",
				ContainerReapInterval:          11000000000,
				CreateWorkPoolSize:             15,
				DeleteWorkPoolSize:             10,
				DiskMB:                         "20000",
				EnableDeclarativeHealthcheck:       true,
				DeclarativeHealthcheckPath:         "/var/vcap/packages/healthcheck",
				EnableContainerProxy:               true,
				ContainerProxyADSServers:           []string{"10.0.0.2:15010", "10.0.0.3:15010"},
				EnableUnproxiedPortMappings:        true,
				EnvoyConfigRefreshDelay:            durationjson.Duration(time.Second),
				EnvoyConfigReloadDuration:          durationjson.Duration(5 * time.Second),
				EnvoyDrainTimeout:                  durationjson.Duration(15 * time.Minute),
				GardenAddr:                         "100.0.0.1",
				GardenHealthcheckCommandRetryPause: 15000000000,
				GardenHealthcheckEmissionInterval:  13000000000,
				GardenHealthcheckInterval:          12000000000,
				GardenHealthcheckProcessArgs:       []string{"arg1", "arg2"},
				GardenHealthcheckProcessDir:        "/tmp/process",
				GardenHealthcheckProcessEnv:        []string{"env1", "env2"},
				GardenHealthcheckProcessPath:       "/tmp/healthcheck-process",
				GardenHealthcheckProcessUser:       "vcap_health",
				GardenHealthcheckTimeout:           14000000000,
				GardenNetwork:                      "test-network",
				GracefulShutdownInterval:           durationjson.Duration(10 * time.Second),
				HealthCheckContainerOwnerName:      "vcap_health",
				HealthCheckWorkPoolSize:            10,
				HealthyMonitoringInterval:          5000000000,
				MaxCacheSizeInBytes:                101,
				MaxConcurrentDownloads:             11,
				MemoryMB:                           "1000",
				MetricsWorkPoolSize:                5,
				PathToCACertsForDownloads:          "/tmp/ca-certs",
				PostSetupHook:                      "post_setup_hook",
				PostSetupUser:                      "post_setup_user",
				ReadWorkPoolSize:                   15,
				ReservedExpirationTime:             10000000000,
				SkipCertVerify:                     true,
				TempDir:                            "/tmp/test",
				TrustedSystemCertificatesPath: "/tmp/trusted",
				UnhealthyMonitoringInterval:   10000000000,
				VolmanDriverPaths:             "/tmp/volman1:/tmp/volman2",
				CSIPaths:                      []string{"/var/vcap/data/csiplugins"},
				CSIMountRootDir:               "/var/vcap/data/csimountroot",
			},
			LagerConfig: lagerflags.LagerConfig{
				LogLevel: lagerflags.DEBUG,
			},
			ListenAddr:            "0.0.0.0:8080",
			ListenAddrSecurable:   "0.0.0.0:8081",
			LockRetryInterval:     durationjson.Duration(5 * time.Second),
			LockTTL:               durationjson.Duration(5 * time.Second),
			OptionalPlacementTags: []string{"otag1", "otag2"},
			PlacementTags:         []string{"tag1", "tag2"},
			PollingInterval:       durationjson.Duration(10 * time.Second),
			PreloadedRootFS:       []config.RootFS{{"test", "value"}, {"test2", "value2"}},
			CertFile:              "/tmp/server_cert",
			KeyFile:               "/tmp/server_key",
			SessionName:           "test",
			SupportedProviders:    []string{"provider1", "provider2"},
			Zone:                  "test-zone",
			LoggregatorConfig: loggingclient.Config{
				UseV2API:      true,
				APIPort:       1234,
				CACertPath:    "ca-path",
				CertPath:      "cert-path",
				KeyPath:       "key-path",
				JobDeployment: "job-deployment",
				JobName:       "job-name",
				JobIndex:      "job-index",
				JobIP:         "job-ip",
				JobOrigin:     "job-origin",
			},
		}))
	})

	Context("when the file does not exist", func() {
		It("returns an error", func() {
			_, err := config.NewRepConfig("foobar")
			Expect(err).To(HaveOccurred())
		})
	})

	Context("when the file does not contain valid json", func() {
		BeforeEach(func() {
			configData = "{{"
		})

		It("returns an error", func() {
			_, err := config.NewRepConfig(configFilePath)
			Expect(err).To(HaveOccurred())
		})

		Context("because the communication_timeout is not valid", func() {
			BeforeEach(func() {
				configData = `{"communication_timeout": 4234342342}`
			})

			It("returns an error", func() {
				_, err := config.NewRepConfig(configFilePath)
				Expect(err).To(HaveOccurred())
			})
		})
	})
})
<commit_msg>Rename ads_servers -> ads_addresses<commit_after>package config_test

import (
	"io/ioutil"
	"os"
	"time"

	"code.cloudfoundry.org/bbs/test_helpers"
	"code.cloudfoundry.org/debugserver"
	loggingclient "code.cloudfoundry.org/diego-logging-client"
	"code.cloudfoundry.org/durationjson"
	executorinit "code.cloudfoundry.org/executor/initializer"
	"code.cloudfoundry.org/lager/lagerflags"
	"code.cloudfoundry.org/locket"
	"code.cloudfoundry.org/rep/cmd/rep/config"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("RepConfig", func() {
	var configFilePath, configData string

	BeforeEach(func() {
		configData = `{
			"proxy_memory_allocation_mb": 6,
			"advertise_domain": "test-domain",
			"bbs_address": "1.1.1.1:9091",
			"bbs_client_session_cache_size": 100,
			"bbs_max_idle_conns_per_host": 10,
			"ca_cert_file": "/tmp/ca_cert",
			"cache_path": "/tmp/cache",
			"cell_id" : "cell_z1/10",
			"communication_timeout": "11s",
			"consul_ca_cert": "/tmp/consul_ca_cert",
			"consul_client_cert": "/tmp/consul_client_cert",
			"consul_client_key": "/tmp/consul_client_key",
			"consul_cluster": "test cluster",
			"container_inode_limit": 1000,
			"container_max_cpu_shares": 4,
			"container_metrics_report_interval": "16s",
			"container_owner_name": "vcap",
			"container_reap_interval": "11s",
			"create_work_pool_size": 15,
			"csi_paths": ["/var/vcap/data/csiplugins"],
			"csi_mount_root_dir": "/var/vcap/data/csimountroot",
			"debug_address": "5.5.5.5:9090",
			"delete_work_pool_size": 10,
			"disk_mb": "20000",
			"enable_declarative_healthcheck": true,
			"declarative_healthcheck_path": "/var/vcap/packages/healthcheck",
			"enable_consul_service_registration": true,
			"enable_legacy_api_endpoints": true,
			"evacuation_polling_interval" : "13s",
			"evacuation_timeout" : "12s",
			"enable_container_proxy": true,
			"container_proxy_ads_addresses": ["10.0.0.2:15010", "10.0.0.3:15010"],
			"enable_unproxied_port_mappings": true,
			"envoy_config_refresh_delay": "1s",
			"envoy_config_reload_duration": "5s",
			"envoy_drain_timeout": "15m",
			"garden_addr": "100.0.0.1",
			"garden_healthcheck_command_retry_pause": "15s",
			"garden_healthcheck_emission_interval": "13s",
			"garden_healthcheck_interval": "12s",
			"garden_healthcheck_process_args": ["arg1", "arg2"],
			"garden_healthcheck_process_dir": "/tmp/process",
			"garden_healthcheck_process_env": ["env1", "env2"],
			"garden_healthcheck_process_path": "/tmp/healthcheck-process",
			"garden_healthcheck_process_user": "vcap_health",
			"garden_healthcheck_timeout": "14s",
			"garden_network": "test-network",
			"graceful_shutdown_interval": "10s",
			"healthcheck_container_owner_name": "vcap_health",
			"healthcheck_work_pool_size": 10,
			"healthy_monitoring_interval": "5s",
			"healthy_monitoring_interval": "5s",
			"listen_addr": "0.0.0.0:8080",
			"listen_addr_admin": "0.0.0.1:8081",
			"listen_addr_securable": "0.0.0.0:8081",
			"lock_retry_interval": "5s",
			"lock_ttl": "5s",
			"cell_registrations_locket_enabled": true,
			"locket_address": "0.0.0.0:909090909",
			"locket_ca_cert_file": "locket-ca-cert",
			"locket_client_cert_file": "locket-client-cert",
			"locket_client_key_file": "locket-client-key",
			"log_level": "debug",
			"loggregator": {
				"loggregator_use_v2_api": true,
				"loggregator_api_port": 1234,
				"loggregator_ca_path": "ca-path",
				"loggregator_cert_path": "cert-path",
				"loggregator_key_path": "key-path",
				"loggregator_job_deployment": "job-deployment",
				"loggregator_job_name": "job-name",
				"loggregator_job_index": "job-index",
				"loggregator_job_ip": "job-ip",
				"loggregator_job_origin": "job-origin"
			},
			"max_cache_size_in_bytes": 101,
			"max_concurrent_downloads": 11,
			"memory_mb": "1000",
			"metrics_work_pool_size": 5,
			"optional_placement_tags": ["otag1", "otag2"],
			"path_to_ca_certs_for_downloads": "/tmp/ca-certs",
			"placement_tags": ["tag1", "tag2"],
			"polling_interval": "10s",
			"post_setup_hook": "post_setup_hook",
			"post_setup_user": "post_setup_user",
			"preloaded_root_fs": ["test:value", "test2:value2"],
			"read_work_pool_size": 15,
			"reserved_expiration_time": "10s",
			"cert_file": "/tmp/server_cert",
			"key_file": "/tmp/server_key",
			"session_name": "test",
			"skip_cert_verify": true,
			"supported_providers": ["provider1", "provider2"],
			"temp_dir": "/tmp/test",
			"trusted_system_certificates_path": "/tmp/trusted",
			"unhealthy_monitoring_interval": "10s",
			"volman_driver_paths": "/tmp/volman1:/tmp/volman2",
			"zone": "test-zone"
		}`
	})

	JustBeforeEach(func() {
		configFile, err := ioutil.TempFile("", "config-file")
		Expect(err).NotTo(HaveOccurred())

		defer configFile.Close()

		n, err := configFile.WriteString(configData)
		Expect(err).NotTo(HaveOccurred())

		Expect(n).To(Equal(len(configData)))

		configFilePath = configFile.Name()
	})

	AfterEach(func() {
		err := os.RemoveAll(configFilePath)
		Expect(err).NotTo(HaveOccurred())
	})

	It("correctly parses the config file", func() {
		repConfig, err := config.NewRepConfig(configFilePath)
		Expect(err).NotTo(HaveOccurred())

		Expect(repConfig).To(test_helpers.DeepEqual(config.RepConfig{
			AdvertiseDomain:                "test-domain",
			BBSAddress:                     "1.1.1.1:9091",
			BBSClientSessionCacheSize:      100,
			BBSMaxIdleConnsPerHost:         10,
			CaCertFile:                     "/tmp/ca_cert",
			CellID:                         "cell_z1/10",
			CellRegistrationsLocketEnabled: true,
			ClientLocketConfig: locket.ClientLocketConfig{
				LocketAddress:        "0.0.0.0:909090909",
				LocketCACertFile:     "locket-ca-cert",
				LocketClientCertFile: "locket-client-cert",
				LocketClientKeyFile:  "locket-client-key",
			},
			CommunicationTimeout: durationjson.Duration(11 * time.Second),
			ConsulCACert:         "/tmp/consul_ca_cert",
			ConsulClientCert:     "/tmp/consul_client_cert",
			ConsulClientKey:      "/tmp/consul_client_key",
			ConsulCluster:        "test cluster",
			DebugServerConfig: debugserver.DebugServerConfig{
				DebugAddress: "5.5.5.5:9090",
			},
			EnableConsulServiceRegistration: true,
			EvacuationPollingInterval:       durationjson.Duration(13 * time.Second),
			EvacuationTimeout:               durationjson.Duration(12 * time.Second),
			ExecutorConfig: executorinit.ExecutorConfig{
				ProxyMemoryAllocationMB:            6,
				CachePath:                          "/tmp/cache",
				ContainerInodeLimit:                1000,
				ContainerMaxCpuShares:              4,
				ContainerMetricsReportInterval:     16000000000,
				ContainerOwnerName:                 "vcap",
				ContainerReapInterval:              11000000000,
				CreateWorkPoolSize:                 15,
				DeleteWorkPoolSize:                 10,
				DiskMB:                             "20000",
				EnableDeclarativeHealthcheck:       true,
				DeclarativeHealthcheckPath:         "/var/vcap/packages/healthcheck",
				EnableContainerProxy:               true,
				ContainerProxyADSServers:           []string{"10.0.0.2:15010", "10.0.0.3:15010"},
				EnableUnproxiedPortMappings:        true,
				EnvoyConfigRefreshDelay:            durationjson.Duration(time.Second),
				EnvoyConfigReloadDuration:          durationjson.Duration(5 * time.Second),
				EnvoyDrainTimeout:                  durationjson.Duration(15 * time.Minute),
				GardenAddr:                         "100.0.0.1",
				GardenHealthcheckCommandRetryPause: 15000000000,
				GardenHealthcheckEmissionInterval:  13000000000,
				GardenHealthcheckInterval:          12000000000,
				GardenHealthcheckProcessArgs:       []string{"arg1", "arg2"},
				GardenHealthcheckProcessDir:        "/tmp/process",
				GardenHealthcheckProcessEnv:        []string{"env1", "env2"},
				GardenHealthcheckProcessPath:       "/tmp/healthcheck-process",
				GardenHealthcheckProcessUser:       "vcap_health",
				GardenHealthcheckTimeout:           14000000000,
				GardenNetwork:                      "test-network",
				GracefulShutdownInterval:           durationjson.Duration(10 * time.Second),
				HealthCheckContainerOwnerName:      "vcap_health",
				HealthCheckWorkPoolSize:            10,
				HealthyMonitoringInterval:          5000000000,
				MaxCacheSizeInBytes:                101,
				MaxConcurrentDownloads:             11,
				MemoryMB:                           "1000",
				MetricsWorkPoolSize:                5,
				PathToCACertsForDownloads:          "/tmp/ca-certs",
				PostSetupHook:                      "post_setup_hook",
				PostSetupUser:                      "post_setup_user",
				ReadWorkPoolSize:                   15,
				ReservedExpirationTime:             10000000000,
				SkipCertVerify:                     true,
				TempDir:                            "/tmp/test",
				TrustedSystemCertificatesPath:      "/tmp/trusted",
				UnhealthyMonitoringInterval:        10000000000,
				VolmanDriverPaths:                  "/tmp/volman1:/tmp/volman2",
				CSIPaths:                           []string{"/var/vcap/data/csiplugins"},
				CSIMountRootDir:                    "/var/vcap/data/csimountroot",
			},
			LagerConfig: lagerflags.LagerConfig{
				LogLevel: lagerflags.DEBUG,
			},
			ListenAddr:            "0.0.0.0:8080",
			ListenAddrSecurable:   "0.0.0.0:8081",
			LockRetryInterval:     durationjson.Duration(5 * time.Second),
			LockTTL:               durationjson.Duration(5 * time.Second),
			OptionalPlacementTags: []string{"otag1", "otag2"},
			PlacementTags:         []string{"tag1", "tag2"},
			PollingInterval:       durationjson.Duration(10 * time.Second),
			PreloadedRootFS:       []config.RootFS{{"test", "value"}, {"test2", "value2"}},
			CertFile:              "/tmp/server_cert",
			KeyFile:               "/tmp/server_key",
			SessionName:           "test",
			SupportedProviders:    []string{"provider1", "provider2"},
			Zone:                  "test-zone",
			LoggregatorConfig: loggingclient.Config{
				UseV2API:      true,
				APIPort:       1234,
				CACertPath:    "ca-path",
				CertPath:      "cert-path",
				KeyPath:       "key-path",
				JobDeployment: "job-deployment",
				JobName:       "job-name",
				JobIndex:      "job-index",
				JobIP:         "job-ip",
				JobOrigin:     "job-origin",
			},
		}))
	})

	Context("when the file does not exist", func() {
		It("returns an error", func() {
			_, err := config.NewRepConfig("foobar")
			Expect(err).To(HaveOccurred())
		})
	})

	Context("when the file does not contain valid json", func() {
		BeforeEach(func() {
			configData = "{{"
		})

		It("returns an error", func() {
			_, err := config.NewRepConfig(configFilePath)
			Expect(err).To(HaveOccurred())
		})

		Context("because the communication_timeout is not valid", func() {
			BeforeEach(func() {
				configData = `{"communication_timeout": 4234342342}`
			})

			It("returns an error", func() {
				_, err := config.NewRepConfig(configFilePath)
				Expect(err).To(HaveOccurred())
			})
		})
	})
})
<|endoftext|>