<commit_before>package cluster

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"io"
	"strconv"
	"time"

	"github.com/pkg/errors"
	"golang.org/x/crypto/scrypt"

	"github.com/lxc/lxd/lxd/config"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/shared/validate"
)

// Config holds cluster-wide configuration values.
type Config struct {
	tx *db.ClusterTx // DB transaction the values in this config are bound to.
	m  config.Map    // Low-level map holding the config values.
}

// ConfigLoad loads a new Config object with the current cluster configuration
// values fetched from the database.
func ConfigLoad(tx *db.ClusterTx) (*Config, error) {
	// Load current raw values from the database, any error is fatal.
	values, err := tx.Config()
	if err != nil {
		return nil, fmt.Errorf("cannot fetch node config from database: %v", err)
	}

	m, err := config.SafeLoad(ConfigSchema, values)
	if err != nil {
		return nil, fmt.Errorf("failed to load node config: %v", err)
	}

	return &Config{tx: tx, m: m}, nil
}

// HTTPSAllowedHeaders returns the relevant CORS setting.
func (c *Config) HTTPSAllowedHeaders() string {
	return c.m.GetString("core.https_allowed_headers")
}

// HTTPSAllowedMethods returns the relevant CORS setting.
func (c *Config) HTTPSAllowedMethods() string {
	return c.m.GetString("core.https_allowed_methods")
}

// HTTPSAllowedOrigin returns the relevant CORS setting.
func (c *Config) HTTPSAllowedOrigin() string {
	return c.m.GetString("core.https_allowed_origin")
}

// HTTPSAllowedCredentials returns the relevant CORS setting.
func (c *Config) HTTPSAllowedCredentials() bool {
	return c.m.GetBool("core.https_allowed_credentials")
}

// TrustPassword returns the LXD trust password for authenticating clients.
func (c *Config) TrustPassword() string {
	return c.m.GetString("core.trust_password")
}

// TrustCACertificates returns whether client certificates are checked
// against a CA.
func (c *Config) TrustCACertificates() bool {
	return c.m.GetBool("core.trust_ca_certificates")
}

// CandidServer returns all the Candid settings needed to connect to a server.
func (c *Config) CandidServer() (string, string, int64, string) {
	return c.m.GetString("candid.api.url"),
		c.m.GetString("candid.api.key"),
		c.m.GetInt64("candid.expiry"),
		c.m.GetString("candid.domains")
}

// RBACServer returns all the Candid settings needed to connect to a server.
func (c *Config) RBACServer() (string, string, int64, string, string, string, string) {
	return c.m.GetString("rbac.api.url"),
		c.m.GetString("rbac.api.key"),
		c.m.GetInt64("rbac.expiry"),
		c.m.GetString("rbac.agent.url"),
		c.m.GetString("rbac.agent.username"),
		c.m.GetString("rbac.agent.private_key"),
		c.m.GetString("rbac.agent.public_key")
}

// ProxyHTTPS returns the configured HTTPS proxy, if any.
func (c *Config) ProxyHTTPS() string {
	return c.m.GetString("core.proxy_https")
}

// ProxyHTTP returns the configured HTTP proxy, if any.
func (c *Config) ProxyHTTP() string {
	return c.m.GetString("core.proxy_http")
}

// ProxyIgnoreHosts returns the configured ignore-hosts proxy setting, if any.
func (c *Config) ProxyIgnoreHosts() string {
	return c.m.GetString("core.proxy_ignore_hosts")
}

// HTTPSTrustedProxy returns the configured HTTPS trusted proxy setting, if any.
func (c *Config) HTTPSTrustedProxy() string {
	return c.m.GetString("core.https_trusted_proxy")
}

// MAASController the configured MAAS url and key, if any.
func (c *Config) MAASController() (string, string) {
	url := c.m.GetString("maas.api.url")
	key := c.m.GetString("maas.api.key")
	return url, key
}

// OfflineThreshold returns the configured heartbeat threshold, i.e. the
// number of seconds before after which an unresponsive node is considered
// offline..
func (c *Config) OfflineThreshold() time.Duration {
	n := c.m.GetInt64("cluster.offline_threshold")
	return time.Duration(n) * time.Second
}

// ImagesMinimalReplica returns the numbers of nodes for cluster images replication
func (c *Config) ImagesMinimalReplica() int64 {
	return c.m.GetInt64("cluster.images_minimal_replica")
}

// MaxVoters returns the maximum number of members in a cluster that will be
// assigned the voter role.
func (c *Config) MaxVoters() int64 {
	return c.m.GetInt64("cluster.max_voters")
}

// MaxStandBy returns the maximum number of standby members in a cluster that
// will be assigned the stand-by role.
func (c *Config) MaxStandBy() int64 {
	return c.m.GetInt64("cluster.max_standby")
}

// ShutdownTimeout returns the number of minutes to wait for running operation to complete
// before LXD server shut down
func (c *Config) ShutdownTimeout() time.Duration {
	n := c.m.GetInt64("core.shutdown_timeout")
	return time.Duration(n) * time.Minute
}

// Dump current configuration keys and their values. Keys with values matching
// their defaults are omitted.
func (c *Config) Dump() map[string]interface{} {
	return c.m.Dump()
}

// Replace the current configuration with the given values.
//
// Return what has actually changed.
func (c *Config) Replace(values map[string]interface{}) (map[string]string, error) {
	return c.update(values)
}

// Patch changes only the configuration keys in the given map.
//
// Return what has actually changed.
func (c *Config) Patch(patch map[string]interface{}) (map[string]string, error) {
	values := c.Dump() // Use current values as defaults
	for name, value := range patch {
		values[name] = value
	}
	return c.update(values)
}

func (c *Config) update(values map[string]interface{}) (map[string]string, error) {
	changed, err := c.m.Change(values)
	if err != nil {
		return nil, err
	}

	err = c.tx.UpdateConfig(changed)
	if err != nil {
		return nil, errors.Wrap(err, "cannot persist configuration changes: %v")
	}

	return changed, nil
}

// ConfigGetString is a convenience for loading the cluster configuration and
// returning the value of a particular key.
//
// It's a deprecated API meant to be used by call sites that are not
// interacting with the database in a transactional way.
func ConfigGetString(cluster *db.Cluster, key string) (string, error) {
	config, err := configGet(cluster)
	if err != nil {
		return "", err
	}
	return config.m.GetString(key), nil
}

// ConfigGetBool is a convenience for loading the cluster configuration and
// returning the value of a particular boolean key.
//
// It's a deprecated API meant to be used by call sites that are not
// interacting with the database in a transactional way.
func ConfigGetBool(cluster *db.Cluster, key string) (bool, error) {
	config, err := configGet(cluster)
	if err != nil {
		return false, err
	}
	return config.m.GetBool(key), nil
}

// ConfigGetInt64 is a convenience for loading the cluster configuration and
// returning the value of a particular key.
//
// It's a deprecated API meant to be used by call sites that are not
// interacting with the database in a transactional way.
func ConfigGetInt64(cluster *db.Cluster, key string) (int64, error) {
	config, err := configGet(cluster)
	if err != nil {
		return 0, err
	}
	return config.m.GetInt64(key), nil
}

func configGet(cluster *db.Cluster) (*Config, error) {
	var config *Config
	err := cluster.Transaction(func(tx *db.ClusterTx) error {
		var err error
		config, err = ConfigLoad(tx)
		return err
	})
	return config, err
}

// ConfigSchema defines available server configuration keys.
var ConfigSchema = config.Schema{
	"backups.compression_algorithm":  {Default: "gzip", Validator: validate.IsCompressionAlgorithm},
	"cluster.offline_threshold":      {Type: config.Int64, Default: offlineThresholdDefault(), Validator: offlineThresholdValidator},
	"cluster.images_minimal_replica": {Type: config.Int64, Default: "3", Validator: imageMinimalReplicaValidator},
	"cluster.max_voters":             {Type: config.Int64, Default: "3", Validator: maxVotersValidator},
	"cluster.max_standby":            {Type: config.Int64, Default: "2", Validator: maxStandByValidator},
	"core.https_allowed_headers":     {},
	"core.https_allowed_methods":     {},
	"core.https_allowed_origin":      {},
	"core.https_allowed_credentials": {Type: config.Bool},
	"core.https_trusted_proxy":       {},
	"core.proxy_http":                {},
	"core.proxy_https":               {},
	"core.proxy_ignore_hosts":        {},
	"core.shutdown_timeout":          {Type: config.Int64, Default: "5"},
	"core.trust_password":            {Hidden: true, Setter: passwordSetter},
	"core.trust_ca_certificates":     {Type: config.Bool},
	"candid.api.key":                 {},
	"candid.api.url":                 {},
	"candid.domains":                 {},
	"candid.expiry":                  {Type: config.Int64, Default: "3600"},
	"images.auto_update_cached":      {Type: config.Bool, Default: "true"},
	"images.auto_update_interval":    {Type: config.Int64, Default: "6"},
	"images.compression_algorithm":   {Default: "gzip", Validator: validate.IsCompressionAlgorithm},
	"images.default_architecture":    {Validator: validate.Optional(validate.IsArchitecture)},
	"images.remote_cache_expiry":     {Type: config.Int64, Default: "10"},
	"maas.api.key":                   {},
	"maas.api.url":                   {},
	"rbac.agent.url":                 {},
	"rbac.agent.username":            {},
	"rbac.agent.private_key":         {},
	"rbac.agent.public_key":          {},
	"rbac.api.expiry":                {Type: config.Int64, Default: "3600"},
	"rbac.api.key":                   {},
	"rbac.api.url":                   {},
	"rbac.expiry":                    {Type: config.Int64, Default: "3600"},

	// Keys deprecated since the implementation of the storage api.
	"storage.lvm_fstype":           {Setter: deprecatedStorage, Default: "ext4"},
	"storage.lvm_mount_options":    {Setter: deprecatedStorage, Default: "discard"},
	"storage.lvm_thinpool_name":    {Setter: deprecatedStorage, Default: "LXDThinPool"},
	"storage.lvm_vg_name":          {Setter: deprecatedStorage},
	"storage.lvm_volume_size":      {Setter: deprecatedStorage, Default: "10GiB"},
	"storage.zfs_pool_name":        {Setter: deprecatedStorage},
	"storage.zfs_remove_snapshots": {Setter: deprecatedStorage, Type: config.Bool},
	"storage.zfs_use_refquota":     {Setter: deprecatedStorage, Type: config.Bool},

	// OVN networking global keys.
	"network.ovn.integration_bridge":    {Default: "br-int"},
	"network.ovn.northbound_connection": {Default: "unix:/var/run/ovn/ovnnb_db.sock"},
}

func offlineThresholdDefault() string {
	return strconv.Itoa(db.DefaultOfflineThreshold)
}

func offlineThresholdValidator(value string) error {
	minThreshold := 10

	// Ensure that the given value is greater than the heartbeat interval,
	// which is the lower bound granularity of the offline check.
	threshold, err := strconv.Atoi(value)
	if err != nil {
		return fmt.Errorf("Offline threshold is not a number")
	}

	if threshold <= minThreshold {
		return fmt.Errorf("Value must be greater than '%d'", minThreshold)
	}

	return nil
}

func imageMinimalReplicaValidator(value string) error {
	count, err := strconv.Atoi(value)
	if err != nil {
		return fmt.Errorf("Minimal image replica count is not a number")
	}

	if count < 1 && count != -1 {
		return fmt.Errorf("Invalid value for image replica count")
	}

	return nil
}

func maxVotersValidator(value string) error {
	n, err := strconv.Atoi(value)
	if err != nil {
		return fmt.Errorf("Value is not a number")
	}

	if n < 3 || n%2 != 1 {
		return fmt.Errorf("Value must be an odd number equal to or higher than 3")
	}

	return nil
}

func maxStandByValidator(value string) error {
	n, err := strconv.Atoi(value)
	if err != nil {
		return fmt.Errorf("Value is not a number")
	}

	if n < 0 || n > 5 {
		return fmt.Errorf("Value must be between 0 and 5")
	}

	return nil
}

func passwordSetter(value string) (string, error) {
	// Nothing to do on unset
	if value == "" {
		return value, nil
	}

	// Hash the password
	buf := make([]byte, 32)
	_, err := io.ReadFull(rand.Reader, buf)
	if err != nil {
		return "", err
	}

	hash, err := scrypt.Key([]byte(value), buf, 1<<14, 8, 1, 64)
	if err != nil {
		return "", err
	}

	buf = append(buf, hash...)
	value = hex.EncodeToString(buf)

	return value, nil
}

func deprecatedStorage(value string) (string, error) {
	if value == "" {
		return "", nil
	}
	return "", fmt.Errorf("deprecated: use storage pool configuration")
}
<commit_msg>lxd/cluster: Add core.bgp_asn<commit_after>package cluster

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"io"
	"strconv"
	"time"

	"github.com/pkg/errors"
	"golang.org/x/crypto/scrypt"

	"github.com/lxc/lxd/lxd/config"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/shared/validate"
)

// Config holds cluster-wide configuration values.
type Config struct {
	tx *db.ClusterTx // DB transaction the values in this config are bound to.
	m  config.Map    // Low-level map holding the config values.
}

// ConfigLoad loads a new Config object with the current cluster configuration
// values fetched from the database.
func ConfigLoad(tx *db.ClusterTx) (*Config, error) {
	// Load current raw values from the database, any error is fatal.
	values, err := tx.Config()
	if err != nil {
		return nil, fmt.Errorf("cannot fetch node config from database: %v", err)
	}

	m, err := config.SafeLoad(ConfigSchema, values)
	if err != nil {
		return nil, fmt.Errorf("failed to load node config: %v", err)
	}

	return &Config{tx: tx, m: m}, nil
}

// BGPASN returns the BGP ASN setting.
func (c *Config) BGPASN() int64 {
	return c.m.GetInt64("core.bgp_asn")
}

// HTTPSAllowedHeaders returns the relevant CORS setting.
func (c *Config) HTTPSAllowedHeaders() string {
	return c.m.GetString("core.https_allowed_headers")
}

// HTTPSAllowedMethods returns the relevant CORS setting.
func (c *Config) HTTPSAllowedMethods() string {
	return c.m.GetString("core.https_allowed_methods")
}

// HTTPSAllowedOrigin returns the relevant CORS setting.
func (c *Config) HTTPSAllowedOrigin() string {
	return c.m.GetString("core.https_allowed_origin")
}

// HTTPSAllowedCredentials returns the relevant CORS setting.
func (c *Config) HTTPSAllowedCredentials() bool {
	return c.m.GetBool("core.https_allowed_credentials")
}

// TrustPassword returns the LXD trust password for authenticating clients.
func (c *Config) TrustPassword() string {
	return c.m.GetString("core.trust_password")
}

// TrustCACertificates returns whether client certificates are checked
// against a CA.
func (c *Config) TrustCACertificates() bool {
	return c.m.GetBool("core.trust_ca_certificates")
}

// CandidServer returns all the Candid settings needed to connect to a server.
func (c *Config) CandidServer() (string, string, int64, string) {
	return c.m.GetString("candid.api.url"),
		c.m.GetString("candid.api.key"),
		c.m.GetInt64("candid.expiry"),
		c.m.GetString("candid.domains")
}

// RBACServer returns all the Candid settings needed to connect to a server.
func (c *Config) RBACServer() (string, string, int64, string, string, string, string) {
	return c.m.GetString("rbac.api.url"),
		c.m.GetString("rbac.api.key"),
		c.m.GetInt64("rbac.expiry"),
		c.m.GetString("rbac.agent.url"),
		c.m.GetString("rbac.agent.username"),
		c.m.GetString("rbac.agent.private_key"),
		c.m.GetString("rbac.agent.public_key")
}

// ProxyHTTPS returns the configured HTTPS proxy, if any.
func (c *Config) ProxyHTTPS() string {
	return c.m.GetString("core.proxy_https")
}

// ProxyHTTP returns the configured HTTP proxy, if any.
func (c *Config) ProxyHTTP() string {
	return c.m.GetString("core.proxy_http")
}

// ProxyIgnoreHosts returns the configured ignore-hosts proxy setting, if any.
func (c *Config) ProxyIgnoreHosts() string {
	return c.m.GetString("core.proxy_ignore_hosts")
}

// HTTPSTrustedProxy returns the configured HTTPS trusted proxy setting, if any.
func (c *Config) HTTPSTrustedProxy() string {
	return c.m.GetString("core.https_trusted_proxy")
}

// MAASController the configured MAAS url and key, if any.
func (c *Config) MAASController() (string, string) {
	url := c.m.GetString("maas.api.url")
	key := c.m.GetString("maas.api.key")
	return url, key
}

// OfflineThreshold returns the configured heartbeat threshold, i.e. the
// number of seconds before after which an unresponsive node is considered
// offline..
func (c *Config) OfflineThreshold() time.Duration {
	n := c.m.GetInt64("cluster.offline_threshold")
	return time.Duration(n) * time.Second
}

// ImagesMinimalReplica returns the numbers of nodes for cluster images replication
func (c *Config) ImagesMinimalReplica() int64 {
	return c.m.GetInt64("cluster.images_minimal_replica")
}

// MaxVoters returns the maximum number of members in a cluster that will be
// assigned the voter role.
func (c *Config) MaxVoters() int64 {
	return c.m.GetInt64("cluster.max_voters")
}

// MaxStandBy returns the maximum number of standby members in a cluster that
// will be assigned the stand-by role.
func (c *Config) MaxStandBy() int64 {
	return c.m.GetInt64("cluster.max_standby")
}

// ShutdownTimeout returns the number of minutes to wait for running operation to complete
// before LXD server shut down
func (c *Config) ShutdownTimeout() time.Duration {
	n := c.m.GetInt64("core.shutdown_timeout")
	return time.Duration(n) * time.Minute
}

// Dump current configuration keys and their values. Keys with values matching
// their defaults are omitted.
func (c *Config) Dump() map[string]interface{} {
	return c.m.Dump()
}

// Replace the current configuration with the given values.
//
// Return what has actually changed.
func (c *Config) Replace(values map[string]interface{}) (map[string]string, error) {
	return c.update(values)
}

// Patch changes only the configuration keys in the given map.
//
// Return what has actually changed.
func (c *Config) Patch(patch map[string]interface{}) (map[string]string, error) {
	values := c.Dump() // Use current values as defaults
	for name, value := range patch {
		values[name] = value
	}
	return c.update(values)
}

func (c *Config) update(values map[string]interface{}) (map[string]string, error) {
	changed, err := c.m.Change(values)
	if err != nil {
		return nil, err
	}

	err = c.tx.UpdateConfig(changed)
	if err != nil {
		return nil, errors.Wrap(err, "cannot persist configuration changes: %v")
	}

	return changed, nil
}

// ConfigGetString is a convenience for loading the cluster configuration and
// returning the value of a particular key.
//
// It's a deprecated API meant to be used by call sites that are not
// interacting with the database in a transactional way.
func ConfigGetString(cluster *db.Cluster, key string) (string, error) {
	config, err := configGet(cluster)
	if err != nil {
		return "", err
	}
	return config.m.GetString(key), nil
}

// ConfigGetBool is a convenience for loading the cluster configuration and
// returning the value of a particular boolean key.
//
// It's a deprecated API meant to be used by call sites that are not
// interacting with the database in a transactional way.
func ConfigGetBool(cluster *db.Cluster, key string) (bool, error) {
	config, err := configGet(cluster)
	if err != nil {
		return false, err
	}
	return config.m.GetBool(key), nil
}

// ConfigGetInt64 is a convenience for loading the cluster configuration and
// returning the value of a particular key.
//
// It's a deprecated API meant to be used by call sites that are not
// interacting with the database in a transactional way.
func ConfigGetInt64(cluster *db.Cluster, key string) (int64, error) {
	config, err := configGet(cluster)
	if err != nil {
		return 0, err
	}
	return config.m.GetInt64(key), nil
}

func configGet(cluster *db.Cluster) (*Config, error) {
	var config *Config
	err := cluster.Transaction(func(tx *db.ClusterTx) error {
		var err error
		config, err = ConfigLoad(tx)
		return err
	})
	return config, err
}

// ConfigSchema defines available server configuration keys.
var ConfigSchema = config.Schema{
	"backups.compression_algorithm":  {Default: "gzip", Validator: validate.IsCompressionAlgorithm},
	"cluster.offline_threshold":      {Type: config.Int64, Default: offlineThresholdDefault(), Validator: offlineThresholdValidator},
	"cluster.images_minimal_replica": {Type: config.Int64, Default: "3", Validator: imageMinimalReplicaValidator},
	"cluster.max_voters":             {Type: config.Int64, Default: "3", Validator: maxVotersValidator},
	"cluster.max_standby":            {Type: config.Int64, Default: "2", Validator: maxStandByValidator},
	"core.bgp_asn":                   {Type: config.Int64, Default: "0", Validator: validate.Optional(validate.IsInRange(0, 4294967294))},
	"core.https_allowed_headers":     {},
	"core.https_allowed_methods":     {},
	"core.https_allowed_origin":      {},
	"core.https_allowed_credentials": {Type: config.Bool},
	"core.https_trusted_proxy":       {},
	"core.proxy_http":                {},
	"core.proxy_https":               {},
	"core.proxy_ignore_hosts":        {},
	"core.shutdown_timeout":          {Type: config.Int64, Default: "5"},
	"core.trust_password":            {Hidden: true, Setter: passwordSetter},
	"core.trust_ca_certificates":     {Type: config.Bool},
	"candid.api.key":                 {},
	"candid.api.url":                 {},
	"candid.domains":                 {},
	"candid.expiry":                  {Type: config.Int64, Default: "3600"},
	"images.auto_update_cached":      {Type: config.Bool, Default: "true"},
	"images.auto_update_interval":    {Type: config.Int64, Default: "6"},
	"images.compression_algorithm":   {Default: "gzip", Validator: validate.IsCompressionAlgorithm},
	"images.default_architecture":    {Validator: validate.Optional(validate.IsArchitecture)},
	"images.remote_cache_expiry":     {Type: config.Int64, Default: "10"},
	"maas.api.key":                   {},
	"maas.api.url":                   {},
	"rbac.agent.url":                 {},
	"rbac.agent.username":            {},
	"rbac.agent.private_key":         {},
	"rbac.agent.public_key":          {},
	"rbac.api.expiry":                {Type: config.Int64, Default: "3600"},
	"rbac.api.key":                   {},
	"rbac.api.url":                   {},
	"rbac.expiry":                    {Type: config.Int64, Default: "3600"},

	// Keys deprecated since the implementation of the storage api.
	"storage.lvm_fstype":           {Setter: deprecatedStorage, Default: "ext4"},
	"storage.lvm_mount_options":    {Setter: deprecatedStorage, Default: "discard"},
	"storage.lvm_thinpool_name":    {Setter: deprecatedStorage, Default: "LXDThinPool"},
	"storage.lvm_vg_name":          {Setter: deprecatedStorage},
	"storage.lvm_volume_size":      {Setter: deprecatedStorage, Default: "10GiB"},
	"storage.zfs_pool_name":        {Setter: deprecatedStorage},
	"storage.zfs_remove_snapshots": {Setter: deprecatedStorage, Type: config.Bool},
	"storage.zfs_use_refquota":     {Setter: deprecatedStorage, Type: config.Bool},

	// OVN networking global keys.
	"network.ovn.integration_bridge":    {Default: "br-int"},
	"network.ovn.northbound_connection": {Default: "unix:/var/run/ovn/ovnnb_db.sock"},
}

func offlineThresholdDefault() string {
	return strconv.Itoa(db.DefaultOfflineThreshold)
}

func offlineThresholdValidator(value string) error {
	minThreshold := 10

	// Ensure that the given value is greater than the heartbeat interval,
	// which is the lower bound granularity of the offline check.
	threshold, err := strconv.Atoi(value)
	if err != nil {
		return fmt.Errorf("Offline threshold is not a number")
	}

	if threshold <= minThreshold {
		return fmt.Errorf("Value must be greater than '%d'", minThreshold)
	}

	return nil
}

func imageMinimalReplicaValidator(value string) error {
	count, err := strconv.Atoi(value)
	if err != nil {
		return fmt.Errorf("Minimal image replica count is not a number")
	}

	if count < 1 && count != -1 {
		return fmt.Errorf("Invalid value for image replica count")
	}

	return nil
}

func maxVotersValidator(value string) error {
	n, err := strconv.Atoi(value)
	if err != nil {
		return fmt.Errorf("Value is not a number")
	}

	if n < 3 || n%2 != 1 {
		return fmt.Errorf("Value must be an odd number equal to or higher than 3")
	}

	return nil
}

func maxStandByValidator(value string) error {
	n, err := strconv.Atoi(value)
	if err != nil {
		return fmt.Errorf("Value is not a number")
	}

	if n < 0 || n > 5 {
		return fmt.Errorf("Value must be between 0 and 5")
	}

	return nil
}

func passwordSetter(value string) (string, error) {
	// Nothing to do on unset
	if value == "" {
		return value, nil
	}

	// Hash the password
	buf := make([]byte, 32)
	_, err := io.ReadFull(rand.Reader, buf)
	if err != nil {
		return "", err
	}

	hash, err := scrypt.Key([]byte(value), buf, 1<<14, 8, 1, 64)
	if err != nil {
		return "", err
	}

	buf = append(buf, hash...)
	value = hex.EncodeToString(buf)

	return value, nil
}

func deprecatedStorage(value string) (string, error) {
	if value == "" {
		return "", nil
	}
	return "", fmt.Errorf("deprecated: use storage pool configuration")
}
<|endoftext|>


<commit_before>package cluster

import (
	"context"
	"sync"
	"time"

	log "gopkg.in/inconshreveable/log15.v2"

	"github.com/lxc/lxd/client"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/endpoints"
	"github.com/lxc/lxd/lxd/revert"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/logger"
	"github.com/lxc/lxd/shared/logging"
)

// eventHubMinHosts is the minimum number of members that must have the event-hub role to trigger switching into
// event-hub mode (where cluster members will only connect to event-hub members rather than all members when
// operating in the normal full-mesh mode).
const eventHubMinHosts = 2

// EventMode indicates the event distribution mode.
type EventMode string

// EventModeFullMesh is when every cluster member connects to every other cluster member to pull events.
const EventModeFullMesh EventMode = "full-mesh"

// EventModeHubServer is when the cluster is operating in event-hub mode and this server is designated as a hub
// server, meaning that it will only connect to the other event-hub members and not other members.
const EventModeHubServer EventMode = "hub-server"

// EventModeHubClient is when the cluster is operating in event-hub mode and this member is designated as a hub
// client, meaning that it is expected to connect to the event-hub members.
const EventModeHubClient EventMode = "hub-client"

// eventListenerClient stores both the event listener and its associated client.
type eventListenerClient struct {
	*lxd.EventListener

	client        lxd.InstanceServer
	hubPushCancel context.CancelFunc
}

// Disconnect disconnects both the listener and the client.
func (lc *eventListenerClient) Disconnect() {
	if lc.hubPushCancel != nil {
		lc.hubPushCancel()
	}

	lc.EventListener.Disconnect()
	lc.client.Disconnect()
}

// SetEventMode applies the specified eventMode of the local server to the listener.
// If the eventMode is EventModeHubClient then a go routine is started that consumes events from eventHubPushCh and
// pushes them to the remote server. If the eventMode is anything else then the go routine is stopped if running.
func (lc *eventListenerClient) SetEventMode(eventMode EventMode, eventHubPushCh chan api.Event) {
	if eventMode == EventModeHubClient {
		if lc.hubPushCancel != nil || !lc.IsActive() {
			return
		}

		ctx, cancel := context.WithCancel(context.Background())

		go func() {
			lc.hubPushCancel = cancel
			info, _ := lc.client.GetConnectionInfo()
			logger.Info("Event hub client started", log.Ctx{"remote": info.URL})
			defer logger.Info("Event hub client stopped", log.Ctx{"remote": info.URL})
			defer func() {
				cancel()
				lc.hubPushCancel = nil
			}()

			for {
				select {
				case event, more := <-eventHubPushCh:
					if !more {
						return
					}

					err := lc.client.SendEvent(event)
					if err != nil {
						//  Send failed, something is wrong with this hub server.
						lc.Disconnect() // Disconnect listener and client.

						// Try and put event back onto event hub push queue for consumption
						// by another consumer.
						ctx, cancel := context.WithTimeout(context.Background(), eventHubPushChTimeout)
						defer cancel()

						select {
						case eventHubPushCh <- event:
						case <-ctx.Done(): // Don't block if all consumers are slow/down.
						}

						return
					}
				case <-ctx.Done():
					return
				}
			}
		}()
	} else if lc.hubPushCancel != nil {
		lc.hubPushCancel()
		lc.hubPushCancel = nil
	}

	return
}

var eventMode EventMode = EventModeFullMesh
var eventHubAddresses []string
var eventHubPushCh = make(chan api.Event, 10) // Buffer size to accommodate slow consumers before dropping events.
var eventHubPushChTimeout = time.Duration(time.Second)
var listeners = map[string]*eventListenerClient{}
var listenersNotify = map[chan struct{}][]string{}
var listenersLock sync.Mutex
var listenersUpdateLock sync.Mutex

// ServerEventMode returns the event distribution mode that this local server is operating in.
func ServerEventMode() EventMode {
	listenersLock.Lock()
	defer listenersLock.Unlock()

	return eventMode
}

// RoleInSlice returns whether or not the rule is within the roles list.
func RoleInSlice(role db.ClusterRole, roles []db.ClusterRole) bool {
	for _, r := range roles {
		if r == role {
			return true
		}
	}

	return false
}

// EventListenerWait waits for there to be listener connected to the specified address, or one of the event hubs
// if operating in event hub mode.
func EventListenerWait(ctx context.Context, address string) error {
	// Check if there is already a listener.
	listenersLock.Lock()
	listener, found := listeners[address]
	if found && listener.IsActive() {
		listenersLock.Unlock()
		return nil
	}

	listenAddresses := []string{address}

	// Check if operating in event hub mode and if one of the event hub connections is available.
	// If so then we are ready to receive events from all members.
	if eventMode != EventModeFullMesh {
		for _, eventHubAddress := range eventHubAddresses {
			listener, found := listeners[eventHubAddress]
			if found && listener.IsActive() {
				listenersLock.Unlock()
				return nil
			}

			listenAddresses = append(listenAddresses, eventHubAddress)
		}
	}

	// If not setup a notification for when the desired address or any of the event hubs connect.
	connected := make(chan struct{})
	listenersNotify[connected] = listenAddresses
	listenersLock.Unlock()

	defer func() {
		listenersLock.Lock()
		delete(listenersNotify, connected)
		listenersLock.Unlock()
	}()

	// Wait for the connected channel to be closed (indicating a new listener has been connected), and return.
	select {
	case <-connected:
		return nil
	case <-ctx.Done():
		return ctx.Err()
	}
}

// hubAddresses returns the addresses of members with event-hub role, and the event mode of the server.
// The event mode will only be hub-server or hub-client if at least eventHubMinHosts have an event-hub role.
// Otherwise the mode will be full-mesh.
func hubAddresses(localAddress string, members map[int64]APIHeartbeatMember) ([]string, EventMode) {
	var hubAddresses []string
	var localHasHubRole bool

	// Do a first pass of members to count the members with event-hub role, and whether we are a hub server.
	for _, member := range members {
		if RoleInSlice(db.ClusterRoleEventHub, member.Roles) {
			hubAddresses = append(hubAddresses, member.Address)

			if member.Address == localAddress {
				localHasHubRole = true
			}
		}
	}

	eventMode := EventModeFullMesh
	if len(hubAddresses) >= eventHubMinHosts {
		if localHasHubRole {
			eventMode = EventModeHubServer
		} else {
			eventMode = EventModeHubClient
		}
	}

	return hubAddresses, eventMode
}

// EventsUpdateListeners refreshes the cluster event listener connections.
func EventsUpdateListeners(endpoints *endpoints.Endpoints, cluster *db.Cluster, serverCert func() *shared.CertInfo, members map[int64]APIHeartbeatMember, f func(int64, api.Event)) {
	listenersUpdateLock.Lock()
	defer listenersUpdateLock.Unlock()

	// If no heartbeat members provided, populate from global database.
	if members == nil {
		var dbMembers []db.NodeInfo
		var offlineThreshold time.Duration

		err := cluster.Transaction(func(tx *db.ClusterTx) error {
			var err error

			dbMembers, err = tx.GetNodes()
			if err != nil {
				return err
			}

			offlineThreshold, err = tx.GetNodeOfflineThreshold()
			if err != nil {
				return err
			}

			return nil
		})
		if err != nil {
			logger.Warn("Failed to get current cluster members", log.Ctx{"err": err})
			return
		}

		members = make(map[int64]APIHeartbeatMember, len(dbMembers))
		for _, dbMember := range dbMembers {
			members[dbMember.ID] = APIHeartbeatMember{
				ID:            dbMember.ID,
				Name:          dbMember.Name,
				Address:       dbMember.Address,
				LastHeartbeat: dbMember.Heartbeat,
				Online:        !dbMember.IsOffline(offlineThreshold),
				Roles:         dbMember.Roles,
			}
		}
	}

	localAddress := endpoints.NetworkAddress()
	hubAddresses, localEventMode := hubAddresses(localAddress, members)

	// Store event hub addresses in global slice.
	listenersLock.Lock()
	eventHubAddresses = hubAddresses
	eventMode = localEventMode
	listenersLock.Unlock()

	keepListeners := make(map[string]struct{})
	wg := sync.WaitGroup{}
	for _, member := range members {
		// Don't bother trying to connect to ourselves or offline members.
		if member.Address == localAddress || !member.Online {
			continue
		}

		if localEventMode != EventModeFullMesh && !RoleInSlice(db.ClusterRoleEventHub, member.Roles) {
			continue // Skip non-event-hub members if we are operating in event-hub mode.
		}

		listenersLock.Lock()
		listener, ok := listeners[member.Address]

		// If the member already has a listener associated to it, check that the listener is still active.
		// If it is, just move on to next member, but if not then we'll try to connect again.
		if ok {
			if listener.IsActive() {
				keepListeners[member.Address] = struct{}{} // Add to current listeners list.
				listener.SetEventMode(localEventMode, eventHubPushCh)
				listenersLock.Unlock()
				continue
			}

			// Disconnect and delete listener, but don't delete any listenersNotify entry as there
			// might be something waiting for a future connection.
			listener.Disconnect()
			delete(listeners, member.Address)

			logger.Info("Removed inactive member event listener client", log.Ctx{"local": localAddress, "remote": member.Address})
		}
		listenersLock.Unlock()

		keepListeners[member.Address] = struct{}{} // Add to current listeners list.

		// Connect to remote concurrently and add to active listeners if successful.
		wg.Add(1)
		go func(m APIHeartbeatMember) {
			logger := logging.AddContext(logger.Log, log.Ctx{"local": localAddress, "remote": m.Address})

			defer wg.Done()
			listener, err := eventsConnect(m.Address, endpoints.NetworkCert(), serverCert())
			if err != nil {
				logger.Warn("Failed adding member event listener client", log.Ctx{"err": err})
				return
			}

			listener.AddHandler(nil, func(event api.Event) { f(m.ID, event) })

			listener.SetEventMode(localEventMode, eventHubPushCh)

			listenersLock.Lock()
			listeners[m.Address] = listener

			// Indicate to any notifiers waiting for this member's address that it is connected.
			for connected, notifyAddresses := range listenersNotify {
				if shared.StringInSlice(m.Address, notifyAddresses) {
					close(connected)
					delete(listenersNotify, connected)
				}
			}

			logger.Info("Added member event listener client")
			listenersLock.Unlock()
		}(member)
	}

	wg.Wait()

	// Disconnect and delete any out of date listeners and their notifiers.
	listenersLock.Lock()
	for address, listener := range listeners {
		if _, found := keepListeners[address]; !found {
			listener.Disconnect()
			delete(listeners, address)

			logger.Info("Removed old member event listener client", log.Ctx{"local": localAddress, "remote": address})
		}
	}
	listenersLock.Unlock()
}

// Establish a client connection to get events from the given node.
func eventsConnect(address string, networkCert *shared.CertInfo, serverCert *shared.CertInfo) (*eventListenerClient, error) {
	client, err := Connect(address, networkCert, serverCert, nil, true)
	if err != nil {
		return nil, err
	}

	revert := revert.New()
	revert.Add(func() {
		client.Disconnect()
	})

	listener, err := client.GetEventsAllProjects()
	if err != nil {
		return nil, err
	}

	revert.Success()

	lc := &eventListenerClient{
		EventListener: listener,
		client:        client,
	}

	return lc, nil
}
<commit_msg>lxd/cluster/events: Ensure logging inside EventsUpdateListeners is done outside of listenersLock lock<commit_after>package cluster

import (
	"context"
	"sync"
	"time"

	log "gopkg.in/inconshreveable/log15.v2"

	"github.com/lxc/lxd/client"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/endpoints"
	"github.com/lxc/lxd/lxd/revert"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/logger"
	"github.com/lxc/lxd/shared/logging"
)

// eventHubMinHosts is the minimum number of members that must have the event-hub role to trigger switching into
// event-hub mode (where cluster members will only connect to event-hub members rather than all members when
// operating in the normal full-mesh mode).
const eventHubMinHosts = 2

// EventMode indicates the event distribution mode.
type EventMode string

// EventModeFullMesh is when every cluster member connects to every other cluster member to pull events.
const EventModeFullMesh EventMode = "full-mesh"

// EventModeHubServer is when the cluster is operating in event-hub mode and this server is designated as a hub
// server, meaning that it will only connect to the other event-hub members and not other members.
const EventModeHubServer EventMode = "hub-server"

// EventModeHubClient is when the cluster is operating in event-hub mode and this member is designated as a hub
// client, meaning that it is expected to connect to the event-hub members.
const EventModeHubClient EventMode = "hub-client"

// eventListenerClient stores both the event listener and its associated client.
type eventListenerClient struct {
	*lxd.EventListener

	client        lxd.InstanceServer
	hubPushCancel context.CancelFunc
}

// Disconnect disconnects both the listener and the client.
func (lc *eventListenerClient) Disconnect() {
	if lc.hubPushCancel != nil {
		lc.hubPushCancel()
	}

	lc.EventListener.Disconnect()
	lc.client.Disconnect()
}

// SetEventMode applies the specified eventMode of the local server to the listener.
// If the eventMode is EventModeHubClient then a go routine is started that consumes events from eventHubPushCh and
// pushes them to the remote server. If the eventMode is anything else then the go routine is stopped if running.
func (lc *eventListenerClient) SetEventMode(eventMode EventMode, eventHubPushCh chan api.Event) {
	if eventMode == EventModeHubClient {
		if lc.hubPushCancel != nil || !lc.IsActive() {
			return
		}

		ctx, cancel := context.WithCancel(context.Background())

		go func() {
			lc.hubPushCancel = cancel
			info, _ := lc.client.GetConnectionInfo()
			logger.Info("Event hub client started", log.Ctx{"remote": info.URL})
			defer logger.Info("Event hub client stopped", log.Ctx{"remote": info.URL})
			defer func() {
				cancel()
				lc.hubPushCancel = nil
			}()

			for {
				select {
				case event, more := <-eventHubPushCh:
					if !more {
						return
					}

					err := lc.client.SendEvent(event)
					if err != nil {
						//  Send failed, something is wrong with this hub server.
						lc.Disconnect() // Disconnect listener and client.

						// Try and put event back onto event hub push queue for consumption
						// by another consumer.
						ctx, cancel := context.WithTimeout(context.Background(), eventHubPushChTimeout)
						defer cancel()

						select {
						case eventHubPushCh <- event:
						case <-ctx.Done(): // Don't block if all consumers are slow/down.
						}

						return
					}
				case <-ctx.Done():
					return
				}
			}
		}()
	} else if lc.hubPushCancel != nil {
		lc.hubPushCancel()
		lc.hubPushCancel = nil
	}

	return
}

var eventMode EventMode = EventModeFullMesh
var eventHubAddresses []string
var eventHubPushCh = make(chan api.Event, 10) // Buffer size to accommodate slow consumers before dropping events.
var eventHubPushChTimeout = time.Duration(time.Second)
var listeners = map[string]*eventListenerClient{}
var listenersNotify = map[chan struct{}][]string{}
var listenersLock sync.Mutex
var listenersUpdateLock sync.Mutex

// ServerEventMode returns the event distribution mode that this local server is operating in.
func ServerEventMode() EventMode {
	listenersLock.Lock()
	defer listenersLock.Unlock()

	return eventMode
}

// RoleInSlice returns whether or not the rule is within the roles list.
func RoleInSlice(role db.ClusterRole, roles []db.ClusterRole) bool {
	for _, r := range roles {
		if r == role {
			return true
		}
	}

	return false
}

// EventListenerWait waits for there to be listener connected to the specified address, or one of the event hubs
// if operating in event hub mode.
func EventListenerWait(ctx context.Context, address string) error {
	// Check if there is already a listener.
	listenersLock.Lock()
	listener, found := listeners[address]
	if found && listener.IsActive() {
		listenersLock.Unlock()
		return nil
	}

	listenAddresses := []string{address}

	// Check if operating in event hub mode and if one of the event hub connections is available.
	// If so then we are ready to receive events from all members.
	if eventMode != EventModeFullMesh {
		for _, eventHubAddress := range eventHubAddresses {
			listener, found := listeners[eventHubAddress]
			if found && listener.IsActive() {
				listenersLock.Unlock()
				return nil
			}

			listenAddresses = append(listenAddresses, eventHubAddress)
		}
	}

	// If not setup a notification for when the desired address or any of the event hubs connect.
	connected := make(chan struct{})
	listenersNotify[connected] = listenAddresses
	listenersLock.Unlock()

	defer func() {
		listenersLock.Lock()
		delete(listenersNotify, connected)
		listenersLock.Unlock()
	}()

	// Wait for the connected channel to be closed (indicating a new listener has been connected), and return.
	select {
	case <-connected:
		return nil
	case <-ctx.Done():
		return ctx.Err()
	}
}

// hubAddresses returns the addresses of members with event-hub role, and the event mode of the server.
// The event mode will only be hub-server or hub-client if at least eventHubMinHosts have an event-hub role.
// Otherwise the mode will be full-mesh.
func hubAddresses(localAddress string, members map[int64]APIHeartbeatMember) ([]string, EventMode) {
	var hubAddresses []string
	var localHasHubRole bool

	// Do a first pass of members to count the members with event-hub role, and whether we are a hub server.
	for _, member := range members {
		if RoleInSlice(db.ClusterRoleEventHub, member.Roles) {
			hubAddresses = append(hubAddresses, member.Address)

			if member.Address == localAddress {
				localHasHubRole = true
			}
		}
	}

	eventMode := EventModeFullMesh
	if len(hubAddresses) >= eventHubMinHosts {
		if localHasHubRole {
			eventMode = EventModeHubServer
		} else {
			eventMode = EventModeHubClient
		}
	}

	return hubAddresses, eventMode
}

// EventsUpdateListeners refreshes the cluster event listener connections.
func EventsUpdateListeners(endpoints *endpoints.Endpoints, cluster *db.Cluster, serverCert func() *shared.CertInfo, members map[int64]APIHeartbeatMember, f func(int64, api.Event)) {
	listenersUpdateLock.Lock()
	defer listenersUpdateLock.Unlock()

	// If no heartbeat members provided, populate from global database.
	if members == nil {
		var dbMembers []db.NodeInfo
		var offlineThreshold time.Duration

		err := cluster.Transaction(func(tx *db.ClusterTx) error {
			var err error

			dbMembers, err = tx.GetNodes()
			if err != nil {
				return err
			}

			offlineThreshold, err = tx.GetNodeOfflineThreshold()
			if err != nil {
				return err
			}

			return nil
		})
		if err != nil {
			logger.Warn("Failed to get current cluster members", log.Ctx{"err": err})
			return
		}

		members = make(map[int64]APIHeartbeatMember, len(dbMembers))
		for _, dbMember := range dbMembers {
			members[dbMember.ID] = APIHeartbeatMember{
				ID:            dbMember.ID,
				Name:          dbMember.Name,
				Address:       dbMember.Address,
				LastHeartbeat: dbMember.Heartbeat,
				Online:        !dbMember.IsOffline(offlineThreshold),
				Roles:         dbMember.Roles,
			}
		}
	}

	localAddress := endpoints.NetworkAddress()
	hubAddresses, localEventMode := hubAddresses(localAddress, members)

	// Store event hub addresses in global slice.
	listenersLock.Lock()
	eventHubAddresses = hubAddresses
	eventMode = localEventMode
	listenersLock.Unlock()

	keepListeners := make(map[string]struct{})
	wg := sync.WaitGroup{}
	for _, member := range members {
		// Don't bother trying to connect to ourselves or offline members.
		if member.Address == localAddress || !member.Online {
			continue
		}

		if localEventMode != EventModeFullMesh && !RoleInSlice(db.ClusterRoleEventHub, member.Roles) {
			continue // Skip non-event-hub members if we are operating in event-hub mode.
		}

		listenersLock.Lock()
		listener, ok := listeners[member.Address]

		// If the member already has a listener associated to it, check that the listener is still active.
		// If it is, just move on to next member, but if not then we'll try to connect again.
		if ok {
			if listener.IsActive() {
				keepListeners[member.Address] = struct{}{} // Add to current listeners list.
				listener.SetEventMode(localEventMode, eventHubPushCh)
				listenersLock.Unlock()
				continue
			}

			// Disconnect and delete listener, but don't delete any listenersNotify entry as there
			// might be something waiting for a future connection.
			listener.Disconnect()
			delete(listeners, member.Address)
			listenersLock.Unlock()

			// Log after releasing listenersLock to avoid deadlock on listenersLock with EventHubPush.
			logger.Info("Removed inactive member event listener client", log.Ctx{"local": localAddress, "remote": member.Address})
		} else {
			listenersLock.Unlock()
		}

		keepListeners[member.Address] = struct{}{} // Add to current listeners list.

		// Connect to remote concurrently and add to active listeners if successful.
		wg.Add(1)
		go func(m APIHeartbeatMember) {
			logger := logging.AddContext(logger.Log, log.Ctx{"local": localAddress, "remote": m.Address})

			defer wg.Done()
			listener, err := eventsConnect(m.Address, endpoints.NetworkCert(), serverCert())
			if err != nil {
				logger.Warn("Failed adding member event listener client", log.Ctx{"err": err})
				return
			}

			listener.AddHandler(nil, func(event api.Event) { f(m.ID, event) })

			listener.SetEventMode(localEventMode, eventHubPushCh)

			listenersLock.Lock()
			listeners[m.Address] = listener

			// Indicate to any notifiers waiting for this member's address that it is connected.
			for connected, notifyAddresses := range listenersNotify {
				if shared.StringInSlice(m.Address, notifyAddresses) {
					close(connected)
					delete(listenersNotify, connected)
				}
			}

			listenersLock.Unlock()

			// Log after releasing listenersLock to avoid deadlock on listenersLock with EventHubPush.
			logger.Info("Added member event listener client")
		}(member)
	}
	wg.Wait()

	// Disconnect and delete any out of date listeners and their notifiers.
	var removedAddresses []string

	listenersLock.Lock()
	for address, listener := range listeners {
		if _, found := keepListeners[address]; !found {
			listener.Disconnect()
			delete(listeners, address)

			// Record address removed, but don't log it here as this could cause a deadlock on
			// listenersLock with EventHubPush
			removedAddresses = append(removedAddresses, address)
		}
	}
	listenersLock.Unlock()

	// Log the listeners removed after releasing listenersLock.
	for _, removedAddress := range removedAddresses {
		logger.Info("Removed old member event listener client", log.Ctx{"local": localAddress, "remote": removedAddress})
	}
}

// Establish a client connection to get events from the given node.
func eventsConnect(address string, networkCert *shared.CertInfo, serverCert *shared.CertInfo) (*eventListenerClient, error) {
	client, err := Connect(address, networkCert, serverCert, nil, true)
	if err != nil {
		return nil, err
	}

	revert := revert.New()
	revert.Add(func() {
		client.Disconnect()
	})

	listener, err := client.GetEventsAllProjects()
	if err != nil {
		return nil, err
	}

	revert.Success()

	lc := &eventListenerClient{
		EventListener: listener,
		client:        client,
	}

	return lc, nil
}
<|endoftext|>


<commit_before>package cluster

import (
	"context"
	"sync"
	"time"

	log "gopkg.in/inconshreveable/log15.v2"

	"github.com/lxc/lxd/client"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/endpoints"
	"github.com/lxc/lxd/lxd/revert"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/logger"
)

// eventHubMinHosts is the minimum number of members that must have the event-hub role to trigger switching into
// event-hub mode (where cluster members will only connect to event-hub members rather than all members when
// operating in the normal full-mesh mode).
const eventHubMinHosts = 2

// EventMode indicates the event distribution mode.
type EventMode string

// EventModeFullMesh is when every cluster member connects to every other cluster member to pull events.
const EventModeFullMesh EventMode = "full-mesh"

// EventModeHubServer is when the cluster is operating in event-hub mode and this server is designated as a hub
// server, meaning that it will only connect to the other event-hub members and not other members.
const EventModeHubServer EventMode = "hub-server"

// EventModeHubClient is when the cluster is operating in event-hub mode and this member is designated as a hub
// client, meaning that it is expected to connect to the event-hub members.
const EventModeHubClient EventMode = "hub-client"

var listeners = map[string]*lxd.EventListener{}
var listenersNotify = map[chan struct{}][]string{}
var listenersLock sync.Mutex
var listenersUpdateLock sync.Mutex

// EventListenerWait waits for there to be listener connected to the specified address, or one of the event hubs
// if operating in event hub mode.
func EventListenerWait(ctx context.Context, address string) error {
	// Check if there is already a listener.
	listenersLock.Lock()
	listener, found := listeners[address]
	if found && listener.IsActive() {
		listenersLock.Unlock()
		return nil
	}

	listenAddresses := []string{address}

	// If not setup a notification for when the desired address or any of the event hubs connect.
	connected := make(chan struct{})
	listenersNotify[connected] = listenAddresses
	listenersLock.Unlock()

	defer func() {
		listenersLock.Lock()
		delete(listenersNotify, connected)
		listenersLock.Unlock()
	}()

	// Wait for the connected channel to be closed (indicating a new listener has been connected), and return.
	select {
	case <-connected:
		return nil
	case <-ctx.Done():
		return ctx.Err()
	}
}

// EventsUpdateListeners refreshes the cluster event listener connections.
func EventsUpdateListeners(endpoints *endpoints.Endpoints, cluster *db.Cluster, serverCert func() *shared.CertInfo, members map[int64]APIHeartbeatMember, f func(int64, api.Event)) {
	listenersUpdateLock.Lock()
	defer listenersUpdateLock.Unlock()

	// If no heartbeat members provided, populate from global database.
	if members == nil {
		var dbMembers []db.NodeInfo
		var offlineThreshold time.Duration

		err := cluster.Transaction(func(tx *db.ClusterTx) error {
			var err error

			dbMembers, err = tx.GetNodes()
			if err != nil {
				return err
			}

			offlineThreshold, err = tx.GetNodeOfflineThreshold()
			if err != nil {
				return err
			}

			return nil
		})
		if err != nil {
			logger.Warn("Failed to get current cluster members", log.Ctx{"err": err})
			return
		}

		members = make(map[int64]APIHeartbeatMember, len(dbMembers))
		for _, dbMember := range dbMembers {
			members[dbMember.ID] = APIHeartbeatMember{
				ID:            dbMember.ID,
				Name:          dbMember.Name,
				Address:       dbMember.Address,
				LastHeartbeat: dbMember.Heartbeat,
				Online:        !dbMember.IsOffline(offlineThreshold),
				Roles:         dbMember.Roles,
			}
		}
	}

	networkAddress := endpoints.NetworkAddress()

	keepListeners := make(map[string]struct{})
	wg := sync.WaitGroup{}
	for _, member := range members {
		// Don't bother trying to connect to ourselves or offline members.
		if member.Address == networkAddress || !member.Online {
			continue
		}

		listenersLock.Lock()
		listener, ok := listeners[member.Address]

		// If the member already has a listener associated to it, check that the listener is still active.
		// If it is, just move on to next member, but if not then we'll try to connect again.
		if ok {
			if listener.IsActive() {
				keepListeners[member.Address] = struct{}{} // Add to current listeners list.
				listenersLock.Unlock()
				continue
			}

			// Disconnect and delete listener, but don't delete any listenersNotify entry as there
			// might be something waiting for a future connection.
			listener.Disconnect()
			delete(listeners, member.Address)
			logger.Info("Removed inactive member event listener client", log.Ctx{"local": networkAddress, "remote": member.Address})
		}
		listenersLock.Unlock()

		keepListeners[member.Address] = struct{}{} // Add to current listeners list.

		// Connect to remote concurrently and add to active listeners if successful.
		wg.Add(1)
		go func(m APIHeartbeatMember) {
			defer wg.Done()
			listener, err := eventsConnect(m.Address, endpoints.NetworkCert(), serverCert())
			if err != nil {
				logger.Warn("Failed adding member event listener client", log.Ctx{"local": networkAddress, "remote": m.Address, "err": err})
				return
			}

			listener.AddHandler(nil, func(event api.Event) { f(m.ID, event) })

			listenersLock.Lock()
			listeners[m.Address] = listener

			// Indicate to any notifiers waiting for this member's address that it is connected.
			for connected, notifyAddresses := range listenersNotify {
				if shared.StringInSlice(m.Address, notifyAddresses) {
					close(connected)
					delete(listenersNotify, connected)
				}
			}

			logger.Info("Added member event listener client", log.Ctx{"local": networkAddress, "remote": m.Address})
			listenersLock.Unlock()
		}(member)
	}

	wg.Wait()

	// Disconnect and delete any out of date listeners and their notifiers.
	listenersLock.Lock()
	for address, listener := range listeners {
		if _, found := keepListeners[address]; !found {
			listener.Disconnect()
			delete(listeners, address)
			logger.Info("Removed old member event listener client", log.Ctx{"local": networkAddress, "remote": address})
		}
	}
	listenersLock.Unlock()
}

// Establish a client connection to get events from the given node.
func eventsConnect(address string, networkCert *shared.CertInfo, serverCert *shared.CertInfo) (*lxd.EventListener, error) {
	client, err := Connect(address, networkCert, serverCert, nil, true)
	if err != nil {
		return nil, err
	}

	revert := revert.New()
	revert.Add(func() {
		client.Disconnect()
	})

	listener, err := client.GetEventsAllProjects()
	if err != nil {
		return nil, err
	}

	revert.Success()
	return listener, nil
}
<commit_msg>lxd/cluster/events: Adds ServerEventMode function<commit_after>package cluster

import (
	"context"
	"sync"
	"time"

	log "gopkg.in/inconshreveable/log15.v2"

	"github.com/lxc/lxd/client"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/endpoints"
	"github.com/lxc/lxd/lxd/revert"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/logger"
)

// eventHubMinHosts is the minimum number of members that must have the event-hub role to trigger switching into
// event-hub mode (where cluster members will only connect to event-hub members rather than all members when
// operating in the normal full-mesh mode).
const eventHubMinHosts = 2

// EventMode indicates the event distribution mode.
type EventMode string

// EventModeFullMesh is when every cluster member connects to every other cluster member to pull events.
const EventModeFullMesh EventMode = "full-mesh"

// EventModeHubServer is when the cluster is operating in event-hub mode and this server is designated as a hub
// server, meaning that it will only connect to the other event-hub members and not other members.
const EventModeHubServer EventMode = "hub-server"

// EventModeHubClient is when the cluster is operating in event-hub mode and this member is designated as a hub
// client, meaning that it is expected to connect to the event-hub members.
const EventModeHubClient EventMode = "hub-client"

var listeners = map[string]*lxd.EventListener{}
var listenersNotify = map[chan struct{}][]string{}
var listenersLock sync.Mutex
var listenersUpdateLock sync.Mutex

// ServerEventMode returns the event distribution mode that this local server is operating in.
func ServerEventMode() EventMode {
	listenersLock.Lock()
	defer listenersLock.Unlock()

	return eventMode
}

// EventListenerWait waits for there to be listener connected to the specified address, or one of the event hubs
// if operating in event hub mode.
func EventListenerWait(ctx context.Context, address string) error {
	// Check if there is already a listener.
	listenersLock.Lock()
	listener, found := listeners[address]
	if found && listener.IsActive() {
		listenersLock.Unlock()
		return nil
	}

	listenAddresses := []string{address}

	// If not setup a notification for when the desired address or any of the event hubs connect.
	connected := make(chan struct{})
	listenersNotify[connected] = listenAddresses
	listenersLock.Unlock()

	defer func() {
		listenersLock.Lock()
		delete(listenersNotify, connected)
		listenersLock.Unlock()
	}()

	// Wait for the connected channel to be closed (indicating a new listener has been connected), and return.
	select {
	case <-connected:
		return nil
	case <-ctx.Done():
		return ctx.Err()
	}
}

// EventsUpdateListeners refreshes the cluster event listener connections.
func EventsUpdateListeners(endpoints *endpoints.Endpoints, cluster *db.Cluster, serverCert func() *shared.CertInfo, members map[int64]APIHeartbeatMember, f func(int64, api.Event)) {
	listenersUpdateLock.Lock()
	defer listenersUpdateLock.Unlock()

	// If no heartbeat members provided, populate from global database.
	if members == nil {
		var dbMembers []db.NodeInfo
		var offlineThreshold time.Duration

		err := cluster.Transaction(func(tx *db.ClusterTx) error {
			var err error

			dbMembers, err = tx.GetNodes()
			if err != nil {
				return err
			}

			offlineThreshold, err = tx.GetNodeOfflineThreshold()
			if err != nil {
				return err
			}

			return nil
		})
		if err != nil {
			logger.Warn("Failed to get current cluster members", log.Ctx{"err": err})
			return
		}

		members = make(map[int64]APIHeartbeatMember, len(dbMembers))
		for _, dbMember := range dbMembers {
			members[dbMember.ID] = APIHeartbeatMember{
				ID:            dbMember.ID,
				Name:          dbMember.Name,
				Address:       dbMember.Address,
				LastHeartbeat: dbMember.Heartbeat,
				Online:        !dbMember.IsOffline(offlineThreshold),
				Roles:         dbMember.Roles,
			}
		}
	}

	networkAddress := endpoints.NetworkAddress()

	keepListeners := make(map[string]struct{})
	wg := sync.WaitGroup{}
	for _, member := range members {
		// Don't bother trying to connect to ourselves or offline members.
		if member.Address == networkAddress || !member.Online {
			continue
		}

		listenersLock.Lock()
		listener, ok := listeners[member.Address]

		// If the member already has a listener associated to it, check that the listener is still active.
		// If it is, just move on to next member, but if not then we'll try to connect again.
		if ok {
			if listener.IsActive() {
				keepListeners[member.Address] = struct{}{} // Add to current listeners list.
				listenersLock.Unlock()
				continue
			}

			// Disconnect and delete listener, but don't delete any listenersNotify entry as there
			// might be something waiting for a future connection.
			listener.Disconnect()
			delete(listeners, member.Address)
			logger.Info("Removed inactive member event listener client", log.Ctx{"local": networkAddress, "remote": member.Address})
		}
		listenersLock.Unlock()

		keepListeners[member.Address] = struct{}{} // Add to current listeners list.

		// Connect to remote concurrently and add to active listeners if successful.
		wg.Add(1)
		go func(m APIHeartbeatMember) {
			defer wg.Done()
			listener, err := eventsConnect(m.Address, endpoints.NetworkCert(), serverCert())
			if err != nil {
				logger.Warn("Failed adding member event listener client", log.Ctx{"local": networkAddress, "remote": m.Address, "err": err})
				return
			}

			listener.AddHandler(nil, func(event api.Event) { f(m.ID, event) })

			listenersLock.Lock()
			listeners[m.Address] = listener

			// Indicate to any notifiers waiting for this member's address that it is connected.
			for connected, notifyAddresses := range listenersNotify {
				if shared.StringInSlice(m.Address, notifyAddresses) {
					close(connected)
					delete(listenersNotify, connected)
				}
			}

			logger.Info("Added member event listener client", log.Ctx{"local": networkAddress, "remote": m.Address})
			listenersLock.Unlock()
		}(member)
	}

	wg.Wait()

	// Disconnect and delete any out of date listeners and their notifiers.
	listenersLock.Lock()
	for address, listener := range listeners {
		if _, found := keepListeners[address]; !found {
			listener.Disconnect()
			delete(listeners, address)
			logger.Info("Removed old member event listener client", log.Ctx{"local": networkAddress, "remote": address})
		}
	}
	listenersLock.Unlock()
}

// Establish a client connection to get events from the given node.
func eventsConnect(address string, networkCert *shared.CertInfo, serverCert *shared.CertInfo) (*lxd.EventListener, error) {
	client, err := Connect(address, networkCert, serverCert, nil, true)
	if err != nil {
		return nil, err
	}

	revert := revert.New()
	revert.Add(func() {
		client.Disconnect()
	})

	listener, err := client.GetEventsAllProjects()
	if err != nil {
		return nil, err
	}

	revert.Success()
	return listener, nil
}
<|endoftext|>


<commit_before>package mock

import (
	"github.com/vsco/dcdr/client"
	"github.com/vsco/dcdr/config"
	"github.com/vsco/dcdr/models"
)

// New creates a `Client` with an empty `FeatureMap` and `Config`.
func New() (d *Client) {
	c, _ := client.New(&config.Config{
		Watcher: config.Watcher{
			OutputPath: "",
		},
	})

	d = &Client{
		Client:     *c,
		featureMap: models.EmptyFeatureMap(),
	}

	d.Client.SetFeatureMap(d.featureMap)
	return
}

// Client mock `Client` for testing.
type Client struct {
	client.Client
	featureMap *models.FeatureMap
	features   models.FeatureScopes
}

// EnableBoolFeature set a boolean feature to true
func (d *Client) EnableBoolFeature(feature string) {
	d.FeatureMap().Dcdr.Defaults()[feature] = true
	d.MergeScopes()
}

// DisableBoolFeature set a boolean feature to false
func (d *Client) DisableBoolFeature(feature string) {
	d.Client.FeatureMap().Dcdr.Defaults()[feature] = false
	d.MergeScopes()
}

// SetPercentileFeature set a percentile feature to an arbitrary value
func (d *Client) SetPercentileFeature(feature string, val float64) {
	d.Client.FeatureMap().Dcdr.Defaults()[feature] = val
	d.MergeScopes()
}

// EnablePercentileFeature set a percentile feature to true
func (d *Client) EnablePercentileFeature(feature string) {
	d.SetPercentileFeature(feature, 1.0)
}

// DisablePercentileFeature set a percentile feature to false
func (d *Client) DisablePercentileFeature(feature string) {
	d.SetPercentileFeature(feature, 0.0)
}

// Features `features` accessor
func (d *Client) Features() models.FeatureScopes {
	return d.features
}

// Watch noop for tests.
func (d *Client) Watch() *Client {
	return d
}
<commit_msg>Remove redundant `features` variable (#62)<commit_after>package mock

import (
	"github.com/vsco/dcdr/client"
	"github.com/vsco/dcdr/config"
	"github.com/vsco/dcdr/models"
)

// New creates a `Client` with an empty `FeatureMap` and `Config`.
func New() (d *Client) {
	c, _ := client.New(&config.Config{
		Watcher: config.Watcher{
			OutputPath: "",
		},
	})

	d = &Client{
		Client:     *c,
		featureMap: models.EmptyFeatureMap(),
	}

	d.Client.SetFeatureMap(d.featureMap)
	return
}

// Client mock `Client` for testing.
type Client struct {
	client.Client
	featureMap *models.FeatureMap
}

// EnableBoolFeature set a boolean feature to true
func (d *Client) EnableBoolFeature(feature string) {
	d.FeatureMap().Dcdr.Defaults()[feature] = true
	d.MergeScopes()
}

// DisableBoolFeature set a boolean feature to false
func (d *Client) DisableBoolFeature(feature string) {
	d.Client.FeatureMap().Dcdr.Defaults()[feature] = false
	d.MergeScopes()
}

// SetPercentileFeature set a percentile feature to an arbitrary value
func (d *Client) SetPercentileFeature(feature string, val float64) {
	d.Client.FeatureMap().Dcdr.Defaults()[feature] = val
	d.MergeScopes()
}

// EnablePercentileFeature set a percentile feature to true
func (d *Client) EnablePercentileFeature(feature string) {
	d.SetPercentileFeature(feature, 1.0)
}

// DisablePercentileFeature set a percentile feature to false
func (d *Client) DisablePercentileFeature(feature string) {
	d.SetPercentileFeature(feature, 0.0)
}

// Features `features` accessor
func (d *Client) Features() models.FeatureScopes {
	return d.Client.FeatureMap().Dcdr.Defaults()
}

// Watch noop for tests.
func (d *Client) Watch() *Client {
	return d
}
<|endoftext|>


<commit_before>// +build appengine

package log

// Provides standard debug logging functionality by wrapping Google App Engine (GAE)'s logging methods.

/*
For example, this can be used to log GORM's database queries:

```
db.LogMode(true)
db.SetLogger(log.NewDebugLogWriter(context.Context(r)))
```
*/

import (
	"fmt"
	"golang.org/x/net/context"
	"google.golang.org/appengine"
	"google.golang.org/appengine/log"
	"log"
)

type DebugLogWriter struct {
	context *context.Context
}

func NewDebugLogWriter(ctx *context.Context) *DebugLogWriter {
	return &DebugLogWriter{context: ctx}
}

func (w DebugLogWriter) Print(v ...interface{}) {
	if !appengine.IsDevAppServer() {
		log.Debugf(*w.Context, fmt.Sprint(v))
	} else {
		log.Print(v)
	}
}
<commit_msg>bug fix<commit_after>// +build appengine

package log

// Provides standard debug logging functionality by wrapping Google App Engine (GAE)'s logging methods.

/*
For example, this can be used to log GORM's database queries:

```
db.LogMode(true)
db.SetLogger(log.NewDebugLogWriter(context.Context(r)))
```
*/

import (
	"fmt"
	"golang.org/x/net/context"
	"google.golang.org/appengine"
	"google.golang.org/appengine/log"
	l "log"
)

type DebugLogWriter struct {
	context *context.Context
}

func NewDebugLogWriter(ctx *context.Context) *DebugLogWriter {
	return &DebugLogWriter{context: ctx}
}

func (w DebugLogWriter) Print(v ...interface{}) {
	if !appengine.IsDevAppServer() {
		log.Debugf(*w.Context, fmt.Sprint(v))
	} else {
		l.Print(v)
	}
}
<|endoftext|>


<commit_before>// Copyright 2013,2014 The go-logger Authors. All rights reserved.
// This code is MIT licensed. See the LICENSE file for more info.

// Tests for the default standard logging object

package log

import (
	"bytes"
	"reflect"
	"runtime"
	"testing"
	"time"
)

func TestStdTemplate(t *testing.T) {
	var buf bytes.Buffer

	SetLevel(LEVEL_DEBUG)
	SetStreams(&buf)

	SetFlags(LdebugFlags)

	SetTemplate("{{.Text}}")
	temp := Template()

	type test struct {
		Text string
	}

	err := temp.Execute(&buf, &test{"Hello, World!"})
	if err != nil {
		t.Fatal(err)
	}

	expe := "Hello, World!"

	if buf.String() != expe {
		t.Errorf("\nGot:\t%s\nExpect:\t%s\n", buf.String(), expe)
	}
}

func TestStdSetTemplate(t *testing.T) {
	var buf bytes.Buffer

	SetLevel(LEVEL_DEBUG)
	SetStreams(&buf)

	SetFlags(LdebugFlags)

	SetTemplate("{{.Text}}")

	Debugln("Hello, World!")

	expe := "Hello, World!\n"

	if buf.String() != expe {
		t.Errorf("\nGot:\t%q\nExpect:\t%q\n", buf.String(), expe)
	}
}

func TestStdSetTemplateBad(t *testing.T) {
	var buf bytes.Buffer

	SetLevel(LEVEL_DEBUG)
	SetStreams(&buf)

	SetFlags(LdebugFlags)

	err := SetTemplate("{{.Text")

	Debugln("template: default:1: unclosed action")

	expe := "template: default:1: unclosed action"

	if err.Error() != expe {
		t.Errorf("\nGot:\t%q\nExpect:\t%q\n", buf.String(), expe)
	}
}

func TestStdSetTemplateBadDataObjectPanic(t *testing.T) {
	var buf bytes.Buffer

	SetLevel(LEVEL_DEBUG)

	SetStreams(&buf)

	SetFlags(Lindent)

	SetIndent(1)

	type test struct {
		Test string
	}

	err := SetTemplate("{{.Tes}}")
	if err != nil {
		t.Fatal(err)
	}

	defer func() {
		if r := recover(); r == nil {
			t.Errorf("\nGot:\t%q\nExpect:\tPANIC\n", buf.String())
		}
	}()

	Debugln("Hello, World!")

	// Reset the standard logging object
	SetTemplate(logFmt)
	SetIndent(0)
}

func TestStdDateFormat(t *testing.T) {
	dateFormat := DateFormat()

	expect := "Mon Jan 02 15:04:05 MST 2006"

	if dateFormat != expect {
		t.Errorf("\nGot:\t%q\nExpect:\t%q\n", dateFormat, expect)
	}
}

func TestStdSetDateFormat(t *testing.T) {
	var buf bytes.Buffer

	SetLevel(LEVEL_PRINT)

	SetStreams(&buf)

	SetFlags(Ldate)

	SetDateFormat("20060102-15:04:05")

	SetTemplate("{{.Date}}")

	Debugln("Hello")

	expect := time.Now().Format(DateFormat())

	if buf.String() != expect {
		t.Errorf("\nGot:\t%q\nExpect:\t%q\n", buf.String(), expect)
	}

	// Reset the standard logging object
	SetTemplate(logFmt)
}

func TestStdFlags(t *testing.T) {
	SetFlags(LstdFlags)

	flags := Flags()

	expect := LstdFlags

	if flags != expect {
		t.Errorf("\nGot:\t%#v\nExpect:\t%#v\n", flags, expect)
	}
}

func TestStdLevel(t *testing.T) {
	SetLevel(LEVEL_DEBUG)

	level := Level()

	expect := "LEVEL_DEBUG"

	if level.String() != expect {
		t.Errorf("\nGot:\t%#v\nExpect:\t%#v\n", level, expect)
	}
}

func TestStdPrefix(t *testing.T) {
	SetPrefix("TEST::")

	prefix := Prefix()

	expect := "TEST::"

	if prefix != expect {
		t.Errorf("\nGot:\t%#v\nExpect:\t%#v\n", prefix, expect)
	}
}

func TestStdStreams(t *testing.T) {
	var buf bytes.Buffer

	SetStreams(&buf)

	bufT := Streams()

	if &buf != bufT[0] {
		t.Errorf("\nGot:\t%p\nExpect:\t%p\n", &buf, bufT[0])
	}
}

func TestStdIndent(t *testing.T) {
	var buf bytes.Buffer

	SetStreams(&buf)

	SetLevel(LEVEL_DEBUG)

	SetFlags(Lindent | Llabel)

	SetIndent(0).Debugln("Test 1")
	SetIndent(2).Debugln("Test 2")

	indent := Indent()

	expe := "[DEBG] Test 1\n[DEBG]         Test 2\n"
	expi := 2

	if buf.String() != expe {
		t.Errorf("\nGot:\n\n%s\n%q\n\nExpect:\n\n%s\n%q\n\n",
			buf.String(), buf.String(), expe, expe)
	}

	if indent != expi {
		t.Errorf("\nGot:\t%d\nExpect:\t%d\n", indent, expi)
	}
}

func TestStdTabStop(t *testing.T) {
	var buf bytes.Buffer

	SetStreams(&buf)

	SetLevel(LEVEL_DEBUG)

	SetFlags(Lindent | Llabel)

	// This SetIndent doesn't have to be on a separate line, but for some
	// reason go test cover wasn't registering its usage when the functions
	// below were chained together.
	SetIndent(1)
	SetTabStop(2).Debugln("Test 1")

	SetIndent(2)
	SetTabStop(4).Debugln("Test 2")

	tabStop := TabStop()

	expe := "[DEBG]   Test 1\n[DEBG]         Test 2\n"
	expt := 4

	if buf.String() != expe {
		t.Errorf("\nGot:\n\n%s\n%q\n\nExpect:\n\n%s\n%q\n\n",
			buf.String(), buf.String(), expe, expe)
	}

	if tabStop != expt {
		t.Errorf("\nGot:\t%d\nExpect:\t%d\n", tabStop, expt)
	}
}

var stdOutputTests = []struct {
	name   string
	format string
	input  string
	expect string
}{
	{name: "Test 1", format: "%s", input: "Hello, world!", expect: "Hello, world!"},
}

func TestStdOutput(t *testing.T) {
	var buf bytes.Buffer

	SetStreams(&buf)

	SetLevel(LEVEL_DEBUG)

	SetFlags(Llabel)

	SetIndent(0)

	for _, test := range stdOutputTests {

		check := func(output, expect, funcName string) {
			if output != expect {
				t.Errorf("\nName: %q\nFunction: %s\nGot: %q\nExpect: %q\n",
					test.name, funcName, output, expect)
			}
		}

		checkOutput := func(pFunc func(...interface{}), lvl string) {
			nl := ""
			pFunc(test.input)
			label := LevelFromString(lvl).Label()
			if len(label) > 1 {
				label = label + " "
			}
			fName := runtime.FuncForPC(reflect.ValueOf(pFunc).Pointer()).Name()
			lenfName := len(fName)
			if fName[lenfName-2:] == "ln" {
				nl = "\n"
			}
			check(buf.String(), label+test.expect+nl, fName)
			buf.Reset()
		}

		checkFormatOutput := func(pFunc func(string, ...interface{}), lvl string) {
			nl := ""
			pFunc(test.format, test.input)
			label := LevelFromString(lvl).Label()
			if len(label) > 1 {
				label = label + " "
			}
			fName := runtime.FuncForPC(reflect.ValueOf(pFunc).Pointer()).Name()
			lenfName := len(fName)
			if fName[lenfName-2:] == "ln" {
				nl = "\n"
			}
			check(buf.String(), label+test.expect+nl, fName)
			buf.Reset()
		}

		checkOutput(Print, "PRINT")
		checkOutput(Println, "PRINT")
		checkFormatOutput(Printf, "PRINT")
		checkOutput(Debug, "DEBUG")
		checkOutput(Debugln, "DEBUG")
		checkFormatOutput(Debugf, "DEBUG")
		checkOutput(Info, "INFO")
		checkOutput(Infoln, "INFO")
		checkFormatOutput(Infof, "INFO")
		checkOutput(Warning, "WARNING")
		checkOutput(Warningln, "WARNING")
		checkFormatOutput(Warningf, "WARNING")
		checkOutput(Error, "ERROR")
		checkOutput(Errorln, "ERROR")
		checkFormatOutput(Errorf, "ERROR")
		checkOutput(Critical, "CRITICAL")
		checkOutput(Criticalln, "CRITICAL")
		checkFormatOutput(Criticalf, "CRITICAL")

	}
}

func TestStdPanic(t *testing.T) {
	var buf bytes.Buffer

	SetStreams(&buf)

	SetLevel(LEVEL_DEBUG)

	SetFlags(Llabel)

	SetIndent(0)

	expect := "[CRIT] Panic Error!"

	defer func() {
		if r := recover(); r == nil {
			t.Errorf("Test should generate panic!")
		}
		if buf.String() != expect {
			t.Errorf("\nGot:\t%q\nExpect:\t%q\n", buf.String(), expect)
		}
	}()

	Panic("Panic Error!")
}

func TestStdPanicln(t *testing.T) {
	var buf bytes.Buffer

	SetStreams(&buf)

	SetLevel(LEVEL_DEBUG)

	SetFlags(Llabel)

	SetIndent(0)

	expect := "[CRIT] Panic Error!\n"

	defer func() {
		if r := recover(); r == nil {
			t.Errorf("Test should generate panic!")
		}
		if buf.String() != expect {
			t.Errorf("\nGot:\t%q\nExpect:\t%q\n", buf.String(), expect)
		}
	}()

	Panicln("Panic Error!")
}

func TestStdPanicf(t *testing.T) {
	var buf bytes.Buffer

	SetStreams(&buf)

	SetLevel(LEVEL_DEBUG)

	SetFlags(Llabel)

	SetIndent(0)

	expect := "[CRIT] Panic Error!\n"

	defer func() {
		if r := recover(); r == nil {
			t.Errorf("Test should generate panic!")
		}
		if buf.String() != expect {
			t.Errorf("\nGot:\t%q\nExpect:\t%q\n", buf.String(), expect)
		}
	}()

	Panicf("%s\n", "Panic Error!")
}
<commit_msg>Add TestStdExcludeByHeirarchyID()<commit_after>// Copyright 2013,2014 The go-logger Authors. All rights reserved.
// This code is MIT licensed. See the LICENSE file for more info.

// Tests for the default standard logging object

package log

import (
	"bytes"
	"reflect"
	"runtime"
	"testing"
	"time"
)

func TestStdTemplate(t *testing.T) {
	var buf bytes.Buffer

	SetLevel(LEVEL_DEBUG)
	SetStreams(&buf)

	SetFlags(LdebugFlags)

	SetTemplate("{{.Text}}")
	temp := Template()

	type test struct {
		Text string
	}

	err := temp.Execute(&buf, &test{"Hello, World!"})
	if err != nil {
		t.Fatal(err)
	}

	expe := "Hello, World!"

	if buf.String() != expe {
		t.Errorf("\nGot:\t%s\nExpect:\t%s\n", buf.String(), expe)
	}
}

func TestStdSetTemplate(t *testing.T) {
	var buf bytes.Buffer

	SetLevel(LEVEL_DEBUG)
	SetStreams(&buf)

	SetFlags(LdebugFlags)

	SetTemplate("{{.Text}}")

	Debugln("Hello, World!")

	expe := "Hello, World!\n"

	if buf.String() != expe {
		t.Errorf("\nGot:\t%q\nExpect:\t%q\n", buf.String(), expe)
	}
}

func TestStdSetTemplateBad(t *testing.T) {
	var buf bytes.Buffer

	SetLevel(LEVEL_DEBUG)
	SetStreams(&buf)

	SetFlags(LdebugFlags)

	err := SetTemplate("{{.Text")

	Debugln("template: default:1: unclosed action")

	expe := "template: default:1: unclosed action"

	if err.Error() != expe {
		t.Errorf("\nGot:\t%q\nExpect:\t%q\n", buf.String(), expe)
	}
}

func TestStdSetTemplateBadDataObjectPanic(t *testing.T) {
	var buf bytes.Buffer

	SetLevel(LEVEL_DEBUG)

	SetStreams(&buf)

	SetFlags(Lindent)

	SetIndent(1)

	type test struct {
		Test string
	}

	err := SetTemplate("{{.Tes}}")
	if err != nil {
		t.Fatal(err)
	}

	defer func() {
		if r := recover(); r == nil {
			t.Errorf("\nGot:\t%q\nExpect:\tPANIC\n", buf.String())
		}
	}()

	Debugln("Hello, World!")

	// Reset the standard logging object
	SetTemplate(logFmt)
	SetIndent(0)
}

func TestStdDateFormat(t *testing.T) {
	dateFormat := DateFormat()

	expect := "Mon Jan 02 15:04:05 MST 2006"

	if dateFormat != expect {
		t.Errorf("\nGot:\t%q\nExpect:\t%q\n", dateFormat, expect)
	}
}

func TestStdSetDateFormat(t *testing.T) {
	var buf bytes.Buffer

	SetLevel(LEVEL_PRINT)

	SetStreams(&buf)

	SetFlags(Ldate)

	SetDateFormat("20060102-15:04:05")

	SetTemplate("{{.Date}}")

	Debugln("Hello")

	expect := time.Now().Format(DateFormat())

	if buf.String() != expect {
		t.Errorf("\nGot:\t%q\nExpect:\t%q\n", buf.String(), expect)
	}

	// Reset the standard logging object
	SetTemplate(logFmt)
}

func TestStdFlags(t *testing.T) {
	SetFlags(LstdFlags)

	flags := Flags()

	expect := LstdFlags

	if flags != expect {
		t.Errorf("\nGot:\t%#v\nExpect:\t%#v\n", flags, expect)
	}
}

func TestStdLevel(t *testing.T) {
	SetLevel(LEVEL_DEBUG)

	level := Level()

	expect := "LEVEL_DEBUG"

	if level.String() != expect {
		t.Errorf("\nGot:\t%#v\nExpect:\t%#v\n", level, expect)
	}
}

func TestStdPrefix(t *testing.T) {
	SetPrefix("TEST::")

	prefix := Prefix()

	expect := "TEST::"

	if prefix != expect {
		t.Errorf("\nGot:\t%#v\nExpect:\t%#v\n", prefix, expect)
	}
}

func TestStdStreams(t *testing.T) {
	var buf bytes.Buffer

	SetStreams(&buf)

	bufT := Streams()

	if &buf != bufT[0] {
		t.Errorf("\nGot:\t%p\nExpect:\t%p\n", &buf, bufT[0])
	}
}

func TestStdIndent(t *testing.T) {
	var buf bytes.Buffer

	SetStreams(&buf)

	SetLevel(LEVEL_DEBUG)

	SetFlags(Lindent | Llabel)

	SetIndent(0).Debugln("Test 1")
	SetIndent(2).Debugln("Test 2")

	indent := Indent()

	expe := "[DEBG] Test 1\n[DEBG]         Test 2\n"
	expi := 2

	if buf.String() != expe {
		t.Errorf("\nGot:\n\n%s\n%q\n\nExpect:\n\n%s\n%q\n\n",
			buf.String(), buf.String(), expe, expe)
	}

	if indent != expi {
		t.Errorf("\nGot:\t%d\nExpect:\t%d\n", indent, expi)
	}
}

func TestStdTabStop(t *testing.T) {
	var buf bytes.Buffer

	SetStreams(&buf)

	SetLevel(LEVEL_DEBUG)

	SetFlags(Lindent | Llabel)

	// This SetIndent doesn't have to be on a separate line, but for some
	// reason go test cover wasn't registering its usage when the functions
	// below were chained together.
	SetIndent(1)
	SetTabStop(2).Debugln("Test 1")

	SetIndent(2)
	SetTabStop(4).Debugln("Test 2")

	tabStop := TabStop()

	expe := "[DEBG]   Test 1\n[DEBG]         Test 2\n"
	expt := 4

	if buf.String() != expe {
		t.Errorf("\nGot:\n\n%s\n%q\n\nExpect:\n\n%s\n%q\n\n",
			buf.String(), buf.String(), expe, expe)
	}

	if tabStop != expt {
		t.Errorf("\nGot:\t%d\nExpect:\t%d\n", tabStop, expt)
	}
}

var stdOutputTests = []struct {
	name   string
	format string
	input  string
	expect string
}{
	{name: "Test 1", format: "%s", input: "Hello, world!", expect: "Hello, world!"},
}

func TestStdOutput(t *testing.T) {
	var buf bytes.Buffer

	SetStreams(&buf)

	SetLevel(LEVEL_DEBUG)

	SetFlags(Llabel)

	SetIndent(0)

	for _, test := range stdOutputTests {

		check := func(output, expect, funcName string) {
			if output != expect {
				t.Errorf("\nName: %q\nFunction: %s\nGot: %q\nExpect: %q\n",
					test.name, funcName, output, expect)
			}
		}

		checkOutput := func(pFunc func(...interface{}), lvl string) {
			nl := ""
			pFunc(test.input)
			label := LevelFromString(lvl).Label()
			if len(label) > 1 {
				label = label + " "
			}
			fName := runtime.FuncForPC(reflect.ValueOf(pFunc).Pointer()).Name()
			lenfName := len(fName)
			if fName[lenfName-2:] == "ln" {
				nl = "\n"
			}
			check(buf.String(), label+test.expect+nl, fName)
			buf.Reset()
		}

		checkFormatOutput := func(pFunc func(string, ...interface{}), lvl string) {
			nl := ""
			pFunc(test.format, test.input)
			label := LevelFromString(lvl).Label()
			if len(label) > 1 {
				label = label + " "
			}
			fName := runtime.FuncForPC(reflect.ValueOf(pFunc).Pointer()).Name()
			lenfName := len(fName)
			if fName[lenfName-2:] == "ln" {
				nl = "\n"
			}
			check(buf.String(), label+test.expect+nl, fName)
			buf.Reset()
		}

		checkOutput(Print, "PRINT")
		checkOutput(Println, "PRINT")
		checkFormatOutput(Printf, "PRINT")
		checkOutput(Debug, "DEBUG")
		checkOutput(Debugln, "DEBUG")
		checkFormatOutput(Debugf, "DEBUG")
		checkOutput(Info, "INFO")
		checkOutput(Infoln, "INFO")
		checkFormatOutput(Infof, "INFO")
		checkOutput(Warning, "WARNING")
		checkOutput(Warningln, "WARNING")
		checkFormatOutput(Warningf, "WARNING")
		checkOutput(Error, "ERROR")
		checkOutput(Errorln, "ERROR")
		checkFormatOutput(Errorf, "ERROR")
		checkOutput(Critical, "CRITICAL")
		checkOutput(Criticalln, "CRITICAL")
		checkFormatOutput(Criticalf, "CRITICAL")

	}
}

func TestStdPanic(t *testing.T) {
	var buf bytes.Buffer

	SetStreams(&buf)

	SetLevel(LEVEL_DEBUG)

	SetFlags(Llabel)

	SetIndent(0)

	expect := "[CRIT] Panic Error!"

	defer func() {
		if r := recover(); r == nil {
			t.Errorf("Test should generate panic!")
		}
		if buf.String() != expect {
			t.Errorf("\nGot:\t%q\nExpect:\t%q\n", buf.String(), expect)
		}
	}()

	Panic("Panic Error!")
}

func TestStdPanicln(t *testing.T) {
	var buf bytes.Buffer

	SetStreams(&buf)

	SetLevel(LEVEL_DEBUG)

	SetFlags(Llabel)

	SetIndent(0)

	expect := "[CRIT] Panic Error!\n"

	defer func() {
		if r := recover(); r == nil {
			t.Errorf("Test should generate panic!")
		}
		if buf.String() != expect {
			t.Errorf("\nGot:\t%q\nExpect:\t%q\n", buf.String(), expect)
		}
	}()

	Panicln("Panic Error!")
}

func TestStdPanicf(t *testing.T) {
	var buf bytes.Buffer

	SetStreams(&buf)

	SetLevel(LEVEL_DEBUG)

	SetFlags(Llabel)

	SetIndent(0)

	expect := "[CRIT] Panic Error!\n"

	defer func() {
		if r := recover(); r == nil {
			t.Errorf("Test should generate panic!")
		}
		if buf.String() != expect {
			t.Errorf("\nGot:\t%q\nExpect:\t%q\n", buf.String(), expect)
		}
	}()

	Panicf("%s\n", "Panic Error!")
}

func TestStdExcludeByHeirarchyID(t *testing.T) {
	var buf bytes.Buffer

	// excludeIDtests is defined in logger_test.go
	for _, test := range excludeIDtests {
		SetLevel(LEVEL_DEBUG)

		SetStreams(&buf)

		SetFlags(test.flags)

		ExcludeByHeirarchyID(test.ids...)

		Debugln("Hello!")
		lvl3 := func() {
			Debugln("Almost forgot...")
		}
		lvl2 := func() {
			Debugln("should be suppressed.")
			lvl3()
			Debugln("but we'll find out!")
		}
		lvl1 := func() {
			Debugln("The things")
			lvl2()
			Debugln("that can be suppressed.")
		}
		lvl1()
		Debugln("Goodbye!")

		if buf.String() != test.expect {
			t.Errorf("\nTest: %s\n\nGot:\n\n%s\n%q\n"+
				"\nExpect:\n\n%s\n%q\n\n",
				test.name, buf.String(), buf.String(),
				test.expect, test.expect)
		}
		buf.Reset()
	}
}
<|endoftext|>


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

// +build debuglog

package logging

import (
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"sync/atomic"
)

func trimSuffix(s, suffix string) (result string, trimmed bool) {
	return strings.TrimSuffix(s, suffix), strings.HasSuffix(s, suffix)
}

func trimPrefix(s, prefix string) (result string, trimmed bool) {
	return strings.TrimPrefix(s, prefix), strings.HasPrefix(s, prefix)
}

func construct() {
	pc, file, _, ok := runtime.Caller(0)
	if !ok {
		return
	}
	fnName := runtime.FuncForPC(pc).Name()
	if pkg, ok := trimSuffix(fnName, "/logging.construct"); ok {
		baristaPkg = pkg
		goSrcRoot, _ = trimSuffix(file, pkg+"/logging/logging.go")
	}
	logger = log.New(os.Stderr, "", 0)
	SetFlags(log.LstdFlags | log.Lshortfile)
	for _, arg := range os.Args {
		if mods, ok := trimPrefix(arg, "--finelog="); ok {
			fineLogModules = append(fineLogModules, strings.Split(mods, ",")...)
		}
	}
}

func init() {
	// Cannot call construct init because runtime.Caller(0) behaves differently
	// in init functions than it does in other named functions.
	construct()
}

var baristaPkg = "#unknown#"
var goSrcRoot = os.Getenv("GOPATH")

// shorten shortens a package/function/type for logging. It removes the full path
// to barista packages, and simplifies functions that have a receiver.
func shorten(path string) string {
	// If path is a function, it can be something like some/package.(*Type).fn,
	// but we'll simplify it to some/package.Type.fn for logging.
	path = strings.Replace(path, "*", "", -1)
	path = strings.Replace(path, "(", "", -1)
	path = strings.Replace(path, ")", "", -1)

	if module, ok := trimPrefix(path, baristaPkg+"/modules/"); ok {
		return fmt.Sprintf("mod:%s", module)
	}
	if core, ok := trimPrefix(path, baristaPkg+"/"); ok {
		return fmt.Sprintf("bar:%s", core)
	}
	if core, ok := trimPrefix(path, baristaPkg+"."); ok {
		return fmt.Sprintf("barista:%s", core)
	}
	return path
}

var fineLogModules = []string{}
var fineLogModulesCache sync.Map

// fineLogEnabled returns true if finelog is enabled for the module.
// It caches results in a sync.Map so subsequent lookups can be faster.
func fineLogEnabled(mod string) bool {
	cache, ok := fineLogModulesCache.Load(mod)
	if ok {
		return cache.(bool)
	}
	for _, fineMod := range fineLogModules {
		if strings.HasPrefix(mod, fineMod) {
			fineLogModulesCache.Store(mod, true)
			return true
		}
	}
	fineLogModulesCache.Store(mod, false)
	return false
}

// callingModule returns the calling module's name and source location.
// The name is prefixed based on origin:
//     - mod:$module for modules included with barista (e.g. mod:cpuinfo)
//     - bar:$core for core barista code (e.g. bar:notifier, bar:base)
//     - ext:$package for external code (e.g. ext:github.com/user/repo/module)
// The source location is empty if neither shortfile nor longfile flags are set,
// otherwise it is the appropriately formatted file name, ":", and line number.
func callingModule() (mod string, loc string) {
	pc, file, line, ok := runtime.Caller(2)
	fFlags := int(atomic.LoadInt64(&fileFlags))
	if fFlags != 0 {
		file, _ = trimPrefix(file, goSrcRoot)
		if fFlags&log.Lshortfile != 0 {
			file = filepath.Base(file)
		}
		loc = fmt.Sprintf("%s:%d", file, line)
	}
	if !ok {
		return "unknown", loc
	}
	fnName := runtime.FuncForPC(pc).Name()
	return shorten(fnName), loc
}

var fileFlags int64
var logger *log.Logger

// doLog actually logs the given statement, with appropriate file information
// depending on the currently set flags.
func doLog(mod, loc string, format string, args ...interface{}) {
	out := fmt.Sprintf(format, args...)
	fFlags := int(atomic.LoadInt64(&fileFlags))
	if fFlags != 0 {
		out = fmt.Sprintf("%s (%s) %s", loc, mod, out)
	}
	logger.Output(3, out)
}

// SetOutput sets the output stream for logging.
func SetOutput(output io.Writer) {
	logger.SetOutput(output)
}

// SetFlags sets flags to control logging output.
func SetFlags(flags int) {
	fFlags := flags & (log.Llongfile | log.Lshortfile)
	atomic.StoreInt64(&fileFlags, int64(fFlags))
	logger.SetFlags(flags &^ fFlags)
}

// Log logs a formatted message.
func Log(format string, args ...interface{}) {
	mod, loc := callingModule()
	doLog(mod, loc, format, args...)
}

// Fine logs a formatted message if fine logging is enabled for the
// calling module. Enable fine logging using the commandline flag,
// `--finelog=$module1,$module2`. [Requires debug logging].
func Fine(format string, args ...interface{}) {
	mod, loc := callingModule()
	if fineLogEnabled(mod) {
		doLog(mod, loc, format, args...)
	}
}
<commit_msg>Accept -finelog in addition to --finelog.<commit_after>// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build debuglog

package logging

import (
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"sync/atomic"
)

func trimSuffix(s, suffix string) (result string, trimmed bool) {
	return strings.TrimSuffix(s, suffix), strings.HasSuffix(s, suffix)
}

func trimPrefix(s, prefix string) (result string, trimmed bool) {
	return strings.TrimPrefix(s, prefix), strings.HasPrefix(s, prefix)
}

func construct() {
	pc, file, _, ok := runtime.Caller(0)
	if !ok {
		return
	}
	fnName := runtime.FuncForPC(pc).Name()
	if pkg, ok := trimSuffix(fnName, "/logging.construct"); ok {
		baristaPkg = pkg
		goSrcRoot, _ = trimSuffix(file, pkg+"/logging/logging.go")
	}
	logger = log.New(os.Stderr, "", 0)
	SetFlags(log.LstdFlags | log.Lshortfile)
	for _, arg := range os.Args {
		if mods, ok := trimPrefix(arg, "--finelog="); ok {
			fineLogModules = append(fineLogModules, strings.Split(mods, ",")...)
		}
		if mods, ok := trimPrefix(arg, "-finelog="); ok {
			fineLogModules = append(fineLogModules, strings.Split(mods, ",")...)
		}
	}
}

func init() {
	// Cannot call construct init because runtime.Caller(0) behaves differently
	// in init functions than it does in other named functions.
	construct()
}

var baristaPkg = "#unknown#"
var goSrcRoot = os.Getenv("GOPATH")

// shorten shortens a package/function/type for logging. It removes the full path
// to barista packages, and simplifies functions that have a receiver.
func shorten(path string) string {
	// If path is a function, it can be something like some/package.(*Type).fn,
	// but we'll simplify it to some/package.Type.fn for logging.
	path = strings.Replace(path, "*", "", -1)
	path = strings.Replace(path, "(", "", -1)
	path = strings.Replace(path, ")", "", -1)

	if module, ok := trimPrefix(path, baristaPkg+"/modules/"); ok {
		return fmt.Sprintf("mod:%s", module)
	}
	if core, ok := trimPrefix(path, baristaPkg+"/"); ok {
		return fmt.Sprintf("bar:%s", core)
	}
	if core, ok := trimPrefix(path, baristaPkg+"."); ok {
		return fmt.Sprintf("barista:%s", core)
	}
	return path
}

var fineLogModules = []string{}
var fineLogModulesCache sync.Map

// fineLogEnabled returns true if finelog is enabled for the module.
// It caches results in a sync.Map so subsequent lookups can be faster.
func fineLogEnabled(mod string) bool {
	cache, ok := fineLogModulesCache.Load(mod)
	if ok {
		return cache.(bool)
	}
	for _, fineMod := range fineLogModules {
		if strings.HasPrefix(mod, fineMod) {
			fineLogModulesCache.Store(mod, true)
			return true
		}
	}
	fineLogModulesCache.Store(mod, false)
	return false
}

// callingModule returns the calling module's name and source location.
// The name is prefixed based on origin:
//     - mod:$module for modules included with barista (e.g. mod:cpuinfo)
//     - bar:$core for core barista code (e.g. bar:notifier, bar:base)
//     - ext:$package for external code (e.g. ext:github.com/user/repo/module)
// The source location is empty if neither shortfile nor longfile flags are set,
// otherwise it is the appropriately formatted file name, ":", and line number.
func callingModule() (mod string, loc string) {
	pc, file, line, ok := runtime.Caller(2)
	fFlags := int(atomic.LoadInt64(&fileFlags))
	if fFlags != 0 {
		file, _ = trimPrefix(file, goSrcRoot)
		if fFlags&log.Lshortfile != 0 {
			file = filepath.Base(file)
		}
		loc = fmt.Sprintf("%s:%d", file, line)
	}
	if !ok {
		return "unknown", loc
	}
	fnName := runtime.FuncForPC(pc).Name()
	return shorten(fnName), loc
}

var fileFlags int64
var logger *log.Logger

// doLog actually logs the given statement, with appropriate file information
// depending on the currently set flags.
func doLog(mod, loc string, format string, args ...interface{}) {
	out := fmt.Sprintf(format, args...)
	fFlags := int(atomic.LoadInt64(&fileFlags))
	if fFlags != 0 {
		out = fmt.Sprintf("%s (%s) %s", loc, mod, out)
	}
	logger.Output(3, out)
}

// SetOutput sets the output stream for logging.
func SetOutput(output io.Writer) {
	logger.SetOutput(output)
}

// SetFlags sets flags to control logging output.
func SetFlags(flags int) {
	fFlags := flags & (log.Llongfile | log.Lshortfile)
	atomic.StoreInt64(&fileFlags, int64(fFlags))
	logger.SetFlags(flags &^ fFlags)
}

// Log logs a formatted message.
func Log(format string, args ...interface{}) {
	mod, loc := callingModule()
	doLog(mod, loc, format, args...)
}

// Fine logs a formatted message if fine logging is enabled for the
// calling module. Enable fine logging using the commandline flag,
// `--finelog=$module1,$module2`. [Requires debug logging].
func Fine(format string, args ...interface{}) {
	mod, loc := callingModule()
	if fineLogEnabled(mod) {
		doLog(mod, loc, format, args...)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"strconv"
	"time"
)

func (c *Controller) HistoricalLogs(deploymentSlug, search, exclude, regexp string) {
	logLimit := 200

	var first time.Time
	var last time.Time

	var command string

	for command != "exit" && command != "e" {
		var historicalLogs []HistoricalLog
		var hostLength int
		var err error

		if command == "n" || command == "next" {
			historicalLogs, hostLength, err = c.Api.GetHistoricalLogs(deploymentSlug, search, exclude, regexp, logLimit, &last, nil)
		} else if command == "p" || command == "previous" {
			historicalLogs, hostLength, err = c.Api.GetHistoricalLogs(deploymentSlug, search, exclude, regexp, logLimit, nil, &first)
		} else if command == "" {
			historicalLogs, hostLength, err = c.Api.GetHistoricalLogs(deploymentSlug, search, exclude, regexp, logLimit, nil, nil)
		} else {
			fmt.Print(command + " is an unknown option.  Please type (n)ext, (p)revious, or (e)xit.")
			continue
		}

		if err != nil {
			fmt.Println("Error retrieving logs: " + err.Error())
			os.Exit(1)
		} else {
			if len(historicalLogs) == 0 {
				fmt.Println("No logs matching query.")
			} else {
				first, last = renderLogs(historicalLogs, hostLength)
			}
		}

		command = prompt("(n)ext (p)revious (e)xit >")
	}
}

func renderLogs(historicalLogs []HistoricalLog, hostLength int) (time.Time, time.Time) {
	var last HistoricalLog

	first := historicalLogs[0]
	for _, log := range historicalLogs {
		last = log
		fmt.Println(fmt.Sprintf("%-"+strconv.Itoa(hostLength+2)+"s%s", formatHostname(log.Host), log.Message))
	}

	return first.Timestamp.Add(time.Millisecond * -1), last.Timestamp.Add(time.Millisecond)
}
<commit_msg>Fix crazy looper<commit_after>package main

import (
	"fmt"
	"os"
	"strconv"
	"time"
)

func (c *Controller) HistoricalLogs(deploymentSlug, search, exclude, regexp string) {
	logLimit := 200

	var first time.Time
	var last time.Time

	var command string

	for command != "exit" && command != "e" {
		var historicalLogs []HistoricalLog
		var hostLength int
		var err error

		if command == "n" || command == "next" {
			historicalLogs, hostLength, err = c.Api.GetHistoricalLogs(deploymentSlug, search, exclude, regexp, logLimit, &last, nil)
		} else if command == "p" || command == "previous" {
			historicalLogs, hostLength, err = c.Api.GetHistoricalLogs(deploymentSlug, search, exclude, regexp, logLimit, nil, &first)
		} else if command == "" {
			historicalLogs, hostLength, err = c.Api.GetHistoricalLogs(deploymentSlug, search, exclude, regexp, logLimit, nil, nil)
		} else {
			fmt.Print(command + " is an unknown option.  Please type (n)ext, (p)revious, or (e)xit.")
			command = prompt("(n)ext (p)revious (e)xit >")
			continue
		}

		if err != nil {
			fmt.Println("Error retrieving logs: " + err.Error())
			os.Exit(1)
		} else {
			if len(historicalLogs) == 0 {
				fmt.Println("No logs matching query.")
			} else {
				first, last = renderLogs(historicalLogs, hostLength)
			}
		}

		command = prompt("(n)ext (p)revious (e)xit >")
	}
}

func renderLogs(historicalLogs []HistoricalLog, hostLength int) (time.Time, time.Time) {
	var last HistoricalLog

	first := historicalLogs[0]
	for _, log := range historicalLogs {
		last = log
		fmt.Println(fmt.Sprintf("%-"+strconv.Itoa(hostLength+2)+"s%s", formatHostname(log.Host), log.Message))
	}

	return first.Timestamp.Add(time.Millisecond * -1), last.Timestamp.Add(time.Millisecond)
}
<|endoftext|>