<commit_before>package main

import (
	"net/http"
	"os"

	"fmt"
	"strconv"
	"time"

	"github.com/Financial-Times/base-ft-rw-app-go/baseftrwapp"
	fthealth "github.com/Financial-Times/go-fthealth/v1_1"
	"github.com/Financial-Times/http-handlers-go/httphandlers"
	"github.com/Financial-Times/neo-utils-go/neoutils"
	"github.com/Financial-Times/public-people-api/people"
	status "github.com/Financial-Times/service-status-go/httphandlers"
	"github.com/gorilla/mux"
	"github.com/jawher/mow.cli"
	"github.com/rcrowley/go-metrics"
	log "github.com/sirupsen/logrus"
)

func main() {
	app := cli.App("public-people-api-neo4j", "A public RESTful API for accessing People in neo4j")
	neoURL := app.String(cli.StringOpt{
		Name:   "neo-url",
		Value:  "http://localhost:7474/db/data",
		Desc:   "neo4j endpoint URL",
		EnvVar: "NEO_URL",
	})
	logLevel := app.String(cli.StringOpt{
		Name:   "log-level",
		Value:  "INFO",
		Desc:   "Log level to use",
		EnvVar: "LOG_LEVEL",
	})
	port := app.String(cli.StringOpt{
		Name:   "port",
		Value:  "8080",
		Desc:   "Port to listen on",
		EnvVar: "APP_PORT",
	})
	graphiteTCPAddress := app.String(cli.StringOpt{
		Name:   "graphiteTCPAddress",
		Value:  "",
		Desc:   "Graphite TCP address, e.g. graphite.ft.com:2003. Leave as default if you do NOT want to output to graphite (e.g. if running locally)",
		EnvVar: "GRAPHITE_ADDRESS",
	})
	graphitePrefix := app.String(cli.StringOpt{
		Name:   "graphitePrefix",
		Value:  "",
		Desc:   "Prefix to use. Should start with content, include the environment, and the host name. e.g. content.test.public.content.by.concept.api.ftaps59382-law1a-eu-t",
		EnvVar: "GRAPHITE_PREFIX",
	})
	logMetrics := app.Bool(cli.BoolOpt{
		Name:   "logMetrics",
		Value:  false,
		Desc:   "Whether to log metrics. Set to true if running locally and you want metrics output",
		EnvVar: "LOG_METRICS",
	})
	env := app.String(cli.StringOpt{
		Name:  "env",
		Value: "local",
		Desc:  "environment this app is running in",
	})
	cacheDuration := app.String(cli.StringOpt{
		Name:   "cache-duration",
		Value:  "30s",
		Desc:   "Duration Get requests should be cached for. e.g. 2h45m would set the max-age value to '7440' seconds",
		EnvVar: "CACHE_DURATION",
	})

	app.Action = func() {
		lvl, err := log.ParseLevel(*logLevel)
		if err != nil {
			log.Warnf("Log level %s could not be parsed, defaulting to info")
			lvl = log.InfoLevel
		}
		log.SetLevel(lvl)
		log.Info(lvl.String() + ": log level set")
		log.SetFormatter(&log.JSONFormatter{})

		baseftrwapp.OutputMetricsIfRequired(*graphiteTCPAddress, *graphitePrefix, *logMetrics)

		log.Infof("public-people-api will listen on port: %s, connecting to: %s", *port, *neoURL)
		runServer(*neoURL, *port, *cacheDuration, *env)
	}
	log.Infof("Application started with args %s", os.Args)
	app.Run(os.Args)
}

func runServer(neoURL string, port string, cacheDuration string, env string) {

	if duration, durationErr := time.ParseDuration(cacheDuration); durationErr != nil {
		log.Fatalf("Failed to parse cache duration string, %v", durationErr)
	} else {
		people.CacheControlHeader = fmt.Sprintf("max-age=%s, public", strconv.FormatFloat(duration.Seconds(), 'f', 0, 64))
	}

	conf := neoutils.ConnectionConfig{
		BatchSize:     1024,
		Transactional: false,
		HTTPClient: &http.Client{
			Transport: &http.Transport{
				MaxIdleConnsPerHost: 100,
			},
			Timeout: 1 * time.Minute,
		},
		BackgroundConnect: true,
	}
	db, err := neoutils.Connect(neoURL, &conf)

	if err != nil {
		log.Fatalf("Error connecting to neo4j %s", err)
	}

	people.PeopleDriver = people.NewCypherDriver(db, env)

	servicesRouter := mux.NewRouter()

	// Health checks and standards first
	checks := []fthealth.Check{people.HealthCheck()}
	timedHC := fthealth.TimedHealthCheck{
		HealthCheck: fthealth.HealthCheck{
			SystemCode:  "public-people-api",
			Name:        "Public people api",
			Description: "Public API for serving information on People within UPP",
			Checks:      checks,
		},
		Timeout: 10 * time.Second,
	}

	servicesRouter.HandleFunc("/__health", fthealth.Handler(timedHC))

	// Then API specific ones:
	servicesRouter.HandleFunc("/people/{uuid}", people.GetPerson).Methods("GET")
	servicesRouter.HandleFunc("/people/{uuid}", people.MethodNotAllowedHandler)

	var monitoringRouter http.Handler = servicesRouter
	monitoringRouter = httphandlers.TransactionAwareRequestLoggingHandler(log.StandardLogger(), monitoringRouter)
	monitoringRouter = httphandlers.HTTPMetricsHandler(metrics.DefaultRegistry, monitoringRouter)

	// The following endpoints should not be monitored or logged (varnish calls one of these every second, depending on config)
	// The top one of these build info endpoints feels more correct, but the lower one matches what we have in Dropwizard,
	// so it's what apps expect currently same as ping, the content of build-info needs more definition
	http.HandleFunc(status.BuildInfoPath, status.BuildInfoHandler)
	http.HandleFunc("/__gtg", status.NewGoodToGoHandler(people.GTG))
	http.Handle("/", monitoringRouter)

	if err := http.ListenAndServe(":"+port, nil); err != nil {
		log.Fatalf("Unable to start server: %v", err)
	}
}
<commit_msg>Updated the service name<commit_after>package main

import (
	"net/http"
	"os"

	"fmt"
	"strconv"
	"time"

	"github.com/Financial-Times/base-ft-rw-app-go/baseftrwapp"
	fthealth "github.com/Financial-Times/go-fthealth/v1_1"
	"github.com/Financial-Times/http-handlers-go/httphandlers"
	"github.com/Financial-Times/neo-utils-go/neoutils"
	"github.com/Financial-Times/public-people-api/people"
	status "github.com/Financial-Times/service-status-go/httphandlers"
	"github.com/gorilla/mux"
	"github.com/jawher/mow.cli"
	"github.com/rcrowley/go-metrics"
	log "github.com/sirupsen/logrus"
)

func main() {
	app := cli.App("public-people-api-neo4j", "A public RESTful API for accessing People in neo4j")
	neoURL := app.String(cli.StringOpt{
		Name:   "neo-url",
		Value:  "http://localhost:7474/db/data",
		Desc:   "neo4j endpoint URL",
		EnvVar: "NEO_URL",
	})
	logLevel := app.String(cli.StringOpt{
		Name:   "log-level",
		Value:  "INFO",
		Desc:   "Log level to use",
		EnvVar: "LOG_LEVEL",
	})
	port := app.String(cli.StringOpt{
		Name:   "port",
		Value:  "8080",
		Desc:   "Port to listen on",
		EnvVar: "APP_PORT",
	})
	graphiteTCPAddress := app.String(cli.StringOpt{
		Name:   "graphiteTCPAddress",
		Value:  "",
		Desc:   "Graphite TCP address, e.g. graphite.ft.com:2003. Leave as default if you do NOT want to output to graphite (e.g. if running locally)",
		EnvVar: "GRAPHITE_ADDRESS",
	})
	graphitePrefix := app.String(cli.StringOpt{
		Name:   "graphitePrefix",
		Value:  "",
		Desc:   "Prefix to use. Should start with content, include the environment, and the host name. e.g. content.test.public.content.by.concept.api.ftaps59382-law1a-eu-t",
		EnvVar: "GRAPHITE_PREFIX",
	})
	logMetrics := app.Bool(cli.BoolOpt{
		Name:   "logMetrics",
		Value:  false,
		Desc:   "Whether to log metrics. Set to true if running locally and you want metrics output",
		EnvVar: "LOG_METRICS",
	})
	env := app.String(cli.StringOpt{
		Name:  "env",
		Value: "local",
		Desc:  "environment this app is running in",
	})
	cacheDuration := app.String(cli.StringOpt{
		Name:   "cache-duration",
		Value:  "30s",
		Desc:   "Duration Get requests should be cached for. e.g. 2h45m would set the max-age value to '7440' seconds",
		EnvVar: "CACHE_DURATION",
	})

	app.Action = func() {
		lvl, err := log.ParseLevel(*logLevel)
		if err != nil {
			log.Warnf("Log level %s could not be parsed, defaulting to info")
			lvl = log.InfoLevel
		}
		log.SetLevel(lvl)
		log.Info(lvl.String() + ": log level set")
		log.SetFormatter(&log.JSONFormatter{})

		baseftrwapp.OutputMetricsIfRequired(*graphiteTCPAddress, *graphitePrefix, *logMetrics)

		log.Infof("public-people-api will listen on port: %s, connecting to: %s", *port, *neoURL)
		runServer(*neoURL, *port, *cacheDuration, *env)
	}
	log.Infof("Application started with args %s", os.Args)
	app.Run(os.Args)
}

func runServer(neoURL string, port string, cacheDuration string, env string) {

	if duration, durationErr := time.ParseDuration(cacheDuration); durationErr != nil {
		log.Fatalf("Failed to parse cache duration string, %v", durationErr)
	} else {
		people.CacheControlHeader = fmt.Sprintf("max-age=%s, public", strconv.FormatFloat(duration.Seconds(), 'f', 0, 64))
	}

	conf := neoutils.ConnectionConfig{
		BatchSize:     1024,
		Transactional: false,
		HTTPClient: &http.Client{
			Transport: &http.Transport{
				MaxIdleConnsPerHost: 100,
			},
			Timeout: 1 * time.Minute,
		},
		BackgroundConnect: true,
	}
	db, err := neoutils.Connect(neoURL, &conf)

	if err != nil {
		log.Fatalf("Error connecting to neo4j %s", err)
	}

	people.PeopleDriver = people.NewCypherDriver(db, env)

	servicesRouter := mux.NewRouter()

	// Health checks and standards first
	checks := []fthealth.Check{people.HealthCheck()}
	timedHC := fthealth.TimedHealthCheck{
		HealthCheck: fthealth.HealthCheck{
			SystemCode:  "public-people-api",
			Name:        "Public People API",
			Description: "Public API for serving information on People within UPP",
			Checks:      checks,
		},
		Timeout: 10 * time.Second,
	}

	servicesRouter.HandleFunc("/__health", fthealth.Handler(timedHC))

	// Then API specific ones:
	servicesRouter.HandleFunc("/people/{uuid}", people.GetPerson).Methods("GET")
	servicesRouter.HandleFunc("/people/{uuid}", people.MethodNotAllowedHandler)

	var monitoringRouter http.Handler = servicesRouter
	monitoringRouter = httphandlers.TransactionAwareRequestLoggingHandler(log.StandardLogger(), monitoringRouter)
	monitoringRouter = httphandlers.HTTPMetricsHandler(metrics.DefaultRegistry, monitoringRouter)

	// The following endpoints should not be monitored or logged (varnish calls one of these every second, depending on config)
	// The top one of these build info endpoints feels more correct, but the lower one matches what we have in Dropwizard,
	// so it's what apps expect currently same as ping, the content of build-info needs more definition
	http.HandleFunc(status.BuildInfoPath, status.BuildInfoHandler)
	http.HandleFunc("/__gtg", status.NewGoodToGoHandler(people.GTG))
	http.Handle("/", monitoringRouter)

	if err := http.ListenAndServe(":"+port, nil); err != nil {
		log.Fatalf("Unable to start server: %v", err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"log"
	"os"
	"strings"
	"sync"

	"github.com/pkg/errors"

	"github.com/andviro/grayproxy/gelf"
)

type urlList []string

func (ul *urlList) Set(val string) error {
	*ul = append(*ul, val)
	return nil
}

func (ul *urlList) String() string {
	return strings.Join(*ul, ",")
}

type listener interface {
	listen(dest chan gelf.Chunk) (err error)
}

type sender interface {
	send(data []byte) (err error)
}

type app struct {
	inputURLs                                         urlList
	outputURLs                                        urlList
	maxChunkSize, maxMessageSize, decompressSizeLimit int
	assembleTimeout, stopTimeout, sendTimeout         int

	ins  []listener
	outs []sender
}

func (app *app) configure() (err error) {
	fs := flag.NewFlagSet("grayproxy", flag.ExitOnError)
	fs.Var(&app.inputURLs, "in", "input address in form schema://address:port (may be specified multiple times). Default: udp://:12201")
	fs.Var(&app.outputURLs, "out", "output address in form schema://address:port (may be specified multiple times)")
	fs.IntVar(&app.maxChunkSize, "maxChunkSize", 8192, "maximum UDP chunk size")
	fs.IntVar(&app.maxMessageSize, "maxMessageSize", 128*1024, "maximum UDP de-chunked message size")
	fs.IntVar(&app.decompressSizeLimit, "decompressSizeLimit", 1024*1024, "maximum decompressed message size")
	fs.IntVar(&app.assembleTimeout, "assembleTimeout", 1000, "maximum UDP chunk assemble time (ms)")
	fs.IntVar(&app.sendTimeout, "sendTimeout", 1000, "maximum TCP or HTTP output timeout (ms)")
	fs.IntVar(&app.stopTimeout, "stopTimeout", 2000, "server stop timeout (ms)")
	if err = fs.Parse(os.Args[1:]); err != nil {
		return errors.Wrap(err, "parsing command-line")
	}
	if len(app.inputURLs) == 0 {
		app.inputURLs = urlList{"udp://:12201"}
	}
	app.ins = make([]listener, len(app.inputURLs))
	for i, v := range app.inputURLs {
		switch {
		case strings.HasPrefix(v, "udp://"):
			app.ins[i] = &udpListener{
				Address:             strings.TrimPrefix(v, "udp://"),
				MaxChunkSize:        app.maxChunkSize,
				MaxMessageSize:      app.maxMessageSize,
				DecompressSizeLimit: app.decompressSizeLimit,
				AssembleTimeout:     app.assembleTimeout,
			}
		case strings.HasPrefix(v, "http://"):
			l := new(httpListener)
			l.Address = strings.TrimPrefix(v, "http://")
			l.StopTimeout = app.stopTimeout
			app.ins[i] = l
		default:
			app.ins[i] = &tcpListener{
				Address: strings.TrimPrefix(v, "tcp://"),
			}
		}
		log.Printf("Added input %d at %s", i, v)
	}

	if len(app.outputURLs) == 0 {
		log.Print("WARNING: no outputs configured")
	}
	app.outs = make([]sender, len(app.outputURLs))
	for i, v := range app.outputURLs {
		switch {
		case strings.HasPrefix(v, "http://") || strings.HasPrefix(v, "https://"):
			app.outs[i] = &httpSender{Address: v, SendTimeout: app.sendTimeout}
		default:
			app.outs[i] = &tcpSender{Address: strings.TrimPrefix("tcp://", v), SendTimeout: app.sendTimeout}
		}
		log.Printf("Added output %d: %s", i, v)
	}
	return
}

func (app *app) run() (err error) {
	if err = app.configure(); err != nil {
		return errors.Wrap(err, "configuring app")
	}

	msgs := make(chan gelf.Chunk, len(app.ins))
	defer close(msgs)
	go func() {
		for msg := range msgs {
			var sent bool
			for i, out := range app.outs {
				if err := out.send(msg); err != nil {
					log.Printf("ERROR: sending message to output %d: %v", i, err)
					continue
				}
				sent = true
				break
			}
			if !sent {
				// TODO: message buffering on disk
				log.Printf("WARNING: message not sent: %q", string(msg))
			}
		}
	}()

	var wg sync.WaitGroup
	for i := range app.ins {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			err := app.ins[i].listen(msgs)
			if err != nil {
				log.Printf("Input %d exited with error: %+v", i, err)
			}
		}(i)
	}
	wg.Wait()
	log.Print("Bye")
	return
}
<commit_msg>closes #1 (thanks, @bangert)<commit_after>package main

import (
	"flag"
	"log"
	"os"
	"strings"
	"sync"

	"github.com/pkg/errors"

	"github.com/andviro/grayproxy/gelf"
)

type urlList []string

func (ul *urlList) Set(val string) error {
	*ul = append(*ul, val)
	return nil
}

func (ul *urlList) String() string {
	return strings.Join(*ul, ",")
}

type listener interface {
	listen(dest chan gelf.Chunk) (err error)
}

type sender interface {
	send(data []byte) (err error)
}

type app struct {
	inputURLs                                         urlList
	outputURLs                                        urlList
	maxChunkSize, maxMessageSize, decompressSizeLimit int
	assembleTimeout, stopTimeout, sendTimeout         int

	ins  []listener
	outs []sender
}

func (app *app) configure() (err error) {
	fs := flag.NewFlagSet("grayproxy", flag.ExitOnError)
	fs.Var(&app.inputURLs, "in", "input address in form schema://address:port (may be specified multiple times). Default: udp://:12201")
	fs.Var(&app.outputURLs, "out", "output address in form schema://address:port (may be specified multiple times)")
	fs.IntVar(&app.maxChunkSize, "maxChunkSize", 8192, "maximum UDP chunk size")
	fs.IntVar(&app.maxMessageSize, "maxMessageSize", 128*1024, "maximum UDP de-chunked message size")
	fs.IntVar(&app.decompressSizeLimit, "decompressSizeLimit", 1024*1024, "maximum decompressed message size")
	fs.IntVar(&app.assembleTimeout, "assembleTimeout", 1000, "maximum UDP chunk assemble time (ms)")
	fs.IntVar(&app.sendTimeout, "sendTimeout", 1000, "maximum TCP or HTTP output timeout (ms)")
	fs.IntVar(&app.stopTimeout, "stopTimeout", 2000, "server stop timeout (ms)")
	if err = fs.Parse(os.Args[1:]); err != nil {
		return errors.Wrap(err, "parsing command-line")
	}
	if len(app.inputURLs) == 0 {
		app.inputURLs = urlList{"udp://:12201"}
	}
	app.ins = make([]listener, len(app.inputURLs))
	for i, v := range app.inputURLs {
		switch {
		case strings.HasPrefix(v, "udp://"):
			app.ins[i] = &udpListener{
				Address:             strings.TrimPrefix(v, "udp://"),
				MaxChunkSize:        app.maxChunkSize,
				MaxMessageSize:      app.maxMessageSize,
				DecompressSizeLimit: app.decompressSizeLimit,
				AssembleTimeout:     app.assembleTimeout,
			}
		case strings.HasPrefix(v, "http://"):
			l := new(httpListener)
			l.Address = strings.TrimPrefix(v, "http://")
			l.StopTimeout = app.stopTimeout
			app.ins[i] = l
		default:
			app.ins[i] = &tcpListener{
				Address: strings.TrimPrefix(v, "tcp://"),
			}
		}
		log.Printf("Added input %d at %s", i, v)
	}

	if len(app.outputURLs) == 0 {
		log.Print("WARNING: no outputs configured")
	}
	app.outs = make([]sender, len(app.outputURLs))
	for i, v := range app.outputURLs {
		switch {
		case strings.HasPrefix(v, "http://") || strings.HasPrefix(v, "https://"):
			app.outs[i] = &httpSender{Address: v, SendTimeout: app.sendTimeout}
		default:
			app.outs[i] = &tcpSender{Address: strings.TrimPrefix(v, "tcp://"), SendTimeout: app.sendTimeout}
		}
		log.Printf("Added output %d: %s", i, v)
	}
	return
}

func (app *app) run() (err error) {
	if err = app.configure(); err != nil {
		return errors.Wrap(err, "configuring app")
	}

	msgs := make(chan gelf.Chunk, len(app.ins))
	defer close(msgs)
	go func() {
		for msg := range msgs {
			var sent bool
			for i, out := range app.outs {
				if err := out.send(msg); err != nil {
					log.Printf("ERROR: sending message to output %d: %v", i, err)
					continue
				}
				sent = true
				break
			}
			if !sent {
				// TODO: message buffering on disk
				log.Printf("WARNING: message not sent: %q", string(msg))
			}
		}
	}()

	var wg sync.WaitGroup
	for i := range app.ins {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			err := app.ins[i].listen(msgs)
			if err != nil {
				log.Printf("Input %d exited with error: %+v", i, err)
			}
		}(i)
	}
	wg.Wait()
	log.Print("Bye")
	return
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/Financial-Times/base-ft-rw-app-go/baseftrwapp"
	"github.com/Financial-Times/go-fthealth/v1a"
	"github.com/Financial-Times/http-handlers-go/httphandlers"
	"github.com/Financial-Times/public-brands-api/brands"
	handlers "github.com/Financial-Times/service-status-go/httphandlers"
	log "github.com/Sirupsen/logrus"
	"github.com/gorilla/mux"
	"github.com/jawher/mow.cli"
	"github.com/jmcvetta/neoism"
	"github.com/rcrowley/go-metrics"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
)

func main() {
	app := cli.App("public-brands-api", "A public RESTful API for accessing Brands in neo4j")
	neoURL := app.StringOpt("neo-url", "http://localhost:7474/db/data", "neo4j endpoint URL")
	port := app.StringOpt("port", "8080", "Port to listen on")
	logLevel := app.StringOpt("log-level", "INFO", "Logging level (DEBUG, INFO, WARN, ERROR)")
	env := app.StringOpt("env", "local", "environment this app is running in")
	graphiteTCPAddress := app.StringOpt("graphiteTCPAddress", "",
		"Graphite TCP address, e.g. graphite.ft.com:2003. Leave as default if you do NOT want to output to graphite (e.g. if running locally)")
	graphitePrefix := app.StringOpt("graphitePrefix", "",
		"Prefix to use. Should start with content, include the environment, and the host name. e.g. content.test.public.brands.api.ftaps59382-law1a-eu-t")
	logMetrics := app.BoolOpt("logMetrics", false, "Whether to log metrics. Set to true if running locally and you want metrics output")
	cacheDuration := app.StringOpt("cache-duration", "1h", "Duration Get requests should be cached for. e.g. 2h45m would set the max-age value to '7440' seconds")

	app.Action = func() {

		baseftrwapp.OutputMetricsIfRequired(*graphiteTCPAddress, *graphitePrefix, *logMetrics)
		if *env != "local" {
			f, err := os.OpenFile("/var/log/apps/public-brands-api-go-app.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0755)
			if err == nil {
				log.SetOutput(f)
				log.SetFormatter(&log.TextFormatter{DisableColors: true})
			} else {
				log.Fatalf("Failed to initialise log file, %v", err)
			}

			defer f.Close()
		}

		log.Infof("public-brands-api will listen on port: %s, connecting to: %s", *port, *neoURL)
		runServer(*neoURL, *port, *cacheDuration, *env)

	}
	setLogLevel(strings.ToUpper(*logLevel))
	log.Infof("Application started with args %s", os.Args)
	app.Run(os.Args)
}

func runServer(neoURL string, port string, cacheDuration string, env string) {

	if duration, durationErr := time.ParseDuration(cacheDuration); durationErr != nil {
		log.Fatalf("Failed to parse cache duration string, %v", durationErr)
	} else {
		brands.CacheControlHeader = fmt.Sprintf("max-age=%s, public", strconv.FormatFloat(duration.Seconds(), 'f', 0, 64))
	}

	db, err := neoism.Connect(neoURL)
	db.Session.Client = &http.Client{Transport: &http.Transport{MaxIdleConnsPerHost: 100}}
	if err != nil {
		log.Fatalf("Error connecting to neo4j %s", err)
	}
	brands.BrandsDriver = brands.NewCypherDriver(db, env)

	servicesRouter := mux.NewRouter()

	servicesRouter.HandleFunc("/brands/{uuid}", brands.GetBrand).Methods("GET")
	servicesRouter.HandleFunc("/brands/{uuid}", brands.MethodNotAllowedHandler)

	var monitoringRouter http.Handler = servicesRouter
	monitoringRouter = httphandlers.TransactionAwareRequestLoggingHandler(log.StandardLogger(), monitoringRouter)
	monitoringRouter = httphandlers.HTTPMetricsHandler(metrics.DefaultRegistry, monitoringRouter)

	http.HandleFunc("/__health", v1a.Handler("PublicBrandsRead Healthchecks",
		"Checks for accessing neo4j", brands.HealthCheck()))
	http.HandleFunc("/health", v1a.Handler("PublicBrandsRead Healthchecks",
		"Checks for accessing neo4j", brands.HealthCheck()))

	http.Handle("/", monitoringRouter)

	mux := http.NewServeMux()
	handlers.RegisterAll(mux)

	if err := http.ListenAndServe(":"+port, nil); err != nil {
		log.Fatalf("Unable to start server: %v", err)
	}

	if err := http.ListenAndServe(":"+port,
		httphandlers.HTTPMetricsHandler(metrics.DefaultRegistry,
			httphandlers.TransactionAwareRequestLoggingHandler(log.StandardLogger(), monitoringRouter))); err != nil {
		log.Fatalf("Unable to start server: %v", err)
	}
}

func setLogLevel(level string) {
	switch level {
	case "DEBUG":
		log.SetLevel(log.DebugLevel)
	case "INFO":
		log.SetLevel(log.InfoLevel)
	case "WARN":
		log.SetLevel(log.WarnLevel)
	case "ERROR":
		log.SetLevel(log.ErrorLevel)
	default:
		log.Errorf("Requested log level %s is not supported, will default to INFO level", level)
		log.SetLevel(log.InfoLevel)
	}
	log.Debugf("Logging level set to %s", level)
}
<commit_msg>Using common status libs<commit_after>package main

import (
	"fmt"
	"github.com/Financial-Times/base-ft-rw-app-go/baseftrwapp"
	"github.com/Financial-Times/go-fthealth/v1a"
	"github.com/Financial-Times/http-handlers-go/httphandlers"
	"github.com/Financial-Times/public-brands-api/brands"
	status "github.com/Financial-Times/service-status-go/httphandlers"
	log "github.com/Sirupsen/logrus"
	"github.com/gorilla/mux"
	"github.com/jawher/mow.cli"
	"github.com/jmcvetta/neoism"
	"github.com/rcrowley/go-metrics"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
)

func main() {
	app := cli.App("public-brands-api", "A public RESTful API for accessing Brands in neo4j")
	neoURL := app.StringOpt("neo-url", "http://localhost:7474/db/data", "neo4j endpoint URL")
	port := app.StringOpt("port", "8080", "Port to listen on")
	logLevel := app.StringOpt("log-level", "INFO", "Logging level (DEBUG, INFO, WARN, ERROR)")
	env := app.StringOpt("env", "local", "environment this app is running in")
	graphiteTCPAddress := app.StringOpt("graphiteTCPAddress", "",
		"Graphite TCP address, e.g. graphite.ft.com:2003. Leave as default if you do NOT want to output to graphite (e.g. if running locally)")
	graphitePrefix := app.StringOpt("graphitePrefix", "",
		"Prefix to use. Should start with content, include the environment, and the host name. e.g. content.test.public.brands.api.ftaps59382-law1a-eu-t")
	logMetrics := app.BoolOpt("logMetrics", false, "Whether to log metrics. Set to true if running locally and you want metrics output")
	cacheDuration := app.StringOpt("cache-duration", "1h", "Duration Get requests should be cached for. e.g. 2h45m would set the max-age value to '7440' seconds")

	app.Action = func() {

		baseftrwapp.OutputMetricsIfRequired(*graphiteTCPAddress, *graphitePrefix, *logMetrics)
		if *env != "local" {
			f, err := os.OpenFile("/var/log/apps/public-brands-api-go-app.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0755)
			if err == nil {
				log.SetOutput(f)
				log.SetFormatter(&log.TextFormatter{DisableColors: true})
			} else {
				log.Fatalf("Failed to initialise log file, %v", err)
			}

			defer f.Close()
		}

		log.Infof("public-brands-api will listen on port: %s, connecting to: %s", *port, *neoURL)
		runServer(*neoURL, *port, *cacheDuration, *env)

	}
	setLogLevel(strings.ToUpper(*logLevel))
	log.Infof("Application started with args %s", os.Args)
	app.Run(os.Args)
}

func runServer(neoURL string, port string, cacheDuration string, env string) {

	if duration, durationErr := time.ParseDuration(cacheDuration); durationErr != nil {
		log.Fatalf("Failed to parse cache duration string, %v", durationErr)
	} else {
		brands.CacheControlHeader = fmt.Sprintf("max-age=%s, public", strconv.FormatFloat(duration.Seconds(), 'f', 0, 64))
	}

	db, err := neoism.Connect(neoURL)
	db.Session.Client = &http.Client{Transport: &http.Transport{MaxIdleConnsPerHost: 100}}
	if err != nil {
		log.Fatalf("Error connecting to neo4j %s", err)
	}
	brands.BrandsDriver = brands.NewCypherDriver(db, env)

	servicesRouter := mux.NewRouter()

	servicesRouter.HandleFunc("/brands/{uuid}", brands.GetBrand).Methods("GET")
	servicesRouter.HandleFunc("/brands/{uuid}", brands.MethodNotAllowedHandler)

	var monitoringRouter http.Handler = servicesRouter
	monitoringRouter = httphandlers.TransactionAwareRequestLoggingHandler(log.StandardLogger(), monitoringRouter)
	monitoringRouter = httphandlers.HTTPMetricsHandler(metrics.DefaultRegistry, monitoringRouter)

	http.HandleFunc("/__health", v1a.Handler("PublicBrandsRead Healthchecks",
		"Checks for accessing neo4j", brands.HealthCheck()))
	http.HandleFunc("/health", v1a.Handler("PublicBrandsRead Healthchecks",
		"Checks for accessing neo4j", brands.HealthCheck()))
	http.HandleFunc(status.PingPath, status.PingHandler)
	http.HandleFunc(status.PingPathDW, status.PingHandler)
	http.HandleFunc(status.BuildInfoPath, status.BuildInfoHandler)
	http.HandleFunc(status.BuildInfoPathDW, status.BuildInfoHandler)
	http.Handle("/", monitoringRouter)

	if err := http.ListenAndServe(":"+port, nil); err != nil {
		log.Fatalf("Unable to start server: %v", err)
	}

	if err := http.ListenAndServe(":"+port,
		httphandlers.HTTPMetricsHandler(metrics.DefaultRegistry,
			httphandlers.TransactionAwareRequestLoggingHandler(log.StandardLogger(), monitoringRouter))); err != nil {
		log.Fatalf("Unable to start server: %v", err)
	}
}

func setLogLevel(level string) {
	switch level {
	case "DEBUG":
		log.SetLevel(log.DebugLevel)
	case "INFO":
		log.SetLevel(log.InfoLevel)
	case "WARN":
		log.SetLevel(log.WarnLevel)
	case "ERROR":
		log.SetLevel(log.ErrorLevel)
	default:
		log.Errorf("Requested log level %s is not supported, will default to INFO level", level)
		log.SetLevel(log.InfoLevel)
	}
	log.Debugf("Logging level set to %s", level)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"
	_ "net/http/pprof"
	"os"
	"strings"

	"github.com/Financial-Times/annotations-rw-neo4j/annotations"
	"github.com/Financial-Times/base-ft-rw-app-go/baseftrwapp"
	"github.com/Financial-Times/go-fthealth/v1a"
	"github.com/Financial-Times/http-handlers-go/httphandlers"
	"github.com/Financial-Times/neo-utils-go/neoutils"
	status "github.com/Financial-Times/service-status-go/httphandlers"
	log "github.com/Sirupsen/logrus"
	"github.com/gorilla/mux"
	"github.com/jawher/mow.cli"
	"github.com/rcrowley/go-metrics"
)

func main() {
	log.Infof("Application started with args %s", os.Args)
	app := cli.App("annotations-rw-neo4j", "A RESTful API for managing Annotations in neo4j")
	neoURL := app.StringOpt("neo-url", "http://localhost:7474/db/data", "neo4j endpoint URL")
	port := app.IntOpt("port", 8080, "Port to listen on")
	env := app.StringOpt("env", "local", "environment this app is running in")
	batchSize := app.IntOpt("batchSize", 1024, "Maximum number of statements to execute per batch")
	graphiteTCPAddress := app.StringOpt("graphiteTCPAddress", "",
		"Graphite TCP address, e.g. graphite.ft.com:2003. Leave as default if you do NOT want to output to graphite (e.g. if running locally)")
	graphitePrefix := app.StringOpt("graphitePrefix", "",
		"Prefix to use. Should start with content, include the environment, and the host name. e.g. content.test.annotation.rw.neo4j.ftaps58938-law1a-eu-t")
	logMetrics := app.BoolOpt("logMetrics", false, "Whether to log metrics. Set to true if running locally and you want metrics output")
	logLevel := app.StringOpt("log-level", "INFO", "Logging level (DEBUG, INFO, WARN, ERROR)")
	platformVersion := app.StringOpt("platformVersion", "", "Annotation source platform. Possible values are: v1 or v2.")

	app.Action = func() {
		log.Infof("annotations-rw-neo4j will listen on port: %d, connecting to: %s", *port, *neoURL)

		conf := neoutils.DefaultConnectionConfig()
		conf.BatchSize = *batchSize
		db, err := neoutils.Connect(*neoURL, conf)

		if err != nil {
			log.Fatalf("Error connecting to neo4j %s", err)
		}

		if *env != "local" {
			f, err := os.OpenFile("/var/log/apps/annotations-rw-neo4j-go-app.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0755)
			if err == nil {
				log.SetOutput(f)
				log.SetFormatter(&log.TextFormatter{})
			} else {
				log.Fatalf("Failed to initialise log file, %v", err)
			}

			defer f.Close()
		}
		annotationsService := annotations.NewCypherAnnotationsService(db, *platformVersion)
		httpHandlers := httpHandlers{annotationsService}

		// don't want to monitor or log these endpoints, they are called a lot
		http.HandleFunc(status.BuildInfoPath, status.BuildInfoHandler)
		http.HandleFunc(status.BuildInfoPathDW, status.BuildInfoHandler)

		r := router(httpHandlers)
		http.Handle("/", r)

		if err := http.ListenAndServe(fmt.Sprintf(":%d", *port), nil); err != nil {
			log.Fatalf("Unable to start server: %v", err)
		}

		if err := http.ListenAndServe(fmt.Sprintf(":%d", *port),
			httphandlers.HTTPMetricsHandler(metrics.DefaultRegistry,
				httphandlers.TransactionAwareRequestLoggingHandler(log.StandardLogger(), r))); err != nil {
			log.Fatalf("Unable to start server: %v", err)
		}
		baseftrwapp.OutputMetricsIfRequired(*graphiteTCPAddress, *graphitePrefix, *logMetrics)

	}
	setLogLevel(strings.ToUpper(*logLevel))
	app.Run(os.Args)
}

func router(hh httpHandlers) http.Handler {
	servicesRouter := mux.NewRouter()
	servicesRouter.Headers("Content-type: application/json")

	// Healthchecks and standards first
	servicesRouter.HandleFunc("/__health", v1a.Handler("Annotations RW Healthchecks",
		"Checks for accessing neo4j", hh.HealthCheck()))
	servicesRouter.HandleFunc(status.PingPath, status.PingHandler)
	servicesRouter.HandleFunc(status.PingPathDW, status.PingHandler)

	// Then API specific ones:
	servicesRouter.HandleFunc("/content/{uuid}/annotations", hh.GetAnnotations).Methods("GET")
	servicesRouter.HandleFunc("/content/{uuid}/annotations", hh.PutAnnotations).Methods("PUT")
	servicesRouter.HandleFunc("/content/{uuid}/annotations", hh.DeleteAnnotations).Methods("DELETE")
	servicesRouter.HandleFunc("/content/annotations/__count", hh.CountAnnotations).Methods("GET")

	var monitoringRouter http.Handler = servicesRouter
	monitoringRouter = httphandlers.TransactionAwareRequestLoggingHandler(log.StandardLogger(), monitoringRouter)
	monitoringRouter = httphandlers.HTTPMetricsHandler(metrics.DefaultRegistry, monitoringRouter)

	return monitoringRouter
}

func setLogLevel(level string) {
	switch level {
	case "DEBUG":
		log.SetLevel(log.DebugLevel)
	case "INFO":
		log.SetLevel(log.InfoLevel)
	case "WARN":
		log.SetLevel(log.WarnLevel)
	case "ERROR":
		log.SetLevel(log.ErrorLevel)
	default:
		log.Errorf("Requested log level %s is not supported, will default to INFO level", level)
		log.SetLevel(log.InfoLevel)
	}
	log.Debugf("Logging level set to %s", level)
}
<commit_msg>Fixed issue with passing ENV to application.<commit_after>package main

import (
	"fmt"
	"net/http"
	_ "net/http/pprof"
	"os"
	"strings"

	"github.com/Financial-Times/annotations-rw-neo4j/annotations"
	"github.com/Financial-Times/base-ft-rw-app-go/baseftrwapp"
	"github.com/Financial-Times/go-fthealth/v1a"
	"github.com/Financial-Times/http-handlers-go/httphandlers"
	"github.com/Financial-Times/neo-utils-go/neoutils"
	status "github.com/Financial-Times/service-status-go/httphandlers"
	log "github.com/Sirupsen/logrus"
	"github.com/gorilla/mux"
	"github.com/jawher/mow.cli"
	"github.com/rcrowley/go-metrics"
)

func main() {

	app :=  cli.App("annotations-rw-neo4j", "A RESTful API for managing Annotations in neo4j")
	neoURL := app.String(cli.StringOpt{
		Name:   "neo-url",
		Value:  "http://localhost:7474/db/data",
		Desc:   "neo4j endpoint URL",
		EnvVar: "NEO_URL",
	})
	graphiteTCPAddress := app.String(cli.StringOpt{
		Name:   "graphiteTCPAddress",
		Value:  "",
		Desc:   "Graphite TCP address, e.g. graphite.ft.com:2003. Leave as default if you do NOT want to output to graphite (e.g. if running locally",
		EnvVar: "GRAPHITE_ADDRESS",
	})
	graphitePrefix := app.String(cli.StringOpt{
		Name:   "graphitePrefix",
		Value:  "",
		Desc:   "Prefix to use. Should start with content, include the environment, and the host name. e.g. coco.pre-prod.roles-rw-neo4j.1 or content.test.people.rw.neo4j.ftaps58938-law1a-eu-t",
		EnvVar: "GRAPHITE_PREFIX",
	})
	port := app.Int(cli.IntOpt{
		Name:   "port",
		Value:  8080,
		Desc:   "Port to listen on",
		EnvVar: "APP_PORT",
	})
	batchSize := app.Int(cli.IntOpt{
		Name:   "batchSize",
		Value:  1024,
		Desc:   "Maximum number of statements to execute per batch",
		EnvVar: "BATCH_SIZE",
	})
	logMetrics := app.Bool(cli.BoolOpt{
		Name:   "logMetrics",
		Value:  false,
		Desc:   "Whether to log metrics. Set to true if running locally and you want metrics output",
		EnvVar: "LOG_METRICS",
	})
	env := app.String(cli.StringOpt{
		Name:  "env",
		Value: "local",
		Desc:  "environment this app is running in",
	})
	logLevel := app.String(cli.StringOpt{
		Name:   "log-level",
		Value:  "INFO",
		Desc:   "Logging level (DEBUG, INFO, WARN, ERROR)",
		EnvVar: "LOG_LEVEL",
	})
	platformVersion := app.String(cli.StringOpt{
		Name:   "platformVersion",
		Value:  "",
		Desc:   "Annotation source platform. Possible values are: v1 or v2.",
		EnvVar: "PLATFORM_VERSION",
	})

	app.Action = func() {
		log.Infof("annotations-rw-neo4j will listen on port: %d, connecting to: %s", *port, *neoURL)

		conf := neoutils.DefaultConnectionConfig()
		conf.BatchSize = *batchSize
		db, err := neoutils.Connect(*neoURL, conf)

		if err != nil {
			log.Fatalf("Error connecting to neo4j %s", err)
		}

		if *env != "local" {
			f, err := os.OpenFile("/var/log/apps/annotations-rw-neo4j-go-app.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0755)
			if err == nil {
				log.SetOutput(f)
				log.SetFormatter(&log.TextFormatter{})
			} else {
				log.Fatalf("Failed to initialise log file, %v", err)
			}

			defer f.Close()
		}
		annotationsService := annotations.NewCypherAnnotationsService(db, *platformVersion)
		httpHandlers := httpHandlers{annotationsService}

		// don't want to monitor or log these endpoints, they are called a lot
		http.HandleFunc(status.BuildInfoPath, status.BuildInfoHandler)
		http.HandleFunc(status.BuildInfoPathDW, status.BuildInfoHandler)

		r := router(httpHandlers)
		http.Handle("/", r)

		if err := http.ListenAndServe(fmt.Sprintf(":%d", *port), nil); err != nil {
			log.Fatalf("Unable to start server: %v", err)
		}

		if err := http.ListenAndServe(fmt.Sprintf(":%d", *port),
			httphandlers.HTTPMetricsHandler(metrics.DefaultRegistry,
				httphandlers.TransactionAwareRequestLoggingHandler(log.StandardLogger(), r))); err != nil {
			log.Fatalf("Unable to start server: %v", err)
		}
		baseftrwapp.OutputMetricsIfRequired(*graphiteTCPAddress, *graphitePrefix, *logMetrics)

	}
	setLogLevel(strings.ToUpper(*logLevel))
	log.Infof("Application started with args %s", os.Args)
	app.Run(os.Args)
}

func router(hh httpHandlers) http.Handler {
	servicesRouter := mux.NewRouter()
	servicesRouter.Headers("Content-type: application/json")

	// Healthchecks and standards first
	servicesRouter.HandleFunc("/__health", v1a.Handler("Annotations RW Healthchecks",
		"Checks for accessing neo4j", hh.HealthCheck()))
	servicesRouter.HandleFunc(status.PingPath, status.PingHandler)
	servicesRouter.HandleFunc(status.PingPathDW, status.PingHandler)

	// Then API specific ones:
	servicesRouter.HandleFunc("/content/{uuid}/annotations", hh.GetAnnotations).Methods("GET")
	servicesRouter.HandleFunc("/content/{uuid}/annotations", hh.PutAnnotations).Methods("PUT")
	servicesRouter.HandleFunc("/content/{uuid}/annotations", hh.DeleteAnnotations).Methods("DELETE")
	servicesRouter.HandleFunc("/content/annotations/__count", hh.CountAnnotations).Methods("GET")

	var monitoringRouter http.Handler = servicesRouter
	monitoringRouter = httphandlers.TransactionAwareRequestLoggingHandler(log.StandardLogger(), monitoringRouter)
	monitoringRouter = httphandlers.HTTPMetricsHandler(metrics.DefaultRegistry, monitoringRouter)

	return monitoringRouter
}

func setLogLevel(level string) {
	switch level {
	case "DEBUG":
		log.SetLevel(log.DebugLevel)
	case "INFO":
		log.SetLevel(log.InfoLevel)
	case "WARN":
		log.SetLevel(log.WarnLevel)
	case "ERROR":
		log.SetLevel(log.ErrorLevel)
	default:
		log.Errorf("Requested log level %s is not supported, will default to INFO level", level)
		log.SetLevel(log.InfoLevel)
	}
	log.Debugf("Logging level set to %s", level)
}
<|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 vsphere

import (
	"context"
	"fmt"
	"time"

	"github.com/onsi/ginkgo"
	"github.com/onsi/gomega"
	"github.com/vmware/govmomi/object"
	vimtypes "github.com/vmware/govmomi/vim25/types"

	appsv1 "k8s.io/api/apps/v1"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	clientset "k8s.io/client-go/kubernetes"
	"k8s.io/kubernetes/test/e2e/framework"
	e2edeploy "k8s.io/kubernetes/test/e2e/framework/deployment"
	e2elog "k8s.io/kubernetes/test/e2e/framework/log"
	"k8s.io/kubernetes/test/e2e/storage/utils"
)

/*
	Test to verify volume status after node power off:
	1. Verify the pod got provisioned on a different node with volume attached to it
	2. Verify the volume is detached from the powered off node
*/
var _ = utils.SIGDescribe("Node Poweroff [Feature:vsphere] [Slow] [Disruptive]", func() {
	f := framework.NewDefaultFramework("node-poweroff")
	var (
		client    clientset.Interface
		namespace string
	)

	ginkgo.BeforeEach(func() {
		framework.SkipUnlessProviderIs("vsphere")
		Bootstrap(f)
		client = f.ClientSet
		namespace = f.Namespace.Name
		framework.ExpectNoError(framework.WaitForAllNodesSchedulable(client, framework.TestContext.NodeSchedulableTimeout))
		nodeList := framework.GetReadySchedulableNodesOrDie(f.ClientSet)
		gomega.Expect(nodeList.Items).NotTo(gomega.BeEmpty(), "Unable to find ready and schedulable Node")
		gomega.Expect(len(nodeList.Items) > 1).To(gomega.BeTrue(), "At least 2 nodes are required for this test")
	})

	/*
		Steps:
		1. Create a StorageClass
		2. Create a PVC with the StorageClass
		3. Create a Deployment with 1 replica, using the PVC
		4. Verify the pod got provisioned on a node
		5. Verify the volume is attached to the node
		6. Power off the node where pod got provisioned
		7. Verify the pod got provisioned on a different node
		8. Verify the volume is attached to the new node
		9. Verify the volume is detached from the old node
		10. Delete the Deployment and wait for the volume to be detached
		11. Delete the PVC
		12. Delete the StorageClass
	*/
	ginkgo.It("verify volume status after node power off", func() {
		ginkgo.By("Creating a Storage Class")
		storageClassSpec := getVSphereStorageClassSpec("test-sc", nil, nil, "")
		storageclass, err := client.StorageV1().StorageClasses().Create(storageClassSpec)
		framework.ExpectNoError(err, fmt.Sprintf("Failed to create storage class with err: %v", err))
		defer client.StorageV1().StorageClasses().Delete(storageclass.Name, nil)

		ginkgo.By("Creating PVC using the Storage Class")
		pvclaimSpec := getVSphereClaimSpecWithStorageClass(namespace, "1Gi", storageclass)
		pvclaim, err := framework.CreatePVC(client, namespace, pvclaimSpec)
		framework.ExpectNoError(err, fmt.Sprintf("Failed to create PVC with err: %v", err))
		defer framework.DeletePersistentVolumeClaim(client, pvclaim.Name, namespace)

		ginkgo.By("Waiting for PVC to be in bound phase")
		pvclaims := []*v1.PersistentVolumeClaim{pvclaim}
		pvs, err := framework.WaitForPVClaimBoundPhase(client, pvclaims, framework.ClaimProvisionTimeout)
		framework.ExpectNoError(err, fmt.Sprintf("Failed to wait until PVC phase set to bound: %v", err))
		volumePath := pvs[0].Spec.VsphereVolume.VolumePath

		ginkgo.By("Creating a Deployment")
		deployment, err := e2edeploy.CreateDeployment(client, int32(1), map[string]string{"test": "app"}, nil, namespace, pvclaims, "")
		framework.ExpectNoError(err, fmt.Sprintf("Failed to create Deployment with err: %v", err))
		defer client.AppsV1().Deployments(namespace).Delete(deployment.Name, &metav1.DeleteOptions{})

		ginkgo.By("Get pod from the deployement")
		podList, err := e2edeploy.GetPodsForDeployment(client, deployment)
		framework.ExpectNoError(err, fmt.Sprintf("Failed to get pod from the deployement with err: %v", err))
		gomega.Expect(podList.Items).NotTo(gomega.BeEmpty())
		pod := podList.Items[0]
		node1 := pod.Spec.NodeName

		ginkgo.By(fmt.Sprintf("Verify disk is attached to the node: %v", node1))
		isAttached, err := diskIsAttached(volumePath, node1)
		framework.ExpectNoError(err)
		gomega.Expect(isAttached).To(gomega.BeTrue(), "Disk is not attached to the node")

		ginkgo.By(fmt.Sprintf("Power off the node: %v", node1))

		nodeInfo := TestContext.NodeMapper.GetNodeInfo(node1)
		vm := object.NewVirtualMachine(nodeInfo.VSphere.Client.Client, nodeInfo.VirtualMachineRef)
		ctx, cancel := context.WithCancel(context.Background())
		defer cancel()
		_, err = vm.PowerOff(ctx)
		framework.ExpectNoError(err)
		defer vm.PowerOn(ctx)

		err = vm.WaitForPowerState(ctx, vimtypes.VirtualMachinePowerStatePoweredOff)
		framework.ExpectNoError(err, "Unable to power off the node")

		// Waiting for the pod to be failed over to a different node
		node2, err := waitForPodToFailover(client, deployment, node1)
		framework.ExpectNoError(err, "Pod did not fail over to a different node")

		ginkgo.By(fmt.Sprintf("Waiting for disk to be attached to the new node: %v", node2))
		err = waitForVSphereDiskToAttach(volumePath, node2)
		framework.ExpectNoError(err, "Disk is not attached to the node")

		ginkgo.By(fmt.Sprintf("Waiting for disk to be detached from the previous node: %v", node1))
		err = waitForVSphereDiskToDetach(volumePath, node1)
		framework.ExpectNoError(err, "Disk is not detached from the node")

		ginkgo.By(fmt.Sprintf("Power on the previous node: %v", node1))
		vm.PowerOn(ctx)
		err = vm.WaitForPowerState(ctx, vimtypes.VirtualMachinePowerStatePoweredOn)
		framework.ExpectNoError(err, "Unable to power on the node")
	})
})

// Wait until the pod failed over to a different node, or time out after 3 minutes
func waitForPodToFailover(client clientset.Interface, deployment *appsv1.Deployment, oldNode string) (string, error) {
	var (
		err      error
		newNode  string
		timeout  = 3 * time.Minute
		pollTime = 10 * time.Second
	)

	err = wait.Poll(pollTime, timeout, func() (bool, error) {
		newNode, err = getNodeForDeployment(client, deployment)
		if err != nil {
			return true, err
		}

		if newNode != oldNode {
			e2elog.Logf("The pod has been failed over from %q to %q", oldNode, newNode)
			return true, nil
		}

		e2elog.Logf("Waiting for pod to be failed over from %q", oldNode)
		return false, nil
	})

	if err != nil {
		if err == wait.ErrWaitTimeout {
			e2elog.Logf("Time out after waiting for %v", timeout)
		}
		e2elog.Logf("Pod did not fail over from %q with error: %v", oldNode, err)
		return "", err
	}

	return getNodeForDeployment(client, deployment)
}

// getNodeForDeployment returns node name for the Deployment
func getNodeForDeployment(client clientset.Interface, deployment *appsv1.Deployment) (string, error) {
	podList, err := e2edeploy.GetPodsForDeployment(client, deployment)
	if err != nil {
		return "", err
	}
	return podList.Items[0].Spec.NodeName, nil
}
<commit_msg>MOD:fix spelling errors<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 vsphere

import (
	"context"
	"fmt"
	"time"

	"github.com/onsi/ginkgo"
	"github.com/onsi/gomega"
	"github.com/vmware/govmomi/object"
	vimtypes "github.com/vmware/govmomi/vim25/types"

	appsv1 "k8s.io/api/apps/v1"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	clientset "k8s.io/client-go/kubernetes"
	"k8s.io/kubernetes/test/e2e/framework"
	e2edeploy "k8s.io/kubernetes/test/e2e/framework/deployment"
	e2elog "k8s.io/kubernetes/test/e2e/framework/log"
	"k8s.io/kubernetes/test/e2e/storage/utils"
)

/*
	Test to verify volume status after node power off:
	1. Verify the pod got provisioned on a different node with volume attached to it
	2. Verify the volume is detached from the powered off node
*/
var _ = utils.SIGDescribe("Node Poweroff [Feature:vsphere] [Slow] [Disruptive]", func() {
	f := framework.NewDefaultFramework("node-poweroff")
	var (
		client    clientset.Interface
		namespace string
	)

	ginkgo.BeforeEach(func() {
		framework.SkipUnlessProviderIs("vsphere")
		Bootstrap(f)
		client = f.ClientSet
		namespace = f.Namespace.Name
		framework.ExpectNoError(framework.WaitForAllNodesSchedulable(client, framework.TestContext.NodeSchedulableTimeout))
		nodeList := framework.GetReadySchedulableNodesOrDie(f.ClientSet)
		gomega.Expect(nodeList.Items).NotTo(gomega.BeEmpty(), "Unable to find ready and schedulable Node")
		gomega.Expect(len(nodeList.Items) > 1).To(gomega.BeTrue(), "At least 2 nodes are required for this test")
	})

	/*
		Steps:
		1. Create a StorageClass
		2. Create a PVC with the StorageClass
		3. Create a Deployment with 1 replica, using the PVC
		4. Verify the pod got provisioned on a node
		5. Verify the volume is attached to the node
		6. Power off the node where pod got provisioned
		7. Verify the pod got provisioned on a different node
		8. Verify the volume is attached to the new node
		9. Verify the volume is detached from the old node
		10. Delete the Deployment and wait for the volume to be detached
		11. Delete the PVC
		12. Delete the StorageClass
	*/
	ginkgo.It("verify volume status after node power off", func() {
		ginkgo.By("Creating a Storage Class")
		storageClassSpec := getVSphereStorageClassSpec("test-sc", nil, nil, "")
		storageclass, err := client.StorageV1().StorageClasses().Create(storageClassSpec)
		framework.ExpectNoError(err, fmt.Sprintf("Failed to create storage class with err: %v", err))
		defer client.StorageV1().StorageClasses().Delete(storageclass.Name, nil)

		ginkgo.By("Creating PVC using the Storage Class")
		pvclaimSpec := getVSphereClaimSpecWithStorageClass(namespace, "1Gi", storageclass)
		pvclaim, err := framework.CreatePVC(client, namespace, pvclaimSpec)
		framework.ExpectNoError(err, fmt.Sprintf("Failed to create PVC with err: %v", err))
		defer framework.DeletePersistentVolumeClaim(client, pvclaim.Name, namespace)

		ginkgo.By("Waiting for PVC to be in bound phase")
		pvclaims := []*v1.PersistentVolumeClaim{pvclaim}
		pvs, err := framework.WaitForPVClaimBoundPhase(client, pvclaims, framework.ClaimProvisionTimeout)
		framework.ExpectNoError(err, fmt.Sprintf("Failed to wait until PVC phase set to bound: %v", err))
		volumePath := pvs[0].Spec.VsphereVolume.VolumePath

		ginkgo.By("Creating a Deployment")
		deployment, err := e2edeploy.CreateDeployment(client, int32(1), map[string]string{"test": "app"}, nil, namespace, pvclaims, "")
		framework.ExpectNoError(err, fmt.Sprintf("Failed to create Deployment with err: %v", err))
		defer client.AppsV1().Deployments(namespace).Delete(deployment.Name, &metav1.DeleteOptions{})

		ginkgo.By("Get pod from the deployment")
		podList, err := e2edeploy.GetPodsForDeployment(client, deployment)
		framework.ExpectNoError(err, fmt.Sprintf("Failed to get pod from the deployment with err: %v", err))
		gomega.Expect(podList.Items).NotTo(gomega.BeEmpty())
		pod := podList.Items[0]
		node1 := pod.Spec.NodeName

		ginkgo.By(fmt.Sprintf("Verify disk is attached to the node: %v", node1))
		isAttached, err := diskIsAttached(volumePath, node1)
		framework.ExpectNoError(err)
		gomega.Expect(isAttached).To(gomega.BeTrue(), "Disk is not attached to the node")

		ginkgo.By(fmt.Sprintf("Power off the node: %v", node1))

		nodeInfo := TestContext.NodeMapper.GetNodeInfo(node1)
		vm := object.NewVirtualMachine(nodeInfo.VSphere.Client.Client, nodeInfo.VirtualMachineRef)
		ctx, cancel := context.WithCancel(context.Background())
		defer cancel()
		_, err = vm.PowerOff(ctx)
		framework.ExpectNoError(err)
		defer vm.PowerOn(ctx)

		err = vm.WaitForPowerState(ctx, vimtypes.VirtualMachinePowerStatePoweredOff)
		framework.ExpectNoError(err, "Unable to power off the node")

		// Waiting for the pod to be failed over to a different node
		node2, err := waitForPodToFailover(client, deployment, node1)
		framework.ExpectNoError(err, "Pod did not fail over to a different node")

		ginkgo.By(fmt.Sprintf("Waiting for disk to be attached to the new node: %v", node2))
		err = waitForVSphereDiskToAttach(volumePath, node2)
		framework.ExpectNoError(err, "Disk is not attached to the node")

		ginkgo.By(fmt.Sprintf("Waiting for disk to be detached from the previous node: %v", node1))
		err = waitForVSphereDiskToDetach(volumePath, node1)
		framework.ExpectNoError(err, "Disk is not detached from the node")

		ginkgo.By(fmt.Sprintf("Power on the previous node: %v", node1))
		vm.PowerOn(ctx)
		err = vm.WaitForPowerState(ctx, vimtypes.VirtualMachinePowerStatePoweredOn)
		framework.ExpectNoError(err, "Unable to power on the node")
	})
})

// Wait until the pod failed over to a different node, or time out after 3 minutes
func waitForPodToFailover(client clientset.Interface, deployment *appsv1.Deployment, oldNode string) (string, error) {
	var (
		err      error
		newNode  string
		timeout  = 3 * time.Minute
		pollTime = 10 * time.Second
	)

	err = wait.Poll(pollTime, timeout, func() (bool, error) {
		newNode, err = getNodeForDeployment(client, deployment)
		if err != nil {
			return true, err
		}

		if newNode != oldNode {
			e2elog.Logf("The pod has been failed over from %q to %q", oldNode, newNode)
			return true, nil
		}

		e2elog.Logf("Waiting for pod to be failed over from %q", oldNode)
		return false, nil
	})

	if err != nil {
		if err == wait.ErrWaitTimeout {
			e2elog.Logf("Time out after waiting for %v", timeout)
		}
		e2elog.Logf("Pod did not fail over from %q with error: %v", oldNode, err)
		return "", err
	}

	return getNodeForDeployment(client, deployment)
}

// getNodeForDeployment returns node name for the Deployment
func getNodeForDeployment(client clientset.Interface, deployment *appsv1.Deployment) (string, error) {
	podList, err := e2edeploy.GetPodsForDeployment(client, deployment)
	if err != nil {
		return "", err
	}
	return podList.Items[0].Spec.NodeName, nil
}
<|endoftext|>


<commit_before>package query

import (
	"fmt"
	"strings"
	"time"

	"github.com/pkg/errors"

	"github.com/yuuki/diamondb/lib/series"
	"github.com/yuuki/diamondb/lib/storage"
)

// UnsupportedFunctionError represents the error of unsupported query function.
type UnsupportedFunctionError struct {
	funcName string
}

// Error returns the error message for UnsupportedFunctionError.
// UnsupportedFunctionError satisfies error interface.
func (e *UnsupportedFunctionError) Error() string {
	return fmt.Sprintf("unsupported function %s", e.funcName)
}

type UnknownExpressionError struct {
	expr Expr
}

func (e *UnknownExpressionError) Error() string {
	return fmt.Sprintf("unknown expression %v", e.expr)
}

type funcArg struct {
	expr        Expr
	seriesSlice series.SeriesSlice
}

type funcArgs []*funcArg

// EvalTargets evaluates the targets concurrently. It is guaranteed that the order
// of the targets as input value and SeriesSlice as retuen value is the same.
func EvalTargets(fetcher storage.Fetcher, targets []string, startTime, endTime time.Time) (series.SeriesSlice, error) {
	type result struct {
		value series.SeriesSlice
		err   error
		index int
	}

	c := make(chan *result)
	for i, target := range targets {
		go func(target string, start, end time.Time, i int) {
			ss, err := EvalTarget(fetcher, target, start, end)
			c <- &result{value: ss, err: err, index: i}
		}(target, startTime, endTime, i)
	}
	ordered := make([]series.SeriesSlice, len(targets))
	for i := 0; i < len(targets); i++ {
		ret := <-c
		if ret.err != nil {
			// return err that is found firstly.
			return nil, errors.Wrapf(ret.err, "failed to evaluate target (%s)", targets[i])
		}
		ordered[ret.index] = ret.value
	}
	results := series.SeriesSlice{}
	for _, ss := range ordered {
		results = append(results, ss...)
	}
	return results, nil
}

// EvalTarget evaluates the target. It parses the target into AST structure and fetches datapoints from storage.
//
// ex. target: "alias(sumSeries(server1.loadavg5,server2.loadavg5),\"server_loadavg5\")"
func EvalTarget(fetcher storage.Fetcher, target string, startTime, endTime time.Time) (series.SeriesSlice, error) {
	expr, err := ParseTarget(target)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to parse target (%s)", target)
	}
	ss, err := invokeExpr(fetcher, expr, startTime, endTime)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to invoke %s", e.Literal)
	}
	return ss, err
}

func invokeExpr(fetcher storage.Fetcher, expr Expr, startTime, endTime time.Time) (series.SeriesSlice, error) {
	switch e := expr.(type) {
	case SeriesListExpr:
		ss, err := fetcher.Fetch(e.Literal, startTime, endTime)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to fetch (%s,%d,%d)", e.Literal, startTime.Unix(), endTime.Unix())
		}
		return ss, nil
	case GroupSeriesExpr:
		joinedValues := make([]string, 0, len(e.ValueList))
		for _, value := range e.ValueList {
			joinedValues = append(joinedValues, e.Prefix+value+e.Postfix)
		}
		expr = SeriesListExpr{Literal: strings.Join(joinedValues, ",")}
		ss, err := invokeExpr(fetcher, expr, startTime, endTime)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to invoke (%s,%d,%d)", e.Literal, startTime.Unix(), endTime.Unix())
		}
		return ss, nil
	case FuncExpr:
		args := funcArgs{}
		for _, expr := range e.SubExprs {
			switch e2 := expr.(type) {
			case BoolExpr:
				args = append(args, &funcArg{expr: expr})
			case NumberExpr:
				args = append(args, &funcArg{expr: expr})
			case StringExpr:
				args = append(args, &funcArg{expr: expr})
			case SeriesListExpr, GroupSeriesExpr:
				ss, err := invokeExpr(fetcher, expr, startTime, endTime)
				if err != nil {
					return nil, errors.Wrapf(err, "failed to invoke %s", e.Literal)
				}
				ex := SeriesListExpr{Literal: ss.FormattedName()}
				args = append(args, &funcArg{expr: ex, seriesSlice: ss})
			case FuncExpr:
				ss, err := invokeExpr(fetcher, expr, startTime, endTime)
				if err != nil {
					return nil, errors.Wrapf(err, "failed to invoke %s", e.Literal)
				}
				// Regard FuncExpr as SeriesListExpr after process function
				ex := SeriesListExpr{Literal: fmt.Sprintf("%s(%s)", e2.Name, ss.FormattedName())}
				args = append(args, &funcArg{expr: ex, seriesSlice: ss})
			default:
				return nil, &UnknownExpressionError{expr: expr}
			}
		}
		switch e.Name {
		case "alias":
			ss, err := doAlias(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "offset":
			ss, err := doOffset(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "group":
			ss, err := doGroup(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "averageSeries", "avg":
			ss, err := doAverageSeries(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "sumSeries", "sum":
			ss, err := doSumSeries(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "minSeries":
			ss, err := doMinSeries(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "maxSeries":
			ss, err := doMaxSeries(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "multiplySeries":
			ss, err := doMultiplySeries(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "divideSeries":
			ss, err := doDivideSeries(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "percentileOfSeries":
			ss, err := doPercentileOfSeries(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "summarize":
			ss, err := doSummarize(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "sumSeriesWithWildcards":
			ss, err := doSumSeriesWithWildcards(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		default:
			return nil, &UnsupportedFunctionError{funcName: e.Name}
		}
	default:
		return nil, &UnknownExpressionError{expr: expr}
	}
}
<commit_msg>Fix build failure<commit_after>package query

import (
	"fmt"
	"strings"
	"time"

	"github.com/pkg/errors"

	"github.com/yuuki/diamondb/lib/series"
	"github.com/yuuki/diamondb/lib/storage"
)

// UnsupportedFunctionError represents the error of unsupported query function.
type UnsupportedFunctionError struct {
	funcName string
}

// Error returns the error message for UnsupportedFunctionError.
// UnsupportedFunctionError satisfies error interface.
func (e *UnsupportedFunctionError) Error() string {
	return fmt.Sprintf("unsupported function %s", e.funcName)
}

type UnknownExpressionError struct {
	expr Expr
}

func (e *UnknownExpressionError) Error() string {
	return fmt.Sprintf("unknown expression %v", e.expr)
}

type funcArg struct {
	expr        Expr
	seriesSlice series.SeriesSlice
}

type funcArgs []*funcArg

// EvalTargets evaluates the targets concurrently. It is guaranteed that the order
// of the targets as input value and SeriesSlice as retuen value is the same.
func EvalTargets(fetcher storage.Fetcher, targets []string, startTime, endTime time.Time) (series.SeriesSlice, error) {
	type result struct {
		value series.SeriesSlice
		err   error
		index int
	}

	c := make(chan *result)
	for i, target := range targets {
		go func(target string, start, end time.Time, i int) {
			ss, err := EvalTarget(fetcher, target, start, end)
			c <- &result{value: ss, err: err, index: i}
		}(target, startTime, endTime, i)
	}
	ordered := make([]series.SeriesSlice, len(targets))
	for i := 0; i < len(targets); i++ {
		ret := <-c
		if ret.err != nil {
			// return err that is found firstly.
			return nil, errors.Wrapf(ret.err, "failed to evaluate target (%s)", targets[i])
		}
		ordered[ret.index] = ret.value
	}
	results := series.SeriesSlice{}
	for _, ss := range ordered {
		results = append(results, ss...)
	}
	return results, nil
}

// EvalTarget evaluates the target. It parses the target into AST structure and fetches datapoints from storage.
//
// ex. target: "alias(sumSeries(server1.loadavg5,server2.loadavg5),\"server_loadavg5\")"
func EvalTarget(fetcher storage.Fetcher, target string, startTime, endTime time.Time) (series.SeriesSlice, error) {
	expr, err := ParseTarget(target)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to parse target (%s)", target)
	}
	ss, err := invokeExpr(fetcher, expr, startTime, endTime)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to invoke %s", expr)
	}
	return ss, err
}

func invokeExpr(fetcher storage.Fetcher, expr Expr, startTime, endTime time.Time) (series.SeriesSlice, error) {
	switch e := expr.(type) {
	case SeriesListExpr:
		ss, err := fetcher.Fetch(e.Literal, startTime, endTime)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to fetch (%s,%d,%d)", e.Literal, startTime.Unix(), endTime.Unix())
		}
		return ss, nil
	case GroupSeriesExpr:
		joinedValues := make([]string, 0, len(e.ValueList))
		for _, value := range e.ValueList {
			joinedValues = append(joinedValues, e.Prefix+value+e.Postfix)
		}
		expr = SeriesListExpr{Literal: strings.Join(joinedValues, ",")}
		ss, err := invokeExpr(fetcher, expr, startTime, endTime)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to invoke (%s,%d,%d)", e, startTime.Unix(), endTime.Unix())
		}
		return ss, nil
	case FuncExpr:
		args := funcArgs{}
		for _, expr := range e.SubExprs {
			switch e2 := expr.(type) {
			case BoolExpr:
				args = append(args, &funcArg{expr: expr})
			case NumberExpr:
				args = append(args, &funcArg{expr: expr})
			case StringExpr:
				args = append(args, &funcArg{expr: expr})
			case SeriesListExpr, GroupSeriesExpr:
				ss, err := invokeExpr(fetcher, expr, startTime, endTime)
				if err != nil {
					return nil, errors.Wrapf(err, "failed to invoke %s", e2)
				}
				ex := SeriesListExpr{Literal: ss.FormattedName()}
				args = append(args, &funcArg{expr: ex, seriesSlice: ss})
			case FuncExpr:
				ss, err := invokeExpr(fetcher, expr, startTime, endTime)
				if err != nil {
					return nil, errors.Wrapf(err, "failed to invoke %s", e2)
				}
				// Regard FuncExpr as SeriesListExpr after process function
				ex := SeriesListExpr{Literal: fmt.Sprintf("%s(%s)", e2.Name, ss.FormattedName())}
				args = append(args, &funcArg{expr: ex, seriesSlice: ss})
			default:
				return nil, &UnknownExpressionError{expr: expr}
			}
		}
		switch e.Name {
		case "alias":
			ss, err := doAlias(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "offset":
			ss, err := doOffset(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "group":
			ss, err := doGroup(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "averageSeries", "avg":
			ss, err := doAverageSeries(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "sumSeries", "sum":
			ss, err := doSumSeries(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "minSeries":
			ss, err := doMinSeries(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "maxSeries":
			ss, err := doMaxSeries(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "multiplySeries":
			ss, err := doMultiplySeries(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "divideSeries":
			ss, err := doDivideSeries(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "percentileOfSeries":
			ss, err := doPercentileOfSeries(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "summarize":
			ss, err := doSummarize(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		case "sumSeriesWithWildcards":
			ss, err := doSumSeriesWithWildcards(args)
			if err != nil {
				return nil, errors.WithStack(err)
			}
			return ss, err
		default:
			return nil, &UnsupportedFunctionError{funcName: e.Name}
		}
	default:
		return nil, &UnknownExpressionError{expr: expr}
	}
}
<|endoftext|>


<commit_before>package tracer

import (
	"sync"
	"time"

	"github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/log"
)

type Span struct {
	sync.Mutex
	emitter       Emitter
	operationName string
	tracer        *Tracer
	fields        map[string]log.Field
	startedAt     time.Time
	baggage       map[string]string
	tags          map[string]interface{}
}

func (s *Span) clone() *Span {
	var fields map[string]log.Field
	if s.fields != nil {
		fields = make(map[string]log.Field, len(s.fields))
		for k, v := range s.fields {
			fields[k] = v
		}
	}

	var baggage map[string]string
	if s.baggage != nil {
		baggage = make(map[string]string, len(s.baggage))
		for k, v := range baggage {
			baggage[k] = v
		}
	}

	return &Span{
		emitter:       s.emitter,
		operationName: s.operationName,
		tracer:        s.tracer,
		fields:        fields,
		startedAt:     time.Now(),
		baggage:       baggage,
	}
}

// Sets the end timestamp and finalizes Span state.
//
// With the exception of calls to Context() (which are always allowed),
// Finish() must be the last call made to any span instance, and to do
// otherwise leads to undefined behavior.
func (s *Span) Finish() {
	s.FinishWithOptions(opentracing.FinishOptions{
		FinishTime: time.Now(),
	})
}

// FinishWithOptions is like Finish() but with explicit control over
// timestamps and log data.
func (s *Span) FinishWithOptions(opts opentracing.FinishOptions) {
	if opts.LogRecords != nil {
		for _, record := range opts.LogRecords {
			s.emitter.Emit(s, "", record.Fields...)
		}
	}

	s.emitter.Emit(s, s.operationName)
}

// Context() yields the SpanContext for this Span. Note that the return
// value of Context() is still valid after a call to Span.Finish(), as is
// a call to Span.Context() after a call to Span.Finish().
func (s *Span) Context() opentracing.SpanContext {
	return s
}

// Sets or changes the operation name.
func (s *Span) SetOperationName(operationName string) opentracing.Span {
	dupe := s.clone()
	dupe.operationName = operationName
	return dupe
}

// Adds a tag to the span.
//
// If there is a pre-existing tag set for `key`, it is overwritten.
//
// Tag values can be numeric types, strings, or bools. The behavior of
// other tag value types is undefined at the OpenTracing level. If a
// tracing system does not know how to handle a particular value type, it
// may ignore the tag, but shall not panic.
func (s *Span) SetTag(key string, value interface{}) opentracing.Span {
	s.Lock()
	defer s.Unlock()

	if s.tags == nil {
		s.tags = make(map[string]interface{}, 1)
	}

	return nil
}

// LogFields is an efficient and type-checked way to record key:value
// logging data about a Span, though the programming interface is a little
// more verbose than LogKV(). Here's an example:
//
//    span.LogFields(
//        log.String("event", "soft error"),
//        log.String("type", "cache timeout"),
//        log.Int("waited.millis", 1500))
//
// Also see Span.FinishWithOptions() and FinishOptions.BulkLogData.
func (s *Span) LogFields(fields ...log.Field) {
	s.Lock()
	defer s.Unlock()

	if s.fields == nil {
		s.fields = map[string]log.Field{}
	}

	for _, field := range fields {
		s.fields[field.Key()] = field
	}
}

// LogKV is a concise, readable way to record key:value logging data about
// a Span, though unfortunately this also makes it less efficient and less
// type-safe than LogFields(). Here's an example:
//
//    span.LogKV(
//        "event", "soft error",
//        "type", "cache timeout",
//        "waited.millis", 1500)
//
// For LogKV (as opposed to LogFields()), the parameters must appear as
// key-value pairs, like
//
//    span.LogKV(key1, val1, key2, val2, key3, val3, ...)
//
// The keys must all be strings. The values may be strings, numeric types,
// bools, Go error instances, or arbitrary structs.
//
// (Note to implementors: consider the log.InterleavedKVToFields() helper)
func (s *Span) LogKV(alternatingKeyValues ...interface{}) {
	fields, err := log.InterleavedKVToFields(alternatingKeyValues...)
	if err != nil {
		panic("LogKV requires an even number of parameters")
	}
	s.LogFields(fields...)
}

// SetBaggageItem sets a key:value pair on this Span and its SpanContext
// that also propagates to descendants of this Span.
//
// SetBaggageItem() enables powerful functionality given a full-stack
// opentracing integration (e.g., arbitrary application data from a mobile
// app can make it, transparently, all the way into the depths of a storage
// system), and with it some powerful costs: use this feature with care.
//
// IMPORTANT NOTE #1: SetBaggageItem() will only propagate baggage items to
// *future* causal descendants of the associated Span.
//
// IMPORTANT NOTE #2: Use this thoughtfully and with care. Every key and
// value is copied into every local *and remote* child of the associated
// Span, and that can add up to a lot of network and cpu overhead.
//
// Returns a reference to this Span for chaining.
func (s *Span) SetBaggageItem(restrictedKey, value string) opentracing.Span {
	s.Lock()
	defer s.Unlock()

	if s.baggage == nil {
		s.baggage = map[string]string{}
	}

	s.baggage[restrictedKey] = value
	return s
}

// Gets the value for a baggage item given its key. Returns the empty string
// if the value isn't found in this Span.
func (s *Span) BaggageItem(restrictedKey string) string {
	s.Lock()
	defer s.Unlock()

	if s.baggage == nil {
		return ""
	}

	return s.baggage[restrictedKey]
}

// Provides access to the Tracer that created this Span.
func (s *Span) Tracer() opentracing.Tracer {
	return s.tracer
}

// Deprecated: use LogFields or LogKV
func (s *Span) LogEvent(event string) {
	panic("LogEvent: Deprecated: use LogFields or LogKV")
}

// Deprecated: use LogFields or LogKV
func (s *Span) LogEventWithPayload(event string, payload interface{}) {
	panic("LogEventWithPayload: Deprecated: use LogFields or LogKV")
}

// Deprecated: use LogFields or LogKV
func (s *Span) Log(data opentracing.LogData) {
	panic("Log: Deprecated: use LogFields or LogKV")
}

// ForeachBaggageItem grants access to all baggage items stored in the
// SpanContext.
// The handler function will be called for each baggage key/value pair.
// The ordering of items is not guaranteed.
//
// The bool return value indicates if the handler wants to continue iterating
// through the rest of the baggage items; for example if the handler is trying to
// find some baggage item by pattern matching the name, it can return false
// as soon as the item is found to stop further iterations.
func (s *Span) ForeachBaggageItem(handler func(k, v string) bool) {
	if s.baggage == nil {
		return
	}

	for k, v := range s.baggage {
		if ok := handler(k, v); !ok {
			return
		}
	}
}

func (s *Span) ForeachField(handler func(k string, f log.Field) bool) {
	if s.fields == nil {
		return
	}

	for k, v := range s.fields {
		if ok := handler(k, v); !ok {
			return
		}
	}
}

// Info allows the Span to log at arbitrary times
func (s *Span) Info(msg string, fields ...log.Field) {
	s.emitter.Emit(s, msg, fields...)
}

// Debug allows the Span to log at arbitrary times
func (s *Span) Debug(msg string, fields ...log.Field) {
	s.emitter.Emit(s, msg, fields...)
}
<commit_msg>- finished records elapsed time<commit_after>package tracer

import (
	"sync"
	"time"

	"github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/log"
)

type Span struct {
	sync.Mutex
	emitter       Emitter
	operationName string
	tracer        *Tracer
	fields        map[string]log.Field
	startedAt     time.Time
	baggage       map[string]string
	tags          map[string]interface{}
}

func (s *Span) clone() *Span {
	var fields map[string]log.Field
	if s.fields != nil {
		fields = make(map[string]log.Field, len(s.fields))
		for k, v := range s.fields {
			fields[k] = v
		}
	}

	var baggage map[string]string
	if s.baggage != nil {
		baggage = make(map[string]string, len(s.baggage))
		for k, v := range baggage {
			baggage[k] = v
		}
	}

	return &Span{
		emitter:       s.emitter,
		operationName: s.operationName,
		tracer:        s.tracer,
		fields:        fields,
		startedAt:     time.Now(),
		baggage:       baggage,
	}
}

// Sets the end timestamp and finalizes Span state.
//
// With the exception of calls to Context() (which are always allowed),
// Finish() must be the last call made to any span instance, and to do
// otherwise leads to undefined behavior.
func (s *Span) Finish() {
	s.FinishWithOptions(opentracing.FinishOptions{
		FinishTime: time.Now(),
	})
}

// FinishWithOptions is like Finish() but with explicit control over
// timestamps and log data.
func (s *Span) FinishWithOptions(opts opentracing.FinishOptions) {
	if opts.LogRecords != nil {
		for _, record := range opts.LogRecords {
			s.emitter.Emit(s, "", record.Fields...)
		}
	}

	elapsed := time.Now().Sub(s.startedAt) / time.Millisecond
	s.emitter.Emit(s, s.operationName, log.Int64("elapsed", int64(elapsed)))
}

// Context() yields the SpanContext for this Span. Note that the return
// value of Context() is still valid after a call to Span.Finish(), as is
// a call to Span.Context() after a call to Span.Finish().
func (s *Span) Context() opentracing.SpanContext {
	return s
}

// Sets or changes the operation name.
func (s *Span) SetOperationName(operationName string) opentracing.Span {
	dupe := s.clone()
	dupe.operationName = operationName
	return dupe
}

// Adds a tag to the span.
//
// If there is a pre-existing tag set for `key`, it is overwritten.
//
// Tag values can be numeric types, strings, or bools. The behavior of
// other tag value types is undefined at the OpenTracing level. If a
// tracing system does not know how to handle a particular value type, it
// may ignore the tag, but shall not panic.
func (s *Span) SetTag(key string, value interface{}) opentracing.Span {
	s.Lock()
	defer s.Unlock()

	if s.tags == nil {
		s.tags = make(map[string]interface{}, 1)
	}

	return nil
}

// LogFields is an efficient and type-checked way to record key:value
// logging data about a Span, though the programming interface is a little
// more verbose than LogKV(). Here's an example:
//
//    span.LogFields(
//        log.String("event", "soft error"),
//        log.String("type", "cache timeout"),
//        log.Int("waited.millis", 1500))
//
// Also see Span.FinishWithOptions() and FinishOptions.BulkLogData.
func (s *Span) LogFields(fields ...log.Field) {
	s.Lock()
	defer s.Unlock()

	if s.fields == nil {
		s.fields = map[string]log.Field{}
	}

	for _, field := range fields {
		s.fields[field.Key()] = field
	}
}

// LogKV is a concise, readable way to record key:value logging data about
// a Span, though unfortunately this also makes it less efficient and less
// type-safe than LogFields(). Here's an example:
//
//    span.LogKV(
//        "event", "soft error",
//        "type", "cache timeout",
//        "waited.millis", 1500)
//
// For LogKV (as opposed to LogFields()), the parameters must appear as
// key-value pairs, like
//
//    span.LogKV(key1, val1, key2, val2, key3, val3, ...)
//
// The keys must all be strings. The values may be strings, numeric types,
// bools, Go error instances, or arbitrary structs.
//
// (Note to implementors: consider the log.InterleavedKVToFields() helper)
func (s *Span) LogKV(alternatingKeyValues ...interface{}) {
	fields, err := log.InterleavedKVToFields(alternatingKeyValues...)
	if err != nil {
		panic("LogKV requires an even number of parameters")
	}
	s.LogFields(fields...)
}

// SetBaggageItem sets a key:value pair on this Span and its SpanContext
// that also propagates to descendants of this Span.
//
// SetBaggageItem() enables powerful functionality given a full-stack
// opentracing integration (e.g., arbitrary application data from a mobile
// app can make it, transparently, all the way into the depths of a storage
// system), and with it some powerful costs: use this feature with care.
//
// IMPORTANT NOTE #1: SetBaggageItem() will only propagate baggage items to
// *future* causal descendants of the associated Span.
//
// IMPORTANT NOTE #2: Use this thoughtfully and with care. Every key and
// value is copied into every local *and remote* child of the associated
// Span, and that can add up to a lot of network and cpu overhead.
//
// Returns a reference to this Span for chaining.
func (s *Span) SetBaggageItem(restrictedKey, value string) opentracing.Span {
	s.Lock()
	defer s.Unlock()

	if s.baggage == nil {
		s.baggage = map[string]string{}
	}

	s.baggage[restrictedKey] = value
	return s
}

// Gets the value for a baggage item given its key. Returns the empty string
// if the value isn't found in this Span.
func (s *Span) BaggageItem(restrictedKey string) string {
	s.Lock()
	defer s.Unlock()

	if s.baggage == nil {
		return ""
	}

	return s.baggage[restrictedKey]
}

// Provides access to the Tracer that created this Span.
func (s *Span) Tracer() opentracing.Tracer {
	return s.tracer
}

// Deprecated: use LogFields or LogKV
func (s *Span) LogEvent(event string) {
	panic("LogEvent: Deprecated: use LogFields or LogKV")
}

// Deprecated: use LogFields or LogKV
func (s *Span) LogEventWithPayload(event string, payload interface{}) {
	panic("LogEventWithPayload: Deprecated: use LogFields or LogKV")
}

// Deprecated: use LogFields or LogKV
func (s *Span) Log(data opentracing.LogData) {
	panic("Log: Deprecated: use LogFields or LogKV")
}

// ForeachBaggageItem grants access to all baggage items stored in the
// SpanContext.
// The handler function will be called for each baggage key/value pair.
// The ordering of items is not guaranteed.
//
// The bool return value indicates if the handler wants to continue iterating
// through the rest of the baggage items; for example if the handler is trying to
// find some baggage item by pattern matching the name, it can return false
// as soon as the item is found to stop further iterations.
func (s *Span) ForeachBaggageItem(handler func(k, v string) bool) {
	if s.baggage == nil {
		return
	}

	for k, v := range s.baggage {
		if ok := handler(k, v); !ok {
			return
		}
	}
}

func (s *Span) ForeachField(handler func(k string, f log.Field) bool) {
	if s.fields == nil {
		return
	}

	for k, v := range s.fields {
		if ok := handler(k, v); !ok {
			return
		}
	}
}

// Info allows the Span to log at arbitrary times
func (s *Span) Info(msg string, fields ...log.Field) {
	s.emitter.Emit(s, msg, fields...)
}

// Debug allows the Span to log at arbitrary times
func (s *Span) Debug(msg string, fields ...log.Field) {
	s.emitter.Emit(s, msg, fields...)
}
<|endoftext|>


<commit_before>// Copyright 2015 ThoughtWorks, Inc.

// This file is part of Gauge.

// Gauge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Gauge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Gauge.  If not, see <http://www.gnu.org/licenses/>.

package track

import (
	"net/http"
	"net/http/httputil"
	"runtime"
	"runtime/debug"
	"strconv"
	"strings"
	"time"

	"github.com/getgauge/gauge/env"

	"fmt"

	"os"

	"sync"

	"github.com/getgauge/gauge/config"
	"github.com/getgauge/gauge/logger"
	"github.com/getgauge/gauge/version"
	"github.com/jpillora/go-ogle-analytics"
)

const (
	gaTrackingID     = "UA-54838477-1"
	gaTestTrackingID = "UA-100778536-1"
	appName          = "Gauge Core"
	consoleMedium    = "console"
	apiMedium        = "api"
	ciMedium         = "CI"
	timeout          = 1
	// GaugeTelemetryMessageHeading is the header printed for telemetry warning
	GaugeTelemetryMessageHeading = `
Telemetry
---------
`
	// GaugeTelemetryMessage is the message printed when user has not explicitly opted in/out
	// of telemetry. Printed only in CLI.
	GaugeTelemetryMessage = `This installation of Gauge collects usage data in order to help us improve your experience.
The data is anonymous and doesn't include command-line arguments.
To turn this message off opt in or out by running 'gauge telemetry on' or 'gauge telemetry off'.

Read more about Gauge telemetry at https://gauge.org/telemetry
`
	// GaugeTelemetryMachineRedableMessage is the message printed when user has not explicitly opted in/out
	// of telemetry. Printed only in CLI.
	GaugeTelemetryMachineRedableMessage = `This installation of Gauge collects usage data in order to help us improve your experience.
Read more here about Gauge telemetry.`

	// GaugeTelemetryLSPMessage is the message printed when user has not explicitly opted in/out
	// of telemetry. Displayed only in LSP Client.
	GaugeTelemetryLSPMessage = `This installation of Gauge collects usage data in order to help us improve your experience.
[Read more here](https://gauge.org/telemetry) about Gauge telemetry.
Would you like to participate?`
)

var gaHTTPTransport = http.DefaultTransport

var telemetryEnabled, telemetryLogEnabled bool

func Init() {
	telemetryEnabled = config.TelemetryEnabled()
	telemetryLogEnabled = config.TelemetryLogEnabled()
}

func send(category, action, label, medium string, wg *sync.WaitGroup) bool {
	if !telemetryEnabled {
		wg.Done()
		return false
	}
	label = strings.Trim(fmt.Sprintf("%s,%s", label, runtime.GOOS), ",")
	sendChan := make(chan bool, 1)
	go func(c chan<- bool) {
		defer recoverPanic()
		t := gaTrackingID
		if env.UseTestGA() {
			t = gaTestTrackingID
		}
		client, err := ga.NewClient(t)
		if err != nil {
			logger.Debugf(true, "Unable to create ga client, %s", err)
		}
		client.HttpClient = &http.Client{}
		client.ClientID(config.UniqueID())
		client.AnonymizeIP(true)
		client.ApplicationName(appName)
		client.ApplicationVersion(version.FullVersion())
		client.CampaignMedium(medium)
		client.CampaignSource(appName)
		client.HttpClient.Transport = gaHTTPTransport
		if telemetryLogEnabled {
			client.HttpClient.Transport = newlogEnabledHTTPTransport()
		}
		ev := ga.NewEvent(category, action)
		if label != "" {
			ev.Label(label)
		}
		err = client.Send(ev)
		if err != nil {
			logger.Debugf(true, "Unable to send analytics data, %s", err)
		}
		c <- true
	}(sendChan)

	for {
		select {
		case <-sendChan:
			wg.Done()
			return true
		case <-time.After(timeout * time.Second):
			logger.Debugf(true, "Unable to send analytics data, timed out")
			wg.Done()
			return false
		}
	}
}

func recoverPanic() {
	if r := recover(); r != nil {
		logger.Errorf(true, "%v\n%s", r, string(debug.Stack()))
	}
}

func trackConsole(category, action, label string) {
	var medium = consoleMedium
	if isCI() {
		medium = ciMedium
	}
	wg := &sync.WaitGroup{}
	wg.Add(1)
	defer wg.Wait()
	go send(category, action, label, medium, wg)
}

func isCI() bool {
	// Travis, AppVeyor, CircleCI, Wercket, drone.io, gitlab-ci
	if ci, _ := strconv.ParseBool(os.Getenv("CI")); ci {
		return true
	}

	// GoCD
	if os.Getenv("GO_SERVER_URL") != "" {
		return true
	}

	// Jenkins
	if os.Getenv("JENKINS_URL") != "" {
		return true
	}

	// Teamcity
	if os.Getenv("TEAMCITY_VERSION") != "" {
		return true
	}

	// TFS
	if ci, _ := strconv.ParseBool(os.Getenv("TFS_BUILD")); ci {
		return true
	}
	return false
}

func daemon(mode, lang string) {
	trackConsole("daemon", mode, lang)
}

func ScheduleDaemonTracking(mode, lang string) {
	daemon(mode, lang)
	ticker := time.NewTicker(28 * time.Minute)
	if env.UseTestGA() && env.TelemetryInterval() != "" {
		duration, _ := strconv.Atoi(env.TelemetryInterval())
		ticker = time.NewTicker(time.Duration(duration) * time.Minute)
	}
	for {
		select {
		case <-ticker.C:
			daemon(mode, lang)
		}
	}
}

func newlogEnabledHTTPTransport() http.RoundTripper {
	return &logEnabledRoundTripper{}
}

type logEnabledRoundTripper struct {
}

func (r logEnabledRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	dump, err := httputil.DumpRequestOut(req, true)
	if err != nil {
		logger.Debugf(true, "Unable to dump analytics request, %s", err)
	}

	logger.Debugf(true, fmt.Sprintf("%q", dump))
	return http.DefaultTransport.RoundTrip(req)
}
<commit_msg>added comments to some exported functions<commit_after>// Copyright 2015 ThoughtWorks, Inc.

// This file is part of Gauge.

// Gauge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Gauge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Gauge.  If not, see <http://www.gnu.org/licenses/>.

package track

import (
	"net/http"
	"net/http/httputil"
	"runtime"
	"runtime/debug"
	"strconv"
	"strings"
	"time"

	"github.com/getgauge/gauge/env"

	"fmt"

	"os"

	"sync"

	"github.com/getgauge/gauge/config"
	"github.com/getgauge/gauge/logger"
	"github.com/getgauge/gauge/version"
	"github.com/jpillora/go-ogle-analytics"
)

const (
	gaTrackingID     = "UA-54838477-1"
	gaTestTrackingID = "UA-100778536-1"
	appName          = "Gauge Core"
	consoleMedium    = "console"
	apiMedium        = "api"
	ciMedium         = "CI"
	timeout          = 1
	// GaugeTelemetryMessageHeading is the header printed for telemetry warning
	GaugeTelemetryMessageHeading = `
Telemetry
---------
`
	// GaugeTelemetryMessage is the message printed when user has not explicitly opted in/out
	// of telemetry. Printed only in CLI.
	GaugeTelemetryMessage = `This installation of Gauge collects usage data in order to help us improve your experience.
The data is anonymous and doesn't include command-line arguments.
To turn this message off opt in or out by running 'gauge telemetry on' or 'gauge telemetry off'.

Read more about Gauge telemetry at https://gauge.org/telemetry
`
	// GaugeTelemetryMachineRedableMessage is the message printed when user has not explicitly opted in/out
	// of telemetry. Printed only in CLI.
	GaugeTelemetryMachineRedableMessage = `This installation of Gauge collects usage data in order to help us improve your experience.
Read more here about Gauge telemetry.`

	// GaugeTelemetryLSPMessage is the message printed when user has not explicitly opted in/out
	// of telemetry. Displayed only in LSP Client.
	GaugeTelemetryLSPMessage = `This installation of Gauge collects usage data in order to help us improve your experience.
[Read more here](https://gauge.org/telemetry) about Gauge telemetry.
Would you like to participate?`
)

var gaHTTPTransport = http.DefaultTransport

var telemetryEnabled, telemetryLogEnabled bool

// Init sets flags used by the package methods.
func Init() {
	telemetryEnabled = config.TelemetryEnabled()
	telemetryLogEnabled = config.TelemetryLogEnabled()
}

func send(category, action, label, medium string, wg *sync.WaitGroup) bool {
	if !telemetryEnabled {
		wg.Done()
		return false
	}
	label = strings.Trim(fmt.Sprintf("%s,%s", label, runtime.GOOS), ",")
	sendChan := make(chan bool, 1)
	go func(c chan<- bool) {
		defer recoverPanic()
		t := gaTrackingID
		if env.UseTestGA() {
			t = gaTestTrackingID
		}
		client, err := ga.NewClient(t)
		if err != nil {
			logger.Debugf(true, "Unable to create ga client, %s", err)
		}
		client.HttpClient = &http.Client{}
		client.ClientID(config.UniqueID())
		client.AnonymizeIP(true)
		client.ApplicationName(appName)
		client.ApplicationVersion(version.FullVersion())
		client.CampaignMedium(medium)
		client.CampaignSource(appName)
		client.HttpClient.Transport = gaHTTPTransport
		if telemetryLogEnabled {
			client.HttpClient.Transport = newlogEnabledHTTPTransport()
		}
		ev := ga.NewEvent(category, action)
		if label != "" {
			ev.Label(label)
		}
		err = client.Send(ev)
		if err != nil {
			logger.Debugf(true, "Unable to send analytics data, %s", err)
		}
		c <- true
	}(sendChan)

	for {
		select {
		case <-sendChan:
			wg.Done()
			return true
		case <-time.After(timeout * time.Second):
			logger.Debugf(true, "Unable to send analytics data, timed out")
			wg.Done()
			return false
		}
	}
}

func recoverPanic() {
	if r := recover(); r != nil {
		logger.Errorf(true, "%v\n%s", r, string(debug.Stack()))
	}
}

func trackConsole(category, action, label string) {
	var medium = consoleMedium
	if isCI() {
		medium = ciMedium
	}
	wg := &sync.WaitGroup{}
	wg.Add(1)
	defer wg.Wait()
	go send(category, action, label, medium, wg)
}

func isCI() bool {
	// Travis, AppVeyor, CircleCI, Wercket, drone.io, gitlab-ci
	if ci, _ := strconv.ParseBool(os.Getenv("CI")); ci {
		return true
	}

	// GoCD
	if os.Getenv("GO_SERVER_URL") != "" {
		return true
	}

	// Jenkins
	if os.Getenv("JENKINS_URL") != "" {
		return true
	}

	// Teamcity
	if os.Getenv("TEAMCITY_VERSION") != "" {
		return true
	}

	// TFS
	if ci, _ := strconv.ParseBool(os.Getenv("TFS_BUILD")); ci {
		return true
	}
	return false
}

func daemon(mode, lang string) {
	trackConsole("daemon", mode, lang)
}

// ScheduleDaemonTracking sends pings to GA at regular intervals. This is used to flag active usage.
func ScheduleDaemonTracking(mode, lang string) {
	daemon(mode, lang)
	ticker := time.NewTicker(28 * time.Minute)
	if env.UseTestGA() && env.TelemetryInterval() != "" {
		duration, _ := strconv.Atoi(env.TelemetryInterval())
		ticker = time.NewTicker(time.Duration(duration) * time.Minute)
	}
	for {
		select {
		case <-ticker.C:
			daemon(mode, lang)
		}
	}
}

func newlogEnabledHTTPTransport() http.RoundTripper {
	return &logEnabledRoundTripper{}
}

type logEnabledRoundTripper struct {
}

func (r logEnabledRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	dump, err := httputil.DumpRequestOut(req, true)
	if err != nil {
		logger.Debugf(true, "Unable to dump analytics request, %s", err)
	}

	logger.Debugf(true, fmt.Sprintf("%q", dump))
	return http.DefaultTransport.RoundTrip(req)
}
<|endoftext|>


<commit_before>// Copyright 2016 Albert Nigmatzianov. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package track

import (
	"runtime"
	"strings"

	"github.com/bogem/nehm/util"
)

const (
	clientID = "11a37feb6ccc034d5975f3f803928a32"
)

type Track struct {
	artist string
	title  string

	// Fields needed for JSON unmarshalling.
	JArtworkURL string  `json:"artwork_url"`
	JCreatedAt  string  `json:"created_at"`
	JDuration   float32 `json:"duration"`
	JID         float32 `json:"id"`
	JTitle      string  `json:"title"`
	JURL        string  `json:"stream_url"`
	JAuthor     struct {
		AvatarURL string `json:"avatar_url"`
		Username  string `json:"username"`
	} `json:"user"`
}

func (t *Track) Artist() string {
	if t.artist == "" {
		t.artist, t.title = t.name()
	}
	return t.artist
}

func (t Track) ArtworkURL() string {
	artworkURL := t.JArtworkURL
	if artworkURL == "" {
		artworkURL = t.JAuthor.AvatarURL
	}
	return strings.Replace(artworkURL, "large", "t500x500", 1)
}

func (t Track) Duration() string {
	return util.DurationString(util.ParseDuration(int(t.JDuration)))
}

func (t Track) Filename() string {
	// Replace all filesystem non-friendly runes with the underscore
	var toReplace string
	if runtime.GOOS == "windows" {
		toReplace = "<>:\"\\/|?*" // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
	} else {
		toReplace = ":/\\"
	}
	replaceRunes := func(r rune) rune {
		if strings.ContainsRune(toReplace, r) {
			return '_'
		}
		return r
	}

	return strings.Map(replaceRunes, t.Fullname()) + ".mp3"
}

func (t Track) Fullname() string {
	return t.Artist() + " — " + t.Title()
}

func (t Track) ID() float32 {
	return t.JID
}

// name splits track's title to artist and title if there is one of separators
// in there.
// If there is no separator in title, it returns t.JAuthor.Username and
// t.JTitle.
// E.g. if track has title "Michael Jackson - Thriller" then this function will
// return as first string "Michael Jackson" and as second string "Thriller".
func (t Track) name() (string, string) {
	separators := [...]string{" - ", " ~ ", " – "}
	for _, sep := range separators {
		if strings.Contains(t.JTitle, sep) {
			splitted := strings.SplitN(t.JTitle, sep, 2)
			return strings.TrimSpace(splitted[0]), strings.TrimSpace(splitted[1])
		}
	}
	return strings.TrimSpace(t.JAuthor.Username), strings.TrimSpace(t.JTitle)
}

func (t *Track) Title() string {
	if t.title == "" {
		t.artist, t.title = t.name()
	}
	return t.title
}

func (t Track) URL() string {
	return t.JURL + "?client_id=" + clientID
}

func (t Track) Year() string {
	return t.JCreatedAt[0:4]
}
<commit_msg>track: Fix Track.URL func<commit_after>// Copyright 2016 Albert Nigmatzianov. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package track

import (
	"runtime"
	"strings"

	"github.com/bogem/nehm/util"
)

const (
	clientID = "11a37feb6ccc034d5975f3f803928a32"
)

type Track struct {
	artist string
	title  string

	// Fields needed for JSON unmarshalling.
	JArtworkURL string  `json:"artwork_url"`
	JCreatedAt  string  `json:"created_at"`
	JDuration   float32 `json:"duration"`
	JID         float32 `json:"id"`
	JTitle      string  `json:"title"`
	JURL        string  `json:"stream_url"`
	JAuthor     struct {
		AvatarURL string `json:"avatar_url"`
		Username  string `json:"username"`
	} `json:"user"`
}

func (t *Track) Artist() string {
	if t.artist == "" {
		t.artist, t.title = t.name()
	}
	return t.artist
}

func (t Track) ArtworkURL() string {
	artworkURL := t.JArtworkURL
	if artworkURL == "" {
		artworkURL = t.JAuthor.AvatarURL
	}
	return strings.Replace(artworkURL, "large", "t500x500", 1)
}

func (t Track) Duration() string {
	return util.DurationString(util.ParseDuration(int(t.JDuration)))
}

func (t Track) Filename() string {
	// Replace all filesystem non-friendly runes with the underscore
	var toReplace string
	if runtime.GOOS == "windows" {
		toReplace = "<>:\"\\/|?*" // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
	} else {
		toReplace = ":/\\"
	}
	replaceRunes := func(r rune) rune {
		if strings.ContainsRune(toReplace, r) {
			return '_'
		}
		return r
	}

	return strings.Map(replaceRunes, t.Fullname()) + ".mp3"
}

func (t Track) Fullname() string {
	return t.Artist() + " — " + t.Title()
}

func (t Track) ID() float32 {
	return t.JID
}

// name splits track's title to artist and title if there is one of separators
// in there.
// If there is no separator in title, it returns t.JAuthor.Username and
// t.JTitle.
// E.g. if track has title "Michael Jackson - Thriller" then this function will
// return as first string "Michael Jackson" and as second string "Thriller".
func (t Track) name() (string, string) {
	separators := [...]string{" - ", " ~ ", " – "}
	for _, sep := range separators {
		if strings.Contains(t.JTitle, sep) {
			splitted := strings.SplitN(t.JTitle, sep, 2)
			return strings.TrimSpace(splitted[0]), strings.TrimSpace(splitted[1])
		}
	}
	return strings.TrimSpace(t.JAuthor.Username), strings.TrimSpace(t.JTitle)
}

func (t *Track) Title() string {
	if t.title == "" {
		t.artist, t.title = t.name()
	}
	return t.title
}

func (t Track) URL() string {
	url := t.JURL
	if strings.ContainsRune(url, '?') { // Check if there is already query in URL.
		return url + "&client_id=" + clientID
	}
	return url + "?client_id=" + clientID
}

func (t Track) Year() string {
	return t.JCreatedAt[0:4]
}
<|endoftext|>