<commit_before>package fluidbackup

import "sync"
import "fmt"
import "io/ioutil"
import "time"
import "math/rand"
import "strings"

const (
	STATUS_ONLINE  = 0
	STATUS_OFFLINE = 1
)

type PeerId struct {
	Address string
	Port    int
}

func (this *PeerId) String() string {
	return fmt.Sprintf("%s:%d", this.Address, this.Port)
}

func strToPeerId(str string) PeerId {
	parts := strings.Split(str, ":")
	return PeerId{
		Address: parts[0],
		Port:    strToInt(parts[1]),
	}
}

/*
 * Represents another peer, storing information about
 * the other peer as necessary, and handling requests/actions
 * involving that other peer (storeShard, etc.)
 *
 * Note: Does not represent the local peer. The local peer
 * is perhaps best represented by a combination of Protocol,
 * and PeerList, and FileStorage, which comprise a system.
 */
type Peer struct {
	mu          sync.Mutex
	protocol    *Protocol
	id          PeerId
	status      int
	localBytes  int // how many bytes we've agreed to store for this peer
	remoteBytes int // how many bytes peer is willing to store for us

	// cached values
	localUsedBytes  int
	remoteUsedBytes int
}

func MakePeer(id PeerId, fluidBackup *FluidBackup, protocol *Protocol) *Peer {
	this := new(Peer)
	this.protocol = protocol
	this.id = id
	this.status = STATUS_ONLINE

	go func() {
		/* keep updating until eternity */
		for !fluidBackup.Stopping() {
			this.update()

			if Debug {
				time.Sleep(time.Duration(rand.Intn(3000))*time.Millisecond + 3*time.Second)
			} else {
				time.Sleep(time.Duration(rand.Intn(60000))*time.Millisecond + 30*time.Second)
			}
		}
	}()

	return this
}

/*
 * Our represented peer wants to propose agreement
 * with the local peer.
 */
func (this *Peer) proposeAgreement(localBytes int, remoteBytes int) bool {
	if this.protocol.proposeAgreement(this.id, localBytes, remoteBytes) {
		this.eventAgreement(localBytes, remoteBytes)
		return true
	}

	return false
}

func (this *Peer) eventAgreement(localBytes int, remoteBytes int) {
	Log.Debug.Printf("New agreement with %s (%d to %d)", this.id.String(), localBytes, remoteBytes)
	this.mu.Lock()
	this.localBytes += localBytes
	this.remoteBytes += remoteBytes
	this.mu.Unlock()
}

/*
 * Our represented peer wants to store data on the local
 * peer (forwarded by protocol)
 */
func (this *Peer) storeShard(shard *BlockShard) bool {
	return this.protocol.storeShard(this.id, int64(shard.Id), shard.Contents)
}

func (this *Peer) retrieveShard(shard *BlockShard) []byte {
	return this.protocol.retrieveShard(this.id, int64(shard.Id))
}

/*
 * Attempts to reserve a number of bytes for storage on this peer.
 * Returns true if the bytes have been reserved for use by caller, or false if reservation failed.
 */
func (this *Peer) reserveBytes(bytes int) bool {
	this.mu.Lock()
	defer this.mu.Unlock()

	if this.remoteBytes-this.remoteUsedBytes >= bytes {
		this.remoteUsedBytes += bytes
		return true
	} else {
		return false
	}
}

func (this *Peer) getShardPath(label int64) string {
	// todo: make the store directory automatically
	return fmt.Sprintf("store/%s_%d.obj", this.id.String(), label)
}

/*
 * called on a representation
 * to say that the peer it represents is trying to
 * store data on our peer
 */
func (this *Peer) eventStoreShard(label int64, bytes []byte) bool {
	this.mu.Lock()
	defer this.mu.Unlock()

	// confirm the peer still has space on our storage to reserve
	if this.remoteBytes < this.remoteUsedBytes+len(bytes) {
		return false
	}

	// okay, store it in the file and update cached usage
	err := ioutil.WriteFile(this.getShardPath(label), bytes, 0644)

	if err != nil {
		Log.Warn.Printf("Failed to write peer shard (%s #%d): %s", this.id.String(), label, err.Error())
		return false
	}

	this.remoteUsedBytes += len(bytes)
	return true
}

func (this *Peer) eventRetrieveShard(label int64) []byte {
	this.mu.Lock()
	defer this.mu.Unlock()

	shardBytes, err := ioutil.ReadFile(this.getShardPath(label))

	if err != nil {
		Log.Warn.Printf("Failed to handle shard retrieval request (%s #%d): %s", this.id.String(), label, err.Error())
		return nil
	} else {
		return shardBytes
	}
}

func (this *Peer) isOnline() bool {
	this.mu.Lock()
	defer this.mu.Unlock()
	return this.status == STATUS_ONLINE
}

/*
 * Call as often as reasonable.
 * Syncs this peer representation with the actual remote
 * peer.
 */
func (this *Peer) update() {
	online := this.protocol.ping(this.id)

	this.mu.Lock()
	if online && this.status == STATUS_OFFLINE {
		Log.Info.Printf("Peer %s came online", this.id.String())
		this.status = STATUS_ONLINE
	} else if !online && this.status == STATUS_ONLINE {
		Log.Info.Printf("Peer %s went offline", this.id.String())
		this.status = STATUS_OFFLINE
	}
	this.mu.Unlock()
}
<commit_msg>Fix bug where we check/adjust remoteUsedBytes instead of localUsedBytes.<commit_after>package fluidbackup

import "sync"
import "fmt"
import "io/ioutil"
import "time"
import "math/rand"
import "strings"

const (
	STATUS_ONLINE  = 0
	STATUS_OFFLINE = 1
)

type PeerId struct {
	Address string
	Port    int
}

func (this *PeerId) String() string {
	return fmt.Sprintf("%s:%d", this.Address, this.Port)
}

func strToPeerId(str string) PeerId {
	parts := strings.Split(str, ":")
	return PeerId{
		Address: parts[0],
		Port:    strToInt(parts[1]),
	}
}

/*
 * Represents another peer, storing information about
 * the other peer as necessary, and handling requests/actions
 * involving that other peer (storeShard, etc.)
 *
 * Note: Does not represent the local peer. The local peer
 * is perhaps best represented by a combination of Protocol,
 * and PeerList, and FileStorage, which comprise a system.
 */
type Peer struct {
	mu          sync.Mutex
	protocol    *Protocol
	id          PeerId
	status      int
	localBytes  int // how many bytes we've agreed to store for this peer
	remoteBytes int // how many bytes peer is willing to store for us

	// cached values
	localUsedBytes  int
	remoteUsedBytes int
}

func MakePeer(id PeerId, fluidBackup *FluidBackup, protocol *Protocol) *Peer {
	this := new(Peer)
	this.protocol = protocol
	this.id = id
	this.status = STATUS_ONLINE

	go func() {
		/* keep updating until eternity */
		for !fluidBackup.Stopping() {
			this.update()

			if Debug {
				time.Sleep(time.Duration(rand.Intn(3000))*time.Millisecond + 3*time.Second)
			} else {
				time.Sleep(time.Duration(rand.Intn(60000))*time.Millisecond + 30*time.Second)
			}
		}
	}()

	return this
}

/*
 * Our represented peer wants to propose agreement
 * with the local peer.
 */
func (this *Peer) proposeAgreement(localBytes int, remoteBytes int) bool {
	if this.protocol.proposeAgreement(this.id, localBytes, remoteBytes) {
		this.eventAgreement(localBytes, remoteBytes)
		return true
	}

	return false
}

func (this *Peer) eventAgreement(localBytes int, remoteBytes int) {
	Log.Debug.Printf("New agreement with %s (%d to %d)", this.id.String(), localBytes, remoteBytes)
	this.mu.Lock()
	this.localBytes += localBytes
	this.remoteBytes += remoteBytes
	this.mu.Unlock()
}

/*
 * Our represented peer wants to store data on the local
 * peer (forwarded by protocol)
 */
func (this *Peer) storeShard(shard *BlockShard) bool {
	return this.protocol.storeShard(this.id, int64(shard.Id), shard.Contents)
}

func (this *Peer) retrieveShard(shard *BlockShard) []byte {
	return this.protocol.retrieveShard(this.id, int64(shard.Id))
}

/*
 * Attempts to reserve a number of bytes for storage on this peer.
 * Returns true if the bytes have been reserved for use by caller, or false if reservation failed.
 */
func (this *Peer) reserveBytes(bytes int) bool {
	this.mu.Lock()
	defer this.mu.Unlock()

	if this.remoteBytes-this.remoteUsedBytes >= bytes {
		this.remoteUsedBytes += bytes
		return true
	} else {
		return false
	}
}

func (this *Peer) getShardPath(label int64) string {
	// todo: make the store directory automatically
	return fmt.Sprintf("store/%s_%d.obj", this.id.String(), label)
}

/*
 * called on a representation
 * to say that the peer it represents is trying to
 * store data on our peer
 */
func (this *Peer) eventStoreShard(label int64, bytes []byte) bool {
	this.mu.Lock()
	defer this.mu.Unlock()

	// confirm the peer still has space on our storage to reserve
	if this.localBytes < this.localUsedBytes+len(bytes) {
		return false
	}

	// okay, store it in the file and update cached usage
	err := ioutil.WriteFile(this.getShardPath(label), bytes, 0644)

	if err != nil {
		Log.Warn.Printf("Failed to write peer shard (%s #%d): %s", this.id.String(), label, err.Error())
		return false
	}

	this.remoteUsedBytes += len(bytes)
	return true
}

func (this *Peer) eventRetrieveShard(label int64) []byte {
	this.mu.Lock()
	defer this.mu.Unlock()

	shardBytes, err := ioutil.ReadFile(this.getShardPath(label))

	if err != nil {
		Log.Warn.Printf("Failed to handle shard retrieval request (%s #%d): %s", this.id.String(), label, err.Error())
		return nil
	} else {
		return shardBytes
	}
}

func (this *Peer) isOnline() bool {
	this.mu.Lock()
	defer this.mu.Unlock()
	return this.status == STATUS_ONLINE
}

/*
 * Call as often as reasonable.
 * Syncs this peer representation with the actual remote
 * peer.
 */
func (this *Peer) update() {
	online := this.protocol.ping(this.id)

	this.mu.Lock()
	if online && this.status == STATUS_OFFLINE {
		Log.Info.Printf("Peer %s came online", this.id.String())
		this.status = STATUS_ONLINE
	} else if !online && this.status == STATUS_ONLINE {
		Log.Info.Printf("Peer %s went offline", this.id.String())
		this.status = STATUS_OFFLINE
	}
	this.mu.Unlock()
}
<|endoftext|>


<commit_before>// Example program with unbuffered channel
package main

import (
	"fmt"
	"sync"
)

// wg is used to wait for the program to finish.
var wg sync.WaitGroup

func main() {

	count := make(chan int)
	// Add a count of two, one for each goroutine.
	wg.Add(2)

	fmt.Println("Start Goroutines")
	// Launch a goroutine with label "Goroutine-1"

	go func(){
		count <- 1
	}()

	go printCounts("Goroutine-1", count)
	// Launch a goroutine with label "Goroutine-2"
	go printCounts("Goroutine-2", count)
	fmt.Println("Communication of channel begins")

	// Wait for the goroutines to finish.
	fmt.Println("Waiting To Finish")
	wg.Wait()
	fmt.Println("\nTerminating the Program")
}

func printCounts(label string, count chan int) {
	// Schedule the call to WaitGroup's Done to tell goroutine is completed.
	defer wg.Done()
	for {
		// Receives message from Channel
		val, ok := <-count
		if !ok {
			fmt.Println("Channel was closed")
			return
		}
		fmt.Printf("Count: %d received from %s \n", val, label)
		if val == 10 {
			fmt.Printf("Channel Closed from %s \n", label)
			// Close the channel
			close(count)
			return
		}
		val++
		// Send count back to the other goroutine.
		count <- val
	}
}
<commit_msg>Minor update<commit_after>// Example program with unbuffered channel
package main

import (
	"fmt"
	"sync"
)

// wg is used to wait for the program to finish.
var wg sync.WaitGroup

func main() {

	count := make(chan int)
	// Add a count of two, one for each goroutine.
	wg.Add(2)

	fmt.Println("Start Goroutines")
	// Launch a goroutine with label "Goroutine-1"
	go printCounts("Goroutine-1", count)
	// Launch a goroutine with label "Goroutine-2"
	go printCounts("Goroutine-2", count)
	fmt.Println("Communication of channel begins")
	count <- 1
	// Wait for the goroutines to finish.
	fmt.Println("Waiting To Finish")
	wg.Wait()
	fmt.Println("\nTerminating the Program")
}

func printCounts(label string, count chan int) {
	// Schedule the call to WaitGroup's Done to tell goroutine is completed.
	defer wg.Done()
	for {
		// Receives message from Channel
		val, ok := <-count
		if !ok {
			fmt.Println("Channel was closed")
			return
		}
		fmt.Printf("Count: %d received from %s \n", val, label)
		if val == 10 {
			fmt.Printf("Channel Closed from %s \n", label)
			// Close the channel
			close(count)
			return
		}
		val++
		// Send count back to the other goroutine.
		count <- val
	}
}
<|endoftext|>


<commit_before>// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package fmt_test

import (
	"fmt"
	"os"
	"strings"
)

// The Errorf function lets us use formatting features
// to create descriptive error messages.
func ExampleErrorf() {
	const name, id = "bueller", 17
	err := fmt.Errorf("user %q (id %d) not found", name, id)
	fmt.Println(err.Error())
	// Output: user "bueller" (id 17) not found
}

func ExampleFscanf() {
	var (
		i int
		b bool
		s string
	)
	r := strings.NewReader("5 true gophers")
	n, err := fmt.Fscanf(r, "%d %t %s", &i, &b, &s)
	if err != nil {
		panic(err)
	}
	fmt.Println(i, b, s)
	fmt.Println(n)
	// Output:
	// 5 true gophers
	// 3
}

func ExampleSprintf() {
	i := 30
	s := "Aug"
	sf := fmt.Sprintf("Today is %d %s", i, s)
	fmt.Println(sf)
	fmt.Println(len(sf))
	// Output:
	// Today is 30 Aug
	// 15
}

func ExamplePrintln() {
	n, err := fmt.Println("there", "are", 99, "gophers")
	if err != nil {
		panic(err)
	}
	fmt.Print(n)
	// Output:
	// there are 99 gophers
	// 21
}

func ExampleSprintln() {
	s := "Aug"
	sl := fmt.Sprintln("Today is 30", s)
	fmt.Printf("%q", sl)
	// Output:
	// "Today is 30 Aug\n"
}

func ExampleFprintln() {
	n, err := fmt.Fprintln(os.Stdout, "there", "are", 99, "gophers")
	if err != nil {
		panic(err)
	}
	fmt.Print(n)
	// Output:
	// there are 99 gophers
	// 21
}
<commit_msg>fmt: add example for Fscanln<commit_after>// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package fmt_test

import (
	"fmt"
	"io"
	"os"
	"strings"
)

// The Errorf function lets us use formatting features
// to create descriptive error messages.
func ExampleErrorf() {
	const name, id = "bueller", 17
	err := fmt.Errorf("user %q (id %d) not found", name, id)
	fmt.Println(err.Error())
	// Output: user "bueller" (id 17) not found
}

func ExampleFscanf() {
	var (
		i int
		b bool
		s string
	)
	r := strings.NewReader("5 true gophers")
	n, err := fmt.Fscanf(r, "%d %t %s", &i, &b, &s)
	if err != nil {
		panic(err)
	}
	fmt.Println(i, b, s)
	fmt.Println(n)
	// Output:
	// 5 true gophers
	// 3
}

func ExampleSprintf() {
	i := 30
	s := "Aug"
	sf := fmt.Sprintf("Today is %d %s", i, s)
	fmt.Println(sf)
	fmt.Println(len(sf))
	// Output:
	// Today is 30 Aug
	// 15
}

func ExamplePrintln() {
	n, err := fmt.Println("there", "are", 99, "gophers")
	if err != nil {
		panic(err)
	}
	fmt.Print(n)
	// Output:
	// there are 99 gophers
	// 21
}

func ExampleSprintln() {
	s := "Aug"
	sl := fmt.Sprintln("Today is 30", s)
	fmt.Printf("%q", sl)
	// Output:
	// "Today is 30 Aug\n"
}

func ExampleFprintln() {
	n, err := fmt.Fprintln(os.Stdout, "there", "are", 99, "gophers")
	if err != nil {
		panic(err)
	}
	fmt.Print(n)
	// Output:
	// there are 99 gophers
	// 21
}

func ExampleFscanln() {
	s := `dmr 1771 1.61803398875
	ken 271828 3.14159`
	r := strings.NewReader(s)
	var a string
	var b int
	var c float64
	for {
		n, err := fmt.Fscanln(r, &a, &b, &c)
		if err == io.EOF {
			break
		}
		if err != nil {
			panic(err)
		}
		fmt.Printf("%d: %s, %d, %f\n", n, a, b, c)
	}
	// Output:
	// 3: dmr, 1771, 1.618034
	// 3: ken, 271828, 3.141590
}
<|endoftext|>


<commit_before>package runner

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"syscall"
	"time"

	"code.cloudfoundry.org/garden/client"
	"code.cloudfoundry.org/garden/client/connection"
	"code.cloudfoundry.org/lager"
	"code.cloudfoundry.org/lager/lagertest"
	"github.com/onsi/ginkgo"
	. "github.com/onsi/ginkgo"
	"github.com/onsi/gomega/gbytes"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/ginkgomon"
)

type Binaries struct {
	OCIRuntime    string `json:"oci_runtime,omitempty"`
	Tar           string `json:"tar,omitempty"`
	Gdn           string `json:"gdn,omitempty"`
	Init          string `json:"init,omitempty"`
	RuntimePlugin string `json:"runtime_plugin,omitempty"`
	ImagePlugin   string `json:"image_plugin,omitempty"`
	NetworkPlugin string `json:"network_plugin,omitempty"`
	NoopPlugin    string `json:"noop_plugin,omitempty"`
	ExecRunner    string `json:"execrunner,omitempty"`
	NSTar         string `json:"nstar,omitempty"`
}

const MNT_DETACH = 0x2

var DataDir string

type RunningGarden struct {
	client.Client

	runner  GardenRunner
	process ifrit.Process

	debugIP   string
	debugPort int

	Pid int

	Tmpdir string

	DepotDir  string
	DataDir   string
	GraphPath string

	logger lager.Logger
}

type GardenRunner struct {
	*ginkgomon.Runner
	Cmd            *exec.Cmd
	TmpDir         string
	GraphPath      string
	ConsoleSockets string
	DepotDir       string
	DebugIp        string
	DebugPort      int
	Network, Addr  string
}

func init() {
	DataDir = os.Getenv("GARDEN_TEST_GRAPHPATH")
	if DataDir == "" {
		// This must be set outside of the Ginkgo node directory (tmpDir) because
		// otherwise the Concourse worker may run into one of the AUFS kernel
		// module bugs that cause the VM to become unresponsive.
		DataDir = "/tmp/aufs_mount"
	}
}

func NewGardenRunner(binaries *Binaries, rootfs, network, address string, user UserCredential, argv ...string) GardenRunner {
	r := GardenRunner{}

	r.Network = network
	r.Addr = address
	r.TmpDir = filepath.Join(
		os.TempDir(),
		fmt.Sprintf("test-garden-%d", ginkgo.GinkgoParallelNode()),
	)

	r.GraphPath = filepath.Join(DataDir, fmt.Sprintf("node-%d", ginkgo.GinkgoParallelNode()))
	r.DepotDir = filepath.Join(r.TmpDir, "containers")
	r.ConsoleSockets = filepath.Join(r.TmpDir, "console-sockets")

	if runtime.GOOS == "linux" {
		MustMountTmpfs(r.GraphPath)
	}

	r.Cmd = cmd(r.TmpDir, r.DepotDir, r.GraphPath, r.ConsoleSockets, r.Network, r.Addr, binaries, rootfs, user, argv...)
	r.Cmd.Env = append(
		[]string{
			fmt.Sprintf("TMPDIR=%s", r.TmpDir),
			fmt.Sprintf("TEMP=%s", r.TmpDir),
			fmt.Sprintf("TMP=%s", r.TmpDir),
		},
		os.Environ()...,
	)

	for i, arg := range r.Cmd.Args {
		if arg == "--debug-bind-ip" {
			r.DebugIp = r.Cmd.Args[i+1]
		}
		if arg == "--debug-bind-port" {
			r.DebugPort, _ = strconv.Atoi(r.Cmd.Args[i+1])
		}
	}

	r.Runner = ginkgomon.New(ginkgomon.Config{
		Name:              "guardian",
		Command:           r.Cmd,
		AnsiColorCode:     "31m",
		StartCheck:        "guardian.started",
		StartCheckTimeout: 30 * time.Second,
	})

	return r
}

func Start(binaries *Binaries, rootfs string, user UserCredential, argv ...string) *RunningGarden {
	network := "unix"
	address := fmt.Sprintf("/tmp/garden_%d.sock", GinkgoParallelNode())

	if runtime.GOOS == "windows" {
		network = "tcp"
		address = fmt.Sprintf("127.0.0.1:999%d", GinkgoParallelNode())
	}

	runner := NewGardenRunner(binaries, rootfs, network, address, user, argv...)

	r := &RunningGarden{
		runner:   runner,
		DepotDir: runner.DepotDir,

		DataDir:   DataDir,
		GraphPath: runner.GraphPath,
		Tmpdir:    runner.TmpDir,
		logger:    lagertest.NewTestLogger("garden-runner"),

		debugIP:   runner.DebugIp,
		debugPort: runner.DebugPort,

		Client: client.New(connection.New(runner.Network, runner.Addr)),
	}

	r.process = ifrit.Invoke(r.runner)
	r.Pid = runner.Cmd.Process.Pid

	return r
}

func (r *RunningGarden) Kill() error {
	r.process.Signal(syscall.SIGKILL)
	select {
	case err := <-r.process.Wait():
		return err
	case <-time.After(time.Second * 10):
		r.process.Signal(syscall.SIGKILL)
		return errors.New("timed out waiting for garden to shutdown after 10 seconds")
	}
}

type ErrGardenStop struct {
	error
}

func (r *RunningGarden) DestroyAndStop() error {
	if err := r.DestroyContainers(); err != nil {
		return err
	}

	if runtime.GOOS == "windows" {
		// Windows doesn't support SIGTERM
		r.Kill()
	} else {
		if err := r.Stop(); err != nil {
			return ErrGardenStop{error: err}
		}
	}

	os.RemoveAll(r.Tmpdir)

	return nil
}

func (r *RunningGarden) Stop() error {
	r.process.Signal(syscall.SIGTERM)

	var err error
	for i := 0; i < 5; i++ {
		select {
		case err := <-r.process.Wait():
			return err
		case <-time.After(time.Second * 5):
			r.process.Signal(syscall.SIGTERM)
			err = errors.New("timed out waiting for garden to shutdown after 5 seconds")
		}
	}

	r.process.Signal(syscall.SIGKILL)
	return err
}

func (r *RunningGarden) DestroyContainers() error {
	containers, err := r.Containers(nil)
	if err != nil {
		return err
	}

	for _, container := range containers {
		if err := r.Destroy(container.Handle()); err != nil {
			return err
		}
	}

	return nil
}

type debugVars struct {
	NumGoRoutines int `json:"numGoRoutines"`
}

func (r *RunningGarden) DumpGoroutines() (string, error) {
	debugURL := fmt.Sprintf("http://%s:%d/debug/pprof/goroutine?debug=2", r.debugIP, r.debugPort)
	res, err := http.Get(debugURL)
	if err != nil {
		return "", err
	}
	defer res.Body.Close()

	b, err := ioutil.ReadAll(res.Body)
	return string(b), err
}

func (r *RunningGarden) NumGoroutines() (int, error) {
	debugURL := fmt.Sprintf("http://%s:%d/debug/vars", r.debugIP, r.debugPort)
	res, err := http.Get(debugURL)
	if err != nil {
		return 0, err
	}
	defer res.Body.Close()

	decoder := json.NewDecoder(res.Body)
	var debugVarsData debugVars
	err = decoder.Decode(&debugVarsData)
	if err != nil {
		return 0, err
	}

	return debugVarsData.NumGoRoutines, nil
}

func (r *RunningGarden) Buffer() *gbytes.Buffer {
	return r.runner.Buffer()
}

func (r *RunningGarden) ExitCode() int {
	return r.runner.ExitCode()
}
<commit_msg>GQT runner doesn't try to remove cgroup mount dir<commit_after>package runner

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"syscall"
	"time"

	"code.cloudfoundry.org/garden/client"
	"code.cloudfoundry.org/garden/client/connection"
	"code.cloudfoundry.org/lager"
	"code.cloudfoundry.org/lager/lagertest"
	"github.com/onsi/ginkgo"
	. "github.com/onsi/ginkgo"
	"github.com/onsi/gomega/gbytes"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/ginkgomon"
)

type Binaries struct {
	OCIRuntime    string `json:"oci_runtime,omitempty"`
	Tar           string `json:"tar,omitempty"`
	Gdn           string `json:"gdn,omitempty"`
	Init          string `json:"init,omitempty"`
	RuntimePlugin string `json:"runtime_plugin,omitempty"`
	ImagePlugin   string `json:"image_plugin,omitempty"`
	NetworkPlugin string `json:"network_plugin,omitempty"`
	NoopPlugin    string `json:"noop_plugin,omitempty"`
	ExecRunner    string `json:"execrunner,omitempty"`
	NSTar         string `json:"nstar,omitempty"`
}

const MNT_DETACH = 0x2

var DataDir string

type RunningGarden struct {
	client.Client

	runner  GardenRunner
	process ifrit.Process

	debugIP   string
	debugPort int

	Pid int

	Tmpdir string

	DepotDir  string
	DataDir   string
	GraphPath string

	logger lager.Logger
}

type GardenRunner struct {
	*ginkgomon.Runner
	Cmd            *exec.Cmd
	TmpDir         string
	GraphPath      string
	ConsoleSockets string
	DepotDir       string
	DebugIp        string
	DebugPort      int
	Network, Addr  string
}

func init() {
	DataDir = os.Getenv("GARDEN_TEST_GRAPHPATH")
	if DataDir == "" {
		// This must be set outside of the Ginkgo node directory (tmpDir) because
		// otherwise the Concourse worker may run into one of the AUFS kernel
		// module bugs that cause the VM to become unresponsive.
		DataDir = "/tmp/aufs_mount"
	}
}

func NewGardenRunner(binaries *Binaries, rootfs, network, address string, user UserCredential, argv ...string) GardenRunner {
	r := GardenRunner{}

	r.Network = network
	r.Addr = address
	r.TmpDir = filepath.Join(
		os.TempDir(),
		fmt.Sprintf("test-garden-%d", ginkgo.GinkgoParallelNode()),
	)

	r.GraphPath = filepath.Join(DataDir, fmt.Sprintf("node-%d", ginkgo.GinkgoParallelNode()))
	r.DepotDir = filepath.Join(r.TmpDir, "containers")
	r.ConsoleSockets = filepath.Join(r.TmpDir, "console-sockets")

	if runtime.GOOS == "linux" {
		MustMountTmpfs(r.GraphPath)
	}

	r.Cmd = cmd(r.TmpDir, r.DepotDir, r.GraphPath, r.ConsoleSockets, r.Network, r.Addr, binaries, rootfs, user, argv...)
	r.Cmd.Env = append(
		[]string{
			fmt.Sprintf("TMPDIR=%s", r.TmpDir),
			fmt.Sprintf("TEMP=%s", r.TmpDir),
			fmt.Sprintf("TMP=%s", r.TmpDir),
		},
		os.Environ()...,
	)

	for i, arg := range r.Cmd.Args {
		if arg == "--debug-bind-ip" {
			r.DebugIp = r.Cmd.Args[i+1]
		}
		if arg == "--debug-bind-port" {
			r.DebugPort, _ = strconv.Atoi(r.Cmd.Args[i+1])
		}
	}

	r.Runner = ginkgomon.New(ginkgomon.Config{
		Name:              "guardian",
		Command:           r.Cmd,
		AnsiColorCode:     "31m",
		StartCheck:        "guardian.started",
		StartCheckTimeout: 30 * time.Second,
	})

	return r
}

func Start(binaries *Binaries, rootfs string, user UserCredential, argv ...string) *RunningGarden {
	network := "unix"
	address := fmt.Sprintf("/tmp/garden_%d.sock", GinkgoParallelNode())

	if runtime.GOOS == "windows" {
		network = "tcp"
		address = fmt.Sprintf("127.0.0.1:999%d", GinkgoParallelNode())
	}

	runner := NewGardenRunner(binaries, rootfs, network, address, user, argv...)

	r := &RunningGarden{
		runner:   runner,
		DepotDir: runner.DepotDir,

		DataDir:   DataDir,
		GraphPath: runner.GraphPath,
		Tmpdir:    runner.TmpDir,
		logger:    lagertest.NewTestLogger("garden-runner"),

		debugIP:   runner.DebugIp,
		debugPort: runner.DebugPort,

		Client: client.New(connection.New(runner.Network, runner.Addr)),
	}

	r.process = ifrit.Invoke(r.runner)
	r.Pid = runner.Cmd.Process.Pid

	return r
}

func (r *RunningGarden) Kill() error {
	r.process.Signal(syscall.SIGKILL)
	select {
	case err := <-r.process.Wait():
		return err
	case <-time.After(time.Second * 10):
		r.process.Signal(syscall.SIGKILL)
		return errors.New("timed out waiting for garden to shutdown after 10 seconds")
	}
}

type ErrGardenStop struct {
	error
}

func (r *RunningGarden) DestroyAndStop() error {
	if err := r.DestroyContainers(); err != nil {
		return err
	}

	if runtime.GOOS == "windows" {
		// Windows doesn't support SIGTERM
		r.Kill()
	} else {
		if err := r.Stop(); err != nil {
			return ErrGardenStop{error: err}
		}
	}

	if err := r.removeTempDirPreservingCgroupMounts(); err != nil {
		return err
	}

	return nil
}

func (r *RunningGarden) removeTempDirPreservingCgroupMounts() error {
	tmpDir, err := os.Open(r.Tmpdir)
	if err != nil {
		return err
	}
	defer tmpDir.Close()
	tmpDirContents, err := tmpDir.Readdir(0)
	if err != nil {
		return err
	}
	for _, tmpDirChild := range tmpDirContents {
		if !strings.Contains(tmpDirChild.Name(), "cgroups-") {
			if err := os.RemoveAll(filepath.Join(r.Tmpdir, tmpDirChild.Name())); err != nil {
				return err
			}
		}
	}
	return nil
}

func (r *RunningGarden) Stop() error {
	r.process.Signal(syscall.SIGTERM)

	var err error
	for i := 0; i < 5; i++ {
		select {
		case err := <-r.process.Wait():
			return err
		case <-time.After(time.Second * 5):
			r.process.Signal(syscall.SIGTERM)
			err = errors.New("timed out waiting for garden to shutdown after 5 seconds")
		}
	}

	r.process.Signal(syscall.SIGKILL)
	return err
}

func (r *RunningGarden) DestroyContainers() error {
	containers, err := r.Containers(nil)
	if err != nil {
		return err
	}

	for _, container := range containers {
		if err := r.Destroy(container.Handle()); err != nil {
			return err
		}
	}

	return nil
}

type debugVars struct {
	NumGoRoutines int `json:"numGoRoutines"`
}

func (r *RunningGarden) DumpGoroutines() (string, error) {
	debugURL := fmt.Sprintf("http://%s:%d/debug/pprof/goroutine?debug=2", r.debugIP, r.debugPort)
	res, err := http.Get(debugURL)
	if err != nil {
		return "", err
	}
	defer res.Body.Close()

	b, err := ioutil.ReadAll(res.Body)
	return string(b), err
}

func (r *RunningGarden) NumGoroutines() (int, error) {
	debugURL := fmt.Sprintf("http://%s:%d/debug/vars", r.debugIP, r.debugPort)
	res, err := http.Get(debugURL)
	if err != nil {
		return 0, err
	}
	defer res.Body.Close()

	decoder := json.NewDecoder(res.Body)
	var debugVarsData debugVars
	err = decoder.Decode(&debugVarsData)
	if err != nil {
		return 0, err
	}

	return debugVarsData.NumGoRoutines, nil
}

func (r *RunningGarden) Buffer() *gbytes.Buffer {
	return r.runner.Buffer()
}

func (r *RunningGarden) ExitCode() int {
	return r.runner.ExitCode()
}
<|endoftext|>


<commit_before>package hammy

import (
	"fmt"
	"io"
	"time"
	"os/exec"
	"bytes"
	"syscall"
	"log"
	"github.com/ugorji/go-msgpack"
)

type process struct {
	*exec.Cmd
	Count uint
	Stdin io.Writer
	Stdout io.Reader
	Stderr bytes.Buffer
}

type WorkerProcessInput struct {
	Key string
	Trigger string
	State *State
	IData IncomingHostData
}

type WorkerProcessOutput struct {
	CmdBuffer *CmdBuffer
	State *State
}

// Executer implementation for subprocesses with MessagePack-based RPC
type SPExecuter struct {
	CmdLine string
	MaxIter uint
	Workers chan *process
	Timeout time.Duration
}

// Create new instance of SPExecutor
// per process
func NewSPExecuter(cfg Config) *SPExecuter {
	if cfg.Workers.PoolSize < 1 || cfg.Workers.CmdLine == "" {
		panic("Invalid argument")
	}

	e := new(SPExecuter)
	e.CmdLine = cfg.Workers.CmdLine
	e.MaxIter = cfg.Workers.MaxIter
	e.Workers = make(chan *process, cfg.Workers.PoolSize)
	e.Timeout = time.Duration(cfg.Workers.Timeout) * time.Second

	for i := uint(0); i < cfg.Workers.PoolSize; i++ {
		e.Workers <- &process{}
	}

	return e
}

func (e *SPExecuter) ProcessTrigger(key string, trigger string, state *State,
		data IncomingHostData) (newState *State, cmdb *CmdBuffer, err error) {
//
	cmdb = NewCmdBuffer(0)
	newState = NewState()
	res := WorkerProcessOutput{
		CmdBuffer: cmdb,
		State: newState,
	}

	// Fetch worker (may be wait for free worker)
	worker, err := e.getWorker()
	if err != nil {
		return
	}
	defer e.freeWorker(worker)

	// Set up timeout
	cEnd := make(chan int)
	go e.workerTimeout(worker, cEnd)

	// marshal and send args
	pInput := WorkerProcessInput{
		Key: key,
		Trigger: trigger,
		State: state,
		IData: data,
	}

	enc := msgpack.NewEncoder(worker.Stdin)
	err = enc.Encode(pInput)
	if err != nil {
		return
	}

	// wait, read and unmarshal result
	dec := msgpack.NewDecoder(worker.Stdout, nil)
	err = dec.Decode(&res)
	if err != nil {
		err = fmt.Errorf("SPExexuter error: %#v, child stderr: %#v", err, worker.Stderr.String())
	}

	close(cEnd)

	return
}

// timeout task
func (e *SPExecuter) workerTimeout(worker *process, cEnd chan int) {
	select {
	case <-cEnd:
		return
	case <-time.After(e.Timeout):
		log.Printf("SPExecuter: worker [%v] timed out", worker.Process.Pid)
		err := worker.Process.Kill()
		if err != nil {
			log.Printf("SPExecuter: Process.Kill error: %#v", err)
		}
		worker.Cmd = nil
	}
}

// Fetch worker (may be wait for free worker)
func (e *SPExecuter) getWorker() (worker *process, err error) {
	worker = <- e.Workers

	if worker == nil {
		panic("nil worker")
	}

	if worker.Cmd != nil {
		// Check process state
		var status syscall.WaitStatus

		// We can't use worker.ProcessState (it's available only after a call to Wait or Run)
		wpid, err := syscall.Wait4(worker.Process.Pid, &status, syscall.WNOHANG, nil)

		switch {
			case err == nil && wpid == 0:
				// Do nothing
			case err == nil && status.Exited():
				worker.Cmd = nil
			case err != nil:
				log.Printf("SPExecuter: syscall.Wait4 error: %#v", err)
				err = worker.Process.Kill()
				if err != nil {
					log.Printf("SPExecuter: Process.Kill error: %#v", err)
				}
				worker.Cmd = nil
			default:
				// Do nothing
		}
	}

	if worker.Cmd == nil {
		// Creating new subprocess
		worker.Count = 0
		worker.Cmd = exec.Command(e.CmdLine)
		worker.Stdin, err = worker.Cmd.StdinPipe()
		if err != nil {
			return
		}
		worker.Stdout, err = worker.Cmd.StdoutPipe()
		if err != nil {
			return
		}
		worker.Cmd.Stderr = &worker.Stderr
		err = worker.Start()
		if err != nil {
			return
		}
	}

	return
}

// Return worker to buffer
func (e *SPExecuter) freeWorker(worker *process) {
	// Increment count of execution for the worker
	worker.Count++

	// Check iteration count
	if worker.Count >= e.MaxIter {
		err := worker.Process.Kill()
		if err != nil {
			log.Printf("SPExecuter: Process.Kill error: %#v", err)
		}
		worker.Cmd = nil
	}

	// Return worker to the queue
	e.Workers <- worker
}
<commit_msg>more details on timeout<commit_after>package hammy

import (
	"fmt"
	"io"
	"time"
	"os/exec"
	"bytes"
	"syscall"
	"log"
	"github.com/ugorji/go-msgpack"
)

type process struct {
	*exec.Cmd
	Count uint
	Stdin io.Writer
	Stdout io.Reader
	Stderr bytes.Buffer
}

type WorkerProcessInput struct {
	Key string
	Trigger string
	State *State
	IData IncomingHostData
}

type WorkerProcessOutput struct {
	CmdBuffer *CmdBuffer
	State *State
}

// Executer implementation for subprocesses with MessagePack-based RPC
type SPExecuter struct {
	CmdLine string
	MaxIter uint
	Workers chan *process
	Timeout time.Duration
}

// Create new instance of SPExecutor
// per process
func NewSPExecuter(cfg Config) *SPExecuter {
	if cfg.Workers.PoolSize < 1 || cfg.Workers.CmdLine == "" {
		panic("Invalid argument")
	}

	e := new(SPExecuter)
	e.CmdLine = cfg.Workers.CmdLine
	e.MaxIter = cfg.Workers.MaxIter
	e.Workers = make(chan *process, cfg.Workers.PoolSize)
	e.Timeout = time.Duration(cfg.Workers.Timeout) * time.Second

	for i := uint(0); i < cfg.Workers.PoolSize; i++ {
		e.Workers <- &process{}
	}

	return e
}

func (e *SPExecuter) ProcessTrigger(key string, trigger string, state *State,
		data IncomingHostData) (newState *State, cmdb *CmdBuffer, err error) {
//
	cmdb = NewCmdBuffer(0)
	newState = NewState()
	res := WorkerProcessOutput{
		CmdBuffer: cmdb,
		State: newState,
	}

	// Fetch worker (may be wait for free worker)
	worker, err := e.getWorker()
	if err != nil {
		return
	}
	defer e.freeWorker(worker)

	// Set up timeout
	cTimedOut := make(chan bool)
	cEnd := make(chan int)
	go e.workerTimeout(worker, cEnd, cTimedOut)

	// marshal and send args
	pInput := WorkerProcessInput{
		Key: key,
		Trigger: trigger,
		State: state,
		IData: data,
	}

	enc := msgpack.NewEncoder(worker.Stdin)
	err = enc.Encode(pInput)
	if err != nil {
		return
	}

	// wait, read and unmarshal result
	dec := msgpack.NewDecoder(worker.Stdout, nil)
	err = dec.Decode(&res)
	close(cEnd)
	if err != nil {
		timedOut := <- cTimedOut
		if timedOut {
			err = fmt.Errorf("SPExexuter timeout: %#v, child stderr: %#v, pInput: %#v", err, worker.Stderr.String(), pInput)
		} else {
			err = fmt.Errorf("SPExexuter error: %#v, child stderr: %#v", err, worker.Stderr.String())
		}
	}
	return
}

// timeout task
func (e *SPExecuter) workerTimeout(worker *process, cEnd chan int, cRes chan bool) {
	select {
	case <-cEnd:
		cRes <- false
	case <-time.After(e.Timeout):
		err := worker.Process.Kill()
		if err != nil {
			log.Printf("SPExecuter: Process.Kill error: %#v", err)
		}
		worker.Cmd = nil
		cRes <- true
	}
}

// Fetch worker (may be wait for free worker)
func (e *SPExecuter) getWorker() (worker *process, err error) {
	worker = <- e.Workers

	if worker == nil {
		panic("nil worker")
	}

	if worker.Cmd != nil {
		// Check process state
		var status syscall.WaitStatus

		// We can't use worker.ProcessState (it's available only after a call to Wait or Run)
		wpid, err := syscall.Wait4(worker.Process.Pid, &status, syscall.WNOHANG, nil)

		switch {
			case err == nil && wpid == 0:
				// Do nothing
			case err == nil && status.Exited():
				worker.Cmd = nil
			case err != nil:
				log.Printf("SPExecuter: syscall.Wait4 error: %#v", err)
				err = worker.Process.Kill()
				if err != nil {
					log.Printf("SPExecuter: Process.Kill error: %#v", err)
				}
				worker.Cmd = nil
			default:
				// Do nothing
		}
	}

	if worker.Cmd == nil {
		// Creating new subprocess
		worker.Count = 0
		worker.Cmd = exec.Command(e.CmdLine)
		worker.Stdin, err = worker.Cmd.StdinPipe()
		if err != nil {
			return
		}
		worker.Stdout, err = worker.Cmd.StdoutPipe()
		if err != nil {
			return
		}
		worker.Cmd.Stderr = &worker.Stderr
		err = worker.Start()
		if err != nil {
			return
		}
	}

	return
}

// Return worker to buffer
func (e *SPExecuter) freeWorker(worker *process) {
	// Increment count of execution for the worker
	worker.Count++

	// Check iteration count
	if worker.Count >= e.MaxIter {
		err := worker.Process.Kill()
		if err != nil {
			log.Printf("SPExecuter: Process.Kill error: %#v", err)
		}
		worker.Cmd = nil
	}

	// Return worker to the queue
	e.Workers <- worker
}
<|endoftext|>


<commit_before>// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package xds

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/gogo/protobuf/types"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	kubetypes "k8s.io/apimachinery/pkg/types"

	"istio.io/api/meta/v1alpha1"
	"istio.io/api/networking/v1alpha3"
	"istio.io/istio/pilot/pkg/features"
	"istio.io/istio/pilot/pkg/model"
	"istio.io/istio/pilot/pkg/networking/util"
	"istio.io/istio/pkg/config"
	"istio.io/istio/pkg/config/schema/gvk"
)

const (
	// TODO use status or another proper API instead of annotations

	// AutoRegistrationGroupAnnotation on a WorkloadEntry stores the associated WorkloadGroup.
	AutoRegistrationGroupAnnotation = "istio.io/autoRegistrationGroup"
	// WorkloadControllerAnnotation on a WorkloadEntry should store the current/last pilot instance connected to the workload for XDS.
	WorkloadControllerAnnotation = "istio.io/workloadController"
	// ConnectedAtAnnotation on a WorkloadEntry stores the time in nanoseconds when the associated workload connected to a Pilot instance.
	ConnectedAtAnnotation = "istio.io/connectedAt"
	// DisconnectedAtAnnotation on a WorkloadEntry stores the time in nanoseconds when the associated workload disconnected from a Pilot instance.
	DisconnectedAtAnnotation = "istio.io/disconnectedAt"

	timeFormat = time.RFC3339Nano
)

type HealthEvent struct {
	// whether or not the agent thought the target was empty
	Healthy bool `json:"healthy,omitempty"`
	// error message propagated
	Message string `json:"err_message,omitempty"`
}

func (sg *InternalGen) RegisterWorkload(proxy *model.Proxy, con *Connection) error {
	if !features.WorkloadEntryAutoRegistration {
		return nil
	}
	// check if the WE already exists, update the status
	entryName := autoregisteredWorkloadEntryName(proxy)
	if entryName == "" {
		return nil
	}

	// Try to patch, if it fails then try to create
	_, err := sg.store.Patch(gvk.WorkloadEntry, entryName, proxy.Metadata.Namespace, func(cfg config.Config) config.Config {
		setConnectMeta(&cfg, sg.Server.instanceID, con)
		return cfg
	})
	// TODO return err from Patch through Get
	if err == nil {
		return nil
	} else if !errors.IsNotFound(err) && err.Error() != "item not found" {
		adsLog.Errorf("updating auto-registered WorkloadEntry %s/%s: %v", proxy.Metadata.Namespace, entryName, err)
		return fmt.Errorf("updating auto-registered WorkloadEntry %s/%s err: %v", proxy.Metadata.Namespace, entryName, err)
	}

	// No WorkloadEntry, create one using fields from the associated WorkloadGroup
	groupCfg := sg.store.Get(gvk.WorkloadGroup, proxy.Metadata.AutoRegisterGroup, proxy.Metadata.Namespace)
	if groupCfg == nil {
		adsLog.Errorf("auto-registration of %v failed: cannot find WorkloadGroup %s/%s",
			proxy.ID, proxy.Metadata.Namespace, proxy.Metadata.AutoRegisterGroup)
		return fmt.Errorf("auto-registration of %v failed: cannot find WorkloadGroup %s/%s",
			proxy.ID, proxy.Metadata.Namespace, proxy.Metadata.AutoRegisterGroup)
	}
	entry := workloadEntryFromGroup(entryName, proxy, groupCfg)
	setConnectMeta(entry, sg.Server.instanceID, con)
	_, err = sg.store.Create(*entry)
	if err != nil {
		adsLog.Errorf("auto-registration of %v failed: error creating WorkloadEntry: %v", proxy.ID, err)
		return fmt.Errorf("auto-registration of %v failed: error creating WorkloadEntry: %v", proxy.ID, err)
	}
	return nil
}

func (sg *InternalGen) QueueUnregisterWorkload(proxy *model.Proxy) {
	if !features.WorkloadEntryAutoRegistration {
		return
	}
	// check if the WE already exists, update the status
	entryName := autoregisteredWorkloadEntryName(proxy)
	if entryName == "" {
		return
	}

	// unset controller, set disconnect time
	cfg := sg.store.Get(gvk.WorkloadEntry, entryName, proxy.Metadata.Namespace)
	if cfg == nil {
		// we failed to create the workload entry in the first place or it is not propagated
		return
	}

	// The wle has reconnected to another istiod and controlled by it.
	if cfg.Annotations[WorkloadControllerAnnotation] != sg.Server.instanceID {
		return
	}
	wle := cfg.DeepCopy()
	delete(wle.Annotations, WorkloadControllerAnnotation)
	wle.Annotations[DisconnectedAtAnnotation] = time.Now().Format(timeFormat)
	// use update instead of patch to prevent race condition
	_, err := sg.store.Update(wle)
	if err != nil && !errors.IsConflict(err) {
		adsLog.Warnf("disconnect: failed updating WorkloadEntry %s/%s: %v", proxy.Metadata.Namespace, entryName, err)
		return
	}

	// after grace period, check if the workload ever reconnected
	ns := proxy.Metadata.Namespace
	sg.cleanupQueue.PushDelayed(func() error {
		wle := sg.store.Get(gvk.WorkloadEntry, entryName, ns)
		if wle == nil {
			return nil
		}
		if shouldCleanupEntry(*wle) {
			sg.cleanupEntry(*wle)
		}
		return nil
	}, features.WorkloadEntryCleanupGracePeriod)
}

// UpdateWorkloadEntryHealth updates the associated WorkloadEntries health status
// based on the corresponding health check performed by istio-agent.
func (sg *InternalGen) UpdateWorkloadEntryHealth(proxy *model.Proxy, event HealthEvent) {
	// we assume that the workload entry exists
	// if auto registration does not exist, try looking
	// up in NodeMetadata
	entryName := autoregisteredWorkloadEntryName(proxy)
	if entryName == "" {
		adsLog.Errorf("unable to derive WorkloadEntry for health update for %v", proxy.ID)
		return
	}

	// get previous status
	cfg := sg.store.Get(gvk.WorkloadEntry, entryName, proxy.Metadata.Namespace)
	if cfg == nil {
		adsLog.Errorf("config was nil when getting WorkloadEntry %v for %v", entryName, proxy.ID)
		return
	}
	wle := cfg.DeepCopy()

	// replace the updated status
	// should this status nil check be in the initial auto registration?
	var status *v1alpha1.IstioStatus
	if wle.Status == nil {
		// for some reason we have a nil status, just make conditions
		// an empty array
		wle.Status = &v1alpha1.IstioStatus{
			Conditions: []*v1alpha1.IstioCondition{},
		}
	}
	status = wle.Status.(*v1alpha1.IstioStatus)
	status.Conditions = UpdateHealthCondition(status.Conditions, event)

	// update the status
	_, err := sg.store.UpdateStatus(wle)
	if err != nil {
		adsLog.Errorf("error while updating WorkloadEntry status: %v for %v", err, proxy.ID)
	}
}

// periodicWorkloadEntryCleanup checks lists all WorkloadEntry
func (sg *InternalGen) periodicWorkloadEntryCleanup(stopCh <-chan struct{}) {
	if !features.WorkloadEntryAutoRegistration {
		return
	}
	ticker := time.NewTicker(10 * features.WorkloadEntryCleanupGracePeriod)
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			wles, err := sg.store.List(gvk.WorkloadEntry, metav1.NamespaceAll)
			if err != nil {
				adsLog.Warnf("error listing WorkloadEntry for cleanup: %v", err)
				continue
			}
			for _, wle := range wles {
				wle := wle
				if shouldCleanupEntry(wle) {
					sg.cleanupQueue.Push(func() error {
						sg.cleanupEntry(wle)
						return nil
					})
				}
			}
		case <-stopCh:
			return
		}
	}
}

func (sg *InternalGen) cleanupEntry(wle config.Config) {
	if err := sg.cleanupLimit.Wait(context.TODO()); err != nil {
		adsLog.Errorf("error in WorkloadEntry cleanup rate limiter: %v", err)
	}
	if err := sg.store.Delete(gvk.WorkloadEntry, wle.Name, wle.Namespace); err != nil {
		adsLog.Warnf("failed cleaning up auto-registered WorkloadEntry %s/%s: %v", wle.Namespace, wle.Name, err)
	}
}

func shouldCleanupEntry(wle config.Config) bool {
	// don't clean-up if connected or non-autoregistered WorkloadEntries
	if wle.Annotations[AutoRegistrationGroupAnnotation] == "" ||
		wle.Annotations[WorkloadControllerAnnotation] != "" {
		return false
	}

	disconnTime := wle.Annotations[DisconnectedAtAnnotation]
	if disconnTime == "" {
		return false
	}

	disconnAt, err := time.Parse(timeFormat, disconnTime)
	// if we haven't passed the grace period, don't cleanup
	if err == nil && time.Since(disconnAt) < features.WorkloadEntryCleanupGracePeriod {
		return false
	}

	return true
}

func setConnectMeta(c *config.Config, controller string, con *Connection) {
	c.Annotations[WorkloadControllerAnnotation] = controller
	c.Annotations[ConnectedAtAnnotation] = con.Connect.Format(timeFormat)
}

var workloadGroupIsController = true

func workloadEntryFromGroup(name string, proxy *model.Proxy, groupCfg *config.Config) *config.Config {
	group := groupCfg.Spec.(*v1alpha3.WorkloadGroup)
	entry := group.Template.DeepCopy()
	entry.Address = proxy.IPAddresses[0]
	// TODO move labels out of entry
	// node metadata > WorkloadGroup.Metadata > WorkloadGroup.Template
	if group.Metadata != nil && group.Metadata.Labels != nil {
		entry.Labels = mergeLabels(entry.Labels, group.Metadata.Labels)
	}
	if proxy.Metadata != nil && proxy.Metadata.Labels != nil {
		entry.Labels = mergeLabels(entry.Labels, proxy.Metadata.Labels)
	}

	annotations := map[string]string{AutoRegistrationGroupAnnotation: groupCfg.Name}
	if group.Metadata != nil && group.Metadata.Annotations != nil {
		annotations = mergeLabels(annotations, group.Metadata.Annotations)
	}

	if proxy.Metadata.Network != "" {
		entry.Network = proxy.Metadata.Network
	}
	if proxy.Locality != nil {
		entry.Locality = util.LocalityToString(proxy.Locality)
	}
	return &config.Config{
		Meta: config.Meta{
			GroupVersionKind: gvk.WorkloadEntry,
			Name:             name,
			Namespace:        proxy.Metadata.Namespace,
			Labels:           entry.Labels,
			Annotations:      annotations,
			OwnerReferences: []metav1.OwnerReference{{
				APIVersion: groupCfg.GroupVersionKind.GroupVersion(),
				Kind:       groupCfg.GroupVersionKind.Kind,
				Name:       groupCfg.Name,
				UID:        kubetypes.UID(groupCfg.UID),
				Controller: &workloadGroupIsController,
			}},
		},
		Spec: entry,
		// TODO status fields used for garbage collection
		Status: nil,
	}
}

func mergeLabels(labels ...map[string]string) map[string]string {
	if len(labels) == 0 {
		return map[string]string{}
	}
	out := make(map[string]string, len(labels)*len(labels[0]))
	for _, lm := range labels {
		for k, v := range lm {
			out[k] = v
		}
	}
	return out
}

func autoregisteredWorkloadEntryName(proxy *model.Proxy) string {
	if proxy.Metadata.AutoRegisterGroup == "" {
		return ""
	}
	if len(proxy.IPAddresses) == 0 {
		adsLog.Errorf("auto-registration of %v failed: missing IP addresses", proxy.ID)
		return ""
	}
	if len(proxy.Metadata.Namespace) == 0 {
		adsLog.Errorf("auto-registration of %v failed: missing namespace", proxy.ID)
		return ""
	}
	p := []string{proxy.Metadata.AutoRegisterGroup, proxy.IPAddresses[0]}
	if proxy.Metadata.Network != "" {
		p = append(p, proxy.Metadata.Network)
	}

	name := strings.Join(p, "-")
	if len(name) > 253 {
		name = name[len(name)-253:]
		adsLog.Warnf("generated WorkloadEntry name is too long, consider making the WorkloadGroup name shorter. Shortening from beginning to: %s", name)
	}
	return name
}

func UpdateHealthCondition(conditions []*v1alpha1.IstioCondition, event HealthEvent) []*v1alpha1.IstioCondition {
	foundHealth := false
	healthIdx := 0
	for i, cond := range conditions {
		if cond.Type == "Healthy" {
			foundHealth = true
			healthIdx = i
			break
		}
	}
	if !foundHealth {
		// we have not inserted a healthy condition yet
		// just append and return
		return append(conditions, transformHealthEvent(event))
	}
	// we should just replace the health status
	conditions[healthIdx] = transformHealthEvent(event)
	return conditions
}

func transformHealthEvent(event HealthEvent) *v1alpha1.IstioCondition {
	cond := &v1alpha1.IstioCondition{
		Type: "Healthy",
		// last probe and transition are the same because
		// we only send on transition in the agent
		LastProbeTime:      types.TimestampNow(),
		LastTransitionTime: types.TimestampNow(),
	}
	if event.Healthy {
		cond.Status = "True"
		return cond
	}
	cond.Status = "False"
	cond.Message = event.Message
	return cond
}
<commit_msg>Expand logging on auto registration (#29416)<commit_after>// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package xds

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/gogo/protobuf/types"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	kubetypes "k8s.io/apimachinery/pkg/types"

	"istio.io/api/meta/v1alpha1"
	"istio.io/api/networking/v1alpha3"
	"istio.io/istio/pilot/pkg/features"
	"istio.io/istio/pilot/pkg/model"
	"istio.io/istio/pilot/pkg/networking/util"
	"istio.io/istio/pkg/config"
	"istio.io/istio/pkg/config/schema/gvk"
)

const (
	// TODO use status or another proper API instead of annotations

	// AutoRegistrationGroupAnnotation on a WorkloadEntry stores the associated WorkloadGroup.
	AutoRegistrationGroupAnnotation = "istio.io/autoRegistrationGroup"
	// WorkloadControllerAnnotation on a WorkloadEntry should store the current/last pilot instance connected to the workload for XDS.
	WorkloadControllerAnnotation = "istio.io/workloadController"
	// ConnectedAtAnnotation on a WorkloadEntry stores the time in nanoseconds when the associated workload connected to a Pilot instance.
	ConnectedAtAnnotation = "istio.io/connectedAt"
	// DisconnectedAtAnnotation on a WorkloadEntry stores the time in nanoseconds when the associated workload disconnected from a Pilot instance.
	DisconnectedAtAnnotation = "istio.io/disconnectedAt"

	timeFormat = time.RFC3339Nano
)

type HealthEvent struct {
	// whether or not the agent thought the target was empty
	Healthy bool `json:"healthy,omitempty"`
	// error message propagated
	Message string `json:"err_message,omitempty"`
}

func (sg *InternalGen) RegisterWorkload(proxy *model.Proxy, con *Connection) error {
	if !features.WorkloadEntryAutoRegistration {
		return nil
	}
	// check if the WE already exists, update the status
	entryName := autoregisteredWorkloadEntryName(proxy)
	if entryName == "" {
		return nil
	}

	// Try to patch, if it fails then try to create
	_, err := sg.store.Patch(gvk.WorkloadEntry, entryName, proxy.Metadata.Namespace, func(cfg config.Config) config.Config {
		setConnectMeta(&cfg, sg.Server.instanceID, con)
		return cfg
	})
	// TODO return err from Patch through Get
	if err == nil {
		adsLog.Debugf("updated auto-registered WorkloadEntry %s/%s", proxy.Metadata.Namespace, entryName)
		return nil
	} else if !errors.IsNotFound(err) && err.Error() != "item not found" {
		adsLog.Errorf("updating auto-registered WorkloadEntry %s/%s: %v", proxy.Metadata.Namespace, entryName, err)
		return fmt.Errorf("updating auto-registered WorkloadEntry %s/%s err: %v", proxy.Metadata.Namespace, entryName, err)
	}

	// No WorkloadEntry, create one using fields from the associated WorkloadGroup
	groupCfg := sg.store.Get(gvk.WorkloadGroup, proxy.Metadata.AutoRegisterGroup, proxy.Metadata.Namespace)
	if groupCfg == nil {
		adsLog.Errorf("auto-registration of %v failed: cannot find WorkloadGroup %s/%s",
			proxy.ID, proxy.Metadata.Namespace, proxy.Metadata.AutoRegisterGroup)
		return fmt.Errorf("auto-registration of %v failed: cannot find WorkloadGroup %s/%s",
			proxy.ID, proxy.Metadata.Namespace, proxy.Metadata.AutoRegisterGroup)
	}
	entry := workloadEntryFromGroup(entryName, proxy, groupCfg)
	setConnectMeta(entry, sg.Server.instanceID, con)
	_, err = sg.store.Create(*entry)
	if err != nil {
		adsLog.Errorf("auto-registration of %v failed: error creating WorkloadEntry: %v", proxy.ID, err)
		return fmt.Errorf("auto-registration of %v failed: error creating WorkloadEntry: %v", proxy.ID, err)
	}
	adsLog.Infof("auto-registered WorkloadEntry %s/%s", proxy.Metadata.Namespace, entryName)
	return nil
}

func (sg *InternalGen) QueueUnregisterWorkload(proxy *model.Proxy) {
	if !features.WorkloadEntryAutoRegistration {
		return
	}
	// check if the WE already exists, update the status
	entryName := autoregisteredWorkloadEntryName(proxy)
	if entryName == "" {
		return
	}

	// unset controller, set disconnect time
	cfg := sg.store.Get(gvk.WorkloadEntry, entryName, proxy.Metadata.Namespace)
	if cfg == nil {
		// we failed to create the workload entry in the first place or it is not propagated
		return
	}

	// The wle has reconnected to another istiod and controlled by it.
	if cfg.Annotations[WorkloadControllerAnnotation] != sg.Server.instanceID {
		return
	}
	wle := cfg.DeepCopy()
	delete(wle.Annotations, WorkloadControllerAnnotation)
	wle.Annotations[DisconnectedAtAnnotation] = time.Now().Format(timeFormat)
	// use update instead of patch to prevent race condition
	_, err := sg.store.Update(wle)
	if err != nil && !errors.IsConflict(err) {
		adsLog.Warnf("disconnect: failed updating WorkloadEntry %s/%s: %v", proxy.Metadata.Namespace, entryName, err)
		return
	}

	// after grace period, check if the workload ever reconnected
	ns := proxy.Metadata.Namespace
	sg.cleanupQueue.PushDelayed(func() error {
		wle := sg.store.Get(gvk.WorkloadEntry, entryName, ns)
		if wle == nil {
			return nil
		}
		if shouldCleanupEntry(*wle) {
			sg.cleanupEntry(*wle)
		}
		return nil
	}, features.WorkloadEntryCleanupGracePeriod)
}

// UpdateWorkloadEntryHealth updates the associated WorkloadEntries health status
// based on the corresponding health check performed by istio-agent.
func (sg *InternalGen) UpdateWorkloadEntryHealth(proxy *model.Proxy, event HealthEvent) {
	// we assume that the workload entry exists
	// if auto registration does not exist, try looking
	// up in NodeMetadata
	entryName := autoregisteredWorkloadEntryName(proxy)
	if entryName == "" {
		adsLog.Errorf("unable to derive WorkloadEntry for health update for %v", proxy.ID)
		return
	}

	// get previous status
	cfg := sg.store.Get(gvk.WorkloadEntry, entryName, proxy.Metadata.Namespace)
	if cfg == nil {
		adsLog.Errorf("config was nil when getting WorkloadEntry %v for %v", entryName, proxy.ID)
		return
	}
	wle := cfg.DeepCopy()

	// replace the updated status
	// should this status nil check be in the initial auto registration?
	var status *v1alpha1.IstioStatus
	if wle.Status == nil {
		// for some reason we have a nil status, just make conditions
		// an empty array
		wle.Status = &v1alpha1.IstioStatus{
			Conditions: []*v1alpha1.IstioCondition{},
		}
	}
	status = wle.Status.(*v1alpha1.IstioStatus)
	status.Conditions = UpdateHealthCondition(status.Conditions, event)

	// update the status
	_, err := sg.store.UpdateStatus(wle)
	if err != nil {
		adsLog.Errorf("error while updating WorkloadEntry status: %v for %v", err, proxy.ID)
	}
}

// periodicWorkloadEntryCleanup checks lists all WorkloadEntry
func (sg *InternalGen) periodicWorkloadEntryCleanup(stopCh <-chan struct{}) {
	if !features.WorkloadEntryAutoRegistration {
		return
	}
	ticker := time.NewTicker(10 * features.WorkloadEntryCleanupGracePeriod)
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			wles, err := sg.store.List(gvk.WorkloadEntry, metav1.NamespaceAll)
			if err != nil {
				adsLog.Warnf("error listing WorkloadEntry for cleanup: %v", err)
				continue
			}
			for _, wle := range wles {
				wle := wle
				if shouldCleanupEntry(wle) {
					sg.cleanupQueue.Push(func() error {
						sg.cleanupEntry(wle)
						return nil
					})
				}
			}
		case <-stopCh:
			return
		}
	}
}

func (sg *InternalGen) cleanupEntry(wle config.Config) {
	if err := sg.cleanupLimit.Wait(context.TODO()); err != nil {
		adsLog.Errorf("error in WorkloadEntry cleanup rate limiter: %v", err)
	}
	if err := sg.store.Delete(gvk.WorkloadEntry, wle.Name, wle.Namespace); err != nil {
		adsLog.Warnf("failed cleaning up auto-registered WorkloadEntry %s/%s: %v", wle.Namespace, wle.Name, err)
	}
	adsLog.Infof("cleaned up auto-registered WorkloadEntry %s/%s", wle.Namespace, wle.Name)
}

func shouldCleanupEntry(wle config.Config) bool {
	// don't clean-up if connected or non-autoregistered WorkloadEntries
	if wle.Annotations[AutoRegistrationGroupAnnotation] == "" ||
		wle.Annotations[WorkloadControllerAnnotation] != "" {
		return false
	}

	disconnTime := wle.Annotations[DisconnectedAtAnnotation]
	if disconnTime == "" {
		return false
	}

	disconnAt, err := time.Parse(timeFormat, disconnTime)
	// if we haven't passed the grace period, don't cleanup
	if err == nil && time.Since(disconnAt) < features.WorkloadEntryCleanupGracePeriod {
		return false
	}

	return true
}

func setConnectMeta(c *config.Config, controller string, con *Connection) {
	c.Annotations[WorkloadControllerAnnotation] = controller
	c.Annotations[ConnectedAtAnnotation] = con.Connect.Format(timeFormat)
}

var workloadGroupIsController = true

func workloadEntryFromGroup(name string, proxy *model.Proxy, groupCfg *config.Config) *config.Config {
	group := groupCfg.Spec.(*v1alpha3.WorkloadGroup)
	entry := group.Template.DeepCopy()
	entry.Address = proxy.IPAddresses[0]
	// TODO move labels out of entry
	// node metadata > WorkloadGroup.Metadata > WorkloadGroup.Template
	if group.Metadata != nil && group.Metadata.Labels != nil {
		entry.Labels = mergeLabels(entry.Labels, group.Metadata.Labels)
	}
	if proxy.Metadata != nil && proxy.Metadata.Labels != nil {
		entry.Labels = mergeLabels(entry.Labels, proxy.Metadata.Labels)
	}

	annotations := map[string]string{AutoRegistrationGroupAnnotation: groupCfg.Name}
	if group.Metadata != nil && group.Metadata.Annotations != nil {
		annotations = mergeLabels(annotations, group.Metadata.Annotations)
	}

	if proxy.Metadata.Network != "" {
		entry.Network = proxy.Metadata.Network
	}
	if proxy.Locality != nil {
		entry.Locality = util.LocalityToString(proxy.Locality)
	}
	return &config.Config{
		Meta: config.Meta{
			GroupVersionKind: gvk.WorkloadEntry,
			Name:             name,
			Namespace:        proxy.Metadata.Namespace,
			Labels:           entry.Labels,
			Annotations:      annotations,
			OwnerReferences: []metav1.OwnerReference{{
				APIVersion: groupCfg.GroupVersionKind.GroupVersion(),
				Kind:       groupCfg.GroupVersionKind.Kind,
				Name:       groupCfg.Name,
				UID:        kubetypes.UID(groupCfg.UID),
				Controller: &workloadGroupIsController,
			}},
		},
		Spec: entry,
		// TODO status fields used for garbage collection
		Status: nil,
	}
}

func mergeLabels(labels ...map[string]string) map[string]string {
	if len(labels) == 0 {
		return map[string]string{}
	}
	out := make(map[string]string, len(labels)*len(labels[0]))
	for _, lm := range labels {
		for k, v := range lm {
			out[k] = v
		}
	}
	return out
}

func autoregisteredWorkloadEntryName(proxy *model.Proxy) string {
	if proxy.Metadata.AutoRegisterGroup == "" {
		return ""
	}
	if len(proxy.IPAddresses) == 0 {
		adsLog.Errorf("auto-registration of %v failed: missing IP addresses", proxy.ID)
		return ""
	}
	if len(proxy.Metadata.Namespace) == 0 {
		adsLog.Errorf("auto-registration of %v failed: missing namespace", proxy.ID)
		return ""
	}
	p := []string{proxy.Metadata.AutoRegisterGroup, proxy.IPAddresses[0]}
	if proxy.Metadata.Network != "" {
		p = append(p, proxy.Metadata.Network)
	}

	name := strings.Join(p, "-")
	if len(name) > 253 {
		name = name[len(name)-253:]
		adsLog.Warnf("generated WorkloadEntry name is too long, consider making the WorkloadGroup name shorter. Shortening from beginning to: %s", name)
	}
	return name
}

func UpdateHealthCondition(conditions []*v1alpha1.IstioCondition, event HealthEvent) []*v1alpha1.IstioCondition {
	foundHealth := false
	healthIdx := 0
	for i, cond := range conditions {
		if cond.Type == "Healthy" {
			foundHealth = true
			healthIdx = i
			break
		}
	}
	if !foundHealth {
		// we have not inserted a healthy condition yet
		// just append and return
		return append(conditions, transformHealthEvent(event))
	}
	// we should just replace the health status
	conditions[healthIdx] = transformHealthEvent(event)
	return conditions
}

func transformHealthEvent(event HealthEvent) *v1alpha1.IstioCondition {
	cond := &v1alpha1.IstioCondition{
		Type: "Healthy",
		// last probe and transition are the same because
		// we only send on transition in the agent
		LastProbeTime:      types.TimestampNow(),
		LastTransitionTime: types.TimestampNow(),
	}
	if event.Healthy {
		cond.Status = "True"
		return cond
	}
	cond.Status = "False"
	cond.Message = event.Message
	return cond
}
<|endoftext|>


<commit_before>//go:build !windows && !darwin
// +build !windows,!darwin

package chrootarchive

import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"

	"github.com/containers/storage/pkg/archive"
	"github.com/containers/storage/pkg/reexec"
	"github.com/containers/storage/pkg/system"
	"github.com/containers/storage/pkg/unshare"
)

type applyLayerResponse struct {
	LayerSize int64 `json:"layerSize"`
}

// applyLayer is the entry-point for storage-applylayer on re-exec. This is not
// used on Windows as it does not support chroot, hence no point sandboxing
// through chroot and rexec.
func applyLayer() {

	var (
		tmpDir  string
		err     error
		options *archive.TarOptions
	)
	runtime.LockOSThread()
	flag.Parse()

	inUserns := unshare.IsRootless()
	if err := chroot(flag.Arg(0)); err != nil {
		fatal(err)
	}

	// We need to be able to set any perms
	oldmask, err := system.Umask(0)
	defer system.Umask(oldmask)
	if err != nil {
		fatal(err)
	}

	if err := json.Unmarshal([]byte(os.Getenv("OPT")), &options); err != nil {
		fatal(err)
	}

	if inUserns {
		options.InUserNS = true
	}

	if tmpDir, err = ioutil.TempDir("/", "temp-storage-extract"); err != nil {
		fatal(err)
	}

	os.Setenv("TMPDIR", tmpDir)
	size, err := archive.UnpackLayer("/", os.Stdin, options)
	os.RemoveAll(tmpDir)
	if err != nil {
		fatal(err)
	}

	encoder := json.NewEncoder(os.Stdout)
	if err := encoder.Encode(applyLayerResponse{size}); err != nil {
		fatal(fmt.Errorf("unable to encode layerSize JSON: %w", err))
	}

	if _, err := flush(os.Stdin); err != nil {
		fatal(err)
	}

	os.Exit(0)
}

// applyLayerHandler parses a diff in the standard layer format from `layer`, and
// applies it to the directory `dest`. Returns the size in bytes of the
// contents of the layer.
func applyLayerHandler(dest string, layer io.Reader, options *archive.TarOptions, decompress bool) (size int64, err error) {
	dest = filepath.Clean(dest)
	if decompress {
		decompressed, err := archive.DecompressStream(layer)
		if err != nil {
			return 0, err
		}
		defer decompressed.Close()

		layer = decompressed
	}
	if options == nil {
		options = &archive.TarOptions{}
		if unshare.IsRootless() {
			options.InUserNS = true
		}
	}
	if options.ExcludePatterns == nil {
		options.ExcludePatterns = []string{}
	}

	data, err := json.Marshal(options)
	if err != nil {
		return 0, fmt.Errorf("ApplyLayer json encode: %w", err)
	}

	cmd := reexec.Command("storage-applyLayer", dest)
	cmd.Stdin = layer
	cmd.Env = append(cmd.Env, fmt.Sprintf("OPT=%s", data))

	outBuf, errBuf := new(bytes.Buffer), new(bytes.Buffer)
	cmd.Stdout, cmd.Stderr = outBuf, errBuf

	if err = cmd.Run(); err != nil {
		return 0, fmt.Errorf("ApplyLayer stdout: %s stderr: %s %w", outBuf, errBuf, err)
	}

	// Stdout should be a valid JSON struct representing an applyLayerResponse.
	response := applyLayerResponse{}
	decoder := json.NewDecoder(outBuf)
	if err = decoder.Decode(&response); err != nil {
		return 0, fmt.Errorf("unable to decode ApplyLayer JSON response: %w", err)
	}

	return response.LayerSize, nil
}
<commit_msg>chrootarchive: propagate env from parent<commit_after>//go:build !windows && !darwin
// +build !windows,!darwin

package chrootarchive

import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"

	"github.com/containers/storage/pkg/archive"
	"github.com/containers/storage/pkg/reexec"
	"github.com/containers/storage/pkg/system"
	"github.com/containers/storage/pkg/unshare"
)

type applyLayerResponse struct {
	LayerSize int64 `json:"layerSize"`
}

// applyLayer is the entry-point for storage-applylayer on re-exec. This is not
// used on Windows as it does not support chroot, hence no point sandboxing
// through chroot and rexec.
func applyLayer() {

	var (
		tmpDir  string
		err     error
		options *archive.TarOptions
	)
	runtime.LockOSThread()
	flag.Parse()

	inUserns := unshare.IsRootless()
	if err := chroot(flag.Arg(0)); err != nil {
		fatal(err)
	}

	// We need to be able to set any perms
	oldmask, err := system.Umask(0)
	defer system.Umask(oldmask)
	if err != nil {
		fatal(err)
	}

	if err := json.Unmarshal([]byte(os.Getenv("OPT")), &options); err != nil {
		fatal(err)
	}

	if inUserns {
		options.InUserNS = true
	}

	if tmpDir, err = ioutil.TempDir("/", "temp-storage-extract"); err != nil {
		fatal(err)
	}

	os.Setenv("TMPDIR", tmpDir)
	size, err := archive.UnpackLayer("/", os.Stdin, options)
	os.RemoveAll(tmpDir)
	if err != nil {
		fatal(err)
	}

	encoder := json.NewEncoder(os.Stdout)
	if err := encoder.Encode(applyLayerResponse{size}); err != nil {
		fatal(fmt.Errorf("unable to encode layerSize JSON: %w", err))
	}

	if _, err := flush(os.Stdin); err != nil {
		fatal(err)
	}

	os.Exit(0)
}

// applyLayerHandler parses a diff in the standard layer format from `layer`, and
// applies it to the directory `dest`. Returns the size in bytes of the
// contents of the layer.
func applyLayerHandler(dest string, layer io.Reader, options *archive.TarOptions, decompress bool) (size int64, err error) {
	dest = filepath.Clean(dest)
	if decompress {
		decompressed, err := archive.DecompressStream(layer)
		if err != nil {
			return 0, err
		}
		defer decompressed.Close()

		layer = decompressed
	}
	if options == nil {
		options = &archive.TarOptions{}
		if unshare.IsRootless() {
			options.InUserNS = true
		}
	}
	if options.ExcludePatterns == nil {
		options.ExcludePatterns = []string{}
	}

	data, err := json.Marshal(options)
	if err != nil {
		return 0, fmt.Errorf("ApplyLayer json encode: %w", err)
	}

	cmd := reexec.Command("storage-applyLayer", dest)
	cmd.Stdin = layer
	cmd.Env = append(os.Environ(), fmt.Sprintf("OPT=%s", data))

	outBuf, errBuf := new(bytes.Buffer), new(bytes.Buffer)
	cmd.Stdout, cmd.Stderr = outBuf, errBuf

	if err = cmd.Run(); err != nil {
		return 0, fmt.Errorf("ApplyLayer stdout: %s stderr: %s %w", outBuf, errBuf, err)
	}

	// Stdout should be a valid JSON struct representing an applyLayerResponse.
	response := applyLayerResponse{}
	decoder := json.NewDecoder(outBuf)
	if err = decoder.Decode(&response); err != nil {
		return 0, fmt.Errorf("unable to decode ApplyLayer JSON response: %w", err)
	}

	return response.LayerSize, nil
}
<|endoftext|>


<commit_before>// Copyright 2018 the Service Broker Project Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tf

import (
	"fmt"

	"code.cloudfoundry.org/lager"
	"github.com/GoogleCloudPlatform/gcp-service-broker/pkg/broker"
	"github.com/GoogleCloudPlatform/gcp-service-broker/pkg/providers/tf/wrapper"
	"github.com/GoogleCloudPlatform/gcp-service-broker/pkg/validation"
	"github.com/GoogleCloudPlatform/gcp-service-broker/pkg/varcontext"
	"github.com/GoogleCloudPlatform/gcp-service-broker/utils"
	"github.com/pivotal-cf/brokerapi"
	"golang.org/x/oauth2/jwt"
)

// TfServiceDefinitionV1 is the first version of user defined services.
type TfServiceDefinitionV1 struct {
	Version           int                         `yaml:"version" validate:"required,eq=1"`
	Name              string                      `yaml:"name" validate:"required"`
	Id                string                      `yaml:"id" validate:"required,uuid"`
	Description       string                      `yaml:"description" validate:"required"`
	DisplayName       string                      `yaml:"display_name" validate:"required"`
	ImageUrl          string                      `yaml:"image_url" validate:"url"`
	DocumentationUrl  string                      `yaml:"documentation_url" validate:"url"`
	SupportUrl        string                      `yaml:"support_url" validate:"url"`
	Tags              []string                    `yaml:"tags,flow"`
	Plans             []TfServiceDefinitionV1Plan `yaml:"plans" validate:"required,dive"`
	ProvisionSettings TfServiceDefinitionV1Action `yaml:"provision" validate:"required,dive"`
	BindSettings      TfServiceDefinitionV1Action `yaml:"bind" validate:"required,dive"`
	Examples          []broker.ServiceExample     `yaml:"examples" validate:"required,dive"`

	// Internal SHOULD be set to true for Google maintained services.
	Internal bool `yaml:"-"`
}

// TfServiceDefinitionV1Plan represents a service plan in a human-friendly format
// that can be converted into an OSB compatible plan.
type TfServiceDefinitionV1Plan struct {
	Name        string            `yaml:"name" validate:"required"`
	Id          string            `yaml:"id" validate:"required,uuid"`
	Description string            `yaml:"description" validate:"required"`
	DisplayName string            `yaml:"display_name" validate:"required"`
	Bullets     []string          `yaml:"bullets,omitempty"`
	Free        bool              `yaml:"free,omitempty"`
	Properties  map[string]string `yaml:"properties" validate:"required"`
}

// Converts this plan definition to a broker.ServicePlan.
func (plan *TfServiceDefinitionV1Plan) ToPlan() broker.ServicePlan {
	masterPlan := brokerapi.ServicePlan{
		ID:          plan.Id,
		Description: plan.Description,
		Name:        plan.Name,
		Free:        brokerapi.FreeValue(plan.Free),
		Metadata: &brokerapi.ServicePlanMetadata{
			Bullets:     plan.Bullets,
			DisplayName: plan.DisplayName,
		},
	}

	return broker.ServicePlan{
		ServicePlan:       masterPlan,
		ServiceProperties: plan.Properties,
	}
}

// TfServiceDefinitionV1Action holds information needed to process user inputs
// for a single provision or bind call.
type TfServiceDefinitionV1Action struct {
	PlanInputs []broker.BrokerVariable      `yaml:"plan_inputs" validate:"dive"`
	UserInputs []broker.BrokerVariable      `yaml:"user_inputs" validate:"dive"`
	Computed   []varcontext.DefaultVariable `yaml:"computed_inputs" validate:"dive"`
	Template   string                       `yaml:"template" validate:"hcl"`
	Outputs    []broker.BrokerVariable      `yaml:"outputs" validate:"dive"`
}

// ValidateTemplateIO makes sure that the inputs supplied by the user are a
// superset of the inputs needed by the Terraform template, and the template
// outputs match the outputs.
func (action *TfServiceDefinitionV1Action) ValidateTemplateIO() error {
	if err := action.validateTemplateInputs(); err != nil {
		return err
	}

	return action.validateTemplateOutputs()
}

// validateTemplateInputs checks that all the inputs of the Terraform template
// are defined by the service.
func (action *TfServiceDefinitionV1Action) validateTemplateInputs() error {
	inputs := utils.NewStringSet()

	for _, in := range action.PlanInputs {
		inputs.Add(in.FieldName)
	}

	for _, in := range action.UserInputs {
		inputs.Add(in.FieldName)
	}

	for _, in := range action.Computed {
		inputs.Add(in.Name)
	}

	tfModule := wrapper.ModuleDefinition{Definition: action.Template}
	tfIn, err := tfModule.Inputs()
	if err != nil {
		return err
	}

	missingFields := utils.NewStringSet(tfIn...).Minus(inputs).ToSlice()
	if len(missingFields) > 0 {
		return fmt.Errorf("The Terraform template requires the fields %v which are missing from the declared inputs.", missingFields)
	}

	return nil
}

// validateTemplateOutputs checks that the Terraform template outputs match
// the names of the defined outputs.
func (action *TfServiceDefinitionV1Action) validateTemplateOutputs() error {
	definedOutputs := utils.NewStringSet()

	for _, in := range action.Outputs {
		definedOutputs.Add(in.FieldName)
	}

	tfModule := wrapper.ModuleDefinition{Definition: action.Template}
	tfOut, err := tfModule.Outputs()
	if err != nil {
		return err
	}

	if !definedOutputs.Equals(utils.NewStringSet(tfOut...)) {
		return fmt.Errorf("The Terraform template outputs %v MUST match the service declared outputs %v.", tfOut, definedOutputs)
	}

	return nil
}

// Validate checks the service definition for semantic errors.
func (tfb *TfServiceDefinitionV1) Validate() error {
	if err := validation.ValidateStruct(tfb); err != nil {
		return err
	}

	if err := tfb.ProvisionSettings.ValidateTemplateIO(); err != nil {
		return err
	}

	return tfb.BindSettings.ValidateTemplateIO()
}

// ToService converts the flat TfServiceDefinitionV1 into a broker.ServiceDefinition
// that the registry can use.
func (tfb *TfServiceDefinitionV1) ToService(executor wrapper.TerraformExecutor) (*broker.ServiceDefinition, error) {
	if err := tfb.Validate(); err != nil {
		return nil, err
	}

	var rawPlans []broker.ServicePlan
	for _, plan := range tfb.Plans {
		rawPlans = append(rawPlans, plan.ToPlan())
	}

	return &broker.ServiceDefinition{
		Id:               tfb.Id,
		Name:             tfb.Name,
		Description:      tfb.Description,
		Bindable:         true,
		PlanUpdateable:   false,
		DisplayName:      tfb.DisplayName,
		DocumentationUrl: tfb.DocumentationUrl,
		SupportUrl:       tfb.SupportUrl,
		ImageUrl:         tfb.ImageUrl,
		Tags:             tfb.Tags,
		Plans:            rawPlans,

		ProvisionInputVariables: tfb.ProvisionSettings.UserInputs,
		ProvisionComputedVariables: append(tfb.ProvisionSettings.Computed, varcontext.DefaultVariable{
			Name:      "tf_id",
			Default:   "tf:${request.instance_id}:",
			Overwrite: true,
		}),
		BindInputVariables: tfb.BindSettings.UserInputs,
		BindComputedVariables: append(tfb.BindSettings.Computed, varcontext.DefaultVariable{
			Name:      "tf_id",
			Default:   "tf:${request.instance_id}:${request.binding_id}",
			Overwrite: true,
		}),
		BindOutputVariables: append(tfb.ProvisionSettings.Outputs, tfb.BindSettings.Outputs...),
		PlanVariables:       append(tfb.ProvisionSettings.PlanInputs, tfb.BindSettings.PlanInputs...),
		Examples:            tfb.Examples,
		ProviderBuilder: func(projectId string, auth *jwt.Config, logger lager.Logger) broker.ServiceProvider {
			jobRunner := NewTfJobRunnerForProject(projectId)
			jobRunner.Executor = executor
			return NewTerraformProvider(jobRunner, logger, constDefn)
		},
	}, nil
}

// generateTfId creates a unique id for a given provision/bind combination that
// will be consistent across calls. This ID will be used in LastOperation polls
// as well as to uniquely identify the workspace.
func generateTfId(instanceId, bindingId string) string {
	return fmt.Sprintf("tf:%s:%s", instanceId, bindingId)
}

// NewExampleTfServiceDefinition creates a new service defintition with sample
// values for the service broker suitable to give a user a template to manually
// edit.
func NewExampleTfServiceDefinition() TfServiceDefinitionV1 {
	return TfServiceDefinitionV1{
		Version:          1,
		Name:             "example-service",
		Id:               "00000000-0000-0000-0000-000000000000",
		Description:      "a longer service description",
		DisplayName:      "Example Service",
		ImageUrl:         "https://example.com/icon.jpg",
		DocumentationUrl: "https://example.com",
		SupportUrl:       "https://example.com/support.html",
		Tags:             []string{"gcp", "example", "service"},
		Plans: []TfServiceDefinitionV1Plan{
			{
				Id:          "00000000-0000-0000-0000-000000000001",
				Name:        "example-email-plan",
				DisplayName: "example.com email builder",
				Description: "Builds emails for example.com.",
				Bullets:     []string{"information point 1", "information point 2", "some caveat here"},
				Free:        false,
				Properties: map[string]string{
					"domain": "example.com",
				},
			},
		},
		ProvisionSettings: TfServiceDefinitionV1Action{
			PlanInputs: []broker.BrokerVariable{
				{
					FieldName: "domain",
					Type:      broker.JsonTypeString,
					Details:   "The domain name",
					Required:  true,
				},
			},
			UserInputs: []broker.BrokerVariable{
				{
					FieldName: "username",
					Type:      broker.JsonTypeString,
					Details:   "The username to create",
					Required:  true,
				},
			},
			Template: `
			variable domain {type = "string"}
			variable username {type = "string"}

			output email {value = "${var.username}@${var.domain}"}
			`,
			Outputs: []broker.BrokerVariable{
				{
					FieldName: "email",
					Type:      broker.JsonTypeString,
					Details:   "The combined email address",
					Required:  true,
				},
			},
		},
		BindSettings: TfServiceDefinitionV1Action{
			Computed: []varcontext.DefaultVariable{
				{Name: "address", Default: `${instance.details["email"]}`, Overwrite: true},
			},
			Template: `
			resource "random_string" "password" {
			  length = 16
			  special = true
			  override_special = "/@\" "
			}

			output uri {value = "smtp://${var.address}:${random_string.password.result}@smtp.mycompany.com"}
			`,
			Outputs: []broker.BrokerVariable{
				{
					FieldName: "uri",
					Type:      broker.JsonTypeString,
					Details:   "The uri to use to connect to this service",
					Required:  true,
				},
			},
		},
		Examples: []broker.ServiceExample{
			{
				Name:            "Example",
				Description:     "Examples are used for documenting your service AND as integration tests.",
				PlanId:          "00000000-0000-0000-0000-000000000001",
				ProvisionParams: map[string]interface{}{"username": "my-account"},
				BindParams:      map[string]interface{}{},
			},
		},
	}
}
<commit_msg>fixed bad merge<commit_after>// Copyright 2018 the Service Broker Project Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tf

import (
	"fmt"

	"code.cloudfoundry.org/lager"
	"github.com/GoogleCloudPlatform/gcp-service-broker/pkg/broker"
	"github.com/GoogleCloudPlatform/gcp-service-broker/pkg/providers/tf/wrapper"
	"github.com/GoogleCloudPlatform/gcp-service-broker/pkg/validation"
	"github.com/GoogleCloudPlatform/gcp-service-broker/pkg/varcontext"
	"github.com/GoogleCloudPlatform/gcp-service-broker/utils"
	"github.com/pivotal-cf/brokerapi"
	"golang.org/x/oauth2/jwt"
)

// TfServiceDefinitionV1 is the first version of user defined services.
type TfServiceDefinitionV1 struct {
	Version           int                         `yaml:"version" validate:"required,eq=1"`
	Name              string                      `yaml:"name" validate:"required"`
	Id                string                      `yaml:"id" validate:"required,uuid"`
	Description       string                      `yaml:"description" validate:"required"`
	DisplayName       string                      `yaml:"display_name" validate:"required"`
	ImageUrl          string                      `yaml:"image_url" validate:"url"`
	DocumentationUrl  string                      `yaml:"documentation_url" validate:"url"`
	SupportUrl        string                      `yaml:"support_url" validate:"url"`
	Tags              []string                    `yaml:"tags,flow"`
	Plans             []TfServiceDefinitionV1Plan `yaml:"plans" validate:"required,dive"`
	ProvisionSettings TfServiceDefinitionV1Action `yaml:"provision" validate:"required,dive"`
	BindSettings      TfServiceDefinitionV1Action `yaml:"bind" validate:"required,dive"`
	Examples          []broker.ServiceExample     `yaml:"examples" validate:"required,dive"`

	// Internal SHOULD be set to true for Google maintained services.
	Internal bool `yaml:"-"`
}

// TfServiceDefinitionV1Plan represents a service plan in a human-friendly format
// that can be converted into an OSB compatible plan.
type TfServiceDefinitionV1Plan struct {
	Name        string            `yaml:"name" validate:"required"`
	Id          string            `yaml:"id" validate:"required,uuid"`
	Description string            `yaml:"description" validate:"required"`
	DisplayName string            `yaml:"display_name" validate:"required"`
	Bullets     []string          `yaml:"bullets,omitempty"`
	Free        bool              `yaml:"free,omitempty"`
	Properties  map[string]string `yaml:"properties" validate:"required"`
}

// Converts this plan definition to a broker.ServicePlan.
func (plan *TfServiceDefinitionV1Plan) ToPlan() broker.ServicePlan {
	masterPlan := brokerapi.ServicePlan{
		ID:          plan.Id,
		Description: plan.Description,
		Name:        plan.Name,
		Free:        brokerapi.FreeValue(plan.Free),
		Metadata: &brokerapi.ServicePlanMetadata{
			Bullets:     plan.Bullets,
			DisplayName: plan.DisplayName,
		},
	}

	return broker.ServicePlan{
		ServicePlan:       masterPlan,
		ServiceProperties: plan.Properties,
	}
}

// TfServiceDefinitionV1Action holds information needed to process user inputs
// for a single provision or bind call.
type TfServiceDefinitionV1Action struct {
	PlanInputs []broker.BrokerVariable      `yaml:"plan_inputs" validate:"dive"`
	UserInputs []broker.BrokerVariable      `yaml:"user_inputs" validate:"dive"`
	Computed   []varcontext.DefaultVariable `yaml:"computed_inputs" validate:"dive"`
	Template   string                       `yaml:"template" validate:"hcl"`
	Outputs    []broker.BrokerVariable      `yaml:"outputs" validate:"dive"`
}

// ValidateTemplateIO makes sure that the inputs supplied by the user are a
// superset of the inputs needed by the Terraform template, and the template
// outputs match the outputs.
func (action *TfServiceDefinitionV1Action) ValidateTemplateIO() error {
	if err := action.validateTemplateInputs(); err != nil {
		return err
	}

	return action.validateTemplateOutputs()
}

// validateTemplateInputs checks that all the inputs of the Terraform template
// are defined by the service.
func (action *TfServiceDefinitionV1Action) validateTemplateInputs() error {
	inputs := utils.NewStringSet()

	for _, in := range action.PlanInputs {
		inputs.Add(in.FieldName)
	}

	for _, in := range action.UserInputs {
		inputs.Add(in.FieldName)
	}

	for _, in := range action.Computed {
		inputs.Add(in.Name)
	}

	tfModule := wrapper.ModuleDefinition{Definition: action.Template}
	tfIn, err := tfModule.Inputs()
	if err != nil {
		return err
	}

	missingFields := utils.NewStringSet(tfIn...).Minus(inputs).ToSlice()
	if len(missingFields) > 0 {
		return fmt.Errorf("The Terraform template requires the fields %v which are missing from the declared inputs.", missingFields)
	}

	return nil
}

// validateTemplateOutputs checks that the Terraform template outputs match
// the names of the defined outputs.
func (action *TfServiceDefinitionV1Action) validateTemplateOutputs() error {
	definedOutputs := utils.NewStringSet()

	for _, in := range action.Outputs {
		definedOutputs.Add(in.FieldName)
	}

	tfModule := wrapper.ModuleDefinition{Definition: action.Template}
	tfOut, err := tfModule.Outputs()
	if err != nil {
		return err
	}

	if !definedOutputs.Equals(utils.NewStringSet(tfOut...)) {
		return fmt.Errorf("The Terraform template outputs %v MUST match the service declared outputs %v.", tfOut, definedOutputs)
	}

	return nil
}

// Validate checks the service definition for semantic errors.
func (tfb *TfServiceDefinitionV1) Validate() error {
	if err := validation.ValidateStruct(tfb); err != nil {
		return err
	}

	if err := tfb.ProvisionSettings.ValidateTemplateIO(); err != nil {
		return err
	}

	return tfb.BindSettings.ValidateTemplateIO()
}

// ToService converts the flat TfServiceDefinitionV1 into a broker.ServiceDefinition
// that the registry can use.
func (tfb *TfServiceDefinitionV1) ToService(executor wrapper.TerraformExecutor) (*broker.ServiceDefinition, error) {
	if err := tfb.Validate(); err != nil {
		return nil, err
	}

	var rawPlans []broker.ServicePlan
	for _, plan := range tfb.Plans {
		rawPlans = append(rawPlans, plan.ToPlan())
	}

	constDefn := *tfb
	return &broker.ServiceDefinition{
		Id:               tfb.Id,
		Name:             tfb.Name,
		Description:      tfb.Description,
		Bindable:         true,
		PlanUpdateable:   false,
		DisplayName:      tfb.DisplayName,
		DocumentationUrl: tfb.DocumentationUrl,
		SupportUrl:       tfb.SupportUrl,
		ImageUrl:         tfb.ImageUrl,
		Tags:             tfb.Tags,
		Plans:            rawPlans,

		ProvisionInputVariables: tfb.ProvisionSettings.UserInputs,
		ProvisionComputedVariables: append(tfb.ProvisionSettings.Computed, varcontext.DefaultVariable{
			Name:      "tf_id",
			Default:   "tf:${request.instance_id}:",
			Overwrite: true,
		}),
		BindInputVariables: tfb.BindSettings.UserInputs,
		BindComputedVariables: append(tfb.BindSettings.Computed, varcontext.DefaultVariable{
			Name:      "tf_id",
			Default:   "tf:${request.instance_id}:${request.binding_id}",
			Overwrite: true,
		}),
		BindOutputVariables: append(tfb.ProvisionSettings.Outputs, tfb.BindSettings.Outputs...),
		PlanVariables:       append(tfb.ProvisionSettings.PlanInputs, tfb.BindSettings.PlanInputs...),
		Examples:            tfb.Examples,
		ProviderBuilder: func(projectId string, auth *jwt.Config, logger lager.Logger) broker.ServiceProvider {
			jobRunner := NewTfJobRunnerForProject(projectId)
			jobRunner.Executor = executor
			return NewTerraformProvider(jobRunner, logger, constDefn)
		},
	}, nil
}

// generateTfId creates a unique id for a given provision/bind combination that
// will be consistent across calls. This ID will be used in LastOperation polls
// as well as to uniquely identify the workspace.
func generateTfId(instanceId, bindingId string) string {
	return fmt.Sprintf("tf:%s:%s", instanceId, bindingId)
}

// NewExampleTfServiceDefinition creates a new service defintition with sample
// values for the service broker suitable to give a user a template to manually
// edit.
func NewExampleTfServiceDefinition() TfServiceDefinitionV1 {
	return TfServiceDefinitionV1{
		Version:          1,
		Name:             "example-service",
		Id:               "00000000-0000-0000-0000-000000000000",
		Description:      "a longer service description",
		DisplayName:      "Example Service",
		ImageUrl:         "https://example.com/icon.jpg",
		DocumentationUrl: "https://example.com",
		SupportUrl:       "https://example.com/support.html",
		Tags:             []string{"gcp", "example", "service"},
		Plans: []TfServiceDefinitionV1Plan{
			{
				Id:          "00000000-0000-0000-0000-000000000001",
				Name:        "example-email-plan",
				DisplayName: "example.com email builder",
				Description: "Builds emails for example.com.",
				Bullets:     []string{"information point 1", "information point 2", "some caveat here"},
				Free:        false,
				Properties: map[string]string{
					"domain": "example.com",
				},
			},
		},
		ProvisionSettings: TfServiceDefinitionV1Action{
			PlanInputs: []broker.BrokerVariable{
				{
					FieldName: "domain",
					Type:      broker.JsonTypeString,
					Details:   "The domain name",
					Required:  true,
				},
			},
			UserInputs: []broker.BrokerVariable{
				{
					FieldName: "username",
					Type:      broker.JsonTypeString,
					Details:   "The username to create",
					Required:  true,
				},
			},
			Template: `
			variable domain {type = "string"}
			variable username {type = "string"}

			output email {value = "${var.username}@${var.domain}"}
			`,
			Outputs: []broker.BrokerVariable{
				{
					FieldName: "email",
					Type:      broker.JsonTypeString,
					Details:   "The combined email address",
					Required:  true,
				},
			},
		},
		BindSettings: TfServiceDefinitionV1Action{
			Computed: []varcontext.DefaultVariable{
				{Name: "address", Default: `${instance.details["email"]}`, Overwrite: true},
			},
			Template: `
			resource "random_string" "password" {
			  length = 16
			  special = true
			  override_special = "/@\" "
			}

			output uri {value = "smtp://${var.address}:${random_string.password.result}@smtp.mycompany.com"}
			`,
			Outputs: []broker.BrokerVariable{
				{
					FieldName: "uri",
					Type:      broker.JsonTypeString,
					Details:   "The uri to use to connect to this service",
					Required:  true,
				},
			},
		},
		Examples: []broker.ServiceExample{
			{
				Name:            "Example",
				Description:     "Examples are used for documenting your service AND as integration tests.",
				PlanId:          "00000000-0000-0000-0000-000000000001",
				ProvisionParams: map[string]interface{}{"username": "my-account"},
				BindParams:      map[string]interface{}{},
			},
		},
	}
}
<|endoftext|>


<commit_before>package router

import (
	"container/list"
	"sync"
	"time"

	"github.com/juju/errors"
	log "github.com/ngaut/logging"
	"github.com/wandoulabs/codis/pkg/proxy/parser"
	"github.com/wandoulabs/codis/pkg/proxy/redisconn"
)

type taskRunner struct {
	evtbus     chan interface{}
	in         chan interface{} //*PipelineRequest
	out        chan interface{}
	redisAddr  string
	tasks      *list.List
	c          *redisconn.Conn
	netTimeout int //second
	closed     bool
	wgClose    *sync.WaitGroup
}

func (tr *taskRunner) readloop() {
	for {
		resp, err := parser.Parse(tr.c.BufioReader())
		if err != nil {
			tr.out <- err
			return
		}

		tr.out <- resp
	}
}

func (tr *taskRunner) dowrite(r *PipelineRequest, flush bool) error {
	b, err := r.req.Bytes()
	if err != nil {
		return errors.Trace(err)
	}

	_, err = tr.c.Write(b)
	if err != nil {
		return errors.Trace(err)
	}

	if flush {
		return errors.Trace(tr.c.Flush())
	}

	return nil
}

func (tr *taskRunner) handleTask(r *PipelineRequest, flush bool) error {
	if r == nil && flush { //just flush
		return tr.c.Flush()
	}

	tr.tasks.PushBack(r)

	return errors.Trace(tr.dowrite(r, flush))
}

func (tr *taskRunner) cleanupQueueTasks() {
	for {
		select {
		case t := <-tr.in:
			tr.processTask(t)
		default:
			return
		}
	}
}

func (tr *taskRunner) cleanupOutgoingTasks(err error) {
	for e := tr.tasks.Front(); e != nil; {
		req := e.Value.(*PipelineRequest)
		log.Info("clean up", req)
		req.backQ <- &PipelineResponse{ctx: req, resp: nil, err: err}
		next := e.Next()
		tr.tasks.Remove(e)
		e = next
	}
}

func (tr *taskRunner) tryRecover(err error) error {
	log.Warning("try recover from ", err)
	tr.cleanupOutgoingTasks(err)
	//try to recover
	c, err := redisconn.NewConnection(tr.redisAddr, tr.netTimeout)
	if err != nil {
		tr.cleanupQueueTasks() //do not block dispatcher
		log.Warning(err)
		time.Sleep(1 * time.Second)
		return err
	}

	tr.c = c
	go tr.readloop()

	return nil
}

func (tr *taskRunner) getOutgoingResponse() {
	for {
		if tr.tasks.Len() == 0 {
			return
		}

		resp := <-tr.out
		err := tr.handleResponse(resp)
		if err != nil {
			tr.cleanupOutgoingTasks(err)
		}
	}
}

func (tr *taskRunner) processTask(t interface{}) {
	var err error
	switch t.(type) {
	case *PipelineRequest:
		r := t.(*PipelineRequest)
		var flush bool
		if len(tr.in) == 0 { //force flush
			flush = true
		}

		err = tr.handleTask(r, flush)
	case *sync.WaitGroup: //close taskrunner
		err = tr.handleTask(nil, true) //flush
		//get all response for out going request
		tr.getOutgoingResponse()
		tr.closed = true
		tr.wgClose = t.(*sync.WaitGroup)
	}

	if err != nil {
		log.Warning(err)
		tr.c.Close()
	}
}

func (tr *taskRunner) handleResponse(e interface{}) error {
	switch e.(type) {
	case error:
		return e.(error)
	case *parser.Resp:
		resp := e.(*parser.Resp)
		e := tr.tasks.Front()
		req := e.Value.(*PipelineRequest)
		req.backQ <- &PipelineResponse{ctx: req, resp: resp, err: nil}
		tr.tasks.Remove(e)
		return nil
	}

	return nil
}

func (tr *taskRunner) writeloop() {
	var err error
	for {
		if tr.closed && tr.tasks.Len() == 0 {
			log.Warning("exit taskrunner", tr.redisAddr)
			tr.wgClose.Done()
			tr.c.Close()
			return
		}

		if err != nil { //clean up
			err = tr.tryRecover(err)
			if err != nil {
				continue
			}
		}

		select {
		case t := <-tr.in:
			tr.processTask(t)
		case resp := <-tr.out:
			err = tr.handleResponse(resp)
		}
	}
}

func NewTaskRunner(addr string, netTimeout int) (*taskRunner, error) {
	tr := &taskRunner{
		in:         make(chan interface{}, 1000),
		out:        make(chan interface{}, 1000),
		redisAddr:  addr,
		tasks:      list.New(),
		netTimeout: netTimeout,
	}

	c, err := redisconn.NewConnection(addr, netTimeout)
	if err != nil {
		return nil, err
	}

	tr.c = c

	go tr.writeloop()
	go tr.readloop()

	return tr, nil
}
<commit_msg>fix block issue when network down<commit_after>package router

import (
	"container/list"
	"sync"
	"time"

	"github.com/juju/errors"
	log "github.com/ngaut/logging"
	"github.com/wandoulabs/codis/pkg/proxy/parser"
	"github.com/wandoulabs/codis/pkg/proxy/redisconn"
)

type taskRunner struct {
	evtbus     chan interface{}
	in         chan interface{} //*PipelineRequest
	out        chan interface{}
	redisAddr  string
	tasks      *list.List
	c          *redisconn.Conn
	netTimeout int //second
	closed     bool
	wgClose    *sync.WaitGroup
	latest     time.Time //latest request time stamp
}

func (tr *taskRunner) readloop() {
	for {
		resp, err := parser.Parse(tr.c.BufioReader())
		if err != nil {
			tr.out <- err
			return
		}

		tr.out <- resp
	}
}

func (tr *taskRunner) dowrite(r *PipelineRequest, flush bool) error {
	b, err := r.req.Bytes()
	if err != nil {
		return errors.Trace(err)
	}

	_, err = tr.c.Write(b)
	if err != nil {
		return errors.Trace(err)
	}

	if flush {
		return errors.Trace(tr.c.Flush())
	}

	return nil
}

func (tr *taskRunner) handleTask(r *PipelineRequest, flush bool) error {
	if r == nil && flush { //just flush
		return tr.c.Flush()
	}

	tr.tasks.PushBack(r)
	tr.latest = time.Now()

	return errors.Trace(tr.dowrite(r, flush))
}

func (tr *taskRunner) cleanupQueueTasks() {
	for {
		select {
		case t := <-tr.in:
			tr.processTask(t)
		default:
			return
		}
	}
}

func (tr *taskRunner) cleanupOutgoingTasks(err error) {
	for e := tr.tasks.Front(); e != nil; {
		req := e.Value.(*PipelineRequest)
		log.Info("clean up", req)
		req.backQ <- &PipelineResponse{ctx: req, resp: nil, err: err}
		next := e.Next()
		tr.tasks.Remove(e)
		e = next
	}
}

func (tr *taskRunner) tryRecover(err error) error {
	log.Warning("try recover from ", err)
	tr.cleanupOutgoingTasks(err)
	//try to recover
	c, err := redisconn.NewConnection(tr.redisAddr, tr.netTimeout)
	if err != nil {
		tr.cleanupQueueTasks() //do not block dispatcher
		log.Warning(err)
		time.Sleep(1 * time.Second)
		return err
	}

	tr.c = c
	go tr.readloop()

	return nil
}

func (tr *taskRunner) getOutgoingResponse() {
	for {
		if tr.tasks.Len() == 0 {
			return
		}

		resp := <-tr.out
		err := tr.handleResponse(resp)
		if err != nil {
			tr.cleanupOutgoingTasks(err)
		}
	}
}

func (tr *taskRunner) processTask(t interface{}) {
	var err error
	switch t.(type) {
	case *PipelineRequest:
		r := t.(*PipelineRequest)
		var flush bool
		if len(tr.in) == 0 { //force flush
			flush = true
		}

		err = tr.handleTask(r, flush)
	case *sync.WaitGroup: //close taskrunner
		err = tr.handleTask(nil, true) //flush
		//get all response for out going request
		tr.getOutgoingResponse()
		tr.closed = true
		tr.wgClose = t.(*sync.WaitGroup)
	}

	if err != nil {
		log.Warning(err)
		tr.c.Close()
	}
}

func (tr *taskRunner) handleResponse(e interface{}) error {
	switch e.(type) {
	case error:
		return e.(error)
	case *parser.Resp:
		resp := e.(*parser.Resp)
		e := tr.tasks.Front()
		req := e.Value.(*PipelineRequest)
		req.backQ <- &PipelineResponse{ctx: req, resp: resp, err: nil}
		tr.tasks.Remove(e)
		return nil
	}

	return nil
}

func (tr *taskRunner) writeloop() {
	var err error
	for {
		if tr.closed && tr.tasks.Len() == 0 {
			log.Warning("exit taskrunner", tr.redisAddr)
			tr.wgClose.Done()
			tr.c.Close()
			return
		}

		if err != nil { //clean up
			err = tr.tryRecover(err)
			if err != nil {
				continue
			}
		}

		select {
		case t := <-tr.in:
			tr.processTask(t)
		case resp := <-tr.out:
			err = tr.handleResponse(resp)
		case <-time.After(1 * time.Second):
			if tr.tasks.Len() > 0 && int(time.Since(tr.latest).Seconds()) > tr.netTimeout {
				tr.c.Close()
			}
		}
	}
}

func NewTaskRunner(addr string, netTimeout int) (*taskRunner, error) {
	tr := &taskRunner{
		in:         make(chan interface{}, 1000),
		out:        make(chan interface{}, 1000),
		redisAddr:  addr,
		tasks:      list.New(),
		netTimeout: netTimeout,
	}

	c, err := redisconn.NewConnection(addr, netTimeout)
	if err != nil {
		return nil, err
	}

	tr.c = c

	go tr.writeloop()
	go tr.readloop()

	return tr, nil
}
<|endoftext|>