<commit_before>/*
Copyright 2015 The Kubernetes 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 util

import (
	"fmt"
	"os"
	"path"

	"github.com/golang/glog"
	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/apis/storage"
	clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
	"k8s.io/kubernetes/pkg/util/mount"
)

const readyFileName = "ready"

// IsReady checks for the existence of a regular file
// called 'ready' in the given directory and returns
// true if that file exists.
func IsReady(dir string) bool {
	readyFile := path.Join(dir, readyFileName)
	s, err := os.Stat(readyFile)
	if err != nil {
		return false
	}

	if !s.Mode().IsRegular() {
		glog.Errorf("ready-file is not a file: %s", readyFile)
		return false
	}

	return true
}

// SetReady creates a file called 'ready' in the given
// directory.  It logs an error if the file cannot be
// created.
func SetReady(dir string) {
	if err := os.MkdirAll(dir, 0750); err != nil && !os.IsExist(err) {
		glog.Errorf("Can't mkdir %s: %v", dir, err)
		return
	}

	readyFile := path.Join(dir, readyFileName)
	file, err := os.Create(readyFile)
	if err != nil {
		glog.Errorf("Can't touch %s: %v", readyFile, err)
		return
	}
	file.Close()
}

// UnmountPath is a common unmount routine that unmounts the given path and
// deletes the remaining directory if successful.
func UnmountPath(mountPath string, mounter mount.Interface) error {
	if pathExists, pathErr := PathExists(mountPath); pathErr != nil {
		return fmt.Errorf("Error checking if path exists: %v", pathErr)
	} else if !pathExists {
		glog.Warningf("Warning: Unmount skipped because path does not exist: %v", mountPath)
		return nil
	}

	notMnt, err := mounter.IsLikelyNotMountPoint(mountPath)
	if err != nil {
		return err
	}
	if notMnt {
		glog.Warningf("Warning: %q is not a mountpoint, deleting", mountPath)
		return os.Remove(mountPath)
	}

	// Unmount the mount path
	if err := mounter.Unmount(mountPath); err != nil {
		return err
	}
	notMnt, mntErr := mounter.IsLikelyNotMountPoint(mountPath)
	if mntErr != nil {
		return err
	}
	if notMnt {
		glog.V(4).Info("%q is unmounted, deleting the directory", mountPath)
		return os.Remove(mountPath)
	}
	return nil
}

// PathExists returns true if the specified path exists.
func PathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	} else if os.IsNotExist(err) {
		return false, nil
	} else {
		return false, err
	}
}

// GetSecretForPod locates secret by name in the pod's namespace and returns secret map
func GetSecretForPod(pod *api.Pod, secretName string, kubeClient clientset.Interface) (map[string]string, error) {
	secret := make(map[string]string)
	if kubeClient == nil {
		return secret, fmt.Errorf("Cannot get kube client")
	}
	secrets, err := kubeClient.Core().Secrets(pod.Namespace).Get(secretName)
	if err != nil {
		return secret, err
	}
	for name, data := range secrets.Data {
		secret[name] = string(data)
	}
	return secret, nil
}

// GetSecretForPV locates secret by name and namespace, verifies the secret type, and returns secret map
func GetSecretForPV(secretNamespace, secretName, volumePluginName string, kubeClient clientset.Interface) (map[string]string, error) {
	secret := make(map[string]string)
	if kubeClient == nil {
		return secret, fmt.Errorf("Cannot get kube client")
	}
	secrets, err := kubeClient.Core().Secrets(secretNamespace).Get(secretName)
	if err != nil {
		return secret, err
	}
	if secrets.Type != api.SecretType(volumePluginName) {
		return secret, fmt.Errorf("Cannot get secret of type %s", volumePluginName)
	}
	for name, data := range secrets.Data {
		secret[name] = string(data)
	}
	return secret, nil
}

func GetClassForVolume(kubeClient clientset.Interface, pv *api.PersistentVolume) (*storage.StorageClass, error) {
	// TODO: replace with a real attribute after beta
	className, found := pv.Annotations["volume.beta.kubernetes.io/storage-class"]
	if !found {
		return nil, fmt.Errorf("Volume has no class annotation")
	}

	class, err := kubeClient.Storage().StorageClasses().Get(className)
	if err != nil {
		return nil, err
	}
	return class, nil
}
<commit_msg>dependencies: pkg/volume<commit_after>/*
Copyright 2015 The Kubernetes 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 util

import (
	"fmt"
	"os"
	"path"

	"github.com/golang/glog"
	"k8s.io/kubernetes/pkg/api/v1"
	storage "k8s.io/kubernetes/pkg/apis/storage/v1beta1"
	clientset "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5"
	"k8s.io/kubernetes/pkg/util/mount"
)

const readyFileName = "ready"

// IsReady checks for the existence of a regular file
// called 'ready' in the given directory and returns
// true if that file exists.
func IsReady(dir string) bool {
	readyFile := path.Join(dir, readyFileName)
	s, err := os.Stat(readyFile)
	if err != nil {
		return false
	}

	if !s.Mode().IsRegular() {
		glog.Errorf("ready-file is not a file: %s", readyFile)
		return false
	}

	return true
}

// SetReady creates a file called 'ready' in the given
// directory.  It logs an error if the file cannot be
// created.
func SetReady(dir string) {
	if err := os.MkdirAll(dir, 0750); err != nil && !os.IsExist(err) {
		glog.Errorf("Can't mkdir %s: %v", dir, err)
		return
	}

	readyFile := path.Join(dir, readyFileName)
	file, err := os.Create(readyFile)
	if err != nil {
		glog.Errorf("Can't touch %s: %v", readyFile, err)
		return
	}
	file.Close()
}

// UnmountPath is a common unmount routine that unmounts the given path and
// deletes the remaining directory if successful.
func UnmountPath(mountPath string, mounter mount.Interface) error {
	if pathExists, pathErr := PathExists(mountPath); pathErr != nil {
		return fmt.Errorf("Error checking if path exists: %v", pathErr)
	} else if !pathExists {
		glog.Warningf("Warning: Unmount skipped because path does not exist: %v", mountPath)
		return nil
	}

	notMnt, err := mounter.IsLikelyNotMountPoint(mountPath)
	if err != nil {
		return err
	}
	if notMnt {
		glog.Warningf("Warning: %q is not a mountpoint, deleting", mountPath)
		return os.Remove(mountPath)
	}

	// Unmount the mount path
	if err := mounter.Unmount(mountPath); err != nil {
		return err
	}
	notMnt, mntErr := mounter.IsLikelyNotMountPoint(mountPath)
	if mntErr != nil {
		return err
	}
	if notMnt {
		glog.V(4).Info("%q is unmounted, deleting the directory", mountPath)
		return os.Remove(mountPath)
	}
	return nil
}

// PathExists returns true if the specified path exists.
func PathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	} else if os.IsNotExist(err) {
		return false, nil
	} else {
		return false, err
	}
}

// GetSecretForPod locates secret by name in the pod's namespace and returns secret map
func GetSecretForPod(pod *v1.Pod, secretName string, kubeClient clientset.Interface) (map[string]string, error) {
	secret := make(map[string]string)
	if kubeClient == nil {
		return secret, fmt.Errorf("Cannot get kube client")
	}
	secrets, err := kubeClient.Core().Secrets(pod.Namespace).Get(secretName)
	if err != nil {
		return secret, err
	}
	for name, data := range secrets.Data {
		secret[name] = string(data)
	}
	return secret, nil
}

// GetSecretForPV locates secret by name and namespace, verifies the secret type, and returns secret map
func GetSecretForPV(secretNamespace, secretName, volumePluginName string, kubeClient clientset.Interface) (map[string]string, error) {
	secret := make(map[string]string)
	if kubeClient == nil {
		return secret, fmt.Errorf("Cannot get kube client")
	}
	secrets, err := kubeClient.Core().Secrets(secretNamespace).Get(secretName)
	if err != nil {
		return secret, err
	}
	if secrets.Type != v1.SecretType(volumePluginName) {
		return secret, fmt.Errorf("Cannot get secret of type %s", volumePluginName)
	}
	for name, data := range secrets.Data {
		secret[name] = string(data)
	}
	return secret, nil
}

func GetClassForVolume(kubeClient clientset.Interface, pv *v1.PersistentVolume) (*storage.StorageClass, error) {
	// TODO: replace with a real attribute after beta
	className, found := pv.Annotations["volume.beta.kubernetes.io/storage-class"]
	if !found {
		return nil, fmt.Errorf("Volume has no class annotation")
	}

	class, err := kubeClient.Storage().StorageClasses().Get(className)
	if err != nil {
		return nil, err
	}
	return class, nil
}
<|endoftext|>


<commit_before>package config

import (
	"compress/gzip"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"sort"
	"time"

	"github.com/getlantern/appdir"
	"github.com/getlantern/fronted"
	"github.com/getlantern/geolookup"
	"github.com/getlantern/golog"
	"github.com/getlantern/proxiedsites"
	"github.com/getlantern/waitforserver"
	"github.com/getlantern/yaml"
	"github.com/getlantern/yamlconf"

	"github.com/getlantern/flashlight/client"
	"github.com/getlantern/flashlight/globals"
	"github.com/getlantern/flashlight/server"
	"github.com/getlantern/flashlight/statreporter"
	"github.com/getlantern/flashlight/util"
)

const (
	CloudConfigPollInterval = 1 * time.Minute

	cloudflare  = "cloudflare"
	etag        = "ETag"
	ifNoneMatch = "If-None-Match"
)

var (
	log                 = golog.LoggerFor("flashlight.config")
	m                   *yamlconf.Manager
	lastCloudConfigETag = ""
)

type Config struct {
	Version       int
	CloudConfig   string
	CloudConfigCA string
	Addr          string
	Role          string
	InstanceId    string
	Country       string
	CpuProfile    string
	MemProfile    string
	UIAddr        string // UI HTTP server address
	AutoReport    *bool  // Report anonymous usage to GA
	Stats         *statreporter.Config
	Server        *server.ServerConfig
	Client        *client.ClientConfig
	ProxiedSites  *proxiedsites.Config // List of proxied site domains that get routed through Lantern rather than accessed directly
	TrustedCAs    []*CA
}

// CA represents a certificate authority
type CA struct {
	CommonName string
	Cert       string // PEM-encoded
}

// Init initializes the configuration system.
func Init() (*Config, error) {
	configPath, err := InConfigDir("lantern.yaml")
	if err != nil {
		return nil, err
	}
	m = &yamlconf.Manager{
		FilePath:         configPath,
		FilePollInterval: 1 * time.Second,
		ConfigServerAddr: *configaddr,
		EmptyConfig: func() yamlconf.Config {
			return &Config{}
		},
		OneTimeSetup: func(ycfg yamlconf.Config) error {
			cfg := ycfg.(*Config)
			return cfg.applyFlags()
		},
		CustomPoll: func(currentCfg yamlconf.Config) (mutate func(yamlconf.Config) error, waitTime time.Duration, err error) {
			// By default, do nothing
			mutate = func(ycfg yamlconf.Config) error {
				// do nothing
				return nil
			}
			cfg := currentCfg.(*Config)
			waitTime = cfg.cloudPollSleepTime()
			if cfg.CloudConfig == "" {
				// Config doesn't have a CloudConfig, just ignore
				return
			}

			var bytes []byte
			bytes, err = cfg.fetchCloudConfig()
			if err == nil && bytes != nil {
				mutate = func(ycfg yamlconf.Config) error {
					log.Debugf("Merging cloud configuration")
					cfg := ycfg.(*Config)
					return cfg.updateFrom(bytes)
				}
			}
			return
		},
	}
	initial, err := m.Start()
	var cfg *Config
	if err == nil {
		cfg = initial.(*Config)
		err = updateGlobals(cfg)
		if err != nil {
			return nil, err
		}
	}
	return cfg, err
}

// Run runs the configuration system.
func Run(updateHandler func(updated *Config)) error {
	for {
		next := m.Next()
		nextCfg := next.(*Config)
		err := updateGlobals(nextCfg)
		if err != nil {
			return err
		}
		updateHandler(nextCfg)
	}
}

func updateGlobals(cfg *Config) error {
	globals.InstanceId = cfg.InstanceId
	loc := &geolookup.City{}
	loc.Country.IsoCode = cfg.Country
	globals.SetLocation(loc)
	err := globals.SetTrustedCAs(cfg.TrustedCACerts())
	if err != nil {
		return fmt.Errorf("Unable to configure trusted CAs: %s", err)
	}
	return nil
}

// Update updates the configuration using the given mutator function.
func Update(mutate func(cfg *Config) error) error {
	return m.Update(func(ycfg yamlconf.Config) error {
		return mutate(ycfg.(*Config))
	})
}

// InConfigDir returns the path to the given filename inside of the configdir.
func InConfigDir(filename string) (string, error) {
	cdir := *configdir

	if cdir == "" {
		if runtime.GOOS == "linux" {
			// It is more common on Linux to expect application related directories
			// in all lowercase. The lantern wrapper also expects a lowercased
			// directory.
			cdir = appdir.General("lantern")

			// Backwards compatibility fix.
			oldDir := appdir.General("Lantern")

			if _, err := os.Stat(oldDir); err == nil {
				// If the old configuration path exists, try to rename it. We don't
				// need to catch the error, if something fails then we'll start a new
				// config directory.
				os.Rename(oldDir, cdir)
			}
		} else {
			// In OSX and Windows, they prefer to see the first letter in uppercase.
			cdir = appdir.General("Lantern")
		}
	}

	log.Debugf("Placing configuration in %v", cdir)
	if _, err := os.Stat(cdir); err != nil {
		if os.IsNotExist(err) {
			// Create config dir
			if err := os.MkdirAll(cdir, 0750); err != nil {
				return "", fmt.Errorf("Unable to create configdir at %s: %s", cdir, err)
			}
		}
	}

	return filepath.Join(cdir, filename), nil
}

// TrustedCACerts returns a slice of PEM-encoded certs for the trusted CAs
func (cfg *Config) TrustedCACerts() []string {
	certs := make([]string, 0, len(cfg.TrustedCAs))
	for _, ca := range cfg.TrustedCAs {
		certs = append(certs, ca.Cert)
	}
	return certs
}

// GetVersion implements the method from interface yamlconf.Config
func (cfg *Config) GetVersion() int {
	return cfg.Version
}

// SetVersion implements the method from interface yamlconf.Config
func (cfg *Config) SetVersion(version int) {
	cfg.Version = version
}

// ApplyDefaults implements the method from interface yamlconf.Config
//
// ApplyDefaults populates default values on a Config to make sure that we have
// a minimum viable config for running.  As new settings are added to
// flashlight, this function should be updated to provide sensible defaults for
// those settings.
func (cfg *Config) ApplyDefaults() {
	if cfg.Role == "" {
		cfg.Role = "client"
	}

	if cfg.Addr == "" {
		cfg.Addr = "localhost:8787"
	}

	if cfg.UIAddr == "" {
		cfg.UIAddr = "localhost:16823"
	}

	if cfg.CloudConfig == "" {
		cfg.CloudConfig = "https://s3.amazonaws.com/lantern_config/cloud.2.0.0-nl.yaml.gz"
	}

	// Default country
	if cfg.Country == "" {
		cfg.Country = *country
	}

	// Make sure we always have a stats config
	if cfg.Stats == nil {
		cfg.Stats = &statreporter.Config{}
	}

	if cfg.Stats.StatshubAddr == "" {
		cfg.Stats.StatshubAddr = *statshubAddr
	}

	if cfg.Client != nil && cfg.Role == "client" {
		cfg.applyClientDefaults()
	}

	if cfg.ProxiedSites == nil {
		log.Debugf("Adding empty proxiedsites")
		cfg.ProxiedSites = &proxiedsites.Config{
			Delta: &proxiedsites.Delta{
				Additions: []string{},
				Deletions: []string{},
			},
			Cloud: []string{},
		}
	}

	if cfg.ProxiedSites.Cloud == nil || len(cfg.ProxiedSites.Cloud) == 0 {
		log.Debugf("Loading default cloud proxiedsites")
		cfg.ProxiedSites.Cloud = defaultProxiedSites
	}

	if cfg.TrustedCAs == nil || len(cfg.TrustedCAs) == 0 {
		cfg.TrustedCAs = defaultTrustedCAs
	}
}

func (cfg *Config) applyClientDefaults() {
	// Make sure we always have at least one masquerade set
	if cfg.Client.MasqueradeSets == nil {
		cfg.Client.MasqueradeSets = make(map[string][]*fronted.Masquerade)
	}
	if len(cfg.Client.MasqueradeSets) == 0 {
		cfg.Client.MasqueradeSets[cloudflare] = cloudflareMasquerades
	}

	// Make sure we always have at least one server
	if cfg.Client.FrontedServers == nil {
		cfg.Client.FrontedServers = make([]*client.FrontedServerInfo, 0)
	}
	if len(cfg.Client.FrontedServers) == 0 && len(cfg.Client.ChainedServers) == 0 {
		cfg.Client.FrontedServers = []*client.FrontedServerInfo{
			&client.FrontedServerInfo{
				Host:           "nl.fallbacks.getiantem.org",
				Port:           443,
				PoolSize:       30,
				MasqueradeSet:  cloudflare,
				MaxMasquerades: 20,
				QOS:            10,
				Weight:         4000,
			},
		}

		cfg.Client.ChainedServers = make(map[string]*client.ChainedServerInfo, len(fallbacks))
		for key, fb := range fallbacks {
			cfg.Client.ChainedServers[key] = fb
		}
	}

	if cfg.AutoReport == nil {
		cfg.AutoReport = new(bool)
		*cfg.AutoReport = true
	}

	// Make sure all servers have a QOS and Weight configured
	for _, server := range cfg.Client.FrontedServers {
		if server.QOS == 0 {
			server.QOS = 5
		}
		if server.Weight == 0 {
			server.Weight = 100
		}
		if server.RedialAttempts == 0 {
			server.RedialAttempts = 2
		}
	}

	// Always make sure we have a map of ChainedServers
	if cfg.Client.ChainedServers == nil {
		cfg.Client.ChainedServers = make(map[string]*client.ChainedServerInfo)
	}

	// Sort servers so that they're always in a predictable order
	cfg.Client.SortServers()
}

func (cfg *Config) IsDownstream() bool {
	return cfg.Role == "client"
}

func (cfg *Config) IsUpstream() bool {
	return !cfg.IsDownstream()
}

func (cfg Config) cloudPollSleepTime() time.Duration {
	return time.Duration((CloudConfigPollInterval.Nanoseconds() / 2) + rand.Int63n(CloudConfigPollInterval.Nanoseconds()))
}

func (cfg Config) fetchCloudConfig() (bytes []byte, err error) {
	log.Debugf("Fetching cloud config from: %s", cfg.CloudConfig)

	if cfg.IsDownstream() {
		// Clients must always proxy the request
		if cfg.Addr == "" {
			err = fmt.Errorf("No proxyAddr")
		} else {
			bytes, err = cfg.doFetchCloudConfig(cfg.Addr)
		}
	} else {
		bytes, err = cfg.doFetchCloudConfig("")
	}
	if err != nil {
		bytes = nil
		err = fmt.Errorf("Unable to read yaml from %s: %s", cfg.CloudConfig, err)
	}
	return
}

func (cfg Config) doFetchCloudConfig(proxyAddr string) ([]byte, error) {
	log.Tracef("doFetchCloudConfig via '%s'", proxyAddr)

	if proxyAddr != "" {
		// Wait for proxy to become available
		err := waitforserver.WaitForServer("tcp", proxyAddr, 30*time.Second)
		if err != nil {
			return nil, fmt.Errorf("Proxy never came up at %v: %v", proxyAddr, err)
		}
	}

	client, err := util.HTTPClient(cfg.CloudConfigCA, proxyAddr)
	if err != nil {
		return nil, fmt.Errorf("Unable to initialize HTTP client: %s", err)
	}

	log.Debugf("Checking for cloud configuration at: %s", cfg.CloudConfig)
	req, err := http.NewRequest("GET", cfg.CloudConfig, nil)
	if err != nil {
		return nil, fmt.Errorf("Unable to construct request for cloud config at %s: %s", cfg.CloudConfig, err)
	}
	if lastCloudConfigETag != "" {
		// Don't bother fetching if unchanged
		req.Header.Set(ifNoneMatch, lastCloudConfigETag)
	}
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("Unable to fetch cloud config at %s: %s", cfg.CloudConfig, err)
	}
	defer resp.Body.Close()

	if resp.StatusCode == 304 {
		log.Debugf("Config unchanged in cloud")
		return nil, nil
	} else if resp.StatusCode != 200 {
		return nil, fmt.Errorf("Unexpected response status: %d", resp.StatusCode)
	}

	lastCloudConfigETag = resp.Header.Get(etag)
	gzReader, err := gzip.NewReader(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("Unable to open gzip reader: %s", err)
	}
	return ioutil.ReadAll(gzReader)
}

// updateFrom creates a new Config by merging the given yaml into this Config.
// Any servers in the updated yaml replace ones in the original Config and any
// masquerade sets in the updated yaml replace ones in the original Config.
func (updated *Config) updateFrom(updateBytes []byte) error {
	err := yaml.Unmarshal(updateBytes, updated)
	if err != nil {
		return fmt.Errorf("Unable to unmarshal YAML for update: %s", err)
	}

	// Need to de-duplicate servers, since yaml appends them
	servers := make(map[string]*client.FrontedServerInfo)
	for _, server := range updated.Client.FrontedServers {
		servers[server.Host] = server
	}
	updated.Client.FrontedServers = make([]*client.FrontedServerInfo, 0, len(servers))
	for _, server := range servers {
		updated.Client.FrontedServers = append(updated.Client.FrontedServers, server)
	}

	// Same with global proxiedsites
	if len(updated.ProxiedSites.Cloud) > 0 {
		wlDomains := make(map[string]bool)
		for _, domain := range updated.ProxiedSites.Cloud {
			wlDomains[domain] = true
		}
		updated.ProxiedSites.Cloud = make([]string, 0, len(wlDomains))
		for domain, _ := range wlDomains {
			updated.ProxiedSites.Cloud = append(updated.ProxiedSites.Cloud, domain)
		}
		sort.Strings(updated.ProxiedSites.Cloud)
	}
	return nil
}
<commit_msg>It is not neccessary to worry about backwards compatiblity yet.<commit_after>package config

import (
	"compress/gzip"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"sort"
	"time"

	"github.com/getlantern/appdir"
	"github.com/getlantern/fronted"
	"github.com/getlantern/geolookup"
	"github.com/getlantern/golog"
	"github.com/getlantern/proxiedsites"
	"github.com/getlantern/waitforserver"
	"github.com/getlantern/yaml"
	"github.com/getlantern/yamlconf"

	"github.com/getlantern/flashlight/client"
	"github.com/getlantern/flashlight/globals"
	"github.com/getlantern/flashlight/server"
	"github.com/getlantern/flashlight/statreporter"
	"github.com/getlantern/flashlight/util"
)

const (
	CloudConfigPollInterval = 1 * time.Minute

	cloudflare  = "cloudflare"
	etag        = "ETag"
	ifNoneMatch = "If-None-Match"
)

var (
	log                 = golog.LoggerFor("flashlight.config")
	m                   *yamlconf.Manager
	lastCloudConfigETag = ""
)

type Config struct {
	Version       int
	CloudConfig   string
	CloudConfigCA string
	Addr          string
	Role          string
	InstanceId    string
	Country       string
	CpuProfile    string
	MemProfile    string
	UIAddr        string // UI HTTP server address
	AutoReport    *bool  // Report anonymous usage to GA
	Stats         *statreporter.Config
	Server        *server.ServerConfig
	Client        *client.ClientConfig
	ProxiedSites  *proxiedsites.Config // List of proxied site domains that get routed through Lantern rather than accessed directly
	TrustedCAs    []*CA
}

// CA represents a certificate authority
type CA struct {
	CommonName string
	Cert       string // PEM-encoded
}

// Init initializes the configuration system.
func Init() (*Config, error) {
	configPath, err := InConfigDir("lantern.yaml")
	if err != nil {
		return nil, err
	}
	m = &yamlconf.Manager{
		FilePath:         configPath,
		FilePollInterval: 1 * time.Second,
		ConfigServerAddr: *configaddr,
		EmptyConfig: func() yamlconf.Config {
			return &Config{}
		},
		OneTimeSetup: func(ycfg yamlconf.Config) error {
			cfg := ycfg.(*Config)
			return cfg.applyFlags()
		},
		CustomPoll: func(currentCfg yamlconf.Config) (mutate func(yamlconf.Config) error, waitTime time.Duration, err error) {
			// By default, do nothing
			mutate = func(ycfg yamlconf.Config) error {
				// do nothing
				return nil
			}
			cfg := currentCfg.(*Config)
			waitTime = cfg.cloudPollSleepTime()
			if cfg.CloudConfig == "" {
				// Config doesn't have a CloudConfig, just ignore
				return
			}

			var bytes []byte
			bytes, err = cfg.fetchCloudConfig()
			if err == nil && bytes != nil {
				mutate = func(ycfg yamlconf.Config) error {
					log.Debugf("Merging cloud configuration")
					cfg := ycfg.(*Config)
					return cfg.updateFrom(bytes)
				}
			}
			return
		},
	}
	initial, err := m.Start()
	var cfg *Config
	if err == nil {
		cfg = initial.(*Config)
		err = updateGlobals(cfg)
		if err != nil {
			return nil, err
		}
	}
	return cfg, err
}

// Run runs the configuration system.
func Run(updateHandler func(updated *Config)) error {
	for {
		next := m.Next()
		nextCfg := next.(*Config)
		err := updateGlobals(nextCfg)
		if err != nil {
			return err
		}
		updateHandler(nextCfg)
	}
}

func updateGlobals(cfg *Config) error {
	globals.InstanceId = cfg.InstanceId
	loc := &geolookup.City{}
	loc.Country.IsoCode = cfg.Country
	globals.SetLocation(loc)
	err := globals.SetTrustedCAs(cfg.TrustedCACerts())
	if err != nil {
		return fmt.Errorf("Unable to configure trusted CAs: %s", err)
	}
	return nil
}

// Update updates the configuration using the given mutator function.
func Update(mutate func(cfg *Config) error) error {
	return m.Update(func(ycfg yamlconf.Config) error {
		return mutate(ycfg.(*Config))
	})
}

// InConfigDir returns the path to the given filename inside of the configdir.
func InConfigDir(filename string) (string, error) {
	cdir := *configdir

	if cdir == "" {
		if runtime.GOOS == "linux" {
			// It is more common on Linux to expect application related directories
			// in all lowercase. The lantern wrapper also expects a lowercased
			// directory.
			cdir = appdir.General("lantern")
		} else {
			// In OSX and Windows, they prefer to see the first letter in uppercase.
			cdir = appdir.General("Lantern")
		}
	}

	log.Debugf("Placing configuration in %v", cdir)
	if _, err := os.Stat(cdir); err != nil {
		if os.IsNotExist(err) {
			// Create config dir
			if err := os.MkdirAll(cdir, 0750); err != nil {
				return "", fmt.Errorf("Unable to create configdir at %s: %s", cdir, err)
			}
		}
	}

	return filepath.Join(cdir, filename), nil
}

// TrustedCACerts returns a slice of PEM-encoded certs for the trusted CAs
func (cfg *Config) TrustedCACerts() []string {
	certs := make([]string, 0, len(cfg.TrustedCAs))
	for _, ca := range cfg.TrustedCAs {
		certs = append(certs, ca.Cert)
	}
	return certs
}

// GetVersion implements the method from interface yamlconf.Config
func (cfg *Config) GetVersion() int {
	return cfg.Version
}

// SetVersion implements the method from interface yamlconf.Config
func (cfg *Config) SetVersion(version int) {
	cfg.Version = version
}

// ApplyDefaults implements the method from interface yamlconf.Config
//
// ApplyDefaults populates default values on a Config to make sure that we have
// a minimum viable config for running.  As new settings are added to
// flashlight, this function should be updated to provide sensible defaults for
// those settings.
func (cfg *Config) ApplyDefaults() {
	if cfg.Role == "" {
		cfg.Role = "client"
	}

	if cfg.Addr == "" {
		cfg.Addr = "localhost:8787"
	}

	if cfg.UIAddr == "" {
		cfg.UIAddr = "localhost:16823"
	}

	if cfg.CloudConfig == "" {
		cfg.CloudConfig = "https://s3.amazonaws.com/lantern_config/cloud.2.0.0-nl.yaml.gz"
	}

	// Default country
	if cfg.Country == "" {
		cfg.Country = *country
	}

	// Make sure we always have a stats config
	if cfg.Stats == nil {
		cfg.Stats = &statreporter.Config{}
	}

	if cfg.Stats.StatshubAddr == "" {
		cfg.Stats.StatshubAddr = *statshubAddr
	}

	if cfg.Client != nil && cfg.Role == "client" {
		cfg.applyClientDefaults()
	}

	if cfg.ProxiedSites == nil {
		log.Debugf("Adding empty proxiedsites")
		cfg.ProxiedSites = &proxiedsites.Config{
			Delta: &proxiedsites.Delta{
				Additions: []string{},
				Deletions: []string{},
			},
			Cloud: []string{},
		}
	}

	if cfg.ProxiedSites.Cloud == nil || len(cfg.ProxiedSites.Cloud) == 0 {
		log.Debugf("Loading default cloud proxiedsites")
		cfg.ProxiedSites.Cloud = defaultProxiedSites
	}

	if cfg.TrustedCAs == nil || len(cfg.TrustedCAs) == 0 {
		cfg.TrustedCAs = defaultTrustedCAs
	}
}

func (cfg *Config) applyClientDefaults() {
	// Make sure we always have at least one masquerade set
	if cfg.Client.MasqueradeSets == nil {
		cfg.Client.MasqueradeSets = make(map[string][]*fronted.Masquerade)
	}
	if len(cfg.Client.MasqueradeSets) == 0 {
		cfg.Client.MasqueradeSets[cloudflare] = cloudflareMasquerades
	}

	// Make sure we always have at least one server
	if cfg.Client.FrontedServers == nil {
		cfg.Client.FrontedServers = make([]*client.FrontedServerInfo, 0)
	}
	if len(cfg.Client.FrontedServers) == 0 && len(cfg.Client.ChainedServers) == 0 {
		cfg.Client.FrontedServers = []*client.FrontedServerInfo{
			&client.FrontedServerInfo{
				Host:           "nl.fallbacks.getiantem.org",
				Port:           443,
				PoolSize:       30,
				MasqueradeSet:  cloudflare,
				MaxMasquerades: 20,
				QOS:            10,
				Weight:         4000,
			},
		}

		cfg.Client.ChainedServers = make(map[string]*client.ChainedServerInfo, len(fallbacks))
		for key, fb := range fallbacks {
			cfg.Client.ChainedServers[key] = fb
		}
	}

	if cfg.AutoReport == nil {
		cfg.AutoReport = new(bool)
		*cfg.AutoReport = true
	}

	// Make sure all servers have a QOS and Weight configured
	for _, server := range cfg.Client.FrontedServers {
		if server.QOS == 0 {
			server.QOS = 5
		}
		if server.Weight == 0 {
			server.Weight = 100
		}
		if server.RedialAttempts == 0 {
			server.RedialAttempts = 2
		}
	}

	// Always make sure we have a map of ChainedServers
	if cfg.Client.ChainedServers == nil {
		cfg.Client.ChainedServers = make(map[string]*client.ChainedServerInfo)
	}

	// Sort servers so that they're always in a predictable order
	cfg.Client.SortServers()
}

func (cfg *Config) IsDownstream() bool {
	return cfg.Role == "client"
}

func (cfg *Config) IsUpstream() bool {
	return !cfg.IsDownstream()
}

func (cfg Config) cloudPollSleepTime() time.Duration {
	return time.Duration((CloudConfigPollInterval.Nanoseconds() / 2) + rand.Int63n(CloudConfigPollInterval.Nanoseconds()))
}

func (cfg Config) fetchCloudConfig() (bytes []byte, err error) {
	log.Debugf("Fetching cloud config from: %s", cfg.CloudConfig)

	if cfg.IsDownstream() {
		// Clients must always proxy the request
		if cfg.Addr == "" {
			err = fmt.Errorf("No proxyAddr")
		} else {
			bytes, err = cfg.doFetchCloudConfig(cfg.Addr)
		}
	} else {
		bytes, err = cfg.doFetchCloudConfig("")
	}
	if err != nil {
		bytes = nil
		err = fmt.Errorf("Unable to read yaml from %s: %s", cfg.CloudConfig, err)
	}
	return
}

func (cfg Config) doFetchCloudConfig(proxyAddr string) ([]byte, error) {
	log.Tracef("doFetchCloudConfig via '%s'", proxyAddr)

	if proxyAddr != "" {
		// Wait for proxy to become available
		err := waitforserver.WaitForServer("tcp", proxyAddr, 30*time.Second)
		if err != nil {
			return nil, fmt.Errorf("Proxy never came up at %v: %v", proxyAddr, err)
		}
	}

	client, err := util.HTTPClient(cfg.CloudConfigCA, proxyAddr)
	if err != nil {
		return nil, fmt.Errorf("Unable to initialize HTTP client: %s", err)
	}

	log.Debugf("Checking for cloud configuration at: %s", cfg.CloudConfig)
	req, err := http.NewRequest("GET", cfg.CloudConfig, nil)
	if err != nil {
		return nil, fmt.Errorf("Unable to construct request for cloud config at %s: %s", cfg.CloudConfig, err)
	}
	if lastCloudConfigETag != "" {
		// Don't bother fetching if unchanged
		req.Header.Set(ifNoneMatch, lastCloudConfigETag)
	}
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("Unable to fetch cloud config at %s: %s", cfg.CloudConfig, err)
	}
	defer resp.Body.Close()

	if resp.StatusCode == 304 {
		log.Debugf("Config unchanged in cloud")
		return nil, nil
	} else if resp.StatusCode != 200 {
		return nil, fmt.Errorf("Unexpected response status: %d", resp.StatusCode)
	}

	lastCloudConfigETag = resp.Header.Get(etag)
	gzReader, err := gzip.NewReader(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("Unable to open gzip reader: %s", err)
	}
	return ioutil.ReadAll(gzReader)
}

// updateFrom creates a new Config by merging the given yaml into this Config.
// Any servers in the updated yaml replace ones in the original Config and any
// masquerade sets in the updated yaml replace ones in the original Config.
func (updated *Config) updateFrom(updateBytes []byte) error {
	err := yaml.Unmarshal(updateBytes, updated)
	if err != nil {
		return fmt.Errorf("Unable to unmarshal YAML for update: %s", err)
	}

	// Need to de-duplicate servers, since yaml appends them
	servers := make(map[string]*client.FrontedServerInfo)
	for _, server := range updated.Client.FrontedServers {
		servers[server.Host] = server
	}
	updated.Client.FrontedServers = make([]*client.FrontedServerInfo, 0, len(servers))
	for _, server := range servers {
		updated.Client.FrontedServers = append(updated.Client.FrontedServers, server)
	}

	// Same with global proxiedsites
	if len(updated.ProxiedSites.Cloud) > 0 {
		wlDomains := make(map[string]bool)
		for _, domain := range updated.ProxiedSites.Cloud {
			wlDomains[domain] = true
		}
		updated.ProxiedSites.Cloud = make([]string, 0, len(wlDomains))
		for domain, _ := range wlDomains {
			updated.ProxiedSites.Cloud = append(updated.ProxiedSites.Cloud, domain)
		}
		sort.Strings(updated.ProxiedSites.Cloud)
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2019 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0
//
// 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 common

import (
	"context"
	"fmt"
	"net"
	"os"
	"strconv"
	"time"

	"github.com/networkservicemesh/networkservicemesh/pkg/tools/spanhelper"

	"github.com/pkg/errors"

	"github.com/sirupsen/logrus"
	"google.golang.org/grpc"

	"github.com/networkservicemesh/networkservicemesh/controlplane/api/connection"
	"github.com/networkservicemesh/networkservicemesh/controlplane/api/crossconnect"
	"github.com/networkservicemesh/networkservicemesh/forwarder/api/forwarder"
	"github.com/networkservicemesh/networkservicemesh/pkg/tools"
	monitor_crossconnect "github.com/networkservicemesh/networkservicemesh/sdk/monitor/crossconnect"
)

type NSMForwarder interface {
	forwarder.MechanismsMonitorServer
	Init(*ForwarderConfig) error
	CreateForwarderServer(*ForwarderConfig) forwarder.ForwarderServer
}

// TODO Convert all the defaults to properly use NsmBaseDir
const (
	NSMBaseDirKey                        = "NSM_BASEDIR"
	NSMBaseDirDefault                    = "/var/lib/networkservicemesh/"
	ForwarderRegistrarSocketKey          = "FORWARDER_REGISTRAR_SOCKET"
	ForwarderRegistrarSocketDefault      = "/var/lib/networkservicemesh/nsm.forwarder-registrar.io.sock"
	ForwarderRegistrarSocketTypeKey      = "FORWARDER_REGISTRAR_SOCKET_TYPE"
	ForwarderRegistrarSocketTypeDefault  = "unix"
	ForwarderMetricsEnabledKey           = "METRICS_COLLECTOR_ENABLED"
	ForwarderMetricsEnabledDefault       = false
	ForwarderMetricsRequestPeriodKey     = "METRICS_COLLECTOR_REQUEST_PERIOD"
	ForwarderMetricsRequestPeriodDefault = time.Second * 2
	ForwarderNameKey                     = "FORWARDER_NAME"
	ForwarderNameDefault                 = "vppagent"
	ForwarderSocketKey                   = "FORWARDER_SOCKET"
	ForwarderSocketDefault               = "/var/lib/networkservicemesh/nsm-vppagent.forwarder.sock"
	ForwarderSocketTypeKey               = "FORWARDER_SOCKET_TYPE"
	ForwarderSocketTypeDefault           = "unix"
	ForwarderSrcIPKey                    = "NSM_FORWARDER_SRC_IP"
)

// ForwarderConfig keeps the common configuration for a forwarding plane
type ForwarderConfig struct {
	Name                    string
	NSMBaseDir              string
	RegistrarSocket         string
	RegistrarSocketType     string
	ForwarderSocket         string
	ForwarderSocketType     string
	MechanismsUpdateChannel chan *Mechanisms
	Mechanisms              *Mechanisms
	MetricsEnabled          bool
	MetricsPeriod           time.Duration
	SrcIP                   net.IP
	EgressInterface         EgressInterfaceType
	GRPCserver              *grpc.Server
	Monitor                 monitor_crossconnect.MonitorServer
	Listener                net.Listener
}

// Mechanisms is a message used to communicate any changes in operational parameters and constraints
type Mechanisms struct {
	RemoteMechanisms []*connection.Mechanism
	LocalMechanisms  []*connection.Mechanism
}

func getEnvWithDefault(span spanhelper.SpanHelper, env, defaultValue string) string {
	result, ok := os.LookupEnv(env)
	if !ok {
		span.LogObject(env, fmt.Sprintf("%s (default value)", defaultValue))
		result = defaultValue
	} else {
		span.LogObject(env, result)
	}
	return result
}

func getEnvWithDefaultBool(span spanhelper.SpanHelper, env string, defaultValue bool) bool {
	result := defaultValue
	resultVal, ok := os.LookupEnv(env)
	if !ok {
		span.LogObject(env, fmt.Sprintf("%v (default value)", defaultValue))
		result = defaultValue
	} else {
		span.LogObject(env, result)
		res, err := strconv.ParseBool(resultVal)
		span.LogError(err)
		if err != nil {
			result = res
		}
	}
	return result
}

func createForwarderConfig(ctx context.Context, forwarderGoals *ForwarderProbeGoals) *ForwarderConfig {
	span := spanhelper.FromContext(ctx, "createForwartderConfig")
	defer span.Finish()

	cfg := &ForwarderConfig{}

	cfg.Name = getEnvWithDefault(span, ForwarderNameKey, ForwarderNameDefault)
	cfg.ForwarderSocket = getEnvWithDefault(span, ForwarderSocketKey, ForwarderSocketDefault)

	if err := tools.SocketCleanup(cfg.ForwarderSocket); err != nil {
		span.Logger().Fatalf("Error cleaning up socket %s: %s", cfg.ForwarderSocket, err)
	} else {
		forwarderGoals.SetSocketCleanReady()
	}

	cfg.ForwarderSocketType = getEnvWithDefault(span, ForwarderSocketTypeKey, ForwarderSocketTypeDefault)

	cfg.NSMBaseDir = getEnvWithDefault(span, NSMBaseDirKey, NSMBaseDirDefault)
	cfg.RegistrarSocket = getEnvWithDefault(span, ForwarderRegistrarSocketKey, ForwarderRegistrarSocketDefault)
	cfg.RegistrarSocketType = getEnvWithDefault(span, ForwarderRegistrarSocketTypeKey, ForwarderRegistrarSocketTypeDefault)

	cfg.MetricsEnabled = getEnvWithDefaultBool(span, ForwarderMetricsEnabledKey, ForwarderMetricsEnabledDefault)
	if cfg.MetricsEnabled {
		cfg.MetricsPeriod = ForwarderMetricsRequestPeriodDefault

		if val, ok := os.LookupEnv(ForwarderMetricsRequestPeriodKey); ok {
			parsedPeriod, err := time.ParseDuration(val)
			if err == nil {
				cfg.MetricsPeriod = parsedPeriod
			}
		}
		span.Logger().Infof("MetricsPeriod: %v ", cfg.MetricsPeriod)
	}

	srcIPStr, ok := os.LookupEnv(ForwarderSrcIPKey)
	if !ok {
		span.Logger().Fatalf("Env variable %s must be set to valid srcIP for use for tunnels from this Pod.  Consider using downward API to do so.", ForwarderSrcIPKey)
	} else {
		forwarderGoals.SetSrcIPReady()
	}
	cfg.SrcIP = net.ParseIP(srcIPStr)
	if cfg.SrcIP == nil {
		span.Logger().Fatalf("Env variable %s must be set to a valid IP address, was set to %s", ForwarderSrcIPKey, srcIPStr)
	} else {
		forwarderGoals.SetValidIPReady()
	}
	var err error
	cfg.EgressInterface, err = NewEgressInterface(cfg.SrcIP)
	if err != nil {
		span.Logger().Fatalf("Unable to find egress Interface: %s", err)
	} else {
		forwarderGoals.SetNewEgressIFReady()
	}
	span.Logger().Infof("SrcIP: %s, IfaceName: %s, SrcIPNet: %s", cfg.SrcIP, cfg.EgressInterface.Name(), cfg.EgressInterface.SrcIPNet())
	span.LogObject("config", cfg)
	return cfg
}

// CreateForwarder creates new Forwarder Registrar client
func CreateForwarder(ctx context.Context, dp NSMForwarder, forwarderGoals *ForwarderProbeGoals) *ForwarderRegistration {
	span := spanhelper.FromContext(ctx, "CreateForwarder")
	defer span.Finish()
	// Populate common configuration
	config := createForwarderConfig(span.Context(), forwarderGoals)

	// Initialize GRPC server
	config.GRPCserver = tools.NewServer(span.Context())
	config.Monitor = monitor_crossconnect.NewMonitorServer()
	crossconnect.RegisterMonitorCrossConnectServer(config.GRPCserver, config.Monitor)

	// Initialize the forwarder
	err := dp.Init(config)
	if err != nil {
		span.Logger().Fatalf("Forwarder initialization failed: %s ", err)
	}

	// Verify the configuration is populated
	if !sanityCheckConfig(config) {
		span.Logger().Fatalf("Forwarder configuration sanity check failed: %s ", err)
	}

	// Prepare the gRPC server
	config.Listener, err = net.Listen(config.ForwarderSocketType, config.ForwarderSocket)
	if err != nil {
		span.Logger().Fatalf("Error listening on socket %s: %s ", config.ForwarderSocket, err)
	} else {
		forwarderGoals.SetSocketListenReady()
	}

	forwarder.RegisterForwarderServer(config.GRPCserver, dp.CreateForwarderServer(config))
	forwarder.RegisterMechanismsMonitorServer(config.GRPCserver, dp)

	// Start the server
	logrus.Infof("Creating %s server...", config.Name)
	go func() {
		_ = config.GRPCserver.Serve(config.Listener)
	}()
	span.Logger().Infof("%s server serving", config.Name)
	span.Logger().Info("Creating Forwarder Registrar Client...")
	registrar := NewForwarderRegistrarClient(config.RegistrarSocketType, config.RegistrarSocket)
	registration := registrar.Register(span.Context(), config.Name, config.ForwarderSocket, nil, nil)
	span.Logger().Info("Registered Forwarder Registrar Client")

	return registration
}

func sanityCheckConfig(forwarderConfig *ForwarderConfig) bool {
	return len(forwarderConfig.Name) > 0 &&
		len(forwarderConfig.NSMBaseDir) > 0 &&
		len(forwarderConfig.RegistrarSocket) > 0 &&
		len(forwarderConfig.RegistrarSocketType) > 0 &&
		len(forwarderConfig.ForwarderSocket) > 0 &&
		len(forwarderConfig.ForwarderSocketType) > 0
}

// SanityCheckConnectionType checks whether the forwarding plane supports the connection type in the request
func SanityCheckConnectionType(mechanisms *Mechanisms, crossConnect *crossconnect.CrossConnect) error {
	localFound, remoteFound := false, false
	/* Verify local mechanisms */
	for _, mech := range mechanisms.LocalMechanisms {
		if crossConnect.GetLocalSource().GetMechanism().GetType() == mech.GetType() ||
			crossConnect.GetLocalDestination().GetMechanism().GetType() == mech.GetType() {
			localFound = true
			break
		}
	}
	/* Verify remote mechanisms */
	for _, mech := range mechanisms.RemoteMechanisms {
		if crossConnect.GetRemoteSource().GetMechanism().GetType() == mech.GetType() ||
			crossConnect.GetRemoteDestination().GetMechanism().GetType() == mech.GetType() {
			remoteFound = true
			break
		}
	}
	/* If none of them matched, mechanism is not supported by the forwarding plane */
	if !localFound && !remoteFound {
		return errors.New("connection mechanism type not supported by the forwarding plane")
	}
	return nil
}
<commit_msg>fix forwarder env reading (#1808)<commit_after>// Copyright 2019 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0
//
// 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 common

import (
	"context"
	"fmt"
	"net"
	"os"
	"strconv"
	"time"

	"github.com/networkservicemesh/networkservicemesh/pkg/tools/spanhelper"

	"github.com/pkg/errors"

	"github.com/sirupsen/logrus"
	"google.golang.org/grpc"

	"github.com/networkservicemesh/networkservicemesh/controlplane/api/connection"
	"github.com/networkservicemesh/networkservicemesh/controlplane/api/crossconnect"
	"github.com/networkservicemesh/networkservicemesh/forwarder/api/forwarder"
	"github.com/networkservicemesh/networkservicemesh/pkg/tools"
	monitor_crossconnect "github.com/networkservicemesh/networkservicemesh/sdk/monitor/crossconnect"
)

type NSMForwarder interface {
	forwarder.MechanismsMonitorServer
	Init(*ForwarderConfig) error
	CreateForwarderServer(*ForwarderConfig) forwarder.ForwarderServer
}

// TODO Convert all the defaults to properly use NsmBaseDir
const (
	NSMBaseDirKey                        = "NSM_BASEDIR"
	NSMBaseDirDefault                    = "/var/lib/networkservicemesh/"
	ForwarderRegistrarSocketKey          = "FORWARDER_REGISTRAR_SOCKET"
	ForwarderRegistrarSocketDefault      = "/var/lib/networkservicemesh/nsm.forwarder-registrar.io.sock"
	ForwarderRegistrarSocketTypeKey      = "FORWARDER_REGISTRAR_SOCKET_TYPE"
	ForwarderRegistrarSocketTypeDefault  = "unix"
	ForwarderMetricsEnabledKey           = "METRICS_COLLECTOR_ENABLED"
	ForwarderMetricsEnabledDefault       = false
	ForwarderMetricsRequestPeriodKey     = "METRICS_COLLECTOR_REQUEST_PERIOD"
	ForwarderMetricsRequestPeriodDefault = time.Second * 2
	ForwarderNameKey                     = "FORWARDER_NAME"
	ForwarderNameDefault                 = "vppagent"
	ForwarderSocketKey                   = "FORWARDER_SOCKET"
	ForwarderSocketDefault               = "/var/lib/networkservicemesh/nsm-vppagent.forwarder.sock"
	ForwarderSocketTypeKey               = "FORWARDER_SOCKET_TYPE"
	ForwarderSocketTypeDefault           = "unix"
	ForwarderSrcIPKey                    = "NSM_FORWARDER_SRC_IP"
)

// ForwarderConfig keeps the common configuration for a forwarding plane
type ForwarderConfig struct {
	Name                    string
	NSMBaseDir              string
	RegistrarSocket         string
	RegistrarSocketType     string
	ForwarderSocket         string
	ForwarderSocketType     string
	MechanismsUpdateChannel chan *Mechanisms
	Mechanisms              *Mechanisms
	MetricsEnabled          bool
	MetricsPeriod           time.Duration
	SrcIP                   net.IP
	EgressInterface         EgressInterfaceType
	GRPCserver              *grpc.Server
	Monitor                 monitor_crossconnect.MonitorServer
	Listener                net.Listener
}

// Mechanisms is a message used to communicate any changes in operational parameters and constraints
type Mechanisms struct {
	RemoteMechanisms []*connection.Mechanism
	LocalMechanisms  []*connection.Mechanism
}

func getEnvWithDefault(span spanhelper.SpanHelper, env, defaultValue string) string {
	result, ok := os.LookupEnv(env)
	if !ok {
		span.LogObject(env, fmt.Sprintf("%s (default value)", defaultValue))
		result = defaultValue
	} else {
		span.LogObject(env, result)
	}
	return result
}

func getEnvWithDefaultBool(span spanhelper.SpanHelper, env string, defaultValue bool) bool {
	result := defaultValue
	resultVal, ok := os.LookupEnv(env)
	var err error
	if !ok {
		span.LogObject(env, fmt.Sprintf("%v (default value)", defaultValue))
		result = defaultValue
	} else {
		span.LogObject(env, result)
		result, err = strconv.ParseBool(resultVal)
		span.LogError(err)
		if err != nil {
			result = defaultValue
		}
	}
	return result
}

func createForwarderConfig(ctx context.Context, forwarderGoals *ForwarderProbeGoals) *ForwarderConfig {
	span := spanhelper.FromContext(ctx, "createForwartderConfig")
	defer span.Finish()

	cfg := &ForwarderConfig{}

	cfg.Name = getEnvWithDefault(span, ForwarderNameKey, ForwarderNameDefault)
	cfg.ForwarderSocket = getEnvWithDefault(span, ForwarderSocketKey, ForwarderSocketDefault)

	if err := tools.SocketCleanup(cfg.ForwarderSocket); err != nil {
		span.Logger().Fatalf("Error cleaning up socket %s: %s", cfg.ForwarderSocket, err)
	} else {
		forwarderGoals.SetSocketCleanReady()
	}

	cfg.ForwarderSocketType = getEnvWithDefault(span, ForwarderSocketTypeKey, ForwarderSocketTypeDefault)

	cfg.NSMBaseDir = getEnvWithDefault(span, NSMBaseDirKey, NSMBaseDirDefault)
	cfg.RegistrarSocket = getEnvWithDefault(span, ForwarderRegistrarSocketKey, ForwarderRegistrarSocketDefault)
	cfg.RegistrarSocketType = getEnvWithDefault(span, ForwarderRegistrarSocketTypeKey, ForwarderRegistrarSocketTypeDefault)

	cfg.MetricsEnabled = getEnvWithDefaultBool(span, ForwarderMetricsEnabledKey, ForwarderMetricsEnabledDefault)
	if cfg.MetricsEnabled {
		cfg.MetricsPeriod = ForwarderMetricsRequestPeriodDefault

		if val, ok := os.LookupEnv(ForwarderMetricsRequestPeriodKey); ok {
			parsedPeriod, err := time.ParseDuration(val)
			if err == nil {
				cfg.MetricsPeriod = parsedPeriod
			}
		}
		span.Logger().Infof("MetricsPeriod: %v ", cfg.MetricsPeriod)
	}

	srcIPStr, ok := os.LookupEnv(ForwarderSrcIPKey)
	if !ok {
		span.Logger().Fatalf("Env variable %s must be set to valid srcIP for use for tunnels from this Pod.  Consider using downward API to do so.", ForwarderSrcIPKey)
	} else {
		forwarderGoals.SetSrcIPReady()
	}
	cfg.SrcIP = net.ParseIP(srcIPStr)
	if cfg.SrcIP == nil {
		span.Logger().Fatalf("Env variable %s must be set to a valid IP address, was set to %s", ForwarderSrcIPKey, srcIPStr)
	} else {
		forwarderGoals.SetValidIPReady()
	}
	var err error
	cfg.EgressInterface, err = NewEgressInterface(cfg.SrcIP)
	if err != nil {
		span.Logger().Fatalf("Unable to find egress Interface: %s", err)
	} else {
		forwarderGoals.SetNewEgressIFReady()
	}
	span.Logger().Infof("SrcIP: %s, IfaceName: %s, SrcIPNet: %s", cfg.SrcIP, cfg.EgressInterface.Name(), cfg.EgressInterface.SrcIPNet())
	span.LogObject("config", cfg)
	return cfg
}

// CreateForwarder creates new Forwarder Registrar client
func CreateForwarder(ctx context.Context, dp NSMForwarder, forwarderGoals *ForwarderProbeGoals) *ForwarderRegistration {
	span := spanhelper.FromContext(ctx, "CreateForwarder")
	defer span.Finish()
	// Populate common configuration
	config := createForwarderConfig(span.Context(), forwarderGoals)

	// Initialize GRPC server
	config.GRPCserver = tools.NewServer(span.Context())
	config.Monitor = monitor_crossconnect.NewMonitorServer()
	crossconnect.RegisterMonitorCrossConnectServer(config.GRPCserver, config.Monitor)

	// Initialize the forwarder
	err := dp.Init(config)
	if err != nil {
		span.Logger().Fatalf("Forwarder initialization failed: %s ", err)
	}

	// Verify the configuration is populated
	if !sanityCheckConfig(config) {
		span.Logger().Fatalf("Forwarder configuration sanity check failed: %s ", err)
	}

	// Prepare the gRPC server
	config.Listener, err = net.Listen(config.ForwarderSocketType, config.ForwarderSocket)
	if err != nil {
		span.Logger().Fatalf("Error listening on socket %s: %s ", config.ForwarderSocket, err)
	} else {
		forwarderGoals.SetSocketListenReady()
	}

	forwarder.RegisterForwarderServer(config.GRPCserver, dp.CreateForwarderServer(config))
	forwarder.RegisterMechanismsMonitorServer(config.GRPCserver, dp)

	// Start the server
	logrus.Infof("Creating %s server...", config.Name)
	go func() {
		_ = config.GRPCserver.Serve(config.Listener)
	}()
	span.Logger().Infof("%s server serving", config.Name)
	span.Logger().Info("Creating Forwarder Registrar Client...")
	registrar := NewForwarderRegistrarClient(config.RegistrarSocketType, config.RegistrarSocket)
	registration := registrar.Register(span.Context(), config.Name, config.ForwarderSocket, nil, nil)
	span.Logger().Info("Registered Forwarder Registrar Client")

	return registration
}

func sanityCheckConfig(forwarderConfig *ForwarderConfig) bool {
	return len(forwarderConfig.Name) > 0 &&
		len(forwarderConfig.NSMBaseDir) > 0 &&
		len(forwarderConfig.RegistrarSocket) > 0 &&
		len(forwarderConfig.RegistrarSocketType) > 0 &&
		len(forwarderConfig.ForwarderSocket) > 0 &&
		len(forwarderConfig.ForwarderSocketType) > 0
}

// SanityCheckConnectionType checks whether the forwarding plane supports the connection type in the request
func SanityCheckConnectionType(mechanisms *Mechanisms, crossConnect *crossconnect.CrossConnect) error {
	localFound, remoteFound := false, false
	/* Verify local mechanisms */
	for _, mech := range mechanisms.LocalMechanisms {
		if crossConnect.GetLocalSource().GetMechanism().GetType() == mech.GetType() ||
			crossConnect.GetLocalDestination().GetMechanism().GetType() == mech.GetType() {
			localFound = true
			break
		}
	}
	/* Verify remote mechanisms */
	for _, mech := range mechanisms.RemoteMechanisms {
		if crossConnect.GetRemoteSource().GetMechanism().GetType() == mech.GetType() ||
			crossConnect.GetRemoteDestination().GetMechanism().GetType() == mech.GetType() {
			remoteFound = true
			break
		}
	}
	/* If none of them matched, mechanism is not supported by the forwarding plane */
	if !localFound && !remoteFound {
		return errors.New("connection mechanism type not supported by the forwarding plane")
	}
	return nil
}
<|endoftext|>


<commit_before>package gfpool_test

import (
	"github.com/gogf/gf/g/os/gfile"
	"github.com/gogf/gf/g/os/gfpool"
	"github.com/gogf/gf/g/test/gtest"
	"os"
	"testing"
	"time"
)

func TestOpen(t *testing.T) {
	testFile := start("TestOpen.txt")

	gtest.Case(t, func() {
		f, _ := gfpool.Open(testFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666)
		f.Close()

		f2, _ := gfpool.Open(testFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666)
		gtest.AssertEQ(f, f2)
		f2.Close()

		// Deprecated test
		f3, _ := gfpool.OpenFile(testFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666)
		gtest.AssertEQ(f, f3)
		f3.Close()

	})

	stop(testFile)
}

func TestOpenErr(t *testing.T) {
	gtest.Case(t, func() {
		testErrFile := "errorPath"
		_, err := gfpool.Open(testErrFile, os.O_RDWR, 0666)
		gtest.AssertNE(err, nil)
	})
}

func TestOpenExipre(t *testing.T) {
	testFile := start("TestOpenExipre.txt")

	gtest.Case(t, func() {
		f, _ := gfpool.Open(testFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666, 100)
		f.Close()

		time.Sleep(150 * time.Millisecond)
		f2, _ := gfpool.Open(testFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666, 100)
		gtest.AssertNE(f, f2)
		f2.Close()

		// Deprecated test
		f3, _ := gfpool.OpenFile(testFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666, 100)
		gtest.AssertEQ(f2, f3)
		f3.Close()
	})

	stop(testFile)
}

func TestNewPool(t *testing.T) {
	testFile := start("TestNewPool.txt")

	gtest.Case(t, func() {
		f, _ := gfpool.Open(testFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666)
		f.Close()

		pool := gfpool.New(testFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666)
		f2, _ := pool.File()
		// pool not equal
		gtest.AssertNE(f, f2)
		f2.Close()

		pool.Close()
	})

	stop(testFile)
}

func start(name string) string {
	testFile := os.TempDir() + string(os.PathSeparator) + name
	if gfile.Exists(testFile) {
		gfile.Remove(testFile)
	}
	content := "123"
	gfile.PutContents(testFile, content)
	return testFile
}

func stop(testFile string) {
	if gfile.Exists(testFile) {
		gfile.Remove(testFile)
	}
}
<commit_msg>update gfpool test:delete err<commit_after>package gfpool_test

import (
	"github.com/gogf/gf/g/os/gfile"
	"github.com/gogf/gf/g/os/gfpool"
	"github.com/gogf/gf/g/os/glog"
	"github.com/gogf/gf/g/test/gtest"
	"os"
	"testing"
	"time"
)

// TestOpen test open file cache
func TestOpen(t *testing.T) {
	testFile := start("TestOpen.txt")

	gtest.Case(t, func() {
		f, _ := gfpool.Open(testFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666)
		f.Close()

		f2, _ := gfpool.Open(testFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666)
		gtest.AssertEQ(f, f2)
		f2.Close()

		// Deprecated test
		f3, _ := gfpool.OpenFile(testFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666)
		gtest.AssertEQ(f, f3)
		f3.Close()

	})

	stop(testFile)
}

// TestOpenErr test open file error
func TestOpenErr(t *testing.T) {
	gtest.Case(t, func() {
		testErrFile := "errorPath"
		_, err := gfpool.Open(testErrFile, os.O_RDWR, 0666)
		gtest.AssertNE(err, nil)

		// delete file error
		testFile := start("TestOpenDeleteErr.txt")
		f, _ := gfpool.Open(testFile, os.O_RDWR, 0666)
		f.Close()
		stop(testFile)
		_, err = gfpool.Open(testFile, os.O_RDWR, 0666)
		gtest.AssertNE(err, nil)

		// append mode delete file error
		testFile = start("TestOpenCreateErr.txt")
		f, _ = gfpool.Open(testFile, os.O_CREATE, 0666)
		f.Close()
		stop(testFile)
		_, err = gfpool.Open(testFile, os.O_CREATE, 0666)
		gtest.AssertNE(err, nil)

		// append mode delete file error
		testFile = start("TestOpenAppendErr.txt")
		f, _ = gfpool.Open(testFile, os.O_APPEND, 0666)
		f.Close()
		stop(testFile)
		_, err = gfpool.Open(testFile, os.O_APPEND, 0666)
		gtest.AssertNE(err, nil)

		// trunc mode delete file error
		testFile = start("TestOpenTruncErr.txt")
		f, _ = gfpool.Open(testFile, os.O_TRUNC, 0666)
		f.Close()
		stop(testFile)
		_, err = gfpool.Open(testFile, os.O_TRUNC, 0666)
		gtest.AssertNE(err, nil)
	})
}

// TestOpenExpire test open file cache expire
func TestOpenExpire(t *testing.T) {
	testFile := start("TestOpenExpire.txt")

	gtest.Case(t, func() {
		f, _ := gfpool.Open(testFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666, 100)
		f.Close()

		time.Sleep(150 * time.Millisecond)
		f2, _ := gfpool.Open(testFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666, 100)
		gtest.AssertNE(f, f2)
		f2.Close()

		// Deprecated test
		f3, _ := gfpool.OpenFile(testFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666, 100)
		gtest.AssertEQ(f2, f3)
		f3.Close()
	})

	stop(testFile)
}

// TestNewPool test gfpool new function
func TestNewPool(t *testing.T) {
	testFile := start("TestNewPool.txt")

	gtest.Case(t, func() {
		f, _ := gfpool.Open(testFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666)
		f.Close()

		pool := gfpool.New(testFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666)
		f2, _ := pool.File()
		// pool not equal
		gtest.AssertNE(f, f2)
		f2.Close()

		pool.Close()
	})

	stop(testFile)
}

// test before
func start(name string) string {
	testFile := os.TempDir() + string(os.PathSeparator) + name
	if gfile.Exists(testFile) {
		gfile.Remove(testFile)
	}
	content := "123"
	gfile.PutContents(testFile, content)
	return testFile
}

// test after
func stop(testFile string) {
	if gfile.Exists(testFile) {
		err := gfile.Remove(testFile)
		if err != nil {
			glog.Error(err)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
	"time"

	//log "github.com/Sirupsen/logrus"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/crewjam/awsregion"
	"github.com/crewjam/ec2cluster"
)

type etcdState struct {
	Name       string         `json:"name"`
	ID         string         `json:"id"`
	State      string         `json:"state"`
	StartTime  time.Time      `json:"startTime"`
	LeaderInfo etcdLeaderInfo `json:"leaderInfo"`
}

type etcdLeaderInfo struct {
	Leader               string    `json:"leader"`
	Uptime               string    `json:"uptime"`
	StartTime            time.Time `json:"startTime"`
	RecvAppendRequestCnt int       `json:"recvAppendRequestCnt"`
	RecvPkgRate          int       `json:"recvPkgRate"`
	RecvBandwidthRate    int       `json:"recvBandwidthRate"`
	SendAppendRequestCnt int       `json:"sendAppendRequestCnt"`
}

type etcdMembers struct {
	Members []etcdMember `json:"members,omitempty"`
}

type etcdMember struct {
	ID         string   `json:"id,omitempty"`
	Name       string   `json:"name,omitempty"`
	PeerURLs   []string `json:"peerURLs,omitempty"`
	ClientURLs []string `json:"clientURLs,omitempty"`
}

var localInstance *ec2.Instance
var peerProtocol string
var clientProtocol string
var etcdCertFile *string
var etcdKeyFile *string
var etcdTrustedCaFile *string
var clientTlsEnabled bool

func getTlsConfig() (*tls.Config, error) {
	// Load client cert
	cert, err := tls.LoadX509KeyPair(*etcdCertFile, *etcdKeyFile)
	if err != nil {
		return nil, fmt.Errorf("ERROR: %s", err)
	}

	// Load CA cert
	caCert, err := ioutil.ReadFile(*etcdTrustedCaFile)
	if err != nil {
		return nil, fmt.Errorf("ERROR: %s", err)
	}
	caCertPool := x509.NewCertPool()
	caCertPool.AppendCertsFromPEM(caCert)

	// Setup HTTPS client
	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{cert},
		RootCAs:      caCertPool,
	}
	tlsConfig.BuildNameToCertificate()
	return tlsConfig, nil
}

func getApiResponse(privateIpAddress string, instanceId string, path string, method string) (*http.Response, error) {
	return getApiResponseWithBody(privateIpAddress, instanceId, path, method, "", nil)
}

func getApiResponseWithBody(privateIpAddress string, instanceId string, path string, method string, bodyType string, body io.Reader) (*http.Response, error) {
	var resp *http.Response
	var err error
	var req *http.Request

	if bodyType == "" {
		req, _ = http.NewRequest(method, fmt.Sprintf("%s://%s:2379/v2/%s", clientProtocol, privateIpAddress, path), body)
	}

	if clientTlsEnabled {
		tlsConfig, _ := getTlsConfig()
		transport := &http.Transport{TLSClientConfig: tlsConfig}
		client := &http.Client{Transport: transport}

		if bodyType != "" {
			client.Post(fmt.Sprintf("%s://%s:2379/v2/%s", clientProtocol, privateIpAddress, path), bodyType, body) // TLS POST request
		} else {
			resp, err = client.Do(req) // TLS request
		}
	} else {
		if bodyType != "" {
			http.Post(fmt.Sprintf("%s://%s:2379/v2/%s", clientProtocol, privateIpAddress, path), bodyType, body) // non-TLS POST request
		} else {
			resp, err = http.DefaultClient.Do(req) // non-TLS request
		}
	}

	if err != nil {
		return nil, fmt.Errorf("%s: %s %s://%s:2379/v2/%s: %s", instanceId, method, clientProtocol, privateIpAddress, path, err)
	}
	return resp, nil
}

func buildCluster(s *ec2cluster.Cluster) (initialClusterState string, initialCluster []string, err error) {

	localInstance, err := s.Instance()
	if err != nil {
		return "", nil, err
	}

	clusterInstances, err := s.Members()
	if err != nil {
		return "", nil, fmt.Errorf("list members: %s", err)
	}

	initialClusterState = "new"
	initialCluster = []string{}
	for _, instance := range clusterInstances {
		if instance.PrivateDnsName == nil {
			continue
		}

		// add this instance to the initialCluster expression
		initialCluster = append(initialCluster, fmt.Sprintf("%s=%s://%s:2380",
			*instance.InstanceId, peerProtocol, *instance.PrivateDnsName))

		// skip the local node, since we know it is not running yet
		if *instance.InstanceId == *localInstance.InstanceId {
			continue
		}

		// fetch the state of the node.
		path := "stats/self"
		resp, err := getApiResponse(*instance.PrivateDnsName, *instance.InstanceId, path, http.MethodGet)
		if err != nil {
			log.Printf("%s: %s://%s:2379/v2/%s: %s", *instance.InstanceId, clientProtocol,
				*instance.PrivateDnsName, path, err)
			continue
		}
		nodeState := etcdState{}
		if err := json.NewDecoder(resp.Body).Decode(&nodeState); err != nil {
			log.Printf("%s: %s://%s:2379/v2/%s: %s", *instance.InstanceId, clientProtocol,
				*instance.PrivateDnsName, path, err)
			continue
		}

		if nodeState.LeaderInfo.Leader == "" {
			log.Printf("%s: %s://%s:2379/v2/%s: alive, no leader", *instance.InstanceId, clientProtocol,
				*instance.PrivateDnsName, path)
			continue
		}

		log.Printf("%s: %s://%s:2379/v2/%s: has leader %s", *instance.InstanceId, clientProtocol,
			*instance.PrivateDnsName, path, nodeState.LeaderInfo.Leader)
		if initialClusterState != "existing" {
			initialClusterState = "existing"

			// inform the node we found about the new node we're about to add so that
			// when etcd starts we can avoid etcd thinking the cluster is out of sync.
			log.Printf("joining cluster via %s", *instance.InstanceId)
			m := etcdMember{
				Name:     *localInstance.InstanceId,
				PeerURLs: []string{fmt.Sprintf("%s://%s:2380", peerProtocol, *localInstance.PrivateDnsName)},
			}
			body, _ := json.Marshal(m)
			getApiResponseWithBody(*instance.PrivateDnsName, *instance.InstanceId, "members", http.MethodPost, "application/json", bytes.NewReader(body))
		}
	}
	return initialClusterState, initialCluster, nil
}

func main() {
	instanceID := flag.String("instance", "",
		"The instance ID of the cluster member. If not supplied, then the instance ID is determined from EC2 metadata")
	clusterTagName := flag.String("tag", "aws:autoscaling:groupName",
		"The instance tag that is common to all members of the cluster")

	etcdCertFile = flag.String("etcd-cert-file", os.Getenv("ETCD_CERT_FILE"),
		"Path to the client server TLS cert file. "+
			"Environment variable: ETCD_CERT_FILE")
	etcdTrustedCaFile = flag.String("etcd-trusted-ca-file", os.Getenv("ETCD_TRUSTED_CA_FILE"),
		"Path to the client server TLS trusted CA key file. "+
			"Environment variable: ETCD_TRUSTED_CA_FILE")

	flag.Parse()

	// Note: We're kinda assuming that if SSL is on at all, it's on for everything.
	clientTlsEnabled = false
	clientProtocol = "http"
	peerProtocol = "http"
	if *etcdCertFile != "" {
		clientTlsEnabled = true
		clientProtocol = "https"
		peerProtocol = "https"
	}

	var err error
	if *instanceID == "" {
		*instanceID, err = ec2cluster.DiscoverInstanceID()
		if err != nil {
			log.Fatalf("ERROR: %s", err)
		}
	}

	awsSession := session.New()
	if region := os.Getenv("AWS_REGION"); region != "" {
		awsSession.Config.WithRegion(region)
	}
	awsregion.GuessRegion(awsSession.Config)

	s := &ec2cluster.Cluster{
		AwsSession: awsSession,
		InstanceID: *instanceID,
		TagName:    *clusterTagName,
	}

	localInstance, err := s.Instance()
	if err != nil {
		log.Fatalf("ERROR: %s", err)
	}

	initialClusterState, initialCluster, err := buildCluster(s)

	envs := []string{
		fmt.Sprintf("ETCD_NAME=%s", *localInstance.InstanceId),
		fmt.Sprintf("ETCD_ADVERTISE_CLIENT_URLS=%s://%s:2379", clientProtocol, *localInstance.PrivateDnsName),
		fmt.Sprintf("ETCD_LISTEN_CLIENT_URLS=%s://0.0.0.0:2379", clientProtocol),
		fmt.Sprintf("ETCD_LISTEN_PEER_URLS=%s://0.0.0.0:2380", peerProtocol),
		fmt.Sprintf("ETCD_INITIAL_CLUSTER_STATE=%s", initialClusterState),
		fmt.Sprintf("ETCD_INITIAL_CLUSTER=%s", strings.Join(initialCluster, ",")),
		fmt.Sprintf("ETCD_INITIAL_ADVERTISE_PEER_URLS=%s://%s:2380", peerProtocol, *localInstance.PrivateDnsName),
	}
	asg, _ := s.AutoscalingGroup()
	if asg != nil {
		envs = append(envs, fmt.Sprintf("ETCD_INITIAL_CLUSTER_TOKEN=%s", *asg.AutoScalingGroupARN))
	}

	for _, line := range envs {
		fmt.Println(line)
	}
}
<commit_msg>Also need a key for client auth.<commit_after>package main

import (
	"bytes"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
	"time"

	//log "github.com/Sirupsen/logrus"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/crewjam/awsregion"
	"github.com/crewjam/ec2cluster"
)

type etcdState struct {
	Name       string         `json:"name"`
	ID         string         `json:"id"`
	State      string         `json:"state"`
	StartTime  time.Time      `json:"startTime"`
	LeaderInfo etcdLeaderInfo `json:"leaderInfo"`
}

type etcdLeaderInfo struct {
	Leader               string    `json:"leader"`
	Uptime               string    `json:"uptime"`
	StartTime            time.Time `json:"startTime"`
	RecvAppendRequestCnt int       `json:"recvAppendRequestCnt"`
	RecvPkgRate          int       `json:"recvPkgRate"`
	RecvBandwidthRate    int       `json:"recvBandwidthRate"`
	SendAppendRequestCnt int       `json:"sendAppendRequestCnt"`
}

type etcdMembers struct {
	Members []etcdMember `json:"members,omitempty"`
}

type etcdMember struct {
	ID         string   `json:"id,omitempty"`
	Name       string   `json:"name,omitempty"`
	PeerURLs   []string `json:"peerURLs,omitempty"`
	ClientURLs []string `json:"clientURLs,omitempty"`
}

var localInstance *ec2.Instance
var peerProtocol string
var clientProtocol string
var etcdCertFile *string
var etcdKeyFile *string
var etcdTrustedCaFile *string
var clientTlsEnabled bool

func getTlsConfig() (*tls.Config, error) {
	// Load client cert
	cert, err := tls.LoadX509KeyPair(*etcdCertFile, *etcdKeyFile)
	if err != nil {
		return nil, fmt.Errorf("ERROR: %s", err)
	}

	// Load CA cert
	caCert, err := ioutil.ReadFile(*etcdTrustedCaFile)
	if err != nil {
		return nil, fmt.Errorf("ERROR: %s", err)
	}
	caCertPool := x509.NewCertPool()
	caCertPool.AppendCertsFromPEM(caCert)

	// Setup HTTPS client
	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{cert},
		RootCAs:      caCertPool,
	}
	tlsConfig.BuildNameToCertificate()
	return tlsConfig, nil
}

func getApiResponse(privateIpAddress string, instanceId string, path string, method string) (*http.Response, error) {
	return getApiResponseWithBody(privateIpAddress, instanceId, path, method, "", nil)
}

func getApiResponseWithBody(privateIpAddress string, instanceId string, path string, method string, bodyType string, body io.Reader) (*http.Response, error) {
	var resp *http.Response
	var err error
	var req *http.Request

	if bodyType == "" {
		req, _ = http.NewRequest(method, fmt.Sprintf("%s://%s:2379/v2/%s", clientProtocol, privateIpAddress, path), body)
	}

	if clientTlsEnabled {
		tlsConfig, _ := getTlsConfig()
		transport := &http.Transport{TLSClientConfig: tlsConfig}
		client := &http.Client{Transport: transport}

		if bodyType != "" {
			client.Post(fmt.Sprintf("%s://%s:2379/v2/%s", clientProtocol, privateIpAddress, path), bodyType, body) // TLS POST request
		} else {
			resp, err = client.Do(req) // TLS request
		}
	} else {
		if bodyType != "" {
			http.Post(fmt.Sprintf("%s://%s:2379/v2/%s", clientProtocol, privateIpAddress, path), bodyType, body) // non-TLS POST request
		} else {
			resp, err = http.DefaultClient.Do(req) // non-TLS request
		}
	}

	if err != nil {
		return nil, fmt.Errorf("%s: %s %s://%s:2379/v2/%s: %s", instanceId, method, clientProtocol, privateIpAddress, path, err)
	}
	return resp, nil
}

func buildCluster(s *ec2cluster.Cluster) (initialClusterState string, initialCluster []string, err error) {

	localInstance, err := s.Instance()
	if err != nil {
		return "", nil, err
	}

	clusterInstances, err := s.Members()
	if err != nil {
		return "", nil, fmt.Errorf("list members: %s", err)
	}

	initialClusterState = "new"
	initialCluster = []string{}
	for _, instance := range clusterInstances {
		if instance.PrivateDnsName == nil {
			continue
		}

		// add this instance to the initialCluster expression
		initialCluster = append(initialCluster, fmt.Sprintf("%s=%s://%s:2380",
			*instance.InstanceId, peerProtocol, *instance.PrivateDnsName))

		// skip the local node, since we know it is not running yet
		if *instance.InstanceId == *localInstance.InstanceId {
			continue
		}

		// fetch the state of the node.
		path := "stats/self"
		resp, err := getApiResponse(*instance.PrivateDnsName, *instance.InstanceId, path, http.MethodGet)
		if err != nil {
			log.Printf("%s: %s://%s:2379/v2/%s: %s", *instance.InstanceId, clientProtocol,
				*instance.PrivateDnsName, path, err)
			continue
		}
		nodeState := etcdState{}
		if err := json.NewDecoder(resp.Body).Decode(&nodeState); err != nil {
			log.Printf("%s: %s://%s:2379/v2/%s: %s", *instance.InstanceId, clientProtocol,
				*instance.PrivateDnsName, path, err)
			continue
		}

		if nodeState.LeaderInfo.Leader == "" {
			log.Printf("%s: %s://%s:2379/v2/%s: alive, no leader", *instance.InstanceId, clientProtocol,
				*instance.PrivateDnsName, path)
			continue
		}

		log.Printf("%s: %s://%s:2379/v2/%s: has leader %s", *instance.InstanceId, clientProtocol,
			*instance.PrivateDnsName, path, nodeState.LeaderInfo.Leader)
		if initialClusterState != "existing" {
			initialClusterState = "existing"

			// inform the node we found about the new node we're about to add so that
			// when etcd starts we can avoid etcd thinking the cluster is out of sync.
			log.Printf("joining cluster via %s", *instance.InstanceId)
			m := etcdMember{
				Name:     *localInstance.InstanceId,
				PeerURLs: []string{fmt.Sprintf("%s://%s:2380", peerProtocol, *localInstance.PrivateDnsName)},
			}
			body, _ := json.Marshal(m)
			getApiResponseWithBody(*instance.PrivateDnsName, *instance.InstanceId, "members", http.MethodPost, "application/json", bytes.NewReader(body))
		}
	}
	return initialClusterState, initialCluster, nil
}

func main() {
	instanceID := flag.String("instance", "",
		"The instance ID of the cluster member. If not supplied, then the instance ID is determined from EC2 metadata")
	clusterTagName := flag.String("tag", "aws:autoscaling:groupName",
		"The instance tag that is common to all members of the cluster")

	etcdKeyFile = flag.String("etcd-key-file", "", "Path to the TLS key")
	etcdCertFile = flag.String("etcd-cert-file", os.Getenv("ETCD_CERT_FILE"),
		"Path to the client server TLS cert file. "+
			"Environment variable: ETCD_CERT_FILE")
	etcdTrustedCaFile = flag.String("etcd-ca-file", os.Getenv("ETCD_TRUSTED_CA_FILE"),
		"Path to the client server TLS trusted CA key file. "+
			"Environment variable: ETCD_TRUSTED_CA_FILE")

	flag.Parse()

	// Note: We're kinda assuming that if SSL is on at all, it's on for everything.
	clientTlsEnabled = false
	clientProtocol = "http"
	peerProtocol = "http"
	if *etcdCertFile != "" {
		clientTlsEnabled = true
		clientProtocol = "https"
		peerProtocol = "https"
	}

	var err error
	if *instanceID == "" {
		*instanceID, err = ec2cluster.DiscoverInstanceID()
		if err != nil {
			log.Fatalf("ERROR: %s", err)
		}
	}

	awsSession := session.New()
	if region := os.Getenv("AWS_REGION"); region != "" {
		awsSession.Config.WithRegion(region)
	}
	awsregion.GuessRegion(awsSession.Config)

	s := &ec2cluster.Cluster{
		AwsSession: awsSession,
		InstanceID: *instanceID,
		TagName:    *clusterTagName,
	}

	localInstance, err := s.Instance()
	if err != nil {
		log.Fatalf("ERROR: %s", err)
	}

	initialClusterState, initialCluster, err := buildCluster(s)

	envs := []string{
		fmt.Sprintf("ETCD_NAME=%s", *localInstance.InstanceId),
		fmt.Sprintf("ETCD_ADVERTISE_CLIENT_URLS=%s://%s:2379", clientProtocol, *localInstance.PrivateDnsName),
		fmt.Sprintf("ETCD_LISTEN_CLIENT_URLS=%s://0.0.0.0:2379", clientProtocol),
		fmt.Sprintf("ETCD_LISTEN_PEER_URLS=%s://0.0.0.0:2380", peerProtocol),
		fmt.Sprintf("ETCD_INITIAL_CLUSTER_STATE=%s", initialClusterState),
		fmt.Sprintf("ETCD_INITIAL_CLUSTER=%s", strings.Join(initialCluster, ",")),
		fmt.Sprintf("ETCD_INITIAL_ADVERTISE_PEER_URLS=%s://%s:2380", peerProtocol, *localInstance.PrivateDnsName),
	}
	asg, _ := s.AutoscalingGroup()
	if asg != nil {
		envs = append(envs, fmt.Sprintf("ETCD_INITIAL_CLUSTER_TOKEN=%s", *asg.AutoScalingGroupARN))
	}

	for _, line := range envs {
		fmt.Println(line)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
	"time"

	//log "github.com/Sirupsen/logrus"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/crewjam/awsregion"
	"github.com/crewjam/ec2cluster"
)

type etcdState struct {
	Name       string         `json:"name"`
	ID         string         `json:"id"`
	State      string         `json:"state"`
	StartTime  time.Time      `json:"startTime"`
	LeaderInfo etcdLeaderInfo `json:"leaderInfo"`
}

type etcdLeaderInfo struct {
	Leader               string    `json:"leader"`
	Uptime               string    `json:"uptime"`
	StartTime            time.Time `json:"startTime"`
	RecvAppendRequestCnt int       `json:"recvAppendRequestCnt"`
	RecvPkgRate          int       `json:"recvPkgRate"`
	RecvBandwidthRate    int       `json:"recvBandwidthRate"`
	SendAppendRequestCnt int       `json:"sendAppendRequestCnt"`
}

type etcdMembers struct {
	Members []etcdMember `json:"members,omitempty"`
}

type etcdMember struct {
	ID         string   `json:"id,omitempty"`
	Name       string   `json:"name,omitempty"`
	PeerURLs   []string `json:"peerURLs,omitempty"`
	ClientURLs []string `json:"clientURLs,omitempty"`
}

var localInstance *ec2.Instance
var peerProtocol string
var clientProtocol string
var etcdCertFile *string
var etcdKeyFile *string
var etcdTrustedCaFile *string
var clientTlsEnabled bool

func getTlsConfig() (*tls.Config, error) {
	// Load client cert
	cert, err := tls.LoadX509KeyPair(*etcdCertFile, *etcdKeyFile)
	if err != nil {
		return nil, fmt.Errorf("ERROR: %s", err)
	}

	// Load CA cert
	caCert, err := ioutil.ReadFile(*etcdTrustedCaFile)
	if err != nil {
		return nil, fmt.Errorf("ERROR: %s", err)
	}
	caCertPool := x509.NewCertPool()
	caCertPool.AppendCertsFromPEM(caCert)

	// Setup HTTPS client
	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{cert},
		RootCAs:      caCertPool,
	}
	tlsConfig.BuildNameToCertificate()
	return tlsConfig, nil
}

func getApiResponse(privateIpAddress string, instanceId string, path string, method string) (*http.Response, error) {
	return getApiResponseWithBody(privateIpAddress, instanceId, path, method, "", nil)
}

func getApiResponseWithBody(privateIpAddress string, instanceId string, path string, method string, bodyType string, body io.Reader) (*http.Response, error) {
	var resp *http.Response
	var err error
	var req *http.Request

	if bodyType == "" {
		req, _ = http.NewRequest(method, fmt.Sprintf("%s://%s:2379/v2/%s", clientProtocol, privateIpAddress, path), body)
	}

	if clientTlsEnabled {
		tlsConfig, _ := getTlsConfig()
		transport := &http.Transport{TLSClientConfig: tlsConfig}
		client := &http.Client{Transport: transport}

		if bodyType != "" {
			client.Post(fmt.Sprintf("%s://%s:2379/v2/%s", clientProtocol, privateIpAddress, path), bodyType, body) // TLS POST request
		} else {
			resp, err = client.Do(req) // TLS request
		}
	} else {
		if bodyType != "" {
			http.Post(fmt.Sprintf("%s://%s:2379/v2/%s", clientProtocol, privateIpAddress, path), bodyType, body) // non-TLS POST request
		} else {
			resp, err = http.DefaultClient.Do(req) // non-TLS request
		}
	}

	if err != nil {
		return nil, fmt.Errorf("%s: %s %s://%s:2379/v2/%s: %s", instanceId, method, clientProtocol, privateIpAddress, path, err)
	}
	return resp, nil
}

func buildCluster(s *ec2cluster.Cluster) (initialClusterState string, initialCluster []string, err error) {

	localInstance, err := s.Instance()
	if err != nil {
		return "", nil, err
	}

	clusterInstances, err := s.Members()
	if err != nil {
		return "", nil, fmt.Errorf("list members: %s", err)
	}

	initialClusterState = "new"
	initialCluster = []string{}
	for _, instance := range clusterInstances {
		if instance.PrivateDnsName == nil {
			continue
		}

		// add this instance to the initialCluster expression
		initialCluster = append(initialCluster, fmt.Sprintf("%s=%s://%s:2380",
			*instance.InstanceId, peerProtocol, *instance.PrivateDnsName))

		// skip the local node, since we know it is not running yet
		if *instance.InstanceId == *localInstance.InstanceId {
			continue
		}

		// fetch the state of the node.
		path := "stats/self"
		resp, err := getApiResponse(*instance.PrivateDnsName, *instance.InstanceId, path, http.MethodGet)
		if err != nil {
			log.Printf("%s: %s://%s:2379/v2/%s: %s", *instance.InstanceId, clientProtocol,
				*instance.PrivateDnsName, path, err)
			continue
		}
		nodeState := etcdState{}
		if err := json.NewDecoder(resp.Body).Decode(&nodeState); err != nil {
			log.Printf("%s: %s://%s:2379/v2/%s: %s", *instance.InstanceId, clientProtocol,
				*instance.PrivateDnsName, path, err)
			continue
		}

		if nodeState.LeaderInfo.Leader == "" {
			log.Printf("%s: %s://%s:2379/v2/%s: alive, no leader", *instance.InstanceId, clientProtocol,
				*instance.PrivateDnsName, path)
			continue
		}

		log.Printf("%s: %s://%s:2379/v2/%s: has leader %s", *instance.InstanceId, clientProtocol,
			*instance.PrivateDnsName, path, nodeState.LeaderInfo.Leader)
		if initialClusterState != "existing" {
			initialClusterState = "existing"

			// inform the node we found about the new node we're about to add so that
			// when etcd starts we can avoid etcd thinking the cluster is out of sync.
			log.Printf("joining cluster via %s", *instance.InstanceId)
			m := etcdMember{
				Name:     *localInstance.InstanceId,
				PeerURLs: []string{fmt.Sprintf("%s://%s:2380", peerProtocol, *localInstance.PrivateDnsName)},
			}
			body, _ := json.Marshal(m)
			getApiResponseWithBody(*instance.PrivateDnsName, *instance.InstanceId, "members", http.MethodPost, "application/json", bytes.NewReader(body))
		}
	}
	return initialClusterState, initialCluster, nil
}

func main() {
	instanceID := flag.String("instance", "",
		"The instance ID of the cluster member. If not supplied, then the instance ID is determined from EC2 metadata")
	clusterTagName := flag.String("tag", "aws:autoscaling:groupName",
		"The instance tag that is common to all members of the cluster")

	etcdKeyFile = flag.String("etcd-key-file", "", "Path to the TLS key")
	etcdCertFile = flag.String("etcd-cert-file", os.Getenv("ETCD_CERT_FILE"),
		"Path to the client server TLS cert file. "+
			"Environment variable: ETCD_CERT_FILE")
	etcdTrustedCaFile = flag.String("etcd-ca-file", os.Getenv("ETCD_TRUSTED_CA_FILE"),
		"Path to the client server TLS trusted CA key file. "+
			"Environment variable: ETCD_TRUSTED_CA_FILE")

	flag.Parse()

	// Note: We're kinda assuming that if SSL is on at all, it's on for everything.
	clientTlsEnabled = false
	clientProtocol = "http"
	peerProtocol = "http"
	if *etcdCertFile != "" {
		clientTlsEnabled = true
		clientProtocol = "https"
		peerProtocol = "https"
	}

	var err error
	if *instanceID == "" {
		*instanceID, err = ec2cluster.DiscoverInstanceID()
		if err != nil {
			log.Fatalf("ERROR: %s", err)
		}
	}

	awsSession := session.New()
	if region := os.Getenv("AWS_REGION"); region != "" {
		awsSession.Config.WithRegion(region)
	}
	awsregion.GuessRegion(awsSession.Config)

	s := &ec2cluster.Cluster{
		AwsSession: awsSession,
		InstanceID: *instanceID,
		TagName:    *clusterTagName,
	}

	localInstance, err := s.Instance()
	if err != nil {
		log.Fatalf("ERROR: %s", err)
	}

	initialClusterState, initialCluster, err := buildCluster(s)

	envs := []string{
		fmt.Sprintf("ETCD_NAME=%s", *localInstance.InstanceId),
		fmt.Sprintf("ETCD_ADVERTISE_CLIENT_URLS=%s://%s:2379", clientProtocol, *localInstance.PrivateDnsName),
		fmt.Sprintf("ETCD_LISTEN_CLIENT_URLS=%s://0.0.0.0:2379", clientProtocol),
		fmt.Sprintf("ETCD_LISTEN_PEER_URLS=%s://0.0.0.0:2380", peerProtocol),
		fmt.Sprintf("ETCD_INITIAL_CLUSTER_STATE=%s", initialClusterState),
		fmt.Sprintf("ETCD_INITIAL_CLUSTER=%s", strings.Join(initialCluster, ",")),
		fmt.Sprintf("ETCD_INITIAL_ADVERTISE_PEER_URLS=%s://%s:2380", peerProtocol, *localInstance.PrivateDnsName),
	}
	asg, _ := s.AutoscalingGroup()
	if asg != nil {
		envs = append(envs, fmt.Sprintf("ETCD_INITIAL_CLUSTER_TOKEN=%s", *asg.AutoScalingGroupARN))
	}

	for _, line := range envs {
		fmt.Println(line)
	}
}
<commit_msg>Fix bizzaro null pointer exception.<commit_after>package main

import (
	"bytes"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
	"time"

	//log "github.com/Sirupsen/logrus"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/crewjam/awsregion"
	"github.com/crewjam/ec2cluster"
)

type etcdState struct {
	Name       string         `json:"name"`
	ID         string         `json:"id"`
	State      string         `json:"state"`
	StartTime  time.Time      `json:"startTime"`
	LeaderInfo etcdLeaderInfo `json:"leaderInfo"`
}

type etcdLeaderInfo struct {
	Leader               string    `json:"leader"`
	Uptime               string    `json:"uptime"`
	StartTime            time.Time `json:"startTime"`
	RecvAppendRequestCnt int       `json:"recvAppendRequestCnt"`
	RecvPkgRate          int       `json:"recvPkgRate"`
	RecvBandwidthRate    int       `json:"recvBandwidthRate"`
	SendAppendRequestCnt int       `json:"sendAppendRequestCnt"`
}

type etcdMembers struct {
	Members []etcdMember `json:"members,omitempty"`
}

type etcdMember struct {
	ID         string   `json:"id,omitempty"`
	Name       string   `json:"name,omitempty"`
	PeerURLs   []string `json:"peerURLs,omitempty"`
	ClientURLs []string `json:"clientURLs,omitempty"`
}

var localInstance *ec2.Instance
var peerProtocol string
var clientProtocol string
var etcdCertFile *string
var etcdKeyFile *string
var etcdTrustedCaFile *string
var clientTlsEnabled bool

func getTlsConfig() (*tls.Config, error) {
	// Load client cert
	cert, err := tls.LoadX509KeyPair(*etcdCertFile, *etcdKeyFile)
	if err != nil {
		return nil, fmt.Errorf("ERROR: %s", err)
	}

	// Load CA cert
	caCert, err := ioutil.ReadFile(*etcdTrustedCaFile)
	if err != nil {
		return nil, fmt.Errorf("ERROR: %s", err)
	}
	caCertPool := x509.NewCertPool()
	caCertPool.AppendCertsFromPEM(caCert)

	// Setup HTTPS client
	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{cert},
		RootCAs:      caCertPool,
	}
	tlsConfig.BuildNameToCertificate()
	return tlsConfig, nil
}

func getApiResponse(privateIpAddress string, instanceId string, path string, method string) (*http.Response, error) {
	return getApiResponseWithBody(privateIpAddress, instanceId, path, method, "", nil)
}

func getApiResponseWithBody(privateIpAddress string, instanceId string, path string, method string, bodyType string, body io.Reader) (*http.Response, error) {
	var resp *http.Response
	var err error
	var req *http.Request

	url := fmt.Sprintf("%s://%s:2379/v2/%s", clientProtocol, privateIpAddress, path)

	req, err = http.NewRequest(method, url, body)
	if err != nil {
		return nil, fmt.Errorf("%s: %s %s://%s:2379/v2/%s: %s", instanceId, method, clientProtocol, privateIpAddress, path, err)
	}

	if bodyType != "" {
		req.Header.Set("Content-Type", bodyType)
	}

	client := http.DefaultClient
	if clientTlsEnabled {
		tlsConfig, err := getTlsConfig()
		if err != nil {
			log.Fatalf("Error in getTlsConfig: %s", err)
		}
		transport := &http.Transport{TLSClientConfig: tlsConfig}
		client = &http.Client{Transport: transport}
	}

	resp, err = client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("%s: %s %s://%s:2379/v2/%s: %s", instanceId, method, clientProtocol, privateIpAddress, path, err)
	}
	return resp, nil
}

func buildCluster(s *ec2cluster.Cluster) (initialClusterState string, initialCluster []string, err error) {

	localInstance, err := s.Instance()
	if err != nil {
		return "", nil, err
	}

	clusterInstances, err := s.Members()
	if err != nil {
		return "", nil, fmt.Errorf("list members: %s", err)
	}

	initialClusterState = "new"
	initialCluster = []string{}
	for _, instance := range clusterInstances {
		if instance.PrivateDnsName == nil {
			continue
		}

		// add this instance to the initialCluster expression
		initialCluster = append(initialCluster, fmt.Sprintf("%s=%s://%s:2380",
			*instance.InstanceId, peerProtocol, *instance.PrivateDnsName))

		// skip the local node, since we know it is not running yet
		if *instance.InstanceId == *localInstance.InstanceId {
			continue
		}

		// fetch the state of the node.
		path := "stats/self"
		resp, err := getApiResponse(*instance.PrivateDnsName, *instance.InstanceId, path, http.MethodGet)
		if err != nil {
			log.Printf("%s: %s://%s:2379/v2/%s: %s", *instance.InstanceId, clientProtocol,
				*instance.PrivateDnsName, path, err)
			continue
		}
		nodeState := etcdState{}
		if err := json.NewDecoder(resp.Body).Decode(&nodeState); err != nil {
			log.Printf("%s: %s://%s:2379/v2/%s: %s", *instance.InstanceId, clientProtocol,
				*instance.PrivateDnsName, path, err)
			continue
		}

		if nodeState.LeaderInfo.Leader == "" {
			log.Printf("%s: %s://%s:2379/v2/%s: alive, no leader", *instance.InstanceId, clientProtocol,
				*instance.PrivateDnsName, path)
			continue
		}

		log.Printf("%s: %s://%s:2379/v2/%s: has leader %s", *instance.InstanceId, clientProtocol,
			*instance.PrivateDnsName, path, nodeState.LeaderInfo.Leader)
		if initialClusterState != "existing" {
			initialClusterState = "existing"

			// inform the node we found about the new node we're about to add so that
			// when etcd starts we can avoid etcd thinking the cluster is out of sync.
			log.Printf("joining cluster via %s", *instance.InstanceId)
			m := etcdMember{
				Name:     *localInstance.InstanceId,
				PeerURLs: []string{fmt.Sprintf("%s://%s:2380", peerProtocol, *localInstance.PrivateDnsName)},
			}
			body, _ := json.Marshal(m)
			getApiResponseWithBody(*instance.PrivateDnsName, *instance.InstanceId, "members", http.MethodPost, "application/json", bytes.NewReader(body))
		}
	}
	return initialClusterState, initialCluster, nil
}

func main() {
	instanceID := flag.String("instance", "",
		"The instance ID of the cluster member. If not supplied, then the instance ID is determined from EC2 metadata")
	clusterTagName := flag.String("tag", "aws:autoscaling:groupName",
		"The instance tag that is common to all members of the cluster")

	etcdKeyFile = flag.String("etcd-key-file", "", "Path to the TLS key")
	etcdCertFile = flag.String("etcd-cert-file", os.Getenv("ETCD_CERT_FILE"),
		"Path to the client server TLS cert file. "+
			"Environment variable: ETCD_CERT_FILE")
	etcdTrustedCaFile = flag.String("etcd-ca-file", os.Getenv("ETCD_TRUSTED_CA_FILE"),
		"Path to the client server TLS trusted CA key file. "+
			"Environment variable: ETCD_TRUSTED_CA_FILE")

	flag.Parse()

	// Note: We're kinda assuming that if SSL is on at all, it's on for everything.
	clientTlsEnabled = false
	clientProtocol = "http"
	peerProtocol = "http"
	if *etcdCertFile != "" {
		clientTlsEnabled = true
		clientProtocol = "https"
		peerProtocol = "https"
	}

	var err error
	if *instanceID == "" {
		*instanceID, err = ec2cluster.DiscoverInstanceID()
		if err != nil {
			log.Fatalf("ERROR: %s", err)
		}
	}

	awsSession := session.New()
	if region := os.Getenv("AWS_REGION"); region != "" {
		awsSession.Config.WithRegion(region)
	}
	awsregion.GuessRegion(awsSession.Config)

	s := &ec2cluster.Cluster{
		AwsSession: awsSession,
		InstanceID: *instanceID,
		TagName:    *clusterTagName,
	}

	localInstance, err := s.Instance()
	if err != nil {
		log.Fatalf("ERROR: %s", err)
	}

	initialClusterState, initialCluster, err := buildCluster(s)

	envs := []string{
		fmt.Sprintf("ETCD_NAME=%s", *localInstance.InstanceId),
		fmt.Sprintf("ETCD_ADVERTISE_CLIENT_URLS=%s://%s:2379", clientProtocol, *localInstance.PrivateDnsName),
		fmt.Sprintf("ETCD_LISTEN_CLIENT_URLS=%s://0.0.0.0:2379", clientProtocol),
		fmt.Sprintf("ETCD_LISTEN_PEER_URLS=%s://0.0.0.0:2380", peerProtocol),
		fmt.Sprintf("ETCD_INITIAL_CLUSTER_STATE=%s", initialClusterState),
		fmt.Sprintf("ETCD_INITIAL_CLUSTER=%s", strings.Join(initialCluster, ",")),
		fmt.Sprintf("ETCD_INITIAL_ADVERTISE_PEER_URLS=%s://%s:2380", peerProtocol, *localInstance.PrivateDnsName),
	}
	asg, _ := s.AutoscalingGroup()
	if asg != nil {
		envs = append(envs, fmt.Sprintf("ETCD_INITIAL_CLUSTER_TOKEN=%s", *asg.AutoScalingGroupARN))
	}

	for _, line := range envs {
		fmt.Println(line)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/boltdb/bolt"
	"github.com/codegangsta/negroni"
	"github.com/gorilla/mux"
	"github.com/meatballhat/negroni-logrus"
	"github.com/unrolled/render"
	"html/template"
	"net/http"
	"strings"
	text_template "text/template"
	"time"
	"vmango"
	"vmango/dal"
	"vmango/handlers"
)

var (
	LISTEN_ADDR   = flag.String("listen", "0.0.0.0:8000", "Listen address")
	TEMPLATE_PATH = flag.String("template-path", "templates", "Template path")
	STATIC_PATH   = flag.String("static-path", "static", "Static path")
	METADB_PATH   = flag.String("metadb-path", "vmango.db", "Metadata database path")
	IMAGES_PATH   = flag.String("images-path", "images", "Machine images repository path")
	VM_TEMPLATE   = flag.String("vm-template", "vm.xml.in", "Virtual machine configuration template")
)

func main() {
	flag.Parse()
	log.SetLevel(log.DebugLevel)

	router := mux.NewRouter().StrictSlash(true)

	renderer := render.New(render.Options{
		Extensions:    []string{".html"},
		IsDevelopment: true,
		Directory:     *TEMPLATE_PATH,
		Funcs: []template.FuncMap{
			template.FuncMap{
				"HasPrefix": strings.HasPrefix,
				"HumanizeDate": func(date time.Time) string {
					return date.Format("Mon Jan 2 15:04:05 -0700 MST 2006")
				},
				"Url": func(name string, params ...string) (string, error) {
					route := router.Get(name)
					if route == nil {
						return "", fmt.Errorf("route named %s not found", name)
					}
					url, err := route.URL(params...)
					if err != nil {
						return "", err
					}
					return url.Path, nil
				},
			},
		},
	})
	vmtpl, err := text_template.ParseFiles(*VM_TEMPLATE)
	if err != nil {
		log.WithError(err).WithField("filename", *VM_TEMPLATE).Fatal("failed to parse machine template")
	}
	machines, err := dal.NewLibvirtMachinerep("qemu:///system", vmtpl)
	if err != nil {
		log.WithError(err).Fatal("failed to initialize libvirt-kvm machines")
	}

	imagerep := dal.NewLocalfsImagerep(*IMAGES_PATH)

	metadb, err := bolt.Open(*METADB_PATH, 0600, nil)
	if err != nil {
		log.WithError(err).Fatal("failed to open metadata db")
	}

	planrep := dal.NewBoltPlanrep(metadb)
	ippool := dal.NewBoltIPPool(metadb)

	ctx := &vmango.Context{
		Render:   renderer,
		Router:   router,
		Machines: machines,
		Logger:   log.New(),
		Meta:     metadb,
		Images:   imagerep,
		IPPool:   ippool,
		Plans:    planrep,
	}

	router.Handle("/", vmango.NewHandler(ctx, handlers.Index)).Name("index")
	router.Handle("/machines/", vmango.NewHandler(ctx, handlers.MachineList)).Name("machine-list")
	router.Handle("/machines/add", vmango.NewHandler(ctx, handlers.MachineAddForm)).Name("machine-add")
	router.Handle("/machines/{name:.+}/", vmango.NewHandler(ctx, handlers.MachineDetail)).Name("machine-detail")
	router.Handle("/images/", vmango.NewHandler(ctx, handlers.ImageList)).Name("image-list")
	router.Handle("/ipaddress/", vmango.NewHandler(ctx, handlers.IPList)).Name("ip-list")
	router.Handle("/plans/", vmango.NewHandler(ctx, handlers.PlanList)).Name("plan-list")

	router.HandleFunc("/static{name:.*}", handlers.MakeStaticHandler(*STATIC_PATH)).Name("static")

	n := negroni.New()
	n.Use(negronilogrus.NewMiddleware())
	n.Use(negroni.NewRecovery())
	n.UseHandler(router)

	log.WithField("address", *LISTEN_ADDR).Info("starting server")
	log.Fatal(http.ListenAndServe(*LISTEN_ADDR, n))
}
<commit_msg>Fixed 'Machine not found' error<commit_after>package main

import (
	"flag"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/boltdb/bolt"
	"github.com/codegangsta/negroni"
	"github.com/gorilla/mux"
	"github.com/meatballhat/negroni-logrus"
	"github.com/unrolled/render"
	"html/template"
	"net/http"
	"strings"
	text_template "text/template"
	"time"
	"vmango"
	"vmango/dal"
	"vmango/handlers"
)

var (
	LISTEN_ADDR   = flag.String("listen", "0.0.0.0:8000", "Listen address")
	TEMPLATE_PATH = flag.String("template-path", "templates", "Template path")
	STATIC_PATH   = flag.String("static-path", "static", "Static path")
	METADB_PATH   = flag.String("metadb-path", "vmango.db", "Metadata database path")
	IMAGES_PATH   = flag.String("images-path", "images", "Machine images repository path")
	VM_TEMPLATE   = flag.String("vm-template", "vm.xml.in", "Virtual machine configuration template")
)

func main() {
	flag.Parse()
	log.SetLevel(log.DebugLevel)

	router := mux.NewRouter().StrictSlash(true)

	renderer := render.New(render.Options{
		Extensions:    []string{".html"},
		IsDevelopment: true,
		Directory:     *TEMPLATE_PATH,
		Funcs: []template.FuncMap{
			template.FuncMap{
				"HasPrefix": strings.HasPrefix,
				"HumanizeDate": func(date time.Time) string {
					return date.Format("Mon Jan 2 15:04:05 -0700 MST 2006")
				},
				"Url": func(name string, params ...string) (string, error) {
					route := router.Get(name)
					if route == nil {
						return "", fmt.Errorf("route named %s not found", name)
					}
					url, err := route.URL(params...)
					if err != nil {
						return "", err
					}
					return url.Path, nil
				},
			},
		},
	})
	vmtpl, err := text_template.ParseFiles(*VM_TEMPLATE)
	if err != nil {
		log.WithError(err).WithField("filename", *VM_TEMPLATE).Fatal("failed to parse machine template")
	}
	machines, err := dal.NewLibvirtMachinerep("qemu:///system", vmtpl)
	if err != nil {
		log.WithError(err).Fatal("failed to initialize libvirt-kvm machines")
	}

	imagerep := dal.NewLocalfsImagerep(*IMAGES_PATH)

	metadb, err := bolt.Open(*METADB_PATH, 0600, nil)
	if err != nil {
		log.WithError(err).Fatal("failed to open metadata db")
	}

	planrep := dal.NewBoltPlanrep(metadb)
	ippool := dal.NewBoltIPPool(metadb)

	ctx := &vmango.Context{
		Render:   renderer,
		Router:   router,
		Machines: machines,
		Logger:   log.New(),
		Meta:     metadb,
		Images:   imagerep,
		IPPool:   ippool,
		Plans:    planrep,
	}

	router.Handle("/", vmango.NewHandler(ctx, handlers.Index)).Name("index")
	router.Handle("/machines/", vmango.NewHandler(ctx, handlers.MachineList)).Name("machine-list")
	router.Handle("/machines/add", vmango.NewHandler(ctx, handlers.MachineAddForm)).Name("machine-add")
	router.Handle("/machines/{name:.+}//", vmango.NewHandler(ctx, handlers.MachineDetail)).Name("machine-detail")
	router.Handle("/images/", vmango.NewHandler(ctx, handlers.ImageList)).Name("image-list")
	router.Handle("/ipaddress/", vmango.NewHandler(ctx, handlers.IPList)).Name("ip-list")
	router.Handle("/plans/", vmango.NewHandler(ctx, handlers.PlanList)).Name("plan-list")

	router.HandleFunc("/static{name:.*}", handlers.MakeStaticHandler(*STATIC_PATH)).Name("static")

	n := negroni.New()
	n.Use(negronilogrus.NewMiddleware())
	n.Use(negroni.NewRecovery())
	n.UseHandler(router)

	log.WithField("address", *LISTEN_ADDR).Info("starting server")
	log.Fatal(http.ListenAndServe(*LISTEN_ADDR, n))
}
<|endoftext|>