func main() { hostname, _ := os.Hostname() externalAddr := flag.String("external", "", "external IP of host") bindAddr := flag.String("bind", "", "bind containers to this IP") configFile := flag.String("config", "", "configuration file") manifestFile := flag.String("manifest", "/etc/flynn-host.json", "manifest file") stateFile := flag.String("state", "", "state file") hostID := flag.String("id", strings.Replace(hostname, "-", "", -1), "host id") force := flag.Bool("force", false, "kill all containers booted by flynn-host before starting") volPath := flag.String("volpath", "/var/lib/flynn-host", "directory to create volumes in") backendName := flag.String("backend", "libvirt-lxc", "runner backend (docker or libvirt-lxc)") flynnInit := flag.String("flynn-init", "/usr/bin/flynn-init", "path to flynn-init binary") metadata := make(MetaFlag) flag.Var(&metadata, "meta", "key=value pair to add as metadata") flag.Parse() grohl.AddContext("app", "host") grohl.Log(grohl.Data{"at": "start"}) g := grohl.NewContext(grohl.Data{"fn": "main"}) if strings.Contains(*hostID, "-") { log.Fatal("host id must not contain dashes") } portAlloc := map[string]*ports.Allocator{ "tcp": ports.NewAllocator(55000, 65535), "udp": ports.NewAllocator(55000, 65535), } sh := newShutdownHandler() state := NewState() var backend Backend var err error switch *backendName { case "libvirt-lxc": backend, err = NewLibvirtLXCBackend(state, portAlloc, *volPath, "/tmp/flynn-host-logs", *flynnInit) case "docker": backend, err = NewDockerBackend(state, portAlloc, *bindAddr) default: log.Fatalf("unknown backend %q", *backendName) } if err != nil { sh.Fatal(err) } if err := serveHTTP(&Host{state: state, backend: backend}, &attachHandler{state: state, backend: backend}, sh); err != nil { sh.Fatal(err) } if *stateFile != "" { sh.BeforeExit(func() { os.Remove(*stateFile) }) if err := state.Restore(*stateFile, backend); err != nil { sh.Fatal(err) } } sh.BeforeExit(func() { backend.Cleanup() }) if *force { if err := backend.Cleanup(); err != nil { sh.Fatal(err) } } runner := &manifestRunner{ env: parseEnviron(), externalAddr: *externalAddr, bindAddr: *bindAddr, backend: backend, state: state, ports: portAlloc, } discAddr := os.Getenv("DISCOVERD") var disc *discoverd.Client if *manifestFile != "" { var r io.Reader var f *os.File if *manifestFile == "-" { r = os.Stdin } else { f, err = os.Open(*manifestFile) if err != nil { sh.Fatal(err) } r = f } services, err := runner.runManifest(r) if err != nil { sh.Fatal(err) } if f != nil { f.Close() } if d, ok := services["discoverd"]; ok { discAddr = fmt.Sprintf("%s:%d", d.InternalIP, d.TCPPorts[0]) var disc *discoverd.Client err = Attempts.Run(func() (err error) { disc, err = discoverd.NewClientWithAddr(discAddr) return }) if err != nil { sh.Fatal(err) } } } if discAddr == "" && *externalAddr != "" { discAddr = *externalAddr + ":1111" } // HACK: use env as global for discoverd connection in sampic os.Setenv("DISCOVERD", discAddr) if disc == nil { disc, err = discoverd.NewClientWithAddr(discAddr) if err != nil { sh.Fatal(err) } } sh.BeforeExit(func() { disc.UnregisterAll() }) sampiStandby, err := disc.RegisterAndStandby("flynn-host", *externalAddr+":1113", map[string]string{"id": *hostID}) if err != nil { sh.Fatal(err) } // Check if we are the leader so that we can use the cluster functions directly sampiCluster := sampi.NewCluster(sampi.NewState()) select { case <-sampiStandby: g.Log(grohl.Data{"at": "sampi_leader"}) rpc.Register(sampiCluster) case <-time.After(5 * time.Millisecond): go func() { <-sampiStandby g.Log(grohl.Data{"at": "sampi_leader"}) rpc.Register(sampiCluster) }() } cluster, err := cluster.NewClientWithSelf(*hostID, NewLocalClient(*hostID, sampiCluster)) if err != nil { sh.Fatal(err) } sh.BeforeExit(func() { cluster.Close() }) g.Log(grohl.Data{"at": "sampi_connected"}) events := state.AddListener("all") go syncScheduler(cluster, events) h := &host.Host{} if *configFile != "" { h, err = openConfig(*configFile) if err != nil { sh.Fatal(err) } } if h.Metadata == nil { h.Metadata = make(map[string]string) } for k, v := range metadata { h.Metadata[k] = v } h.ID = *hostID for { newLeader := cluster.NewLeaderSignal() h.Jobs = state.ClusterJobs() jobs := make(chan *host.Job) hostErr := cluster.RegisterHost(h, jobs) g.Log(grohl.Data{"at": "host_registered"}) for job := range jobs { if *externalAddr != "" { if job.Config.Env == nil { job.Config.Env = make(map[string]string) } job.Config.Env["EXTERNAL_IP"] = *externalAddr job.Config.Env["DISCOVERD"] = discAddr } if err := backend.Run(job); err != nil { state.SetStatusFailed(job.ID, err) } } g.Log(grohl.Data{"at": "sampi_disconnected", "err": *hostErr}) <-newLeader } }
func runDaemon(args *docopt.Args) { hostname, _ := os.Hostname() externalAddr := args.String["--external"] bindAddr := args.String["--bind"] configFile := args.String["--config"] manifestFile := args.String["--manifest"] stateFile := args.String["--state"] hostID := args.String["--id"] force := args.Bool["--force"] volPath := args.String["--volpath"] backendName := args.String["--backend"] flynnInit := args.String["--flynn-init"] metadata := args.All["--meta"].([]string) grohl.AddContext("app", "host") grohl.Log(grohl.Data{"at": "start"}) g := grohl.NewContext(grohl.Data{"fn": "main"}) if hostID == "" { hostID = strings.Replace(hostname, "-", "", -1) } if strings.Contains(hostID, "-") { log.Fatal("host id must not contain dashes") } portAlloc := map[string]*ports.Allocator{ "tcp": ports.NewAllocator(55000, 65535), "udp": ports.NewAllocator(55000, 65535), } sh := newShutdownHandler() state := NewState() var backend Backend var err error switch backendName { case "libvirt-lxc": backend, err = NewLibvirtLXCBackend(state, portAlloc, volPath, "/tmp/flynn-host-logs", flynnInit) case "docker": backend, err = NewDockerBackend(state, portAlloc, bindAddr) default: log.Fatalf("unknown backend %q", backendName) } if err != nil { sh.Fatal(err) } if err := serveHTTP(&Host{state: state, backend: backend}, &attachHandler{state: state, backend: backend}, sh); err != nil { sh.Fatal(err) } if stateFile != "" { if err := state.Restore(stateFile, backend); err != nil { sh.Fatal(err) } } var jobStream cluster.Stream sh.BeforeExit(func() { if jobStream != nil { jobStream.Close() } backend.Cleanup() }) if force { if err := backend.Cleanup(); err != nil { sh.Fatal(err) } } runner := &manifestRunner{ env: parseEnviron(), externalAddr: externalAddr, bindAddr: bindAddr, backend: backend, state: state, ports: portAlloc, } discAddr := os.Getenv("DISCOVERD") var disc *discoverd.Client if manifestFile != "" { var r io.Reader var f *os.File if manifestFile == "-" { r = os.Stdin } else { f, err = os.Open(manifestFile) if err != nil { sh.Fatal(err) } r = f } services, err := runner.runManifest(r) if err != nil { sh.Fatal(err) } if f != nil { f.Close() } if d, ok := services["discoverd"]; ok { discAddr = fmt.Sprintf("%s:%d", d.InternalIP, d.TCPPorts[0]) var disc *discoverd.Client err = Attempts.Run(func() (err error) { disc, err = discoverd.NewClientWithAddr(discAddr) return }) if err != nil { sh.Fatal(err) } } } if discAddr == "" && externalAddr != "" { discAddr = externalAddr + ":1111" } // HACK: use env as global for discoverd connection in sampic os.Setenv("DISCOVERD", discAddr) if disc == nil { disc, err = discoverd.NewClientWithAddr(discAddr) if err != nil { sh.Fatal(err) } } sh.BeforeExit(func() { disc.UnregisterAll() }) sampiStandby, err := disc.RegisterAndStandby("flynn-host", externalAddr+":1113", map[string]string{"id": hostID}) if err != nil { sh.Fatal(err) } // Check if we are the leader so that we can use the cluster functions directly sampiCluster := sampi.NewCluster(sampi.NewState()) select { case <-sampiStandby: g.Log(grohl.Data{"at": "sampi_leader"}) rpc.Register(sampiCluster) case <-time.After(5 * time.Millisecond): go func() { <-sampiStandby g.Log(grohl.Data{"at": "sampi_leader"}) rpc.Register(sampiCluster) }() } cluster, err := cluster.NewClientWithSelf(hostID, NewLocalClient(hostID, sampiCluster)) if err != nil { sh.Fatal(err) } sh.BeforeExit(func() { cluster.Close() }) g.Log(grohl.Data{"at": "sampi_connected"}) events := state.AddListener("all") go syncScheduler(cluster, events) h := &host.Host{} if configFile != "" { h, err = openConfig(configFile) if err != nil { sh.Fatal(err) } } if h.Metadata == nil { h.Metadata = make(map[string]string) } for _, s := range metadata { kv := strings.SplitN(s, "=", 2) h.Metadata[kv[0]] = kv[1] } h.ID = hostID for { newLeader := cluster.NewLeaderSignal() h.Jobs = state.ClusterJobs() jobs := make(chan *host.Job) jobStream = cluster.RegisterHost(h, jobs) g.Log(grohl.Data{"at": "host_registered"}) for job := range jobs { if externalAddr != "" { if job.Config.Env == nil { job.Config.Env = make(map[string]string) } job.Config.Env["EXTERNAL_IP"] = externalAddr job.Config.Env["DISCOVERD"] = discAddr } if err := backend.Run(job); err != nil { state.SetStatusFailed(job.ID, err) } } g.Log(grohl.Data{"at": "sampi_disconnected", "err": jobStream.Err}) // if the process is shutting down, just block if sh.Active { <-make(chan struct{}) } <-newLeader } }