Support N distributed agents
This is the third main feature of this system. The code needs a bunch of polish, but it actually all works :) I've tested this briefly with N <= 3. Currently you have to build your own etcd cluster. It's quite easy, just run `etcd` and it will be ready. I usually run it in a throw away /tmp/ dir so that I can blow away the stored data easily.
This commit is contained in:
43
config.go
43
config.go
@@ -66,7 +66,23 @@ func (c *graphConfig) Parse(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateGraphFromConfig(filename, hostname string, g *Graph, kapi etcd.KeysAPI) bool {
|
func ParseConfigFromFile(filename string) *graphConfig {
|
||||||
|
data, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error: Config: ParseConfigFromFile: File: %v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var config graphConfig
|
||||||
|
if err := config.Parse(data); err != nil {
|
||||||
|
log.Printf("Error: Config: ParseConfigFromFile: Parse: %v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &config
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateGraphFromConfig(config *graphConfig, hostname string, g *Graph, kapi etcd.KeysAPI) {
|
||||||
|
|
||||||
var NoopMap map[string]*Vertex = make(map[string]*Vertex)
|
var NoopMap map[string]*Vertex = make(map[string]*Vertex)
|
||||||
var FileMap map[string]*Vertex = make(map[string]*Vertex)
|
var FileMap map[string]*Vertex = make(map[string]*Vertex)
|
||||||
@@ -77,17 +93,6 @@ func UpdateGraphFromConfig(filename, hostname string, g *Graph, kapi etcd.KeysAP
|
|||||||
lookup["file"] = FileMap
|
lookup["file"] = FileMap
|
||||||
lookup["service"] = ServiceMap
|
lookup["service"] = ServiceMap
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var config graphConfig
|
|
||||||
if err := config.Parse(data); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
//fmt.Printf("%+v\n", config) // debug
|
//fmt.Printf("%+v\n", config) // debug
|
||||||
|
|
||||||
g.SetName(config.Graph) // set graph name
|
g.SetName(config.Graph) // set graph name
|
||||||
@@ -116,7 +121,7 @@ func UpdateGraphFromConfig(filename, hostname string, g *Graph, kapi etcd.KeysAP
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
obj := NewFileType(t.Name, t.Path, t.Content, t.State)
|
obj := NewFileType(t.Name, t.Path, t.Dirname, t.Basename, t.Content, t.State)
|
||||||
v := g.GetVertexMatch(obj)
|
v := g.GetVertexMatch(obj)
|
||||||
if v == nil { // no match found
|
if v == nil { // no match found
|
||||||
v = NewVertex(obj)
|
v = NewVertex(obj)
|
||||||
@@ -145,15 +150,19 @@ func UpdateGraphFromConfig(filename, hostname string, g *Graph, kapi etcd.KeysAP
|
|||||||
if ok {
|
if ok {
|
||||||
for _, t := range config.Collector {
|
for _, t := range config.Collector {
|
||||||
// XXX: use t.Type and optionally t.Pattern to collect from etcd storage
|
// XXX: use t.Type and optionally t.Pattern to collect from etcd storage
|
||||||
log.Printf("Collect: %v(%v)", t.Type, t.Pattern)
|
log.Printf("Collect: %v; Pattern: %v", t.Type, t.Pattern)
|
||||||
|
|
||||||
for _, x := range EtcdGetProcess(nodes, "file") {
|
for _, x := range EtcdGetProcess(nodes, "file") {
|
||||||
var obj *FileType
|
var obj *FileType
|
||||||
if B64ToObj(x, &obj) != true {
|
if B64ToObj(x, &obj) != true {
|
||||||
log.Printf("File: %v error!", x)
|
log.Printf("Collect: File: %v not collected!", x)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Printf("File: %v found!", obj.GetName())
|
if t.Pattern != "" { // XXX: currently the pattern for files can only override the Dirname variable :P
|
||||||
|
obj.Dirname = t.Pattern
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Collect: File: %v collected!", obj.GetName())
|
||||||
|
|
||||||
// XXX: similar to file add code:
|
// XXX: similar to file add code:
|
||||||
v := g.GetVertexMatch(obj)
|
v := g.GetVertexMatch(obj)
|
||||||
@@ -182,6 +191,4 @@ func UpdateGraphFromConfig(filename, hostname string, g *Graph, kapi etcd.KeysAP
|
|||||||
for _, e := range config.Edges {
|
for _, e := range config.Edges {
|
||||||
g.AddEdge(lookup[e.From.Type][e.From.Name], lookup[e.To.Type][e.To.Name], NewEdge(e.Name))
|
g.AddEdge(lookup[e.From.Type][e.From.Name], lookup[e.To.Type][e.To.Name], NewEdge(e.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|||||||
155
configwatch.go
Normal file
155
configwatch.go
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
// 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 (
|
||||||
|
"gopkg.in/fsnotify.v1"
|
||||||
|
//"github.com/go-fsnotify/fsnotify" // git master of "gopkg.in/fsnotify.v1"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// XXX: it would be great if we could reuse code between this and the file type
|
||||||
|
// XXX: patch this to submit it as part of go-fsnotify if they're interested...
|
||||||
|
func ConfigWatch(file string) chan bool {
|
||||||
|
ch := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
var safename = path.Clean(file) // no trailing slash
|
||||||
|
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer watcher.Close()
|
||||||
|
|
||||||
|
patharray := PathSplit(safename) // tokenize the path
|
||||||
|
var index = len(patharray) // starting index
|
||||||
|
var current string // current "watcher" location
|
||||||
|
var delta_depth int // depth delta between watcher and event
|
||||||
|
var send = false // send event?
|
||||||
|
|
||||||
|
for {
|
||||||
|
current = strings.Join(patharray[0:index], "/")
|
||||||
|
if current == "" { // the empty string top is the root dir ("/")
|
||||||
|
current = "/"
|
||||||
|
}
|
||||||
|
log.Printf("Watching: %v\n", current) // attempting to watch...
|
||||||
|
|
||||||
|
// initialize in the loop so that we can reset on rm-ed handles
|
||||||
|
err = watcher.Add(current)
|
||||||
|
if err != nil {
|
||||||
|
if err == syscall.ENOENT {
|
||||||
|
index-- // usually not found, move up one dir
|
||||||
|
} else if err == syscall.ENOSPC {
|
||||||
|
// XXX: occasionally: no space left on device,
|
||||||
|
// XXX: probably due to lack of inotify watches
|
||||||
|
log.Printf("Lack of watches for config(%v) error: %+v\n", file, err.Error) // 0x408da0
|
||||||
|
log.Fatal(err)
|
||||||
|
} else {
|
||||||
|
log.Printf("Unknown config(%v) error:\n", file)
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
index = int(math.Max(1, float64(index)))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case event := <-watcher.Events:
|
||||||
|
// the deeper you go, the bigger the delta_depth is...
|
||||||
|
// this is the difference between what we're watching,
|
||||||
|
// and the event... doesn't mean we can't watch deeper
|
||||||
|
if current == event.Name {
|
||||||
|
delta_depth = 0 // i was watching what i was looking for
|
||||||
|
|
||||||
|
} else if HasPathPrefix(event.Name, current) {
|
||||||
|
delta_depth = len(PathSplit(current)) - len(PathSplit(event.Name)) // -1 or less
|
||||||
|
|
||||||
|
} else if HasPathPrefix(current, event.Name) {
|
||||||
|
delta_depth = len(PathSplit(event.Name)) - len(PathSplit(current)) // +1 or more
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// TODO different watchers get each others events!
|
||||||
|
// https://github.com/go-fsnotify/fsnotify/issues/95
|
||||||
|
// this happened with two values such as:
|
||||||
|
// event.Name: /tmp/mgmt/f3 and current: /tmp/mgmt/f2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
//log.Printf("The delta depth is: %v\n", delta_depth)
|
||||||
|
|
||||||
|
// if we have what we wanted, awesome, send an event...
|
||||||
|
if event.Name == safename {
|
||||||
|
//log.Println("Event!")
|
||||||
|
send = true
|
||||||
|
|
||||||
|
// file removed, move the watch upwards
|
||||||
|
if delta_depth >= 0 && (event.Op&fsnotify.Remove == fsnotify.Remove) {
|
||||||
|
//log.Println("Removal!")
|
||||||
|
watcher.Remove(current)
|
||||||
|
index--
|
||||||
|
}
|
||||||
|
|
||||||
|
// we must be a parent watcher, so descend in
|
||||||
|
if delta_depth < 0 {
|
||||||
|
watcher.Remove(current)
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
|
||||||
|
// if safename starts with event.Name, we're above, and no event should be sent
|
||||||
|
} else if HasPathPrefix(safename, event.Name) {
|
||||||
|
//log.Println("Above!")
|
||||||
|
|
||||||
|
if delta_depth >= 0 && (event.Op&fsnotify.Remove == fsnotify.Remove) {
|
||||||
|
log.Println("Removal!")
|
||||||
|
watcher.Remove(current)
|
||||||
|
index--
|
||||||
|
}
|
||||||
|
|
||||||
|
if delta_depth < 0 {
|
||||||
|
log.Println("Parent!")
|
||||||
|
if PathPrefixDelta(safename, event.Name) == 1 { // we're the parent dir
|
||||||
|
//send = true
|
||||||
|
}
|
||||||
|
watcher.Remove(current)
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
|
||||||
|
// if event.Name startswith safename, send event, we're already deeper
|
||||||
|
} else if HasPathPrefix(event.Name, safename) {
|
||||||
|
//log.Println("Event2!")
|
||||||
|
//send = true
|
||||||
|
}
|
||||||
|
|
||||||
|
case err := <-watcher.Errors:
|
||||||
|
log.Println("error:", err)
|
||||||
|
log.Fatal(err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// do our event sending all together to avoid duplicate msgs
|
||||||
|
if send {
|
||||||
|
send = false
|
||||||
|
ch <- true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(ch)
|
||||||
|
}()
|
||||||
|
return ch
|
||||||
|
}
|
||||||
47
etcd.go
47
etcd.go
@@ -27,10 +27,19 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func EtcdGetKAPI() etcd.KeysAPI {
|
//go:generate stringer -type=etcdMsg -output=etcdmsg_stringer.go
|
||||||
|
type etcdMsg int
|
||||||
|
|
||||||
|
const (
|
||||||
|
etcdStart etcdMsg = iota
|
||||||
|
etcdEvent
|
||||||
|
etcdFoo
|
||||||
|
etcdBar
|
||||||
|
)
|
||||||
|
|
||||||
|
func EtcdGetKAPI(seed string) etcd.KeysAPI {
|
||||||
cfg := etcd.Config{
|
cfg := etcd.Config{
|
||||||
Endpoints: []string{"http://127.0.0.1:2379"},
|
Endpoints: []string{seed},
|
||||||
Transport: etcd.DefaultTransport,
|
Transport: etcd.DefaultTransport,
|
||||||
// set timeout per request to fail fast when the target endpoint is unavailable
|
// set timeout per request to fail fast when the target endpoint is unavailable
|
||||||
HeaderTimeoutPerRequest: time.Second,
|
HeaderTimeoutPerRequest: time.Second,
|
||||||
@@ -56,24 +65,21 @@ func EtcdGetKAPI() etcd.KeysAPI {
|
|||||||
return etcd.NewKeysAPI(c)
|
return etcd.NewKeysAPI(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func EtcdWatch(kapi etcd.KeysAPI, kick bool) chan string {
|
func EtcdWatch(kapi etcd.KeysAPI) chan etcdMsg {
|
||||||
// XXX: i think we need this buffered so that when we're hanging on the
|
// XXX: i think we need this buffered so that when we're hanging on the
|
||||||
// channel, which is inside the EtcdWatch main loop, we still want the
|
// channel, which is inside the EtcdWatch main loop, we still want the
|
||||||
// calls to Get/Set on etcd to succeed, so blocking them here would
|
// calls to Get/Set on etcd to succeed, so blocking them here would
|
||||||
// kill the whole thing
|
// kill the whole thing
|
||||||
ch := make(chan string, 1) // XXX: buffer of at least 1 is required
|
ch := make(chan etcdMsg, 1) // XXX: buffer of at least 1 is required
|
||||||
if kick {
|
go func(ch chan etcdMsg) {
|
||||||
ch <- "hello"
|
|
||||||
}
|
|
||||||
go func(ch chan string) {
|
|
||||||
tmin := 500 // initial (min) delay in ms
|
tmin := 500 // initial (min) delay in ms
|
||||||
t := tmin // current time
|
t := tmin // current time
|
||||||
tmult := 2 // multiplier for exponential delay
|
tmult := 2 // multiplier for exponential delay
|
||||||
tmax := 16000 // max delay
|
tmax := 16000 // max delay
|
||||||
watcher := kapi.Watcher("/exported/", &etcd.WatcherOptions{Recursive: true})
|
watcher := kapi.Watcher("/exported/", &etcd.WatcherOptions{Recursive: true})
|
||||||
for {
|
for {
|
||||||
log.Printf("Watching etcd...")
|
log.Printf("Etcd: Watching...")
|
||||||
resp, err := watcher.Next(etcd_context.Background())
|
resp, err := watcher.Next(etcd_context.Background()) // blocks here
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == etcd_context.Canceled {
|
if err == etcd_context.Canceled {
|
||||||
// ctx is canceled by another routine
|
// ctx is canceled by another routine
|
||||||
@@ -87,8 +93,18 @@ func EtcdWatch(kapi etcd.KeysAPI, kick bool) chan string {
|
|||||||
for _, e := range cerr.Errors {
|
for _, e := range cerr.Errors {
|
||||||
if strings.HasSuffix(e.Error(), "getsockopt: connection refused") {
|
if strings.HasSuffix(e.Error(), "getsockopt: connection refused") {
|
||||||
t = int(math.Min(float64(t*tmult), float64(tmax)))
|
t = int(math.Min(float64(t*tmult), float64(tmax)))
|
||||||
log.Printf("Waiting %d ms for etcd...", t)
|
log.Printf("Etcd: Waiting %d ms for connection...", t)
|
||||||
time.Sleep(time.Duration(t) * time.Millisecond) // sleep for t ms
|
time.Sleep(time.Duration(t) * time.Millisecond) // sleep for t ms
|
||||||
|
|
||||||
|
} else if e.Error() == "unexpected EOF" {
|
||||||
|
log.Printf("Etcd: Disconnected...")
|
||||||
|
|
||||||
|
} else if strings.HasPrefix(e.Error(), "unsupported protocol scheme") {
|
||||||
|
// usually a bad peer endpoint value
|
||||||
|
log.Fatal("Bad peer endpoint value?")
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log.Fatal("Woops: ", e.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -114,7 +130,8 @@ func EtcdWatch(kapi etcd.KeysAPI, kick bool) chan string {
|
|||||||
// IOW, ignore everything except for the value or some
|
// IOW, ignore everything except for the value or some
|
||||||
// field which gets set last... this could be the max count field thing...
|
// field which gets set last... this could be the max count field thing...
|
||||||
|
|
||||||
ch <- resp.Node.Value // event
|
log.Printf("Etcd: Value: %v", resp.Node.Value) // event
|
||||||
|
ch <- etcdEvent // event
|
||||||
}
|
}
|
||||||
|
|
||||||
} // end for loop
|
} // end for loop
|
||||||
@@ -127,7 +144,7 @@ func EtcdWatch(kapi etcd.KeysAPI, kick bool) chan string {
|
|||||||
func EtcdPut(kapi etcd.KeysAPI, hostname, key, typ string, obj interface{}) bool {
|
func EtcdPut(kapi etcd.KeysAPI, hostname, key, typ string, obj interface{}) bool {
|
||||||
output, ok := ObjToB64(obj)
|
output, ok := ObjToB64(obj)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("Could not encode %v for etcd.", key)
|
log.Printf("Etcd: Could not encode %v key.", key)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +163,7 @@ func EtcdPut(kapi etcd.KeysAPI, hostname, key, typ string, obj interface{}) bool
|
|||||||
//if e == etcd.ErrClusterUnavailable
|
//if e == etcd.ErrClusterUnavailable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Printf("Could not store %v in etcd.", key)
|
log.Printf("Etcd: Could not store %v key.", key)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
log.Print("Etcd: ", resp) // w00t... bonus
|
log.Print("Etcd: ", resp) // w00t... bonus
|
||||||
@@ -155,7 +172,6 @@ func EtcdPut(kapi etcd.KeysAPI, hostname, key, typ string, obj interface{}) bool
|
|||||||
|
|
||||||
// lookup /exported/ node hierarchy
|
// lookup /exported/ node hierarchy
|
||||||
func EtcdGet(kapi etcd.KeysAPI) (etcd.Nodes, bool) {
|
func EtcdGet(kapi etcd.KeysAPI) (etcd.Nodes, bool) {
|
||||||
|
|
||||||
// key structure is /exported/<hostname>/types/...
|
// key structure is /exported/<hostname>/types/...
|
||||||
resp, err := kapi.Get(etcd_context.Background(), "/exported/", &etcd.GetOptions{Recursive: true})
|
resp, err := kapi.Get(etcd_context.Background(), "/exported/", &etcd.GetOptions{Recursive: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -165,7 +181,6 @@ func EtcdGet(kapi etcd.KeysAPI) (etcd.Nodes, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func EtcdGetProcess(nodes etcd.Nodes, typ string) []string {
|
func EtcdGetProcess(nodes etcd.Nodes, typ string) []string {
|
||||||
|
|
||||||
//path := fmt.Sprintf("/exported/%s/types/", h)
|
//path := fmt.Sprintf("/exported/%s/types/", h)
|
||||||
top := "/exported/"
|
top := "/exported/"
|
||||||
log.Printf("Etcd: Get: %+v", nodes) // Get().Nodes.Nodes
|
log.Printf("Etcd: Get: %+v", nodes) // Get().Nodes.Nodes
|
||||||
|
|||||||
3
event.go
3
event.go
@@ -24,11 +24,8 @@ const (
|
|||||||
eventExit eventName = iota
|
eventExit eventName = iota
|
||||||
eventStart
|
eventStart
|
||||||
eventPause
|
eventPause
|
||||||
eventContinue
|
|
||||||
eventPoke
|
eventPoke
|
||||||
eventChanged
|
eventChanged
|
||||||
//eventPaused
|
|
||||||
eventStarted
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
|
|||||||
@@ -5,22 +5,22 @@ types:
|
|||||||
- name: noop1
|
- name: noop1
|
||||||
file:
|
file:
|
||||||
- name: file1
|
- name: file1
|
||||||
path: /tmp/mgmt/f1
|
path: "/tmp/mgmt/f1"
|
||||||
content: |
|
content: |
|
||||||
i am f1
|
i am f1
|
||||||
state: exists
|
state: exists
|
||||||
- name: file2
|
- name: file2
|
||||||
path: /tmp/mgmt/f2
|
path: "/tmp/mgmt/f2"
|
||||||
content: |
|
content: |
|
||||||
i am f2
|
i am f2
|
||||||
state: exists
|
state: exists
|
||||||
- name: file3
|
- name: file3
|
||||||
path: /tmp/mgmt/f3
|
path: "/tmp/mgmt/f3"
|
||||||
content: |
|
content: |
|
||||||
i am f3
|
i am f3
|
||||||
state: exists
|
state: exists
|
||||||
- name: file4
|
- name: file4
|
||||||
path: /tmp/mgmt/f4
|
path: "/tmp/mgmt/f4"
|
||||||
content: |
|
content: |
|
||||||
i am f4 and i should not be here
|
i am f4 and i should not be here
|
||||||
state: absent
|
state: absent
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ types:
|
|||||||
- name: noop1
|
- name: noop1
|
||||||
file:
|
file:
|
||||||
- name: file1
|
- name: file1
|
||||||
path: /tmp/mgmt/f1
|
path: "/tmp/mgmt/f1"
|
||||||
content: |
|
content: |
|
||||||
i am f1
|
i am f1
|
||||||
state: exists
|
state: exists
|
||||||
|
|||||||
@@ -2,43 +2,43 @@
|
|||||||
graph: mygraph
|
graph: mygraph
|
||||||
types:
|
types:
|
||||||
noop:
|
noop:
|
||||||
- name: noop1
|
- name: noop1a
|
||||||
file:
|
file:
|
||||||
- name: file1
|
- name: file1a
|
||||||
path: /tmp/mgmt/f1
|
path: "/tmp/mgmt1/f1a"
|
||||||
content: |
|
content: |
|
||||||
i am f1
|
i am f1
|
||||||
state: exists
|
state: exists
|
||||||
- name: file2
|
- name: file2a
|
||||||
path: /tmp/mgmt/f2
|
path: "/tmp/mgmt1/f2a"
|
||||||
content: |
|
content: |
|
||||||
i am f2
|
i am f2
|
||||||
state: exists
|
state: exists
|
||||||
- name: '@@file3'
|
- name: "@@file3a"
|
||||||
path: /tmp/mgmt/f3
|
path: "/tmp/mgmt1/f3a"
|
||||||
content: |
|
content: |
|
||||||
i am f3, exported from host A
|
i am f3, exported from host A
|
||||||
state: exists
|
state: exists
|
||||||
- name: '@@file4'
|
- name: "@@file4a"
|
||||||
path: /tmp/mgmt/f4
|
path: "/tmp/mgmt1/f4a"
|
||||||
content: |
|
content: |
|
||||||
i am f4, exported from host A
|
i am f4, exported from host A
|
||||||
state: exists
|
state: exists
|
||||||
collect:
|
collect:
|
||||||
- type: file
|
- type: file
|
||||||
pattern: ''
|
pattern: "/tmp/mgmt1/"
|
||||||
edges:
|
edges:
|
||||||
- name: e1
|
- name: e1
|
||||||
from:
|
from:
|
||||||
type: noop
|
type: noop
|
||||||
name: noop1
|
name: noop1a
|
||||||
to:
|
to:
|
||||||
type: file
|
type: file
|
||||||
name: file1
|
name: file1a
|
||||||
- name: e2
|
- name: e2
|
||||||
from:
|
from:
|
||||||
type: file
|
type: file
|
||||||
name: file1
|
name: file1a
|
||||||
to:
|
to:
|
||||||
type: file
|
type: file
|
||||||
name: file2
|
name: file2a
|
||||||
|
|||||||
@@ -2,43 +2,43 @@
|
|||||||
graph: mygraph
|
graph: mygraph
|
||||||
types:
|
types:
|
||||||
noop:
|
noop:
|
||||||
- name: noop1
|
- name: noop1b
|
||||||
file:
|
file:
|
||||||
- name: file1
|
- name: file1b
|
||||||
path: /tmp/mgmt/f1
|
path: "/tmp/mgmt2/f1b"
|
||||||
content: |
|
content: |
|
||||||
i am f1
|
i am f1
|
||||||
state: exists
|
state: exists
|
||||||
- name: file2
|
- name: file2b
|
||||||
path: /tmp/mgmt/f2
|
path: "/tmp/mgmt2/f2b"
|
||||||
content: |
|
content: |
|
||||||
i am f2
|
i am f2
|
||||||
state: exists
|
state: exists
|
||||||
- name: '@@file3'
|
- name: "@@file3b"
|
||||||
path: /tmp/mgmt/f3
|
path: "/tmp/mgmt2/f3b"
|
||||||
content: |
|
content: |
|
||||||
i am f3, exported from host B
|
i am f3, exported from host B
|
||||||
state: exists
|
state: exists
|
||||||
- name: '@@file4'
|
- name: "@@file4b"
|
||||||
path: /tmp/mgmt/f4
|
path: "/tmp/mgmt2/f4b"
|
||||||
content: |
|
content: |
|
||||||
i am f4, exported from host B
|
i am f4, exported from host B
|
||||||
state: exists
|
state: exists
|
||||||
collect:
|
collect:
|
||||||
- type: file
|
- type: file
|
||||||
pattern: ''
|
pattern: "/tmp/mgmt2/"
|
||||||
edges:
|
edges:
|
||||||
- name: e1
|
- name: e1
|
||||||
from:
|
from:
|
||||||
type: noop
|
type: noop
|
||||||
name: noop1
|
name: noop1b
|
||||||
to:
|
to:
|
||||||
type: file
|
type: file
|
||||||
name: file1
|
name: file1b
|
||||||
- name: e2
|
- name: e2
|
||||||
from:
|
from:
|
||||||
type: file
|
type: file
|
||||||
name: file1
|
name: file1b
|
||||||
to:
|
to:
|
||||||
type: file
|
type: file
|
||||||
name: file2
|
name: file2b
|
||||||
|
|||||||
44
examples/graph3c.yaml
Normal file
44
examples/graph3c.yaml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
graph: mygraph
|
||||||
|
types:
|
||||||
|
noop:
|
||||||
|
- name: noop1c
|
||||||
|
file:
|
||||||
|
- name: file1c
|
||||||
|
path: "/tmp/mgmt3/f1c"
|
||||||
|
content: |
|
||||||
|
i am f1
|
||||||
|
state: exists
|
||||||
|
- name: file2c
|
||||||
|
path: "/tmp/mgmt3/f2c"
|
||||||
|
content: |
|
||||||
|
i am f2
|
||||||
|
state: exists
|
||||||
|
- name: "@@file3c"
|
||||||
|
path: "/tmp/mgmt3/f3c"
|
||||||
|
content: |
|
||||||
|
i am f3, exported from host C
|
||||||
|
state: exists
|
||||||
|
- name: "@@file4c"
|
||||||
|
path: "/tmp/mgmt3/f4c"
|
||||||
|
content: |
|
||||||
|
i am f4, exported from host C
|
||||||
|
state: exists
|
||||||
|
collect:
|
||||||
|
- type: file
|
||||||
|
pattern: "/tmp/mgmt3/"
|
||||||
|
edges:
|
||||||
|
- name: e1
|
||||||
|
from:
|
||||||
|
type: noop
|
||||||
|
name: noop1c
|
||||||
|
to:
|
||||||
|
type: file
|
||||||
|
name: file1c
|
||||||
|
- name: e2
|
||||||
|
from:
|
||||||
|
type: file
|
||||||
|
name: file1c
|
||||||
|
to:
|
||||||
|
type: file
|
||||||
|
name: file2c
|
||||||
@@ -3,12 +3,12 @@ graph: mygraph
|
|||||||
types:
|
types:
|
||||||
file:
|
file:
|
||||||
- name: file1
|
- name: file1
|
||||||
path: /tmp/mgmt/f1
|
path: "/tmp/mgmt/f1"
|
||||||
content: |
|
content: |
|
||||||
i am f1
|
i am f1
|
||||||
state: exists
|
state: exists
|
||||||
- name: '@@file3'
|
- name: "@@file3"
|
||||||
path: /tmp/mgmt/f3
|
path: "/tmp/mgmt/f3"
|
||||||
content: |
|
content: |
|
||||||
i am f3, exported from host A
|
i am f3, exported from host A
|
||||||
state: exists
|
state: exists
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ graph: mygraph
|
|||||||
types:
|
types:
|
||||||
file:
|
file:
|
||||||
- name: file1
|
- name: file1
|
||||||
path: /tmp/mgmt/f1
|
path: "/tmp/mgmt/f1"
|
||||||
content: |
|
content: |
|
||||||
i am f1
|
i am f1
|
||||||
state: exists
|
state: exists
|
||||||
|
|||||||
73
file.go
73
file.go
@@ -35,12 +35,14 @@ import (
|
|||||||
type FileType struct {
|
type FileType struct {
|
||||||
BaseType `yaml:",inline"`
|
BaseType `yaml:",inline"`
|
||||||
Path string `yaml:"path"` // path variable (should default to name)
|
Path string `yaml:"path"` // path variable (should default to name)
|
||||||
|
Dirname string `yaml:"dirname"`
|
||||||
|
Basename string `yaml:"basename"`
|
||||||
Content string `yaml:"content"`
|
Content string `yaml:"content"`
|
||||||
State string `yaml:"state"` // state: exists/present?, absent, (undefined?)
|
State string `yaml:"state"` // state: exists/present?, absent, (undefined?)
|
||||||
sha256sum string
|
sha256sum string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileType(name, path, content, state string) *FileType {
|
func NewFileType(name, path, dirname, basename, content, state string) *FileType {
|
||||||
// FIXME if path = nil, path = name ...
|
// FIXME if path = nil, path = name ...
|
||||||
return &FileType{
|
return &FileType{
|
||||||
BaseType: BaseType{
|
BaseType: BaseType{
|
||||||
@@ -49,6 +51,8 @@ func NewFileType(name, path, content, state string) *FileType {
|
|||||||
vertex: nil,
|
vertex: nil,
|
||||||
},
|
},
|
||||||
Path: path,
|
Path: path,
|
||||||
|
Dirname: dirname,
|
||||||
|
Basename: basename,
|
||||||
Content: content,
|
Content: content,
|
||||||
State: state,
|
State: state,
|
||||||
sha256sum: "",
|
sha256sum: "",
|
||||||
@@ -59,15 +63,52 @@ func (obj *FileType) GetType() string {
|
|||||||
return "File"
|
return "File"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validate if the params passed in are valid data
|
||||||
|
func (obj *FileType) Validate() bool {
|
||||||
|
if obj.Dirname != "" {
|
||||||
|
// must end with /
|
||||||
|
if obj.Dirname[len(obj.Dirname)-1:] != "/" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if obj.Basename != "" {
|
||||||
|
// must not start with /
|
||||||
|
if obj.Basename[0:1] == "/" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *FileType) GetPath() string {
|
||||||
|
d := Dirname(obj.Path)
|
||||||
|
b := Basename(obj.Path)
|
||||||
|
if !obj.Validate() || (obj.Dirname == "" && obj.Basename == "") {
|
||||||
|
return obj.Path
|
||||||
|
} else if obj.Dirname == "" {
|
||||||
|
return d + obj.Basename
|
||||||
|
} else if obj.Basename == "" {
|
||||||
|
return obj.Dirname + b
|
||||||
|
} else { // if obj.dirname != "" && obj.basename != "" {
|
||||||
|
return obj.Dirname + obj.Basename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// File watcher for files and directories
|
// File watcher for files and directories
|
||||||
// Modify with caution, probably important to write some test cases first!
|
// Modify with caution, probably important to write some test cases first!
|
||||||
// obj.Path: file or directory
|
// obj.GetPath(): file or directory
|
||||||
func (obj *FileType) Watch() {
|
func (obj *FileType) Watch() {
|
||||||
|
if obj.IsWatching() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
obj.SetWatching(true)
|
||||||
|
defer obj.SetWatching(false)
|
||||||
|
|
||||||
//var recursive bool = false
|
//var recursive bool = false
|
||||||
//var isdir = (obj.Path[len(obj.Path)-1:] == "/") // dirs have trailing slashes
|
//var isdir = (obj.GetPath()[len(obj.GetPath())-1:] == "/") // dirs have trailing slashes
|
||||||
//fmt.Printf("IsDirectory: %v\n", isdir)
|
//fmt.Printf("IsDirectory: %v\n", isdir)
|
||||||
//vertex := obj.GetVertex() // stored with SetVertex
|
//vertex := obj.GetVertex() // stored with SetVertex
|
||||||
var safename = path.Clean(obj.Path) // no trailing slash
|
var safename = path.Clean(obj.GetPath()) // no trailing slash
|
||||||
|
|
||||||
watcher, err := fsnotify.NewWatcher()
|
watcher, err := fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -204,7 +245,7 @@ func (obj *FileType) HashSHA256fromContent() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (obj *FileType) StateOK() bool {
|
func (obj *FileType) StateOK() bool {
|
||||||
if _, err := os.Stat(obj.Path); os.IsNotExist(err) {
|
if _, err := os.Stat(obj.GetPath()); os.IsNotExist(err) {
|
||||||
// no such file or directory
|
// no such file or directory
|
||||||
if obj.State == "absent" {
|
if obj.State == "absent" {
|
||||||
return true // missing file should be missing, phew :)
|
return true // missing file should be missing, phew :)
|
||||||
@@ -216,7 +257,7 @@ func (obj *FileType) StateOK() bool {
|
|||||||
|
|
||||||
// TODO: add file mode check here...
|
// TODO: add file mode check here...
|
||||||
|
|
||||||
if PathIsDir(obj.Path) {
|
if PathIsDir(obj.GetPath()) {
|
||||||
return obj.StateOKDir()
|
return obj.StateOKDir()
|
||||||
} else {
|
} else {
|
||||||
return obj.StateOKFile()
|
return obj.StateOKFile()
|
||||||
@@ -224,7 +265,7 @@ func (obj *FileType) StateOK() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (obj *FileType) StateOKFile() bool {
|
func (obj *FileType) StateOKFile() bool {
|
||||||
if PathIsDir(obj.Path) {
|
if PathIsDir(obj.GetPath()) {
|
||||||
log.Fatal("This should only be called on a File type.")
|
log.Fatal("This should only be called on a File type.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +273,7 @@ func (obj *FileType) StateOKFile() bool {
|
|||||||
|
|
||||||
hash := sha256.New()
|
hash := sha256.New()
|
||||||
|
|
||||||
f, err := os.Open(obj.Path)
|
f, err := os.Open(obj.GetPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//log.Fatal(err)
|
//log.Fatal(err)
|
||||||
return false
|
return false
|
||||||
@@ -255,7 +296,7 @@ func (obj *FileType) StateOKFile() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (obj *FileType) StateOKDir() bool {
|
func (obj *FileType) StateOKDir() bool {
|
||||||
if !PathIsDir(obj.Path) {
|
if !PathIsDir(obj.GetPath()) {
|
||||||
log.Fatal("This should only be called on a Dir type.")
|
log.Fatal("This should only be called on a Dir type.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,7 +308,7 @@ func (obj *FileType) StateOKDir() bool {
|
|||||||
func (obj *FileType) Apply() bool {
|
func (obj *FileType) Apply() bool {
|
||||||
fmt.Printf("Apply->File[%v]\n", obj.Name)
|
fmt.Printf("Apply->File[%v]\n", obj.Name)
|
||||||
|
|
||||||
if PathIsDir(obj.Path) {
|
if PathIsDir(obj.GetPath()) {
|
||||||
return obj.ApplyDir()
|
return obj.ApplyDir()
|
||||||
} else {
|
} else {
|
||||||
return obj.ApplyFile()
|
return obj.ApplyFile()
|
||||||
@@ -276,13 +317,13 @@ func (obj *FileType) Apply() bool {
|
|||||||
|
|
||||||
func (obj *FileType) ApplyFile() bool {
|
func (obj *FileType) ApplyFile() bool {
|
||||||
|
|
||||||
if PathIsDir(obj.Path) {
|
if PathIsDir(obj.GetPath()) {
|
||||||
log.Fatal("This should only be called on a File type.")
|
log.Fatal("This should only be called on a File type.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if obj.State == "absent" {
|
if obj.State == "absent" {
|
||||||
log.Printf("About to remove: %v\n", obj.Path)
|
log.Printf("About to remove: %v\n", obj.GetPath())
|
||||||
err := os.Remove(obj.Path)
|
err := os.Remove(obj.GetPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -290,7 +331,7 @@ func (obj *FileType) ApplyFile() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//fmt.Println("writing: " + filename)
|
//fmt.Println("writing: " + filename)
|
||||||
f, err := os.Create(obj.Path)
|
f, err := os.Create(obj.GetPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("error:", err)
|
log.Println("error:", err)
|
||||||
return false
|
return false
|
||||||
@@ -307,7 +348,7 @@ func (obj *FileType) ApplyFile() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (obj *FileType) ApplyDir() bool {
|
func (obj *FileType) ApplyDir() bool {
|
||||||
if !PathIsDir(obj.Path) {
|
if !PathIsDir(obj.GetPath()) {
|
||||||
log.Fatal("This should only be called on a Dir type.")
|
log.Fatal("This should only be called on a Dir type.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,7 +370,7 @@ func (obj *FileType) compare(typ *FileType) bool {
|
|||||||
if obj.Name != typ.Name {
|
if obj.Name != typ.Name {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if obj.Path != typ.Path {
|
if obj.GetPath() != typ.Path {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if obj.Content != typ.Content {
|
if obj.Content != typ.Content {
|
||||||
|
|||||||
81
main.go
81
main.go
@@ -74,21 +74,61 @@ func run(c *cli.Context) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initial etcd peer endpoint
|
||||||
|
seed := c.String("seed")
|
||||||
|
if seed == "" {
|
||||||
|
// XXX: start up etcd server, others will join me!
|
||||||
|
seed = "http://127.0.0.1:2379" // thus we use the local server!
|
||||||
|
}
|
||||||
|
// then, connect to `seed` as a client
|
||||||
|
|
||||||
|
// FIXME: validate seed, or wait for it to fail in etcd init?
|
||||||
|
|
||||||
// etcd
|
// etcd
|
||||||
hostname := c.String("hostname")
|
hostname := c.String("hostname")
|
||||||
if hostname == "" {
|
if hostname == "" {
|
||||||
hostname, _ = os.Hostname() // etcd watch key // XXX: this is not the correct key name this is the set key name... WOOPS
|
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) {
|
go func() {
|
||||||
|
startchan := make(chan struct{}) // start signal
|
||||||
|
go func() { startchan <- struct{}{} }()
|
||||||
|
file := c.String("file")
|
||||||
|
configchan := ConfigWatch(file)
|
||||||
log.Printf("Starting etcd...\n")
|
log.Printf("Starting etcd...\n")
|
||||||
kapi := EtcdGetKAPI()
|
kapi := EtcdGetKAPI(seed)
|
||||||
|
etcdchan := EtcdWatch(kapi)
|
||||||
first := true // first loop or not
|
first := true // first loop or not
|
||||||
for x := range EtcdWatch(kapi, true) {
|
for {
|
||||||
|
select {
|
||||||
|
case _ = <-startchan: // kick the loop once at start
|
||||||
|
// pass
|
||||||
|
case msg := <-etcdchan:
|
||||||
|
switch msg {
|
||||||
|
// some types of messages we ignore...
|
||||||
|
case etcdFoo, etcdBar:
|
||||||
|
continue
|
||||||
|
// while others passthrough and cause a compile!
|
||||||
|
case etcdStart, etcdEvent:
|
||||||
|
// pass
|
||||||
|
default:
|
||||||
|
log.Fatal("Etcd: Unhandled message: %v", msg)
|
||||||
|
}
|
||||||
|
case msg := <-configchan:
|
||||||
|
if c.Bool("no-watch") || !msg {
|
||||||
|
continue // not ready to read config
|
||||||
|
}
|
||||||
|
|
||||||
|
//case compile_event: XXX
|
||||||
|
}
|
||||||
|
|
||||||
|
config := ParseConfigFromFile(file)
|
||||||
|
if config == nil {
|
||||||
|
log.Printf("Config parse failure")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// run graph vertex LOCK...
|
// run graph vertex LOCK...
|
||||||
if !first {
|
if !first { // XXX: we can flatten this check out I think
|
||||||
log.Printf("Watcher().Node.Value(%v): %+v", hostname, x)
|
|
||||||
|
|
||||||
G.SetState(graphPausing)
|
G.SetState(graphPausing)
|
||||||
log.Printf("State: %v", G.State())
|
log.Printf("State: %v", G.State())
|
||||||
G.Pause() // sync
|
G.Pause() // sync
|
||||||
@@ -97,10 +137,8 @@ func run(c *cli.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// build the graph from a config file
|
// build the graph from a config file
|
||||||
// build the graph on events (eg: from etcd) but kick it once...
|
// build the graph on events (eg: from etcd)
|
||||||
if !UpdateGraphFromConfig(c.String("file"), hostname, G, kapi) {
|
UpdateGraphFromConfig(config, hostname, G, kapi)
|
||||||
log.Fatal("Graph failure")
|
|
||||||
}
|
|
||||||
log.Printf("Graph: %v\n", G) // show graph
|
log.Printf("Graph: %v\n", G) // show graph
|
||||||
err := G.ExecGraphviz(c.String("graphviz-filter"), c.String("graphviz"))
|
err := G.ExecGraphviz(c.String("graphviz-filter"), c.String("graphviz"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -109,7 +147,6 @@ func run(c *cli.Context) {
|
|||||||
log.Printf("Graphviz: Successfully generated graph!")
|
log.Printf("Graphviz: Successfully generated graph!")
|
||||||
}
|
}
|
||||||
G.SetVertex()
|
G.SetVertex()
|
||||||
if first {
|
|
||||||
// G.Start(...) needs to be synchronous or wait,
|
// G.Start(...) needs to be synchronous or wait,
|
||||||
// because if half of the nodes are started and
|
// because if half of the nodes are started and
|
||||||
// some are not ready yet and the EtcdWatch
|
// some are not ready yet and the EtcdWatch
|
||||||
@@ -117,21 +154,13 @@ func run(c *cli.Context) {
|
|||||||
// even got going, thus causing nil pointer errors
|
// even got going, thus causing nil pointer errors
|
||||||
G.SetState(graphStarting)
|
G.SetState(graphStarting)
|
||||||
log.Printf("State: %v", G.State())
|
log.Printf("State: %v", G.State())
|
||||||
G.Start(&wg)
|
G.Start(&wg) // sync
|
||||||
G.SetState(graphStarted)
|
G.SetState(graphStarted)
|
||||||
log.Printf("State: %v", G.State())
|
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
|
first = false
|
||||||
}
|
}
|
||||||
}(hostname)
|
}()
|
||||||
|
|
||||||
log.Println("Running...")
|
log.Println("Running...")
|
||||||
|
|
||||||
@@ -175,6 +204,10 @@ func main() {
|
|||||||
Value: "",
|
Value: "",
|
||||||
Usage: "graph definition to run",
|
Usage: "graph definition to run",
|
||||||
},
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "no-watch",
|
||||||
|
Usage: "do not update graph on watched graph definition file changes",
|
||||||
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "code, c",
|
Name: "code, c",
|
||||||
Value: "",
|
Value: "",
|
||||||
@@ -196,6 +229,12 @@ func main() {
|
|||||||
Value: "",
|
Value: "",
|
||||||
Usage: "hostname to use",
|
Usage: "hostname to use",
|
||||||
},
|
},
|
||||||
|
// if empty, it will startup a new server
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "seed, s",
|
||||||
|
Value: "",
|
||||||
|
Usage: "default etc peer endpoint",
|
||||||
|
},
|
||||||
cli.IntFlag{
|
cli.IntFlag{
|
||||||
Name: "exittime",
|
Name: "exittime",
|
||||||
Value: 0,
|
Value: 0,
|
||||||
|
|||||||
11
misc.go
11
misc.go
@@ -27,10 +27,21 @@ import (
|
|||||||
|
|
||||||
// Similar to the GNU dirname command
|
// Similar to the GNU dirname command
|
||||||
func Dirname(p string) string {
|
func Dirname(p string) string {
|
||||||
|
if p == "/" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
d, _ := path.Split(path.Clean(p))
|
d, _ := path.Split(path.Clean(p))
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Basename(p string) string {
|
||||||
|
_, b := path.Split(path.Clean(p))
|
||||||
|
if p[len(p)-1:] == "/" { // don't loose the tail slash
|
||||||
|
b += "/"
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
// Split a path into an array of tokens excluding any trailing empty tokens
|
// Split a path into an array of tokens excluding any trailing empty tokens
|
||||||
func PathSplit(p string) []string {
|
func PathSplit(p string) []string {
|
||||||
return strings.Split(path.Clean(p), "/")
|
return strings.Split(path.Clean(p), "/")
|
||||||
|
|||||||
15
misc_test.go
15
misc_test.go
@@ -32,9 +32,22 @@ func TestMiscT1(t *testing.T) {
|
|||||||
t.Errorf("Result is incorrect.")
|
t.Errorf("Result is incorrect.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if Dirname("/") != "/" {
|
if Dirname("/") != "" { // TODO: should this equal "/" or "" ?
|
||||||
t.Errorf("Result is incorrect.")
|
t.Errorf("Result is incorrect.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if Basename("/foo/bar/baz") != "baz" {
|
||||||
|
t.Errorf("Result is incorrect.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if Basename("/foo/bar/baz/") != "baz/" {
|
||||||
|
t.Errorf("Result is incorrect.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if Basename("/") != "/" { // TODO: should this equal "" or "/" ?
|
||||||
|
t.Errorf("Result is incorrect.")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMiscT2(t *testing.T) {
|
func TestMiscT2(t *testing.T) {
|
||||||
|
|||||||
3
omv.yaml
3
omv.yaml
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
:domain: example.com
|
:domain: example.com
|
||||||
:network: 192.168.123.0/24
|
:network: 192.168.123.0/24
|
||||||
:image: centos-7.1
|
:image: fedora-23
|
||||||
:cpus: ''
|
:cpus: ''
|
||||||
:memory: ''
|
:memory: ''
|
||||||
:disks: 0
|
:disks: 0
|
||||||
@@ -34,5 +34,6 @@
|
|||||||
:reboot: false
|
:reboot: false
|
||||||
:unsafe: false
|
:unsafe: false
|
||||||
:nested: false
|
:nested: false
|
||||||
|
:tests: []
|
||||||
:comment: ''
|
:comment: ''
|
||||||
:reallyrm: false
|
:reallyrm: false
|
||||||
|
|||||||
12
pgraph.go
12
pgraph.go
@@ -39,7 +39,6 @@ const (
|
|||||||
graphStarted
|
graphStarted
|
||||||
graphPausing
|
graphPausing
|
||||||
graphPaused
|
graphPaused
|
||||||
graphContinuing
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// The graph abstract data type (ADT) is defined as follows:
|
// The graph abstract data type (ADT) is defined as follows:
|
||||||
@@ -538,10 +537,11 @@ func HeisenbergCount(ch chan *Vertex) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// main kick to start the graph
|
// main kick to start the graph
|
||||||
func (g *Graph) Start(wg *sync.WaitGroup) {
|
func (g *Graph) Start(wg *sync.WaitGroup) { // start or continue
|
||||||
t, _ := g.TopologicalSort()
|
t, _ := g.TopologicalSort()
|
||||||
for _, v := range Reverse(t) {
|
for _, v := range Reverse(t) {
|
||||||
|
|
||||||
|
if !v.Type.IsWatching() { // if Watch() is not running...
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
// must pass in value to avoid races...
|
// must pass in value to avoid races...
|
||||||
// see: https://ttboj.wordpress.com/2015/07/27/golang-parallelism-issues-causing-too-many-open-files-error/
|
// see: https://ttboj.wordpress.com/2015/07/27/golang-parallelism-issues-causing-too-many-open-files-error/
|
||||||
@@ -550,6 +550,7 @@ func (g *Graph) Start(wg *sync.WaitGroup) {
|
|||||||
vv.Type.Watch()
|
vv.Type.Watch()
|
||||||
log.Printf("Finish: %v", vv.GetName())
|
log.Printf("Finish: %v", vv.GetName())
|
||||||
}(v)
|
}(v)
|
||||||
|
}
|
||||||
|
|
||||||
// ensure state is started before continuing on to next vertex
|
// ensure state is started before continuing on to next vertex
|
||||||
v.Type.SendEvent(eventStart, true)
|
v.Type.SendEvent(eventStart, true)
|
||||||
@@ -557,13 +558,6 @@ func (g *Graph) Start(wg *sync.WaitGroup) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Graph) Continue() {
|
|
||||||
t, _ := g.TopologicalSort()
|
|
||||||
for _, v := range Reverse(t) {
|
|
||||||
v.Type.SendEvent(eventContinue, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Graph) Pause() {
|
func (g *Graph) Pause() {
|
||||||
t, _ := g.TopologicalSort()
|
t, _ := g.TopologicalSort()
|
||||||
for _, v := range t { // squeeze out the events...
|
for _, v := range t { // squeeze out the events...
|
||||||
|
|||||||
@@ -51,6 +51,12 @@ func (obj *ServiceType) GetType() string {
|
|||||||
|
|
||||||
// Service watcher
|
// Service watcher
|
||||||
func (obj *ServiceType) Watch() {
|
func (obj *ServiceType) Watch() {
|
||||||
|
if obj.IsWatching() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
obj.SetWatching(true)
|
||||||
|
defer obj.SetWatching(false)
|
||||||
|
|
||||||
// obj.Name: service name
|
// obj.Name: service name
|
||||||
//vertex := obj.GetVertex() // stored with SetVertex
|
//vertex := obj.GetVertex() // stored with SetVertex
|
||||||
if !util.IsRunningSystemd() {
|
if !util.IsRunningSystemd() {
|
||||||
|
|||||||
21
types.go
21
types.go
@@ -33,6 +33,8 @@ type Type interface {
|
|||||||
SetVertex(*Vertex)
|
SetVertex(*Vertex)
|
||||||
Compare(Type) bool
|
Compare(Type) bool
|
||||||
SendEvent(eventName, bool)
|
SendEvent(eventName, bool)
|
||||||
|
IsWatching() bool
|
||||||
|
SetWatching(bool)
|
||||||
GetTimestamp() int64
|
GetTimestamp() int64
|
||||||
UpdateTimestamp() int64
|
UpdateTimestamp() int64
|
||||||
//Process()
|
//Process()
|
||||||
@@ -43,6 +45,7 @@ type BaseType struct {
|
|||||||
timestamp int64 // last updated timestamp ?
|
timestamp int64 // last updated timestamp ?
|
||||||
events chan Event
|
events chan Event
|
||||||
vertex *Vertex
|
vertex *Vertex
|
||||||
|
watching bool // is Watch() loop running ?
|
||||||
}
|
}
|
||||||
|
|
||||||
type NoopType struct {
|
type NoopType struct {
|
||||||
@@ -84,6 +87,16 @@ func (obj *BaseType) SetVertex(v *Vertex) {
|
|||||||
obj.vertex = v
|
obj.vertex = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// is the Watch() function running?
|
||||||
|
func (obj *BaseType) IsWatching() bool {
|
||||||
|
return obj.watching
|
||||||
|
}
|
||||||
|
|
||||||
|
// store status of if the Watch() function is running
|
||||||
|
func (obj *BaseType) SetWatching(b bool) {
|
||||||
|
obj.watching = b
|
||||||
|
}
|
||||||
|
|
||||||
// get timestamp of a vertex
|
// get timestamp of a vertex
|
||||||
func (obj *BaseType) GetTimestamp() int64 {
|
func (obj *BaseType) GetTimestamp() int64 {
|
||||||
return obj.timestamp
|
return obj.timestamp
|
||||||
@@ -160,7 +173,7 @@ func (obj *BaseType) ReadEvent(event *Event) bool {
|
|||||||
e.ACK()
|
e.ACK()
|
||||||
if e.Name == eventExit {
|
if e.Name == eventExit {
|
||||||
return false
|
return false
|
||||||
} else if e.Name == eventContinue {
|
} else if e.Name == eventStart { // eventContinue
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
log.Fatal("Unknown event: ", e)
|
log.Fatal("Unknown event: ", e)
|
||||||
@@ -208,6 +221,12 @@ func (obj *NoopType) GetType() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (obj *NoopType) Watch() {
|
func (obj *NoopType) Watch() {
|
||||||
|
if obj.IsWatching() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
obj.SetWatching(true)
|
||||||
|
defer obj.SetWatching(false)
|
||||||
|
|
||||||
//vertex := obj.vertex // stored with SetVertex
|
//vertex := obj.vertex // stored with SetVertex
|
||||||
var send = false // send event?
|
var send = false // send event?
|
||||||
for {
|
for {
|
||||||
|
|||||||
Reference in New Issue
Block a user