github.com/dustinrc/deis@v1.10.1-0.20150917223407-0894a5fb979e/mesos/pkg/boot/main.go (about)

     1  //go:generate go-extpoints
     2  
     3  package boot
     4  
     5  import (
     6  	"net/http"
     7  	_ "net/http/pprof" //pprof is used for profiling servers
     8  	"os"
     9  	"os/signal"
    10  	"runtime"
    11  	"strconv"
    12  	"strings"
    13  	"syscall"
    14  	"time"
    15  
    16  	"github.com/deis/deis/mesos/pkg/boot/extpoints"
    17  	"github.com/deis/deis/mesos/pkg/confd"
    18  	"github.com/deis/deis/mesos/pkg/etcd"
    19  	logger "github.com/deis/deis/mesos/pkg/log"
    20  	"github.com/deis/deis/mesos/pkg/net"
    21  	oswrapper "github.com/deis/deis/mesos/pkg/os"
    22  	"github.com/deis/deis/mesos/pkg/types"
    23  	"github.com/deis/deis/version"
    24  	"github.com/robfig/cron"
    25  )
    26  
    27  const (
    28  	timeout time.Duration = 10 * time.Second
    29  	ttl     time.Duration = timeout * 2
    30  )
    31  
    32  var (
    33  	signalChan  = make(chan os.Signal, 1)
    34  	log         = logger.New()
    35  	bootProcess = extpoints.BootComponents
    36  	component   extpoints.BootComponent
    37  )
    38  
    39  func init() {
    40  	runtime.GOMAXPROCS(runtime.NumCPU())
    41  }
    42  
    43  // RegisterComponent register an externsion to be used with this application
    44  func RegisterComponent(component extpoints.BootComponent, name string) bool {
    45  	return bootProcess.Register(component, name)
    46  }
    47  
    48  // Start initiate the boot process of the current component
    49  // etcdPath is the base path used to publish the component in etcd
    50  // externalPort is the base path used to publish the component in etcd
    51  func Start(etcdPath string, externalPort int) {
    52  	log.Infof("boot version [%v]", version.Version)
    53  
    54  	go func() {
    55  		log.Debugf("starting pprof http server in port 6060")
    56  		http.ListenAndServe("localhost:6060", nil)
    57  	}()
    58  
    59  	signal.Notify(signalChan,
    60  		syscall.SIGHUP,
    61  		syscall.SIGINT,
    62  		syscall.SIGKILL,
    63  		syscall.SIGTERM,
    64  		syscall.SIGQUIT,
    65  		os.Interrupt,
    66  	)
    67  
    68  	// Wait for a signal and exit
    69  	exitChan := make(chan int)
    70  	go func() {
    71  		for {
    72  			s := <-signalChan
    73  			log.Debugf("Signal received: %v", s)
    74  			switch s {
    75  			case syscall.SIGTERM:
    76  				exitChan <- 0
    77  			case syscall.SIGQUIT:
    78  				exitChan <- 0
    79  			case syscall.SIGKILL:
    80  				exitChan <- 1
    81  			default:
    82  				exitChan <- 1
    83  			}
    84  		}
    85  	}()
    86  
    87  	component = bootProcess.Lookup("boot")
    88  	if component == nil {
    89  		log.Error("error loading boot extension...")
    90  		signalChan <- syscall.SIGINT
    91  	}
    92  
    93  	host := oswrapper.Getopt("HOST", "127.0.0.1")
    94  	etcdPort, _ := strconv.Atoi(oswrapper.Getopt("ETCD_PORT", "4001"))
    95  	etcdPeers := oswrapper.Getopt("ETCD_PEERS", "127.0.0.1:"+strconv.Itoa(etcdPort))
    96  	etcdClient := etcd.NewClient(etcd.GetHTTPEtcdUrls(host+":"+strconv.Itoa(etcdPort), etcdPeers))
    97  
    98  	etcdURL := etcd.GetHTTPEtcdUrls(host+":"+strconv.Itoa(etcdPort), etcdPeers)
    99  
   100  	currentBoot := &types.CurrentBoot{
   101  		ConfdNodes: getConfdNodes(host+":"+strconv.Itoa(etcdPort), etcdPeers),
   102  		EtcdClient: etcdClient,
   103  		EtcdPath:   etcdPath,
   104  		EtcdPort:   etcdPort,
   105  		EtcdPeers:  etcdPeers,
   106  		EtcdURL:    etcdURL,
   107  		Host:       net.ParseIP(host),
   108  		Timeout:    timeout,
   109  		TTL:        timeout * 2,
   110  		Port:       externalPort,
   111  	}
   112  
   113  	// do the real work in a goroutine to be able to exit if
   114  	// a signal is received during the boot process
   115  	go start(currentBoot)
   116  
   117  	code := <-exitChan
   118  
   119  	// pre shutdown tasks
   120  	log.Debugf("executing pre shutdown scripts")
   121  	preShutdownScripts := component.PreShutdownScripts(currentBoot)
   122  	runAllScripts(signalChan, preShutdownScripts)
   123  
   124  	log.Debugf("execution terminated with exit code %v", code)
   125  	os.Exit(code)
   126  }
   127  
   128  func start(currentBoot *types.CurrentBoot) {
   129  	log.Info("starting component...")
   130  
   131  	log.Debug("creating required etcd directories")
   132  	for _, key := range component.MkdirsEtcd() {
   133  		etcd.Mkdir(currentBoot.EtcdClient, key)
   134  	}
   135  
   136  	log.Debug("setting default etcd values")
   137  	for key, value := range component.EtcdDefaults() {
   138  		etcd.SetDefault(currentBoot.EtcdClient, key, value)
   139  	}
   140  
   141  	// component.PreBoot(currentBoot)
   142  
   143  	initial, daemon := component.UseConfd()
   144  	if initial {
   145  		// wait for confd to run once and install initial templates
   146  		log.Debug("waiting for initial confd configuration")
   147  		confd.WaitForInitialConf(currentBoot.ConfdNodes, currentBoot.Timeout)
   148  	}
   149  
   150  	log.Debug("running preboot code")
   151  	component.PreBoot(currentBoot)
   152  
   153  	log.Debug("running pre boot scripts")
   154  	preBootScripts := component.PreBootScripts(currentBoot)
   155  	runAllScripts(signalChan, preBootScripts)
   156  
   157  	if daemon {
   158  		// spawn confd in the background to update services based on etcd changes
   159  		log.Debug("launching confd")
   160  		go confd.Launch(signalChan, currentBoot.ConfdNodes)
   161  	}
   162  
   163  	log.Debug("running boot daemons")
   164  	servicesToStart := component.BootDaemons(currentBoot)
   165  	for _, daemon := range servicesToStart {
   166  		go oswrapper.RunProcessAsDaemon(signalChan, daemon.Command, daemon.Args)
   167  	}
   168  
   169  	// if the returned ips contains the value contained in $HOST it means
   170  	// that we are running docker with --net=host
   171  	ipToListen := "0.0.0.0"
   172  	netIfaces := net.GetNetworkInterfaces()
   173  	for _, iface := range netIfaces {
   174  		if strings.Index(iface.IP, currentBoot.Host.String()) > -1 {
   175  			ipToListen = currentBoot.Host.String()
   176  			break
   177  		}
   178  	}
   179  
   180  	portsToWaitFor := component.WaitForPorts()
   181  	log.Debugf("waiting for a service in the port %v in ip %v", portsToWaitFor, ipToListen)
   182  	for _, portToWait := range portsToWaitFor {
   183  		if portToWait > 0 {
   184  			err := net.WaitForPort("tcp", ipToListen, portToWait, timeout)
   185  			if err != nil {
   186  				log.Errorf("error waiting for port %v using ip %v: %v", portToWait, ipToListen, err)
   187  				signalChan <- syscall.SIGINT
   188  			}
   189  		}
   190  	}
   191  
   192  	time.Sleep(60 * time.Second)
   193  
   194  	// we only publish the service in etcd if the port if > 0
   195  	if currentBoot.Port > 0 {
   196  		log.Debug("starting periodic publication in etcd...")
   197  		log.Debugf("etcd publication path %s, host %s and port %v", currentBoot.EtcdPath, currentBoot.Host, currentBoot.Port)
   198  		go etcd.PublishService(currentBoot.EtcdClient, currentBoot.EtcdPath+"/"+currentBoot.Host.String(), currentBoot.Host.String(), currentBoot.Port, uint64(ttl.Seconds()), timeout)
   199  
   200  		// Wait for the first publication
   201  		time.Sleep(timeout / 2)
   202  	}
   203  
   204  	log.Debug("running post boot scripts")
   205  	postBootScripts := component.PostBootScripts(currentBoot)
   206  	runAllScripts(signalChan, postBootScripts)
   207  
   208  	log.Debug("checking for cron tasks...")
   209  	crons := component.ScheduleTasks(currentBoot)
   210  	_cron := cron.New()
   211  	for _, cronTask := range crons {
   212  		_cron.AddFunc(cronTask.Frequency, cronTask.Code)
   213  	}
   214  	_cron.Start()
   215  
   216  	component.PostBoot(currentBoot)
   217  }
   218  
   219  func getConfdNodes(host, etcdCtlPeers string) []string {
   220  	if etcdCtlPeers != "127.0.0.1:4001" {
   221  		hosts := strings.Split(etcdCtlPeers, ",")
   222  		result := []string{}
   223  		for _, _host := range hosts {
   224  			result = append(result, _host)
   225  		}
   226  		return result
   227  	}
   228  
   229  	return []string{host}
   230  }
   231  
   232  func runAllScripts(signalChan chan os.Signal, scripts []*types.Script) {
   233  	for _, script := range scripts {
   234  		if script.Params == nil {
   235  			script.Params = map[string]string{}
   236  		}
   237  		// add HOME variable to avoid warning from ceph commands
   238  		script.Params["HOME"] = "/tmp"
   239  		if log.Level.String() == "debug" {
   240  			script.Params["DEBUG"] = "true"
   241  		}
   242  		err := oswrapper.RunScript(script.Name, script.Params, script.Content)
   243  		if err != nil {
   244  			log.Errorf("script %v execution finished with error: %v", script.Name, err)
   245  			signalChan <- syscall.SIGTERM
   246  		}
   247  	}
   248  }