<commit_before>package main

import (
	"fmt"
	"log"
	"strings"
	"sync"
)

// Use Pointer when we need to update the struct with receiver
// https://golang.org/doc/faq#methods_on_values_or_pointers

// Work Flow
// 1. Create a graph `Data`.
// 2. Create a `Vertex`.
// 3. Add a `Vertex` to a graph Data.
// 4. Connect with an Edge with `AddEdge`

// Data contains graph data, represented in adjacency list and slice.
type Data struct {
	Vertices []*Vertex

	sync.Mutex

	// OutEdges maps each Vertex to its outgoing edges
	OutEdges map[*Vertex][]Edge

	// InEdges maps each Vertex to its incoming edges
	InEdges map[*Vertex][]Edge

	// to prevent duplicating vertex IDs
	vertexIDs map[string]bool
}

// Vertex is a vertex(node) in Graph.
type Vertex struct {
	// ID of Vertex is assumed to be unique between vertices.
	ID string

	// Color is used for graph traversal.
	Color string

	sync.Mutex

	// Stamp stores stamp records for several graph algorithms.
	Stamp map[string]float64
}

// Edge is an edge(arc) in a graph that has direction from one to another vertex.
type Edge struct {
	// Vtx can be either source or destination
	Vtx *Vertex

	// Weight contains the weight value in float64.
	// Note that `Weight` is a single floating value.
	// Define with []float64 if we want duplicate edge values.
	Weight float64
}

// NewData returns a new Data.
func NewData() *Data {
	return &Data{
		Vertices: []*Vertex{},
		OutEdges: make(map[*Vertex][]Edge),
		InEdges:  make(map[*Vertex][]Edge),
	}
}

// NewVertex returns a new Vertex.
func NewVertex(id string) *Vertex {
	return &Vertex{
		ID:    id,
		Color: "",
		Stamp: make(map[string]float64),
	}
}

// AddVertex adds a vertex to a graph Data.
func (d *Data) AddVertex(vtx *Vertex) (bool, error) {
	if _, ok := vertexIDs[vtx.ID]; ok {
		return false, fmt.Errorf("`%s` already exists", vtx.ID)
	}
	d.Mutex.Lock()
	d.vertexIDs[vtx.ID] = true
	d.Mutex.Unlock()
	d.Vertices = append(d.Vertices, vtx)
	return true, nil
}

// Connect adds an edge from src to dst Vertex, to a graph Data.
func (d *Data) Connect(src, dst *Vertex, weight float64) {
	added, _ := d.AddVertex(src)
	if added {
		log.Printf("`%s` was previously added to Data\n", src.ID)
	} else {
		log.Printf("`%s` is added to Data\n", dst.ID)
	}
	added, _ = d.AddVertex(dst)
	if added {
		log.Printf("`%s` was previously added to Data\n", dst.ID)
	} else {
		log.Printf("`%s` is added to Data\n", dst.ID)
	}
	edgeSrc := Edge{
		Vtx:    src,
		Weight: weight,
	}
	edgeDst := Edge{
		Vtx:    dst,
		Weight: weight,
	}
	d.Mutex.Lock()
	if _, ok := d.OutEdges[src]; !ok {
		d.OutEdges[src] = []Edge{edgeDst}
	} else {
		// if OutEdges already exists
		duplicate := false
		for _, elem := range d.OutEdges[src] {
			// if there is a duplicate(parallel) edge
			if elem.Vtx == src {
				log.Println("Duplicate(Parallel) Edge Found. Overwriting the Weight value.")
				log.Printf("%v --> %v + %v\n", elem.Weight, elem.Weight, weight)
				elem.Weight += weight
				duplicate = true
				break
			}
		}
		// if this is just another edge from `src` Vertex
		if !duplicate {
			d.OutEdges[src] = append(d.OutEdges[src], edgeDst)
		}
	}
	if _, ok := d.InEdges[dst]; !ok {
		d.InEdges[dst] = []Edge{edgeSrc}
	} else {
		// if InEdges already exists
		duplicate := false
		for _, elem := range d.InEdges[dst] {
			// if there is a duplicate(parallel) edge
			if elem.Vtx == dst {
				log.Println("Duplicate(Parallel) Edge Found. Overwriting the Weight value.")
				log.Printf("%v --> %v + %v\n", elem.Weight, elem.Weight, weight)
				elem.Weight += weight
				duplicate = true
				break
			}
		}
		// if this is just another edge to `dst` Vertex
		if !duplicate {
			d.InEdges[dst] = append(d.InEdges[dst], edgeSrc)
		}
	}
	d.Mutex.Unlock()
}

// Init initializes the graph Data.
func (d *Data) Init() {
	// (X) d = NewData()
	// this only updates the pointer
	//
	// Do this.
	*d = *NewData()
}

// GetVertexSize returns the size of Vertex of the graph Data.
func (d Data) GetVertexSize() int64 {
	return int64(len(d.Vertices))
}

// String describes the graph Data.
func (d Data) String() string {
	if d.GetVertexSize() == 0 {
		return "Graph is empty."
	}
	slice := []string{}
	for _, vtx := range d.Vertices {
		slice = append(slice, fmt.Sprintf("Vertex: %s", vtx.ID))
		d.Mutex.Lock()
		if _, ok := d.OutEdges[vtx]; !ok {
			slice = append(slice, fmt.Sprintf("No Outgoing Edge from %s", vtx.ID))
		} else {
			for _, edge := range d.OutEdges[vtx] {
				slice = append(slice, fmt.Sprintf("Outgoing Edges: [%s] -- %f --> [%s]\n", edge.Vtx.ID, edge.Weight, vtx.ID))
			}
		}
		if _, ok := d.InEdges[vtx]; !ok {
			slice = append(slice, fmt.Sprintf("No Incoming Edge from %s", vtx.ID))
		} else {
			for _, edge := range d.InEdges[vtx] {
				slice = append(slice, fmt.Sprintf("Incoming Edges: [%s] -- %f --> [%s]\n", edge.Vtx.ID, edge.Weight, vtx.ID))
			}
		}
		slice = append(slice, "\n")
		d.Mutex.Unlock()
	}
	return strings.Join(slice, "\n")
}

// FindVertexByID finds a Vertex by ID.
func (d Data) FindVertexByID(id string) *Vertex {
	for _, vtx := range d.Vertices {
		if vtx.ID == id {
			return vtx
		}
	}
}

// DeleteVertex deletes a Vertex from the graph Data.
func (d *Data) DeleteVertex(vtx *Vertex) {
	for idx, elem := range d.Vertices {
		if elem == vtx {
			copy(d.Vertices[idx:], d.Vertices[idx+1:])
			d.Vertices[len(d.Vertices)-1] = nil // zero value of type or nil
			d.Vertices = d.Vertices[:len(d.Vertices)-1 : len(d.Vertices)-1]
			break
		}
	}
	d.Mutex.Lock()
	delete(d.OutEdges, vtx)
	delete(d.InEdges, vtx)
	d.Mutex.Unlock()
}

// DeleteEdge deletes an Edge from src to dst from the graph Data.
func (d *Data) DeleteEdge(src, dst *Vertex) {

}

// Clone clones the graph Data.
// It does `Deep Copy`.
// That is, changing the cloned Data would not affect the original Data.
func (d *Data) Clone() *Data {

}
<commit_msg>Update<commit_after>package main

import (
	"fmt"
	"log"
	"strings"
	"sync"
)

// Use Pointer when we need to update the struct with receiver
// https://golang.org/doc/faq#methods_on_values_or_pointers

// Work Flow
// 1. Create a graph `Data`.
// 2. Create a `Vertex`.
// 3. Add a `Vertex` to a graph Data.
// 4. Connect with an Edge with `AddEdge`

// Data contains graph data, represented in adjacency list and slice.
type Data struct {
	Vertices []*Vertex

	sync.Mutex

	// OutEdges maps each Vertex to its outgoing edges
	OutEdges map[*Vertex][]Edge

	// InEdges maps each Vertex to its incoming edges
	InEdges map[*Vertex][]Edge

	// to prevent duplicating vertex IDs
	vertexIDs map[string]bool
}

// Vertex is a vertex(node) in Graph.
type Vertex struct {
	// ID of Vertex is assumed to be unique between vertices.
	ID string

	// Color is used for graph traversal.
	Color string

	sync.Mutex

	// Stamp stores stamp records for several graph algorithms.
	Stamp map[string]float64
}

// Edge is an edge(arc) in a graph that has direction from one to another vertex.
type Edge struct {
	// Vtx can be either source or destination
	Vtx *Vertex

	// Weight contains the weight value in float64.
	// Note that `Weight` is a single floating value.
	// Define with []float64 if we want duplicate edge values.
	Weight float64
}

// NewData returns a new Data.
func NewData() *Data {
	return &Data{
		Vertices: []*Vertex{},
		OutEdges: make(map[*Vertex][]Edge),
		InEdges:  make(map[*Vertex][]Edge),
	}
}

// NewVertex returns a new Vertex.
func NewVertex(id string) *Vertex {
	return &Vertex{
		ID:    id,
		Color: "",
		Stamp: make(map[string]float64),
	}
}

// AddVertex adds a vertex to a graph Data.
func (d *Data) AddVertex(vtx *Vertex) (bool, error) {
	if _, ok := vertexIDs[vtx.ID]; ok {
		return false, fmt.Errorf("`%s` already exists", vtx.ID)
	}
	d.Mutex.Lock()
	d.vertexIDs[vtx.ID] = true
	d.Mutex.Unlock()
	d.Vertices = append(d.Vertices, vtx)
	return true, nil
}

// Connect adds an edge from src to dst Vertex, to a graph Data.
func (d *Data) Connect(src, dst *Vertex, weight float64) {
	added, _ := d.AddVertex(src)
	if added {
		log.Printf("`%s` was previously added to Data\n", src.ID)
	} else {
		log.Printf("`%s` is added to Data\n", dst.ID)
	}
	added, _ = d.AddVertex(dst)
	if added {
		log.Printf("`%s` was previously added to Data\n", dst.ID)
	} else {
		log.Printf("`%s` is added to Data\n", dst.ID)
	}
	edgeSrc := Edge{
		Vtx:    src,
		Weight: weight,
	}
	edgeDst := Edge{
		Vtx:    dst,
		Weight: weight,
	}
	d.Mutex.Lock()
	if _, ok := d.OutEdges[src]; !ok {
		d.OutEdges[src] = []Edge{edgeDst}
	} else {
		// if OutEdges already exists
		duplicate := false
		for _, elem := range d.OutEdges[src] {
			// if there is a duplicate(parallel) edge
			if elem.Vtx == src {
				log.Println("Duplicate(Parallel) Edge Found. Overwriting the Weight value.")
				log.Printf("%v --> %v + %v\n", elem.Weight, elem.Weight, weight)
				elem.Weight += weight
				duplicate = true
				break
			}
		}
		// if this is just another edge from `src` Vertex
		if !duplicate {
			d.OutEdges[src] = append(d.OutEdges[src], edgeDst)
		}
	}
	if _, ok := d.InEdges[dst]; !ok {
		d.InEdges[dst] = []Edge{edgeSrc}
	} else {
		// if InEdges already exists
		duplicate := false
		for _, elem := range d.InEdges[dst] {
			// if there is a duplicate(parallel) edge
			if elem.Vtx == dst {
				log.Println("Duplicate(Parallel) Edge Found. Overwriting the Weight value.")
				log.Printf("%v --> %v + %v\n", elem.Weight, elem.Weight, weight)
				elem.Weight += weight
				duplicate = true
				break
			}
		}
		// if this is just another edge to `dst` Vertex
		if !duplicate {
			d.InEdges[dst] = append(d.InEdges[dst], edgeSrc)
		}
	}
	d.Mutex.Unlock()
}

// Init initializes the graph Data.
func (d *Data) Init() {
	// (X) d = NewData()
	// this only updates the pointer
	//
	// Do this.
	*d = *NewData()
}

// GetVertexSize returns the size of Vertex of the graph Data.
func (d Data) GetVertexSize() int64 {
	return int64(len(d.Vertices))
}

// String describes the graph Data.
func (d Data) String() string {
	if d.GetVertexSize() == 0 {
		return "Graph is empty."
	}
	slice := []string{}
	for _, vtx := range d.Vertices {
		slice = append(slice, fmt.Sprintf("Vertex: %s", vtx.ID))
		d.Mutex.Lock()
		if _, ok := d.OutEdges[vtx]; !ok {
			slice = append(slice, fmt.Sprintf("No Outgoing Edge from %s", vtx.ID))
		} else {
			for _, edge := range d.OutEdges[vtx] {
				slice = append(slice, fmt.Sprintf("Outgoing Edges: [%s] -- %f --> [%s]\n", edge.Vtx.ID, edge.Weight, vtx.ID))
			}
		}
		if _, ok := d.InEdges[vtx]; !ok {
			slice = append(slice, fmt.Sprintf("No Incoming Edge from %s", vtx.ID))
		} else {
			for _, edge := range d.InEdges[vtx] {
				slice = append(slice, fmt.Sprintf("Incoming Edges: [%s] -- %f --> [%s]\n", edge.Vtx.ID, edge.Weight, vtx.ID))
			}
		}
		slice = append(slice, "\n")
		d.Mutex.Unlock()
	}
	return strings.Join(slice, "\n")
}

// FindVertexByID finds a Vertex by ID.
func (d Data) FindVertexByID(id string) *Vertex {
	for _, vtx := range d.Vertices {
		if vtx.ID == id {
			return vtx
		}
	}
}

// DeleteVertex deletes a Vertex from the graph Data.
func (d *Data) DeleteVertex(vtx *Vertex) {
	// delete from d.Vertices
	for idx, elem := range d.Vertices {
		if elem == vtx {
			copy(d.Vertices[idx:], d.Vertices[idx+1:])
			d.Vertices[len(d.Vertices)-1] = nil // zero value of type or nil
			d.Vertices = d.Vertices[:len(d.Vertices)-1 : len(d.Vertices)-1]
			break
		}
	}
	// delete from maps
	d.Mutex.Lock()
	delete(d.OutEdges, vtx)
	delete(d.InEdges, vtx)
	delete(d.vertexIDs, vtx.ID)
	d.Mutex.Unlock()
}

// DeleteEdge deletes an Edge from src to dst from the graph Data.
func (d *Data) DeleteEdge(src, dst *Vertex) {
	// delete an edge from OutEdges
	for idx, edge := range d.OutEdges[src] {
		if edge.Vtx == dst {
			copy(d.OutEdges[src][idx:], d.OutEdges[src][idx+1:])
			d.OutEdges[src][len(d.OutEdges[src])-1] = nil // zero value of type or nil
			d.OutEdges[src] = d.OutEdges[src][:len(d.OutEdges[src])-1 : len(d.OutEdges[src])-1]
			break
		}
	}
	// delete an edge from InEdges
	for idx, edge := range d.InEdges[dst] {
		if edge.Vtx == src {
			copy(d.InEdges[src][idx:], d.InEdges[src][idx+1:])
			d.InEdges[src][len(d.InEdges[src])-1] = nil // zero value of type or nil
			d.InEdges[src] = d.InEdges[src][:len(d.InEdges[src])-1 : len(d.InEdges[src])-1]
			break
		}
	}
}

// Clone clones the graph Data.
// It does `Deep Copy`.
// That is, changing the cloned Data would not affect the original Data.
func (d *Data) Clone() *Data {

}
<|endoftext|>


<commit_before>// Copyright 2019 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 locality

import (
	"bytes"
	"fmt"
	"math/rand"
	"net/http"
	"regexp"
	"testing"
	"text/template"
	"time"

	envoyAdmin "github.com/envoyproxy/go-control-plane/envoy/admin/v2alpha"

	"istio.io/istio/pkg/config/protocol"
	"istio.io/istio/pkg/test"
	"istio.io/istio/pkg/test/framework"
	"istio.io/istio/pkg/test/framework/components/echo"
	"istio.io/istio/pkg/test/framework/components/environment"
	"istio.io/istio/pkg/test/framework/components/galley"
	"istio.io/istio/pkg/test/framework/components/istio"
	"istio.io/istio/pkg/test/framework/components/namespace"
	"istio.io/istio/pkg/test/framework/components/pilot"
	"istio.io/istio/pkg/test/framework/label"
	"istio.io/istio/pkg/test/framework/resource"
	"istio.io/istio/pkg/test/util/retry"
	"istio.io/istio/pkg/test/util/structpath"
)

const (
	sendCount = 100

	deploymentYAML = `
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: {{.Name}}
  namespace: {{.Namespace}}
spec:
  hosts:
  - {{.Host}}
  exportTo:
  - "."
  ports:
  - number: 80
    name: http
    protocol: HTTP
  resolution: {{.Resolution}}
  location: MESH_EXTERNAL
  endpoints:
  {{ if ne .NonExistantService "" }}
  - address: {{.NonExistantService}}
    locality: {{.NonExistantServiceLocality}}
  {{ end }}
  - address: {{.ServiceBAddress}}
    locality: {{.ServiceBLocality}}
  - address: {{.ServiceCAddress}}
    locality: {{.ServiceCLocality}}
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: {{.Name}}-route
  namespace: {{.Namespace}}
spec:
  hosts:
  - {{.Host}}
  http:
  - route:
    - destination:
        host: {{.Host}}
    retries:
      attempts: 3
      perTryTimeout: 1s
      retryOn: gateway-error,connect-failure,refused-stream
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: {{.Name}}-destination
  namespace: {{.Namespace}}
spec:
  host: {{.Host}}
  trafficPolicy:
    outlierDetection:
      consecutiveErrors: 100
      interval: 1s
      baseEjectionTime: 3m
      maxEjectionPercent: 100
`
)

var (
	bHostnameMatcher   = regexp.MustCompile("^b-.*$")
	deploymentTemplate *template.Template

	ist istio.Instance
	p   pilot.Instance
	g   galley.Instance
	r   *rand.Rand
)

func init() {
	var err error
	deploymentTemplate, err = template.New("localityTemplate").Parse(deploymentYAML)
	if err != nil {
		panic(err)
	}
}

func TestMain(m *testing.M) {
	framework.NewSuite("locality_prioritized_failover_loadbalancing", m).
		// TODO(https://github.com/istio/istio/issues/13812) remove flaky labels
		Label(label.CustomSetup, label.Flaky).
		SetupOnEnv(environment.Kube, istio.Setup(&ist, setupConfig)).
		Setup(func(ctx resource.Context) (err error) {
			if g, err = galley.New(ctx, galley.Config{}); err != nil {
				return err
			}
			if p, err = pilot.New(ctx, pilot.Config{Galley: g}); err != nil {
				return err
			}
			r = rand.New(rand.NewSource(time.Now().UnixNano()))
			return nil
		}).
		Run()
}

func setupConfig(cfg *istio.Config) {
	if cfg == nil {
		return
	}
	cfg.Values["pilot.autoscaleEnabled"] = "false"
	cfg.Values["global.localityLbSetting.failover[0].from"] = "region"
	cfg.Values["global.localityLbSetting.failover[0].to"] = "closeregion"
}

func echoConfig(ns namespace.Instance, name string) echo.Config {
	return echo.Config{
		Service:   name,
		Namespace: ns,
		Locality:  "region.zone.subzone",
		Ports: []echo.Port{
			{
				Name:        "http",
				Protocol:    protocol.HTTP,
				ServicePort: 80,
			},
		},
		Galley: g,
		Pilot:  p,
	}
}

type serviceConfig struct {
	Name                       string
	Host                       string
	Namespace                  string
	Resolution                 string
	ServiceBAddress            string
	ServiceBLocality           string
	ServiceCAddress            string
	ServiceCLocality           string
	NonExistantService         string
	NonExistantServiceLocality string
}

func deploy(t test.Failer, ns namespace.Instance, se serviceConfig, from echo.Instance) {
	t.Helper()
	var buf bytes.Buffer
	if err := deploymentTemplate.Execute(&buf, se); err != nil {
		t.Fatal(err)
	}
	g.ApplyConfigOrFail(t, ns, buf.String())

	err := WaitUntilRoute(from, se.Host)
	if err != nil {
		t.Fatalf("Failed to get expected route: %v", err)
	}
}

// Wait for our route for the "fake" target to be established
func WaitUntilRoute(c echo.Instance, dest string) error {
	accept := func(cfg *envoyAdmin.ConfigDump) (bool, error) {
		validator := structpath.ForProto(cfg)
		if err := validator.
			Exists("{.configs[*].dynamicRouteConfigs[*].routeConfig.virtualHosts[?(@.name == '%s')]}", dest+":80").
			Check(); err != nil {
			return false, err
		}
		clusterName := fmt.Sprintf("outbound|%d||%s", 80, dest)
		if err := validator.
			Exists("{.configs[*].dynamicActiveClusters[?(@.cluster.name == '%s')]}", clusterName).
			Check(); err != nil {
			return false, err
		}
		return true, nil
	}

	workloads, _ := c.Workloads()
	// Wait for the outbound config to be received by each workload from Pilot.
	for _, w := range workloads {
		if w.Sidecar() != nil {
			if err := w.Sidecar().WaitForConfig(accept, retry.Timeout(time.Second*10)); err != nil {
				return err
			}
		}
	}

	return nil
}

func sendTraffic(from echo.Instance, host string) error {
	headers := http.Header{}
	headers.Add("Host", host)
	// This is a hack to remain infrastructure agnostic when running these tests
	// We actually call the host set above not the endpoint we pass
	resp, err := from.Call(echo.CallOptions{
		Target:   from,
		PortName: "http",
		Headers:  headers,
		Count:    sendCount,
	})
	if err != nil {
		return fmt.Errorf("%s->%s failed sending: %v", from.Config().Service, host, err)
	}
	if len(resp) != sendCount {
		return fmt.Errorf("%s->%s expected %d responses, received %d", from.Config().Service, host, sendCount, len(resp))
	}
	numFailed := 0
	for i, r := range resp {
		if match := bHostnameMatcher.FindString(r.Hostname); len(match) == 0 {
			numFailed++
			return fmt.Errorf("%s->%s request[%d] made to unexpected service: %s", from.Config().Service, host, i, r.Hostname)
		}
	}
	if numFailed > 0 {
		return fmt.Errorf("%s->%s total requests to unexpected service=%d/%d", from.Config().Service, host, numFailed, len(resp))
	}
	return nil
}
<commit_msg>Enable locality lb tests in presubmit (#16380)<commit_after>// Copyright 2019 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 locality

import (
	"bytes"
	"fmt"
	"math/rand"
	"net/http"
	"regexp"
	"testing"
	"text/template"
	"time"

	envoyAdmin "github.com/envoyproxy/go-control-plane/envoy/admin/v2alpha"

	"istio.io/istio/pkg/config/protocol"
	"istio.io/istio/pkg/test"
	"istio.io/istio/pkg/test/framework"
	"istio.io/istio/pkg/test/framework/components/echo"
	"istio.io/istio/pkg/test/framework/components/environment"
	"istio.io/istio/pkg/test/framework/components/galley"
	"istio.io/istio/pkg/test/framework/components/istio"
	"istio.io/istio/pkg/test/framework/components/namespace"
	"istio.io/istio/pkg/test/framework/components/pilot"
	"istio.io/istio/pkg/test/framework/label"
	"istio.io/istio/pkg/test/framework/resource"
	"istio.io/istio/pkg/test/util/retry"
	"istio.io/istio/pkg/test/util/structpath"
)

const (
	sendCount = 100

	deploymentYAML = `
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: {{.Name}}
  namespace: {{.Namespace}}
spec:
  hosts:
  - {{.Host}}
  exportTo:
  - "."
  ports:
  - number: 80
    name: http
    protocol: HTTP
  resolution: {{.Resolution}}
  location: MESH_EXTERNAL
  endpoints:
  {{ if ne .NonExistantService "" }}
  - address: {{.NonExistantService}}
    locality: {{.NonExistantServiceLocality}}
  {{ end }}
  - address: {{.ServiceBAddress}}
    locality: {{.ServiceBLocality}}
  - address: {{.ServiceCAddress}}
    locality: {{.ServiceCLocality}}
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: {{.Name}}-route
  namespace: {{.Namespace}}
spec:
  hosts:
  - {{.Host}}
  http:
  - route:
    - destination:
        host: {{.Host}}
    retries:
      attempts: 3
      perTryTimeout: 1s
      retryOn: gateway-error,connect-failure,refused-stream
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: {{.Name}}-destination
  namespace: {{.Namespace}}
spec:
  host: {{.Host}}
  trafficPolicy:
    outlierDetection:
      consecutiveErrors: 100
      interval: 1s
      baseEjectionTime: 3m
      maxEjectionPercent: 100
`
)

var (
	bHostnameMatcher   = regexp.MustCompile("^b-.*$")
	deploymentTemplate *template.Template

	ist istio.Instance
	p   pilot.Instance
	g   galley.Instance
	r   *rand.Rand
)

func init() {
	var err error
	deploymentTemplate, err = template.New("localityTemplate").Parse(deploymentYAML)
	if err != nil {
		panic(err)
	}
}

func TestMain(m *testing.M) {
	framework.NewSuite("locality_prioritized_failover_loadbalancing", m).
		Label(label.CustomSetup).
		SetupOnEnv(environment.Kube, istio.Setup(&ist, setupConfig)).
		Setup(func(ctx resource.Context) (err error) {
			if g, err = galley.New(ctx, galley.Config{}); err != nil {
				return err
			}
			if p, err = pilot.New(ctx, pilot.Config{Galley: g}); err != nil {
				return err
			}
			r = rand.New(rand.NewSource(time.Now().UnixNano()))
			return nil
		}).
		Run()
}

func setupConfig(cfg *istio.Config) {
	if cfg == nil {
		return
	}
	cfg.Values["pilot.autoscaleEnabled"] = "false"
	cfg.Values["global.localityLbSetting.failover[0].from"] = "region"
	cfg.Values["global.localityLbSetting.failover[0].to"] = "closeregion"
}

func echoConfig(ns namespace.Instance, name string) echo.Config {
	return echo.Config{
		Service:   name,
		Namespace: ns,
		Locality:  "region.zone.subzone",
		Ports: []echo.Port{
			{
				Name:        "http",
				Protocol:    protocol.HTTP,
				ServicePort: 80,
			},
		},
		Galley: g,
		Pilot:  p,
	}
}

type serviceConfig struct {
	Name                       string
	Host                       string
	Namespace                  string
	Resolution                 string
	ServiceBAddress            string
	ServiceBLocality           string
	ServiceCAddress            string
	ServiceCLocality           string
	NonExistantService         string
	NonExistantServiceLocality string
}

func deploy(t test.Failer, ns namespace.Instance, se serviceConfig, from echo.Instance) {
	t.Helper()
	var buf bytes.Buffer
	if err := deploymentTemplate.Execute(&buf, se); err != nil {
		t.Fatal(err)
	}
	g.ApplyConfigOrFail(t, ns, buf.String())

	err := WaitUntilRoute(from, se.Host)
	if err != nil {
		t.Fatalf("Failed to get expected route: %v", err)
	}
}

// Wait for our route for the "fake" target to be established
func WaitUntilRoute(c echo.Instance, dest string) error {
	accept := func(cfg *envoyAdmin.ConfigDump) (bool, error) {
		validator := structpath.ForProto(cfg)
		if err := validator.
			Exists("{.configs[*].dynamicRouteConfigs[*].routeConfig.virtualHosts[?(@.name == '%s')]}", dest+":80").
			Check(); err != nil {
			return false, err
		}
		clusterName := fmt.Sprintf("outbound|%d||%s", 80, dest)
		if err := validator.
			Exists("{.configs[*].dynamicActiveClusters[?(@.cluster.name == '%s')]}", clusterName).
			Check(); err != nil {
			return false, err
		}
		return true, nil
	}

	workloads, _ := c.Workloads()
	// Wait for the outbound config to be received by each workload from Pilot.
	for _, w := range workloads {
		if w.Sidecar() != nil {
			if err := w.Sidecar().WaitForConfig(accept, retry.Timeout(time.Second*10)); err != nil {
				return err
			}
		}
	}

	return nil
}

func sendTraffic(from echo.Instance, host string) error {
	headers := http.Header{}
	headers.Add("Host", host)
	// This is a hack to remain infrastructure agnostic when running these tests
	// We actually call the host set above not the endpoint we pass
	resp, err := from.Call(echo.CallOptions{
		Target:   from,
		PortName: "http",
		Headers:  headers,
		Count:    sendCount,
	})
	if err != nil {
		return fmt.Errorf("%s->%s failed sending: %v", from.Config().Service, host, err)
	}
	if len(resp) != sendCount {
		return fmt.Errorf("%s->%s expected %d responses, received %d", from.Config().Service, host, sendCount, len(resp))
	}
	numFailed := 0
	for i, r := range resp {
		if match := bHostnameMatcher.FindString(r.Hostname); len(match) == 0 {
			numFailed++
			return fmt.Errorf("%s->%s request[%d] made to unexpected service: %s", from.Config().Service, host, i, r.Hostname)
		}
	}
	if numFailed > 0 {
		return fmt.Errorf("%s->%s total requests to unexpected service=%d/%d", from.Config().Service, host, numFailed, len(resp))
	}
	return nil
}
<|endoftext|>


<commit_before>package terraform

import (
	"fmt"
	"log"
	"strings"
	"sync"

	"github.com/hashicorp/terraform/config"
)

// BuiltinEvalContext is an EvalContext implementation that is used by
// Terraform by default.
type BuiltinEvalContext struct {
	// PathValue is the Path that this context is operating within.
	PathValue []string

	// Interpolater setting below affect the interpolation of variables.
	//
	// The InterpolaterVars are the exact value for ${var.foo} values.
	// The map is shared between all contexts and is a mapping of
	// PATH to KEY to VALUE. Because it is shared by all contexts as well
	// as the Interpolater itself, it is protected by InterpolaterVarLock
	// which must be locked during any access to the map.
	Interpolater        *Interpolater
	InterpolaterVars    map[string]map[string]interface{}
	InterpolaterVarLock *sync.Mutex

	Components          contextComponentFactory
	Hooks               []Hook
	InputValue          UIInput
	ProviderCache       map[string]ResourceProvider
	ProviderConfigCache map[string]*ResourceConfig
	ProviderInputConfig map[string]map[string]interface{}
	ProviderLock        *sync.Mutex
	ProvisionerCache    map[string]ResourceProvisioner
	ProvisionerLock     *sync.Mutex
	DiffValue           *Diff
	DiffLock            *sync.RWMutex
	StateValue          *State
	StateLock           *sync.RWMutex

	once sync.Once
}

func (ctx *BuiltinEvalContext) Hook(fn func(Hook) (HookAction, error)) error {
	for _, h := range ctx.Hooks {
		action, err := fn(h)
		if err != nil {
			return err
		}

		switch action {
		case HookActionContinue:
			continue
		case HookActionHalt:
			// Return an early exit error to trigger an early exit
			log.Printf("[WARN] Early exit triggered by hook: %T", h)
			return EvalEarlyExitError{}
		}
	}

	return nil
}

func (ctx *BuiltinEvalContext) Input() UIInput {
	return ctx.InputValue
}

func (ctx *BuiltinEvalContext) InitProvider(n string) (ResourceProvider, error) {
	ctx.once.Do(ctx.init)

	// If we already initialized, it is an error
	if p := ctx.Provider(n); p != nil {
		return nil, fmt.Errorf("Provider '%s' already initialized", n)
	}

	// Warning: make sure to acquire these locks AFTER the call to Provider
	// above, since it also acquires locks.
	ctx.ProviderLock.Lock()
	defer ctx.ProviderLock.Unlock()

	typeName := strings.SplitN(n, ".", 2)[0]
	uid := n

	p, err := ctx.Components.ResourceProvider(typeName, uid)
	if err != nil {
		return nil, err
	}

	providerPath := make([]string, len(ctx.Path())+1)
	copy(providerPath, ctx.Path())
	providerPath[len(providerPath)-1] = n

	ctx.ProviderCache[PathCacheKey(providerPath)] = p
	return p, nil
}

func (ctx *BuiltinEvalContext) Provider(n string) ResourceProvider {
	ctx.once.Do(ctx.init)

	ctx.ProviderLock.Lock()
	defer ctx.ProviderLock.Unlock()

	providerPath := make([]string, len(ctx.Path())+1)
	copy(providerPath, ctx.Path())
	providerPath[len(providerPath)-1] = n

	return ctx.ProviderCache[PathCacheKey(providerPath)]
}

func (ctx *BuiltinEvalContext) CloseProvider(n string) error {
	ctx.once.Do(ctx.init)

	ctx.ProviderLock.Lock()
	defer ctx.ProviderLock.Unlock()

	providerPath := make([]string, len(ctx.Path())+1)
	copy(providerPath, ctx.Path())
	providerPath[len(providerPath)-1] = n

	var provider interface{}
	provider = ctx.ProviderCache[PathCacheKey(providerPath)]
	if provider != nil {
		if p, ok := provider.(ResourceProviderCloser); ok {
			delete(ctx.ProviderCache, PathCacheKey(providerPath))
			return p.Close()
		}
	}

	return nil
}

func (ctx *BuiltinEvalContext) ConfigureProvider(
	n string, cfg *ResourceConfig) error {
	p := ctx.Provider(n)
	if p == nil {
		return fmt.Errorf("Provider '%s' not initialized", n)
	}

	if err := ctx.SetProviderConfig(n, cfg); err != nil {
		return nil
	}

	return p.Configure(cfg)
}

func (ctx *BuiltinEvalContext) SetProviderConfig(
	n string, cfg *ResourceConfig) error {
	providerPath := make([]string, len(ctx.Path())+1)
	copy(providerPath, ctx.Path())
	providerPath[len(providerPath)-1] = n

	// Save the configuration
	ctx.ProviderLock.Lock()
	ctx.ProviderConfigCache[PathCacheKey(providerPath)] = cfg
	ctx.ProviderLock.Unlock()

	return nil
}

func (ctx *BuiltinEvalContext) ProviderInput(n string) map[string]interface{} {
	ctx.ProviderLock.Lock()
	defer ctx.ProviderLock.Unlock()

	// Make a copy of the path so we can safely edit it
	path := ctx.Path()
	pathCopy := make([]string, len(path)+1)
	copy(pathCopy, path)

	// Go up the tree.
	for i := len(path) - 1; i >= 0; i-- {
		pathCopy[i+1] = n
		k := PathCacheKey(pathCopy[:i+2])
		if v, ok := ctx.ProviderInputConfig[k]; ok {
			return v
		}
	}

	return nil
}

func (ctx *BuiltinEvalContext) SetProviderInput(n string, c map[string]interface{}) {
	providerPath := make([]string, len(ctx.Path())+1)
	copy(providerPath, ctx.Path())
	providerPath[len(providerPath)-1] = n

	// Save the configuration
	ctx.ProviderLock.Lock()
	ctx.ProviderInputConfig[PathCacheKey(providerPath)] = c
	ctx.ProviderLock.Unlock()
}

func (ctx *BuiltinEvalContext) ParentProviderConfig(n string) *ResourceConfig {
	ctx.ProviderLock.Lock()
	defer ctx.ProviderLock.Unlock()

	// Make a copy of the path so we can safely edit it
	path := ctx.Path()
	pathCopy := make([]string, len(path)+1)
	copy(pathCopy, path)

	// Go up the tree.
	for i := len(path) - 1; i >= 0; i-- {
		pathCopy[i+1] = n
		k := PathCacheKey(pathCopy[:i+2])
		if v, ok := ctx.ProviderConfigCache[k]; ok {
			return v
		}
	}

	return nil
}

func (ctx *BuiltinEvalContext) InitProvisioner(
	n string) (ResourceProvisioner, error) {
	ctx.once.Do(ctx.init)

	// If we already initialized, it is an error
	if p := ctx.Provisioner(n); p != nil {
		return nil, fmt.Errorf("Provisioner '%s' already initialized", n)
	}

	// Warning: make sure to acquire these locks AFTER the call to Provisioner
	// above, since it also acquires locks.
	ctx.ProvisionerLock.Lock()
	defer ctx.ProvisionerLock.Unlock()

	p, err := ctx.Components.ResourceProvisioner(n, n)
	if err != nil {
		return nil, err
	}

	provPath := make([]string, len(ctx.Path())+1)
	copy(provPath, ctx.Path())
	provPath[len(provPath)-1] = n

	ctx.ProvisionerCache[PathCacheKey(provPath)] = p
	return p, nil
}

func (ctx *BuiltinEvalContext) Provisioner(n string) ResourceProvisioner {
	ctx.once.Do(ctx.init)

	ctx.ProvisionerLock.Lock()
	defer ctx.ProvisionerLock.Unlock()

	provPath := make([]string, len(ctx.Path())+1)
	copy(provPath, ctx.Path())
	provPath[len(provPath)-1] = n

	return ctx.ProvisionerCache[PathCacheKey(provPath)]
}

func (ctx *BuiltinEvalContext) CloseProvisioner(n string) error {
	ctx.once.Do(ctx.init)

	ctx.ProvisionerLock.Lock()
	defer ctx.ProvisionerLock.Unlock()

	provPath := make([]string, len(ctx.Path())+1)
	copy(provPath, ctx.Path())
	provPath[len(provPath)-1] = n

	var prov interface{}
	prov = ctx.ProvisionerCache[PathCacheKey(provPath)]
	if prov != nil {
		if p, ok := prov.(ResourceProvisionerCloser); ok {
			delete(ctx.ProvisionerCache, PathCacheKey(provPath))
			return p.Close()
		}
	}

	return nil
}

func (ctx *BuiltinEvalContext) Interpolate(
	cfg *config.RawConfig, r *Resource) (*ResourceConfig, error) {
	if cfg != nil {
		scope := &InterpolationScope{
			Path:     ctx.Path(),
			Resource: r,
		}

		vs, err := ctx.Interpolater.Values(scope, cfg.Variables)
		if err != nil {
			return nil, err
		}

		// Do the interpolation
		if err := cfg.Interpolate(vs); err != nil {
			return nil, err
		}
	}

	result := NewResourceConfig(cfg)
	result.interpolateForce()
	return result, nil
}

func (ctx *BuiltinEvalContext) Path() []string {
	return ctx.PathValue
}

func (ctx *BuiltinEvalContext) SetVariables(n string, vs map[string]interface{}) {
	ctx.InterpolaterVarLock.Lock()
	defer ctx.InterpolaterVarLock.Unlock()

	path := make([]string, len(ctx.Path())+1)
	copy(path, ctx.Path())
	path[len(path)-1] = n
	key := PathCacheKey(path)

	vars := ctx.InterpolaterVars[key]
	if vars == nil {
		vars = make(map[string]interface{})
		ctx.InterpolaterVars[key] = vars
	}

	for k, v := range vs {
		vars[k] = v
	}
}

func (ctx *BuiltinEvalContext) Diff() (*Diff, *sync.RWMutex) {
	return ctx.DiffValue, ctx.DiffLock
}

func (ctx *BuiltinEvalContext) State() (*State, *sync.RWMutex) {
	return ctx.StateValue, ctx.StateLock
}

func (ctx *BuiltinEvalContext) init() {
}
<commit_msg>terraform: component uid includes the path<commit_after>package terraform

import (
	"fmt"
	"log"
	"strings"
	"sync"

	"github.com/hashicorp/terraform/config"
)

// BuiltinEvalContext is an EvalContext implementation that is used by
// Terraform by default.
type BuiltinEvalContext struct {
	// PathValue is the Path that this context is operating within.
	PathValue []string

	// Interpolater setting below affect the interpolation of variables.
	//
	// The InterpolaterVars are the exact value for ${var.foo} values.
	// The map is shared between all contexts and is a mapping of
	// PATH to KEY to VALUE. Because it is shared by all contexts as well
	// as the Interpolater itself, it is protected by InterpolaterVarLock
	// which must be locked during any access to the map.
	Interpolater        *Interpolater
	InterpolaterVars    map[string]map[string]interface{}
	InterpolaterVarLock *sync.Mutex

	Components          contextComponentFactory
	Hooks               []Hook
	InputValue          UIInput
	ProviderCache       map[string]ResourceProvider
	ProviderConfigCache map[string]*ResourceConfig
	ProviderInputConfig map[string]map[string]interface{}
	ProviderLock        *sync.Mutex
	ProvisionerCache    map[string]ResourceProvisioner
	ProvisionerLock     *sync.Mutex
	DiffValue           *Diff
	DiffLock            *sync.RWMutex
	StateValue          *State
	StateLock           *sync.RWMutex

	once sync.Once
}

func (ctx *BuiltinEvalContext) Hook(fn func(Hook) (HookAction, error)) error {
	for _, h := range ctx.Hooks {
		action, err := fn(h)
		if err != nil {
			return err
		}

		switch action {
		case HookActionContinue:
			continue
		case HookActionHalt:
			// Return an early exit error to trigger an early exit
			log.Printf("[WARN] Early exit triggered by hook: %T", h)
			return EvalEarlyExitError{}
		}
	}

	return nil
}

func (ctx *BuiltinEvalContext) Input() UIInput {
	return ctx.InputValue
}

func (ctx *BuiltinEvalContext) InitProvider(n string) (ResourceProvider, error) {
	ctx.once.Do(ctx.init)

	// If we already initialized, it is an error
	if p := ctx.Provider(n); p != nil {
		return nil, fmt.Errorf("Provider '%s' already initialized", n)
	}

	// Warning: make sure to acquire these locks AFTER the call to Provider
	// above, since it also acquires locks.
	ctx.ProviderLock.Lock()
	defer ctx.ProviderLock.Unlock()

	providerPath := make([]string, len(ctx.Path())+1)
	copy(providerPath, ctx.Path())
	providerPath[len(providerPath)-1] = n
	key := PathCacheKey(providerPath)

	typeName := strings.SplitN(n, ".", 2)[0]
	p, err := ctx.Components.ResourceProvider(typeName, key)
	if err != nil {
		return nil, err
	}

	ctx.ProviderCache[key] = p
	return p, nil
}

func (ctx *BuiltinEvalContext) Provider(n string) ResourceProvider {
	ctx.once.Do(ctx.init)

	ctx.ProviderLock.Lock()
	defer ctx.ProviderLock.Unlock()

	providerPath := make([]string, len(ctx.Path())+1)
	copy(providerPath, ctx.Path())
	providerPath[len(providerPath)-1] = n

	return ctx.ProviderCache[PathCacheKey(providerPath)]
}

func (ctx *BuiltinEvalContext) CloseProvider(n string) error {
	ctx.once.Do(ctx.init)

	ctx.ProviderLock.Lock()
	defer ctx.ProviderLock.Unlock()

	providerPath := make([]string, len(ctx.Path())+1)
	copy(providerPath, ctx.Path())
	providerPath[len(providerPath)-1] = n

	var provider interface{}
	provider = ctx.ProviderCache[PathCacheKey(providerPath)]
	if provider != nil {
		if p, ok := provider.(ResourceProviderCloser); ok {
			delete(ctx.ProviderCache, PathCacheKey(providerPath))
			return p.Close()
		}
	}

	return nil
}

func (ctx *BuiltinEvalContext) ConfigureProvider(
	n string, cfg *ResourceConfig) error {
	p := ctx.Provider(n)
	if p == nil {
		return fmt.Errorf("Provider '%s' not initialized", n)
	}

	if err := ctx.SetProviderConfig(n, cfg); err != nil {
		return nil
	}

	return p.Configure(cfg)
}

func (ctx *BuiltinEvalContext) SetProviderConfig(
	n string, cfg *ResourceConfig) error {
	providerPath := make([]string, len(ctx.Path())+1)
	copy(providerPath, ctx.Path())
	providerPath[len(providerPath)-1] = n

	// Save the configuration
	ctx.ProviderLock.Lock()
	ctx.ProviderConfigCache[PathCacheKey(providerPath)] = cfg
	ctx.ProviderLock.Unlock()

	return nil
}

func (ctx *BuiltinEvalContext) ProviderInput(n string) map[string]interface{} {
	ctx.ProviderLock.Lock()
	defer ctx.ProviderLock.Unlock()

	// Make a copy of the path so we can safely edit it
	path := ctx.Path()
	pathCopy := make([]string, len(path)+1)
	copy(pathCopy, path)

	// Go up the tree.
	for i := len(path) - 1; i >= 0; i-- {
		pathCopy[i+1] = n
		k := PathCacheKey(pathCopy[:i+2])
		if v, ok := ctx.ProviderInputConfig[k]; ok {
			return v
		}
	}

	return nil
}

func (ctx *BuiltinEvalContext) SetProviderInput(n string, c map[string]interface{}) {
	providerPath := make([]string, len(ctx.Path())+1)
	copy(providerPath, ctx.Path())
	providerPath[len(providerPath)-1] = n

	// Save the configuration
	ctx.ProviderLock.Lock()
	ctx.ProviderInputConfig[PathCacheKey(providerPath)] = c
	ctx.ProviderLock.Unlock()
}

func (ctx *BuiltinEvalContext) ParentProviderConfig(n string) *ResourceConfig {
	ctx.ProviderLock.Lock()
	defer ctx.ProviderLock.Unlock()

	// Make a copy of the path so we can safely edit it
	path := ctx.Path()
	pathCopy := make([]string, len(path)+1)
	copy(pathCopy, path)

	// Go up the tree.
	for i := len(path) - 1; i >= 0; i-- {
		pathCopy[i+1] = n
		k := PathCacheKey(pathCopy[:i+2])
		if v, ok := ctx.ProviderConfigCache[k]; ok {
			return v
		}
	}

	return nil
}

func (ctx *BuiltinEvalContext) InitProvisioner(
	n string) (ResourceProvisioner, error) {
	ctx.once.Do(ctx.init)

	// If we already initialized, it is an error
	if p := ctx.Provisioner(n); p != nil {
		return nil, fmt.Errorf("Provisioner '%s' already initialized", n)
	}

	// Warning: make sure to acquire these locks AFTER the call to Provisioner
	// above, since it also acquires locks.
	ctx.ProvisionerLock.Lock()
	defer ctx.ProvisionerLock.Unlock()

	provPath := make([]string, len(ctx.Path())+1)
	copy(provPath, ctx.Path())
	provPath[len(provPath)-1] = n
	key := PathCacheKey(provPath)

	p, err := ctx.Components.ResourceProvisioner(n, key)
	if err != nil {
		return nil, err
	}

	ctx.ProvisionerCache[key] = p
	return p, nil
}

func (ctx *BuiltinEvalContext) Provisioner(n string) ResourceProvisioner {
	ctx.once.Do(ctx.init)

	ctx.ProvisionerLock.Lock()
	defer ctx.ProvisionerLock.Unlock()

	provPath := make([]string, len(ctx.Path())+1)
	copy(provPath, ctx.Path())
	provPath[len(provPath)-1] = n

	return ctx.ProvisionerCache[PathCacheKey(provPath)]
}

func (ctx *BuiltinEvalContext) CloseProvisioner(n string) error {
	ctx.once.Do(ctx.init)

	ctx.ProvisionerLock.Lock()
	defer ctx.ProvisionerLock.Unlock()

	provPath := make([]string, len(ctx.Path())+1)
	copy(provPath, ctx.Path())
	provPath[len(provPath)-1] = n

	var prov interface{}
	prov = ctx.ProvisionerCache[PathCacheKey(provPath)]
	if prov != nil {
		if p, ok := prov.(ResourceProvisionerCloser); ok {
			delete(ctx.ProvisionerCache, PathCacheKey(provPath))
			return p.Close()
		}
	}

	return nil
}

func (ctx *BuiltinEvalContext) Interpolate(
	cfg *config.RawConfig, r *Resource) (*ResourceConfig, error) {
	if cfg != nil {
		scope := &InterpolationScope{
			Path:     ctx.Path(),
			Resource: r,
		}

		vs, err := ctx.Interpolater.Values(scope, cfg.Variables)
		if err != nil {
			return nil, err
		}

		// Do the interpolation
		if err := cfg.Interpolate(vs); err != nil {
			return nil, err
		}
	}

	result := NewResourceConfig(cfg)
	result.interpolateForce()
	return result, nil
}

func (ctx *BuiltinEvalContext) Path() []string {
	return ctx.PathValue
}

func (ctx *BuiltinEvalContext) SetVariables(n string, vs map[string]interface{}) {
	ctx.InterpolaterVarLock.Lock()
	defer ctx.InterpolaterVarLock.Unlock()

	path := make([]string, len(ctx.Path())+1)
	copy(path, ctx.Path())
	path[len(path)-1] = n
	key := PathCacheKey(path)

	vars := ctx.InterpolaterVars[key]
	if vars == nil {
		vars = make(map[string]interface{})
		ctx.InterpolaterVars[key] = vars
	}

	for k, v := range vs {
		vars[k] = v
	}
}

func (ctx *BuiltinEvalContext) Diff() (*Diff, *sync.RWMutex) {
	return ctx.DiffValue, ctx.DiffLock
}

func (ctx *BuiltinEvalContext) State() (*State, *sync.RWMutex) {
	return ctx.StateValue, ctx.StateLock
}

func (ctx *BuiltinEvalContext) init() {
}
<|endoftext|>


<commit_before>package redsync

import (
	"crypto/rand"
	"encoding/base64"
	"sync"
	"time"

	"github.com/gomodule/redigo/redis"
)

// A Mutex is a distributed mutual exclusion lock.
type Mutex struct {
	name   string
	expiry time.Duration

	tries int
	delay time.Duration

	factor float64

	quorum int

	value string
	until time.Time

	nodem sync.Mutex

	pools []Pool
}

// Lock locks m. In case it returns an error on failure, you may retry to acquire the lock by calling this method again.
func (m *Mutex) Lock() error {
	m.nodem.Lock()
	defer m.nodem.Unlock()

	value, err := m.genValue()
	if err != nil {
		return err
	}

	for i := 0; i < m.tries; i++ {
		if i != 0 {
			time.Sleep(m.delay)
		}

		start := time.Now()

		n := 0
		for _, pool := range m.pools {
			ok := m.acquire(pool, value)
			if ok {
				n++
			}
		}

		until := time.Now().Add(m.expiry - time.Now().Sub(start) - time.Duration(int64(float64(m.expiry)*m.factor)) + 2*time.Millisecond)
		if n >= m.quorum && time.Now().Before(until) {
			m.value = value
			m.until = until
			return nil
		}
		for _, pool := range m.pools {
			m.release(pool, value)
		}
	}

	return ErrFailed
}

// Unlock unlocks m and returns the status of unlock.
func (m *Mutex) Unlock() bool {
	m.nodem.Lock()
	defer m.nodem.Unlock()

	n := 0
	for _, pool := range m.pools {
		ok := m.release(pool, m.value)
		if ok {
			n++
		}
	}
	return n >= m.quorum
}

// Extend resets the mutex's expiry and returns the status of expiry extension.
func (m *Mutex) Extend() bool {
	m.nodem.Lock()
	defer m.nodem.Unlock()

	n := 0
	for _, pool := range m.pools {
		ok := m.touch(pool, m.value, int(m.expiry/time.Millisecond))
		if ok {
			n++
		}
	}
	return n >= m.quorum
}

func (m *Mutex) genValue() (string, error) {
	b := make([]byte, 32)
	_, err := rand.Read(b)
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(b), nil
}

func (m *Mutex) acquire(pool Pool, value string) bool {
	conn := pool.Get()
	defer conn.Close()
	reply, err := redis.String(conn.Do("SET", m.name, value, "NX", "PX", int(m.expiry/time.Millisecond)))
	return err == nil && reply == "OK"
}

var deleteScript = redis.NewScript(1, `
	if redis.call("GET", KEYS[1]) == ARGV[1] then
		return redis.call("DEL", KEYS[1])
	else
		return 0
	end
`)

func (m *Mutex) release(pool Pool, value string) bool {
	conn := pool.Get()
	defer conn.Close()
	status, err := deleteScript.Do(conn, m.name, value)
	return err == nil && status != 0
}

var touchScript = redis.NewScript(1, `
	if redis.call("GET", KEYS[1]) == ARGV[1] then
		return redis.call("SET", KEYS[1], ARGV[1], "XX", "PX", ARGV[2])
	else
		return "ERR"
	end
`)

func (m *Mutex) touch(pool Pool, value string, expiry int) bool {
	conn := pool.Get()
	defer conn.Close()
	status, err := redis.String(touchScript.Do(conn, m.name, value, expiry))
	return err == nil && status != "ERR"
}
<commit_msg>acquire/release/touch locks ASAP<commit_after>package redsync

import (
	"crypto/rand"
	"encoding/base64"
	"sync"
	"time"

	"github.com/gomodule/redigo/redis"
)

// A Mutex is a distributed mutual exclusion lock.
type Mutex struct {
	name   string
	expiry time.Duration

	tries int
	delay time.Duration

	factor float64

	quorum int

	value string
	until time.Time

	nodem sync.Mutex

	pools []Pool
}

// Lock locks m. In case it returns an error on failure, you may retry to acquire the lock by calling this method again.
func (m *Mutex) Lock() error {
	m.nodem.Lock()
	defer m.nodem.Unlock()

	value, err := m.genValue()
	if err != nil {
		return err
	}

	for i := 0; i < m.tries; i++ {
		if i != 0 {
			time.Sleep(m.delay)
		}

		start := time.Now()

		n := m.acquireAsync(value)

		until := time.Now().Add(m.expiry - time.Now().Sub(start) - time.Duration(int64(float64(m.expiry)*m.factor)) + 2*time.Millisecond)
		if n >= m.quorum && time.Now().Before(until) {
			m.value = value
			m.until = until
			return nil
		}
		for _, pool := range m.pools {
			m.release(pool, value)
		}
	}

	return ErrFailed
}

// Unlock unlocks m and returns the status of unlock.
func (m *Mutex) Unlock() bool {
	m.nodem.Lock()
	defer m.nodem.Unlock()

	return m.releaseAsync(m.value) >= m.quorum
}

// Extend resets the mutex's expiry and returns the status of expiry extension.
func (m *Mutex) Extend() bool {
	m.nodem.Lock()
	defer m.nodem.Unlock()

	return m.touchAsync(m.value, int(m.expiry/time.Millisecond)) >= m.quorum
}

func (m *Mutex) genValue() (string, error) {
	b := make([]byte, 32)
	_, err := rand.Read(b)
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(b), nil
}

func (m *Mutex) acquireAsync(value string) int {
	ch := make(chan bool)
	for _, pool := range m.pools {
		go func(pool Pool) {
			ch <- m.acquire(pool, value)
		}(pool)
	}
	n := 0
	for range m.pools {
		if <-ch {
			n++
		}
	}
	return n
}

func (m *Mutex) acquire(pool Pool, value string) bool {
	conn := pool.Get()
	defer conn.Close()
	reply, err := redis.String(conn.Do("SET", m.name, value, "NX", "PX", int(m.expiry/time.Millisecond)))
	return err == nil && reply == "OK"
}

func (m *Mutex) releaseAsync(value string) int {
	ch := make(chan bool)
	for _, pool := range m.pools {
		go func(pool Pool) {
			ch <- m.release(pool, value)
		}(pool)
	}
	n := 0
	for range m.pools {
		if <-ch {
			n++
		}
	}
	return n
}

var deleteScript = redis.NewScript(1, `
	if redis.call("GET", KEYS[1]) == ARGV[1] then
		return redis.call("DEL", KEYS[1])
	else
		return 0
	end
`)

func (m *Mutex) release(pool Pool, value string) bool {
	conn := pool.Get()
	defer conn.Close()
	status, err := deleteScript.Do(conn, m.name, value)
	return err == nil && status != 0
}

func (m *Mutex) touchAsync(value string, expiry int) int {
	ch := make(chan bool)
	for _, pool := range m.pools {
		go func(pool Pool) {
			ch <- m.touch(pool, value, expiry)
		}(pool)
	}
	n := 0
	for range m.pools {
		if <-ch {
			n++
		}
	}
	return n
}

var touchScript = redis.NewScript(1, `
	if redis.call("GET", KEYS[1]) == ARGV[1] then
		return redis.call("SET", KEYS[1], ARGV[1], "XX", "PX", ARGV[2])
	else
		return "ERR"
	end
`)

func (m *Mutex) touch(pool Pool, value string, expiry int) bool {
	conn := pool.Get()
	defer conn.Close()
	status, err := redis.String(touchScript.Do(conn, m.name, value, expiry))
	return err == nil && status != "ERR"
}
<|endoftext|>


<commit_before>package api_test

import (
	"bytes"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"

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

	"github.com/winston-ci/prole/api"
	"github.com/winston-ci/prole/api/builds"
	"github.com/winston-ci/prole/scheduler/fakescheduler"
)

var _ = Describe("API", func() {
	var scheduler *fakescheduler.FakeScheduler

	var server *httptest.Server
	var client *http.Client

	BeforeEach(func() {
		scheduler = fakescheduler.New()

		handler, err := api.New(scheduler)
		Ω(err).ShouldNot(HaveOccurred())

		server = httptest.NewServer(handler)
		client = &http.Client{
			Transport: &http.Transport{},
		}
	})

	Describe("POST /builds", func() {
		var build builds.Build
		var requestBody string
		var response *http.Response

		buildPayload := func(build builds.Build) string {
			payload, err := json.Marshal(build)
			Ω(err).ShouldNot(HaveOccurred())

			return string(payload)
		}

		BeforeEach(func() {
			build = builds.Build{
				Guid: "abc",
				Source: builds.BuildSource{
					Type:   "git",
					URI:    "https://github.com/winston-ci/prole.git",
					Branch: "master",
					Ref:    "deadbeef",
				},
			}

			requestBody = buildPayload(build)
		})

		JustBeforeEach(func() {
			var err error

			response, err = client.Post(
				server.URL+"/builds",
				"application/json",
				bytes.NewBufferString(requestBody),
			)
			Ω(err).ShouldNot(HaveOccurred())
		})

		It("returns 201", func() {
			Ω(response.StatusCode).Should(Equal(http.StatusCreated))
		})

		It("returns the build", func() {
			var returnedBuild builds.Build

			err := json.NewDecoder(response.Body).Decode(&returnedBuild)
			Ω(err).ShouldNot(HaveOccurred())

			Ω(returnedBuild).Should(Equal(build))
		})

		It("schedules the build", func() {
			Ω(scheduler.Scheduled()).Should(ContainElement(build))
		})

		Context("when scheduling fails", func() {
			BeforeEach(func() {
				scheduler.ScheduleError = errors.New("oh no!")
			})

			It("returns 503", func() {
				Ω(response.StatusCode).Should(Equal(http.StatusServiceUnavailable))
			})
		})

		Context("when guid is omitted", func() {
			BeforeEach(func() {
				build.Guid = ""
				requestBody = buildPayload(build)
			})

			It("returns 400", func() {
				Ω(response.StatusCode).Should(Equal(http.StatusBadRequest))
			})
		})

		Context("when ref is not given for a git source", func() {
			BeforeEach(func() {
				build.Source.Type = "git"
				build.Source.Ref = ""
				requestBody = buildPayload(build)
			})

			It("returns 400", func() {
				Ω(response.StatusCode).Should(Equal(http.StatusBadRequest))
			})
		})

		Context("when the callback url is malformed", func() {
			BeforeEach(func() {
				build.Callback = "ß"
				requestBody = buildPayload(build)
			})

			It("returns 400", func() {
				Ω(response.StatusCode).Should(Equal(http.StatusBadRequest))
			})
		})

		Context("when the payload is malformed JSON", func() {
			BeforeEach(func() {
				requestBody = "ß"
			})

			It("returns 400", func() {
				Ω(response.StatusCode).Should(Equal(http.StatusBadRequest))
			})
		})
	})
})
<commit_msg>remove guid omission validation test<commit_after>package api_test

import (
	"bytes"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"

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

	"github.com/winston-ci/prole/api"
	"github.com/winston-ci/prole/api/builds"
	"github.com/winston-ci/prole/scheduler/fakescheduler"
)

var _ = Describe("API", func() {
	var scheduler *fakescheduler.FakeScheduler

	var server *httptest.Server
	var client *http.Client

	BeforeEach(func() {
		scheduler = fakescheduler.New()

		handler, err := api.New(scheduler)
		Ω(err).ShouldNot(HaveOccurred())

		server = httptest.NewServer(handler)
		client = &http.Client{
			Transport: &http.Transport{},
		}
	})

	Describe("POST /builds", func() {
		var build builds.Build
		var requestBody string
		var response *http.Response

		buildPayload := func(build builds.Build) string {
			payload, err := json.Marshal(build)
			Ω(err).ShouldNot(HaveOccurred())

			return string(payload)
		}

		BeforeEach(func() {
			build = builds.Build{
				Guid: "abc",
				Source: builds.BuildSource{
					Type:   "git",
					URI:    "https://github.com/winston-ci/prole.git",
					Branch: "master",
					Ref:    "deadbeef",
				},
			}

			requestBody = buildPayload(build)
		})

		JustBeforeEach(func() {
			var err error

			response, err = client.Post(
				server.URL+"/builds",
				"application/json",
				bytes.NewBufferString(requestBody),
			)
			Ω(err).ShouldNot(HaveOccurred())
		})

		It("returns 201", func() {
			Ω(response.StatusCode).Should(Equal(http.StatusCreated))
		})

		It("returns the build", func() {
			var returnedBuild builds.Build

			err := json.NewDecoder(response.Body).Decode(&returnedBuild)
			Ω(err).ShouldNot(HaveOccurred())

			Ω(returnedBuild).Should(Equal(build))
		})

		It("schedules the build", func() {
			Ω(scheduler.Scheduled()).Should(ContainElement(build))
		})

		Context("when scheduling fails", func() {
			BeforeEach(func() {
				scheduler.ScheduleError = errors.New("oh no!")
			})

			It("returns 503", func() {
				Ω(response.StatusCode).Should(Equal(http.StatusServiceUnavailable))
			})
		})

		Context("when ref is not given for a git source", func() {
			BeforeEach(func() {
				build.Source.Type = "git"
				build.Source.Ref = ""
				requestBody = buildPayload(build)
			})

			It("returns 400", func() {
				Ω(response.StatusCode).Should(Equal(http.StatusBadRequest))
			})
		})

		Context("when the callback url is malformed", func() {
			BeforeEach(func() {
				build.Callback = "ß"
				requestBody = buildPayload(build)
			})

			It("returns 400", func() {
				Ω(response.StatusCode).Should(Equal(http.StatusBadRequest))
			})
		})

		Context("when the payload is malformed JSON", func() {
			BeforeEach(func() {
				requestBody = "ß"
			})

			It("returns 400", func() {
				Ω(response.StatusCode).Should(Equal(http.StatusBadRequest))
			})
		})
	})
})
<|endoftext|>


<commit_before>package cli

import (
	"fmt"
	"os"
	"text/tabwriter"

	"github.com/codegangsta/cli"

	"github.com/libopenstorage/openstorage/api"
	"github.com/libopenstorage/openstorage/api/client"
	"github.com/libopenstorage/openstorage/cluster"
)

type clusterClient struct {
	manager cluster.Cluster
	name    string
}

func (c *clusterClient) clusterOptions(context *cli.Context) {
	clnt, err := client.NewClient("http://localhost:9001", "v1")
	if err != nil {
		fmt.Printf("Failed to initialize client library: %v\n", err)
		os.Exit(1)
	}
	c.manager = clnt.ClusterManager()
}

func (c *clusterClient) inspect(context *cli.Context) {
	c.clusterOptions(context)
	jsonOut := context.GlobalBool("json")
	outFd := os.Stdout
	fn := "inspect"

	cluster, err := c.manager.Enumerate()
	if err != nil {
		cmdError(context, fn, err)
		return
	}

	if jsonOut {
		fmtOutput(context, &Format{Cluster: &cluster})
	} else {
		fmt.Fprintf(outFd, "ID %s: Status: %v\n",
			cluster.Id, cluster.Status)

		w := new(tabwriter.Writer)
		w.Init(outFd, 12, 12, 1, ' ', 0)

		fmt.Fprintln(w, "ID\t IP\t STATUS\t CPU\t MEMORY\t CONTAINERS")
		for _, n := range cluster.Nodes {
			status := ""
			if n.Status == api.StatusInit {
				status = "Initializing"
			} else if n.Status == api.StatusOk {
				status = "OK"
			} else if n.Status == api.StatusOffline {
				status = "Off Line"
			} else {
				status = "Error"
			}

			fmt.Fprintln(w, n.Id, "\t", n.Ip, "\t", status, "\t",
				n.Cpu, "\t", n.Memory, "\t", len(n.Containers))
		}

		fmt.Fprintln(w)
		w.Flush()
	}
}

func (c *clusterClient) enumerate(context *cli.Context) {
	c.clusterOptions(context)
	jsonOut := context.GlobalBool("json")
	outFd := os.Stdout
	fn := "enumerate"

	cluster, err := c.manager.Enumerate()
	if err != nil {
		cmdError(context, fn, err)
		return
	}

	if jsonOut {
		fmtOutput(context, &Format{Cluster: &cluster})
	} else {
		w := new(tabwriter.Writer)
		for _, n := range cluster.Nodes {
			for _, c := range n.Containers {
				w.Init(outFd, 12, 12, 1, ' ', 0)

				fmt.Fprintln(w, "ID\t IMAGE\t STATUS\t NAMES\t NODE")
				for _, n := range cluster.Nodes {
					fmt.Fprintln(w, c.ID, "\t", c.Image, "\t", c.Status, "\t",
						c.Names, "\t", n.Ip)
				}
			}
		}

		fmt.Fprintln(w)
		w.Flush()
	}
}

func (c *clusterClient) remove(context *cli.Context) {
}

func (c *clusterClient) shutdown(context *cli.Context) {
}

func (c *clusterClient) disableGossip(context *cli.Context) {
	c.clusterOptions(context)
	c.manager.DisableGossipUpdates()
}

func (c *clusterClient) enableGossip(context *cli.Context) {
	c.clusterOptions(context)
	c.manager.EnableGossipUpdates()
}

// ClusterCommands exports CLI comamnds for File VolumeDriver
func ClusterCommands(name string) []cli.Command {
	c := &clusterClient{name: name}

	commands := []cli.Command{
		{
			Name:    "inspect",
			Aliases: []string{"i"},
			Usage:   "Inspect the cluster",
			Action:  c.inspect,
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "machine,m",
					Usage: "Comma separated machine ids, e.g uuid1,uuid2",
					Value: "",
				},
			},
		},
		{
			Name:    "enumerate",
			Aliases: []string{"e"},
			Usage:   "Enumerate containers in the cluster",
			Action:  c.enumerate,
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "machine,m",
					Usage: "Comma separated machine ids, e.g uuid1,uuid2",
					Value: "",
				},
			},
		},
		{
			Name:    "disable-gossip",
			Aliases: []string{"dg"},
			Usage:   "Disable gossip updates",
			Action:  c.disableGossip,
		},
		{
			Name:    "enable-gossip",
			Aliases: []string{"eg"},
			Usage:   "Enable gossip updates",
			Action:  c.enableGossip,
		},
		{
			Name:    "remove",
			Aliases: []string{"r"},
			Usage:   "Remove a machine from the cluster",
			Action:  c.remove,
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "machine,m",
					Usage: "Comma separated machine ids, e.g uuid1,uuid2",
					Value: "",
				},
			},
		},
		{
			Name:   "shutdown",
			Usage:  "Shutdown a cluster or a specific machine",
			Action: c.shutdown,
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "machine,m",
					Usage: "Comma separated machine ids, e.g uuid1,uuid2",
					Value: "",
				},
			},
		},
	}
	return commands
}
<commit_msg>bug fix for clustered enumeration of apps<commit_after>package cli

import (
	"fmt"
	"os"
	"text/tabwriter"

	"github.com/codegangsta/cli"

	"github.com/libopenstorage/openstorage/api"
	"github.com/libopenstorage/openstorage/api/client"
	"github.com/libopenstorage/openstorage/cluster"
)

type clusterClient struct {
	manager cluster.Cluster
	name    string
}

func (c *clusterClient) clusterOptions(context *cli.Context) {
	clnt, err := client.NewClient("http://localhost:9001", "v1")
	if err != nil {
		fmt.Printf("Failed to initialize client library: %v\n", err)
		os.Exit(1)
	}
	c.manager = clnt.ClusterManager()
}

func (c *clusterClient) inspect(context *cli.Context) {
	c.clusterOptions(context)
	jsonOut := context.GlobalBool("json")
	outFd := os.Stdout
	fn := "inspect"

	cluster, err := c.manager.Enumerate()
	if err != nil {
		cmdError(context, fn, err)
		return
	}

	if jsonOut {
		fmtOutput(context, &Format{Cluster: &cluster})
	} else {
		fmt.Fprintf(outFd, "ID %s: Status: %v\n",
			cluster.Id, cluster.Status)

		w := new(tabwriter.Writer)
		w.Init(outFd, 12, 12, 1, ' ', 0)

		fmt.Fprintln(w, "ID\t IP\t STATUS\t CPU\t MEMORY\t CONTAINERS")
		for _, n := range cluster.Nodes {
			status := ""
			if n.Status == api.StatusInit {
				status = "Initializing"
			} else if n.Status == api.StatusOk {
				status = "OK"
			} else if n.Status == api.StatusOffline {
				status = "Off Line"
			} else {
				status = "Error"
			}

			fmt.Fprintln(w, n.Id, "\t", n.Ip, "\t", status, "\t",
				n.Cpu, "\t", n.Memory, "\t", len(n.Containers))
		}

		fmt.Fprintln(w)
		w.Flush()
	}
}

func (c *clusterClient) enumerate(context *cli.Context) {
	c.clusterOptions(context)
	jsonOut := context.GlobalBool("json")
	outFd := os.Stdout
	fn := "enumerate"

	cluster, err := c.manager.Enumerate()
	if err != nil {
		cmdError(context, fn, err)
		return
	}

	if jsonOut {
		fmtOutput(context, &Format{Cluster: &cluster})
	} else {
		w := new(tabwriter.Writer)
		w.Init(outFd, 12, 12, 1, ' ', 0)

		fmt.Fprintln(w, "ID\t IMAGE\t STATUS\t NAMES\t NODE")
		for _, n := range cluster.Nodes {
			for _, c := range n.Containers {
				fmt.Fprintln(w, c.ID, "\t", c.Image, "\t", c.Status, "\t",
					c.Names, "\t", n.Ip)
			}
		}

		fmt.Fprintln(w)
		w.Flush()
	}
}

func (c *clusterClient) remove(context *cli.Context) {
}

func (c *clusterClient) shutdown(context *cli.Context) {
}

func (c *clusterClient) disableGossip(context *cli.Context) {
	c.clusterOptions(context)
	c.manager.DisableGossipUpdates()
}

func (c *clusterClient) enableGossip(context *cli.Context) {
	c.clusterOptions(context)
	c.manager.EnableGossipUpdates()
}

// ClusterCommands exports CLI comamnds for File VolumeDriver
func ClusterCommands(name string) []cli.Command {
	c := &clusterClient{name: name}

	commands := []cli.Command{
		{
			Name:    "inspect",
			Aliases: []string{"i"},
			Usage:   "Inspect the cluster",
			Action:  c.inspect,
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "machine,m",
					Usage: "Comma separated machine ids, e.g uuid1,uuid2",
					Value: "",
				},
			},
		},
		{
			Name:    "enumerate",
			Aliases: []string{"e"},
			Usage:   "Enumerate containers in the cluster",
			Action:  c.enumerate,
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "machine,m",
					Usage: "Comma separated machine ids, e.g uuid1,uuid2",
					Value: "",
				},
			},
		},
		{
			Name:    "disable-gossip",
			Aliases: []string{"dg"},
			Usage:   "Disable gossip updates",
			Action:  c.disableGossip,
		},
		{
			Name:    "enable-gossip",
			Aliases: []string{"eg"},
			Usage:   "Enable gossip updates",
			Action:  c.enableGossip,
		},
		{
			Name:    "remove",
			Aliases: []string{"r"},
			Usage:   "Remove a machine from the cluster",
			Action:  c.remove,
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "machine,m",
					Usage: "Comma separated machine ids, e.g uuid1,uuid2",
					Value: "",
				},
			},
		},
		{
			Name:   "shutdown",
			Usage:  "Shutdown a cluster or a specific machine",
			Action: c.shutdown,
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "machine,m",
					Usage: "Comma separated machine ids, e.g uuid1,uuid2",
					Value: "",
				},
			},
		},
	}
	return commands
}
<|endoftext|>


<commit_before>// +-------------------------------------------------------------------------
// | Copyright (C) 2016 Yunify, Inc.
// +-------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this work except in compliance with the License.
// | You may obtain a copy of the License in the LICENSE file, or 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 specs

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/yunify/snips/capsules"
	"github.com/yunify/snips/utils"
)

// A Service holds the information of API service.
type Service struct {
	Filename         string
	FilePath         string
	LatestAPIVersion *APIVersion
	APIVersions      map[string]*APIVersion
}

// A APIVersion holds the information of an API service's version.
type APIVersion struct {
	Filename string
	FilePath string
	Spec     *Spec
}

// A Spec holds the information of an API spec file.
type Spec struct {
	Filename    string
	FilePath    string
	FileContent string

	Format string

	Data *capsules.Data
}

// LoadServices walks through the specs directory and load API spec information.
func LoadServices(specDirectory, specFormat string, serviceModule string) (*Service, error) {
	if serviceModule != strings.ToLower(serviceModule) {
		serviceModule = utils.CamelCaseToSnakeCase(serviceModule)
	}
	serviceModule = utils.SnakeCaseToSnakeCase(serviceModule, true)

	if _, err := os.Stat(specDirectory + "/" + serviceModule); err != nil {
		return nil, fmt.Errorf("spec of service \"%s\" not found", serviceModule)
	}

	service := &Service{
		Filename: serviceModule,
		FilePath: specDirectory + "/" + serviceModule,
	}

	apiVersions, err := LoadAPIVersions(service, specFormat)

	if err != nil {
		return nil, err
	}
	service.APIVersions = apiVersions

	var latestAPIVersion *APIVersion
	for _, apiVersion := range apiVersions {
		if latestAPIVersion != nil {
			if apiVersion.Filename > latestAPIVersion.Filename {
				latestAPIVersion = apiVersion
			}
		} else {
			latestAPIVersion = apiVersion
		}
	}
	service.LatestAPIVersion = latestAPIVersion
	service.APIVersions["latest"] = latestAPIVersion

	return service, nil
}

// LoadAPIVersions loads all API version files information.
func LoadAPIVersions(service *Service, specFormat string) (map[string]*APIVersion, error) {
	apiVersions := map[string]*APIVersion{}

	files, err := ioutil.ReadDir(service.FilePath)
	if err != nil {
		return nil, err
	}

	for _, file := range files {
		if !file.IsDir() {
			continue
		}

		apiVersion := &APIVersion{
			Filename: file.Name(),
			FilePath: service.FilePath + "/" + file.Name(),
		}

		var format, filename string
		switch specFormat {
		case "Swagger", "Swagger-v2.0", "OpenAPI", "OpenAPI-v2.0":
			format = "swagger"
			filename = "api_v2.0.json"
		default:
			return apiVersions, errors.New("Spec format not supported: " + specFormat)
		}

		specFilePath := apiVersion.FilePath + "/" + format + "/" + filename
		specFileContent, err := ioutil.ReadFile(specFilePath)
		if err != nil {
			return apiVersions, err
		}

		apiVersion.Spec = &Spec{
			Filename:    filename,
			FilePath:    specFilePath,
			FileContent: string(specFileContent),
			Format:      specFormat,
			Data:        &capsules.Data{},
		}

		swagger := Swagger{
			FilePath: specFilePath,
			Data:     apiVersion.Spec.Data,
		}
		err = swagger.Parse("v2.0")
		if err != nil {
			return apiVersions, err
		}

		apiVersions[apiVersion.Filename] = apiVersion
	}

	return apiVersions, nil
}
<commit_msg>Skip hidden dirs when loading specs, such as .git<commit_after>// +-------------------------------------------------------------------------
// | Copyright (C) 2016 Yunify, Inc.
// +-------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this work except in compliance with the License.
// | You may obtain a copy of the License in the LICENSE file, or 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 specs

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/yunify/snips/capsules"
	"github.com/yunify/snips/utils"
)

// A Service holds the information of API service.
type Service struct {
	Filename         string
	FilePath         string
	LatestAPIVersion *APIVersion
	APIVersions      map[string]*APIVersion
}

// A APIVersion holds the information of an API service's version.
type APIVersion struct {
	Filename string
	FilePath string
	Spec     *Spec
}

// A Spec holds the information of an API spec file.
type Spec struct {
	Filename    string
	FilePath    string
	FileContent string

	Format string

	Data *capsules.Data
}

// LoadServices walks through the specs directory and load API spec information.
func LoadServices(specDirectory, specFormat string, serviceModule string) (*Service, error) {
	if serviceModule != strings.ToLower(serviceModule) {
		serviceModule = utils.CamelCaseToSnakeCase(serviceModule)
	}
	serviceModule = utils.SnakeCaseToSnakeCase(serviceModule, true)

	if _, err := os.Stat(specDirectory + "/" + serviceModule); err != nil {
		return nil, fmt.Errorf("spec of service \"%s\" not found", serviceModule)
	}

	service := &Service{
		Filename: serviceModule,
		FilePath: specDirectory + "/" + serviceModule,
	}

	apiVersions, err := LoadAPIVersions(service, specFormat)

	if err != nil {
		return nil, err
	}
	service.APIVersions = apiVersions

	var latestAPIVersion *APIVersion
	for _, apiVersion := range apiVersions {
		if latestAPIVersion != nil {
			if apiVersion.Filename > latestAPIVersion.Filename {
				latestAPIVersion = apiVersion
			}
		} else {
			latestAPIVersion = apiVersion
		}
	}
	service.LatestAPIVersion = latestAPIVersion
	service.APIVersions["latest"] = latestAPIVersion

	return service, nil
}

// LoadAPIVersions loads all API version files information.
func LoadAPIVersions(service *Service, specFormat string) (map[string]*APIVersion, error) {
	apiVersions := map[string]*APIVersion{}

	files, err := ioutil.ReadDir(service.FilePath)
	if err != nil {
		return nil, err
	}

	for _, file := range files {
		if !file.IsDir() {
			continue
		}

		if strings.HasPrefix(file.Name(), "."){
			continue
		}

		apiVersion := &APIVersion{
			Filename: file.Name(),
			FilePath: service.FilePath + "/" + file.Name(),
		}

		var format, filename string
		switch specFormat {
		case "Swagger", "Swagger-v2.0", "OpenAPI", "OpenAPI-v2.0":
			format = "swagger"
			filename = "api_v2.0.json"
		default:
			return apiVersions, errors.New("Spec format not supported: " + specFormat)
		}

		specFilePath := apiVersion.FilePath + "/" + format + "/" + filename
		specFileContent, err := ioutil.ReadFile(specFilePath)
		if err != nil {
			return apiVersions, err
		}

		apiVersion.Spec = &Spec{
			Filename:    filename,
			FilePath:    specFilePath,
			FileContent: string(specFileContent),
			Format:      specFormat,
			Data:        &capsules.Data{},
		}

		swagger := Swagger{
			FilePath: specFilePath,
			Data:     apiVersion.Spec.Data,
		}
		err = swagger.Parse("v2.0")
		if err != nil {
			return apiVersions, err
		}

		apiVersions[apiVersion.Filename] = apiVersion
	}

	return apiVersions, nil
}
<|endoftext|>


<commit_before>package mysql

import (
	"database/sql"
	"fmt"
	"math"
	"strings"

	_ "github.com/go-sql-driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/callbacks"
	"gorm.io/gorm/clause"
	"gorm.io/gorm/logger"
	"gorm.io/gorm/migrator"
	"gorm.io/gorm/schema"
)

type Config struct {
	DSN                       string
	SkipInitializeWithVersion bool
	DefaultStringSize         uint
	DisableDatetimePrecision  bool
	DontSupportRenameIndex    bool
	DontSupportRenameColumn   bool
}

type Dialector struct {
	*Config
}

func Open(dsn string) gorm.Dialector {
	return &Dialector{Config: &Config{DSN: dsn}}
}

func New(config Config) gorm.Dialector {
	return &Dialector{Config: &config}
}

func (dialector Dialector) Name() string {
	return "mysql"
}

func (dialector Dialector) Initialize(db *gorm.DB) (err error) {
	// register callbacks
	callbacks.RegisterDefaultCallbacks(db, &callbacks.Config{})
	db.ConnPool, err = sql.Open("mysql", dialector.DSN)

	if !dialector.Config.SkipInitializeWithVersion {
		var version string
		db.ConnPool.(*sql.DB).QueryRow("SELECT VERSION()").Scan(&version)

		if strings.Contains(version, "MariaDB") {
			dialector.Config.DontSupportRenameIndex = true
			dialector.Config.DontSupportRenameColumn = true
		} else if strings.HasPrefix(version, "5.6.") {
			dialector.Config.DontSupportRenameIndex = true
			dialector.Config.DontSupportRenameColumn = true
		} else if strings.HasPrefix(version, "5.7.") {
			dialector.Config.DontSupportRenameColumn = true
		} else if strings.HasPrefix(version, "5.") {
			dialector.Config.DisableDatetimePrecision = true
			dialector.Config.DontSupportRenameIndex = true
			dialector.Config.DontSupportRenameColumn = true
		}

		fmt.Printf("%#v \n", dialector.Config)
	}

	for k, v := range dialector.ClauseBuilders() {
		db.ClauseBuilders[k] = v
	}
	return
}

func (dialector Dialector) ClauseBuilders() map[string]clause.ClauseBuilder {
	return map[string]clause.ClauseBuilder{
		"ON CONFLICT": func(c clause.Clause, builder clause.Builder) {
			if onConflict, ok := c.Expression.(clause.OnConflict); ok {
				builder.WriteString("ON DUPLICATE KEY UPDATE ")
				if len(onConflict.DoUpdates) == 0 {
					if s := builder.(*gorm.Statement).Schema; s != nil {
						var column clause.Column
						onConflict.DoNothing = false

						if s.PrioritizedPrimaryField != nil {
							column = clause.Column{Name: s.PrioritizedPrimaryField.DBName}
						} else {
							for _, field := range s.FieldsByDBName {
								column = clause.Column{Name: field.DBName}
								break
							}
						}
						onConflict.DoUpdates = []clause.Assignment{{Column: column, Value: column}}
					}
				}

				for idx, assignment := range onConflict.DoUpdates {
					if idx > 0 {
						builder.WriteByte(',')
					}

					builder.WriteQuoted(assignment.Column)
					builder.WriteByte('=')
					if column, ok := assignment.Value.(clause.Column); ok && column.Table == "excluded" {
						column.Table = ""
						builder.WriteString("VALUES(")
						builder.WriteQuoted(column)
						builder.WriteByte(')')
					} else {
						builder.AddVar(builder, assignment.Value)
					}
				}
			} else {
				c.Build(builder)
			}
		},
		"VALUES": func(c clause.Clause, builder clause.Builder) {
			if values, ok := c.Expression.(clause.Values); ok && len(values.Columns) == 0 {
				builder.WriteString("VALUES()")
				return
			}
			c.Build(builder)
		},
	}
}

func (dialector Dialector) DefaultValueOf(field *schema.Field) clause.Expression {
	return clause.Expr{SQL: "DEFAULT"}
}

func (dialector Dialector) Migrator(db *gorm.DB) gorm.Migrator {
	return Migrator{
		Migrator: migrator.Migrator{
			Config: migrator.Config{
				DB:        db,
				Dialector: dialector,
			},
		},
		Dialector: dialector,
	}
}

func (dialector Dialector) BindVarTo(writer clause.Writer, stmt *gorm.Statement, v interface{}) {
	writer.WriteByte('?')
}

func (dialector Dialector) QuoteTo(writer clause.Writer, str string) {
	writer.WriteByte('`')
	writer.WriteString(str)
	writer.WriteByte('`')
}

func (dialector Dialector) Explain(sql string, vars ...interface{}) string {
	return logger.ExplainSQL(sql, nil, `"`, vars...)
}

func (dialector Dialector) DataTypeOf(field *schema.Field) string {
	switch field.DataType {
	case schema.Bool:
		return "boolean"
	case schema.Int, schema.Uint:
		sqlType := "int"
		switch {
		case field.Size <= 8:
			sqlType = "tinyint"
		case field.Size <= 16:
			sqlType = "smallint"
		case field.Size <= 32:
			sqlType = "int"
		default:
			sqlType = "bigint"
		}

		if field.DataType == schema.Uint {
			sqlType += " unsigned"
		}

		if field.AutoIncrement || field == field.Schema.PrioritizedPrimaryField {
			sqlType += " AUTO_INCREMENT"
		}
		return sqlType
	case schema.Float:
		if field.Size <= 32 {
			return "float"
		}
		return "double"
	case schema.String:
		size := field.Size
		defaultSize := dialector.DefaultStringSize

		if size == 0 {
			if defaultSize > 0 {
				size = int(defaultSize)
			} else {
				hasIndex := field.TagSettings["INDEX"] != "" || field.TagSettings["UNIQUE"] != ""
				// TEXT, GEOMETRY or JSON column can't have a default value
				if field.PrimaryKey || field.HasDefaultValue || hasIndex {
					size = 191 // utf8mb4
				}
			}
		}

		if size >= 65536 && size <= int(math.Pow(2, 24)) {
			return "mediumtext"
		} else if size > int(math.Pow(2, 24)) || size <= 0 {
			return "longtext"
		}
		return fmt.Sprintf("varchar(%d)", size)
	case schema.Time:
		precision := ""

		if !dialector.DisableDatetimePrecision {
			if field.Precision == 0 {
				field.Precision = 3
			}

			if field.Precision > 0 {
				precision = fmt.Sprintf("(%d)", field.Precision)
			}
		}

		if field.NotNull || field.PrimaryKey {
			return "datetime" + precision
		}
		return "datetime" + precision + " NULL"
	case schema.Bytes:
		if field.Size > 0 && field.Size < 65536 {
			return fmt.Sprintf("varbinary(%d)", field.Size)
		}

		if field.Size >= 65536 && field.Size <= int(math.Pow(2, 24)) {
			return "mediumblob"
		}

		return "longblob"
	}

	return string(field.DataType)
}

func (dialectopr Dialector) SavePoint(tx *gorm.DB, name string) error {
	tx.Exec("SAVEPOINT " + name)
	return nil
}

func (dialectopr Dialector) RollbackTo(tx *gorm.DB, name string) error {
	tx.Exec("ROLLBACK TO SAVEPOINT " + name)
	return nil
}
<commit_msg>Allow quote column name with table name<commit_after>package mysql

import (
	"database/sql"
	"fmt"
	"math"
	"strings"

	_ "github.com/go-sql-driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/callbacks"
	"gorm.io/gorm/clause"
	"gorm.io/gorm/logger"
	"gorm.io/gorm/migrator"
	"gorm.io/gorm/schema"
)

type Config struct {
	DSN                       string
	SkipInitializeWithVersion bool
	DefaultStringSize         uint
	DisableDatetimePrecision  bool
	DontSupportRenameIndex    bool
	DontSupportRenameColumn   bool
}

type Dialector struct {
	*Config
}

func Open(dsn string) gorm.Dialector {
	return &Dialector{Config: &Config{DSN: dsn}}
}

func New(config Config) gorm.Dialector {
	return &Dialector{Config: &config}
}

func (dialector Dialector) Name() string {
	return "mysql"
}

func (dialector Dialector) Initialize(db *gorm.DB) (err error) {
	// register callbacks
	callbacks.RegisterDefaultCallbacks(db, &callbacks.Config{})
	db.ConnPool, err = sql.Open("mysql", dialector.DSN)

	if !dialector.Config.SkipInitializeWithVersion {
		var version string
		db.ConnPool.(*sql.DB).QueryRow("SELECT VERSION()").Scan(&version)

		if strings.Contains(version, "MariaDB") {
			dialector.Config.DontSupportRenameIndex = true
			dialector.Config.DontSupportRenameColumn = true
		} else if strings.HasPrefix(version, "5.6.") {
			dialector.Config.DontSupportRenameIndex = true
			dialector.Config.DontSupportRenameColumn = true
		} else if strings.HasPrefix(version, "5.7.") {
			dialector.Config.DontSupportRenameColumn = true
		} else if strings.HasPrefix(version, "5.") {
			dialector.Config.DisableDatetimePrecision = true
			dialector.Config.DontSupportRenameIndex = true
			dialector.Config.DontSupportRenameColumn = true
		}

		fmt.Printf("%#v \n", dialector.Config)
	}

	for k, v := range dialector.ClauseBuilders() {
		db.ClauseBuilders[k] = v
	}
	return
}

func (dialector Dialector) ClauseBuilders() map[string]clause.ClauseBuilder {
	return map[string]clause.ClauseBuilder{
		"ON CONFLICT": func(c clause.Clause, builder clause.Builder) {
			if onConflict, ok := c.Expression.(clause.OnConflict); ok {
				builder.WriteString("ON DUPLICATE KEY UPDATE ")
				if len(onConflict.DoUpdates) == 0 {
					if s := builder.(*gorm.Statement).Schema; s != nil {
						var column clause.Column
						onConflict.DoNothing = false

						if s.PrioritizedPrimaryField != nil {
							column = clause.Column{Name: s.PrioritizedPrimaryField.DBName}
						} else {
							for _, field := range s.FieldsByDBName {
								column = clause.Column{Name: field.DBName}
								break
							}
						}
						onConflict.DoUpdates = []clause.Assignment{{Column: column, Value: column}}
					}
				}

				for idx, assignment := range onConflict.DoUpdates {
					if idx > 0 {
						builder.WriteByte(',')
					}

					builder.WriteQuoted(assignment.Column)
					builder.WriteByte('=')
					if column, ok := assignment.Value.(clause.Column); ok && column.Table == "excluded" {
						column.Table = ""
						builder.WriteString("VALUES(")
						builder.WriteQuoted(column)
						builder.WriteByte(')')
					} else {
						builder.AddVar(builder, assignment.Value)
					}
				}
			} else {
				c.Build(builder)
			}
		},
		"VALUES": func(c clause.Clause, builder clause.Builder) {
			if values, ok := c.Expression.(clause.Values); ok && len(values.Columns) == 0 {
				builder.WriteString("VALUES()")
				return
			}
			c.Build(builder)
		},
	}
}

func (dialector Dialector) DefaultValueOf(field *schema.Field) clause.Expression {
	return clause.Expr{SQL: "DEFAULT"}
}

func (dialector Dialector) Migrator(db *gorm.DB) gorm.Migrator {
	return Migrator{
		Migrator: migrator.Migrator{
			Config: migrator.Config{
				DB:        db,
				Dialector: dialector,
			},
		},
		Dialector: dialector,
	}
}

func (dialector Dialector) BindVarTo(writer clause.Writer, stmt *gorm.Statement, v interface{}) {
	writer.WriteByte('?')
}

func (dialector Dialector) QuoteTo(writer clause.Writer, str string) {
	writer.WriteByte('`')
	if strings.Contains(str, ".") {
		for idx, str := range strings.Split(str, ".") {
			if idx > 0 {
				writer.WriteString(".`")
			}
			writer.WriteString(str)
			writer.WriteByte('`')
		}
	} else {
		writer.WriteString(str)
		writer.WriteByte('`')
	}
}

func (dialector Dialector) Explain(sql string, vars ...interface{}) string {
	return logger.ExplainSQL(sql, nil, `"`, vars...)
}

func (dialector Dialector) DataTypeOf(field *schema.Field) string {
	switch field.DataType {
	case schema.Bool:
		return "boolean"
	case schema.Int, schema.Uint:
		sqlType := "int"
		switch {
		case field.Size <= 8:
			sqlType = "tinyint"
		case field.Size <= 16:
			sqlType = "smallint"
		case field.Size <= 32:
			sqlType = "int"
		default:
			sqlType = "bigint"
		}

		if field.DataType == schema.Uint {
			sqlType += " unsigned"
		}

		if field.AutoIncrement || field == field.Schema.PrioritizedPrimaryField {
			sqlType += " AUTO_INCREMENT"
		}
		return sqlType
	case schema.Float:
		if field.Size <= 32 {
			return "float"
		}
		return "double"
	case schema.String:
		size := field.Size
		defaultSize := dialector.DefaultStringSize

		if size == 0 {
			if defaultSize > 0 {
				size = int(defaultSize)
			} else {
				hasIndex := field.TagSettings["INDEX"] != "" || field.TagSettings["UNIQUE"] != ""
				// TEXT, GEOMETRY or JSON column can't have a default value
				if field.PrimaryKey || field.HasDefaultValue || hasIndex {
					size = 191 // utf8mb4
				}
			}
		}

		if size >= 65536 && size <= int(math.Pow(2, 24)) {
			return "mediumtext"
		} else if size > int(math.Pow(2, 24)) || size <= 0 {
			return "longtext"
		}
		return fmt.Sprintf("varchar(%d)", size)
	case schema.Time:
		precision := ""

		if !dialector.DisableDatetimePrecision {
			if field.Precision == 0 {
				field.Precision = 3
			}

			if field.Precision > 0 {
				precision = fmt.Sprintf("(%d)", field.Precision)
			}
		}

		if field.NotNull || field.PrimaryKey {
			return "datetime" + precision
		}
		return "datetime" + precision + " NULL"
	case schema.Bytes:
		if field.Size > 0 && field.Size < 65536 {
			return fmt.Sprintf("varbinary(%d)", field.Size)
		}

		if field.Size >= 65536 && field.Size <= int(math.Pow(2, 24)) {
			return "mediumblob"
		}

		return "longblob"
	}

	return string(field.DataType)
}

func (dialectopr Dialector) SavePoint(tx *gorm.DB, name string) error {
	tx.Exec("SAVEPOINT " + name)
	return nil
}

func (dialectopr Dialector) RollbackTo(tx *gorm.DB, name string) error {
	tx.Exec("ROLLBACK TO SAVEPOINT " + name)
	return nil
}
<|endoftext|>


<commit_before>package app

import (
	"fmt"
	"github.com/timeredbull/tsuru/config"
	"github.com/timeredbull/tsuru/fs"
	"github.com/timeredbull/tsuru/log"
	"io/ioutil"
	"launchpad.net/goyaml"
	"os"
	"path"
	"syscall"
	"time"
)

const (
	ChanSize    = 10
	runAttempts = 5
)

type Message struct {
	app     *App
	success chan bool
}

var env chan Message = make(chan Message, ChanSize)

var EnvironConfPath = path.Join(os.ExpandEnv("${HOME}"), ".juju", "environments.yml")

type Cmd struct {
	cmd    string
	result chan CmdResult
	u      Unit
}

type CmdResult struct {
	err    error
	output []byte
}

var cmds chan Cmd = make(chan Cmd)

func init() {
	go collectEnvVars()
	go runCommands()
}

func runCommands() {
	for cmd := range cmds {
		out, err := cmd.u.Command(cmd.cmd)
		if cmd.result != nil {
			r := CmdResult{output: out, err: err}
			cmd.result <- r
		}
	}
}

func runCmd(cmd string, msg Message) {
	c := Cmd{
		u:      *msg.app.unit(),
		cmd:    cmd,
		result: make(chan CmdResult),
	}
	cmds <- c
	var r CmdResult
	r = <-c.result
	for i := 0; r.err != nil && i < runAttempts; i++ {
		cmds <- c
		r = <-c.result
	}
	log.Printf("running %s on %s, output:\n %s", cmd, msg.app.Name, string(r.output))
	if msg.success != nil {
		msg.success <- r.err == nil
	}
}

func collectEnvVars() {
	for e := range env {
		cmd := "cat > /home/application/apprc <<END\n"
		cmd += fmt.Sprintf("# generated by tsuru at %s\n", time.Now().Format(time.RFC822Z))
		for k, v := range e.app.Env {
			cmd += fmt.Sprintf(`export %s="%s"`+"\n", k, v.Value)
		}
		cmd += "END\n"
		runCmd(cmd, e)
	}
}

type JujuEnv struct {
	AccessKey     string `yaml:"access-key"`
	SecretKey     string `yaml:"secret-key"`
	Ec2           string
	S3            string
	JujuOrigin    string
	Type          string
	AdminSecret   string
	ControlBucket string
	Series        string
	ImageId       string
	InstanceType  string
}

func newJujuEnv(access, secret string) (JujuEnv, error) {
	ec2, err := config.GetString("juju:ec2")
	if err != nil {
		return JujuEnv{}, err
	}
	s3, err := config.GetString("juju:s3")
	if err != nil {
		return JujuEnv{}, err
	}
	jujuOrigin, err := config.GetString("juju:origin")
	if err != nil {
		return JujuEnv{}, err
	}
	series, err := config.GetString("juju:series")
	if err != nil {
		return JujuEnv{}, err
	}
	imageId, err := config.GetString("juju:image-id")
	if err != nil {
		return JujuEnv{}, err
	}
	instaceType, err := config.GetString("juju:instance-type")
	if err != nil {
		return JujuEnv{}, err
	}
	return JujuEnv{
		Ec2:           ec2,
		S3:            s3,
		JujuOrigin:    jujuOrigin,
		Type:          "ec2",
		AdminSecret:   "",
		ControlBucket: "",
		Series:        series,
		ImageId:       imageId,
		InstanceType:  instaceType,
		AccessKey:     access,
		SecretKey:     secret,
	}, nil
}

func NewEnviron(name, access, secret string) error {
	envs := map[string]map[string]JujuEnv{}
	file, err := filesystem().OpenFile(EnvironConfPath, syscall.O_CREAT|syscall.O_RDWR, 0600)
	if err != nil {
		return err
	}
	defer file.Close()
	content, err := ioutil.ReadAll(file)
	if err != nil {
		return err
	}
	err = goyaml.Unmarshal(content, &envs)
	if err != nil {
		return err
	}
	if _, ok := envs["environments"]; !ok {
		envs["environments"] = map[string]JujuEnv{}
	}
	jujuEnv, err := newJujuEnv(access, secret)
	if err != nil {
		return err
	}
	envs["environments"][name] = jujuEnv
	data, err := goyaml.Marshal(&envs)
	_, err = file.Write(data)
	return err
}

var fsystem fs.Fs

func filesystem() fs.Fs {
	if fsystem == nil {
		fsystem = fs.OsFs{}
	}
	return fsystem
}
<commit_msg>specified yaml representation for jujuEnv attributes<commit_after>package app

import (
	"fmt"
	"github.com/timeredbull/tsuru/config"
	"github.com/timeredbull/tsuru/fs"
	"github.com/timeredbull/tsuru/log"
	"io/ioutil"
	"launchpad.net/goyaml"
	"os"
	"path"
	"syscall"
	"time"
)

const (
	ChanSize    = 10
	runAttempts = 5
)

type Message struct {
	app     *App
	success chan bool
}

var env chan Message = make(chan Message, ChanSize)

var EnvironConfPath = path.Join(os.ExpandEnv("${HOME}"), ".juju", "environments.yml")

type Cmd struct {
	cmd    string
	result chan CmdResult
	u      Unit
}

type CmdResult struct {
	err    error
	output []byte
}

var cmds chan Cmd = make(chan Cmd)

func init() {
	go collectEnvVars()
	go runCommands()
}

func runCommands() {
	for cmd := range cmds {
		out, err := cmd.u.Command(cmd.cmd)
		if cmd.result != nil {
			r := CmdResult{output: out, err: err}
			cmd.result <- r
		}
	}
}

func runCmd(cmd string, msg Message) {
	c := Cmd{
		u:      *msg.app.unit(),
		cmd:    cmd,
		result: make(chan CmdResult),
	}
	cmds <- c
	var r CmdResult
	r = <-c.result
	for i := 0; r.err != nil && i < runAttempts; i++ {
		cmds <- c
		r = <-c.result
	}
	log.Printf("running %s on %s, output:\n %s", cmd, msg.app.Name, string(r.output))
	if msg.success != nil {
		msg.success <- r.err == nil
	}
}

func collectEnvVars() {
	for e := range env {
		cmd := "cat > /home/application/apprc <<END\n"
		cmd += fmt.Sprintf("# generated by tsuru at %s\n", time.Now().Format(time.RFC822Z))
		for k, v := range e.app.Env {
			cmd += fmt.Sprintf(`export %s="%s"`+"\n", k, v.Value)
		}
		cmd += "END\n"
		runCmd(cmd, e)
	}
}

type JujuEnv struct {
	AccessKey     string `yaml:"access-key"`
	SecretKey     string `yaml:"secret-key"`
	Ec2           string `yaml:"ec2-uri"`
	S3            string `yaml:"s3-uri"`
	JujuOrigin    string `yaml:"juju-origin"`
	Type          string `yaml:"type"`
	AdminSecret   string `yaml:"admin-secret"`
	ControlBucket string `yaml:"control-bucket"`
	Series        string `yaml:"default-series"`
	ImageId       string `yaml:"default-image-id"`
	InstanceType  string `yaml:"default-instance-type"`
}

func newJujuEnv(access, secret string) (JujuEnv, error) {
	ec2, err := config.GetString("juju:ec2")
	if err != nil {
		return JujuEnv{}, err
	}
	s3, err := config.GetString("juju:s3")
	if err != nil {
		return JujuEnv{}, err
	}
	jujuOrigin, err := config.GetString("juju:origin")
	if err != nil {
		return JujuEnv{}, err
	}
	series, err := config.GetString("juju:series")
	if err != nil {
		return JujuEnv{}, err
	}
	imageId, err := config.GetString("juju:image-id")
	if err != nil {
		return JujuEnv{}, err
	}
	instaceType, err := config.GetString("juju:instance-type")
	if err != nil {
		return JujuEnv{}, err
	}
	return JujuEnv{
		Ec2:           ec2,
		S3:            s3,
		JujuOrigin:    jujuOrigin,
		Type:          "ec2",
		AdminSecret:   "",
		ControlBucket: "",
		Series:        series,
		ImageId:       imageId,
		InstanceType:  instaceType,
		AccessKey:     access,
		SecretKey:     secret,
	}, nil
}

func NewEnviron(name, access, secret string) error {
	envs := map[string]map[string]JujuEnv{}
	file, err := filesystem().OpenFile(EnvironConfPath, syscall.O_CREAT|syscall.O_RDWR, 0600)
	if err != nil {
		return err
	}
	defer file.Close()
	content, err := ioutil.ReadAll(file)
	if err != nil {
		return err
	}
	err = goyaml.Unmarshal(content, &envs)
	if err != nil {
		return err
	}
	if _, ok := envs["environments"]; !ok {
		envs["environments"] = map[string]JujuEnv{}
	}
	jujuEnv, err := newJujuEnv(access, secret)
	if err != nil {
		return err
	}
	envs["environments"][name] = jujuEnv
	data, err := goyaml.Marshal(&envs)
	_, err = file.Write(data)
	return err
}

var fsystem fs.Fs

func filesystem() fs.Fs {
	if fsystem == nil {
		fsystem = fs.OsFs{}
	}
	return fsystem
}
<|endoftext|>