<commit_before>package check

import (
	"testing"
)

var zip string

// Argentina
func TestArgentinaZip(t *testing.T) {
	zip = "B1657"
	if !Zip(zip).OfCountry("ar") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Australia
func TestAustraliaZip(t *testing.T) {
	zip = "2000"
	if !Zip(zip).OfCountry("au") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Austria
func TestAustriaZip(t *testing.T) {
	zip = "1010"
	if !Zip(zip).OfCountry("at") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Belgium
func TestBelgiumZip(t *testing.T) {
	zip = "3840"
	if !Zip(zip).OfCountry("be") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Brazil
func TestBrazilZip(t *testing.T) {
	zip = "00000-000"
	if !Zip(zip).OfCountry("br") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Bulgaria
func TestBulgariaZip(t *testing.T) {
	zip = "5094"
	if !Zip(zip).OfCountry("bg") {
		t.Errorf("%v = false, want true", zip)
	}

}

// Canada
func TestCanadaZip(t *testing.T) {
	zip = "L4C 3V2"
	if !Zip(zip).OfCountry("ca") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Croatia
func TestCroatiaZip(t *testing.T) {
	zip = "10000"
	if !Zip(zip).OfCountry("hr") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Cyprus
func TestCyprusZip(t *testing.T) {
	zip = "8501"
	if !Zip(zip).OfCountry("cy") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Czech Republic
func TestCzechRepublicZip(t *testing.T) {
	zip = "160 00"
	if !Zip(zip).OfCountry("cz") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Denmark
func TestDenmarkZip(t *testing.T) {
	zip = "2750"
	if !Zip(zip).OfCountry("dk") {
		t.Errorf("%v = false, want true", zip)
	}

	zip = "1750"
	if Zip(zip).OfCountry("dk") {
		t.Errorf("%v = false, want true", zip)
	}

}

// Estonia
func TestEstoniaZip(t *testing.T) {
	zip = "42106"
	if !Zip(zip).OfCountry("ee") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Finland
func TestFinlandZip(t *testing.T) {
	zip = "55100"
	if !Zip(zip).OfCountry("fi") {
		t.Errorf("%v = false, want true", zip)
	}
}

// France
func TestFranceZip(t *testing.T) {
	zip = "52110"
	if !Zip(zip).OfCountry("fr") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Germany
func TestGermanyZip(t *testing.T) {
	zip = "79258"
	if !Zip(zip).OfCountry("de") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Great Britain
func TestGreatBritainZip(t *testing.T) {
	zip = "EC1A 1BB"
	if !Zip(zip).OfCountry("gb") {
		t.Errorf("%v = false, want true", zip)
	}

	zip = "W1A 1HQ"
	if !Zip(zip).OfCountry("gb") {
		t.Errorf("%v = false, want true", zip)
	}

	zip = "M1 1AA"
	if !Zip(zip).OfCountry("gb") {
		t.Errorf("%v = false, want true", zip)
	}

	zip = "B33 8TH"
	if !Zip(zip).OfCountry("gb") {
		t.Errorf("%v = false, want true", zip)
	}

	zip = "CR2 6XH"
	if !Zip(zip).OfCountry("gb") {
		t.Errorf("%v = false, want true", zip)
	}

	zip = "DN55 1PT"
	if !Zip(zip).OfCountry("gb") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Greece
func TestGreeceZip(t *testing.T) {
	zip = "681 00"
	if !Zip(zip).OfCountry("gr") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Hungary
func TestHungaryZip(t *testing.T) {
	zip = "1013"
	if !Zip(zip).OfCountry("hu") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Iceland
func TestIcelandZip(t *testing.T) {
	zip = "720"
	if !Zip(zip).OfCountry("is") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Italy
func TestItalyZip(t *testing.T) {
	zip = "26812"
	if !Zip(zip).OfCountry("it") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Japan
func TestJapanZip(t *testing.T) {
	zip = "107-0061"
	if !Zip(zip).OfCountry("jp") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Latvia
func TestLatviaZip(t *testing.T) {
	zip = "LV-3701"
	if !Zip(zip).OfCountry("lv") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Lithuania
func TestLithuaniaZip(t *testing.T) {
	zip = "73461"
	if !Zip(zip).OfCountry("lt") {
		t.Errorf("%v = false, want true", zip)
	}

	zip = "LT-73184"
	if !Zip(zip).OfCountry("lt") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Luxembourg
func TestLuxembourgZip(t *testing.T) {
	// zip = "1010"
	// if !Zip(zip).OfCountry("lu") {
	// 	t.Errorf("%v = false, want true", zip)
	// }

	zip = "L-2920"
	if !Zip(zip).OfCountry("lu") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Malta
func TestMaltaZip(t *testing.T) {
	zip = "BBG 1014"
	if !Zip(zip).OfCountry("mt") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Netherlands
func TestNetherlandsZip(t *testing.T) {
	zip = "1000 AP"
	if !Zip(zip).OfCountry("nl") {
		t.Errorf("%v = false, want true", zip)
	}

	// zip = "1000"
	// if !Zip(zip).OfCountry("nl") {
	// 	t.Errorf("%v = false, want true", zip)
	// }
}

// Norway
func TestNorwayZip(t *testing.T) {
	zip = "0001"
	if !Zip(zip).OfCountry("no") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Poland
func TestPolandZip(t *testing.T) {
	zip = "26-600"
	if !Zip(zip).OfCountry("pl") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Portugal
func TestPortugalZip(t *testing.T) {
	zip = "1050"
	if !Zip(zip).OfCountry("pt") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Romania
func TestRomaniaZip(t *testing.T) {
	zip = "827019"
	if !Zip(zip).OfCountry("ro") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Slovakia
func TestSlovakiaZip(t *testing.T) {
	zip = "811 02"
	if !Zip(zip).OfCountry("sk") {
		t.Errorf("%v = false, want true", zip)
	}

	zip = "SK-811 02"
	if !Zip(zip).OfCountry("sk") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Slovenia
func TestSloveniaZip(t *testing.T) {
	zip = "1233"
	if !Zip(zip).OfCountry("si") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Sweden
func TestSwedenZip(t *testing.T) {
	zip = "254 76"
	if !Zip(zip).OfCountry("se") {
		t.Errorf("%v = false, want true", zip)
	}

	zip = "25476"
	if !Zip(zip).OfCountry("se") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Spain
func TestSpainZip(t *testing.T) {
	zip = "33559"
	if !Zip(zip).OfCountry("es") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Turkey
func TestTurkeyZip(t *testing.T) {
	zip = "21500"
	if !Zip(zip).OfCountry("tr") {
		t.Errorf("%v = false, want true", zip)
	}
}

// United States
func TestUSZip(t *testing.T) {
	zip = "83406"
	if !Zip(zip).OfCountry("us") {
		t.Errorf("%v = false, want true", zip)
	}

	zip = "83406-6715"
	if !Zip(zip).OfCountry("us") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Ukraine
func TestUkraineZip(t *testing.T) {
	zip = "27420"
	if !Zip(zip).OfCountry("ua") {
		t.Errorf("%v = false, want true", zip)
	}
}
<commit_msg>Re-order the tests for alphabetical purposes.<commit_after>package check

import (
	"testing"
)

var zip string

// Argentina
func TestArgentinaZip(t *testing.T) {
	zip = "B1657"
	if !Zip(zip).OfCountry("ar") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Australia
func TestAustraliaZip(t *testing.T) {
	zip = "2000"
	if !Zip(zip).OfCountry("au") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Austria
func TestAustriaZip(t *testing.T) {
	zip = "1010"
	if !Zip(zip).OfCountry("at") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Belgium
func TestBelgiumZip(t *testing.T) {
	zip = "3840"
	if !Zip(zip).OfCountry("be") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Brazil
func TestBrazilZip(t *testing.T) {
	zip = "00000-000"
	if !Zip(zip).OfCountry("br") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Bulgaria
func TestBulgariaZip(t *testing.T) {
	zip = "5094"
	if !Zip(zip).OfCountry("bg") {
		t.Errorf("%v = false, want true", zip)
	}

}

// Canada
func TestCanadaZip(t *testing.T) {
	zip = "L4C 3V2"
	if !Zip(zip).OfCountry("ca") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Croatia
func TestCroatiaZip(t *testing.T) {
	zip = "10000"
	if !Zip(zip).OfCountry("hr") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Cyprus
func TestCyprusZip(t *testing.T) {
	zip = "8501"
	if !Zip(zip).OfCountry("cy") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Czech Republic
func TestCzechRepublicZip(t *testing.T) {
	zip = "160 00"
	if !Zip(zip).OfCountry("cz") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Denmark
func TestDenmarkZip(t *testing.T) {
	zip = "2750"
	if !Zip(zip).OfCountry("dk") {
		t.Errorf("%v = false, want true", zip)
	}

	zip = "1750"
	if Zip(zip).OfCountry("dk") {
		t.Errorf("%v = false, want true", zip)
	}

}

// Estonia
func TestEstoniaZip(t *testing.T) {
	zip = "42106"
	if !Zip(zip).OfCountry("ee") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Finland
func TestFinlandZip(t *testing.T) {
	zip = "55100"
	if !Zip(zip).OfCountry("fi") {
		t.Errorf("%v = false, want true", zip)
	}
}

// France
func TestFranceZip(t *testing.T) {
	zip = "52110"
	if !Zip(zip).OfCountry("fr") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Germany
func TestGermanyZip(t *testing.T) {
	zip = "79258"
	if !Zip(zip).OfCountry("de") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Great Britain
func TestGreatBritainZip(t *testing.T) {
	zip = "EC1A 1BB"
	if !Zip(zip).OfCountry("gb") {
		t.Errorf("%v = false, want true", zip)
	}

	zip = "W1A 1HQ"
	if !Zip(zip).OfCountry("gb") {
		t.Errorf("%v = false, want true", zip)
	}

	zip = "M1 1AA"
	if !Zip(zip).OfCountry("gb") {
		t.Errorf("%v = false, want true", zip)
	}

	zip = "B33 8TH"
	if !Zip(zip).OfCountry("gb") {
		t.Errorf("%v = false, want true", zip)
	}

	zip = "CR2 6XH"
	if !Zip(zip).OfCountry("gb") {
		t.Errorf("%v = false, want true", zip)
	}

	zip = "DN55 1PT"
	if !Zip(zip).OfCountry("gb") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Greece
func TestGreeceZip(t *testing.T) {
	zip = "681 00"
	if !Zip(zip).OfCountry("gr") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Hungary
func TestHungaryZip(t *testing.T) {
	zip = "1013"
	if !Zip(zip).OfCountry("hu") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Iceland
func TestIcelandZip(t *testing.T) {
	zip = "720"
	if !Zip(zip).OfCountry("is") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Italy
func TestItalyZip(t *testing.T) {
	zip = "26812"
	if !Zip(zip).OfCountry("it") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Japan
func TestJapanZip(t *testing.T) {
	zip = "107-0061"
	if !Zip(zip).OfCountry("jp") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Latvia
func TestLatviaZip(t *testing.T) {
	zip = "LV-3701"
	if !Zip(zip).OfCountry("lv") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Lithuania
func TestLithuaniaZip(t *testing.T) {
	zip = "73461"
	if !Zip(zip).OfCountry("lt") {
		t.Errorf("%v = false, want true", zip)
	}

	zip = "LT-73184"
	if !Zip(zip).OfCountry("lt") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Luxembourg
func TestLuxembourgZip(t *testing.T) {
	// zip = "1010"
	// if !Zip(zip).OfCountry("lu") {
	// 	t.Errorf("%v = false, want true", zip)
	// }

	zip = "L-2920"
	if !Zip(zip).OfCountry("lu") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Malta
func TestMaltaZip(t *testing.T) {
	zip = "BBG 1014"
	if !Zip(zip).OfCountry("mt") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Netherlands
func TestNetherlandsZip(t *testing.T) {
	zip = "1000 AP"
	if !Zip(zip).OfCountry("nl") {
		t.Errorf("%v = false, want true", zip)
	}

	// zip = "1000"
	// if !Zip(zip).OfCountry("nl") {
	// 	t.Errorf("%v = false, want true", zip)
	// }
}

// Norway
func TestNorwayZip(t *testing.T) {
	zip = "0001"
	if !Zip(zip).OfCountry("no") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Poland
func TestPolandZip(t *testing.T) {
	zip = "26-600"
	if !Zip(zip).OfCountry("pl") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Portugal
func TestPortugalZip(t *testing.T) {
	zip = "1050"
	if !Zip(zip).OfCountry("pt") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Romania
func TestRomaniaZip(t *testing.T) {
	zip = "827019"
	if !Zip(zip).OfCountry("ro") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Slovakia
func TestSlovakiaZip(t *testing.T) {
	zip = "811 02"
	if !Zip(zip).OfCountry("sk") {
		t.Errorf("%v = false, want true", zip)
	}

	zip = "SK-811 02"
	if !Zip(zip).OfCountry("sk") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Slovenia
func TestSloveniaZip(t *testing.T) {
	zip = "1233"
	if !Zip(zip).OfCountry("si") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Spain
func TestSpainZip(t *testing.T) {
	zip = "33559"
	if !Zip(zip).OfCountry("es") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Sweden
func TestSwedenZip(t *testing.T) {
	zip = "254 76"
	if !Zip(zip).OfCountry("se") {
		t.Errorf("%v = false, want true", zip)
	}

	zip = "25476"
	if !Zip(zip).OfCountry("se") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Turkey
func TestTurkeyZip(t *testing.T) {
	zip = "21500"
	if !Zip(zip).OfCountry("tr") {
		t.Errorf("%v = false, want true", zip)
	}
}

// United States
func TestUSZip(t *testing.T) {
	zip = "83406"
	if !Zip(zip).OfCountry("us") {
		t.Errorf("%v = false, want true", zip)
	}

	zip = "83406-6715"
	if !Zip(zip).OfCountry("us") {
		t.Errorf("%v = false, want true", zip)
	}
}

// Ukraine
func TestUkraineZip(t *testing.T) {
	zip = "27420"
	if !Zip(zip).OfCountry("ua") {
		t.Errorf("%v = false, want true", zip)
	}
}
<|endoftext|>


<commit_before>package plugin

import (
	"encoding/json"
	"fmt"
	"sync"

	"github.com/hellofresh/janus/pkg/proxy"
	"github.com/pkg/errors"
	log "github.com/sirupsen/logrus"
)

var (
	lock sync.RWMutex

	// plugins is a map of plugin name to Plugin.
	plugins = make(map[string]Plugin)

	// eventHooks is a map of hook name to Hook. All hooks plugins
	// must have a name.
	eventHooks = make(map[string][]EventHook)
)

// SetupFunc is used to set up a plugin, or in other words,
// execute a directive. It will be called once per key for
// each server block it appears in.
type SetupFunc func(def *proxy.RouterDefinition, rawConfig Config) error

// ValidateFunc validates configuration data against the plugin struct
type ValidateFunc func(rawConfig Config) (bool, error)

// Config initialization options.
type Config map[string]interface{}

// Plugin defines basic methods for plugins
type Plugin struct {
	Action   SetupFunc
	Validate ValidateFunc
}

// RegisterPlugin plugs in plugin. All plugins should register
// themselves, even if they do not perform an action associated
// with a directive. It is important for the process to know
// which plugins are available.
//
// The plugin MUST have a name: lower case and one word.
// If this plugin has an action, it must be the name of
// the directive that invokes it. A name is always required
// and must be unique for the server type.
func RegisterPlugin(name string, plugin Plugin) error {
	lock.Lock()
	defer lock.Unlock()

	if name == "" {
		return errors.New("plugin must have a name")
	}
	if _, dup := plugins[name]; dup {
		return fmt.Errorf("plugin named %s  already registered", name)
	}
	plugins[name] = plugin
	return nil
}

// EventHook is a type which holds information about a startup hook plugin.
type EventHook func(event interface{}) error

// RegisterEventHook plugs in hook. All the hooks should register themselves
// and they must have a name.
func RegisterEventHook(name string, hook EventHook) error {
	log.WithField("event_name", name).Debug("Event registered")
	lock.Lock()
	defer lock.Unlock()

	if name == "" {
		return errors.New("event hook must have a name")
	}

	if hooks, dup := eventHooks[name]; dup {
		eventHooks[name] = append(hooks, hook)
	} else {
		eventHooks[name] = append([]EventHook{}, hook)
	}

	return nil
}

// EmitEvent executes the different hooks passing the EventType as an
// argument. This is a blocking function. Hook developers should
// use 'go' keyword if they don't want to block Janus.
func EmitEvent(name string, event interface{}) error {
	log.WithField("event_name", name).Debug("Event triggered")

	hooks, found := eventHooks[name]
	if !found {
		return errors.New("Plugin not found")
	}

	for _, hook := range hooks {
		err := hook(event)
		if err != nil {
			log.WithError(err).WithField("event_name", name).Warn("an error occurred when an event was triggered")
		}
	}

	return nil
}

// ValidateConfig validates the plugin configuration data
func ValidateConfig(name string, rawConfig Config) (bool, error) {
	if plugin, ok := plugins[name]; ok {
		if plugin.Validate == nil {
			return true, nil
		}

		result, err := plugin.Validate(rawConfig)
		return result, err
	}
	return false, fmt.Errorf("no validate function found for plugin '%s'", name)
}

// DirectiveAction gets the action for a plugin
func DirectiveAction(name string) (SetupFunc, error) {
	if plugin, ok := plugins[name]; ok {
		return plugin.Action, nil
	}
	return nil, fmt.Errorf("no action found for plugin '%s' (missing a plugin?)", name)
}

// Decode decodes a map string interface into a struct
// for some reasons mapstructure.Decode() gives empty arrays for all resulting config fields
// this is quick workaround hack t make it work
// FIXME: investigate and fix mapstructure.Decode() behaviour and remove this dirty hack
func Decode(rawConfig map[string]interface{}, obj interface{}) error {
	valJSON, err := json.Marshal(rawConfig)
	if nil != err {
		return err
	}

	err = json.Unmarshal(valJSON, obj)
	if nil != err {
		return err
	}

	return nil
}
<commit_msg>Improve logging<commit_after>package plugin

import (
	"encoding/json"
	"fmt"
	"sync"

	"github.com/hellofresh/janus/pkg/proxy"
	"github.com/pkg/errors"
	log "github.com/sirupsen/logrus"
)

var (
	lock sync.RWMutex

	// plugins is a map of plugin name to Plugin.
	plugins = make(map[string]Plugin)

	// eventHooks is a map of hook name to Hook. All hooks plugins
	// must have a name.
	eventHooks = make(map[string][]EventHook)
)

// SetupFunc is used to set up a plugin, or in other words,
// execute a directive. It will be called once per key for
// each server block it appears in.
type SetupFunc func(def *proxy.RouterDefinition, rawConfig Config) error

// ValidateFunc validates configuration data against the plugin struct
type ValidateFunc func(rawConfig Config) (bool, error)

// Config initialization options.
type Config map[string]interface{}

// Plugin defines basic methods for plugins
type Plugin struct {
	Action   SetupFunc
	Validate ValidateFunc
}

// RegisterPlugin plugs in plugin. All plugins should register
// themselves, even if they do not perform an action associated
// with a directive. It is important for the process to know
// which plugins are available.
//
// The plugin MUST have a name: lower case and one word.
// If this plugin has an action, it must be the name of
// the directive that invokes it. A name is always required
// and must be unique for the server type.
func RegisterPlugin(name string, plugin Plugin) error {
	lock.Lock()
	defer lock.Unlock()

	if name == "" {
		return errors.New("plugin must have a name")
	}
	if _, dup := plugins[name]; dup {
		return fmt.Errorf("plugin named %s  already registered", name)
	}
	plugins[name] = plugin
	return nil
}

// EventHook is a type which holds information about a startup hook plugin.
type EventHook func(event interface{}) error

// RegisterEventHook plugs in hook. All the hooks should register themselves
// and they must have a name.
func RegisterEventHook(name string, hook EventHook) error {
	log.WithField("event_name", name).Debug("Event registered")
	lock.Lock()
	defer lock.Unlock()

	if name == "" {
		return errors.New("event hook must have a name")
	}

	if hooks, dup := eventHooks[name]; dup {
		eventHooks[name] = append(hooks, hook)
	} else {
		eventHooks[name] = append([]EventHook{}, hook)
	}

	return nil
}

// EmitEvent executes the different hooks passing the EventType as an
// argument. This is a blocking function. Hook developers should
// use 'go' keyword if they don't want to block Janus.
func EmitEvent(name string, event interface{}) error {
	log.WithField("event_name", name).Debug("Event triggered")

	hooks, found := eventHooks[name]
	if !found {
		return errors.New("Plugin not found")
	}

	for _, hook := range hooks {
		err := hook(event)
		if err != nil {
			log.WithError(err).WithField("event_name", name).Warn("an error occurred when an event was triggered")
		}
	}

	return nil
}

// ValidateConfig validates the plugin configuration data
func ValidateConfig(name string, rawConfig Config) (bool, error) {
	logger := log.WithField("plugin_name", name)

	if plugin, ok := plugins[name]; ok {
		if plugin.Validate == nil {
			logger.Debug("Validation function undefined; assuming valid configuration")
			return true, nil
		}

		result, err := plugin.Validate(rawConfig)
		if !result || err != nil {
			logger.WithField("config", rawConfig).Info("Invalid plugin configuration")
		}

		return result, err
	}

	return false, fmt.Errorf("Plugin %q not found", name)
}

// DirectiveAction gets the action for a plugin
func DirectiveAction(name string) (SetupFunc, error) {
	if plugin, ok := plugins[name]; ok {
		if plugin.Action == nil {
			return nil, fmt.Errorf("Action function undefined for plugin %q", name)
		}

		return plugin.Action, nil
	}

	return nil, fmt.Errorf("Plugin %q not found", name)
}

// Decode decodes a map string interface into a struct
// for some reasons mapstructure.Decode() gives empty arrays for all resulting config fields
// this is quick workaround hack t make it work
// FIXME: investigate and fix mapstructure.Decode() behaviour and remove this dirty hack
func Decode(rawConfig map[string]interface{}, obj interface{}) error {
	valJSON, err := json.Marshal(rawConfig)
	if nil != err {
		return err
	}

	err = json.Unmarshal(valJSON, obj)
	if nil != err {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package router

import (
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"

	"sync"

	"github.com/matt-deboer/mpp/pkg/locator"
	"github.com/matt-deboer/mpp/pkg/selector"
	"github.com/matt-deboer/mpp/pkg/version"
	log "github.com/sirupsen/logrus"
	"github.com/vulcand/oxy/buffer"
	"github.com/vulcand/oxy/forward"
)

// Router provides dynamic routing of http requests based on a configurable strategy
type Router struct {
	locators        []locator.Locator
	selector        *selector.Selector
	selection       *selector.Result
	forward         http.Handler
	buffer          *buffer.Buffer
	rewriter        urlRewriter
	affinityOptions []AffinityOption
	interval        time.Duration
	metrics         *metrics
	// used to mark control of the selection process
	theConch            chan struct{}
	selectionInProgress sync.RWMutex
	shutdownHook        chan struct{}
}

// Status contains a snapshot status summary of the router state
type Status struct {
	Endpoints           []*locator.PrometheusEndpoint
	Strategy            string
	StrategyDescription string
	AffinityOptions     string
	ComparisonMetric    string
	Interval            time.Duration
}

type urlRewriter func(u *url.URL)

var noOpRewriter = func(u *url.URL) {}

// NewRouter constructs a new router based on the provided stategy and locators
func NewRouter(interval time.Duration, affinityOptions []AffinityOption,
	locators []locator.Locator, strategyArgs ...string) (*Router, error) {

	sel, err := selector.NewSelector(locators, strategyArgs...)
	if err != nil {
		return nil, err
	}

	r := &Router{
		locators:        locators,
		selector:        sel,
		affinityOptions: affinityOptions,
		interval:        interval,
		rewriter:        noOpRewriter,
		metrics:         newMetrics(version.Name),
		selection:       &selector.Result{},
		theConch:        make(chan struct{}, 1),
		shutdownHook:    make(chan struct{}, 1),
	}

	// Set up the lock
	r.theConch <- struct{}{}
	r.doSelection()
	go func() {
		// TODO: create shutdown channel for this
		for {
			if log.GetLevel() >= log.DebugLevel {
				log.Debugf("Backend selection is sleeping for %s", interval)
			}
			time.Sleep(r.interval)

			select {
			case _ = <-r.shutdownHook:
				return
			default:
				r.doSelection()
			}
		}
	}()

	r.forward, _ = forward.New()
	r.buffer, _ = buffer.New(&internalRouter{
		router:   r,
		affinity: newAffinityProvider(affinityOptions),
	},
		buffer.Retry(`IsNetworkError() && Attempts() < 2`))
	return r, nil
}

// Close stops the router's background selection routine
func (r *Router) Close() {
	r.shutdownHook <- struct{}{}
}

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	r.buffer.ServeHTTP(w, retryableRequest(req))
}

func (r *Router) doSelection() {
	select {
	case _ = <-r.theConch:
		r.selectionInProgress.Lock()
		defer func() {
			r.selectionInProgress.Unlock()
			if log.GetLevel() >= log.DebugLevel {
				log.Debugf("Returning selection lock")
			}
			r.theConch <- struct{}{}
		}()

		if log.GetLevel() >= log.DebugLevel {
			log.Debugf("Got selection lock; performing selection")
		}

		result, err := r.selector.Select()

		if len(result.Selection) == 0 {
			if err != nil {
				log.Errorf("Selector returned no valid selection, and error: %v", err)
				if r.selection == nil {
					r.selection = result
					r.rewriter = noOpRewriter
				}
			} else {
				r.selection = result
				r.rewriter = noOpRewriter
				log.Warnf("Selector returned no valid selection")
			}
		} else {
			if log.GetLevel() >= log.DebugLevel {
				log.Debugf("Selected targets: %v", result.Selection)
			}
			if r.selection == nil || !equal(r.selection.Selection, result.Selection) {
				log.Infof("New targets differ from current selection %v; updating rewriter => %v", r.selection, result)
				r.rewriter = func(u *url.URL) {
					selection := result.Selection
					i := r.selector.Strategy.NextIndex(selection)
					target := selection[i]
					u.Host = target.Host
					u.Scheme = target.Scheme
				}
			} else {
				log.Infof("Selection is unchanged: %v, out of candidates: %v", r.selection.Selection, r.selection.Candidates)
			}
			r.selection = result
		}

		r.metrics.selectedBackends.Set(float64(len(result.Selection)))
		r.metrics.selectionEvents.Inc()

	default:
		log.Warnf("Selection is already in-progress; awaiting result")
		r.selectionInProgress.RLock()
		r.selectionInProgress.RUnlock()
	}
}

func equal(a, b []*url.URL) bool {
	for i, v := range a {
		if *v != *b[i] {
			return false
		}
	}
	return len(a) == len(b)
}

func contains(a []*url.URL, u *url.URL) bool {
	for _, v := range a {
		if *u == *v {
			return true
		}
	}
	return false
}

func backend(u *url.URL) string {
	return fmt.Sprintf("%s://%s", u.Scheme, u.Host)
}

// Status returns a summary of the router's current state
func (r *Router) Status() *Status {
	return &Status{
		Endpoints:           r.selection.Candidates,
		Strategy:            r.selector.Strategy.Name(),
		StrategyDescription: r.selector.Strategy.Description(),
		ComparisonMetric:    r.selector.Strategy.ComparisonMetricName(),
		AffinityOptions:     strings.Trim(fmt.Sprintf("%v", r.affinityOptions), "[]"),
		Interval:            r.interval,
	}
}
<commit_msg>fixed url array comparison<commit_after>package router

import (
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"

	"sync"

	"github.com/matt-deboer/mpp/pkg/locator"
	"github.com/matt-deboer/mpp/pkg/selector"
	"github.com/matt-deboer/mpp/pkg/version"
	log "github.com/sirupsen/logrus"
	"github.com/vulcand/oxy/buffer"
	"github.com/vulcand/oxy/forward"
)

// Router provides dynamic routing of http requests based on a configurable strategy
type Router struct {
	locators        []locator.Locator
	selector        *selector.Selector
	selection       *selector.Result
	forward         http.Handler
	buffer          *buffer.Buffer
	rewriter        urlRewriter
	affinityOptions []AffinityOption
	interval        time.Duration
	metrics         *metrics
	// used to mark control of the selection process
	theConch            chan struct{}
	selectionInProgress sync.RWMutex
	shutdownHook        chan struct{}
}

// Status contains a snapshot status summary of the router state
type Status struct {
	Endpoints           []*locator.PrometheusEndpoint
	Strategy            string
	StrategyDescription string
	AffinityOptions     string
	ComparisonMetric    string
	Interval            time.Duration
}

type urlRewriter func(u *url.URL)

var noOpRewriter = func(u *url.URL) {}

// NewRouter constructs a new router based on the provided stategy and locators
func NewRouter(interval time.Duration, affinityOptions []AffinityOption,
	locators []locator.Locator, strategyArgs ...string) (*Router, error) {

	sel, err := selector.NewSelector(locators, strategyArgs...)
	if err != nil {
		return nil, err
	}

	r := &Router{
		locators:        locators,
		selector:        sel,
		affinityOptions: affinityOptions,
		interval:        interval,
		rewriter:        noOpRewriter,
		metrics:         newMetrics(version.Name),
		selection:       &selector.Result{},
		theConch:        make(chan struct{}, 1),
		shutdownHook:    make(chan struct{}, 1),
	}

	// Set up the lock
	r.theConch <- struct{}{}
	r.doSelection()
	go func() {
		// TODO: create shutdown channel for this
		for {
			if log.GetLevel() >= log.DebugLevel {
				log.Debugf("Backend selection is sleeping for %s", interval)
			}
			time.Sleep(r.interval)

			select {
			case _ = <-r.shutdownHook:
				return
			default:
				r.doSelection()
			}
		}
	}()

	r.forward, _ = forward.New()
	r.buffer, _ = buffer.New(&internalRouter{
		router:   r,
		affinity: newAffinityProvider(affinityOptions),
	},
		buffer.Retry(`IsNetworkError() && Attempts() < 2`))
	return r, nil
}

// Close stops the router's background selection routine
func (r *Router) Close() {
	r.shutdownHook <- struct{}{}
}

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	r.buffer.ServeHTTP(w, retryableRequest(req))
}

func (r *Router) doSelection() {
	select {
	case _ = <-r.theConch:
		r.selectionInProgress.Lock()
		defer func() {
			r.selectionInProgress.Unlock()
			if log.GetLevel() >= log.DebugLevel {
				log.Debugf("Returning selection lock")
			}
			r.theConch <- struct{}{}
		}()

		if log.GetLevel() >= log.DebugLevel {
			log.Debugf("Got selection lock; performing selection")
		}

		result, err := r.selector.Select()

		if len(result.Selection) == 0 {
			if err != nil {
				log.Errorf("Selector returned no valid selection, and error: %v", err)
				if r.selection == nil {
					r.selection = result
					r.rewriter = noOpRewriter
				}
			} else {
				r.selection = result
				r.rewriter = noOpRewriter
				log.Warnf("Selector returned no valid selection")
			}
		} else {
			if log.GetLevel() >= log.DebugLevel {
				log.Debugf("Selected targets: %v", result.Selection)
			}
			if r.selection == nil || !equal(r.selection.Selection, result.Selection) {
				log.Infof("New targets differ from current selection %v; updating rewriter => %v", r.selection, result)
				r.rewriter = func(u *url.URL) {
					selection := result.Selection
					i := r.selector.Strategy.NextIndex(selection)
					target := selection[i]
					u.Host = target.Host
					u.Scheme = target.Scheme
				}
			} else {
				log.Infof("Selection is unchanged: %v, out of candidates: %v", r.selection.Selection, r.selection.Candidates)
			}
			r.selection = result
		}

		r.metrics.selectedBackends.Set(float64(len(result.Selection)))
		r.metrics.selectionEvents.Inc()

	default:
		log.Warnf("Selection is already in-progress; awaiting result")
		r.selectionInProgress.RLock()
		r.selectionInProgress.RUnlock()
	}
}

func equal(a, b []*url.URL) bool {
	if len(a) == len(b) {
		for i, v := range a {
			if *v != *b[i] {
				return false
			}
		}
		return true
	}
	return false
}

func contains(a []*url.URL, u *url.URL) bool {
	for _, v := range a {
		if *u == *v {
			return true
		}
	}
	return false
}

func backend(u *url.URL) string {
	return fmt.Sprintf("%s://%s", u.Scheme, u.Host)
}

// Status returns a summary of the router's current state
func (r *Router) Status() *Status {
	return &Status{
		Endpoints:           r.selection.Candidates,
		Strategy:            r.selector.Strategy.Name(),
		StrategyDescription: r.selector.Strategy.Description(),
		ComparisonMetric:    r.selector.Strategy.ComparisonMetricName(),
		AffinityOptions:     strings.Trim(fmt.Sprintf("%v", r.affinityOptions), "[]"),
		Interval:            r.interval,
	}
}
<|endoftext|>


<commit_before>// Copyright 2012 The Plotinum Authors. All rights reserved.
// Use of this source code is governed by an MIT-style license
// that can be found in the LICENSE file.

package plotter

import (
	"code.google.com/p/plotinum/plot"
	"fmt"
	"image/color"
	"math"
)

// Histogram implements the Plotter interface,
// drawing a histogram of the data.
type Histogram struct {
	// Bins is the set of bins for this histogram.
	Bins []Bin

	// Width is the width of each bin.
	Width float64

	// FillColor is the color used to fill each
	// bar of the histogram.  If the color is nil
	// then the bars are not filled.
	FillColor color.Color

	// LineStyle is the style of the outline of each
	// bar of the histogram.
	plot.LineStyle
}

// NewHistogram returns a new histogram
// that represents the distribution of values
// using the given number of bins.
//
// Each y value is assumed to be the frequency
// count for the corresponding x.
//
// If the number of bins is non-positive than
// a reasonable default is used.
func NewHistogram(xy XYer, n int) *Histogram {
	bins, width := binPoints(xy, n)
	return &Histogram{
		Bins:      bins,
		Width:     width,
		FillColor: color.Gray{128},
		LineStyle: DefaultLineStyle,
	}
}

// NewHist returns a new histogram, as in
// NewHistogram, except that it accepts a Valuer
// instead of an XYer.
func NewHist(vs Valuer, n int) *Histogram {
	return NewHistogram(unitYs{vs}, n)
}

type unitYs struct {
	Valuer
}

func (u unitYs) XY(i int) (float64, float64) {
	return u.Value(i), 1.0
}

// Plot implements the Plotter interface, drawing a line
// that connects each point in the Line.
func (h *Histogram) Plot(da plot.DrawArea, p *plot.Plot) {
	trX, trY := p.Transforms(&da)

	for _, bin := range h.Bins {
		pts := []plot.Point{
			{trX(bin.Min), trY(0)},
			{trX(bin.Max), trY(0)},
			{trX(bin.Max), trY(bin.Weight)},
			{trX(bin.Min), trY(bin.Weight)},
		}
		if h.FillColor != nil {
			da.FillPolygon(h.FillColor, da.ClipPolygonXY(pts))
		}
		pts = append(pts, plot.Point{trX(bin.Min), trY(0)})
		da.StrokeLines(h.LineStyle, da.ClipLinesXY(pts)...)
	}
}

// DataRange returns the minimum and maximum X and Y values
func (h *Histogram) DataRange() (xmin, xmax, ymin, ymax float64) {
	xmin = math.Inf(1)
	xmax = math.Inf(-1)
	ymax = math.Inf(-1)
	for _, bin := range h.Bins {
		if bin.Max > xmax {
			xmax = bin.Max
		}
		if bin.Min < xmin {
			xmin = bin.Min
		}
		if bin.Weight > ymax {
			ymax = bin.Weight
		}
	}
	return
}

// Normalize normalizes the histogram so that the
// total area beneath it sums to a given value.
func (h *Histogram) Normalize(sum float64) {
	mass := 0.0
	for _, b := range h.Bins {
		mass += b.Weight
	}
	for i := range h.Bins {
		h.Bins[i].Weight *= sum / (h.Width * mass)
	}
}

// binPoints returns a slice containing the
// given number of bins, and the width of
// each bin.
//
// If the given number of bins is not positive
// then a reasonable default is used.  The
// default is the square root of the sum of
// the y values.
func binPoints(xys XYer, n int) ([]Bin, float64) {
	xmin, xmax := Range(XValues{xys})
	if n <= 0 {
		m := 0.0
		for i := 0; i < xys.Len(); i++ {
			_, y := xys.XY(i)
			m += math.Max(y, 1.0)
		}
		n = int(math.Ceil(math.Sqrt(m)))
	}
	if n < 1 || xmax <= xmin {
		n = 1
	}

	bins := make([]Bin, n)

	w := (xmax - xmin) / float64(n)
	for i := range bins {
		bins[i].Min = xmin + float64(i)*w
		bins[i].Max = xmin + float64(i+1)*w
	}

	for i := 0; i < xys.Len(); i++ {
		x, y := xys.XY(i)
		bin := int((x - xmin) / w)
		if x == xmax {
			bin = n - 1
		}
		if bin < 0 || bin >= n {
			panic(fmt.Sprintf("%g, xmin=%g, xmax=%g, w=%g, bin=%d, n=%d\n",
				x, xmin, xmax, w, bin, n))
		}
		bins[bin].Weight += y
	}
	return bins, w
}

// Bin is a histogram bin.
type Bin struct {
	Min, Max float64
	Weight   float64
}
<commit_msg>Rename Bin to HistogramBin.<commit_after>// Copyright 2012 The Plotinum Authors. All rights reserved.
// Use of this source code is governed by an MIT-style license
// that can be found in the LICENSE file.

package plotter

import (
	"code.google.com/p/plotinum/plot"
	"fmt"
	"image/color"
	"math"
)

// Histogram implements the Plotter interface,
// drawing a histogram of the data.
type Histogram struct {
	// Bins is the set of bins for this histogram.
	Bins []HistogramBin

	// Width is the width of each bin.
	Width float64

	// FillColor is the color used to fill each
	// bar of the histogram.  If the color is nil
	// then the bars are not filled.
	FillColor color.Color

	// LineStyle is the style of the outline of each
	// bar of the histogram.
	plot.LineStyle
}

// NewHistogram returns a new histogram
// that represents the distribution of values
// using the given number of bins.
//
// Each y value is assumed to be the frequency
// count for the corresponding x.
//
// If the number of bins is non-positive than
// a reasonable default is used.
func NewHistogram(xy XYer, n int) *Histogram {
	bins, width := binPoints(xy, n)
	return &Histogram{
		Bins:      bins,
		Width:     width,
		FillColor: color.Gray{128},
		LineStyle: DefaultLineStyle,
	}
}

// NewHist returns a new histogram, as in
// NewHistogram, except that it accepts a Valuer
// instead of an XYer.
func NewHist(vs Valuer, n int) *Histogram {
	return NewHistogram(unitYs{vs}, n)
}

type unitYs struct {
	Valuer
}

func (u unitYs) XY(i int) (float64, float64) {
	return u.Value(i), 1.0
}

// Plot implements the Plotter interface, drawing a line
// that connects each point in the Line.
func (h *Histogram) Plot(da plot.DrawArea, p *plot.Plot) {
	trX, trY := p.Transforms(&da)

	for _, bin := range h.Bins {
		pts := []plot.Point{
			{trX(bin.Min), trY(0)},
			{trX(bin.Max), trY(0)},
			{trX(bin.Max), trY(bin.Weight)},
			{trX(bin.Min), trY(bin.Weight)},
		}
		if h.FillColor != nil {
			da.FillPolygon(h.FillColor, da.ClipPolygonXY(pts))
		}
		pts = append(pts, plot.Point{trX(bin.Min), trY(0)})
		da.StrokeLines(h.LineStyle, da.ClipLinesXY(pts)...)
	}
}

// DataRange returns the minimum and maximum X and Y values
func (h *Histogram) DataRange() (xmin, xmax, ymin, ymax float64) {
	xmin = math.Inf(1)
	xmax = math.Inf(-1)
	ymax = math.Inf(-1)
	for _, bin := range h.Bins {
		if bin.Max > xmax {
			xmax = bin.Max
		}
		if bin.Min < xmin {
			xmin = bin.Min
		}
		if bin.Weight > ymax {
			ymax = bin.Weight
		}
	}
	return
}

// Normalize normalizes the histogram so that the
// total area beneath it sums to a given value.
func (h *Histogram) Normalize(sum float64) {
	mass := 0.0
	for _, b := range h.Bins {
		mass += b.Weight
	}
	for i := range h.Bins {
		h.Bins[i].Weight *= sum / (h.Width * mass)
	}
}

// binPoints returns a slice containing the
// given number of bins, and the width of
// each bin.
//
// If the given number of bins is not positive
// then a reasonable default is used.  The
// default is the square root of the sum of
// the y values.
func binPoints(xys XYer, n int) ([]HistogramBin, float64) {
	xmin, xmax := Range(XValues{xys})
	if n <= 0 {
		m := 0.0
		for i := 0; i < xys.Len(); i++ {
			_, y := xys.XY(i)
			m += math.Max(y, 1.0)
		}
		n = int(math.Ceil(math.Sqrt(m)))
	}
	if n < 1 || xmax <= xmin {
		n = 1
	}

	bins := make([]HistogramBin, n)

	w := (xmax - xmin) / float64(n)
	for i := range bins {
		bins[i].Min = xmin + float64(i)*w
		bins[i].Max = xmin + float64(i+1)*w
	}

	for i := 0; i < xys.Len(); i++ {
		x, y := xys.XY(i)
		bin := int((x - xmin) / w)
		if x == xmax {
			bin = n - 1
		}
		if bin < 0 || bin >= n {
			panic(fmt.Sprintf("%g, xmin=%g, xmax=%g, w=%g, bin=%d, n=%d\n",
				x, xmin, xmax, w, bin, n))
		}
		bins[bin].Weight += y
	}
	return bins, w
}

// A HistogramBin approximates the number of values
// within a range by a single number (the weight).
type HistogramBin struct {
	Min, Max float64
	Weight   float64
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

package gnmi

import (
	"bufio"
	"bytes"
	"context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"strconv"
	"strings"
	"time"

	"github.com/aristanetworks/glog"
	pb "github.com/openconfig/gnmi/proto/gnmi"
	"google.golang.org/grpc/codes"
)

// Get sents a GetRequest to the given client.
func Get(ctx context.Context, client pb.GNMIClient, paths [][]string, origin string) error {
	req, err := NewGetRequest(paths, origin)
	if err != nil {
		return err
	}
	resp, err := client.Get(ctx, req)
	if err != nil {
		return err
	}
	for _, notif := range resp.Notification {
		prefix := StrPath(notif.Prefix)
		for _, update := range notif.Update {
			fmt.Printf("%s:\n", path.Join(prefix, StrPath(update.Path)))
			fmt.Println(StrUpdateVal(update))
		}
	}
	return nil
}

// Capabilities retuns the capabilities of the client.
func Capabilities(ctx context.Context, client pb.GNMIClient) error {
	resp, err := client.Capabilities(ctx, &pb.CapabilityRequest{})
	if err != nil {
		return err
	}
	fmt.Printf("Version: %s\n", resp.GNMIVersion)
	for _, mod := range resp.SupportedModels {
		fmt.Printf("SupportedModel: %s\n", mod)
	}
	for _, enc := range resp.SupportedEncodings {
		fmt.Printf("SupportedEncoding: %s\n", enc)
	}
	return nil
}

// val may be a path to a file or it may be json. First see if it is a
// file, if so return its contents, otherwise return val
func extractJSON(val string) []byte {
	if jsonBytes, err := ioutil.ReadFile(val); err == nil {
		return jsonBytes
	}
	// Best effort check if the value might a string literal, in which
	// case wrap it in quotes. This is to allow a user to do:
	//   gnmi update ../hostname host1234
	//   gnmi update ../description 'This is a description'
	// instead of forcing them to quote the string:
	//   gnmi update ../hostname '"host1234"'
	//   gnmi update ../description '"This is a description"'
	maybeUnquotedStringLiteral := func(s string) bool {
		if s == "true" || s == "false" || s == "null" || // JSON reserved words
			strings.ContainsAny(s, `"'{}[]`) { // Already quoted or is a JSON object or array
			return false
		} else if _, err := strconv.ParseInt(s, 0, 32); err == nil {
			// Integer. Using byte size of 32 because larger integer
			// types are supposed to be sent as strings in JSON.
			return false
		} else if _, err := strconv.ParseFloat(s, 64); err == nil {
			// Float
			return false
		}

		return true
	}
	if maybeUnquotedStringLiteral(val) {
		out := make([]byte, len(val)+2)
		out[0] = '"'
		copy(out[1:], val)
		out[len(out)-1] = '"'
		return out
	}
	return []byte(val)
}

// StrUpdateVal will return a string representing the value within the supplied update
func StrUpdateVal(u *pb.Update) string {
	if u.Value != nil {
		// Backwards compatibility with pre-v0.4 gnmi
		switch u.Value.Type {
		case pb.Encoding_JSON, pb.Encoding_JSON_IETF:
			return strJSON(u.Value.Value)
		case pb.Encoding_BYTES, pb.Encoding_PROTO:
			return base64.StdEncoding.EncodeToString(u.Value.Value)
		case pb.Encoding_ASCII:
			return string(u.Value.Value)
		default:
			return string(u.Value.Value)
		}
	}
	return StrVal(u.Val)
}

// StrVal will return a string representing the supplied value
func StrVal(val *pb.TypedValue) string {
	switch v := val.GetValue().(type) {
	case *pb.TypedValue_StringVal:
		return v.StringVal
	case *pb.TypedValue_JsonIetfVal:
		return strJSON(v.JsonIetfVal)
	case *pb.TypedValue_JsonVal:
		return strJSON(v.JsonVal)
	case *pb.TypedValue_IntVal:
		return strconv.FormatInt(v.IntVal, 10)
	case *pb.TypedValue_UintVal:
		return strconv.FormatUint(v.UintVal, 10)
	case *pb.TypedValue_BoolVal:
		return strconv.FormatBool(v.BoolVal)
	case *pb.TypedValue_BytesVal:
		return base64.StdEncoding.EncodeToString(v.BytesVal)
	case *pb.TypedValue_DecimalVal:
		return strDecimal64(v.DecimalVal)
	case *pb.TypedValue_FloatVal:
		return strconv.FormatFloat(float64(v.FloatVal), 'g', -1, 32)
	case *pb.TypedValue_LeaflistVal:
		return strLeaflist(v.LeaflistVal)
	case *pb.TypedValue_AsciiVal:
		return v.AsciiVal
	case *pb.TypedValue_AnyVal:
		return v.AnyVal.String()
	default:
		panic(v)
	}
}

func strJSON(inJSON []byte) string {
	var out bytes.Buffer
	err := json.Indent(&out, inJSON, "", "  ")
	if err != nil {
		return fmt.Sprintf("(error unmarshalling json: %s)\n", err) + string(inJSON)
	}
	return out.String()
}

func strDecimal64(d *pb.Decimal64) string {
	var i, frac int64
	if d.Precision > 0 {
		div := int64(10)
		it := d.Precision - 1
		for it > 0 {
			div *= 10
			it--
		}
		i = d.Digits / div
		frac = d.Digits % div
	} else {
		i = d.Digits
	}
	if frac < 0 {
		frac = -frac
	}
	return fmt.Sprintf("%d.%d", i, frac)
}

// strLeafList builds a human-readable form of a leaf-list. e.g. [1, 2, 3] or [a, b, c]
func strLeaflist(v *pb.ScalarArray) string {
	var b strings.Builder
	b.WriteByte('[')

	for i, elm := range v.Element {
		b.WriteString(StrVal(elm))
		if i < len(v.Element)-1 {
			b.WriteString(", ")
		}
	}

	b.WriteByte(']')
	return b.String()
}

func update(p *pb.Path, val string) *pb.Update {
	var v *pb.TypedValue
	switch p.Origin {
	case "":
		v = &pb.TypedValue{
			Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: extractJSON(val)}}
	case "eos_native":
		v = &pb.TypedValue{
			Value: &pb.TypedValue_JsonVal{JsonVal: extractJSON(val)}}
	case "cli", "test-regen-cli":
		v = &pb.TypedValue{
			Value: &pb.TypedValue_AsciiVal{AsciiVal: val}}
	case "p4_config":
		b, err := ioutil.ReadFile(val)
		if err != nil {
			glog.Fatalf("Cannot read p4 file: %s", err)
		}
		v = &pb.TypedValue{
			Value: &pb.TypedValue_ProtoBytes{ProtoBytes: b}}
	default:
		panic(fmt.Errorf("unexpected origin: %q", p.Origin))
	}

	return &pb.Update{Path: p, Val: v}
}

// Operation describes an gNMI operation.
type Operation struct {
	Type   string
	Origin string
	Path   []string
	Val    string
}

func newSetRequest(setOps []*Operation) (*pb.SetRequest, error) {
	req := &pb.SetRequest{}
	for _, op := range setOps {
		p, err := ParseGNMIElements(op.Path)
		if err != nil {
			return nil, err
		}
		p.Origin = op.Origin

		switch op.Type {
		case "delete":
			req.Delete = append(req.Delete, p)
		case "update":
			req.Update = append(req.Update, update(p, op.Val))
		case "replace":
			req.Replace = append(req.Replace, update(p, op.Val))
		}
	}
	return req, nil
}

// Set sends a SetRequest to the given client.
func Set(ctx context.Context, client pb.GNMIClient, setOps []*Operation) error {
	req, err := newSetRequest(setOps)
	if err != nil {
		return err
	}
	resp, err := client.Set(ctx, req)
	if err != nil {
		return err
	}
	if resp.Message != nil && codes.Code(resp.Message.Code) != codes.OK {
		return errors.New(resp.Message.Message)
	}
	// TODO: Iterate over SetResponse.Response for more detailed error message?

	return nil
}

// Subscribe sends a SubscribeRequest to the given client.
func Subscribe(ctx context.Context, client pb.GNMIClient, subscribeOptions *SubscribeOptions,
	respChan chan<- *pb.SubscribeResponse, errChan chan<- error) {
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	defer close(respChan)

	stream, err := client.Subscribe(ctx)
	if err != nil {
		errChan <- err
		return
	}
	req, err := NewSubscribeRequest(subscribeOptions)
	if err != nil {
		errChan <- err
		return
	}
	if err := stream.Send(req); err != nil {
		errChan <- err
		return
	}

	for {
		resp, err := stream.Recv()
		if err != nil {
			if err == io.EOF {
				return
			}
			errChan <- err
			return
		}
		respChan <- resp

		// For POLL subscriptions, initiate a poll request by pressing ENTER
		if subscribeOptions.Mode == "poll" {
			switch resp.Response.(type) {
			case *pb.SubscribeResponse_SyncResponse:
				fmt.Print("Press ENTER to send a poll request: ")
				reader := bufio.NewReader(os.Stdin)
				reader.ReadString('\n')

				pollReq := &pb.SubscribeRequest{
					Request: &pb.SubscribeRequest_Poll{
						Poll: &pb.Poll{},
					},
				}
				if err := stream.Send(pollReq); err != nil {
					errChan <- err
					return
				}
			}
		}
	}
}

// LogSubscribeResponse logs update responses to stderr.
func LogSubscribeResponse(response *pb.SubscribeResponse) error {
	switch resp := response.Response.(type) {
	case *pb.SubscribeResponse_Error:
		return errors.New(resp.Error.Message)
	case *pb.SubscribeResponse_SyncResponse:
		if !resp.SyncResponse {
			return errors.New("initial sync failed")
		}
	case *pb.SubscribeResponse_Update:
		t := time.Unix(0, resp.Update.Timestamp).UTC()
		prefix := StrPath(resp.Update.Prefix)
		for _, update := range resp.Update.Update {
			fmt.Printf("[%s] %s = %s\n", t.Format(time.RFC3339Nano),
				path.Join(prefix, StrPath(update.Path)),
				StrUpdateVal(update))
		}
		for _, del := range resp.Update.Delete {
			fmt.Printf("[%s] Deleted %s\n", t.Format(time.RFC3339Nano),
				path.Join(prefix, StrPath(del)))
		}
	}
	return nil
}
<commit_msg>gnmi: propagate errors<commit_after>// Copyright (c) 2017 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

package gnmi

import (
	"bufio"
	"bytes"
	"context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"strconv"
	"strings"
	"time"

	pb "github.com/openconfig/gnmi/proto/gnmi"
	"google.golang.org/grpc/codes"
)

// Get sents a GetRequest to the given client.
func Get(ctx context.Context, client pb.GNMIClient, paths [][]string, origin string) error {
	req, err := NewGetRequest(paths, origin)
	if err != nil {
		return err
	}
	resp, err := client.Get(ctx, req)
	if err != nil {
		return err
	}
	for _, notif := range resp.Notification {
		prefix := StrPath(notif.Prefix)
		for _, update := range notif.Update {
			fmt.Printf("%s:\n", path.Join(prefix, StrPath(update.Path)))
			fmt.Println(StrUpdateVal(update))
		}
	}
	return nil
}

// Capabilities retuns the capabilities of the client.
func Capabilities(ctx context.Context, client pb.GNMIClient) error {
	resp, err := client.Capabilities(ctx, &pb.CapabilityRequest{})
	if err != nil {
		return err
	}
	fmt.Printf("Version: %s\n", resp.GNMIVersion)
	for _, mod := range resp.SupportedModels {
		fmt.Printf("SupportedModel: %s\n", mod)
	}
	for _, enc := range resp.SupportedEncodings {
		fmt.Printf("SupportedEncoding: %s\n", enc)
	}
	return nil
}

// val may be a path to a file or it may be json. First see if it is a
// file, if so return its contents, otherwise return val
func extractJSON(val string) []byte {
	if jsonBytes, err := ioutil.ReadFile(val); err == nil {
		return jsonBytes
	}
	// Best effort check if the value might a string literal, in which
	// case wrap it in quotes. This is to allow a user to do:
	//   gnmi update ../hostname host1234
	//   gnmi update ../description 'This is a description'
	// instead of forcing them to quote the string:
	//   gnmi update ../hostname '"host1234"'
	//   gnmi update ../description '"This is a description"'
	maybeUnquotedStringLiteral := func(s string) bool {
		if s == "true" || s == "false" || s == "null" || // JSON reserved words
			strings.ContainsAny(s, `"'{}[]`) { // Already quoted or is a JSON object or array
			return false
		} else if _, err := strconv.ParseInt(s, 0, 32); err == nil {
			// Integer. Using byte size of 32 because larger integer
			// types are supposed to be sent as strings in JSON.
			return false
		} else if _, err := strconv.ParseFloat(s, 64); err == nil {
			// Float
			return false
		}

		return true
	}
	if maybeUnquotedStringLiteral(val) {
		out := make([]byte, len(val)+2)
		out[0] = '"'
		copy(out[1:], val)
		out[len(out)-1] = '"'
		return out
	}
	return []byte(val)
}

// StrUpdateVal will return a string representing the value within the supplied update
func StrUpdateVal(u *pb.Update) string {
	if u.Value != nil {
		// Backwards compatibility with pre-v0.4 gnmi
		switch u.Value.Type {
		case pb.Encoding_JSON, pb.Encoding_JSON_IETF:
			return strJSON(u.Value.Value)
		case pb.Encoding_BYTES, pb.Encoding_PROTO:
			return base64.StdEncoding.EncodeToString(u.Value.Value)
		case pb.Encoding_ASCII:
			return string(u.Value.Value)
		default:
			return string(u.Value.Value)
		}
	}
	return StrVal(u.Val)
}

// StrVal will return a string representing the supplied value
func StrVal(val *pb.TypedValue) string {
	switch v := val.GetValue().(type) {
	case *pb.TypedValue_StringVal:
		return v.StringVal
	case *pb.TypedValue_JsonIetfVal:
		return strJSON(v.JsonIetfVal)
	case *pb.TypedValue_JsonVal:
		return strJSON(v.JsonVal)
	case *pb.TypedValue_IntVal:
		return strconv.FormatInt(v.IntVal, 10)
	case *pb.TypedValue_UintVal:
		return strconv.FormatUint(v.UintVal, 10)
	case *pb.TypedValue_BoolVal:
		return strconv.FormatBool(v.BoolVal)
	case *pb.TypedValue_BytesVal:
		return base64.StdEncoding.EncodeToString(v.BytesVal)
	case *pb.TypedValue_DecimalVal:
		return strDecimal64(v.DecimalVal)
	case *pb.TypedValue_FloatVal:
		return strconv.FormatFloat(float64(v.FloatVal), 'g', -1, 32)
	case *pb.TypedValue_LeaflistVal:
		return strLeaflist(v.LeaflistVal)
	case *pb.TypedValue_AsciiVal:
		return v.AsciiVal
	case *pb.TypedValue_AnyVal:
		return v.AnyVal.String()
	default:
		panic(v)
	}
}

func strJSON(inJSON []byte) string {
	var out bytes.Buffer
	err := json.Indent(&out, inJSON, "", "  ")
	if err != nil {
		return fmt.Sprintf("(error unmarshalling json: %s)\n", err) + string(inJSON)
	}
	return out.String()
}

func strDecimal64(d *pb.Decimal64) string {
	var i, frac int64
	if d.Precision > 0 {
		div := int64(10)
		it := d.Precision - 1
		for it > 0 {
			div *= 10
			it--
		}
		i = d.Digits / div
		frac = d.Digits % div
	} else {
		i = d.Digits
	}
	if frac < 0 {
		frac = -frac
	}
	return fmt.Sprintf("%d.%d", i, frac)
}

// strLeafList builds a human-readable form of a leaf-list. e.g. [1, 2, 3] or [a, b, c]
func strLeaflist(v *pb.ScalarArray) string {
	var b strings.Builder
	b.WriteByte('[')

	for i, elm := range v.Element {
		b.WriteString(StrVal(elm))
		if i < len(v.Element)-1 {
			b.WriteString(", ")
		}
	}

	b.WriteByte(']')
	return b.String()
}

func update(p *pb.Path, val string) (*pb.Update, error) {
	var v *pb.TypedValue
	switch p.Origin {
	case "":
		v = &pb.TypedValue{
			Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: extractJSON(val)}}
	case "eos_native":
		v = &pb.TypedValue{
			Value: &pb.TypedValue_JsonVal{JsonVal: extractJSON(val)}}
	case "cli", "test-regen-cli":
		v = &pb.TypedValue{
			Value: &pb.TypedValue_AsciiVal{AsciiVal: val}}
	case "p4_config":
		b, err := ioutil.ReadFile(val)
		if err != nil {
			return nil, err
		}
		v = &pb.TypedValue{
			Value: &pb.TypedValue_ProtoBytes{ProtoBytes: b}}
	default:
		return nil, fmt.Errorf("unexpected origin: %q", p.Origin)
	}

	return &pb.Update{Path: p, Val: v}, nil
}

// Operation describes an gNMI operation.
type Operation struct {
	Type   string
	Origin string
	Path   []string
	Val    string
}

func newSetRequest(setOps []*Operation) (*pb.SetRequest, error) {
	req := &pb.SetRequest{}
	for _, op := range setOps {
		p, err := ParseGNMIElements(op.Path)
		if err != nil {
			return nil, err
		}
		p.Origin = op.Origin

		switch op.Type {
		case "delete":
			req.Delete = append(req.Delete, p)
		case "update":
			u, err := update(p, op.Val)
			if err != nil {
				return nil, err
			}
			req.Update = append(req.Update, u)
		case "replace":
			u, err := update(p, op.Val)
			if err != nil {
				return nil, err
			}
			req.Replace = append(req.Replace, u)
		}
	}
	return req, nil
}

// Set sends a SetRequest to the given client.
func Set(ctx context.Context, client pb.GNMIClient, setOps []*Operation) error {
	req, err := newSetRequest(setOps)
	if err != nil {
		return err
	}
	resp, err := client.Set(ctx, req)
	if err != nil {
		return err
	}
	if resp.Message != nil && codes.Code(resp.Message.Code) != codes.OK {
		return errors.New(resp.Message.Message)
	}
	// TODO: Iterate over SetResponse.Response for more detailed error message?

	return nil
}

// Subscribe sends a SubscribeRequest to the given client.
func Subscribe(ctx context.Context, client pb.GNMIClient, subscribeOptions *SubscribeOptions,
	respChan chan<- *pb.SubscribeResponse, errChan chan<- error) {
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	defer close(respChan)

	stream, err := client.Subscribe(ctx)
	if err != nil {
		errChan <- err
		return
	}
	req, err := NewSubscribeRequest(subscribeOptions)
	if err != nil {
		errChan <- err
		return
	}
	if err := stream.Send(req); err != nil {
		errChan <- err
		return
	}

	for {
		resp, err := stream.Recv()
		if err != nil {
			if err == io.EOF {
				return
			}
			errChan <- err
			return
		}
		respChan <- resp

		// For POLL subscriptions, initiate a poll request by pressing ENTER
		if subscribeOptions.Mode == "poll" {
			switch resp.Response.(type) {
			case *pb.SubscribeResponse_SyncResponse:
				fmt.Print("Press ENTER to send a poll request: ")
				reader := bufio.NewReader(os.Stdin)
				reader.ReadString('\n')

				pollReq := &pb.SubscribeRequest{
					Request: &pb.SubscribeRequest_Poll{
						Poll: &pb.Poll{},
					},
				}
				if err := stream.Send(pollReq); err != nil {
					errChan <- err
					return
				}
			}
		}
	}
}

// LogSubscribeResponse logs update responses to stderr.
func LogSubscribeResponse(response *pb.SubscribeResponse) error {
	switch resp := response.Response.(type) {
	case *pb.SubscribeResponse_Error:
		return errors.New(resp.Error.Message)
	case *pb.SubscribeResponse_SyncResponse:
		if !resp.SyncResponse {
			return errors.New("initial sync failed")
		}
	case *pb.SubscribeResponse_Update:
		t := time.Unix(0, resp.Update.Timestamp).UTC()
		prefix := StrPath(resp.Update.Prefix)
		for _, update := range resp.Update.Update {
			fmt.Printf("[%s] %s = %s\n", t.Format(time.RFC3339Nano),
				path.Join(prefix, StrPath(update.Path)),
				StrUpdateVal(update))
		}
		for _, del := range resp.Update.Delete {
			fmt.Printf("[%s] Deleted %s\n", t.Format(time.RFC3339Nano),
				path.Join(prefix, StrPath(del)))
		}
	}
	return nil
}
<|endoftext|>


<commit_before>// The package sklog offers a way to log using glog or Google Cloud Logging in a seemless way.
// By default, the Module level functions (e.g. Infof, Errorln) will all log using glog.  Simply
// call sklog.InitCloudLogging() to immediately start sending log messages to the configured
// Google Cloud Logging endpoint.

package sklog

import (
	"bytes"
	"fmt"
	"net/http"
	"runtime"
	"runtime/debug"
	"strconv"
	"strings"
	"time"

	"github.com/golang/glog"
	"github.com/gorilla/mux"
	"go.skia.org/infra/go/skerr"
)

const (
	// Severities used primarily by Cloud Logging.
	DEBUG    = "DEBUG"
	INFO     = "INFO"
	NOTICE   = "NOTICE"
	WARNING  = "WARNING"
	ERROR    = "ERROR"
	CRITICAL = "CRITICAL"
	ALERT    = "ALERT"

	// Template used to build log links.
	LOG_LINK_TMPL = "https://console.cloud.google.com/logs/viewer?project=%s&minLogLevel=200&expandAll=false&resource=logging_log%%2Fname%%2F%s&logName=projects%%2F%s%%2Flogs%%2F%s"

	// PROJECT_ID is defined here instead of in go/common to prevent an
	// import cycle.
	PROJECT_ID = "google.com:skia-buildbots"

	// b/120145392
	KUBERNETES_FILE_LINE_NUMBER_WORKAROUND = true
)

type MetricsCallback func(severity string)

var (
	// logger is the module-level logger. If this is nil, we will just log using glog.
	logger CloudLogger

	// defaultReportName is the module-level default report name, set in PreInitCloudLogging.
	// See cloud_logging.go for more information.
	defaultReportName string

	// logGroupingName is the module-level log grouping name, set in PreInitCloudLogging.
	logGroupingName string

	// sawLogWithSeverity is used to report metrics about logs seen so we can
	// alert if many ERRORs are seen, for example. This is set up to break a
	// dependency cycle, such that sklog does not depend on metrics2.
	sawLogWithSeverity MetricsCallback = func(s string) {}

	// AllSeverities is the list of all severities that sklog supports.
	AllSeverities = []string{
		DEBUG,
		INFO,
		NOTICE,
		WARNING,
		ERROR,
		CRITICAL,
		ALERT,
	}
)

// SetMetricsCallback sets sawLogWithSeverity.
//
// This is set up to break a dependency cycle, such that sklog does not depend
// on metrics2.
func SetMetricsCallback(metricsCallback MetricsCallback) {
	if metricsCallback != nil {
		sawLogWithSeverity = metricsCallback
	}
}

// These convenience methods will either make a Cloud Logging Entry using the current time and the
// default report name associated with the CloudLogger or log to glog if Cloud Logging is not
// configured.  They are a superset of the glog interface.  Info and Infoln do the same thing
// (as do all pairs), because adding a newline to the end of a Cloud Logging Entry or a glog entry
// means nothing as all logs are separate entries.  InfofWithDepth allow the caller to change
// where the stacktrace starts. 0 (the default in all other calls) means to report starting at
// the caller. 1 would mean one level above, the caller's caller.  2 would be a level above that
// and so on.
func Debug(msg ...interface{}) {
	sawLogWithSeverity(DEBUG)
	log(0, DEBUG, defaultReportName, fmt.Sprint(msg...))
}

func Debugf(format string, v ...interface{}) {
	sawLogWithSeverity(DEBUG)
	log(0, DEBUG, defaultReportName, fmt.Sprintf(format, v...))
}

func DebugfWithDepth(depth int, format string, v ...interface{}) {
	sawLogWithSeverity(DEBUG)
	log(depth, DEBUG, defaultReportName, fmt.Sprintf(format, v...))
}

func Debugln(msg ...interface{}) {
	sawLogWithSeverity(DEBUG)
	log(0, DEBUG, defaultReportName, fmt.Sprintln(msg...))
}
func Info(msg ...interface{}) {
	sawLogWithSeverity(INFO)
	log(0, INFO, defaultReportName, fmt.Sprint(msg...))
}

func Infof(format string, v ...interface{}) {
	sawLogWithSeverity(INFO)
	log(0, INFO, defaultReportName, fmt.Sprintf(format, v...))
}

func InfofWithDepth(depth int, format string, v ...interface{}) {
	sawLogWithSeverity(INFO)
	log(depth, INFO, defaultReportName, fmt.Sprintf(format, v...))
}

func Infoln(msg ...interface{}) {
	sawLogWithSeverity(INFO)
	log(0, INFO, defaultReportName, fmt.Sprintln(msg...))
}

func Warning(msg ...interface{}) {
	sawLogWithSeverity(WARNING)
	log(0, WARNING, defaultReportName, fmt.Sprint(msg...))
}

func Warningf(format string, v ...interface{}) {
	sawLogWithSeverity(WARNING)
	log(0, WARNING, defaultReportName, fmt.Sprintf(format, v...))
}

func WarningfWithDepth(depth int, format string, v ...interface{}) {
	sawLogWithSeverity(WARNING)
	log(depth, WARNING, defaultReportName, fmt.Sprintf(format, v...))
}

func Warningln(msg ...interface{}) {
	sawLogWithSeverity(WARNING)
	log(0, WARNING, defaultReportName, fmt.Sprintln(msg...))
}

func Error(msg ...interface{}) {
	sawLogWithSeverity(ERROR)
	log(0, ERROR, defaultReportName, fmt.Sprint(msg...))
}

func Errorf(format string, v ...interface{}) {
	sawLogWithSeverity(ERROR)
	log(0, ERROR, defaultReportName, fmt.Sprintf(format, v...))
}

func ErrorfWithDepth(depth int, format string, v ...interface{}) {
	sawLogWithSeverity(ERROR)
	log(depth, ERROR, defaultReportName, fmt.Sprintf(format, v...))
}

func Errorln(msg ...interface{}) {
	sawLogWithSeverity(ERROR)
	log(0, ERROR, defaultReportName, fmt.Sprintln(msg...))
}

// Fatal* uses an ALERT Cloud Logging Severity and then panics, similar to glog.Fatalf()
// In Fatal*, there is no callback to sawLogWithSeverity, as the program will soon exit
// and the counter will be reset to 0.
func Fatal(msg ...interface{}) {
	log(0, ALERT, defaultReportName, fmt.Sprint(msg...))
}

func Fatalf(format string, v ...interface{}) {
	log(0, ALERT, defaultReportName, fmt.Sprintf(format, v...))
}

func FatalfWithDepth(depth int, format string, v ...interface{}) {
	log(depth, ALERT, defaultReportName, fmt.Sprintf(format, v...))
}

func Fatalln(msg ...interface{}) {
	log(0, ALERT, defaultReportName, fmt.Sprintln(msg...))
}

func Flush() {
	if logger != nil {
		logger.Flush()
	}
	glog.Flush()
}

// CustomLog allows any clients to write a LogPayload to a report with a
// custom group name (e.g. "log file name"). This is the simplist way for
// an app to send logs to somewhere other than the default report name
// (typically based on the app-name).
func CustomLog(reportName string, payload *LogPayload) {
	if logger != nil {
		logger.CloudLog(reportName, payload)
	} else {
		// must be local or not initialized
		logToGlog(3, payload.Severity, payload.Payload)
	}
}

// SetLogger changes the package to use the given CloudLogger.
func SetLogger(lg CloudLogger) {
	logger = lg
}

// GetLogger retrieves the CloudLogger used by this package, if any.
func GetLogger() CloudLogger {
	return logger
}

// log creates a log entry.  This log entry is either sent to Cloud Logging or glog if the former is
// not configured.  reportName is the "virtual log file" used by cloud logging.  reportName is
// ignored by glog. Both logs include file and line information.
func log(depthOffset int, severity, reportName, payload string) {
	// We want to start at least 3 levels up, which is where the caller called
	// sklog.Infof (or whatever). Otherwise, we'll be including unneeded stack lines.
	stackDepth := 3 + depthOffset
	stacks := skerr.CallStack(5, stackDepth)

	prettyPayload := fmt.Sprintf("%s %v", stacks[0].String(), payload)
	if logger == nil {
		// TODO(kjlubick): After cloud logging has baked in a while, remove the backup logs to glog
		if severity == ALERT {
			// Include the stacktrace.
			payload += "\n\n" + string(debug.Stack())

			// First log directly to glog as an error, in case the write to
			// cloud logging fails to ensure that the message does get
			// logged to disk. ALERT, aka, Fatal* will be logged to glog
			// after the call to CloudLog. If we called logToGlog with
			// alert, it will die before reporting the fatal to CloudLog.
			logToGlog(stackDepth, ERROR, fmt.Sprintf("FATAL: %s", payload))
		} else {
			// In the non-ALERT case, log using glog before CloudLog, in
			// case something goes wrong.
			logToGlog(stackDepth, severity, payload)
		}
	}

	if logger != nil {
		stack := map[string]string{
			"stacktrace_0": stacks[0].String(),
			"stacktrace_1": stacks[1].String(),
			"stacktrace_2": stacks[2].String(),
			"stacktrace_3": stacks[3].String(),
			"stacktrace_4": stacks[4].String(),
		}
		logger.CloudLog(reportName, &LogPayload{
			Time:        time.Now(),
			Severity:    severity,
			Payload:     prettyPayload,
			ExtraLabels: stack,
		})
	}
	if severity == ALERT {
		Flush()
		logToGlog(stackDepth, ALERT, payload)
	}
}

// logToGlog creates a glog entry.  Depth is how far up the call stack to extract file information.
// Severity and msg (message) are self explanatory.
func logToGlog(depth int, severity string, msg string) {
	if KUBERNETES_FILE_LINE_NUMBER_WORKAROUND {
		_, file, line, ok := runtime.Caller(depth)
		if !ok {
			file = "???"
			line = 1
		} else {
			slash := strings.LastIndex(file, "/")
			if slash >= 0 {
				file = file[slash+1:]
			}
		}

		// Following the example of glog, avoiding fmt.Printf for performance reasons
		//https://github.com/golang/glog/blob/master/glog.go#L560
		buf := bytes.Buffer{}
		buf.WriteString(file)
		buf.WriteRune(':')
		buf.WriteString(strconv.Itoa(line))
		buf.WriteRune(' ')
		buf.WriteString(msg)
		msg = buf.String()
	}
	switch severity {
	case DEBUG:
		glog.InfoDepth(depth, msg)
	case INFO:
		glog.InfoDepth(depth, msg)
	case WARNING:
		glog.WarningDepth(depth, msg)
	case ERROR:
		glog.ErrorDepth(depth, msg)
	case ALERT:
		glog.FatalDepth(depth, msg)
	default:
		glog.ErrorDepth(depth, msg)
	}
}

// LogLink returns a link to the logs for this process.
func LogLink() string {
	return fmt.Sprintf(LOG_LINK_TMPL, PROJECT_ID, logGroupingName, PROJECT_ID, defaultReportName)
}

// AddLogsRedirect adds an endpoint which redirects to the GCloud log page for
// this process at /logs.
func AddLogsRedirect(r *mux.Router) {
	r.HandleFunc("/logs", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, LogLink(), http.StatusMovedPermanently)
	})
}

// FmtError is a wrapper around fmt.Errorf that prepends the source location
// (filename and line number) of the caller.
func FmtErrorf(fmtStr string, args ...interface{}) error {
	stackEntry := skerr.CallStack(1, 2)[0]
	codeRef := fmt.Sprintf("%s:%d:", stackEntry.File, stackEntry.Line)
	return fmt.Errorf(codeRef+fmtStr, args...)
}
<commit_msg>Replace newline with ⏎ in k8s logs<commit_after>// The package sklog offers a way to log using glog or Google Cloud Logging in a seemless way.
// By default, the Module level functions (e.g. Infof, Errorln) will all log using glog.  Simply
// call sklog.InitCloudLogging() to immediately start sending log messages to the configured
// Google Cloud Logging endpoint.

package sklog

import (
	"bytes"
	"fmt"
	"net/http"
	"runtime"
	"runtime/debug"
	"strconv"
	"strings"
	"time"

	"github.com/golang/glog"
	"github.com/gorilla/mux"
	"go.skia.org/infra/go/skerr"
)

const (
	// Severities used primarily by Cloud Logging.
	DEBUG    = "DEBUG"
	INFO     = "INFO"
	NOTICE   = "NOTICE"
	WARNING  = "WARNING"
	ERROR    = "ERROR"
	CRITICAL = "CRITICAL"
	ALERT    = "ALERT"

	// Template used to build log links.
	LOG_LINK_TMPL = "https://console.cloud.google.com/logs/viewer?project=%s&minLogLevel=200&expandAll=false&resource=logging_log%%2Fname%%2F%s&logName=projects%%2F%s%%2Flogs%%2F%s"

	// PROJECT_ID is defined here instead of in go/common to prevent an
	// import cycle.
	PROJECT_ID = "google.com:skia-buildbots"

	// b/120145392
	KUBERNETES_FILE_LINE_NUMBER_WORKAROUND = true
)

type MetricsCallback func(severity string)

var (
	// logger is the module-level logger. If this is nil, we will just log using glog.
	logger CloudLogger

	// defaultReportName is the module-level default report name, set in PreInitCloudLogging.
	// See cloud_logging.go for more information.
	defaultReportName string

	// logGroupingName is the module-level log grouping name, set in PreInitCloudLogging.
	logGroupingName string

	// sawLogWithSeverity is used to report metrics about logs seen so we can
	// alert if many ERRORs are seen, for example. This is set up to break a
	// dependency cycle, such that sklog does not depend on metrics2.
	sawLogWithSeverity MetricsCallback = func(s string) {}

	// AllSeverities is the list of all severities that sklog supports.
	AllSeverities = []string{
		DEBUG,
		INFO,
		NOTICE,
		WARNING,
		ERROR,
		CRITICAL,
		ALERT,
	}
)

// SetMetricsCallback sets sawLogWithSeverity.
//
// This is set up to break a dependency cycle, such that sklog does not depend
// on metrics2.
func SetMetricsCallback(metricsCallback MetricsCallback) {
	if metricsCallback != nil {
		sawLogWithSeverity = metricsCallback
	}
}

// These convenience methods will either make a Cloud Logging Entry using the current time and the
// default report name associated with the CloudLogger or log to glog if Cloud Logging is not
// configured.  They are a superset of the glog interface.  Info and Infoln do the same thing
// (as do all pairs), because adding a newline to the end of a Cloud Logging Entry or a glog entry
// means nothing as all logs are separate entries.  InfofWithDepth allow the caller to change
// where the stacktrace starts. 0 (the default in all other calls) means to report starting at
// the caller. 1 would mean one level above, the caller's caller.  2 would be a level above that
// and so on.
func Debug(msg ...interface{}) {
	sawLogWithSeverity(DEBUG)
	log(0, DEBUG, defaultReportName, fmt.Sprint(msg...))
}

func Debugf(format string, v ...interface{}) {
	sawLogWithSeverity(DEBUG)
	log(0, DEBUG, defaultReportName, fmt.Sprintf(format, v...))
}

func DebugfWithDepth(depth int, format string, v ...interface{}) {
	sawLogWithSeverity(DEBUG)
	log(depth, DEBUG, defaultReportName, fmt.Sprintf(format, v...))
}

func Debugln(msg ...interface{}) {
	sawLogWithSeverity(DEBUG)
	log(0, DEBUG, defaultReportName, fmt.Sprintln(msg...))
}
func Info(msg ...interface{}) {
	sawLogWithSeverity(INFO)
	log(0, INFO, defaultReportName, fmt.Sprint(msg...))
}

func Infof(format string, v ...interface{}) {
	sawLogWithSeverity(INFO)
	log(0, INFO, defaultReportName, fmt.Sprintf(format, v...))
}

func InfofWithDepth(depth int, format string, v ...interface{}) {
	sawLogWithSeverity(INFO)
	log(depth, INFO, defaultReportName, fmt.Sprintf(format, v...))
}

func Infoln(msg ...interface{}) {
	sawLogWithSeverity(INFO)
	log(0, INFO, defaultReportName, fmt.Sprintln(msg...))
}

func Warning(msg ...interface{}) {
	sawLogWithSeverity(WARNING)
	log(0, WARNING, defaultReportName, fmt.Sprint(msg...))
}

func Warningf(format string, v ...interface{}) {
	sawLogWithSeverity(WARNING)
	log(0, WARNING, defaultReportName, fmt.Sprintf(format, v...))
}

func WarningfWithDepth(depth int, format string, v ...interface{}) {
	sawLogWithSeverity(WARNING)
	log(depth, WARNING, defaultReportName, fmt.Sprintf(format, v...))
}

func Warningln(msg ...interface{}) {
	sawLogWithSeverity(WARNING)
	log(0, WARNING, defaultReportName, fmt.Sprintln(msg...))
}

func Error(msg ...interface{}) {
	sawLogWithSeverity(ERROR)
	log(0, ERROR, defaultReportName, fmt.Sprint(msg...))
}

func Errorf(format string, v ...interface{}) {
	sawLogWithSeverity(ERROR)
	log(0, ERROR, defaultReportName, fmt.Sprintf(format, v...))
}

func ErrorfWithDepth(depth int, format string, v ...interface{}) {
	sawLogWithSeverity(ERROR)
	log(depth, ERROR, defaultReportName, fmt.Sprintf(format, v...))
}

func Errorln(msg ...interface{}) {
	sawLogWithSeverity(ERROR)
	log(0, ERROR, defaultReportName, fmt.Sprintln(msg...))
}

// Fatal* uses an ALERT Cloud Logging Severity and then panics, similar to glog.Fatalf()
// In Fatal*, there is no callback to sawLogWithSeverity, as the program will soon exit
// and the counter will be reset to 0.
func Fatal(msg ...interface{}) {
	log(0, ALERT, defaultReportName, fmt.Sprint(msg...))
}

func Fatalf(format string, v ...interface{}) {
	log(0, ALERT, defaultReportName, fmt.Sprintf(format, v...))
}

func FatalfWithDepth(depth int, format string, v ...interface{}) {
	log(depth, ALERT, defaultReportName, fmt.Sprintf(format, v...))
}

func Fatalln(msg ...interface{}) {
	log(0, ALERT, defaultReportName, fmt.Sprintln(msg...))
}

func Flush() {
	if logger != nil {
		logger.Flush()
	}
	glog.Flush()
}

// CustomLog allows any clients to write a LogPayload to a report with a
// custom group name (e.g. "log file name"). This is the simplist way for
// an app to send logs to somewhere other than the default report name
// (typically based on the app-name).
func CustomLog(reportName string, payload *LogPayload) {
	if logger != nil {
		logger.CloudLog(reportName, payload)
	} else {
		// must be local or not initialized
		logToGlog(3, payload.Severity, payload.Payload)
	}
}

// SetLogger changes the package to use the given CloudLogger.
func SetLogger(lg CloudLogger) {
	logger = lg
}

// GetLogger retrieves the CloudLogger used by this package, if any.
func GetLogger() CloudLogger {
	return logger
}

// log creates a log entry.  This log entry is either sent to Cloud Logging or glog if the former is
// not configured.  reportName is the "virtual log file" used by cloud logging.  reportName is
// ignored by glog. Both logs include file and line information.
func log(depthOffset int, severity, reportName, payload string) {
	// We want to start at least 3 levels up, which is where the caller called
	// sklog.Infof (or whatever). Otherwise, we'll be including unneeded stack lines.
	stackDepth := 3 + depthOffset
	stacks := skerr.CallStack(5, stackDepth)

	prettyPayload := fmt.Sprintf("%s %v", stacks[0].String(), payload)
	if logger == nil {
		// TODO(kjlubick): After cloud logging has baked in a while, remove the backup logs to glog
		if severity == ALERT {
			// Include the stacktrace.
			payload += "\n\n" + string(debug.Stack())

			// First log directly to glog as an error, in case the write to
			// cloud logging fails to ensure that the message does get
			// logged to disk. ALERT, aka, Fatal* will be logged to glog
			// after the call to CloudLog. If we called logToGlog with
			// alert, it will die before reporting the fatal to CloudLog.
			logToGlog(stackDepth, ERROR, fmt.Sprintf("FATAL: %s", payload))
		} else {
			// In the non-ALERT case, log using glog before CloudLog, in
			// case something goes wrong.
			logToGlog(stackDepth, severity, payload)
		}
	}

	if logger != nil {
		stack := map[string]string{
			"stacktrace_0": stacks[0].String(),
			"stacktrace_1": stacks[1].String(),
			"stacktrace_2": stacks[2].String(),
			"stacktrace_3": stacks[3].String(),
			"stacktrace_4": stacks[4].String(),
		}
		logger.CloudLog(reportName, &LogPayload{
			Time:        time.Now(),
			Severity:    severity,
			Payload:     prettyPayload,
			ExtraLabels: stack,
		})
	}
	if severity == ALERT {
		Flush()
		logToGlog(stackDepth, ALERT, payload)
	}
}

// logToGlog creates a glog entry.  Depth is how far up the call stack to extract file information.
// Severity and msg (message) are self explanatory.
func logToGlog(depth int, severity string, msg string) {
	if KUBERNETES_FILE_LINE_NUMBER_WORKAROUND {
		_, file, line, ok := runtime.Caller(depth)
		if !ok {
			file = "???"
			line = 1
		} else {
			slash := strings.LastIndex(file, "/")
			if slash >= 0 {
				file = file[slash+1:]
			}
		}

		// Following the example of glog, avoiding fmt.Printf for performance reasons
		//https://github.com/golang/glog/blob/master/glog.go#L560
		buf := bytes.Buffer{}
		buf.WriteString(file)
		buf.WriteRune(':')
		buf.WriteString(strconv.Itoa(line))
		buf.WriteRune(' ')
		for _, c := range msg {
			if c == '\n' {
				buf.WriteRune('⏎')
			} else {
				buf.WriteRune(c)
			}
		}
		msg = buf.String()
	}
	switch severity {
	case DEBUG:
		glog.InfoDepth(depth, msg)
	case INFO:
		glog.InfoDepth(depth, msg)
	case WARNING:
		glog.WarningDepth(depth, msg)
	case ERROR:
		glog.ErrorDepth(depth, msg)
	case ALERT:
		glog.FatalDepth(depth, msg)
	default:
		glog.ErrorDepth(depth, msg)
	}
}

// LogLink returns a link to the logs for this process.
func LogLink() string {
	return fmt.Sprintf(LOG_LINK_TMPL, PROJECT_ID, logGroupingName, PROJECT_ID, defaultReportName)
}

// AddLogsRedirect adds an endpoint which redirects to the GCloud log page for
// this process at /logs.
func AddLogsRedirect(r *mux.Router) {
	r.HandleFunc("/logs", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, LogLink(), http.StatusMovedPermanently)
	})
}

// FmtError is a wrapper around fmt.Errorf that prepends the source location
// (filename and line number) of the caller.
func FmtErrorf(fmtStr string, args ...interface{}) error {
	stackEntry := skerr.CallStack(1, 2)[0]
	codeRef := fmt.Sprintf("%s:%d:", stackEntry.File, stackEntry.Line)
	return fmt.Errorf(codeRef+fmtStr, args...)
}
<|endoftext|>


<commit_before>package main

import (
	"net/http"
	"os"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/chrislusf/weed-fs/go/glog"
	"github.com/chrislusf/weed-fs/go/util"
	"github.com/chrislusf/weed-fs/go/weed/weed_server"
)

var (
	v VolumeServerOptions
)

type VolumeServerOptions struct {
	port                  *int
	adminPort             *int
	folders               []string
	folderMaxLimits       []int
	ip                    *string
	publicUrl             *string
	bindIp                *string
	master                *string
	pulseSeconds          *int
	idleConnectionTimeout *int
	maxCpu                *int
	dataCenter            *string
	rack                  *string
	whiteList             []string
	fixJpgOrientation     *bool
}

func init() {
	cmdVolume.Run = runVolume // break init cycle
	v.port = cmdVolume.Flag.Int("port", 8080, "http listen port")
	v.adminPort = cmdVolume.Flag.Int("port.admin", 0, "admin port to talk with master and other volume servers")
	v.ip = cmdVolume.Flag.String("ip", "", "ip or server name")
	v.publicUrl = cmdVolume.Flag.String("publicUrl", "", "Publicly accessible address")
	v.bindIp = cmdVolume.Flag.String("ip.bind", "0.0.0.0", "ip address to bind to")
	v.master = cmdVolume.Flag.String("mserver", "localhost:9333", "master server location")
	v.pulseSeconds = cmdVolume.Flag.Int("pulseSeconds", 5, "number of seconds between heartbeats, must be smaller than or equal to the master's setting")
	v.idleConnectionTimeout = cmdVolume.Flag.Int("idleTimeout", 10, "connection idle seconds")
	v.maxCpu = cmdVolume.Flag.Int("maxCpu", 0, "maximum number of CPUs. 0 means all available CPUs")
	v.dataCenter = cmdVolume.Flag.String("dataCenter", "", "current volume server's data center name")
	v.rack = cmdVolume.Flag.String("rack", "", "current volume server's rack name")
	v.fixJpgOrientation = cmdVolume.Flag.Bool("images.fix.orientation", true, "Adjust jpg orientation when uploading.")
}

var cmdVolume = &Command{
	UsageLine: "volume -port=8080 -dir=/tmp -max=5 -ip=server_name -mserver=localhost:9333",
	Short:     "start a volume server",
	Long: `start a volume server to provide storage spaces

  `,
}

var (
	volumeFolders         = cmdVolume.Flag.String("dir", os.TempDir(), "directories to store data files. dir[,dir]...")
	maxVolumeCounts       = cmdVolume.Flag.String("max", "7", "maximum numbers of volumes, count[,count]...")
	volumeWhiteListOption = cmdVolume.Flag.String("whiteList", "", "comma separated Ip addresses having write permission. No limit if empty.")
)

func runVolume(cmd *Command, args []string) bool {
	if *v.maxCpu < 1 {
		*v.maxCpu = runtime.NumCPU()
	}
	runtime.GOMAXPROCS(*v.maxCpu)

	//Set multiple folders and each folder's max volume count limit'
	v.folders = strings.Split(*volumeFolders, ",")
	maxCountStrings := strings.Split(*maxVolumeCounts, ",")
	for _, maxString := range maxCountStrings {
		if max, e := strconv.Atoi(maxString); e == nil {
			v.folderMaxLimits = append(v.folderMaxLimits, max)
		} else {
			glog.Fatalf("The max specified in -max not a valid number %s", maxString)
		}
	}
	if len(v.folders) != len(v.folderMaxLimits) {
		glog.Fatalf("%d directories by -dir, but only %d max is set by -max", len(v.folders), len(v.folderMaxLimits))
	}
	for _, folder := range v.folders {
		if err := util.TestFolderWritable(folder); err != nil {
			glog.Fatalf("Check Data Folder(-dir) Writable %s : %s", folder, err)
		}
	}

	//security related white list configuration
	if *volumeWhiteListOption != "" {
		v.whiteList = strings.Split(*volumeWhiteListOption, ",")
	}

	if *v.ip == "" {
		*v.ip = "127.0.0.1"
	}

	if *v.adminPort == 0 {
		*v.adminPort = *v.port
	}
	isSeperatedAdminPort := *v.adminPort != *v.port

	publicMux := http.NewServeMux()
	adminMux := publicMux
	if isSeperatedAdminPort {
		adminMux = http.NewServeMux()
	}

	volumeServer := weed_server.NewVolumeServer(publicMux, adminMux,
		*v.ip, *v.port, *v.adminPort, *v.publicUrl,
		v.folders, v.folderMaxLimits,
		*v.master, *v.pulseSeconds, *v.dataCenter, *v.rack,
		v.whiteList,
		*v.fixJpgOrientation,
	)

	listeningAddress := *v.bindIp + ":" + strconv.Itoa(*v.port)
	glog.V(0).Infoln("Start Seaweed volume server", util.VERSION, "at", listeningAddress)
	listener, e := util.NewListener(listeningAddress, time.Duration(*v.idleConnectionTimeout)*time.Second)
	if e != nil {
		glog.Fatalf("Volume server listener error:%v", e)
	}
	if isSeperatedAdminPort {
		adminListeningAddress := *v.bindIp + ":" + strconv.Itoa(*v.adminPort)
		glog.V(0).Infoln("Start Seaweed volume server", util.VERSION, "admin at", adminListeningAddress)
		adminListener, e := util.NewListener(adminListeningAddress, time.Duration(*v.idleConnectionTimeout)*time.Second)
		if e != nil {
			glog.Fatalf("Volume server listener error:%v", e)
		}
		go func() {
			if e := http.Serve(adminListener, adminMux); e != nil {
				glog.Fatalf("Volume server fail to serve admin: %v", e)
			}
		}()
	}

	OnInterrupt(func() {
		volumeServer.Shutdown()
	})

	if e := http.Serve(listener, publicMux); e != nil {
		glog.Fatalf("Volume server fail to serve: %v", e)
	}
	return true
}
<commit_msg>ensure non empty volume publicUrl<commit_after>package main

import (
	"net/http"
	"os"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/chrislusf/weed-fs/go/glog"
	"github.com/chrislusf/weed-fs/go/util"
	"github.com/chrislusf/weed-fs/go/weed/weed_server"
)

var (
	v VolumeServerOptions
)

type VolumeServerOptions struct {
	port                  *int
	adminPort             *int
	folders               []string
	folderMaxLimits       []int
	ip                    *string
	publicUrl             *string
	bindIp                *string
	master                *string
	pulseSeconds          *int
	idleConnectionTimeout *int
	maxCpu                *int
	dataCenter            *string
	rack                  *string
	whiteList             []string
	fixJpgOrientation     *bool
}

func init() {
	cmdVolume.Run = runVolume // break init cycle
	v.port = cmdVolume.Flag.Int("port", 8080, "http listen port")
	v.adminPort = cmdVolume.Flag.Int("port.admin", 0, "admin port to talk with master and other volume servers")
	v.ip = cmdVolume.Flag.String("ip", "", "ip or server name")
	v.publicUrl = cmdVolume.Flag.String("publicUrl", "", "Publicly accessible address")
	v.bindIp = cmdVolume.Flag.String("ip.bind", "0.0.0.0", "ip address to bind to")
	v.master = cmdVolume.Flag.String("mserver", "localhost:9333", "master server location")
	v.pulseSeconds = cmdVolume.Flag.Int("pulseSeconds", 5, "number of seconds between heartbeats, must be smaller than or equal to the master's setting")
	v.idleConnectionTimeout = cmdVolume.Flag.Int("idleTimeout", 10, "connection idle seconds")
	v.maxCpu = cmdVolume.Flag.Int("maxCpu", 0, "maximum number of CPUs. 0 means all available CPUs")
	v.dataCenter = cmdVolume.Flag.String("dataCenter", "", "current volume server's data center name")
	v.rack = cmdVolume.Flag.String("rack", "", "current volume server's rack name")
	v.fixJpgOrientation = cmdVolume.Flag.Bool("images.fix.orientation", true, "Adjust jpg orientation when uploading.")
}

var cmdVolume = &Command{
	UsageLine: "volume -port=8080 -dir=/tmp -max=5 -ip=server_name -mserver=localhost:9333",
	Short:     "start a volume server",
	Long: `start a volume server to provide storage spaces

  `,
}

var (
	volumeFolders         = cmdVolume.Flag.String("dir", os.TempDir(), "directories to store data files. dir[,dir]...")
	maxVolumeCounts       = cmdVolume.Flag.String("max", "7", "maximum numbers of volumes, count[,count]...")
	volumeWhiteListOption = cmdVolume.Flag.String("whiteList", "", "comma separated Ip addresses having write permission. No limit if empty.")
)

func runVolume(cmd *Command, args []string) bool {
	if *v.maxCpu < 1 {
		*v.maxCpu = runtime.NumCPU()
	}
	runtime.GOMAXPROCS(*v.maxCpu)

	//Set multiple folders and each folder's max volume count limit'
	v.folders = strings.Split(*volumeFolders, ",")
	maxCountStrings := strings.Split(*maxVolumeCounts, ",")
	for _, maxString := range maxCountStrings {
		if max, e := strconv.Atoi(maxString); e == nil {
			v.folderMaxLimits = append(v.folderMaxLimits, max)
		} else {
			glog.Fatalf("The max specified in -max not a valid number %s", maxString)
		}
	}
	if len(v.folders) != len(v.folderMaxLimits) {
		glog.Fatalf("%d directories by -dir, but only %d max is set by -max", len(v.folders), len(v.folderMaxLimits))
	}
	for _, folder := range v.folders {
		if err := util.TestFolderWritable(folder); err != nil {
			glog.Fatalf("Check Data Folder(-dir) Writable %s : %s", folder, err)
		}
	}

	//security related white list configuration
	if *volumeWhiteListOption != "" {
		v.whiteList = strings.Split(*volumeWhiteListOption, ",")
	}

	if *v.ip == "" {
		*v.ip = "127.0.0.1"
	}
	if *v.publicUrl == "" {
		*v.publicUrl = *v.ip + ":" + *v.port
	}

	if *v.adminPort == 0 {
		*v.adminPort = *v.port
	}
	isSeperatedAdminPort := *v.adminPort != *v.port

	publicMux := http.NewServeMux()
	adminMux := publicMux
	if isSeperatedAdminPort {
		adminMux = http.NewServeMux()
	}

	volumeServer := weed_server.NewVolumeServer(publicMux, adminMux,
		*v.ip, *v.port, *v.adminPort, *v.publicUrl,
		v.folders, v.folderMaxLimits,
		*v.master, *v.pulseSeconds, *v.dataCenter, *v.rack,
		v.whiteList,
		*v.fixJpgOrientation,
	)

	listeningAddress := *v.bindIp + ":" + strconv.Itoa(*v.port)
	glog.V(0).Infoln("Start Seaweed volume server", util.VERSION, "at", listeningAddress)
	listener, e := util.NewListener(listeningAddress, time.Duration(*v.idleConnectionTimeout)*time.Second)
	if e != nil {
		glog.Fatalf("Volume server listener error:%v", e)
	}
	if isSeperatedAdminPort {
		adminListeningAddress := *v.bindIp + ":" + strconv.Itoa(*v.adminPort)
		glog.V(0).Infoln("Start Seaweed volume server", util.VERSION, "admin at", adminListeningAddress)
		adminListener, e := util.NewListener(adminListeningAddress, time.Duration(*v.idleConnectionTimeout)*time.Second)
		if e != nil {
			glog.Fatalf("Volume server listener error:%v", e)
		}
		go func() {
			if e := http.Serve(adminListener, adminMux); e != nil {
				glog.Fatalf("Volume server fail to serve admin: %v", e)
			}
		}()
	}

	OnInterrupt(func() {
		volumeServer.Shutdown()
	})

	if e := http.Serve(listener, publicMux); e != nil {
		glog.Fatalf("Volume server fail to serve: %v", e)
	}
	return true
}
<|endoftext|>