<commit_before>package gstruct

import (
	"errors"
	"fmt"
	"reflect"
	"runtime/debug"
	"strings"

	"github.com/onsi/gomega/format"
	errorsutil "github.com/onsi/gomega/gstruct/errors"
	"github.com/onsi/gomega/types"
)

func MatchAllKeys(keys Keys) types.GomegaMatcher {
	return &KeysMatcher{
		Keys: keys,
	}
}

func MatchKeys(options Options, keys Keys) types.GomegaMatcher {
	return &KeysMatcher{
		Keys:          keys,
		IgnoreExtras:  options&IgnoreExtras != 0,
		IgnoreMissing: options&IgnoreMissing != 0,
	}
}

type KeysMatcher struct {
	// Matchers for each key.
	Keys Keys

	// Whether to ignore extra keys or consider it an error.
	IgnoreExtras bool
	// Whether to ignore missing keys or consider it an error.
	IgnoreMissing bool

	// State.
	failures []error
}

type Keys map[interface{}]types.GomegaMatcher

func (m *KeysMatcher) Match(actual interface{}) (success bool, err error) {
	if reflect.TypeOf(actual).Kind() != reflect.Map {
		return false, fmt.Errorf("%v is type %T, expected map", actual, actual)
	}

	m.failures = m.matchKeys(actual)
	if len(m.failures) > 0 {
		return false, nil
	}
	return true, nil
}

func (m *KeysMatcher) matchKeys(actual interface{}) (errs []error) {
	actualValue := reflect.ValueOf(actual)
	keys := map[interface{}]bool{}
	for _, keyValue := range actualValue.MapKeys() {
		key := keyValue.Interface()
		keys[key] = true

		err := func() (err error) {
			// This test relies heavily on reflect, which tends to panic.
			// Recover here to provide more useful error messages in that case.
			defer func() {
				if r := recover(); r != nil {
					err = fmt.Errorf("panic checking %+v: %v\n%s", actual, r, debug.Stack())
				}
			}()

			matcher, ok := m.Keys[key]
			if !ok {
				if !m.IgnoreExtras {
					return fmt.Errorf("unexpected key %s: %+v", key, actual)
				}
				return nil
			}

			valValue := actualValue.MapIndex(keyValue)

			match, err := matcher.Match(valValue.Interface())
			if err != nil {
				return err
			}

			if !match {
				if nesting, ok := matcher.(errorsutil.NestingMatcher); ok {
					return errorsutil.AggregateError(nesting.Failures())
				}
				return errors.New(matcher.FailureMessage(valValue))
			}
			return nil
		}()
		if err != nil {
			errs = append(errs, errorsutil.Nest(fmt.Sprintf(".%#v", key), err))
		}
	}

	for key := range m.Keys {
		if !keys[key] && !m.IgnoreMissing {
			errs = append(errs, fmt.Errorf("missing expected key %s", key))
		}
	}

	return errs
}

func (m *KeysMatcher) FailureMessage(actual interface{}) (message string) {
	failures := make([]string, len(m.failures))
	for i := range m.failures {
		failures[i] = m.failures[i].Error()
	}
	return format.Message(reflect.TypeOf(actual).Name(),
		fmt.Sprintf("to match keys: {\n%v\n}\n", strings.Join(failures, "\n")))
}

func (m *KeysMatcher) NegatedFailureMessage(actual interface{}) (message string) {
	return format.Message(actual, "not to match keys")
}

func (m *KeysMatcher) Failures() []error {
	return m.failures
}
<commit_msg>ensure error messages refer to the underlying interface, not the `reflect.Value`<commit_after>package gstruct

import (
	"errors"
	"fmt"
	"reflect"
	"runtime/debug"
	"strings"

	"github.com/onsi/gomega/format"
	errorsutil "github.com/onsi/gomega/gstruct/errors"
	"github.com/onsi/gomega/types"
)

func MatchAllKeys(keys Keys) types.GomegaMatcher {
	return &KeysMatcher{
		Keys: keys,
	}
}

func MatchKeys(options Options, keys Keys) types.GomegaMatcher {
	return &KeysMatcher{
		Keys:          keys,
		IgnoreExtras:  options&IgnoreExtras != 0,
		IgnoreMissing: options&IgnoreMissing != 0,
	}
}

type KeysMatcher struct {
	// Matchers for each key.
	Keys Keys

	// Whether to ignore extra keys or consider it an error.
	IgnoreExtras bool
	// Whether to ignore missing keys or consider it an error.
	IgnoreMissing bool

	// State.
	failures []error
}

type Keys map[interface{}]types.GomegaMatcher

func (m *KeysMatcher) Match(actual interface{}) (success bool, err error) {
	if reflect.TypeOf(actual).Kind() != reflect.Map {
		return false, fmt.Errorf("%v is type %T, expected map", actual, actual)
	}

	m.failures = m.matchKeys(actual)
	if len(m.failures) > 0 {
		return false, nil
	}
	return true, nil
}

func (m *KeysMatcher) matchKeys(actual interface{}) (errs []error) {
	actualValue := reflect.ValueOf(actual)
	keys := map[interface{}]bool{}
	for _, keyValue := range actualValue.MapKeys() {
		key := keyValue.Interface()
		keys[key] = true

		err := func() (err error) {
			// This test relies heavily on reflect, which tends to panic.
			// Recover here to provide more useful error messages in that case.
			defer func() {
				if r := recover(); r != nil {
					err = fmt.Errorf("panic checking %+v: %v\n%s", actual, r, debug.Stack())
				}
			}()

			matcher, ok := m.Keys[key]
			if !ok {
				if !m.IgnoreExtras {
					return fmt.Errorf("unexpected key %s: %+v", key, actual)
				}
				return nil
			}

			valInterface := actualValue.MapIndex(keyValue).Interface()

			match, err := matcher.Match(valInterface)
			if err != nil {
				return err
			}

			if !match {
				if nesting, ok := matcher.(errorsutil.NestingMatcher); ok {
					return errorsutil.AggregateError(nesting.Failures())
				}
				return errors.New(matcher.FailureMessage(valInterface))
			}
			return nil
		}()
		if err != nil {
			errs = append(errs, errorsutil.Nest(fmt.Sprintf(".%#v", key), err))
		}
	}

	for key := range m.Keys {
		if !keys[key] && !m.IgnoreMissing {
			errs = append(errs, fmt.Errorf("missing expected key %s", key))
		}
	}

	return errs
}

func (m *KeysMatcher) FailureMessage(actual interface{}) (message string) {
	failures := make([]string, len(m.failures))
	for i := range m.failures {
		failures[i] = m.failures[i].Error()
	}
	return format.Message(reflect.TypeOf(actual).Name(),
		fmt.Sprintf("to match keys: {\n%v\n}\n", strings.Join(failures, "\n")))
}

func (m *KeysMatcher) NegatedFailureMessage(actual interface{}) (message string) {
	return format.Message(actual, "not to match keys")
}

func (m *KeysMatcher) Failures() []error {
	return m.failures
}
<|endoftext|>


<commit_before>package user_printer

import (
	"fmt"

	. "github.com/cloudfoundry/cli/cf/i18n"
	"github.com/cloudfoundry/cli/cf/models"
	"github.com/cloudfoundry/cli/cf/terminal"
	"github.com/cloudfoundry/cli/plugin/models"
)

type UserPrinter interface {
	PrintUsers(guid string, username string)
}

type SpaceUsersPluginPrinter struct {
	UserPrinter
	Roles       []string
	UserLister  func(spaceGuid string, role string) ([]models.UserFields, error)
	PluginModel *[]plugin_models.GetSpaceUsers_Model
	users       users
}

type OrgUsersPluginPrinter struct {
	UserPrinter
	Roles       []string
	UserLister  func(orgGuid string, role string) ([]models.UserFields, error)
	PluginModel *[]plugin_models.GetOrgUsers_Model
	users       users
}

type SpaceUsersUiPrinter struct {
	UserPrinter
	Ui               terminal.UI
	UserLister       func(spaceGuid string, role string) ([]models.UserFields, error)
	Roles            []string
	RoleDisplayNames map[string]string
}

type OrgUsersUiPrinter struct {
	UserPrinter
	Roles            []string
	RoleDisplayNames map[string]string
	UserLister       func(orgGuid string, role string) ([]models.UserFields, error)
	Ui               terminal.UI
}

type userWithRoles struct {
	models.UserFields
	Roles []string
}

type users struct {
	db map[string]userWithRoles
}

func NewOrgUsersPluginPrinter(
	pluginModel *[]plugin_models.GetOrgUsers_Model,
	userLister func(guid string, role string) ([]models.UserFields, error),
	roles []string,
) (printer *OrgUsersPluginPrinter) {
	return &OrgUsersPluginPrinter{
		PluginModel: pluginModel,
		users:       users{db: make(map[string]userWithRoles)},
		UserLister:  userLister,
		Roles:       roles,
	}
}

func NewSpaceUsersPluginPrinter(
	pluginModel *[]plugin_models.GetSpaceUsers_Model,
	userLister func(guid string, role string) ([]models.UserFields, error),
	roles []string,
) (printer *SpaceUsersPluginPrinter) {
	return &SpaceUsersPluginPrinter{
		PluginModel: pluginModel,
		users:       users{db: make(map[string]userWithRoles)},
		UserLister:  userLister,
		Roles:       roles,
	}
}

func (p *OrgUsersPluginPrinter) PrintUsers(guid string, username string) {
	for _, role := range p.Roles {
		users, _ := p.UserLister(guid, role)
		for _, user := range users {
			p.users.StoreAppendingRole(role, user.Username, user.Guid, user.IsAdmin)
		}
	}
	*(p.PluginModel) = p.users.AsOrgUsers()
}

func (p *SpaceUsersPluginPrinter) PrintUsers(guid string, username string) {
	for _, role := range p.Roles {
		users, _ := p.UserLister(guid, role)
		for _, user := range users {
			p.users.StoreAppendingRole(role, user.Username, user.Guid, user.IsAdmin)
		}
	}
	*(p.PluginModel) = p.users.AsSpaceUsers()
}

func (p *OrgUsersUiPrinter) PrintUsers(guid string, username string) {
	for _, role := range p.Roles {
		displayName := p.RoleDisplayNames[role]
		users, err := p.UserLister(guid, role)
		if err != nil {
			p.Ui.Failed(T("Failed fetching org-users for role {{.OrgRoleToDisplayName}}.\n{{.Error}}",
				map[string]interface{}{
					"Error":                err.Error(),
					"OrgRoleToDisplayName": displayName,
				}))
			return
		}
		p.Ui.Say("")
		p.Ui.Say("%s", terminal.HeaderColor(displayName))

		if len(users) == 0 {
			p.Ui.Say(fmt.Sprintf("  "+T("No %s found"), displayName))
		} else {
			for _, user := range users {
				p.Ui.Say("  %s", user.Username)
			}
		}
	}
}

func (p *SpaceUsersUiPrinter) PrintUsers(guid string, username string) {
	for _, role := range p.Roles {
		displayName := p.RoleDisplayNames[role]
		users, err := p.UserLister(guid, role)
		if err != nil {
			p.Ui.Failed(T("Failed fetching space-users for role {{.SpaceRoleToDisplayName}}.\n{{.Error}}",
				map[string]interface{}{
					"Error":                  err.Error(),
					"SpaceRoleToDisplayName": displayName,
				}))
			return
		}
		p.Ui.Say("")
		p.Ui.Say("%s", terminal.HeaderColor(displayName))

		if len(users) == 0 {
			p.Ui.Say(fmt.Sprintf("  "+T("No %s found"), displayName))
		} else {
			for _, user := range users {
				p.Ui.Say("  %s", user.Username)
			}
		}
	}
}

func (c *users) StoreAppendingRole(role string, username string, guid string, isAdmin bool) {
	u := c.db[username]
	u.Roles = append(u.Roles, role)
	u.Username = username
	u.Guid = guid
	u.IsAdmin = isAdmin
	c.db[username] = u
}

func (c *users) AsOrgUsers() (coll []plugin_models.GetOrgUsers_Model) {
	for _, u := range c.db {
		coll = append(coll, plugin_models.GetOrgUsers_Model{
			Guid:     u.Guid,
			Username: u.Username,
			IsAdmin:  u.IsAdmin,
			Roles:    u.Roles,
		})
	}
	return coll
}

func (c *users) AsSpaceUsers() (coll []plugin_models.GetSpaceUsers_Model) {
	for _, u := range c.db {
		coll = append(coll, plugin_models.GetSpaceUsers_Model{
			Guid:     u.Guid,
			Username: u.Username,
			IsAdmin:  u.IsAdmin,
			Roles:    u.Roles,
		})
	}
	return coll
}
<commit_msg>More privatisation.<commit_after>package user_printer

import (
	"fmt"

	. "github.com/cloudfoundry/cli/cf/i18n"
	"github.com/cloudfoundry/cli/cf/models"
	"github.com/cloudfoundry/cli/cf/terminal"
	"github.com/cloudfoundry/cli/plugin/models"
)

type UserPrinter interface {
	PrintUsers(guid string, username string)
}

type spaceUsersPluginPrinter struct {
	UserPrinter
	roles       []string
	userLister  func(spaceGuid string, role string) ([]models.UserFields, error)
	pluginModel *[]plugin_models.GetSpaceUsers_Model
	users       users
}

type orgUsersPluginPrinter struct {
	UserPrinter
	roles       []string
	userLister  func(orgGuid string, role string) ([]models.UserFields, error)
	pluginModel *[]plugin_models.GetOrgUsers_Model
	users       users
}

type SpaceUsersUiPrinter struct {
	UserPrinter
	Ui               terminal.UI
	UserLister       func(spaceGuid string, role string) ([]models.UserFields, error)
	Roles            []string
	RoleDisplayNames map[string]string
}

type OrgUsersUiPrinter struct {
	UserPrinter
	Roles            []string
	RoleDisplayNames map[string]string
	UserLister       func(orgGuid string, role string) ([]models.UserFields, error)
	Ui               terminal.UI
}

type userWithRoles struct {
	models.UserFields
	roles []string
}

type users struct {
	db map[string]userWithRoles
}

func NewOrgUsersPluginPrinter(
	pluginModel *[]plugin_models.GetOrgUsers_Model,
	userLister func(guid string, role string) ([]models.UserFields, error),
	roles []string,
) (printer *orgUsersPluginPrinter) {
	return &orgUsersPluginPrinter{
		pluginModel: pluginModel,
		users:       users{db: make(map[string]userWithRoles)},
		userLister:  userLister,
		roles:       roles,
	}
}

func NewSpaceUsersPluginPrinter(
	pluginModel *[]plugin_models.GetSpaceUsers_Model,
	userLister func(guid string, role string) ([]models.UserFields, error),
	roles []string,
) (printer *spaceUsersPluginPrinter) {
	return &spaceUsersPluginPrinter{
		pluginModel: pluginModel,
		users:       users{db: make(map[string]userWithRoles)},
		userLister:  userLister,
		roles:       roles,
	}
}

func (p *orgUsersPluginPrinter) PrintUsers(guid string, username string) {
	for _, role := range p.roles {
		users, _ := p.userLister(guid, role)
		for _, user := range users {
			p.users.storeAppendingRole(role, user.Username, user.Guid, user.IsAdmin)
		}
	}
	*(p.pluginModel) = p.users.asOrgUsers()
}

func (p *spaceUsersPluginPrinter) PrintUsers(guid string, username string) {
	for _, role := range p.roles {
		users, _ := p.userLister(guid, role)
		for _, user := range users {
			p.users.storeAppendingRole(role, user.Username, user.Guid, user.IsAdmin)
		}
	}
	*(p.pluginModel) = p.users.asSpaceUsers()
}

func (p *OrgUsersUiPrinter) PrintUsers(guid string, username string) {
	for _, role := range p.Roles {
		displayName := p.RoleDisplayNames[role]
		users, err := p.UserLister(guid, role)
		if err != nil {
			p.Ui.Failed(T("Failed fetching org-users for role {{.OrgRoleToDisplayName}}.\n{{.Error}}",
				map[string]interface{}{
					"Error":                err.Error(),
					"OrgRoleToDisplayName": displayName,
				}))
			return
		}
		p.Ui.Say("")
		p.Ui.Say("%s", terminal.HeaderColor(displayName))

		if len(users) == 0 {
			p.Ui.Say(fmt.Sprintf("  "+T("No %s found"), displayName))
		} else {
			for _, user := range users {
				p.Ui.Say("  %s", user.Username)
			}
		}
	}
}

func (p *SpaceUsersUiPrinter) PrintUsers(guid string, username string) {
	for _, role := range p.Roles {
		displayName := p.RoleDisplayNames[role]
		users, err := p.UserLister(guid, role)
		if err != nil {
			p.Ui.Failed(T("Failed fetching space-users for role {{.SpaceRoleToDisplayName}}.\n{{.Error}}",
				map[string]interface{}{
					"Error":                  err.Error(),
					"SpaceRoleToDisplayName": displayName,
				}))
			return
		}
		p.Ui.Say("")
		p.Ui.Say("%s", terminal.HeaderColor(displayName))

		if len(users) == 0 {
			p.Ui.Say(fmt.Sprintf("  "+T("No %s found"), displayName))
		} else {
			for _, user := range users {
				p.Ui.Say("  %s", user.Username)
			}
		}
	}
}

func (c *users) storeAppendingRole(role string, username string, guid string, isAdmin bool) {
	u := c.db[username]
	u.roles = append(u.roles, role)
	u.Username = username
	u.Guid = guid
	u.IsAdmin = isAdmin
	c.db[username] = u
}

func (c *users) asOrgUsers() (coll []plugin_models.GetOrgUsers_Model) {
	for _, u := range c.db {
		coll = append(coll, plugin_models.GetOrgUsers_Model{
			Guid:     u.Guid,
			Username: u.Username,
			IsAdmin:  u.IsAdmin,
			Roles:    u.roles,
		})
	}
	return coll
}

func (c *users) asSpaceUsers() (coll []plugin_models.GetSpaceUsers_Model) {
	for _, u := range c.db {
		coll = append(coll, plugin_models.GetSpaceUsers_Model{
			Guid:     u.Guid,
			Username: u.Username,
			IsAdmin:  u.IsAdmin,
			Roles:    u.roles,
		})
	}
	return coll
}
<|endoftext|>


<commit_before>// +build linux

package fs

import (
	"testing"

	"github.com/opencontainers/runc/libcontainer/configs"
)

var (
	allowedDevices = []*configs.Device{
		{
			Path:        "/dev/zero",
			Type:        'c',
			Major:       1,
			Minor:       5,
			Permissions: "rwm",
			FileMode:    0666,
		},
	}
	allowedList   = "c 1:5 rwm"
	deniedDevices = []*configs.Device{
		{
			Path:        "/dev/null",
			Type:        'c',
			Major:       1,
			Minor:       3,
			Permissions: "rwm",
			FileMode:    0666,
		},
	}
	deniedList = "c 1:3 rwm"
)

func TestDevicesSetAllow(t *testing.T) {
	helper := NewCgroupTestUtil("devices", t)
	defer helper.cleanup()

	helper.writeFileContents(map[string]string{
		"devices.deny": "a",
	})
	allowAllDevices := false
	helper.CgroupData.config.Resources.AllowAllDevices = &allowAllDevices
	helper.CgroupData.config.Resources.AllowedDevices = allowedDevices
	devices := &DevicesGroup{}
	if err := devices.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
		t.Fatal(err)
	}

	value, err := getCgroupParamString(helper.CgroupPath, "devices.allow")
	if err != nil {
		t.Fatalf("Failed to parse devices.allow - %s", err)
	}

	if value != allowedList {
		t.Fatal("Got the wrong value, set devices.allow failed.")
	}
}

func TestDevicesSetDeny(t *testing.T) {
	helper := NewCgroupTestUtil("devices", t)
	defer helper.cleanup()

	helper.writeFileContents(map[string]string{
		"devices.allow": "a",
	})

	allowAllDevices := true
	helper.CgroupData.config.Resources.AllowAllDevices = &allowAllDevices
	helper.CgroupData.config.Resources.DeniedDevices = deniedDevices
	devices := &DevicesGroup{}
	if err := devices.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
		t.Fatal(err)
	}

	value, err := getCgroupParamString(helper.CgroupPath, "devices.deny")
	if err != nil {
		t.Fatalf("Failed to parse devices.deny - %s", err)
	}

	if value != deniedList {
		t.Fatal("Got the wrong value, set devices.deny failed.")
	}
}
<commit_msg>Add test<commit_after>// +build linux

package fs

import (
	"testing"

	"github.com/opencontainers/runc/libcontainer/configs"
)

var (
	allowedDevices = []*configs.Device{
		{
			Path:        "/dev/zero",
			Type:        'c',
			Major:       1,
			Minor:       5,
			Permissions: "rwm",
			FileMode:    0666,
		},
	}
	allowedList   = "c 1:5 rwm"
	deniedDevices = []*configs.Device{
		{
			Path:        "/dev/null",
			Type:        'c',
			Major:       1,
			Minor:       3,
			Permissions: "rwm",
			FileMode:    0666,
		},
	}
	deniedList = "c 1:3 rwm"
)

func TestDevicesSetAllow(t *testing.T) {
	helper := NewCgroupTestUtil("devices", t)
	defer helper.cleanup()

	helper.writeFileContents(map[string]string{
		"devices.deny": "a",
	})
	allowAllDevices := false
	helper.CgroupData.config.Resources.AllowAllDevices = &allowAllDevices
	helper.CgroupData.config.Resources.AllowedDevices = allowedDevices
	devices := &DevicesGroup{}
	if err := devices.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
		t.Fatal(err)
	}

	value, err := getCgroupParamString(helper.CgroupPath, "devices.allow")
	if err != nil {
		t.Fatalf("Failed to parse devices.allow - %s", err)
	}

	if value != allowedList {
		t.Fatal("Got the wrong value, set devices.allow failed.")
	}

	// When AllowAllDevices is nil, devices.allow file should not be modified.
	helper.CgroupData.config.Resources.AllowAllDevices = nil
	if err := devices.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
		t.Fatal(err)
	}
	value, err = getCgroupParamString(helper.CgroupPath, "devices.allow")
	if err != nil {
		t.Fatalf("Failed to parse devices.allow - %s", err)
	}
	if value != allowedList {
		t.Fatal("devices policy shouldn't have changed on AllowedAllDevices=nil.")
	}
}

func TestDevicesSetDeny(t *testing.T) {
	helper := NewCgroupTestUtil("devices", t)
	defer helper.cleanup()

	helper.writeFileContents(map[string]string{
		"devices.allow": "a",
	})

	allowAllDevices := true
	helper.CgroupData.config.Resources.AllowAllDevices = &allowAllDevices
	helper.CgroupData.config.Resources.DeniedDevices = deniedDevices
	devices := &DevicesGroup{}
	if err := devices.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
		t.Fatal(err)
	}

	value, err := getCgroupParamString(helper.CgroupPath, "devices.deny")
	if err != nil {
		t.Fatalf("Failed to parse devices.deny - %s", err)
	}

	if value != deniedList {
		t.Fatal("Got the wrong value, set devices.deny failed.")
	}
}
<|endoftext|>


<commit_before>package checkntpoffset

import (
	"bufio"
	"fmt"
	"io"
	"math"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/beevik/ntp"
	"github.com/jessevdk/go-flags"
	"github.com/mackerelio/checkers"
)

var opts struct {
	Crit       float64 `short:"c" long:"critical" default:"100" description:"Critical threshold of ntp offset(ms)"`
	Warn       float64 `short:"w" long:"warning" default:"50" description:"Warning threshold of ntp offset(ms)"`
	NTPServers string  `short:"s" long:"ntp-servers" default:"" description:"Use specified NTP Servers(plural servers can be set separated by ,). When set plural servers, use first response. If not set, use local command just like ntpd/chronyd."`
	NTPTimeout int     `short:"t" long:"ntp-timeout" default:"15" description:"Timeout of NTP Server Querying(in seconds)."`
}

var ntpTimeout int

func init() {
	ntpTimeout = 15
}

// Do the plugin
func Do() {
	ckr := run(os.Args[1:])
	ckr.Name = "NTP"
	ckr.Exit()
}

func run(args []string) *checkers.Checker {
	_, err := flags.ParseArgs(&opts, args)
	if err != nil {
		os.Exit(1)
	}
	ntpTimeout = opts.NTPTimeout

	offset, err := getNTPOffset(opts.NTPServers)
	if err != nil {
		return checkers.Unknown(err.Error())
	}

	var chkSt checkers.Status
	var msg string
	stratum, err := getNTPStratum()
	if err != nil {
		msg = fmt.Sprintf("ntp stratum not found offset is %f(actual), %f(warning threshold), %f(critical threshold)", math.Abs(offset), opts.Warn, opts.Crit)
		return checkers.NewChecker(checkers.WARNING, msg)
	}

	if opts.Crit < math.Abs(offset) {
		msg = fmt.Sprintf("ntp offset is over %f(actual) > %f(threshold)", math.Abs(offset), opts.Crit)
		chkSt = checkers.CRITICAL
	} else if opts.Warn < math.Abs(offset) {
		msg = fmt.Sprintf("ntp offset is over %f(actual) > %f(threshold)", math.Abs(offset), opts.Warn)
		chkSt = checkers.WARNING
	} else {
		msg = fmt.Sprintf("ntp stratum=%s, offset is %f(actual) < %f(warning threshold), %f(critial threshold)", stratum, math.Abs(offset), opts.Warn, opts.Crit)
		chkSt = checkers.OK
	}

	return checkers.NewChecker(chkSt, msg)
}

func withCmd(cmd *exec.Cmd, fn func(io.Reader) error) error {
	out, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}
	if err := cmd.Start(); err != nil {
		return err
	}
	if err := fn(out); err != nil {
		return err
	}
	return cmd.Wait()
}

const (
	ntpNTPD    = "ntpd"
	ntpChronyd = "chronyd"

	cmdNTPq    = "ntpq"
	cmdChronyc = "chronyc"
)

func hasCommand(cmd string) bool {
	_, err := exec.LookPath(cmd)
	return err == nil
}

func detectNTPDname() (ntpdName string, err error) {
	err = withCmd(exec.Command("ps", "-eo", "comm"), func(out io.Reader) error {
		scr := bufio.NewScanner(out)
		for scr.Scan() {
			switch filepath.Base(scr.Text()) {
			case ntpChronyd:
				if hasCommand(cmdChronyc) {
					ntpdName = ntpChronyd
					return nil
				}
			case ntpNTPD:
				if hasCommand(cmdNTPq) {
					ntpdName = ntpNTPD
					return nil
				}
			}
		}
		return fmt.Errorf("no ntp daemons detected")
	})
	return ntpdName, err
}

func getNTPOffset(ntpServers string) (float64, error) {
	if ntpServers != "" {
		return getNTPOffsetFromNTPServers(ntpServers)
	}
	ntpdName, err := detectNTPDname()
	if err != nil {
		return 0.0, err
	}
	switch ntpdName {
	case ntpNTPD:
		return getNTPOffsetFromNTPD()
	case ntpChronyd:
		return getNTPOffsetFromChrony()
	}
	return 0.0, fmt.Errorf("unsupported ntp daemon %q", ntpdName)
}

// getNTPOffsetFromNTPServers ask time to ntp servers and return NTP Offset.
// Use first response, ignore others
//
// FIXME need fluent cancel mechanism
func getNTPOffsetFromNTPServers(ntpServers string) (offset float64, err error) {
	resultChan := make(chan float64)
	for _, ntpServer := range strings.Split(ntpServers, ",") {
		go func(ntpServer string) error {
			ntpServer = strings.Trim(ntpServer, " ")
			options := ntp.QueryOptions{Timeout: time.Duration(ntpTimeout) * time.Second}
			response, err := ntp.QueryWithOptions(ntpServer, options)
			if err != nil {
				return err
			}
			resultChan <- float64(response.ClockOffset / time.Millisecond)
			return nil
		}(ntpServer)
	}

	select {
	case <-time.After(time.Duration(ntpTimeout) * time.Second):
		// return error only when all NTPServers are failed
		return 0.0, fmt.Errorf("NTP offset cannot get from %q", ntpServers)
	case offset = <-resultChan:
		return offset, nil
	}
}

func getNTPOffsetFromNTPD() (offset float64, err error) {
	err = withCmd(exec.Command(cmdNTPq, "-c", "rv 0 offset"), func(out io.Reader) error {
		offset, err = parseNTPOffsetFromNTPD(out)
		return err
	})
	return offset, err
}

func parseNTPOffsetFromNTPD(out io.Reader) (float64, error) {
	scr := bufio.NewScanner(out)
	const offsetPrefix = "offset="
	for scr.Scan() {
		line := scr.Text()
		if strings.HasPrefix(line, offsetPrefix) {
			return strconv.ParseFloat(strings.TrimPrefix(line, offsetPrefix), 64)
		}
	}
	return 0.0, fmt.Errorf("couldn't get ntp offset. ntpd process may be down")
}

func getNTPOffsetFromChrony() (offset float64, err error) {
	err = withCmd(exec.Command(cmdChronyc, "tracking"), func(out io.Reader) error {
		offset, err = parseNTPOffsetFromChrony(out)
		return err
	})
	return offset, err
}

func parseNTPOffsetFromChrony(out io.Reader) (offset float64, err error) {
	scr := bufio.NewScanner(out)
	for scr.Scan() {
		line := scr.Text()
		if strings.HasPrefix(line, "Last offset") {
			flds := strings.Fields(line)
			if len(flds) != 5 {
				return 0.0, fmt.Errorf("failed to get ntp offset")
			}
			offset, err = strconv.ParseFloat(flds[3], 64)
			if err != nil {
				return 0.0, err
			}
			return offset * 1000, nil
		}
	}
	return 0.0, fmt.Errorf("failed to get ntp offset")
}

func getNTPStratum() (string, error) {
	ntpdName, err := detectNTPDname()
	if err != nil {
		return "unknown", err
	}
	switch ntpdName {
	case ntpNTPD:
		return getNTPStratumFromNTPD()
	case ntpChronyd:
		return getNTPStratumFromChrony()
	}
	return "unknown", fmt.Errorf("unsupported ntp daemon %q", ntpdName)
}

func getNTPStratumFromNTPD() (stratum string, err error) {
	err = withCmd(exec.Command(cmdNTPq, "-np"), func(out io.Reader) error {
		stratum, err = parseNTPStratumFromNTPD(out)
		return err
	})
	return stratum, err
}

func parseNTPStratumFromNTPD(out io.Reader) (string, error) {
	scr := bufio.NewScanner(out)
	const stratumPrefix = "*"
	for scr.Scan() {
		line := scr.Text()
		if strings.HasPrefix(line, stratumPrefix) {
			flds := strings.Fields(line)
			if len(flds) != 10 {
				return "unknown", fmt.Errorf("failed to get ntp stratum")
			}
			return strings.TrimPrefix(flds[0], stratumPrefix), nil
		}
	}
	return "unknown", fmt.Errorf("failed to get ntp stratum")
}

func getNTPStratumFromChrony() (stratum string, err error) {
	err = withCmd(exec.Command(cmdChronyc, "-n", "sources"), func(out io.Reader) error {
		stratum, err = parseNTPStratumFromChrony(out)
		return err
	})
	return stratum, err
}

func parseNTPStratumFromChrony(out io.Reader) (string, error) {
	scr := bufio.NewScanner(out)
	const stratumPrefix = "^*"
	for scr.Scan() {
		line := scr.Text()
		if strings.HasPrefix(line, stratumPrefix) {
			flds := strings.Fields(line)
			if len(flds) != 10 {
				return "unknown", fmt.Errorf("failed to get ntp stratum")
			}
			return flds[1], nil
		}
	}
	return "unknown", fmt.Errorf("failed to get ntp stratum")
}
<commit_msg>replace `unknown` with empty string<commit_after>package checkntpoffset

import (
	"bufio"
	"fmt"
	"io"
	"math"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/beevik/ntp"
	"github.com/jessevdk/go-flags"
	"github.com/mackerelio/checkers"
)

var opts struct {
	Crit       float64 `short:"c" long:"critical" default:"100" description:"Critical threshold of ntp offset(ms)"`
	Warn       float64 `short:"w" long:"warning" default:"50" description:"Warning threshold of ntp offset(ms)"`
	NTPServers string  `short:"s" long:"ntp-servers" default:"" description:"Use specified NTP Servers(plural servers can be set separated by ,). When set plural servers, use first response. If not set, use local command just like ntpd/chronyd."`
	NTPTimeout int     `short:"t" long:"ntp-timeout" default:"15" description:"Timeout of NTP Server Querying(in seconds)."`
}

var ntpTimeout int

func init() {
	ntpTimeout = 15
}

// Do the plugin
func Do() {
	ckr := run(os.Args[1:])
	ckr.Name = "NTP"
	ckr.Exit()
}

func run(args []string) *checkers.Checker {
	_, err := flags.ParseArgs(&opts, args)
	if err != nil {
		os.Exit(1)
	}
	ntpTimeout = opts.NTPTimeout

	offset, err := getNTPOffset(opts.NTPServers)
	if err != nil {
		return checkers.Unknown(err.Error())
	}

	var chkSt checkers.Status
	var msg string
	stratum, err := getNTPStratum()
	if err != nil {
		msg = fmt.Sprintf("ntp stratum not found offset is %f(actual), %f(warning threshold), %f(critical threshold)", math.Abs(offset), opts.Warn, opts.Crit)
		return checkers.NewChecker(checkers.WARNING, msg)
	}

	if opts.Crit < math.Abs(offset) {
		msg = fmt.Sprintf("ntp offset is over %f(actual) > %f(threshold)", math.Abs(offset), opts.Crit)
		chkSt = checkers.CRITICAL
	} else if opts.Warn < math.Abs(offset) {
		msg = fmt.Sprintf("ntp offset is over %f(actual) > %f(threshold)", math.Abs(offset), opts.Warn)
		chkSt = checkers.WARNING
	} else {
		msg = fmt.Sprintf("ntp stratum=%s, offset is %f(actual) < %f(warning threshold), %f(critial threshold)", stratum, math.Abs(offset), opts.Warn, opts.Crit)
		chkSt = checkers.OK
	}

	return checkers.NewChecker(chkSt, msg)
}

func withCmd(cmd *exec.Cmd, fn func(io.Reader) error) error {
	out, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}
	if err := cmd.Start(); err != nil {
		return err
	}
	if err := fn(out); err != nil {
		return err
	}
	return cmd.Wait()
}

const (
	ntpNTPD    = "ntpd"
	ntpChronyd = "chronyd"

	cmdNTPq    = "ntpq"
	cmdChronyc = "chronyc"
)

func hasCommand(cmd string) bool {
	_, err := exec.LookPath(cmd)
	return err == nil
}

func detectNTPDname() (ntpdName string, err error) {
	err = withCmd(exec.Command("ps", "-eo", "comm"), func(out io.Reader) error {
		scr := bufio.NewScanner(out)
		for scr.Scan() {
			switch filepath.Base(scr.Text()) {
			case ntpChronyd:
				if hasCommand(cmdChronyc) {
					ntpdName = ntpChronyd
					return nil
				}
			case ntpNTPD:
				if hasCommand(cmdNTPq) {
					ntpdName = ntpNTPD
					return nil
				}
			}
		}
		return fmt.Errorf("no ntp daemons detected")
	})
	return ntpdName, err
}

func getNTPOffset(ntpServers string) (float64, error) {
	if ntpServers != "" {
		return getNTPOffsetFromNTPServers(ntpServers)
	}
	ntpdName, err := detectNTPDname()
	if err != nil {
		return 0.0, err
	}
	switch ntpdName {
	case ntpNTPD:
		return getNTPOffsetFromNTPD()
	case ntpChronyd:
		return getNTPOffsetFromChrony()
	}
	return 0.0, fmt.Errorf("unsupported ntp daemon %q", ntpdName)
}

// getNTPOffsetFromNTPServers ask time to ntp servers and return NTP Offset.
// Use first response, ignore others
//
// FIXME need fluent cancel mechanism
func getNTPOffsetFromNTPServers(ntpServers string) (offset float64, err error) {
	resultChan := make(chan float64)
	for _, ntpServer := range strings.Split(ntpServers, ",") {
		go func(ntpServer string) error {
			ntpServer = strings.Trim(ntpServer, " ")
			options := ntp.QueryOptions{Timeout: time.Duration(ntpTimeout) * time.Second}
			response, err := ntp.QueryWithOptions(ntpServer, options)
			if err != nil {
				return err
			}
			resultChan <- float64(response.ClockOffset / time.Millisecond)
			return nil
		}(ntpServer)
	}

	select {
	case <-time.After(time.Duration(ntpTimeout) * time.Second):
		// return error only when all NTPServers are failed
		return 0.0, fmt.Errorf("NTP offset cannot get from %q", ntpServers)
	case offset = <-resultChan:
		return offset, nil
	}
}

func getNTPOffsetFromNTPD() (offset float64, err error) {
	err = withCmd(exec.Command(cmdNTPq, "-c", "rv 0 offset"), func(out io.Reader) error {
		offset, err = parseNTPOffsetFromNTPD(out)
		return err
	})
	return offset, err
}

func parseNTPOffsetFromNTPD(out io.Reader) (float64, error) {
	scr := bufio.NewScanner(out)
	const offsetPrefix = "offset="
	for scr.Scan() {
		line := scr.Text()
		if strings.HasPrefix(line, offsetPrefix) {
			return strconv.ParseFloat(strings.TrimPrefix(line, offsetPrefix), 64)
		}
	}
	return 0.0, fmt.Errorf("couldn't get ntp offset. ntpd process may be down")
}

func getNTPOffsetFromChrony() (offset float64, err error) {
	err = withCmd(exec.Command(cmdChronyc, "tracking"), func(out io.Reader) error {
		offset, err = parseNTPOffsetFromChrony(out)
		return err
	})
	return offset, err
}

func parseNTPOffsetFromChrony(out io.Reader) (offset float64, err error) {
	scr := bufio.NewScanner(out)
	for scr.Scan() {
		line := scr.Text()
		if strings.HasPrefix(line, "Last offset") {
			flds := strings.Fields(line)
			if len(flds) != 5 {
				return 0.0, fmt.Errorf("failed to get ntp offset")
			}
			offset, err = strconv.ParseFloat(flds[3], 64)
			if err != nil {
				return 0.0, err
			}
			return offset * 1000, nil
		}
	}
	return 0.0, fmt.Errorf("failed to get ntp offset")
}

func getNTPStratum() (string, error) {
	ntpdName, err := detectNTPDname()
	if err != nil {
		return "", err
	}
	switch ntpdName {
	case ntpNTPD:
		return getNTPStratumFromNTPD()
	case ntpChronyd:
		return getNTPStratumFromChrony()
	}
	return "", fmt.Errorf("unsupported ntp daemon %q", ntpdName)
}

func getNTPStratumFromNTPD() (stratum string, err error) {
	err = withCmd(exec.Command(cmdNTPq, "-np"), func(out io.Reader) error {
		stratum, err = parseNTPStratumFromNTPD(out)
		return err
	})
	return stratum, err
}

func parseNTPStratumFromNTPD(out io.Reader) (string, error) {
	scr := bufio.NewScanner(out)
	const stratumPrefix = "*"
	for scr.Scan() {
		line := scr.Text()
		if strings.HasPrefix(line, stratumPrefix) {
			flds := strings.Fields(line)
			if len(flds) != 10 {
				return "unknown", fmt.Errorf("failed to get ntp stratum")
			}
			return strings.TrimPrefix(flds[0], stratumPrefix), nil
		}
	}
	return "unknown", fmt.Errorf("failed to get ntp stratum")
}

func getNTPStratumFromChrony() (stratum string, err error) {
	err = withCmd(exec.Command(cmdChronyc, "-n", "sources"), func(out io.Reader) error {
		stratum, err = parseNTPStratumFromChrony(out)
		return err
	})
	return stratum, err
}

func parseNTPStratumFromChrony(out io.Reader) (string, error) {
	scr := bufio.NewScanner(out)
	const stratumPrefix = "^*"
	for scr.Scan() {
		line := scr.Text()
		if strings.HasPrefix(line, stratumPrefix) {
			flds := strings.Fields(line)
			if len(flds) != 10 {
				return "unknown", fmt.Errorf("failed to get ntp stratum")
			}
			return flds[1], nil
		}
	}
	return "unknown", fmt.Errorf("failed to get ntp stratum")
}
<|endoftext|>


<commit_before>package upkeep

import (
	pxy "github.com/jeffjen/ambd/ambctl/arg"

	log "github.com/Sirupsen/logrus"
	docker "github.com/fsouza/go-dockerclient"
	dri "github.com/jeffjen/docker-monitor/driver"
	disc "github.com/jeffjen/go-discovery"
	node "github.com/jeffjen/go-discovery/info"

	etcd "github.com/coreos/etcd/client"
	ctx "golang.org/x/net/context"

	"fmt"
	"os"
	"path"
	"time"
)

func init() {
	var level = os.Getenv("LOG_LEVEL")
	switch level {
	case "DEBUG":
		log.SetLevel(log.DebugLevel)
		break
	case "INFO":
		log.SetLevel(log.InfoLevel)
		break
	case "WARNING":
		log.SetLevel(log.WarnLevel)
		break
	case "ERROR":
		log.SetLevel(log.ErrorLevel)
		break
	case "FATAL":
		log.SetLevel(log.FatalLevel)
		break
	case "PANIC":
		log.SetLevel(log.PanicLevel)
		break
	default:
		log.SetLevel(log.InfoLevel)
		break
	}
}

const (
	UpkeepTimeout = 250 * time.Millisecond

	ProbeTimeout = 100 * time.Millisecond

	MaxFailAttemps = 3
)

type Service struct {
	Hb  time.Duration `json: "Heartbeat"`
	TTL time.Duration `json: "TTL"`

	PHb           time.Duration `json: "ProbeHeartbeat"`
	ProbeType     string        `json: "ProbeType"`
	ProbeEndpoint string        `json: "ProbeEndpoint"`

	Id       string           `json: "ContainerID"`
	Srv      string           `json: "Service"`
	Port     string           `json: "Port"`
	Net      []docker.APIPort `json: "Net"`
	Proxy    []pxy.Info       `json: "Proxy"`
	ProxyCfg string           `json: "ProxyCfg"`
}

type srv struct {
	*Service // embedded type

	key []string

	kAPI etcd.KeysAPI
	opts *etcd.SetOptions

	driver dri.Driver
}

func (serv *srv) keep(c ctx.Context) (resp <-chan error) {
	output := make(chan error, 1)
	go func() {
		var err error
		for _, k := range serv.key {
			_, err = serv.kAPI.Set(c, k, node.MetaData, serv.opts)
			if err == nil {
				serv.opts.PrevExist = etcd.PrevExist
			} else {
				serv.opts.PrevExist = etcd.PrevIgnore
				break
			}
		}
		log.WithFields(log.Fields{"err": err, "key": serv.key}).Debug("keep")
		output <- err
	}()
	return output
}

func (serv *srv) probe(c ctx.Context) (resp <-chan error) {
	output := make(chan error, 1)
	go func() {
		var err error
		if err = serv.driver.Probe(c); err != nil {
			output <- err
		} else {
			output <- nil
		}
		log.WithFields(log.Fields{"err": err, "srv": serv.Srv}).Debug("probe")
	}()
	return output
}

func Get(iden string) (s *Service) {
	s, _ = rec.Get(iden).(*Service)
	return
}

func Place(service *Service) {
	if r, ok := Record[service.Id]; ok {
		r.Abort()
	}
	rec.Set(service.Id, service)
	Register(service)
}

func Register(service *Service) {
	alloc := AllocHelper(service.ProbeType)

	serv := &srv{
		service,
		make([]string, 0),
		etcd.NewKeysAPI(disc.NewDiscovery()),
		&etcd.SetOptions{TTL: service.TTL},
		nil,
	}

	logger := log.WithFields(log.Fields{
		"ID": serv.Id[:12], "srv": serv.Srv, "heartbeat": serv.Hb, "ttl": serv.TTL,
	})

	// Advertise key on the discovery service
	if serv.Port != "" {
		serv.key = append(serv.key, fmt.Sprintf("%s/%s:%s", serv.Srv, Advertise, serv.Port))
	} else if len(serv.Net) > 0 {
		serv.key = make([]string, 0)
		for _, p := range serv.Net {
			if p.PublicPort != 0 && p.IP == "0.0.0.0" {
				serv.key = append(serv.key, fmt.Sprintf("%s/%s:%d", serv.Srv, Advertise, p.PublicPort))
			}
		}
	}

	var endpoint string
	if serv.ProbeEndpoint == "" {
		endpoint = path.Base(serv.key[0])
	} else {
		endpoint = fmt.Sprintf("%s/%s", path.Base(serv.key[0]), serv.ProbeEndpoint)
	}

	// TODO:  setup driver for probing
	driver, drr := alloc(endpoint)
	if drr != nil {
		logger.WithFields(log.Fields{"err": drr}).Error("-register")
		return
	}
	serv.driver = driver
	logger.Debug("+register")

	c, abort := ctx.WithCancel(RootContext)
	go func() {
		// Request to establish proxy port to ambassador
		openProxyConfig(serv.ProxyCfg, serv.Proxy)

		// setup work cycle
		heartbeat, probe := time.NewTicker(serv.Hb), time.NewTicker(serv.PHb)
		defer func() {
			heartbeat.Stop()
			probe.Stop()
		}()

		logger.Info("start")
		func() {
			d, abort := ctx.WithTimeout(c, UpkeepTimeout)
			select {
			case <-d.Done():
				logger.WithFields(log.Fields{"err": d.Err()}).Error("*up")
			case e := <-serv.keep(d):
				if e != nil {
					logger.WithFields(log.Fields{"err": e}).Error("-up")
				} else {
					logger.Info("+up")
				}
			}
			abort() // release
		}()

		var chk = NewFail(MaxFailAttemps)
		for yay := true; yay; {
			select {
			case <-heartbeat.C:
				if !chk.Pass() {
					serv.opts.PrevExist = etcd.PrevIgnore
					logger.Error("!up")
				} else {
					d, abort := ctx.WithTimeout(c, UpkeepTimeout)
					select {
					case <-d.Done():
						logger.WithFields(log.Fields{"err": d.Err()}).Error("*up")
					case e := <-serv.keep(d):
						if e != nil {
							logger.WithFields(log.Fields{"err": e}).Error("-up")
						} else {
							logger.Info("+up")
						}
					}
					abort() // release
				}

			case <-probe.C:
				d, abort := ctx.WithTimeout(c, ProbeTimeout)
				select {
				case <-d.Done():
					count := chk.Bad()
					logger.WithFields(log.Fields{"err": d.Err(), "fail": count}).Warning("*probe")
				case e := <-serv.probe(d):
					if e != nil {
						count := chk.Bad()
						logger.WithFields(log.Fields{"err": e, "fail": count}).Warning("-probe")
					} else {
						chk.Good()
						logger.Info("+probe")
					}
				}
				abort() // release

			case <-c.Done():
				logger.Warning("down")
				yay = false
			}
		}
	}()

	Record[serv.Id] = &RunningRecord{serv.Srv, abort} // register abort function for this service
}

func Suspend(iden string) {
	if r, ok := Record[iden]; ok {
		r.Abort()
	}
}

func Unregister(iden string) {
	if r, ok := Record[iden]; ok {
		delete(Record, iden)
		r.Abort()
		rec.Del(iden)
		log.WithFields(log.Fields{"ID": iden[:12], "srv": r.Srv}).Warning("die")
	}
}
<commit_msg>UPDATE: streamline upkeep method<commit_after>package upkeep

import (
	pxy "github.com/jeffjen/ambd/ambctl/arg"

	log "github.com/Sirupsen/logrus"
	docker "github.com/fsouza/go-dockerclient"
	dri "github.com/jeffjen/docker-monitor/driver"
	disc "github.com/jeffjen/go-discovery"
	node "github.com/jeffjen/go-discovery/info"

	etcd "github.com/coreos/etcd/client"
	ctx "golang.org/x/net/context"

	"fmt"
	"os"
	"path"
	"time"
)

func init() {
	var level = os.Getenv("LOG_LEVEL")
	switch level {
	case "DEBUG":
		log.SetLevel(log.DebugLevel)
		break
	case "INFO":
		log.SetLevel(log.InfoLevel)
		break
	case "WARNING":
		log.SetLevel(log.WarnLevel)
		break
	case "ERROR":
		log.SetLevel(log.ErrorLevel)
		break
	case "FATAL":
		log.SetLevel(log.FatalLevel)
		break
	case "PANIC":
		log.SetLevel(log.PanicLevel)
		break
	default:
		log.SetLevel(log.InfoLevel)
		break
	}
}

const (
	UpkeepTimeout = 250 * time.Millisecond

	ProbeTimeout = 100 * time.Millisecond

	MaxFailAttemps = 3
)

type Service struct {
	Hb  time.Duration `json: "Heartbeat"`
	TTL time.Duration `json: "TTL"`

	PHb           time.Duration `json: "ProbeHeartbeat"`
	ProbeType     string        `json: "ProbeType"`
	ProbeEndpoint string        `json: "ProbeEndpoint"`

	Id       string           `json: "ContainerID"`
	Srv      string           `json: "Service"`
	Port     string           `json: "Port"`
	Net      []docker.APIPort `json: "Net"`
	Proxy    []pxy.Info       `json: "Proxy"`
	ProxyCfg string           `json: "ProxyCfg"`
}

type srv struct {
	*Service // embedded type

	key []string

	kAPI etcd.KeysAPI
	opts *etcd.SetOptions

	driver dri.Driver
}

func (serv *srv) keep(c ctx.Context) (err error) {
	output := make(chan error, 1)
	go func() {
		for _, k := range serv.key {
			_, krr := serv.kAPI.Set(c, k, node.MetaData, serv.opts)
			if krr != nil {
				serv.opts.PrevExist = etcd.PrevIgnore
				output <- err
				return // break out
			} else {
				serv.opts.PrevExist = etcd.PrevExist
			}
		}
		output <- nil // all good
	}()
	select {
	case <-c.Done():
		err = c.Err()
	case e := <-output:
		err = e
	}
	return
}

func (serv *srv) probe(c ctx.Context) (err error) {
	output := make(chan error, 1)
	go func() {
		prr := serv.driver.Probe(c)
		if err != nil {
			output <- prr
		} else {
			output <- nil
		}
		log.WithFields(log.Fields{"err": err, "srv": serv.Srv}).Debug("probe")
	}()
	select {
	case <-c.Done():
		err = c.Err()
	case e := <-output:
		err = e
	}
	return
}

func Get(iden string) (s *Service) {
	s, _ = rec.Get(iden).(*Service)
	return
}

func Place(service *Service) {
	if r, ok := Record[service.Id]; ok {
		r.Abort()
	}
	rec.Set(service.Id, service)
	Register(service)
}

func Register(service *Service) {
	alloc := AllocHelper(service.ProbeType)

	serv := &srv{
		service,
		make([]string, 0),
		etcd.NewKeysAPI(disc.NewDiscovery()),
		&etcd.SetOptions{TTL: service.TTL},
		nil,
	}

	logger := log.WithFields(log.Fields{
		"ID": serv.Id[:12], "srv": serv.Srv, "heartbeat": serv.Hb, "ttl": serv.TTL,
	})

	// Advertise key on the discovery service
	if serv.Port != "" {
		serv.key = append(serv.key, fmt.Sprintf("%s/%s:%s", serv.Srv, Advertise, serv.Port))
	} else if len(serv.Net) > 0 {
		serv.key = make([]string, 0)
		for _, p := range serv.Net {
			if p.PublicPort != 0 && p.IP == "0.0.0.0" {
				serv.key = append(serv.key, fmt.Sprintf("%s/%s:%d", serv.Srv, Advertise, p.PublicPort))
			}
		}
	}

	var endpoint string
	if serv.ProbeEndpoint == "" {
		endpoint = path.Base(serv.key[0])
	} else {
		endpoint = fmt.Sprintf("%s/%s", path.Base(serv.key[0]), serv.ProbeEndpoint)
	}

	// TODO:  setup driver for probing
	driver, drr := alloc(endpoint)
	if drr != nil {
		logger.WithFields(log.Fields{"err": drr}).Error("-register")
		return
	}
	serv.driver = driver
	logger.Debug("+register")

	wk, abort := ctx.WithCancel(RootContext)
	go func() {
		// Request to establish proxy port to ambassador
		openProxyConfig(serv.ProxyCfg, serv.Proxy)

		// setup work cycle
		heartbeat, probe := time.NewTicker(serv.Hb), time.NewTicker(serv.PHb)
		defer func() {
			heartbeat.Stop()
			probe.Stop()
		}()

		logger.Info("start")
		func() {
			d, abort := ctx.WithTimeout(wk, UpkeepTimeout)
			if err := serv.keep(d); err != nil {
				logger.WithFields(log.Fields{"err": err}).Error("-up")
			} else {
				logger.Info("+up")
			}
			abort() // release
		}()

		var chk = NewFail(MaxFailAttemps)
		for yay := true; yay; {
			select {
			case <-heartbeat.C:
				d, abort := ctx.WithTimeout(wk, UpkeepTimeout)
				if !chk.Pass() {
					serv.opts.PrevExist = etcd.PrevIgnore
					logger.Error("!up")
				} else {
					if err := serv.keep(d); err != nil {
						logger.WithFields(log.Fields{"err": err}).Error("-up")
					} else {
						logger.Info("+up")
					}
				}
				abort() // release

			case <-probe.C:
				d, abort := ctx.WithTimeout(wk, ProbeTimeout)
				if err := serv.probe(d); err != nil {
					count := chk.Bad()
					logger.WithFields(log.Fields{"err": err, "fail": count}).Warning("-probe")
				} else {
					chk.Good()
					logger.Info("+probe")
				}
				abort() // release

			case <-wk.Done():
				logger.Warning("down")
				yay = false
			}
		}
	}()

	Record[serv.Id] = &RunningRecord{serv.Srv, abort} // register abort function for this service
}

func Suspend(iden string) {
	if r, ok := Record[iden]; ok {
		r.Abort()
	}
}

func Unregister(iden string) {
	if r, ok := Record[iden]; ok {
		delete(Record, iden)
		r.Abort()
		rec.Del(iden)
		log.WithFields(log.Fields{"ID": iden[:12], "srv": r.Srv}).Warning("die")
	}
}
<|endoftext|>


<commit_before>package fetch

import (
	"bytes"
	"fmt"
	"github.com/disintegration/imaging"
	"github.com/rwcarlsen/goexif/exif"
	"image"
	"image/jpeg"
	"image/png"
	"io"
	"io/ioutil"
)

func needsRotation(src io.Reader) (bool, int) {
	metadata, err := exif.Decode(src)
	if err != nil {
		fmt.Println(err.Error())
		return false, 0
	}

	orientation, err := metadata.Get(exif.Orientation)
	if err != nil {
		fmt.Println(err.Error())
		return false, 0
	}

	angle := 0
	rotate := false

	switch orientation.String() {
	case "6":
		angle = 90
		rotate = true
	case "3":
		angle = 180
		rotate = true
	case "8":
		angle = 270
		rotate = true
	}

	return rotate, angle
}

func Resize(src io.Reader, c *CacheContext) (io.Reader, error) {
	raw, err := ioutil.ReadAll(src)
	if err != nil {
		fmt.Println(err.Error())
		return nil, err
	}

	data := bytes.NewReader(raw)

	image, format, err := image.Decode(data)
	if err != nil {
		fmt.Println(err.Error())
		return nil, err
	}

	buf := new(bytes.Buffer)

	factor := float64(c.Width) / float64(image.Bounds().Size().X)
	height := int(float64(image.Bounds().Size().Y) * factor)

	image = imaging.Resize(image, c.Width, height, imaging.Linear)

	data.Seek(0, 0)

	if rotate, angle := needsRotation(data); rotate {
		switch angle {
		case 90:
			image = imaging.Rotate90(image)
		case 180:
			image = imaging.Rotate180(image)
		case 270:
			image = imaging.Rotate270(image)
		}
	}

	switch format {
	case "jpeg":
		jpeg.Encode(buf, image, nil)
	case "png":
		err = png.Encode(buf, image)
	}

	return buf, err
}

func CenterCrop(src io.Reader, c *CacheContext) (io.Reader, error) {
	raw, err := ioutil.ReadAll(src)
	if err != nil {
		fmt.Println(err.Error())
		return nil, err
	}

	data := bytes.NewReader(raw)

	image, format, err := image.Decode(data)
	if err != nil {
		fmt.Println(err.Error())
		return nil, err
	}

	data.Seek(0, 0)

	if rotate, angle := needsRotation(data); rotate {
		switch angle {
		case 90:
			image = imaging.Rotate90(image)
		case 180:
			image = imaging.Rotate180(image)
		case 270:
			image = imaging.Rotate270(image)
		}
	}

	buf := new(bytes.Buffer)

	height := image.Bounds().Size().Y
	width := image.Bounds().Size().X

	if width < height {
		image = imaging.CropCenter(image, width, width)
	} else if width > height {
		image = imaging.CropCenter(image, height, height)
	} else {
		image = imaging.CropCenter(image, width, height)
	}

	switch format {
	case "jpeg":
		jpeg.Encode(buf, image, nil)
	case "png":
		err = png.Encode(buf, image)
	}

	return buf, err
}
<commit_msg>More helpful error messages<commit_after>package fetch

import (
	"bytes"
	"fmt"
	"github.com/disintegration/imaging"
	"github.com/rwcarlsen/goexif/exif"
	"image"
	"image/jpeg"
	"image/png"
	"io"
	"io/ioutil"
)

func needsRotation(src io.Reader) (bool, int) {
	metadata, err := exif.Decode(src)
	if err != nil {
		fmt.Println("Error decoding EXIF data: " + err.Error())
		return false, 0
	}

	orientation, err := metadata.Get(exif.Orientation)
	if err != nil {
		fmt.Println("Error trying to find EXIF orientation: " + err.Error())
		return false, 0
	}

	angle := 0
	rotate := false

	switch orientation.String() {
	case "6":
		angle = 90
		rotate = true
	case "3":
		angle = 180
		rotate = true
	case "8":
		angle = 270
		rotate = true
	}

	return rotate, angle
}

func Resize(src io.Reader, c *CacheContext) (io.Reader, error) {
	raw, err := ioutil.ReadAll(src)
	if err != nil {
		fmt.Println(err.Error())
		return nil, err
	}

	data := bytes.NewReader(raw)

	image, format, err := image.Decode(data)
	if err != nil {
		fmt.Println(err.Error())
		return nil, err
	}

	buf := new(bytes.Buffer)

	factor := float64(c.Width) / float64(image.Bounds().Size().X)
	height := int(float64(image.Bounds().Size().Y) * factor)

	image = imaging.Resize(image, c.Width, height, imaging.Linear)

	data.Seek(0, 0)

	if rotate, angle := needsRotation(data); rotate {
		switch angle {
		case 90:
			image = imaging.Rotate90(image)
		case 180:
			image = imaging.Rotate180(image)
		case 270:
			image = imaging.Rotate270(image)
		}
	}

	switch format {
	case "jpeg":
		jpeg.Encode(buf, image, nil)
	case "png":
		err = png.Encode(buf, image)
	}

	return buf, err
}

func CenterCrop(src io.Reader, c *CacheContext) (io.Reader, error) {
	raw, err := ioutil.ReadAll(src)
	if err != nil {
		fmt.Println(err.Error())
		return nil, err
	}

	data := bytes.NewReader(raw)

	image, format, err := image.Decode(data)
	if err != nil {
		fmt.Println(err.Error())
		return nil, err
	}

	data.Seek(0, 0)

	if rotate, angle := needsRotation(data); rotate {
		switch angle {
		case 90:
			image = imaging.Rotate90(image)
		case 180:
			image = imaging.Rotate180(image)
		case 270:
			image = imaging.Rotate270(image)
		}
	}

	buf := new(bytes.Buffer)

	height := image.Bounds().Size().Y
	width := image.Bounds().Size().X

	if width < height {
		image = imaging.CropCenter(image, width, width)
	} else if width > height {
		image = imaging.CropCenter(image, height, height)
	} else {
		image = imaging.CropCenter(image, width, height)
	}

	switch format {
	case "jpeg":
		jpeg.Encode(buf, image, nil)
	case "png":
		err = png.Encode(buf, image)
	}

	return buf, err
}
<|endoftext|>


<commit_before>package datadog

import (
	"compress/zlib"
	"context"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/DataDog/datadog-go/statsd"
	"github.com/sirupsen/logrus"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/stripe/veneur/samplers"
	"github.com/stripe/veneur/ssf"
)

// DDMetricsRequest represents the body of the POST request
// for sending metrics data to Datadog
// Eventually we'll want to define this symmetrically.
type DDMetricsRequest struct {
	Series []DDMetric
}

func TestDatadogRate(t *testing.T) {
	ddSink := DatadogMetricSink{
		hostname: "somehostname",
		tags:     []string{"a:b", "c:d"},
		interval: 10,
	}

	metrics := []samplers.InterMetric{{
		Name:      "foo.bar.baz",
		Timestamp: time.Now().Unix(),
		Value:     float64(10),
		Tags:      []string{"gorch:frobble", "x:e"},
		Type:      samplers.CounterMetric,
	}}
	ddMetrics := ddSink.finalizeMetrics(metrics)
	assert.Equal(t, "rate", ddMetrics[0].MetricType, "Metric type should be rate")
	assert.Equal(t, float64(1.0), ddMetrics[0].Value[0][1], "Metric rate wasnt computed correctly")
}

func TestServerTags(t *testing.T) {
	ddSink := DatadogMetricSink{
		hostname: "somehostname",
		tags:     []string{"a:b", "c:d"},
		interval: 10,
	}

	metrics := []samplers.InterMetric{{
		Name:      "foo.bar.baz",
		Timestamp: time.Now().Unix(),
		Value:     float64(10),
		Tags:      []string{"gorch:frobble", "x:e"},
		Type:      samplers.CounterMetric,
	}}

	ddMetrics := ddSink.finalizeMetrics(metrics)
	assert.Equal(t, "somehostname", ddMetrics[0].Hostname, "Metric hostname uses argument")
	assert.Contains(t, ddMetrics[0].Tags, "a:b", "Tags should contain server tags")
}

func TestHostMagicTag(t *testing.T) {
	ddSink := DatadogMetricSink{
		hostname: "badhostname",
		tags:     []string{"a:b", "c:d"},
	}

	metrics := []samplers.InterMetric{{
		Name:      "foo.bar.baz",
		Timestamp: time.Now().Unix(),
		Value:     float64(10),
		Tags:      []string{"gorch:frobble", "host:abc123", "x:e"},
		Type:      samplers.CounterMetric,
	}}

	ddMetrics := ddSink.finalizeMetrics(metrics)
	assert.Equal(t, "abc123", ddMetrics[0].Hostname, "Metric hostname should be from tag")
	assert.NotContains(t, ddMetrics[0].Tags, "host:abc123", "Host tag should be removed")
	assert.Contains(t, ddMetrics[0].Tags, "x:e", "Last tag is still around")
}

func TestDeviceMagicTag(t *testing.T) {
	ddSink := DatadogMetricSink{
		hostname: "badhostname",
		tags:     []string{"a:b", "c:d"},
	}

	metrics := []samplers.InterMetric{{
		Name:      "foo.bar.baz",
		Timestamp: time.Now().Unix(),
		Value:     float64(10),
		Tags:      []string{"gorch:frobble", "device:abc123", "x:e"},
		Type:      samplers.CounterMetric,
	}}

	ddMetrics := ddSink.finalizeMetrics(metrics)
	assert.Equal(t, "abc123", ddMetrics[0].DeviceName, "Metric devicename should be from tag")
	assert.NotContains(t, ddMetrics[0].Tags, "device:abc123", "Host tag should be removed")
	assert.Contains(t, ddMetrics[0].Tags, "x:e", "Last tag is still around")
}

func TestNewDatadogSpanSinkConfig(t *testing.T) {
	// test the variables that have been renamed
	stats, _ := statsd.NewBuffered("localhost:1235", 1024)
	ddSink, err := NewDatadogSpanSink("http://example.com", 100, stats, &http.Client{}, nil, logrus.New())
	if err != nil {
		t.Fatal(err)
	}
	err = ddSink.Start(nil)
	if err != nil {
		t.Fatal(err)
	}

	assert.Equal(t, "http://example.com", ddSink.traceAddress)
}

type DatadogRoundTripper struct {
	Endpoint      string
	Contains      string
	GotCalled     bool
	ThingReceived bool
}

func (rt *DatadogRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	rec := httptest.NewRecorder()
	log.Printf("Got a request to %v", req.URL)
	if strings.HasPrefix(req.URL.Path, rt.Endpoint) {
		body, _ := ioutil.ReadAll(req.Body)
		defer req.Body.Close()
		log.Printf("body: %v", string(body))
		if strings.Contains(string(body), rt.Contains) {
			rt.ThingReceived = true
		}

		rec.Code = http.StatusOK
		rt.GotCalled = true
	}

	return rec.Result(), nil
}

func TestDatadogFlushSpans(t *testing.T) {
	// test the variables that have been renamed
	stats, _ := statsd.NewBuffered("localhost:1235", 1024)

	transport := &DatadogRoundTripper{Endpoint: "/spans", Contains: "farts-srv"}

	ddSink, err := NewDatadogSpanSink("http://example.com", 100, stats, &http.Client{Transport: transport}, nil, logrus.New())
	assert.NoError(t, err)

	start := time.Now()
	end := start.Add(2 * time.Second)

	testSpan := &ssf.SSFSpan{
		TraceId:        1,
		ParentId:       1,
		Id:             2,
		StartTimestamp: int64(start.UnixNano()),
		EndTimestamp:   int64(end.UnixNano()),
		Error:          false,
		Service:        "farts-srv",
		Tags: map[string]string{
			"baz": "qux",
		},
		Indicator: false,
		Name:      "farting farty farts",
	}
	err = ddSink.Ingest(testSpan)
	assert.NoError(t, err)

	ddSink.Flush()
	assert.Equal(t, true, transport.GotCalled, "Did not call spans endpoint")
}

type result struct {
	received  bool
	contained bool
}

func ddTestServer(t *testing.T, endpoint, contains string) (*httptest.Server, chan result) {
	received := make(chan result)
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		res := result{}
		bstream := r.Body
		if r.Header.Get("Content-Encoding") == "deflate" {
			bstream, _ = zlib.NewReader(r.Body)
		}
		body, _ := ioutil.ReadAll(bstream)
		defer bstream.Close()
		if strings.HasPrefix(r.URL.Path, endpoint) {
			res.received = true
			res.contained = strings.Contains(string(body), contains)
		}
		w.WriteHeader(200)
		received <- res
	})
	return httptest.NewServer(handler), received
}

func TestDatadogMetricRouting(t *testing.T) {
	// test the variables that have been renamed
	stats, _ := statsd.NewBuffered("localhost:1235", 1024)
	client := &http.Client{Transport: &http.Transport{DisableCompression: true}}

	tests := []struct {
		metric samplers.InterMetric
		expect bool
	}{
		{
			samplers.InterMetric{
				Name:      "to.anybody.in.particular",
				Timestamp: time.Now().Unix(),
				Value:     float64(10),
				Tags:      []string{"gorch:frobble", "x:e"},
				Type:      samplers.CounterMetric,
			},
			true,
		},
		{
			samplers.InterMetric{
				Name:      "to.datadog",
				Timestamp: time.Now().Unix(),
				Value:     float64(10),
				Tags:      []string{"gorch:frobble", "x:e"},
				Type:      samplers.CounterMetric,
				Sinks:     samplers.RouteInformation{"datadog": struct{}{}},
			},
			true,
		},
		{
			samplers.InterMetric{
				Name:      "to.kafka.only",
				Timestamp: time.Now().Unix(),
				Value:     float64(10),
				Tags:      []string{"gorch:frobble", "x:e"},
				Type:      samplers.CounterMetric,
				Sinks:     samplers.RouteInformation{"kafka": struct{}{}},
			},
			false,
		},
	}
	for _, elt := range tests {
		test := elt
		t.Run(test.metric.Name, func(t *testing.T) {
			t.Parallel()
			srv, rcved := ddTestServer(t, "/api/v1/series", test.metric.Name)
			ddSink := DatadogMetricSink{
				DDHostname:      srv.URL,
				HTTPClient:      client,
				flushMaxPerBody: 15,
				log:             logrus.New(),
				tags:            []string{"a:b", "c:d"},
				interval:        10,
				statsd:          stats,
			}
			done := make(chan struct{})
			go func() {
				result, ok := <-rcved
				if test.expect {
					// TODO: negative case
					assert.True(t, result.contained, "Should have sent the metric")
				} else {
					if ok {
						assert.False(t, result.contained, "Should definitely not have sent the metric!")
					}
				}
				close(done)
			}()
			err := ddSink.Flush(context.TODO(), []samplers.InterMetric{test.metric})
			require.NoError(t, err)
			close(rcved)
			<-done
		})
	}

}
<commit_msg>Remove debug printfs from datadog test<commit_after>package datadog

import (
	"compress/zlib"
	"context"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/DataDog/datadog-go/statsd"
	"github.com/sirupsen/logrus"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/stripe/veneur/samplers"
	"github.com/stripe/veneur/ssf"
)

// DDMetricsRequest represents the body of the POST request
// for sending metrics data to Datadog
// Eventually we'll want to define this symmetrically.
type DDMetricsRequest struct {
	Series []DDMetric
}

func TestDatadogRate(t *testing.T) {
	ddSink := DatadogMetricSink{
		hostname: "somehostname",
		tags:     []string{"a:b", "c:d"},
		interval: 10,
	}

	metrics := []samplers.InterMetric{{
		Name:      "foo.bar.baz",
		Timestamp: time.Now().Unix(),
		Value:     float64(10),
		Tags:      []string{"gorch:frobble", "x:e"},
		Type:      samplers.CounterMetric,
	}}
	ddMetrics := ddSink.finalizeMetrics(metrics)
	assert.Equal(t, "rate", ddMetrics[0].MetricType, "Metric type should be rate")
	assert.Equal(t, float64(1.0), ddMetrics[0].Value[0][1], "Metric rate wasnt computed correctly")
}

func TestServerTags(t *testing.T) {
	ddSink := DatadogMetricSink{
		hostname: "somehostname",
		tags:     []string{"a:b", "c:d"},
		interval: 10,
	}

	metrics := []samplers.InterMetric{{
		Name:      "foo.bar.baz",
		Timestamp: time.Now().Unix(),
		Value:     float64(10),
		Tags:      []string{"gorch:frobble", "x:e"},
		Type:      samplers.CounterMetric,
	}}

	ddMetrics := ddSink.finalizeMetrics(metrics)
	assert.Equal(t, "somehostname", ddMetrics[0].Hostname, "Metric hostname uses argument")
	assert.Contains(t, ddMetrics[0].Tags, "a:b", "Tags should contain server tags")
}

func TestHostMagicTag(t *testing.T) {
	ddSink := DatadogMetricSink{
		hostname: "badhostname",
		tags:     []string{"a:b", "c:d"},
	}

	metrics := []samplers.InterMetric{{
		Name:      "foo.bar.baz",
		Timestamp: time.Now().Unix(),
		Value:     float64(10),
		Tags:      []string{"gorch:frobble", "host:abc123", "x:e"},
		Type:      samplers.CounterMetric,
	}}

	ddMetrics := ddSink.finalizeMetrics(metrics)
	assert.Equal(t, "abc123", ddMetrics[0].Hostname, "Metric hostname should be from tag")
	assert.NotContains(t, ddMetrics[0].Tags, "host:abc123", "Host tag should be removed")
	assert.Contains(t, ddMetrics[0].Tags, "x:e", "Last tag is still around")
}

func TestDeviceMagicTag(t *testing.T) {
	ddSink := DatadogMetricSink{
		hostname: "badhostname",
		tags:     []string{"a:b", "c:d"},
	}

	metrics := []samplers.InterMetric{{
		Name:      "foo.bar.baz",
		Timestamp: time.Now().Unix(),
		Value:     float64(10),
		Tags:      []string{"gorch:frobble", "device:abc123", "x:e"},
		Type:      samplers.CounterMetric,
	}}

	ddMetrics := ddSink.finalizeMetrics(metrics)
	assert.Equal(t, "abc123", ddMetrics[0].DeviceName, "Metric devicename should be from tag")
	assert.NotContains(t, ddMetrics[0].Tags, "device:abc123", "Host tag should be removed")
	assert.Contains(t, ddMetrics[0].Tags, "x:e", "Last tag is still around")
}

func TestNewDatadogSpanSinkConfig(t *testing.T) {
	// test the variables that have been renamed
	stats, _ := statsd.NewBuffered("localhost:1235", 1024)
	ddSink, err := NewDatadogSpanSink("http://example.com", 100, stats, &http.Client{}, nil, logrus.New())
	if err != nil {
		t.Fatal(err)
	}
	err = ddSink.Start(nil)
	if err != nil {
		t.Fatal(err)
	}

	assert.Equal(t, "http://example.com", ddSink.traceAddress)
}

type DatadogRoundTripper struct {
	Endpoint      string
	Contains      string
	GotCalled     bool
	ThingReceived bool
}

func (rt *DatadogRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	rec := httptest.NewRecorder()
	if strings.HasPrefix(req.URL.Path, rt.Endpoint) {
		body, _ := ioutil.ReadAll(req.Body)
		defer req.Body.Close()
		if strings.Contains(string(body), rt.Contains) {
			rt.ThingReceived = true
		}

		rec.Code = http.StatusOK
		rt.GotCalled = true
	}

	return rec.Result(), nil
}

func TestDatadogFlushSpans(t *testing.T) {
	// test the variables that have been renamed
	stats, _ := statsd.NewBuffered("localhost:1235", 1024)

	transport := &DatadogRoundTripper{Endpoint: "/spans", Contains: "farts-srv"}

	ddSink, err := NewDatadogSpanSink("http://example.com", 100, stats, &http.Client{Transport: transport}, nil, logrus.New())
	assert.NoError(t, err)

	start := time.Now()
	end := start.Add(2 * time.Second)

	testSpan := &ssf.SSFSpan{
		TraceId:        1,
		ParentId:       1,
		Id:             2,
		StartTimestamp: int64(start.UnixNano()),
		EndTimestamp:   int64(end.UnixNano()),
		Error:          false,
		Service:        "farts-srv",
		Tags: map[string]string{
			"baz": "qux",
		},
		Indicator: false,
		Name:      "farting farty farts",
	}
	err = ddSink.Ingest(testSpan)
	assert.NoError(t, err)

	ddSink.Flush()
	assert.Equal(t, true, transport.GotCalled, "Did not call spans endpoint")
}

type result struct {
	received  bool
	contained bool
}

func ddTestServer(t *testing.T, endpoint, contains string) (*httptest.Server, chan result) {
	received := make(chan result)
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		res := result{}
		bstream := r.Body
		if r.Header.Get("Content-Encoding") == "deflate" {
			bstream, _ = zlib.NewReader(r.Body)
		}
		body, _ := ioutil.ReadAll(bstream)
		defer bstream.Close()
		if strings.HasPrefix(r.URL.Path, endpoint) {
			res.received = true
			res.contained = strings.Contains(string(body), contains)
		}
		w.WriteHeader(200)
		received <- res
	})
	return httptest.NewServer(handler), received
}

func TestDatadogMetricRouting(t *testing.T) {
	// test the variables that have been renamed
	stats, _ := statsd.NewBuffered("localhost:1235", 1024)
	client := &http.Client{Transport: &http.Transport{DisableCompression: true}}

	tests := []struct {
		metric samplers.InterMetric
		expect bool
	}{
		{
			samplers.InterMetric{
				Name:      "to.anybody.in.particular",
				Timestamp: time.Now().Unix(),
				Value:     float64(10),
				Tags:      []string{"gorch:frobble", "x:e"},
				Type:      samplers.CounterMetric,
			},
			true,
		},
		{
			samplers.InterMetric{
				Name:      "to.datadog",
				Timestamp: time.Now().Unix(),
				Value:     float64(10),
				Tags:      []string{"gorch:frobble", "x:e"},
				Type:      samplers.CounterMetric,
				Sinks:     samplers.RouteInformation{"datadog": struct{}{}},
			},
			true,
		},
		{
			samplers.InterMetric{
				Name:      "to.kafka.only",
				Timestamp: time.Now().Unix(),
				Value:     float64(10),
				Tags:      []string{"gorch:frobble", "x:e"},
				Type:      samplers.CounterMetric,
				Sinks:     samplers.RouteInformation{"kafka": struct{}{}},
			},
			false,
		},
	}
	for _, elt := range tests {
		test := elt
		t.Run(test.metric.Name, func(t *testing.T) {
			t.Parallel()
			srv, rcved := ddTestServer(t, "/api/v1/series", test.metric.Name)
			ddSink := DatadogMetricSink{
				DDHostname:      srv.URL,
				HTTPClient:      client,
				flushMaxPerBody: 15,
				log:             logrus.New(),
				tags:            []string{"a:b", "c:d"},
				interval:        10,
				statsd:          stats,
			}
			done := make(chan struct{})
			go func() {
				result, ok := <-rcved
				if test.expect {
					// TODO: negative case
					assert.True(t, result.contained, "Should have sent the metric")
				} else {
					if ok {
						assert.False(t, result.contained, "Should definitely not have sent the metric!")
					}
				}
				close(done)
			}()
			err := ddSink.Flush(context.TODO(), []samplers.InterMetric{test.metric})
			require.NoError(t, err)
			close(rcved)
			<-done
		})
	}

}
<|endoftext|>


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

// Command luci_machine_tokend runs on all machines via cron.
//
// It wakes up each ~10 min, checks whether it needs to refresh existing machine
// token, and refreshes it if necessary.
//
// It also dumps information about its run into a status file (as JSON), that
// can be picked up sysmon and transformed into ts_mon metrics (most important
// one being "time since last successful token refresh").
package main

import (
	"crypto/sha1"
	"encoding/hex"
	"flag"
	"fmt"
	"os"
	"sort"
	"strings"
	"time"

	"golang.org/x/net/context"

	"github.com/luci/luci-go/common/clock"
	"github.com/luci/luci-go/common/logging"
	"github.com/luci/luci-go/common/logging/gologger"
	"github.com/luci/luci-go/common/logging/memlogger"
	"github.com/luci/luci-go/common/logging/teelogger"
	"github.com/luci/luci-go/common/retry"
	"github.com/luci/luci-go/common/tsmon"

	"github.com/luci/luci-go/client/tokenclient"
	"github.com/luci/luci-go/common/api/tokenserver"
	"github.com/luci/luci-go/common/api/tokenserver/minter/v1"
)

// Version identifies the major revision of the tokend code.
//
// It is put in the status file (and subsequently reported to monitoring).
const Version = "1.0"

// commandLine contains all command line flags.
//
// See registerFlags() for description of each individual flag.
type commandLine struct {
	PrivateKeyPath  string
	CertificatePath string
	Backend         string
	TokenFile       string
	StatusFile      string
	Timeout         time.Duration
	ForceRefresh    bool
}

func defaults() commandLine {
	return commandLine{
		Timeout: 60 * time.Second,
	}
}

func (c *commandLine) registerFlags(f *flag.FlagSet) {
	f.StringVar(&c.PrivateKeyPath, "pkey-pem", c.PrivateKeyPath, "path to a private key file")
	f.StringVar(&c.CertificatePath, "cert-pem", c.CertificatePath, "path to a certificate file")
	f.StringVar(&c.Backend, "backend", c.Backend, "hostname of the backend to use")
	f.StringVar(&c.TokenFile, "token-file", c.TokenFile, "where to put the token file")
	f.StringVar(&c.StatusFile, "status-file", c.StatusFile, "where to put details about this run (optional)")
	f.DurationVar(&c.Timeout, "timeout", c.Timeout, "how long to retry on errors before giving up")
	f.BoolVar(&c.ForceRefresh, "force-refresh", c.ForceRefresh, "forcefully refresh the token even if it is still valid")
}

func (c *commandLine) check() error {
	if c.PrivateKeyPath == "" {
		return fmt.Errorf("-pkey-pem is required")
	}
	if c.CertificatePath == "" {
		return fmt.Errorf("-cert-pem is required")
	}
	if c.Backend == "" {
		return fmt.Errorf("-backend is required")
	}
	if c.TokenFile == "" {
		return fmt.Errorf("-token-file is required")
	}
	return nil
}

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

func realMain() int {
	opts := defaults()
	opts.registerFlags(flag.CommandLine)

	tsmonFlags := tsmon.NewFlags()
	tsmonFlags.Target.TargetType = "task"
	tsmonFlags.Target.TaskServiceName = "luci_machine_tokend"
	tsmonFlags.Target.TaskJobName = "default"
	tsmonFlags.Flush = "manual"
	tsmonFlags.Register(flag.CommandLine)

	flag.Parse()

	if err := opts.check(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		flag.Usage()
		return 2
	}

	clientParams := tokenclient.ClientParameters{
		PrivateKeyPath:  opts.PrivateKeyPath,
		CertificatePath: opts.CertificatePath,
		Backend:         opts.Backend,
		Retry: func() retry.Iterator {
			return &retry.ExponentialBackoff{
				Limited: retry.Limited{
					Delay:   200 * time.Millisecond,
					Retries: 100000, // limit only by time, not number of retries
				},
				MaxDelay:   opts.Timeout,
				Multiplier: 1.5,
			}
		},
	}
	if strings.HasPrefix(clientParams.Backend, "localhost:") {
		clientParams.Insecure = true
	}

	log := &memlogger.MemLogger{}

	// Write Debug log to both memlogger and gologger.
	memLogFactory := func(context.Context) logging.Logger {
		return log
	}
	root := teelogger.Use(context.Background(), memLogFactory, gologger.StdConfig.NewLogger)
	root = logging.SetLevel(root, logging.Debug)

	// Apply tsmon config.
	if err := tsmon.InitializeFromFlags(root, &tsmonFlags); err != nil {
		logging.Errorf(root, "Failed to initialize tsmon - %s", err)
		return 1
	}

	ctx, cancel := context.WithTimeout(root, opts.Timeout)
	catchInterrupt(cancel)

	statusReport := StatusReport{
		Version: Version,
		Started: clock.Now(ctx),
	}
	defer func() {
		// Dump the status of this run. It's picked up by monitoring. Ignore errors
		// here, they are not important compared to 'run' errors. Use root context
		// to be to flush errors to monitoring even if 'ctx' has expired.
		statusReport.Finished = clock.Now(ctx)
		if err := statusReport.SendMetrics(root); err != nil {
			logging.Errorf(root, "Failed to send tsmon metrics - %s", err)
		}
		if opts.StatusFile != "" {
			if err := statusReport.SaveToFile(root, log, opts.StatusFile); err != nil {
				logging.Errorf(root, "Failed to save the status - %s", err)
			}
		}
	}()
	if err := run(ctx, clientParams, opts, &statusReport); err != nil {
		return 1
	}
	return 0
}

func run(ctx context.Context, clientParams tokenclient.ClientParameters, opts commandLine, status *StatusReport) error {
	// Read existing token file on disk to check whether we really need to update
	// it. We update the token if it is missing, close to expiration, or when
	// parameters change.
	existingToken, existingState := readTokenFile(ctx, opts.TokenFile)

	// Record the info about existing token in status report, it is useful even if
	// we fail to refresh the token.
	status.LastToken = existingToken

	// Initialize the client. It will read private key and certificate file into
	// memory and validate them.
	client, err := tokenclient.NewClient(clientParams)
	if err != nil {
		logging.Errorf(ctx, "Failed to initialize the client - %s", err)
		status.FailureError = err
		status.UpdateOutcome = OutcomeCantReadKey
		// Fill in some update reason to avoid "" as metric value.
		if existingToken.NextUpdate == 0 {
			status.UpdateReason = UpdateReasonNewToken
		} else {
			// We successfully updated the token in the past, but now the keys are
			// suddenly unreadable, they probably changed.
			status.UpdateReason = UpdateReasonParametersChange
		}
		return err
	}

	// Generate a hash of all input parameters. It is used to detect that we
	// need to refresh the token file even if the token is still valid. It
	// happens if we change a key or backend URL.
	signer := client.Signer.(*tokenclient.X509Signer)
	inputsDigest := calcDigest(map[string][]byte{
		"forceBump": {1}, // bump this to forcefully regenerate all tokens
		"pkey":      signer.PrivateKeyPEM,
		"cert":      signer.CertificatePEM,
		"backend":   []byte(clientParams.Backend),
	})

	// Record a reason for token update (if we need to update the token).
	now := clock.Now(ctx)
	switch {
	case existingToken.NextUpdate == 0:
		status.UpdateReason = UpdateReasonNewToken
	case now.After(time.Unix(existingToken.NextUpdate, 0)):
		status.UpdateReason = UpdateReasonExpiration
	case existingState.InputsDigest != inputsDigest:
		status.UpdateReason = UpdateReasonParametersChange
	case opts.ForceRefresh:
		status.UpdateReason = UpdateReasonForceRefresh
	default:
		logging.Infof(ctx, "The token is valid, skipping the update")
		status.UpdateReason = UpdateReasonTokenIsGood
		status.UpdateOutcome = OutcomeTokenIsGood
		return nil
	}

	// Grab a new token. MintMachineToken does retries internally, until success
	// or context deadline.
	resp, err := client.MintMachineToken(ctx, &minter.MachineTokenRequest{
		TokenType: minter.TokenType_LUCI_MACHINE_TOKEN,
	})
	status.MintTokenDuration = clock.Now(ctx).Sub(now)
	if err != nil {
		logging.Errorf(ctx, "Failed to generate a new token - %s", err)
		status.FailureError = err
		status.UpdateOutcome = OutcomeFromRPCError(err)
		return err
	}

	// Grab machine_token field.
	var tok *minter.LuciMachineToken
	if tt, _ := resp.TokenType.(*minter.MachineTokenResponse_LuciMachineToken); tt != nil {
		tok = tt.LuciMachineToken
	}
	if tok == nil {
		err = fmt.Errorf("bad response, empty luci_machine_token field")
		logging.Errorf(ctx, "%s", err)
		status.FailureError = err
		status.UpdateOutcome = OutcomeMalformedReponse
		return err
	}

	now = clock.Now(ctx)
	expiry := tok.Expiry.Time()
	lifetime := expiry.Sub(now)

	// lifetime should usually be 1h, add a safeguard to avoid hammering
	// the backend in case the lifetime is unexpectedly wrong.
	if lifetime < 5*time.Minute {
		logging.Warningf(ctx, "Returned token lifetime is unexpectedly too short (%s)", lifetime)
		lifetime = 5 * time.Minute
	}

	// We start to attempt to refresh the token after half of its lifetime has
	// passed, to be able survive short (~30 min) backend outages in exchange for
	// 2x RPC rate.
	newTokenFile := tokenserver.TokenFile{
		LuciMachineToken: tok.MachineToken,
		Expiry:           expiry.Unix(),
		LastUpdate:       now.Unix(),
		NextUpdate:       now.Add(lifetime / 2).Unix(),
	}
	newState := stateInToken{
		InputsDigest: inputsDigest,
		Version:      Version,
	}
	if err = writeTokenFile(ctx, &newTokenFile, &newState, opts.TokenFile); err != nil {
		logging.Errorf(ctx, "Failed to save token file - %s", err)
		status.FailureError = err
		if os.IsPermission(err) {
			status.UpdateOutcome = OutcomePermissionError
		} else {
			status.UpdateOutcome = OutcomeUnknownSaveTokenError
		}
		return err
	}

	status.LastToken = &newTokenFile
	status.UpdateOutcome = OutcomeUpdateSuccess
	return nil
}

// calcDigest produces a digest of a given map using some stable serialization.
func calcDigest(inputs map[string][]byte) string {
	keys := make([]string, 0, len(inputs))
	for k := range inputs {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	h := sha1.New()
	for _, k := range keys {
		v := inputs[k]
		fmt.Fprintf(h, "%s\n%d\n", k, len(v))
		h.Write(v)
	}
	blob := h.Sum(nil)
	return hex.EncodeToString(blob[:])
}
<commit_msg>token-server: Bump luci_machine_tokend version.<commit_after>// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Command luci_machine_tokend runs on all machines via cron.
//
// It wakes up each ~10 min, checks whether it needs to refresh existing machine
// token, and refreshes it if necessary.
//
// It also dumps information about its run into a status file (as JSON), that
// can be picked up sysmon and transformed into ts_mon metrics (most important
// one being "time since last successful token refresh").
package main

import (
	"crypto/sha1"
	"encoding/hex"
	"flag"
	"fmt"
	"os"
	"sort"
	"strings"
	"time"

	"golang.org/x/net/context"

	"github.com/luci/luci-go/common/clock"
	"github.com/luci/luci-go/common/logging"
	"github.com/luci/luci-go/common/logging/gologger"
	"github.com/luci/luci-go/common/logging/memlogger"
	"github.com/luci/luci-go/common/logging/teelogger"
	"github.com/luci/luci-go/common/retry"
	"github.com/luci/luci-go/common/tsmon"

	"github.com/luci/luci-go/client/tokenclient"
	"github.com/luci/luci-go/common/api/tokenserver"
	"github.com/luci/luci-go/common/api/tokenserver/minter/v1"
)

// Version identifies the major revision of the tokend code.
//
// It is put in the status file (and subsequently reported to monitoring).
const Version = "1.1"

// commandLine contains all command line flags.
//
// See registerFlags() for description of each individual flag.
type commandLine struct {
	PrivateKeyPath  string
	CertificatePath string
	Backend         string
	TokenFile       string
	StatusFile      string
	Timeout         time.Duration
	ForceRefresh    bool
}

func defaults() commandLine {
	return commandLine{
		Timeout: 60 * time.Second,
	}
}

func (c *commandLine) registerFlags(f *flag.FlagSet) {
	f.StringVar(&c.PrivateKeyPath, "pkey-pem", c.PrivateKeyPath, "path to a private key file")
	f.StringVar(&c.CertificatePath, "cert-pem", c.CertificatePath, "path to a certificate file")
	f.StringVar(&c.Backend, "backend", c.Backend, "hostname of the backend to use")
	f.StringVar(&c.TokenFile, "token-file", c.TokenFile, "where to put the token file")
	f.StringVar(&c.StatusFile, "status-file", c.StatusFile, "where to put details about this run (optional)")
	f.DurationVar(&c.Timeout, "timeout", c.Timeout, "how long to retry on errors before giving up")
	f.BoolVar(&c.ForceRefresh, "force-refresh", c.ForceRefresh, "forcefully refresh the token even if it is still valid")
}

func (c *commandLine) check() error {
	if c.PrivateKeyPath == "" {
		return fmt.Errorf("-pkey-pem is required")
	}
	if c.CertificatePath == "" {
		return fmt.Errorf("-cert-pem is required")
	}
	if c.Backend == "" {
		return fmt.Errorf("-backend is required")
	}
	if c.TokenFile == "" {
		return fmt.Errorf("-token-file is required")
	}
	return nil
}

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

func realMain() int {
	opts := defaults()
	opts.registerFlags(flag.CommandLine)

	tsmonFlags := tsmon.NewFlags()
	tsmonFlags.Target.TargetType = "task"
	tsmonFlags.Target.TaskServiceName = "luci_machine_tokend"
	tsmonFlags.Target.TaskJobName = "default"
	tsmonFlags.Flush = "manual"
	tsmonFlags.Register(flag.CommandLine)

	flag.Parse()

	if err := opts.check(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		flag.Usage()
		return 2
	}

	clientParams := tokenclient.ClientParameters{
		PrivateKeyPath:  opts.PrivateKeyPath,
		CertificatePath: opts.CertificatePath,
		Backend:         opts.Backend,
		Retry: func() retry.Iterator {
			return &retry.ExponentialBackoff{
				Limited: retry.Limited{
					Delay:   200 * time.Millisecond,
					Retries: 100000, // limit only by time, not number of retries
				},
				MaxDelay:   opts.Timeout,
				Multiplier: 1.5,
			}
		},
	}
	if strings.HasPrefix(clientParams.Backend, "localhost:") {
		clientParams.Insecure = true
	}

	log := &memlogger.MemLogger{}

	// Write Debug log to both memlogger and gologger.
	memLogFactory := func(context.Context) logging.Logger {
		return log
	}
	root := teelogger.Use(context.Background(), memLogFactory, gologger.StdConfig.NewLogger)
	root = logging.SetLevel(root, logging.Debug)

	// Apply tsmon config.
	if err := tsmon.InitializeFromFlags(root, &tsmonFlags); err != nil {
		logging.Errorf(root, "Failed to initialize tsmon - %s", err)
		return 1
	}

	ctx, cancel := context.WithTimeout(root, opts.Timeout)
	catchInterrupt(cancel)

	statusReport := StatusReport{
		Version: Version,
		Started: clock.Now(ctx),
	}
	defer func() {
		// Dump the status of this run. It's picked up by monitoring. Ignore errors
		// here, they are not important compared to 'run' errors. Use root context
		// to be to flush errors to monitoring even if 'ctx' has expired.
		statusReport.Finished = clock.Now(ctx)
		if err := statusReport.SendMetrics(root); err != nil {
			logging.Errorf(root, "Failed to send tsmon metrics - %s", err)
		}
		if opts.StatusFile != "" {
			if err := statusReport.SaveToFile(root, log, opts.StatusFile); err != nil {
				logging.Errorf(root, "Failed to save the status - %s", err)
			}
		}
	}()
	if err := run(ctx, clientParams, opts, &statusReport); err != nil {
		return 1
	}
	return 0
}

func run(ctx context.Context, clientParams tokenclient.ClientParameters, opts commandLine, status *StatusReport) error {
	// Read existing token file on disk to check whether we really need to update
	// it. We update the token if it is missing, close to expiration, or when
	// parameters change.
	existingToken, existingState := readTokenFile(ctx, opts.TokenFile)

	// Record the info about existing token in status report, it is useful even if
	// we fail to refresh the token.
	status.LastToken = existingToken

	// Initialize the client. It will read private key and certificate file into
	// memory and validate them.
	client, err := tokenclient.NewClient(clientParams)
	if err != nil {
		logging.Errorf(ctx, "Failed to initialize the client - %s", err)
		status.FailureError = err
		status.UpdateOutcome = OutcomeCantReadKey
		// Fill in some update reason to avoid "" as metric value.
		if existingToken.NextUpdate == 0 {
			status.UpdateReason = UpdateReasonNewToken
		} else {
			// We successfully updated the token in the past, but now the keys are
			// suddenly unreadable, they probably changed.
			status.UpdateReason = UpdateReasonParametersChange
		}
		return err
	}

	// Generate a hash of all input parameters. It is used to detect that we
	// need to refresh the token file even if the token is still valid. It
	// happens if we change a key or backend URL.
	signer := client.Signer.(*tokenclient.X509Signer)
	inputsDigest := calcDigest(map[string][]byte{
		"forceBump": {1}, // bump this to forcefully regenerate all tokens
		"pkey":      signer.PrivateKeyPEM,
		"cert":      signer.CertificatePEM,
		"backend":   []byte(clientParams.Backend),
	})

	// Record a reason for token update (if we need to update the token).
	now := clock.Now(ctx)
	switch {
	case existingToken.NextUpdate == 0:
		status.UpdateReason = UpdateReasonNewToken
	case now.After(time.Unix(existingToken.NextUpdate, 0)):
		status.UpdateReason = UpdateReasonExpiration
	case existingState.InputsDigest != inputsDigest:
		status.UpdateReason = UpdateReasonParametersChange
	case opts.ForceRefresh:
		status.UpdateReason = UpdateReasonForceRefresh
	default:
		logging.Infof(ctx, "The token is valid, skipping the update")
		status.UpdateReason = UpdateReasonTokenIsGood
		status.UpdateOutcome = OutcomeTokenIsGood
		return nil
	}

	// Grab a new token. MintMachineToken does retries internally, until success
	// or context deadline.
	resp, err := client.MintMachineToken(ctx, &minter.MachineTokenRequest{
		TokenType: minter.TokenType_LUCI_MACHINE_TOKEN,
	})
	status.MintTokenDuration = clock.Now(ctx).Sub(now)
	if err != nil {
		logging.Errorf(ctx, "Failed to generate a new token - %s", err)
		status.FailureError = err
		status.UpdateOutcome = OutcomeFromRPCError(err)
		return err
	}

	// Grab machine_token field.
	var tok *minter.LuciMachineToken
	if tt, _ := resp.TokenType.(*minter.MachineTokenResponse_LuciMachineToken); tt != nil {
		tok = tt.LuciMachineToken
	}
	if tok == nil {
		err = fmt.Errorf("bad response, empty luci_machine_token field")
		logging.Errorf(ctx, "%s", err)
		status.FailureError = err
		status.UpdateOutcome = OutcomeMalformedReponse
		return err
	}

	now = clock.Now(ctx)
	expiry := tok.Expiry.Time()
	lifetime := expiry.Sub(now)

	// lifetime should usually be 1h, add a safeguard to avoid hammering
	// the backend in case the lifetime is unexpectedly wrong.
	if lifetime < 5*time.Minute {
		logging.Warningf(ctx, "Returned token lifetime is unexpectedly too short (%s)", lifetime)
		lifetime = 5 * time.Minute
	}

	// We start to attempt to refresh the token after half of its lifetime has
	// passed, to be able survive short (~30 min) backend outages in exchange for
	// 2x RPC rate.
	newTokenFile := tokenserver.TokenFile{
		LuciMachineToken: tok.MachineToken,
		Expiry:           expiry.Unix(),
		LastUpdate:       now.Unix(),
		NextUpdate:       now.Add(lifetime / 2).Unix(),
	}
	newState := stateInToken{
		InputsDigest: inputsDigest,
		Version:      Version,
	}
	if err = writeTokenFile(ctx, &newTokenFile, &newState, opts.TokenFile); err != nil {
		logging.Errorf(ctx, "Failed to save token file - %s", err)
		status.FailureError = err
		if os.IsPermission(err) {
			status.UpdateOutcome = OutcomePermissionError
		} else {
			status.UpdateOutcome = OutcomeUnknownSaveTokenError
		}
		return err
	}

	status.LastToken = &newTokenFile
	status.UpdateOutcome = OutcomeUpdateSuccess
	return nil
}

// calcDigest produces a digest of a given map using some stable serialization.
func calcDigest(inputs map[string][]byte) string {
	keys := make([]string, 0, len(inputs))
	for k := range inputs {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	h := sha1.New()
	for _, k := range keys {
		v := inputs[k]
		fmt.Fprintf(h, "%s\n%d\n", k, len(v))
		h.Write(v)
	}
	blob := h.Sum(nil)
	return hex.EncodeToString(blob[:])
}
<|endoftext|>


<commit_before>//fiddoServer.go
package main

import (
	"net/http"
	"log"
	"encoding/json"
)

type Commit struct {
	Id, Url string
}

type body_struct struct {
	Ref string
	Commits []Commit
}

func main() {
	http.HandleFunc("/webhook", webhook)
	http.ListenAndServe(":5000", nil)
}

func webhook(w http.ResponseWriter, r *http.Request) {
	decoder := json.NewDecoder(r.Body)
	var body body_struct
	err := decoder.Decode(&body)
	if err != nil {
		panic(err)
	}
	defer r.Body.Close()
	log.Println("Ref be like: " + body.Ref)
	log.Printf("Commits be like: %v", body.Commits)
}<commit_msg>adding repo struct for more actionable info<commit_after>//fiddoServer.go
package main

import (
	"net/http"
	"log"
	"encoding/json"
)

type Commit struct {
	Id, Url string
}

type repo struct {
	Id, Name string
}

type body_struct struct {
	Ref string
	Commits []Commit
	Repository repo
}

func main() {
	http.HandleFunc("/webhook", webhook)
	http.ListenAndServe(":5000", nil)
}

func webhook(w http.ResponseWriter, r *http.Request) {
	decoder := json.NewDecoder(r.Body)
	var body body_struct
	err := decoder.Decode(&body)
	if err != nil {
		panic(err)
	}
	defer r.Body.Close()
	log.Printf("Ref be like: %v", body.Ref)
	log.Printf("Commits be like: %v", body.Commits)
	log.Printf("Repo be like: %v", body.Repository)
}<|endoftext|>