<commit_before>package main

import (
	"crypto/rand"
	"database/sql"
	"encoding/hex"
	"fmt"
	"io"
	"os/exec"
	"strconv"
	"strings"
	"sync"

	"golang.org/x/crypto/scrypt"
	log "gopkg.in/inconshreveable/log15.v2"

	dbapi "github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/logger"
)

var daemonConfigLock sync.Mutex
var daemonConfig map[string]*daemonConfigKey

type daemonConfigKey struct {
	valueType    string
	defaultValue string
	validValues  []string
	currentValue string
	hiddenValue  bool

	validator func(d *Daemon, key string, value string) error
	setter    func(d *Daemon, key string, value string) (string, error)
	trigger   func(d *Daemon, key string, value string)
}

func (k *daemonConfigKey) name() string {
	name := ""

	// Look for a matching entry in daemonConfig
	daemonConfigLock.Lock()
	for key, value := range daemonConfig {
		if value == k {
			name = key
			break
		}
	}
	daemonConfigLock.Unlock()

	return name
}

func (k *daemonConfigKey) Validate(d *Daemon, value string) error {
	// Handle unsetting
	if value == "" {
		value = k.defaultValue

		if k.validator != nil {
			err := k.validator(d, k.name(), value)
			if err != nil {
				return err
			}
		}

		return nil
	}

	// Validate booleans
	if k.valueType == "bool" && !shared.StringInSlice(strings.ToLower(value), []string{"true", "false", "1", "0", "yes", "no", "on", "off"}) {
		return fmt.Errorf("Invalid value for a boolean: %s", value)
	}

	// Validate integers
	if k.valueType == "int" {
		_, err := strconv.ParseInt(value, 10, 64)
		if err != nil {
			return err
		}
	}

	// Check against valid values
	if k.validValues != nil && !shared.StringInSlice(value, k.validValues) {
		return fmt.Errorf("Invalid value, only the following values are allowed: %s", k.validValues)
	}

	// Run external validation function
	if k.validator != nil {
		err := k.validator(d, k.name(), value)
		if err != nil {
			return err
		}
	}

	return nil
}

func (k *daemonConfigKey) Set(d *Daemon, value string) error {
	var name string

	// Check if we are actually changing things
	oldValue := k.currentValue
	if oldValue == value {
		return nil
	}

	// Validate the new value
	err := k.Validate(d, value)
	if err != nil {
		return err
	}

	// Run external setting function
	if k.setter != nil {
		value, err = k.setter(d, k.name(), value)
		if err != nil {
			return err
		}
	}

	// Get the configuration key and make sure daemonConfig is sane
	name = k.name()
	if name == "" {
		return fmt.Errorf("Corrupted configuration cache")
	}

	// Actually apply the change
	daemonConfigLock.Lock()
	k.currentValue = value
	daemonConfigLock.Unlock()

	err = dbapi.ConfigValueSet(d.db, name, value)
	if err != nil {
		return err
	}

	return nil
}

func (k *daemonConfigKey) Get() string {
	value := k.currentValue

	// Get the default value if not set
	if value == "" {
		value = k.defaultValue
	}

	return value
}

func (k *daemonConfigKey) GetBool() bool {
	value := k.currentValue

	// Get the default value if not set
	if value == "" {
		value = k.defaultValue
	}

	// Convert to boolean
	return shared.IsTrue(value)
}

func (k *daemonConfigKey) GetInt64() int64 {
	value := k.currentValue

	// Get the default value if not set
	if value == "" {
		value = k.defaultValue
	}

	// Convert to int64
	ret, _ := strconv.ParseInt(value, 10, 64)
	return ret
}

func daemonConfigInit(db *sql.DB) error {
	// Set all the keys
	daemonConfig = map[string]*daemonConfigKey{
		"core.https_address":             {valueType: "string", setter: daemonConfigSetAddress},
		"core.https_allowed_headers":     {valueType: "string"},
		"core.https_allowed_methods":     {valueType: "string"},
		"core.https_allowed_origin":      {valueType: "string"},
		"core.https_allowed_credentials": {valueType: "bool"},
		"core.proxy_http":                {valueType: "string", setter: daemonConfigSetProxy},
		"core.proxy_https":               {valueType: "string", setter: daemonConfigSetProxy},
		"core.proxy_ignore_hosts":        {valueType: "string", setter: daemonConfigSetProxy},
		"core.trust_password":            {valueType: "string", hiddenValue: true, setter: daemonConfigSetPassword},

		"images.auto_update_cached":    {valueType: "bool", defaultValue: "true"},
		"images.auto_update_interval":  {valueType: "int", defaultValue: "6", trigger: daemonConfigTriggerAutoUpdateInterval},
		"images.compression_algorithm": {valueType: "string", validator: daemonConfigValidateCompression, defaultValue: "gzip"},
		"images.remote_cache_expiry":   {valueType: "int", defaultValue: "10", trigger: daemonConfigTriggerExpiry},

		// Keys deprecated since the implementation of the storage api.
		"storage.lvm_fstype":           {valueType: "string", defaultValue: "ext4", validValues: []string{"btrfs", "ext4", "xfs"}, validator: storageDeprecatedKeys},
		"storage.lvm_mount_options":    {valueType: "string", defaultValue: "discard", validator: storageDeprecatedKeys},
		"storage.lvm_thinpool_name":    {valueType: "string", defaultValue: "LXDPool", validator: storageDeprecatedKeys},
		"storage.lvm_vg_name":          {valueType: "string", validator: storageDeprecatedKeys},
		"storage.lvm_volume_size":      {valueType: "string", defaultValue: "10GiB", validator: storageDeprecatedKeys},
		"storage.zfs_pool_name":        {valueType: "string", validator: storageDeprecatedKeys},
		"storage.zfs_remove_snapshots": {valueType: "bool", validator: storageDeprecatedKeys},
		"storage.zfs_use_refquota":     {valueType: "bool", validator: storageDeprecatedKeys},
	}

	// Load the values from the DB
	dbValues, err := dbapi.ConfigValuesGet(db)
	if err != nil {
		return err
	}

	daemonConfigLock.Lock()
	for k, v := range dbValues {
		_, ok := daemonConfig[k]
		if !ok {
			logger.Error("Found unknown configuration key in database", log.Ctx{"key": k})
			continue
		}

		daemonConfig[k].currentValue = v
	}
	daemonConfigLock.Unlock()

	return nil
}

func daemonConfigRender() map[string]interface{} {
	config := map[string]interface{}{}

	// Turn the config into a JSON-compatible map
	for k, v := range daemonConfig {
		value := v.Get()
		if value != v.defaultValue {
			if v.hiddenValue {
				config[k] = true
			} else {
				config[k] = value
			}
		}
	}

	return config
}

func daemonConfigSetPassword(d *Daemon, key string, 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 daemonConfigSetAddress(d *Daemon, key string, value string) (string, error) {
	// Update the current https address
	err := d.UpdateHTTPsPort(value)
	if err != nil {
		return "", err
	}

	return value, nil
}

func daemonConfigSetProxy(d *Daemon, key string, value string) (string, error) {
	// Get the current config
	config := map[string]string{}
	config["core.proxy_https"] = daemonConfig["core.proxy_https"].Get()
	config["core.proxy_http"] = daemonConfig["core.proxy_http"].Get()
	config["core.proxy_ignore_hosts"] = daemonConfig["core.proxy_ignore_hosts"].Get()

	// Apply the change
	config[key] = value

	// Update the cached proxy function
	d.proxy = shared.ProxyFromConfig(
		config["core.proxy_https"],
		config["core.proxy_http"],
		config["core.proxy_ignore_hosts"],
	)

	// Clear the simplestreams cache as it's tied to the old proxy config
	imageStreamCacheLock.Lock()
	for k := range imageStreamCache {
		delete(imageStreamCache, k)
	}
	imageStreamCacheLock.Unlock()

	return value, nil
}

func daemonConfigTriggerExpiry(d *Daemon, key string, value string) {
	// Trigger an image pruning run
	d.pruneChan <- true
}

func daemonConfigTriggerAutoUpdateInterval(d *Daemon, key string, value string) {
	// Reset the auto-update interval loop
	d.resetAutoUpdateChan <- true
}

func daemonConfigValidateCompression(d *Daemon, key string, value string) error {
	if value == "none" {
		return nil
	}

	_, err := exec.LookPath(value)
	return err
}

func storageDeprecatedKeys(d *Daemon, key string, value string) error {
	if value == "" || daemonConfig[key].defaultValue == value {
		return nil
	}

	return fmt.Errorf("Setting the key \"%s\" is deprecated in favor of storage pool configuration.", key)
}
<commit_msg>daemon: Fix handling of config triggers<commit_after>package main

import (
	"crypto/rand"
	"database/sql"
	"encoding/hex"
	"fmt"
	"io"
	"os/exec"
	"strconv"
	"strings"
	"sync"

	"golang.org/x/crypto/scrypt"
	log "gopkg.in/inconshreveable/log15.v2"

	dbapi "github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/logger"
)

var daemonConfigLock sync.Mutex
var daemonConfig map[string]*daemonConfigKey

type daemonConfigKey struct {
	valueType    string
	defaultValue string
	validValues  []string
	currentValue string
	hiddenValue  bool

	validator func(d *Daemon, key string, value string) error
	setter    func(d *Daemon, key string, value string) (string, error)
	trigger   func(d *Daemon, key string, value string)
}

func (k *daemonConfigKey) name() string {
	name := ""

	// Look for a matching entry in daemonConfig
	daemonConfigLock.Lock()
	for key, value := range daemonConfig {
		if value == k {
			name = key
			break
		}
	}
	daemonConfigLock.Unlock()

	return name
}

func (k *daemonConfigKey) Validate(d *Daemon, value string) error {
	// Handle unsetting
	if value == "" {
		value = k.defaultValue

		if k.validator != nil {
			err := k.validator(d, k.name(), value)
			if err != nil {
				return err
			}
		}

		return nil
	}

	// Validate booleans
	if k.valueType == "bool" && !shared.StringInSlice(strings.ToLower(value), []string{"true", "false", "1", "0", "yes", "no", "on", "off"}) {
		return fmt.Errorf("Invalid value for a boolean: %s", value)
	}

	// Validate integers
	if k.valueType == "int" {
		_, err := strconv.ParseInt(value, 10, 64)
		if err != nil {
			return err
		}
	}

	// Check against valid values
	if k.validValues != nil && !shared.StringInSlice(value, k.validValues) {
		return fmt.Errorf("Invalid value, only the following values are allowed: %s", k.validValues)
	}

	// Run external validation function
	if k.validator != nil {
		err := k.validator(d, k.name(), value)
		if err != nil {
			return err
		}
	}

	return nil
}

func (k *daemonConfigKey) Set(d *Daemon, value string) error {
	var name string

	// Check if we are actually changing things
	oldValue := k.currentValue
	if oldValue == value {
		return nil
	}

	// Validate the new value
	err := k.Validate(d, value)
	if err != nil {
		return err
	}

	// Run external setting function
	if k.setter != nil {
		value, err = k.setter(d, k.name(), value)
		if err != nil {
			return err
		}
	}

	// Get the configuration key and make sure daemonConfig is sane
	name = k.name()
	if name == "" {
		return fmt.Errorf("Corrupted configuration cache")
	}

	// Actually apply the change
	daemonConfigLock.Lock()
	k.currentValue = value
	daemonConfigLock.Unlock()

	err = dbapi.ConfigValueSet(d.db, name, value)
	if err != nil {
		return err
	}

	// Run the trigger (if any)
	if k.trigger != nil {
		k.trigger(d, k.name(), value)
	}

	return nil
}

func (k *daemonConfigKey) Get() string {
	value := k.currentValue

	// Get the default value if not set
	if value == "" {
		value = k.defaultValue
	}

	return value
}

func (k *daemonConfigKey) GetBool() bool {
	value := k.currentValue

	// Get the default value if not set
	if value == "" {
		value = k.defaultValue
	}

	// Convert to boolean
	return shared.IsTrue(value)
}

func (k *daemonConfigKey) GetInt64() int64 {
	value := k.currentValue

	// Get the default value if not set
	if value == "" {
		value = k.defaultValue
	}

	// Convert to int64
	ret, _ := strconv.ParseInt(value, 10, 64)
	return ret
}

func daemonConfigInit(db *sql.DB) error {
	// Set all the keys
	daemonConfig = map[string]*daemonConfigKey{
		"core.https_address":             {valueType: "string", setter: daemonConfigSetAddress},
		"core.https_allowed_headers":     {valueType: "string"},
		"core.https_allowed_methods":     {valueType: "string"},
		"core.https_allowed_origin":      {valueType: "string"},
		"core.https_allowed_credentials": {valueType: "bool"},
		"core.proxy_http":                {valueType: "string", setter: daemonConfigSetProxy},
		"core.proxy_https":               {valueType: "string", setter: daemonConfigSetProxy},
		"core.proxy_ignore_hosts":        {valueType: "string", setter: daemonConfigSetProxy},
		"core.trust_password":            {valueType: "string", hiddenValue: true, setter: daemonConfigSetPassword},

		"images.auto_update_cached":    {valueType: "bool", defaultValue: "true"},
		"images.auto_update_interval":  {valueType: "int", defaultValue: "6", trigger: daemonConfigTriggerAutoUpdateInterval},
		"images.compression_algorithm": {valueType: "string", validator: daemonConfigValidateCompression, defaultValue: "gzip"},
		"images.remote_cache_expiry":   {valueType: "int", defaultValue: "10", trigger: daemonConfigTriggerExpiry},

		// Keys deprecated since the implementation of the storage api.
		"storage.lvm_fstype":           {valueType: "string", defaultValue: "ext4", validValues: []string{"btrfs", "ext4", "xfs"}, validator: storageDeprecatedKeys},
		"storage.lvm_mount_options":    {valueType: "string", defaultValue: "discard", validator: storageDeprecatedKeys},
		"storage.lvm_thinpool_name":    {valueType: "string", defaultValue: "LXDPool", validator: storageDeprecatedKeys},
		"storage.lvm_vg_name":          {valueType: "string", validator: storageDeprecatedKeys},
		"storage.lvm_volume_size":      {valueType: "string", defaultValue: "10GiB", validator: storageDeprecatedKeys},
		"storage.zfs_pool_name":        {valueType: "string", validator: storageDeprecatedKeys},
		"storage.zfs_remove_snapshots": {valueType: "bool", validator: storageDeprecatedKeys},
		"storage.zfs_use_refquota":     {valueType: "bool", validator: storageDeprecatedKeys},
	}

	// Load the values from the DB
	dbValues, err := dbapi.ConfigValuesGet(db)
	if err != nil {
		return err
	}

	daemonConfigLock.Lock()
	for k, v := range dbValues {
		_, ok := daemonConfig[k]
		if !ok {
			logger.Error("Found unknown configuration key in database", log.Ctx{"key": k})
			continue
		}

		daemonConfig[k].currentValue = v
	}
	daemonConfigLock.Unlock()

	return nil
}

func daemonConfigRender() map[string]interface{} {
	config := map[string]interface{}{}

	// Turn the config into a JSON-compatible map
	for k, v := range daemonConfig {
		value := v.Get()
		if value != v.defaultValue {
			if v.hiddenValue {
				config[k] = true
			} else {
				config[k] = value
			}
		}
	}

	return config
}

func daemonConfigSetPassword(d *Daemon, key string, 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 daemonConfigSetAddress(d *Daemon, key string, value string) (string, error) {
	// Update the current https address
	err := d.UpdateHTTPsPort(value)
	if err != nil {
		return "", err
	}

	return value, nil
}

func daemonConfigSetProxy(d *Daemon, key string, value string) (string, error) {
	// Get the current config
	config := map[string]string{}
	config["core.proxy_https"] = daemonConfig["core.proxy_https"].Get()
	config["core.proxy_http"] = daemonConfig["core.proxy_http"].Get()
	config["core.proxy_ignore_hosts"] = daemonConfig["core.proxy_ignore_hosts"].Get()

	// Apply the change
	config[key] = value

	// Update the cached proxy function
	d.proxy = shared.ProxyFromConfig(
		config["core.proxy_https"],
		config["core.proxy_http"],
		config["core.proxy_ignore_hosts"],
	)

	// Clear the simplestreams cache as it's tied to the old proxy config
	imageStreamCacheLock.Lock()
	for k := range imageStreamCache {
		delete(imageStreamCache, k)
	}
	imageStreamCacheLock.Unlock()

	return value, nil
}

func daemonConfigTriggerExpiry(d *Daemon, key string, value string) {
	// Trigger an image pruning run
	d.pruneChan <- true
}

func daemonConfigTriggerAutoUpdateInterval(d *Daemon, key string, value string) {
	// Reset the auto-update interval loop
	d.resetAutoUpdateChan <- true
}

func daemonConfigValidateCompression(d *Daemon, key string, value string) error {
	if value == "none" {
		return nil
	}

	_, err := exec.LookPath(value)
	return err
}

func storageDeprecatedKeys(d *Daemon, key string, value string) error {
	if value == "" || daemonConfig[key].defaultValue == value {
		return nil
	}

	return fmt.Errorf("Setting the key \"%s\" is deprecated in favor of storage pool configuration.", key)
}
<|endoftext|>


<commit_before>package events

import (
	"context"
	"encoding/json"
	"fmt"
	"net"
	"sync"
	"time"

	"github.com/pborman/uuid"

	"github.com/gorilla/websocket"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	log "github.com/lxc/lxd/shared/log15"
	"github.com/lxc/lxd/shared/logger"
)

// Server represents an instance of an event server.
type Server struct {
	debug   bool
	verbose bool

	listeners map[string]*Listener
	lock      sync.Mutex
}

// NewServer returns a new event server.
func NewServer(debug bool, verbose bool) *Server {
	server := &Server{
		debug:     debug,
		verbose:   verbose,
		listeners: map[string]*Listener{},
	}

	return server
}

// AddListener creates and returns a new event listener.
func (s *Server) AddListener(group string, connection *websocket.Conn, messageTypes []string, location string, noForward bool) (*Listener, error) {
	ctx, ctxCancel := context.WithCancel(context.Background())

	listener := &Listener{
		Conn: connection,

		group:        group,
		messageTypes: messageTypes,
		location:     location,
		noForward:    noForward,
		ctx:          ctx,
		ctxCancel:    ctxCancel,
		id:           uuid.New(),
	}

	s.lock.Lock()
	defer s.lock.Unlock()

	if s.listeners[listener.id] != nil {
		return nil, fmt.Errorf("A listener with id '%s' already exists", listener.id)
	}

	s.listeners[listener.id] = listener

	go listener.heartbeat()

	return listener, nil
}

// SendLifecycle broadcasts a lifecycle event.
func (s *Server) SendLifecycle(group string, event api.EventLifecycle) {
	s.Send(group, "lifecycle", event)
}

// Send broadcasts a custom event.
func (s *Server) Send(group, eventType string, eventMessage interface{}) error {
	encodedMessage, err := json.Marshal(eventMessage)
	if err != nil {
		return err
	}
	event := api.Event{
		Type:      eventType,
		Timestamp: time.Now(),
		Metadata:  encodedMessage,
	}

	return s.broadcast(group, event, false)
}

// Forward to the local events dispatcher an event received from another node.
func (s *Server) Forward(id int64, event api.Event) {
	if event.Type == "logging" {
		// Parse the message
		logEntry := api.EventLogging{}
		err := json.Unmarshal(event.Metadata, &logEntry)
		if err != nil {
			return
		}

		if !s.debug && logEntry.Level == "dbug" {
			return
		}

		if !s.debug && !s.verbose && logEntry.Level == "info" {
			return
		}
	}

	err := s.broadcast("", event, true)
	if err != nil {
		logger.Warnf("Failed to forward event from node %d: %v", id, err)
	}
}

func (s *Server) broadcast(group string, event api.Event, isForward bool) error {
	s.lock.Lock()
	listeners := s.listeners
	for _, listener := range listeners {
		if group != "" && listener.group != "*" && group != listener.group {
			continue
		}

		if isForward && listener.noForward {
			continue
		}

		if !shared.StringInSlice(event.Type, listener.messageTypes) {
			continue
		}

		go func(listener *Listener, event api.Event) {
			// Check that the listener still exists
			if listener == nil {
				return
			}

			// Make sure we're not done already
			if listener.IsClosed() {
				return
			}

			// Set the Location to the expected serverName
			if event.Location == "" {
				eventCopy := api.Event{}
				err := shared.DeepCopy(&event, &eventCopy)
				if err != nil {
					return
				}
				eventCopy.Location = listener.location

				event = eventCopy
			}

			listener.SetWriteDeadline(time.Now().Add(5 * time.Second))
			err := listener.WriteJSON(event)
			if err != nil {
				// Remove the listener from the list
				s.lock.Lock()
				delete(s.listeners, listener.id)
				s.lock.Unlock()

				listener.Close()
			}
		}(listener, event)
	}
	s.lock.Unlock()

	return nil
}

// Listener describes an event listener.
type Listener struct {
	*websocket.Conn

	group        string
	messageTypes []string
	ctx          context.Context
	ctxCancel    func()
	id           string
	lock         sync.Mutex
	location     string
	lastPong     time.Time

	// If true, this listener won't get events forwarded from other
	// nodes. It only used by listeners created internally by LXD nodes
	// connecting to other LXD nodes to get their local events only.
	noForward bool
}

func (e *Listener) heartbeat() {
	pingInterval := time.Second * 5
	e.lastPong = time.Now() // To allow initial heartbeat ping to be sent.

	e.SetPongHandler(func(msg string) error {
		e.lastPong = time.Now()
		return nil
	})

	for {
		if e.IsClosed() {
			return
		}

		if e.lastPong.Add(pingInterval * 2).Before(time.Now()) {
			e.Close()
			return
		}

		e.lock.Lock()
		err := e.WriteControl(websocket.PingMessage, []byte("keepalive"), time.Now().Add(5*time.Second))
		if err == websocket.ErrCloseSent {
			e.lock.Unlock()
			return
		} else if netErr, ok := err.(net.Error); ok && netErr.Temporary() {
			e.lock.Unlock()
			return
		} else if err != nil {
			e.lock.Unlock()
			e.Close()
			return
		}
		e.lock.Unlock()

		select {
		case <-time.After(pingInterval):
		case <-e.ctx.Done():
			return
		}
	}
}

// MessageTypes returns a list of message types the listener will be notified of.
func (e *Listener) MessageTypes() []string {
	return e.messageTypes
}

// IsClosed returns true if the listener is closed.
func (e *Listener) IsClosed() bool {
	return e.ctx.Err() != nil
}

// ID returns the listener ID.
func (e *Listener) ID() string {
	return e.id
}

// Wait waits for a message on its active channel or the context is cancelled, then returns.
func (e *Listener) Wait(ctx context.Context) {
	select {
	case <-ctx.Done():
	case <-e.ctx.Done():
	}
}

// Close Disconnects the listener.
func (e *Listener) Close() {
	if e.IsClosed() {
		return
	}

	logger.Debug("Disconnected event listener", log.Ctx{"listener": e.id})

	e.Conn.Close()
	e.ctxCancel()
}

// WriteJSON message to the connection.
func (e *Listener) WriteJSON(v interface{}) error {
	e.lock.Lock()
	defer e.lock.Unlock()

	return e.Conn.WriteJSON(v)
}

// WriteMessage to the connection.
func (e *Listener) WriteMessage(messageType int, data []byte) error {
	e.lock.Lock()
	defer e.lock.Unlock()

	return e.Conn.WriteMessage(messageType, data)
}
<commit_msg>lxd/events/events: Moves blocking reader into heartbeat function<commit_after>package events

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

	"github.com/pborman/uuid"

	"github.com/gorilla/websocket"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	log "github.com/lxc/lxd/shared/log15"
	"github.com/lxc/lxd/shared/logger"
)

// Server represents an instance of an event server.
type Server struct {
	debug   bool
	verbose bool

	listeners map[string]*Listener
	lock      sync.Mutex
}

// NewServer returns a new event server.
func NewServer(debug bool, verbose bool) *Server {
	server := &Server{
		debug:     debug,
		verbose:   verbose,
		listeners: map[string]*Listener{},
	}

	return server
}

// AddListener creates and returns a new event listener.
func (s *Server) AddListener(group string, connection *websocket.Conn, messageTypes []string, location string, noForward bool) (*Listener, error) {
	ctx, ctxCancel := context.WithCancel(context.Background())

	listener := &Listener{
		Conn: connection,

		group:        group,
		messageTypes: messageTypes,
		location:     location,
		noForward:    noForward,
		ctx:          ctx,
		ctxCancel:    ctxCancel,
		id:           uuid.New(),
	}

	s.lock.Lock()
	defer s.lock.Unlock()

	if s.listeners[listener.id] != nil {
		return nil, fmt.Errorf("A listener with id '%s' already exists", listener.id)
	}

	s.listeners[listener.id] = listener

	go listener.heartbeat()

	return listener, nil
}

// SendLifecycle broadcasts a lifecycle event.
func (s *Server) SendLifecycle(group string, event api.EventLifecycle) {
	s.Send(group, "lifecycle", event)
}

// Send broadcasts a custom event.
func (s *Server) Send(group, eventType string, eventMessage interface{}) error {
	encodedMessage, err := json.Marshal(eventMessage)
	if err != nil {
		return err
	}
	event := api.Event{
		Type:      eventType,
		Timestamp: time.Now(),
		Metadata:  encodedMessage,
	}

	return s.broadcast(group, event, false)
}

// Forward to the local events dispatcher an event received from another node.
func (s *Server) Forward(id int64, event api.Event) {
	if event.Type == "logging" {
		// Parse the message
		logEntry := api.EventLogging{}
		err := json.Unmarshal(event.Metadata, &logEntry)
		if err != nil {
			return
		}

		if !s.debug && logEntry.Level == "dbug" {
			return
		}

		if !s.debug && !s.verbose && logEntry.Level == "info" {
			return
		}
	}

	err := s.broadcast("", event, true)
	if err != nil {
		logger.Warnf("Failed to forward event from node %d: %v", id, err)
	}
}

func (s *Server) broadcast(group string, event api.Event, isForward bool) error {
	s.lock.Lock()
	listeners := s.listeners
	for _, listener := range listeners {
		if group != "" && listener.group != "*" && group != listener.group {
			continue
		}

		if isForward && listener.noForward {
			continue
		}

		if !shared.StringInSlice(event.Type, listener.messageTypes) {
			continue
		}

		go func(listener *Listener, event api.Event) {
			// Check that the listener still exists
			if listener == nil {
				return
			}

			// Make sure we're not done already
			if listener.IsClosed() {
				return
			}

			// Set the Location to the expected serverName
			if event.Location == "" {
				eventCopy := api.Event{}
				err := shared.DeepCopy(&event, &eventCopy)
				if err != nil {
					return
				}
				eventCopy.Location = listener.location

				event = eventCopy
			}

			listener.SetWriteDeadline(time.Now().Add(5 * time.Second))
			err := listener.WriteJSON(event)
			if err != nil {
				// Remove the listener from the list
				s.lock.Lock()
				delete(s.listeners, listener.id)
				s.lock.Unlock()

				listener.Close()
			}
		}(listener, event)
	}
	s.lock.Unlock()

	return nil
}

// Listener describes an event listener.
type Listener struct {
	*websocket.Conn

	group        string
	messageTypes []string
	ctx          context.Context
	ctxCancel    func()
	id           string
	lock         sync.Mutex
	location     string
	lastPong     time.Time

	// If true, this listener won't get events forwarded from other
	// nodes. It only used by listeners created internally by LXD nodes
	// connecting to other LXD nodes to get their local events only.
	noForward bool
}

func (e *Listener) heartbeat() {
	defer e.Close()

	pingInterval := time.Second * 5
	e.lastPong = time.Now() // To allow initial heartbeat ping to be sent.

	e.SetPongHandler(func(msg string) error {
		e.lastPong = time.Now()
		return nil
	})

	// Run a blocking reader to detect if the remote side is closed.
	// We don't expect to get anything from the remote side, so this should remain blocked until disconnected.
	go func() {
		e.Conn.NextReader()
		e.Close()
	}()

	for {
		if e.IsClosed() {
			return
		}

		if e.lastPong.Add(pingInterval * 2).Before(time.Now()) {
			logger.Warn("Hearbeat for event listener timed out", log.Ctx{"listener": e.ID()})
			return
		}

		e.lock.Lock()
		err := e.WriteControl(websocket.PingMessage, []byte("keepalive"), time.Now().Add(5*time.Second))
		if err != nil {
			e.lock.Unlock()
			return
		}
		e.lock.Unlock()

		select {
		case <-time.After(pingInterval):
		case <-e.ctx.Done():
			return
		}
	}
}

// MessageTypes returns a list of message types the listener will be notified of.
func (e *Listener) MessageTypes() []string {
	return e.messageTypes
}

// IsClosed returns true if the listener is closed.
func (e *Listener) IsClosed() bool {
	return e.ctx.Err() != nil
}

// ID returns the listener ID.
func (e *Listener) ID() string {
	return e.id
}

// Wait waits for a message on its active channel or the context is cancelled, then returns.
func (e *Listener) Wait(ctx context.Context) {
	select {
	case <-ctx.Done():
	case <-e.ctx.Done():
	}
}

// Close Disconnects the listener.
func (e *Listener) Close() {
	e.lock.Lock()
	defer e.lock.Unlock()

	if e.IsClosed() {
		return
	}

	logger.Debug("Disconnected event listener", log.Ctx{"listener": e.id})

	e.Conn.Close()
	e.ctxCancel()
}

// WriteJSON message to the connection.
func (e *Listener) WriteJSON(v interface{}) error {
	e.lock.Lock()
	defer e.lock.Unlock()

	return e.Conn.WriteJSON(v)
}

// WriteMessage to the connection.
func (e *Listener) WriteMessage(messageType int, data []byte) error {
	e.lock.Lock()
	defer e.lock.Unlock()

	return e.Conn.WriteMessage(messageType, data)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strings"
	"time"

	"github.com/lxc/lxd/client"
)

func cmdShutdown(args *Args) error {
	connArgs := &lxd.ConnectionArgs{
		SkipGetServer: true,
	}
	c, err := lxd.ConnectLXDUnix("", connArgs)
	if err != nil {
		return err
	}

	_, _, err = c.RawQuery("PUT", "/internal/shutdown", nil, "")
	if err != nil && strings.HasSuffix(err.Error(), ": EOF") {
		// NOTE: if we got an EOF error here it means that the daemon
		// has shutdown so quickly that it already closed the unix
		// socket. We consider the daemon dead in this case.
		return err
	}

	chMonitor := make(chan bool, 1)
	go func() {
		monitor, err := c.GetEvents()
		if err != nil {
			close(chMonitor)
			return
		}

		monitor.Wait()
		close(chMonitor)
	}()

	if args.Timeout > 0 {
		select {
		case <-chMonitor:
			break
		case <-time.After(time.Second * time.Duration(args.Timeout)):
			return fmt.Errorf("LXD still running after %ds timeout.", args.Timeout)
		}
	} else {
		<-chMonitor
	}

	return nil
}
<commit_msg>lxd/shutdown: Fix typo in error handling<commit_after>package main

import (
	"fmt"
	"strings"
	"time"

	"github.com/lxc/lxd/client"
)

func cmdShutdown(args *Args) error {
	connArgs := &lxd.ConnectionArgs{
		SkipGetServer: true,
	}
	c, err := lxd.ConnectLXDUnix("", connArgs)
	if err != nil {
		return err
	}

	_, _, err = c.RawQuery("PUT", "/internal/shutdown", nil, "")
	if err != nil && !strings.HasSuffix(err.Error(), ": EOF") {
		// NOTE: if we got an EOF error here it means that the daemon
		// has shutdown so quickly that it already closed the unix
		// socket. We consider the daemon dead in this case.
		return err
	}

	chMonitor := make(chan bool, 1)
	go func() {
		monitor, err := c.GetEvents()
		if err != nil {
			close(chMonitor)
			return
		}

		monitor.Wait()
		close(chMonitor)
	}()

	if args.Timeout > 0 {
		select {
		case <-chMonitor:
			break
		case <-time.After(time.Second * time.Duration(args.Timeout)):
			return fmt.Errorf("LXD still running after %ds timeout.", args.Timeout)
		}
	} else {
		<-chMonitor
	}

	return nil
}
<|endoftext|>


<commit_before>/*

*/
package mandrake

import (

	"golang.org/x/exp/inotify"
	"github.com/hosom/gomandrake/config"
	"github.com/hosom/gomandrake/inputs"
	//"github.com/hosom/gomandrake/plugin"
)

type Mandrake struct {
	AnalysisPipeline	chan string
	Input				string
}

func NewMandrake(c config.Config) (*Mandrake, error) {
	return &Mandrake{make(chan string), c.Input}, nil
}

func (m *Mandrake) ListenAndServe() {

	i := inputs.INotify{"/tmp/", m.AnalysisPipeline}
	//plugin.CreateListenerAndServe(m.AnalysisPipeline, m.Input)
}

func (m *Mandrake) DispatchAnalysis() {
	for fpath := range m.AnalysisPipeline {
		log.Printf("%s", fpath)
	}
}<commit_msg>updates<commit_after>/*

*/
package mandrake

import (
	"log"
	//"golang.org/x/exp/inotify"
	"github.com/hosom/gomandrake/config"
	"github.com/hosom/gomandrake/inputs"
	//"github.com/hosom/gomandrake/plugin"
)

type Mandrake struct {
	AnalysisPipeline	chan string
	Input				string
}

func NewMandrake(c config.Config) (*Mandrake, error) {
	return &Mandrake{make(chan string), c.Input}, nil
}

func (m *Mandrake) ListenAndServe() {

	i := inputs.INotify{"/tmp/", m.AnalysisPipeline}
	//plugin.CreateListenerAndServe(m.AnalysisPipeline, m.Input)
}

func (m *Mandrake) DispatchAnalysis() {
	for fpath := range m.AnalysisPipeline {
		log.Printf("%s", fpath)
	}
}<|endoftext|>


<commit_before>/*
Copyright IBM Corp. 2016 All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

		 http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"net"
	"os"
	"path/filepath"
	"runtime"

	"strings"

	"github.com/hyperledger/fabric/core/crypto"
	"github.com/hyperledger/fabric/flogging"
	"github.com/hyperledger/fabric/membersrvc/ca"
	"github.com/op/go-logging"
	"github.com/spf13/viper"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

const envPrefix = "MEMBERSRVC_CA"

var logger = logging.MustGetLogger("server")

func main() {

	viper.SetEnvPrefix(envPrefix)
	viper.AutomaticEnv()
	replacer := strings.NewReplacer(".", "_")
	viper.SetEnvKeyReplacer(replacer)
	viper.SetConfigName("membersrvc")
	viper.SetConfigType("yaml")
	viper.AddConfigPath("./")
	// Path to look for the config file based on GOPATH
	gopath := os.Getenv("GOPATH")
	for _, p := range filepath.SplitList(gopath) {
		cfgpath := filepath.Join(p, "src/github.com/hyperledger/fabric/membersrvc")
		viper.AddConfigPath(cfgpath)
	}
	err := viper.ReadInConfig()
	if err != nil {
		logger.Panicf("Fatal error when reading %s config file: %s", "membersrvc", err)
	}

	flogging.LoggingInit("server")

	// Init the crypto layer
	if err := crypto.Init(); err != nil {
		logger.Panicf("Failed initializing the crypto layer [%s]", err)
	}

	// cache configure
	ca.CacheConfiguration()

	logger.Infof("CA Server (" + viper.GetString("server.version") + ")")

	aca := ca.NewACA()
	defer aca.Stop()

	eca := ca.NewECA()
	defer eca.Stop()

	tca := ca.NewTCA(eca)
	defer tca.Stop()

	tlsca := ca.NewTLSCA(eca)
	defer tlsca.Stop()

	runtime.GOMAXPROCS(viper.GetInt("server.gomaxprocs"))

	var opts []grpc.ServerOption
	if viper.GetString("server.tls.cert.file") != "" {
		creds, err := credentials.NewServerTLSFromFile(viper.GetString("server.tls.cert.file"), viper.GetString("server.tls.key.file"))
		if err != nil {
			logger.Panic(err)
		}
		opts = []grpc.ServerOption{grpc.Creds(creds)}
	}
	srv := grpc.NewServer(opts...)

	aca.Start(srv)
	eca.Start(srv)
	tca.Start(srv)
	tlsca.Start(srv)

	if sock, err := net.Listen("tcp", viper.GetString("server.port")); err != nil {
		logger.Errorf("Fail to start CA Server: %s", err)
		os.Exit(1)
	} else {
		srv.Serve(sock)
		sock.Close()
	}
}
<commit_msg>Start ACA only when "aca.enabled = true"<commit_after>/*
Copyright IBM Corp. 2016 All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

		 http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"net"
	"os"
	"path/filepath"
	"runtime"

	"strings"

	"github.com/hyperledger/fabric/core/crypto"
	"github.com/hyperledger/fabric/flogging"
	"github.com/hyperledger/fabric/membersrvc/ca"
	"github.com/op/go-logging"
	"github.com/spf13/viper"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

const envPrefix = "MEMBERSRVC_CA"

var logger = logging.MustGetLogger("server")

func main() {

	viper.SetEnvPrefix(envPrefix)
	viper.AutomaticEnv()
	replacer := strings.NewReplacer(".", "_")
	viper.SetEnvKeyReplacer(replacer)
	viper.SetConfigName("membersrvc")
	viper.SetConfigType("yaml")
	viper.AddConfigPath("./")
	// Path to look for the config file based on GOPATH
	gopath := os.Getenv("GOPATH")
	for _, p := range filepath.SplitList(gopath) {
		cfgpath := filepath.Join(p, "src/github.com/hyperledger/fabric/membersrvc")
		viper.AddConfigPath(cfgpath)
	}
	err := viper.ReadInConfig()
	if err != nil {
		logger.Panicf("Fatal error when reading %s config file: %s", "membersrvc", err)
	}

	flogging.LoggingInit("server")

	// Init the crypto layer
	if err := crypto.Init(); err != nil {
		logger.Panicf("Failed initializing the crypto layer [%s]", err)
	}

	// cache configure
	ca.CacheConfiguration()

	logger.Infof("CA Server (" + viper.GetString("server.version") + ")")

	aca := ca.NewACA()
	defer aca.Stop()

	eca := ca.NewECA()
	defer eca.Stop()

	tca := ca.NewTCA(eca)
	defer tca.Stop()

	tlsca := ca.NewTLSCA(eca)
	defer tlsca.Stop()

	runtime.GOMAXPROCS(viper.GetInt("server.gomaxprocs"))

	var opts []grpc.ServerOption
	if viper.GetString("server.tls.cert.file") != "" {
		creds, err := credentials.NewServerTLSFromFile(viper.GetString("server.tls.cert.file"), viper.GetString("server.tls.key.file"))
		if err != nil {
			logger.Panic(err)
		}
		opts = []grpc.ServerOption{grpc.Creds(creds)}
	}
	srv := grpc.NewServer(opts...)

	if viper.GetBool("aca.enabled") {
		aca.Start(srv)
	}
	eca.Start(srv)
	tca.Start(srv)
	tlsca.Start(srv)

	if sock, err := net.Listen("tcp", viper.GetString("server.port")); err != nil {
		logger.Errorf("Fail to start CA Server: %s", err)
		os.Exit(1)
	} else {
		srv.Serve(sock)
		sock.Close()
	}
}
<|endoftext|>


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

// Package metadata define the structure of the metadata supported by gRPC library.
// Please refer to https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md
// for more information about custom-metadata.
package metadata // import "google.golang.org/grpc/metadata"

import (
	"context"
	"fmt"
	"strings"
)

// DecodeKeyValue returns k, v, nil.
//
// Deprecated: use k and v directly instead.
func DecodeKeyValue(k, v string) (string, string, error) {
	return k, v, nil
}

// MD is a mapping from metadata keys to values. Users should use the following
// two convenience functions New and Pairs to generate MD.
type MD map[string][]string

// New creates an MD from a given key-value map.
//
// Only the following ASCII characters are allowed in keys:
//  - digits: 0-9
//  - uppercase letters: A-Z (normalized to lower)
//  - lowercase letters: a-z
//  - special characters: -_.
// Uppercase letters are automatically converted to lowercase.
//
// Keys beginning with "grpc-" are reserved for grpc-internal use only and may
// result in errors if set in metadata.
func New(m map[string]string) MD {
	md := MD{}
	for k, val := range m {
		key := strings.ToLower(k)
		md[key] = append(md[key], val)
	}
	return md
}

// Pairs returns an MD formed by the mapping of key, value ...
// Pairs panics if len(kv) is odd.
//
// Only the following ASCII characters are allowed in keys:
//  - digits: 0-9
//  - uppercase letters: A-Z (normalized to lower)
//  - lowercase letters: a-z
//  - special characters: -_.
// Uppercase letters are automatically converted to lowercase.
//
// Keys beginning with "grpc-" are reserved for grpc-internal use only and may
// result in errors if set in metadata.
func Pairs(kv ...string) MD {
	if len(kv)%2 == 1 {
		panic(fmt.Sprintf("metadata: Pairs got the odd number of input pairs for metadata: %d", len(kv)))
	}
	md := MD{}
	for i := 0; i < len(kv); i += 2 {
		key := strings.ToLower(kv[i])
		md[key] = append(md[key], kv[i+1])
	}
	return md
}

// Len returns the number of items in md.
func (md MD) Len() int {
	return len(md)
}

// Copy returns a copy of md.
func (md MD) Copy() MD {
	return Join(md)
}

// Get obtains the values for a given key.
//
// k is converted to lowercase before searching in md.
func (md MD) Get(k string) []string {
	k = strings.ToLower(k)
	return md[k]
}

// Set sets the value of a given key with a slice of values.
//
// k is converted to lowercase before storing in md.
func (md MD) Set(k string, vals ...string) {
	if len(vals) == 0 {
		return
	}
	k = strings.ToLower(k)
	md[k] = vals
}

// Append adds the values to key k, not overwriting what was already stored at
// that key.
//
// k is converted to lowercase before storing in md.
func (md MD) Append(k string, vals ...string) {
	if len(vals) == 0 {
		return
	}
	k = strings.ToLower(k)
	md[k] = append(md[k], vals...)
}

// Delete removes the values for a given key k which is converted to lowercase
// before removing it from md.
func (md MD) Delete(k string) {
	k = strings.ToLower(k)
	delete(md, k)
}

// Join joins any number of mds into a single MD.
//
// The order of values for each key is determined by the order in which the mds
// containing those values are presented to Join.
func Join(mds ...MD) MD {
	out := MD{}
	for _, md := range mds {
		for k, v := range md {
			out[k] = append(out[k], v...)
		}
	}
	return out
}

type mdIncomingKey struct{}
type mdOutgoingKey struct{}

// NewIncomingContext creates a new context with incoming md attached.
func NewIncomingContext(ctx context.Context, md MD) context.Context {
	return context.WithValue(ctx, mdIncomingKey{}, md)
}

// NewOutgoingContext creates a new context with outgoing md attached. If used
// in conjunction with AppendToOutgoingContext, NewOutgoingContext will
// overwrite any previously-appended metadata.
func NewOutgoingContext(ctx context.Context, md MD) context.Context {
	return context.WithValue(ctx, mdOutgoingKey{}, rawMD{md: md})
}

// AppendToOutgoingContext returns a new context with the provided kv merged
// with any existing metadata in the context. Please refer to the documentation
// of Pairs for a description of kv.
func AppendToOutgoingContext(ctx context.Context, kv ...string) context.Context {
	if len(kv)%2 == 1 {
		panic(fmt.Sprintf("metadata: AppendToOutgoingContext got an odd number of input pairs for metadata: %d", len(kv)))
	}
	md, _ := ctx.Value(mdOutgoingKey{}).(rawMD)
	added := make([][]string, len(md.added)+1)
	copy(added, md.added)
	added[len(added)-1] = make([]string, len(kv))
	copy(added[len(added)-1], kv)
	return context.WithValue(ctx, mdOutgoingKey{}, rawMD{md: md.md, added: added})
}

// FromIncomingContext returns the incoming metadata in ctx if it exists.
//
// All keys in the returned MD are lowercase.
func FromIncomingContext(ctx context.Context) (MD, bool) {
	md, ok := ctx.Value(mdIncomingKey{}).(MD)
	if !ok {
		return nil, false
	}
	out := MD{}
	for k, v := range md {
		// We need to manually convert all keys to lower case, because MD is a
		// map, and there's no guarantee that the MD attached to the context is
		// created using our helper functions.
		key := strings.ToLower(k)
		out[key] = v
	}
	return out, true
}

// FromOutgoingContextRaw returns the un-merged, intermediary contents of rawMD.
//
// Remember to perform strings.ToLower on the keys, for both the returned MD (MD
// is a map, there's no guarantee it's created using our helper functions) and
// the extra kv pairs (AppendToOutgoingContext doesn't turn them into
// lowercase).
//
// This is intended for gRPC-internal use ONLY. Users should use
// FromOutgoingContext instead.
func FromOutgoingContextRaw(ctx context.Context) (MD, [][]string, bool) {
	raw, ok := ctx.Value(mdOutgoingKey{}).(rawMD)
	if !ok {
		return nil, nil, false
	}

	return raw.md, raw.added, true
}

// FromOutgoingContext returns the outgoing metadata in ctx if it exists.
//
// All keys in the returned MD are lowercase.
func FromOutgoingContext(ctx context.Context) (MD, bool) {
	raw, ok := ctx.Value(mdOutgoingKey{}).(rawMD)
	if !ok {
		return nil, false
	}

	out := MD{}
	for k, v := range raw.md {
		// We need to manually convert all keys to lower case, because MD is a
		// map, and there's no guarantee that the MD attached to the context is
		// created using our helper functions.
		key := strings.ToLower(k)
		out[key] = v
	}
	for _, added := range raw.added {
		if len(added)%2 == 1 {
			panic(fmt.Sprintf("metadata: FromOutgoingContext got an odd number of input pairs for metadata: %d", len(added)))
		}

		for i := 0; i < len(added); i += 2 {
			key := strings.ToLower(added[i])
			out[key] = append(out[key], added[i+1])
		}
	}
	return out, ok
}

type rawMD struct {
	md    MD
	added [][]string
}
<commit_msg>metadata: copy slices in FromContext() functions (#5267)<commit_after>/*
 *
 * Copyright 2014 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

// Package metadata define the structure of the metadata supported by gRPC library.
// Please refer to https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md
// for more information about custom-metadata.
package metadata // import "google.golang.org/grpc/metadata"

import (
	"context"
	"fmt"
	"strings"
)

// DecodeKeyValue returns k, v, nil.
//
// Deprecated: use k and v directly instead.
func DecodeKeyValue(k, v string) (string, string, error) {
	return k, v, nil
}

// MD is a mapping from metadata keys to values. Users should use the following
// two convenience functions New and Pairs to generate MD.
type MD map[string][]string

// New creates an MD from a given key-value map.
//
// Only the following ASCII characters are allowed in keys:
//  - digits: 0-9
//  - uppercase letters: A-Z (normalized to lower)
//  - lowercase letters: a-z
//  - special characters: -_.
// Uppercase letters are automatically converted to lowercase.
//
// Keys beginning with "grpc-" are reserved for grpc-internal use only and may
// result in errors if set in metadata.
func New(m map[string]string) MD {
	md := MD{}
	for k, val := range m {
		key := strings.ToLower(k)
		md[key] = append(md[key], val)
	}
	return md
}

// Pairs returns an MD formed by the mapping of key, value ...
// Pairs panics if len(kv) is odd.
//
// Only the following ASCII characters are allowed in keys:
//  - digits: 0-9
//  - uppercase letters: A-Z (normalized to lower)
//  - lowercase letters: a-z
//  - special characters: -_.
// Uppercase letters are automatically converted to lowercase.
//
// Keys beginning with "grpc-" are reserved for grpc-internal use only and may
// result in errors if set in metadata.
func Pairs(kv ...string) MD {
	if len(kv)%2 == 1 {
		panic(fmt.Sprintf("metadata: Pairs got the odd number of input pairs for metadata: %d", len(kv)))
	}
	md := MD{}
	for i := 0; i < len(kv); i += 2 {
		key := strings.ToLower(kv[i])
		md[key] = append(md[key], kv[i+1])
	}
	return md
}

// Len returns the number of items in md.
func (md MD) Len() int {
	return len(md)
}

// Copy returns a copy of md.
func (md MD) Copy() MD {
	return Join(md)
}

// Get obtains the values for a given key.
//
// k is converted to lowercase before searching in md.
func (md MD) Get(k string) []string {
	k = strings.ToLower(k)
	return md[k]
}

// Set sets the value of a given key with a slice of values.
//
// k is converted to lowercase before storing in md.
func (md MD) Set(k string, vals ...string) {
	if len(vals) == 0 {
		return
	}
	k = strings.ToLower(k)
	md[k] = vals
}

// Append adds the values to key k, not overwriting what was already stored at
// that key.
//
// k is converted to lowercase before storing in md.
func (md MD) Append(k string, vals ...string) {
	if len(vals) == 0 {
		return
	}
	k = strings.ToLower(k)
	md[k] = append(md[k], vals...)
}

// Delete removes the values for a given key k which is converted to lowercase
// before removing it from md.
func (md MD) Delete(k string) {
	k = strings.ToLower(k)
	delete(md, k)
}

// Join joins any number of mds into a single MD.
//
// The order of values for each key is determined by the order in which the mds
// containing those values are presented to Join.
func Join(mds ...MD) MD {
	out := MD{}
	for _, md := range mds {
		for k, v := range md {
			out[k] = append(out[k], v...)
		}
	}
	return out
}

type mdIncomingKey struct{}
type mdOutgoingKey struct{}

// NewIncomingContext creates a new context with incoming md attached.
func NewIncomingContext(ctx context.Context, md MD) context.Context {
	return context.WithValue(ctx, mdIncomingKey{}, md)
}

// NewOutgoingContext creates a new context with outgoing md attached. If used
// in conjunction with AppendToOutgoingContext, NewOutgoingContext will
// overwrite any previously-appended metadata.
func NewOutgoingContext(ctx context.Context, md MD) context.Context {
	return context.WithValue(ctx, mdOutgoingKey{}, rawMD{md: md})
}

// AppendToOutgoingContext returns a new context with the provided kv merged
// with any existing metadata in the context. Please refer to the documentation
// of Pairs for a description of kv.
func AppendToOutgoingContext(ctx context.Context, kv ...string) context.Context {
	if len(kv)%2 == 1 {
		panic(fmt.Sprintf("metadata: AppendToOutgoingContext got an odd number of input pairs for metadata: %d", len(kv)))
	}
	md, _ := ctx.Value(mdOutgoingKey{}).(rawMD)
	added := make([][]string, len(md.added)+1)
	copy(added, md.added)
	added[len(added)-1] = make([]string, len(kv))
	copy(added[len(added)-1], kv)
	return context.WithValue(ctx, mdOutgoingKey{}, rawMD{md: md.md, added: added})
}

// FromIncomingContext returns the incoming metadata in ctx if it exists.
//
// All keys in the returned MD are lowercase.
func FromIncomingContext(ctx context.Context) (MD, bool) {
	md, ok := ctx.Value(mdIncomingKey{}).(MD)
	if !ok {
		return nil, false
	}
	out := MD{}
	for k, v := range md {
		// We need to manually convert all keys to lower case, because MD is a
		// map, and there's no guarantee that the MD attached to the context is
		// created using our helper functions.
		key := strings.ToLower(k)
		s := make([]string, len(v))
		copy(s, v)
		out[key] = s
	}
	return out, true
}

// FromOutgoingContextRaw returns the un-merged, intermediary contents of rawMD.
//
// Remember to perform strings.ToLower on the keys, for both the returned MD (MD
// is a map, there's no guarantee it's created using our helper functions) and
// the extra kv pairs (AppendToOutgoingContext doesn't turn them into
// lowercase).
//
// This is intended for gRPC-internal use ONLY. Users should use
// FromOutgoingContext instead.
func FromOutgoingContextRaw(ctx context.Context) (MD, [][]string, bool) {
	raw, ok := ctx.Value(mdOutgoingKey{}).(rawMD)
	if !ok {
		return nil, nil, false
	}

	return raw.md, raw.added, true
}

// FromOutgoingContext returns the outgoing metadata in ctx if it exists.
//
// All keys in the returned MD are lowercase.
func FromOutgoingContext(ctx context.Context) (MD, bool) {
	raw, ok := ctx.Value(mdOutgoingKey{}).(rawMD)
	if !ok {
		return nil, false
	}

	out := MD{}
	for k, v := range raw.md {
		// We need to manually convert all keys to lower case, because MD is a
		// map, and there's no guarantee that the MD attached to the context is
		// created using our helper functions.
		key := strings.ToLower(k)
		s := make([]string, len(v))
		copy(s, v)
		out[key] = s
	}
	for _, added := range raw.added {
		if len(added)%2 == 1 {
			panic(fmt.Sprintf("metadata: FromOutgoingContext got an odd number of input pairs for metadata: %d", len(added)))
		}

		for i := 0; i < len(added); i += 2 {
			key := strings.ToLower(added[i])
			out[key] = append(out[key], added[i+1])
		}
	}
	return out, ok
}

type rawMD struct {
	md    MD
	added [][]string
}
<|endoftext|>


<commit_before>package metadata

import (
	"errors"
	"fmt"
	"github.com/yunify/metad/backends"
	"github.com/yunify/metad/log"
	"github.com/yunify/metad/store"
	"github.com/yunify/metad/util"
	"github.com/yunify/metad/util/flatmap"
	"net"
	"path"
	"reflect"
	"strings"
	"time"
)

type MetadataRepo struct {
	onlySelf        bool
	mapping         store.Store
	storeClient     backends.StoreClient
	data            store.Store
	metaStopChan    chan bool
	mappingStopChan chan bool
	timerPool       *util.TimerPool
}

func New(onlySelf bool, storeClient backends.StoreClient) *MetadataRepo {
	metadataRepo := MetadataRepo{
		onlySelf:        onlySelf,
		mapping:         store.New(),
		storeClient:     storeClient,
		data:            store.New(),
		metaStopChan:    make(chan bool),
		mappingStopChan: make(chan bool),
		timerPool:       util.NewTimerPool(100 * time.Millisecond),
	}
	return &metadataRepo
}

func (r *MetadataRepo) SetOnlySelf(onlySelf bool) {
	r.onlySelf = onlySelf
}

func (r *MetadataRepo) StartSync() {
	log.Info("Start Sync")
	r.startMetaSync()
	r.startMappingSync()
}

func (r *MetadataRepo) startMetaSync() {
	r.storeClient.Sync(r.data, r.metaStopChan)
}

func (r *MetadataRepo) startMappingSync() {
	r.storeClient.SyncMapping(r.mapping, r.mappingStopChan)
}

func (r *MetadataRepo) ReSync() {
	log.Info("ReSync")
	//TODO lock
	r.StopSync()
	r.data.Delete("/")
	r.mapping.Delete("/")
	r.StartSync()
}

func (r *MetadataRepo) StopSync() {
	log.Info("Stop Sync")
	r.metaStopChan <- true
	r.mappingStopChan <- true
}

func (r *MetadataRepo) Root(clientIP string, nodePath string) (currentVersion int64, val interface{}) {
	nodePath = path.Join("/", nodePath)
	if r.onlySelf {
		currentVersion = r.data.Version()
		if nodePath == "/" {
			mapVal := make(map[string]interface{})
			selfVal := r.Self(clientIP, "/")
			if selfVal != nil {
				mapVal["self"] = selfVal
			}
			val = mapVal
		}
	} else {
		currentVersion, val = r.data.Get(nodePath)
		if val != nil && nodePath == "/" {
			selfVal := r.Self(clientIP, "/")
			if selfVal != nil {
				mapVal, ok := val.(map[string]interface{})
				if ok {
					mapVal["self"] = selfVal
				}
			}
		}
	}
	return
}

func (r *MetadataRepo) Watch(clientIP string, nodePath string, closeChan <-chan bool) interface{} {
	nodePath = path.Join("/", nodePath)

	if r.onlySelf {
		if nodePath == "/" {
			return r.WatchSelf(clientIP, "/", closeChan)
		} else {
			return nil
		}
	} else {
		w := r.data.Watch(nodePath)
		return r.changeToResult(w, closeChan)
	}
}

var TIMER_NIL *time.Timer = &time.Timer{C: nil}

func (r *MetadataRepo) changeToResult(watcher store.Watcher, stopChan <-chan bool) interface{} {
	defer watcher.Remove()
	m := make(map[string]string)
	timer := TIMER_NIL

	for {
		var finish bool = false
		select {
		case e, ok := <-watcher.EventChan():
			if ok {
				value := fmt.Sprintf("%s|%s", e.Action, e.Value)
				// if event is one leaf node, just return value.
				if e.Path == "/" {
					return value
				}
				m[e.Path] = value
				timer = r.timerPool.AcquireTimer()
			} else {
				finish = true
			}
		case <-timer.C:
			finish = true
		case <-stopChan:
			//when stop, return empty map, discard prev result.
			m = make(map[string]string)
			finish = true
		}
		if finish {
			if timer.C != nil {
				r.timerPool.ReleaseTimer(timer)
			}
			break
		}
		//TODO check map size, avoid too big result.
	}
	return flatmap.Expand(m, "/")
}

func (r *MetadataRepo) WatchSelf(clientIP string, nodePath string, closeChan <-chan bool) interface{} {
	nodePath = path.Join(clientIP, "/", nodePath)
	if log.IsDebugEnable() {
		log.Debug("WatchSelf nodePath: %s", nodePath)
	}
	mappingData := r.GetMapping(nodePath)
	if mappingData == nil {
		return nil
	}
	mappingWatcher := r.mapping.Watch(nodePath)
	defer mappingWatcher.Remove()

	stopChan := make(chan bool)
	defer close(stopChan)
	go func() {
		select {
		case _, ok := <-mappingWatcher.EventChan():
			if ok {
				stopChan <- true
			}
		case <-closeChan:
			stopChan <- true
		}
	}()

	mapping, mok := mappingData.(map[string]interface{})
	if !mok {
		dataNodePath := fmt.Sprintf("%s", mappingData)
		//log.Debug("watcher: %v", dataNodePath)
		w := r.data.Watch(dataNodePath)
		return r.changeToResult(w, stopChan)
	} else {
		flatMapping := flatmap.Flatten(mapping)
		watchers := make(map[string]store.Watcher)
		for k, v := range flatMapping {
			watchers[k] = r.data.Watch(v)
		}
		//log.Debug("aggWatcher: %v", watchers)
		aggWatcher := store.NewAggregateWatcher(watchers)
		return r.changeToResult(aggWatcher, stopChan)
	}
}

func (r *MetadataRepo) Self(clientIP string, nodePath string) interface{} {
	if clientIP == "" {
		panic(errors.New("clientIP must not be empty."))
	}
	nodePath = path.Join("/", nodePath)
	mappingData := r.GetMapping(path.Join("/", clientIP))
	if mappingData == nil {
		if log.IsDebugEnable() {
			log.Debug("Can not find mapping for %s", clientIP)
		}
		return nil
	}
	mapping, mok := mappingData.(map[string]interface{})
	if !mok {
		log.Warning("Mapping for %s is not a map, result:%v", clientIP, mappingData)
		return nil
	}
	return r.getMappingDatas(nodePath, mapping)
}

func (r *MetadataRepo) getMappingData(nodePath, link string) interface{} {
	nodePath = path.Join(link, nodePath)
	_, val := r.data.Get(nodePath)
	return val
}

func (r *MetadataRepo) getMappingDatas(nodePath string, mapping map[string]interface{}) interface{} {
	nodePath = path.Join("/", nodePath)
	paths := strings.Split(nodePath, "/")[1:] // trim first blank item
	// nodePath is "/"
	if paths[0] == "" {
		meta := make(map[string]interface{})
		for k, v := range mapping {
			submapping, isMap := v.(map[string]interface{})
			if isMap {
				val := r.getMappingDatas("/", submapping)
				if val != nil {
					meta[k] = val
				} else {
					log.Warning("Can not get values from backend by mapping: %v", submapping)
				}
			} else {
				subNodePath := fmt.Sprintf("%v", v)
				val := r.getMappingData("/", subNodePath)
				if val != nil {
					meta[k] = val
				} else {
					log.Warning("Can not get values from backend by mapping: %v", subNodePath)
				}
			}

		}
		return meta
	} else {
		elemName := paths[0]
		elemValue, ok := mapping[elemName]
		if ok {
			submapping, isMap := elemValue.(map[string]interface{})
			if isMap {
				return r.getMappingDatas(path.Join(paths[1:]...), submapping)
			} else {
				return r.getMappingData(path.Join(paths[1:]...), fmt.Sprintf("%v", elemValue))
			}
		} else {
			if log.IsDebugEnable() {
				log.Debug("Can not find mapping for : %v, mapping:%v", nodePath, mapping)
			}
			return nil
		}
	}
}

func (r *MetadataRepo) GetData(nodePath string) interface{} {
	_, val := r.data.Get(nodePath)
	return val
}

func (r *MetadataRepo) PutData(nodePath string, data interface{}, replace bool) error {
	return r.storeClient.Put(nodePath, data, replace)
}

func (r *MetadataRepo) DeleteData(nodePath string, subs ...string) error {
	err := checkSubs(subs)
	if err != nil {
		return err
	}
	if len(subs) > 0 {
		for _, sub := range subs {
			subPath := path.Join(nodePath, sub)
			_, v := r.data.Get(subPath)
			// if subPath metadata not exist, just ignore.
			if v != nil {
				_, dir := v.(map[string]interface{})
				err = r.storeClient.Delete(subPath, dir)
				if err != nil {
					return err
				}
			}
		}
		return nil
	} else {
		_, v := r.data.Get(nodePath)
		if v != nil {
			_, dir := v.(map[string]interface{})
			return r.storeClient.Delete(nodePath, dir)
		}
		return nil
	}

}

func (r *MetadataRepo) GetMapping(nodePath string) interface{} {
	_, val := r.mapping.Get(nodePath)
	return val
}

func (r *MetadataRepo) PutMapping(nodePath string, data interface{}, replace bool) error {
	nodePath = path.Join("/", nodePath)
	if nodePath == "/" {
		m, ok := data.(map[string]interface{})
		if !ok {
			log.Warning("Unexpect data type for mapping: %s", reflect.TypeOf(data))
			return errors.New("mapping data should be json object.")
		}
		for k, v := range m {
			ip := net.ParseIP(k)
			if ip == nil {
				return errors.New("mapping's first level key should be ip .")
			}
			err := checkMapping(v)
			if err != nil {
				return err
			}
		}
	} else {
		parts := strings.Split(nodePath, "/")
		ip := net.ParseIP(parts[1])
		if ip == nil {
			return errors.New("mapping's first level key should be ip .")
		}
		// nodePath: /ip
		if len(parts) == 2 {
			err := checkMapping(data)
			if err != nil {
				return err
			}
		} else {
			// nodePath: /ip/{key:.*}
			_, isMap := data.(map[string]interface{})
			if isMap {
				err := checkMapping(data)
				if err != nil {
					return err
				}
			} else {
				err := checkMappingPath(data)
				if err != nil {
					return err
				}
			}
		}
	}
	return r.storeClient.PutMapping(nodePath, data, replace)
}

func (r *MetadataRepo) DeleteMapping(nodePath string, subs ...string) error {
	err := checkSubs(subs)
	if err != nil {
		return err
	}
	if len(subs) > 0 {
		for _, sub := range subs {
			subPath := path.Join(nodePath, sub)
			_, v := r.mapping.Get(subPath)
			// if subPath mapping not exist, just ignore.
			if v != nil {
				_, dir := v.(map[string]interface{})
				err = r.storeClient.DeleteMapping(subPath, dir)
				if err != nil {
					return err
				}
			}
		}
		return nil
	} else {
		_, v := r.mapping.Get(nodePath)
		if v != nil {
			_, dir := v.(map[string]interface{})
			return r.storeClient.DeleteMapping(nodePath, dir)
		}
		return nil
	}
}

func (r *MetadataRepo) DataVersion() int64 {
	return r.data.Version()
}

func checkSubs(subs []string) error {
	for _, sub := range subs {
		if strings.Index(sub, "/") >= 0 {
			return errors.New("Sub node must not a path.")
		}
	}
	return nil
}

func checkMapping(data interface{}) error {
	m, ok := data.(map[string]interface{})
	if !ok {
		return errors.New("mapping data should be json object.")
	}
	for k, v := range m {
		if strings.Index(k, "/") >= 0 {
			return errors.New("mapping key should not be path.")
		}
		_, isMap := v.(map[string]interface{})
		if isMap {
			err := checkMapping(v)
			if err != nil {
				return err
			}
		} else {
			err := checkMappingPath(v)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

func checkMappingPath(v interface{}) error {
	vs, vok := v.(string)
	if !vok {
		return errors.New("mapping's value should be path .")
	}
	if vs == "" || vs[0] != '/' {
		return errors.New("mapping's value should be path .")
	}
	return nil
}
<commit_msg>try fix send closed channel for some conditions<commit_after>package metadata

import (
	"errors"
	"fmt"
	"github.com/yunify/metad/backends"
	"github.com/yunify/metad/log"
	"github.com/yunify/metad/store"
	"github.com/yunify/metad/util"
	"github.com/yunify/metad/util/flatmap"
	"net"
	"path"
	"reflect"
	"strings"
	"time"
)

type MetadataRepo struct {
	onlySelf        bool
	mapping         store.Store
	storeClient     backends.StoreClient
	data            store.Store
	metaStopChan    chan bool
	mappingStopChan chan bool
	timerPool       *util.TimerPool
}

func New(onlySelf bool, storeClient backends.StoreClient) *MetadataRepo {
	metadataRepo := MetadataRepo{
		onlySelf:        onlySelf,
		mapping:         store.New(),
		storeClient:     storeClient,
		data:            store.New(),
		metaStopChan:    make(chan bool),
		mappingStopChan: make(chan bool),
		timerPool:       util.NewTimerPool(100 * time.Millisecond),
	}
	return &metadataRepo
}

func (r *MetadataRepo) SetOnlySelf(onlySelf bool) {
	r.onlySelf = onlySelf
}

func (r *MetadataRepo) StartSync() {
	log.Info("Start Sync")
	r.startMetaSync()
	r.startMappingSync()
}

func (r *MetadataRepo) startMetaSync() {
	r.storeClient.Sync(r.data, r.metaStopChan)
}

func (r *MetadataRepo) startMappingSync() {
	r.storeClient.SyncMapping(r.mapping, r.mappingStopChan)
}

func (r *MetadataRepo) ReSync() {
	log.Info("ReSync")
	//TODO lock
	r.StopSync()
	r.data.Delete("/")
	r.mapping.Delete("/")
	r.StartSync()
}

func (r *MetadataRepo) StopSync() {
	log.Info("Stop Sync")
	r.metaStopChan <- true
	r.mappingStopChan <- true
}

func (r *MetadataRepo) Root(clientIP string, nodePath string) (currentVersion int64, val interface{}) {
	nodePath = path.Join("/", nodePath)
	if r.onlySelf {
		currentVersion = r.data.Version()
		if nodePath == "/" {
			mapVal := make(map[string]interface{})
			selfVal := r.Self(clientIP, "/")
			if selfVal != nil {
				mapVal["self"] = selfVal
			}
			val = mapVal
		}
	} else {
		currentVersion, val = r.data.Get(nodePath)
		if val != nil && nodePath == "/" {
			selfVal := r.Self(clientIP, "/")
			if selfVal != nil {
				mapVal, ok := val.(map[string]interface{})
				if ok {
					mapVal["self"] = selfVal
				}
			}
		}
	}
	return
}

func (r *MetadataRepo) Watch(clientIP string, nodePath string, closeChan <-chan bool) interface{} {
	nodePath = path.Join("/", nodePath)

	if r.onlySelf {
		if nodePath == "/" {
			return r.WatchSelf(clientIP, "/", closeChan)
		} else {
			return nil
		}
	} else {
		w := r.data.Watch(nodePath)
		return r.changeToResult(w, closeChan)
	}
}

var TIMER_NIL *time.Timer = &time.Timer{C: nil}

func (r *MetadataRepo) changeToResult(watcher store.Watcher, stopChan <-chan bool) interface{} {
	defer watcher.Remove()
	m := make(map[string]string)
	timer := TIMER_NIL

	for {
		var finish bool = false
		select {
		case e, ok := <-watcher.EventChan():
			if ok {
				value := fmt.Sprintf("%s|%s", e.Action, e.Value)
				// if event is one leaf node, just return value.
				if e.Path == "/" {
					return value
				}
				m[e.Path] = value
				timer = r.timerPool.AcquireTimer()
			} else {
				finish = true
			}
		case <-timer.C:
			finish = true
		case <-stopChan:
			//when stop, return empty map, discard prev result.
			m = make(map[string]string)
			finish = true
		}
		if finish {
			if timer.C != nil {
				r.timerPool.ReleaseTimer(timer)
			}
			break
		}
		//TODO check map size, avoid too big result.
	}
	return flatmap.Expand(m, "/")
}

func (r *MetadataRepo) WatchSelf(clientIP string, nodePath string, closeChan <-chan bool) interface{} {
	nodePath = path.Join(clientIP, "/", nodePath)
	if log.IsDebugEnable() {
		log.Debug("WatchSelf nodePath: %s", nodePath)
	}
	mappingData := r.GetMapping(nodePath)
	if mappingData == nil {
		return nil
	}
	mappingWatcher := r.mapping.Watch(nodePath)
	defer mappingWatcher.Remove()

	stopChan := make(chan bool)
	defer close(stopChan)
	go func() {
		select {
		case _, ok := <-mappingWatcher.EventChan():
			if ok {
				stopChan <- true
			}
		case <-closeChan:
			stopChan <- true
		case <-stopChan:
			return
		}
	}()

	mapping, mok := mappingData.(map[string]interface{})
	if !mok {
		dataNodePath := fmt.Sprintf("%s", mappingData)
		//log.Debug("watcher: %v", dataNodePath)
		w := r.data.Watch(dataNodePath)
		return r.changeToResult(w, stopChan)
	} else {
		flatMapping := flatmap.Flatten(mapping)
		watchers := make(map[string]store.Watcher)
		for k, v := range flatMapping {
			watchers[k] = r.data.Watch(v)
		}
		//log.Debug("aggWatcher: %v", watchers)
		aggWatcher := store.NewAggregateWatcher(watchers)
		return r.changeToResult(aggWatcher, stopChan)
	}
}

func (r *MetadataRepo) Self(clientIP string, nodePath string) interface{} {
	if clientIP == "" {
		panic(errors.New("clientIP must not be empty."))
	}
	nodePath = path.Join("/", nodePath)
	mappingData := r.GetMapping(path.Join("/", clientIP))
	if mappingData == nil {
		if log.IsDebugEnable() {
			log.Debug("Can not find mapping for %s", clientIP)
		}
		return nil
	}
	mapping, mok := mappingData.(map[string]interface{})
	if !mok {
		log.Warning("Mapping for %s is not a map, result:%v", clientIP, mappingData)
		return nil
	}
	return r.getMappingDatas(nodePath, mapping)
}

func (r *MetadataRepo) getMappingData(nodePath, link string) interface{} {
	nodePath = path.Join(link, nodePath)
	_, val := r.data.Get(nodePath)
	return val
}

func (r *MetadataRepo) getMappingDatas(nodePath string, mapping map[string]interface{}) interface{} {
	nodePath = path.Join("/", nodePath)
	paths := strings.Split(nodePath, "/")[1:] // trim first blank item
	// nodePath is "/"
	if paths[0] == "" {
		meta := make(map[string]interface{})
		for k, v := range mapping {
			submapping, isMap := v.(map[string]interface{})
			if isMap {
				val := r.getMappingDatas("/", submapping)
				if val != nil {
					meta[k] = val
				} else {
					log.Warning("Can not get values from backend by mapping: %v", submapping)
				}
			} else {
				subNodePath := fmt.Sprintf("%v", v)
				val := r.getMappingData("/", subNodePath)
				if val != nil {
					meta[k] = val
				} else {
					log.Warning("Can not get values from backend by mapping: %v", subNodePath)
				}
			}

		}
		return meta
	} else {
		elemName := paths[0]
		elemValue, ok := mapping[elemName]
		if ok {
			submapping, isMap := elemValue.(map[string]interface{})
			if isMap {
				return r.getMappingDatas(path.Join(paths[1:]...), submapping)
			} else {
				return r.getMappingData(path.Join(paths[1:]...), fmt.Sprintf("%v", elemValue))
			}
		} else {
			if log.IsDebugEnable() {
				log.Debug("Can not find mapping for : %v, mapping:%v", nodePath, mapping)
			}
			return nil
		}
	}
}

func (r *MetadataRepo) GetData(nodePath string) interface{} {
	_, val := r.data.Get(nodePath)
	return val
}

func (r *MetadataRepo) PutData(nodePath string, data interface{}, replace bool) error {
	return r.storeClient.Put(nodePath, data, replace)
}

func (r *MetadataRepo) DeleteData(nodePath string, subs ...string) error {
	err := checkSubs(subs)
	if err != nil {
		return err
	}
	if len(subs) > 0 {
		for _, sub := range subs {
			subPath := path.Join(nodePath, sub)
			_, v := r.data.Get(subPath)
			// if subPath metadata not exist, just ignore.
			if v != nil {
				_, dir := v.(map[string]interface{})
				err = r.storeClient.Delete(subPath, dir)
				if err != nil {
					return err
				}
			}
		}
		return nil
	} else {
		_, v := r.data.Get(nodePath)
		if v != nil {
			_, dir := v.(map[string]interface{})
			return r.storeClient.Delete(nodePath, dir)
		}
		return nil
	}

}

func (r *MetadataRepo) GetMapping(nodePath string) interface{} {
	_, val := r.mapping.Get(nodePath)
	return val
}

func (r *MetadataRepo) PutMapping(nodePath string, data interface{}, replace bool) error {
	nodePath = path.Join("/", nodePath)
	if nodePath == "/" {
		m, ok := data.(map[string]interface{})
		if !ok {
			log.Warning("Unexpect data type for mapping: %s", reflect.TypeOf(data))
			return errors.New("mapping data should be json object.")
		}
		for k, v := range m {
			ip := net.ParseIP(k)
			if ip == nil {
				return errors.New("mapping's first level key should be ip .")
			}
			err := checkMapping(v)
			if err != nil {
				return err
			}
		}
	} else {
		parts := strings.Split(nodePath, "/")
		ip := net.ParseIP(parts[1])
		if ip == nil {
			return errors.New("mapping's first level key should be ip .")
		}
		// nodePath: /ip
		if len(parts) == 2 {
			err := checkMapping(data)
			if err != nil {
				return err
			}
		} else {
			// nodePath: /ip/{key:.*}
			_, isMap := data.(map[string]interface{})
			if isMap {
				err := checkMapping(data)
				if err != nil {
					return err
				}
			} else {
				err := checkMappingPath(data)
				if err != nil {
					return err
				}
			}
		}
	}
	return r.storeClient.PutMapping(nodePath, data, replace)
}

func (r *MetadataRepo) DeleteMapping(nodePath string, subs ...string) error {
	err := checkSubs(subs)
	if err != nil {
		return err
	}
	if len(subs) > 0 {
		for _, sub := range subs {
			subPath := path.Join(nodePath, sub)
			_, v := r.mapping.Get(subPath)
			// if subPath mapping not exist, just ignore.
			if v != nil {
				_, dir := v.(map[string]interface{})
				err = r.storeClient.DeleteMapping(subPath, dir)
				if err != nil {
					return err
				}
			}
		}
		return nil
	} else {
		_, v := r.mapping.Get(nodePath)
		if v != nil {
			_, dir := v.(map[string]interface{})
			return r.storeClient.DeleteMapping(nodePath, dir)
		}
		return nil
	}
}

func (r *MetadataRepo) DataVersion() int64 {
	return r.data.Version()
}

func checkSubs(subs []string) error {
	for _, sub := range subs {
		if strings.Index(sub, "/") >= 0 {
			return errors.New("Sub node must not a path.")
		}
	}
	return nil
}

func checkMapping(data interface{}) error {
	m, ok := data.(map[string]interface{})
	if !ok {
		return errors.New("mapping data should be json object.")
	}
	for k, v := range m {
		if strings.Index(k, "/") >= 0 {
			return errors.New("mapping key should not be path.")
		}
		_, isMap := v.(map[string]interface{})
		if isMap {
			err := checkMapping(v)
			if err != nil {
				return err
			}
		} else {
			err := checkMappingPath(v)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

func checkMappingPath(v interface{}) error {
	vs, vok := v.(string)
	if !vok {
		return errors.New("mapping's value should be path .")
	}
	if vs == "" || vs[0] != '/' {
		return errors.New("mapping's value should be path .")
	}
	return nil
}
<|endoftext|>


<commit_before>package metrics

import (
	"context"

	"github.com/juju/errors"
	"github.com/prometheus/client_golang/prometheus"
)

var (
	errCollectorHasRegistered  = "metrics: collector '%s' has registered"
	errRegisterCollector       = "metrics: register collector '%s'"
	errRegisterCustomCollector = "metrics: register custom collector '%s'"
)

var m *Metrics

var collectors map[string]prometheus.Collector

func init() {
	m = New()
}

// New returns an initialized Viper instance.
func New() *Metrics {
	collectors = make(map[string]prometheus.Collector, 0)
	return new(Metrics)
}

type Metrics struct {
}

func (i *Metrics) Prepare(ctx context.Context) (context.Context, error) {
	return ctx, nil
}
func (i *Metrics) Initiate(ctx context.Context) (context.Context, error) {
	return ctx, nil
}
func (i *Metrics) OnStartup(ctx context.Context) (context.Context, error) {
	return ctx, nil
}
func (i *Metrics) OnShutdown(ctx context.Context) (context.Context, error) {
	return ctx, nil
}
func RegisterCollector(name string, cs prometheus.Collector) error {
	return m.RegisterCollector(name, cs)
}
func (i *Metrics) RegisterCollector(name string, cs prometheus.Collector) error {
	if _, ok := collectors[name]; !ok {
		collectors[name] = cs
		prometheus.MustRegister(cs)
	} else {
		return errors.Annotate(errors.Errorf(errCollectorHasRegistered, name), errRegisterCollector)
	}
	return nil
}
func GetCollector(name string) prometheus.Collector { return m.GetCollector(name) }
func (i *Metrics) GetCollector(name string) (cs prometheus.Collector) {
	if _, ok := collectors[name]; ok {
		return collectors[name]
	} else {
		return nil
	}
}

func RegisterCustomCollector(name string, reg prometheus.Registerer, descs []*prometheus.Desc, zone string, hosts []string) error {
	return m.RegisterCustomCollector(name, reg, descs, zone, hosts)
}
func (i *Metrics) RegisterCustomCollector(name string, reg prometheus.Registerer, descs []*prometheus.Desc, zone string, hosts []string) error {
	if _, ok := collectors[name]; !ok {
		c := &ClusterCollector{
			descs: descs,
			hosts: hosts,
		}
		prometheus.WrapRegistererWith(prometheus.Labels{"zone": zone}, reg).MustRegister(c)
		reg.MustRegister(
			prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}),
			prometheus.NewGoCollector(),
		)
		collectors[name] = c
	} else {
		return errors.Annotate(errors.Errorf(errCollectorHasRegistered, name), errRegisterCustomCollector)
	}
	return nil
}

type ClusterCollector struct {
	descs []*prometheus.Desc
	hosts []string
}

func (i *ClusterCollector) Describe(ch chan<- *prometheus.Desc) {
	prometheus.DescribeByCollect(i, ch)
}

func (i *ClusterCollector) Collect(ch chan<- prometheus.Metric) {
	for _, desc := range i.descs {
		for index, host := range i.hosts {
			ch <- prometheus.MustNewConstMetric(
				desc,
				prometheus.CounterValue,
				float64(index),
				host,
			)
		}

	}
}
<commit_msg>metric<commit_after>package metrics

import (
	"context"

	"github.com/juju/errors"
	"github.com/prometheus/client_golang/prometheus"
)

var (
	errSummaryHasRegistered   = "metrics: summary '%s' has registered"
	errRegisterSummary        = "metrics: register summary '%s'"
	errCounterHasRegistered   = "metrics: counter '%s' has registered"
	errRegisterCounter        = "metrics: register counter '%s'"
	errGaugeHasRegistered     = "metrics: gauge '%s' has registered"
	errRegisterGauge          = "metrics: register gauge '%s'"
	errHistogramHasRegistered = "metrics: histogram '%s' has registered"
	errRegisterHistogram      = "metrics: register histogram '%s'"
)

var m *Metrics

var summarys map[string]*prometheus.SummaryVec
var counters map[string]*prometheus.CounterVec
var gauges map[string]*prometheus.GaugeVec
var histograms map[string]*prometheus.HistogramVec

func init() {
	m = New()
}

// New returns an initialized Viper instance.
func New() *Metrics {
	summarys = make(map[string]*prometheus.SummaryVec, 0)
	counters = make(map[string]*prometheus.CounterVec, 0)
	gauges = make(map[string]*prometheus.GaugeVec, 0)
	histograms = make(map[string]*prometheus.HistogramVec, 0)
	return new(Metrics)
}

type Metrics struct {
}

func (i *Metrics) Prepare(ctx context.Context) (context.Context, error) {
	return ctx, nil
}
func (i *Metrics) Initiate(ctx context.Context) (context.Context, error) {
	return ctx, nil
}
func (i *Metrics) OnStartup(ctx context.Context) (context.Context, error) {
	return ctx, nil
}
func (i *Metrics) OnShutdown(ctx context.Context) (context.Context, error) {
	return ctx, nil
}

func RegisterSummary(name string, cs *prometheus.SummaryVec) error {
	return m.RegisterSummary(name, cs)
}
func (i *Metrics) RegisterSummary(name string, cs *prometheus.SummaryVec) error {
	if _, ok := summarys[name]; !ok {
		summarys[name] = cs
		prometheus.MustRegister(cs)
	} else {
		return errors.Annotate(errors.Errorf(errSummaryHasRegistered, name), errRegisterSummary)
	}
	return nil
}

func GetSummary(name string) *prometheus.SummaryVec { return m.GetSummary(name) }
func (i *Metrics) GetSummary(name string) (cs *prometheus.SummaryVec) {
	if _, ok := summarys[name]; ok {
		return summarys[name]
	} else {
		return nil
	}
}

func RegisterCounter(name string, cs *prometheus.CounterVec) error {
	return m.RegisterCounter(name, cs)
}
func (i *Metrics) RegisterCounter(name string, cs *prometheus.CounterVec) error {
	if _, ok := counters[name]; !ok {
		counters[name] = cs
		prometheus.MustRegister(cs)
	} else {
		return errors.Annotate(errors.Errorf(errCounterHasRegistered, name), errRegisterCounter)
	}
	return nil
}

func GetCounter(name string) *prometheus.CounterVec { return m.GetCounter(name) }
func (i *Metrics) GetCounter(name string) (cs *prometheus.CounterVec) {
	if _, ok := counters[name]; ok {
		return counters[name]
	} else {
		return nil
	}
}

func RegisterGauge(name string, cs *prometheus.GaugeVec) error {
	return m.RegisterGauge(name, cs)
}
func (i *Metrics) RegisterGauge(name string, cs *prometheus.GaugeVec) error {
	if _, ok := gauges[name]; !ok {
		gauges[name] = cs
		prometheus.MustRegister(cs)
	} else {
		return errors.Annotate(errors.Errorf(errGaugeHasRegistered, name), errRegisterGauge)
	}
	return nil
}

func GetGauge(name string) *prometheus.GaugeVec { return m.GetGauge(name) }
func (i *Metrics) GetGauge(name string) (cs *prometheus.GaugeVec) {
	if _, ok := gauges[name]; ok {
		return gauges[name]
	} else {
		return nil
	}
}

func RegisterHistogram(name string, cs *prometheus.HistogramVec) error {
	return m.RegisterHistogram(name, cs)
}
func (i *Metrics) RegisterHistogram(name string, cs *prometheus.HistogramVec) error {
	if _, ok := histograms[name]; !ok {
		histograms[name] = cs
		prometheus.MustRegister(cs)
	} else {
		return errors.Annotate(errors.Errorf(errHistogramHasRegistered, name), errRegisterHistogram)
	}
	return nil
}

func GetHistogram(name string) *prometheus.HistogramVec { return m.GetHistogram(name) }
func (i *Metrics) GetHistogram(name string) (cs *prometheus.HistogramVec) {
	if _, ok := histograms[name]; ok {
		return histograms[name]
	} else {
		return nil
	}
}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Aaron Meihm ameihm@mozilla.com [:alm]
package main

import (
	"fmt"
	"github.com/gorhill/cronexpr"
	"gopkg.in/gcfg.v1"
	"mig.ninja/mig"
	"mig.ninja/mig/client"
	"path"
	"time"
)

type entity struct {
	name     string
	baseDir  string
	confPath string
	modTime  time.Time

	deadChan chan bool
	abortRun chan bool
	cfg      entityConfig
}

type entityConfig struct {
	Configuration struct {
		Schedule string
		Plugin   string
	}
}

func (e *entityConfig) validate() error {
	if e.Configuration.Schedule == "" {
		return fmt.Errorf("missing schedule")
	}
	_, err := cronexpr.Parse(e.Configuration.Schedule)
	if err != nil {
		return fmt.Errorf("cron expression: %v", err)
	}
	return nil
}

func (e *entity) launchAction() (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("launchAction() -> %v", e)
		}
	}()
	// Load the action from the entity run directory
	actpath := path.Join(e.baseDir, "action.json")
	act, err := mig.ActionFromFile(actpath)
	if err != nil {
		panic(err)
	}

	cli, err := client.NewClient(ctx.ClientConf, "mig-runner")
	if err != nil {
		panic(err)
	}

	// Borrow some logic from the action generator.
	act.ValidFrom = time.Now().Add(-60 * time.Second).UTC()
	period, err := time.ParseDuration("2m")
	if err != nil {
		panic(err)
	}
	act.ExpireAfter = act.ValidFrom.Add(period)
	asig, err := cli.SignAction(act)
	if err != nil {
		panic(err)
	}
	act = asig

	res, err := cli.PostAction(act)
	if err != nil {
		panic(err)
	}
	mlog("%v: launched action %.0f", e.name, res.ID)

	// Notify the results processor an action is in-flight
	re := mig.RunnerResult{}
	re.EntityName = e.name
	re.Action = res
	re.UsePlugin = e.cfg.Configuration.Plugin
	ctx.Channels.Results <- re

	return nil
}

func (e *entity) start() {
	xr := func(s string, args ...interface{}) {
		mlog(s, args...)
		e.deadChan <- true
	}

	e.abortRun = make(chan bool, 1)
	for {
		cexpr, err := cronexpr.Parse(e.cfg.Configuration.Schedule)
		if err != nil {
			xr("%v: %v", e.name, err)
			return
		}
		nrun := cexpr.Next(time.Now())
		waitduration := nrun.Sub(time.Now())
		mlog("%v: will run at %v (in %v)", e.name, nrun, waitduration)
		select {
		case <-e.abortRun:
			mlog("%v: asked to terminate, stopping", e.name)
			return
		case <-time.After(waitduration):
		}
		mlog("%v: running", e.name)
		err = e.launchAction()
		if err != nil {
			xr("%v: %v", e.name, err)
			return
		}
	}
	mlog("%v: job exiting", e.name)
	e.deadChan <- true
}

func (e *entity) stop() {
	close(e.abortRun)
}

func (e *entity) load() (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("load() -> %v", e)
		}
	}()
	err = gcfg.ReadFileInto(&e.cfg, e.confPath)
	if err != nil {
		panic(err)
	}

	// Make sure an action file exists and is valid before we
	// schedule this.
	actpath := path.Join(e.baseDir, "action.json")
	_, err = mig.ActionFromFile(actpath)
	if err != nil {
		panic(err)
	}

	err = e.cfg.validate()
	if err != nil {
		panic(err)
	}
	return nil
}
<commit_msg>[minor] improve flexibility for action expiry handling<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Aaron Meihm ameihm@mozilla.com [:alm]
package main

import (
	"fmt"
	"github.com/gorhill/cronexpr"
	"gopkg.in/gcfg.v1"
	"mig.ninja/mig"
	"mig.ninja/mig/client"
	"path"
	"time"
)

// The default expiry time for an action launched by the runner if the
// entity configuration does not include an expiry.
var defaultExpiry = "5m"

type entity struct {
	name     string
	baseDir  string
	confPath string
	modTime  time.Time

	deadChan chan bool
	abortRun chan bool
	cfg      entityConfig
}

type entityConfig struct {
	Configuration struct {
		Schedule string
		Plugin   string
		Expiry   string
	}
}

func (e *entityConfig) validate() error {
	if e.Configuration.Schedule == "" {
		return fmt.Errorf("missing schedule")
	}
	_, err := cronexpr.Parse(e.Configuration.Schedule)
	if err != nil {
		return fmt.Errorf("cron expression: %v", err)
	}
	return nil
}

func (e *entity) launchAction() (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("launchAction() -> %v", e)
		}
	}()
	// Load the action from the entity run directory
	actpath := path.Join(e.baseDir, "action.json")
	act, err := mig.ActionFromFile(actpath)
	if err != nil {
		panic(err)
	}

	cli, err := client.NewClient(ctx.ClientConf, "mig-runner")
	if err != nil {
		panic(err)
	}

	// Borrow some logic from the action generator. Set a validation
	// period starting in the past so our action starts immediately.
	window := time.Duration(-60 * time.Second)
	act.ValidFrom = time.Now().Add(window).UTC()
	exstring := defaultExpiry
	if e.cfg.Configuration.Expiry != "" {
		exstring = e.cfg.Configuration.Expiry
	}
	period, err := time.ParseDuration(exstring)
	if err != nil {
		panic(err)
	}
	// Add the window period to the desired expiry since our start
	// time begins in the past.
	period += -window
	act.ExpireAfter = act.ValidFrom.Add(period)
	asig, err := cli.SignAction(act)
	if err != nil {
		panic(err)
	}
	act = asig

	res, err := cli.PostAction(act)
	if err != nil {
		panic(err)
	}
	mlog("%v: launched action %.0f", e.name, res.ID)

	// Notify the results processor an action is in-flight
	re := mig.RunnerResult{}
	re.EntityName = e.name
	re.Action = res
	re.UsePlugin = e.cfg.Configuration.Plugin
	ctx.Channels.Results <- re

	return nil
}

func (e *entity) start() {
	xr := func(s string, args ...interface{}) {
		mlog(s, args...)
		e.deadChan <- true
	}

	e.abortRun = make(chan bool, 1)
	for {
		cexpr, err := cronexpr.Parse(e.cfg.Configuration.Schedule)
		if err != nil {
			xr("%v: %v", e.name, err)
			return
		}
		nrun := cexpr.Next(time.Now())
		waitduration := nrun.Sub(time.Now())
		mlog("%v: will run at %v (in %v)", e.name, nrun, waitduration)
		select {
		case <-e.abortRun:
			mlog("%v: asked to terminate, stopping", e.name)
			return
		case <-time.After(waitduration):
		}
		mlog("%v: running", e.name)
		err = e.launchAction()
		if err != nil {
			xr("%v: %v", e.name, err)
			return
		}
	}
	mlog("%v: job exiting", e.name)
	e.deadChan <- true
}

func (e *entity) stop() {
	close(e.abortRun)
}

func (e *entity) load() (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("load() -> %v", e)
		}
	}()
	err = gcfg.ReadFileInto(&e.cfg, e.confPath)
	if err != nil {
		panic(err)
	}

	// Make sure an action file exists and is valid before we
	// schedule this.
	actpath := path.Join(e.baseDir, "action.json")
	_, err = mig.ActionFromFile(actpath)
	if err != nil {
		panic(err)
	}

	err = e.cfg.validate()
	if err != nil {
		panic(err)
	}
	return nil
}
<|endoftext|>


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

package main

import (
	"fmt"
	"io"
	"os"
	"testing"
	"time"

	"v.io/v23"
	"v.io/v23/context"
	"v.io/v23/options"
	"v.io/x/ref/services/mounttable/mounttablelib"
	"v.io/x/ref/test"
	"v.io/x/ref/test/modules"
)

//go:generate v23 test generate

func rootMT(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
	ctx, shutdown := v23.Init()
	defer shutdown()

	lspec := v23.GetListenSpec(ctx)
	server, err := v23.NewServer(ctx, options.ServesMountTable(true))
	if err != nil {
		return fmt.Errorf("root failed: %v", err)
	}
	mt, err := mounttablelib.NewMountTableDispatcher("", "mounttable")
	if err != nil {
		return fmt.Errorf("mounttable.NewMountTableDispatcher failed: %s", err)
	}
	eps, err := server.Listen(lspec)
	if err != nil {
		return fmt.Errorf("server.Listen failed: %s", err)
	}
	if err := server.ServeDispatcher("", mt); err != nil {
		return fmt.Errorf("root failed: %s", err)
	}
	fmt.Fprintf(stdout, "PID=%d\n", os.Getpid())
	for _, ep := range eps {
		fmt.Fprintf(stdout, "MT_NAME=%s\n", ep.Name())
	}
	modules.WaitForEOF(stdin)
	return nil
}

// Starts a mounttable.  Returns the name and a stop function.
func startMountTable(t *testing.T, ctx *context.T) (string, func()) {
	sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}

	rootMT, err := sh.Start("rootMT", nil, "--v23.tcp.address=127.0.0.1:0")
	if err != nil {
		t.Fatalf("failed to start root mount table: %s", err)
	}
	rootMT.ExpectVar("PID")
	rootName := rootMT.ExpectVar("MT_NAME")

	return rootName, func() {
		if err := sh.Cleanup(nil, nil); err != nil {
			t.Fatalf("failed to cleanup shell: %s", rootMT.Error())
		}
	}
}

// Asserts that the channel contains members with expected names and no others.
func AssertMembersWithNames(channel *channel, expectedNames []string) error {
	members, err := channel.getMembers()
	if err != nil {
		return fmt.Errorf("channel.getMembers() failed: %v", err)
	}
	if actualLen, expectedLen := len(members), len(expectedNames); actualLen != expectedLen {
		return fmt.Errorf("Wrong number of members.  Expected %v, actual %v.", expectedLen, actualLen)
	}
	for _, expectedName := range expectedNames {
		found := false
		for _, member := range members {
			if member.Name == expectedName {
				found = true
				break
			}
		}
		if !found {
			return fmt.Errorf("Expected member with name %v, but did not find one.", expectedName)
		}
	}
	return nil
}

func TestMembers(t *testing.T) {
	ctx, shutdown := test.InitForTest()
	defer shutdown()

	mounttable, stopMountTable := startMountTable(t, ctx)
	defer stopMountTable()

	proxy := ""

	path := "path/to/channel"

	// Create a new channel.
	channel, err := newChannel(ctx, mounttable, proxy, path)
	if err != nil {
		t.Fatal("newChannel(%v, %v, %v) failed: %v", ctx, mounttable, proxy, path)
	}

	// New channel should be empty.
	if err := AssertMembersWithNames(channel, []string{}); err != nil {
		t.Error(err)
	}

	// Join the channel.
	if err := channel.join(); err != nil {
		t.Fatalf("channel.join() failed: %v", err)
	}

	// Channel should contain only current user.
	if err := AssertMembersWithNames(channel, []string{channel.UserName()}); err != nil {
		t.Error(err)
	}

	// Create and join the channel a second time.
	channel2, err := newChannel(ctx, mounttable, proxy, path)
	if err != nil {
		t.Fatal("newChannel(%v, %v, %v) failed: %v", ctx, mounttable, proxy, path)
	}
	if err := channel2.join(); err != nil {
		t.Fatalf("channel2.join() failed: %v", err)
	}

	// Channel should contain both users.
	if err := AssertMembersWithNames(channel, []string{channel.UserName(), channel2.UserName()}); err != nil {
		t.Error(err)
	}

	// Leave first instance of channel.
	if err := channel.leave(); err != nil {
		t.Fatalf("channel.leave() failed: %v", err)
	}

	// Channel should contain only second user.
	if err := AssertMembersWithNames(channel, []string{channel2.UserName()}); err != nil {
		t.Error(err)
	}

	// Leave second instance of channel.
	if err := channel2.leave(); err != nil {
		t.Fatalf("channel2.leave() failed: %v", err)
	}

	// Channel should be empty.
	if err := AssertMembersWithNames(channel, []string{}); err != nil {
		t.Error(err)
	}
}

func TestBroadcastMessage(t *testing.T) {
	ctx, shutdown := test.InitForTest()
	defer shutdown()

	mounttable, stopMountTable := startMountTable(t, ctx)
	defer stopMountTable()

	proxy := ""

	path := "path/to/channel"

	channel, err := newChannel(ctx, mounttable, proxy, path)
	if err != nil {
		t.Fatalf("newChannel(%v, %v, %v) failed: %v", ctx, mounttable, proxy, path, err)
	}

	defer channel.leave()

	if err := channel.join(); err != nil {
		t.Fatalf("channel.join() failed: %v", err)
	}

	message := "Hello Vanadium world!"

	go func() {
		// Call getMembers(), which will set channel.members, used by
		// channel.broadcastMessage().
		if _, err := channel.getMembers(); err != nil {
			t.Fatalf("channel.getMembers() failed: %v", err)
		}
		if err := channel.broadcastMessage(message); err != nil {
			t.Fatalf("channel.broadcastMessage(%v) failed: %v", message, err)
		}
	}()

	select {
	case <-time.After(1 * time.Second):
		t.Errorf("Timeout waiting for message to be received.")
	case m := <-channel.messages:
		if m.Text != message {
			t.Errorf("Expected message text to be %v but got %v", message, m.Text)
		}
		if got, want := m.SenderName, channel.UserName(); got != want {
			t.Errorf("Got m.SenderName = %v, want %v", got, want)
		}
	}
}
<commit_msg>chat: Increase the deadline for a test.<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"io"
	"os"
	"testing"
	"time"

	"v.io/v23"
	"v.io/v23/context"
	"v.io/v23/options"
	"v.io/x/ref/services/mounttable/mounttablelib"
	"v.io/x/ref/test"
	"v.io/x/ref/test/modules"
)

//go:generate v23 test generate

func rootMT(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
	ctx, shutdown := v23.Init()
	defer shutdown()

	lspec := v23.GetListenSpec(ctx)
	server, err := v23.NewServer(ctx, options.ServesMountTable(true))
	if err != nil {
		return fmt.Errorf("root failed: %v", err)
	}
	mt, err := mounttablelib.NewMountTableDispatcher("", "mounttable")
	if err != nil {
		return fmt.Errorf("mounttable.NewMountTableDispatcher failed: %s", err)
	}
	eps, err := server.Listen(lspec)
	if err != nil {
		return fmt.Errorf("server.Listen failed: %s", err)
	}
	if err := server.ServeDispatcher("", mt); err != nil {
		return fmt.Errorf("root failed: %s", err)
	}
	fmt.Fprintf(stdout, "PID=%d\n", os.Getpid())
	for _, ep := range eps {
		fmt.Fprintf(stdout, "MT_NAME=%s\n", ep.Name())
	}
	modules.WaitForEOF(stdin)
	return nil
}

// Starts a mounttable.  Returns the name and a stop function.
func startMountTable(t *testing.T, ctx *context.T) (string, func()) {
	sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}

	rootMT, err := sh.Start("rootMT", nil, "--v23.tcp.address=127.0.0.1:0")
	if err != nil {
		t.Fatalf("failed to start root mount table: %s", err)
	}
	rootMT.ExpectVar("PID")
	rootName := rootMT.ExpectVar("MT_NAME")

	return rootName, func() {
		if err := sh.Cleanup(nil, nil); err != nil {
			t.Fatalf("failed to cleanup shell: %s", rootMT.Error())
		}
	}
}

// Asserts that the channel contains members with expected names and no others.
func AssertMembersWithNames(channel *channel, expectedNames []string) error {
	members, err := channel.getMembers()
	if err != nil {
		return fmt.Errorf("channel.getMembers() failed: %v", err)
	}
	if actualLen, expectedLen := len(members), len(expectedNames); actualLen != expectedLen {
		return fmt.Errorf("Wrong number of members.  Expected %v, actual %v.", expectedLen, actualLen)
	}
	for _, expectedName := range expectedNames {
		found := false
		for _, member := range members {
			if member.Name == expectedName {
				found = true
				break
			}
		}
		if !found {
			return fmt.Errorf("Expected member with name %v, but did not find one.", expectedName)
		}
	}
	return nil
}

func TestMembers(t *testing.T) {
	ctx, shutdown := test.InitForTest()
	defer shutdown()

	mounttable, stopMountTable := startMountTable(t, ctx)
	defer stopMountTable()

	proxy := ""

	path := "path/to/channel"

	// Create a new channel.
	channel, err := newChannel(ctx, mounttable, proxy, path)
	if err != nil {
		t.Fatal("newChannel(%v, %v, %v) failed: %v", ctx, mounttable, proxy, path)
	}

	// New channel should be empty.
	if err := AssertMembersWithNames(channel, []string{}); err != nil {
		t.Error(err)
	}

	// Join the channel.
	if err := channel.join(); err != nil {
		t.Fatalf("channel.join() failed: %v", err)
	}

	// Channel should contain only current user.
	if err := AssertMembersWithNames(channel, []string{channel.UserName()}); err != nil {
		t.Error(err)
	}

	// Create and join the channel a second time.
	channel2, err := newChannel(ctx, mounttable, proxy, path)
	if err != nil {
		t.Fatal("newChannel(%v, %v, %v) failed: %v", ctx, mounttable, proxy, path)
	}
	if err := channel2.join(); err != nil {
		t.Fatalf("channel2.join() failed: %v", err)
	}

	// Channel should contain both users.
	if err := AssertMembersWithNames(channel, []string{channel.UserName(), channel2.UserName()}); err != nil {
		t.Error(err)
	}

	// Leave first instance of channel.
	if err := channel.leave(); err != nil {
		t.Fatalf("channel.leave() failed: %v", err)
	}

	// Channel should contain only second user.
	if err := AssertMembersWithNames(channel, []string{channel2.UserName()}); err != nil {
		t.Error(err)
	}

	// Leave second instance of channel.
	if err := channel2.leave(); err != nil {
		t.Fatalf("channel2.leave() failed: %v", err)
	}

	// Channel should be empty.
	if err := AssertMembersWithNames(channel, []string{}); err != nil {
		t.Error(err)
	}
}

func TestBroadcastMessage(t *testing.T) {
	ctx, shutdown := test.InitForTest()
	defer shutdown()

	mounttable, stopMountTable := startMountTable(t, ctx)
	defer stopMountTable()

	proxy := ""

	path := "path/to/channel"

	channel, err := newChannel(ctx, mounttable, proxy, path)
	if err != nil {
		t.Fatalf("newChannel(%v, %v, %v) failed: %v", ctx, mounttable, proxy, path, err)
	}

	defer channel.leave()

	if err := channel.join(); err != nil {
		t.Fatalf("channel.join() failed: %v", err)
	}

	message := "Hello Vanadium world!"

	go func() {
		// Call getMembers(), which will set channel.members, used by
		// channel.broadcastMessage().
		if _, err := channel.getMembers(); err != nil {
			t.Fatalf("channel.getMembers() failed: %v", err)
		}
		if err := channel.broadcastMessage(message); err != nil {
			t.Fatalf("channel.broadcastMessage(%v) failed: %v", message, err)
		}
	}()

	select {
	case <-time.After(10 * time.Second):
		t.Errorf("Timeout waiting for message to be received.")
	case m := <-channel.messages:
		if m.Text != message {
			t.Errorf("Expected message text to be %v but got %v", message, m.Text)
		}
		if got, want := m.SenderName, channel.UserName(); got != want {
			t.Errorf("Got m.SenderName = %v, want %v", got, want)
		}
	}
}
<|endoftext|>