<commit_before>package drouter

import (
	log "github.com/Sirupsen/logrus"
	"github.com/vishvananda/netlink"
	"github.com/ziutek/utils/netaddr"
	"net"
	"os"
)

type p2pNetwork struct {
	network       *net.IPNet
	hostIP        net.IP
	selfIP        net.IP
	hostNamespace *netlink.Handle
	hostUnderlay  *net.IPNet
}

func newP2PNetwork(p2paddr string) (*p2pNetwork, error) {
	hns, err := netlinkHandleFromPid(1)
	if err != nil {
		log.Error("Failed to get the host's namespace.")
		return nil, err
	}

	log.Debugf("Making a p2p network for: %v", p2paddr)

	//check if drouter_veth0 already exists
	host_link, err := hns.LinkByName("drouter_veth0")
	if err != nil {
		//doesn't exist, create it
		host_link_veth := &netlink.Veth{
			LinkAttrs: netlink.LinkAttrs{Name: "drouter_veth0"},
			PeerName:  "drouter_veth1",
		}
		err2 := hns.LinkAdd(host_link_veth)
		if err2 != nil {
			return nil, err2
		}
		host_link, err2 = hns.LinkByName("drouter_veth0")
		if err2 != nil {
			return nil, err2
		}
	}

	int_link, err := hns.LinkByName("drouter_veth1")
	if err != nil {
		return nil, err
	}
	err = hns.LinkSetNsPid(int_link, os.Getpid())
	if err != nil {
		return nil, err
	}
	int_link, err = netlink.LinkByName("drouter_veth1")
	if err != nil {
		return nil, err
	}

	_, p2pIPNet, err := net.ParseCIDR(p2paddr)
	if err != nil {
		log.Errorf("Failed to parse the CIDR string for p2p network: %v", p2paddr)
		return nil, err
	}

	host_addr := *p2pIPNet
	host_addr.IP = netaddr.IPAdd(host_addr.IP, 1)
	host_netlink_addr := &netlink.Addr{
		IPNet: &host_addr,
		Label: "",
	}
	err = hns.AddrAdd(host_link, host_netlink_addr)
	if err != nil {
		return nil, err
	}

	int_addr := *p2pIPNet
	int_addr.IP = netaddr.IPAdd(int_addr.IP, 2)
	int_netlink_addr := &netlink.Addr{
		IPNet: &int_addr,
		Label: "",
	}
	err = netlink.AddrAdd(int_link, int_netlink_addr)
	if err != nil {
		return nil, err
	}

	err = netlink.LinkSetUp(int_link)
	if err != nil {
		return nil, err
	}

	err = hns.LinkSetUp(host_link)
	if err != nil {
		return nil, err
	}

	//discover host underlay address/network
	hunderlay := &net.IPNet{}
	hroutes, err := hns.RouteList(nil, netlink.FAMILY_V4)
	if err != nil {
		return nil, err
	}
Hroutes:
	for _, r := range hroutes {
		if r.Gw != nil {
			continue
		}
		link, err := hns.LinkByIndex(r.LinkIndex)
		if err != nil {
			return nil, err
		}
		addrs, err := hns.AddrList(link, netlink.FAMILY_V4)
		if err != nil {
			return nil, err
		}
		for _, addr := range addrs {
			if !addr.IP.Equal(r.Src) {
				continue
			}
			hunderlay.IP = addr.IP
			hunderlay.Mask = addr.Mask
			break Hroutes
		}
	}

	log.Debugf("Discovered host underlay as: %v", hunderlay)

	staticRoutes = append(staticRoutes, networkID(hunderlay))

	hroute := &netlink.Route{
		LinkIndex: int_link.Attrs().Index,
		Dst:       networkID(hunderlay),
		Gw:        host_addr.IP,
	}

	log.Debug("Adding drouter route to %v via %v.", hroute.Dst, hroute.Gw)
	err = netlink.RouteAdd(hroute)
	if err != nil {
		return nil, err
	}

	p2pnet := &p2pNetwork{
		network:       p2pIPNet,
		hostIP:        host_addr.IP,
		selfIP:        int_addr.IP,
		hostNamespace: hns,
		hostUnderlay:  hunderlay,
	}

	for _, sr := range staticRoutes {
		if subnetContainsSubnet(hroute.Dst, sr) {
			log.Debugf("Skipping static route %v covered by host underlay: %v", sr, hroute.Dst)
			continue
		}
		go p2pnet.addHostRoute(sr)
	}

	return p2pnet, nil
}

func (p *p2pNetwork) remove() error {
	host_link, err := p.hostNamespace.LinkByName("drouter_veth0")
	if err != nil {
		return err
	}

	return p.hostNamespace.LinkDel(host_link)
}

func (p *p2pNetwork) addHostRoute(sn *net.IPNet) {
	route := &netlink.Route{
		Gw:  p.selfIP,
		Dst: sn,
		Src: p.hostUnderlay.IP,
	}
	if (route.Dst.IP.To4() == nil) != (route.Gw.To4() == nil) {
		// Dst is a different IP family
		return
	}

	log.Debugf("Injecting shortcut route to %v via drouter into host routing table.", sn)
	err := p.hostNamespace.RouteAdd(route)
	if err != nil {
		log.Error(err)
	}
}

func (p *p2pNetwork) delHostRoute(sn *net.IPNet) {
	route := &netlink.Route{
		Gw:  p.selfIP,
		Dst: sn,
	}
	if (route.Dst.IP.To4() == nil) != (route.Gw.To4() == nil) {
		// Dst is a different IP family
		return
	}

	log.Debugf("Removing shortcut route to %v via drouter from host routing table.", sn)
	err := p.hostNamespace.RouteDel(route)
	if err != nil {
		log.Error(err)
	}
}
<commit_msg>delete instead<commit_after>package drouter

import (
	log "github.com/Sirupsen/logrus"
	"github.com/vishvananda/netlink"
	"github.com/ziutek/utils/netaddr"
	"net"
	"os"
)

type p2pNetwork struct {
	network       *net.IPNet
	hostIP        net.IP
	selfIP        net.IP
	hostNamespace *netlink.Handle
	hostUnderlay  *net.IPNet
}

func newP2PNetwork(p2paddr string) (*p2pNetwork, error) {
	hns, err := netlinkHandleFromPid(1)
	if err != nil {
		log.Error("Failed to get the host's namespace.")
		return nil, err
	}

	log.Debugf("Making a p2p network for: %v", p2paddr)

	//check if drouter_veth0 already exists
	host_link, err := hns.LinkByName("drouter_veth0")
	if err == nil {
		//exists already... delete
		hns.LinkDel(host_link)
	}
	err = nil

	host_link_veth := &netlink.Veth{
		LinkAttrs: netlink.LinkAttrs{Name: "drouter_veth0"},
		PeerName:  "drouter_veth1",
	}
	err = hns.LinkAdd(host_link_veth)
	if err != nil {
		return nil, err
	}
	host_link, err = hns.LinkByName("drouter_veth0")
	if err != nil {
		return nil, err
	}

	int_link, err := hns.LinkByName("drouter_veth1")
	if err != nil {
		return nil, err
	}
	err = hns.LinkSetNsPid(int_link, os.Getpid())
	if err != nil {
		return nil, err
	}
	int_link, err = netlink.LinkByName("drouter_veth1")
	if err != nil {
		return nil, err
	}

	_, p2pIPNet, err := net.ParseCIDR(p2paddr)
	if err != nil {
		log.Errorf("Failed to parse the CIDR string for p2p network: %v", p2paddr)
		return nil, err
	}

	host_addr := *p2pIPNet
	host_addr.IP = netaddr.IPAdd(host_addr.IP, 1)
	host_netlink_addr := &netlink.Addr{
		IPNet: &host_addr,
		Label: "",
	}
	err = hns.AddrAdd(host_link, host_netlink_addr)
	if err != nil {
		return nil, err
	}

	int_addr := *p2pIPNet
	int_addr.IP = netaddr.IPAdd(int_addr.IP, 2)
	int_netlink_addr := &netlink.Addr{
		IPNet: &int_addr,
		Label: "",
	}
	err = netlink.AddrAdd(int_link, int_netlink_addr)
	if err != nil {
		return nil, err
	}

	err = netlink.LinkSetUp(int_link)
	if err != nil {
		return nil, err
	}

	err = hns.LinkSetUp(host_link)
	if err != nil {
		return nil, err
	}

	//discover host underlay address/network
	hunderlay := &net.IPNet{}
	hroutes, err := hns.RouteList(nil, netlink.FAMILY_V4)
	if err != nil {
		return nil, err
	}
Hroutes:
	for _, r := range hroutes {
		if r.Gw != nil {
			continue
		}
		link, err := hns.LinkByIndex(r.LinkIndex)
		if err != nil {
			return nil, err
		}
		addrs, err := hns.AddrList(link, netlink.FAMILY_V4)
		if err != nil {
			return nil, err
		}
		for _, addr := range addrs {
			if !addr.IP.Equal(r.Src) {
				continue
			}
			hunderlay.IP = addr.IP
			hunderlay.Mask = addr.Mask
			break Hroutes
		}
	}

	log.Debugf("Discovered host underlay as: %v", hunderlay)

	staticRoutes = append(staticRoutes, networkID(hunderlay))

	hroute := &netlink.Route{
		LinkIndex: int_link.Attrs().Index,
		Dst:       networkID(hunderlay),
		Gw:        host_addr.IP,
	}

	log.Debug("Adding drouter route to %v via %v.", hroute.Dst, hroute.Gw)
	err = netlink.RouteAdd(hroute)
	if err != nil {
		return nil, err
	}

	p2pnet := &p2pNetwork{
		network:       p2pIPNet,
		hostIP:        host_addr.IP,
		selfIP:        int_addr.IP,
		hostNamespace: hns,
		hostUnderlay:  hunderlay,
	}

	for _, sr := range staticRoutes {
		if subnetContainsSubnet(hroute.Dst, sr) {
			log.Debugf("Skipping static route %v covered by host underlay: %v", sr, hroute.Dst)
			continue
		}
		go p2pnet.addHostRoute(sr)
	}

	return p2pnet, nil
}

func (p *p2pNetwork) remove() error {
	host_link, err := p.hostNamespace.LinkByName("drouter_veth0")
	if err != nil {
		return err
	}

	return p.hostNamespace.LinkDel(host_link)
}

func (p *p2pNetwork) addHostRoute(sn *net.IPNet) {
	route := &netlink.Route{
		Gw:  p.selfIP,
		Dst: sn,
		Src: p.hostUnderlay.IP,
	}
	if (route.Dst.IP.To4() == nil) != (route.Gw.To4() == nil) {
		// Dst is a different IP family
		return
	}

	log.Debugf("Injecting shortcut route to %v via drouter into host routing table.", sn)
	err := p.hostNamespace.RouteAdd(route)
	if err != nil {
		log.Error(err)
	}
}

func (p *p2pNetwork) delHostRoute(sn *net.IPNet) {
	route := &netlink.Route{
		Gw:  p.selfIP,
		Dst: sn,
	}
	if (route.Dst.IP.To4() == nil) != (route.Gw.To4() == nil) {
		// Dst is a different IP family
		return
	}

	log.Debugf("Removing shortcut route to %v via drouter from host routing table.", sn)
	err := p.hostNamespace.RouteDel(route)
	if err != nil {
		log.Error(err)
	}
}
<|endoftext|>


<commit_before>// Copyright 2017, 2018 Authors of Cilium
//
// 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 envoy

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/cilium/cilium/pkg/flowdebug"
	"github.com/cilium/cilium/pkg/logging"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/metrics"

	"github.com/sirupsen/logrus"
	"gopkg.in/natefinch/lumberjack.v2"
)

var log = logging.DefaultLogger.WithField(logfields.LogSubsys, "envoy-manager")

var (
	// RequiredEnvoyVersionSHA is set during build
	// Running Envoy version will be checked against `RequiredEnvoyVersionSHA`.
	// By default cilium-agent will fail to start if there is a version mismatch.
	RequiredEnvoyVersionSHA string

	// envoyLevelMap maps logrus.Level values to Envoy (spdlog) log levels.
	envoyLevelMap = map[logrus.Level]string{
		logrus.PanicLevel: "off",
		logrus.FatalLevel: "critical",
		logrus.ErrorLevel: "error",
		logrus.WarnLevel:  "warning",
		logrus.InfoLevel:  "info",
		logrus.DebugLevel: "debug",
		// spdlog "trace" not mapped
	}

	tracing = false
)

const (
	adminSock   = "envoy-admin.sock"
	ciliumEnvoy = "cilium-envoy"
)

// EnableTracing changes Envoy log level to "trace", producing the most logs.
func EnableTracing() {
	tracing = true
}

func mapLogLevel(level logrus.Level) string {
	if tracing {
		return "trace"
	}

	// Suppress the debug level if not debugging at flow level.
	if level == logrus.DebugLevel && !flowdebug.Enabled() {
		level = logrus.InfoLevel
	}
	return envoyLevelMap[level]
}

type admin struct {
	adminURL string
	unixPath string
	level    string
}

func (a *admin) transact(query string) error {
	// Use a custom dialer to use a Unix domain socket for a HTTP connection.
	client := &http.Client{
		Transport: &http.Transport{
			Dial: func(_, _ string) (net.Conn, error) { return net.Dial("unix", a.unixPath) },
		},
	}

	resp, err := client.Post(a.adminURL+query, "", nil)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	ret := strings.Replace(string(body), "\r", "", -1)
	log.Debugf("Envoy: Admin response to %s: %s", query, ret)
	return nil
}

func (a *admin) changeLogLevel(level logrus.Level) error {
	envoyLevel := mapLogLevel(level)

	if envoyLevel == a.level {
		log.Debugf("Envoy: Log level is already set as: %v", envoyLevel)
		return nil
	}

	err := a.transact("logging?level=" + envoyLevel)
	if err != nil {
		log.WithError(err).Warnf("Envoy: Failed to set log level to: %v", envoyLevel)
	} else {
		a.level = envoyLevel
	}
	return err
}

func (a *admin) quit() error {
	return a.transact("quitquitquit")
}

// Envoy manages a running Envoy proxy instance via the
// ListenerDiscoveryService and RouteDiscoveryService gRPC APIs.
type Envoy struct {
	stopCh chan struct{}
	errCh  chan error
	admin  *admin
}

// GetEnvoyVersion returns the envoy binary version string
func GetEnvoyVersion() string {
	out, err := exec.Command(ciliumEnvoy, "--version").Output()
	if err != nil {
		log.WithError(err).Fatalf("Envoy: Binary %q cannot be executed", ciliumEnvoy)
	}
	return strings.TrimSpace(string(out))
}

// StartEnvoy starts an Envoy proxy instance.
func StartEnvoy(stateDir, logPath string, baseID uint64) *Envoy {
	bootstrapPath := filepath.Join(stateDir, "bootstrap.pb")
	xdsPath := getXDSPath(stateDir)

	// Have to use a fake IP address:port even when we Dial to a Unix domain socket.
	// The address:port will be visible to Envoy as ':authority', but its value is
	// not meaningful.
	// Not using the normal localhost address to make it obvious that we are not
	// connecting to Envoy's admin interface via the IP stack.
	adminAddress := "192.0.2.34:56"
	adminPath := filepath.Join(stateDir, adminSock)

	e := &Envoy{
		stopCh: make(chan struct{}),
		errCh:  make(chan error, 1),
		admin: &admin{
			adminURL: "http://" + adminAddress + "/",
			unixPath: adminPath,
		},
	}

	// Use the same structure as Istio's pilot-agent for the node ID:
	// nodeType~ipAddress~proxyId~domain
	nodeId := "host~127.0.0.1~no-id~localdomain"

	// Create static configuration
	createBootstrap(bootstrapPath, nodeId, ingressClusterName,
		xdsPath, egressClusterName, ingressClusterName, adminPath)

	log.Debugf("Envoy: Starting: %v", *e)

	// make it a buffered channel so we can not only
	// read the written value but also skip it in
	// case no one reader reads it.
	started := make(chan bool, 1)
	go func() {
		var logWriter io.WriteCloser
		var logFormat string
		if logPath != "" {
			// Use the Envoy default log format when logging to a separate file
			logFormat = "[%Y-%m-%d %T.%e][%t][%l][%n] %v"
			logger := &lumberjack.Logger{
				Filename:   logPath,
				MaxSize:    100, // megabytes
				MaxBackups: 3,
				MaxAge:     28,   //days
				Compress:   true, // disabled by default
			}
			logWriter = logger
		} else {
			// Use log format that looks like Cilium logs when integrating logs
			// The logs will be reported as coming from the cilium-agent, so
			// we add the thread id to be able to differentiate between Envoy's
			// main and worker threads.
			logFormat = "%t|%l|%n|%v"

			// Create a piper that parses and writes into logrus the log
			// messages from Envoy.
			logWriter = newEnvoyLogPiper()
		}
		defer logWriter.Close()

		for {
			logLevel := logging.GetLevel(logging.DefaultLogger)
			cmd := exec.Command(ciliumEnvoy, "-l", mapLogLevel(logLevel), "-c", bootstrapPath, "--base-id", strconv.FormatUint(baseID, 10), "--log-format", logFormat)
			cmd.Stderr = logWriter
			cmd.Stdout = logWriter

			if err := cmd.Start(); err != nil {
				log.WithError(err).Warn("Envoy: Failed to start proxy")
				select {
				case started <- false:
				default:
				}
				return
			}
			log.Debugf("Envoy: Started proxy")
			select {
			case started <- true:
			default:
			}

			log.Infof("Envoy: Proxy started with pid %d", cmd.Process.Pid)
			metrics.SubprocessStart.WithLabelValues(ciliumEnvoy).Inc()

			// We do not return after a successful start, but watch the Envoy process
			// and restart it if it crashes.
			// Waiting for the process execution is done in the goroutime.
			// The purpose of the "crash channel" is to inform the loop about their
			// Envoy process crash - after closing that channel by the goroutime,
			// the loop continues, the channel is recreated and the new process
			// is watched again.
			crashCh := make(chan struct{})
			go func() {
				if err := cmd.Wait(); err != nil {
					log.WithError(err).Warn("Envoy: Proxy crashed")
				}
				close(crashCh)
			}()

			// start again after a short wait. If Cilium exits this should be enough
			// time to not start Envoy again in that case.
			log.Info("Envoy: Sleeping for 100ms before restarting proxy")
			time.Sleep(100 * time.Millisecond)

			select {
			case <-crashCh:
				// Start Envoy again
				continue
			case <-e.stopCh:
				log.Infof("Envoy: Stopping proxy with pid %d", cmd.Process.Pid)
				if err := e.admin.quit(); err != nil {
					log.WithError(err).Fatalf("Envoy: Envoy admin quit failed, killing process with pid %d", cmd.Process.Pid)

					if err := cmd.Process.Kill(); err != nil {
						log.WithError(err).Fatal("Envoy: Stopping Envoy failed")
						e.errCh <- err
					}
				}
				close(e.errCh)
				return
			}
		}
	}()

	if <-started {
		return e
	}

	return nil
}

// newEnvoyLogPiper creates a writer that parses and logs log messages written by Envoy.
func newEnvoyLogPiper() io.WriteCloser {
	reader, writer := io.Pipe()
	scanner := bufio.NewScanner(reader)
	scanner.Buffer(nil, 1024*1024)
	go func() {
		scopedLog := log.WithFields(logrus.Fields{
			logfields.LogSubsys: "unknown",
			logfields.ThreadID:  "unknown",
		})
		level := "debug"

		for scanner.Scan() {
			line := scanner.Text()
			var msg string

			parts := strings.SplitN(line, "|", 4)
			// Parse the line as a log message written by Envoy, assuming it
			// uses the configured format: "%t|%l|%n|%v".
			if len(parts) == 4 {
				threadID := parts[0]
				level = parts[1]
				loggerName := parts[2]
				// TODO: Parse msg to extract the source filename, line number, etc.
				msg = fmt.Sprintf("[%s", parts[3])

				scopedLog = log.WithFields(logrus.Fields{
					logfields.LogSubsys: fmt.Sprintf("envoy-%s", loggerName),
					logfields.ThreadID:  threadID,
				})
			} else {
				// If this line can't be parsed, it continues a multi-line log
				// message. In this case, log it at the same level and with the
				// same fields as the previous line.
				msg = line
			}

			if len(msg) == 0 {
				continue
			}

			// Map the Envoy log level to a logrus level.
			switch level {
			case "off", "critical", "error":
				scopedLog.Error(msg)
			case "warning":
				scopedLog.Warn(msg)
			case "info":
				scopedLog.Info(msg)
			case "debug", "trace":
				scopedLog.Debug(msg)
			default:
				scopedLog.Debug(msg)
			}
		}
		if err := scanner.Err(); err != nil {
			log.WithError(err).Error("Error while parsing Envoy logs")
		}
		reader.Close()
	}()
	return writer
}

// isEOF returns true if the error message ends in "EOF". ReadMsgUnix returns extra info in the beginning.
func isEOF(err error) bool {
	strerr := err.Error()
	errlen := len(strerr)
	return errlen >= 3 && strerr[errlen-3:] == io.EOF.Error()
}

// StopEnvoy kills the Envoy process started with StartEnvoy. The gRPC API streams are terminated
// first.
func (e *Envoy) StopEnvoy() error {
	close(e.stopCh)
	err, ok := <-e.errCh
	if ok {
		return err
	}
	return nil
}

// ChangeLogLevel changes Envoy log level to correspond to the logrus log level 'level'.
func (e *Envoy) ChangeLogLevel(level logrus.Level) {
	e.admin.changeLogLevel(level)
}
<commit_msg>envoy: Silently discard expected warnings if flowdebug is not enabled<commit_after>// Copyright 2017, 2018 Authors of Cilium
//
// 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 envoy

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/cilium/cilium/pkg/flowdebug"
	"github.com/cilium/cilium/pkg/logging"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/metrics"

	"github.com/sirupsen/logrus"
	"gopkg.in/natefinch/lumberjack.v2"
)

var log = logging.DefaultLogger.WithField(logfields.LogSubsys, "envoy-manager")

var (
	// RequiredEnvoyVersionSHA is set during build
	// Running Envoy version will be checked against `RequiredEnvoyVersionSHA`.
	// By default cilium-agent will fail to start if there is a version mismatch.
	RequiredEnvoyVersionSHA string

	// envoyLevelMap maps logrus.Level values to Envoy (spdlog) log levels.
	envoyLevelMap = map[logrus.Level]string{
		logrus.PanicLevel: "off",
		logrus.FatalLevel: "critical",
		logrus.ErrorLevel: "error",
		logrus.WarnLevel:  "warning",
		logrus.InfoLevel:  "info",
		logrus.DebugLevel: "debug",
		// spdlog "trace" not mapped
	}

	tracing = false
)

const (
	adminSock   = "envoy-admin.sock"
	ciliumEnvoy = "cilium-envoy"
)

// EnableTracing changes Envoy log level to "trace", producing the most logs.
func EnableTracing() {
	tracing = true
}

func mapLogLevel(level logrus.Level) string {
	if tracing {
		return "trace"
	}

	// Suppress the debug level if not debugging at flow level.
	if level == logrus.DebugLevel && !flowdebug.Enabled() {
		level = logrus.InfoLevel
	}
	return envoyLevelMap[level]
}

type admin struct {
	adminURL string
	unixPath string
	level    string
}

func (a *admin) transact(query string) error {
	// Use a custom dialer to use a Unix domain socket for a HTTP connection.
	client := &http.Client{
		Transport: &http.Transport{
			Dial: func(_, _ string) (net.Conn, error) { return net.Dial("unix", a.unixPath) },
		},
	}

	resp, err := client.Post(a.adminURL+query, "", nil)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	ret := strings.Replace(string(body), "\r", "", -1)
	log.Debugf("Envoy: Admin response to %s: %s", query, ret)
	return nil
}

func (a *admin) changeLogLevel(level logrus.Level) error {
	envoyLevel := mapLogLevel(level)

	if envoyLevel == a.level {
		log.Debugf("Envoy: Log level is already set as: %v", envoyLevel)
		return nil
	}

	err := a.transact("logging?level=" + envoyLevel)
	if err != nil {
		log.WithError(err).Warnf("Envoy: Failed to set log level to: %v", envoyLevel)
	} else {
		a.level = envoyLevel
	}
	return err
}

func (a *admin) quit() error {
	return a.transact("quitquitquit")
}

// Envoy manages a running Envoy proxy instance via the
// ListenerDiscoveryService and RouteDiscoveryService gRPC APIs.
type Envoy struct {
	stopCh chan struct{}
	errCh  chan error
	admin  *admin
}

// GetEnvoyVersion returns the envoy binary version string
func GetEnvoyVersion() string {
	out, err := exec.Command(ciliumEnvoy, "--version").Output()
	if err != nil {
		log.WithError(err).Fatalf("Envoy: Binary %q cannot be executed", ciliumEnvoy)
	}
	return strings.TrimSpace(string(out))
}

// StartEnvoy starts an Envoy proxy instance.
func StartEnvoy(stateDir, logPath string, baseID uint64) *Envoy {
	bootstrapPath := filepath.Join(stateDir, "bootstrap.pb")
	xdsPath := getXDSPath(stateDir)

	// Have to use a fake IP address:port even when we Dial to a Unix domain socket.
	// The address:port will be visible to Envoy as ':authority', but its value is
	// not meaningful.
	// Not using the normal localhost address to make it obvious that we are not
	// connecting to Envoy's admin interface via the IP stack.
	adminAddress := "192.0.2.34:56"
	adminPath := filepath.Join(stateDir, adminSock)

	e := &Envoy{
		stopCh: make(chan struct{}),
		errCh:  make(chan error, 1),
		admin: &admin{
			adminURL: "http://" + adminAddress + "/",
			unixPath: adminPath,
		},
	}

	// Use the same structure as Istio's pilot-agent for the node ID:
	// nodeType~ipAddress~proxyId~domain
	nodeId := "host~127.0.0.1~no-id~localdomain"

	// Create static configuration
	createBootstrap(bootstrapPath, nodeId, ingressClusterName,
		xdsPath, egressClusterName, ingressClusterName, adminPath)

	log.Debugf("Envoy: Starting: %v", *e)

	// make it a buffered channel so we can not only
	// read the written value but also skip it in
	// case no one reader reads it.
	started := make(chan bool, 1)
	go func() {
		var logWriter io.WriteCloser
		var logFormat string
		if logPath != "" {
			// Use the Envoy default log format when logging to a separate file
			logFormat = "[%Y-%m-%d %T.%e][%t][%l][%n] %v"
			logger := &lumberjack.Logger{
				Filename:   logPath,
				MaxSize:    100, // megabytes
				MaxBackups: 3,
				MaxAge:     28,   //days
				Compress:   true, // disabled by default
			}
			logWriter = logger
		} else {
			// Use log format that looks like Cilium logs when integrating logs
			// The logs will be reported as coming from the cilium-agent, so
			// we add the thread id to be able to differentiate between Envoy's
			// main and worker threads.
			logFormat = "%t|%l|%n|%v"

			// Create a piper that parses and writes into logrus the log
			// messages from Envoy.
			logWriter = newEnvoyLogPiper()
		}
		defer logWriter.Close()

		for {
			logLevel := logging.GetLevel(logging.DefaultLogger)
			cmd := exec.Command(ciliumEnvoy, "-l", mapLogLevel(logLevel), "-c", bootstrapPath, "--base-id", strconv.FormatUint(baseID, 10), "--log-format", logFormat)
			cmd.Stderr = logWriter
			cmd.Stdout = logWriter

			if err := cmd.Start(); err != nil {
				log.WithError(err).Warn("Envoy: Failed to start proxy")
				select {
				case started <- false:
				default:
				}
				return
			}
			log.Debugf("Envoy: Started proxy")
			select {
			case started <- true:
			default:
			}

			log.Infof("Envoy: Proxy started with pid %d", cmd.Process.Pid)
			metrics.SubprocessStart.WithLabelValues(ciliumEnvoy).Inc()

			// We do not return after a successful start, but watch the Envoy process
			// and restart it if it crashes.
			// Waiting for the process execution is done in the goroutime.
			// The purpose of the "crash channel" is to inform the loop about their
			// Envoy process crash - after closing that channel by the goroutime,
			// the loop continues, the channel is recreated and the new process
			// is watched again.
			crashCh := make(chan struct{})
			go func() {
				if err := cmd.Wait(); err != nil {
					log.WithError(err).Warn("Envoy: Proxy crashed")
				}
				close(crashCh)
			}()

			// start again after a short wait. If Cilium exits this should be enough
			// time to not start Envoy again in that case.
			log.Info("Envoy: Sleeping for 100ms before restarting proxy")
			time.Sleep(100 * time.Millisecond)

			select {
			case <-crashCh:
				// Start Envoy again
				continue
			case <-e.stopCh:
				log.Infof("Envoy: Stopping proxy with pid %d", cmd.Process.Pid)
				if err := e.admin.quit(); err != nil {
					log.WithError(err).Fatalf("Envoy: Envoy admin quit failed, killing process with pid %d", cmd.Process.Pid)

					if err := cmd.Process.Kill(); err != nil {
						log.WithError(err).Fatal("Envoy: Stopping Envoy failed")
						e.errCh <- err
					}
				}
				close(e.errCh)
				return
			}
		}
	}()

	if <-started {
		return e
	}

	return nil
}

// newEnvoyLogPiper creates a writer that parses and logs log messages written by Envoy.
func newEnvoyLogPiper() io.WriteCloser {
	reader, writer := io.Pipe()
	scanner := bufio.NewScanner(reader)
	scanner.Buffer(nil, 1024*1024)
	go func() {
		scopedLog := log.WithFields(logrus.Fields{
			logfields.LogSubsys: "unknown",
			logfields.ThreadID:  "unknown",
		})
		level := "debug"

		for scanner.Scan() {
			line := scanner.Text()
			var msg string

			parts := strings.SplitN(line, "|", 4)
			// Parse the line as a log message written by Envoy, assuming it
			// uses the configured format: "%t|%l|%n|%v".
			if len(parts) == 4 {
				threadID := parts[0]
				level = parts[1]
				loggerName := parts[2]
				// TODO: Parse msg to extract the source filename, line number, etc.
				msg = fmt.Sprintf("[%s", parts[3])

				scopedLog = log.WithFields(logrus.Fields{
					logfields.LogSubsys: fmt.Sprintf("envoy-%s", loggerName),
					logfields.ThreadID:  threadID,
				})
			} else {
				// If this line can't be parsed, it continues a multi-line log
				// message. In this case, log it at the same level and with the
				// same fields as the previous line.
				msg = line
			}

			if len(msg) == 0 {
				continue
			}

			// Map the Envoy log level to a logrus level.
			switch level {
			case "off", "critical", "error":
				scopedLog.Error(msg)
			case "warning":
				// Silently drop expected warnings if flowdebug is not enabled
				// TODO: Remove this special case when https://github.com/envoyproxy/envoy/issues/13504 is fixed.
				if !flowdebug.Enabled() && strings.Contains(msg, "Unable to use runtime singleton for feature envoy.http.headermap.lazy_map_min_size") {
					continue
				}
				scopedLog.Warn(msg)
			case "info":
				scopedLog.Info(msg)
			case "debug", "trace":
				scopedLog.Debug(msg)
			default:
				scopedLog.Debug(msg)
			}
		}
		if err := scanner.Err(); err != nil {
			log.WithError(err).Error("Error while parsing Envoy logs")
		}
		reader.Close()
	}()
	return writer
}

// isEOF returns true if the error message ends in "EOF". ReadMsgUnix returns extra info in the beginning.
func isEOF(err error) bool {
	strerr := err.Error()
	errlen := len(strerr)
	return errlen >= 3 && strerr[errlen-3:] == io.EOF.Error()
}

// StopEnvoy kills the Envoy process started with StartEnvoy. The gRPC API streams are terminated
// first.
func (e *Envoy) StopEnvoy() error {
	close(e.stopCh)
	err, ok := <-e.errCh
	if ok {
		return err
	}
	return nil
}

// ChangeLogLevel changes Envoy log level to correspond to the logrus log level 'level'.
func (e *Envoy) ChangeLogLevel(level logrus.Level) {
	e.admin.changeLogLevel(level)
}
<|endoftext|>


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

package gcrane

import (
	"context"
	"fmt"
	"log"
	"strings"

	"github.com/google/go-containerregistry/pkg/authn"
	"github.com/google/go-containerregistry/pkg/logs"
	"github.com/google/go-containerregistry/pkg/name"
	"github.com/google/go-containerregistry/pkg/v1/google"
	"github.com/google/go-containerregistry/pkg/v1/remote"
	"github.com/google/go-containerregistry/pkg/v1/types"
	"github.com/spf13/cobra"
	"golang.org/x/sync/errgroup"
)

func init() { Root.AddCommand(NewCmdCopy()) }

// NewCmdCopy creates a new cobra.Command for the copy subcommand.
func NewCmdCopy() *cobra.Command {
	recursive := false
	cmd := &cobra.Command{
		Use:     "copy",
		Aliases: []string{"cp"},
		Short:   "Efficiently copy a remote image from src to dst",
		Args:    cobra.ExactArgs(2),
		Run: func(cc *cobra.Command, args []string) {
			doCopy(args, recursive)
		},
	}

	cmd.Flags().BoolVarP(&recursive, "recursive", "r", false, "Whether to recurse through repos")

	return cmd
}

func doCopy(args []string, recursive bool) {
	src, dst := args[0], args[1]

	if recursive {
		if err := recursiveCopy(src, dst); err != nil {
			log.Fatalf("failed to copy images: %v", err)
		}
	} else {
		srcAuth, dstAuth, err := parseRefAuths(src, dst)
		if err != nil {
			log.Fatal(err)
		}

		// First, try to copy as an index.
		// If that fails, try to copy as an image.
		// We have to try this second because fallback logic exists in the registry
		// to convert an index to an image.
		//
		// TODO(#407): Refactor crane so we can just call into that logic in the
		// single-image case.
		if err := copyIndex(src, dst, srcAuth, dstAuth); err != nil {
			if err := copyImage(src, dst, srcAuth, dstAuth); err != nil {
				log.Fatalf("failed to copy image: %v", err)
			}
		}
	}
}

type copier struct {
	srcRepo name.Repository
	dstRepo name.Repository

	srcAuth authn.Authenticator
	dstAuth authn.Authenticator
}

func newCopier(src, dst string) (*copier, error) {
	srcRepo, err := name.NewRepository(src)
	if err != nil {
		return nil, fmt.Errorf("parsing repo %q: %v", src, err)
	}

	dstRepo, err := name.NewRepository(dst)
	if err != nil {
		return nil, fmt.Errorf("parsing repo %q: %v", dst, err)
	}

	srcAuth, err := google.Keychain.Resolve(srcRepo.Registry)
	if err != nil {
		return nil, fmt.Errorf("getting auth for %q: %v", src, err)
	}

	dstAuth, err := google.Keychain.Resolve(dstRepo.Registry)
	if err != nil {
		return nil, fmt.Errorf("getting auth for %q: %v", dst, err)
	}

	return &copier{srcRepo, dstRepo, srcAuth, dstAuth}, nil
}

func copyImage(src, dst string, srcAuth, dstAuth authn.Authenticator) error {
	srcRef, err := name.ParseReference(src)
	if err != nil {
		return fmt.Errorf("parsing reference %q: %v", src, err)
	}

	dstRef, err := name.ParseReference(dst)
	if err != nil {
		return fmt.Errorf("parsing reference %q: %v", dst, err)
	}

	img, err := remote.Image(srcRef, remote.WithAuth(srcAuth))
	if err != nil {
		return fmt.Errorf("reading image %q: %v", src, err)
	}

	if err := remote.Write(dstRef, img, remote.WithAuth(dstAuth)); err != nil {
		return fmt.Errorf("writing image %q: %v", dst, err)
	}

	return nil
}

func copyIndex(src, dst string, srcAuth, dstAuth authn.Authenticator) error {
	srcRef, err := name.ParseReference(src)
	if err != nil {
		return fmt.Errorf("parsing reference %q: %v", src, err)
	}

	dstRef, err := name.ParseReference(dst)
	if err != nil {
		return fmt.Errorf("parsing reference %q: %v", dst, err)
	}

	idx, err := remote.Index(srcRef, remote.WithAuth(srcAuth))
	if err != nil {
		return fmt.Errorf("reading image %q: %v", src, err)
	}

	if err := remote.WriteIndex(dstRef, idx, remote.WithAuth(dstAuth)); err != nil {
		return fmt.Errorf("writing image %q: %v", dst, err)
	}

	return nil
}

// recursiveCopy copies images from repo src to repo dst, rather quickly. tl;dr:
//
//  for each repo in src {
//		go func {
//			for each image in repo {
//				go func {
//					for each tag in image {
//						go func {
//							copyImage(tag, rename(tag, dst))
//						}
//					}
//				}
//			}
//			for each index in repo {
//				go func {
//					for each tag in index {
//						go func {
//							copyIndex(tag, rename(tag, dst))
//						}
//					}
//				}
//			}
//		}
//	}
func recursiveCopy(src, dst string) error {
	c, err := newCopier(src, dst)
	if err != nil {
		return err
	}

	g, ctx := errgroup.WithContext(context.Background())

	// Captures c, g, and ctx.
	walkFn := func(repo name.Repository, tags *google.Tags, err error) error {
		if err != nil {
			return fmt.Errorf("failed walkFn for repo %s: %v", repo, err)
		}

		g.Go(func() error {
			return c.copyRepo(ctx, repo, tags)
		})

		return nil
	}

	if err := google.Walk(c.srcRepo, walkFn, google.WithAuth(c.srcAuth)); err != nil {
		return fmt.Errorf("failed to Walk: %v", err)
	}

	return g.Wait()
}

// copyRepo figures out the name for our destination repo (newRepo), lists the
// contents of newRepo, calculates the diff of what needs to be copied, then
// starts a goroutine to copy each image we need, and waits for them to finish.
func (c *copier) copyRepo(ctx context.Context, oldRepo name.Repository, tags *google.Tags) error {
	newRepo, err := c.rename(oldRepo)
	if err != nil {
		return fmt.Errorf("rename failed: %v", err)
	}

	// Figure out what we actually need to copy.
	want := tags.Manifests
	have := make(map[string]google.ManifestInfo)
	haveTags, err := google.List(newRepo, google.WithAuth(c.dstAuth))
	if err != nil {
		// Possibly, we could see a 404.  If we get an error here, log it and assume
		// we just need to copy everything.
		//
		// TODO: refactor remote.Error to expose response code?
		logs.Warn.Printf("failed to list %s: %v", newRepo, err)
	} else {
		have = haveTags.Manifests
	}
	need := diffImages(want, have)

	g, ctx := errgroup.WithContext(ctx)

	// First go through copying just manifests, skipping manifest lists, since
	// manifest lists might reference them.
	todos := make(map[string]google.ManifestInfo)
	for digest, manifest := range need {
		if manifest.MediaType == string(types.DockerManifestList) || manifest.MediaType == string(types.OCIImageIndex) {
			todos[digest] = manifest
			continue
		}

		digest, manifest := digest, manifest // https://golang.org/doc/faq#closures_and_goroutines
		g.Go(func() error {
			return c.copyImages(ctx, digest, manifest, oldRepo, newRepo)
		})
	}

	if err := g.Wait(); err != nil {
		return fmt.Errorf("Failed to copy %s: %v", oldRepo, err)
	}

	// Now copy the manifest lists, since it should be safe.
	for digest, manifest := range todos {
		digest, manifest := digest, manifest // https://golang.org/doc/faq#closures_and_goroutines
		g.Go(func() error {
			return c.copyIndexes(ctx, digest, manifest, oldRepo, newRepo)
		})
	}

	if err := g.Wait(); err != nil {
		return fmt.Errorf("Failed to copy %s: %v", oldRepo, err)
	}

	return nil
}

// copyImages starts a goroutine for each tag that points to the image
// oldRepo@digest, or just copies the image by digest if there are no tags.
func (c *copier) copyImages(ctx context.Context, digest string, manifest google.ManifestInfo, oldRepo, newRepo name.Repository) error {
	// We only have to explicitly copy by digest if there are no tags pointing to this manifest.
	if len(manifest.Tags) == 0 {
		srcImg := fmt.Sprintf("%s@%s", oldRepo, digest)
		dstImg := fmt.Sprintf("%s@%s", newRepo, digest)

		return copyImage(srcImg, dstImg, c.srcAuth, c.dstAuth)
	}

	// Copy all the tags.
	g, _ := errgroup.WithContext(ctx)
	for _, tag := range manifest.Tags {
		tag := tag // https://golang.org/doc/faq#closures_and_goroutines
		g.Go(func() error {
			srcImg := fmt.Sprintf("%s:%s", oldRepo, tag)
			dstImg := fmt.Sprintf("%s:%s", newRepo, tag)

			return copyImage(srcImg, dstImg, c.srcAuth, c.dstAuth)
		})
	}
	return g.Wait()
}

// copyIndexes starts a goroutine for each tag that points to the index
// oldRepo@digest, or just copies the index by digest if there are no tags.
func (c *copier) copyIndexes(ctx context.Context, digest string, manifest google.ManifestInfo, oldRepo, newRepo name.Repository) error {
	// We only have to explicitly copy by digest if there are no tags pointing to this manifest.
	if len(manifest.Tags) == 0 {
		srcImg := fmt.Sprintf("%s@%s", oldRepo, digest)
		dstImg := fmt.Sprintf("%s@%s", newRepo, digest)

		return copyIndex(srcImg, dstImg, c.srcAuth, c.dstAuth)
	}

	// Copy all the tags.
	g, _ := errgroup.WithContext(ctx)
	for _, tag := range manifest.Tags {
		tag := tag // https://golang.org/doc/faq#closures_and_goroutines
		g.Go(func() error {
			srcImg := fmt.Sprintf("%s:%s", oldRepo, tag)
			dstImg := fmt.Sprintf("%s:%s", newRepo, tag)

			// TODO: Just implement an AddTag thing.
			return copyIndex(srcImg, dstImg, c.srcAuth, c.dstAuth)
		})
	}
	return g.Wait()
}

// rename figures out the name of the new repository to copy to, e.g.:
//
// $ gcrane cp -r gcr.io/foo gcr.io/baz
//
// rename("gcr.io/foo/bar") == "gcr.io/baz/bar"
func (c *copier) rename(repo name.Repository) (name.Repository, error) {
	replaced := strings.Replace(repo.String(), c.srcRepo.String(), c.dstRepo.String(), 1)
	return name.NewRepository(replaced, name.StrictValidation)
}

// diffImages returns a map of digests to google.ManifestInfos for images or
// tags that are present in "want" but not in "have".
func diffImages(want, have map[string]google.ManifestInfo) map[string]google.ManifestInfo {
	need := make(map[string]google.ManifestInfo)

	for digest, wantManifest := range want {
		if haveManifest, ok := have[digest]; !ok {
			// Missing the whole image, we need to copy everything.
			need[digest] = wantManifest
		} else {
			missingTags := subtractStringLists(wantManifest.Tags, haveManifest.Tags)
			if len(missingTags) == 0 {
				continue
			}

			// Missing just some tags, add the ones we need to copy.
			todo := wantManifest
			todo.Tags = missingTags
			need[digest] = todo
		}
	}

	return need
}

// subtractStringLists returns a list of strings that are in minuend and not
// in subtrahend; order is unimportant.
func subtractStringLists(minuend, subtrahend []string) []string {
	bSet := toStringSet(subtrahend)
	difference := []string{}

	for _, a := range minuend {
		if _, ok := bSet[a]; !ok {
			difference = append(difference, a)
		}
	}

	return difference
}

func toStringSet(slice []string) map[string]struct{} {
	set := make(map[string]struct{}, len(slice))
	for _, s := range slice {
		set[s] = struct{}{}
	}
	return set
}

func parseRefAuths(src, dst string) (authn.Authenticator, authn.Authenticator, error) {
	srcRef, err := name.ParseReference(src)
	if err != nil {
		return nil, nil, fmt.Errorf("parsing reference %q: %v", src, err)
	}

	dstRef, err := name.ParseReference(dst)
	if err != nil {
		return nil, nil, fmt.Errorf("parsing reference %q: %v", dst, err)
	}

	srcAuth, err := authn.DefaultKeychain.Resolve(srcRef.Context().Registry)
	if err != nil {
		return nil, nil, fmt.Errorf("getting auth for %q: %v", src, err)
	}

	dstAuth, err := authn.DefaultKeychain.Resolve(dstRef.Context().Registry)
	if err != nil {
		return nil, nil, fmt.Errorf("getting auth for %q: %v", dst, err)
	}

	return srcAuth, dstAuth, nil
}
<commit_msg>gcrane: Modify copy to error if it gets a non 404 error code. (#499)<commit_after>// Copyright 2018 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gcrane

import (
	"context"
	"fmt"
	"log"
	"strings"

	"github.com/google/go-containerregistry/pkg/authn"
	"github.com/google/go-containerregistry/pkg/logs"
	"github.com/google/go-containerregistry/pkg/name"
	"github.com/google/go-containerregistry/pkg/v1/google"
	"github.com/google/go-containerregistry/pkg/v1/remote"
	"github.com/google/go-containerregistry/pkg/v1/remote/transport"
	"github.com/google/go-containerregistry/pkg/v1/types"
	"github.com/spf13/cobra"
	"golang.org/x/sync/errgroup"
)

func init() { Root.AddCommand(NewCmdCopy()) }

// NewCmdCopy creates a new cobra.Command for the copy subcommand.
func NewCmdCopy() *cobra.Command {
	recursive := false
	cmd := &cobra.Command{
		Use:     "copy",
		Aliases: []string{"cp"},
		Short:   "Efficiently copy a remote image from src to dst",
		Args:    cobra.ExactArgs(2),
		Run: func(cc *cobra.Command, args []string) {
			doCopy(args, recursive)
		},
	}

	cmd.Flags().BoolVarP(&recursive, "recursive", "r", false, "Whether to recurse through repos")

	return cmd
}

func doCopy(args []string, recursive bool) {
	src, dst := args[0], args[1]

	if recursive {
		if err := recursiveCopy(src, dst); err != nil {
			log.Fatalf("failed to copy images: %v", err)
		}
	} else {
		srcAuth, dstAuth, err := parseRefAuths(src, dst)
		if err != nil {
			log.Fatal(err)
		}

		// First, try to copy as an index.
		// If that fails, try to copy as an image.
		// We have to try this second because fallback logic exists in the registry
		// to convert an index to an image.
		//
		// TODO(#407): Refactor crane so we can just call into that logic in the
		// single-image case.
		if err := copyIndex(src, dst, srcAuth, dstAuth); err != nil {
			if err := copyImage(src, dst, srcAuth, dstAuth); err != nil {
				log.Fatalf("failed to copy image: %v", err)
			}
		}
	}
}

type copier struct {
	srcRepo name.Repository
	dstRepo name.Repository

	srcAuth authn.Authenticator
	dstAuth authn.Authenticator
}

func newCopier(src, dst string) (*copier, error) {
	srcRepo, err := name.NewRepository(src)
	if err != nil {
		return nil, fmt.Errorf("parsing repo %q: %v", src, err)
	}

	dstRepo, err := name.NewRepository(dst)
	if err != nil {
		return nil, fmt.Errorf("parsing repo %q: %v", dst, err)
	}

	srcAuth, err := google.Keychain.Resolve(srcRepo.Registry)
	if err != nil {
		return nil, fmt.Errorf("getting auth for %q: %v", src, err)
	}

	dstAuth, err := google.Keychain.Resolve(dstRepo.Registry)
	if err != nil {
		return nil, fmt.Errorf("getting auth for %q: %v", dst, err)
	}

	return &copier{srcRepo, dstRepo, srcAuth, dstAuth}, nil
}

func copyImage(src, dst string, srcAuth, dstAuth authn.Authenticator) error {
	srcRef, err := name.ParseReference(src)
	if err != nil {
		return fmt.Errorf("parsing reference %q: %v", src, err)
	}

	dstRef, err := name.ParseReference(dst)
	if err != nil {
		return fmt.Errorf("parsing reference %q: %v", dst, err)
	}

	img, err := remote.Image(srcRef, remote.WithAuth(srcAuth))
	if err != nil {
		return fmt.Errorf("reading image %q: %v", src, err)
	}

	if err := remote.Write(dstRef, img, remote.WithAuth(dstAuth)); err != nil {
		return fmt.Errorf("writing image %q: %v", dst, err)
	}

	return nil
}

func copyIndex(src, dst string, srcAuth, dstAuth authn.Authenticator) error {
	srcRef, err := name.ParseReference(src)
	if err != nil {
		return fmt.Errorf("parsing reference %q: %v", src, err)
	}

	dstRef, err := name.ParseReference(dst)
	if err != nil {
		return fmt.Errorf("parsing reference %q: %v", dst, err)
	}

	idx, err := remote.Index(srcRef, remote.WithAuth(srcAuth))
	if err != nil {
		return fmt.Errorf("reading image %q: %v", src, err)
	}

	if err := remote.WriteIndex(dstRef, idx, remote.WithAuth(dstAuth)); err != nil {
		return fmt.Errorf("writing image %q: %v", dst, err)
	}

	return nil
}

// recursiveCopy copies images from repo src to repo dst, rather quickly. tl;dr:
//
//  for each repo in src {
//		go func {
//			for each image in repo {
//				go func {
//					for each tag in image {
//						go func {
//							copyImage(tag, rename(tag, dst))
//						}
//					}
//				}
//			}
//			for each index in repo {
//				go func {
//					for each tag in index {
//						go func {
//							copyIndex(tag, rename(tag, dst))
//						}
//					}
//				}
//			}
//		}
//	}
func recursiveCopy(src, dst string) error {
	c, err := newCopier(src, dst)
	if err != nil {
		return err
	}

	g, ctx := errgroup.WithContext(context.Background())

	// Captures c, g, and ctx.
	walkFn := func(repo name.Repository, tags *google.Tags, err error) error {
		if err != nil {
			return fmt.Errorf("failed walkFn for repo %s: %v", repo, err)
		}

		g.Go(func() error {
			return c.copyRepo(ctx, repo, tags)
		})

		return nil
	}

	if err := google.Walk(c.srcRepo, walkFn, google.WithAuth(c.srcAuth)); err != nil {
		return fmt.Errorf("failed to Walk: %v", err)
	}

	return g.Wait()
}

func notFoundError(err error) bool {
	if err, ok := err.(*transport.Error); ok {
		if err.StatusCode == 404 {
			return true
		}
	}

	return false
}

// copyRepo figures out the name for our destination repo (newRepo), lists the
// contents of newRepo, calculates the diff of what needs to be copied, then
// starts a goroutine to copy each image we need, and waits for them to finish.
func (c *copier) copyRepo(ctx context.Context, oldRepo name.Repository, tags *google.Tags) error {
	newRepo, err := c.rename(oldRepo)
	if err != nil {
		return fmt.Errorf("rename failed: %v", err)
	}

	// Figure out what we actually need to copy.
	want := tags.Manifests
	have := make(map[string]google.ManifestInfo)
	haveTags, err := google.List(newRepo, google.WithAuth(c.dstAuth))
	if err != nil {
		if !notFoundError(err) {
			return err
		}
		// This is a 404 code, so we just need to copy everything.
		logs.Warn.Printf("failed to list %s: %v", newRepo, err)
	} else {
		have = haveTags.Manifests
	}
	need := diffImages(want, have)

	g, ctx := errgroup.WithContext(ctx)

	// First go through copying just manifests, skipping manifest lists, since
	// manifest lists might reference them.
	todos := make(map[string]google.ManifestInfo)
	for digest, manifest := range need {
		if manifest.MediaType == string(types.DockerManifestList) || manifest.MediaType == string(types.OCIImageIndex) {
			todos[digest] = manifest
			continue
		}

		digest, manifest := digest, manifest // https://golang.org/doc/faq#closures_and_goroutines
		g.Go(func() error {
			return c.copyImages(ctx, digest, manifest, oldRepo, newRepo)
		})
	}

	if err := g.Wait(); err != nil {
		return fmt.Errorf("Failed to copy %s: %v", oldRepo, err)
	}

	// Now copy the manifest lists, since it should be safe.
	for digest, manifest := range todos {
		digest, manifest := digest, manifest // https://golang.org/doc/faq#closures_and_goroutines
		g.Go(func() error {
			return c.copyIndexes(ctx, digest, manifest, oldRepo, newRepo)
		})
	}

	if err := g.Wait(); err != nil {
		return fmt.Errorf("Failed to copy %s: %v", oldRepo, err)
	}

	return nil
}

// copyImages starts a goroutine for each tag that points to the image
// oldRepo@digest, or just copies the image by digest if there are no tags.
func (c *copier) copyImages(ctx context.Context, digest string, manifest google.ManifestInfo, oldRepo, newRepo name.Repository) error {
	// We only have to explicitly copy by digest if there are no tags pointing to this manifest.
	if len(manifest.Tags) == 0 {
		srcImg := fmt.Sprintf("%s@%s", oldRepo, digest)
		dstImg := fmt.Sprintf("%s@%s", newRepo, digest)

		return copyImage(srcImg, dstImg, c.srcAuth, c.dstAuth)
	}

	// Copy all the tags.
	g, _ := errgroup.WithContext(ctx)
	for _, tag := range manifest.Tags {
		tag := tag // https://golang.org/doc/faq#closures_and_goroutines
		g.Go(func() error {
			srcImg := fmt.Sprintf("%s:%s", oldRepo, tag)
			dstImg := fmt.Sprintf("%s:%s", newRepo, tag)

			return copyImage(srcImg, dstImg, c.srcAuth, c.dstAuth)
		})
	}
	return g.Wait()
}

// copyIndexes starts a goroutine for each tag that points to the index
// oldRepo@digest, or just copies the index by digest if there are no tags.
func (c *copier) copyIndexes(ctx context.Context, digest string, manifest google.ManifestInfo, oldRepo, newRepo name.Repository) error {
	// We only have to explicitly copy by digest if there are no tags pointing to this manifest.
	if len(manifest.Tags) == 0 {
		srcImg := fmt.Sprintf("%s@%s", oldRepo, digest)
		dstImg := fmt.Sprintf("%s@%s", newRepo, digest)

		return copyIndex(srcImg, dstImg, c.srcAuth, c.dstAuth)
	}

	// Copy all the tags.
	g, _ := errgroup.WithContext(ctx)
	for _, tag := range manifest.Tags {
		tag := tag // https://golang.org/doc/faq#closures_and_goroutines
		g.Go(func() error {
			srcImg := fmt.Sprintf("%s:%s", oldRepo, tag)
			dstImg := fmt.Sprintf("%s:%s", newRepo, tag)

			// TODO: Just implement an AddTag thing.
			return copyIndex(srcImg, dstImg, c.srcAuth, c.dstAuth)
		})
	}
	return g.Wait()
}

// rename figures out the name of the new repository to copy to, e.g.:
//
// $ gcrane cp -r gcr.io/foo gcr.io/baz
//
// rename("gcr.io/foo/bar") == "gcr.io/baz/bar"
func (c *copier) rename(repo name.Repository) (name.Repository, error) {
	replaced := strings.Replace(repo.String(), c.srcRepo.String(), c.dstRepo.String(), 1)
	return name.NewRepository(replaced, name.StrictValidation)
}

// diffImages returns a map of digests to google.ManifestInfos for images or
// tags that are present in "want" but not in "have".
func diffImages(want, have map[string]google.ManifestInfo) map[string]google.ManifestInfo {
	need := make(map[string]google.ManifestInfo)

	for digest, wantManifest := range want {
		if haveManifest, ok := have[digest]; !ok {
			// Missing the whole image, we need to copy everything.
			need[digest] = wantManifest
		} else {
			missingTags := subtractStringLists(wantManifest.Tags, haveManifest.Tags)
			if len(missingTags) == 0 {
				continue
			}

			// Missing just some tags, add the ones we need to copy.
			todo := wantManifest
			todo.Tags = missingTags
			need[digest] = todo
		}
	}

	return need
}

// subtractStringLists returns a list of strings that are in minuend and not
// in subtrahend; order is unimportant.
func subtractStringLists(minuend, subtrahend []string) []string {
	bSet := toStringSet(subtrahend)
	difference := []string{}

	for _, a := range minuend {
		if _, ok := bSet[a]; !ok {
			difference = append(difference, a)
		}
	}

	return difference
}

func toStringSet(slice []string) map[string]struct{} {
	set := make(map[string]struct{}, len(slice))
	for _, s := range slice {
		set[s] = struct{}{}
	}
	return set
}

func parseRefAuths(src, dst string) (authn.Authenticator, authn.Authenticator, error) {
	srcRef, err := name.ParseReference(src)
	if err != nil {
		return nil, nil, fmt.Errorf("parsing reference %q: %v", src, err)
	}

	dstRef, err := name.ParseReference(dst)
	if err != nil {
		return nil, nil, fmt.Errorf("parsing reference %q: %v", dst, err)
	}

	srcAuth, err := authn.DefaultKeychain.Resolve(srcRef.Context().Registry)
	if err != nil {
		return nil, nil, fmt.Errorf("getting auth for %q: %v", src, err)
	}

	dstAuth, err := authn.DefaultKeychain.Resolve(dstRef.Context().Registry)
	if err != nil {
		return nil, nil, fmt.Errorf("getting auth for %q: %v", dst, err)
	}

	return srcAuth, dstAuth, nil
}
<|endoftext|>


<commit_before>// Package junit defines a JUnit XML report and includes convenience methods
// for working with these reports.
package junit

import (
	"encoding/xml"
	"fmt"
	"strings"
	"time"

	"github.com/jstemmer/go-junit-report/v2/pkg/gtr"
)

// Testsuites is a collection of JUnit testsuites.
type Testsuites struct {
	XMLName xml.Name `xml:"testsuites"`

	Name     string `xml:"name,attr,omitempty"`
	Time     string `xml:"time,attr,omitempty"` // total duration in seconds
	Tests    int    `xml:"tests,attr,omitempty"`
	Errors   int    `xml:"errors,attr,omitempty"`
	Failures int    `xml:"failures,attr,omitempty"`
	Disabled int    `xml:"disabled,attr,omitempty"`

	Suites []Testsuite `xml:"testsuite,omitempty"`
}

// AddSuite adds a Testsuite and updates this testssuites' totals.
func (t *Testsuites) AddSuite(ts Testsuite) {
	t.Suites = append(t.Suites, ts)
	t.Tests += ts.Tests
	t.Errors += ts.Errors
	t.Failures += ts.Failures
	t.Disabled += ts.Disabled
}

// Testsuite is a single JUnit testsuite containing testcases.
type Testsuite struct {
	// required attributes
	Name     string `xml:"name,attr"`
	Tests    int    `xml:"tests,attr"`
	Failures int    `xml:"failures,attr"`
	Errors   int    `xml:"errors,attr"`

	// optional attributes
	Disabled  int    `xml:"disabled,attr,omitempty"`
	Hostname  string `xml:"hostname,attr,omitempty"`
	ID        int    `xml:"id,attr,omitempty"`
	Package   string `xml:"package,attr,omitempty"`
	Skipped   int    `xml:"skipped,attr,omitempty"`
	Time      string `xml:"time,attr"`                // duration in seconds
	Timestamp string `xml:"timestamp,attr,omitempty"` // date and time in ISO8601

	Properties *[]Property `xml:"properties>property,omitempty"`
	Testcases  []Testcase  `xml:"testcase,omitempty"`
	SystemOut  *Output     `xml:"system-out,omitempty"`
	SystemErr  *Output     `xml:"system-err,omitempty"`
}

// AddProperty adds a property with the given name and value to this Testsuite.
func (t *Testsuite) AddProperty(name, value string) {
	prop := Property{Name: name, Value: value}
	if t.Properties == nil {
		t.Properties = &[]Property{prop}
		return
	}
	props := append(*t.Properties, prop)
	t.Properties = &props
}

// AddTestcase adds Testcase tc to this Testsuite.
func (t *Testsuite) AddTestcase(tc Testcase) {
	t.Testcases = append(t.Testcases, tc)
	t.Tests += 1

	if tc.Error != nil {
		t.Errors += 1
	}

	if tc.Failure != nil {
		t.Failures += 1
	}

	if tc.Skipped != nil {
		t.Skipped += 1
	}
}

// SetTimestamp sets the timestamp in this Testsuite.
func (ts *Testsuite) SetTimestamp(t time.Time) {
	ts.Timestamp = t.Format(time.RFC3339)
}

// Testcase represents a single test with its results.
type Testcase struct {
	// required attributes
	Name      string `xml:"name,attr"`
	Classname string `xml:"classname,attr"`

	// optional attributes
	Time   string `xml:"time,attr,omitempty"` // duration in seconds
	Status string `xml:"status,attr,omitempty"`

	Skipped   *Result `xml:"skipped,omitempty"`
	Error     *Result `xml:"error,omitempty"`
	Failure   *Result `xml:"failure,omitempty"`
	SystemOut *Output `xml:"system-out,omitempty"`
	SystemErr *Output `xml:"system-err,omitempty"`
}

// Property represents a key/value pair.
type Property struct {
	Name  string `xml:"name,attr"`
	Value string `xml:"value,attr"`
}

// Result represents the result of a single test.
type Result struct {
	Message string `xml:"message,attr"`
	Type    string `xml:"type,attr,omitempty"`
	Data    string `xml:",cdata"`
}

// Output represents output written to stdout or sderr.
type Output struct {
	Data string `xml:",cdata"`
}

// CreateFromReport creates a JUnit representation of the given gtr.Report.
func CreateFromReport(report gtr.Report, hostname string) Testsuites {
	var suites Testsuites
	for _, pkg := range report.Packages {
		var duration time.Duration
		suite := Testsuite{
			Name:     pkg.Name,
			Hostname: hostname,
		}

		if !pkg.Timestamp.IsZero() {
			suite.SetTimestamp(pkg.Timestamp)
		}

		for k, v := range pkg.Properties {
			suite.AddProperty(k, v)
		}

		if len(pkg.Output) > 0 {
			suite.SystemOut = &Output{Data: formatOutput(pkg.Output, 0)}
		}

		if pkg.Coverage > 0 {
			suite.AddProperty("coverage.statements.pct", fmt.Sprintf("%.2f", pkg.Coverage))
		}

		for _, test := range pkg.Tests {
			duration += test.Duration

			tc := Testcase{
				Classname: pkg.Name,
				Name:      test.Name,
				Time:      formatDuration(test.Duration),
			}

			if test.Result == gtr.Fail {
				tc.Failure = &Result{
					Message: "Failed",
					Data:    formatOutput(test.Output, test.Level),
				}
			} else if test.Result == gtr.Skip {
				tc.Skipped = &Result{
					Message: formatOutput(test.Output, test.Level),
				}
			} else if test.Result == gtr.Unknown {
				tc.Error = &Result{
					Message: "No test result found",
					Data:    formatOutput(test.Output, test.Level),
				}
			}

			suite.AddTestcase(tc)
		}

		for _, bm := range groupBenchmarksByName(pkg.Benchmarks) {
			tc := Testcase{
				Classname: pkg.Name,
				Name:      bm.Name,
				Time:      formatBenchmarkTime(time.Duration(bm.NsPerOp)),
			}

			if bm.Result == gtr.Fail {
				tc.Failure = &Result{
					Message: "Failed",
				}
			}

			suite.AddTestcase(tc)
		}

		// JUnit doesn't have a good way of dealing with build or runtime
		// errors that happen before a test has started, so we create a single
		// failing test that contains the build error details.
		if pkg.BuildError.Name != "" {
			tc := Testcase{
				Classname: pkg.BuildError.Name,
				Name:      pkg.BuildError.Cause,
				Time:      formatDuration(0),
				Error: &Result{
					Message: "Build error",
					Data:    strings.Join(pkg.BuildError.Output, "\n"),
				},
			}
			suite.AddTestcase(tc)
		}

		if pkg.RunError.Name != "" {
			tc := Testcase{
				Classname: pkg.RunError.Name,
				Name:      "Failure",
				Time:      formatDuration(0),
				Error: &Result{
					Message: "Run error",
					Data:    strings.Join(pkg.RunError.Output, "\n"),
				},
			}
			suite.AddTestcase(tc)
		}

		if (pkg.Duration) == 0 {
			suite.Time = formatDuration(duration)
		} else {
			suite.Time = formatDuration(pkg.Duration)
		}
		suites.AddSuite(suite)
	}
	return suites
}

// formatDuration returns the JUnit string representation of the given
// duration.
func formatDuration(d time.Duration) string {
	return fmt.Sprintf("%.3f", d.Seconds())
}

// formatBenchmarkTime returns the JUnit string representation of the given
// benchmark time.
func formatBenchmarkTime(d time.Duration) string {
	return fmt.Sprintf("%.9f", d.Seconds())
}

// formatOutput trims the test whitespace prefix from each line and joins all
// the lines.
func formatOutput(output []string, indent int) string {
	var lines []string
	for _, line := range output {
		lines = append(lines, gtr.TrimPrefixSpaces(line, indent))
	}
	return strings.Join(lines, "\n")
}

func groupBenchmarksByName(benchmarks []gtr.Benchmark) []gtr.Benchmark {
	var grouped []gtr.Benchmark

	benchmap := make(map[string][]gtr.Benchmark)
	for _, bm := range benchmarks {
		if _, ok := benchmap[bm.Name]; !ok {
			grouped = append(grouped, gtr.Benchmark{Name: bm.Name})
		}
		benchmap[bm.Name] = append(benchmap[bm.Name], bm)
	}

	for i, bm := range grouped {
		for _, b := range benchmap[bm.Name] {
			bm.NsPerOp += b.NsPerOp
			bm.MBPerSec += b.MBPerSec
			bm.BytesPerOp += b.BytesPerOp
			bm.AllocsPerOp += b.AllocsPerOp
		}
		n := len(benchmap[bm.Name])
		grouped[i].NsPerOp = bm.NsPerOp / float64(n)
		grouped[i].MBPerSec = bm.MBPerSec / float64(n)
		grouped[i].BytesPerOp = bm.BytesPerOp / int64(n)
		grouped[i].AllocsPerOp = bm.AllocsPerOp / int64(n)
	}

	return grouped
}
<commit_msg>fixup: add skipped attr to testsuites<commit_after>// Package junit defines a JUnit XML report and includes convenience methods
// for working with these reports.
package junit

import (
	"encoding/xml"
	"fmt"
	"strings"
	"time"

	"github.com/jstemmer/go-junit-report/v2/pkg/gtr"
)

// Testsuites is a collection of JUnit testsuites.
type Testsuites struct {
	XMLName xml.Name `xml:"testsuites"`

	Name     string `xml:"name,attr,omitempty"`
	Time     string `xml:"time,attr,omitempty"` // total duration in seconds
	Tests    int    `xml:"tests,attr,omitempty"`
	Errors   int    `xml:"errors,attr,omitempty"`
	Failures int    `xml:"failures,attr,omitempty"`
	Skipped  int    `xml:"skipped,attr,omitempty"`
	Disabled int    `xml:"disabled,attr,omitempty"`

	Suites []Testsuite `xml:"testsuite,omitempty"`
}

// AddSuite adds a Testsuite and updates this testssuites' totals.
func (t *Testsuites) AddSuite(ts Testsuite) {
	t.Suites = append(t.Suites, ts)
	t.Tests += ts.Tests
	t.Errors += ts.Errors
	t.Failures += ts.Failures
	t.Skipped += ts.Skipped
	t.Disabled += ts.Disabled
}

// Testsuite is a single JUnit testsuite containing testcases.
type Testsuite struct {
	// required attributes
	Name     string `xml:"name,attr"`
	Tests    int    `xml:"tests,attr"`
	Failures int    `xml:"failures,attr"`
	Errors   int    `xml:"errors,attr"`

	// optional attributes
	Disabled  int    `xml:"disabled,attr,omitempty"`
	Hostname  string `xml:"hostname,attr,omitempty"`
	ID        int    `xml:"id,attr,omitempty"`
	Package   string `xml:"package,attr,omitempty"`
	Skipped   int    `xml:"skipped,attr,omitempty"`
	Time      string `xml:"time,attr"`                // duration in seconds
	Timestamp string `xml:"timestamp,attr,omitempty"` // date and time in ISO8601

	Properties *[]Property `xml:"properties>property,omitempty"`
	Testcases  []Testcase  `xml:"testcase,omitempty"`
	SystemOut  *Output     `xml:"system-out,omitempty"`
	SystemErr  *Output     `xml:"system-err,omitempty"`
}

// AddProperty adds a property with the given name and value to this Testsuite.
func (t *Testsuite) AddProperty(name, value string) {
	prop := Property{Name: name, Value: value}
	if t.Properties == nil {
		t.Properties = &[]Property{prop}
		return
	}
	props := append(*t.Properties, prop)
	t.Properties = &props
}

// AddTestcase adds Testcase tc to this Testsuite.
func (t *Testsuite) AddTestcase(tc Testcase) {
	t.Testcases = append(t.Testcases, tc)
	t.Tests += 1

	if tc.Error != nil {
		t.Errors += 1
	}

	if tc.Failure != nil {
		t.Failures += 1
	}

	if tc.Skipped != nil {
		t.Skipped += 1
	}
}

// SetTimestamp sets the timestamp in this Testsuite.
func (ts *Testsuite) SetTimestamp(t time.Time) {
	ts.Timestamp = t.Format(time.RFC3339)
}

// Testcase represents a single test with its results.
type Testcase struct {
	// required attributes
	Name      string `xml:"name,attr"`
	Classname string `xml:"classname,attr"`

	// optional attributes
	Time   string `xml:"time,attr,omitempty"` // duration in seconds
	Status string `xml:"status,attr,omitempty"`

	Skipped   *Result `xml:"skipped,omitempty"`
	Error     *Result `xml:"error,omitempty"`
	Failure   *Result `xml:"failure,omitempty"`
	SystemOut *Output `xml:"system-out,omitempty"`
	SystemErr *Output `xml:"system-err,omitempty"`
}

// Property represents a key/value pair.
type Property struct {
	Name  string `xml:"name,attr"`
	Value string `xml:"value,attr"`
}

// Result represents the result of a single test.
type Result struct {
	Message string `xml:"message,attr"`
	Type    string `xml:"type,attr,omitempty"`
	Data    string `xml:",cdata"`
}

// Output represents output written to stdout or sderr.
type Output struct {
	Data string `xml:",cdata"`
}

// CreateFromReport creates a JUnit representation of the given gtr.Report.
func CreateFromReport(report gtr.Report, hostname string) Testsuites {
	var suites Testsuites
	for _, pkg := range report.Packages {
		var duration time.Duration
		suite := Testsuite{
			Name:     pkg.Name,
			Hostname: hostname,
		}

		if !pkg.Timestamp.IsZero() {
			suite.SetTimestamp(pkg.Timestamp)
		}

		for k, v := range pkg.Properties {
			suite.AddProperty(k, v)
		}

		if len(pkg.Output) > 0 {
			suite.SystemOut = &Output{Data: formatOutput(pkg.Output, 0)}
		}

		if pkg.Coverage > 0 {
			suite.AddProperty("coverage.statements.pct", fmt.Sprintf("%.2f", pkg.Coverage))
		}

		for _, test := range pkg.Tests {
			duration += test.Duration

			tc := Testcase{
				Classname: pkg.Name,
				Name:      test.Name,
				Time:      formatDuration(test.Duration),
			}

			if test.Result == gtr.Fail {
				tc.Failure = &Result{
					Message: "Failed",
					Data:    formatOutput(test.Output, test.Level),
				}
			} else if test.Result == gtr.Skip {
				tc.Skipped = &Result{
					Message: formatOutput(test.Output, test.Level),
				}
			} else if test.Result == gtr.Unknown {
				tc.Error = &Result{
					Message: "No test result found",
					Data:    formatOutput(test.Output, test.Level),
				}
			}

			suite.AddTestcase(tc)
		}

		for _, bm := range groupBenchmarksByName(pkg.Benchmarks) {
			tc := Testcase{
				Classname: pkg.Name,
				Name:      bm.Name,
				Time:      formatBenchmarkTime(time.Duration(bm.NsPerOp)),
			}

			if bm.Result == gtr.Fail {
				tc.Failure = &Result{
					Message: "Failed",
				}
			}

			suite.AddTestcase(tc)
		}

		// JUnit doesn't have a good way of dealing with build or runtime
		// errors that happen before a test has started, so we create a single
		// failing test that contains the build error details.
		if pkg.BuildError.Name != "" {
			tc := Testcase{
				Classname: pkg.BuildError.Name,
				Name:      pkg.BuildError.Cause,
				Time:      formatDuration(0),
				Error: &Result{
					Message: "Build error",
					Data:    strings.Join(pkg.BuildError.Output, "\n"),
				},
			}
			suite.AddTestcase(tc)
		}

		if pkg.RunError.Name != "" {
			tc := Testcase{
				Classname: pkg.RunError.Name,
				Name:      "Failure",
				Time:      formatDuration(0),
				Error: &Result{
					Message: "Run error",
					Data:    strings.Join(pkg.RunError.Output, "\n"),
				},
			}
			suite.AddTestcase(tc)
		}

		if (pkg.Duration) == 0 {
			suite.Time = formatDuration(duration)
		} else {
			suite.Time = formatDuration(pkg.Duration)
		}
		suites.AddSuite(suite)
	}
	return suites
}

// formatDuration returns the JUnit string representation of the given
// duration.
func formatDuration(d time.Duration) string {
	return fmt.Sprintf("%.3f", d.Seconds())
}

// formatBenchmarkTime returns the JUnit string representation of the given
// benchmark time.
func formatBenchmarkTime(d time.Duration) string {
	return fmt.Sprintf("%.9f", d.Seconds())
}

// formatOutput trims the test whitespace prefix from each line and joins all
// the lines.
func formatOutput(output []string, indent int) string {
	var lines []string
	for _, line := range output {
		lines = append(lines, gtr.TrimPrefixSpaces(line, indent))
	}
	return strings.Join(lines, "\n")
}

func groupBenchmarksByName(benchmarks []gtr.Benchmark) []gtr.Benchmark {
	var grouped []gtr.Benchmark

	benchmap := make(map[string][]gtr.Benchmark)
	for _, bm := range benchmarks {
		if _, ok := benchmap[bm.Name]; !ok {
			grouped = append(grouped, gtr.Benchmark{Name: bm.Name})
		}
		benchmap[bm.Name] = append(benchmap[bm.Name], bm)
	}

	for i, bm := range grouped {
		for _, b := range benchmap[bm.Name] {
			bm.NsPerOp += b.NsPerOp
			bm.MBPerSec += b.MBPerSec
			bm.BytesPerOp += b.BytesPerOp
			bm.AllocsPerOp += b.AllocsPerOp
		}
		n := len(benchmap[bm.Name])
		grouped[i].NsPerOp = bm.NsPerOp / float64(n)
		grouped[i].MBPerSec = bm.MBPerSec / float64(n)
		grouped[i].BytesPerOp = bm.BytesPerOp / int64(n)
		grouped[i].AllocsPerOp = bm.AllocsPerOp / int64(n)
	}

	return grouped
}
<|endoftext|>


<commit_before>// Copyright 2012-2017 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package pci

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"io"
	"os"
)

//Devices contains a slice of one or more PCI devices
type Devices []*PCI

// Print prints information to an io.Writer
func (d Devices) Print(o io.Writer, verbose, confSize int) error {
	for _, pci := range d {
		if _, err := fmt.Fprintf(o, "%s\n", pci.String()); err != nil {
			return err
		}
		var extraNL bool
		// Make sure we have read enough config space to satisfy the verbose and confSize requests.
		// If len(pci.Config) is > 64, that's the only test we need.
		if (verbose > 1 || confSize > 64) && len(pci.Config) < 256 {
			return os.ErrPermission
		}
		if verbose >= 1 {
			c := pci.Config
			if _, err := fmt.Fprintf(o, "\tControl: %s\n\tStatus: %s\n\tLatency: %d", pci.Control.String(), pci.Status.String(), pci.Latency); err != nil {
				return err
			}
			if pci.Bridge {
				// Bus: primary=00, secondary=05, subordinate=0c, sec-latency=0
				// I/O behind bridge: 00002000-00002fff [size=4K]
				// Memory behind bridge: f0000000-f1ffffff [size=32M]
				// Prefetchable memory behind bridge: 00000000f2900000-00000000f29fffff [size=1M]
				if _, err := fmt.Fprintf(o, ", Cache Line Size: %d bytes", c[CacheLineSize]); err != nil {
					return err
				}
				if _, err := fmt.Fprintf(o, "\n\tBus: primary=%s, secondary=%s, subordinate=%s, sec-latency=%s",
					pci.Primary, pci.Secondary, pci.Subordinate, pci.SecLatency); err != nil {
					return err
				}
				// I hate this code.
				// I miss Rust tuples at times.
				for _, e := range []struct {
					h, f string
					b, l uint64
				}{
					{"\n\tI/O behind bridge: ", "%#08x-%#08x [size=%#x]", pci.IO.Base, pci.IO.Lim},
					{"\n\tMemory behind bridge: ", "%#08x-%#08x [size=%#x]", pci.Mem.Base, pci.Mem.Lim},
					{"\n\tPrefetchable memory behind bridge: ", "%#08x-%#08x [size=%#x]", pci.PrefMem.Base, pci.PrefMem.Lim},
				} {
					s := e.h + " [disabled]"
					if e.b != 0 {
						sz := e.l - e.b + 1
						s = fmt.Sprintf(e.h+e.f, e.b, e.l, sz)
					}
					if _, err := fmt.Fprintf(o, s); err != nil {
						return err
					}
				}
			}
			fmt.Fprintf(o, "\n")
			if pci.IRQPin != 0 {
				if _, err := fmt.Fprintf(o, "\tInterrupt: pin %X routed to IRQ %X\n", 9+pci.IRQPin, pci.IRQLine); err != nil {
					return err
				}

			}
			if !pci.Bridge {
				for _, b := range pci.BARS {
					if _, err := fmt.Fprintf(o, "\t%v\n", b.String()); err != nil {
						return err
					}
				}

			}
			if verbose >= 2 {
				if !pci.Bridge {
				} else {
				}

				//Latency: 0, Cache Line Size: 64 bytes
				//Interrupt: pin D routed to IRQ 19
			}
			extraNL = true
		}

		if confSize > 0 {
			r := io.LimitReader(bytes.NewBuffer(pci.Config), int64(confSize))
			e := hex.Dumper(o)
			if _, err := io.Copy(e, r); err != nil {
				return err
			}
			extraNL = true
		}
		// lspci likes that extra line of separation
		if extraNL {
			fmt.Fprintf(o, "\n")
		}
	}
	return nil
}

// SetVendorDeviceName sets all numeric IDs of all the devices
// using the pci device SetVendorDeviceName.
func (d Devices) SetVendorDeviceName() {
	for _, p := range d {
		p.SetVendorDeviceName()
	}
}

// ReadConfig reads the config info for all the devices.
func (d Devices) ReadConfig() error {
	for _, p := range d {
		if err := p.ReadConfig(); err != nil {
			return err
		}
	}
	return nil
}

// ReadConfigRegister reads the config info for all the devices.
func (d Devices) ReadConfigRegister(offset, size int64) ([]uint64, error) {
	var vals []uint64
	for _, p := range d {
		val, err := p.ReadConfigRegister(offset, size)
		if err != nil {
			return nil, err
		}
		vals = append(vals, val)
	}
	return vals, nil
}

// WriteConfigRegister writes the config info for all the devices.
func (d Devices) WriteConfigRegister(offset, size int64, val uint64) error {
	for _, p := range d {
		if err := p.WriteConfigRegister(offset, size, val); err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>remove dead code<commit_after>// Copyright 2012-2017 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package pci

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"io"
	"os"
)

//Devices contains a slice of one or more PCI devices
type Devices []*PCI

// Print prints information to an io.Writer
func (d Devices) Print(o io.Writer, verbose, confSize int) error {
	for _, pci := range d {
		if _, err := fmt.Fprintf(o, "%s\n", pci.String()); err != nil {
			return err
		}
		var extraNL bool
		// Make sure we have read enough config space to satisfy the verbose and confSize requests.
		// If len(pci.Config) is > 64, that's the only test we need.
		if (verbose > 1 || confSize > 64) && len(pci.Config) < 256 {
			return os.ErrPermission
		}
		if verbose >= 1 {
			c := pci.Config
			if _, err := fmt.Fprintf(o, "\tControl: %s\n\tStatus: %s\n\tLatency: %d", pci.Control.String(), pci.Status.String(), pci.Latency); err != nil {
				return err
			}
			if pci.Bridge {
				// Bus: primary=00, secondary=05, subordinate=0c, sec-latency=0
				// I/O behind bridge: 00002000-00002fff [size=4K]
				// Memory behind bridge: f0000000-f1ffffff [size=32M]
				// Prefetchable memory behind bridge: 00000000f2900000-00000000f29fffff [size=1M]
				if _, err := fmt.Fprintf(o, ", Cache Line Size: %d bytes", c[CacheLineSize]); err != nil {
					return err
				}
				if _, err := fmt.Fprintf(o, "\n\tBus: primary=%s, secondary=%s, subordinate=%s, sec-latency=%s",
					pci.Primary, pci.Secondary, pci.Subordinate, pci.SecLatency); err != nil {
					return err
				}
				// I hate this code.
				// I miss Rust tuples at times.
				for _, e := range []struct {
					h, f string
					b, l uint64
				}{
					{"\n\tI/O behind bridge: ", "%#08x-%#08x [size=%#x]", pci.IO.Base, pci.IO.Lim},
					{"\n\tMemory behind bridge: ", "%#08x-%#08x [size=%#x]", pci.Mem.Base, pci.Mem.Lim},
					{"\n\tPrefetchable memory behind bridge: ", "%#08x-%#08x [size=%#x]", pci.PrefMem.Base, pci.PrefMem.Lim},
				} {
					s := e.h + " [disabled]"
					if e.b != 0 {
						sz := e.l - e.b + 1
						s = fmt.Sprintf(e.h+e.f, e.b, e.l, sz)
					}
					if _, err := fmt.Fprintf(o, s); err != nil {
						return err
					}
				}
			}
			fmt.Fprintf(o, "\n")
			if pci.IRQPin != 0 {
				if _, err := fmt.Fprintf(o, "\tInterrupt: pin %X routed to IRQ %X\n", 9+pci.IRQPin, pci.IRQLine); err != nil {
					return err
				}

			}
			if !pci.Bridge {
				for _, b := range pci.BARS {
					if _, err := fmt.Fprintf(o, "\t%v\n", b.String()); err != nil {
						return err
					}
				}

			}
			extraNL = true
		}

		if confSize > 0 {
			r := io.LimitReader(bytes.NewBuffer(pci.Config), int64(confSize))
			e := hex.Dumper(o)
			if _, err := io.Copy(e, r); err != nil {
				return err
			}
			extraNL = true
		}
		// lspci likes that extra line of separation
		if extraNL {
			fmt.Fprintf(o, "\n")
		}
	}
	return nil
}

// SetVendorDeviceName sets all numeric IDs of all the devices
// using the pci device SetVendorDeviceName.
func (d Devices) SetVendorDeviceName() {
	for _, p := range d {
		p.SetVendorDeviceName()
	}
}

// ReadConfig reads the config info for all the devices.
func (d Devices) ReadConfig() error {
	for _, p := range d {
		if err := p.ReadConfig(); err != nil {
			return err
		}
	}
	return nil
}

// ReadConfigRegister reads the config info for all the devices.
func (d Devices) ReadConfigRegister(offset, size int64) ([]uint64, error) {
	var vals []uint64
	for _, p := range d {
		val, err := p.ReadConfigRegister(offset, size)
		if err != nil {
			return nil, err
		}
		vals = append(vals, val)
	}
	return vals, nil
}

// WriteConfigRegister writes the config info for all the devices.
func (d Devices) WriteConfigRegister(offset, size int64, val uint64) error {
	for _, p := range d {
		if err := p.WriteConfigRegister(offset, size, val); err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2016 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libfuse

import (
	"os"
	"path"

	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/logger"
	"github.com/keybase/kbfs/libfs"
	"github.com/keybase/kbfs/libkbfs"
	"github.com/keybase/kbfs/simplefs"
	"golang.org/x/net/context"
)

// StartOptions are options for starting up
type StartOptions struct {
	KbfsParams     libkbfs.InitParams
	PlatformParams PlatformParams
	RuntimeDir     string
	Label          string
	ForceMount     bool
	SkipMount      bool
	MountPoint     string
}

func startMounting(
	ctx context.Context, config libkbfs.Config, options StartOptions,
	log logger.Logger, mi *libfs.MountInterrupter) error {
	log.CDebugf(ctx, "Mounting: %q", options.MountPoint)

	var mounter = &mounter{options: options}
	err := mi.MountAndSetUnmount(mounter)
	if err != nil {
		return err
	}

	<-mounter.c.Ready
	err = mounter.c.MountError
	if err != nil {
		return err
	}

	log.CDebugf(ctx, "Creating filesystem")
	fs := NewFS(config, mounter.c, options.KbfsParams.Debug, options.PlatformParams)
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	ctx = context.WithValue(ctx, libfs.CtxAppIDKey, fs)
	log.CDebugf(ctx, "Serving filesystem")
	if err = fs.Serve(ctx); err != nil {
		return err
	}

	log.CDebugf(ctx, "Ending")
	return nil
}

// Start the filesystem
func Start(options StartOptions, kbCtx libkbfs.Context) *libfs.Error {
	// Hook simplefs implementation in.
	options.KbfsParams.CreateSimpleFSInstance = simplefs.NewSimpleFS

	log, err := libkbfs.InitLog(options.KbfsParams, kbCtx)
	if err != nil {
		return libfs.InitError(err.Error())
	}

	if options.RuntimeDir != "" {
		info := libkb.NewServiceInfo(libkbfs.Version, libkbfs.PrereleaseBuild, options.Label, os.Getpid())
		err := info.WriteFile(path.Join(options.RuntimeDir, "kbfs.info"), log)
		if err != nil {
			return libfs.InitError(err.Error())
		}
	}

	log.Debug("Initializing")
	mi := libfs.NewMountInterrupter(log)
	ctx := context.Background()
	config, err := libkbfs.Init(
		ctx, kbCtx, options.KbfsParams, nil, mi.Done, log)
	if err != nil {
		return libfs.InitError(err.Error())
	}
	defer libkbfs.Shutdown()

	if options.SkipMount {
		log.Debug("Skipping mounting filesystem")
	} else {
		err = startMounting(ctx, config, options, log, mi)
		if err != nil {
			// Abort on error if we were force mounting, otherwise continue.
			if options.ForceMount {
				// If we exit we might want to clean a mount behind us.
				mi.Done()
				return libfs.MountError(err.Error())
			}
		}
	}
	mi.Wait()
	return nil
}
<commit_msg>libfuse: serve FS requests before the mounter is ready to fix hang<commit_after>// Copyright 2016 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libfuse

import (
	"os"
	"path"

	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/logger"
	"github.com/keybase/kbfs/libfs"
	"github.com/keybase/kbfs/libkbfs"
	"github.com/keybase/kbfs/simplefs"
	"golang.org/x/net/context"
)

// StartOptions are options for starting up
type StartOptions struct {
	KbfsParams     libkbfs.InitParams
	PlatformParams PlatformParams
	RuntimeDir     string
	Label          string
	ForceMount     bool
	SkipMount      bool
	MountPoint     string
}

func startMounting(
	ctx context.Context, config libkbfs.Config, options StartOptions,
	log logger.Logger, mi *libfs.MountInterrupter) error {
	log.CDebugf(ctx, "Mounting: %q", options.MountPoint)

	var mounter = &mounter{options: options}
	err := mi.MountAndSetUnmount(mounter)
	if err != nil {
		return err
	}

	log.CDebugf(ctx, "Creating filesystem")
	fs := NewFS(config, mounter.c, options.KbfsParams.Debug, options.PlatformParams)
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	ctx = context.WithValue(ctx, libfs.CtxAppIDKey, fs)

	go func() {
		select {
		case <-mounter.c.Ready:
			// We wait for the mounter to finish asynchronously with
			// calling fs.Serve() below, for the rare osxfuse case
			// where `mount(2)` makes a blocking STATFS call before
			// completing.  If we aren't listening for the STATFS call
			// when this happens, there will be a deadlock, and the
			// mount will silently fail after two minutes.  See
			// KBFS-2409.
			err = mounter.c.MountError
			if err != nil {
				log.CWarningf(ctx, "Mount error: %+v", err)
				cancel()
				return
			}
			log.CDebugf(ctx, "Mount ready")
		case <-ctx.Done():
		}
	}()

	log.CDebugf(ctx, "Serving filesystem")
	if err = fs.Serve(ctx); err != nil {
		return err
	}

	log.CDebugf(ctx, "Ending")
	return nil
}

// Start the filesystem
func Start(options StartOptions, kbCtx libkbfs.Context) *libfs.Error {
	// Hook simplefs implementation in.
	options.KbfsParams.CreateSimpleFSInstance = simplefs.NewSimpleFS

	log, err := libkbfs.InitLog(options.KbfsParams, kbCtx)
	if err != nil {
		return libfs.InitError(err.Error())
	}

	if options.RuntimeDir != "" {
		info := libkb.NewServiceInfo(libkbfs.Version, libkbfs.PrereleaseBuild, options.Label, os.Getpid())
		err := info.WriteFile(path.Join(options.RuntimeDir, "kbfs.info"), log)
		if err != nil {
			return libfs.InitError(err.Error())
		}
	}

	log.Debug("Initializing")
	mi := libfs.NewMountInterrupter(log)
	ctx := context.Background()
	config, err := libkbfs.Init(
		ctx, kbCtx, options.KbfsParams, nil, mi.Done, log)
	if err != nil {
		return libfs.InitError(err.Error())
	}
	defer libkbfs.Shutdown()

	if options.SkipMount {
		log.Debug("Skipping mounting filesystem")
	} else {
		err = startMounting(ctx, config, options, log, mi)
		if err != nil {
			// Abort on error if we were force mounting, otherwise continue.
			if options.ForceMount {
				// If we exit we might want to clean a mount behind us.
				mi.Done()
				return libfs.MountError(err.Error())
			}
		}
	}
	mi.Wait()
	return nil
}
<|endoftext|>