<commit_before>package backend

//go:generate go run ../../../main.go htmlGen ../../../cmd/htmlGen/test.yaml

import (
	"expvar"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"text/template"

	"encoding/json"
	"strings"

	log "github.com/Sirupsen/logrus"
	afex "github.com/afex/hystrix-go/hystrix"
	"github.com/afex/hystrix-go/hystrix/metric_collector"
	"github.com/gorilla/handlers"
	"github.com/justinas/alice"
	"github.com/mchudgins/go-service-helper/actuator"
	gsh "github.com/mchudgins/go-service-helper/handlers"
	"github.com/mchudgins/go-service-helper/hystrix"
	"github.com/mchudgins/go-service-helper/serveSwagger"
	"github.com/mchudgins/playground/pkg/cmd/backend/htmlGen"
	"github.com/mchudgins/playground/pkg/healthz"
	"github.com/mchudgins/playground/tmp"
	"github.com/prometheus/client_golang/prometheus"
)

var (
	indexTemplate *template.Template
	html          = `
<!doctype html>

  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <title>Welcome to OpenShift</title>

This is {{.Hostname}}

Page: {{.URL}}

Handler: {{.Handler}}

`
)

func init() {
	indexTemplate = template.Must(template.New("/").Parse(html))
}

func Run(port, host string) error {
	log.Printf("backend.Run()")

	hostname, err := os.Hostname()
	if err != nil {
		log.Fatal(err)
	}
	if len(host) == 0 {
		host = hostname
	}

	// make a channel to listen on events,
	// then launch the servers.

	errc := make(chan error)

	// interrupt handler
	go func() {
		c := make(chan os.Signal, 1)
		signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
		errc <- fmt.Errorf("%s", <-c)
	}()

	// http server
	go func() {
		mux := actuator.NewActuatorMux("")

		hc, err := healthz.NewConfig()
		healthzHandler, err := healthz.Handler(hc)
		if err != nil {
			log.Panic(err)
		}

		mux.Handle("/debug/vars", expvar.Handler())
		mux.Handle("/healthz", healthzHandler)
		mux.Handle("/metrics", prometheus.Handler())

		swaggerProxy, _ := serveSwagger.NewSwaggerProxy("/swagger-ui/")
		mux.Handle("/swagger-ui/", swaggerProxy)

		mux.Handle("/swagger/",
			http.StripPrefix("/swagger/", Server))

		apiMux := http.NewServeMux()
		apiMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

			type data struct {
				Hostname string
				URL      string
				Handler  string
			}

			type echo struct {
				Message string `json:"message"`
			}

			if strings.HasPrefix(r.URL.Path, "/api/v1/echo/") {
				m := &echo{
					Message: "hello, " + r.URL.Path[len("/api/v1/echo/"):],
				}
				buf, err := json.Marshal(m)
				if err != nil {
					log.WithError(err).WithField("message", m.Message).
						Error("while serializing echo response")
					w.WriteHeader(http.StatusInternalServerError)
				} else {
					w.Header().Set("Content-Type", "application/json")
					w.Write(buf)
				}
			} else {
				err = indexTemplate.Execute(w, data{Hostname: hostname, URL: r.URL.Path, Handler: "/api/v1"})
				if err != nil {
					log.WithError(err).
						WithField("template", indexTemplate.Name()).
						WithField("path", r.URL.Path).
						Error("Unable to execute template")
				}
			}

		})
		circuitBreaker, err := hystrix.NewHystrixHelper("grpc-backend")
		if err != nil {
			log.WithError(err).
				Fatalf("Error creating circuitBreaker")
		}
		metricCollector.Registry.Register(circuitBreaker.NewPrometheusCollector)
		mux.Handle("/api/v1/", circuitBreaker.Handler(apiMux))

		mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

			type data struct {
				Hostname string
				URL      string
				Handler  string
			}

			switch r.URL.Path {
			case "/apis-explorer":
				r.URL.Path = "/apiList.html"
				htmlGen.Server.ServeHTTP(w, r)
				break

			case "/test":
				err = indexTemplate.Execute(w, data{Hostname: hostname, URL: r.URL.Path, Handler: "/"})
				if err != nil {
					log.WithError(err).
						WithField("template", indexTemplate.Name()).
						WithField("path", r.URL.Path).
						Error("Unable to execute template")
				}
				break

			default:
				if r.URL.Path == "/" {
					r.URL.Path = "/index.html"
				}

				tmp.ServeHTTPWithIndexes(w, r)
				//				status = http.StatusNotFound
				//				http.NotFound(w, r)
			}

		})

		canonical := handlers.CanonicalHost(host, http.StatusPermanentRedirect)
		var tracer func(http.Handler) http.Handler
		tracer = gsh.TracerFromHTTPRequest(gsh.NewTracer("playground"), "playground")
		chain := alice.New(tracer, gsh.HTTPMetricsCollector, gsh.HTTPLogrusLogger, canonical, VerifyIdentity).Then(mux)

		log.WithField("port", port).Info("HTTP service listening.")
		errc <- http.ListenAndServe(port, chain)
	}()

	// start the hystrix stream provider
	go func() {
		hystrixStreamHandler := afex.NewStreamHandler()
		hystrixStreamHandler.Start()
		errc <- http.ListenAndServe(":8081", hystrixStreamHandler)
	}()

	// wait for somthin'
	log.Infof("exit: %s", <-errc)

	return nil
}
<commit_msg>experimentation<commit_after>package backend

//go:generate go run ../../../main.go htmlGen ../../../cmd/htmlGen/test.yaml

import (
	"expvar"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"text/template"

	"encoding/json"
	"strings"

	log "github.com/Sirupsen/logrus"
	afex "github.com/afex/hystrix-go/hystrix"
	"github.com/afex/hystrix-go/hystrix/metric_collector"
	"github.com/gorilla/handlers"
	"github.com/justinas/alice"
	"github.com/mchudgins/go-service-helper/actuator"
	gsh "github.com/mchudgins/go-service-helper/handlers"
	"github.com/mchudgins/go-service-helper/hystrix"
	"github.com/mchudgins/go-service-helper/serveSwagger"
	"github.com/mchudgins/playground/pkg/cmd/backend/htmlGen"
	"github.com/mchudgins/playground/pkg/healthz"
	"github.com/mchudgins/playground/tmp"
	"github.com/prometheus/client_golang/prometheus"
)

var (
	indexTemplate *template.Template
	html          = `
<!doctype html>

  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <title>Welcome to OpenShift</title>

This is {{.Hostname}}

Page: {{.URL}}

Handler: {{.Handler}}

`
)

func init() {
	indexTemplate = template.Must(template.New("/").Parse(html))
}

func Run(port, host string) error {
	log.Printf("backend.Run()")

	hostname, err := os.Hostname()
	if err != nil {
		log.Fatal(err)
	}
	if len(host) == 0 {
		host = hostname
	}

	// make a channel to listen on events,
	// then launch the servers.

	errc := make(chan error)

	// interrupt handler
	go func() {
		c := make(chan os.Signal, 1)
		signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
		errc <- fmt.Errorf("%s", <-c)
	}()

	// http server
	go func() {
		mux := actuator.NewActuatorMux("")

		hc, err := healthz.NewConfig()
		healthzHandler, err := healthz.Handler(hc)
		if err != nil {
			log.Panic(err)
		}

		mux.Handle("/debug/vars", expvar.Handler())
		mux.Handle("/healthz", healthzHandler)
		mux.Handle("/metrics", prometheus.Handler())

		swaggerProxy, _ := serveSwagger.NewSwaggerProxy("/swagger-ui/")
		mux.Handle("/swagger-ui/", swaggerProxy)

		mux.Handle("/swagger/",
			http.StripPrefix("/swagger/", Server))

		apiMux := http.NewServeMux()
		apiMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

			log.Info("api called")
			logger, ok := gsh.FromContext(r.Context())
			if ok {
				logger.WithField("url", r.URL.Path).Info("api called")
			}

			type data struct {
				Hostname string
				URL      string
				Handler  string
			}

			type echo struct {
				Message string `json:"message"`
			}

			if strings.HasPrefix(r.URL.Path, "/api/v1/echo/") {
				m := &echo{
					Message: "hello, " + r.URL.Path[len("/api/v1/echo/"):],
				}
				buf, err := json.Marshal(m)
				if err != nil {
					log.WithError(err).WithField("message", m.Message).
						Error("while serializing echo response")
					w.WriteHeader(http.StatusInternalServerError)
				} else {
					w.Header().Set("Content-Type", "application/json")
					w.Write(buf)
				}
			} else {
				err = indexTemplate.Execute(w, data{Hostname: hostname, URL: r.URL.Path, Handler: "/api/v1"})
				if err != nil {
					log.WithError(err).
						WithField("template", indexTemplate.Name()).
						WithField("path", r.URL.Path).
						Error("Unable to execute template")
				}
			}

		})
		circuitBreaker, err := hystrix.NewHystrixHelper("grpc-backend")
		if err != nil {
			log.WithError(err).
				Fatalf("Error creating circuitBreaker")
		}
		metricCollector.Registry.Register(circuitBreaker.NewPrometheusCollector)
		mux.Handle("/api/v1/", circuitBreaker.Handler(apiMux))

		mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

			type data struct {
				Hostname string
				URL      string
				Handler  string
			}

			switch r.URL.Path {
			case "/apis-explorer":
				r.URL.Path = "/apiList.html"
				htmlGen.Server.ServeHTTP(w, r)
				break

			case "/test":
				err = indexTemplate.Execute(w, data{Hostname: hostname, URL: r.URL.Path, Handler: "/"})
				if err != nil {
					log.WithError(err).
						WithField("template", indexTemplate.Name()).
						WithField("path", r.URL.Path).
						Error("Unable to execute template")
				}
				break

			default:
				if r.URL.Path == "/" {
					r.URL.Path = "/index.html"
				}

				tmp.ServeHTTPWithIndexes(w, r)
				//				status = http.StatusNotFound
				//				http.NotFound(w, r)
			}

		})

		canonical := handlers.CanonicalHost(host, http.StatusPermanentRedirect)
		var tracer func(http.Handler) http.Handler
		tracer = gsh.TracerFromHTTPRequest(gsh.NewTracer("playground"), "playground")
		chain := alice.New(tracer, gsh.HTTPMetricsCollector, gsh.HTTPLogrusLogger, canonical, VerifyIdentity).Then(mux)

		log.WithField("port", port).Info("HTTP service listening.")
		errc <- http.ListenAndServe(port, chain)
	}()

	// start the hystrix stream provider
	go func() {
		hystrixStreamHandler := afex.NewStreamHandler()
		hystrixStreamHandler.Start()
		errc <- http.ListenAndServe(":8081", hystrixStreamHandler)
	}()

	// wait for somthin'
	log.Infof("exit: %s", <-errc)

	return nil
}
<|endoftext|>


<commit_before>package discovery

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

	log "github.com/Sirupsen/logrus"
	etcd "github.com/coreos/etcd/client"
	"github.com/matt-deboer/etcdcd/pkg/platform"
)

// Discovery provides correct startup details for etcd with respect to
// known vs. expected cluster membership
type Discovery struct {
	ConfigFile   string
	Platform     string
	ClientPort   int
	ServerPort   int
	ClientScheme string
	ServerScheme string
	MaxTries     int
	ProxyMode    bool
	MasterFilter string
	DryRun       bool
}

func findMemberByName(members []etcd.Member, name string) *etcd.Member {
	for _, member := range members {
		if name == member.Name {
			return &member
		}
	}
	return nil
}

func containsMember(members []etcd.Member, member etcd.Member) bool {
	for _, master := range members {
		for _, peerURL := range master.PeerURLs {
			for _, memberPeerURL := range member.PeerURLs {
				if peerURL == memberPeerURL {
					return true
				}
			}
		}
	}
	return false
}

// DiscoverEnvironment produces an environment hash
func (d *Discovery) DiscoverEnvironment() (map[string]string, error) {

	p, err := platform.Get(d.Platform, d.ConfigFile)
	if p == nil {
		if err != nil {
			return nil, err
		}
		return nil, errors.New("No such platform: " + d.Platform)
	}

	var expectedMembers []etcd.Member
	if members, err := p.ExpectedMembers(d.MasterFilter, d.ClientScheme,
		d.ClientPort, d.ServerScheme, d.ServerPort); err == nil {
		for _, m := range members {
			// have to cast here because of golang type-system--ugh!
			expectedMembers = append(expectedMembers, etcd.Member(m))
		}
	} else {
		return nil, err
	}
	if log.GetLevel() >= log.DebugLevel {
		log.Debugf("Expected cluster members: %v#", expectedMembers)
	}

	var currentMembers []etcd.Member
	ctx := context.Background()

	var membersAPI etcd.MembersAPI
	for tries := 0; tries <= d.MaxTries; tries++ {
		for _, master := range expectedMembers {

			cfg := etcd.Config{
				Endpoints: master.ClientURLs,
				Transport: etcd.DefaultTransport,
				// set timeout per request to fail fast when the target endpoint is unavailable
				HeaderTimeoutPerRequest: time.Second,
			}
			etcdClient, err := etcd.New(cfg)
			if err != nil {
				log.Warnf("Error connecting to %s [ %s ], %v", master.Name)
				continue
			}

			membersAPI := etcd.NewMembersAPI(etcdClient)
			currentMembers, err = membersAPI.List(ctx)
			if err != nil {
				log.Warnf("Error listing members %s [ %s ], %v", master.Name)
				continue
			}
			if log.GetLevel() >= log.DebugLevel {
				log.Debugf("Actual cluster members: %#v", currentMembers)
			}

			break
		}
		if len(currentMembers) == 0 {
			// TODO: what's our timeout here?
			sleepTime := (3 * time.Second)
			if log.GetLevel() >= log.DebugLevel {
				log.Debugf("Failed to resolve members; sleeping for %s", sleepTime)
			}
			time.Sleep(sleepTime)
		} else {
			break
		}
	}

	environment := map[string]string{}
	environment["ETCD_NAME"] = p.LocalInstanceName()
	environment["ETCD_INITIAL_CLUSTER"] = initialClusterString(expectedMembers)

	localMaster := findMemberByName(expectedMembers, p.LocalInstanceName())
	if localMaster != nil {
		if log.GetLevel() >= log.DebugLevel {
			log.Debugf("Local master: %#v", *localMaster)
		}
		// this instance is an expected master
		if len(currentMembers) > 0 {
			// there is an existing cluster
			d.evictBadPeers(membersAPI, expectedMembers, currentMembers)
			log.Infof("Joining existing cluster as a master")
			// TODO: what if we encounter a state where not of the expected masters are
			// members of the current cluster?
			if err := d.joinExistingCluster(membersAPI, *localMaster); err != nil {
				log.Fatal(err)
			}
			environment["ETCD_INITIAL_CLUSTER_STATE"] = "existing"
		} else {
			log.Infof("Creating a new cluster")
			environment["ETCD_INITIAL_CLUSTER_STATE"] = "new"
		}
	} else if d.ProxyMode {
		log.Infof("Proxying existing cluster")
		environment["ETCD_INITIAL_CLUSTER_STATE"] = "existing"
		environment["ETCD_PROXY"] = "on"
	} else {
		return nil, fmt.Errorf(
			"Invalid cluster configuration: localhost (%s) is not an expected master, and not in proxy mode",
			p.LocalInstanceName())
	}
	return environment, nil
}

func initialClusterString(members []etcd.Member) string {
	initialCluster := make([]string, 0, len(members))
	for _, m := range members {
		member := fmt.Sprintf("%s=%s", m.Name, m.PeerURLs[0])
		initialCluster = append(initialCluster, member)
	}
	return strings.Join(initialCluster, ",")
}

func (d *Discovery) evictBadPeers(membersAPI etcd.MembersAPI, expectedMembers []etcd.Member, currentMembers []etcd.Member) {
	for _, peer := range currentMembers {
		if !containsMember(expectedMembers, peer) {
			msg := fmt.Sprintf("Ejecting bad peer %s %v from the cluster:", peer.Name, peer.PeerURLs)
			if d.DryRun {
				log.Infof("DRY_RUN: would have ejected peer %s %v from the cluster", peer.Name, peer.PeerURLs)
			} else {
				for tries := 0; tries < d.MaxTries; tries++ {
					err := membersAPI.Remove(context.Background(), peer.ID)
					if err == nil {
						log.Infof("%s DONE", msg)
						break
					} else if (tries + 1) == d.MaxTries {
						log.Errorf("%s ERROR: %v", msg, err)
					}
				}
			}
		}
	}
}

func (d *Discovery) joinExistingCluster(membersAPI etcd.MembersAPI, localMember etcd.Member) error {
	msg := "Joining existing cluster: "
	for tries := 0; tries < d.MaxTries; tries++ {
		if d.DryRun {
			log.Infof("DRY_RUN: would have added %s %v to the cluster", localMember.Name, localMember.PeerURLs)
		} else {
			_, err := membersAPI.Add(context.Background(), localMember.PeerURLs[0])
			if err == nil {
				log.Infof("%s DONE", msg)
				break
			} else if (tries + 1) == d.MaxTries {
				log.Errorf("%s ERROR: %v", msg, err)
				return err
			}
		}
	}
	return nil
}
<commit_msg>check for sane cluster state<commit_after>package discovery

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	etcd "github.com/coreos/etcd/client"
	"github.com/matt-deboer/etcdcd/pkg/platform"
)

// Discovery provides correct startup details for etcd with respect to
// known vs. expected cluster membership
type Discovery struct {
	ConfigFile           string
	Platform             string
	ClientPort           int
	ServerPort           int
	ClientScheme         string
	ServerScheme         string
	MaxTries             int
	ProxyMode            bool
	MasterFilter         string
	DryRun               bool
	IgnoreNamingMismatch bool
}

func findMemberByName(members []etcd.Member, name string) *etcd.Member {
	for _, member := range members {
		if name == member.Name {
			return &member
		}
	}
	return nil
}

func containsMember(members []etcd.Member, member etcd.Member) bool {
	for _, m := range members {
		for _, peerURL := range m.PeerURLs {
			for _, memberPeerURL := range member.PeerURLs {
				if peerURL == memberPeerURL {
					return true
				}
			}
		}
	}
	return false
}

// DiscoverEnvironment produces an environment hash
func (d *Discovery) DiscoverEnvironment() (map[string]string, error) {

	p, err := platform.Get(d.Platform, d.ConfigFile)
	if p == nil {
		if err != nil {
			return nil, err
		}
		return nil, errors.New("No such platform: " + d.Platform)
	}

	var expectedMembers []etcd.Member
	if members, err := p.ExpectedMembers(d.MasterFilter, d.ClientScheme,
		d.ClientPort, d.ServerScheme, d.ServerPort); err == nil {
		for _, m := range members {
			// have to cast here because of golang type-system--ugh!
			expectedMembers = append(expectedMembers, etcd.Member(m))
		}
	} else {
		return nil, err
	}
	if log.GetLevel() >= log.DebugLevel {
		log.Debugf("Expected cluster members: %v#", expectedMembers)
	}

	var currentMembers []etcd.Member
	ctx := context.Background()

	var membersAPI etcd.MembersAPI
	for tries := 0; tries <= d.MaxTries; tries++ {
		for _, master := range expectedMembers {

			cfg := etcd.Config{
				Endpoints: master.ClientURLs,
				Transport: etcd.DefaultTransport,
				// set timeout per request to fail fast when the target endpoint is unavailable
				HeaderTimeoutPerRequest: time.Second,
			}
			etcdClient, err := etcd.New(cfg)
			if err != nil {
				log.Warnf("Error connecting to %s [ %s ], %v", master.Name)
				continue
			}

			membersAPI := etcd.NewMembersAPI(etcdClient)
			currentMembers, err = membersAPI.List(ctx)
			if err != nil {
				log.Warnf("Error listing members %s [ %s ], %v", master.Name)
				continue
			}
			if log.GetLevel() >= log.DebugLevel {
				log.Debugf("Actual cluster members: %#v", currentMembers)
			}

			break
		}
		if len(currentMembers) == 0 {
			// TODO: what's our timeout here?
			sleepTime := (3 * time.Second)
			if log.GetLevel() >= log.DebugLevel {
				log.Debugf("Failed to resolve members; sleeping for %s", sleepTime)
			}
			time.Sleep(sleepTime)
		} else {
			break
		}
	}

	environment := map[string]string{}
	environment["ETCD_NAME"] = p.LocalInstanceName()
	environment["ETCD_INITIAL_CLUSTER"] = initialClusterString(expectedMembers)

	localMaster := findMemberByName(expectedMembers, p.LocalInstanceName())
	if localMaster != nil {
		if log.GetLevel() >= log.DebugLevel {
			log.Debugf("Local master: %#v", *localMaster)
		}
		// this instance is an expected master
		if len(currentMembers) > 0 {
			// there is an existing cluster
			if err = d.assertSaneClusterState(expectedMembers, currentMembers); err != nil {
				log.Fatal(err)
			}

			d.evictBadPeers(membersAPI, expectedMembers, currentMembers)
			log.Infof("Joining existing cluster as a master")
			// TODO: what if we encounter a state where not of the expected masters are
			// members of the current cluster?
			if err := d.joinExistingCluster(membersAPI, *localMaster); err != nil {
				log.Fatal(err)
			}
			environment["ETCD_INITIAL_CLUSTER_STATE"] = "existing"
		} else {
			log.Infof("Creating a new cluster")
			environment["ETCD_INITIAL_CLUSTER_STATE"] = "new"
		}
	} else if d.ProxyMode {
		log.Infof("Proxying existing cluster")
		environment["ETCD_INITIAL_CLUSTER_STATE"] = "existing"
		environment["ETCD_PROXY"] = "on"
	} else {
		return nil, fmt.Errorf(
			"Invalid cluster configuration: localhost (%s) is not an expected master, and not in proxy mode",
			p.LocalInstanceName())
	}
	return environment, nil
}

func initialClusterString(members []etcd.Member) string {
	initialCluster := make([]string, 0, len(members))
	for _, m := range members {
		member := fmt.Sprintf("%s=%s", m.Name, m.PeerURLs[0])
		initialCluster = append(initialCluster, member)
	}
	return strings.Join(initialCluster, ",")
}

func (d *Discovery) assertSaneClusterState(expectedMembers []etcd.Member, currentMembers []etcd.Member) error {
	partialMatchCount := 0
	for _, current := range currentMembers {
		for _, expected := range expectedMembers {
			matchingPeerURL := ""
			for _, expectedPeerURL := range expected.PeerURLs {
				for _, currentPeerURL := range current.PeerURLs {
					if expectedPeerURL == currentPeerURL {
						matchingPeerURL = expectedPeerURL
						partialMatchCount++
						break
					}
				}
				if len(matchingPeerURL) > 0 {
					break
				}
			}
			if len(matchingPeerURL) > 0 && current.Name != expected.Name {
				if !d.IgnoreNamingMismatch {
					return fmt.Errorf("Expected peer %s with peer URL %s already exists with a different name: %s",
						expected.Name, matchingPeerURL, current.Name)
				} else if log.GetLevel() >= log.DebugLevel {
					log.Debugf("Ignoring expected peer %s with peer URL %s already exists with a different name: %s",
						expected.Name, matchingPeerURL, current.Name)
				}
			}
		}
	}
	if partialMatchCount == 0 && len(expectedMembers) > 0 && len(currentMembers) > 0 {
		expectedJSON, _ := json.Marshal(expectedMembers)
		currentJSON, _ := json.Marshal(currentMembers)
		return fmt.Errorf("Invalid cluster state: found no intersection between peer URLs of expected members %s and current members %s",
			expectedJSON, currentJSON)
	}

	return nil
}

func (d *Discovery) evictBadPeers(membersAPI etcd.MembersAPI, expectedMembers []etcd.Member, currentMembers []etcd.Member) {

	for _, peer := range currentMembers {
		if !containsMember(expectedMembers, peer) {
			msg := fmt.Sprintf("Ejecting bad peer %s %v from the cluster:", peer.Name, peer.PeerURLs)
			if d.DryRun {
				log.Infof("DRY_RUN: would have ejected peer %s %v from the cluster", peer.Name, peer.PeerURLs)
			} else {
				for tries := 0; tries < d.MaxTries; tries++ {
					err := membersAPI.Remove(context.Background(), peer.ID)
					if err == nil {
						log.Infof("%s DONE", msg)
						break
					} else if (tries + 1) == d.MaxTries {
						log.Errorf("%s ERROR: %v", msg, err)
					}
				}
			}
		}
	}
}

func (d *Discovery) joinExistingCluster(membersAPI etcd.MembersAPI, localMember etcd.Member) error {
	msg := "Joining existing cluster: "
	for tries := 0; tries < d.MaxTries; tries++ {
		if d.DryRun {
			log.Infof("DRY_RUN: would have added %s %v to the cluster", localMember.Name, localMember.PeerURLs)
		} else {
			_, err := membersAPI.Add(context.Background(), localMember.PeerURLs[0])
			if err == nil {
				log.Infof("%s DONE", msg)
				break
			} else if (tries + 1) == d.MaxTries {
				log.Errorf("%s ERROR: %v", msg, err)
				return err
			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package executor

import (
	. "github.com/smartystreets/goconvey/convey"
	"testing"
	"syscall"
	"os/exec"
)

const (
	fifoTestDirTemplate = "/tmp/swan_local_test.XXXXXXXXXXX"
	fifoTestName = "swan_fifo"
)

// TestLocal
func TestLocal(t *testing.T) {
	// Prepare unique tmp directory for the following tests.
	cmd := exec.Command("mktemp", "-d", fifoTestDirTemplate)
	// Parse unique dir output.
	dirBytes, err := cmd.Output()
	if err != nil {
		t.Fatal(err)
	}

	// Remove last element - it's newline.
	fifoDir := string(dirBytes[:len(dirBytes)-1])

	fifoPath := fifoDir + "/" + fifoTestName

	// Create fifo for the following tests.
	err = syscall.Mkfifo(fifoPath, syscall.S_IFIFO)
	if err != nil {
		t.Fatal(err)
	}

	Convey("Using Local Shell", t, func() {
		l := NewLocal()

		Convey("When command waiting for signal in fifo " +
			   "is executed and we wait for it with timeout 1ms", func() {
			task, err := l.Run("read -n 1 <" + fifoPath)

			taskNotTimeouted := task.Wait(1)

			taskState, _ := task.Status()

			Convey("The task should be still running", func() {
				So(taskState, ShouldEqual, RUNNING)
			})

			Convey("And the timeout should exceed", func() {
				So(taskNotTimeouted, ShouldBeFalse)
			})

			Convey("And error is nil", func() {
				So(err, ShouldBeNil)
			})

			task.Stop()
		})

		Convey("When command waiting for signal in fifo " +

			   "is executed and we stop it after start", func() {
			task, err := l.Run("read -n 1 <" + fifoPath)

			task.Stop()

			taskState, taskStatus := task.Status()

			Convey("The task should be not running", func() {
				So(taskState, ShouldEqual, TERMINATED)
			})

			Convey("And the exit status should be -1", func() {
				So(taskStatus.code, ShouldEqual, -1)
			})

			Convey("And error is nil", func() {
				So(err, ShouldBeNil)
			})
		})

		Convey("When command `echo output` is executed and we wait for it", func() {
			task, err := l.Execute("echo output")

			taskNotTimeouted := task.Wait(500)

			taskState, taskStatus := task.Status()

			Convey("The task should be not running", func() {
				So(taskState, ShouldEqual, TERMINATED)
			})

			Convey("And the exit status should be 0", func() {
				So(taskStatus.code, ShouldEqual, 0)
			})

			Convey("And command stdout needs to match 'output", func() {
				So(taskStatus.stdout, ShouldEqual, "output\n")
			})

			Convey("And the timeout should NOT exceed", func() {
				So(taskNotTimeouted, ShouldBeTrue)
			})

			Convey("And error is nil", func() {
				So(err, ShouldBeNil)
			})
		})

		Convey("When command which does not exists is executed and we wait for it", func() {
			task, err := l.Execute("commandThatDoesNotExists")

			taskNotTimeouted := task.Wait(500)

			taskState, taskStatus := task.Status()

			Convey("The task should be not running", func() {
				So(taskState, ShouldEqual, TERMINATED)
			})

			Convey("And the exit status should be 127", func() {
				So(taskStatus.code, ShouldEqual, 127)
			})

			Convey("And the timeout should NOT exceed", func() {
				So(taskNotTimeouted, ShouldBeTrue)
			})

			Convey("And error is nil", func() {
				So(err, ShouldBeNil)
			})
		})

		Convey("When we execute two tasks in the same time", func() {
			task, err := l.Execute("echo output1")
			task2, err2 := l.Execute("echo output2")

			task.Wait(0)
			task2.Wait(0)

			taskState1, taskStatus1 := task.Status()
			taskState2, taskStatus2 := task2.Status()

			Convey("The tasks should be not running", func() {
				So(taskState1, ShouldEqual, TERMINATED)
				So(taskState2, ShouldEqual, TERMINATED)
			})

			Convey("The commands stdouts needs to match 'output1' & 'output2'", func() {
				So(taskStatus1.stdout, ShouldEqual, "output1\n")
				So(taskStatus2.stdout, ShouldEqual, "output2\n")
			})

			Convey("Both exit statuses should be 0", func() {
				So(taskStatus1.code, ShouldEqual, 0)
				So(taskStatus2.code, ShouldEqual, 0)
			})

			Convey("And errors are nil", func() {
				So(err, ShouldBeNil)
				So(err2, ShouldBeNil)
			})
		})
	})

	//Clean up
	cmd = exec.Command("rm", "-rf", fifoDir)
	err = cmd.Run()

	if err != nil {
		t.Fatal(err)
	}
}
<commit_msg>Rebased to master.<commit_after>package executor

import (
	. "github.com/smartystreets/goconvey/convey"
	"testing"
	"syscall"
	"os/exec"
)

const (
	fifoTestDirTemplate = "/tmp/swan_local_test.XXXXXXXXXXX"
	fifoTestName = "swan_fifo"
)

// TestLocal
func TestLocal(t *testing.T) {
	// Prepare unique tmp directory for the following tests.
	cmd := exec.Command("mktemp", "-d", fifoTestDirTemplate)
	// Parse unique dir output.
	dirBytes, err := cmd.Output()
	if err != nil {
		t.Fatal(err)
	}

	// Remove last element - it's newline.
	fifoDir := string(dirBytes[:len(dirBytes)-1])

	fifoPath := fifoDir + "/" + fifoTestName

	// Create fifo for the following tests.
	err = syscall.Mkfifo(fifoPath, syscall.S_IFIFO)
	if err != nil {
		t.Fatal(err)
	}

	Convey("Using Local Shell", t, func() {
		l := NewLocal()

		Convey("When command waiting for signal in fifo " +
			   "is executed and we wait for it with timeout 1ms", func() {
			task, err := l.Execute("read -n 1 <" + fifoPath)

			taskNotTimeouted := task.Wait(1)

			taskState, _ := task.Status()

			Convey("The task should be still running", func() {
				So(taskState, ShouldEqual, RUNNING)
			})

			Convey("And the timeout should exceed", func() {
				So(taskNotTimeouted, ShouldBeFalse)
			})

			Convey("And error is nil", func() {
				So(err, ShouldBeNil)
			})

			task.Stop()
		})

		Convey("When command waiting for signal in fifo " +

			   "is executed and we stop it after start", func() {
			task, err := l.Execute("read -n 1 <" + fifoPath)

			task.Stop()

			taskState, taskStatus := task.Status()

			Convey("The task should be not running", func() {
				So(taskState, ShouldEqual, TERMINATED)
			})

			Convey("And the exit status should be -1", func() {
				So(taskStatus.code, ShouldEqual, -1)
			})

			Convey("And error is nil", func() {
				So(err, ShouldBeNil)
			})
		})

		Convey("When command `echo output` is executed and we wait for it", func() {
			task, err := l.Execute("echo output")

			taskNotTimeouted := task.Wait(500)

			taskState, taskStatus := task.Status()

			Convey("The task should be not running", func() {
				So(taskState, ShouldEqual, TERMINATED)
			})

			Convey("And the exit status should be 0", func() {
				So(taskStatus.code, ShouldEqual, 0)
			})

			Convey("And command stdout needs to match 'output", func() {
				So(taskStatus.stdout, ShouldEqual, "output\n")
			})

			Convey("And the timeout should NOT exceed", func() {
				So(taskNotTimeouted, ShouldBeTrue)
			})

			Convey("And error is nil", func() {
				So(err, ShouldBeNil)
			})
		})

		Convey("When command which does not exists is executed and we wait for it", func() {
			task, err := l.Execute("commandThatDoesNotExists")

			taskNotTimeouted := task.Wait(500)

			taskState, taskStatus := task.Status()

			Convey("The task should be not running", func() {
				So(taskState, ShouldEqual, TERMINATED)
			})

			Convey("And the exit status should be 127", func() {
				So(taskStatus.code, ShouldEqual, 127)
			})

			Convey("And the timeout should NOT exceed", func() {
				So(taskNotTimeouted, ShouldBeTrue)
			})

			Convey("And error is nil", func() {
				So(err, ShouldBeNil)
			})
		})

		Convey("When we execute two tasks in the same time", func() {
			task, err := l.Execute("echo output1")
			task2, err2 := l.Execute("echo output2")

			task.Wait(0)
			task2.Wait(0)

			taskState1, taskStatus1 := task.Status()
			taskState2, taskStatus2 := task2.Status()

			Convey("The tasks should be not running", func() {
				So(taskState1, ShouldEqual, TERMINATED)
				So(taskState2, ShouldEqual, TERMINATED)
			})

			Convey("The commands stdouts needs to match 'output1' & 'output2'", func() {
				So(taskStatus1.stdout, ShouldEqual, "output1\n")
				So(taskStatus2.stdout, ShouldEqual, "output2\n")
			})

			Convey("Both exit statuses should be 0", func() {
				So(taskStatus1.code, ShouldEqual, 0)
				So(taskStatus2.code, ShouldEqual, 0)
			})

			Convey("And errors are nil", func() {
				So(err, ShouldBeNil)
				So(err2, ShouldBeNil)
			})
		})
	})

	//Clean up
	cmd = exec.Command("rm", "-rf", fifoDir)
	err = cmd.Run()

	if err != nil {
		t.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package landscaper

import (
	"encoding/json"
	"errors"

	"github.com/Sirupsen/logrus"
	"github.com/pmezard/go-difflib/difflib"
	"google.golang.org/grpc"
	"k8s.io/helm/pkg/helm"
)

// Executor is responsible for applying a desired landscape to the actual landscape
type Executor interface {
	Apply([]*Component, []*Component) error

	CreateComponent(*Component) error
	UpdateComponent(*Component) error
	DeleteComponent(*Component) error
}

type executor struct {
	env             *Environment
	secretsProvider SecretsProvider
}

// NewExecutor is a factory method to create a new Executor
func NewExecutor(env *Environment, secretsProvider SecretsProvider) Executor {
	return &executor{
		env:             env,
		secretsProvider: secretsProvider,
	}
}

// Apply transforms the current state into the desired state
func (e *executor) Apply(desired, current []*Component) error {
	create, update, delete := diff(desired, current)

	logrus.WithFields(logrus.Fields{"create": len(create), "update": len(update), "delete": len(delete)}).Info("Apply desired state")

	if err := logDifferences(current, create, update, delete, logrus.Infof); err != nil {
		return err
	}

	for _, cmp := range delete {
		if err := e.DeleteComponent(cmp); err != nil {
			logrus.Error("DeleteComponent failed", err)
			return err
		}
	}

	for _, cmp := range create {
		if err := e.CreateComponent(cmp); err != nil {
			logrus.Error("CreateComponent failed", err)
			return err
		}
	}

	for _, cmp := range update {
		if err := e.UpdateComponent(cmp); err != nil {
			logrus.Error("UpdateComponent failed", err)
			return err
		}
	}

	logrus.WithFields(logrus.Fields{"created": len(create), "updated": len(update), "deleted": len(delete)}).Info("Applied desired state sucessfully")
	return nil
}

// CreateComponent creates the given Component
func (e *executor) CreateComponent(cmp *Component) error {
	// We need to ensure the chart is available on the local system. LoadChart will ensure
	// this is the case by downloading the chart if it is not there yet
	chartRef, err := cmp.FullChartRef()
	if err != nil {
		return err
	}
	_, chartPath, err := e.env.ChartLoader.Load(chartRef)
	if err != nil {
		return err
	}

	rawValues, err := cmp.Configuration.YAML()
	if err != nil {
		return err
	}

	logrus.WithFields(logrus.Fields{
		"release":   cmp.Name,
		"chart":     cmp.Release.Chart,
		"chartPath": chartPath,
		"values":    cmp.Configuration,
		"dryrun":    e.env.DryRun,
	}).Debug("Create component")

	if len(cmp.Secrets) > 0 && !e.env.DryRun {
		err = e.secretsProvider.Write(cmp.Name, cmp.SecretValues)
		if err != nil {
			return err
		}
	}

	_, err = e.env.HelmClient().InstallRelease(
		chartPath,
		e.env.Namespace,
		helm.ValueOverrides([]byte(rawValues)),
		helm.ReleaseName(cmp.Name),
		helm.InstallDryRun(e.env.DryRun),
		helm.InstallReuseName(true),
	)
	if err != nil {
		return errors.New(grpc.ErrorDesc(err))
	}

	return nil
}

// UpdateComponent updates the given Component
func (e *executor) UpdateComponent(cmp *Component) error {
	// We need to ensure the chart is available on the local system. LoadChart will ensure
	// this is the case by downloading the chart if it is not there yet
	chartRef, err := cmp.FullChartRef()
	if err != nil {
		return err
	}
	_, chartPath, err := e.env.ChartLoader.Load(chartRef)
	if err != nil {
		return err
	}

	rawValues, err := cmp.Configuration.YAML()
	if err != nil {
		return err
	}

	if !e.env.DryRun {
		err = e.secretsProvider.Delete(cmp.Name)

		if len(cmp.Secrets) > 0 {
			err = e.secretsProvider.Write(cmp.Name, cmp.SecretValues)
			if err != nil {
				return err
			}
		}
	}

	logrus.WithFields(logrus.Fields{
		"release":   cmp.Name,
		"chart":     cmp.Release.Chart,
		"chartPath": chartPath,
		"values":    cmp.Configuration,
		"dryrun":    e.env.DryRun,
	}).Debug("Update component")

	_, err = e.env.HelmClient().UpdateRelease(
		cmp.Name,
		chartPath,
		helm.UpdateValueOverrides([]byte(rawValues)),
		helm.UpgradeDryRun(e.env.DryRun),
	)
	if err != nil {
		return errors.New(grpc.ErrorDesc(err))
	}

	return nil
}

// DeleteComponent removes the given Component
func (e *executor) DeleteComponent(cmp *Component) error {
	logrus.WithFields(logrus.Fields{
		"release": cmp.Name,
		"values":  cmp.Configuration,
		"dryrun":  e.env.DryRun,
	}).Debug("Delete component")

	if len(cmp.Secrets) > 0 && !e.env.DryRun {
		err := e.secretsProvider.Delete(cmp.Name)
		if err != nil {
			return err
		}
	}

	_, err := e.env.HelmClient().DeleteRelease(
		cmp.Name,
		helm.DeletePurge(true),
		helm.DeleteDryRun(e.env.DryRun),
	)
	if err != nil {
		return errors.New(grpc.ErrorDesc(err))
	}

	return nil
}

// diff takes desired and current components, and returns the components to create, update and delete to get from current to desired
func diff(desired, current []*Component) (create, update, delete []*Component) {
	desiredMap := make(map[string]*Component)
	currentMap := make(map[string]*Component)

	for _, c := range desired {
		desiredMap[c.Name] = c
	}
	for _, c := range current {
		currentMap[c.Name] = c
	}

	for name, desiredCmp := range desiredMap {
		if currentCmp, ok := currentMap[name]; ok {
			if !desiredCmp.Equals(currentCmp) {
				update = append(update, desiredCmp)
			}
		} else {
			create = append(create, desiredCmp)
		}
	}

	for name, currentCmp := range currentMap {
		if _, ok := desiredMap[name]; !ok {
			delete = append(delete, currentCmp)
		}
	}

	return create, update, delete
}

// componentDiffText returns a diff as text. current and desired can be nil and indicate non-existence (e.g. current nil and desired non-nil means: create)
func componentDiffText(current, desired *Component) (string, error) {
	cText, dText := []string{}, []string{}
	cName, dName := "<none>", "<none>"
	if current != nil {
		cs, err := json.MarshalIndent(current, "", "  ")
		if err != nil {
			return "", err
		}
		cText = difflib.SplitLines(string(cs))
		cName = current.Name
	}
	if desired != nil {
		ds, err := json.MarshalIndent(desired, "", "  ")
		if err != nil {
			return "", err
		}
		dText = difflib.SplitLines(string(ds))
		dName = desired.Name
	}

	return difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
		A:        cText,
		FromFile: "Current " + cName,
		B:        dText,
		ToFile:   "Desired " + dName,
		Context:  3,
	})
}

// logDifferences logs the Create, Update and Delete w.r.t. current to logf
func logDifferences(current, creates, updates, deletes []*Component, logf func(format string, args ...interface{})) error {
	currentMap := make(map[string]*Component)
	for _, c := range current {
		currentMap[c.Name] = c
	}

	log := func(action string, current, desired *Component) error {
		diff, err := componentDiffText(current, desired)
		if err != nil {
			return err
		}
		logf("%s\n%s", action, diff)
		return nil
	}

	for _, d := range creates {
		if err := log("Create: "+d.Name, nil, d); err != nil {
			return err
		}
	}

	for _, d := range updates {
		c := currentMap[d.Name]
		if err := log("Update: "+d.Name, c, d); err != nil {
			return err
		}
	}

	for _, d := range deletes {
		logf("Delete: %s", d.Name)
	}

	return nil
}
<commit_msg>Fix dryrun for now<commit_after>package landscaper

import (
	"encoding/json"
	"errors"

	"github.com/Sirupsen/logrus"
	"github.com/pmezard/go-difflib/difflib"
	"google.golang.org/grpc"
	"k8s.io/helm/pkg/helm"
)

// Executor is responsible for applying a desired landscape to the actual landscape
type Executor interface {
	Apply([]*Component, []*Component) error

	CreateComponent(*Component) error
	UpdateComponent(*Component) error
	DeleteComponent(*Component) error
}

type executor struct {
	env             *Environment
	secretsProvider SecretsProvider
}

// NewExecutor is a factory method to create a new Executor
func NewExecutor(env *Environment, secretsProvider SecretsProvider) Executor {
	return &executor{
		env:             env,
		secretsProvider: secretsProvider,
	}
}

// Apply transforms the current state into the desired state
func (e *executor) Apply(desired, current []*Component) error {
	create, update, delete := diff(desired, current)

	logrus.WithFields(logrus.Fields{"create": len(create), "update": len(update), "delete": len(delete)}).Info("Apply desired state")

	if err := logDifferences(current, create, update, delete, logrus.Infof); err != nil {
		return err
	}

	for _, cmp := range delete {
		if err := e.DeleteComponent(cmp); err != nil {
			logrus.Error("DeleteComponent failed", err)
			return err
		}
	}

	for _, cmp := range create {
		if err := e.CreateComponent(cmp); err != nil {
			logrus.Error("CreateComponent failed", err)
			return err
		}
	}

	for _, cmp := range update {
		if err := e.UpdateComponent(cmp); err != nil {
			logrus.Error("UpdateComponent failed", err)
			return err
		}
	}

	logrus.WithFields(logrus.Fields{"created": len(create), "updated": len(update), "deleted": len(delete)}).Info("Applied desired state sucessfully")
	return nil
}

// CreateComponent creates the given Component
func (e *executor) CreateComponent(cmp *Component) error {
	// We need to ensure the chart is available on the local system. LoadChart will ensure
	// this is the case by downloading the chart if it is not there yet
	chartRef, err := cmp.FullChartRef()
	if err != nil {
		return err
	}
	_, chartPath, err := e.env.ChartLoader.Load(chartRef)
	if err != nil {
		return err
	}

	rawValues, err := cmp.Configuration.YAML()
	if err != nil {
		return err
	}

	logrus.WithFields(logrus.Fields{
		"release":   cmp.Name,
		"chart":     cmp.Release.Chart,
		"chartPath": chartPath,
		"values":    cmp.Configuration,
		"dryrun":    e.env.DryRun,
	}).Debug("Create component")

	if len(cmp.Secrets) > 0 && !e.env.DryRun {
		err = e.secretsProvider.Write(cmp.Name, cmp.SecretValues)
		if err != nil {
			return err
		}
	}

	_, err = e.env.HelmClient().InstallRelease(
		chartPath,
		e.env.Namespace,
		helm.ValueOverrides([]byte(rawValues)),
		helm.ReleaseName(cmp.Name),
		helm.InstallDryRun(e.env.DryRun),
		helm.InstallReuseName(true),
	)
	if err != nil {
		return errors.New(grpc.ErrorDesc(err))
	}

	return nil
}

// UpdateComponent updates the given Component
func (e *executor) UpdateComponent(cmp *Component) error {
	// We need to ensure the chart is available on the local system. LoadChart will ensure
	// this is the case by downloading the chart if it is not there yet
	chartRef, err := cmp.FullChartRef()
	if err != nil {
		return err
	}
	_, chartPath, err := e.env.ChartLoader.Load(chartRef)
	if err != nil {
		return err
	}

	rawValues, err := cmp.Configuration.YAML()
	if err != nil {
		return err
	}

	if !e.env.DryRun {
		err = e.secretsProvider.Delete(cmp.Name)

		if len(cmp.Secrets) > 0 {
			err = e.secretsProvider.Write(cmp.Name, cmp.SecretValues)
			if err != nil {
				return err
			}
		}
	}

	logrus.WithFields(logrus.Fields{
		"release":   cmp.Name,
		"chart":     cmp.Release.Chart,
		"chartPath": chartPath,
		"values":    cmp.Configuration,
		"dryrun":    e.env.DryRun,
	}).Debug("Update component")

	_, err = e.env.HelmClient().UpdateRelease(
		cmp.Name,
		chartPath,
		helm.UpdateValueOverrides([]byte(rawValues)),
		helm.UpgradeDryRun(e.env.DryRun),
	)
	if err != nil {
		return errors.New(grpc.ErrorDesc(err))
	}

	return nil
}

// DeleteComponent removes the given Component
func (e *executor) DeleteComponent(cmp *Component) error {
	logrus.WithFields(logrus.Fields{
		"release": cmp.Name,
		"values":  cmp.Configuration,
		"dryrun":  e.env.DryRun,
	}).Debug("Delete component")

	if len(cmp.Secrets) > 0 && !e.env.DryRun {
		err := e.secretsProvider.Delete(cmp.Name)
		if err != nil {
			return err
		}
	}

	if !e.env.DryRun {
		_, err := e.env.HelmClient().DeleteRelease(
			cmp.Name,
			helm.DeletePurge(true),
			helm.DeleteDryRun(e.env.DryRun),
		)
		if err != nil {
			return errors.New(grpc.ErrorDesc(err))
		}
	}

	return nil
}

// diff takes desired and current components, and returns the components to create, update and delete to get from current to desired
func diff(desired, current []*Component) (create, update, delete []*Component) {
	desiredMap := make(map[string]*Component)
	currentMap := make(map[string]*Component)

	for _, c := range desired {
		desiredMap[c.Name] = c
	}
	for _, c := range current {
		currentMap[c.Name] = c
	}

	for name, desiredCmp := range desiredMap {
		if currentCmp, ok := currentMap[name]; ok {
			if !desiredCmp.Equals(currentCmp) {
				update = append(update, desiredCmp)
			}
		} else {
			create = append(create, desiredCmp)
		}
	}

	for name, currentCmp := range currentMap {
		if _, ok := desiredMap[name]; !ok {
			delete = append(delete, currentCmp)
		}
	}

	return create, update, delete
}

// componentDiffText returns a diff as text. current and desired can be nil and indicate non-existence (e.g. current nil and desired non-nil means: create)
func componentDiffText(current, desired *Component) (string, error) {
	cText, dText := []string{}, []string{}
	cName, dName := "<none>", "<none>"
	if current != nil {
		cs, err := json.MarshalIndent(current, "", "  ")
		if err != nil {
			return "", err
		}
		cText = difflib.SplitLines(string(cs))
		cName = current.Name
	}
	if desired != nil {
		ds, err := json.MarshalIndent(desired, "", "  ")
		if err != nil {
			return "", err
		}
		dText = difflib.SplitLines(string(ds))
		dName = desired.Name
	}

	return difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
		A:        cText,
		FromFile: "Current " + cName,
		B:        dText,
		ToFile:   "Desired " + dName,
		Context:  3,
	})
}

// logDifferences logs the Create, Update and Delete w.r.t. current to logf
func logDifferences(current, creates, updates, deletes []*Component, logf func(format string, args ...interface{})) error {
	currentMap := make(map[string]*Component)
	for _, c := range current {
		currentMap[c.Name] = c
	}

	log := func(action string, current, desired *Component) error {
		diff, err := componentDiffText(current, desired)
		if err != nil {
			return err
		}
		logf("%s\n%s", action, diff)
		return nil
	}

	for _, d := range creates {
		if err := log("Create: "+d.Name, nil, d); err != nil {
			return err
		}
	}

	for _, d := range updates {
		c := currentMap[d.Name]
		if err := log("Update: "+d.Name, c, d); err != nil {
			return err
		}
	}

	for _, d := range deletes {
		logf("Delete: %s", d.Name)
	}

	return nil
}
<|endoftext|>


<commit_before>//
// Last.Backend LLC CONFIDENTIAL
// __________________
//
// [2014] - [2018] Last.Backend LLC
// All Rights Reserved.
//
// NOTICE:  All information contained herein is, and remains
// the property of Last.Backend LLC and its suppliers,
// if any.  The intellectual and technical concepts contained
// herein are proprietary to Last.Backend LLC
// and its suppliers and may be covered by Russian Federation and Foreign Patents,
// patents in process, and are protected by trade secret or copyright law.
// Dissemination of this information or reproduction of this material
// is strictly forbidden unless prior written permission is obtained
// from Last.Backend LLC.
//

package runtime

import (
	"context"
	"github.com/lastbackend/lastbackend/pkg/distribution/errors"
	"github.com/lastbackend/lastbackend/pkg/distribution/types"
	"github.com/lastbackend/lastbackend/pkg/log"
	"github.com/lastbackend/lastbackend/pkg/node/envs"
)

func VolumeCreate(ctx context.Context, name string, mf *types.VolumeManifest) (*types.VolumeState, error) {

	log.V(logLevel).Debugf("Create volume: %s", mf)
	if mf.Type == types.EmptyString {
		mf.Type = types.VOLUMETYPELOCAL
	}

	si, err := envs.Get().GetCSI(mf.Type)
	if err != nil {
		log.Errorf("Can-not get storage interface: %s", err)
		return nil, err
	}

	st, err := si.Create(ctx, name, mf)
	if err != nil {
		log.Errorf("Can-not get secret from api: %s", err)
		return nil, err
	}

	envs.Get().GetState().Volumes().AddVolume(name, st)

	return st, nil
}

func VolumeDestroy(ctx context.Context, name string) error {

	vol := envs.Get().GetState().Volumes().GetVolume(name)

	if vol == nil {
		return nil
	}

	if vol.Type == types.EmptyString {
		vol.Type = types.VOLUMETYPELOCAL
	}

	si, err := envs.Get().GetCSI(vol.Type)
	if err != nil {
		log.Errorf("Remove volume failed: %s", err.Error())
		return err
	}

	if err := si.Remove(ctx, vol); err != nil {
		log.Warnf("can note remove volume: %s: %s", name, err.Error())
	}

	envs.Get().GetState().Volumes().DelVolume(name)

	return nil
}

func VolumeUpdate(ctx context.Context, name string, manifest *types.VolumeManifest) (*types.VolumeState, error) {
	return nil, nil
}

func VolumeRestore(ctx context.Context) error {

	log.Debug("Start volumes restore")

	tp := envs.Get().ListCSI()

	for _, t := range tp {

		log.Debugf("restore volumes type: %s", t)
		sci, err := envs.Get().GetCSI(t)
		if err != nil {
			log.Errorf("storage interface init err: %s", err.Error())
			return err
		}

		if sci == nil {
			return errors.New("container storage runtime interface not supported")
		}

		states, err := sci.List(ctx)
		if err != nil {
			log.Errorf("volumes restore err: %s", err.Error())
			return err
		}

		for name, state := range states {
			envs.Get().GetState().Volumes().SetVolume(name, state)
		}

	}

	return nil
}

func VolumeSetSecretData (ctx context.Context, name string, secret string) error {
	return nil
}

func VolumeCheckSecretData(ctx context.Context, name string, secret string) (bool, error) {
	log.Debug("volume check secret data: %s > %s", secret, name)
	return true, nil
}

func VolumeCheckConfigData(ctx context.Context, name string, config string) (bool, error) {
	log.Debugf("volume check config data: %s > %s", config, name)

	vol := envs.Get().GetState().Volumes().GetVolume(name)
	cfg := envs.Get().GetState().Configs().GetConfig(config)

	if vol == nil {
		return  false, errors.New("volume not exists")
	}

	if vol.Type == types.EmptyString {
		vol.Type = types.VOLUMETYPELOCAL
	}

	si, err := envs.Get().GetCSI(vol.Type)
	if err != nil {
		log.Errorf("Remove volume failed: %s", err.Error())
		return false, err
	}

	return si.FilesCheck(ctx, vol, cfg.Data)
}

func VolumeSetConfigData (ctx context.Context, name string, config string) error {

	log.Debugf("set volume config data: %s > %s", config, name)

	vol := envs.Get().GetState().Volumes().GetVolume(name)
	cfg := envs.Get().GetState().Configs().GetConfig(config)

	if vol == nil {
		return errors.New("volume not exists")
	}

	if vol.Type == types.EmptyString {
		vol.Type = types.VOLUMETYPELOCAL
	}

	si, err := envs.Get().GetCSI(vol.Type)
	if err != nil {
		log.Errorf("Remove volume failed: %s", err.Error())
		return err
	}

	return si.FilesPut(ctx, vol, cfg.Data)
}
<commit_msg>fix Debug output<commit_after>//
// Last.Backend LLC CONFIDENTIAL
// __________________
//
// [2014] - [2018] Last.Backend LLC
// All Rights Reserved.
//
// NOTICE:  All information contained herein is, and remains
// the property of Last.Backend LLC and its suppliers,
// if any.  The intellectual and technical concepts contained
// herein are proprietary to Last.Backend LLC
// and its suppliers and may be covered by Russian Federation and Foreign Patents,
// patents in process, and are protected by trade secret or copyright law.
// Dissemination of this information or reproduction of this material
// is strictly forbidden unless prior written permission is obtained
// from Last.Backend LLC.
//

package runtime

import (
	"context"
	"github.com/lastbackend/lastbackend/pkg/distribution/errors"
	"github.com/lastbackend/lastbackend/pkg/distribution/types"
	"github.com/lastbackend/lastbackend/pkg/log"
	"github.com/lastbackend/lastbackend/pkg/node/envs"
)

func VolumeCreate(ctx context.Context, name string, mf *types.VolumeManifest) (*types.VolumeState, error) {

	log.V(logLevel).Debugf("Create volume: %s", mf)
	if mf.Type == types.EmptyString {
		mf.Type = types.VOLUMETYPELOCAL
	}

	si, err := envs.Get().GetCSI(mf.Type)
	if err != nil {
		log.Errorf("Can-not get storage interface: %s", err)
		return nil, err
	}

	st, err := si.Create(ctx, name, mf)
	if err != nil {
		log.Errorf("Can-not get secret from api: %s", err)
		return nil, err
	}

	envs.Get().GetState().Volumes().AddVolume(name, st)

	return st, nil
}

func VolumeDestroy(ctx context.Context, name string) error {

	vol := envs.Get().GetState().Volumes().GetVolume(name)

	if vol == nil {
		return nil
	}

	if vol.Type == types.EmptyString {
		vol.Type = types.VOLUMETYPELOCAL
	}

	si, err := envs.Get().GetCSI(vol.Type)
	if err != nil {
		log.Errorf("Remove volume failed: %s", err.Error())
		return err
	}

	if err := si.Remove(ctx, vol); err != nil {
		log.Warnf("can note remove volume: %s: %s", name, err.Error())
	}

	envs.Get().GetState().Volumes().DelVolume(name)

	return nil
}

func VolumeUpdate(ctx context.Context, name string, manifest *types.VolumeManifest) (*types.VolumeState, error) {
	return nil, nil
}

func VolumeRestore(ctx context.Context) error {

	log.Debug("Start volumes restore")

	tp := envs.Get().ListCSI()

	for _, t := range tp {

		log.Debugf("restore volumes type: %s", t)
		sci, err := envs.Get().GetCSI(t)
		if err != nil {
			log.Errorf("storage interface init err: %s", err.Error())
			return err
		}

		if sci == nil {
			return errors.New("container storage runtime interface not supported")
		}

		states, err := sci.List(ctx)
		if err != nil {
			log.Errorf("volumes restore err: %s", err.Error())
			return err
		}

		for name, state := range states {
			envs.Get().GetState().Volumes().SetVolume(name, state)
		}

	}

	return nil
}

func VolumeSetSecretData (ctx context.Context, name string, secret string) error {
	return nil
}

func VolumeCheckSecretData(ctx context.Context, name string, secret string) (bool, error) {
	log.Debugf("volume check secret data: %s > %s", secret, name)
	return true, nil
}

func VolumeCheckConfigData(ctx context.Context, name string, config string) (bool, error) {
	log.Debugf("volume check config data: %s > %s", config, name)

	vol := envs.Get().GetState().Volumes().GetVolume(name)
	cfg := envs.Get().GetState().Configs().GetConfig(config)

	if vol == nil {
		return  false, errors.New("volume not exists")
	}

	if vol.Type == types.EmptyString {
		vol.Type = types.VOLUMETYPELOCAL
	}

	si, err := envs.Get().GetCSI(vol.Type)
	if err != nil {
		log.Errorf("Remove volume failed: %s", err.Error())
		return false, err
	}

	return si.FilesCheck(ctx, vol, cfg.Data)
}

func VolumeSetConfigData (ctx context.Context, name string, config string) error {

	log.Debugf("set volume config data: %s > %s", config, name)

	vol := envs.Get().GetState().Volumes().GetVolume(name)
	cfg := envs.Get().GetState().Configs().GetConfig(config)

	if vol == nil {
		return errors.New("volume not exists")
	}

	if vol.Type == types.EmptyString {
		vol.Type = types.VOLUMETYPELOCAL
	}

	si, err := envs.Get().GetCSI(vol.Type)
	if err != nil {
		log.Errorf("Remove volume failed: %s", err.Error())
		return err
	}

	return si.FilesPut(ctx, vol, cfg.Data)
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes 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 server

import (
	"testing"

	imagedigest "github.com/opencontainers/go-digest"
	"github.com/stretchr/testify/assert"

	"github.com/kubernetes-incubator/cri-containerd/pkg/util"
)

// TestGetUserFromImage tests the logic of getting image uid or user name of image user.
func TestGetUserFromImage(t *testing.T) {
	newI64 := func(i int64) *int64 { return &i }
	for c, test := range map[string]struct {
		user string
		uid  *int64
		name string
	}{
		"no gid": {
			user: "0",
			uid:  newI64(0),
		},
		"uid/gid": {
			user: "0:1",
			uid:  newI64(0),
		},
		"empty user": {
			user: "",
		},
		"multiple spearators": {
			user: "1:2:3",
			uid:  newI64(1),
		},
		"root username": {
			user: "root:root",
			name: "root",
		},
		"username": {
			user: "test:test",
			name: "test",
		},
	} {
		t.Logf("TestCase - %q", c)
		actualUID, actualName := getUserFromImage(test.user)
		assert.Equal(t, test.uid, actualUID)
		assert.Equal(t, test.name, actualName)
	}
}

func TestGetRepoDigestAndTag(t *testing.T) {
	digest := imagedigest.Digest("sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582")
	for desc, test := range map[string]struct {
		ref                string
		schema1            bool
		expectedRepoDigest string
		expectedRepoTag    string
	}{
		"repo tag should be empty if original ref has no tag": {
			ref:                "gcr.io/library/busybox@" + digest.String(),
			expectedRepoDigest: "gcr.io/library/busybox@" + digest.String(),
		},
		"repo tag should not be empty if original ref has tag": {
			ref:                "gcr.io/library/busybox:latest",
			expectedRepoDigest: "gcr.io/library/busybox@" + digest.String(),
			expectedRepoTag:    "gcr.io/library/busybox:latest",
		},
		"repo digest should be empty if original ref is schema1 and has no digest": {
			ref:                "gcr.io/library/busybox:latest",
			schema1:            true,
			expectedRepoDigest: "",
			expectedRepoTag:    "gcr.io/library/busybox:latest",
		},
		"repo digest should not be empty if orignal ref is schema1 but has digest": {
			ref:                "gcr.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59594",
			schema1:            true,
			expectedRepoDigest: "gcr.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59594",
			expectedRepoTag:    "",
		},
	} {
		t.Logf("TestCase %q", desc)
		named, err := util.NormalizeImageRef(test.ref)
		assert.NoError(t, err)
		repoDigest, repoTag := getRepoDigestAndTag(named, digest, test.schema1)
		assert.Equal(t, test.expectedRepoDigest, repoDigest)
		assert.Equal(t, test.expectedRepoTag, repoTag)
	}
}

func TestGetCgroupsPath(t *testing.T) {
	testID := "test-id"
	for desc, test := range map[string]struct {
		cgroupsParent string
		systemdCgroup bool
		expected      string
	}{
		"should support regular cgroup path": {
			cgroupsParent: "/a/b",
			systemdCgroup: false,
			expected:      "/a/b/test-id",
		},
		"should support systemd cgroup path": {
			cgroupsParent: "/a.slice/b.slice",
			systemdCgroup: true,
			expected:      "b.slice:cri-containerd:test-id",
		},
	} {
		t.Logf("TestCase %q", desc)
		got := getCgroupsPath(test.cgroupsParent, testID, test.systemdCgroup)
		assert.Equal(t, test.expected, got)
	}
}
<commit_msg>Add simple unit test.<commit_after>/*
Copyright 2017 The Kubernetes 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 server

import (
	"testing"

	imagedigest "github.com/opencontainers/go-digest"
	"github.com/stretchr/testify/assert"

	"github.com/kubernetes-incubator/cri-containerd/pkg/util"
)

// TestGetUserFromImage tests the logic of getting image uid or user name of image user.
func TestGetUserFromImage(t *testing.T) {
	newI64 := func(i int64) *int64 { return &i }
	for c, test := range map[string]struct {
		user string
		uid  *int64
		name string
	}{
		"no gid": {
			user: "0",
			uid:  newI64(0),
		},
		"uid/gid": {
			user: "0:1",
			uid:  newI64(0),
		},
		"empty user": {
			user: "",
		},
		"multiple spearators": {
			user: "1:2:3",
			uid:  newI64(1),
		},
		"root username": {
			user: "root:root",
			name: "root",
		},
		"username": {
			user: "test:test",
			name: "test",
		},
	} {
		t.Logf("TestCase - %q", c)
		actualUID, actualName := getUserFromImage(test.user)
		assert.Equal(t, test.uid, actualUID)
		assert.Equal(t, test.name, actualName)
	}
}

func TestGetRepoDigestAndTag(t *testing.T) {
	digest := imagedigest.Digest("sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582")
	for desc, test := range map[string]struct {
		ref                string
		schema1            bool
		expectedRepoDigest string
		expectedRepoTag    string
	}{
		"repo tag should be empty if original ref has no tag": {
			ref:                "gcr.io/library/busybox@" + digest.String(),
			expectedRepoDigest: "gcr.io/library/busybox@" + digest.String(),
		},
		"repo tag should not be empty if original ref has tag": {
			ref:                "gcr.io/library/busybox:latest",
			expectedRepoDigest: "gcr.io/library/busybox@" + digest.String(),
			expectedRepoTag:    "gcr.io/library/busybox:latest",
		},
		"repo digest should be empty if original ref is schema1 and has no digest": {
			ref:                "gcr.io/library/busybox:latest",
			schema1:            true,
			expectedRepoDigest: "",
			expectedRepoTag:    "gcr.io/library/busybox:latest",
		},
		"repo digest should not be empty if orignal ref is schema1 but has digest": {
			ref:                "gcr.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59594",
			schema1:            true,
			expectedRepoDigest: "gcr.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59594",
			expectedRepoTag:    "",
		},
	} {
		t.Logf("TestCase %q", desc)
		named, err := util.NormalizeImageRef(test.ref)
		assert.NoError(t, err)
		repoDigest, repoTag := getRepoDigestAndTag(named, digest, test.schema1)
		assert.Equal(t, test.expectedRepoDigest, repoDigest)
		assert.Equal(t, test.expectedRepoTag, repoTag)
	}
}

func TestGetCgroupsPath(t *testing.T) {
	testID := "test-id"
	for desc, test := range map[string]struct {
		cgroupsParent string
		systemdCgroup bool
		expected      string
	}{
		"should support regular cgroup path": {
			cgroupsParent: "/a/b",
			systemdCgroup: false,
			expected:      "/a/b/test-id",
		},
		"should support systemd cgroup path": {
			cgroupsParent: "/a.slice/b.slice",
			systemdCgroup: true,
			expected:      "b.slice:cri-containerd:test-id",
		},
	} {
		t.Logf("TestCase %q", desc)
		got := getCgroupsPath(test.cgroupsParent, testID, test.systemdCgroup)
		assert.Equal(t, test.expected, got)
	}
}

func TestBuildLabels(t *testing.T) {
	configLabels := map[string]string{
		"a": "b",
		"c": "d",
	}
	newLabels := buildLabels(configLabels, containerKindSandbox)
	assert.Len(t, newLabels, 3)
	assert.Equal(t, "b", newLabels["a"])
	assert.Equal(t, "d", newLabels["c"])
	assert.Equal(t, containerKindSandbox, newLabels[containerKindLabel])

	newLabels["a"] = "e"
	assert.Empty(t, configLabels[containerKindLabel], "should not add new labels into original label")
	assert.Equal(t, "b", configLabels["a"], "change in new labels should not affect original label")
}
<|endoftext|>


<commit_before>/*

   Copyright 2015 CoreOS, Inc.

   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 wait

import (
	"testing"
	"time"
)

func TestWaitTime(t *testing.T) {
	wt := NewTimeList()
	ch1 := wt.Wait(time.Now())
	t1 := time.Now()
	wt.Trigger(t1)
	select {
	case <-ch1:
	case <-time.After(10 * time.Millisecond):
		t.Fatalf("cannot receive from ch as expected")
	}

	ch2 := wt.Wait(time.Now())
	t2 := time.Now()
	wt.Trigger(t1)
	select {
	case <-ch2:
		t.Fatalf("unexpected to receive from ch")
	case <-time.After(10 * time.Millisecond):
	}
	wt.Trigger(t2)
	select {
	case <-ch2:
	case <-time.After(10 * time.Millisecond):
		t.Fatalf("cannot receive from ch as expected")
	}
}

func TestWaitTestStress(t *testing.T) {
	chs := make([]<-chan struct{}, 0)
	wt := NewTimeList()
	for i := 0; i < 10000; i++ {
		chs = append(chs, wt.Wait(time.Now()))
	}
	wt.Trigger(time.Now())

	for _, ch := range chs {
		select {
		case <-ch:
		case <-time.After(10 * time.Millisecond):
			t.Fatalf("cannot receive from ch as expected")
		}
	}
}

func BenchmarkWaitTime(b *testing.B) {
	t := time.Now()
	wt := NewTimeList()
	for i := 0; i < b.N; i++ {
		wt.Wait(t)
	}
}

func BenchmarkTriggerAnd10KWaitTime(b *testing.B) {
	for i := 0; i < b.N; i++ {
		t := time.Now()
		wt := NewTimeList()
		for j := 0; j < 10000; j++ {
			wt.Wait(t)
		}
		wt.Trigger(time.Now())
	}
}
<commit_msg>pkg/wait: fix TestWaitTestStress<commit_after>/*

   Copyright 2015 CoreOS, Inc.

   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 wait

import (
	"testing"
	"time"
)

func TestWaitTime(t *testing.T) {
	wt := NewTimeList()
	ch1 := wt.Wait(time.Now())
	t1 := time.Now()
	wt.Trigger(t1)
	select {
	case <-ch1:
	case <-time.After(10 * time.Millisecond):
		t.Fatalf("cannot receive from ch as expected")
	}

	ch2 := wt.Wait(time.Now())
	t2 := time.Now()
	wt.Trigger(t1)
	select {
	case <-ch2:
		t.Fatalf("unexpected to receive from ch")
	case <-time.After(10 * time.Millisecond):
	}
	wt.Trigger(t2)
	select {
	case <-ch2:
	case <-time.After(10 * time.Millisecond):
		t.Fatalf("cannot receive from ch as expected")
	}
}

func TestWaitTestStress(t *testing.T) {
	chs := make([]<-chan struct{}, 0)
	wt := NewTimeList()
	for i := 0; i < 10000; i++ {
		chs = append(chs, wt.Wait(time.Now()))
		// sleep one nanosecond before waiting on the next event
		time.Sleep(time.Nanosecond)
	}
	wt.Trigger(time.Now())

	for _, ch := range chs {
		select {
		case <-ch:
		case <-time.After(10 * time.Millisecond):
			t.Fatalf("cannot receive from ch as expected")
		}
	}
}

func BenchmarkWaitTime(b *testing.B) {
	t := time.Now()
	wt := NewTimeList()
	for i := 0; i < b.N; i++ {
		wt.Wait(t)
	}
}

func BenchmarkTriggerAnd10KWaitTime(b *testing.B) {
	for i := 0; i < b.N; i++ {
		t := time.Now()
		wt := NewTimeList()
		for j := 0; j < 10000; j++ {
			wt.Wait(t)
		}
		wt.Trigger(time.Now())
	}
}
<|endoftext|>


<commit_before>package poller

import (
	"encoding/json"
	"github.com/zetafunction/steam-monster-game/messages"
	"github.com/zetafunction/steam-monster-game/steam"
	"log"
	"sort"
	"time"
)

// TODO: Remember where the search started previously.
const searchStart = 48308

func findGameIndex(service *steam.APIService, start int) (int, error) {
	log.Print("Searching for invalid games starting at game ", start)
	lastValid := -1
	lastInvalid := -1
	// Probe around the starting index to find a range to binary search over.
	for i, inc := start, 1; ; i, inc = i+inc, inc*2 {
		log.Print("Probing game ", i)
		result := <-service.GetGameData(i)
		if result.Err != nil {
			// TODO: This should be more robust.
			log.Print("GetGameData failed: ", result.Err)
			return 0, result.Err
		}
		switch result.Response.GetGameData().GetStatus() {
		case messages.EMiniGameStatus_k_EMiniGameStatus_Invalid:
			lastInvalid = i
			if lastValid == -1 {
				log.Print("Initial index was invalid: searching downwards!")
				inc = -1
			}
		default:
			log.Print("Saw valid game at index ", i)
			lastValid = i
		}
		if lastValid != -1 && lastInvalid != -1 {
			break
		}
	}
	log.Printf("Binary searching between valid game at %d and invalid game at %d\n", lastValid, lastInvalid)
	// Strictly speaking, a binary search is a bit dangerous because things might change.
	// Hopefully it returns close enough to the right result.
	var err error
	invalidOffset := sort.Search(lastInvalid-lastValid+1, func(i int) bool {
		// TODO: Panic?
		result := <-service.GetGameData(lastValid + i)
		if result.Err != nil {
			err = result.Err
		}
		return result.Response.GetGameData().GetStatus() == messages.EMiniGameStatus_k_EMiniGameStatus_Invalid
	})
	return lastValid + invalidOffset, err
}

type NewGameScanner struct {
	service *steam.APIService
	// The first invalid game ID. This may occasionally point to a valid game, since
	// the scanner scans 5 games ahead at a time.
	invalid int
	// If there are a lot of games in the waiting state, the new game scanner
	// sometimes has to temporarily increase the number of games to poll. The flex count
	// indicates the number of extra games that need to be polled at a given point.
	flex int

	DataUpdate        chan []byte
	InvalidGameUpdate chan int
}

func (p *NewGameScanner) Start() {
	go func() {
		for {
			if json, err := p.updateData(); err == nil {
				p.DataUpdate <- json
			} else {
				log.Print("updateData failed: ", err)
			}
			time.Sleep(time.Second)
		}
	}()
}

func (p *NewGameScanner) updateData() ([]byte, error) {
	log.Printf("Updating data (invalid: %d, flex: %d)\n", p.invalid, p.flex)
	start := p.invalid - 25 - p.flex
	end := p.invalid + 5

	type update struct {
		id     int
		result *steam.GameDataResult
	}
	c := make(chan update)
	requests := 0
	failed := 0
	for i := start; i < end; i++ {
		go func(i int) {
			result := <-p.service.GetGameData(i)
			if result.Err != nil {
				failed++
			}
			c <- update{i, result}
		}(i)
		requests++
	}
	m := make(map[int]*steam.GameDataResult)
	for requests > 0 {
		update := <-c
		m[update.id] = update.result
		requests--
	}

	type statusEntry struct {
		ID      int
		Status  string
		Players uint32
	}
	var results []statusEntry
	firstWaiting := end
	firstInvalid := p.invalid
	for i := start; i < end; i++ {
		// Sometimes, the server likes to give out 500 errors, just because...
		if m[i].Err != nil {
			results = append(results, statusEntry{i, "???????", 0})
			continue
		}
		var status string
		switch m[i].Response.GetGameData().GetStatus() {
		case messages.EMiniGameStatus_k_EMiniGameStatus_Invalid:
			if i < firstInvalid {
				firstInvalid = i
			}
			status = "invalid"
		case messages.EMiniGameStatus_k_EMiniGameStatus_Running:
			status = "running"
		case messages.EMiniGameStatus_k_EMiniGameStatus_WaitingForPlayers:
			if i < firstWaiting {
				firstWaiting = i
			}
			status = "waiting"
		case messages.EMiniGameStatus_k_EMiniGameStatus_Ended:
			status = "ended"
		}
		results = append(results, statusEntry{
			i,
			status,
			m[i].Response.GetStats().GetNumPlayers(),
		})
	}

	// Always try to have at least one actively updated non-waiting entry.
	reclaimableFlex := firstWaiting - (start + 1)
	if reclaimableFlex > 0 && p.flex > 0 {
		p.flex -= reclaimableFlex
		if p.flex < 0 {
			p.flex = 0
		}
	}
	p.flex += firstInvalid - p.invalid
	p.invalid = firstInvalid

	return json.Marshal(results)
}

func NewNewGameScanner(service *steam.APIService) (*NewGameScanner, error) {
	// TODO: This should probably be a receiver method of NewGameScanner.
	invalid, err := findGameIndex(service, searchStart)
	if err != nil {
		log.Print("findGameIndex failed: ", err)
		return nil, err
	}
	log.Print("First invalid game around ", invalid)
	p := &NewGameScanner{service, invalid, 25, make(chan []byte), make(chan int)}
	return p, nil
}
<commit_msg>Fix scan range not increasing when no invalid games are seen.<commit_after>package poller

import (
	"encoding/json"
	"github.com/zetafunction/steam-monster-game/messages"
	"github.com/zetafunction/steam-monster-game/steam"
	"log"
	"sort"
	"time"
)

// TODO: Remember where the search started previously.
const searchStart = 48308

func findGameIndex(service *steam.APIService, start int) (int, error) {
	log.Print("Searching for invalid games starting at game ", start)
	lastValid := -1
	lastInvalid := -1
	// Probe around the starting index to find a range to binary search over.
	for i, inc := start, 1; ; i, inc = i+inc, inc*2 {
		log.Print("Probing game ", i)
		result := <-service.GetGameData(i)
		if result.Err != nil {
			// TODO: This should be more robust.
			log.Print("GetGameData failed: ", result.Err)
			return 0, result.Err
		}
		switch result.Response.GetGameData().GetStatus() {
		case messages.EMiniGameStatus_k_EMiniGameStatus_Invalid:
			lastInvalid = i
			if lastValid == -1 {
				log.Print("Initial index was invalid: searching downwards!")
				inc = -1
			}
		default:
			log.Print("Saw valid game at index ", i)
			lastValid = i
		}
		if lastValid != -1 && lastInvalid != -1 {
			break
		}
	}
	log.Printf("Binary searching between valid game at %d and invalid game at %d\n", lastValid, lastInvalid)
	// Strictly speaking, a binary search is a bit dangerous because things might change.
	// Hopefully it returns close enough to the right result.
	var err error
	invalidOffset := sort.Search(lastInvalid-lastValid+1, func(i int) bool {
		// TODO: Panic?
		result := <-service.GetGameData(lastValid + i)
		if result.Err != nil {
			err = result.Err
		}
		return result.Response.GetGameData().GetStatus() == messages.EMiniGameStatus_k_EMiniGameStatus_Invalid
	})
	return lastValid + invalidOffset, err
}

type NewGameScanner struct {
	service *steam.APIService
	// The first invalid game ID. This may occasionally point to a valid game, since
	// the scanner scans 5 games ahead at a time.
	invalid int
	// If there are a lot of games in the waiting state, the new game scanner
	// sometimes has to temporarily increase the number of games to poll. The flex count
	// indicates the number of extra games that need to be polled at a given point.
	flex int

	DataUpdate        chan []byte
	InvalidGameUpdate chan int
}

func (p *NewGameScanner) Start() {
	go func() {
		for {
			if json, err := p.updateData(); err == nil {
				p.DataUpdate <- json
			} else {
				log.Print("updateData failed: ", err)
			}
			time.Sleep(time.Second)
		}
	}()
}

func (p *NewGameScanner) updateData() ([]byte, error) {
	log.Printf("Updating data (invalid: %d, flex: %d)\n", p.invalid, p.flex)
	start := p.invalid - 25 - p.flex
	end := p.invalid + 5

	type update struct {
		id     int
		result *steam.GameDataResult
	}
	c := make(chan update)
	requests := 0
	failed := 0
	for i := start; i < end; i++ {
		go func(i int) {
			result := <-p.service.GetGameData(i)
			if result.Err != nil {
				failed++
			}
			c <- update{i, result}
		}(i)
		requests++
	}
	m := make(map[int]*steam.GameDataResult)
	for requests > 0 {
		update := <-c
		m[update.id] = update.result
		requests--
	}

	type statusEntry struct {
		ID      int
		Status  string
		Players uint32
	}
	var results []statusEntry
	firstWaiting := end
	firstInvalid := end
	for i := start; i < end; i++ {
		// Sometimes, the server likes to give out 500 errors, just because...
		if m[i].Err != nil {
			results = append(results, statusEntry{i, "???????", 0})
			continue
		}
		var status string
		switch m[i].Response.GetGameData().GetStatus() {
		case messages.EMiniGameStatus_k_EMiniGameStatus_Invalid:
			if i < firstInvalid {
				firstInvalid = i
			}
			status = "invalid"
		case messages.EMiniGameStatus_k_EMiniGameStatus_Running:
			status = "running"
		case messages.EMiniGameStatus_k_EMiniGameStatus_WaitingForPlayers:
			if i < firstWaiting {
				firstWaiting = i
			}
			status = "waiting"
		case messages.EMiniGameStatus_k_EMiniGameStatus_Ended:
			status = "ended"
		}
		results = append(results, statusEntry{
			i,
			status,
			m[i].Response.GetStats().GetNumPlayers(),
		})
	}

	// Always try to have at least one actively updated non-waiting entry.
	reclaimableFlex := firstWaiting - (start + 1)
	if reclaimableFlex > 0 && p.flex > 0 {
		p.flex -= reclaimableFlex
		if p.flex < 0 {
			p.flex = 0
		}
	}
	p.flex += firstInvalid - p.invalid
	p.invalid = firstInvalid

	return json.Marshal(results)
}

func NewNewGameScanner(service *steam.APIService) (*NewGameScanner, error) {
	// TODO: This should probably be a receiver method of NewGameScanner.
	invalid, err := findGameIndex(service, searchStart)
	if err != nil {
		log.Print("findGameIndex failed: ", err)
		return nil, err
	}
	log.Print("First invalid game around ", invalid)
	p := &NewGameScanner{service, invalid, 25, make(chan []byte), make(chan int)}
	return p, nil
}
<|endoftext|>


<commit_before>/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package nwo_test

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"syscall"

	docker "github.com/fsouza/go-dockerclient"
	"github.com/hyperledger/fabric/integration/nwo"
	"github.com/hyperledger/fabric/integration/nwo/commands"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
	"github.com/tedsuo/ifrit"
	yaml "gopkg.in/yaml.v2"
)

var _ = Describe("Network", func() {
	var (
		client  *docker.Client
		tempDir string
	)

	BeforeEach(func() {
		var err error
		tempDir, err = ioutil.TempDir("", "nwo")
		Expect(err).NotTo(HaveOccurred())

		client, err = docker.NewClientFromEnv()
		Expect(err).NotTo(HaveOccurred())
	})

	AfterEach(func() {
		os.RemoveAll(tempDir)
	})

	Describe("solo network", func() {
		var network *nwo.Network
		var process ifrit.Process

		BeforeEach(func() {
			soloBytes, err := ioutil.ReadFile("solo.yaml")
			Expect(err).NotTo(HaveOccurred())

			var config *nwo.Config
			err = yaml.Unmarshal(soloBytes, &config)
			Expect(err).NotTo(HaveOccurred())

			network = nwo.New(config, tempDir, client, StartPort(), components)

			// Generate config and bootstrap the network
			network.GenerateConfigTree()
			network.Bootstrap()

			// Start all of the fabric processes
			networkRunner := network.NetworkGroupRunner()
			process = ifrit.Invoke(networkRunner)
			Eventually(process.Ready(), network.EventuallyTimeout).Should(BeClosed())
		})

		AfterEach(func() {
			// Shutodwn processes and cleanup
			process.Signal(syscall.SIGTERM)
			Eventually(process.Wait(), network.EventuallyTimeout).Should(Receive())
			network.Cleanup()
		})

		It("deploys and executes chaincode (simple)", func() {
			orderer := network.Orderer("orderer0")
			peer := network.Peer("org1", "peer2")

			chaincode := nwo.Chaincode{
				Name:    "mycc",
				Version: "0.0",
				Path:    "github.com/hyperledger/fabric/integration/chaincode/simple/cmd",
				Ctor:    `{"Args":["init","a","100","b","200"]}`,
				Policy:  `AND ('Org1ExampleCom.member','Org2ExampleCom.member')`,
			}

			network.CreateAndJoinChannels(orderer)
			nwo.DeployChaincode(network, "testchannel", orderer, chaincode)
			RunQueryInvokeQuery(network, orderer, peer)
		})

		It("deploys and executes chaincode (simple) using the _lifecycle", func() {
			orderer := network.Orderer("orderer0")
			peer := network.Peer("org1", "peer2")

			chaincode := nwo.Chaincode{
				Name:              "mycc",
				Version:           "0.0",
				Path:              "github.com/hyperledger/fabric/integration/chaincode/simple/cmd",
				Lang:              "golang",
				PackageFile:       filepath.Join(tempDir, "simplecc.tar.gz"),
				Ctor:              `{"Args":["init","a","100","b","200"]}`,
				EndorsementPlugin: "escc",
				ValidationPlugin:  "vscc",
				Policy:            `AND ('Org1ExampleCom.member','Org2ExampleCom.member')`,
				Sequence:          "1",
				InitRequired:      true,
			}

			network.CreateAndJoinChannels(orderer)

			network.UpdateChannelAnchors(orderer, "testchannel")
			network.VerifyMembership(network.PeersWithChannel("testchannel"), "testchannel")

			nwo.EnableV2_0Capabilities(network, "testchannel", orderer, network.Peer("org1", "peer1"), network.Peer("org2", "peer1"))
			nwo.DeployChaincodeNewLifecycle(network, "testchannel", orderer, chaincode)

			RunQueryInvokeQuery(network, orderer, peer)
		})
	})

	Describe("kafka network", func() {
		var (
			config    nwo.Config
			network   *nwo.Network
			processes map[string]ifrit.Process
		)

		BeforeEach(func() {
			soloBytes, err := ioutil.ReadFile("solo.yaml")
			Expect(err).NotTo(HaveOccurred())

			err = yaml.Unmarshal(soloBytes, &config)
			Expect(err).NotTo(HaveOccurred())

			// Switch from solo to kafka
			config.Consensus.Type = "kafka"
			config.Consensus.ZooKeepers = 1
			config.Consensus.Brokers = 1

			network = nwo.New(&config, tempDir, client, StartPort(), components)
			network.GenerateConfigTree()
			network.Bootstrap()
			processes = map[string]ifrit.Process{}
		})

		AfterEach(func() {
			for _, p := range processes {
				p.Signal(syscall.SIGTERM)
				Eventually(p.Wait(), network.EventuallyTimeout).Should(Receive())
			}
			network.Cleanup()
		})

		It("deploys and executes chaincode (the hard way)", func() {
			// This demonstrates how to control the processes that make up a network.
			// If you don't care about a collection of processes (like the brokers or
			// the orderers) use the group runner to manage those processes.
			zookeepers := []string{}
			for i := 0; i < network.Consensus.ZooKeepers; i++ {
				zk := network.ZooKeeperRunner(i)
				zookeepers = append(zookeepers, fmt.Sprintf("%s:2181", zk.Name))

				p := ifrit.Invoke(zk)
				processes[zk.Name] = p
				Eventually(p.Ready(), network.EventuallyTimeout).Should(BeClosed())
			}

			for i := 0; i < network.Consensus.Brokers; i++ {
				b := network.BrokerRunner(i, zookeepers)
				p := ifrit.Invoke(b)
				processes[b.Name] = p
				Eventually(p.Ready(), network.EventuallyTimeout).Should(BeClosed())
			}

			for _, o := range network.Orderers {
				or := network.OrdererRunner(o)
				p := ifrit.Invoke(or)
				processes[o.ID()] = p
				Eventually(p.Ready(), network.EventuallyTimeout).Should(BeClosed())
			}

			for _, peer := range network.Peers {
				pr := network.PeerRunner(peer)
				p := ifrit.Invoke(pr)
				processes[peer.ID()] = p
				Eventually(p.Ready(), network.EventuallyTimeout).Should(BeClosed())
			}

			orderer := network.Orderer("orderer0")
			testPeers := network.PeersWithChannel("testchannel")
			network.CreateChannel("testchannel", orderer, testPeers[0])
			network.JoinChannel("testchannel", orderer, testPeers...)

			chaincode := nwo.Chaincode{
				Name:    "mycc",
				Version: "0.0",
				Path:    "github.com/hyperledger/fabric/integration/chaincode/simple/cmd",
				Ctor:    `{"Args":["init","a","100","b","200"]}`,
				Policy:  `AND ('Org1ExampleCom.member','Org2ExampleCom.member')`,
			}
			nwo.InstallChaincode(network, chaincode, testPeers...)
			nwo.InstantiateChaincode(network, "testchannel", orderer, chaincode, testPeers[0])
			nwo.EnsureInstantiated(network, "testchannel", "mycc", "0.0", testPeers...)

			RunQueryInvokeQuery(network, orderer, testPeers[0])
		})

		It("packages and installs chaincode (the hard way) using the _lifecycle", func() {
			// This demonstrates how to control the processes that make up a network.
			// If you don't care about a collection of processes (like the brokers or
			// the orderers) use the group runner to manage those processes.
			zookeepers := []string{}
			for i := 0; i < network.Consensus.ZooKeepers; i++ {
				zk := network.ZooKeeperRunner(i)
				zookeepers = append(zookeepers, fmt.Sprintf("%s:2181", zk.Name))

				p := ifrit.Invoke(zk)
				processes[zk.Name] = p
				Eventually(p.Ready(), network.EventuallyTimeout).Should(BeClosed())
			}

			for i := 0; i < network.Consensus.Brokers; i++ {
				b := network.BrokerRunner(i, zookeepers)
				p := ifrit.Invoke(b)
				processes[b.Name] = p
				Eventually(p.Ready(), network.EventuallyTimeout).Should(BeClosed())
			}

			for _, o := range network.Orderers {
				or := network.OrdererRunner(o)
				p := ifrit.Invoke(or)
				processes[o.ID()] = p
				Eventually(p.Ready(), network.EventuallyTimeout).Should(BeClosed())
			}

			for _, peer := range network.Peers {
				pr := network.PeerRunner(peer)
				p := ifrit.Invoke(pr)
				processes[peer.ID()] = p
				Eventually(p.Ready(), network.EventuallyTimeout).Should(BeClosed())
			}

			orderer := network.Orderer("orderer0")
			testPeers := network.PeersWithChannel("testchannel")
			network.CreateChannel("testchannel", orderer, testPeers[0])
			network.JoinChannel("testchannel", orderer, testPeers...)

			network.UpdateChannelAnchors(orderer, "testchannel")
			network.VerifyMembership(testPeers, "testchannel")

			nwo.EnableV2_0Capabilities(network, "testchannel", orderer, network.Peer("org1", "peer1"), network.Peer("org2", "peer1"))

			chaincode := nwo.Chaincode{
				Name:              "mycc",
				Version:           "0.0",
				Path:              "github.com/hyperledger/fabric/integration/chaincode/simple/cmd",
				Lang:              "golang",
				PackageFile:       filepath.Join(tempDir, "simplecc.tar.gz"),
				Ctor:              `{"Args":["init","a","100","b","200"]}`,
				EndorsementPlugin: "escc",
				ValidationPlugin:  "vscc",
				Policy:            `AND ('Org1ExampleCom.member','Org2ExampleCom.member')`,
				Sequence:          "1",
				InitRequired:      true,
			}
			nwo.PackageChaincodeNewLifecycle(network, chaincode, testPeers[0])
			nwo.InstallChaincodeNewLifecycle(network, chaincode, testPeers...)
			maxLedgerHeight := nwo.GetMaxLedgerHeight(network, "testchannel", testPeers...)
			for _, org := range network.PeerOrgs() {
				nwo.ApproveChaincodeForMyOrgNewLifecycle(network, "testchannel", orderer, chaincode, network.PeersInOrg(org.Name)...)
			}
			// wait for all peers to have same ledger height (to ensure the
			// ApproveChaincodeDefinitionForMyOrg blocks have been gossiped
			// to the other peers in each org
			nwo.WaitUntilEqualLedgerHeight(network, "testchannel", maxLedgerHeight+len(network.PeerOrgs()), testPeers...)

			nwo.CommitChaincodeNewLifecycle(network, "testchannel", orderer, chaincode, testPeers[0], testPeers...)
			nwo.InitChaincodeNewLifecycle(network, "testchannel", orderer, chaincode, testPeers...)

			RunQueryInvokeQuery(network, orderer, testPeers[0])
		})
	})
})

func RunQueryInvokeQuery(n *nwo.Network, orderer *nwo.Orderer, peer *nwo.Peer) {
	By("querying the chaincode")
	sess, err := n.PeerUserSession(peer, "User1", commands.ChaincodeQuery{
		ChannelID: "testchannel",
		Name:      "mycc",
		Ctor:      `{"Args":["query","a"]}`,
	})
	Expect(err).NotTo(HaveOccurred())
	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
	Expect(sess).To(gbytes.Say("100"))

	sess, err = n.PeerUserSession(peer, "User1", commands.ChaincodeInvoke{
		ChannelID: "testchannel",
		Orderer:   n.OrdererAddress(orderer, nwo.ListenPort),
		Name:      "mycc",
		Ctor:      `{"Args":["invoke","a","b","10"]}`,
		PeerAddresses: []string{
			n.PeerAddress(n.Peer("org1", "peer1"), nwo.ListenPort),
			n.PeerAddress(n.Peer("org2", "peer2"), nwo.ListenPort),
		},
		WaitForEvent: true,
	})
	Expect(err).NotTo(HaveOccurred())
	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
	Expect(sess.Err).To(gbytes.Say("Chaincode invoke successful. result: status:200"))

	sess, err = n.PeerUserSession(peer, "User1", commands.ChaincodeQuery{
		ChannelID: "testchannel",
		Name:      "mycc",
		Ctor:      `{"Args":["query","a"]}`,
	})
	Expect(err).NotTo(HaveOccurred())
	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
	Expect(sess).To(gbytes.Say("90"))
}
<commit_msg>Add _lifecycle chaincode upgrade to integration tests<commit_after>/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package nwo_test

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"syscall"

	docker "github.com/fsouza/go-dockerclient"
	"github.com/hyperledger/fabric/integration/nwo"
	"github.com/hyperledger/fabric/integration/nwo/commands"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
	"github.com/tedsuo/ifrit"
	yaml "gopkg.in/yaml.v2"
)

var _ = Describe("Network", func() {
	var (
		client  *docker.Client
		tempDir string
	)

	BeforeEach(func() {
		var err error
		tempDir, err = ioutil.TempDir("", "nwo")
		Expect(err).NotTo(HaveOccurred())

		client, err = docker.NewClientFromEnv()
		Expect(err).NotTo(HaveOccurred())
	})

	AfterEach(func() {
		os.RemoveAll(tempDir)
	})

	Describe("solo network", func() {
		var network *nwo.Network
		var process ifrit.Process

		BeforeEach(func() {
			soloBytes, err := ioutil.ReadFile("solo.yaml")
			Expect(err).NotTo(HaveOccurred())

			var config *nwo.Config
			err = yaml.Unmarshal(soloBytes, &config)
			Expect(err).NotTo(HaveOccurred())

			network = nwo.New(config, tempDir, client, StartPort(), components)

			// Generate config and bootstrap the network
			network.GenerateConfigTree()
			network.Bootstrap()

			// Start all of the fabric processes
			networkRunner := network.NetworkGroupRunner()
			process = ifrit.Invoke(networkRunner)
			Eventually(process.Ready(), network.EventuallyTimeout).Should(BeClosed())
		})

		AfterEach(func() {
			// Shutodwn processes and cleanup
			process.Signal(syscall.SIGTERM)
			Eventually(process.Wait(), network.EventuallyTimeout).Should(Receive())
			network.Cleanup()
		})

		It("deploys and executes chaincode (simple)", func() {
			orderer := network.Orderer("orderer0")
			peer := network.Peer("org1", "peer2")

			chaincode := nwo.Chaincode{
				Name:    "mycc",
				Version: "0.0",
				Path:    "github.com/hyperledger/fabric/integration/chaincode/simple/cmd",
				Ctor:    `{"Args":["init","a","100","b","200"]}`,
				Policy:  `AND ('Org1ExampleCom.member','Org2ExampleCom.member')`,
			}

			network.CreateAndJoinChannels(orderer)
			nwo.DeployChaincode(network, "testchannel", orderer, chaincode)
			RunQueryInvokeQuery(network, orderer, peer, 100)
		})

		It("deploys and executes chaincode (simple) using _lifecycle", func() {
			orderer := network.Orderer("orderer0")
			peer := network.Peer("org1", "peer2")

			chaincode := nwo.Chaincode{
				Name:              "mycc",
				Version:           "0.0",
				Path:              "github.com/hyperledger/fabric/integration/chaincode/simple/cmd",
				Lang:              "golang",
				PackageFile:       filepath.Join(tempDir, "simplecc.tar.gz"),
				Ctor:              `{"Args":["init","a","100","b","200"]}`,
				EndorsementPlugin: "escc",
				ValidationPlugin:  "vscc",
				Policy:            `AND ('Org1ExampleCom.member','Org2ExampleCom.member')`,
				Sequence:          "1",
				InitRequired:      true,
			}

			network.CreateAndJoinChannels(orderer)

			network.UpdateChannelAnchors(orderer, "testchannel")
			network.VerifyMembership(network.PeersWithChannel("testchannel"), "testchannel")

			nwo.EnableV2_0Capabilities(network, "testchannel", orderer, network.Peer("org1", "peer1"), network.Peer("org2", "peer1"))
			nwo.DeployChaincodeNewLifecycle(network, "testchannel", orderer, chaincode)

			RunQueryInvokeQuery(network, orderer, peer, 100)
		})
	})

	Describe("kafka network", func() {
		var (
			config    nwo.Config
			network   *nwo.Network
			processes map[string]ifrit.Process
		)

		BeforeEach(func() {
			soloBytes, err := ioutil.ReadFile("solo.yaml")
			Expect(err).NotTo(HaveOccurred())

			err = yaml.Unmarshal(soloBytes, &config)
			Expect(err).NotTo(HaveOccurred())

			// Switch from solo to kafka
			config.Consensus.Type = "kafka"
			config.Consensus.ZooKeepers = 1
			config.Consensus.Brokers = 1

			network = nwo.New(&config, tempDir, client, StartPort(), components)
			network.GenerateConfigTree()
			network.Bootstrap()
			processes = map[string]ifrit.Process{}
		})

		AfterEach(func() {
			for _, p := range processes {
				p.Signal(syscall.SIGTERM)
				Eventually(p.Wait(), network.EventuallyTimeout).Should(Receive())
			}
			network.Cleanup()
		})

		It("deploys and executes chaincode (the hard way)", func() {
			// This demonstrates how to control the processes that make up a network.
			// If you don't care about a collection of processes (like the brokers or
			// the orderers) use the group runner to manage those processes.
			zookeepers := []string{}
			for i := 0; i < network.Consensus.ZooKeepers; i++ {
				zk := network.ZooKeeperRunner(i)
				zookeepers = append(zookeepers, fmt.Sprintf("%s:2181", zk.Name))

				p := ifrit.Invoke(zk)
				processes[zk.Name] = p
				Eventually(p.Ready(), network.EventuallyTimeout).Should(BeClosed())
			}

			for i := 0; i < network.Consensus.Brokers; i++ {
				b := network.BrokerRunner(i, zookeepers)
				p := ifrit.Invoke(b)
				processes[b.Name] = p
				Eventually(p.Ready(), network.EventuallyTimeout).Should(BeClosed())
			}

			for _, o := range network.Orderers {
				or := network.OrdererRunner(o)
				p := ifrit.Invoke(or)
				processes[o.ID()] = p
				Eventually(p.Ready(), network.EventuallyTimeout).Should(BeClosed())
			}

			for _, peer := range network.Peers {
				pr := network.PeerRunner(peer)
				p := ifrit.Invoke(pr)
				processes[peer.ID()] = p
				Eventually(p.Ready(), network.EventuallyTimeout).Should(BeClosed())
			}

			orderer := network.Orderer("orderer0")
			testPeers := network.PeersWithChannel("testchannel")
			network.CreateChannel("testchannel", orderer, testPeers[0])
			network.JoinChannel("testchannel", orderer, testPeers...)

			chaincode := nwo.Chaincode{
				Name:    "mycc",
				Version: "0.0",
				Path:    "github.com/hyperledger/fabric/integration/chaincode/simple/cmd",
				Ctor:    `{"Args":["init","a","100","b","200"]}`,
				Policy:  `AND ('Org1ExampleCom.member','Org2ExampleCom.member')`,
			}
			nwo.InstallChaincode(network, chaincode, testPeers...)
			nwo.InstantiateChaincode(network, "testchannel", orderer, chaincode, testPeers[0])
			nwo.EnsureInstantiated(network, "testchannel", "mycc", "0.0", testPeers...)

			RunQueryInvokeQuery(network, orderer, testPeers[0], 100)
		})

		It("packages and installs chaincode (the hard way) using _lifecycle and then upgrades it", func() {
			// This demonstrates how to control the processes that make up a network.
			// If you don't care about a collection of processes (like the brokers or
			// the orderers) use the group runner to manage those processes.
			zookeepers := []string{}
			for i := 0; i < network.Consensus.ZooKeepers; i++ {
				zk := network.ZooKeeperRunner(i)
				zookeepers = append(zookeepers, fmt.Sprintf("%s:2181", zk.Name))

				p := ifrit.Invoke(zk)
				processes[zk.Name] = p
				Eventually(p.Ready(), network.EventuallyTimeout).Should(BeClosed())
			}

			for i := 0; i < network.Consensus.Brokers; i++ {
				b := network.BrokerRunner(i, zookeepers)
				p := ifrit.Invoke(b)
				processes[b.Name] = p
				Eventually(p.Ready(), network.EventuallyTimeout).Should(BeClosed())
			}

			for _, o := range network.Orderers {
				or := network.OrdererRunner(o)
				p := ifrit.Invoke(or)
				processes[o.ID()] = p
				Eventually(p.Ready(), network.EventuallyTimeout).Should(BeClosed())
			}

			for _, peer := range network.Peers {
				pr := network.PeerRunner(peer)
				p := ifrit.Invoke(pr)
				processes[peer.ID()] = p
				Eventually(p.Ready(), network.EventuallyTimeout).Should(BeClosed())
			}

			orderer := network.Orderer("orderer0")
			testPeers := network.PeersWithChannel("testchannel")
			network.CreateChannel("testchannel", orderer, testPeers[0])
			network.JoinChannel("testchannel", orderer, testPeers...)

			network.UpdateChannelAnchors(orderer, "testchannel")
			network.VerifyMembership(testPeers, "testchannel")

			nwo.EnableV2_0Capabilities(network, "testchannel", orderer, network.Peer("org1", "peer1"), network.Peer("org2", "peer1"))

			chaincode := nwo.Chaincode{
				Name:              "mycc",
				Version:           "0.0",
				Path:              "github.com/hyperledger/fabric/integration/chaincode/simple/cmd",
				Lang:              "golang",
				PackageFile:       filepath.Join(tempDir, "simplecc.tar.gz"),
				Ctor:              `{"Args":["init","a","100","b","200"]}`,
				EndorsementPlugin: "escc",
				ValidationPlugin:  "vscc",
				Policy:            `AND ('Org1ExampleCom.member','Org2ExampleCom.member')`,
				Sequence:          "1",
				InitRequired:      true,
			}
			nwo.PackageChaincodeNewLifecycle(network, chaincode, testPeers[0])
			nwo.InstallChaincodeNewLifecycle(network, chaincode, testPeers...)
			maxLedgerHeight := nwo.GetMaxLedgerHeight(network, "testchannel", testPeers...)
			for _, org := range network.PeerOrgs() {
				nwo.ApproveChaincodeForMyOrgNewLifecycle(network, "testchannel", orderer, chaincode, network.PeersInOrg(org.Name)...)
			}
			// wait for all peers to have same ledger height (to ensure the
			// ApproveChaincodeDefinitionForMyOrg blocks have been gossiped
			// to the other peers in each org
			nwo.WaitUntilEqualLedgerHeight(network, "testchannel", maxLedgerHeight+len(network.PeerOrgs()), testPeers...)

			nwo.CommitChaincodeNewLifecycle(network, "testchannel", orderer, chaincode, testPeers[0], testPeers...)
			nwo.InitChaincodeNewLifecycle(network, "testchannel", orderer, chaincode, testPeers...)

			RunQueryInvokeQuery(network, orderer, testPeers[0], 100)

			// upgrade chaincode to sequence 2
			chaincode.Sequence = "2"
			maxLedgerHeight = nwo.GetMaxLedgerHeight(network, "testchannel", testPeers...)
			for _, org := range network.PeerOrgs() {
				nwo.ApproveChaincodeForMyOrgNewLifecycle(network, "testchannel", orderer, chaincode, network.PeersInOrg(org.Name)...)
			}

			nwo.WaitUntilEqualLedgerHeight(network, "testchannel", maxLedgerHeight+len(network.PeerOrgs()), testPeers...)

			nwo.CommitChaincodeNewLifecycle(network, "testchannel", orderer, chaincode, testPeers[0], testPeers...)

			RunQueryInvokeQuery(network, orderer, testPeers[0], 90)
		})
	})
})

func RunQueryInvokeQuery(n *nwo.Network, orderer *nwo.Orderer, peer *nwo.Peer, initialQueryResult int) {
	By("querying the chaincode")
	sess, err := n.PeerUserSession(peer, "User1", commands.ChaincodeQuery{
		ChannelID: "testchannel",
		Name:      "mycc",
		Ctor:      `{"Args":["query","a"]}`,
	})
	Expect(err).NotTo(HaveOccurred())
	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
	Expect(sess).To(gbytes.Say(fmt.Sprint(initialQueryResult)))

	sess, err = n.PeerUserSession(peer, "User1", commands.ChaincodeInvoke{
		ChannelID: "testchannel",
		Orderer:   n.OrdererAddress(orderer, nwo.ListenPort),
		Name:      "mycc",
		Ctor:      `{"Args":["invoke","a","b","10"]}`,
		PeerAddresses: []string{
			n.PeerAddress(n.Peer("org1", "peer1"), nwo.ListenPort),
			n.PeerAddress(n.Peer("org2", "peer2"), nwo.ListenPort),
		},
		WaitForEvent: true,
	})
	Expect(err).NotTo(HaveOccurred())
	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
	Expect(sess.Err).To(gbytes.Say("Chaincode invoke successful. result: status:200"))

	sess, err = n.PeerUserSession(peer, "User1", commands.ChaincodeQuery{
		ChannelID: "testchannel",
		Name:      "mycc",
		Ctor:      `{"Args":["query","a"]}`,
	})
	Expect(err).NotTo(HaveOccurred())
	Eventually(sess, n.EventuallyTimeout).Should(gexec.Exit(0))
	Expect(sess).To(gbytes.Say(fmt.Sprint(initialQueryResult - 10)))
}
<|endoftext|>