<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"expvar"
	"flag"
	"fmt"
	"html/template"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"time"

	"github.com/mat/besticon/besticon"
	"github.com/mat/besticon/besticon/iconserver/assets"
)

func indexHandler(w http.ResponseWriter, r *http.Request) {
	renderHTMLTemplate(w, 200, indexHTML, nil)
}

func iconsHandler(w http.ResponseWriter, r *http.Request) {
	url := r.FormValue(urlParam)
	if len(url) == 0 {
		http.Redirect(w, r, "/", 302)
		return
	}

	icons, e := fetchIcons(url)
	switch {
	case e != nil:
		renderHTMLTemplate(w, 404, iconsHTML, pageInfo{URL: url, Error: e})
	case len(icons) <= 0:
		errNoIcons := errors.New("this poor site has no icons at all :-(")
		renderHTMLTemplate(w, 404, iconsHTML, pageInfo{URL: url, Error: errNoIcons})
	default:
		renderHTMLTemplate(w, 200, iconsHTML, pageInfo{Icons: icons, URL: url})
	}
}

const urlParam = "url"
const bestParam = "i_am_feeling_lucky"
const prettyParam = "pretty"

func apiHandler(w http.ResponseWriter, r *http.Request) {
	pretty := r.FormValue(prettyParam) == "yes"
	url := r.FormValue(urlParam)
	if len(url) == 0 {
		errMissingURL := errors.New("need url query parameter")
		writeAPIError(w, 400, errMissingURL, pretty)
		return
	}

	bestMode := r.FormValue(bestParam) == "yes"
	if bestMode {
		icon, e := fetchBestIcon(url)
		if e != nil {
			writeAPIError(w, 404, e, pretty)
			return
		}

		http.Redirect(w, r, icon.URL, 302)
	} else {
		icons, e := fetchIcons(url)
		if e != nil {
			writeAPIError(w, 404, e, pretty)
			return
		}

		writeAPIIcons(w, url, icons, pretty)
	}
}

func fetchIcons(url string) ([]besticon.Icon, error) {
	fetchCount.Add(1)
	icons, err := besticon.FetchIcons(url, true)
	if err != nil {
		fetchErrors.Add(1)
	}
	return icons, err
}

func fetchBestIcon(url string) (*besticon.Icon, error) {
	fetchCount.Add(1)
	icon, err := besticon.FetchBestIcon(url, true)
	if err != nil {
		fetchErrors.Add(1)
	}
	return icon, err
}

func writeAPIError(w http.ResponseWriter, httpStatus int, e error, pretty bool) {
	data := struct {
		Error string `json:"error"`
	}{
		e.Error(),
	}

	if pretty {
		renderJSONResponsePretty(w, httpStatus, data)
	} else {
		renderJSONResponse(w, httpStatus, data)
	}
}

func writeAPIIcons(w http.ResponseWriter, url string, icons []besticon.Icon, pretty bool) {
	data := struct {
		URL   string          `json:"url"`
		Icons []besticon.Icon `json:"icons"`
	}{
		url,
		icons,
	}

	if pretty {
		renderJSONResponsePretty(w, 200, data)
	} else {
		renderJSONResponse(w, 200, data)
	}
}

const (
	contentType     = "Content-Type"
	applicationJSON = "application/json"
)

func renderJSONResponse(w http.ResponseWriter, httpStatus int, data interface{}) {
	w.Header().Add(contentType, applicationJSON)
	w.WriteHeader(httpStatus)
	enc := json.NewEncoder(w)
	enc.Encode(data)
}

func renderJSONResponsePretty(w http.ResponseWriter, httpStatus int, data interface{}) {
	w.Header().Add(contentType, applicationJSON)
	w.WriteHeader(httpStatus)
	b, _ := json.MarshalIndent(data, "", "  ")
	w.Write(b)
}

type pageInfo struct {
	URL   string
	Icons []besticon.Icon
	Error error
}

func (pi pageInfo) Host() string {
	u := pi.URL
	url, _ := url.Parse(u)
	if url != nil && url.Host != "" {
		return url.Host
	}
	return pi.URL
}

func (pi pageInfo) Best() string {
	if len(pi.Icons) > 0 {
		best := pi.Icons[0]
		return best.URL
	}
	return ""
}

func renderHTMLTemplate(w http.ResponseWriter, httpStatus int, templ *template.Template, data interface{}) {
	w.Header().Add(contentType, "text/html; charset=utf-8")
	w.WriteHeader(httpStatus)

	err := templ.Execute(w, data)
	if err != nil {
		err = fmt.Errorf("server: could not generate output: %s", err)
		logger.Print(err)
		w.Write([]byte(err.Error()))
	}
}

func startServer(port int) {
	http.HandleFunc("/", indexHandler)
	http.HandleFunc("/icons", iconsHandler)
	http.HandleFunc("/api/icons", apiHandler)

	serveAsset("/pure-0.5.0-min.css", "besticon/iconserver/assets/pure-0.5.0-min.css", oneYear)
	serveAsset("/grids-responsive-0.5.0-min.css", "besticon/iconserver/assets/grids-responsive-0.5.0-min.css", oneYear)
	serveAsset("/main-min.css", "besticon/iconserver/assets/main-min.css", oneYear)

	serveAsset("/icon.svg", "besticon/iconserver/assets/icon.svg", oneYear)
	serveAsset("/favicon.ico", "besticon/iconserver/assets/favicon.ico", oneYear)
	serveAsset("/apple-touch-icon.png", "besticon/iconserver/assets/apple-touch-icon.png", oneYear)
	serveAsset("/robots.txt", "besticon/iconserver/assets/robots.txt", nocache)

	logger.Print("Starting server on port ", port, "...")
	e := http.ListenAndServe(":"+strconv.Itoa(port), newLoggingMux())
	if e != nil {
		fmt.Printf("cannot start server: %s\n", e)
	}
}

const (
	oneYear = 365 * 24 * 3600
	nocache = -1
)

func serveAsset(path string, assetPath string, maxAgeSeconds int) {
	http.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
		assetInfo, err := assets.AssetInfo(assetPath)
		if err != nil {
			panic(err)
		}

		if maxAgeSeconds != nocache {
			w.Header().Add("Cache-Control", fmt.Sprintf("max-age=%d", maxAgeSeconds))
		}

		http.ServeContent(w, r, assetInfo.Name(), assetInfo.ModTime(),
			bytes.NewReader(assets.MustAsset(assetPath)))
	})
}

func main() {
	port := flag.Int("port", 0, "Port in server mode")
	flag.Parse()

	if *port > 0 {
		startServer(*port)
	} else {
		flag.PrintDefaults()
	}
}

func init() {
	indexHTML = templateFromAsset("besticon/iconserver/assets/index.html", "index.html")
	iconsHTML = templateFromAsset("besticon/iconserver/assets/icons.html", "icons.html")
}

func templateFromAsset(assetPath, templateName string) *template.Template {
	bytes := assets.MustAsset(assetPath)
	return template.Must(template.New(templateName).Funcs(funcMap).Parse(string(bytes)))
}

var indexHTML *template.Template
var iconsHTML *template.Template

var funcMap = template.FuncMap{
	"GoogleAnalyticsID": googleAnalyticsID,
	"ImgWidth":          imgWidth,
}

func googleAnalyticsID() string {
	return os.Getenv("ICONS_ANALYTICS_ID")
}

func imgWidth(i *besticon.Icon) int {
	return i.Width / 2.0
}

func init() {
	besticon.SetCacheMaxSize(64)

	expvar.Publish("cacheBytes", expvar.Func(func() interface{} { return besticon.GetCacheStats().Bytes }))
	expvar.Publish("cacheItems", expvar.Func(func() interface{} { return besticon.GetCacheStats().Items }))
	expvar.Publish("cacheGets", expvar.Func(func() interface{} { return besticon.GetCacheStats().Gets }))
	expvar.Publish("cacheHits", expvar.Func(func() interface{} { return besticon.GetCacheStats().Hits }))
	expvar.Publish("cacheEvictions", expvar.Func(func() interface{} { return besticon.GetCacheStats().Evictions }))

	ticker := time.NewTicker(time.Minute * 1)
	go func() {
		for range ticker.C {
			logger.Printf("Cache: %+v", besticon.GetCacheStats())
		}
	}()
}
<commit_msg>Tiny renamings<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"expvar"
	"flag"
	"fmt"
	"html/template"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"time"

	"github.com/mat/besticon/besticon"
	"github.com/mat/besticon/besticon/iconserver/assets"
)

func indexHandler(w http.ResponseWriter, r *http.Request) {
	renderHTMLTemplate(w, 200, indexHTML, nil)
}

func iconsHandler(w http.ResponseWriter, r *http.Request) {
	url := r.FormValue(urlParam)
	if len(url) == 0 {
		http.Redirect(w, r, "/", 302)
		return
	}

	icons, e := fetchIcons(url)
	switch {
	case e != nil:
		renderHTMLTemplate(w, 404, iconsHTML, pageInfo{URL: url, Error: e})
	case len(icons) <= 0:
		errNoIcons := errors.New("this poor site has no icons at all :-(")
		renderHTMLTemplate(w, 404, iconsHTML, pageInfo{URL: url, Error: errNoIcons})
	default:
		renderHTMLTemplate(w, 200, iconsHTML, pageInfo{Icons: icons, URL: url})
	}
}

const urlParam = "url"
const feelingLuckyParam = "i_am_feeling_lucky"
const prettyParam = "pretty"

func apiHandler(w http.ResponseWriter, r *http.Request) {
	pretty := r.FormValue(prettyParam) == "yes"
	url := r.FormValue(urlParam)
	if len(url) == 0 {
		errMissingURL := errors.New("need url query parameter")
		writeAPIError(w, 400, errMissingURL, pretty)
		return
	}

	bestOnly := r.FormValue(feelingLuckyParam) == "yes"
	if bestOnly {
		icon, e := fetchBestIcon(url)
		if e != nil {
			writeAPIError(w, 404, e, pretty)
			return
		}

		http.Redirect(w, r, icon.URL, 302)
	} else {
		icons, e := fetchIcons(url)
		if e != nil {
			writeAPIError(w, 404, e, pretty)
			return
		}

		writeAPIIcons(w, url, icons, pretty)
	}
}

func fetchIcons(url string) ([]besticon.Icon, error) {
	fetchCount.Add(1)
	icons, err := besticon.FetchIcons(url, true)
	if err != nil {
		fetchErrors.Add(1)
	}
	return icons, err
}

func fetchBestIcon(url string) (*besticon.Icon, error) {
	fetchCount.Add(1)
	icon, err := besticon.FetchBestIcon(url, true)
	if err != nil {
		fetchErrors.Add(1)
	}
	return icon, err
}

func writeAPIError(w http.ResponseWriter, httpStatus int, e error, pretty bool) {
	data := struct {
		Error string `json:"error"`
	}{
		e.Error(),
	}

	if pretty {
		renderJSONResponsePretty(w, httpStatus, data)
	} else {
		renderJSONResponse(w, httpStatus, data)
	}
}

func writeAPIIcons(w http.ResponseWriter, url string, icons []besticon.Icon, pretty bool) {
	data := struct {
		URL   string          `json:"url"`
		Icons []besticon.Icon `json:"icons"`
	}{
		url,
		icons,
	}

	if pretty {
		renderJSONResponsePretty(w, 200, data)
	} else {
		renderJSONResponse(w, 200, data)
	}
}

const (
	contentType     = "Content-Type"
	applicationJSON = "application/json"
)

func renderJSONResponse(w http.ResponseWriter, httpStatus int, data interface{}) {
	w.Header().Add(contentType, applicationJSON)
	w.WriteHeader(httpStatus)
	enc := json.NewEncoder(w)
	enc.Encode(data)
}

func renderJSONResponsePretty(w http.ResponseWriter, httpStatus int, data interface{}) {
	w.Header().Add(contentType, applicationJSON)
	w.WriteHeader(httpStatus)
	b, _ := json.MarshalIndent(data, "", "  ")
	w.Write(b)
}

type pageInfo struct {
	URL   string
	Icons []besticon.Icon
	Error error
}

func (pi pageInfo) Host() string {
	u := pi.URL
	url, _ := url.Parse(u)
	if url != nil && url.Host != "" {
		return url.Host
	}
	return pi.URL
}

func (pi pageInfo) Best() string {
	if len(pi.Icons) > 0 {
		best := pi.Icons[0]
		return best.URL
	}
	return ""
}

func renderHTMLTemplate(w http.ResponseWriter, httpStatus int, templ *template.Template, data interface{}) {
	w.Header().Add(contentType, "text/html; charset=utf-8")
	w.WriteHeader(httpStatus)

	err := templ.Execute(w, data)
	if err != nil {
		err = fmt.Errorf("server: could not generate output: %s", err)
		logger.Print(err)
		w.Write([]byte(err.Error()))
	}
}

func startServer(port int) {
	http.HandleFunc("/", indexHandler)
	http.HandleFunc("/icons", iconsHandler)
	http.HandleFunc("/api/icons", apiHandler)

	serveAsset("/pure-0.5.0-min.css", "besticon/iconserver/assets/pure-0.5.0-min.css", oneYear)
	serveAsset("/grids-responsive-0.5.0-min.css", "besticon/iconserver/assets/grids-responsive-0.5.0-min.css", oneYear)
	serveAsset("/main-min.css", "besticon/iconserver/assets/main-min.css", oneYear)

	serveAsset("/icon.svg", "besticon/iconserver/assets/icon.svg", oneYear)
	serveAsset("/favicon.ico", "besticon/iconserver/assets/favicon.ico", oneYear)
	serveAsset("/apple-touch-icon.png", "besticon/iconserver/assets/apple-touch-icon.png", oneYear)
	serveAsset("/robots.txt", "besticon/iconserver/assets/robots.txt", nocache)

	logger.Print("Starting server on port ", port, "...")
	e := http.ListenAndServe(":"+strconv.Itoa(port), newLoggingMux())
	if e != nil {
		fmt.Printf("cannot start server: %s\n", e)
	}
}

const (
	oneYear = 365 * 24 * 3600
	nocache = -1
)

func serveAsset(path string, assetPath string, maxAgeSeconds int) {
	http.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
		assetInfo, err := assets.AssetInfo(assetPath)
		if err != nil {
			panic(err)
		}

		if maxAgeSeconds != nocache {
			w.Header().Add("Cache-Control", fmt.Sprintf("max-age=%d", maxAgeSeconds))
		}

		http.ServeContent(w, r, assetInfo.Name(), assetInfo.ModTime(),
			bytes.NewReader(assets.MustAsset(assetPath)))
	})
}

func main() {
	port := flag.Int("port", 0, "Port in server mode")
	flag.Parse()

	if *port > 0 {
		startServer(*port)
	} else {
		flag.PrintDefaults()
	}
}

func init() {
	indexHTML = templateFromAsset("besticon/iconserver/assets/index.html", "index.html")
	iconsHTML = templateFromAsset("besticon/iconserver/assets/icons.html", "icons.html")
}

func templateFromAsset(assetPath, templateName string) *template.Template {
	bytes := assets.MustAsset(assetPath)
	return template.Must(template.New(templateName).Funcs(funcMap).Parse(string(bytes)))
}

var indexHTML *template.Template
var iconsHTML *template.Template

var funcMap = template.FuncMap{
	"GoogleAnalyticsID": googleAnalyticsID,
	"ImgWidth":          imgWidth,
}

func googleAnalyticsID() string {
	return os.Getenv("ICONS_ANALYTICS_ID")
}

func imgWidth(i *besticon.Icon) int {
	return i.Width / 2.0
}

func init() {
	besticon.SetCacheMaxSize(64)

	expvar.Publish("cacheBytes", expvar.Func(func() interface{} { return besticon.GetCacheStats().Bytes }))
	expvar.Publish("cacheItems", expvar.Func(func() interface{} { return besticon.GetCacheStats().Items }))
	expvar.Publish("cacheGets", expvar.Func(func() interface{} { return besticon.GetCacheStats().Gets }))
	expvar.Publish("cacheHits", expvar.Func(func() interface{} { return besticon.GetCacheStats().Hits }))
	expvar.Publish("cacheEvictions", expvar.Func(func() interface{} { return besticon.GetCacheStats().Evictions }))

	ticker := time.NewTicker(time.Minute * 1)
	go func() {
		for range ticker.C {
			logger.Printf("Cache: %+v", besticon.GetCacheStats())
		}
	}()
}
<|endoftext|>


<commit_before>// Copyright 2018 The gVisor 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.

// Binary local_server provides a local 9P2000.L server for the p9 package.
//
// To use, first start the server:
//     local_server 127.0.0.1:3333
//
// Then, connect using the Linux 9P filesystem:
//     mount -t 9p -o trans=tcp,port=3333 127.0.0.1 /mnt
package main

import (
	"flag"
	"log"
	"net"
	"os"

	"github.com/hugelgupf/p9/localfs"
	"github.com/hugelgupf/p9/p9"
)

var (
	verbose = flag.Bool("v", false, "verbose logging")
	root    = flag.String("root", "/", "root dir of file system to expose")
)

func main() {
	flag.Parse()
	if *verbose {
		p9.Debug = log.Printf
	}

	if len(os.Args) != 2 {
		log.Fatalf("usage: %s <bind-addr>", os.Args[0])
	}

	// Bind and listen on the socket.
	serverSocket, err := net.Listen("tcp", os.Args[1])
	if err != nil {
		log.Fatalf("err binding: %v", err)
	}

	// Run the server.
	s := p9.NewServer(localfs.Attacher(*root))
	s.Serve(serverSocket)
}
<commit_msg>local_server: fix arguments<commit_after>// Copyright 2018 The gVisor 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.

// Binary local_server provides a local 9P2000.L server for the p9 package.
//
// To use, first start the server:
//     local_server 127.0.0.1:3333
//
// Then, connect using the Linux 9P filesystem:
//     mount -t 9p -o trans=tcp,port=3333 127.0.0.1 /mnt
package main

import (
	"flag"
	"log"
	"net"
	"os"

	"github.com/hugelgupf/p9/localfs"
	"github.com/hugelgupf/p9/p9"
)

var (
	verbose = flag.Bool("v", false, "verbose logging")
	root    = flag.String("root", "/", "root dir of file system to expose")
)

func main() {
	flag.Parse()
	if *verbose {
		p9.Debug = log.Printf
	}

	if len(flag.Args()) != 1 {
		log.Fatalf("usage: %s <bind-addr>", os.Args[0])
	}

	// Bind and listen on the socket.
	serverSocket, err := net.Listen("tcp", flag.Args()[0])
	if err != nil {
		log.Fatalf("err binding: %v", err)
	}

	// Run the server.
	s := p9.NewServer(localfs.Attacher(*root))
	s.Serve(serverSocket)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"reflect"
	"strings"
	"time"

	"github.com/jessevdk/go-flags"
	"github.com/lestrrat/go-server-starter"
)

const version = "0.0.2"

type options struct {
	OptArgs                []string
	OptCommand             string
	OptDir                 string   `long:"dir" arg:"path" description:"working directory, start_server do chdir to before exec (optional)"`
	OptInterval            int      `long:"interval" arg:"seconds" description:"minimum interval (in seconds) to respawn the server program (default: 1)"`
	OptPorts               []string `long:"port" arg:"(port|host:port)" description:"TCP port to listen to (if omitted, will not bind to any ports)"`
	OptPaths               []string `long:"path" arg:"path" description:"path at where to listen using unix socket (optional)"`
	OptSignalOnHUP         string   `long:"signal-on-hup" arg:"Signal" description:"name of the signal to be sent to the server process when start_server\nreceives a SIGHUP (default: HUP). If you use this option, be sure to\nalso use '--signal-on-term' below."`
	OptSignalOnTERM        string   `long:"signal-on-term" arg:"Signal" description:"name of the signal to be sent to the server process when start_server\nreceives a SIGTERM (default: TERM)"`
	OptPidFile             string   `long:"pid-file" arg:"filename" description:"if set, writes the process id of the start_server process to the file"`
	OptStatusFile          string   `long:"status-file" arg:"filename" description:"if set, writes the status of the server process(es) to the file"`
	OptEnvdir              string   `long:"envdir" arg:"Envdir" description:"directory that contains environment variables to the server processes.\nIt is intended for use with \"envdir\" in \"daemontools\". This can be\noverwritten by environment variable \"ENVDIR\"."`
	OptEnableAutoRestart   bool     `long:"enable-auto-restart" description:"enables automatic restart by time. This can be overwritten by\nenvironment variable \"ENABLE_AUTO_RESTART\"." note:"unimplemented"`
	OptAutoRestartInterval int      `long:"auto-restart-interval" arg:"seconds" description:"automatic restart interval (default 360). It is used with\n\"--enable-auto-restart\" option. This can be overwritten by environment\nvariable \"AUTO_RESTART_INTERVAL\"." note:"unimplemented"`
	OptKillOldDelay        int      `long:"kill-old-delay" arg:"seconds" description:"time to suspend to send a signal to the old worker. The default value is\n5 when \"--enable-auto-restart\" is set, 0 otherwise. This can be\noverwritten by environment variable \"KILL_OLD_DELAY\"."`
	OptRestart             bool     `long:"restart" description:"this is a wrapper command that reads the pid of the start_server process\nfrom --pid-file, sends SIGHUP to the process and waits until the\nserver(s) of the older generation(s) die by monitoring the contents of\nthe --status-file" note:"unimplemented"`
	OptHelp                bool     `long:"help" description:"prints this help"`
	OptVersion             bool     `long:"version" description:"printes the version number"`
}

func (o options) Args() []string          { return o.OptArgs }
func (o options) Command() string         { return o.OptCommand }
func (o options) Dir() string             { return o.OptDir }
func (o options) Interval() time.Duration { return time.Duration(o.OptInterval) * time.Second }
func (o options) PidFile() string         { return o.OptPidFile }
func (o options) Ports() []string         { return o.OptPorts }
func (o options) Paths() []string         { return o.OptPaths }
func (o options) SignalOnHUP() os.Signal  { return starter.SigFromName(o.OptSignalOnHUP) }
func (o options) SignalOnTERM() os.Signal { return starter.SigFromName(o.OptSignalOnTERM) }
func (o options) StatusFile() string      { return o.OptStatusFile }

func showHelp() {
	// The ONLY reason we're not using go-flags' help option is
	// because I wanted to tweak the format just a bit... but
	// there wasn't an easy way to do so
	os.Stderr.WriteString(`
Usage:
      start_server [options] -- server-prog server-arg1 server-arg2 ...

      # start Plack using Starlet listening at TCP port 8000
      start_server --port=8000 -- plackup -s Starlet --max-workers=100 index.psgi

Options:
`)

	t := reflect.TypeOf(options{})

	// This weird indexing stuff is done purely to keep ourselves
	// compatible with the original start_server program
	// (This is the order that the help is displayed in)
	names := []string{
		"OptPorts",
		"OptPaths",
		"OptDir",
		"OptInterval",
		"OptSignalOnHUP",
		"OptSignalOnTERM",
		"OptPidFile",
		"OptStatusFile",
		"OptEnvdir",
		"OptEnableAutoRestart",
		"OptAutoRestartInterval",
		"OptKillOldDelay",
		"OptRestart",
		"OptHelp",
		"OptVersion",
	}

	for _, name := range names {
		f, ok := t.FieldByName(name)
		if !ok {
			continue
		}

		tag := f.Tag
		if tag == "" {
			continue
		}
		if s := tag.Get("long"); s != "" {
			fmt.Fprintf(os.Stderr, "  --%s", s)
			if a := tag.Get("arg"); a != "" {
				fmt.Fprintf(os.Stderr, "=%s", a)
			}
			if tag.Get("note") == "unimplemented" {
				fmt.Fprintf(os.Stderr, " (UNIMPLEMENTED)")
			}
			fmt.Fprintf(os.Stderr, ":\n")
		}
		for _, l := range strings.Split(tag.Get("description"), "\n") {
			fmt.Fprintf(os.Stderr, "    %s\n", l)
		}
		fmt.Fprintf(os.Stderr, "\n")
	}
}

func main() {
	os.Exit(_main())
}

func _main() (st int) {
	st = 1

	opts := &options{OptInterval: -1}
	p := flags.NewParser(opts, flags.PrintErrors|flags.PassDoubleDash)
	args, err := p.Parse()
	if err != nil || opts.OptHelp {
		showHelp()
		return
	}

	if opts.OptVersion {
		fmt.Printf("%s\n", version)
		st = 0
		return
	}

	if opts.OptInterval < 0 {
		opts.OptInterval = 1
	}

	if len(args) == 0 {
		fmt.Fprintf(os.Stderr, "server program not specified\n")
		return
	}

	opts.OptCommand = args[0]
	if len(args) > 1 {
		opts.OptArgs = args[1:]
	}

	if opts.OptEnvdir != "" {
		os.Setenv("ENVDIR", opts.OptEnvdir)
	}

	s, err := starter.NewStarter(opts)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: %s\n", err)
		return
	}
	if err := s.Run(); err != nil {
		fmt.Fprintf(os.Stderr, "error: %s\n", err)
		return
	}
	st = 0
	return
}
<commit_msg>Fix description of signal-on-hup.<commit_after>package main

import (
	"fmt"
	"os"
	"reflect"
	"strings"
	"time"

	"github.com/jessevdk/go-flags"
	"github.com/lestrrat/go-server-starter"
)

const version = "0.0.2"

type options struct {
	OptArgs                []string
	OptCommand             string
	OptDir                 string   `long:"dir" arg:"path" description:"working directory, start_server do chdir to before exec (optional)"`
	OptInterval            int      `long:"interval" arg:"seconds" description:"minimum interval (in seconds) to respawn the server program (default: 1)"`
	OptPorts               []string `long:"port" arg:"(port|host:port)" description:"TCP port to listen to (if omitted, will not bind to any ports)"`
	OptPaths               []string `long:"path" arg:"path" description:"path at where to listen using unix socket (optional)"`
	OptSignalOnHUP         string   `long:"signal-on-hup" arg:"Signal" description:"name of the signal to be sent to the server process when start_server\nreceives a SIGHUP (default: TERM). If you use this option, be sure to\nalso use '--signal-on-term' below."`
	OptSignalOnTERM        string   `long:"signal-on-term" arg:"Signal" description:"name of the signal to be sent to the server process when start_server\nreceives a SIGTERM (default: TERM)"`
	OptPidFile             string   `long:"pid-file" arg:"filename" description:"if set, writes the process id of the start_server process to the file"`
	OptStatusFile          string   `long:"status-file" arg:"filename" description:"if set, writes the status of the server process(es) to the file"`
	OptEnvdir              string   `long:"envdir" arg:"Envdir" description:"directory that contains environment variables to the server processes.\nIt is intended for use with \"envdir\" in \"daemontools\". This can be\noverwritten by environment variable \"ENVDIR\"."`
	OptEnableAutoRestart   bool     `long:"enable-auto-restart" description:"enables automatic restart by time. This can be overwritten by\nenvironment variable \"ENABLE_AUTO_RESTART\"." note:"unimplemented"`
	OptAutoRestartInterval int      `long:"auto-restart-interval" arg:"seconds" description:"automatic restart interval (default 360). It is used with\n\"--enable-auto-restart\" option. This can be overwritten by environment\nvariable \"AUTO_RESTART_INTERVAL\"." note:"unimplemented"`
	OptKillOldDelay        int      `long:"kill-old-delay" arg:"seconds" description:"time to suspend to send a signal to the old worker. The default value is\n5 when \"--enable-auto-restart\" is set, 0 otherwise. This can be\noverwritten by environment variable \"KILL_OLD_DELAY\"."`
	OptRestart             bool     `long:"restart" description:"this is a wrapper command that reads the pid of the start_server process\nfrom --pid-file, sends SIGHUP to the process and waits until the\nserver(s) of the older generation(s) die by monitoring the contents of\nthe --status-file" note:"unimplemented"`
	OptHelp                bool     `long:"help" description:"prints this help"`
	OptVersion             bool     `long:"version" description:"printes the version number"`
}

func (o options) Args() []string          { return o.OptArgs }
func (o options) Command() string         { return o.OptCommand }
func (o options) Dir() string             { return o.OptDir }
func (o options) Interval() time.Duration { return time.Duration(o.OptInterval) * time.Second }
func (o options) PidFile() string         { return o.OptPidFile }
func (o options) Ports() []string         { return o.OptPorts }
func (o options) Paths() []string         { return o.OptPaths }
func (o options) SignalOnHUP() os.Signal  { return starter.SigFromName(o.OptSignalOnHUP) }
func (o options) SignalOnTERM() os.Signal { return starter.SigFromName(o.OptSignalOnTERM) }
func (o options) StatusFile() string      { return o.OptStatusFile }

func showHelp() {
	// The ONLY reason we're not using go-flags' help option is
	// because I wanted to tweak the format just a bit... but
	// there wasn't an easy way to do so
	os.Stderr.WriteString(`
Usage:
      start_server [options] -- server-prog server-arg1 server-arg2 ...

      # start Plack using Starlet listening at TCP port 8000
      start_server --port=8000 -- plackup -s Starlet --max-workers=100 index.psgi

Options:
`)

	t := reflect.TypeOf(options{})

	// This weird indexing stuff is done purely to keep ourselves
	// compatible with the original start_server program
	// (This is the order that the help is displayed in)
	names := []string{
		"OptPorts",
		"OptPaths",
		"OptDir",
		"OptInterval",
		"OptSignalOnHUP",
		"OptSignalOnTERM",
		"OptPidFile",
		"OptStatusFile",
		"OptEnvdir",
		"OptEnableAutoRestart",
		"OptAutoRestartInterval",
		"OptKillOldDelay",
		"OptRestart",
		"OptHelp",
		"OptVersion",
	}

	for _, name := range names {
		f, ok := t.FieldByName(name)
		if !ok {
			continue
		}

		tag := f.Tag
		if tag == "" {
			continue
		}
		if s := tag.Get("long"); s != "" {
			fmt.Fprintf(os.Stderr, "  --%s", s)
			if a := tag.Get("arg"); a != "" {
				fmt.Fprintf(os.Stderr, "=%s", a)
			}
			if tag.Get("note") == "unimplemented" {
				fmt.Fprintf(os.Stderr, " (UNIMPLEMENTED)")
			}
			fmt.Fprintf(os.Stderr, ":\n")
		}
		for _, l := range strings.Split(tag.Get("description"), "\n") {
			fmt.Fprintf(os.Stderr, "    %s\n", l)
		}
		fmt.Fprintf(os.Stderr, "\n")
	}
}

func main() {
	os.Exit(_main())
}

func _main() (st int) {
	st = 1

	opts := &options{OptInterval: -1}
	p := flags.NewParser(opts, flags.PrintErrors|flags.PassDoubleDash)
	args, err := p.Parse()
	if err != nil || opts.OptHelp {
		showHelp()
		return
	}

	if opts.OptVersion {
		fmt.Printf("%s\n", version)
		st = 0
		return
	}

	if opts.OptInterval < 0 {
		opts.OptInterval = 1
	}

	if len(args) == 0 {
		fmt.Fprintf(os.Stderr, "server program not specified\n")
		return
	}

	opts.OptCommand = args[0]
	if len(args) > 1 {
		opts.OptArgs = args[1:]
	}

	if opts.OptEnvdir != "" {
		os.Setenv("ENVDIR", opts.OptEnvdir)
	}

	s, err := starter.NewStarter(opts)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: %s\n", err)
		return
	}
	if err := s.Run(); err != nil {
		fmt.Fprintf(os.Stderr, "error: %s\n", err)
		return
	}
	st = 0
	return
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/litl/galaxy/log"
)

var (
	httpRouter *HostRouter
)

type RequestLogger struct{}

// This works along with the ServiceRegistry, and the individual Services to
// route http requests based on the Host header. The Resgistry hold the mapping
// of VHost names to individual services, and each service has it's own
// ReeverseProxy to fulfill the request.
// HostRouter contains the ReverseProxy http Listener, and has an http.Handler
// to service the requets.
type HostRouter struct {
	sync.Mutex
	// the http frontend
	server *http.Server

	// track our listener so we can kill the server
	listener net.Listener
}

func NewHostRouter() *HostRouter {
	return &HostRouter{}
}

func (r *HostRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	var err error
	host := req.Host
	if strings.Contains(host, ":") {
		host, _, err = net.SplitHostPort(req.Host)
		if err != nil {
			log.Warnf("%s", err)
		}
	}

	svc := Registry.GetVHostService(host)

	if svc != nil && svc.httpProxy != nil {
		// The vhost has a service registered, give it to the proxy
		svc.ServeHTTP(w, req)
		return
	}

	r.adminHandler(w, req)
}

func (r *HostRouter) adminHandler(w http.ResponseWriter, req *http.Request) {
	r.Lock()
	defer r.Unlock()

	if Registry.VHostsLen() == 0 {
		http.Error(w, "no backends available", http.StatusServiceUnavailable)
		return
	}

	// TODO: better status lines
	stats := Registry.Stats()
	for _, svc := range stats {
		if len(svc.VirtualHosts) == 0 {
			continue
		}
		fmt.Fprintf(w, "%v\n", svc.VirtualHosts)
		for _, b := range svc.Backends {
			js, _ := json.Marshal(b)
			fmt.Fprintf(w, "\t%s\n", string(js))
		}
	}

	fmt.Fprintf(w, "\n")
	return
}

// TODO: collect more stats?

// Start the HTTP Router frontend.
// Takes a channel to notify when the listener is started
// to safely synchronize tests.
func (r *HostRouter) Start(ready chan bool) {
	//FIXME: poor locking strategy
	r.Lock()

	log.Printf("HTTP server listening at %s", listenAddr)

	// Proxy acts as http handler:
	r.server = &http.Server{
		Addr:           listenAddr,
		Handler:        r,
		ReadTimeout:    60 * time.Second,
		WriteTimeout:   60 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}

	var err error
	r.listener, err = net.Listen("tcp", listenAddr)
	if err != nil {
		log.Errorf("%s", err)
		r.Unlock()
		return
	}

	r.Unlock()
	if ready != nil {
		close(ready)
	}

	// This will log a closed connection error every time we Stop
	// but that's mostly a testing issue.
	log.Errorf("%s", r.server.Serve(r.listener))
}

func (r *HostRouter) Stop() {
	r.listener.Close()
}

func startHTTPServer() {
	//FIXME: this global wg?
	defer wg.Done()
	httpRouter = NewHostRouter()
	httpRouter.Start(nil)
}

func sslRedirect(pr *ProxyRequest) bool {
	pr.Request.Header.Set("X-Request-Id", genId())

	if sslOnly && pr.Request.Header.Get("X-Forwarded-Proto") != "https" {
		//TODO: verify RequestURI
		redirLoc := "https://" + pr.Request.Host + pr.Request.RequestURI
		http.Redirect(pr.ResponseWriter, pr.Request, redirLoc, http.StatusMovedPermanently)
		return false
	}

	return true
}

type ErrorPage struct {
	// The Mutex protects access to the body slice.
	// Everything else should be static once the ErrorPage is created.
	sync.Mutex

	Location    string
	StatusCodes []int

	// body contains the cached error page
	body []byte
}

func (e *ErrorPage) Body() []byte {
	e.Lock()
	defer e.Unlock()
	return e.body
}

func (e *ErrorPage) SetBody(b []byte) {
	e.Lock()
	defer e.Unlock()
	e.body = b
}

// ErrorResponse provides vulcan middleware to process a response and insert
// custom error pages for a virtual host.
type ErrorResponse struct {
	sync.Mutex

	// map them by status for responses
	pages map[int]*ErrorPage

	// keep this handy to refresh the pages
	client *http.Client
}

func NewErrorResponse(pages map[string][]int) *ErrorResponse {
	errors := &ErrorResponse{
		pages: make(map[int]*ErrorPage),
	}

	if pages != nil {
		errors.Update(pages)
	}

	// aggressively timeout connections
	errors.client = &http.Client{
		Transport: &http.Transport{
			Dial: (&net.Dialer{
				Timeout: 2 * time.Second,
			}).Dial,
			TLSHandshakeTimeout: 2 * time.Second,
		},
		Timeout: 5 * time.Second,
	}

	go errors.update()
	return errors
}

// attempt to fetch and cache all error pages
func (e *ErrorResponse) update() {
	e.Lock()
	// find the set of error pages
	pages := make(map[*ErrorPage]bool)
	for _, page := range e.pages {
		pages[page] = true
	}

	// Unlock while fetching the pages so we don't block any incoming errors
	e.Unlock()

	for page := range pages {
		if page.Body() != nil {
			continue
		}

		e.fetch(page)
	}
}

// Get the error page body
// We permanently cache error pages once we've seen them
func (e *ErrorResponse) Get(code int) []byte {
	e.Lock()
	page, ok := e.pages[code]
	e.Unlock()

	if !ok {
		// this is a code we don't handle
		return nil
	}

	body := page.Body()
	if body != nil {
		return body
	}

	// we haven't successfully fetched this error
	e.fetch(page)
	return page.Body()
}

func (e *ErrorResponse) fetch(page *ErrorPage) {
	log.Debugf("Fetching error page from %s", page.Location)
	resp, err := e.client.Get(page.Location)
	if err != nil {
		log.Warnf("Could not fetch %s: %s", page.Location, err.Error())
		return
	}
	defer resp.Body.Close()

	// If the StatusCode matches any of our registered codes, it's OK
	for _, code := range page.StatusCodes {
		if resp.StatusCode == code {
			break
		}
	}

	if resp.StatusCode != http.StatusOK {
		log.Warnf("Server returned %d when fetching %s", resp.StatusCode, page.Location)
		return
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Warnf("Error reading response from %s: %s", page.Location, err.Error())
		return
	}

	if len(body) > 0 {
		page.SetBody(body)
		return
	}
	log.Warnf("Empty response from %s", page.Location)
}

// This replaces all existing ErrorPages
func (e *ErrorResponse) Update(pages map[string][]int) {
	e.Lock()
	defer e.Unlock()

	e.pages = make(map[int]*ErrorPage)

	for loc, codes := range pages {
		page := &ErrorPage{
			StatusCodes: codes,
			Location:    loc,
		}

		for _, code := range codes {
			e.pages[code] = page
		}
	}
	go e.update()
}

func (e *ErrorResponse) CheckResponse(pr *ProxyRequest) bool {

	errPage := e.Get(pr.Response.StatusCode)
	if errPage != nil {
		pr.ResponseWriter.WriteHeader(pr.Response.StatusCode)
		pr.ResponseWriter.Write(errPage)
		return false
	}

	return true
}

func logProxyRequest(pr *ProxyRequest) bool {
	// TODO: we may to be able to switch this off
	if pr == nil {
		return true
	}

	var id, method, clientIP, url, backend, agent string

	duration := pr.FinishTime.Sub(pr.StartTime)

	if pr.Request != nil {
		id = pr.Request.Header.Get("X-Request-Id")
		method = pr.Request.Method
		clientIP = pr.Request.RemoteAddr
		url = pr.Request.Host + pr.Request.RequestURI
		agent = pr.Request.UserAgent()
	}

	if pr.Response != nil && pr.Response.Request != nil && pr.Response.Request.URL != nil {
		backend = pr.Response.Request.URL.Host
	}

	err := fmt.Sprintf("%v", pr.ProxyError)

	fmtStr := "id=%s method=%s clientIp=%s url=%s backend=%s duration=%s agent=%s, err=%s"

	log.Printf(fmtStr, id, method, clientIP, url, backend, duration, agent, err)
	return true
}
<commit_msg>Fix NewErrorResponse<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/litl/galaxy/log"
)

var (
	httpRouter *HostRouter
)

type RequestLogger struct{}

// This works along with the ServiceRegistry, and the individual Services to
// route http requests based on the Host header. The Resgistry hold the mapping
// of VHost names to individual services, and each service has it's own
// ReeverseProxy to fulfill the request.
// HostRouter contains the ReverseProxy http Listener, and has an http.Handler
// to service the requets.
type HostRouter struct {
	sync.Mutex
	// the http frontend
	server *http.Server

	// track our listener so we can kill the server
	listener net.Listener
}

func NewHostRouter() *HostRouter {
	return &HostRouter{}
}

func (r *HostRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	var err error
	host := req.Host
	if strings.Contains(host, ":") {
		host, _, err = net.SplitHostPort(req.Host)
		if err != nil {
			log.Warnf("%s", err)
		}
	}

	svc := Registry.GetVHostService(host)

	if svc != nil && svc.httpProxy != nil {
		// The vhost has a service registered, give it to the proxy
		svc.ServeHTTP(w, req)
		return
	}

	r.adminHandler(w, req)
}

func (r *HostRouter) adminHandler(w http.ResponseWriter, req *http.Request) {
	r.Lock()
	defer r.Unlock()

	if Registry.VHostsLen() == 0 {
		http.Error(w, "no backends available", http.StatusServiceUnavailable)
		return
	}

	// TODO: better status lines
	stats := Registry.Stats()
	for _, svc := range stats {
		if len(svc.VirtualHosts) == 0 {
			continue
		}
		fmt.Fprintf(w, "%v\n", svc.VirtualHosts)
		for _, b := range svc.Backends {
			js, _ := json.Marshal(b)
			fmt.Fprintf(w, "\t%s\n", string(js))
		}
	}

	fmt.Fprintf(w, "\n")
	return
}

// TODO: collect more stats?

// Start the HTTP Router frontend.
// Takes a channel to notify when the listener is started
// to safely synchronize tests.
func (r *HostRouter) Start(ready chan bool) {
	//FIXME: poor locking strategy
	r.Lock()

	log.Printf("HTTP server listening at %s", listenAddr)

	// Proxy acts as http handler:
	r.server = &http.Server{
		Addr:           listenAddr,
		Handler:        r,
		ReadTimeout:    60 * time.Second,
		WriteTimeout:   60 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}

	var err error
	r.listener, err = net.Listen("tcp", listenAddr)
	if err != nil {
		log.Errorf("%s", err)
		r.Unlock()
		return
	}

	r.Unlock()
	if ready != nil {
		close(ready)
	}

	// This will log a closed connection error every time we Stop
	// but that's mostly a testing issue.
	log.Errorf("%s", r.server.Serve(r.listener))
}

func (r *HostRouter) Stop() {
	r.listener.Close()
}

func startHTTPServer() {
	//FIXME: this global wg?
	defer wg.Done()
	httpRouter = NewHostRouter()
	httpRouter.Start(nil)
}

func sslRedirect(pr *ProxyRequest) bool {
	pr.Request.Header.Set("X-Request-Id", genId())

	if sslOnly && pr.Request.Header.Get("X-Forwarded-Proto") != "https" {
		//TODO: verify RequestURI
		redirLoc := "https://" + pr.Request.Host + pr.Request.RequestURI
		http.Redirect(pr.ResponseWriter, pr.Request, redirLoc, http.StatusMovedPermanently)
		return false
	}

	return true
}

type ErrorPage struct {
	// The Mutex protects access to the body slice.
	// Everything else should be static once the ErrorPage is created.
	sync.Mutex

	Location    string
	StatusCodes []int

	// body contains the cached error page
	body []byte
}

func (e *ErrorPage) Body() []byte {
	e.Lock()
	defer e.Unlock()
	return e.body
}

func (e *ErrorPage) SetBody(b []byte) {
	e.Lock()
	defer e.Unlock()
	e.body = b
}

// ErrorResponse provides a ReverProxy callback to process a response and
// insert custom error pages for a virtual host.
type ErrorResponse struct {
	sync.Mutex

	// map them by status for responses
	pages map[int]*ErrorPage

	// keep this handy to refresh the pages
	client *http.Client
}

func NewErrorResponse(pages map[string][]int) *ErrorResponse {
	errors := &ErrorResponse{
		pages: make(map[int]*ErrorPage),
	}

	// aggressively timeout connections
	errors.client = &http.Client{
		Transport: &http.Transport{
			Dial: (&net.Dialer{
				Timeout: 2 * time.Second,
			}).Dial,
			TLSHandshakeTimeout: 2 * time.Second,
		},
		Timeout: 5 * time.Second,
	}

	if pages != nil {
		errors.Update(pages)
	}
	return errors
}

// Get the error page body
// We permanently cache error pages once we've seen them
func (e *ErrorResponse) Get(code int) []byte {
	e.Lock()
	page, ok := e.pages[code]
	e.Unlock()

	if !ok {
		// this is a code we don't handle
		return nil
	}

	body := page.Body()
	if body != nil {
		return body
	}

	// we haven't successfully fetched this error
	e.fetch(page)
	return page.Body()
}

func (e *ErrorResponse) fetch(page *ErrorPage) {
	log.Debugf("Fetching error page from %s", page.Location)
	resp, err := e.client.Get(page.Location)
	if err != nil {
		log.Warnf("Could not fetch %s: %s", page.Location, err.Error())
		return
	}
	defer resp.Body.Close()

	// If the StatusCode matches any of our registered codes, it's OK
	for _, code := range page.StatusCodes {
		if resp.StatusCode == code {
			break
		}
	}

	if resp.StatusCode != http.StatusOK {
		log.Warnf("Server returned %d when fetching %s", resp.StatusCode, page.Location)
		return
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Warnf("Error reading response from %s: %s", page.Location, err.Error())
		return
	}

	if len(body) > 0 {
		page.SetBody(body)
		return
	}
	log.Warnf("Empty response from %s", page.Location)
}

// This replaces all existing ErrorPages
func (e *ErrorResponse) Update(pages map[string][]int) {
	e.Lock()
	defer e.Unlock()

	e.pages = make(map[int]*ErrorPage)

	for loc, codes := range pages {
		page := &ErrorPage{
			StatusCodes: codes,
			Location:    loc,
		}

		for _, code := range codes {
			e.pages[code] = page
		}
		go e.fetch(page)
	}
}

func (e *ErrorResponse) CheckResponse(pr *ProxyRequest) bool {

	errPage := e.Get(pr.Response.StatusCode)
	if errPage != nil {
		pr.ResponseWriter.WriteHeader(pr.Response.StatusCode)
		pr.ResponseWriter.Write(errPage)
		return false
	}

	return true
}

func logProxyRequest(pr *ProxyRequest) bool {
	// TODO: we may to be able to switch this off
	if pr == nil {
		return true
	}

	var id, method, clientIP, url, backend, agent string

	duration := pr.FinishTime.Sub(pr.StartTime)

	if pr.Request != nil {
		id = pr.Request.Header.Get("X-Request-Id")
		method = pr.Request.Method
		clientIP = pr.Request.RemoteAddr
		url = pr.Request.Host + pr.Request.RequestURI
		agent = pr.Request.UserAgent()
	}

	if pr.Response != nil && pr.Response.Request != nil && pr.Response.Request.URL != nil {
		backend = pr.Response.Request.URL.Host
	}

	err := fmt.Sprintf("%v", pr.ProxyError)

	fmtStr := "id=%s method=%s clientIp=%s url=%s backend=%s duration=%s agent=%s, err=%s"

	log.Printf(fmtStr, id, method, clientIP, url, backend, duration, agent, err)
	return true
}
<|endoftext|>


<commit_before>package types

import (
	"time"
	"net"
	"encoding/binary"
)

const (
	netAddrLen = 38
	versionLen = 80
	varIntLen = 9
)

type VarInt uint64

type Hash []byte

type NetAddr struct {
	Time time.Time
	Stream uint32
	Services uint64
	IP net.IP
	Port uint16
}

func (n *NetAddr) Serialize() []byte {
        ret := make([]byte, 0, netAddrLen)
        binary.BigEndian.PutUint64(uint64(n.Time.Unix()))
        binary.BigEndian.PutUint32(n.Stream)
	binary.BigEndian.PutUint64(n.Services)
	append(ret, []byte(n.IP.To16()))
	binary.BigEndian.PutUint16(n.Port)
	return ret
}

func (n *NetAddr) Unserialize(work []byte) error {
	if len(work) < netAddrLen {
		return SerialError(ELEN)
	}
	if n == nil {
		return SerialEroror(ENIL)
	}

	n.Time = time.Unix(binary.BigEndian.Uint64(work[:8], 0))
	n.Stream = binary.BigEndian.Uint32(work[8:12])
	n.Services = binary.BigEndian.Uint64(work[12:20])
	copy([]byte(n.IP), work[20:36])
	n.Port = binary.BigEndian.Uint16(work[36:38])
	return nil
}

// See https://bitmessage.org/wiki/Protocol_specification#Message_types

type Version struct {
	Version int32
	Services uint64
	Timestamp time.Time
	Addr_Recv NetAddr
	Addr_From NetAddr
	Nonce uint64
	Streams []uint64
}

func (v *Version) Serialize() []byte {
	ret := make([]byte, 0, versionLen)

	binary.BigEndian.PutUint32(uint32

	return ret
}

type Addr []NetAddr

type Inv []Hash

type GetData Inv
<commit_msg>Changes to message types.<commit_after>package types

import (
	"time"
	"net"
	"encoding/binary"
)

const (
	netAddrLen = 38
	versionLen = 80
	varIntLen = 9
)

type VarInt uint64

type Hash []byte

type NetAddr struct {
	Time time.Time
	Stream uint32
	Services uint64
	IP net.IP
	Port uint16
}

func (n *NetAddr) Serialize() []byte {
	if n == nil {
		return nil
	}
        ret := make([]byte, 0, netAddrLen)
        binary.BigEndian.PutUint64(uint64(n.Time.Unix()))
        binary.BigEndian.PutUint32(n.Stream)
	binary.BigEndian.PutUint64(n.Services)
	append(ret, []byte(n.IP.To16()))
	binary.BigEndian.PutUint16(n.Port)
	return ret
}

func (n *NetAddr) Unserialize(work []byte) error {
	if len(work) < netAddrLen {
		return SerialError(ELEN)
	}
	if n == nil {
		return SerialEroror(ENIL)
	}

	n.Time = time.Unix(binary.BigEndian.Uint64(work[:8], 0))
	n.Stream = binary.BigEndian.Uint32(work[8:12])
	n.Services = binary.BigEndian.Uint64(work[12:20])
	copy([]byte(n.IP), work[20:36])
	n.Port = binary.BigEndian.Uint16(work[36:38])
	return nil
}

// See https://bitmessage.org/wiki/Protocol_specification#Message_types

type Version struct {
	Version uint32
	Services uint64
	Timestamp time.Time
	Addr_Recv NetAddr
	Addr_From NetAddr
	Nonce uint64
	Streams []uint64
}

func (v *Version) Serialize() []byte {
	ret := make([]byte, 0, versionLen)

	binary.BigEndian.PutUint32(ret, v.Version)
	binary.BigEndian.PutUint64(ret, v.Services)
	binary.BigEndian.PutUint64(ret, uint64(v.Timestamp.Unix()))
	net := n.Addr_Recv.Serialize()
	append(ret, net[12:])
	net = n.Addr_From.Serialize()
	append(ret, net[12:])
	binary.BigEndian.PutUint64(ret, v.Nonce)

	return ret
}

type Addr []NetAddr

type Inv []Hash

type GetData Inv
<|endoftext|>


<commit_before>/*
   Copyright The containerd 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 v1

import (
	"encoding/json"
	"github.com/pkg/errors"
)

// Plugin type and configuration
type Plugin struct {
	// Type of plugin
	Type string `json:"type"`
	// Conf for the specific plugin
	Conf json.RawMessage `json:"conf,omitempty"`
}

// ConfigList for the global configuration of NRI
//
// Normally located at /etc/nri/conf.json
type ConfigList struct {
	// Verion of the list
	Version string `json:"version"`
	// Plugins
	Plugins []*Plugin `json:"plugins"`
}

// Spec for the container being processed
type Spec struct {
	// Resources struct from the OCI specification
	//
	// Can be WindowsResources or LinuxResources
	Resources json.RawMessage `json:"resources"`
	// Namespaces for the container
	Namespaces map[string]string `json:"namespaces,omitempty"`
	// CgroupsPath for the container
	CgroupsPath string `json:"cgroupsPath,omitempty"`
	// Annotations passed down to the OCI runtime specification
	Annotations map[string]string `json:"annotations,omitempty"`
}

// State of the request
type State string

const (
	// Create the initial resource for the container
	Create State = "create"
	// Delete any resources for the container
	Delete State = "delete"
	// Update the resources for the container
	Update State = "update"
	// Pause action of the container
	Pause State = "pause"
	// Resume action for the container
	Resume State = "resume"
)

// Request for a plugin invocation
type Request struct {
	// Conf specific for the plugin
	Conf json.RawMessage `json:"conf,omitempty"`

	// Version of the plugin
	Version string `json:"version"`
	// State action for the request
	State State `json:"state"`
	// ID for the container
	ID string `json:"id"`
	// SandboxID for the sandbox that the request belongs to
	//
	// If ID and SandboxID are the same, this is a request for the sandbox
	// SandboxID is empty for a non sandboxed container
	SandboxID string `json:"sandboxID,omitempty"`
	// Pid of the container
	//
	// -1 if there is no pid
	Pid int `json:"pid,omitempty"`
	// Spec generated from the OCI runtime specification
	Spec *Spec `json:"spec"`
	// Labels of a sandbox
	Labels map[string]string `json:"labels,omitempty"`
	// Results from previous plugins in the chain
	Results []*Result `json:"results,omitempty"`
}

// IsSandbox returns true if the request is for a sandbox
func (r *Request) IsSandbox() bool {
	return r.ID == r.SandboxID
}

// NewResult returns a result from the original request
func (r *Request) NewResult(plugin string) *Result {
	return &Result{
		Plugin:   plugin,
		Version:  r.Version,
		Metadata: make(map[string]string),
	}
}

// Result of the plugin invocation
type Result struct {
	// Plugin name that populated the result
	Plugin string `json:"plugin"`
	// Version of the plugin
	Version string `json:"version"`
	// Error message in case of failures
	Error string `json:"error"`
	// Metadata specific to actions taken by the plugin
	Metadata map[string]string `json:"metadata,omitempty"`
}

// Err creates an Error object if ErrorMessage is populated
func (r *Result) Err() error {
	if r.Error != "" {
		return errors.New(r.Error)
	}
	return nil
}
<commit_msg>types/v1: fix goimports<commit_after>/*
   Copyright The containerd 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 v1

import (
	"encoding/json"

	"github.com/pkg/errors"
)

// Plugin type and configuration
type Plugin struct {
	// Type of plugin
	Type string `json:"type"`
	// Conf for the specific plugin
	Conf json.RawMessage `json:"conf,omitempty"`
}

// ConfigList for the global configuration of NRI
//
// Normally located at /etc/nri/conf.json
type ConfigList struct {
	// Verion of the list
	Version string `json:"version"`
	// Plugins
	Plugins []*Plugin `json:"plugins"`
}

// Spec for the container being processed
type Spec struct {
	// Resources struct from the OCI specification
	//
	// Can be WindowsResources or LinuxResources
	Resources json.RawMessage `json:"resources"`
	// Namespaces for the container
	Namespaces map[string]string `json:"namespaces,omitempty"`
	// CgroupsPath for the container
	CgroupsPath string `json:"cgroupsPath,omitempty"`
	// Annotations passed down to the OCI runtime specification
	Annotations map[string]string `json:"annotations,omitempty"`
}

// State of the request
type State string

const (
	// Create the initial resource for the container
	Create State = "create"
	// Delete any resources for the container
	Delete State = "delete"
	// Update the resources for the container
	Update State = "update"
	// Pause action of the container
	Pause State = "pause"
	// Resume action for the container
	Resume State = "resume"
)

// Request for a plugin invocation
type Request struct {
	// Conf specific for the plugin
	Conf json.RawMessage `json:"conf,omitempty"`

	// Version of the plugin
	Version string `json:"version"`
	// State action for the request
	State State `json:"state"`
	// ID for the container
	ID string `json:"id"`
	// SandboxID for the sandbox that the request belongs to
	//
	// If ID and SandboxID are the same, this is a request for the sandbox
	// SandboxID is empty for a non sandboxed container
	SandboxID string `json:"sandboxID,omitempty"`
	// Pid of the container
	//
	// -1 if there is no pid
	Pid int `json:"pid,omitempty"`
	// Spec generated from the OCI runtime specification
	Spec *Spec `json:"spec"`
	// Labels of a sandbox
	Labels map[string]string `json:"labels,omitempty"`
	// Results from previous plugins in the chain
	Results []*Result `json:"results,omitempty"`
}

// IsSandbox returns true if the request is for a sandbox
func (r *Request) IsSandbox() bool {
	return r.ID == r.SandboxID
}

// NewResult returns a result from the original request
func (r *Request) NewResult(plugin string) *Result {
	return &Result{
		Plugin:   plugin,
		Version:  r.Version,
		Metadata: make(map[string]string),
	}
}

// Result of the plugin invocation
type Result struct {
	// Plugin name that populated the result
	Plugin string `json:"plugin"`
	// Version of the plugin
	Version string `json:"version"`
	// Error message in case of failures
	Error string `json:"error"`
	// Metadata specific to actions taken by the plugin
	Metadata map[string]string `json:"metadata,omitempty"`
}

// Err creates an Error object if ErrorMessage is populated
func (r *Result) Err() error {
	if r.Error != "" {
		return errors.New(r.Error)
	}
	return nil
}
<|endoftext|>


<commit_before>package rock7

import (
	"encoding/hex"
	"github.com/kellydunn/golang-geo"
	"net/http"
	"strconv"
	"time"
)

type Message struct {
	IMEI         string
	MOMSN        int
	TransmitTime time.Time
	IridumPos    *geo.Point
	IridiumCep   int
	HexData      string
	Data         string
}

type Endpoint struct {
	channel chan Message
}

func NewEndpoint() *Endpoint {
	return &Endpoint{
		make(chan Message),
	}
}

// Returns a channel contained all recieved messages.
// Getter method is required due to access locking.
func (end *Endpoint) GetChannel() <-chan Message {
	return end.channel
}

func readInt(n string) int {
	num, mErr := strconv.Atoi(n)

	if mErr != nil {
		panic("Invalid request, panic'ing to cancel request")
	}
	return num
}

func readFloat64(n string) float64 {
	num, mErr := strconv.ParseFloat(n, 64)

	if mErr != nil {
		panic("Invalid request, panic'ing to cancel request")
	}
	return num
}

func (end *Endpoint) ServeHTTP(writer http.ResponseWriter, req *http.Request) {

	if req.Method != "POST" {
		writer.WriteHeader(http.StatusBadRequest)
		return
	}

	data := req.FormValue("data")
	by, err := hex.DecodeString(data)

	// TODO: Read time
	if err == nil {
		end.channel <- Message{
			req.FormValue("imei"),
			readInt(req.FormValue("momsn")),
			time.Now(),
			geo.NewPoint(
				readFloat64(req.FormValue("iridium_latitude")),
				readFloat64(req.FormValue("iridium_longitude")),
			),
			readInt(req.FormValue("iridium_cep")),
			data,
			string(by),
		}
		writer.WriteHeader(http.StatusOK)
	} else {
		writer.WriteHeader(http.StatusBadRequest)
	}

}
<commit_msg>Clean up handling code<commit_after>package rock7

import (
	"encoding/hex"
	"github.com/kellydunn/golang-geo"
	"net/http"
	"strconv"
	"time"
)

type Message struct {
	IMEI         string
	MOMSN        int
	TransmitTime time.Time
	IridumPos    *geo.Point
	IridiumCep   int
	HexData      string
	Data         string
}

type Endpoint struct {
	channel chan Message
}

func NewEndpoint() *Endpoint {
	return &Endpoint{
		make(chan Message),
	}
}

// Returns a channel contained all recieved messages.
// Getter method is required due to access locking.
func (end *Endpoint) GetChannel() <-chan Message {
	return end.channel
}

func readInt(n string) int {
	num, mErr := strconv.Atoi(n)

	if mErr != nil {
		panic("Invalid request, panic'ing to cancel request")
	}
	return num
}

func readFloat64(n string) float64 {
	num, mErr := strconv.ParseFloat(n, 64)

	if mErr != nil {
		panic("Invalid request, panic'ing to cancel request")
	}
	return num
}

func readHex(n string) string {
	by, err := hex.DecodeString(n)

	if err != nil {
		panic("Invalid request, panic'ing to cancel request")
	}
	return string(by)
}

func (end *Endpoint) ServeHTTP(writer http.ResponseWriter, req *http.Request) {

	if req.Method != "POST" {
		writer.WriteHeader(http.StatusBadRequest)
		return
	}

	// TODO: Read time
	end.channel <- Message{
		req.FormValue("imei"),
		readInt(req.FormValue("momsn")),
		time.Now(),
		geo.NewPoint(
			readFloat64(req.FormValue("iridium_latitude")),
			readFloat64(req.FormValue("iridium_longitude")),
		),
		readInt(req.FormValue("iridium_cep")),
		req.FormValue("data"),
		readHex(req.FormValue("data")),
	}
	writer.WriteHeader(http.StatusOK)
}
<|endoftext|>


<commit_before>package s3

import (
	"errors"
	"net/url"
	"strings"
)

// Config contains all configuration necessary to connect to an s3 compatible
// server.
type Config struct {
	Endpoint      string
	UseHTTP       bool
	KeyID, Secret string
	Bucket        string
	Prefix        string
}

const defaultPrefix = "restic"

// ParseConfig parses the string s and extracts the s3 config. The two
// supported configuration formats are s3://host/bucketname/prefix and
// s3:host:bucketname/prefix. The host can also be a valid s3 region
// name. If no prefix is given the prefix "restic" will be used.
func ParseConfig(s string) (interface{}, error) {
	switch {
	case strings.HasPrefix(s, "s3:http"):
		// assume that a URL has been specified, parse it and
		// use the host as the endpoint and the path as the
		// bucket name and prefix
		url, err := url.Parse(s[3:])
		if err != nil {
			return nil, err
		}

		if url.Path == "" {
			return nil, errors.New("s3: bucket name not found")
		}

		path := strings.SplitN(url.Path[1:], "/", 2)
		return createConfig(url.Host, path, url.Scheme == "http")
	case strings.HasPrefix(s, "s3:"):
		// use the first entry of the path as the endpoint and the
		// remainder as bucket name and prefix
		s = s[3:]
		if strings.HasPrefix(s, "//") {
			s = s[2:]
		}
		path := strings.SplitN(s, "/", 3)
		return createConfig(path[0], path[1:], false)
	default:
		return nil, errors.New("s3: invalid format")
	}
}

func createConfig(endpoint string, path []string, useHttp bool) (interface{}, error) {
	var prefix string
	switch {
	case len(path) < 1:
		return nil, errors.New("s3: invalid format, host/region or bucket name not found")
	case len(path) == 1:
		prefix = defaultPrefix
	default:
		prefix = strings.TrimRight(path[1], "/")
	}
	return Config{
		Endpoint: endpoint,
		UseHTTP:  useHttp,
		Bucket:   path[0],
		Prefix:   prefix,
	}, nil
}
<commit_msg>fix Hound warning<commit_after>package s3

import (
	"errors"
	"net/url"
	"strings"
)

// Config contains all configuration necessary to connect to an s3 compatible
// server.
type Config struct {
	Endpoint      string
	UseHTTP       bool
	KeyID, Secret string
	Bucket        string
	Prefix        string
}

const defaultPrefix = "restic"

// ParseConfig parses the string s and extracts the s3 config. The two
// supported configuration formats are s3://host/bucketname/prefix and
// s3:host:bucketname/prefix. The host can also be a valid s3 region
// name. If no prefix is given the prefix "restic" will be used.
func ParseConfig(s string) (interface{}, error) {
	switch {
	case strings.HasPrefix(s, "s3:http"):
		// assume that a URL has been specified, parse it and
		// use the host as the endpoint and the path as the
		// bucket name and prefix
		url, err := url.Parse(s[3:])
		if err != nil {
			return nil, err
		}

		if url.Path == "" {
			return nil, errors.New("s3: bucket name not found")
		}

		path := strings.SplitN(url.Path[1:], "/", 2)
		return createConfig(url.Host, path, url.Scheme == "http")
	case strings.HasPrefix(s, "s3:"):
		// use the first entry of the path as the endpoint and the
		// remainder as bucket name and prefix
		s = s[3:]
		if strings.HasPrefix(s, "//") {
			s = s[2:]
		}
		path := strings.SplitN(s, "/", 3)
		return createConfig(path[0], path[1:], false)
	default:
		return nil, errors.New("s3: invalid format")
	}
}

func createConfig(endpoint string, path []string, useHTTP bool) (interface{}, error) {
	var prefix string
	switch {
	case len(path) < 1:
		return nil, errors.New("s3: invalid format, host/region or bucket name not found")
	case len(path) == 1:
		prefix = defaultPrefix
	default:
		prefix = strings.TrimRight(path[1], "/")
	}
	return Config{
		Endpoint: endpoint,
		UseHTTP:  useHTTP,
		Bucket:   path[0],
		Prefix:   prefix,
	}, nil
}
<|endoftext|>


<commit_before>package app

import (
	"log"
	"net/http"
	"net/http/httputil"
	"net"
	"strings"
)

func logRequest(req *http.Request, status int, reason string) {
	host, _, err := net.SplitHostPort(req.RemoteAddr)
	if err != nil {
		host = req.RemoteAddr
	}

	log.Printf("%s %s %s %d \"%s\"\n", host, req.Method, req.RequestURI, http.StatusForbidden, reason)
}

func showConnectionProgress(backend backend, w http.ResponseWriter, req *http.Request) bool {
	// Only do this for modern browsers.
	useragent := req.Header.Get("User-Agent")
	if !strings.Contains(useragent, "Mozilla") || isWebsocket(req) {
		return false
	}

	// Not for images and those kind of stuff?
	if backend.IsReady() {
		return false
	}

	serveProgressPage(w, req)
	return true
}

func Forward(w http.ResponseWriter, req *http.Request) {
	token := ""
	if cookie, err := req.Cookie("access_token"); err == nil {
		token = cookie.Value
	}
	if !LookupAccess(token, req.URL.Path) {
		logRequest(req, http.StatusForbidden, "Invalid access token")
		http.Error(w, "Access denied", http.StatusForbidden)
		return
	}

	backend := LookupBackend(req.URL.Path)
	if backend == nil {
		logRequest(req, http.StatusNotFound, "Path mapping not found")
		http.Error(w, "Path not mapped", http.StatusNotFound)
		return
	}

	if serveProgressWebSocket(backend, w, req) {
		return
	}

	if showConnectionProgress(backend, w, req) {
		return
	}

	if serveStatic(backend.GetInfo().StaticOverrides, w, req) {
		return
	}

	conn := backend.Connect()
	if conn == nil {
		logRequest(req, http.StatusInternalServerError, "Couldn't connect to backend server")
		return
	}

	director := func(req *http.Request) {
		req.URL.Path = backend.GetInfo().Backend.BasePath + strings.TrimPrefix(req.URL.Path, backend.GetInfo().Prefix)
		req.URL.Scheme = "http"
		req.URL.Host = backend.GetInfo().Backend.Address
	}

	var revProxy http.Handler
	if (isWebsocket(req)) {
		revProxy = &WebsocketReverseProxy{
			Director: director,
			Dial: func(network, addr string) (net.Conn, error) {
				return conn, nil
			},
		}

	} else {
		revProxy = &httputil.ReverseProxy{
			Director: director,
			Transport: &http.Transport{
				Dial: func(network, addr string) (net.Conn, error) {
					return conn, nil
				},
			},
		}
	}
	revProxy.ServeHTTP(w, req)
}
<commit_msg>Added (temporary) goroutine dump debug printouts<commit_after>package app

import (
	"log"
	"net/http"
	"net/http/httputil"
	"net"
	"strings"
	"runtime"
)

func logRequest(req *http.Request, status int, reason string) {
	host, _, err := net.SplitHostPort(req.RemoteAddr)
	if err != nil {
		host = req.RemoteAddr
	}

	log.Printf("%s %s %s %d \"%s\"\n", host, req.Method, req.RequestURI, http.StatusForbidden, reason)
}

func showConnectionProgress(backend backend, w http.ResponseWriter, req *http.Request) bool {
	// Only do this for modern browsers.
	useragent := req.Header.Get("User-Agent")
	if !strings.Contains(useragent, "Mozilla") || isWebsocket(req) {
		return false
	}

	// Not for images and those kind of stuff?
	if backend.IsReady() {
		return false
	}

	serveProgressPage(w, req)
	return true
}

func Forward(w http.ResponseWriter, req *http.Request) {
	if strings.HasSuffix(req.RequestURI, "/__ugdump") {
		buf := make([]byte, 1 << 20)
		runtime.Stack(buf, true)
		log.Printf("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end\n", buf)
		return
	}
	token := ""
	if cookie, err := req.Cookie("access_token"); err == nil {
		token = cookie.Value
	}
	if !LookupAccess(token, req.URL.Path) {
		logRequest(req, http.StatusForbidden, "Invalid access token")
		http.Error(w, "Access denied", http.StatusForbidden)
		return
	}

	backend := LookupBackend(req.URL.Path)
	if backend == nil {
		logRequest(req, http.StatusNotFound, "Path mapping not found")
		http.Error(w, "Path not mapped", http.StatusNotFound)
		return
	}

	if serveProgressWebSocket(backend, w, req) {
		return
	}

	if showConnectionProgress(backend, w, req) {
		return
	}

	if serveStatic(backend.GetInfo().StaticOverrides, w, req) {
		return
	}

	conn := backend.Connect()
	if conn == nil {
		logRequest(req, http.StatusInternalServerError, "Couldn't connect to backend server")
		return
	}

	director := func(req *http.Request) {
		req.URL.Path = backend.GetInfo().Backend.BasePath + strings.TrimPrefix(req.URL.Path, backend.GetInfo().Prefix)
		req.URL.Scheme = "http"
		req.URL.Host = backend.GetInfo().Backend.Address
	}

	var revProxy http.Handler
	if (isWebsocket(req)) {
		revProxy = &WebsocketReverseProxy{
			Director: director,
			Dial: func(network, addr string) (net.Conn, error) {
				return conn, nil
			},
		}

	} else {
		revProxy = &httputil.ReverseProxy{
			Director: director,
			Transport: &http.Transport{
				Dial: func(network, addr string) (net.Conn, error) {
					return conn, nil
				},
			},
		}
	}
	revProxy.ServeHTTP(w, req)
}
<|endoftext|>


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

// +build integration,go1.7

package e2e

import (
	"bytes"
	"flag"
	"fmt"
	"os"
	"runtime"
	"strings"
	"testing"
	"text/template"
	"time"

	"cloud.google.com/go/profiler/proftest"
	"golang.org/x/net/context"
	"golang.org/x/oauth2/google"
	compute "google.golang.org/api/compute/v1"
)

var (
	repo           = flag.String("repo", "https://github.com/googleapis/cloud-profiler-nodejs.git", "git repo to test")
	branch         = flag.String("branch", "", "git branch to test")
	commit         = flag.String("commit", "", "git commit to test")
	pr             = flag.Int("pr", 0, "git pull request to test")
	runBackoffTest = flag.Bool("run_backoff_test", false, "Enables the backoff integration test. This integration test requires over 45 mins to run, so it is not run by default.")

	runID             = strings.Replace(time.Now().Format("2006-01-02-15-04-05.000000-0700"), ".", "-", -1)
	benchFinishString = "benchmark application(s) complete"
	errorString       = "failed to set up or run the benchmark"
)

const (
	cloudScope       = "https://www.googleapis.com/auth/cloud-platform"
	gceBenchDuration = 600 * time.Second
	gceTestTimeout   = 25 * time.Minute

	// For any agents to receive backoff, there must be more than 32 agents in
	// the deployment. The initial backoff received will be 33 minutes; each
	// subsequent backoff will be one minute longer. Running 45 benchmarks for
	// 45 minutes will ensure that several agents receive backoff responses and
	// are able to wait for the backoff duration then send another request.
	numBackoffBenchmarks = 45
	backoffBenchDuration = 45 * time.Minute
	backoffTestTimeout   = 60 * time.Minute
)

const startupTemplate = `
{{ define "setup"}}

npm_install() {
	timeout 60 npm install --quiet --no-color --no-progress "${@}"
}

# Install git
retry apt-get update >/dev/null
retry apt-get -y -q install git >/dev/null

# Install desired version of Node.js
retry curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash >/dev/null
export NVM_DIR="$HOME/.nvm" >/dev/null
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" >/dev/null

# nvm install writes to stderr and stdout on successful install, so both are
# redirected to serial port 3.
retry nvm install {{.NodeVersion}} &>/dev/ttyS2
npm -v
node -v
NODEDIR=$(dirname $(dirname $(which node)))

# Install agent
git_clone_repo() {
	rm -rf cloud-profiler-nodejs && git clone {{.Repo}}
}
retry git_clone_repo
cd cloud-profiler-nodejs
retry git fetch origin {{if .PR}}pull/{{.PR}}/head{{else}}{{.Branch}}{{end}}:pull_branch
git checkout pull_branch
git reset --hard {{.Commit}}

retry npm_install --nodedir="$NODEDIR"

npm run compile
npm pack --nodedir="$NODEDIR" >/dev/null
VERSION=$(node -e "console.log(require('./package.json').version);")
PROFILER="$HOME/cloud-profiler-nodejs/google-cloud-profiler-$VERSION.tgz"

TESTDIR="$HOME/test"
mkdir -p "$TESTDIR"
cp -r "system-test/busybench" "$TESTDIR"
cd "$TESTDIR/busybench"

retry npm_install node-pre-gyp
retry npm_install --nodedir="$NODEDIR" "$PROFILER" typescript gts

npm run compile
{{- end }}

{{ define "integration" -}}
{{- template "prologue" . }}
{{- template "setup" . }}
# Run benchmark with agent
GCLOUD_PROFILER_LOGLEVEL=5 GAE_SERVICE={{.Service}} node --trace-warnings build/src/busybench.js {{.DurationSec}}

# Indicate to test that script has finished running
echo "{{.FinishString}}"

{{ template "epilogue" . -}}
{{end}}

{{ define "integration_backoff" -}}
{{- template "prologue" . }}
{{- template "setup" . }}

# Do not display commands being run to simplify logging output.
set +x

# Run benchmarks with agent.
echo "Starting {{.NumBackoffBenchmarks}} benchmarks."
for (( i = 0; i < {{.NumBackoffBenchmarks}}; i++ )); do
	# A Node.js application will not exit while a CreateProfile request is
	# inflight, so timeout is used to force the application to terminate.
	(timeout {{.DurationSec}} sh -c \
      'GCLOUD_PROFILER_LOGLEVEL=5 GAE_SERVICE={{.Service}} node --trace-warnings build/src/busybench.js {{.DurationSec}} 1'
	) |& while read line; do echo "benchmark $i: ${line}"; done || [ "$?" -eq "124" ] &
done
echo "Successfully started {{.NumBackoffBenchmarks}} benchmarks."

wait

# Continue displaying commands being run.
set -x

echo "{{.FinishString}}"

{{ template "epilogue" . -}}
{{ end }}

`

type profileSummary struct {
	profileType  string
	functionName string
	sourceFile   string
}

type nodeGCETestCase struct {
	proftest.InstanceConfig
	name          string
	nodeVersion   string
	benchDuration time.Duration
	timeout       time.Duration

	backoffTest bool

	// wantProfileTypes will not be used when the test is a backoff integration
	// test.
	wantProfiles []profileSummary
}

func (tc *nodeGCETestCase) initializeStartUpScript(template *template.Template) error {
	params := struct {
		Service              string
		NodeVersion          string
		Repo                 string
		PR                   int
		Branch               string
		Commit               string
		FinishString         string
		ErrorString          string
		DurationSec          int
		NumBackoffBenchmarks int
	}{
		Service:      tc.name,
		NodeVersion:  tc.nodeVersion,
		Repo:         *repo,
		PR:           *pr,
		Branch:       *branch,
		Commit:       *commit,
		FinishString: benchFinishString,
		ErrorString:  errorString,
		DurationSec:  int(tc.benchDuration.Seconds()),
	}

	testTemplate := "integration"
	if tc.backoffTest {
		testTemplate = "integration_backoff"
		params.NumBackoffBenchmarks = numBackoffBenchmarks
	}

	var buf bytes.Buffer
	err := template.Lookup(testTemplate).Execute(&buf, params)
	if err != nil {
		return fmt.Errorf("failed to render startup script for %s: %v", tc.name, err)
	}
	tc.StartupScript = buf.String()
	return nil
}

func TestAgentIntegration(t *testing.T) {
	projectID := os.Getenv("GCLOUD_TESTS_NODEJS_PROJECT_ID")
	if projectID == "" {
		t.Fatalf("Getenv(GCLOUD_TESTS_NODEJS_PROJECT_ID) got empty string")
	}

	zone := os.Getenv("GCLOUD_TESTS_NODEJS_ZONE")
	if zone == "" {
		t.Fatalf("Getenv(GCLOUD_TESTS_NODEJS_ZONE) got empty string")
	}

	if *commit == "" {
		t.Fatal("commit flag is not set")
	}

	ctx := context.Background()

	client, err := google.DefaultClient(ctx, cloudScope)
	if err != nil {
		t.Fatalf("failed to get default client: %v", err)
	}

	computeService, err := compute.New(client)
	if err != nil {
		t.Fatalf("failed to initialize compute Service: %v", err)
	}

	template, err := proftest.BaseStartupTmpl.Parse(startupTemplate)
	if err != nil {
		t.Fatalf("failed to parse startup script template: %v", err)
	}

	gceTr := proftest.GCETestRunner{
		TestRunner: proftest.TestRunner{
			Client: client,
		},
		ComputeService: computeService,
	}

	wantProfiles := []profileSummary{
		{"WALL", "busyLoop", "busybench.ts"},
		{"HEAP", "benchmark", "busybench.ts"},
	}

	testcases := []nodeGCETestCase{
		{
			InstanceConfig: proftest.InstanceConfig{
				ProjectID:   projectID,
				Zone:        zone,
				Name:        fmt.Sprintf("profiler-test-node10-%s", runID),
				MachineType: "n1-standard-1",
			},
			name:          fmt.Sprintf("profiler-test-node10-%s-gce", runID),
			wantProfiles:  wantProfiles,
			nodeVersion:   "10",
			timeout:       gceTestTimeout,
			benchDuration: gceBenchDuration,
		},
		{
			InstanceConfig: proftest.InstanceConfig{
				ProjectID:   projectID,
				Zone:        zone,
				Name:        fmt.Sprintf("profiler-test-node12-%s", runID),
				MachineType: "n1-standard-1",
			},
			name:          fmt.Sprintf("profiler-test-node12-%s-gce", runID),
			wantProfiles:  wantProfiles,
			nodeVersion:   "12",
			timeout:       gceTestTimeout,
			benchDuration: gceBenchDuration,
		},
		{
			InstanceConfig: proftest.InstanceConfig{
				ProjectID:   projectID,
				Zone:        zone,
				Name:        fmt.Sprintf("profiler-test-node14-%s", runID),
				MachineType: "n1-standard-1",
			},
			name:          fmt.Sprintf("profiler-test-node14-%s-gce", runID),
			wantProfiles:  wantProfiles,
			nodeVersion:   "14",
			timeout:       gceTestTimeout,
			benchDuration: gceBenchDuration,
		},
	}

	if *runBackoffTest {
		testcases = append(testcases,
			nodeGCETestCase{
				InstanceConfig: proftest.InstanceConfig{
					ProjectID: projectID,
					Zone:      zone,
					Name:      fmt.Sprintf("profiler-backoff-test-node12-%s", runID),

					// Running many copies of the benchmark requires more
					// memory than is available on an n1-standard-1. Use a
					// machine type with more memory for backoff test.
					MachineType: "n1-highmem-2",
				},
				name:          fmt.Sprintf("profiler-backoff-test-node12-%s", runID),
				backoffTest:   true,
				nodeVersion:   "12",
				timeout:       backoffTestTimeout,
				benchDuration: backoffBenchDuration,
			})
	}

	// Allow test cases to run in parallel.
	runtime.GOMAXPROCS(len(testcases))

	for _, tc := range testcases {
		tc := tc // capture range variable
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			if err := tc.initializeStartUpScript(template); err != nil {
				t.Fatalf("failed to initialize startup script: %v", err)
			}

			err := gceTr.StartInstance(ctx, &tc.InstanceConfig)
			if err != nil {
				t.Fatalf("failed to start GCE instance: %v", err)
			}
			defer func() {
				if gceTr.DeleteInstance(ctx, &tc.InstanceConfig); err != nil {
					t.Fatal(err)
				}
			}()

			timeoutCtx, cancel := context.WithTimeout(ctx, tc.timeout)
			defer cancel()
			output, err := gceTr.PollAndLogSerialPort(timeoutCtx, &tc.InstanceConfig, benchFinishString, errorString, t.Logf)
			if err != nil {
				t.Fatal(err)
			}

			if tc.backoffTest {
				if err := proftest.CheckSerialOutputForBackoffs(output, numBackoffBenchmarks, "action throttled, backoff", "Attempting to create profile", "benchmark"); err != nil {
					t.Errorf("failed to check serial output for backoffs: %v", err)
				}
				return
			}

			timeNow := time.Now()
			endTime := timeNow.Format(time.RFC3339)
			startTime := timeNow.Add(-1 * time.Hour).Format(time.RFC3339)
			for _, wantProfile := range tc.wantProfiles {
				pr, err := gceTr.TestRunner.QueryProfilesWithZone(tc.ProjectID, tc.name, startTime, endTime, wantProfile.profileType, tc.Zone)
				if err != nil {
					t.Errorf("QueryProfiles(%s, %s, %s, %s, %s) got error: %v", tc.ProjectID, tc.name, startTime, endTime, wantProfile.profileType, err)
					continue
				}
				if wantProfile.sourceFile != "" {
					if err := pr.HasFunctionInFile(wantProfile.functionName, wantProfile.sourceFile); err != nil {
						t.Errorf("Function %s not found in source file %s in profiles of type %s: %v", wantProfile.functionName, wantProfile.sourceFile, wantProfile.profileType, err)
					}
					continue
				}
				if err := pr.HasFunction(wantProfile.functionName); err != nil {
					t.Errorf("Function %s not found in profiles of type %s: %v", wantProfile.functionName, wantProfile.profileType, err)
				}
			}
		})
	}
}
<commit_msg>chore: clean npm cache on package installations (#748)<commit_after>// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build integration,go1.7

package e2e

import (
	"bytes"
	"flag"
	"fmt"
	"os"
	"runtime"
	"strings"
	"testing"
	"text/template"
	"time"

	"cloud.google.com/go/profiler/proftest"
	"golang.org/x/net/context"
	"golang.org/x/oauth2/google"
	compute "google.golang.org/api/compute/v1"
)

var (
	repo           = flag.String("repo", "https://github.com/googleapis/cloud-profiler-nodejs.git", "git repo to test")
	branch         = flag.String("branch", "", "git branch to test")
	commit         = flag.String("commit", "", "git commit to test")
	pr             = flag.Int("pr", 0, "git pull request to test")
	runBackoffTest = flag.Bool("run_backoff_test", false, "Enables the backoff integration test. This integration test requires over 45 mins to run, so it is not run by default.")

	runID             = strings.Replace(time.Now().Format("2006-01-02-15-04-05.000000-0700"), ".", "-", -1)
	benchFinishString = "benchmark application(s) complete"
	errorString       = "failed to set up or run the benchmark"
)

const (
	cloudScope       = "https://www.googleapis.com/auth/cloud-platform"
	gceBenchDuration = 600 * time.Second
	gceTestTimeout   = 25 * time.Minute

	// For any agents to receive backoff, there must be more than 32 agents in
	// the deployment. The initial backoff received will be 33 minutes; each
	// subsequent backoff will be one minute longer. Running 45 benchmarks for
	// 45 minutes will ensure that several agents receive backoff responses and
	// are able to wait for the backoff duration then send another request.
	numBackoffBenchmarks = 45
	backoffBenchDuration = 45 * time.Minute
	backoffTestTimeout   = 60 * time.Minute
)

const startupTemplate = `
{{ define "setup"}}

npm_install() {
	npm cache clean --force # Avoid persistent errors on rare cache corruptions.
	timeout 60 npm install --quiet --no-color --no-progress "${@}"
}

# Install git
retry apt-get update >/dev/null
retry apt-get -y -q install git >/dev/null

# Install desired version of Node.js
retry curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash >/dev/null
export NVM_DIR="$HOME/.nvm" >/dev/null
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" >/dev/null

# nvm install writes to stderr and stdout on successful install, so both are
# redirected to serial port 3.
retry nvm install {{.NodeVersion}} &>/dev/ttyS2
npm -v
node -v
NODEDIR=$(dirname $(dirname $(which node)))

# Install agent
git_clone_repo() {
	rm -rf cloud-profiler-nodejs && git clone {{.Repo}}
}
retry git_clone_repo
cd cloud-profiler-nodejs
retry git fetch origin {{if .PR}}pull/{{.PR}}/head{{else}}{{.Branch}}{{end}}:pull_branch
git checkout pull_branch
git reset --hard {{.Commit}}

retry npm_install --nodedir="$NODEDIR"

npm run compile
npm pack --nodedir="$NODEDIR" >/dev/null
VERSION=$(node -e "console.log(require('./package.json').version);")
PROFILER="$HOME/cloud-profiler-nodejs/google-cloud-profiler-$VERSION.tgz"

TESTDIR="$HOME/test"
mkdir -p "$TESTDIR"
cp -r "system-test/busybench" "$TESTDIR"
cd "$TESTDIR/busybench"

retry npm_install node-pre-gyp
retry npm_install --nodedir="$NODEDIR" "$PROFILER" typescript gts

npm run compile
{{- end }}

{{ define "integration" -}}
{{- template "prologue" . }}
{{- template "setup" . }}
# Run benchmark with agent
GCLOUD_PROFILER_LOGLEVEL=5 GAE_SERVICE={{.Service}} node --trace-warnings build/src/busybench.js {{.DurationSec}}

# Indicate to test that script has finished running
echo "{{.FinishString}}"

{{ template "epilogue" . -}}
{{end}}

{{ define "integration_backoff" -}}
{{- template "prologue" . }}
{{- template "setup" . }}

# Do not display commands being run to simplify logging output.
set +x

# Run benchmarks with agent.
echo "Starting {{.NumBackoffBenchmarks}} benchmarks."
for (( i = 0; i < {{.NumBackoffBenchmarks}}; i++ )); do
	# A Node.js application will not exit while a CreateProfile request is
	# inflight, so timeout is used to force the application to terminate.
	(timeout {{.DurationSec}} sh -c \
      'GCLOUD_PROFILER_LOGLEVEL=5 GAE_SERVICE={{.Service}} node --trace-warnings build/src/busybench.js {{.DurationSec}} 1'
	) |& while read line; do echo "benchmark $i: ${line}"; done || [ "$?" -eq "124" ] &
done
echo "Successfully started {{.NumBackoffBenchmarks}} benchmarks."

wait

# Continue displaying commands being run.
set -x

echo "{{.FinishString}}"

{{ template "epilogue" . -}}
{{ end }}

`

type profileSummary struct {
	profileType  string
	functionName string
	sourceFile   string
}

type nodeGCETestCase struct {
	proftest.InstanceConfig
	name          string
	nodeVersion   string
	benchDuration time.Duration
	timeout       time.Duration

	backoffTest bool

	// wantProfileTypes will not be used when the test is a backoff integration
	// test.
	wantProfiles []profileSummary
}

func (tc *nodeGCETestCase) initializeStartUpScript(template *template.Template) error {
	params := struct {
		Service              string
		NodeVersion          string
		Repo                 string
		PR                   int
		Branch               string
		Commit               string
		FinishString         string
		ErrorString          string
		DurationSec          int
		NumBackoffBenchmarks int
	}{
		Service:      tc.name,
		NodeVersion:  tc.nodeVersion,
		Repo:         *repo,
		PR:           *pr,
		Branch:       *branch,
		Commit:       *commit,
		FinishString: benchFinishString,
		ErrorString:  errorString,
		DurationSec:  int(tc.benchDuration.Seconds()),
	}

	testTemplate := "integration"
	if tc.backoffTest {
		testTemplate = "integration_backoff"
		params.NumBackoffBenchmarks = numBackoffBenchmarks
	}

	var buf bytes.Buffer
	err := template.Lookup(testTemplate).Execute(&buf, params)
	if err != nil {
		return fmt.Errorf("failed to render startup script for %s: %v", tc.name, err)
	}
	tc.StartupScript = buf.String()
	return nil
}

func TestAgentIntegration(t *testing.T) {
	projectID := os.Getenv("GCLOUD_TESTS_NODEJS_PROJECT_ID")
	if projectID == "" {
		t.Fatalf("Getenv(GCLOUD_TESTS_NODEJS_PROJECT_ID) got empty string")
	}

	zone := os.Getenv("GCLOUD_TESTS_NODEJS_ZONE")
	if zone == "" {
		t.Fatalf("Getenv(GCLOUD_TESTS_NODEJS_ZONE) got empty string")
	}

	if *commit == "" {
		t.Fatal("commit flag is not set")
	}

	ctx := context.Background()

	client, err := google.DefaultClient(ctx, cloudScope)
	if err != nil {
		t.Fatalf("failed to get default client: %v", err)
	}

	computeService, err := compute.New(client)
	if err != nil {
		t.Fatalf("failed to initialize compute Service: %v", err)
	}

	template, err := proftest.BaseStartupTmpl.Parse(startupTemplate)
	if err != nil {
		t.Fatalf("failed to parse startup script template: %v", err)
	}

	gceTr := proftest.GCETestRunner{
		TestRunner: proftest.TestRunner{
			Client: client,
		},
		ComputeService: computeService,
	}

	wantProfiles := []profileSummary{
		{"WALL", "busyLoop", "busybench.ts"},
		{"HEAP", "benchmark", "busybench.ts"},
	}

	testcases := []nodeGCETestCase{
		{
			InstanceConfig: proftest.InstanceConfig{
				ProjectID:   projectID,
				Zone:        zone,
				Name:        fmt.Sprintf("profiler-test-node10-%s", runID),
				MachineType: "n1-standard-1",
			},
			name:          fmt.Sprintf("profiler-test-node10-%s-gce", runID),
			wantProfiles:  wantProfiles,
			nodeVersion:   "10",
			timeout:       gceTestTimeout,
			benchDuration: gceBenchDuration,
		},
		{
			InstanceConfig: proftest.InstanceConfig{
				ProjectID:   projectID,
				Zone:        zone,
				Name:        fmt.Sprintf("profiler-test-node12-%s", runID),
				MachineType: "n1-standard-1",
			},
			name:          fmt.Sprintf("profiler-test-node12-%s-gce", runID),
			wantProfiles:  wantProfiles,
			nodeVersion:   "12",
			timeout:       gceTestTimeout,
			benchDuration: gceBenchDuration,
		},
		{
			InstanceConfig: proftest.InstanceConfig{
				ProjectID:   projectID,
				Zone:        zone,
				Name:        fmt.Sprintf("profiler-test-node14-%s", runID),
				MachineType: "n1-standard-1",
			},
			name:          fmt.Sprintf("profiler-test-node14-%s-gce", runID),
			wantProfiles:  wantProfiles,
			nodeVersion:   "14",
			timeout:       gceTestTimeout,
			benchDuration: gceBenchDuration,
		},
	}

	if *runBackoffTest {
		testcases = append(testcases,
			nodeGCETestCase{
				InstanceConfig: proftest.InstanceConfig{
					ProjectID: projectID,
					Zone:      zone,
					Name:      fmt.Sprintf("profiler-backoff-test-node12-%s", runID),

					// Running many copies of the benchmark requires more
					// memory than is available on an n1-standard-1. Use a
					// machine type with more memory for backoff test.
					MachineType: "n1-highmem-2",
				},
				name:          fmt.Sprintf("profiler-backoff-test-node12-%s", runID),
				backoffTest:   true,
				nodeVersion:   "12",
				timeout:       backoffTestTimeout,
				benchDuration: backoffBenchDuration,
			})
	}

	// Allow test cases to run in parallel.
	runtime.GOMAXPROCS(len(testcases))

	for _, tc := range testcases {
		tc := tc // capture range variable
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			if err := tc.initializeStartUpScript(template); err != nil {
				t.Fatalf("failed to initialize startup script: %v", err)
			}

			err := gceTr.StartInstance(ctx, &tc.InstanceConfig)
			if err != nil {
				t.Fatalf("failed to start GCE instance: %v", err)
			}
			defer func() {
				if gceTr.DeleteInstance(ctx, &tc.InstanceConfig); err != nil {
					t.Fatal(err)
				}
			}()

			timeoutCtx, cancel := context.WithTimeout(ctx, tc.timeout)
			defer cancel()
			output, err := gceTr.PollAndLogSerialPort(timeoutCtx, &tc.InstanceConfig, benchFinishString, errorString, t.Logf)
			if err != nil {
				t.Fatal(err)
			}

			if tc.backoffTest {
				if err := proftest.CheckSerialOutputForBackoffs(output, numBackoffBenchmarks, "action throttled, backoff", "Attempting to create profile", "benchmark"); err != nil {
					t.Errorf("failed to check serial output for backoffs: %v", err)
				}
				return
			}

			timeNow := time.Now()
			endTime := timeNow.Format(time.RFC3339)
			startTime := timeNow.Add(-1 * time.Hour).Format(time.RFC3339)
			for _, wantProfile := range tc.wantProfiles {
				pr, err := gceTr.TestRunner.QueryProfilesWithZone(tc.ProjectID, tc.name, startTime, endTime, wantProfile.profileType, tc.Zone)
				if err != nil {
					t.Errorf("QueryProfiles(%s, %s, %s, %s, %s) got error: %v", tc.ProjectID, tc.name, startTime, endTime, wantProfile.profileType, err)
					continue
				}
				if wantProfile.sourceFile != "" {
					if err := pr.HasFunctionInFile(wantProfile.functionName, wantProfile.sourceFile); err != nil {
						t.Errorf("Function %s not found in source file %s in profiles of type %s: %v", wantProfile.functionName, wantProfile.sourceFile, wantProfile.profileType, err)
					}
					continue
				}
				if err := pr.HasFunction(wantProfile.functionName); err != nil {
					t.Errorf("Function %s not found in profiles of type %s: %v", wantProfile.functionName, wantProfile.profileType, err)
				}
			}
		})
	}
}
<|endoftext|>