<commit_before>package accesslog

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
	"unicode"
)

// optFunc is a type of function that can add options to the option struct during initialization
type optFunc func(*opt)

// opt is the struct that holds the options for logging.
type opt struct {
	Out  io.Writer
	Time time.Time
}

// newOpt returns a new struct to hold options, with the default output to stdout.
func newOpt() *opt {
	o := new(opt)
	o.Out = os.Stdout
	return o
}

// WithOutput sets the io.Writer output for the log file.
func WithOutput(out io.Writer) optFunc {
	return func(o *opt) {
		o.Out = out
	}
}

// alog is the internal struct that will hold the status and number of bytes written
type alog struct {
	http.ResponseWriter

	status int
	wLen   int

	start time.Time
}

// WriteHeader intercepts the http.ResponseWriter WriteHeader method so we can save the status to display later
func (a *alog) WriteHeader(i int) {
	if a.status == 0 {
		a.status = i
	}
	a.ResponseWriter.WriteHeader(i)
}

// Write intercepts the http.ResponseWriter Write method so we can capture the bytes written
func (a *alog) Write(p []byte) (n int, err error) {
	if a.status == 0 {
		a.status = http.StatusOK
	}
	n, err = a.ResponseWriter.Write(p)
	a.wLen += n
	return
}

// startTime sets the start time to calculate the elapsed time for the %D directive
func (a *alog) startTime() {
	a.start = time.Now()
}

const (
	ApacheCommonLogFormat   = "%h %l %u %t \"%r\" %>s %b"                                    // The Common Log directives
	ApacheCombinedLogFormat = "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" // The Combined Log directives
)

// ApacheCommonLog will log HTTP requests using the Apache Common Log format
var ApacheCommonLog = Format(ApacheCommonLogFormat)

// ApacheCombinedLog will log HTTP requests using the Apache Combined Log format
var ApacheCombinedLog = Format(ApacheCombinedLogFormat)

// convertTimeFormat converts strftime formatting directives to a go time.Time format
func convertTimeFormat(t time.Time, s string) string {
	m := map[rune]string{
		'a': "Mon",
		'A': "Monday",
		'b': "Jan",
		'B': "January",
		'c': "?",
		'C': "06",
		'd': "02",
		'D': "01/02/06",
		'e': "_2",
		'E': "?",
		'F': "2006-01-02",
		'G': "%d",
		'g': "%s",
		'h': "Jan",
		'H': "15",
		'I': "3",
		'j': "%d",
		'k': "•15",
		'l': "_3",
		'm': "01",
		'M': "04",
		'n': "\n",
		'O': "?",
		'p': "PM",
		'P': "pm",
		'r': "03:04:05 PM",
		'R': "15:04",
		's': "%d",
		'S': "05",
		't': "\t",
		'T': "15:04:05",
		'u': "%d",
		'U': "?",
		'V': "%d",
		'w': "%d",
		'W': "?",
		'x': "?",
		'X': "?",
		'y': "06",
		'Y': "2006",
		'z': "-700",
		'Z': "MST",
		'+': "?",
		'%': "%%",
	}

	var i []interface{}
	var x bool
	buf := new(bytes.Buffer)
	for _, r := range s {
		if r == '%' {
			x = true
			continue
		}
		if x {
			x = false
			if val, ok := m[r]; ok {
				if val == "%s" || val == "%d" {
					switch r {
					case 'G':
						y, _ := t.ISOWeek()
						i = append(i, y)
					case 'g':
						y, _ := t.ISOWeek()
						i = append(i, strconv.Itoa(y)[2:])
					case 'j':
						i = append(i, t.YearDay())
					case 's':
						i = append(i, t.Unix())
					case 'u':
						w := t.Weekday()
						if w == 0 {
							w = 7
						}
						i = append(i, w)
					case 'V':
						_, w := t.ISOWeek()
						i = append(i, w)
					case 'w':
						i = append(i, t.Weekday())
					}
				}
				buf.WriteString(val)
				continue
			}
			buf.WriteRune('%')
		}
		buf.WriteRune(r)
	}
	f := t.Format(buf.String())
	if len(i) > 0 {
		f = fmt.Sprintf(f, i...)
	}
	buf.Reset()
	return f
}

// logfmt the type that will hold all of the runtime formating
type logfmt struct {
	ti time.Time
	rq *http.Request
	al *alog

	h, u, t, q, e, ws, wl string
}

func (lf *logfmt) withTime(o *opt) *logfmt {
	if !o.Time.IsZero() {
		lf.ti = o.Time
		return lf
	}
	lf.ti = time.Now()
	return lf
}

func (lf *logfmt) withRequest(r *http.Request) *logfmt {
	lf.rq = r
	return lf
}

func (lf *logfmt) withResponse(a *alog) *logfmt {
	lf.al = a
	return lf
}

func (lf *logfmt) timeFormatted(format string) string {
	if len(lf.t) == 0 {
		lf.t = lf.ti.Format(format)
	}
	return lf.t
}

func (lf *logfmt) remoteHostname() string {
	if len(lf.h) == 0 {
		lf.h = lf.rq.URL.Host
		if len(lf.h) == 0 {
			lf.h = "127.0.0.1"
		}
	}
	return lf.h
}

func (lf *logfmt) username() string {
	if len(lf.u) == 0 {
		lf.u = "-"
		if s := strings.SplitN(lf.rq.Header.Get("Authorization"), " ", 2); len(s) == 2 {
			if b, err := base64.StdEncoding.DecodeString(s[1]); err == nil {
				if pair := strings.SplitN(string(b), ":", 2); len(pair) == 2 {
					lf.u = pair[0]
				}
			}
		}
	}
	return lf.u
}

func (lf *logfmt) requestLine() string {
	if len(lf.q) == 0 {
		lf.q = strings.ToUpper(lf.rq.Method) + " " + lf.rq.URL.Path + " " + lf.rq.Proto
	}
	return lf.q
}

func (lf *logfmt) status() string {
	if len(lf.ws) == 0 {
		lf.ws = strconv.Itoa(lf.al.status)
	}
	return lf.ws
}

func (lf *logfmt) bytesWritten() string {
	if len(lf.wl) == 0 {
		lf.wl = strconv.Itoa(lf.al.wLen)
	}
	return lf.wl
}

func (lf *logfmt) timeElapsed() string {
	if len(lf.e) > 0 {
		lf.e = time.Now().Sub(lf.al.start).String()
	}
	return lf.e
}

// flatten takes two slices and merges them into one
func flatten(o *opt, a, b []string) func(w *alog, r *http.Request) string {
	return func(w *alog, r *http.Request) string {
		lf := new(logfmt)
		lf.withTime(o).withRequest(r).withResponse(w)

		buf := new(bytes.Buffer)
		for i, s := range a {
			switch s {
			case "":
				buf.WriteString(b[i])
			case "%h":
				buf.WriteString(lf.remoteHostname())
			case "%l":
				buf.WriteString("-")
			case "%u":
				buf.WriteString(lf.username())
			case "%t":
				buf.WriteString(lf.timeFormatted("[02/01/2006:03:04:05 -0700]"))
			case "%r":
				buf.WriteString(lf.requestLine())
			case "%s", "%>s":
				buf.WriteString(lf.status())
			case "%b":
				buf.WriteString(lf.bytesWritten())
			case "%D":
				buf.WriteString(lf.timeElapsed())
			default:
				if len(s) > 4 && s[:2] == "%{" && s[len(s)-2] == '}' {
					label := s[2 : len(s)-2]
					switch s[len(s)-1] {
					case 'i':
						buf.WriteString(r.Header.Get(label))
					case 't':
						buf.WriteString(convertTimeFormat(lf.ti, label))
					}
				}
			}
		}
		return buf.String()
	}
}

// Format accepts a format using Apache formatting and returns a function accepting option functions
// which then returns a function that can handle standard HTTP middleware. This is to have better standard
// logging functions accessible from the library
func Format(format string) func(...optFunc) func(http.Handler) http.Handler {
	return func(opts ...optFunc) func(http.Handler) http.Handler {
		return FormatWith(format, opts...)
	}
}

// FormatWith accepts a format using Apache formatting directives with option functions and returns a function that can handle standard HTTP middleware.
func FormatWith(format string, opts ...optFunc) func(http.Handler) http.Handler {
	options := newOpt()
	for _, opt := range opts {
		opt(options)
	}

	var directives, betweens = make([]string, 0, 50), make([]string, 0, 50)
	var mBuf *bytes.Buffer
	aBuf, bBuf := new(bytes.Buffer), new(bytes.Buffer)
	mBuf = bBuf

	var isDirective, isEnclosure bool
	for i, r := range format {
		switch r {
		case '%':
			if isDirective {
				mBuf.WriteRune(r)
				continue
			}
			isDirective = true
			if i != 0 {
				directives = append(directives, aBuf.String())
				betweens = append(betweens, bBuf.String())
				aBuf.Reset()
				bBuf.Reset()
			}
			mBuf = aBuf
		case '{':
			isEnclosure = true
		case '}':
			isEnclosure = false
		case '>':
			// do the same thing
		default:
			if isDirective && !isEnclosure && !unicode.IsLetter(r) {
				isDirective = false
				isEnclosure = false
				if i != 0 {
					directives = append(directives, aBuf.String())
					betweens = append(betweens, bBuf.String())
					aBuf.Reset()
					bBuf.Reset()
				}
				mBuf = bBuf
			}
		}
		mBuf.WriteRune(r)
	}

	directives = append(directives, aBuf.String())
	betweens = append(betweens, bBuf.String())
	aBuf.Reset()
	bBuf.Reset()

	logFunc := flatten(options, directives, betweens)

	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			a := &alog{ResponseWriter: w}
			a.startTime()
			next.ServeHTTP(a, r)
			fmt.Fprintln(options.Out, logFunc(a, r))
		})
	}
}
<commit_msg>Added more consistency to naming and formatting<commit_after>package accesslog

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
	"unicode"
)

// optFunc is the type to use to options to the option struct during initialization
type optFunc func(*opt)

// opt is the internal struct that holds the options for logging.
type opt struct {
	Output io.Writer
	Time   time.Time
}

// newOpt returns a new struct to hold options, with the default output to stdout.
func newOpt() *opt {
	o := new(opt)
	o.Output = os.Stdout
	return o
}

// WithOutput sets the io.Writer output for the log file.
func WithOutput(out io.Writer) optFunc {
	return func(o *opt) {
		o.Output = out
	}
}

// responseWriter is the internal struct that will wrap the http.ResponseWriter
// and hold the status and number of bytes written
type responseWriter struct {
	http.ResponseWriter

	status    int
	byteCount int

	start time.Time
}

// WriteHeader intercepts the http.ResponseWriter WriteHeader method so we can save the status to display later
func (rw *responseWriter) WriteHeader(i int) {
	if rw.status == 0 {
		rw.status = i
	}
	rw.ResponseWriter.WriteHeader(i)
}

// Write intercepts the http.ResponseWriter Write method so we can capture the bytes written
func (rw *responseWriter) Write(p []byte) (n int, err error) {
	if rw.status == 0 {
		rw.status = http.StatusOK
	}
	n, err = rw.ResponseWriter.Write(p)
	rw.byteCount += n
	return
}

// startTime sets the start time to calculate the elapsed time for the %D directive
func (rw *responseWriter) startTime() {
	rw.start = time.Now()
}

const (
	// ApacheCommonLogFormat is the Apache Common Log directives
	ApacheCommonLogFormat = "%h %l %u %t \"%r\" %>s %b"

	// ApacheCombinedLogFormat is the Apache Combined Log directives
	ApacheCombinedLogFormat = "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\""
)

// ApacheCommonLog will log HTTP requests using the Apache Common Log format
var ApacheCommonLog = Format(ApacheCommonLogFormat)

// ApacheCombinedLog will log HTTP requests using the Apache Combined Log format
var ApacheCombinedLog = Format(ApacheCombinedLogFormat)

// convertTimeFormat converts strftime formatting directives to a go time.Time format
func convertTimeFormat(now time.Time, format string) string {
	m := map[rune]string{
		'a': "Mon", 'A': "Monday", 'b': "Jan", 'B': "January", 'C': "06",
		'd': "02", 'D': "01/02/06", 'e': "_2", 'F': "2006-01-02",
		'h': "Jan", 'H': "15", 'I': "3", 'k': "•15", 'l': "_3",
		'm': "01", 'M': "04", 'n': "\n", 'p': "PM", 'P': "pm",
		'r': "03:04:05 PM", 'R': "15:04", 'S': "05",
		't': "\t", 'T': "15:04:05", 'y': "06", 'Y': "2006",
		'z': "-700", 'Z': "MST", '%': "%%",

		// require calculated time
		'G': "%v", 'g': "%v", 'j': "%v", 's': "%v",
		'u': "%v", 'V': "%v", 'w': "%v",

		// Unsupported directives
		'c': "?", 'E': "?", 'O': "?", 'U': "?",
		'W': "?", 'x': "?", 'X': "?", '+': "?",
	}

	var isDirective bool
	var calcTime []interface{}
	var buf = new(bytes.Buffer)
	for _, r := range format {
		if !isDirective && r == '%' {
			isDirective = true
			continue
		}
		if !isDirective {
			buf.WriteRune(r)
			continue
		}
		if val, ok := m[r]; ok {
			if val == "%v" {
				switch r {
				case 'G':
					y, _ := now.ISOWeek()
					calcTime = append(calcTime, y)
				case 'g':
					y, _ := now.ISOWeek()
					calcTime = append(calcTime, strconv.Itoa(y)[2:])
				case 'j':
					calcTime = append(calcTime, now.YearDay())
				case 's':
					calcTime = append(calcTime, now.Unix())
				case 'u':
					w := now.Weekday()
					if w == 0 {
						w = 7
					}
					calcTime = append(calcTime, w)
				case 'V':
					_, w := now.ISOWeek()
					calcTime = append(calcTime, w)
				case 'w':
					calcTime = append(calcTime, now.Weekday())
				}
			}
			buf.WriteString(val)
			isDirective = false
			continue
		}
		buf.WriteString("(%" + string(r) + " is invalid)")
	}
	s := now.Format(buf.String())
	if len(calcTime) > 0 {
		s = fmt.Sprintf(s, calcTime...)
	}
	buf.Reset()
	return s
}

// line is the type that will hold all of the runtime formating directives for the log line
type line struct {
	time    time.Time
	request *http.Request
	writer  *responseWriter

	// directives
	h, u, t, r, s, b, D string
}

func (ln *line) withTime(o *opt) *line {
	if !o.Time.IsZero() {
		ln.time = o.Time
		return ln
	}
	ln.time = time.Now()
	return ln
}

func (ln *line) withRequest(r *http.Request) *line {
	ln.request = r
	return ln
}

func (ln *line) withResponse(a *responseWriter) *line {
	ln.writer = a
	return ln
}

// remoteHostname - %h
func (ln *line) remoteHostname() string {
	if len(ln.h) == 0 {
		ln.h = ln.request.URL.Host
		if len(ln.h) == 0 {
			ln.h = "127.0.0.1"
		}
	}
	return ln.h
}

// username - %u
func (ln *line) username() string {
	if len(ln.u) == 0 {
		ln.u = "-"
		if s := strings.SplitN(ln.request.Header.Get("Authorization"), " ", 2); len(s) == 2 {
			if b, err := base64.StdEncoding.DecodeString(s[1]); err == nil {
				if pair := strings.SplitN(string(b), ":", 2); len(pair) == 2 {
					ln.u = pair[0]
				}
			}
		}
	}
	return ln.u
}

//timeFormatted - %t
func (ln *line) timeFormatted(format string) string {
	if len(ln.t) == 0 {
		ln.t = ln.time.Format(format)
	}
	return ln.t
}

// requestLine - %r
func (ln *line) requestLine() string {
	if len(ln.r) == 0 {
		ln.r = strings.ToUpper(ln.request.Method) + " " + ln.request.URL.Path + " " + ln.request.Proto
	}
	return ln.r
}

// status - %s
func (ln *line) status() string {
	if len(ln.s) == 0 {
		ln.s = strconv.Itoa(ln.writer.status)
	}
	return ln.s
}

// bytesWritten - %b
func (ln *line) bytesWritten() string {
	if len(ln.b) == 0 {
		ln.b = strconv.Itoa(ln.writer.byteCount)
	}
	return ln.b
}

// timeElapsed - %D
func (ln *line) timeElapsed() string {
	if len(ln.D) > 0 {
		ln.D = time.Now().Sub(ln.writer.start).String()
	}
	return ln.D
}

// flatten takes two slices and merges them into one
func flatten(o *opt, a, b []string) func(w *responseWriter, r *http.Request) string {
	return func(w *responseWriter, r *http.Request) string {
		ln := new(line)
		ln.withTime(o).withRequest(r).withResponse(w)

		buf := new(bytes.Buffer)
		for i, s := range a {
			switch s {
			case "":
				buf.WriteString(b[i])
			case "%h":
				buf.WriteString(ln.remoteHostname())
			case "%l":
				buf.WriteString("-")
			case "%u":
				buf.WriteString(ln.username())
			case "%t":
				buf.WriteString(ln.timeFormatted("[02/01/2006:03:04:05 -0700]"))
			case "%r":
				buf.WriteString(ln.requestLine())
			case "%s", "%>s":
				buf.WriteString(ln.status())
			case "%b":
				buf.WriteString(ln.bytesWritten())
			case "%D":
				buf.WriteString(ln.timeElapsed())
			default:
				if len(s) > 4 && s[:2] == "%{" && s[len(s)-2] == '}' {
					label := s[2 : len(s)-2]
					switch s[len(s)-1] {
					case 'i':
						buf.WriteString(r.Header.Get(label))
					case 't':
						buf.WriteString(convertTimeFormat(ln.time, label))
					}
				}
			}
		}
		return buf.String()
	}
}

// Format accepts a format string using Apache formatting directives and returns
// a function accepting internal option functions which then returns
// a function that can handle standard HTTP middleware.
// This function more convenient to use when saving formatting to
// a variable, then using with standard HTTP middleware
func Format(format string) func(...optFunc) func(http.Handler) http.Handler {
	return func(opts ...optFunc) func(http.Handler) http.Handler {
		return FormatWith(format, opts...)
	}
}

// FormatWith accepts a format string using Apache formatting directives with
// option functions and returns a function that can handle standard HTTP middleware.
func FormatWith(format string, opts ...optFunc) func(http.Handler) http.Handler {
	options := newOpt()
	for _, opt := range opts {
		opt(options)
	}

	var directives, betweens = make([]string, 0, 50), make([]string, 0, 50)
	var cBuf *bytes.Buffer // current buffer
	aBuf, bBuf := new(bytes.Buffer), new(bytes.Buffer)
	cBuf = bBuf

	var isDirective, isEnclosure bool
	for i, r := range format {
		switch r {
		case '%':
			if isDirective {
				cBuf.WriteRune(r)
				continue
			}
			isDirective = true
			if i != 0 {
				directives = append(directives, aBuf.String())
				betweens = append(betweens, bBuf.String())
				aBuf.Reset()
				bBuf.Reset()
			}
			cBuf = aBuf
		case '{':
			isEnclosure = true
		case '}':
			isEnclosure = false
		case '>':
			// nothing - no change in status
		default:
			if isDirective && !isEnclosure && !unicode.IsLetter(r) {
				isDirective = false
				isEnclosure = false
				if i != 0 {
					directives = append(directives, aBuf.String())
					betweens = append(betweens, bBuf.String())
					aBuf.Reset()
					bBuf.Reset()
				}
				cBuf = bBuf
			}
		}
		cBuf.WriteRune(r)
	}

	directives = append(directives, aBuf.String())
	betweens = append(betweens, bBuf.String())
	aBuf.Reset()
	bBuf.Reset()

	logFunc := flatten(options, directives, betweens)

	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			rw := &responseWriter{ResponseWriter: w}
			rw.startTime()
			next.ServeHTTP(rw, r)
			fmt.Fprintln(options.Output, logFunc(rw, r))
		})
	}
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/appcelerator/amp/cluster/agent/admin"
	"github.com/appcelerator/amp/cluster/agent/pkg/docker"
	"github.com/appcelerator/amp/cluster/agent/pkg/docker/stack"
	"github.com/docker/cli/cli/command"
	"github.com/docker/cli/cli/compose/convert"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/api/types/swarm"
	"github.com/docker/docker/client"
	"github.com/docker/docker/pkg/term"
	"github.com/spf13/cobra"
)

const (
	TARGET_SINGLE  = "single"
	TARGET_CLUSTER = "cluster"
)

func NewInstallCommand() *cobra.Command {
	installCmd := &cobra.Command{
		Use:   "install",
		Short: "Set up amp services in swarm environment",
		RunE:  install,
	}
	return installCmd
}

func install(cmd *cobra.Command, args []string) error {
	stdin, stdout, stderr := term.StdStreams()
	dockerCli := docker.NewDockerCli(stdin, stdout, stderr)

	// Create initial secrets
	createInitialSecrets()

	// Create initial networks
	createInitialNetworks()

	namespace := "amp"
	if len(args) > 0 {
		namespace = args[0]
	}

	etcdClusterMode, err := serviceDeploymentMode(dockerCli.Client(), "amp.type.kv", "true")
	if err != nil {
		return err
	}
	elasticsearchClusterMode, err := serviceDeploymentMode(dockerCli.Client(), "amp.type.search", "true")
	if err != nil {
		return err
	}
	clusterMode := map[string]string{"elasticsearch": elasticsearchClusterMode, "etcd": etcdClusterMode}
	files, err := getStackFiles("./stacks", clusterMode)
	if err != nil {
		return err
	}

	for _, f := range files {
		log.Println(f)
		if strings.Contains(f, "test") {
			err := deployTest(dockerCli, f, "test", 60 /* timeout in seconds */)
			stack.Remove(dockerCli, stack.RemoveOptions{Namespaces: []string{"test"}})
			if err != nil {
				return err
			}
		} else {
			err := deploy(dockerCli, f, namespace)
			if err != nil {
				return err
			}
			time.Sleep(10 * time.Second)
		}
	}
	return nil
}

// returns the deployment mode
// based on the number of nodes with the label passed as argument
// if number of nodes > 2, mode = cluster, else mode = single
func serviceDeploymentMode(c client.APIClient, labelKey string, labelValue string) (string, error) {
	// unfortunately filtering labels on NodeList won't work as expected, Cf. https://github.com/moby/moby/issues/27231
	nodes, err := c.NodeList(context.Background(), types.NodeListOptions{})
	if err != nil {
		return "", err
	}
	matchingNodes := 0
	for _, node := range nodes {
		// node is a swarm.Node
		for k, v := range node.Spec.Labels {
			if k == labelKey {
				if labelValue == "" || labelValue == v {
					matchingNodes++
				}
			}
		}
	}
	switch matchingNodes {
	case 0:
		return "", fmt.Errorf("can't find a node with label %s", labelKey)
	case 1:
		fallthrough
	case 2:
		return TARGET_SINGLE, nil
	default:
		return TARGET_CLUSTER, nil
	}
}

// returns sorted list of yaml file pathnames
func getStackFiles(path string, clusterMode map[string]string) ([]string, error) {
	if path == "" {
		path = "./stacks"
	}

	// a bit more work but we can't just use filepath.Glob
	// since we need to match both *.yml and *.yaml
	files, err := ioutil.ReadDir(path)
	if err != nil {
		return nil, err
	}
	stackfiles := []string{}
	for _, f := range files {
		name := f.Name()
		// not compiling regex since only expecting less than a dozen stackfiles
		matched, err := regexp.MatchString("\\.ya?ml$", name)
		if err != nil {
			log.Println(err)
		} else if matched {
			// looking for the service name, in case there's an indication for the cluster mode (single vs cluster)
			// expecting a file with a name NN-SERVICENAME-mode.*
			split := strings.Split(name, "-")
			if len(split) == 3 {
				serviceName := split[1]
				if strings.Contains(name, TARGET_SINGLE) && clusterMode[serviceName] != TARGET_SINGLE {
					continue
				}
				if strings.Contains(name, TARGET_CLUSTER) && clusterMode[serviceName] != TARGET_CLUSTER {
					continue
				}
			}
			stackfiles = append(stackfiles, filepath.Join(path, name))
		}
	}
	return stackfiles, nil
}

func deploy(d *command.DockerCli, stackfile string, namespace string) error {
	return deployExpectingState(d, stackfile, namespace, swarm.TaskStateRunning)
}

func deployExpectingState(d *command.DockerCli, stackfile string, namespace string, expectedState swarm.TaskState) error {
	if namespace == "" {
		// use the stackfile basename as the default stack namespace
		namespace = filepath.Base(stackfile)
		namespace = strings.TrimSuffix(namespace, filepath.Ext(namespace))
	}

	opts := stack.DeployOptions{
		Namespace:        namespace,
		Composefile:      stackfile,
		ResolveImage:     stack.ResolveImageNever,
		SendRegistryAuth: false,
		Prune:            false,
		ExpectedState:    expectedState,
	}

	return stack.Deploy(context.Background(), d, opts)
}

func deployTest(d *command.DockerCli, stackfile string, namespace string, timeout int) error {
	// Deploy the test stack
	if err := deployExpectingState(d, stackfile, namespace, swarm.TaskStateComplete); err != nil {
		return err
	}

	// Create a docker client
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return err
	}

	// List stack tasks
	options := types.TaskListOptions{Filters: filters.NewArgs()}
	options.Filters.Add("label", convert.LabelNamespace+"="+namespace)
	tasks, err := stack.ListTasks(context.Background(), c, options)
	if err != nil {
		return err
	}

	// Assert we have at least one task
	if len(tasks) == 0 {
		return fmt.Errorf("no task for test")
	}

	// Assert we have only one task
	if len(tasks) != 1 {
		return fmt.Errorf("too many tasks for test: %d", len(tasks))
	}

	// If the task has an error, the test has failed
	task := tasks[0]
	if task.Status.Err != "" {
		return fmt.Errorf("test failed with status: %s", task.Status.Err)
	}

	log.Println("Test successful")
	return nil
}

func ListSecrets() ([]swarm.Secret, error) {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return nil, err
	}
	return c.SecretList(context.Background(), types.SecretListOptions{})
}

func SecretExists(name string) (bool, error) {
	secrets, err := ListSecrets()
	if err != nil {
		return false, err
	}
	for _, secret := range secrets {
		if secret.Spec.Name == name {
			return true, nil
		}
	}
	return false, nil
}

func CreateSecret(name string, data []byte) error {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return err
	}
	spec := swarm.SecretSpec{
		Annotations: swarm.Annotations{
			Name: name,
		},
		Data: data,
	}
	_, err = c.SecretCreate(context.Background(), spec)
	if err != nil {
		return err
	}
	return nil
}

// AMP secrets map: Secret name paired to secret file in ./defaults
var ampSecrets = map[string]string{
	"alertmanager_yml": "alertmanager.yml",
	"amplifier_yml":    "amplifier.yml",
	"certificate_amp":  "certificate.amp",
}

// This is the default secrets path
const defaultSecretsPath = "defaults"

// exists returns whether the given file or directory exists or not
func pathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return true, err
}

func createInitialSecrets() error {
	// Computing secret path
	secretPath := path.Join("/", defaultSecretsPath)
	pe, err := pathExists(secretPath)
	if err != nil {
		return err
	}
	if !pe {
		secretPath = defaultSecretsPath
	}
	secretPath, err = filepath.Abs(secretPath)
	if err != nil {
		return err
	}
	log.Println("Using the following path for secrets:", secretPath)

	// Creating secrets
	for secret, filename := range ampSecrets {
		// Check if secret already exists
		exists, err := SecretExists(secret)
		if err != nil {
			return err
		}
		if exists {
			log.Println("Skipping already existing secret:", secret)
			continue
		}

		// Load secret data
		data, err := ioutil.ReadFile(path.Join(secretPath, filename))
		if err != nil {
			return err
		}

		// Create secret
		if err := CreateSecret(secret, data); err != nil {
			return err
		}
		log.Println("Successfully created secret:", secret)
	}
	return nil
}

func ListNetworks() ([]types.NetworkResource, error) {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return nil, err
	}
	return c.NetworkList(context.Background(), types.NetworkListOptions{})
}

func NetworkExists(name string) (bool, error) {
	networks, err := ListNetworks()
	if err != nil {
		return false, err
	}
	for _, network := range networks {
		if network.Name == name {
			return true, nil
		}
	}
	return false, nil
}

func CreateNetwork(name string, overlay bool, attachable bool) error {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return err
	}
	spec := types.NetworkCreate{
		CheckDuplicate: true,
		Attachable:     attachable,
	}
	if overlay {
		spec.Driver = "overlay"
	}
	_, err = c.NetworkCreate(context.Background(), name, spec)
	if err != nil {
		return err
	}
	return nil
}

const ampnet = "ampnet"

func createInitialNetworks() error {
	// Check if network already exists
	exists, err := NetworkExists(ampnet)
	if err != nil {
		return err
	}
	if exists {
		log.Println("Skipping already existing network:", ampnet)
		return nil
	}

	// Create network
	if err := CreateNetwork(ampnet, true, true); err != nil {
		return err
	}
	log.Println("Successfully created network:", ampnet)
	return nil
}
<commit_msg>Adding initial config creation<commit_after>package main

import (
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/appcelerator/amp/cluster/agent/admin"
	"github.com/appcelerator/amp/cluster/agent/pkg/docker"
	"github.com/appcelerator/amp/cluster/agent/pkg/docker/stack"
	"github.com/docker/cli/cli/command"
	"github.com/docker/cli/cli/compose/convert"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/api/types/swarm"
	"github.com/docker/docker/client"
	"github.com/docker/docker/pkg/term"
	"github.com/spf13/cobra"
)

const (
	TARGET_SINGLE  = "single"
	TARGET_CLUSTER = "cluster"
)

func NewInstallCommand() *cobra.Command {
	installCmd := &cobra.Command{
		Use:   "install",
		Short: "Set up amp services in swarm environment",
		RunE:  install,
	}
	return installCmd
}

func install(cmd *cobra.Command, args []string) error {
	stdin, stdout, stderr := term.StdStreams()
	dockerCli := docker.NewDockerCli(stdin, stdout, stderr)

	// Create initial secrets
	createInitialSecrets()

	// Create initial configs
	createInitialConfigs()

	// Create initial networks
	createInitialNetworks()

	namespace := "amp"
	if len(args) > 0 {
		namespace = args[0]
	}

	etcdClusterMode, err := serviceDeploymentMode(dockerCli.Client(), "amp.type.kv", "true")
	if err != nil {
		return err
	}
	elasticsearchClusterMode, err := serviceDeploymentMode(dockerCli.Client(), "amp.type.search", "true")
	if err != nil {
		return err
	}
	clusterMode := map[string]string{"elasticsearch": elasticsearchClusterMode, "etcd": etcdClusterMode}
	files, err := getStackFiles("./stacks", clusterMode)
	if err != nil {
		return err
	}

	for _, f := range files {
		log.Println(f)
		if strings.Contains(f, "test") {
			err := deployTest(dockerCli, f, "test", 60 /* timeout in seconds */)
			stack.Remove(dockerCli, stack.RemoveOptions{Namespaces: []string{"test"}})
			if err != nil {
				return err
			}
		} else {
			err := deploy(dockerCli, f, namespace)
			if err != nil {
				return err
			}
			time.Sleep(10 * time.Second)
		}
	}
	return nil
}

// returns the deployment mode
// based on the number of nodes with the label passed as argument
// if number of nodes > 2, mode = cluster, else mode = single
func serviceDeploymentMode(c client.APIClient, labelKey string, labelValue string) (string, error) {
	// unfortunately filtering labels on NodeList won't work as expected, Cf. https://github.com/moby/moby/issues/27231
	nodes, err := c.NodeList(context.Background(), types.NodeListOptions{})
	if err != nil {
		return "", err
	}
	matchingNodes := 0
	for _, node := range nodes {
		// node is a swarm.Node
		for k, v := range node.Spec.Labels {
			if k == labelKey {
				if labelValue == "" || labelValue == v {
					matchingNodes++
				}
			}
		}
	}
	switch matchingNodes {
	case 0:
		return "", fmt.Errorf("can't find a node with label %s", labelKey)
	case 1:
		fallthrough
	case 2:
		return TARGET_SINGLE, nil
	default:
		return TARGET_CLUSTER, nil
	}
}

// returns sorted list of yaml file pathnames
func getStackFiles(path string, clusterMode map[string]string) ([]string, error) {
	if path == "" {
		path = "./stacks"
	}

	// a bit more work but we can't just use filepath.Glob
	// since we need to match both *.yml and *.yaml
	files, err := ioutil.ReadDir(path)
	if err != nil {
		return nil, err
	}
	stackfiles := []string{}
	for _, f := range files {
		name := f.Name()
		// not compiling regex since only expecting less than a dozen stackfiles
		matched, err := regexp.MatchString("\\.ya?ml$", name)
		if err != nil {
			log.Println(err)
		} else if matched {
			// looking for the service name, in case there's an indication for the cluster mode (single vs cluster)
			// expecting a file with a name NN-SERVICENAME-mode.*
			split := strings.Split(name, "-")
			if len(split) == 3 {
				serviceName := split[1]
				if strings.Contains(name, TARGET_SINGLE) && clusterMode[serviceName] != TARGET_SINGLE {
					continue
				}
				if strings.Contains(name, TARGET_CLUSTER) && clusterMode[serviceName] != TARGET_CLUSTER {
					continue
				}
			}
			stackfiles = append(stackfiles, filepath.Join(path, name))
		}
	}
	return stackfiles, nil
}

func deploy(d *command.DockerCli, stackfile string, namespace string) error {
	return deployExpectingState(d, stackfile, namespace, swarm.TaskStateRunning)
}

func deployExpectingState(d *command.DockerCli, stackfile string, namespace string, expectedState swarm.TaskState) error {
	if namespace == "" {
		// use the stackfile basename as the default stack namespace
		namespace = filepath.Base(stackfile)
		namespace = strings.TrimSuffix(namespace, filepath.Ext(namespace))
	}

	opts := stack.DeployOptions{
		Namespace:        namespace,
		Composefile:      stackfile,
		ResolveImage:     stack.ResolveImageNever,
		SendRegistryAuth: false,
		Prune:            false,
		ExpectedState:    expectedState,
	}

	return stack.Deploy(context.Background(), d, opts)
}

func deployTest(d *command.DockerCli, stackfile string, namespace string, timeout int) error {
	// Deploy the test stack
	if err := deployExpectingState(d, stackfile, namespace, swarm.TaskStateComplete); err != nil {
		return err
	}

	// Create a docker client
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return err
	}

	// List stack tasks
	options := types.TaskListOptions{Filters: filters.NewArgs()}
	options.Filters.Add("label", convert.LabelNamespace+"="+namespace)
	tasks, err := stack.ListTasks(context.Background(), c, options)
	if err != nil {
		return err
	}

	// Assert we have at least one task
	if len(tasks) == 0 {
		return fmt.Errorf("no task for test")
	}

	// Assert we have only one task
	if len(tasks) != 1 {
		return fmt.Errorf("too many tasks for test: %d", len(tasks))
	}

	// If the task has an error, the test has failed
	task := tasks[0]
	if task.Status.Err != "" {
		return fmt.Errorf("test failed with status: %s", task.Status.Err)
	}

	log.Println("Test successful")
	return nil
}

func ListSecrets() ([]swarm.Secret, error) {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return nil, err
	}
	return c.SecretList(context.Background(), types.SecretListOptions{})
}

func SecretExists(name string) (bool, error) {
	secrets, err := ListSecrets()
	if err != nil {
		return false, err
	}
	for _, secret := range secrets {
		if secret.Spec.Name == name {
			return true, nil
		}
	}
	return false, nil
}

func CreateSecret(name string, data []byte) error {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return err
	}
	spec := swarm.SecretSpec{
		Annotations: swarm.Annotations{
			Name: name,
		},
		Data: data,
	}
	_, err = c.SecretCreate(context.Background(), spec)
	if err != nil {
		return err
	}
	return nil
}

// AMP secrets map: Secret name paired to secret file in ./defaults
var ampSecrets = map[string]string{
	"alertmanager_yml": "alertmanager.yml",
	"amplifier_yml":    "amplifier.yml",
	"certificate_amp":  "certificate.amp",
}

// This is the default secrets path
const defaultSecretsPath = "defaults"

// exists returns whether the given file or directory exists or not
func pathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return true, err
}

func createInitialSecrets() error {
	// Computing secret path
	secretPath := path.Join("/", defaultSecretsPath)
	pe, err := pathExists(secretPath)
	if err != nil {
		return err
	}
	if !pe {
		secretPath = defaultSecretsPath
	}
	secretPath, err = filepath.Abs(secretPath)
	if err != nil {
		return err
	}
	log.Println("Using the following path for secrets:", secretPath)

	// Creating secrets
	for secret, filename := range ampSecrets {
		// Check if secret already exists
		exists, err := SecretExists(secret)
		if err != nil {
			return err
		}
		if exists {
			log.Println("Skipping already existing secret:", secret)
			continue
		}

		// Load secret data
		data, err := ioutil.ReadFile(path.Join(secretPath, filename))
		if err != nil {
			return err
		}

		// Create secret
		if err := CreateSecret(secret, data); err != nil {
			return err
		}
		log.Println("Successfully created secret:", secret)
	}
	return nil
}

func ListNetworks() ([]types.NetworkResource, error) {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return nil, err
	}
	return c.NetworkList(context.Background(), types.NetworkListOptions{})
}

func NetworkExists(name string) (bool, error) {
	networks, err := ListNetworks()
	if err != nil {
		return false, err
	}
	for _, network := range networks {
		if network.Name == name {
			return true, nil
		}
	}
	return false, nil
}

func CreateNetwork(name string, overlay bool, attachable bool) error {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return err
	}
	spec := types.NetworkCreate{
		CheckDuplicate: true,
		Attachable:     attachable,
	}
	if overlay {
		spec.Driver = "overlay"
	}
	_, err = c.NetworkCreate(context.Background(), name, spec)
	if err != nil {
		return err
	}
	return nil
}

const ampnet = "ampnet"

func createInitialNetworks() error {
	// Check if network already exists
	exists, err := NetworkExists(ampnet)
	if err != nil {
		return err
	}
	if exists {
		log.Println("Skipping already existing network:", ampnet)
		return nil
	}

	// Create network
	if err := CreateNetwork(ampnet, true, true); err != nil {
		return err
	}
	log.Println("Successfully created network:", ampnet)
	return nil
}

func ListConfigs() ([]swarm.Config, error) {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return nil, err
	}
	return c.ConfigList(context.Background(), types.ConfigListOptions{})
}

func ConfigExists(name string) (bool, error) {
	configs, err := ListConfigs()
	if err != nil {
		return false, err
	}
	for _, config := range configs {
		if config.Spec.Name == name {
			return true, nil
		}
	}
	return false, nil
}

func CreateConfig(name string, data []byte) error {
	c, err := client.NewClient(admin.DefaultURL, admin.DefaultVersion, nil, nil)
	if err != nil {
		return err
	}
	spec := swarm.ConfigSpec{
		Annotations: swarm.Annotations{
			Name: name,
		},
		Data: data,
	}
	_, err = c.ConfigCreate(context.Background(), spec)
	if err != nil {
		return err
	}
	return nil
}

// AMP configs map: Config name paired to config file in ./defaults
var ampConfigs = map[string]string{
	"prometheus_alerts_rules":  "prometheus_alerts.rules",
}

// This is the default configs path
const defaultConfigsPath = "defaults"

func createInitialConfigs() error {
	// Computing config path
	configPath := path.Join("/", defaultConfigsPath)
	pe, err := pathExists(configPath)
	if err != nil {
		return err
	}
	if !pe {
		configPath = defaultConfigsPath
	}
	configPath, err = filepath.Abs(configPath)
	if err != nil {
		return err
	}
	log.Println("Using the following path for configs:", configPath)

	// Creating configs
	for config, filename := range ampConfigs {
		// Check if config already exists
		exists, err := ConfigExists(config)
		if err != nil {
			return err
		}
		if exists {
			log.Println("Skipping already existing config:", config)
			continue
		}

		// Load config data
		data, err := ioutil.ReadFile(path.Join(configPath, filename))
		if err != nil {
			return err
		}

		// Create config
		if err := CreateConfig(config, data); err != nil {
			return err
		}
		log.Println("Successfully created config:", config)
	}
	return nil
}
<|endoftext|>


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

import (
	"context"
	"testing"

	"github.com/kr/pretty"
	"k8s.io/api/extensions/v1beta1"
	"k8s.io/apimachinery/pkg/util/intstr"
	"k8s.io/ingress-gce/pkg/e2e"
	"k8s.io/ingress-gce/pkg/fuzz"
	"k8s.io/ingress-gce/pkg/fuzz/features"
)

func TestUpgrade(t *testing.T) {
	t.Parallel()

	port80 := intstr.FromInt(80)

	for _, tc := range []struct {
		desc string
		ing  *v1beta1.Ingress

		numForwardingRules int
		numBackendServices int
	}{
		{
			desc: "http default backend",
			ing: fuzz.NewIngressBuilder("", "ingress-1", "").
				DefaultBackend("service-1", port80).
				Build(),
			numForwardingRules: 1,
			numBackendServices: 1,
		},
	} {
		tc := tc // Capture tc as we are running this in parallel.
		Framework.RunWithSandbox(tc.desc, t, func(t *testing.T, s *e2e.Sandbox) {
			t.Parallel()

			_, _, err := e2e.CreateEchoService(s, "service-1", nil)
			if err != nil {
				t.Fatalf("error creating echo service: %v", err)
			}
			t.Logf("Echo service created (%s/%s)", s.Namespace, "service-1")

			if _, err := Framework.Clientset.Extensions().Ingresses(s.Namespace).Create(tc.ing); err != nil {
				t.Fatalf("error creating Ingress spec: %v", err)
			}
			t.Logf("Ingress created (%s/%s)", s.Namespace, tc.ing.Name)
			s.PutStatus(e2e.Unstable)

			ing := waitForStableIngress(true, tc.ing, s, t)
			t.Logf("GCLB resources created (%s/%s)", s.Namespace, tc.ing.Name)

			whiteboxTest(ing, s, tc.numForwardingRules, tc.numBackendServices, t)

			for {
				// While k8s master is upgrading, it will return a connection refused
				// error for any k8s resource we try to hit. We loop until the
				// master upgrade has finished.
				if s.MasterUpgrading() {
					continue
				}

				if s.MasterUpgraded() {
					t.Logf("Detected master upgrade, adding a path to Ingress to force Ingress update")
					// force ingress update. only add path once
					newIng := fuzz.NewIngressBuilderFromExisting(tc.ing).
						AddPath("test.com", "/", "service-1", intstr.FromInt(80)).
						Build()

					if _, err := Framework.Clientset.Extensions().Ingresses(s.Namespace).Update(newIng); err != nil {
						t.Fatalf("error creating Ingress spec: %v", err)
					} else {
						// If Ingress upgrade succeeds, we update the status on this Ingress
						// to Unstable. It is set back to Stable after WaitForIngress below
						// finishes successfully.
						s.PutStatus(e2e.Unstable)
					}

					break
				}
			}

			// Verify the Ingress has stabilized after the master upgrade and we
			// trigger an Ingress update
			ing = waitForStableIngress(false, ing, s, t)
			t.Logf("GCLB is stable (%s/%s)", s.Namespace, tc.ing.Name)
			whiteboxTest(ing, s, tc.numForwardingRules, tc.numBackendServices, t)

			// If the Master has upgraded and the Ingress is stable,
			// we delete the Ingress and exit out of the loop to indicate that
			// the test is done.
			deleteOptions := &fuzz.GCLBDeleteOptions{
				SkipDefaultBackend: true,
			}

			vip := ing.Status.LoadBalancer.Ingress[0].IP
			gclb, err := fuzz.GCLBForVIP(context.Background(), Framework.Cloud, vip, fuzz.FeatureValidators(features.All))
			if err != nil {
				t.Fatalf("Error getting GCP resources for LB with IP = %q: %v", vip, err)
			}

			if err := e2e.WaitForIngressDeletion(context.Background(), gclb, s, ing, deleteOptions); err != nil { // Sometimes times out waiting
				t.Errorf("e2e.WaitForIngressDeletion(..., %q, nil) = %v, want nil", ing.Name, err)
			}
		})
	}
}

func waitForStableIngress(expectUnreachable bool, ing *v1beta1.Ingress, s *e2e.Sandbox, t *testing.T) *v1beta1.Ingress {
	options := &e2e.WaitForIngressOptions{
		ExpectUnreachable: expectUnreachable,
	}

	ing, err := e2e.WaitForIngress(s, ing, options)
	if err != nil {
		t.Fatalf("error waiting for Ingress to stabilize: %v", err)
	}

	s.PutStatus(e2e.Stable)
	return ing
}

func whiteboxTest(ing *v1beta1.Ingress, s *e2e.Sandbox, numForwardingRules, numBackendServices int, t *testing.T) {
	if len(ing.Status.LoadBalancer.Ingress) < 1 {
		t.Fatalf("Ingress does not have an IP: %+v", ing.Status)
	}

	vip := ing.Status.LoadBalancer.Ingress[0].IP
	t.Logf("Ingress %s/%s VIP = %s", s.Namespace, ing.Name, vip)
	gclb, err := fuzz.GCLBForVIP(context.Background(), Framework.Cloud, vip, fuzz.FeatureValidators(features.All))
	if err != nil {
		t.Fatalf("Error getting GCP resources for LB with IP = %q: %v", vip, err)
	}

	// Do some cursory checks on the GCP objects.
	if len(gclb.ForwardingRule) != numForwardingRules {
		t.Errorf("got %d fowarding rules, want %d;\n%s", len(gclb.ForwardingRule), numForwardingRules, pretty.Sprint(gclb.ForwardingRule))
	}
	if len(gclb.BackendService) != numBackendServices {
		t.Errorf("got %d backend services, want %d;\n%s", len(gclb.BackendService), numBackendServices, pretty.Sprint(gclb.BackendService))
	}
}
<commit_msg>Fix upgrade test to create an Ingress with no default backend<commit_after>/*
Copyright 2018 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 main

import (
	"context"
	"testing"

	"github.com/kr/pretty"
	"k8s.io/api/extensions/v1beta1"
	"k8s.io/apimachinery/pkg/util/intstr"
	"k8s.io/ingress-gce/pkg/e2e"
	"k8s.io/ingress-gce/pkg/fuzz"
	"k8s.io/ingress-gce/pkg/fuzz/features"
)

func TestUpgrade(t *testing.T) {
	t.Parallel()

	port80 := intstr.FromInt(80)

	for _, tc := range []struct {
		desc string
		ing  *v1beta1.Ingress

		numForwardingRules int
		numBackendServices int
	}{
		{
			desc: "http default backend",
			ing: fuzz.NewIngressBuilder("", "ingress-1", "").
				AddPath("foo.com", "/", "service-1", port80).
				Build(),
			numForwardingRules: 1,
			numBackendServices: 1,
		},
	} {
		tc := tc // Capture tc as we are running this in parallel.
		Framework.RunWithSandbox(tc.desc, t, func(t *testing.T, s *e2e.Sandbox) {
			t.Parallel()

			_, _, err := e2e.CreateEchoService(s, "service-1", nil)
			if err != nil {
				t.Fatalf("error creating echo service: %v", err)
			}
			t.Logf("Echo service created (%s/%s)", s.Namespace, "service-1")

			if _, err := Framework.Clientset.Extensions().Ingresses(s.Namespace).Create(tc.ing); err != nil {
				t.Fatalf("error creating Ingress spec: %v", err)
			}
			t.Logf("Ingress created (%s/%s)", s.Namespace, tc.ing.Name)
			s.PutStatus(e2e.Unstable)

			ing := waitForStableIngress(true, tc.ing, s, t)
			t.Logf("GCLB resources created (%s/%s)", s.Namespace, tc.ing.Name)

			whiteboxTest(ing, s, tc.numForwardingRules, tc.numBackendServices, t)

			for {
				// While k8s master is upgrading, it will return a connection refused
				// error for any k8s resource we try to hit. We loop until the
				// master upgrade has finished.
				if s.MasterUpgrading() {
					continue
				}

				if s.MasterUpgraded() {
					t.Logf("Detected master upgrade, adding a path to Ingress to force Ingress update")
					// force ingress update. only add path once
					newIng := fuzz.NewIngressBuilderFromExisting(tc.ing).
						AddPath("bar.com", "/", "service-1", port80).
						Build()

					if _, err := Framework.Clientset.Extensions().Ingresses(s.Namespace).Update(newIng); err != nil {
						t.Fatalf("error creating Ingress spec: %v", err)
					} else {
						// If Ingress upgrade succeeds, we update the status on this Ingress
						// to Unstable. It is set back to Stable after WaitForIngress below
						// finishes successfully.
						s.PutStatus(e2e.Unstable)
					}

					break
				}
			}

			// Verify the Ingress has stabilized after the master upgrade and we
			// trigger an Ingress update
			ing = waitForStableIngress(false, ing, s, t)
			t.Logf("GCLB is stable (%s/%s)", s.Namespace, tc.ing.Name)
			whiteboxTest(ing, s, tc.numForwardingRules, tc.numBackendServices, t)

			// If the Master has upgraded and the Ingress is stable,
			// we delete the Ingress and exit out of the loop to indicate that
			// the test is done.
			deleteOptions := &fuzz.GCLBDeleteOptions{
				SkipDefaultBackend: true,
			}

			vip := ing.Status.LoadBalancer.Ingress[0].IP
			gclb, err := fuzz.GCLBForVIP(context.Background(), Framework.Cloud, vip, fuzz.FeatureValidators(features.All))
			if err != nil {
				t.Fatalf("Error getting GCP resources for LB with IP = %q: %v", vip, err)
			}

			if err := e2e.WaitForIngressDeletion(context.Background(), gclb, s, ing, deleteOptions); err != nil { // Sometimes times out waiting
				t.Errorf("e2e.WaitForIngressDeletion(..., %q, nil) = %v, want nil", ing.Name, err)
			}
		})
	}
}

func waitForStableIngress(expectUnreachable bool, ing *v1beta1.Ingress, s *e2e.Sandbox, t *testing.T) *v1beta1.Ingress {
	options := &e2e.WaitForIngressOptions{
		ExpectUnreachable: expectUnreachable,
	}

	ing, err := e2e.WaitForIngress(s, ing, options)
	if err != nil {
		t.Fatalf("error waiting for Ingress to stabilize: %v", err)
	}

	s.PutStatus(e2e.Stable)
	return ing
}

func whiteboxTest(ing *v1beta1.Ingress, s *e2e.Sandbox, numForwardingRules, numBackendServices int, t *testing.T) {
	if len(ing.Status.LoadBalancer.Ingress) < 1 {
		t.Fatalf("Ingress does not have an IP: %+v", ing.Status)
	}

	vip := ing.Status.LoadBalancer.Ingress[0].IP
	t.Logf("Ingress %s/%s VIP = %s", s.Namespace, ing.Name, vip)
	gclb, err := fuzz.GCLBForVIP(context.Background(), Framework.Cloud, vip, fuzz.FeatureValidators(features.All))
	if err != nil {
		t.Fatalf("Error getting GCP resources for LB with IP = %q: %v", vip, err)
	}

	// Do some cursory checks on the GCP objects.
	if len(gclb.ForwardingRule) != numForwardingRules {
		t.Errorf("got %d fowarding rules, want %d;\n%s", len(gclb.ForwardingRule), numForwardingRules, pretty.Sprint(gclb.ForwardingRule))
	}
	if len(gclb.BackendService) != numBackendServices {
		t.Errorf("got %d backend services, want %d;\n%s", len(gclb.BackendService), numBackendServices, pretty.Sprint(gclb.BackendService))
	}
}
<|endoftext|>


<commit_before>// mailbox_campaign - create, list, hash campaign info.
/*

Usage:

	mailbox_campaign [flags ...] [cmd]

The cmd are:

	`create`: create a campaign
	`list`: list all campaign info
	`hash`: make a hash with c(cid), u(uid)

The flags are:

	`-c`: cid
	`-u`: uid

*/
package main

import (
	"database/sql"
	"flag"
	"fmt"
	"log"
	"net/url"
	"os"
	"text/tabwriter"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"github.com/toomore/mailbox/campaign"
	"github.com/toomore/mailbox/utils"
)

var (
	conn *sql.DB
	cid  = flag.String("cid", "", "campaign id")
	uid  = flag.String("uid", "", "User id")
)

func create() ([8]byte, [8]byte) {
	id, seed := utils.GenSeed(), utils.GenSeed()
	_, err := conn.Query(fmt.Sprintf(`INSERT INTO campaign(id,seed) VALUES('%s', '%s')`, id, seed))
	defer conn.Close()
	if err != nil {
		log.Fatal(err)
	}
	return id, seed
}

func list() {
	rows, err := conn.Query(`SELECT id,seed,created,updated FROM campaign ORDER BY updated DESC`)
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()
	var (
		id      string
		seed    string
		created time.Time
		updated time.Time
	)
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 0, ' ', tabwriter.AlignRight|tabwriter.Debug)
	fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", "id", "seed", "created", "updated")
	for rows.Next() {
		if err := rows.Scan(&id, &seed, &created, &updated); err != nil {
			log.Println("[err]", err)
		} else {
			fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", id, seed, created, updated)
		}
	}
	w.Flush()
}

func makeHash() {
	data := url.Values{}
	data.Add("c", *cid)
	data.Add("u", *uid)
	log.Printf("/read/%x?%s\n", campaign.MakeMac(*cid, data), data.Encode())
}

func openGroups(cid string, groups string) {
	rows, err := conn.Query(`
	SELECT id,email,f_name,reader.created
	FROM user
	LEFT JOIN reader ON (id=reader.uid and reader.cid=?)
	WHERE groups=?
	GROUP BY id;`, cid, groups)
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()
	var (
		id        string
		email     string
		fname     string
		created   sql.NullString
		nums      int
		openCount int
	)
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 0, ' ', tabwriter.AlignRight|tabwriter.Debug)
	fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", "id", "email", "f_name", "open")
	for rows.Next() {
		if err := rows.Scan(&id, &email, &fname, &created); err != nil {
			log.Println("[err]", err)
		} else {
			nums++
			if created.Valid {
				fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", id, email, fname, created.String)
				openCount++
			} else {
				fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", id, email, fname, "Not open")
			}
		}
	}
	fmt.Fprintf(w, "%s\t%s\t%s\t%.2f%%\n", "", "", "", float64(openCount)/float64(nums)*100)
	w.Flush()
}

func openList(cid string) {
	rows, err := conn.Query(`
	SELECT uid,u.email,count(*) AS count
	FROM reader, user AS u
	WHERE uid=u.id AND cid=?
	GROUP BY uid
	ORDER BY count DESC`, cid)
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()
	var (
		count int
		email string
		nums  int
		sum   int
		uid   string
	)
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 0, ' ', tabwriter.AlignRight|tabwriter.Debug)
	fmt.Fprintf(w, "%s\t%s\t%s\n", "uid", "email", "count")
	for rows.Next() {
		if err := rows.Scan(&uid, &email, &count); err != nil {
			log.Println("[err]", err)
		} else {
			sum += count
			nums++
			fmt.Fprintf(w, "%s\t%s\t%d\n", uid, email, count)
		}
	}
	fmt.Fprintf(w, "%d\t%.02f%%\t%d\n", nums, float64(sum)/float64(nums)*100, sum)
	w.Flush()
}

func openHistory(cid string, groups string) {
	rows, err := conn.Query(`
	SELECT no,uid,u.email,u.f_name,reader.created,ip,agent
	FROM reader, user AS u
	WHERE cid=? AND uid=u.id AND u.groups=?;
	`, cid, groups)
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()
	var (
		no      string
		uid     string
		email   string
		fname   string
		created time.Time
		ip      string
		agent   string
		count   int
	)
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 0, ' ', tabwriter.AlignRight|tabwriter.Debug)
	fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", "no", "uid", "email", "fname", "created", "ip", "agent")
	for rows.Next() {
		if err := rows.Scan(&no, &uid, &email, &fname, &created, &ip, &agent); err != nil {
			log.Println("[err]", err)
		} else {
			count++
			fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", no, uid, email, fname, created, ip, agent)
		}
	}
	fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", "no", "uid", "email", "fname", "created", "ip", "agent")
	w.Flush()
	fmt.Printf("Count: %d\n", count)
}

func printTips() {
	fmt.Println("mailbox_campaign [cmd]\ncmd: `create`, `list`, `hash`, `open [cid] [groups]`, `openlist [cid]`, `openhistory [cid] [groups]`")
}

func main() {
	flag.Parse()
	args := flag.Args()
	if len(args) >= 1 {
		conn = utils.GetConn()
		switch args[0] {
		case "create":
			id, seed := create()
			log.Printf("id: %s, seed: %s", id, seed)
		case "list":
			list()
		case "hash":
			makeHash()
		case "open":
			if len(args) >= 3 {
				openGroups(args[1], args[2])
			} else {
				printTips()
			}
		case "openlist":
			if len(args) >= 2 {
				openList(args[1])
			} else {
				printTips()
			}
		case "openhistory":
			if len(args) >= 3 {
				openHistory(args[1], args[2])
			} else {
				printTips()
			}
		default:
			printTips()
		}
	} else {
		printTips()
	}
}
<commit_msg>Add order by at campaign history<commit_after>// mailbox_campaign - create, list, hash campaign info.
/*

Usage:

	mailbox_campaign [flags ...] [cmd]

The cmd are:

	`create`: create a campaign
	`list`: list all campaign info
	`hash`: make a hash with c(cid), u(uid)

The flags are:

	`-c`: cid
	`-u`: uid

*/
package main

import (
	"database/sql"
	"flag"
	"fmt"
	"log"
	"net/url"
	"os"
	"text/tabwriter"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"github.com/toomore/mailbox/campaign"
	"github.com/toomore/mailbox/utils"
)

var (
	conn *sql.DB
	cid  = flag.String("cid", "", "campaign id")
	uid  = flag.String("uid", "", "User id")
)

func create() ([8]byte, [8]byte) {
	id, seed := utils.GenSeed(), utils.GenSeed()
	_, err := conn.Query(fmt.Sprintf(`INSERT INTO campaign(id,seed) VALUES('%s', '%s')`, id, seed))
	defer conn.Close()
	if err != nil {
		log.Fatal(err)
	}
	return id, seed
}

func list() {
	rows, err := conn.Query(`SELECT id,seed,created,updated FROM campaign ORDER BY updated DESC`)
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()
	var (
		id      string
		seed    string
		created time.Time
		updated time.Time
	)
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 0, ' ', tabwriter.AlignRight|tabwriter.Debug)
	fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", "id", "seed", "created", "updated")
	for rows.Next() {
		if err := rows.Scan(&id, &seed, &created, &updated); err != nil {
			log.Println("[err]", err)
		} else {
			fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", id, seed, created, updated)
		}
	}
	w.Flush()
}

func makeHash() {
	data := url.Values{}
	data.Add("c", *cid)
	data.Add("u", *uid)
	log.Printf("/read/%x?%s\n", campaign.MakeMac(*cid, data), data.Encode())
}

func openGroups(cid string, groups string) {
	rows, err := conn.Query(`
	SELECT id,email,f_name,reader.created
	FROM user
	LEFT JOIN reader ON (id=reader.uid AND reader.cid=?)
	WHERE groups=?
	GROUP BY id;`, cid, groups)
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()
	var (
		id        string
		email     string
		fname     string
		created   sql.NullString
		nums      int
		openCount int
	)
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 0, ' ', tabwriter.AlignRight|tabwriter.Debug)
	fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", "id", "email", "f_name", "open")
	for rows.Next() {
		if err := rows.Scan(&id, &email, &fname, &created); err != nil {
			log.Println("[err]", err)
		} else {
			nums++
			if created.Valid {
				fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", id, email, fname, created.String)
				openCount++
			} else {
				fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", id, email, fname, "Not open")
			}
		}
	}
	fmt.Fprintf(w, "%s\t%s\t%s\t%.2f%%\n", "", "", "", float64(openCount)/float64(nums)*100)
	w.Flush()
}

func openList(cid string) {
	rows, err := conn.Query(`
	SELECT uid,u.email,count(*) AS count
	FROM reader, user AS u
	WHERE uid=u.id AND cid=?
	GROUP BY uid
	ORDER BY count DESC`, cid)
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()
	var (
		count int
		email string
		nums  int
		sum   int
		uid   string
	)
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 0, ' ', tabwriter.AlignRight|tabwriter.Debug)
	fmt.Fprintf(w, "%s\t%s\t%s\n", "uid", "email", "count")
	for rows.Next() {
		if err := rows.Scan(&uid, &email, &count); err != nil {
			log.Println("[err]", err)
		} else {
			sum += count
			nums++
			fmt.Fprintf(w, "%s\t%s\t%d\n", uid, email, count)
		}
	}
	fmt.Fprintf(w, "%d\t%.02f%%\t%d\n", nums, float64(sum)/float64(nums)*100, sum)
	w.Flush()
}

func openHistory(cid string, groups string) {
	rows, err := conn.Query(`
	SELECT no,uid,u.email,u.f_name,reader.created,ip,agent
	FROM reader, user AS u
	WHERE cid=? AND uid=u.id AND u.groups=?
	ORDER BY reader.created ASC;
	`, cid, groups)
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()
	var (
		no      string
		uid     string
		email   string
		fname   string
		created time.Time
		ip      string
		agent   string
		count   int
	)
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 0, ' ', tabwriter.AlignRight|tabwriter.Debug)
	fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", "no", "uid", "email", "fname", "created", "ip", "agent")
	for rows.Next() {
		if err := rows.Scan(&no, &uid, &email, &fname, &created, &ip, &agent); err != nil {
			log.Println("[err]", err)
		} else {
			count++
			fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", no, uid, email, fname, created, ip, agent)
		}
	}
	fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", "no", "uid", "email", "fname", "created", "ip", "agent")
	w.Flush()
	fmt.Printf("Count: %d\n", count)
}

func printTips() {
	fmt.Println("mailbox_campaign [cmd]\ncmd: `create`, `list`, `hash`, `open [cid] [groups]`, `openlist [cid]`, `openhistory [cid] [groups]`")
}

func main() {
	flag.Parse()
	args := flag.Args()
	if len(args) >= 1 {
		conn = utils.GetConn()
		switch args[0] {
		case "create":
			id, seed := create()
			log.Printf("id: %s, seed: %s", id, seed)
		case "list":
			list()
		case "hash":
			makeHash()
		case "open":
			if len(args) >= 3 {
				openGroups(args[1], args[2])
			} else {
				printTips()
			}
		case "openlist":
			if len(args) >= 2 {
				openList(args[1])
			} else {
				printTips()
			}
		case "openhistory":
			if len(args) >= 3 {
				openHistory(args[1], args[2])
			} else {
				printTips()
			}
		default:
			printTips()
		}
	} else {
		printTips()
	}
}
<|endoftext|>


<commit_before>package worker

import (
	"encoding/json"
	"fmt"
	"sync"

	"github.com/streadway/amqp"
	"github.com/travis-ci/worker/context"
	gocontext "golang.org/x/net/context"
)

type cancelCommand struct {
	Type   string `json:"type"`
	JobID  uint64 `json:"job_id"`
	Source string `json:"source"`
}

type Canceller interface {
	Subscribe(id uint64, ch chan<- struct{}) error
	Unsubscribe(id uint64)
}

type CommandDispatcher struct {
	conn *amqp.Connection
	ctx  gocontext.Context

	cancelMutex sync.Mutex
	cancelMap   map[uint64](chan<- struct{})
}

func NewCommandDispatcher(ctx gocontext.Context, conn *amqp.Connection) *CommandDispatcher {
	ctx = context.FromComponent(ctx, "command_dispatcher")

	return &CommandDispatcher{
		ctx:       ctx,
		conn:      conn,
		cancelMap: make(map[uint64](chan<- struct{})),
	}
}

func (d *CommandDispatcher) Run() {
	amqpChan, err := d.conn.Channel()
	if err != nil {
		context.LoggerFromContext(d.ctx).WithField("err", err).Error("couldn't open channel")
		return
	}
	defer amqpChan.Close()

	err = amqpChan.Qos(1, 0, false)
	if err != nil {
		context.LoggerFromContext(d.ctx).WithField("err", err).Error("couldn't set prefetch")
		return
	}

	err = amqpChan.ExchangeDeclare("worker.commands", "fanout", false, false, false, false, nil)
	if err != nil {
		context.LoggerFromContext(d.ctx).WithField("err", err).Error("couldn't declare exchange")
		return
	}

	queue, err := amqpChan.QueueDeclare("", true, false, true, false, nil)
	if err != nil {
		context.LoggerFromContext(d.ctx).WithField("err", err).Error("couldn't declare queue")
		return
	}

	err = amqpChan.QueueBind(queue.Name, "", "worker.commands", false, nil)
	if err != nil {
		context.LoggerFromContext(d.ctx).WithField("err", err).Error("couldn't bind queue to exchange")
		return
	}

	deliveries, err := amqpChan.Consume(queue.Name, "commands", false, true, false, false, nil)
	if err != nil {
		context.LoggerFromContext(d.ctx).WithField("err", err).Error("couldn't consume queue")
		return
	}

	for delivery := range deliveries {
		d.processCommand(delivery)
		delivery.Ack(false)
	}
}

func (d *CommandDispatcher) Subscribe(id uint64, ch chan<- struct{}) error {
	d.cancelMutex.Lock()
	defer d.cancelMutex.Unlock()

	if _, ok := d.cancelMap[id]; ok {
		return fmt.Errorf("there's already a subscription for job %d", id)
	}

	d.cancelMap[id] = ch

	return nil
}

func (d *CommandDispatcher) Unsubscribe(id uint64) {
	d.cancelMutex.Lock()
	defer d.cancelMutex.Unlock()

	delete(d.cancelMap, id)
}

func (d *CommandDispatcher) processCommand(delivery amqp.Delivery) {
	var command cancelCommand
	err := json.Unmarshal(delivery.Body, &command)
	if err != nil {
		context.LoggerFromContext(d.ctx).WithField("err", err).Error("unable to parse JSON")
		return
	}

	if command.Type != "cancel_job" {
		context.LoggerFromContext(d.ctx).WithField("command", command.Type).Error("unknown worker command")
		return
	}

	d.cancelMutex.Lock()
	defer d.cancelMutex.Unlock()

	cancelChan, ok := d.cancelMap[command.JobID]
	if !ok {
		context.LoggerFromContext(d.ctx).WithField("command", command.Type).WithField("job", command.JobID).Info("no job with this ID found on this worker")
		return
	}

	if tryClose(cancelChan) {
		context.LoggerFromContext(d.ctx).WithField("command", command.Type).WithField("job", command.JobID).Info("cancelling job")
	} else {
		context.LoggerFromContext(d.ctx).WithField("command", command.Type).WithField("job", command.JobID).Warn("job already cancelled")
	}
}

func tryClose(ch chan<- struct{}) (closedNow bool) {
	closedNow = true
	defer func() {
		if x := recover(); x != nil {
			closedNow = false
		}
	}()

	close(ch)

	return
}
<commit_msg>canceller: log error if we can't ack message<commit_after>package worker

import (
	"encoding/json"
	"fmt"
	"sync"

	"github.com/streadway/amqp"
	"github.com/travis-ci/worker/context"
	gocontext "golang.org/x/net/context"
)

type cancelCommand struct {
	Type   string `json:"type"`
	JobID  uint64 `json:"job_id"`
	Source string `json:"source"`
}

type Canceller interface {
	Subscribe(id uint64, ch chan<- struct{}) error
	Unsubscribe(id uint64)
}

type CommandDispatcher struct {
	conn *amqp.Connection
	ctx  gocontext.Context

	cancelMutex sync.Mutex
	cancelMap   map[uint64](chan<- struct{})
}

func NewCommandDispatcher(ctx gocontext.Context, conn *amqp.Connection) *CommandDispatcher {
	ctx = context.FromComponent(ctx, "command_dispatcher")

	return &CommandDispatcher{
		ctx:       ctx,
		conn:      conn,
		cancelMap: make(map[uint64](chan<- struct{})),
	}
}

func (d *CommandDispatcher) Run() {
	amqpChan, err := d.conn.Channel()
	if err != nil {
		context.LoggerFromContext(d.ctx).WithField("err", err).Error("couldn't open channel")
		return
	}
	defer amqpChan.Close()

	err = amqpChan.Qos(1, 0, false)
	if err != nil {
		context.LoggerFromContext(d.ctx).WithField("err", err).Error("couldn't set prefetch")
		return
	}

	err = amqpChan.ExchangeDeclare("worker.commands", "fanout", false, false, false, false, nil)
	if err != nil {
		context.LoggerFromContext(d.ctx).WithField("err", err).Error("couldn't declare exchange")
		return
	}

	queue, err := amqpChan.QueueDeclare("", true, false, true, false, nil)
	if err != nil {
		context.LoggerFromContext(d.ctx).WithField("err", err).Error("couldn't declare queue")
		return
	}

	err = amqpChan.QueueBind(queue.Name, "", "worker.commands", false, nil)
	if err != nil {
		context.LoggerFromContext(d.ctx).WithField("err", err).Error("couldn't bind queue to exchange")
		return
	}

	deliveries, err := amqpChan.Consume(queue.Name, "commands", false, true, false, false, nil)
	if err != nil {
		context.LoggerFromContext(d.ctx).WithField("err", err).Error("couldn't consume queue")
		return
	}

	for delivery := range deliveries {
		d.processCommand(delivery)
		err := delivery.Ack(false)
		if err != nil {
			context.LoggerFromContext(d.ctx).WithField("err", err).WithField("delivery", delivery).Error("couldn't ack delivery")
		}
	}
}

func (d *CommandDispatcher) Subscribe(id uint64, ch chan<- struct{}) error {
	d.cancelMutex.Lock()
	defer d.cancelMutex.Unlock()

	if _, ok := d.cancelMap[id]; ok {
		return fmt.Errorf("there's already a subscription for job %d", id)
	}

	d.cancelMap[id] = ch

	return nil
}

func (d *CommandDispatcher) Unsubscribe(id uint64) {
	d.cancelMutex.Lock()
	defer d.cancelMutex.Unlock()

	delete(d.cancelMap, id)
}

func (d *CommandDispatcher) processCommand(delivery amqp.Delivery) {
	var command cancelCommand
	err := json.Unmarshal(delivery.Body, &command)
	if err != nil {
		context.LoggerFromContext(d.ctx).WithField("err", err).Error("unable to parse JSON")
		return
	}

	if command.Type != "cancel_job" {
		context.LoggerFromContext(d.ctx).WithField("command", command.Type).Error("unknown worker command")
		return
	}

	d.cancelMutex.Lock()
	defer d.cancelMutex.Unlock()

	cancelChan, ok := d.cancelMap[command.JobID]
	if !ok {
		context.LoggerFromContext(d.ctx).WithField("command", command.Type).WithField("job", command.JobID).Info("no job with this ID found on this worker")
		return
	}

	if tryClose(cancelChan) {
		context.LoggerFromContext(d.ctx).WithField("command", command.Type).WithField("job", command.JobID).Info("cancelling job")
	} else {
		context.LoggerFromContext(d.ctx).WithField("command", command.Type).WithField("job", command.JobID).Warn("job already cancelled")
	}
}

func tryClose(ch chan<- struct{}) (closedNow bool) {
	closedNow = true
	defer func() {
		if x := recover(); x != nil {
			closedNow = false
		}
	}()

	close(ch)

	return
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Pani Networks
// All Rights Reserved.
//
// 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 client

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"reflect"
	"testing"

	"github.com/romana/core/common/api"
)

/*
var (
	client *Client
	// Keep track of state of some tests
	state int
)

func init() {
	rand.Seed(time.Now().UnixNano())
	testSaver = &TestSaver{}
}

func tearDown(t *testing.T) {
	var err error
	err = client.Store.DeleteTree(client.config.EtcdPrefix)
	if err != nil {
		t.Errorf("Error tearing down: %s", err)
	}
	t.Logf("Tore down %s", client.config.EtcdPrefix)
}

func initClient(t *testing.T, topoConf string) *Client {
	var err error
	cfg := &common.Config{EtcdEndpoints: []string{"localhost:2379"},
		EtcdPrefix: fmt.Sprintf("/romanaTest%d", rand.Int63n(100000)),
	}
	client, err := NewClient(cfg)
	if err != nil {
		t.Fatal(err)
	}
	if topoConf != "" {
		topoReq := api.TopologyUpdateRequest{}
		err = json.Unmarshal([]byte(topoConf), &topoReq)
		if err != nil {
			t.Fatalf("Cannot parse %s: %v", topoConf, err)
		}
		err = client.IPAM.UpdateTopology(topoReq, false)
		if err != nil {
			t.Fatal(err)
		}
	}
	return client
}

// TestWatchHostsWithCallback tests WatchHostsWithCallback -- and since it
// uses WatchHosts internally, implicitly also WatchHosts
/*
func TestWatchHostsWithCallback(t *testing.T) {
	client := initClient(t, "")
	defer tearDown(t)
	errCh := make(chan string)
	okCh := make(chan string)
	err := client.WatchHostsWithCallback(func(hl api.HostList) {
		state++

		var msg string
		fmt.Printf("HostListCallback: Got host list of length %d and revision %d in state %d\n", len(hl.Hosts), hl.Revision, state)
		if state == 1 {
			if len(hl.Hosts) != 0 {
				msg = fmt.Sprintf("HostListCallback: Expected host length at this point to be 0, got %d", len(hl.Hosts))
				fmt.Println(msg)
				errCh <- msg
				return
			}
			if hl.Revision != 0 {
				msg = fmt.Sprintf("HostListCallback: Expected host list revision at this point to be 0, got %d", hl.Revision)
				fmt.Println(msg)
				errCh <- msg
				return
			}
		} else if state == 2 {
			if len(hl.Hosts) != 2 {
				msg = fmt.Sprintf("HostListCallback: Expected host length at this point to be 2, got %d", len(hl.Hosts))
				fmt.Println(msg)
				errCh <- msg
				return
			}
			if hl.Revision != 1 {
				msg = fmt.Sprintf("HostListCallback: Expected host list revision at this point to be 1, got %d", hl.Revision)
				fmt.Println(msg)
				errCh <- msg
				return
			}
			// Here everything is ok!
			okCh <- "OK"
		} else {
			msg = fmt.Sprintf("HostListCallback: Did not expect HostListCallback to be called in state %d", state)
			fmt.Println(state)
			errCh <- msg

		}

	})
	if err != nil {
		t.Fatal(err)
	}

	topoConf := `{
	"networks": [{
		"name": "net1",
		"cidr": "10.0.0.0/8",
		"block_mask": 30
	}],

	"topologies": [{
		"networks": ["net1"],
		"map": [{
			"routing": "bla",
			"groups": [{
					"name": "host1",
					"ip": "192.168.99.10"
				},
				{
					"name": "host2",
					"ip": "192.168.99.11"
				}
			]
		}]
	}]
}`
	topoReq := api.TopologyUpdateRequest{}
	err = json.Unmarshal([]byte(topoConf), &topoReq)
	if err != nil {
		t.Fatalf("Cannot parse %s: %v", topoConf, err)
	}
	err = client.IPAM.UpdateTopology(topoReq)
	if err != nil {
		t.Fatal(err)
	}
	for {
		select {
		case msg := <-errCh:
			t.Fatal(msg)
			break
		case <-okCh:
			return
		}
	}

}
*/

// TestWatchBlocksWithCallback tests WatchBlocksWithCallback -- and since it
// uses WatchBlocks internally, implicitly also WatchBlocks
/*
func TestWatchBlocksWithCallback(t *testing.T) {
	defer tearDown(t)
	errCh := make(chan string)
	okCh := make(chan string)
	topoConf := `{
	"networks": [{
		"name": "net1",
		"cidr": "10.0.0.0/8",
		"block_mask": 31
	}],

	"topologies": [{
		"networks": ["net1"],
		"map": [{
			"routing": "bla",
			"groups": [{
					"name": "host1",
					"ip": "192.168.99.10"
				},
				{
					"name": "host2",
					"ip": "192.168.99.11"
				}
			]
		}]
	}]
}`
	client = initClient(t, topoConf)
	err := client.WatchBlocksWithCallback(func(hl api.IPAMBlocksResponse) {
		state++

		var msg string
		fmt.Printf("BlocksListCallback: Got block list of length %d and revision %d in state %d\n", len(hl.Blocks), hl.Revision, state)
		switch state {
		case 1:
			if len(hl.Blocks) != 0 {
				msg = fmt.Sprintf("BlocksListCallback: Expected block length at this point to be 0, got %d", len(hl.Blocks))
				fmt.Println(msg)
				errCh <- msg
				return
			}
			if hl.Revision != 0 {
				msg = fmt.Sprintf("BlocksListCallback: Expected block list revision at this point to be 0, got %d", hl.Revision)
				fmt.Println(msg)
				errCh <- msg
				return
			}
		case 2, 3:
			if len(hl.Blocks) != 1 {
				msg = fmt.Sprintf("BlocksListCallback: Expected block length at this point to be 1, got %d", len(hl.Blocks))
				fmt.Println(msg)
				errCh <- msg
				return
			}
			if hl.Revision != state-1 {
				msg = fmt.Sprintf("HostListCallback: Expected block list revision at this point to be %d, got %d", state-1, hl.Revision)
				fmt.Println(msg)
				errCh <- msg
				return
			}

		case 4, 5:
			if len(hl.Blocks) != 2 {
				msg = fmt.Sprintf("BlocksListCallback: Expected block length at this point to be 2, got %d", len(hl.Blocks))
				fmt.Println(msg)
				errCh <- msg
				return
			}
			if hl.Revision != state-1 {
				msg = fmt.Sprintf("HostListCallback: Expected block list revision at this point to be %d, got %d", state-1, hl.Revision)
				fmt.Println(msg)
				errCh <- msg
				return
			}
			if state == 5 {
				// Here everything is ok!
				okCh <- "OK"
			}
		default:
			msg = fmt.Sprintf("HostListCallback: Did not expect HostListCallback to be called in state %d", state)
			errCh <- msg

		}

	})
	if err != nil {
		t.Fatal(err)
	}

	_, err = client.IPAM.AllocateIP("addr1", "host1", "t1", "s1")
	if err != nil {
		t.Fatal(err)
	}
	_, err = client.IPAM.AllocateIP("addr2", "host1", "t1", "s1")
	if err != nil {
		t.Fatal(err)
	}
	_, err = client.IPAM.AllocateIP("addr3", "host1", "t1", "s1")
	if err != nil {
		t.Fatal(err)
	}
	_, err = client.IPAM.AllocateIP("addr4", "host1", "t1", "s1")
	if err != nil {
		t.Fatal(err)
	}
	for {
		select {
		case msg := <-errCh:
			t.Fatal(msg)
			break
		case <-okCh:
			return
		}
	}
}
*/

/*
func TestConcurrency(t *testing.T) {
	//	defer tearDown(t)
	client = initClient(t, "")

	barrier := make(chan int)
	cnt := 8
	locker, err := client.Store.NewLocker("/lock")
	if err != nil {
		t.Fatal(err)
	}
	for i := 0; i < cnt; i++ {
		go func(i int) {
			fmt.Printf("%d %d Entered\n", i, getGID())
			defer func(i int) {
				fmt.Printf("%d %d Unlocking\n", i, getGID())
				locker.Unlock()
				fmt.Printf("%d %d Unlocked\n", i, getGID())
			}(i)
			fmt.Printf("%d %d Waiting for lock\n", i, getGID())
			locker.Lock()
			fmt.Printf("%d %d Got lock\n", i, getGID())
			val := fmt.Sprintf("Hello from %d %d", i, getGID())
			err := client.Store.Put(fmt.Sprintf("/test%d", i), []byte(val), nil)
			if err != nil {
				fmt.Println(err)
			}
			barrier <- 1
		}(i)
	}
	finishedCnt := 0

WAIT_FOR_FINISH:
	for {
		select {
		case <-barrier:
			finishedCnt++
			fmt.Printf("%d routines finished\n", finishedCnt)
			if finishedCnt == cnt {
				break WAIT_FOR_FINISH
			}
		}
	}
}
*/

type testcase struct {
	name string
	ipam *IPAM
	want interface{}
}

func loadTestFiles(name string, inFile string, outFile string) (testcase, error) {
	input, err := ioutil.ReadFile(inFile)
	if err != nil {
		return testcase{}, fmt.Errorf("error reading test input file: %s", err)
	}
	output, err := ioutil.ReadFile(outFile)
	if err != nil {
		return testcase{}, fmt.Errorf("error reading test output file: %s", err)
	}

	ipamState := &IPAM{}
	err = json.Unmarshal(input, ipamState)
	if err != nil {
		return testcase{}, fmt.Errorf("failed to unmarshal ipam information: %s", err)
	}
	topologyState := &api.TopologyUpdateRequest{}
	err = json.Unmarshal(output, topologyState)
	if err != nil {
		return testcase{}, fmt.Errorf("failed to unmarshal topology information: %s", err)
	}

	return testcase{name, ipamState, topologyState}, nil
}

func Test_getTopologyFromIPAMState(t *testing.T) {
	var testcases []testcase

	testfiles := map[string][]string{
		"multinetwork": {
			"testdata/TestTopologyGetInputMultiNetworks.json",
			"testdata/TestTopologyGetOutputMultiNetworks.json",
		},
		"kubeadm": {
			"testdata/TestTopologyGetInputKubeadm.json",
			"testdata/TestTopologyGetOutputKubeadm.json",
		},
	}

	for name, file := range testfiles {
		tc, err := loadTestFiles(name, file[0], file[1])
		if err != nil {
			t.Errorf("error loading test files: %s", err)
			continue
		}
		testcases = append(testcases, tc)
	}

	for _, tt := range testcases {
		t.Run(tt.name, func(t *testing.T) {
			// TODO: got and tt.want below are not sorted, so there is a fair chance the
			// testcases here may fail, in which case this needs to be fixed by sorting
			// it first.
			if got := getTopologyFromIPAMState(tt.ipam); !reflect.DeepEqual(got, tt.want) {
				bodyGot, errGot := json.MarshalIndent(got, "", "\t")
				bodyWant, errWant := json.MarshalIndent(tt.want, "", "\t")
				if errGot == nil && errWant == nil {
					t.Errorf("getTopology() = \ngot(%s)\nwant(%s)",
						string(bodyGot), string(bodyWant))
				} else {
					t.Errorf("getTopology() = \ngot(%#v)\nwant(%#v)", got, tt.want)
				}
			}
		})
	}
}
<commit_msg>test: close earlier comment.<commit_after>// Copyright (c) 2017 Pani Networks
// All Rights Reserved.
//
// 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 client

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"reflect"
	"testing"

	"github.com/romana/core/common/api"
)

/*
var (
	client *Client
	// Keep track of state of some tests
	state int
)

func init() {
	rand.Seed(time.Now().UnixNano())
	testSaver = &TestSaver{}
}

func tearDown(t *testing.T) {
	var err error
	err = client.Store.DeleteTree(client.config.EtcdPrefix)
	if err != nil {
		t.Errorf("Error tearing down: %s", err)
	}
	t.Logf("Tore down %s", client.config.EtcdPrefix)
}

func initClient(t *testing.T, topoConf string) *Client {
	var err error
	cfg := &common.Config{EtcdEndpoints: []string{"localhost:2379"},
		EtcdPrefix: fmt.Sprintf("/romanaTest%d", rand.Int63n(100000)),
	}
	client, err := NewClient(cfg)
	if err != nil {
		t.Fatal(err)
	}
	if topoConf != "" {
		topoReq := api.TopologyUpdateRequest{}
		err = json.Unmarshal([]byte(topoConf), &topoReq)
		if err != nil {
			t.Fatalf("Cannot parse %s: %v", topoConf, err)
		}
		err = client.IPAM.UpdateTopology(topoReq, false)
		if err != nil {
			t.Fatal(err)
		}
	}
	return client
}
*/

// TestWatchHostsWithCallback tests WatchHostsWithCallback -- and since it
// uses WatchHosts internally, implicitly also WatchHosts
/*
func TestWatchHostsWithCallback(t *testing.T) {
	client := initClient(t, "")
	defer tearDown(t)
	errCh := make(chan string)
	okCh := make(chan string)
	err := client.WatchHostsWithCallback(func(hl api.HostList) {
		state++

		var msg string
		fmt.Printf("HostListCallback: Got host list of length %d and revision %d in state %d\n", len(hl.Hosts), hl.Revision, state)
		if state == 1 {
			if len(hl.Hosts) != 0 {
				msg = fmt.Sprintf("HostListCallback: Expected host length at this point to be 0, got %d", len(hl.Hosts))
				fmt.Println(msg)
				errCh <- msg
				return
			}
			if hl.Revision != 0 {
				msg = fmt.Sprintf("HostListCallback: Expected host list revision at this point to be 0, got %d", hl.Revision)
				fmt.Println(msg)
				errCh <- msg
				return
			}
		} else if state == 2 {
			if len(hl.Hosts) != 2 {
				msg = fmt.Sprintf("HostListCallback: Expected host length at this point to be 2, got %d", len(hl.Hosts))
				fmt.Println(msg)
				errCh <- msg
				return
			}
			if hl.Revision != 1 {
				msg = fmt.Sprintf("HostListCallback: Expected host list revision at this point to be 1, got %d", hl.Revision)
				fmt.Println(msg)
				errCh <- msg
				return
			}
			// Here everything is ok!
			okCh <- "OK"
		} else {
			msg = fmt.Sprintf("HostListCallback: Did not expect HostListCallback to be called in state %d", state)
			fmt.Println(state)
			errCh <- msg

		}

	})
	if err != nil {
		t.Fatal(err)
	}

	topoConf := `{
	"networks": [{
		"name": "net1",
		"cidr": "10.0.0.0/8",
		"block_mask": 30
	}],

	"topologies": [{
		"networks": ["net1"],
		"map": [{
			"routing": "bla",
			"groups": [{
					"name": "host1",
					"ip": "192.168.99.10"
				},
				{
					"name": "host2",
					"ip": "192.168.99.11"
				}
			]
		}]
	}]
}`
	topoReq := api.TopologyUpdateRequest{}
	err = json.Unmarshal([]byte(topoConf), &topoReq)
	if err != nil {
		t.Fatalf("Cannot parse %s: %v", topoConf, err)
	}
	err = client.IPAM.UpdateTopology(topoReq)
	if err != nil {
		t.Fatal(err)
	}
	for {
		select {
		case msg := <-errCh:
			t.Fatal(msg)
			break
		case <-okCh:
			return
		}
	}

}
*/

// TestWatchBlocksWithCallback tests WatchBlocksWithCallback -- and since it
// uses WatchBlocks internally, implicitly also WatchBlocks
/*
func TestWatchBlocksWithCallback(t *testing.T) {
	defer tearDown(t)
	errCh := make(chan string)
	okCh := make(chan string)
	topoConf := `{
	"networks": [{
		"name": "net1",
		"cidr": "10.0.0.0/8",
		"block_mask": 31
	}],

	"topologies": [{
		"networks": ["net1"],
		"map": [{
			"routing": "bla",
			"groups": [{
					"name": "host1",
					"ip": "192.168.99.10"
				},
				{
					"name": "host2",
					"ip": "192.168.99.11"
				}
			]
		}]
	}]
}`
	client = initClient(t, topoConf)
	err := client.WatchBlocksWithCallback(func(hl api.IPAMBlocksResponse) {
		state++

		var msg string
		fmt.Printf("BlocksListCallback: Got block list of length %d and revision %d in state %d\n", len(hl.Blocks), hl.Revision, state)
		switch state {
		case 1:
			if len(hl.Blocks) != 0 {
				msg = fmt.Sprintf("BlocksListCallback: Expected block length at this point to be 0, got %d", len(hl.Blocks))
				fmt.Println(msg)
				errCh <- msg
				return
			}
			if hl.Revision != 0 {
				msg = fmt.Sprintf("BlocksListCallback: Expected block list revision at this point to be 0, got %d", hl.Revision)
				fmt.Println(msg)
				errCh <- msg
				return
			}
		case 2, 3:
			if len(hl.Blocks) != 1 {
				msg = fmt.Sprintf("BlocksListCallback: Expected block length at this point to be 1, got %d", len(hl.Blocks))
				fmt.Println(msg)
				errCh <- msg
				return
			}
			if hl.Revision != state-1 {
				msg = fmt.Sprintf("HostListCallback: Expected block list revision at this point to be %d, got %d", state-1, hl.Revision)
				fmt.Println(msg)
				errCh <- msg
				return
			}

		case 4, 5:
			if len(hl.Blocks) != 2 {
				msg = fmt.Sprintf("BlocksListCallback: Expected block length at this point to be 2, got %d", len(hl.Blocks))
				fmt.Println(msg)
				errCh <- msg
				return
			}
			if hl.Revision != state-1 {
				msg = fmt.Sprintf("HostListCallback: Expected block list revision at this point to be %d, got %d", state-1, hl.Revision)
				fmt.Println(msg)
				errCh <- msg
				return
			}
			if state == 5 {
				// Here everything is ok!
				okCh <- "OK"
			}
		default:
			msg = fmt.Sprintf("HostListCallback: Did not expect HostListCallback to be called in state %d", state)
			errCh <- msg

		}

	})
	if err != nil {
		t.Fatal(err)
	}

	_, err = client.IPAM.AllocateIP("addr1", "host1", "t1", "s1")
	if err != nil {
		t.Fatal(err)
	}
	_, err = client.IPAM.AllocateIP("addr2", "host1", "t1", "s1")
	if err != nil {
		t.Fatal(err)
	}
	_, err = client.IPAM.AllocateIP("addr3", "host1", "t1", "s1")
	if err != nil {
		t.Fatal(err)
	}
	_, err = client.IPAM.AllocateIP("addr4", "host1", "t1", "s1")
	if err != nil {
		t.Fatal(err)
	}
	for {
		select {
		case msg := <-errCh:
			t.Fatal(msg)
			break
		case <-okCh:
			return
		}
	}
}
*/

/*
func TestConcurrency(t *testing.T) {
	//	defer tearDown(t)
	client = initClient(t, "")

	barrier := make(chan int)
	cnt := 8
	locker, err := client.Store.NewLocker("/lock")
	if err != nil {
		t.Fatal(err)
	}
	for i := 0; i < cnt; i++ {
		go func(i int) {
			fmt.Printf("%d %d Entered\n", i, getGID())
			defer func(i int) {
				fmt.Printf("%d %d Unlocking\n", i, getGID())
				locker.Unlock()
				fmt.Printf("%d %d Unlocked\n", i, getGID())
			}(i)
			fmt.Printf("%d %d Waiting for lock\n", i, getGID())
			locker.Lock()
			fmt.Printf("%d %d Got lock\n", i, getGID())
			val := fmt.Sprintf("Hello from %d %d", i, getGID())
			err := client.Store.Put(fmt.Sprintf("/test%d", i), []byte(val), nil)
			if err != nil {
				fmt.Println(err)
			}
			barrier <- 1
		}(i)
	}
	finishedCnt := 0

WAIT_FOR_FINISH:
	for {
		select {
		case <-barrier:
			finishedCnt++
			fmt.Printf("%d routines finished\n", finishedCnt)
			if finishedCnt == cnt {
				break WAIT_FOR_FINISH
			}
		}
	}
}
*/

type testcase struct {
	name string
	ipam *IPAM
	want interface{}
}

func loadTestFiles(name string, inFile string, outFile string) (testcase, error) {
	input, err := ioutil.ReadFile(inFile)
	if err != nil {
		return testcase{}, fmt.Errorf("error reading test input file: %s", err)
	}
	output, err := ioutil.ReadFile(outFile)
	if err != nil {
		return testcase{}, fmt.Errorf("error reading test output file: %s", err)
	}

	ipamState := &IPAM{}
	err = json.Unmarshal(input, ipamState)
	if err != nil {
		return testcase{}, fmt.Errorf("failed to unmarshal ipam information: %s", err)
	}
	topologyState := &api.TopologyUpdateRequest{}
	err = json.Unmarshal(output, topologyState)
	if err != nil {
		return testcase{}, fmt.Errorf("failed to unmarshal topology information: %s", err)
	}

	return testcase{name, ipamState, topologyState}, nil
}

func Test_getTopologyFromIPAMState(t *testing.T) {
	var testcases []testcase

	testfiles := map[string][]string{
		"multinetwork": {
			"testdata/TestTopologyGetInputMultiNetworks.json",
			"testdata/TestTopologyGetOutputMultiNetworks.json",
		},
		"kubeadm": {
			"testdata/TestTopologyGetInputKubeadm.json",
			"testdata/TestTopologyGetOutputKubeadm.json",
		},
	}

	for name, file := range testfiles {
		tc, err := loadTestFiles(name, file[0], file[1])
		if err != nil {
			t.Errorf("error loading test files: %s", err)
			continue
		}
		testcases = append(testcases, tc)
	}

	for _, tt := range testcases {
		t.Run(tt.name, func(t *testing.T) {
			// TODO: got and tt.want below are not sorted, so there is a fair chance the
			// testcases here may fail, in which case this needs to be fixed by sorting
			// it first.
			if got := getTopologyFromIPAMState(tt.ipam); !reflect.DeepEqual(got, tt.want) {
				bodyGot, errGot := json.MarshalIndent(got, "", "\t")
				bodyWant, errWant := json.MarshalIndent(tt.want, "", "\t")
				if errGot == nil && errWant == nil {
					t.Errorf("getTopology() = \ngot(%s)\nwant(%s)",
						string(bodyGot), string(bodyWant))
				} else {
					t.Errorf("getTopology() = \ngot(%#v)\nwant(%#v)", got, tt.want)
				}
			}
		})
	}
}
<|endoftext|>