<commit_before>package main

import (
	"fmt"
	"strings"

	"github.com/pkg/errors"
	"github.com/spf13/cobra"

	"github.com/lxc/lxd/client"
	"github.com/lxc/lxd/shared/api"
	cli "github.com/lxc/lxd/shared/cmd"
	"github.com/lxc/lxd/shared/validate"
)

type cmdRecover struct {
	global *cmdGlobal
}

func (c *cmdRecover) Command() *cobra.Command {
	cmd := &cobra.Command{}
	cmd.Use = "recover"
	cmd.Short = "Recover missing instances and volumes from existing and unknown storage pools"
	cmd.Long = `Description:
	Recover missing instances and volumes from existing and unknown storage pools

  This command is mostly used for disaster recovery. It will ask you about unknown storage pools and attempt to
  access them, along with existing storage pools, and identify any missing instances and volumes that exist on the
  pools but are not in the LXD database. It will then offer to recreate these database records.
`
	cmd.RunE = c.Run

	return cmd
}

func (c *cmdRecover) Run(cmd *cobra.Command, args []string) error {
	// Quick checks.
	if len(args) > 0 {
		return fmt.Errorf("Invalid arguments")
	}

	d, err := lxd.ConnectLXDUnix("", nil)
	if err != nil {
		return err
	}

	server, _, err := d.GetServer()
	if err != nil {
		return err
	}

	// Get list of existing storage pools to scan.
	existingPools, err := d.GetStoragePools()
	if err != nil {
		return errors.Wrapf(err, "Failed getting existing storage pools")
	}

	fmt.Print("This LXD server currently has the following storage pools:\n")
	for _, existingPool := range existingPools {
		fmt.Printf(" - %s (backend=%q, source=%q)\n", existingPool.Name, existingPool.Driver, existingPool.Config["source"])
	}

	// Build up a list of unknown pools to scan.
	unknownPools := make([]api.StoragePoolsPost, 0, len(existingPools))

	var supportedDriverNames []string

	for {
		addUnknownPool, err := cli.AskBool("Would you like to recover another storage pool? (yes/no) [default=no]: ", "no")
		if err != nil {
			return err
		}

		if !addUnknownPool {
			break
		}

		// Get available storage drivers if not done already.
		if supportedDriverNames == nil {
			for _, supportedDriver := range server.Environment.StorageSupportedDrivers {
				supportedDriverNames = append(supportedDriverNames, supportedDriver.Name)
			}
		}

		unknownPool := api.StoragePoolsPost{
			StoragePoolPut: api.StoragePoolPut{
				Config: make(map[string]string),
			},
		}

		unknownPool.Name, err = cli.AskString("Name of the storage pool: ", "", validate.Required(func(value string) error {
			if value == "" {
				return fmt.Errorf("Pool name cannot be empty")
			}

			for _, p := range unknownPools {
				if value == p.Name {
					return fmt.Errorf("Storage pool %q is already on recover list", value)
				}
			}

			return nil
		}))
		if err != nil {
			return err
		}

		unknownPool.Driver, err = cli.AskString(fmt.Sprintf("Name of the storage backend (%s): ", strings.Join(supportedDriverNames, ", ")), "", validate.IsOneOf(supportedDriverNames...))
		if err != nil {
			return err
		}

		unknownPool.Config["source"], err = cli.AskString("Source of the storage pool (block device, volume group, dataset, path, ... as applicable): ", "", validate.IsNotEmpty)
		if err != nil {
			return err
		}

		for {
			var configKey, configValue string
			cli.AskString("Additional storage pool configuration property (KEY=VALUE, empty when done): ", "", validate.Optional(func(value string) error {
				configParts := strings.SplitN(value, "=", 2)
				if len(configParts) < 2 {
					return fmt.Errorf("Config option should be in the format KEY=VALUE")
				}

				configKey = configParts[0]
				configValue = configParts[1]

				return nil
			}))

			if configKey == "" {
				break
			}

			unknownPool.Config[configKey] = configValue
		}

		unknownPools = append(unknownPools, unknownPool)
	}

	fmt.Printf("The recovery process will be scanning the following storage pools:\n")
	for _, p := range existingPools {
		fmt.Printf(" - EXISTING: %q (backend=%q, source=%q)\n", p.Name, p.Driver, p.Config["source"])
	}

	for _, p := range unknownPools {
		fmt.Printf(" - NEW: %q (backend=%q, source=%q)\n", p.Name, p.Driver, p.Config["source"])
	}

	proceed, err := cli.AskBool("Would you like to continue with scanning for lost volumes? (yes/no) [default=yes]: ", "yes")
	if err != nil {
		return err
	}

	if !proceed {
		return nil
	}

	fmt.Print("Scanning for unknown volumes...\n")

	// Send /internal/recover/validate request to LXD.
	reqValidate := internalRecoverValidatePost{
		Pools: make([]api.StoragePoolsPost, 0, len(existingPools)+len(unknownPools)),
	}

	// Add existing pools to request.
	for _, p := range existingPools {
		reqValidate.Pools = append(reqValidate.Pools, api.StoragePoolsPost{
			Name: p.Name, // Only send existing pool name, the rest will be looked up on server.
		})
	}

	// Add unknown pools to request.
	reqValidate.Pools = append(reqValidate.Pools, unknownPools...)

	for {
		resp, _, err := d.RawQuery("POST", "/internal/recover/validate", reqValidate, "")
		if err != nil {
			return errors.Wrapf(err, "Failed validation request")
		}

		var res internalRecoverValidateResult

		err = resp.MetadataAsStruct(&res)
		if err != nil {
			return errors.Wrapf(err, "Failed parsing validation response")
		}

		if len(res.UnknownVolumes) > 0 {
			fmt.Print("The following unknown volumes have been found:\n")
			for _, unknownVol := range res.UnknownVolumes {
				fmt.Printf(" - %s %q on pool %q in project %q (includes %d snapshots)\n", strings.Title(unknownVol.Type), unknownVol.Name, unknownVol.Pool, unknownVol.Project, unknownVol.SnapshotCount)
			}
		}

		if len(res.DependencyErrors) > 0 {
			fmt.Print("You are currently missing the following:\n")

			for _, depErr := range res.DependencyErrors {
				fmt.Printf(" - %s\n", depErr)
			}

			cli.AskString("Please create those missing entries and then hit ENTER: ", "", validate.Optional())
		} else {
			if len(res.UnknownVolumes) <= 0 {
				fmt.Print("No unknown volumes found. Nothing to do.\n")
				return nil
			}

			break // Dependencies met.
		}
	}

	proceed, err = cli.AskBool("Would you like those to be recovered? (yes/no) [default=no]: ", "no")
	if err != nil {
		return err
	}

	if !proceed {
		return nil
	}

	fmt.Print("Starting recovery...\n")

	// Send /internal/recover/import request to LXD.
	reqImport := internalRecoverImportPost{
		Pools: reqValidate.Pools,
	}

	_, _, err = d.RawQuery("POST", "/internal/recover/import", reqImport, "")
	if err != nil {
		return errors.Wrapf(err, "Failed import request")
	}

	return nil
}
<commit_msg>lxd/main/recover: Don't offer the option to enter additional storage pools when clustered<commit_after>package main

import (
	"fmt"
	"strings"

	"github.com/pkg/errors"
	"github.com/spf13/cobra"

	"github.com/lxc/lxd/client"
	"github.com/lxc/lxd/shared/api"
	cli "github.com/lxc/lxd/shared/cmd"
	"github.com/lxc/lxd/shared/validate"
)

type cmdRecover struct {
	global *cmdGlobal
}

func (c *cmdRecover) Command() *cobra.Command {
	cmd := &cobra.Command{}
	cmd.Use = "recover"
	cmd.Short = "Recover missing instances and volumes from existing and unknown storage pools"
	cmd.Long = `Description:
	Recover missing instances and volumes from existing and unknown storage pools

  This command is mostly used for disaster recovery. It will ask you about unknown storage pools and attempt to
  access them, along with existing storage pools, and identify any missing instances and volumes that exist on the
  pools but are not in the LXD database. It will then offer to recreate these database records.
`
	cmd.RunE = c.Run

	return cmd
}

func (c *cmdRecover) Run(cmd *cobra.Command, args []string) error {
	// Quick checks.
	if len(args) > 0 {
		return fmt.Errorf("Invalid arguments")
	}

	d, err := lxd.ConnectLXDUnix("", nil)
	if err != nil {
		return err
	}

	server, _, err := d.GetServer()
	if err != nil {
		return err
	}

	isClustered := d.IsClustered()

	// Get list of existing storage pools to scan.
	existingPools, err := d.GetStoragePools()
	if err != nil {
		return errors.Wrapf(err, "Failed getting existing storage pools")
	}

	fmt.Print("This LXD server currently has the following storage pools:\n")
	for _, existingPool := range existingPools {
		fmt.Printf(" - %s (backend=%q, source=%q)\n", existingPool.Name, existingPool.Driver, existingPool.Config["source"])
	}

	unknownPools := make([]api.StoragePoolsPost, 0, len(existingPools))

	// Build up a list of unknown pools to scan.
	// We don't offer this option if the server is clustered because we don't allow creating storage pools on
	// an individual server when clustered.
	if !isClustered {
		var supportedDriverNames []string

		for {
			addUnknownPool, err := cli.AskBool("Would you like to recover another storage pool? (yes/no) [default=no]: ", "no")
			if err != nil {
				return err
			}

			if !addUnknownPool {
				break
			}

			// Get available storage drivers if not done already.
			if supportedDriverNames == nil {
				for _, supportedDriver := range server.Environment.StorageSupportedDrivers {
					supportedDriverNames = append(supportedDriverNames, supportedDriver.Name)
				}
			}

			unknownPool := api.StoragePoolsPost{
				StoragePoolPut: api.StoragePoolPut{
					Config: make(map[string]string),
				},
			}

			unknownPool.Name, err = cli.AskString("Name of the storage pool: ", "", validate.Required(func(value string) error {
				if value == "" {
					return fmt.Errorf("Pool name cannot be empty")
				}

				for _, p := range unknownPools {
					if value == p.Name {
						return fmt.Errorf("Storage pool %q is already on recover list", value)
					}
				}

				return nil
			}))
			if err != nil {
				return err
			}

			unknownPool.Driver, err = cli.AskString(fmt.Sprintf("Name of the storage backend (%s): ", strings.Join(supportedDriverNames, ", ")), "", validate.IsOneOf(supportedDriverNames...))
			if err != nil {
				return err
			}

			unknownPool.Config["source"], err = cli.AskString("Source of the storage pool (block device, volume group, dataset, path, ... as applicable): ", "", validate.IsNotEmpty)
			if err != nil {
				return err
			}

			for {
				var configKey, configValue string
				cli.AskString("Additional storage pool configuration property (KEY=VALUE, empty when done): ", "", validate.Optional(func(value string) error {
					configParts := strings.SplitN(value, "=", 2)
					if len(configParts) < 2 {
						return fmt.Errorf("Config option should be in the format KEY=VALUE")
					}

					configKey = configParts[0]
					configValue = configParts[1]

					return nil
				}))

				if configKey == "" {
					break
				}

				unknownPool.Config[configKey] = configValue
			}

			unknownPools = append(unknownPools, unknownPool)
		}
	}

	fmt.Printf("The recovery process will be scanning the following storage pools:\n")
	for _, p := range existingPools {
		fmt.Printf(" - EXISTING: %q (backend=%q, source=%q)\n", p.Name, p.Driver, p.Config["source"])
	}

	for _, p := range unknownPools {
		fmt.Printf(" - NEW: %q (backend=%q, source=%q)\n", p.Name, p.Driver, p.Config["source"])
	}

	proceed, err := cli.AskBool("Would you like to continue with scanning for lost volumes? (yes/no) [default=yes]: ", "yes")
	if err != nil {
		return err
	}

	if !proceed {
		return nil
	}

	fmt.Print("Scanning for unknown volumes...\n")

	// Send /internal/recover/validate request to LXD.
	reqValidate := internalRecoverValidatePost{
		Pools: make([]api.StoragePoolsPost, 0, len(existingPools)+len(unknownPools)),
	}

	// Add existing pools to request.
	for _, p := range existingPools {
		reqValidate.Pools = append(reqValidate.Pools, api.StoragePoolsPost{
			Name: p.Name, // Only send existing pool name, the rest will be looked up on server.
		})
	}

	// Add unknown pools to request.
	reqValidate.Pools = append(reqValidate.Pools, unknownPools...)

	for {
		resp, _, err := d.RawQuery("POST", "/internal/recover/validate", reqValidate, "")
		if err != nil {
			return errors.Wrapf(err, "Failed validation request")
		}

		var res internalRecoverValidateResult

		err = resp.MetadataAsStruct(&res)
		if err != nil {
			return errors.Wrapf(err, "Failed parsing validation response")
		}

		if len(res.UnknownVolumes) > 0 {
			fmt.Print("The following unknown volumes have been found:\n")
			for _, unknownVol := range res.UnknownVolumes {
				fmt.Printf(" - %s %q on pool %q in project %q (includes %d snapshots)\n", strings.Title(unknownVol.Type), unknownVol.Name, unknownVol.Pool, unknownVol.Project, unknownVol.SnapshotCount)
			}
		}

		if len(res.DependencyErrors) > 0 {
			fmt.Print("You are currently missing the following:\n")

			for _, depErr := range res.DependencyErrors {
				fmt.Printf(" - %s\n", depErr)
			}

			cli.AskString("Please create those missing entries and then hit ENTER: ", "", validate.Optional())
		} else {
			if len(res.UnknownVolumes) <= 0 {
				fmt.Print("No unknown volumes found. Nothing to do.\n")
				return nil
			}

			break // Dependencies met.
		}
	}

	proceed, err = cli.AskBool("Would you like those to be recovered? (yes/no) [default=no]: ", "no")
	if err != nil {
		return err
	}

	if !proceed {
		return nil
	}

	fmt.Print("Starting recovery...\n")

	// Send /internal/recover/import request to LXD.
	reqImport := internalRecoverImportPost{
		Pools: reqValidate.Pools,
	}

	_, _, err = d.RawQuery("POST", "/internal/recover/import", reqImport, "")
	if err != nil {
		return errors.Wrapf(err, "Failed import request")
	}

	return nil
}
<|endoftext|>


<commit_before><commit_msg>Tweaks:<commit_after><|endoftext|>


<commit_before>package micro

import (
	"fmt"
	"net"
	"strconv"
	"sync"
	"sync/atomic"
	"time"

	"github.com/shouyingo/consul"
	"github.com/shouyingo/micro/microproto"
	"github.com/vizee/timer"
)

type rpccontext struct {
	state  int32 // 0: undone, 1: done
	c      *Client
	method string
	id     uint64
	fn     Callback
}

func (c *rpccontext) done() bool {
	return atomic.CompareAndSwapInt32(&c.state, 0, 1)
}

func (c *rpccontext) OnTime() {
	if c.done() {
		c.c.removeCtx(c.id)
		c.fn(c.method, CodeTimeout, nil)
	}
}

type serviceEntry struct {
	state int32 // 0: alive, 1: down
	id    string
	name  string
	addr  string
}

type Client struct {
	deps map[string]*conngroup // service => group

	nreqid  uint64
	ctxmu   sync.Mutex
	ctxs    map[uint64]*rpccontext // reqid => request
	timeout timer.Timer

	svcmu sync.RWMutex
	svcs  map[string]*serviceEntry // service-id => entry

	registry *consul.Client
	OnError  ErrFunc
}

func (c *Client) removeCtx(reqid uint64) *rpccontext {
	c.ctxmu.Lock()
	ctx := c.ctxs[reqid]
	if ctx != nil {
		delete(c.ctxs, reqid)
	}
	c.ctxmu.Unlock()
	return ctx
}

func (c *Client) handleServer(svc *serviceEntry) {
	g := c.deps[svc.name]
	if g == nil {
		return
	}
	for atomic.LoadInt32(&svc.state) == 0 {
		nc, err := net.Dial("tcp", svc.addr)
		if err != nil {
			if eh := c.OnError; eh != nil {
				eh("dial", err)
			}
			time.Sleep(time.Second)
			continue
		}
		server := goconn(nc)
		g.add(server)
	mainloop:
		for {
			select {
			case msg := <-server.chrq:
				resp, ok := msg.(*microproto.Response)
				if !ok {
					continue
				}
				ctx := c.removeCtx(resp.Id)
				if ctx != nil && ctx.done() {
					ctx.fn(ctx.method, int(resp.Code), resp.Result)
				}
			case <-server.chdown:
				break mainloop
			}
		}
		g.remove(server)
	}
}

func (d *Client) onWatch(action int, id string, svc *consul.CatalogService) {
	switch action {
	case 0:
		s := &serviceEntry{
			id:   id,
			name: svc.ServiceName,
			addr: net.JoinHostPort(svc.ServiceAddress, strconv.Itoa(svc.ServicePort)),
		}
		d.svcmu.Lock()
		old := d.svcs[id]
		d.svcs[id] = s
		d.svcmu.Unlock()
		if old != nil {
			atomic.StoreInt32(&old.state, 1)
		}
		go d.handleServer(s)
	case 2:
		d.svcmu.Lock()
		s := d.svcs[id]
		if s != nil {
			atomic.StoreInt32(&s.state, 1)
			delete(d.svcs, id)
		}
		d.svcmu.Unlock()
	}
}

func (c *Client) watchService(service string) {
	for {
		err := c.registry.Watch(service, "", c.onWatch)
		if err != nil {
			if eh := c.OnError; eh != nil {
				eh("watch", err)
			}
		}
		time.Sleep(time.Second)
	}
}

func (c *Client) Call(service string, method string, params []byte, fn Callback) error {
	g := c.deps[service]
	if g == nil {
		return fmt.Errorf("%s not dependent service", service)
	}

	reqid := atomic.AddUint64(&c.nreqid, 1)
	ctx := &rpccontext{
		c:      c,
		method: method,
		id:     reqid,
		fn:     fn,
	}
	c.ctxmu.Lock()
	c.ctxs[reqid] = ctx
	c.ctxmu.Unlock()

	mc := g.randone()
	if mc == nil {
		c.removeCtx(reqid)
		return fmt.Errorf("service(%s) no alive session", service)
	}

	c.timeout.Add(ctx, time.Now().Add(rpcTimeout).UnixNano())
	ok := mc.send(&microproto.Request{
		Method: service + "." + method,
		Id:     reqid,
		Params: params,
	})
	if ok || !ctx.done() {
		return nil
	}
	c.removeCtx(reqid)
	return fmt.Errorf("service(%s) send request failed", service)
}

func NewClient(r *consul.Client, depends []string) *Client {
	deps := make(map[string]*conngroup, len(depends))
	for _, dep := range depends {
		deps[dep] = &conngroup{}
	}
	c := &Client{
		deps:     deps,
		ctxs:     make(map[uint64]*rpccontext),
		svcs:     make(map[string]*serviceEntry),
		registry: r,
	}
	for svc := range c.deps {
		go c.watchService(svc)
	}
	return c
}
<commit_msg>update consul.Watch => WatchCatalogService<commit_after>package micro

import (
	"fmt"
	"net"
	"strconv"
	"sync"
	"sync/atomic"
	"time"

	"github.com/shouyingo/consul"
	"github.com/shouyingo/micro/microproto"
	"github.com/vizee/timer"
)

type rpccontext struct {
	state  int32 // 0: undone, 1: done
	c      *Client
	method string
	id     uint64
	fn     Callback
}

func (c *rpccontext) done() bool {
	return atomic.CompareAndSwapInt32(&c.state, 0, 1)
}

func (c *rpccontext) OnTime() {
	if c.done() {
		c.c.removeCtx(c.id)
		c.fn(c.method, CodeTimeout, nil)
	}
}

type serviceEntry struct {
	state int32 // 0: alive, 1: down
	id    string
	name  string
	addr  string
}

type Client struct {
	deps map[string]*conngroup // service => group

	nreqid  uint64
	ctxmu   sync.Mutex
	ctxs    map[uint64]*rpccontext // reqid => request
	timeout timer.Timer

	svcmu sync.RWMutex
	svcs  map[string]*serviceEntry // service-id => entry

	registry *consul.Client
	OnError  ErrFunc
}

func (c *Client) removeCtx(reqid uint64) *rpccontext {
	c.ctxmu.Lock()
	ctx := c.ctxs[reqid]
	if ctx != nil {
		delete(c.ctxs, reqid)
	}
	c.ctxmu.Unlock()
	return ctx
}

func (c *Client) handleServer(svc *serviceEntry) {
	g := c.deps[svc.name]
	if g == nil {
		return
	}
	for atomic.LoadInt32(&svc.state) == 0 {
		nc, err := net.Dial("tcp", svc.addr)
		if err != nil {
			if eh := c.OnError; eh != nil {
				eh("dial", err)
			}
			time.Sleep(time.Second)
			continue
		}
		server := goconn(nc)
		g.add(server)
	mainloop:
		for {
			select {
			case msg := <-server.chrq:
				resp, ok := msg.(*microproto.Response)
				if !ok {
					continue
				}
				ctx := c.removeCtx(resp.Id)
				if ctx != nil && ctx.done() {
					ctx.fn(ctx.method, int(resp.Code), resp.Result)
				}
			case <-server.chdown:
				break mainloop
			}
		}
		g.remove(server)
	}
}

func (d *Client) onWatch(action int, id string, svc *consul.CatalogService) {
	switch action {
	case 0:
		s := &serviceEntry{
			id:   id,
			name: svc.ServiceName,
			addr: net.JoinHostPort(svc.ServiceAddress, strconv.Itoa(svc.ServicePort)),
		}
		d.svcmu.Lock()
		old := d.svcs[id]
		d.svcs[id] = s
		d.svcmu.Unlock()
		if old != nil {
			atomic.StoreInt32(&old.state, 1)
		}
		go d.handleServer(s)
	case 2:
		d.svcmu.Lock()
		s := d.svcs[id]
		if s != nil {
			atomic.StoreInt32(&s.state, 1)
			delete(d.svcs, id)
		}
		d.svcmu.Unlock()
	}
}

func (c *Client) watchService(service string) {
	for {
		err := c.registry.WatchCatalogService(service, "", c.onWatch)
		if err != nil {
			if eh := c.OnError; eh != nil {
				eh("watch", err)
			}
		}
		time.Sleep(time.Second)
	}
}

func (c *Client) Call(service string, method string, params []byte, fn Callback) error {
	g := c.deps[service]
	if g == nil {
		return fmt.Errorf("%s not dependent service", service)
	}

	reqid := atomic.AddUint64(&c.nreqid, 1)
	ctx := &rpccontext{
		c:      c,
		method: method,
		id:     reqid,
		fn:     fn,
	}
	c.ctxmu.Lock()
	c.ctxs[reqid] = ctx
	c.ctxmu.Unlock()

	mc := g.randone()
	if mc == nil {
		c.removeCtx(reqid)
		return fmt.Errorf("service(%s) no alive session", service)
	}

	c.timeout.Add(ctx, time.Now().Add(rpcTimeout).UnixNano())
	ok := mc.send(&microproto.Request{
		Method: service + "." + method,
		Id:     reqid,
		Params: params,
	})
	if ok || !ctx.done() {
		return nil
	}
	c.removeCtx(reqid)
	return fmt.Errorf("service(%s) send request failed", service)
}

func NewClient(r *consul.Client, depends []string) *Client {
	deps := make(map[string]*conngroup, len(depends))
	for _, dep := range depends {
		deps[dep] = &conngroup{}
	}
	c := &Client{
		deps:     deps,
		ctxs:     make(map[uint64]*rpccontext),
		svcs:     make(map[string]*serviceEntry),
		registry: r,
	}
	for svc := range c.deps {
		go c.watchService(svc)
	}
	return c
}
<|endoftext|>


<commit_before>package endpoint

import (
	"crypto/tls"
	"crypto/x509"
	"crypto/rand"
	"fmt"
	"io/ioutil"
	"sync"
	"time"

	paho "github.com/eclipse/paho.mqtt.golang"
	"github.com/tidwall/tile38/internal/log"
)

const (
	mqttExpiresAfter = time.Second * 30
)

// MQTTConn is an endpoint connection
type MQTTConn struct {
	mu   sync.Mutex
	ep   Endpoint
	conn paho.Client
	ex   bool
	t    time.Time
}

// Expired returns true if the connection has expired
func (conn *MQTTConn) Expired() bool {
	conn.mu.Lock()
	defer conn.mu.Unlock()
	if !conn.ex {
		if time.Now().Sub(conn.t) > mqttExpiresAfter {
			conn.close()
			conn.ex = true
		}
	}
	return conn.ex
}

func (conn *MQTTConn) close() {
	if conn.conn != nil {
		if conn.conn.IsConnected() {
			conn.conn.Disconnect(250)
		}

		conn.conn = nil
	}
}

// Send sends a message
func (conn *MQTTConn) Send(msg string) error {
	conn.mu.Lock()
	defer conn.mu.Unlock()

	if conn.ex {
		return errExpired
	}
	conn.t = time.Now()

	if conn.conn == nil {
		uri := fmt.Sprintf("tcp://%s:%d", conn.ep.MQTT.Host, conn.ep.MQTT.Port)
		ops := paho.NewClientOptions()
		if conn.ep.MQTT.CertFile != "" || conn.ep.MQTT.KeyFile != "" ||
			conn.ep.MQTT.CACertFile != "" {
			var config tls.Config
			if conn.ep.MQTT.CertFile != "" || conn.ep.MQTT.KeyFile != "" {
				cert, err := tls.LoadX509KeyPair(conn.ep.MQTT.CertFile,
					conn.ep.MQTT.KeyFile)
				if err != nil {
					return err
				}
				config.Certificates = append(config.Certificates, cert)
			}
			if conn.ep.MQTT.CACertFile != "" {
				// Load CA cert
				caCert, err := ioutil.ReadFile(conn.ep.MQTT.CACertFile)
				if err != nil {
					return err
				}
				caCertPool := x509.NewCertPool()
				caCertPool.AppendCertsFromPEM(caCert)
				config.RootCAs = caCertPool
			}
			ops = ops.SetTLSConfig(&config)
		}
		//generate UUID for the client-id.
		b := make([]byte, 16)
		_, err := rand.Read(b)
		if err != nil {
			log.Debugf("Failed to generate guid for the mqtt client. The endpoint will not work")
			return err;
		}
		uuid := fmt.Sprintf("tile38-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])

		ops = ops.SetClientID(uuid).AddBroker(uri)
		c := paho.NewClient(ops)

		if token := c.Connect(); token.Wait() && token.Error() != nil {
			return token.Error()
		}

		conn.conn = c
	}

	t := conn.conn.Publish(conn.ep.MQTT.QueueName, conn.ep.MQTT.Qos,
		conn.ep.MQTT.Retained, msg)
	t.Wait()

	if t.Error() != nil {
		conn.close()
		return t.Error()
	}

	return nil
}

func newMQTTConn(ep Endpoint) *MQTTConn {
	return &MQTTConn{
		ep: ep,
		t:  time.Now(),
	}
}
<commit_msg>randomize mqtt client id with math/rand<commit_after>package endpoint

import (
	"crypto/tls"
	"crypto/x509"
	"math/rand"
	"fmt"
	"io/ioutil"
	"sync"
	"time"

	paho "github.com/eclipse/paho.mqtt.golang"
	"github.com/tidwall/tile38/internal/log"
)

const (
	mqttExpiresAfter = time.Second * 30
)

// MQTTConn is an endpoint connection
type MQTTConn struct {
	mu   sync.Mutex
	ep   Endpoint
	conn paho.Client
	ex   bool
	t    time.Time
}

// Expired returns true if the connection has expired
func (conn *MQTTConn) Expired() bool {
	conn.mu.Lock()
	defer conn.mu.Unlock()
	if !conn.ex {
		if time.Now().Sub(conn.t) > mqttExpiresAfter {
			conn.close()
			conn.ex = true
		}
	}
	return conn.ex
}

func (conn *MQTTConn) close() {
	if conn.conn != nil {
		if conn.conn.IsConnected() {
			conn.conn.Disconnect(250)
		}

		conn.conn = nil
	}
}

// Send sends a message
func (conn *MQTTConn) Send(msg string) error {
	conn.mu.Lock()
	defer conn.mu.Unlock()

	if conn.ex {
		return errExpired
	}
	conn.t = time.Now()

	if conn.conn == nil {
		uri := fmt.Sprintf("tcp://%s:%d", conn.ep.MQTT.Host, conn.ep.MQTT.Port)
		ops := paho.NewClientOptions()
		if conn.ep.MQTT.CertFile != "" || conn.ep.MQTT.KeyFile != "" ||
			conn.ep.MQTT.CACertFile != "" {
			var config tls.Config
			if conn.ep.MQTT.CertFile != "" || conn.ep.MQTT.KeyFile != "" {
				cert, err := tls.LoadX509KeyPair(conn.ep.MQTT.CertFile,
					conn.ep.MQTT.KeyFile)
				if err != nil {
					return err
				}
				config.Certificates = append(config.Certificates, cert)
			}
			if conn.ep.MQTT.CACertFile != "" {
				// Load CA cert
				caCert, err := ioutil.ReadFile(conn.ep.MQTT.CACertFile)
				if err != nil {
					return err
				}
				caCertPool := x509.NewCertPool()
				caCertPool.AppendCertsFromPEM(caCert)
				config.RootCAs = caCertPool
			}
			ops = ops.SetTLSConfig(&config)
		}
		//generate UUID for the client-id.
		b := make([]byte, 16)
		_, err := rand.Read(b)
		if err != nil {
			log.Debugf("Failed to generate guid for the mqtt client. The endpoint will not work")
			return err;
		}
		uuid := fmt.Sprintf("tile38-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])

		ops = ops.SetClientID(uuid).AddBroker(uri)
		c := paho.NewClient(ops)

		if token := c.Connect(); token.Wait() && token.Error() != nil {
			return token.Error()
		}

		conn.conn = c
	}

	t := conn.conn.Publish(conn.ep.MQTT.QueueName, conn.ep.MQTT.Qos,
		conn.ep.MQTT.Retained, msg)
	t.Wait()

	if t.Error() != nil {
		conn.close()
		return t.Error()
	}

	return nil
}

func newMQTTConn(ep Endpoint) *MQTTConn {
	return &MQTTConn{
		ep: ep,
		t:  time.Now(),
	}
}
<|endoftext|>


<commit_before><commit_msg>Update backup.go<commit_after><|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"path/filepath"
	"strconv"
	"strings"
	"github.com/nutrun/lentil"
)

type Client struct {
	q       *lentil.Beanstalkd
	verbose bool
}

func NewClient(verbose bool) (*Client, error) {
	this := new(Client)
	q, err := lentil.Dial(Config.QueueAddr)
	if err != nil {
		return nil, err
	}
	this.q = q
	this.verbose = verbose
	return this, nil
}

func isValidMessage(msg map[string]interface{}) error {
    _, found_executable := msg["executable"]
    _, found_cmd := msg["cmd"]
    if found_executable && found_cmd {
        return errors.New("Found both executable and cmd in message. Don't know which one to use")
    }
    if !found_executable && !found_cmd {
        return errors.New("Neither executable nor cmd field provided in message")
    }
    return nil
}

func (this *Client) put(cmd, executable string, arguments []string, mailto, workdir, out, tube string, priority, delay int) error {
    msg := make(map[string]interface{})
    msg["cmd"] = cmd

	msg["executable"] = executable

    argument_json, e := json.Marshal(arguments)
    if e != nil {
        panic(e)
    }
	msg["arguments"] = string(argument_json)

    msg["tube"] = tube
	msg["pri"] = fmt.Sprintf("%d", priority) // Not used except for debugging
	if tube == "" {
		return errors.New("Missing required param -tube")
	}
	msg["delay"] = fmt.Sprintf("%d", delay) // Not used except for debugging
	workdir, e = filepath.Abs(workdir)
	if e != nil {
		return e
	}
	msg["workdir"] = workdir
	msg["out"] = out

    if e = isValidMessage(msg); e != nil {
        return e
    }

	message, e := json.Marshal(msg)
	if this.verbose {
		log.Printf("QUEUEING UP: %s\n", message)
	}
	if e != nil {
		return e
	}
	if tube != "default" {
		e = this.q.Use(tube)
		if e != nil {
			return e
		}
	}
	_, e = this.q.Put(priority, delay, 60*60, message) // An hour TTR?
	return e
}

func (this *Client) putMany(input []byte) error {
	jobs := make([]map[string]interface{}, 0)
	e := json.Unmarshal(input, &jobs)
	if e != nil {
		return e
	}
	for _, job := range jobs {
        if e := isValidMessage(job); e != nil {
            panic(e)
        }
		priority := 0
		if priorityStr, exists := job["pri"]; exists {
			priority, e = strconv.Atoi(priorityStr.(string))
			if e != nil {
				return e
			}
		}
		delay := 0
		if delayStr, exists := job["delay"]; exists {
			delay, e = strconv.Atoi(delayStr.(string))
			if e != nil {
				return e
			}
		}
		out, exists := job["out"]
		if !exists {
			out = "/dev/null"
		}
		workdir := "/tmp"
		dir, exists := job["workdir"]
		if exists {
			workdir = dir.(string)
		}

		e = this.put(job["cmd"].(string), job["executable"].(string), job["arguments"].([]string),
                     job["mailto"].(string), workdir, out.(string), job["tube"].(string),
                     priority, delay)
		if e != nil {
			return e
		}
	}
	return nil
}

func (this *Client) stats() error {
	q := NewJobQueue(this.q, false, make([]string, 0))
	stats, err := json.Marshal(q)
	if err != nil {
		return err
	}
	buffer := bytes.NewBufferString("")
	err = json.Indent(buffer, stats, "", "\t")
	if err != nil {
		return err
	}
	fmt.Printf("%s\n", buffer.String())
	return nil
}

func (this *Client) drain(tube string) error {
	for _, tube := range strings.Split(tube, ",") {
		_, err := this.q.Watch(tube)
		if err != nil {
			return err
		}
		_, err = this.q.Ignore("default")
		if err != nil {
			return err
		}
		for job, err := this.q.ReserveWithTimeout(0); err == nil; job, err = this.q.ReserveWithTimeout(0) {
			log.Printf("DRAINED: %s", job.Body)
			this.q.Delete(job.Id)
		}
		if err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>created message struct. got it to compile. WIP<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"path/filepath"
	"strings"
	"github.com/nutrun/lentil"
)

type Client struct {
	q       *lentil.Beanstalkd
	verbose bool
}

func NewClient(verbose bool) (*Client, error) {
	this := new(Client)
	q, err := lentil.Dial(Config.QueueAddr)
	if err != nil {
		return nil, err
	}
	this.q = q
	this.verbose = verbose
	return this, nil
}

type Message struct {
    Command     string      `json:command`
    Executable  string      `json:executable`
    Arguments   []string    `json:arguments`
    Mailto      string      `json:mailto`
    Workdir     string      `json:workdir`
    Out         string      `json:out`
    Tube        string      `json:tube`
    Priority    int         `json:priority`
    Delay       int         `json:delay`
}

func isValidMessage(msg *Message) error {
    if msg.Command != "" && msg.Executable != "" {
        return errors.New("Found both executable and cmd in message. Don't know which one to use")
    }
    if msg.Command == "" && msg.Executable == "" {
        return errors.New("Neither executable nor cmd field provided in message")
    }
    if msg.Tube == "" {
		return errors.New("Missing required param -tube")
    }
    return nil
}

func (this *Client) put_message(msg *Message) error {
    if e := isValidMessage(msg); e != nil {
        return e
    }

	message, e := json.Marshal(msg)
	if this.verbose {
		log.Printf("QUEUEING UP: %s\n", message)
	}
	if e != nil {
		return e
	}
	if msg.Tube != "default" {
		e = this.q.Use(msg.Tube)
		if e != nil {
			return e
		}
	}
	_, e = this.q.Put(msg.Priority, msg.Delay, 60*60, message) // An hour TTR?
	return e
}

func (this *Client) put(cmd, executable string, arguments []string, mailto string, workdir, out, tube string, priority, delay int) error {
    workdir, e := filepath.Abs(workdir)
	if e != nil {
		return e
	}
    msg := &Message{cmd, executable, arguments, mailto, workdir, out, tube, priority, delay}
    return this.put_message(msg)
}

func (this *Client) putMany(input []byte) error {
	jobs := make([]*Message, 0)
	e := json.Unmarshal(input, &jobs)
	if e != nil {
		return e
	}
	for _, job := range jobs {
        if e := isValidMessage(job); e != nil {
            panic(e)
        }
        if job.Out == "" {
            job.Out = "/dev/null"
        }
		if job.Workdir == "" {
            job.Workdir = "/tmp"
        }

		e = this.put_message(job)
		if e != nil {
			return e
		}
	}
	return nil
}

func (this *Client) stats() error {
	q := NewJobQueue(this.q, false, make([]string, 0))
	stats, err := json.Marshal(q)
	if err != nil {
		return err
	}
	buffer := bytes.NewBufferString("")
	err = json.Indent(buffer, stats, "", "\t")
	if err != nil {
		return err
	}
	fmt.Printf("%s\n", buffer.String())
	return nil
}

func (this *Client) drain(tube string) error {
	for _, tube := range strings.Split(tube, ",") {
		_, err := this.q.Watch(tube)
		if err != nil {
			return err
		}
		_, err = this.q.Ignore("default")
		if err != nil {
			return err
		}
		for job, err := this.q.ReserveWithTimeout(0); err == nil; job, err = this.q.ReserveWithTimeout(0) {
			log.Printf("DRAINED: %s", job.Body)
			this.q.Delete(job.Id)
		}
		if err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package health

import (
	"context"
	"time"

	"github.com/GoogleCloudPlatform/cloud-run-release-manager/internal/config"
	"github.com/GoogleCloudPlatform/cloud-run-release-manager/internal/metrics"
	"github.com/GoogleCloudPlatform/cloud-run-release-manager/internal/util"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// DiagnosisResult is a possible result after a diagnosis.
type DiagnosisResult int

// Possible diagnosis results.
const (
	Unknown DiagnosisResult = iota
	Inconclusive
	Healthy
	Unhealthy
)

func (d DiagnosisResult) String() string {
	switch d {
	case Inconclusive:
		return "inconclusive"
	case Healthy:
		return "healthy"
	case Unhealthy:
		return "unhealthy"
	default:
		return "unknown"
	}
}

// Diagnosis is the information about the health of the revision.
type Diagnosis struct {
	OverallResult DiagnosisResult
	CheckResults  []CheckResult
}

// CheckResult is information about a metrics criteria check.
type CheckResult struct {
	Threshold     float64
	ActualValue   float64
	IsCriteriaMet bool
}

// Diagnose attempts to determine the health of a revision.
//
// If no health criteria is specified or the size of the health criteria and the
// actual values are not the same, the diagnosis is Unknown and an error is
// returned.
//
// If the minimum number of requests is not met, then health cannot be
// determined and diagnosis is Inconclusive.
//
// Otherwise, all metrics criteria are checked to determine whether the revision
// is healthy or not.
func Diagnose(ctx context.Context, healthCriteria []config.HealthCriterion, actualValues []float64) (Diagnosis, error) {
	logger := util.LoggerFrom(ctx)
	if len(healthCriteria) != len(actualValues) {
		return Diagnosis{Unknown, nil}, errors.New("the size of health criteria is not the same to the size of the actual metrics values")
	}
	if len(healthCriteria) == 0 {
		return Diagnosis{Unknown, nil}, errors.New("health criteria must be specified")
	}

	diagnosis := Unknown
	var results []CheckResult
	for i, value := range actualValues {
		criteria := healthCriteria[i]
		logger := logger.WithFields(logrus.Fields{
			"metrics":     criteria.Metric,
			"percentile":  criteria.Percentile,
			"threshold":   criteria.Threshold,
			"actualValue": value,
		})
		isMet := isCriteriaMet(criteria.Metric, criteria.Threshold, value)

		// For unmet request count, return inconclusive and empty results.
		if !isMet && criteria.Metric == config.RequestCountMetricsCheck {
			logger.Debug("unmet criterion")
			diagnosis = Inconclusive
			results = nil
			break
		}

		result := CheckResult{Threshold: criteria.Threshold, ActualValue: value}
		if !isMet {
			logger.Debug("unmet criterion")
			diagnosis = Unhealthy
			results = append(results, result)
			continue
		}

		// Only switch to healthy once a first criteria is met.
		if diagnosis == Unknown && criteria.Metric != config.RequestCountMetricsCheck {
			diagnosis = Healthy
		}
		result.IsCriteriaMet = true
		results = append(results, result)
		logger.Debug("met criterion")
	}

	return Diagnosis{diagnosis, results}, nil
}

// CollectMetrics gets a metrics value for each of the given health criteria and
// returns a result for each criterion.
func CollectMetrics(ctx context.Context, provider metrics.Provider, offset time.Duration, healthCriteria []config.HealthCriterion) ([]float64, error) {
	if len(healthCriteria) == 0 {
		return nil, errors.New("health criteria must be specified")
	}
	var metricsValues []float64
	for _, criteria := range healthCriteria {
		var metricsValue float64
		var err error

		switch criteria.Metric {
		case config.RequestCountMetricsCheck:
			metricsValue, err = requestCount(ctx, provider, offset)
		case config.LatencyMetricsCheck:
			metricsValue, err = latency(ctx, provider, offset, criteria.Percentile)
		case config.ErrorRateMetricsCheck:
			metricsValue, err = errorRatePercent(ctx, provider, offset)
		default:
			return nil, errors.Errorf("unimplemented metrics %q", criteria.Metric)
		}

		if err != nil {
			return nil, errors.Wrapf(err, "failed to obtain metrics %q", criteria.Metric)
		}
		metricsValues = append(metricsValues, metricsValue)
	}

	return metricsValues, nil
}

// isCriteriaMet concludes if metrics criteria was met.
func isCriteriaMet(metricsType config.MetricsCheck, threshold float64, actualValue float64) bool {
	// Of all the supported metrics, only the threshold for request count has an
	// expected minimum value.
	if metricsType == config.RequestCountMetricsCheck {
		return actualValue >= threshold
	}
	return actualValue <= threshold
}

// requestCount returns the number of requests during the given offset.
func requestCount(ctx context.Context, provider metrics.Provider, offset time.Duration) (float64, error) {
	logger := util.LoggerFrom(ctx)
	logger.Debug("querying for request count metrics")
	count, err := provider.RequestCount(ctx, offset)
	return float64(count), errors.Wrap(err, "failed to get request count metrics")
}

// latency returns the latency for the given offset and percentile.
func latency(ctx context.Context, provider metrics.Provider, offset time.Duration, percentile float64) (float64, error) {
	alignerReducer, err := metrics.PercentileToAlignReduce(percentile)
	if err != nil {
		return 0, errors.Wrap(err, "failed to parse percentile")
	}

	logger := util.LoggerFrom(ctx).WithField("percentile", percentile)
	logger.Debug("querying for latency metrics")
	latency, err := provider.Latency(ctx, offset, alignerReducer)
	if err != nil {
		return 0, errors.Wrap(err, "failed to get latency metrics")
	}
	logger.WithField("value", latency).Debug("latency successfully retrieved")

	return latency, nil
}

// errorRatePercent returns the percentage of errors during the given offset.
func errorRatePercent(ctx context.Context, provider metrics.Provider, offset time.Duration) (float64, error) {
	logger := util.LoggerFrom(ctx)
	logger.Debug("querying for error rate metrics")
	rate, err := provider.ErrorRate(ctx, offset)
	if err != nil {
		return 0, errors.Wrap(err, "failed to get error rate metrics")
	}

	// Multiply rate by 100 to have a percentage.
	rate *= 100
	logger.WithField("value", rate).Debug("error rate successfully retrieved")
	return rate, nil
}
<commit_msg>health: Update message for met/unmet criterion (#100)<commit_after>package health

import (
	"context"
	"time"

	"github.com/GoogleCloudPlatform/cloud-run-release-manager/internal/config"
	"github.com/GoogleCloudPlatform/cloud-run-release-manager/internal/metrics"
	"github.com/GoogleCloudPlatform/cloud-run-release-manager/internal/util"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// DiagnosisResult is a possible result after a diagnosis.
type DiagnosisResult int

// Possible diagnosis results.
const (
	Unknown DiagnosisResult = iota
	Inconclusive
	Healthy
	Unhealthy
)

func (d DiagnosisResult) String() string {
	switch d {
	case Inconclusive:
		return "inconclusive"
	case Healthy:
		return "healthy"
	case Unhealthy:
		return "unhealthy"
	default:
		return "unknown"
	}
}

// Diagnosis is the information about the health of the revision.
type Diagnosis struct {
	OverallResult DiagnosisResult
	CheckResults  []CheckResult
}

// CheckResult is information about a metrics criteria check.
type CheckResult struct {
	Threshold     float64
	ActualValue   float64
	IsCriteriaMet bool
}

// Diagnose attempts to determine the health of a revision.
//
// If no health criteria is specified or the size of the health criteria and the
// actual values are not the same, the diagnosis is Unknown and an error is
// returned.
//
// If the minimum number of requests is not met, then health cannot be
// determined and diagnosis is Inconclusive.
//
// Otherwise, all metrics criteria are checked to determine whether the revision
// is healthy or not.
func Diagnose(ctx context.Context, healthCriteria []config.HealthCriterion, actualValues []float64) (Diagnosis, error) {
	logger := util.LoggerFrom(ctx)
	if len(healthCriteria) != len(actualValues) {
		return Diagnosis{Unknown, nil}, errors.New("the size of health criteria is not the same to the size of the actual metrics values")
	}
	if len(healthCriteria) == 0 {
		return Diagnosis{Unknown, nil}, errors.New("health criteria must be specified")
	}

	diagnosis := Unknown
	var results []CheckResult
	for i, value := range actualValues {
		criteria := healthCriteria[i]
		logger := logger.WithFields(logrus.Fields{
			"metrics":       criteria.Metric,
			"threshold":     criteria.Threshold,
			"expectedValue": value,
		})
		if criteria.Metric == config.LatencyMetricsCheck {
			logger = logger.WithField("percentile", criteria.Percentile)
		}

		isMet := isCriteriaMet(criteria.Metric, criteria.Threshold, value)

		// For unmet request count, return inconclusive and empty results.
		if !isMet && criteria.Metric == config.RequestCountMetricsCheck {
			logger.Debug("unmet criterion")
			diagnosis = Inconclusive
			results = nil
			break
		}

		result := CheckResult{Threshold: criteria.Threshold, ActualValue: value}
		if !isMet {
			logger.Debug("unmet criterion")
			diagnosis = Unhealthy
			results = append(results, result)
			continue
		}

		// Only switch to healthy once a first criteria is met.
		if diagnosis == Unknown && criteria.Metric != config.RequestCountMetricsCheck {
			diagnosis = Healthy
		}
		result.IsCriteriaMet = true
		results = append(results, result)
		logger.Debug("met criterion")
	}

	return Diagnosis{diagnosis, results}, nil
}

// CollectMetrics gets a metrics value for each of the given health criteria and
// returns a result for each criterion.
func CollectMetrics(ctx context.Context, provider metrics.Provider, offset time.Duration, healthCriteria []config.HealthCriterion) ([]float64, error) {
	if len(healthCriteria) == 0 {
		return nil, errors.New("health criteria must be specified")
	}
	var metricsValues []float64
	for _, criteria := range healthCriteria {
		var metricsValue float64
		var err error

		switch criteria.Metric {
		case config.RequestCountMetricsCheck:
			metricsValue, err = requestCount(ctx, provider, offset)
		case config.LatencyMetricsCheck:
			metricsValue, err = latency(ctx, provider, offset, criteria.Percentile)
		case config.ErrorRateMetricsCheck:
			metricsValue, err = errorRatePercent(ctx, provider, offset)
		default:
			return nil, errors.Errorf("unimplemented metrics %q", criteria.Metric)
		}

		if err != nil {
			return nil, errors.Wrapf(err, "failed to obtain metrics %q", criteria.Metric)
		}
		metricsValues = append(metricsValues, metricsValue)
	}

	return metricsValues, nil
}

// isCriteriaMet concludes if metrics criteria was met.
func isCriteriaMet(metricsType config.MetricsCheck, threshold float64, actualValue float64) bool {
	// Of all the supported metrics, only the threshold for request count has an
	// expected minimum value.
	if metricsType == config.RequestCountMetricsCheck {
		return actualValue >= threshold
	}
	return actualValue <= threshold
}

// requestCount returns the number of requests during the given offset.
func requestCount(ctx context.Context, provider metrics.Provider, offset time.Duration) (float64, error) {
	logger := util.LoggerFrom(ctx)
	logger.Debug("querying for request count metrics")
	count, err := provider.RequestCount(ctx, offset)
	return float64(count), errors.Wrap(err, "failed to get request count metrics")
}

// latency returns the latency for the given offset and percentile.
func latency(ctx context.Context, provider metrics.Provider, offset time.Duration, percentile float64) (float64, error) {
	alignerReducer, err := metrics.PercentileToAlignReduce(percentile)
	if err != nil {
		return 0, errors.Wrap(err, "failed to parse percentile")
	}

	logger := util.LoggerFrom(ctx).WithField("percentile", percentile)
	logger.Debug("querying for latency metrics")
	latency, err := provider.Latency(ctx, offset, alignerReducer)
	if err != nil {
		return 0, errors.Wrap(err, "failed to get latency metrics")
	}
	logger.WithField("value", latency).Debug("latency successfully retrieved")

	return latency, nil
}

// errorRatePercent returns the percentage of errors during the given offset.
func errorRatePercent(ctx context.Context, provider metrics.Provider, offset time.Duration) (float64, error) {
	logger := util.LoggerFrom(ctx)
	logger.Debug("querying for error rate metrics")
	rate, err := provider.ErrorRate(ctx, offset)
	if err != nil {
		return 0, errors.Wrap(err, "failed to get error rate metrics")
	}

	// Multiply rate by 100 to have a percentage.
	rate *= 100
	logger.WithField("value", rate).Debug("error rate successfully retrieved")
	return rate, nil
}
<|endoftext|>


<commit_before>package main

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

	"github.com/gorilla/mux"
	"github.com/pkg/errors"

	lxd "github.com/lxc/lxd/client"
	"github.com/lxc/lxd/lxd/cluster"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/util"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/version"
)

var profilesCmd = Command{
	name: "profiles",
	get:  profilesGet,
	post: profilesPost,
}

var profileCmd = Command{
	name:   "profiles/{name}",
	get:    profileGet,
	put:    profilePut,
	delete: profileDelete,
	post:   profilePost,
	patch:  profilePatch,
}

/* This is used for both profiles post and profile put */
func profilesGet(d *Daemon, r *http.Request) Response {
	project := projectParam(r)

	recursion := util.IsRecursionRequest(r)

	var result interface{}
	err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
		hasProfiles, err := tx.ProjectHasProfiles(project)
		if err != nil {
			return errors.Wrap(err, "Check project features")
		}

		if !hasProfiles {
			project = "default"
		}

		filter := db.ProfileFilter{
			Project: project,
		}
		if recursion {
			profiles, err := tx.ProfileList(filter)
			if err != nil {
				return err
			}
			apiProfiles := make([]*api.Profile, len(profiles))
			for i, profile := range profiles {
				apiProfiles[i] = db.ProfileToAPI(&profile)
			}

			result = apiProfiles
		} else {
			result, err = tx.ProfileURIs(filter)
		}
		return err
	})
	if err != nil {
		return SmartError(err)
	}

	return SyncResponse(true, result)
}

func profilesPost(d *Daemon, r *http.Request) Response {
	project := projectParam(r)
	req := api.ProfilesPost{}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		return BadRequest(err)
	}

	// Sanity checks
	if req.Name == "" {
		return BadRequest(fmt.Errorf("No name provided"))
	}

	_, profile, _ := d.cluster.ProfileGet(project, req.Name)
	if profile != nil {
		return BadRequest(fmt.Errorf("The profile already exists"))
	}

	if strings.Contains(req.Name, "/") {
		return BadRequest(fmt.Errorf("Profile names may not contain slashes"))
	}

	if shared.StringInSlice(req.Name, []string{".", ".."}) {
		return BadRequest(fmt.Errorf("Invalid profile name '%s'", req.Name))
	}

	err := containerValidConfig(d.os, req.Config, true, false)
	if err != nil {
		return BadRequest(err)
	}

	err = containerValidDevices(d.cluster, req.Devices, true, false)
	if err != nil {
		return BadRequest(err)
	}

	// Update DB entry
	err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
		profile := db.Profile{
			Project:     project,
			Name:        req.Name,
			Description: req.Description,
			Config:      req.Config,
			Devices:     req.Devices,
		}
		_, err := tx.ProfileCreate(profile)
		return err
	})
	if err != nil {
		return SmartError(
			fmt.Errorf("Error inserting %s into database: %s", req.Name, err))
	}

	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", version.APIVersion, req.Name))
}

func profileGet(d *Daemon, r *http.Request) Response {
	project := projectParam(r)
	name := mux.Vars(r)["name"]

	var resp *api.Profile

	err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
		hasProfiles, err := tx.ProjectHasProfiles(project)
		if err != nil {
			return errors.Wrap(err, "Check project features")
		}

		if !hasProfiles {
			project = "default"
		}

		profile, err := tx.ProfileGet(project, name)
		if err != nil {
			return errors.Wrap(err, "Fetch profile")
		}

		resp = db.ProfileToAPI(profile)

		return nil
	})
	if err != nil {
		return SmartError(err)
	}

	etag := []interface{}{resp.Config, resp.Description, resp.Devices}
	return SyncResponseETag(true, resp, etag)
}

func profilePut(d *Daemon, r *http.Request) Response {
	// Get the project
	project := projectParam(r)

	// Get the profile
	name := mux.Vars(r)["name"]

	if isClusterNotification(r) {
		// In this case the ProfilePut request payload contains
		// information about the old profile, since the new one has
		// already been saved in the database.
		old := api.ProfilePut{}
		err := json.NewDecoder(r.Body).Decode(&old)
		if err != nil {
			return BadRequest(err)
		}
		err = doProfileUpdateCluster(d, project, name, old)
		return SmartError(err)

	}

	id, profile, err := d.cluster.ProfileGet(project, name)
	if err != nil {
		return SmartError(fmt.Errorf("Failed to retrieve profile='%s'", name))
	}

	// Validate the ETag
	etag := []interface{}{profile.Config, profile.Description, profile.Devices}
	err = util.EtagCheck(r, etag)
	if err != nil {
		return PreconditionFailed(err)
	}

	req := api.ProfilePut{}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		return BadRequest(err)
	}

	err = doProfileUpdate(d, project, name, id, profile, req)

	if err == nil && !isClusterNotification(r) {
		// Notify all other nodes. If a node is down, it will be ignored.
		notifier, err := cluster.NewNotifier(d.State(), d.endpoints.NetworkCert(), cluster.NotifyAlive)
		if err != nil {
			return SmartError(err)
		}
		err = notifier(func(client lxd.ContainerServer) error {
			return client.UpdateProfile(name, profile.ProfilePut, "")
		})
		if err != nil {
			return SmartError(err)
		}
	}

	return SmartError(err)
}

func profilePatch(d *Daemon, r *http.Request) Response {
	// Get the project
	project := projectParam(r)

	// Get the profile
	name := mux.Vars(r)["name"]
	id, profile, err := d.cluster.ProfileGet(project, name)
	if err != nil {
		return SmartError(fmt.Errorf("Failed to retrieve profile='%s'", name))
	}

	// Validate the ETag
	etag := []interface{}{profile.Config, profile.Description, profile.Devices}
	err = util.EtagCheck(r, etag)
	if err != nil {
		return PreconditionFailed(err)
	}

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return InternalError(err)
	}

	rdr1 := ioutil.NopCloser(bytes.NewBuffer(body))
	rdr2 := ioutil.NopCloser(bytes.NewBuffer(body))

	reqRaw := shared.Jmap{}
	if err := json.NewDecoder(rdr1).Decode(&reqRaw); err != nil {
		return BadRequest(err)
	}

	req := api.ProfilePut{}
	if err := json.NewDecoder(rdr2).Decode(&req); err != nil {
		return BadRequest(err)
	}

	// Get Description
	_, err = reqRaw.GetString("description")
	if err != nil {
		req.Description = profile.Description
	}

	// Get Config
	if req.Config == nil {
		req.Config = profile.Config
	} else {
		for k, v := range profile.Config {
			_, ok := req.Config[k]
			if !ok {
				req.Config[k] = v
			}
		}
	}

	// Get Devices
	if req.Devices == nil {
		req.Devices = profile.Devices
	} else {
		for k, v := range profile.Devices {
			_, ok := req.Devices[k]
			if !ok {
				req.Devices[k] = v
			}
		}
	}

	return SmartError(doProfileUpdate(d, project, name, id, profile, req))
}

// The handler for the post operation.
func profilePost(d *Daemon, r *http.Request) Response {
	project := projectParam(r)
	name := mux.Vars(r)["name"]

	if name == "default" {
		return Forbidden(errors.New("The 'default' profile cannot be renamed"))
	}

	req := api.ProfilePost{}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		return BadRequest(err)
	}

	// Sanity checks
	if req.Name == "" {
		return BadRequest(fmt.Errorf("No name provided"))
	}

	if strings.Contains(req.Name, "/") {
		return BadRequest(fmt.Errorf("Profile names may not contain slashes"))
	}

	if shared.StringInSlice(req.Name, []string{".", ".."}) {
		return BadRequest(fmt.Errorf("Invalid profile name '%s'", req.Name))
	}

	err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
		// Check that the name isn't already in use
		_, err := tx.ProfileGet(project, req.Name)
		if err == nil {
			return fmt.Errorf("Name '%s' already in use", req.Name)
		}

		return tx.ProfileRename(project, name, req.Name)
	})
	if err != nil {
		return SmartError(err)
	}

	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", version.APIVersion, req.Name))
}

// The handler for the delete operation.
func profileDelete(d *Daemon, r *http.Request) Response {
	project := projectParam(r)
	name := mux.Vars(r)["name"]

	if name == "default" {
		return Forbidden(errors.New("The 'default' profile cannot be deleted"))
	}

	_, _, err := d.cluster.ProfileGet(project, name)
	if err != nil {
		return SmartError(err)
	}

	clist := getContainersWithProfile(d.State(), project, name)
	if len(clist) != 0 {
		return BadRequest(fmt.Errorf("Profile is currently in use"))
	}

	err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
		return tx.ProfileDelete(project, name)
	})
	if err != nil {
		return SmartError(err)
	}

	return EmptySyncResponse
}
<commit_msg>More efficient profile delete API handler<commit_after>package main

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

	"github.com/gorilla/mux"
	"github.com/pkg/errors"

	lxd "github.com/lxc/lxd/client"
	"github.com/lxc/lxd/lxd/cluster"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/util"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/version"
)

var profilesCmd = Command{
	name: "profiles",
	get:  profilesGet,
	post: profilesPost,
}

var profileCmd = Command{
	name:   "profiles/{name}",
	get:    profileGet,
	put:    profilePut,
	delete: profileDelete,
	post:   profilePost,
	patch:  profilePatch,
}

/* This is used for both profiles post and profile put */
func profilesGet(d *Daemon, r *http.Request) Response {
	project := projectParam(r)

	recursion := util.IsRecursionRequest(r)

	var result interface{}
	err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
		hasProfiles, err := tx.ProjectHasProfiles(project)
		if err != nil {
			return errors.Wrap(err, "Check project features")
		}

		if !hasProfiles {
			project = "default"
		}

		filter := db.ProfileFilter{
			Project: project,
		}
		if recursion {
			profiles, err := tx.ProfileList(filter)
			if err != nil {
				return err
			}
			apiProfiles := make([]*api.Profile, len(profiles))
			for i, profile := range profiles {
				apiProfiles[i] = db.ProfileToAPI(&profile)
			}

			result = apiProfiles
		} else {
			result, err = tx.ProfileURIs(filter)
		}
		return err
	})
	if err != nil {
		return SmartError(err)
	}

	return SyncResponse(true, result)
}

func profilesPost(d *Daemon, r *http.Request) Response {
	project := projectParam(r)
	req := api.ProfilesPost{}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		return BadRequest(err)
	}

	// Sanity checks
	if req.Name == "" {
		return BadRequest(fmt.Errorf("No name provided"))
	}

	_, profile, _ := d.cluster.ProfileGet(project, req.Name)
	if profile != nil {
		return BadRequest(fmt.Errorf("The profile already exists"))
	}

	if strings.Contains(req.Name, "/") {
		return BadRequest(fmt.Errorf("Profile names may not contain slashes"))
	}

	if shared.StringInSlice(req.Name, []string{".", ".."}) {
		return BadRequest(fmt.Errorf("Invalid profile name '%s'", req.Name))
	}

	err := containerValidConfig(d.os, req.Config, true, false)
	if err != nil {
		return BadRequest(err)
	}

	err = containerValidDevices(d.cluster, req.Devices, true, false)
	if err != nil {
		return BadRequest(err)
	}

	// Update DB entry
	err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
		profile := db.Profile{
			Project:     project,
			Name:        req.Name,
			Description: req.Description,
			Config:      req.Config,
			Devices:     req.Devices,
		}
		_, err := tx.ProfileCreate(profile)
		return err
	})
	if err != nil {
		return SmartError(
			fmt.Errorf("Error inserting %s into database: %s", req.Name, err))
	}

	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", version.APIVersion, req.Name))
}

func profileGet(d *Daemon, r *http.Request) Response {
	project := projectParam(r)
	name := mux.Vars(r)["name"]

	var resp *api.Profile

	err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
		hasProfiles, err := tx.ProjectHasProfiles(project)
		if err != nil {
			return errors.Wrap(err, "Check project features")
		}

		if !hasProfiles {
			project = "default"
		}

		profile, err := tx.ProfileGet(project, name)
		if err != nil {
			return errors.Wrap(err, "Fetch profile")
		}

		resp = db.ProfileToAPI(profile)

		return nil
	})
	if err != nil {
		return SmartError(err)
	}

	etag := []interface{}{resp.Config, resp.Description, resp.Devices}
	return SyncResponseETag(true, resp, etag)
}

func profilePut(d *Daemon, r *http.Request) Response {
	// Get the project
	project := projectParam(r)

	// Get the profile
	name := mux.Vars(r)["name"]

	if isClusterNotification(r) {
		// In this case the ProfilePut request payload contains
		// information about the old profile, since the new one has
		// already been saved in the database.
		old := api.ProfilePut{}
		err := json.NewDecoder(r.Body).Decode(&old)
		if err != nil {
			return BadRequest(err)
		}
		err = doProfileUpdateCluster(d, project, name, old)
		return SmartError(err)

	}

	id, profile, err := d.cluster.ProfileGet(project, name)
	if err != nil {
		return SmartError(fmt.Errorf("Failed to retrieve profile='%s'", name))
	}

	// Validate the ETag
	etag := []interface{}{profile.Config, profile.Description, profile.Devices}
	err = util.EtagCheck(r, etag)
	if err != nil {
		return PreconditionFailed(err)
	}

	req := api.ProfilePut{}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		return BadRequest(err)
	}

	err = doProfileUpdate(d, project, name, id, profile, req)

	if err == nil && !isClusterNotification(r) {
		// Notify all other nodes. If a node is down, it will be ignored.
		notifier, err := cluster.NewNotifier(d.State(), d.endpoints.NetworkCert(), cluster.NotifyAlive)
		if err != nil {
			return SmartError(err)
		}
		err = notifier(func(client lxd.ContainerServer) error {
			return client.UpdateProfile(name, profile.ProfilePut, "")
		})
		if err != nil {
			return SmartError(err)
		}
	}

	return SmartError(err)
}

func profilePatch(d *Daemon, r *http.Request) Response {
	// Get the project
	project := projectParam(r)

	// Get the profile
	name := mux.Vars(r)["name"]
	id, profile, err := d.cluster.ProfileGet(project, name)
	if err != nil {
		return SmartError(fmt.Errorf("Failed to retrieve profile='%s'", name))
	}

	// Validate the ETag
	etag := []interface{}{profile.Config, profile.Description, profile.Devices}
	err = util.EtagCheck(r, etag)
	if err != nil {
		return PreconditionFailed(err)
	}

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return InternalError(err)
	}

	rdr1 := ioutil.NopCloser(bytes.NewBuffer(body))
	rdr2 := ioutil.NopCloser(bytes.NewBuffer(body))

	reqRaw := shared.Jmap{}
	if err := json.NewDecoder(rdr1).Decode(&reqRaw); err != nil {
		return BadRequest(err)
	}

	req := api.ProfilePut{}
	if err := json.NewDecoder(rdr2).Decode(&req); err != nil {
		return BadRequest(err)
	}

	// Get Description
	_, err = reqRaw.GetString("description")
	if err != nil {
		req.Description = profile.Description
	}

	// Get Config
	if req.Config == nil {
		req.Config = profile.Config
	} else {
		for k, v := range profile.Config {
			_, ok := req.Config[k]
			if !ok {
				req.Config[k] = v
			}
		}
	}

	// Get Devices
	if req.Devices == nil {
		req.Devices = profile.Devices
	} else {
		for k, v := range profile.Devices {
			_, ok := req.Devices[k]
			if !ok {
				req.Devices[k] = v
			}
		}
	}

	return SmartError(doProfileUpdate(d, project, name, id, profile, req))
}

// The handler for the post operation.
func profilePost(d *Daemon, r *http.Request) Response {
	project := projectParam(r)
	name := mux.Vars(r)["name"]

	if name == "default" {
		return Forbidden(errors.New("The 'default' profile cannot be renamed"))
	}

	req := api.ProfilePost{}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		return BadRequest(err)
	}

	// Sanity checks
	if req.Name == "" {
		return BadRequest(fmt.Errorf("No name provided"))
	}

	if strings.Contains(req.Name, "/") {
		return BadRequest(fmt.Errorf("Profile names may not contain slashes"))
	}

	if shared.StringInSlice(req.Name, []string{".", ".."}) {
		return BadRequest(fmt.Errorf("Invalid profile name '%s'", req.Name))
	}

	err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
		// Check that the name isn't already in use
		_, err := tx.ProfileGet(project, req.Name)
		if err == nil {
			return fmt.Errorf("Name '%s' already in use", req.Name)
		}

		return tx.ProfileRename(project, name, req.Name)
	})
	if err != nil {
		return SmartError(err)
	}

	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", version.APIVersion, req.Name))
}

// The handler for the delete operation.
func profileDelete(d *Daemon, r *http.Request) Response {
	project := projectParam(r)
	name := mux.Vars(r)["name"]

	if name == "default" {
		return Forbidden(errors.New("The 'default' profile cannot be deleted"))
	}

	err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
		profile, err := tx.ProfileGet(project, name)
		if err != nil {
			return err
		}
		if len(profile.UsedBy) > 0 {
			return fmt.Errorf("Profile is currently in use")
		}

		return tx.ProfileDelete(project, name)
	})
	if err != nil {
		return SmartError(err)
	}

	return EmptySyncResponse
}
<|endoftext|>


<commit_before>package fcm

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
)

const (
	// DefaultEndpoint contains endpoint URL of FCM service.
	DefaultEndpoint = "https://fcm.googleapis.com/fcm/send"
)

var (
	// ErrInvalidAPIKey occurs if API key is not set.
	ErrInvalidAPIKey = errors.New("client API Key is invalid")
)

// Client abstracts the interaction between the application server and the
// FCM server via HTTP protocol. The developer must obtain an API key from the
// Google APIs Console page and pass it to the `Client` so that it can
// perform authorized requests on the application server's behalf.
// To send a message to one or more devices use the Client's Send.
//
// If the `HTTP` field is nil, a zeroed http.Client will be allocated and used
// to send messages.
type Client struct {
	apiKey   string
	client   *http.Client
	endpoint string
}

// NewClient creates new Firebase Cloud Messaging Client based on API key and
// with default endpoint and http client.
func NewClient(apiKey string) (*Client, error) {
	if apiKey == "" {
		return nil, ErrInvalidAPIKey
	}

	return &Client{
		apiKey:   apiKey,
		endpoint: DefaultEndpoint,
		client:   &http.Client{},
	}, nil
}

// NewClientFromHTTP creates a new Client based on API key, endpoint and http
// client.
func NewClientFromHTTP(c *http.Client, apiKey, endpoint string) (*Client, error) {
	// check client
	if c == nil {
		c = &http.Client{}
	}

	// check endpoint
	if endpoint == "" {
		endpoint = DefaultEndpoint
	}

	// check api key
	if apiKey == "" {
		return nil, ErrInvalidAPIKey
	}

	return &Client{
		apiKey:   apiKey,
		endpoint: endpoint,
		client:   c,
	}, nil
}

// Send sends a message to the FCM server without retrying in case of service
// unavailability. A non-nil error is returned if a non-recoverable error
// occurs (i.e. if the response status is not "200 OK").
func (c *Client) Send(msg *Message) (*Response, error) {
	// validate
	if err := msg.Validate(); err != nil {
		return nil, err
	}

	// marshal message
	data, err := json.Marshal(msg)
	if err != nil {
		return nil, err
	}

	return c.send(data)
}

// SendWithRetry sends a message to the FCM server with defined number of
// retrying in case of temporary error.
func (c *Client) SendWithRetry(msg *Message, retryAttempts int) (*Response, error) {
	resp := new(Response)
	err := retry(func() error {
		var err error
		resp, err = c.Send(msg)
		return err
	}, retryAttempts)
	if err != nil {
		return nil, err
	}

	return resp, nil
}

// send sends a request.
func (c *Client) send(data []byte) (*Response, error) {
	// create request
	req, err := http.NewRequest("POST", c.endpoint, bytes.NewBuffer(data))
	if err != nil {
		return nil, err
	}

	// add headers
	req.Header.Add("Authorization", fmt.Sprintf("key=%s", c.apiKey))
	req.Header.Add("Content-Type", "application/json")

	// execute request
	resp, err := c.client.Do(req)
	if err != nil {
		return nil, connectionError(err.Error())
	}
	defer resp.Body.Close()

	// check response status
	if resp.StatusCode != http.StatusOK {
		if resp.StatusCode >= http.StatusInternalServerError {
			return nil, serverError(fmt.Sprintf("%d error: %s", resp.StatusCode, resp.Status))
		}
		return nil, fmt.Errorf("%d error: %s", resp.StatusCode, resp.Status)
	}

	// build return
	response := new(Response)
	if err := json.NewDecoder(resp.Body).Decode(response); err != nil {
		return nil, err
	}

	return response, nil
}
<commit_msg>fcm: marshal message only once with retry<commit_after>package fcm

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
)

const (
	// DefaultEndpoint contains endpoint URL of FCM service.
	DefaultEndpoint = "https://fcm.googleapis.com/fcm/send"
)

var (
	// ErrInvalidAPIKey occurs if API key is not set.
	ErrInvalidAPIKey = errors.New("client API Key is invalid")
)

// Client abstracts the interaction between the application server and the
// FCM server via HTTP protocol. The developer must obtain an API key from the
// Google APIs Console page and pass it to the `Client` so that it can
// perform authorized requests on the application server's behalf.
// To send a message to one or more devices use the Client's Send.
//
// If the `HTTP` field is nil, a zeroed http.Client will be allocated and used
// to send messages.
type Client struct {
	apiKey   string
	client   *http.Client
	endpoint string
}

// NewClient creates new Firebase Cloud Messaging Client based on API key and
// with default endpoint and http client.
func NewClient(apiKey string) (*Client, error) {
	if apiKey == "" {
		return nil, ErrInvalidAPIKey
	}

	return &Client{
		apiKey:   apiKey,
		endpoint: DefaultEndpoint,
		client:   &http.Client{},
	}, nil
}

// NewClientFromHTTP creates a new Client based on API key, endpoint and http
// client.
func NewClientFromHTTP(c *http.Client, apiKey, endpoint string) (*Client, error) {
	// check client
	if c == nil {
		c = &http.Client{}
	}

	// check endpoint
	if endpoint == "" {
		endpoint = DefaultEndpoint
	}

	// check api key
	if apiKey == "" {
		return nil, ErrInvalidAPIKey
	}

	return &Client{
		apiKey:   apiKey,
		endpoint: endpoint,
		client:   c,
	}, nil
}

// Send sends a message to the FCM server without retrying in case of service
// unavailability. A non-nil error is returned if a non-recoverable error
// occurs (i.e. if the response status is not "200 OK").
func (c *Client) Send(msg *Message) (*Response, error) {
	// validate
	if err := msg.Validate(); err != nil {
		return nil, err
	}

	// marshal message
	data, err := json.Marshal(msg)
	if err != nil {
		return nil, err
	}

	return c.send(data)
}

// SendWithRetry sends a message to the FCM server with defined number of
// retrying in case of temporary error.
func (c *Client) SendWithRetry(msg *Message, retryAttempts int) (*Response, error) {
	// validate
	if err := msg.Validate(); err != nil {
		return nil, err
	}
	// marshal message
	data, err := json.Marshal(msg)
	if err != nil {
		return nil, err
	}

	resp := new(Response)
	err = retry(func() error {
		var err error
		resp, err = c.send(data)
		return err
	}, retryAttempts)
	if err != nil {
		return nil, err
	}

	return resp, nil
}

// send sends a request.
func (c *Client) send(data []byte) (*Response, error) {
	// create request
	req, err := http.NewRequest("POST", c.endpoint, bytes.NewBuffer(data))
	if err != nil {
		return nil, err
	}

	// add headers
	req.Header.Add("Authorization", fmt.Sprintf("key=%s", c.apiKey))
	req.Header.Add("Content-Type", "application/json")

	// execute request
	resp, err := c.client.Do(req)
	if err != nil {
		return nil, connectionError(err.Error())
	}
	defer resp.Body.Close()

	// check response status
	if resp.StatusCode != http.StatusOK {
		if resp.StatusCode >= http.StatusInternalServerError {
			return nil, serverError(fmt.Sprintf("%d error: %s", resp.StatusCode, resp.Status))
		}
		return nil, fmt.Errorf("%d error: %s", resp.StatusCode, resp.Status)
	}

	// build return
	response := new(Response)
	if err := json.NewDecoder(resp.Body).Decode(response); err != nil {
		return nil, err
	}

	return response, nil
}
<|endoftext|>


<commit_before><commit_msg>vom: preallocate kind list used for prepare type<commit_after><|endoftext|>


<commit_before>// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package logger

import (
	"context"
	"encoding/hex"
	"errors"
	"fmt"
	"go/build"
	"hash"
	"net/http"
	"path/filepath"
	"reflect"
	"runtime"
	"strings"
	"time"

	"github.com/minio/highwayhash"
	"github.com/minio/minio-go/v7/pkg/set"
	"github.com/minio/minio/internal/logger/message/log"
)

var (
	// HighwayHash key for logging in anonymous mode
	magicHighwayHash256Key = []byte("\x4b\xe7\x34\xfa\x8e\x23\x8a\xcd\x26\x3e\x83\xe6\xbb\x96\x85\x52\x04\x0f\x93\x5d\xa3\x9f\x44\x14\x97\xe0\x9d\x13\x22\xde\x36\xa0")
	// HighwayHash hasher for logging in anonymous mode
	loggerHighwayHasher hash.Hash
)

// Disable disables all logging, false by default. (used for "go test")
var Disable = false

// Level type
type Level int8

// Enumerated level types
const (
	InformationLvl Level = iota + 1
	ErrorLvl
	FatalLvl
)

var trimStrings []string

var globalDeploymentID string

// TimeFormat - logging time format.
const TimeFormat string = "15:04:05 MST 01/02/2006"

var matchingFuncNames = [...]string{
	"http.HandlerFunc.ServeHTTP",
	"cmd.serverMain",
	"cmd.StartGateway",
	"cmd.(*webAPIHandlers).ListBuckets",
	"cmd.(*webAPIHandlers).MakeBucket",
	"cmd.(*webAPIHandlers).DeleteBucket",
	"cmd.(*webAPIHandlers).ListObjects",
	"cmd.(*webAPIHandlers).RemoveObject",
	"cmd.(*webAPIHandlers).Login",
	"cmd.(*webAPIHandlers).SetAuth",
	"cmd.(*webAPIHandlers).CreateURLToken",
	"cmd.(*webAPIHandlers).Upload",
	"cmd.(*webAPIHandlers).Download",
	"cmd.(*webAPIHandlers).DownloadZip",
	"cmd.(*webAPIHandlers).GetBucketPolicy",
	"cmd.(*webAPIHandlers).ListAllBucketPolicies",
	"cmd.(*webAPIHandlers).SetBucketPolicy",
	"cmd.(*webAPIHandlers).PresignedGet",
	"cmd.(*webAPIHandlers).ServerInfo",
	"cmd.(*webAPIHandlers).StorageInfo",
	// add more here ..
}

func (level Level) String() string {
	var lvlStr string
	switch level {
	case InformationLvl:
		lvlStr = "INFO"
	case ErrorLvl:
		lvlStr = "ERROR"
	case FatalLvl:
		lvlStr = "FATAL"
	}
	return lvlStr
}

// quietFlag: Hide startup messages if enabled
// jsonFlag: Display in JSON format, if enabled
var (
	quietFlag, jsonFlag, anonFlag bool
	// Custom function to format error
	errorFmtFunc func(string, error, bool) string
)

// EnableQuiet - turns quiet option on.
func EnableQuiet() {
	quietFlag = true
}

// EnableJSON - outputs logs in json format.
func EnableJSON() {
	jsonFlag = true
	quietFlag = true
}

// EnableAnonymous - turns anonymous flag
// to avoid printing sensitive information.
func EnableAnonymous() {
	anonFlag = true
}

// IsJSON - returns true if jsonFlag is true
func IsJSON() bool {
	return jsonFlag
}

// IsQuiet - returns true if quietFlag is true
func IsQuiet() bool {
	return quietFlag
}

// RegisterError registers the specified rendering function. This latter
// will be called for a pretty rendering of fatal errors.
func RegisterError(f func(string, error, bool) string) {
	errorFmtFunc = f
}

// Remove any duplicates and return unique entries.
func uniqueEntries(paths []string) []string {
	m := make(set.StringSet)
	for _, p := range paths {
		if !m.Contains(p) {
			m.Add(p)
		}
	}
	return m.ToSlice()
}

// SetDeploymentID -- Deployment Id from the main package is set here
func SetDeploymentID(deploymentID string) {
	globalDeploymentID = deploymentID
}

// Init sets the trimStrings to possible GOPATHs
// and GOROOT directories. Also append github.com/minio/minio
// This is done to clean up the filename, when stack trace is
// displayed when an error happens.
func Init(goPath string, goRoot string) {

	var goPathList []string
	var goRootList []string
	var defaultgoPathList []string
	var defaultgoRootList []string
	pathSeperator := ":"
	// Add all possible GOPATH paths into trimStrings
	// Split GOPATH depending on the OS type
	if runtime.GOOS == "windows" {
		pathSeperator = ";"
	}

	goPathList = strings.Split(goPath, pathSeperator)
	goRootList = strings.Split(goRoot, pathSeperator)
	defaultgoPathList = strings.Split(build.Default.GOPATH, pathSeperator)
	defaultgoRootList = strings.Split(build.Default.GOROOT, pathSeperator)

	// Add trim string "{GOROOT}/src/" into trimStrings
	trimStrings = []string{filepath.Join(runtime.GOROOT(), "src") + string(filepath.Separator)}

	// Add all possible path from GOPATH=path1:path2...:pathN
	// as "{path#}/src/" into trimStrings
	for _, goPathString := range goPathList {
		trimStrings = append(trimStrings, filepath.Join(goPathString, "src")+string(filepath.Separator))
	}

	for _, goRootString := range goRootList {
		trimStrings = append(trimStrings, filepath.Join(goRootString, "src")+string(filepath.Separator))
	}

	for _, defaultgoPathString := range defaultgoPathList {
		trimStrings = append(trimStrings, filepath.Join(defaultgoPathString, "src")+string(filepath.Separator))
	}

	for _, defaultgoRootString := range defaultgoRootList {
		trimStrings = append(trimStrings, filepath.Join(defaultgoRootString, "src")+string(filepath.Separator))
	}

	// Remove duplicate entries.
	trimStrings = uniqueEntries(trimStrings)

	// Add "github.com/minio/minio" as the last to cover
	// paths like "{GOROOT}/src/github.com/minio/minio"
	// and "{GOPATH}/src/github.com/minio/minio"
	trimStrings = append(trimStrings, filepath.Join("github.com", "minio", "minio")+string(filepath.Separator))

	loggerHighwayHasher, _ = highwayhash.New(magicHighwayHash256Key) // New will never return error since key is 256 bit
}

func trimTrace(f string) string {
	for _, trimString := range trimStrings {
		f = strings.TrimPrefix(filepath.ToSlash(f), filepath.ToSlash(trimString))
	}
	return filepath.FromSlash(f)
}

func getSource(level int) string {
	pc, file, lineNumber, ok := runtime.Caller(level)
	if ok {
		// Clean up the common prefixes
		file = trimTrace(file)
		_, funcName := filepath.Split(runtime.FuncForPC(pc).Name())
		return fmt.Sprintf("%v:%v:%v()", file, lineNumber, funcName)
	}
	return ""
}

// getTrace method - creates and returns stack trace
func getTrace(traceLevel int) []string {
	var trace []string
	pc, file, lineNumber, ok := runtime.Caller(traceLevel)

	for ok && file != "" {
		// Clean up the common prefixes
		file = trimTrace(file)
		// Get the function name
		_, funcName := filepath.Split(runtime.FuncForPC(pc).Name())
		// Skip duplicate traces that start with file name, "<autogenerated>"
		// and also skip traces with function name that starts with "runtime."
		if !strings.HasPrefix(file, "<autogenerated>") &&
			!strings.HasPrefix(funcName, "runtime.") {
			// Form and append a line of stack trace into a
			// collection, 'trace', to build full stack trace
			trace = append(trace, fmt.Sprintf("%v:%v:%v()", file, lineNumber, funcName))

			// Ignore trace logs beyond the following conditions
			for _, name := range matchingFuncNames {
				if funcName == name {
					return trace
				}
			}
		}
		traceLevel++
		// Read stack trace information from PC
		pc, file, lineNumber, ok = runtime.Caller(traceLevel)
	}
	return trace
}

// Return the highway hash of the passed string
func hashString(input string) string {
	defer loggerHighwayHasher.Reset()
	loggerHighwayHasher.Write([]byte(input))
	checksum := loggerHighwayHasher.Sum(nil)
	return hex.EncodeToString(checksum)
}

// Kind specifies the kind of error log
type Kind string

const (
	// Minio errors
	Minio Kind = "MINIO"
	// Application errors
	Application Kind = "APPLICATION"
	// All errors
	All Kind = "ALL"
)

// LogAlwaysIf prints a detailed error message during
// the execution of the server.
func LogAlwaysIf(ctx context.Context, err error, errKind ...interface{}) {
	if err == nil {
		return
	}

	logIf(ctx, err, errKind...)
}

// LogIf prints a detailed error message during
// the execution of the server, if it is not an
// ignored error.
func LogIf(ctx context.Context, err error, errKind ...interface{}) {
	if err == nil {
		return
	}

	if errors.Is(err, context.Canceled) {
		return
	}

	if err.Error() == http.ErrServerClosed.Error() || err.Error() == "disk not found" {
		return
	}

	logIf(ctx, err, errKind...)
}

// logIf prints a detailed error message during
// the execution of the server.
func logIf(ctx context.Context, err error, errKind ...interface{}) {
	if Disable {
		return
	}
	logKind := string(Minio)
	if len(errKind) > 0 {
		if ek, ok := errKind[0].(Kind); ok {
			logKind = string(ek)
		}
	}
	req := GetReqInfo(ctx)

	if req == nil {
		req = &ReqInfo{API: "SYSTEM"}
	}

	API := "SYSTEM"
	if req.API != "" {
		API = req.API
	}

	kv := req.GetTags()
	tags := make(map[string]interface{}, len(kv))
	for _, entry := range kv {
		tags[entry.Key] = entry.Val
	}

	// Get full stack trace
	trace := getTrace(3)

	// Get the cause for the Error
	message := fmt.Sprintf("%v (%T)", err, err)
	if req.DeploymentID == "" {
		req.DeploymentID = globalDeploymentID
	}
	entry := log.Entry{
		DeploymentID: req.DeploymentID,
		Level:        ErrorLvl.String(),
		LogKind:      logKind,
		RemoteHost:   req.RemoteHost,
		Host:         req.Host,
		RequestID:    req.RequestID,
		UserAgent:    req.UserAgent,
		Time:         time.Now().UTC().Format(time.RFC3339Nano),
		API: &log.API{
			Name: API,
			Args: &log.Args{
				Bucket: req.BucketName,
				Object: req.ObjectName,
			},
		},
		Trace: &log.Trace{
			Message:   message,
			Source:    trace,
			Variables: tags,
		},
	}

	if anonFlag {
		entry.API.Args.Bucket = hashString(entry.API.Args.Bucket)
		entry.API.Args.Object = hashString(entry.API.Args.Object)
		entry.RemoteHost = hashString(entry.RemoteHost)
		entry.Trace.Message = reflect.TypeOf(err).String()
		entry.Trace.Variables = make(map[string]interface{})
	}

	// Iterate over all logger targets to send the log entry
	for _, t := range Targets() {
		t.Send(entry, entry.LogKind)
	}
}

// ErrCritical is the value panic'd whenever CriticalIf is called.
var ErrCritical struct{}

// CriticalIf logs the provided error on the console. It fails the
// current go-routine by causing a `panic(ErrCritical)`.
func CriticalIf(ctx context.Context, err error, errKind ...interface{}) {
	if err != nil {
		LogIf(ctx, err, errKind...)
		panic(ErrCritical)
	}
}

// FatalIf is similar to Fatal() but it ignores passed nil error
func FatalIf(err error, msg string, data ...interface{}) {
	if err == nil {
		return
	}
	fatal(err, msg, data...)
}
<commit_msg>allocate new highwayhash for each string hash (#13623)<commit_after>// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package logger

import (
	"context"
	"encoding/hex"
	"errors"
	"fmt"
	"go/build"
	"net/http"
	"path/filepath"
	"reflect"
	"runtime"
	"strings"
	"time"

	"github.com/minio/highwayhash"
	"github.com/minio/minio-go/v7/pkg/set"
	"github.com/minio/minio/internal/logger/message/log"
)

var (
	// HighwayHash key for logging in anonymous mode
	magicHighwayHash256Key = []byte("\x4b\xe7\x34\xfa\x8e\x23\x8a\xcd\x26\x3e\x83\xe6\xbb\x96\x85\x52\x04\x0f\x93\x5d\xa3\x9f\x44\x14\x97\xe0\x9d\x13\x22\xde\x36\xa0")
)

// Disable disables all logging, false by default. (used for "go test")
var Disable = false

// Level type
type Level int8

// Enumerated level types
const (
	InformationLvl Level = iota + 1
	ErrorLvl
	FatalLvl
)

var trimStrings []string

var globalDeploymentID string

// TimeFormat - logging time format.
const TimeFormat string = "15:04:05 MST 01/02/2006"

var matchingFuncNames = [...]string{
	"http.HandlerFunc.ServeHTTP",
	"cmd.serverMain",
	"cmd.StartGateway",
	"cmd.(*webAPIHandlers).ListBuckets",
	"cmd.(*webAPIHandlers).MakeBucket",
	"cmd.(*webAPIHandlers).DeleteBucket",
	"cmd.(*webAPIHandlers).ListObjects",
	"cmd.(*webAPIHandlers).RemoveObject",
	"cmd.(*webAPIHandlers).Login",
	"cmd.(*webAPIHandlers).SetAuth",
	"cmd.(*webAPIHandlers).CreateURLToken",
	"cmd.(*webAPIHandlers).Upload",
	"cmd.(*webAPIHandlers).Download",
	"cmd.(*webAPIHandlers).DownloadZip",
	"cmd.(*webAPIHandlers).GetBucketPolicy",
	"cmd.(*webAPIHandlers).ListAllBucketPolicies",
	"cmd.(*webAPIHandlers).SetBucketPolicy",
	"cmd.(*webAPIHandlers).PresignedGet",
	"cmd.(*webAPIHandlers).ServerInfo",
	"cmd.(*webAPIHandlers).StorageInfo",
	// add more here ..
}

func (level Level) String() string {
	var lvlStr string
	switch level {
	case InformationLvl:
		lvlStr = "INFO"
	case ErrorLvl:
		lvlStr = "ERROR"
	case FatalLvl:
		lvlStr = "FATAL"
	}
	return lvlStr
}

// quietFlag: Hide startup messages if enabled
// jsonFlag: Display in JSON format, if enabled
var (
	quietFlag, jsonFlag, anonFlag bool
	// Custom function to format error
	errorFmtFunc func(string, error, bool) string
)

// EnableQuiet - turns quiet option on.
func EnableQuiet() {
	quietFlag = true
}

// EnableJSON - outputs logs in json format.
func EnableJSON() {
	jsonFlag = true
	quietFlag = true
}

// EnableAnonymous - turns anonymous flag
// to avoid printing sensitive information.
func EnableAnonymous() {
	anonFlag = true
}

// IsJSON - returns true if jsonFlag is true
func IsJSON() bool {
	return jsonFlag
}

// IsQuiet - returns true if quietFlag is true
func IsQuiet() bool {
	return quietFlag
}

// RegisterError registers the specified rendering function. This latter
// will be called for a pretty rendering of fatal errors.
func RegisterError(f func(string, error, bool) string) {
	errorFmtFunc = f
}

// Remove any duplicates and return unique entries.
func uniqueEntries(paths []string) []string {
	m := make(set.StringSet)
	for _, p := range paths {
		if !m.Contains(p) {
			m.Add(p)
		}
	}
	return m.ToSlice()
}

// SetDeploymentID -- Deployment Id from the main package is set here
func SetDeploymentID(deploymentID string) {
	globalDeploymentID = deploymentID
}

// Init sets the trimStrings to possible GOPATHs
// and GOROOT directories. Also append github.com/minio/minio
// This is done to clean up the filename, when stack trace is
// displayed when an error happens.
func Init(goPath string, goRoot string) {

	var goPathList []string
	var goRootList []string
	var defaultgoPathList []string
	var defaultgoRootList []string
	pathSeperator := ":"
	// Add all possible GOPATH paths into trimStrings
	// Split GOPATH depending on the OS type
	if runtime.GOOS == "windows" {
		pathSeperator = ";"
	}

	goPathList = strings.Split(goPath, pathSeperator)
	goRootList = strings.Split(goRoot, pathSeperator)
	defaultgoPathList = strings.Split(build.Default.GOPATH, pathSeperator)
	defaultgoRootList = strings.Split(build.Default.GOROOT, pathSeperator)

	// Add trim string "{GOROOT}/src/" into trimStrings
	trimStrings = []string{filepath.Join(runtime.GOROOT(), "src") + string(filepath.Separator)}

	// Add all possible path from GOPATH=path1:path2...:pathN
	// as "{path#}/src/" into trimStrings
	for _, goPathString := range goPathList {
		trimStrings = append(trimStrings, filepath.Join(goPathString, "src")+string(filepath.Separator))
	}

	for _, goRootString := range goRootList {
		trimStrings = append(trimStrings, filepath.Join(goRootString, "src")+string(filepath.Separator))
	}

	for _, defaultgoPathString := range defaultgoPathList {
		trimStrings = append(trimStrings, filepath.Join(defaultgoPathString, "src")+string(filepath.Separator))
	}

	for _, defaultgoRootString := range defaultgoRootList {
		trimStrings = append(trimStrings, filepath.Join(defaultgoRootString, "src")+string(filepath.Separator))
	}

	// Remove duplicate entries.
	trimStrings = uniqueEntries(trimStrings)

	// Add "github.com/minio/minio" as the last to cover
	// paths like "{GOROOT}/src/github.com/minio/minio"
	// and "{GOPATH}/src/github.com/minio/minio"
	trimStrings = append(trimStrings, filepath.Join("github.com", "minio", "minio")+string(filepath.Separator))
}

func trimTrace(f string) string {
	for _, trimString := range trimStrings {
		f = strings.TrimPrefix(filepath.ToSlash(f), filepath.ToSlash(trimString))
	}
	return filepath.FromSlash(f)
}

func getSource(level int) string {
	pc, file, lineNumber, ok := runtime.Caller(level)
	if ok {
		// Clean up the common prefixes
		file = trimTrace(file)
		_, funcName := filepath.Split(runtime.FuncForPC(pc).Name())
		return fmt.Sprintf("%v:%v:%v()", file, lineNumber, funcName)
	}
	return ""
}

// getTrace method - creates and returns stack trace
func getTrace(traceLevel int) []string {
	var trace []string
	pc, file, lineNumber, ok := runtime.Caller(traceLevel)

	for ok && file != "" {
		// Clean up the common prefixes
		file = trimTrace(file)
		// Get the function name
		_, funcName := filepath.Split(runtime.FuncForPC(pc).Name())
		// Skip duplicate traces that start with file name, "<autogenerated>"
		// and also skip traces with function name that starts with "runtime."
		if !strings.HasPrefix(file, "<autogenerated>") &&
			!strings.HasPrefix(funcName, "runtime.") {
			// Form and append a line of stack trace into a
			// collection, 'trace', to build full stack trace
			trace = append(trace, fmt.Sprintf("%v:%v:%v()", file, lineNumber, funcName))

			// Ignore trace logs beyond the following conditions
			for _, name := range matchingFuncNames {
				if funcName == name {
					return trace
				}
			}
		}
		traceLevel++
		// Read stack trace information from PC
		pc, file, lineNumber, ok = runtime.Caller(traceLevel)
	}
	return trace
}

// Return the highway hash of the passed string
func hashString(input string) string {
	hh, _ := highwayhash.New(magicHighwayHash256Key)
	hh.Write([]byte(input))
	return hex.EncodeToString(hh.Sum(nil))
}

// Kind specifies the kind of error log
type Kind string

const (
	// Minio errors
	Minio Kind = "MINIO"
	// Application errors
	Application Kind = "APPLICATION"
	// All errors
	All Kind = "ALL"
)

// LogAlwaysIf prints a detailed error message during
// the execution of the server.
func LogAlwaysIf(ctx context.Context, err error, errKind ...interface{}) {
	if err == nil {
		return
	}

	logIf(ctx, err, errKind...)
}

// LogIf prints a detailed error message during
// the execution of the server, if it is not an
// ignored error.
func LogIf(ctx context.Context, err error, errKind ...interface{}) {
	if err == nil {
		return
	}

	if errors.Is(err, context.Canceled) {
		return
	}

	if err.Error() == http.ErrServerClosed.Error() || err.Error() == "disk not found" {
		return
	}

	logIf(ctx, err, errKind...)
}

// logIf prints a detailed error message during
// the execution of the server.
func logIf(ctx context.Context, err error, errKind ...interface{}) {
	if Disable {
		return
	}
	logKind := string(Minio)
	if len(errKind) > 0 {
		if ek, ok := errKind[0].(Kind); ok {
			logKind = string(ek)
		}
	}
	req := GetReqInfo(ctx)

	if req == nil {
		req = &ReqInfo{API: "SYSTEM"}
	}

	API := "SYSTEM"
	if req.API != "" {
		API = req.API
	}

	kv := req.GetTags()
	tags := make(map[string]interface{}, len(kv))
	for _, entry := range kv {
		tags[entry.Key] = entry.Val
	}

	// Get full stack trace
	trace := getTrace(3)

	// Get the cause for the Error
	message := fmt.Sprintf("%v (%T)", err, err)
	if req.DeploymentID == "" {
		req.DeploymentID = globalDeploymentID
	}
	entry := log.Entry{
		DeploymentID: req.DeploymentID,
		Level:        ErrorLvl.String(),
		LogKind:      logKind,
		RemoteHost:   req.RemoteHost,
		Host:         req.Host,
		RequestID:    req.RequestID,
		UserAgent:    req.UserAgent,
		Time:         time.Now().UTC().Format(time.RFC3339Nano),
		API: &log.API{
			Name: API,
			Args: &log.Args{
				Bucket: req.BucketName,
				Object: req.ObjectName,
			},
		},
		Trace: &log.Trace{
			Message:   message,
			Source:    trace,
			Variables: tags,
		},
	}

	if anonFlag {
		entry.API.Args.Bucket = hashString(entry.API.Args.Bucket)
		entry.API.Args.Object = hashString(entry.API.Args.Object)
		entry.RemoteHost = hashString(entry.RemoteHost)
		entry.Trace.Message = reflect.TypeOf(err).String()
		entry.Trace.Variables = make(map[string]interface{})
	}

	// Iterate over all logger targets to send the log entry
	for _, t := range Targets() {
		t.Send(entry, entry.LogKind)
	}
}

// ErrCritical is the value panic'd whenever CriticalIf is called.
var ErrCritical struct{}

// CriticalIf logs the provided error on the console. It fails the
// current go-routine by causing a `panic(ErrCritical)`.
func CriticalIf(ctx context.Context, err error, errKind ...interface{}) {
	if err != nil {
		LogIf(ctx, err, errKind...)
		panic(ErrCritical)
	}
}

// FatalIf is similar to Fatal() but it ignores passed nil error
func FatalIf(err error, msg string, data ...interface{}) {
	if err == nil {
		return
	}
	fatal(err, msg, data...)
}
<|endoftext|>