github.com/rochacon/deis@v1.0.2-0.20150903015341-6839b592a1ff/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 }