This is still a dirty prototype, so please excuse the mess. Please excuse the fact that this is a mega patch. Once things settle down this won't happen any more. Some of the changes squashed into here include: * Merge vertex loop with type loop (The file watcher seems to cache events anyways) * Improve pgraph library * Add indegree, outdegree, and topological sort with tests * Add reverse function for vertex list * Tons of additional cleanup! Amazingly, on my first successful compile, this seemed to run! A special thanks to Ira Cooper who helped me talk through some of the algorithmic decisions and for his help in finding better ones!
194 lines
4.9 KiB
Go
194 lines
4.9 KiB
Go
// Mgmt
|
|
// Copyright (C) 2013-2015+ James Shubin and the project contributors
|
|
// Written by James Shubin <james@shubin.ca> and the project contributors
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/codegangsta/cli"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
// set at compile time
|
|
var (
|
|
version string
|
|
program string
|
|
)
|
|
|
|
const (
|
|
DEBUG = false
|
|
)
|
|
|
|
// signal handler
|
|
func waitForSignal(exit chan bool) {
|
|
signals := make(chan os.Signal, 1)
|
|
signal.Notify(signals, os.Interrupt) // catch ^C
|
|
//signal.Notify(signals, os.Kill) // catch signals
|
|
signal.Notify(signals, syscall.SIGTERM)
|
|
|
|
select {
|
|
case e := <-signals: // any signal will do
|
|
if e == os.Interrupt {
|
|
fmt.Println() // put ^C char from terminal on its own line
|
|
log.Println("Interrupted by ^C")
|
|
} else {
|
|
log.Println("Interrupted by signal")
|
|
}
|
|
case <-exit: // or a manual signal
|
|
log.Println("Interrupted by exit signal")
|
|
}
|
|
}
|
|
|
|
func run(c *cli.Context) {
|
|
var start int64 = time.Now().UnixNano()
|
|
var wg sync.WaitGroup
|
|
exit := make(chan bool) // exit signal
|
|
log.Printf("This is: %v, version: %v\n", program, version)
|
|
log.Printf("Start: %v\n", start)
|
|
G := NewGraph("Graph") // give graph a default name
|
|
|
|
// exit after `exittime` seconds for no reason at all...
|
|
if i := c.Int("exittime"); i > 0 {
|
|
go func() {
|
|
time.Sleep(time.Duration(i) * time.Second)
|
|
exit <- true
|
|
}()
|
|
}
|
|
|
|
// etcd
|
|
hostname := c.String("hostname")
|
|
if hostname == "" {
|
|
hostname, _ = os.Hostname() // etcd watch key // XXX: this is not the correct key name this is the set key name... WOOPS
|
|
}
|
|
go func(hostname string) {
|
|
log.Printf("Starting etcd...\n")
|
|
kapi := EtcdGetKAPI()
|
|
first := true // first loop or not
|
|
for x := range EtcdWatch(kapi, true) {
|
|
|
|
// run graph vertex LOCK...
|
|
if !first {
|
|
log.Printf("Watcher().Node.Value(%v): %+v", hostname, x)
|
|
|
|
G.SetState(graphPausing)
|
|
log.Printf("State: %v", G.State())
|
|
G.Pause() // sync
|
|
G.SetState(graphPaused)
|
|
log.Printf("State: %v", G.State())
|
|
}
|
|
|
|
// build the graph from a config file
|
|
// build the graph on events (eg: from etcd) but kick it once...
|
|
if !UpdateGraphFromConfig(c.String("file"), hostname, G, kapi) {
|
|
log.Fatal("Graph failure")
|
|
}
|
|
log.Printf("Graph: %v\n", G) // show graph
|
|
G.SetVertex()
|
|
if first {
|
|
// G.Start(...) needs to be synchronous or wait,
|
|
// because if half of the nodes are started and
|
|
// some are not ready yet and the EtcdWatch
|
|
// loops, we'll cause G.Pause(...) before we
|
|
// even got going, thus causing nil pointer errors
|
|
G.SetState(graphStarting)
|
|
log.Printf("State: %v", G.State())
|
|
G.Start(&wg)
|
|
G.SetState(graphStarted)
|
|
log.Printf("State: %v", G.State())
|
|
|
|
} else {
|
|
G.SetState(graphContinuing)
|
|
log.Printf("State: %v", G.State())
|
|
|
|
G.Continue() // sync
|
|
G.SetState(graphStarted)
|
|
log.Printf("State: %v", G.State())
|
|
}
|
|
first = false
|
|
}
|
|
}(hostname)
|
|
|
|
log.Println("Running...")
|
|
|
|
waitForSignal(exit) // pass in exit channel to watch
|
|
|
|
G.Exit() // tell all the children to exit
|
|
|
|
if DEBUG {
|
|
for i := range G.GetVerticesChan() {
|
|
fmt.Printf("Vertex: %v\n", i)
|
|
}
|
|
fmt.Printf("Graph: %v\n", G)
|
|
}
|
|
|
|
wg.Wait() // wait for primary go routines to exit
|
|
|
|
// TODO: wait for each vertex to exit...
|
|
log.Println("Goodbye!")
|
|
}
|
|
|
|
func main() {
|
|
//if DEBUG {
|
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
|
//}
|
|
log.SetFlags(log.Flags() - log.Ldate) // remove the date for now
|
|
app := cli.NewApp()
|
|
app.Name = program
|
|
app.Usage = "next generation config management"
|
|
app.Version = version
|
|
//app.Action = ... // without a default action, help runs
|
|
|
|
app.Commands = []cli.Command{
|
|
{
|
|
Name: "run",
|
|
Aliases: []string{"r"},
|
|
Usage: "run",
|
|
Action: run,
|
|
Flags: []cli.Flag{
|
|
cli.StringFlag{
|
|
Name: "file, f",
|
|
Value: "",
|
|
Usage: "graph definition to run",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "code, c",
|
|
Value: "",
|
|
Usage: "code definition to run",
|
|
},
|
|
// useful for testing multiple instances on same machine
|
|
cli.StringFlag{
|
|
Name: "hostname",
|
|
Value: "",
|
|
Usage: "hostname to use",
|
|
},
|
|
cli.IntFlag{
|
|
Name: "exittime",
|
|
Value: 0,
|
|
Usage: "exit after a maximum of approximately this many seconds",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
app.EnableBashCompletion = true
|
|
app.Run(os.Args)
|
|
}
|