<commit_before>package service

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log/syslog"
	"net"
	"net/http"
	"os"
	"os/exec"
	"os/signal"
	"syscall"
	"time"

	log "github.com/Sirupsen/logrus"
	logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
	logrus_logstash "github.com/bshuster-repo/logrus-logstash-hook"
	etcd "github.com/coreos/etcd/client"
	"github.com/mailgun/manners"
	"github.com/mailgun/metrics"
	"github.com/mailgun/scroll"
	"github.com/vulcand/vulcand/api"
	"github.com/vulcand/vulcand/engine"
	"github.com/vulcand/vulcand/engine/etcdv2ng"
	"github.com/vulcand/vulcand/plugin"
	"github.com/vulcand/vulcand/proxy"
	"github.com/vulcand/vulcand/secret"
	"github.com/vulcand/vulcand/stapler"
	"github.com/vulcand/vulcand/supervisor"
)

func Run(registry *plugin.Registry) error {
	options, err := ParseCommandLine()
	if err != nil {
		return fmt.Errorf("failed to parse command line: %s", err)
	}
	service := NewService(options, registry)
	if err := service.Start(); err != nil {
		log.Errorf("Failed to start service: %v", err)
		return fmt.Errorf("service start failure: %s", err)
	} else {
		log.Infof("Service exited gracefully")
	}
	return nil
}

type Service struct {
	client        etcd.Client
	options       Options
	registry      *plugin.Registry
	apiApp        *scroll.App
	errorC        chan error
	sigC          chan os.Signal
	supervisor    *supervisor.Supervisor
	metricsClient metrics.Client
	apiServer     *manners.GracefulServer
	ng            engine.Engine
	stapler       stapler.Stapler
}

func NewService(options Options, registry *plugin.Registry) *Service {
	return &Service{
		registry: registry,
		options:  options,
		errorC:   make(chan error),
		// Channel receiving signals has to be non blocking, otherwise the service can miss a signal.
		sigC: make(chan os.Signal, 1024),
	}
}

func (s *Service) Start() error {
	if s.options.Log == "console" {
		log.SetFormatter(&log.TextFormatter{})
	} else if s.options.Log == "syslog" {
		hook, err := logrus_syslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
		if err != nil {
			return err
		}
		log.SetFormatter(&log.TextFormatter{DisableColors: true})
		log.AddHook(hook)
	} else if s.options.Log == "json" {
		log.SetFormatter(&log.JSONFormatter{})
	} else if s.options.Log == "logstash" {
		log.SetFormatter(&logrus_logstash.LogstashFormatter{Type: "logs"})
	} else {
		log.Warnf("Invalid logger type %v, fallback to default.", s.options.Log)
	}
	log.SetOutput(os.Stdout)
	log.SetLevel(s.options.LogSeverity.S)

	log.Infof("Service starts with options: %#v", s.options)

	if s.options.PidPath != "" {
		ioutil.WriteFile(s.options.PidPath, []byte(fmt.Sprint(os.Getpid())), 0644)
	}

	if s.options.MetricsClient != nil {
		s.metricsClient = s.options.MetricsClient
	} else if s.options.StatsdAddr != "" {
		var err error
		s.metricsClient, err = metrics.NewWithOptions(s.options.StatsdAddr, s.options.StatsdPrefix, metrics.Options{UseBuffering: true})
		if err != nil {
			return err
		}
	}

	apiFile, muxFiles, err := s.getFiles()
	if err != nil {
		return err
	}

	if err := s.newEngine(); err != nil {
		return err
	}

	s.stapler = stapler.New()
	s.supervisor = supervisor.New(
		s.newProxy, s.ng, s.errorC, supervisor.Options{Files: muxFiles})

	// Tells configurator to perform initial proxy configuration and start watching changes
	if err := s.supervisor.Start(); err != nil {
		return err
	}

	if err := s.initApi(); err != nil {
		return err
	}

	go func() {
		s.errorC <- s.startApi(apiFile)
	}()

	if s.metricsClient != nil {
		go s.reportSystemMetrics()
	}
	signal.Notify(s.sigC, os.Interrupt, os.Kill, syscall.SIGTERM, syscall.SIGUSR2, syscall.SIGCHLD)

	// Block until a signal is received or we got an error
	for {
		select {
		case signal := <-s.sigC:
			switch signal {
			case syscall.SIGTERM, syscall.SIGINT:
				log.Infof("Got signal '%s', shutting down gracefully", signal)
				s.supervisor.Stop(true)
				log.Infof("All servers stopped")
				return nil
			case syscall.SIGKILL:
				log.Infof("Got signal '%s', exiting now without waiting", signal)
				s.supervisor.Stop(false)
				return nil
			case syscall.SIGUSR2:
				log.Infof("Got signal '%s', forking a new self", signal)
				if err := s.startChild(); err != nil {
					log.Infof("Failed to start self: %s", err)
				} else {
					log.Infof("Successfully started self")
				}
			case syscall.SIGCHLD:
				log.Warningf("Child exited, got '%s', collecting status", signal)
				var wait syscall.WaitStatus
				syscall.Wait4(-1, &wait, syscall.WNOHANG, nil)
				log.Warningf("Collected exit status from child")
			default:
				log.Infof("Ignoring '%s'", signal)
			}
		case err := <-s.errorC:
			log.Infof("Got request to shutdown with error: %s", err)
			return err
		}
	}
}

func (s *Service) getFiles() (*proxy.FileDescriptor, []*proxy.FileDescriptor, error) {
	// These files may be passed in by the parent process
	filesString := os.Getenv(vulcandFilesKey)
	if filesString == "" {
		return nil, nil, nil
	}

	files, err := filesFromString(filesString)
	if err != nil {
		return nil, nil, fmt.Errorf("child failed to start: failed to read files from string, error %s", err)
	}

	if len(files) != 0 {
		log.Infof("I am a child that has been passed files: %s", files)
	}

	return s.splitFiles(files)
}

func (s *Service) splitFiles(files []*proxy.FileDescriptor) (*proxy.FileDescriptor, []*proxy.FileDescriptor, error) {
	apiAddr := fmt.Sprintf("%s:%d", s.options.ApiInterface, s.options.ApiPort)
	for i, f := range files {
		if f.Address.Address == apiAddr {
			return files[i], append(files[:i], files[i+1:]...), nil
		}
	}
	return nil, nil, fmt.Errorf("API address %s not found in %s", apiAddr, files)
}

func (s *Service) startChild() error {
	log.Infof("Starting child")
	path, err := execPath()
	if err != nil {
		return err
	}

	wd, err := os.Getwd()
	if nil != err {
		return err
	}

	// Get socket files currently in use by the underlying http server controlled by supervisor
	extraFiles, err := s.supervisor.GetFiles()
	if err != nil {
		return err
	}

	apiFile, err := s.GetAPIFile()
	if err != nil {
		return err
	}

	extraFiles = append(extraFiles, apiFile)

	// These files will be passed to the child process
	files := []*os.File{os.Stdin, os.Stdout, os.Stderr}
	for _, f := range extraFiles {
		files = append(files, f.File)
	}

	// Serialize files to JSON string representation
	vals, err := filesToString(extraFiles)
	if err != nil {
		return err
	}

	log.Infof("Passing %s to child", vals)
	os.Setenv(vulcandFilesKey, vals)

	p, err := os.StartProcess(path, os.Args, &os.ProcAttr{
		Dir:   wd,
		Env:   os.Environ(),
		Files: files,
		Sys:   &syscall.SysProcAttr{},
	})

	if err != nil {
		return err
	}

	log.Infof("Started new child pid=%d binary=%s", p.Pid, path)
	return nil
}

func (s *Service) GetAPIFile() (*proxy.FileDescriptor, error) {
	file, err := s.apiServer.GetFile()
	if err != nil {
		return nil, err
	}
	a := engine.Address{
		Network: "tcp",
		Address: fmt.Sprintf("%s:%d", s.options.ApiInterface, s.options.ApiPort),
	}
	return &proxy.FileDescriptor{File: file, Address: a}, nil
}

func (s *Service) newBox() (*secret.Box, error) {
	if s.options.SealKey == "" {
		return nil, nil
	}
	key, err := secret.KeyFromString(s.options.SealKey)
	if err != nil {
		return nil, err
	}
	return secret.NewBox(key)
}

func (s *Service) newEngine() error {
	box, err := s.newBox()
	if err != nil {
		return err
	}
	ng, err := etcdv2ng.New(
		s.options.EtcdNodes,
		s.options.EtcdKey,
		s.registry,
		etcdv2ng.Options{
			EtcdCaFile:              s.options.EtcdCaFile,
			EtcdCertFile:            s.options.EtcdCertFile,
			EtcdKeyFile:             s.options.EtcdKeyFile,
			EtcdConsistency:         s.options.EtcdConsistency,
			EtcdSyncIntervalSeconds: s.options.EtcdSyncIntervalSeconds,
			Box: box,
		})
	if err != nil {
		return err
	}
	s.ng = ng
	return err
}

func (s *Service) reportSystemMetrics() {
	defer func() {
		if r := recover(); r != nil {
			log.Infof("Recovered in reportSystemMetrics", r)
		}
	}()
	for {
		s.metricsClient.ReportRuntimeMetrics("sys", 1.0)
		// we have 256 time buckets for gc stats, GC is being executed every 4ms on average
		// so we have 256 * 4 = 1024 around one second to report it. To play safe, let's report every 300ms
		time.Sleep(300 * time.Millisecond)
	}
}

func (s *Service) newProxy(id int) (proxy.Proxy, error) {
	return proxy.New(id, s.stapler, proxy.Options{
		MetricsClient:      s.metricsClient,
		DialTimeout:        s.options.EndpointDialTimeout,
		ReadTimeout:        s.options.ServerReadTimeout,
		WriteTimeout:       s.options.ServerWriteTimeout,
		MaxHeaderBytes:     s.options.ServerMaxHeaderBytes,
		DefaultListener:    constructDefaultListener(s.options),
		NotFoundMiddleware: s.registry.GetNotFoundMiddleware(),
		Router:             s.registry.GetRouter(),
		IncomingConnectionTracker: s.registry.GetIncomingConnectionTracker(),
		OutgoingConnectionTracker: s.registry.GetOutgoingConnectionTracker(),
	})
}

func (s *Service) initApi() error {
	s.apiApp = scroll.NewApp()
	api.InitProxyController(s.ng, s.supervisor, s.apiApp)
	return nil
}

func (s *Service) startApi(file *proxy.FileDescriptor) error {
	addr := fmt.Sprintf("%s:%d", s.options.ApiInterface, s.options.ApiPort)

	server := &http.Server{
		Addr:           addr,
		Handler:        s.apiApp.GetHandler(),
		ReadTimeout:    s.options.ServerReadTimeout,
		WriteTimeout:   s.options.ServerWriteTimeout,
		MaxHeaderBytes: 1 << 20,
	}

	var listener net.Listener
	if file != nil {
		var err error
		listener, err = file.ToListener()
		if err != nil {
			return err
		}
	}

	s.apiServer = manners.NewWithOptions(manners.Options{Server: server, Listener: listener})
	return s.apiServer.ListenAndServe()
}

func constructDefaultListener(options Options) *engine.Listener {
	if options.DefaultListener {
		return &engine.Listener{
			Id:       "DefaultListener",
			Protocol: "http",
			Address: engine.Address{
				Network: "tcp",
				Address: fmt.Sprintf("%s:%d", options.Interface, options.Port),
			},
		}
	}
	return nil
}

func execPath() (string, error) {
	name, err := exec.LookPath(os.Args[0])
	if err != nil {
		return "", err
	}
	if _, err = os.Stat(name); nil != err {
		return "", err
	}
	return name, err
}

type fileDescriptor struct {
	Address  engine.Address
	FileFD   int
	FileName string
}

// filesToString serializes file descriptors as well as accompanying information (like socket host and port)
func filesToString(files []*proxy.FileDescriptor) (string, error) {
	out := make([]fileDescriptor, len(files))
	for i, f := range files {
		out[i] = fileDescriptor{
			// Once files will be passed to the child process and their FDs will change.
			// The first three passed files are stdin, stdout and stderr, every next file will have the index + 3
			// That's why we rearrange the FDs for child processes to get the correct file descriptors.
			FileFD:   i + 3,
			FileName: f.File.Name(),
			Address:  f.Address,
		}
	}
	bytes, err := json.Marshal(out)
	if err != nil {
		return "", err
	}
	return string(bytes), nil
}

// filesFromString de-serializes the file descriptors and turns them in the os.Files
func filesFromString(in string) ([]*proxy.FileDescriptor, error) {
	var out []fileDescriptor
	if err := json.Unmarshal([]byte(in), &out); err != nil {
		return nil, err
	}
	files := make([]*proxy.FileDescriptor, len(out))
	for i, o := range out {
		files[i] = &proxy.FileDescriptor{
			File:    os.NewFile(uintptr(o.FileFD), o.FileName),
			Address: o.Address,
		}
	}
	return files, nil
}

const vulcandFilesKey = "VULCAND_FILES_KEY"
<commit_msg>Do not log to stdout if --log=syslog<commit_after>package service

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log/syslog"
	"net"
	"net/http"
	"os"
	"os/exec"
	"os/signal"
	"syscall"
	"time"

	log "github.com/Sirupsen/logrus"
	logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
	logrus_logstash "github.com/bshuster-repo/logrus-logstash-hook"
	etcd "github.com/coreos/etcd/client"
	"github.com/mailgun/manners"
	"github.com/mailgun/metrics"
	"github.com/mailgun/scroll"
	"github.com/vulcand/vulcand/api"
	"github.com/vulcand/vulcand/engine"
	"github.com/vulcand/vulcand/engine/etcdv2ng"
	"github.com/vulcand/vulcand/plugin"
	"github.com/vulcand/vulcand/proxy"
	"github.com/vulcand/vulcand/secret"
	"github.com/vulcand/vulcand/stapler"
	"github.com/vulcand/vulcand/supervisor"
)

func Run(registry *plugin.Registry) error {
	options, err := ParseCommandLine()
	if err != nil {
		return fmt.Errorf("failed to parse command line: %s", err)
	}
	service := NewService(options, registry)
	if err := service.Start(); err != nil {
		log.Errorf("Failed to start service: %v", err)
		return fmt.Errorf("service start failure: %s", err)
	} else {
		log.Infof("Service exited gracefully")
	}
	return nil
}

type Service struct {
	client        etcd.Client
	options       Options
	registry      *plugin.Registry
	apiApp        *scroll.App
	errorC        chan error
	sigC          chan os.Signal
	supervisor    *supervisor.Supervisor
	metricsClient metrics.Client
	apiServer     *manners.GracefulServer
	ng            engine.Engine
	stapler       stapler.Stapler
}

func NewService(options Options, registry *plugin.Registry) *Service {
	return &Service{
		registry: registry,
		options:  options,
		errorC:   make(chan error),
		// Channel receiving signals has to be non blocking, otherwise the service can miss a signal.
		sigC: make(chan os.Signal, 1024),
	}
}

func (s *Service) Start() error {
	if s.options.Log == "console" {
		log.SetOutput(os.Stdout)
		log.SetFormatter(&log.TextFormatter{})
	} else if s.options.Log == "syslog" {
		devNull, err := os.OpenFile("/dev/null", os.O_WRONLY, 0)
		if err != nil {
			panic(err)
		}
		log.SetOutput(devNull)
		hook, err := logrus_syslog.NewSyslogHook("", "", syslog.LOG_INFO|syslog.LOG_MAIL, "vulcand")
		if err != nil {
			return err
		}
		log.SetFormatter(&log.TextFormatter{DisableColors: true})
		log.AddHook(hook)
	} else if s.options.Log == "json" {
		log.SetOutput(os.Stdout)
		log.SetFormatter(&log.JSONFormatter{})
	} else if s.options.Log == "logstash" {
		log.SetOutput(os.Stdout)
		log.SetFormatter(&logrus_logstash.LogstashFormatter{Type: "logs"})
	} else {
		log.SetOutput(os.Stdout)
		log.Warnf("Invalid logger type %v, fallback to default.", s.options.Log)
	}
	log.SetLevel(s.options.LogSeverity.S)

	log.Infof("Service starts with options: %#v", s.options)

	if s.options.PidPath != "" {
		ioutil.WriteFile(s.options.PidPath, []byte(fmt.Sprint(os.Getpid())), 0644)
	}

	if s.options.MetricsClient != nil {
		s.metricsClient = s.options.MetricsClient
	} else if s.options.StatsdAddr != "" {
		var err error
		s.metricsClient, err = metrics.NewWithOptions(s.options.StatsdAddr, s.options.StatsdPrefix, metrics.Options{UseBuffering: true})
		if err != nil {
			return err
		}
	}

	apiFile, muxFiles, err := s.getFiles()
	if err != nil {
		return err
	}

	if err := s.newEngine(); err != nil {
		return err
	}

	s.stapler = stapler.New()
	s.supervisor = supervisor.New(
		s.newProxy, s.ng, s.errorC, supervisor.Options{Files: muxFiles})

	// Tells configurator to perform initial proxy configuration and start watching changes
	if err := s.supervisor.Start(); err != nil {
		return err
	}

	if err := s.initApi(); err != nil {
		return err
	}

	go func() {
		s.errorC <- s.startApi(apiFile)
	}()

	if s.metricsClient != nil {
		go s.reportSystemMetrics()
	}
	signal.Notify(s.sigC, os.Interrupt, os.Kill, syscall.SIGTERM, syscall.SIGUSR2, syscall.SIGCHLD)

	// Block until a signal is received or we got an error
	for {
		select {
		case signal := <-s.sigC:
			switch signal {
			case syscall.SIGTERM, syscall.SIGINT:
				log.Infof("Got signal '%s', shutting down gracefully", signal)
				s.supervisor.Stop(true)
				log.Infof("All servers stopped")
				return nil
			case syscall.SIGKILL:
				log.Infof("Got signal '%s', exiting now without waiting", signal)
				s.supervisor.Stop(false)
				return nil
			case syscall.SIGUSR2:
				log.Infof("Got signal '%s', forking a new self", signal)
				if err := s.startChild(); err != nil {
					log.Infof("Failed to start self: %s", err)
				} else {
					log.Infof("Successfully started self")
				}
			case syscall.SIGCHLD:
				log.Warningf("Child exited, got '%s', collecting status", signal)
				var wait syscall.WaitStatus
				syscall.Wait4(-1, &wait, syscall.WNOHANG, nil)
				log.Warningf("Collected exit status from child")
			default:
				log.Infof("Ignoring '%s'", signal)
			}
		case err := <-s.errorC:
			log.Infof("Got request to shutdown with error: %s", err)
			return err
		}
	}
}

func (s *Service) getFiles() (*proxy.FileDescriptor, []*proxy.FileDescriptor, error) {
	// These files may be passed in by the parent process
	filesString := os.Getenv(vulcandFilesKey)
	if filesString == "" {
		return nil, nil, nil
	}

	files, err := filesFromString(filesString)
	if err != nil {
		return nil, nil, fmt.Errorf("child failed to start: failed to read files from string, error %s", err)
	}

	if len(files) != 0 {
		log.Infof("I am a child that has been passed files: %s", files)
	}

	return s.splitFiles(files)
}

func (s *Service) splitFiles(files []*proxy.FileDescriptor) (*proxy.FileDescriptor, []*proxy.FileDescriptor, error) {
	apiAddr := fmt.Sprintf("%s:%d", s.options.ApiInterface, s.options.ApiPort)
	for i, f := range files {
		if f.Address.Address == apiAddr {
			return files[i], append(files[:i], files[i+1:]...), nil
		}
	}
	return nil, nil, fmt.Errorf("API address %s not found in %s", apiAddr, files)
}

func (s *Service) startChild() error {
	log.Infof("Starting child")
	path, err := execPath()
	if err != nil {
		return err
	}

	wd, err := os.Getwd()
	if nil != err {
		return err
	}

	// Get socket files currently in use by the underlying http server controlled by supervisor
	extraFiles, err := s.supervisor.GetFiles()
	if err != nil {
		return err
	}

	apiFile, err := s.GetAPIFile()
	if err != nil {
		return err
	}

	extraFiles = append(extraFiles, apiFile)

	// These files will be passed to the child process
	files := []*os.File{os.Stdin, os.Stdout, os.Stderr}
	for _, f := range extraFiles {
		files = append(files, f.File)
	}

	// Serialize files to JSON string representation
	vals, err := filesToString(extraFiles)
	if err != nil {
		return err
	}

	log.Infof("Passing %s to child", vals)
	os.Setenv(vulcandFilesKey, vals)

	p, err := os.StartProcess(path, os.Args, &os.ProcAttr{
		Dir:   wd,
		Env:   os.Environ(),
		Files: files,
		Sys:   &syscall.SysProcAttr{},
	})

	if err != nil {
		return err
	}

	log.Infof("Started new child pid=%d binary=%s", p.Pid, path)
	return nil
}

func (s *Service) GetAPIFile() (*proxy.FileDescriptor, error) {
	file, err := s.apiServer.GetFile()
	if err != nil {
		return nil, err
	}
	a := engine.Address{
		Network: "tcp",
		Address: fmt.Sprintf("%s:%d", s.options.ApiInterface, s.options.ApiPort),
	}
	return &proxy.FileDescriptor{File: file, Address: a}, nil
}

func (s *Service) newBox() (*secret.Box, error) {
	if s.options.SealKey == "" {
		return nil, nil
	}
	key, err := secret.KeyFromString(s.options.SealKey)
	if err != nil {
		return nil, err
	}
	return secret.NewBox(key)
}

func (s *Service) newEngine() error {
	box, err := s.newBox()
	if err != nil {
		return err
	}
	ng, err := etcdv2ng.New(
		s.options.EtcdNodes,
		s.options.EtcdKey,
		s.registry,
		etcdv2ng.Options{
			EtcdCaFile:              s.options.EtcdCaFile,
			EtcdCertFile:            s.options.EtcdCertFile,
			EtcdKeyFile:             s.options.EtcdKeyFile,
			EtcdConsistency:         s.options.EtcdConsistency,
			EtcdSyncIntervalSeconds: s.options.EtcdSyncIntervalSeconds,
			Box: box,
		})
	if err != nil {
		return err
	}
	s.ng = ng
	return err
}

func (s *Service) reportSystemMetrics() {
	defer func() {
		if r := recover(); r != nil {
			log.Infof("Recovered in reportSystemMetrics", r)
		}
	}()
	for {
		s.metricsClient.ReportRuntimeMetrics("sys", 1.0)
		// we have 256 time buckets for gc stats, GC is being executed every 4ms on average
		// so we have 256 * 4 = 1024 around one second to report it. To play safe, let's report every 300ms
		time.Sleep(300 * time.Millisecond)
	}
}

func (s *Service) newProxy(id int) (proxy.Proxy, error) {
	return proxy.New(id, s.stapler, proxy.Options{
		MetricsClient:      s.metricsClient,
		DialTimeout:        s.options.EndpointDialTimeout,
		ReadTimeout:        s.options.ServerReadTimeout,
		WriteTimeout:       s.options.ServerWriteTimeout,
		MaxHeaderBytes:     s.options.ServerMaxHeaderBytes,
		DefaultListener:    constructDefaultListener(s.options),
		NotFoundMiddleware: s.registry.GetNotFoundMiddleware(),
		Router:             s.registry.GetRouter(),
		IncomingConnectionTracker: s.registry.GetIncomingConnectionTracker(),
		OutgoingConnectionTracker: s.registry.GetOutgoingConnectionTracker(),
	})
}

func (s *Service) initApi() error {
	s.apiApp = scroll.NewApp()
	api.InitProxyController(s.ng, s.supervisor, s.apiApp)
	return nil
}

func (s *Service) startApi(file *proxy.FileDescriptor) error {
	addr := fmt.Sprintf("%s:%d", s.options.ApiInterface, s.options.ApiPort)

	server := &http.Server{
		Addr:           addr,
		Handler:        s.apiApp.GetHandler(),
		ReadTimeout:    s.options.ServerReadTimeout,
		WriteTimeout:   s.options.ServerWriteTimeout,
		MaxHeaderBytes: 1 << 20,
	}

	var listener net.Listener
	if file != nil {
		var err error
		listener, err = file.ToListener()
		if err != nil {
			return err
		}
	}

	s.apiServer = manners.NewWithOptions(manners.Options{Server: server, Listener: listener})
	return s.apiServer.ListenAndServe()
}

func constructDefaultListener(options Options) *engine.Listener {
	if options.DefaultListener {
		return &engine.Listener{
			Id:       "DefaultListener",
			Protocol: "http",
			Address: engine.Address{
				Network: "tcp",
				Address: fmt.Sprintf("%s:%d", options.Interface, options.Port),
			},
		}
	}
	return nil
}

func execPath() (string, error) {
	name, err := exec.LookPath(os.Args[0])
	if err != nil {
		return "", err
	}
	if _, err = os.Stat(name); nil != err {
		return "", err
	}
	return name, err
}

type fileDescriptor struct {
	Address  engine.Address
	FileFD   int
	FileName string
}

// filesToString serializes file descriptors as well as accompanying information (like socket host and port)
func filesToString(files []*proxy.FileDescriptor) (string, error) {
	out := make([]fileDescriptor, len(files))
	for i, f := range files {
		out[i] = fileDescriptor{
			// Once files will be passed to the child process and their FDs will change.
			// The first three passed files are stdin, stdout and stderr, every next file will have the index + 3
			// That's why we rearrange the FDs for child processes to get the correct file descriptors.
			FileFD:   i + 3,
			FileName: f.File.Name(),
			Address:  f.Address,
		}
	}
	bytes, err := json.Marshal(out)
	if err != nil {
		return "", err
	}
	return string(bytes), nil
}

// filesFromString de-serializes the file descriptors and turns them in the os.Files
func filesFromString(in string) ([]*proxy.FileDescriptor, error) {
	var out []fileDescriptor
	if err := json.Unmarshal([]byte(in), &out); err != nil {
		return nil, err
	}
	files := make([]*proxy.FileDescriptor, len(out))
	for i, o := range out {
		files[i] = &proxy.FileDescriptor{
			File:    os.NewFile(uintptr(o.FileFD), o.FileName),
			Address: o.Address,
		}
	}
	return files, nil
}

const vulcandFilesKey = "VULCAND_FILES_KEY"
<|endoftext|>


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

package service

import (
	"fmt"
	"io"
	//   "runtime/debug"
	"strings"
	"sync"
	"sync/atomic"

	"github.com/nagae-memooff/surgemq/sessions"
	"github.com/nagae-memooff/surgemq/topics"
	"github.com/surgemq/message"
)

type (
	OnCompleteFunc func(msg, ack message.Message, err error) error
	OnPublishFunc  func(msg *message.PublishMessage) error
)

type stat struct {
	bytes int64
	msgs  int64
}

func (this *stat) increment(n int64) {
	atomic.AddInt64(&this.bytes, n)
	atomic.AddInt64(&this.msgs, 1)
}

var (
	gsvcid uint64 = 0
)

type service struct {
	// The ID of this service, it's not related to the Client ID, just a number that's
	// incremented for every new service.
	id uint64

	// Is this a client or server. It's set by either Connect (client) or
	// HandleConnection (server).
	client bool

	// The number of seconds to keep the connection live if there's no data.
	// If not set then default to 5 mins.
	keepAlive int

	// The number of seconds to wait for the CONNACK message before disconnecting.
	// If not set then default to 2 seconds.
	connectTimeout int

	// The number of seconds to wait for any ACK messages before failing.
	// If not set then default to 20 seconds.
	ackTimeout int

	// The number of times to retry sending a packet if ACK is not received.
	// If no set then default to 3 retries.
	timeoutRetries int

	// Network connection for this service
	conn io.Closer

	// Session manager for tracking all the clients
	sessMgr *sessions.Manager

	// Topics manager for all the client subscriptions
	topicsMgr *topics.Manager

	// sess is the session object for this MQTT session. It keeps track session variables
	// such as ClientId, KeepAlive, Username, etc
	sess *sessions.Session

	// Wait for the various goroutines to finish starting and stopping
	wgStarted sync.WaitGroup
	wgStopped sync.WaitGroup

	// writeMessage mutex - serializes writes to the outgoing buffer.
	wmu sync.Mutex
	rmu sync.Mutex

	// Whether this is service is closed or not.
	closed int64

	// Quit signal for determining when this service should end. If channel is closed,
	// then exit.
	done chan struct{}

	// Incoming data buffer. Bytes are read from the connection and put in here.
	in *buffer

	// Outgoing data buffer. Bytes written here are in turn written out to the connection.
	out *buffer

	// onpub is the method that gets added to the topic subscribers list by the
	// processSubscribe() method. When the server finishes the ack cycle for a
	// PUBLISH message, it will call the subscriber, which is this method.
	//
	// For the server, when this method is called, it means there's a message that
	// should be published to the client on the other end of this connection. So we
	// will call publish() to send the message.
	onpub OnPublishFunc

	inStat  stat
	outStat stat

	intmp  []byte
	outtmp []byte

	//   subs  []interface{}
	//   qoss  []byte
	//   rmsgs []*message.PublishMessage
}

func (this *service) start(client_id string) error {
	var err error

	//   debug.PrintStack()
	// Create the incoming ring buffer
	Log.Debugc(func() string { return fmt.Sprintf("make new buffer for client: %s", client_id) })

	if strings.Contains(client_id, "master") {
		this.in, err = newBuffer(MasterInBufferSize)
		if err != nil {
			return err
		}

		// Create the outgoing ring buffer
		this.out, err = newBuffer(MasterOutBufferSize)
		if err != nil {
			return err
		}
	} else {
		this.in, err = newBuffer(DeviceInBufferSize)
		if err != nil {
			return err
		}

		// Create the outgoing ring buffer
		this.out, err = newBuffer(DeviceOutBufferSize)
		if err != nil {
			return err
		}
	}

	// If this is a server
	if !this.client {
		// Creat the onPublishFunc so it can be used for published messages
		this.onpub = func(msg *message.PublishMessage) error {
			if err := this.publish(msg, nil); err != nil {
				Log.Errorc(func() string { return fmt.Sprintf("service/onPublish: Error publishing message: %v", err) })
				return err
			}

			return nil
		}

		// If this is a recovered session, then add any topics it subscribed before
		topics, qoss, err := this.sess.Topics()
		if err != nil {
			return err
		} else {
			for i, t := range topics {
				this.topicsMgr.Subscribe([]byte(t), qoss[i], &this.onpub, this.sess.ID())
			}
		}
	}

	// Processor is responsible for reading messages out of the buffer and processing
	// them accordingly.
	this.wgStarted.Add(1)
	this.wgStopped.Add(1)
	go this.processor()
	/*this.wgStarted.Add(1)
	this.wgStopped.Add(1)
	go this.processor_not_readFrom()*/

	// Receiver is responsible for reading from the connection and putting data into
	// a buffer.
	this.wgStarted.Add(1)
	this.wgStopped.Add(1)
	go this.receiver()

	// Sender is responsible for writing data in the buffer into the connection.
	this.wgStarted.Add(1)
	this.wgStopped.Add(1)
	go this.sender()

	// Wait for all the goroutines to start before returning
	this.wgStarted.Wait()

	return nil
}

// FIXME: The order of closing here causes panic sometimes. For example, if receiver
// calls this, and closes the buffers, somehow it causes buffer.go:476 to panid.
func (this *service) stop() {
	defer func() {
		// Let's recover from panic
		if r := recover(); r != nil {
			Log.Errorc(func() string { return fmt.Sprintf("(%s) Recovering from panic: %v", this.cid(), r) })
		}
	}()

	doit := atomic.CompareAndSwapInt64(&this.closed, 0, 1)
	if !doit {
		return
	}

	// Close quit channel, effectively telling all the goroutines it's time to quit
	if this.done != nil {
		Log.Debugc(func() string { return fmt.Sprintf("(%s) closing this.done", this.cid()) })
		close(this.done)
	}

	// Close the network connection
	if this.conn != nil {
		Log.Debugc(func() string { return fmt.Sprintf("(%s) closing this.conn", this.cid()) })
		this.conn.Close()
		ClientMapCleanProcessor <- this.sess.ID()
	}

	this.in.Close()
	this.out.Close()

	// Wait for all the goroutines to stop.
	this.wgStopped.Wait()

	Log.Debugc(func() string {
		return fmt.Sprintf("(%s) Received %d bytes in %d messages.", this.cid(), this.inStat.bytes, this.inStat.msgs)
	})
	Log.Debugc(func() string {
		return fmt.Sprintf("(%s) Sent %d bytes in %d messages.", this.cid(), this.outStat.bytes, this.outStat.msgs)
	})

	// Unsubscribe from all the topics for this client, only for the server side though
	if !this.client && this.sess != nil {
		topics, _, err := this.sess.Topics()
		if err != nil {
			Log.Errorc(func() string { return fmt.Sprintf("(%s/%d): %v", this.cid(), this.id, err) })
		} else {
			for _, t := range topics {
				if err := this.topicsMgr.Unsubscribe([]byte(t), &this.onpub); err != nil {
					Log.Errorc(func() string { return fmt.Sprintf("(%s): Error unsubscribing topic %q: %v", this.cid(), t, err) })
				}
			}
		}
	}

	// Publish will message if WillFlag is set. Server side only.
	if !this.client && this.sess.Cmsg.WillFlag() {
		Log.Infoc(func() string {
			return fmt.Sprintf("(%s) service/stop: connection unexpectedly closed. Sending Will.", this.cid())
		})
		this.onPublish(this.sess.Will)
	}

	// Remove the client topics manager
	if this.client {
		topics.Unregister(this.sess.ID())
	}

	// Remove the session from session store if it's suppose to be clean session
	if this.sess.Cmsg.CleanSession() && this.sessMgr != nil {
		this.sessMgr.Del(this.sess.ID())
	}

	this.conn = nil
	this.in = nil
	this.out = nil
}

func (this *service) publish(msg *message.PublishMessage, onComplete OnCompleteFunc) error {
	//Log.Debugc(func() string{ return fmt.Sprintf("service/publish: Publishing %s", msg)})
	//   Log.Errorc(func() string{ return fmt.Sprintf("msg is : %v", msg)})
	_, err := this.writeMessage(msg)
	if err != nil {
		return fmt.Errorf("(%s) Error sending %s message: %v", this.cid(), msg.Name(), err)
	}

	switch msg.QoS() {
	case message.QosAtMostOnce:
		if onComplete != nil {
			return onComplete(msg, nil, nil)
		}

		return nil

	case message.QosAtLeastOnce:
		return this.sess.Pub1ack.Wait(msg, onComplete)

	case message.QosExactlyOnce:
		return this.sess.Pub2out.Wait(msg, onComplete)
	}

	return nil
}

func (this *service) subscribe(msg *message.SubscribeMessage, onComplete OnCompleteFunc, onPublish OnPublishFunc) error {
	if onPublish == nil {
		return fmt.Errorf("onPublish function is nil. No need to subscribe.")
	}

	_, err := this.writeMessage(msg)
	if err != nil {
		return fmt.Errorf("(%s) Error sending %s message: %v", this.cid(), msg.Name(), err)
	}

	var onc OnCompleteFunc = func(msg, ack message.Message, err error) error {
		onComplete := onComplete
		onPublish := onPublish

		if err != nil {
			if onComplete != nil {
				return onComplete(msg, ack, err)
			}
			return err
		}

		sub, ok := msg.(*message.SubscribeMessage)
		if !ok {
			if onComplete != nil {
				return onComplete(msg, ack, fmt.Errorf("Invalid SubscribeMessage received"))
			}
			return nil
		}

		suback, ok := ack.(*message.SubackMessage)
		if !ok {
			if onComplete != nil {
				return onComplete(msg, ack, fmt.Errorf("Invalid SubackMessage received"))
			}
			return nil
		}

		if sub.PacketId() != suback.PacketId() {
			if onComplete != nil {
				return onComplete(msg, ack, fmt.Errorf("Sub and Suback packet ID not the same. %d != %d.", sub.PacketId(), suback.PacketId()))
			}
			return nil
		}

		retcodes := suback.ReturnCodes()
		topics := sub.Topics()

		if len(topics) != len(retcodes) {
			if onComplete != nil {
				return onComplete(msg, ack, fmt.Errorf("Incorrect number of return codes received. Expecting %d, got %d.", len(topics), len(retcodes)))
			}
			return nil
		}

		var err2 error = nil

		for i, t := range topics {
			c := retcodes[i]

			if c == message.QosFailure {
				err2 = fmt.Errorf("Failed to subscribe to '%s'\n%v", string(t), err2)
			} else {
				this.sess.AddTopic(string(t), c)
				_, err := this.topicsMgr.Subscribe(t, c, &onPublish, this.sess.ID())
				if err != nil {
					err2 = fmt.Errorf("Failed to subscribe to '%s' (%v)\n%v", string(t), err, err2)
				}
			}
		}

		if onComplete != nil {
			return onComplete(msg, ack, err2)
		}

		return err2
	}

	return this.sess.Suback.Wait(msg, onc)
}

func (this *service) unsubscribe(msg *message.UnsubscribeMessage, onComplete OnCompleteFunc) error {
	_, err := this.writeMessage(msg)
	if err != nil {
		return fmt.Errorf("(%s) Error sending %s message: %v", this.cid(), msg.Name(), err)
	}

	var onc OnCompleteFunc = func(msg, ack message.Message, err error) error {
		onComplete := onComplete

		if err != nil {
			if onComplete != nil {
				return onComplete(msg, ack, err)
			}
			return err
		}

		unsub, ok := msg.(*message.UnsubscribeMessage)
		if !ok {
			if onComplete != nil {
				return onComplete(msg, ack, fmt.Errorf("Invalid UnsubscribeMessage received"))
			}
			return nil
		}

		unsuback, ok := ack.(*message.UnsubackMessage)
		if !ok {
			if onComplete != nil {
				return onComplete(msg, ack, fmt.Errorf("Invalid UnsubackMessage received"))
			}
			return nil
		}

		if unsub.PacketId() != unsuback.PacketId() {
			if onComplete != nil {
				return onComplete(msg, ack, fmt.Errorf("Unsub and Unsuback packet ID not the same. %d != %d.", unsub.PacketId(), unsuback.PacketId()))
			}
			return nil
		}

		var err2 error = nil

		for _, tb := range unsub.Topics() {
			// Remove all subscribers, which basically it's just this client, since
			// each client has it's own topic tree.
			err := this.topicsMgr.Unsubscribe(tb, nil)
			if err != nil {
				err2 = fmt.Errorf("%v\n%v", err2, err)
			}

			this.sess.RemoveTopic(string(tb))
		}

		if onComplete != nil {
			return onComplete(msg, ack, err2)
		}

		return err2
	}

	return this.sess.Unsuback.Wait(msg, onc)
}

func (this *service) ping(onComplete OnCompleteFunc) error {
	msg := message.NewPingreqMessage()

	_, err := this.writeMessage(msg)
	if err != nil {
		return fmt.Errorf("(%s) Error sending %s message: %v", this.cid(), msg.Name(), err)
	}

	return this.sess.Pingack.Wait(msg, onComplete)
}

func (this *service) isDone() bool {
	select {
	case <-this.done:
		return true

	default:
	}

	return false
}

func (this *service) cid() string {
	return fmt.Sprintf("%d/%s", this.id, this.sess.ID())
}
<commit_msg>修改 buffer.go process.go sendrecv.go xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 删除service.go中没用被使用到的代码<commit_after>// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package service

import (
	"fmt"
	"io"
	//   "runtime/debug"
	"strings"
	"sync"
	"sync/atomic"

	"github.com/nagae-memooff/surgemq/sessions"
	"github.com/nagae-memooff/surgemq/topics"
	"github.com/surgemq/message"
)

type (
	OnCompleteFunc func(msg, ack message.Message, err error) error
	OnPublishFunc  func(msg *message.PublishMessage) error
)

type stat struct {
	bytes int64
	msgs  int64
}

func (this *stat) increment(n int64) {
	atomic.AddInt64(&this.bytes, n)
	atomic.AddInt64(&this.msgs, 1)
}

var (
	gsvcid uint64 = 0
)

type service struct {
	// The ID of this service, it's not related to the Client ID, just a number that's
	// incremented for every new service.
	id uint64

	// Is this a client or server. It's set by either Connect (client) or
	// HandleConnection (server).
	client bool

	// The number of seconds to keep the connection live if there's no data.
	// If not set then default to 5 mins.
	keepAlive int

	// The number of seconds to wait for the CONNACK message before disconnecting.
	// If not set then default to 2 seconds.
	connectTimeout int

	// The number of seconds to wait for any ACK messages before failing.
	// If not set then default to 20 seconds.
	ackTimeout int

	// The number of times to retry sending a packet if ACK is not received.
	// If no set then default to 3 retries.
	timeoutRetries int

	// Network connection for this service
	conn io.Closer

	// Session manager for tracking all the clients
	sessMgr *sessions.Manager

	// Topics manager for all the client subscriptions
	topicsMgr *topics.Manager

	// sess is the session object for this MQTT session. It keeps track session variables
	// such as ClientId, KeepAlive, Username, etc
	sess *sessions.Session

	// Wait for the various goroutines to finish starting and stopping
	wgStarted sync.WaitGroup
	wgStopped sync.WaitGroup

	// writeMessage mutex - serializes writes to the outgoing buffer.
	wmu sync.Mutex
	rmu sync.Mutex

	// Whether this is service is closed or not.
	closed int64

	// Quit signal for determining when this service should end. If channel is closed,
	// then exit.
	done chan struct{}

	// Incoming data buffer. Bytes are read from the connection and put in here.
	in *buffer

	// Outgoing data buffer. Bytes written here are in turn written out to the connection.
	out *buffer

	// onpub is the method that gets added to the topic subscribers list by the
	// processSubscribe() method. When the server finishes the ack cycle for a
	// PUBLISH message, it will call the subscriber, which is this method.
	//
	// For the server, when this method is called, it means there's a message that
	// should be published to the client on the other end of this connection. So we
	// will call publish() to send the message.
	onpub OnPublishFunc

	inStat  stat
	outStat stat

	intmp  []byte
	outtmp []byte

	//   subs  []interface{}
	//   qoss  []byte
	//   rmsgs []*message.PublishMessage
}

func (this *service) start(client_id string) error {
	var err error

	//   debug.PrintStack()
	// Create the incoming ring buffer
	Log.Debugc(func() string { return fmt.Sprintf("make new buffer for client: %s", client_id) })

	if strings.Contains(client_id, "master") {
		this.in, err = newBuffer(MasterInBufferSize)
		if err != nil {
			return err
		}

		// Create the outgoing ring buffer
		this.out, err = newBuffer(MasterOutBufferSize)
		if err != nil {
			return err
		}
	} else {
		this.in, err = newBuffer(DeviceInBufferSize)
		if err != nil {
			return err
		}

		// Create the outgoing ring buffer
		this.out, err = newBuffer(DeviceOutBufferSize)
		if err != nil {
			return err
		}
	}

	// If this is a server
	if !this.client {
		// Creat the onPublishFunc so it can be used for published messages
		this.onpub = func(msg *message.PublishMessage) error {
			if err := this.publish(msg, nil); err != nil {
				Log.Errorc(func() string { return fmt.Sprintf("service/onPublish: Error publishing message: %v", err) })
				return err
			}

			return nil
		}

		// If this is a recovered session, then add any topics it subscribed before
		topics, qoss, err := this.sess.Topics()
		if err != nil {
			return err
		} else {
			for i, t := range topics {
				this.topicsMgr.Subscribe([]byte(t), qoss[i], &this.onpub, this.sess.ID())
			}
		}
	}

	// Processor is responsible for reading messages out of the buffer and processing
	// them accordingly.
	this.wgStarted.Add(1)
	this.wgStopped.Add(1)
	go this.processor()

	// Receiver is responsible for reading from the connection and putting data into
	// a buffer.
	this.wgStarted.Add(1)
	this.wgStopped.Add(1)
	go this.receiver()

	// Sender is responsible for writing data in the buffer into the connection.
	this.wgStarted.Add(1)
	this.wgStopped.Add(1)
	go this.sender()

	// Wait for all the goroutines to start before returning
	this.wgStarted.Wait()

	return nil
}

// FIXME: The order of closing here causes panic sometimes. For example, if receiver
// calls this, and closes the buffers, somehow it causes buffer.go:476 to panid.
func (this *service) stop() {
	defer func() {
		// Let's recover from panic
		if r := recover(); r != nil {
			Log.Errorc(func() string { return fmt.Sprintf("(%s) Recovering from panic: %v", this.cid(), r) })
		}
	}()

	doit := atomic.CompareAndSwapInt64(&this.closed, 0, 1)
	if !doit {
		return
	}

	// Close quit channel, effectively telling all the goroutines it's time to quit
	if this.done != nil {
		Log.Debugc(func() string { return fmt.Sprintf("(%s) closing this.done", this.cid()) })
		close(this.done)
	}

	// Close the network connection
	if this.conn != nil {
		Log.Debugc(func() string { return fmt.Sprintf("(%s) closing this.conn", this.cid()) })
		this.conn.Close()
		ClientMapCleanProcessor <- this.sess.ID()
	}

	this.in.Close()
	this.out.Close()

	// Wait for all the goroutines to stop.
	this.wgStopped.Wait()

	Log.Debugc(func() string {
		return fmt.Sprintf("(%s) Received %d bytes in %d messages.", this.cid(), this.inStat.bytes, this.inStat.msgs)
	})
	Log.Debugc(func() string {
		return fmt.Sprintf("(%s) Sent %d bytes in %d messages.", this.cid(), this.outStat.bytes, this.outStat.msgs)
	})

	// Unsubscribe from all the topics for this client, only for the server side though
	if !this.client && this.sess != nil {
		topics, _, err := this.sess.Topics()
		if err != nil {
			Log.Errorc(func() string { return fmt.Sprintf("(%s/%d): %v", this.cid(), this.id, err) })
		} else {
			for _, t := range topics {
				if err := this.topicsMgr.Unsubscribe([]byte(t), &this.onpub); err != nil {
					Log.Errorc(func() string { return fmt.Sprintf("(%s): Error unsubscribing topic %q: %v", this.cid(), t, err) })
				}
			}
		}
	}

	// Publish will message if WillFlag is set. Server side only.
	if !this.client && this.sess.Cmsg.WillFlag() {
		Log.Infoc(func() string {
			return fmt.Sprintf("(%s) service/stop: connection unexpectedly closed. Sending Will.", this.cid())
		})
		this.onPublish(this.sess.Will)
	}

	// Remove the client topics manager
	if this.client {
		topics.Unregister(this.sess.ID())
	}

	// Remove the session from session store if it's suppose to be clean session
	if this.sess.Cmsg.CleanSession() && this.sessMgr != nil {
		this.sessMgr.Del(this.sess.ID())
	}

	this.conn = nil
	this.in = nil
	this.out = nil
}

func (this *service) publish(msg *message.PublishMessage, onComplete OnCompleteFunc) error {
	//Log.Debugc(func() string{ return fmt.Sprintf("service/publish: Publishing %s", msg)})
	//   Log.Errorc(func() string{ return fmt.Sprintf("msg is : %v", msg)})
	_, err := this.writeMessage(msg)
	if err != nil {
		return fmt.Errorf("(%s) Error sending %s message: %v", this.cid(), msg.Name(), err)
	}

	switch msg.QoS() {
	case message.QosAtMostOnce:
		if onComplete != nil {
			return onComplete(msg, nil, nil)
		}

		return nil

	case message.QosAtLeastOnce:
		return this.sess.Pub1ack.Wait(msg, onComplete)

	case message.QosExactlyOnce:
		return this.sess.Pub2out.Wait(msg, onComplete)
	}

	return nil
}

func (this *service) subscribe(msg *message.SubscribeMessage, onComplete OnCompleteFunc, onPublish OnPublishFunc) error {
	if onPublish == nil {
		return fmt.Errorf("onPublish function is nil. No need to subscribe.")
	}

	_, err := this.writeMessage(msg)
	if err != nil {
		return fmt.Errorf("(%s) Error sending %s message: %v", this.cid(), msg.Name(), err)
	}

	var onc OnCompleteFunc = func(msg, ack message.Message, err error) error {
		onComplete := onComplete
		onPublish := onPublish

		if err != nil {
			if onComplete != nil {
				return onComplete(msg, ack, err)
			}
			return err
		}

		sub, ok := msg.(*message.SubscribeMessage)
		if !ok {
			if onComplete != nil {
				return onComplete(msg, ack, fmt.Errorf("Invalid SubscribeMessage received"))
			}
			return nil
		}

		suback, ok := ack.(*message.SubackMessage)
		if !ok {
			if onComplete != nil {
				return onComplete(msg, ack, fmt.Errorf("Invalid SubackMessage received"))
			}
			return nil
		}

		if sub.PacketId() != suback.PacketId() {
			if onComplete != nil {
				return onComplete(msg, ack, fmt.Errorf("Sub and Suback packet ID not the same. %d != %d.", sub.PacketId(), suback.PacketId()))
			}
			return nil
		}

		retcodes := suback.ReturnCodes()
		topics := sub.Topics()

		if len(topics) != len(retcodes) {
			if onComplete != nil {
				return onComplete(msg, ack, fmt.Errorf("Incorrect number of return codes received. Expecting %d, got %d.", len(topics), len(retcodes)))
			}
			return nil
		}

		var err2 error = nil

		for i, t := range topics {
			c := retcodes[i]

			if c == message.QosFailure {
				err2 = fmt.Errorf("Failed to subscribe to '%s'\n%v", string(t), err2)
			} else {
				this.sess.AddTopic(string(t), c)
				_, err := this.topicsMgr.Subscribe(t, c, &onPublish, this.sess.ID())
				if err != nil {
					err2 = fmt.Errorf("Failed to subscribe to '%s' (%v)\n%v", string(t), err, err2)
				}
			}
		}

		if onComplete != nil {
			return onComplete(msg, ack, err2)
		}

		return err2
	}

	return this.sess.Suback.Wait(msg, onc)
}

func (this *service) unsubscribe(msg *message.UnsubscribeMessage, onComplete OnCompleteFunc) error {
	_, err := this.writeMessage(msg)
	if err != nil {
		return fmt.Errorf("(%s) Error sending %s message: %v", this.cid(), msg.Name(), err)
	}

	var onc OnCompleteFunc = func(msg, ack message.Message, err error) error {
		onComplete := onComplete

		if err != nil {
			if onComplete != nil {
				return onComplete(msg, ack, err)
			}
			return err
		}

		unsub, ok := msg.(*message.UnsubscribeMessage)
		if !ok {
			if onComplete != nil {
				return onComplete(msg, ack, fmt.Errorf("Invalid UnsubscribeMessage received"))
			}
			return nil
		}

		unsuback, ok := ack.(*message.UnsubackMessage)
		if !ok {
			if onComplete != nil {
				return onComplete(msg, ack, fmt.Errorf("Invalid UnsubackMessage received"))
			}
			return nil
		}

		if unsub.PacketId() != unsuback.PacketId() {
			if onComplete != nil {
				return onComplete(msg, ack, fmt.Errorf("Unsub and Unsuback packet ID not the same. %d != %d.", unsub.PacketId(), unsuback.PacketId()))
			}
			return nil
		}

		var err2 error = nil

		for _, tb := range unsub.Topics() {
			// Remove all subscribers, which basically it's just this client, since
			// each client has it's own topic tree.
			err := this.topicsMgr.Unsubscribe(tb, nil)
			if err != nil {
				err2 = fmt.Errorf("%v\n%v", err2, err)
			}

			this.sess.RemoveTopic(string(tb))
		}

		if onComplete != nil {
			return onComplete(msg, ack, err2)
		}

		return err2
	}

	return this.sess.Unsuback.Wait(msg, onc)
}

func (this *service) ping(onComplete OnCompleteFunc) error {
	msg := message.NewPingreqMessage()

	_, err := this.writeMessage(msg)
	if err != nil {
		return fmt.Errorf("(%s) Error sending %s message: %v", this.cid(), msg.Name(), err)
	}

	return this.sess.Pingack.Wait(msg, onComplete)
}

func (this *service) isDone() bool {
	select {
	case <-this.done:
		return true

	default:
	}

	return false
}

func (this *service) cid() string {
	return fmt.Sprintf("%d/%s", this.id, this.sess.ID())
}
<|endoftext|>


<commit_before>package service

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log/syslog"
	"net"
	"net/http"
	"os"
	"os/exec"
	"os/signal"
	"syscall"
	"time"

	log "github.com/Sirupsen/logrus"
	logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
	logrus_logstash "github.com/bshuster-repo/logrus-logstash-hook"
	etcd "github.com/coreos/etcd/client"
	"github.com/mailgun/manners"
	"github.com/mailgun/metrics"
	"github.com/mailgun/scroll"
	"github.com/vulcand/vulcand/api"
	"github.com/vulcand/vulcand/engine"
	"github.com/vulcand/vulcand/engine/etcdv2ng"
	"github.com/vulcand/vulcand/plugin"
	"github.com/vulcand/vulcand/proxy"
	"github.com/vulcand/vulcand/secret"
	"github.com/vulcand/vulcand/stapler"
	"github.com/vulcand/vulcand/supervisor"
)

func Run(registry *plugin.Registry) error {
	options, err := ParseCommandLine()
	if err != nil {
		return fmt.Errorf("failed to parse command line: %s", err)
	}
	service := NewService(options, registry)
	if err := service.Start(); err != nil {
		log.Errorf("Failed to start service: %v", err)
		return fmt.Errorf("service start failure: %s", err)
	} else {
		log.Infof("Service exited gracefully")
	}
	return nil
}

type Service struct {
	client        etcd.Client
	options       Options
	registry      *plugin.Registry
	apiApp        *scroll.App
	errorC        chan error
	sigC          chan os.Signal
	supervisor    *supervisor.Supervisor
	metricsClient metrics.Client
	apiServer     *manners.GracefulServer
	ng            engine.Engine
	stapler       stapler.Stapler
}

func NewService(options Options, registry *plugin.Registry) *Service {
	return &Service{
		registry: registry,
		options:  options,
		errorC:   make(chan error),
		// Channel receiving signals has to be non blocking, otherwise the service can miss a signal.
		sigC: make(chan os.Signal, 1024),
	}
}

func (s *Service) Start() error {
	// if .LogFormatter is set, it'll be used in log.SetFormatter() and .Log will be ignored.
	if s.options.LogFormatter != nil {
		log.SetFormatter(s.options.LogFormatter)
	} else {
		switch s.options.Log {
			case "console": {
				log.SetFormatter(&log.TextFormatter{})
			}
			case "json": {
				log.SetFormatter(&log.JSONFormatter{})
			}
			case "syslog": {
				hook, err := logrus_syslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
				if err != nil {
					return err
				}
				log.SetFormatter(&log.TextFormatter{DisableColors: true})
				log.AddHook(hook)
			}
			case "logstash": {
				log.SetFormatter(&logrus_logstash.LogstashFormatter{Type: "logs"})
			}
			default:
				log.Warnf("Invalid logger type %v, fallback to default.", s.options.Log)
				log.SetFormatter(&log.TextFormatter{})
		}
	}
	log.SetOutput(os.Stdout)
	log.SetLevel(s.options.LogSeverity.S)

	log.Infof("Service starts with options: %#v", s.options)

	if s.options.PidPath != "" {
		ioutil.WriteFile(s.options.PidPath, []byte(fmt.Sprint(os.Getpid())), 0644)
	}

	if s.options.MetricsClient != nil {
		s.metricsClient = s.options.MetricsClient
	} else if s.options.StatsdAddr != "" {
		var err error
		s.metricsClient, err = metrics.NewWithOptions(s.options.StatsdAddr, s.options.StatsdPrefix, metrics.Options{UseBuffering: true})
		if err != nil {
			return err
		}
	}

	apiFile, muxFiles, err := s.getFiles()
	if err != nil {
		return err
	}

	if err := s.newEngine(); err != nil {
		return err
	}

	s.stapler = stapler.New()
	s.supervisor = supervisor.New(
		s.newProxy, s.ng, s.errorC, supervisor.Options{Files: muxFiles})

	// Tells configurator to perform initial proxy configuration and start watching changes
	if err := s.supervisor.Start(); err != nil {
		return err
	}

	if err := s.initApi(); err != nil {
		return err
	}

	go func() {
		s.errorC <- s.startApi(apiFile)
	}()

	if s.metricsClient != nil {
		go s.reportSystemMetrics()
	}
	signal.Notify(s.sigC, os.Interrupt, os.Kill, syscall.SIGTERM, syscall.SIGUSR2, syscall.SIGCHLD)

	// Block until a signal is received or we got an error
	for {
		select {
		case signal := <-s.sigC:
			switch signal {
			case syscall.SIGTERM, syscall.SIGINT:
				log.Infof("Got signal '%s', shutting down gracefully", signal)
				s.supervisor.Stop(true)
				log.Infof("All servers stopped")
				return nil
			case syscall.SIGKILL:
				log.Infof("Got signal '%s', exiting now without waiting", signal)
				s.supervisor.Stop(false)
				return nil
			case syscall.SIGUSR2:
				log.Infof("Got signal '%s', forking a new self", signal)
				if err := s.startChild(); err != nil {
					log.Infof("Failed to start self: %s", err)
				} else {
					log.Infof("Successfully started self")
				}
			case syscall.SIGCHLD:
				log.Warningf("Child exited, got '%s', collecting status", signal)
				var wait syscall.WaitStatus
				syscall.Wait4(-1, &wait, syscall.WNOHANG, nil)
				log.Warningf("Collected exit status from child")
			default:
				log.Infof("Ignoring '%s'", signal)
			}
		case err := <-s.errorC:
			log.Infof("Got request to shutdown with error: %s", err)
			return err
		}
	}
}

func (s *Service) getFiles() (*proxy.FileDescriptor, []*proxy.FileDescriptor, error) {
	// These files may be passed in by the parent process
	filesString := os.Getenv(vulcandFilesKey)
	if filesString == "" {
		return nil, nil, nil
	}

	files, err := filesFromString(filesString)
	if err != nil {
		return nil, nil, fmt.Errorf("child failed to start: failed to read files from string, error %s", err)
	}

	if len(files) != 0 {
		log.Infof("I am a child that has been passed files: %s", files)
	}

	return s.splitFiles(files)
}

func (s *Service) splitFiles(files []*proxy.FileDescriptor) (*proxy.FileDescriptor, []*proxy.FileDescriptor, error) {
	apiAddr := fmt.Sprintf("%s:%d", s.options.ApiInterface, s.options.ApiPort)
	for i, f := range files {
		if f.Address.Address == apiAddr {
			return files[i], append(files[:i], files[i+1:]...), nil
		}
	}
	return nil, nil, fmt.Errorf("API address %s not found in %s", apiAddr, files)
}

func (s *Service) startChild() error {
	log.Infof("Starting child")
	path, err := execPath()
	if err != nil {
		return err
	}

	wd, err := os.Getwd()
	if nil != err {
		return err
	}

	// Get socket files currently in use by the underlying http server controlled by supervisor
	extraFiles, err := s.supervisor.GetFiles()
	if err != nil {
		return err
	}

	apiFile, err := s.GetAPIFile()
	if err != nil {
		return err
	}

	extraFiles = append(extraFiles, apiFile)

	// These files will be passed to the child process
	files := []*os.File{os.Stdin, os.Stdout, os.Stderr}
	for _, f := range extraFiles {
		files = append(files, f.File)
	}

	// Serialize files to JSON string representation
	vals, err := filesToString(extraFiles)
	if err != nil {
		return err
	}

	log.Infof("Passing %s to child", vals)
	os.Setenv(vulcandFilesKey, vals)

	p, err := os.StartProcess(path, os.Args, &os.ProcAttr{
		Dir:   wd,
		Env:   os.Environ(),
		Files: files,
		Sys:   &syscall.SysProcAttr{},
	})

	if err != nil {
		return err
	}

	log.Infof("Started new child pid=%d binary=%s", p.Pid, path)
	return nil
}

func (s *Service) GetAPIFile() (*proxy.FileDescriptor, error) {
	file, err := s.apiServer.GetFile()
	if err != nil {
		return nil, err
	}
	a := engine.Address{
		Network: "tcp",
		Address: fmt.Sprintf("%s:%d", s.options.ApiInterface, s.options.ApiPort),
	}
	return &proxy.FileDescriptor{File: file, Address: a}, nil
}

func (s *Service) newBox() (*secret.Box, error) {
	if s.options.SealKey == "" {
		return nil, nil
	}
	key, err := secret.KeyFromString(s.options.SealKey)
	if err != nil {
		return nil, err
	}
	return secret.NewBox(key)
}

func (s *Service) newEngine() error {
	box, err := s.newBox()
	if err != nil {
		return err
	}
	ng, err := etcdv2ng.New(
		s.options.EtcdNodes,
		s.options.EtcdKey,
		s.registry,
		etcdv2ng.Options{
			EtcdCaFile:              s.options.EtcdCaFile,
			EtcdCertFile:            s.options.EtcdCertFile,
			EtcdKeyFile:             s.options.EtcdKeyFile,
			EtcdConsistency:         s.options.EtcdConsistency,
			EtcdSyncIntervalSeconds: s.options.EtcdSyncIntervalSeconds,
			Box: box,
		})
	if err != nil {
		return err
	}
	s.ng = ng
	return err
}

func (s *Service) reportSystemMetrics() {
	defer func() {
		if r := recover(); r != nil {
			log.Infof("Recovered in reportSystemMetrics", r)
		}
	}()
	for {
		s.metricsClient.ReportRuntimeMetrics("sys", 1.0)
		// we have 256 time buckets for gc stats, GC is being executed every 4ms on average
		// so we have 256 * 4 = 1024 around one second to report it. To play safe, let's report every 300ms
		time.Sleep(300 * time.Millisecond)
	}
}

func (s *Service) newProxy(id int) (proxy.Proxy, error) {
	return proxy.New(id, s.stapler, proxy.Options{
		MetricsClient:      s.metricsClient,
		DialTimeout:        s.options.EndpointDialTimeout,
		ReadTimeout:        s.options.ServerReadTimeout,
		WriteTimeout:       s.options.ServerWriteTimeout,
		MaxHeaderBytes:     s.options.ServerMaxHeaderBytes,
		DefaultListener:    constructDefaultListener(s.options),
		NotFoundMiddleware: s.registry.GetNotFoundMiddleware(),
		Router:             s.registry.GetRouter(),
		IncomingConnectionTracker: s.registry.GetIncomingConnectionTracker(),
		OutgoingConnectionTracker: s.registry.GetOutgoingConnectionTracker(),
	})
}

func (s *Service) initApi() error {
	s.apiApp = scroll.NewApp()
	api.InitProxyController(s.ng, s.supervisor, s.apiApp)
	return nil
}

func (s *Service) startApi(file *proxy.FileDescriptor) error {
	addr := fmt.Sprintf("%s:%d", s.options.ApiInterface, s.options.ApiPort)

	server := &http.Server{
		Addr:           addr,
		Handler:        s.apiApp.GetHandler(),
		ReadTimeout:    s.options.ServerReadTimeout,
		WriteTimeout:   s.options.ServerWriteTimeout,
		MaxHeaderBytes: 1 << 20,
	}

	var listener net.Listener
	if file != nil {
		var err error
		listener, err = file.ToListener()
		if err != nil {
			return err
		}
	}

	s.apiServer = manners.NewWithOptions(manners.Options{Server: server, Listener: listener})
	return s.apiServer.ListenAndServe()
}

func constructDefaultListener(options Options) *engine.Listener {
	if options.DefaultListener {
		return &engine.Listener{
			Id:       "DefaultListener",
			Protocol: "http",
			Address: engine.Address{
				Network: "tcp",
				Address: fmt.Sprintf("%s:%d", options.Interface, options.Port),
			},
		}
	}
	return nil
}

func execPath() (string, error) {
	name, err := exec.LookPath(os.Args[0])
	if err != nil {
		return "", err
	}
	if _, err = os.Stat(name); nil != err {
		return "", err
	}
	return name, err
}

type fileDescriptor struct {
	Address  engine.Address
	FileFD   int
	FileName string
}

// filesToString serializes file descriptors as well as accompanying information (like socket host and port)
func filesToString(files []*proxy.FileDescriptor) (string, error) {
	out := make([]fileDescriptor, len(files))
	for i, f := range files {
		out[i] = fileDescriptor{
			// Once files will be passed to the child process and their FDs will change.
			// The first three passed files are stdin, stdout and stderr, every next file will have the index + 3
			// That's why we rearrange the FDs for child processes to get the correct file descriptors.
			FileFD:   i + 3,
			FileName: f.File.Name(),
			Address:  f.Address,
		}
	}
	bytes, err := json.Marshal(out)
	if err != nil {
		return "", err
	}
	return string(bytes), nil
}

// filesFromString de-serializes the file descriptors and turns them in the os.Files
func filesFromString(in string) ([]*proxy.FileDescriptor, error) {
	var out []fileDescriptor
	if err := json.Unmarshal([]byte(in), &out); err != nil {
		return nil, err
	}
	files := make([]*proxy.FileDescriptor, len(out))
	for i, o := range out {
		files[i] = &proxy.FileDescriptor{
			File:    os.NewFile(uintptr(o.FileFD), o.FileName),
			Address: o.Address,
		}
	}
	return files, nil
}

const vulcandFilesKey = "VULCAND_FILES_KEY"
<commit_msg>Make sure fallback LogFormatter kicks in<commit_after>package service

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log/syslog"
	"net"
	"net/http"
	"os"
	"os/exec"
	"os/signal"
	"syscall"
	"time"

	log "github.com/Sirupsen/logrus"
	logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
	logrus_logstash "github.com/bshuster-repo/logrus-logstash-hook"
	etcd "github.com/coreos/etcd/client"
	"github.com/mailgun/manners"
	"github.com/mailgun/metrics"
	"github.com/mailgun/scroll"
	"github.com/vulcand/vulcand/api"
	"github.com/vulcand/vulcand/engine"
	"github.com/vulcand/vulcand/engine/etcdv2ng"
	"github.com/vulcand/vulcand/plugin"
	"github.com/vulcand/vulcand/proxy"
	"github.com/vulcand/vulcand/secret"
	"github.com/vulcand/vulcand/stapler"
	"github.com/vulcand/vulcand/supervisor"
)

func Run(registry *plugin.Registry) error {
	options, err := ParseCommandLine()
	if err != nil {
		return fmt.Errorf("failed to parse command line: %s", err)
	}
	service := NewService(options, registry)
	if err := service.Start(); err != nil {
		log.Errorf("Failed to start service: %v", err)
		return fmt.Errorf("service start failure: %s", err)
	} else {
		log.Infof("Service exited gracefully")
	}
	return nil
}

type Service struct {
	client        etcd.Client
	options       Options
	registry      *plugin.Registry
	apiApp        *scroll.App
	errorC        chan error
	sigC          chan os.Signal
	supervisor    *supervisor.Supervisor
	metricsClient metrics.Client
	apiServer     *manners.GracefulServer
	ng            engine.Engine
	stapler       stapler.Stapler
}

func NewService(options Options, registry *plugin.Registry) *Service {
	return &Service{
		registry: registry,
		options:  options,
		errorC:   make(chan error),
		// Channel receiving signals has to be non blocking, otherwise the service can miss a signal.
		sigC: make(chan os.Signal, 1024),
	}
}

func (s *Service) Start() error {
	// if .LogFormatter is set, it'll be used in log.SetFormatter() and .Log will be ignored.
	if s.options.LogFormatter != nil {
		log.SetFormatter(s.options.LogFormatter)
	} else {
		switch s.options.Log {
			case "console": {
				log.SetFormatter(&log.TextFormatter{})
			}
			case "json": {
				log.SetFormatter(&log.JSONFormatter{})
			}
			case "syslog": {
				hook, err := logrus_syslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
				if err == nil {
					log.SetFormatter(&log.TextFormatter{DisableColors: true})
					log.AddHook(hook)
				} else {
					setFallbackLogFormatter(s.options)
				}
			}
			case "logstash": {
				log.SetFormatter(&logrus_logstash.LogstashFormatter{Type: "logs"})
			}
			default:
				setFallbackLogFormatter(s.options)
		}
	}
	log.SetOutput(os.Stdout)
	log.SetLevel(s.options.LogSeverity.S)

	log.Infof("Service starts with options: %#v", s.options)

	if s.options.PidPath != "" {
		ioutil.WriteFile(s.options.PidPath, []byte(fmt.Sprint(os.Getpid())), 0644)
	}

	if s.options.MetricsClient != nil {
		s.metricsClient = s.options.MetricsClient
	} else if s.options.StatsdAddr != "" {
		var err error
		s.metricsClient, err = metrics.NewWithOptions(s.options.StatsdAddr, s.options.StatsdPrefix, metrics.Options{UseBuffering: true})
		if err != nil {
			return err
		}
	}

	apiFile, muxFiles, err := s.getFiles()
	if err != nil {
		return err
	}

	if err := s.newEngine(); err != nil {
		return err
	}

	s.stapler = stapler.New()
	s.supervisor = supervisor.New(
		s.newProxy, s.ng, s.errorC, supervisor.Options{Files: muxFiles})

	// Tells configurator to perform initial proxy configuration and start watching changes
	if err := s.supervisor.Start(); err != nil {
		return err
	}

	if err := s.initApi(); err != nil {
		return err
	}

	go func() {
		s.errorC <- s.startApi(apiFile)
	}()

	if s.metricsClient != nil {
		go s.reportSystemMetrics()
	}
	signal.Notify(s.sigC, os.Interrupt, os.Kill, syscall.SIGTERM, syscall.SIGUSR2, syscall.SIGCHLD)

	// Block until a signal is received or we got an error
	for {
		select {
		case signal := <-s.sigC:
			switch signal {
			case syscall.SIGTERM, syscall.SIGINT:
				log.Infof("Got signal '%s', shutting down gracefully", signal)
				s.supervisor.Stop(true)
				log.Infof("All servers stopped")
				return nil
			case syscall.SIGKILL:
				log.Infof("Got signal '%s', exiting now without waiting", signal)
				s.supervisor.Stop(false)
				return nil
			case syscall.SIGUSR2:
				log.Infof("Got signal '%s', forking a new self", signal)
				if err := s.startChild(); err != nil {
					log.Infof("Failed to start self: %s", err)
				} else {
					log.Infof("Successfully started self")
				}
			case syscall.SIGCHLD:
				log.Warningf("Child exited, got '%s', collecting status", signal)
				var wait syscall.WaitStatus
				syscall.Wait4(-1, &wait, syscall.WNOHANG, nil)
				log.Warningf("Collected exit status from child")
			default:
				log.Infof("Ignoring '%s'", signal)
			}
		case err := <-s.errorC:
			log.Infof("Got request to shutdown with error: %s", err)
			return err
		}
	}
}

func (s *Service) getFiles() (*proxy.FileDescriptor, []*proxy.FileDescriptor, error) {
	// These files may be passed in by the parent process
	filesString := os.Getenv(vulcandFilesKey)
	if filesString == "" {
		return nil, nil, nil
	}

	files, err := filesFromString(filesString)
	if err != nil {
		return nil, nil, fmt.Errorf("child failed to start: failed to read files from string, error %s", err)
	}

	if len(files) != 0 {
		log.Infof("I am a child that has been passed files: %s", files)
	}

	return s.splitFiles(files)
}

func (s *Service) splitFiles(files []*proxy.FileDescriptor) (*proxy.FileDescriptor, []*proxy.FileDescriptor, error) {
	apiAddr := fmt.Sprintf("%s:%d", s.options.ApiInterface, s.options.ApiPort)
	for i, f := range files {
		if f.Address.Address == apiAddr {
			return files[i], append(files[:i], files[i+1:]...), nil
		}
	}
	return nil, nil, fmt.Errorf("API address %s not found in %s", apiAddr, files)
}

func (s *Service) startChild() error {
	log.Infof("Starting child")
	path, err := execPath()
	if err != nil {
		return err
	}

	wd, err := os.Getwd()
	if nil != err {
		return err
	}

	// Get socket files currently in use by the underlying http server controlled by supervisor
	extraFiles, err := s.supervisor.GetFiles()
	if err != nil {
		return err
	}

	apiFile, err := s.GetAPIFile()
	if err != nil {
		return err
	}

	extraFiles = append(extraFiles, apiFile)

	// These files will be passed to the child process
	files := []*os.File{os.Stdin, os.Stdout, os.Stderr}
	for _, f := range extraFiles {
		files = append(files, f.File)
	}

	// Serialize files to JSON string representation
	vals, err := filesToString(extraFiles)
	if err != nil {
		return err
	}

	log.Infof("Passing %s to child", vals)
	os.Setenv(vulcandFilesKey, vals)

	p, err := os.StartProcess(path, os.Args, &os.ProcAttr{
		Dir:   wd,
		Env:   os.Environ(),
		Files: files,
		Sys:   &syscall.SysProcAttr{},
	})

	if err != nil {
		return err
	}

	log.Infof("Started new child pid=%d binary=%s", p.Pid, path)
	return nil
}

func (s *Service) GetAPIFile() (*proxy.FileDescriptor, error) {
	file, err := s.apiServer.GetFile()
	if err != nil {
		return nil, err
	}
	a := engine.Address{
		Network: "tcp",
		Address: fmt.Sprintf("%s:%d", s.options.ApiInterface, s.options.ApiPort),
	}
	return &proxy.FileDescriptor{File: file, Address: a}, nil
}

func (s *Service) newBox() (*secret.Box, error) {
	if s.options.SealKey == "" {
		return nil, nil
	}
	key, err := secret.KeyFromString(s.options.SealKey)
	if err != nil {
		return nil, err
	}
	return secret.NewBox(key)
}

func (s *Service) newEngine() error {
	box, err := s.newBox()
	if err != nil {
		return err
	}
	ng, err := etcdv2ng.New(
		s.options.EtcdNodes,
		s.options.EtcdKey,
		s.registry,
		etcdv2ng.Options{
			EtcdCaFile:              s.options.EtcdCaFile,
			EtcdCertFile:            s.options.EtcdCertFile,
			EtcdKeyFile:             s.options.EtcdKeyFile,
			EtcdConsistency:         s.options.EtcdConsistency,
			EtcdSyncIntervalSeconds: s.options.EtcdSyncIntervalSeconds,
			Box: box,
		})
	if err != nil {
		return err
	}
	s.ng = ng
	return err
}

func (s *Service) reportSystemMetrics() {
	defer func() {
		if r := recover(); r != nil {
			log.Infof("Recovered in reportSystemMetrics", r)
		}
	}()
	for {
		s.metricsClient.ReportRuntimeMetrics("sys", 1.0)
		// we have 256 time buckets for gc stats, GC is being executed every 4ms on average
		// so we have 256 * 4 = 1024 around one second to report it. To play safe, let's report every 300ms
		time.Sleep(300 * time.Millisecond)
	}
}

func (s *Service) newProxy(id int) (proxy.Proxy, error) {
	return proxy.New(id, s.stapler, proxy.Options{
		MetricsClient:      s.metricsClient,
		DialTimeout:        s.options.EndpointDialTimeout,
		ReadTimeout:        s.options.ServerReadTimeout,
		WriteTimeout:       s.options.ServerWriteTimeout,
		MaxHeaderBytes:     s.options.ServerMaxHeaderBytes,
		DefaultListener:    constructDefaultListener(s.options),
		NotFoundMiddleware: s.registry.GetNotFoundMiddleware(),
		Router:             s.registry.GetRouter(),
		IncomingConnectionTracker: s.registry.GetIncomingConnectionTracker(),
		OutgoingConnectionTracker: s.registry.GetOutgoingConnectionTracker(),
	})
}

func (s *Service) initApi() error {
	s.apiApp = scroll.NewApp()
	api.InitProxyController(s.ng, s.supervisor, s.apiApp)
	return nil
}

func (s *Service) startApi(file *proxy.FileDescriptor) error {
	addr := fmt.Sprintf("%s:%d", s.options.ApiInterface, s.options.ApiPort)

	server := &http.Server{
		Addr:           addr,
		Handler:        s.apiApp.GetHandler(),
		ReadTimeout:    s.options.ServerReadTimeout,
		WriteTimeout:   s.options.ServerWriteTimeout,
		MaxHeaderBytes: 1 << 20,
	}

	var listener net.Listener
	if file != nil {
		var err error
		listener, err = file.ToListener()
		if err != nil {
			return err
		}
	}

	s.apiServer = manners.NewWithOptions(manners.Options{Server: server, Listener: listener})
	return s.apiServer.ListenAndServe()
}

func constructDefaultListener(options Options) *engine.Listener {
	if options.DefaultListener {
		return &engine.Listener{
			Id:       "DefaultListener",
			Protocol: "http",
			Address: engine.Address{
				Network: "tcp",
				Address: fmt.Sprintf("%s:%d", options.Interface, options.Port),
			},
		}
	}
	return nil
}

func execPath() (string, error) {
	name, err := exec.LookPath(os.Args[0])
	if err != nil {
		return "", err
	}
	if _, err = os.Stat(name); nil != err {
		return "", err
	}
	return name, err
}

type fileDescriptor struct {
	Address  engine.Address
	FileFD   int
	FileName string
}

// filesToString serializes file descriptors as well as accompanying information (like socket host and port)
func filesToString(files []*proxy.FileDescriptor) (string, error) {
	out := make([]fileDescriptor, len(files))
	for i, f := range files {
		out[i] = fileDescriptor{
			// Once files will be passed to the child process and their FDs will change.
			// The first three passed files are stdin, stdout and stderr, every next file will have the index + 3
			// That's why we rearrange the FDs for child processes to get the correct file descriptors.
			FileFD:   i + 3,
			FileName: f.File.Name(),
			Address:  f.Address,
		}
	}
	bytes, err := json.Marshal(out)
	if err != nil {
		return "", err
	}
	return string(bytes), nil
}

// filesFromString de-serializes the file descriptors and turns them in the os.Files
func filesFromString(in string) ([]*proxy.FileDescriptor, error) {
	var out []fileDescriptor
	if err := json.Unmarshal([]byte(in), &out); err != nil {
		return nil, err
	}
	files := make([]*proxy.FileDescriptor, len(out))
	for i, o := range out {
		files[i] = &proxy.FileDescriptor{
			File:    os.NewFile(uintptr(o.FileFD), o.FileName),
			Address: o.Address,
		}
	}
	return files, nil
}

func setFallbackLogFormatter(options Options) {
	log.Warnf("Invalid logger ty pe %v, and no LogFormatter %v, fallback to default.", options.Log, options.LogFormatter)
	log.SetFormatter(&log.TextFormatter{})
}

const vulcandFilesKey = "VULCAND_FILES_KEY"
<|endoftext|>


<commit_before>package main

// This is a simple quick script to take a goship config file and put into ETCD. Note: It does not wipe out your
// existing etcd setup.

import (
	"flag"
	"fmt"
	"log"
	"strings"

	"github.com/coreos/go-etcd/etcd"
	"github.com/gengo/goship/goship"
	"github.com/kylelemons/go-gypsy/yaml"
)

var (
	ConfigFile = flag.String("c", "config.yml", "Path to data directory (default config.yml)")
	ETCDServer = flag.String("e", "http://127.0.0.1:4001", "Etcd Server (default http://127.0.0.1:4001")
)

// getYAMLString is a helper function for extracting strings from a yaml.Node.
func getYAMLString(n yaml.Node, key string) string {
	s, ok := n.(yaml.Map)[key].(yaml.Scalar)
	if !ok {
		return ""
	}
	return strings.TrimSpace(s.String())
}

// Send etcd Data and output.
func setETCD(client *etcd.Client, full_key, value string) {
	log.Printf("Setting %s => %s \n", full_key, value)
	client.Create(full_key, value, 0)
}

// parseYAMLEnvironment populates an Environment given a yaml.Node and returns the Environment.
func YAMLtoETCDEnvironment(m yaml.Node, client *etcd.Client, projPath string) {

	for k, v := range m.(yaml.Map) {
		projPath = projPath + "environments/" + k + "/"

		log.Printf("Setting env name=> %s \n", projPath)
		client.CreateDir(projPath, 0)

		branch := getYAMLString(v, "branch")
		setETCD(client, projPath+"branch", branch)

		repoPath := getYAMLString(v, "repo_path")
		setETCD(client, projPath+"repo_path", repoPath)

		deploy := getYAMLString(v, "deploy")
		setETCD(client, projPath+"deploy", deploy)

		projPath = projPath + "hosts/"
		log.Printf("Creating Host Directory => %s \n", projPath+"hosts/")
		client.CreateDir(projPath, 0)

		for _, host := range v.(yaml.Map)["hosts"].(yaml.List) {
			h := goship.Host{URI: host.(yaml.Scalar).String()}
			log.Printf("Setting Hosts => %s \n", projPath+h.URI)
			client.CreateDir(projPath+h.URI, 0)
		}
	}
}

// parseYAML parses the config.yml file and returns the appropriate structs and strings.
func YAMLtoETCD(client *etcd.Client) (c goship.Config, err error) {
	config, err := yaml.ReadFile(*ConfigFile)
	if err != nil {
		return c, err
	}
	log.Printf("Setting project root => /projects")
	client.CreateDir("/projects", 0)
	configRoot, _ := config.Root.(yaml.Map)
	projects, _ := configRoot["projects"].(yaml.List)
	for _, p := range projects {
		for k, v := range p.(yaml.Map) {

			projectPath := "/projects/" + k + "/"

			log.Printf("Setting project => %s \n", projectPath)
			client.CreateDir(projectPath, 0)

			name := getYAMLString(v, "project_name")
			setETCD(client, projectPath+"project_name", name)

			repoOwner := getYAMLString(v, "repo_owner")
			setETCD(client, projectPath+"repo_owner", repoOwner)

			repoName := getYAMLString(v, "repo_name")
			setETCD(client, projectPath+"repo_name", repoName)

			for _, v := range v.(yaml.Map)["environments"].(yaml.List) {
				YAMLtoETCDEnvironment(v, client, projectPath)
			}

		}
	}

	piv_project, _ := config.Get("pivotal_project")
	setETCD(client, "pivotal_project", piv_project)

	piv_token, _ := config.Get("pivotal_token")
	setETCD(client, "pivotal_token", piv_token)

	deploy_user, _ := config.Get("deploy_user")
	setETCD(client, "deploy_user", deploy_user)

	goship_host, _ := config.Get("goship_host")
	setETCD(client, "goship_host", goship_host)

	notify, _ := config.Get("notify")
	setETCD(client, "notify", notify)

	return c, err
}

func main() {
	flag.Parse()
	log.Printf("Reading Config file: %s Connecting to ETCD server: %s", *ConfigFile, *ETCDServer)
	// Note the ETCD client library swallows errors connecting to etcd (worry)
	a := etcd.NewClient([]string{*ETCDServer})
	_, err := YAMLtoETCD(a)
	if err != nil {
		fmt.Printf("Failed to Parse Yaml and Add to ETCD [%s]\n", err)
	}
}
<commit_msg>Update lib folder<commit_after>package main

// This is a simple quick script to take a goship config file and put into ETCD. Note: It does not wipe out your
// existing etcd setup.

import (
	"flag"
	"fmt"
	"log"
	"strings"

	"github.com/coreos/go-etcd/etcd"
	"github.com/gengo/lib/goship"
	"github.com/kylelemons/go-gypsy/yaml"
)

var (
	ConfigFile = flag.String("c", "config.yml", "Path to data directory (default config.yml)")
	ETCDServer = flag.String("e", "http://127.0.0.1:4001", "Etcd Server (default http://127.0.0.1:4001")
)

// getYAMLString is a helper function for extracting strings from a yaml.Node.
func getYAMLString(n yaml.Node, key string) string {
	s, ok := n.(yaml.Map)[key].(yaml.Scalar)
	if !ok {
		return ""
	}
	return strings.TrimSpace(s.String())
}

// Send etcd Data and output.
func setETCD(client *etcd.Client, full_key, value string) {
	log.Printf("Setting %s => %s \n", full_key, value)
	client.Create(full_key, value, 0)
}

// parseYAMLEnvironment populates an Environment given a yaml.Node and returns the Environment.
func YAMLtoETCDEnvironment(m yaml.Node, client *etcd.Client, projPath string) {

	for k, v := range m.(yaml.Map) {
		projPath = projPath + "environments/" + k + "/"

		log.Printf("Setting env name=> %s \n", projPath)
		client.CreateDir(projPath, 0)

		branch := getYAMLString(v, "branch")
		setETCD(client, projPath+"branch", branch)

		repoPath := getYAMLString(v, "repo_path")
		setETCD(client, projPath+"repo_path", repoPath)

		deploy := getYAMLString(v, "deploy")
		setETCD(client, projPath+"deploy", deploy)

		projPath = projPath + "hosts/"
		log.Printf("Creating Host Directory => %s \n", projPath+"hosts/")
		client.CreateDir(projPath, 0)

		for _, host := range v.(yaml.Map)["hosts"].(yaml.List) {
			h := goship.Host{URI: host.(yaml.Scalar).String()}
			log.Printf("Setting Hosts => %s \n", projPath+h.URI)
			client.CreateDir(projPath+h.URI, 0)
		}
	}
}

// parseYAML parses the config.yml file and returns the appropriate structs and strings.
func YAMLtoETCD(client *etcd.Client) (c goship.Config, err error) {
	config, err := yaml.ReadFile(*ConfigFile)
	if err != nil {
		return c, err
	}
	log.Printf("Setting project root => /projects")
	client.CreateDir("/projects", 0)
	configRoot, _ := config.Root.(yaml.Map)
	projects, _ := configRoot["projects"].(yaml.List)
	for _, p := range projects {
		for k, v := range p.(yaml.Map) {

			projectPath := "/projects/" + k + "/"

			log.Printf("Setting project => %s \n", projectPath)
			client.CreateDir(projectPath, 0)

			name := getYAMLString(v, "project_name")
			setETCD(client, projectPath+"project_name", name)

			repoOwner := getYAMLString(v, "repo_owner")
			setETCD(client, projectPath+"repo_owner", repoOwner)

			repoName := getYAMLString(v, "repo_name")
			setETCD(client, projectPath+"repo_name", repoName)

			for _, v := range v.(yaml.Map)["environments"].(yaml.List) {
				YAMLtoETCDEnvironment(v, client, projectPath)
			}

		}
	}

	piv_project, _ := config.Get("pivotal_project")
	setETCD(client, "pivotal_project", piv_project)

	piv_token, _ := config.Get("pivotal_token")
	setETCD(client, "pivotal_token", piv_token)

	deploy_user, _ := config.Get("deploy_user")
	setETCD(client, "deploy_user", deploy_user)

	goship_host, _ := config.Get("goship_host")
	setETCD(client, "goship_host", goship_host)

	notify, _ := config.Get("notify")
	setETCD(client, "notify", notify)

	return c, err
}

func main() {
	flag.Parse()
	log.Printf("Reading Config file: %s Connecting to ETCD server: %s", *ConfigFile, *ETCDServer)
	// Note the ETCD client library swallows errors connecting to etcd (worry)
	a := etcd.NewClient([]string{*ETCDServer})
	_, err := YAMLtoETCD(a)
	if err != nil {
		fmt.Printf("Failed to Parse Yaml and Add to ETCD [%s]\n", err)
	}
}
<|endoftext|>


<commit_before>package common

import (
	"github.com/aws/aws-sdk-go/aws"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"testing"
	"github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface"
	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
)

type mockedCwLogs struct {
	mock.Mock
	cloudwatchlogsiface.CloudWatchLogsAPI
}
func (m *mockedCwLogs) FilterLogEventsPages(input *cloudwatchlogs.FilterLogEventsInput, cb func(*cloudwatchlogs.FilterLogEventsOutput, bool) bool) error {
	args := m.Called(input, cb)
	return args.Error(0)
}

func TestLogsManager_ViewLogs(t *testing.T) {
	assert := assert.New(t)

	m := new(mockedCwLogs)
	m.On("FilterLogEventsPages", mock.AnythingOfType("*cloudwatchlogs.FilterLogEventsInput"), mock.AnythingOfType("func(*cloudwatchlogs.FilterLogEventsOutput, bool) bool")).
		Return(nil).
		Run(func(args mock.Arguments) {
			cb := args.Get(1).(func(*cloudwatchlogs.FilterLogEventsOutput, bool) bool)
			cb(&cloudwatchlogs.FilterLogEventsOutput{
				Events: []*cloudwatchlogs.FilteredLogEvent {
					{
						Message: aws.String("hello world"),
					},
					{
						Message: aws.String("hello agains"),
					},
				},
			}, true)
		})

	lm := logsManager{
		logsAPI: m,
	}

	events := 0
	cb := func(loggroup string, message string, ts int64) {
		events ++
	}

	err := lm.ViewLogs("foo", false, "", cb)
	assert.Nil(err)
	assert.Equal(2, events)

	m.AssertExpectations(t)
	m.AssertNumberOfCalls(t, "FilterLogEventsPages", 1)
}

<commit_msg>documentation for logs<commit_after>package common

import (
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
	"github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"testing"
)

type mockedCwLogs struct {
	mock.Mock
	cloudwatchlogsiface.CloudWatchLogsAPI
}

func (m *mockedCwLogs) FilterLogEventsPages(input *cloudwatchlogs.FilterLogEventsInput, cb func(*cloudwatchlogs.FilterLogEventsOutput, bool) bool) error {
	args := m.Called(input, cb)
	return args.Error(0)
}

func TestLogsManager_ViewLogs(t *testing.T) {
	assert := assert.New(t)

	m := new(mockedCwLogs)
	m.On("FilterLogEventsPages", mock.AnythingOfType("*cloudwatchlogs.FilterLogEventsInput"), mock.AnythingOfType("func(*cloudwatchlogs.FilterLogEventsOutput, bool) bool")).
		Return(nil).
		Run(func(args mock.Arguments) {
			cb := args.Get(1).(func(*cloudwatchlogs.FilterLogEventsOutput, bool) bool)
			cb(&cloudwatchlogs.FilterLogEventsOutput{
				Events: []*cloudwatchlogs.FilteredLogEvent{
					{
						Message: aws.String("hello world"),
					},
					{
						Message: aws.String("hello agains"),
					},
				},
			}, true)
		})

	lm := logsManager{
		logsAPI: m,
	}

	events := 0
	cb := func(loggroup string, message string, ts int64) {
		events++
	}

	err := lm.ViewLogs("foo", false, "", cb)
	assert.Nil(err)
	assert.Equal(2, events)

	m.AssertExpectations(t)
	m.AssertNumberOfCalls(t, "FilterLogEventsPages", 1)
}
<|endoftext|>


<commit_before>package daemon

import (
	"flag"
	"fmt"
	"os"
)

type Dependencies struct {
	*flag.FlagSet
	slots map[DependencyKey]depSlots
}

type Config interface {
	Populate(*Dependencies)
	Prepare() (StartFunc, error)
}

type DependencySlot interface {
	Key() DependencyKey
	Assign(value interface{})
}

type DependencyKey interface {
	MakeConfig() DependencyConfig
}

type DependencyConfig interface {
	Populate(*Dependencies)
	MakeValue() (interface{}, error)
}

type depSlots struct {
	config DependencyConfig
	slots  []DependencySlot
}

func (deps *Dependencies) Dependency(slot DependencySlot) {
	key := slot.Key()
	slots, found := deps.slots[key]
	if !found {
		slots.config = key.MakeConfig()
		slots.config.Populate(deps)
	}

	slots.slots = append(slots.slots, slot)
	deps.slots[key] = slots
}

func ConfigsMain(configs ...Config) {
	sfs, err := configsToStartFuncs(configs)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	Main(Aggregate(sfs...))
}

func configsToStartFuncs(configs []Config) ([]StartFunc, error) {
	deps := &Dependencies{
		FlagSet: flag.NewFlagSet(os.Args[0], flag.ContinueOnError),
		slots:   make(map[DependencyKey]depSlots),
	}

	for _, c := range configs {
		c.Populate(deps)
	}

	if err := deps.Parse(os.Args[1:]); err != nil {
		return nil, err
	}

	if deps.NArg() > 0 {
		return nil, fmt.Errorf("excess command line arguments")
	}

	// Make dependency values, and assign them to slots
	for _, slots := range deps.slots {
		val, err := slots.config.MakeValue()
		if err != nil {
			return nil, err
		}

		for _, d := range slots.slots {
			d.Assign(val)
		}
	}

	var res []StartFunc
	for _, c := range configs {
		sf, err := c.Prepare()
		if err != nil {
			return nil, err
		}

		res = append(res, sf)
	}

	return res, nil
}
<commit_msg>Call MakeValue on dependencies in the correct order<commit_after>package daemon

import (
	"flag"
	"fmt"
	"os"
)

type Dependencies struct {
	*flag.FlagSet
	slots map[DependencyKey]depSlots

	// The dependency graph between DependencyConfigs is implicit.
	// So we have to record the order in which they were created,
	// in order to call MakeValue on them in the reverse order.
	keysOrder []DependencyKey
}

type Config interface {
	Populate(*Dependencies)
	Prepare() (StartFunc, error)
}

type DependencySlot interface {
	Key() DependencyKey
	Assign(value interface{})
}

type DependencyKey interface {
	MakeConfig() DependencyConfig
}

type DependencyConfig interface {
	Populate(*Dependencies)
	MakeValue() (interface{}, error)
}

type depSlots struct {
	config DependencyConfig
	slots  []DependencySlot
}

func (deps *Dependencies) Dependency(slot DependencySlot) {
	key := slot.Key()
	slots, found := deps.slots[key]
	if !found {
		deps.keysOrder = append(deps.keysOrder, key)
		slots.config = key.MakeConfig()
		slots.config.Populate(deps)
	}

	slots.slots = append(slots.slots, slot)
	deps.slots[key] = slots
}

func ConfigsMain(configs ...Config) {
	sfs, err := configsToStartFuncs(configs)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	Main(Aggregate(sfs...))
}

func configsToStartFuncs(configs []Config) ([]StartFunc, error) {
	deps := &Dependencies{
		FlagSet: flag.NewFlagSet(os.Args[0], flag.ContinueOnError),
		slots:   make(map[DependencyKey]depSlots),
	}

	for _, c := range configs {
		c.Populate(deps)
	}

	if err := deps.Parse(os.Args[1:]); err != nil {
		return nil, err
	}

	if deps.NArg() > 0 {
		return nil, fmt.Errorf("excess command line arguments")
	}

	// Make dependency values, and assign them to slots
	for i := len(deps.keysOrder) - 1; i >= 0; i-- {
		slots := deps.slots[deps.keysOrder[i]]

		val, err := slots.config.MakeValue()
		if err != nil {
			return nil, err
		}

		for _, d := range slots.slots {
			d.Assign(val)
		}
	}

	var res []StartFunc
	for _, c := range configs {
		sf, err := c.Prepare()
		if err != nil {
			return nil, err
		}

		res = append(res, sf)
	}

	return res, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2016 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 awstasks

import (
	"fmt"

	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/route53"
	"github.com/golang/glog"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
	"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
)

//go:generate fitask -type=DNSName
type DNSName struct {
	Name         *string
	ID           *string
	Zone         *DNSZone
	ResourceType *string

	TargetLoadBalancer *LoadBalancer
}

func (e *DNSName) Find(c *fi.Context) (*DNSName, error) {
	cloud := c.Cloud.(awsup.AWSCloud)

	findName := fi.StringValue(e.Name)
	if findName == "" {
		return nil, nil
	}
	findName = strings.TrimSuffix(findName, ".")

	findType := fi.StringValue(e.ResourceType)
	if findType == "" {
		return nil, nil
	}

	request := &route53.ListResourceRecordSetsInput{
		HostedZoneId: e.Zone.ZoneID,
		// TODO: Start at correct name?
	}

	var found *route53.ResourceRecordSet

	err := cloud.Route53().ListResourceRecordSetsPages(request, func(p *route53.ListResourceRecordSetsOutput, lastPage bool) (shouldContinue bool) {
		for _, rr := range p.ResourceRecordSets {
			resourceType := aws.StringValue(rr.Type)
			name := aws.StringValue(rr.Name)

			glog.V(4).Infof("Found DNS resource %q %q", resourceType, name)

			if findType != resourceType {
				continue
			}

			name = strings.TrimSuffix(name, ".")

			if name == findName {
				found = rr
				break
			}
		}

		// TODO: Also exit if we are on the 'next' name?

		return found == nil
	})

	if err != nil {
		return nil, fmt.Errorf("error listing DNS ResourceRecords: %v", err)
	}

	if found == nil {
		return nil, nil
	}

	actual := &DNSName{}
	actual.Zone = e.Zone
	actual.Name = e.Name
	actual.ResourceType = e.ResourceType

	if found.AliasTarget != nil {
		dnsName := aws.StringValue(found.AliasTarget.DNSName)
		glog.Infof("AliasTarget for %q is %q", aws.StringValue(found.Name), dnsName)
		if dnsName != "" {
			// TODO: check "looks like" an ELB?
			lb, err := findLoadBalancerByAlias(cloud, found.AliasTarget)
			if err != nil {
				return nil, fmt.Errorf("error mapping DNSName %q to LoadBalancer: %v", dnsName, err)
			}
			if lb == nil {
				glog.Warningf("Unable to find load balancer with DNS name: %q", dnsName)
			} else {
				actual.TargetLoadBalancer = &LoadBalancer{ID: lb.LoadBalancerName}
			}
		}
	}

	return actual, nil
}

func (e *DNSName) Run(c *fi.Context) error {
	return fi.DefaultDeltaRunMethod(e, c)
}

func (s *DNSName) CheckChanges(a, e, changes *DNSName) error {
	if a == nil {
		if fi.StringValue(e.Name) == "" {
			return fi.RequiredField("Name")
		}
	}
	return nil
}

func (_ *DNSName) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *DNSName) error {
	rrs := &route53.ResourceRecordSet{
		Name: e.Name,
		Type: e.ResourceType,
	}

	if e.TargetLoadBalancer != nil {
		rrs.AliasTarget = &route53.AliasTarget{
			DNSName:              e.TargetLoadBalancer.DNSName,
			EvaluateTargetHealth: aws.Bool(false),
			HostedZoneId:         e.TargetLoadBalancer.HostedZoneId,
		}
	}

	change := &route53.Change{
		Action:            aws.String("UPSERT"),
		ResourceRecordSet: rrs,
	}

	changeBatch := &route53.ChangeBatch{}
	changeBatch.Changes = []*route53.Change{change}

	request := &route53.ChangeResourceRecordSetsInput{}
	request.HostedZoneId = e.Zone.ZoneID
	request.ChangeBatch = changeBatch

	glog.V(2).Infof("Updating DNS record %q", *e.Name)

	response, err := t.Cloud.Route53().ChangeResourceRecordSets(request)
	if err != nil {
		return fmt.Errorf("error creating ResourceRecordSets: %v", err)
	}

	glog.V(2).Infof("Change id is %q", aws.StringValue(response.ChangeInfo.Id))

	return nil
}

type terraformRoute53Record struct {
	Name    *string  `json:"name"`
	Type    *string  `json:"type"`
	TTL     *string  `json:"ttl,omitempty"`
	Records []string `json:"records,omitempty"`

	Alias  *terraformAlias    `json:"alias,omitempty"`
	ZoneID *terraform.Literal `json:"zone_id"`
}

type terraformAlias struct {
	Name                 *terraform.Literal `json:"name"`
	ZoneID               *terraform.Literal `json:"zone_id"`
	EvaluateTargetHealth *bool              `json:"evaluate_target_health"`
}

func (_ *DNSName) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *DNSName) error {
	tf := &terraformRoute53Record{
		Name:   e.Name,
		ZoneID: e.Zone.TerraformLink(),
		Type:   e.ResourceType,
	}

	if e.TargetLoadBalancer != nil {
		tf.Alias = &terraformAlias{
			Name:                 e.TargetLoadBalancer.TerraformLink("dns_name"),
			EvaluateTargetHealth: aws.Bool(false),
			ZoneID:               e.TargetLoadBalancer.TerraformLink("zone_id"),
		}
	}

	return t.RenderResource("aws_route53_record", *e.Name, tf)
}

func (e *DNSName) TerraformLink() *terraform.Literal {
	return terraform.LiteralSelfLink("aws_route53_record", *e.Name)
}
<commit_msg>Skip DNSName lookup if Zone not created<commit_after>/*
Copyright 2016 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 awstasks

import (
	"fmt"

	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/route53"
	"github.com/golang/glog"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
	"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
)

//go:generate fitask -type=DNSName
type DNSName struct {
	Name         *string
	ID           *string
	Zone         *DNSZone
	ResourceType *string

	TargetLoadBalancer *LoadBalancer
}

func (e *DNSName) Find(c *fi.Context) (*DNSName, error) {
	cloud := c.Cloud.(awsup.AWSCloud)

	if e.Zone == nil || e.Zone.ZoneID == nil {
		glog.V(4).Infof("Zone / ZoneID not found for %s, skipping Find", fi.StringValue(e.Name))
		return nil, nil
	}

	findName := fi.StringValue(e.Name)
	if findName == "" {
		return nil, nil
	}
	findName = strings.TrimSuffix(findName, ".")

	findType := fi.StringValue(e.ResourceType)
	if findType == "" {
		return nil, nil
	}

	request := &route53.ListResourceRecordSetsInput{
		HostedZoneId: e.Zone.ZoneID,
		// TODO: Start at correct name?
	}

	var found *route53.ResourceRecordSet

	err := cloud.Route53().ListResourceRecordSetsPages(request, func(p *route53.ListResourceRecordSetsOutput, lastPage bool) (shouldContinue bool) {
		for _, rr := range p.ResourceRecordSets {
			resourceType := aws.StringValue(rr.Type)
			name := aws.StringValue(rr.Name)

			glog.V(4).Infof("Found DNS resource %q %q", resourceType, name)

			if findType != resourceType {
				continue
			}

			name = strings.TrimSuffix(name, ".")

			if name == findName {
				found = rr
				break
			}
		}

		// TODO: Also exit if we are on the 'next' name?

		return found == nil
	})

	if err != nil {
		return nil, fmt.Errorf("error listing DNS ResourceRecords: %v", err)
	}

	if found == nil {
		return nil, nil
	}

	actual := &DNSName{}
	actual.Zone = e.Zone
	actual.Name = e.Name
	actual.ResourceType = e.ResourceType

	if found.AliasTarget != nil {
		dnsName := aws.StringValue(found.AliasTarget.DNSName)
		glog.Infof("AliasTarget for %q is %q", aws.StringValue(found.Name), dnsName)
		if dnsName != "" {
			// TODO: check "looks like" an ELB?
			lb, err := findLoadBalancerByAlias(cloud, found.AliasTarget)
			if err != nil {
				return nil, fmt.Errorf("error mapping DNSName %q to LoadBalancer: %v", dnsName, err)
			}
			if lb == nil {
				glog.Warningf("Unable to find load balancer with DNS name: %q", dnsName)
			} else {
				actual.TargetLoadBalancer = &LoadBalancer{ID: lb.LoadBalancerName}
			}
		}
	}

	return actual, nil
}

func (e *DNSName) Run(c *fi.Context) error {
	return fi.DefaultDeltaRunMethod(e, c)
}

func (s *DNSName) CheckChanges(a, e, changes *DNSName) error {
	if a == nil {
		if fi.StringValue(e.Name) == "" {
			return fi.RequiredField("Name")
		}
	}
	return nil
}

func (_ *DNSName) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *DNSName) error {
	rrs := &route53.ResourceRecordSet{
		Name: e.Name,
		Type: e.ResourceType,
	}

	if e.TargetLoadBalancer != nil {
		rrs.AliasTarget = &route53.AliasTarget{
			DNSName:              e.TargetLoadBalancer.DNSName,
			EvaluateTargetHealth: aws.Bool(false),
			HostedZoneId:         e.TargetLoadBalancer.HostedZoneId,
		}
	}

	change := &route53.Change{
		Action:            aws.String("UPSERT"),
		ResourceRecordSet: rrs,
	}

	changeBatch := &route53.ChangeBatch{}
	changeBatch.Changes = []*route53.Change{change}

	request := &route53.ChangeResourceRecordSetsInput{}
	request.HostedZoneId = e.Zone.ZoneID
	request.ChangeBatch = changeBatch

	glog.V(2).Infof("Updating DNS record %q", *e.Name)

	response, err := t.Cloud.Route53().ChangeResourceRecordSets(request)
	if err != nil {
		return fmt.Errorf("error creating ResourceRecordSets: %v", err)
	}

	glog.V(2).Infof("Change id is %q", aws.StringValue(response.ChangeInfo.Id))

	return nil
}

type terraformRoute53Record struct {
	Name    *string  `json:"name"`
	Type    *string  `json:"type"`
	TTL     *string  `json:"ttl,omitempty"`
	Records []string `json:"records,omitempty"`

	Alias  *terraformAlias    `json:"alias,omitempty"`
	ZoneID *terraform.Literal `json:"zone_id"`
}

type terraformAlias struct {
	Name                 *terraform.Literal `json:"name"`
	ZoneID               *terraform.Literal `json:"zone_id"`
	EvaluateTargetHealth *bool              `json:"evaluate_target_health"`
}

func (_ *DNSName) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *DNSName) error {
	tf := &terraformRoute53Record{
		Name:   e.Name,
		ZoneID: e.Zone.TerraformLink(),
		Type:   e.ResourceType,
	}

	if e.TargetLoadBalancer != nil {
		tf.Alias = &terraformAlias{
			Name:                 e.TargetLoadBalancer.TerraformLink("dns_name"),
			EvaluateTargetHealth: aws.Bool(false),
			ZoneID:               e.TargetLoadBalancer.TerraformLink("zone_id"),
		}
	}

	return t.RenderResource("aws_route53_record", *e.Name, tf)
}

func (e *DNSName) TerraformLink() *terraform.Literal {
	return terraform.LiteralSelfLink("aws_route53_record", *e.Name)
}
<|endoftext|>