Rename type to resource (res) and service to svc

Naming the resources "type" was a stupid mistake, and is a huge source
of confusion when also talking about real types. Fix this before it gets
out of hand.
This commit is contained in:
James Shubin
2016-02-21 15:45:05 -05:00
parent d20b529508
commit 3a85384377
37 changed files with 446 additions and 446 deletions

View File

@@ -3,7 +3,7 @@ If you're looking for something to do, look here!
Let us know if you're working on one of the items.
## Package resource
- [ ] base type [bug](https://github.com/purpleidea/mgmt/issues/11)
- [ ] base resource [bug](https://github.com/purpleidea/mgmt/issues/11)
- [ ] dnf blocker [bug](https://github.com/hughsie/PackageKit/issues/110)
- [ ] install signal blocker [bug](https://github.com/hughsie/PackageKit/issues/109)

View File

@@ -25,13 +25,13 @@ import (
"strings"
)
type collectorTypeConfig struct {
Type string `yaml:"type"`
type collectorResConfig struct {
Res string `yaml:"res"`
Pattern string `yaml:"pattern"` // XXX: Not Implemented
}
type vertexConfig struct {
Type string `yaml:"type"`
Res string `yaml:"res"`
Name string `yaml:"name"`
}
@@ -43,13 +43,13 @@ type edgeConfig struct {
type GraphConfig struct {
Graph string `yaml:"graph"`
Types struct {
Noop []NoopType `yaml:"noop"`
File []FileType `yaml:"file"`
Service []ServiceType `yaml:"service"`
Exec []ExecType `yaml:"exec"`
} `yaml:"types"`
Collector []collectorTypeConfig `yaml:"collect"`
Resources struct {
Noop []NoopRes `yaml:"noop"`
File []FileRes `yaml:"file"`
Svc []SvcRes `yaml:"svc"`
Exec []ExecRes `yaml:"exec"`
} `yaml:"resources"`
Collector []collectorResConfig `yaml:"collect"`
Edges []edgeConfig `yaml:"edges"`
Comment string `yaml:"comment"`
}
@@ -95,13 +95,13 @@ func UpdateGraphFromConfig(config *GraphConfig, hostname string, g *Graph, etcdO
var NoopMap = make(map[string]*Vertex)
var FileMap = make(map[string]*Vertex)
var ServiceMap = make(map[string]*Vertex)
var SvcMap = make(map[string]*Vertex)
var ExecMap = make(map[string]*Vertex)
var lookup = make(map[string]map[string]*Vertex)
lookup["noop"] = NoopMap
lookup["file"] = FileMap
lookup["service"] = ServiceMap
lookup["svc"] = SvcMap
lookup["exec"] = ExecMap
//log.Printf("%+v", config) // debug
@@ -110,8 +110,8 @@ func UpdateGraphFromConfig(config *GraphConfig, hostname string, g *Graph, etcdO
var keep []*Vertex // list of vertex which are the same in new graph
for _, t := range config.Types.Noop {
obj := NewNoopType(t.Name)
for _, t := range config.Resources.Noop {
obj := NewNoopRes(t.Name)
v := g.GetVertexMatch(obj)
if v == nil { // no match found
v = NewVertex(obj)
@@ -121,7 +121,7 @@ func UpdateGraphFromConfig(config *GraphConfig, hostname string, g *Graph, etcdO
keep = append(keep, v) // append
}
for _, t := range config.Types.File {
for _, t := range config.Resources.File {
// XXX: should we export based on a @@ prefix, or a metaparam
// like exported => true || exported => (host pattern)||(other pattern?)
if strings.HasPrefix(t.Name, "@@") { // exported resource
@@ -132,7 +132,7 @@ func UpdateGraphFromConfig(config *GraphConfig, hostname string, g *Graph, etcdO
continue
}
} else {
obj := NewFileType(t.Name, t.Path, t.Dirname, t.Basename, t.Content, t.State)
obj := NewFileRes(t.Name, t.Path, t.Dirname, t.Basename, t.Content, t.State)
v := g.GetVertexMatch(obj)
if v == nil { // no match found
v = NewVertex(obj)
@@ -143,19 +143,19 @@ func UpdateGraphFromConfig(config *GraphConfig, hostname string, g *Graph, etcdO
}
}
for _, t := range config.Types.Service {
obj := NewServiceType(t.Name, t.State, t.Startup)
for _, t := range config.Resources.Svc {
obj := NewSvcRes(t.Name, t.State, t.Startup)
v := g.GetVertexMatch(obj)
if v == nil { // no match found
v = NewVertex(obj)
g.AddVertex(v) // call standalone in case not part of an edge
}
ServiceMap[obj.Name] = v // used for constructing edges
SvcMap[obj.Name] = v // used for constructing edges
keep = append(keep, v) // append
}
for _, t := range config.Types.Exec {
obj := NewExecType(t.Name, t.Cmd, t.Shell, t.Timeout, t.WatchCmd, t.WatchShell, t.IfCmd, t.IfShell, t.PollInt, t.State)
for _, t := range config.Resources.Exec {
obj := NewExecRes(t.Name, t.Cmd, t.Shell, t.Timeout, t.WatchCmd, t.WatchShell, t.IfCmd, t.IfShell, t.PollInt, t.State)
v := g.GetVertexMatch(obj)
if v == nil { // no match found
v = NewVertex(obj)
@@ -171,11 +171,11 @@ func UpdateGraphFromConfig(config *GraphConfig, hostname string, g *Graph, etcdO
nodes, ok := etcdO.EtcdGet()
if ok {
for _, t := range config.Collector {
// XXX: use t.Type and optionally t.Pattern to collect from etcd storage
log.Printf("Collect: %v; Pattern: %v", t.Type, t.Pattern)
// XXX: use t.Res and optionally t.Pattern to collect from etcd storage
log.Printf("Collect: %v; Pattern: %v", t.Res, t.Pattern)
for _, x := range etcdO.EtcdGetProcess(nodes, "file") {
var obj *FileType
var obj *FileRes
if B64ToObj(x, &obj) != true {
log.Printf("Collect: File: %v not collected!", x)
continue
@@ -205,25 +205,25 @@ func UpdateGraphFromConfig(config *GraphConfig, hostname string, g *Graph, etcdO
for _, v := range g.GetVertices() {
if !HasVertex(v, keep) {
// wait for exit before starting new graph!
v.Type.SendEvent(eventExit, true, false)
v.Res.SendEvent(eventExit, true, false)
g.DeleteVertex(v)
}
}
for _, e := range config.Edges {
if _, ok := lookup[e.From.Type]; !ok {
if _, ok := lookup[e.From.Res]; !ok {
return false
}
if _, ok := lookup[e.To.Type]; !ok {
if _, ok := lookup[e.To.Res]; !ok {
return false
}
if _, ok := lookup[e.From.Type][e.From.Name]; !ok {
if _, ok := lookup[e.From.Res][e.From.Name]; !ok {
return false
}
if _, ok := lookup[e.To.Type][e.To.Name]; !ok {
if _, ok := lookup[e.To.Res][e.To.Name]; !ok {
return false
}
g.AddEdge(lookup[e.From.Type][e.From.Name], lookup[e.To.Type][e.To.Name], NewEdge(e.Name))
g.AddEdge(lookup[e.From.Res][e.From.Name], lookup[e.To.Res][e.To.Name], NewEdge(e.Name))
}
return true
}

View File

@@ -27,7 +27,7 @@ import (
"syscall"
)
// XXX: it would be great if we could reuse code between this and the file type
// XXX: it would be great if we could reuse code between this and the file resource
// 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)

30
etcd.go
View File

@@ -191,8 +191,8 @@ func (etcdO *EtcdWObject) EtcdWatch() chan etcdMsg {
}
}
// FIXME: we get events on key/type/value changes for
// each type directory... ignore the non final ones...
// FIXME: we get events on key/res/value changes for
// each res directory... ignore the non final ones...
// IOW, ignore everything except for the value or some
// field which gets set last... this could be the max count field thing...
@@ -207,7 +207,7 @@ func (etcdO *EtcdWObject) EtcdWatch() chan etcdMsg {
}
// helper function to store our data in etcd
func (etcdO *EtcdWObject) EtcdPut(hostname, key, typ string, obj interface{}) bool {
func (etcdO *EtcdWObject) EtcdPut(hostname, key, res string, obj interface{}) bool {
kapi := etcdO.GetKAPI()
output, ok := ObjToB64(obj)
if !ok {
@@ -215,11 +215,11 @@ func (etcdO *EtcdWObject) EtcdPut(hostname, key, typ string, obj interface{}) bo
return false
}
path := fmt.Sprintf("/exported/%s/types/%s/type", hostname, key)
_, err := kapi.Set(etcd_context.Background(), path, typ, nil)
path := fmt.Sprintf("/exported/%s/resources/%s/res", hostname, key)
_, err := kapi.Set(etcd_context.Background(), path, res, nil)
// XXX validate...
path = fmt.Sprintf("/exported/%s/types/%s/value", hostname, key)
path = fmt.Sprintf("/exported/%s/resources/%s/value", hostname, key)
resp, err := kapi.Set(etcd_context.Background(), path, output, nil)
if err != nil {
if cerr, ok := err.(*etcd.ClusterError); ok {
@@ -240,7 +240,7 @@ func (etcdO *EtcdWObject) EtcdPut(hostname, key, typ string, obj interface{}) bo
// lookup /exported/ node hierarchy
func (etcdO *EtcdWObject) EtcdGet() (etcd.Nodes, bool) {
kapi := etcdO.GetKAPI()
// key structure is /exported/<hostname>/types/...
// key structure is /exported/<hostname>/resources/...
resp, err := kapi.Get(etcd_context.Background(), "/exported/", &etcd.GetOptions{Recursive: true})
if err != nil {
return nil, false // not found
@@ -248,8 +248,8 @@ func (etcdO *EtcdWObject) EtcdGet() (etcd.Nodes, bool) {
return resp.Node.Nodes, true
}
func (etcdO *EtcdWObject) EtcdGetProcess(nodes etcd.Nodes, typ string) []string {
//path := fmt.Sprintf("/exported/%s/types/", h)
func (etcdO *EtcdWObject) EtcdGetProcess(nodes etcd.Nodes, res string) []string {
//path := fmt.Sprintf("/exported/%s/resources/", h)
top := "/exported/"
log.Printf("Etcd: Get: %+v", nodes) // Get().Nodes.Nodes
var output []string
@@ -261,20 +261,20 @@ func (etcdO *EtcdWObject) EtcdGetProcess(nodes etcd.Nodes, typ string) []string
host := x.Key[len(top):]
//log.Printf("Get().Nodes[%v]: %+v ==> %+v", -1, host, x.Nodes)
//log.Printf("Get().Nodes[%v]: %+v ==> %+v", i, x.Key, x.Nodes)
types, ok := EtcdGetChildNodeByKey(x, "types")
resources, ok := EtcdGetChildNodeByKey(x, "resources")
if !ok {
continue
}
for _, y := range types.Nodes { // loop through types
for _, y := range resources.Nodes { // loop through resources
//key := y.Key # UUID?
//log.Printf("Get(%v): TYPE[%v]", host, y.Key)
t, ok := EtcdGetChildNodeByKey(y, "type")
//log.Printf("Get(%v): RES[%v]", host, y.Key)
t, ok := EtcdGetChildNodeByKey(y, "res")
if !ok {
continue
}
if typ != "" && typ != t.Value {
if res != "" && res != t.Value {
continue
} // filter based on type
} // filter based on res
v, ok := EtcdGetChildNodeByKey(y, "value") // B64ToObj this
if !ok {

View File

@@ -1,7 +1,7 @@
---
graph: mygraph
comment: hello world example
types:
resources:
noop:
- name: noop1
file:
@@ -13,8 +13,8 @@ types:
edges:
- name: e1
from:
type: noop
res: noop
name: noop1
to:
type: file
res: file
name: file1

View File

@@ -1,6 +1,6 @@
---
graph: mygraph
types:
resources:
noop:
- name: noop1
file:
@@ -27,15 +27,15 @@ types:
edges:
- name: e1
from:
type: file
res: file
name: file1
to:
type: file
res: file
name: file2
- name: e2
from:
type: file
res: file
name: file2
to:
type: file
res: file
name: file3

View File

@@ -1,7 +1,7 @@
---
graph: mygraph
comment: simple exec fan in to fan out example to demonstrate optimization
types:
resources:
exec:
- name: exec1
cmd: sleep 10s
@@ -86,43 +86,43 @@ types:
edges:
- name: e1
from:
type: exec
res: exec
name: exec1
to:
type: exec
res: exec
name: exec4
- name: e2
from:
type: exec
res: exec
name: exec2
to:
type: exec
res: exec
name: exec4
- name: e3
from:
type: exec
res: exec
name: exec3
to:
type: exec
res: exec
name: exec4
- name: e4
from:
type: exec
res: exec
name: exec4
to:
type: exec
res: exec
name: exec5
- name: e5
from:
type: exec
res: exec
name: exec4
to:
type: exec
res: exec
name: exec6
- name: e6
from:
type: exec
res: exec
name: exec4
to:
type: exec
res: exec
name: exec7

View File

@@ -1,6 +1,6 @@
---
graph: mygraph
types:
resources:
file:
- name: file1
path: "/tmp/mgmt/f1"
@@ -15,8 +15,8 @@ types:
edges:
- name: e1
from:
type: file
res: file
name: file1
to:
type: file
res: file
name: file2

View File

@@ -1,6 +1,6 @@
---
graph: mygraph
types:
resources:
file:
- name: file2
path: "/tmp/mgmt/f2"
@@ -15,8 +15,8 @@ types:
edges:
- name: e2
from:
type: file
res: file
name: file2
to:
type: file
res: file
name: file3

View File

@@ -1,6 +1,6 @@
---
graph: mygraph
types:
resources:
noop:
- name: noop1
file:
@@ -9,22 +9,22 @@ types:
content: |
i am f1
state: exists
service:
svc:
- name: purpleidea
state: running
startup: enabled
edges:
- name: e1
from:
type: noop
res: noop
name: noop1
to:
type: file
res: file
name: file1
- name: e2
from:
type: file
res: file
name: file1
to:
type: service
res: svc
name: purpleidea

View File

@@ -1,6 +1,6 @@
---
graph: mygraph
types:
resources:
file:
- name: file1a
path: "/tmp/mgmtA/f1a"
@@ -23,6 +23,6 @@ types:
i am f4, exported from host A
state: exists
collect:
- type: file
- res: file
pattern: "/tmp/mgmtA/"
edges: []

View File

@@ -1,6 +1,6 @@
---
graph: mygraph
types:
resources:
file:
- name: file1b
path: "/tmp/mgmtB/f1b"
@@ -23,6 +23,6 @@ types:
i am f4, exported from host B
state: exists
collect:
- type: file
- res: file
pattern: "/tmp/mgmtB/"
edges: []

View File

@@ -1,6 +1,6 @@
---
graph: mygraph
types:
resources:
file:
- name: file1c
path: "/tmp/mgmtC/f1c"
@@ -23,6 +23,6 @@ types:
i am f4, exported from host C
state: exists
collect:
- type: file
- res: file
pattern: "/tmp/mgmtC/"
edges: []

View File

@@ -1,6 +1,6 @@
---
graph: mygraph
types:
resources:
file:
- name: file1
path: "/tmp/mgmt/f1"
@@ -13,6 +13,6 @@ types:
i am f3, exported from host A
state: exists
collect:
- type: file
- res: file
pattern: ''
edges:

View File

@@ -1,6 +1,6 @@
---
graph: mygraph
types:
resources:
file:
- name: file1
path: "/tmp/mgmt/f1"
@@ -8,6 +8,6 @@ types:
i am f1
state: exists
collect:
- type: file
- res: file
pattern: ''
edges:

View File

@@ -1,6 +1,6 @@
---
graph: mygraph
types:
resources:
noop:
- name: noop1
edges:

View File

@@ -1,6 +1,6 @@
---
graph: mygraph
types:
resources:
noop:
- name: noop1
exec:

View File

@@ -1,6 +1,6 @@
---
graph: mygraph
types:
resources:
exec:
- name: exec1
cmd: sleep 10s
@@ -45,15 +45,15 @@ types:
edges:
- name: e1
from:
type: exec
res: exec
name: exec1
to:
type: exec
res: exec
name: exec2
- name: e2
from:
type: exec
res: exec
name: exec2
to:
type: exec
res: exec
name: exec3

View File

@@ -1,6 +1,6 @@
---
graph: mygraph
types:
resources:
exec:
- name: exec1
cmd: sleep 10s
@@ -25,8 +25,8 @@ types:
edges:
- name: e1
from:
type: exec
res: exec
name: exec1
to:
type: exec
res: exec
name: exec2

View File

@@ -1,6 +1,6 @@
---
graph: mygraph
types:
resources:
exec:
- name: exec1
cmd: sleep 10s
@@ -25,8 +25,8 @@ types:
edges:
- name: e1
from:
type: exec
res: exec
name: exec1
to:
type: exec
res: exec
name: exec2

View File

@@ -1,6 +1,6 @@
---
graph: mygraph
types:
resources:
exec:
- name: exec1
cmd: echo hello from exec1
@@ -25,8 +25,8 @@ types:
edges:
- name: e1
from:
type: exec
res: exec
name: exec1
to:
type: exec
res: exec
name: exec2

View File

@@ -1,6 +1,6 @@
---
graph: mygraph
types:
resources:
exec:
- name: exec1
cmd: echo hello from exec1

View File

@@ -1,7 +1,7 @@
---
graph: mygraph
comment: simple exec fan in example to demonstrate optimization
types:
resources:
exec:
- name: exec1
cmd: sleep 10s
@@ -56,22 +56,22 @@ types:
edges:
- name: e1
from:
type: exec
res: exec
name: exec1
to:
type: exec
res: exec
name: exec5
- name: e2
from:
type: exec
res: exec
name: exec2
to:
type: exec
res: exec
name: exec5
- name: e3
from:
type: exec
res: exec
name: exec3
to:
type: exec
res: exec
name: exec5

76
exec.go
View File

@@ -25,8 +25,8 @@ import (
"strings"
)
type ExecType struct {
BaseType `yaml:",inline"`
type ExecRes struct {
BaseRes `yaml:",inline"`
State string `yaml:"state"` // state: exists/present?, absent, (undefined?)
Cmd string `yaml:"cmd"` // the command to run
Shell string `yaml:"shell"` // the (optional) shell to use to run the cmd
@@ -38,10 +38,10 @@ type ExecType struct {
PollInt int `yaml:"pollint"` // the poll interval for the ifcmd
}
func NewExecType(name, cmd, shell string, timeout int, watchcmd, watchshell, ifcmd, ifshell string, pollint int, state string) *ExecType {
func NewExecRes(name, cmd, shell string, timeout int, watchcmd, watchshell, ifcmd, ifshell string, pollint int, state string) *ExecRes {
// FIXME if path = nil, path = name ...
return &ExecType{
BaseType: BaseType{
return &ExecRes{
BaseRes: BaseRes{
Name: name,
events: make(chan Event),
vertex: nil,
@@ -58,13 +58,13 @@ func NewExecType(name, cmd, shell string, timeout int, watchcmd, watchshell, ifc
}
}
func (obj *ExecType) GetType() string {
func (obj *ExecRes) GetRes() string {
return "Exec"
}
// validate if the params passed in are valid data
// FIXME: where should this get called ?
func (obj *ExecType) Validate() bool {
func (obj *ExecRes) Validate() bool {
if obj.Cmd == "" { // this is the only thing that is really required
return false
}
@@ -78,7 +78,7 @@ func (obj *ExecType) Validate() bool {
}
// wraps the scanner output in a channel
func (obj *ExecType) BufioChanScanner(scanner *bufio.Scanner) (chan string, chan error) {
func (obj *ExecRes) BufioChanScanner(scanner *bufio.Scanner) (chan string, chan error) {
ch, errch := make(chan string), make(chan error)
go func() {
for scanner.Scan() {
@@ -96,7 +96,7 @@ func (obj *ExecType) BufioChanScanner(scanner *bufio.Scanner) (chan string, chan
}
// Exec watcher
func (obj *ExecType) Watch() {
func (obj *ExecRes) Watch() {
if obj.IsWatching() {
return
}
@@ -128,7 +128,7 @@ func (obj *ExecType) Watch() {
cmdReader, err := cmd.StdoutPipe()
if err != nil {
log.Printf("%v[%v]: Error creating StdoutPipe for Cmd: %v", obj.GetType(), obj.GetName(), err)
log.Printf("%v[%v]: Error creating StdoutPipe for Cmd: %v", obj.GetRes(), obj.GetName(), err)
log.Fatal(err) // XXX: how should we handle errors?
}
scanner := bufio.NewScanner(cmdReader)
@@ -140,7 +140,7 @@ func (obj *ExecType) Watch() {
cmd.Process.Kill() // TODO: is this necessary?
}()
if err := cmd.Start(); err != nil {
log.Printf("%v[%v]: Error starting Cmd: %v", obj.GetType(), obj.GetName(), err)
log.Printf("%v[%v]: Error starting Cmd: %v", obj.GetRes(), obj.GetName(), err)
log.Fatal(err) // XXX: how should we handle errors?
}
@@ -148,36 +148,36 @@ func (obj *ExecType) Watch() {
}
for {
obj.SetState(typeWatching) // reset
obj.SetState(resStateWatching) // reset
select {
case text := <-bufioch:
obj.SetConvergedState(typeConvergedNil)
obj.SetConvergedState(resConvergedNil)
// each time we get a line of output, we loop!
log.Printf("%v[%v]: Watch output: %s", obj.GetType(), obj.GetName(), text)
log.Printf("%v[%v]: Watch output: %s", obj.GetRes(), obj.GetName(), text)
if text != "" {
send = true
}
case err := <-errch:
obj.SetConvergedState(typeConvergedNil) // XXX ?
obj.SetConvergedState(resConvergedNil) // XXX ?
if err == nil { // EOF
// FIXME: add an "if watch command ends/crashes"
// restart or generate error option
log.Printf("%v[%v]: Reached EOF", obj.GetType(), obj.GetName())
log.Printf("%v[%v]: Reached EOF", obj.GetRes(), obj.GetName())
return
}
log.Printf("%v[%v]: Error reading input?: %v", obj.GetType(), obj.GetName(), err)
log.Printf("%v[%v]: Error reading input?: %v", obj.GetRes(), obj.GetName(), err)
log.Fatal(err)
// XXX: how should we handle errors?
case event := <-obj.events:
obj.SetConvergedState(typeConvergedNil)
obj.SetConvergedState(resConvergedNil)
if exit, send = obj.ReadEvent(&event); exit {
return // exit
}
case _ = <-TimeAfterOrBlock(obj.ctimeout):
obj.SetConvergedState(typeConvergedTimeout)
obj.SetConvergedState(resConvergedTimeout)
obj.converged <- true
continue
}
@@ -193,7 +193,7 @@ func (obj *ExecType) Watch() {
}
// TODO: expand the IfCmd to be a list of commands
func (obj *ExecType) StateOK() bool {
func (obj *ExecRes) StateOK() bool {
// if there is a watch command, but no if command, run based on state
if b := obj.isStateOK; obj.WatchCmd != "" && obj.IfCmd == "" {
@@ -241,8 +241,8 @@ func (obj *ExecType) StateOK() bool {
}
}
func (obj *ExecType) Apply() bool {
log.Printf("%v[%v]: Apply", obj.GetType(), obj.GetName())
func (obj *ExecRes) Apply() bool {
log.Printf("%v[%v]: Apply", obj.GetRes(), obj.GetName())
var cmdName string
var cmdArgs []string
if obj.Shell == "" {
@@ -264,7 +264,7 @@ func (obj *ExecType) Apply() bool {
cmd.Stdout = &out
if err := cmd.Start(); err != nil {
log.Printf("%v[%v]: Error starting Cmd: %v", obj.GetType(), obj.GetName(), err)
log.Printf("%v[%v]: Error starting Cmd: %v", obj.GetRes(), obj.GetName(), err)
return false
}
@@ -278,12 +278,12 @@ func (obj *ExecType) Apply() bool {
select {
case err := <-done:
if err != nil {
log.Printf("%v[%v]: Error waiting for Cmd: %v", obj.GetType(), obj.GetName(), err)
log.Printf("%v[%v]: Error waiting for Cmd: %v", obj.GetRes(), obj.GetName(), err)
return false
}
case <-TimeAfterOrBlock(timeout):
log.Printf("%v[%v]: Timeout waiting for Cmd", obj.GetType(), obj.GetName())
log.Printf("%v[%v]: Timeout waiting for Cmd", obj.GetRes(), obj.GetName())
//cmd.Process.Kill() // TODO: is this necessary?
return false
}
@@ -301,35 +301,35 @@ func (obj *ExecType) Apply() bool {
return true
}
func (obj *ExecType) Compare(typ Type) bool {
switch typ.(type) {
case *ExecType:
typ := typ.(*ExecType)
if obj.Name != typ.Name {
func (obj *ExecRes) Compare(res Res) bool {
switch res.(type) {
case *ExecRes:
res := res.(*ExecRes)
if obj.Name != res.Name {
return false
}
if obj.Cmd != typ.Cmd {
if obj.Cmd != res.Cmd {
return false
}
if obj.Shell != typ.Shell {
if obj.Shell != res.Shell {
return false
}
if obj.Timeout != typ.Timeout {
if obj.Timeout != res.Timeout {
return false
}
if obj.WatchCmd != typ.WatchCmd {
if obj.WatchCmd != res.WatchCmd {
return false
}
if obj.WatchShell != typ.WatchShell {
if obj.WatchShell != res.WatchShell {
return false
}
if obj.IfCmd != typ.IfCmd {
if obj.IfCmd != res.IfCmd {
return false
}
if obj.PollInt != typ.PollInt {
if obj.PollInt != res.PollInt {
return false
}
if obj.State != typ.State {
if obj.State != res.State {
return false
}
default:

68
file.go
View File

@@ -31,8 +31,8 @@ import (
"syscall"
)
type FileType struct {
BaseType `yaml:",inline"`
type FileRes struct {
BaseRes `yaml:",inline"`
Path string `yaml:"path"` // path variable (should default to name)
Dirname string `yaml:"dirname"`
Basename string `yaml:"basename"`
@@ -41,10 +41,10 @@ type FileType struct {
sha256sum string
}
func NewFileType(name, path, dirname, basename, content, state string) *FileType {
func NewFileRes(name, path, dirname, basename, content, state string) *FileRes {
// FIXME if path = nil, path = name ...
return &FileType{
BaseType: BaseType{
return &FileRes{
BaseRes: BaseRes{
Name: name,
events: make(chan Event),
vertex: nil,
@@ -58,12 +58,12 @@ func NewFileType(name, path, dirname, basename, content, state string) *FileType
}
}
func (obj *FileType) GetType() string {
func (obj *FileRes) GetRes() string {
return "File"
}
// validate if the params passed in are valid data
func (obj *FileType) Validate() bool {
func (obj *FileRes) Validate() bool {
if obj.Dirname != "" {
// must end with /
if obj.Dirname[len(obj.Dirname)-1:] != "/" {
@@ -79,7 +79,7 @@ func (obj *FileType) Validate() bool {
return true
}
func (obj *FileType) GetPath() string {
func (obj *FileRes) GetPath() string {
d := Dirname(obj.Path)
b := Basename(obj.Path)
if !obj.Validate() || (obj.Dirname == "" && obj.Basename == "") {
@@ -96,7 +96,7 @@ func (obj *FileType) GetPath() string {
// File watcher for files and directories
// Modify with caution, probably important to write some test cases first!
// obj.GetPath(): file or directory
func (obj *FileType) Watch() {
func (obj *FileRes) Watch() {
if obj.IsWatching() {
return
}
@@ -152,13 +152,13 @@ func (obj *FileType) Watch() {
continue
}
obj.SetState(typeWatching) // reset
obj.SetState(resStateWatching) // reset
select {
case event := <-watcher.Events:
if DEBUG {
log.Printf("File[%v]: Watch(%v), Event(%v): %v", obj.GetName(), current, event.Name, event.Op)
}
obj.SetConvergedState(typeConvergedNil) // XXX: technically i can detect if the event is erroneous or not first
obj.SetConvergedState(resConvergedNil) // XXX: technically i can detect if the event is erroneous or not first
// the deeper you go, the bigger the deltaDepth is...
// this is the difference between what we're watching,
// and the event... doesn't mean we can't watch deeper
@@ -228,20 +228,20 @@ func (obj *FileType) Watch() {
}
case err := <-watcher.Errors:
obj.SetConvergedState(typeConvergedNil) // XXX ?
obj.SetConvergedState(resConvergedNil) // XXX ?
log.Println("error:", err)
log.Fatal(err)
//obj.events <- fmt.Sprintf("file: %v", "error") // XXX: how should we handle errors?
case event := <-obj.events:
obj.SetConvergedState(typeConvergedNil)
obj.SetConvergedState(resConvergedNil)
if exit, send = obj.ReadEvent(&event); exit {
return // exit
}
//dirty = false // these events don't invalidate state
case _ = <-TimeAfterOrBlock(obj.ctimeout):
obj.SetConvergedState(typeConvergedTimeout)
obj.SetConvergedState(resConvergedTimeout)
obj.converged <- true
continue
}
@@ -259,7 +259,7 @@ func (obj *FileType) Watch() {
}
}
func (obj *FileType) HashSHA256fromContent() string {
func (obj *FileRes) HashSHA256fromContent() string {
if obj.sha256sum != "" { // return if already computed
return obj.sha256sum
}
@@ -271,7 +271,7 @@ func (obj *FileType) HashSHA256fromContent() string {
}
// FIXME: add the obj.CleanState() calls all over the true returns!
func (obj *FileType) StateOK() bool {
func (obj *FileRes) StateOK() bool {
if obj.isStateOK { // cache the state
return true
}
@@ -295,9 +295,9 @@ func (obj *FileType) StateOK() bool {
}
}
func (obj *FileType) StateOKFile() bool {
func (obj *FileRes) StateOKFile() bool {
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 resource.")
}
// run a diff, and return true if needs changing
@@ -326,9 +326,9 @@ func (obj *FileType) StateOKFile() bool {
return false
}
func (obj *FileType) StateOKDir() bool {
func (obj *FileRes) StateOKDir() bool {
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 resource.")
}
// XXX: not implemented
@@ -336,8 +336,8 @@ func (obj *FileType) StateOKDir() bool {
return false
}
func (obj *FileType) Apply() bool {
log.Printf("%v[%v]: Apply", obj.GetType(), obj.GetName())
func (obj *FileRes) Apply() bool {
log.Printf("%v[%v]: Apply", obj.GetRes(), obj.GetName())
if PathIsDir(obj.GetPath()) {
return obj.ApplyDir()
@@ -346,10 +346,10 @@ func (obj *FileType) Apply() bool {
}
}
func (obj *FileType) ApplyFile() bool {
func (obj *FileRes) ApplyFile() bool {
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 resource.")
}
if obj.State == "absent" {
@@ -378,9 +378,9 @@ func (obj *FileType) ApplyFile() bool {
return true
}
func (obj *FileType) ApplyDir() bool {
func (obj *FileRes) ApplyDir() bool {
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 resource.")
}
// XXX: not implemented
@@ -388,20 +388,20 @@ func (obj *FileType) ApplyDir() bool {
return true
}
func (obj *FileType) Compare(typ Type) bool {
switch typ.(type) {
case *FileType:
typ := typ.(*FileType)
if obj.Name != typ.Name {
func (obj *FileRes) Compare(res Res) bool {
switch res.(type) {
case *FileRes:
res := res.(*FileRes)
if obj.Name != res.Name {
return false
}
if obj.GetPath() != typ.Path {
if obj.GetPath() != res.Path {
return false
}
if obj.Content != typ.Content {
if obj.Content != res.Content {
return false
}
if obj.State != typ.State {
if obj.State != res.State {
return false
}
default:

View File

@@ -177,7 +177,7 @@ func run(c *cli.Context) {
continue
}
for v := range G.GetVerticesChan() {
if v.Type.GetConvergedState() != typeConvergedTimeout {
if v.Res.GetConvergedState() != resConvergedTimeout {
continue ConvergedLoop
}
}

View File

@@ -152,7 +152,7 @@ func TestMiscT6(t *testing.T) {
type foo struct {
Name string `yaml:"name"`
Type string `yaml:"type"`
Res string `yaml:"res"`
Value int `yaml:"value"`
}
@@ -175,7 +175,7 @@ func TestMiscT7(t *testing.T) {
type Foo struct {
Name string `yaml:"name"`
Type string `yaml:"type"`
Res string `yaml:"res"`
Value int `yaml:"value"`
}

View File

@@ -57,7 +57,7 @@ type Graph struct {
type Vertex struct {
graph *Graph // store a pointer to the graph it's on
Type // anonymous field
Res // anonymous field
data map[string]string // XXX: currently unused i think, remove?
}
@@ -73,9 +73,9 @@ func NewGraph(name string) *Graph {
}
}
func NewVertex(t Type) *Vertex {
func NewVertex(r Res) *Vertex {
return &Vertex{
Type: t,
Res: r,
data: make(map[string]string),
}
}
@@ -111,10 +111,10 @@ func (g *Graph) SetState(state graphState) graphState {
return prev
}
// store a pointer in the type to it's parent vertex
// store a pointer in the resource to it's parent vertex
func (g *Graph) SetVertex() {
for v := range g.GetVerticesChan() {
v.Type.SetVertex(v)
v.Res.SetVertex(v)
}
}
@@ -160,7 +160,7 @@ func (g *Graph) GetVertex(name string) chan *Vertex {
return ch
}
func (g *Graph) GetVertexMatch(obj Type) *Vertex {
func (g *Graph) GetVertexMatch(obj Res) *Vertex {
for k := range g.Adjacency {
if k.Compare(obj) { // XXX test
return k
@@ -241,7 +241,7 @@ func (g *Graph) Graphviz() (out string) {
//out += "\tnode [shape=box];\n"
str := ""
for i := range g.Adjacency { // reverse paths
out += fmt.Sprintf("\t%v [label=\"%v[%v]\"];\n", i.GetName(), i.GetType(), i.GetName())
out += fmt.Sprintf("\t%v [label=\"%v[%v]\"];\n", i.GetName(), i.GetRes(), i.GetName())
for j := range g.Adjacency[i] {
k := g.Adjacency[i][j]
// use str for clearer output ordering
@@ -549,14 +549,14 @@ func (g *Graph) Start(wg *sync.WaitGroup, first bool) { // start or continue
indegree := g.InDegree() // compute all of the indegree's
for _, v := range Reverse(t) {
if !v.Type.IsWatching() { // if Watch() is not running...
if !v.Res.IsWatching() { // if Watch() is not running...
wg.Add(1)
// must pass in value to avoid races...
// see: https://ttboj.wordpress.com/2015/07/27/golang-parallelism-issues-causing-too-many-open-files-error/
go func(vv *Vertex) {
defer wg.Done()
vv.Type.Watch()
log.Printf("%v[%v]: Exited", vv.GetType(), vv.GetName())
vv.Res.Watch()
log.Printf("%v[%v]: Exited", vv.GetRes(), vv.GetName())
}(v)
}
@@ -574,10 +574,10 @@ func (g *Graph) Start(wg *sync.WaitGroup, first bool) { // start or continue
// and not just selectively the subset with no indegree.
if (!first) || indegree[v] == 0 {
// ensure state is started before continuing on to next vertex
for !v.Type.SendEvent(eventStart, true, false) {
for !v.Res.SendEvent(eventStart, true, false) {
if DEBUG {
// if SendEvent fails, we aren't up yet
log.Printf("%v[%v]: Retrying SendEvent(Start)", v.GetType(), v.GetName())
log.Printf("%v[%v]: Retrying SendEvent(Start)", v.GetRes(), v.GetName())
// sleep here briefly or otherwise cause
// a different goroutine to be scheduled
time.Sleep(1 * time.Millisecond)
@@ -590,7 +590,7 @@ func (g *Graph) Start(wg *sync.WaitGroup, first bool) { // start or continue
func (g *Graph) Pause() {
t, _ := g.TopologicalSort()
for _, v := range t { // squeeze out the events...
v.Type.SendEvent(eventPause, true, false)
v.Res.SendEvent(eventPause, true, false)
}
}
@@ -602,13 +602,13 @@ func (g *Graph) Exit() {
// when we hit the 'default' in the select statement!
// XXX: we can do this to quiesce, but it's not necessary now
v.Type.SendEvent(eventExit, true, false)
v.Res.SendEvent(eventExit, true, false)
}
}
func (g *Graph) SetConvergedCallback(ctimeout int, converged chan bool) {
for v := range g.GetVerticesChan() {
v.Type.SetConvegedCallback(ctimeout, converged)
v.Res.SetConvergedCallback(ctimeout, converged)
}
}

View File

@@ -36,8 +36,8 @@ func TestPgraphT1(t *testing.T) {
t.Errorf("Should have 0 edges instead of: %d.", i)
}
v1 := NewVertex(NewNoopType("v1"))
v2 := NewVertex(NewNoopType("v2"))
v1 := NewVertex(NewNoopRes("v1"))
v2 := NewVertex(NewNoopRes("v2"))
e1 := NewEdge("e1")
G.AddEdge(v1, v2, e1)
@@ -53,12 +53,12 @@ func TestPgraphT1(t *testing.T) {
func TestPgraphT2(t *testing.T) {
G := NewGraph("g2")
v1 := NewVertex(NewNoopType("v1"))
v2 := NewVertex(NewNoopType("v2"))
v3 := NewVertex(NewNoopType("v3"))
v4 := NewVertex(NewNoopType("v4"))
v5 := NewVertex(NewNoopType("v5"))
v6 := NewVertex(NewNoopType("v6"))
v1 := NewVertex(NewNoopRes("v1"))
v2 := NewVertex(NewNoopRes("v2"))
v3 := NewVertex(NewNoopRes("v3"))
v4 := NewVertex(NewNoopRes("v4"))
v5 := NewVertex(NewNoopRes("v5"))
v6 := NewVertex(NewNoopRes("v6"))
e1 := NewEdge("e1")
e2 := NewEdge("e2")
e3 := NewEdge("e3")
@@ -80,12 +80,12 @@ func TestPgraphT2(t *testing.T) {
func TestPgraphT3(t *testing.T) {
G := NewGraph("g3")
v1 := NewVertex(NewNoopType("v1"))
v2 := NewVertex(NewNoopType("v2"))
v3 := NewVertex(NewNoopType("v3"))
v4 := NewVertex(NewNoopType("v4"))
v5 := NewVertex(NewNoopType("v5"))
v6 := NewVertex(NewNoopType("v6"))
v1 := NewVertex(NewNoopRes("v1"))
v2 := NewVertex(NewNoopRes("v2"))
v3 := NewVertex(NewNoopRes("v3"))
v4 := NewVertex(NewNoopRes("v4"))
v5 := NewVertex(NewNoopRes("v5"))
v6 := NewVertex(NewNoopRes("v6"))
e1 := NewEdge("e1")
e2 := NewEdge("e2")
e3 := NewEdge("e3")
@@ -121,9 +121,9 @@ func TestPgraphT3(t *testing.T) {
func TestPgraphT4(t *testing.T) {
G := NewGraph("g4")
v1 := NewVertex(NewNoopType("v1"))
v2 := NewVertex(NewNoopType("v2"))
v3 := NewVertex(NewNoopType("v3"))
v1 := NewVertex(NewNoopRes("v1"))
v2 := NewVertex(NewNoopRes("v2"))
v3 := NewVertex(NewNoopRes("v3"))
e1 := NewEdge("e1")
e2 := NewEdge("e2")
e3 := NewEdge("e3")
@@ -143,12 +143,12 @@ func TestPgraphT4(t *testing.T) {
func TestPgraphT5(t *testing.T) {
G := NewGraph("g5")
v1 := NewVertex(NewNoopType("v1"))
v2 := NewVertex(NewNoopType("v2"))
v3 := NewVertex(NewNoopType("v3"))
v4 := NewVertex(NewNoopType("v4"))
v5 := NewVertex(NewNoopType("v5"))
v6 := NewVertex(NewNoopType("v6"))
v1 := NewVertex(NewNoopRes("v1"))
v2 := NewVertex(NewNoopRes("v2"))
v3 := NewVertex(NewNoopRes("v3"))
v4 := NewVertex(NewNoopRes("v4"))
v5 := NewVertex(NewNoopRes("v5"))
v6 := NewVertex(NewNoopRes("v6"))
e1 := NewEdge("e1")
e2 := NewEdge("e2")
e3 := NewEdge("e3")
@@ -172,12 +172,12 @@ func TestPgraphT5(t *testing.T) {
func TestPgraphT6(t *testing.T) {
G := NewGraph("g6")
v1 := NewVertex(NewNoopType("v1"))
v2 := NewVertex(NewNoopType("v2"))
v3 := NewVertex(NewNoopType("v3"))
v4 := NewVertex(NewNoopType("v4"))
v5 := NewVertex(NewNoopType("v5"))
v6 := NewVertex(NewNoopType("v6"))
v1 := NewVertex(NewNoopRes("v1"))
v2 := NewVertex(NewNoopRes("v2"))
v3 := NewVertex(NewNoopRes("v3"))
v4 := NewVertex(NewNoopRes("v4"))
v5 := NewVertex(NewNoopRes("v5"))
v6 := NewVertex(NewNoopRes("v6"))
e1 := NewEdge("e1")
e2 := NewEdge("e2")
e3 := NewEdge("e3")
@@ -210,9 +210,9 @@ func TestPgraphT6(t *testing.T) {
func TestPgraphT7(t *testing.T) {
G := NewGraph("g7")
v1 := NewVertex(NewNoopType("v1"))
v2 := NewVertex(NewNoopType("v2"))
v3 := NewVertex(NewNoopType("v3"))
v1 := NewVertex(NewNoopRes("v1"))
v2 := NewVertex(NewNoopRes("v2"))
v3 := NewVertex(NewNoopRes("v3"))
e1 := NewEdge("e1")
e2 := NewEdge("e2")
e3 := NewEdge("e3")
@@ -251,28 +251,28 @@ func TestPgraphT7(t *testing.T) {
func TestPgraphT8(t *testing.T) {
v1 := NewVertex(NewNoopType("v1"))
v2 := NewVertex(NewNoopType("v2"))
v3 := NewVertex(NewNoopType("v3"))
v1 := NewVertex(NewNoopRes("v1"))
v2 := NewVertex(NewNoopRes("v2"))
v3 := NewVertex(NewNoopRes("v3"))
if HasVertex(v1, []*Vertex{v1, v2, v3}) != true {
t.Errorf("Should be true instead of false.")
}
v4 := NewVertex(NewNoopType("v4"))
v5 := NewVertex(NewNoopType("v5"))
v6 := NewVertex(NewNoopType("v6"))
v4 := NewVertex(NewNoopRes("v4"))
v5 := NewVertex(NewNoopRes("v5"))
v6 := NewVertex(NewNoopRes("v6"))
if HasVertex(v4, []*Vertex{v5, v6}) != false {
t.Errorf("Should be false instead of true.")
}
v7 := NewVertex(NewNoopType("v7"))
v8 := NewVertex(NewNoopType("v8"))
v9 := NewVertex(NewNoopType("v9"))
v7 := NewVertex(NewNoopRes("v7"))
v8 := NewVertex(NewNoopRes("v8"))
v9 := NewVertex(NewNoopRes("v9"))
if HasVertex(v8, []*Vertex{v7, v8, v9}) != true {
t.Errorf("Should be true instead of false.")
}
v1b := NewVertex(NewNoopType("v1")) // same value, different objects
v1b := NewVertex(NewNoopRes("v1")) // same value, different objects
if HasVertex(v1b, []*Vertex{v1, v2, v3}) != false {
t.Errorf("Should be false instead of true.")
}
@@ -281,12 +281,12 @@ func TestPgraphT8(t *testing.T) {
func TestPgraphT9(t *testing.T) {
G := NewGraph("g9")
v1 := NewVertex(NewNoopType("v1"))
v2 := NewVertex(NewNoopType("v2"))
v3 := NewVertex(NewNoopType("v3"))
v4 := NewVertex(NewNoopType("v4"))
v5 := NewVertex(NewNoopType("v5"))
v6 := NewVertex(NewNoopType("v6"))
v1 := NewVertex(NewNoopRes("v1"))
v2 := NewVertex(NewNoopRes("v2"))
v3 := NewVertex(NewNoopRes("v3"))
v4 := NewVertex(NewNoopRes("v4"))
v5 := NewVertex(NewNoopRes("v5"))
v6 := NewVertex(NewNoopRes("v6"))
e1 := NewEdge("e1")
e2 := NewEdge("e2")
e3 := NewEdge("e3")
@@ -348,7 +348,7 @@ func TestPgraphT9(t *testing.T) {
t.Errorf("Topological sort failed, status: %v.", ok)
str := "Found:"
for _, v := range s {
str += " " + v.Type.GetName()
str += " " + v.Res.GetName()
}
t.Errorf(str)
}
@@ -357,12 +357,12 @@ func TestPgraphT9(t *testing.T) {
func TestPgraphT10(t *testing.T) {
G := NewGraph("g10")
v1 := NewVertex(NewNoopType("v1"))
v2 := NewVertex(NewNoopType("v2"))
v3 := NewVertex(NewNoopType("v3"))
v4 := NewVertex(NewNoopType("v4"))
v5 := NewVertex(NewNoopType("v5"))
v6 := NewVertex(NewNoopType("v6"))
v1 := NewVertex(NewNoopRes("v1"))
v2 := NewVertex(NewNoopRes("v2"))
v3 := NewVertex(NewNoopRes("v3"))
v4 := NewVertex(NewNoopRes("v4"))
v5 := NewVertex(NewNoopRes("v5"))
v6 := NewVertex(NewNoopRes("v6"))
e1 := NewEdge("e1")
e2 := NewEdge("e2")
e3 := NewEdge("e3")
@@ -382,12 +382,12 @@ func TestPgraphT10(t *testing.T) {
}
func TestPgraphT11(t *testing.T) {
v1 := NewVertex(NewNoopType("v1"))
v2 := NewVertex(NewNoopType("v2"))
v3 := NewVertex(NewNoopType("v3"))
v4 := NewVertex(NewNoopType("v4"))
v5 := NewVertex(NewNoopType("v5"))
v6 := NewVertex(NewNoopType("v6"))
v1 := NewVertex(NewNoopRes("v1"))
v2 := NewVertex(NewNoopRes("v2"))
v3 := NewVertex(NewNoopRes("v3"))
v4 := NewVertex(NewNoopRes("v4"))
v5 := NewVertex(NewNoopRes("v5"))
v6 := NewVertex(NewNoopRes("v6"))
if rev := Reverse([]*Vertex{}); !reflect.DeepEqual(rev, []*Vertex{}) {
t.Errorf("Reverse of vertex slice failed.")

View File

@@ -22,43 +22,43 @@ import (
"time"
)
//go:generate stringer -type=typeState -output=typestate_stringer.go
type typeState int
//go:generate stringer -type=resState -output=resstate_stringer.go
type resState int
const (
typeNil typeState = iota
typeWatching
typeEvent // an event has happened, but we haven't poked yet
typeApplying
typePoking
resStateNil resState = iota
resStateWatching
resStateEvent // an event has happened, but we haven't poked yet
resStateCheckApply
resStatePoking
)
//go:generate stringer -type=typeConvergedState -output=typeconvergedstate_stringer.go
type typeConvergedState int
//go:generate stringer -type=resConvergedState -output=resconvergedstate_stringer.go
type resConvergedState int
const (
typeConvergedNil typeConvergedState = iota
//typeConverged
typeConvergedTimeout
resConvergedNil resConvergedState = iota
//resConverged
resConvergedTimeout
)
type Type interface {
type Res interface {
Init()
GetName() string // can't be named "Name()" because of struct field
GetType() string
GetRes() string
Watch()
StateOK() bool // TODO: can we rename this to something better?
Apply() bool
SetVertex(*Vertex)
SetConvegedCallback(ctimeout int, converged chan bool)
Compare(Type) bool
SetConvergedCallback(ctimeout int, converged chan bool)
Compare(Res) bool
SendEvent(eventName, bool, bool) bool
IsWatching() bool
SetWatching(bool)
GetConvergedState() typeConvergedState
SetConvergedState(typeConvergedState)
GetState() typeState
SetState(typeState)
GetConvergedState() resConvergedState
SetConvergedState(resConvergedState)
GetState() resState
SetState(resState)
GetTimestamp() int64
UpdateTimestamp() int64
OKTimestamp() bool
@@ -66,28 +66,28 @@ type Type interface {
BackPoke()
}
type BaseType struct {
type BaseRes struct {
Name string `yaml:"name"`
timestamp int64 // last updated timestamp ?
events chan Event
vertex *Vertex
state typeState
convergedState typeConvergedState
state resState
convergedState resConvergedState
watching bool // is Watch() loop running ?
ctimeout int // converged timeout
converged chan bool
isStateOK bool // whether the state is okay based on events or not
}
type NoopType struct {
BaseType `yaml:",inline"`
type NoopRes struct {
BaseRes `yaml:",inline"`
Comment string `yaml:"comment"` // extra field for example purposes
}
func NewNoopType(name string) *NoopType {
func NewNoopRes(name string) *NoopRes {
// FIXME: we could get rid of this New constructor and use raw object creation with a required Init()
return &NoopType{
BaseType: BaseType{
return &NoopRes{
BaseRes: BaseRes{
Name: name,
events: make(chan Event), // unbuffered chan size to avoid stale events
vertex: nil,
@@ -97,74 +97,74 @@ func NewNoopType(name string) *NoopType {
}
// initialize structures like channels if created without New constructor
func (obj *BaseType) Init() {
func (obj *BaseRes) Init() {
obj.events = make(chan Event)
}
// this method gets used by all the types, if we have one of (obj NoopType) it would get overridden in that case!
func (obj *BaseType) GetName() string {
// this method gets used by all the types, if we have one of (obj NoopRes) it would get overridden in that case!
func (obj *BaseRes) GetName() string {
return obj.Name
}
func (obj *BaseType) GetType() string {
func (obj *BaseRes) GetRes() string {
return "Base"
}
func (obj *BaseType) GetVertex() *Vertex {
func (obj *BaseRes) GetVertex() *Vertex {
return obj.vertex
}
func (obj *BaseType) SetVertex(v *Vertex) {
func (obj *BaseRes) SetVertex(v *Vertex) {
obj.vertex = v
}
func (obj *BaseType) SetConvegedCallback(ctimeout int, converged chan bool) {
func (obj *BaseRes) SetConvergedCallback(ctimeout int, converged chan bool) {
obj.ctimeout = ctimeout
obj.converged = converged
}
// is the Watch() function running?
func (obj *BaseType) IsWatching() bool {
func (obj *BaseRes) IsWatching() bool {
return obj.watching
}
// store status of if the Watch() function is running
func (obj *BaseType) SetWatching(b bool) {
func (obj *BaseRes) SetWatching(b bool) {
obj.watching = b
}
func (obj *BaseType) GetConvergedState() typeConvergedState {
func (obj *BaseRes) GetConvergedState() resConvergedState {
return obj.convergedState
}
func (obj *BaseType) SetConvergedState(state typeConvergedState) {
func (obj *BaseRes) SetConvergedState(state resConvergedState) {
obj.convergedState = state
}
func (obj *BaseType) GetState() typeState {
func (obj *BaseRes) GetState() resState {
return obj.state
}
func (obj *BaseType) SetState(state typeState) {
func (obj *BaseRes) SetState(state resState) {
if DEBUG {
log.Printf("%v[%v]: State: %v -> %v", obj.GetType(), obj.GetName(), obj.GetState(), state)
log.Printf("%v[%v]: State: %v -> %v", obj.GetRes(), obj.GetName(), obj.GetState(), state)
}
obj.state = state
}
// GetTimestamp returns the timestamp of a vertex
func (obj *BaseType) GetTimestamp() int64 {
func (obj *BaseRes) GetTimestamp() int64 {
return obj.timestamp
}
// UpdateTimestamp updates the timestamp on a vertex and returns the new value
func (obj *BaseType) UpdateTimestamp() int64 {
func (obj *BaseRes) UpdateTimestamp() int64 {
obj.timestamp = time.Now().UnixNano() // update
return obj.timestamp
}
// can this element run right now?
func (obj *BaseType) OKTimestamp() bool {
func (obj *BaseRes) OKTimestamp() bool {
v := obj.GetVertex()
g := v.GetGraph()
// these are all the vertices pointing TO v, eg: ??? -> v
@@ -173,9 +173,9 @@ func (obj *BaseType) OKTimestamp() bool {
// then we can't run right now...
// if they're equal (eg: on init of 0) then we also can't run
// b/c we should let our pre-req's go first...
x, y := obj.GetTimestamp(), n.Type.GetTimestamp()
x, y := obj.GetTimestamp(), n.Res.GetTimestamp()
if DEBUG {
log.Printf("%v[%v]: OKTimestamp: (%v) >= %v[%v](%v): !%v", obj.GetType(), obj.GetName(), x, n.GetType(), n.GetName(), y, x >= y)
log.Printf("%v[%v]: OKTimestamp: (%v) >= %v[%v](%v): !%v", obj.GetRes(), obj.GetName(), x, n.GetRes(), n.GetName(), y, x >= y)
}
if x >= y {
return false
@@ -186,55 +186,55 @@ func (obj *BaseType) OKTimestamp() bool {
// notify nodes after me in the dependency graph that they need refreshing...
// NOTE: this assumes that this can never fail or need to be rescheduled
func (obj *BaseType) Poke(activity bool) {
func (obj *BaseRes) Poke(activity bool) {
v := obj.GetVertex()
g := v.GetGraph()
// these are all the vertices pointing AWAY FROM v, eg: v -> ???
for _, n := range g.OutgoingGraphEdges(v) {
// XXX: if we're in state event and haven't been cancelled by
// apply, then we can cancel a poke to a child, right? XXX
// XXX: if n.Type.GetState() != typeEvent { // is this correct?
// XXX: if n.Res.GetState() != resStateEvent { // is this correct?
if true { // XXX
if DEBUG {
log.Printf("%v[%v]: Poke: %v[%v]", v.GetType(), v.GetName(), n.GetType(), n.GetName())
log.Printf("%v[%v]: Poke: %v[%v]", v.GetRes(), v.GetName(), n.GetRes(), n.GetName())
}
n.SendEvent(eventPoke, false, activity) // XXX: can this be switched to sync?
} else {
if DEBUG {
log.Printf("%v[%v]: Poke: %v[%v]: Skipped!", v.GetType(), v.GetName(), n.GetType(), n.GetName())
log.Printf("%v[%v]: Poke: %v[%v]: Skipped!", v.GetRes(), v.GetName(), n.GetRes(), n.GetName())
}
}
}
}
// poke the pre-requisites that are stale and need to run before I can run...
func (obj *BaseType) BackPoke() {
func (obj *BaseRes) BackPoke() {
v := obj.GetVertex()
g := v.GetGraph()
// these are all the vertices pointing TO v, eg: ??? -> v
for _, n := range g.IncomingGraphEdges(v) {
x, y, s := obj.GetTimestamp(), n.Type.GetTimestamp(), n.Type.GetState()
x, y, s := obj.GetTimestamp(), n.Res.GetTimestamp(), n.Res.GetState()
// if the parent timestamp needs poking AND it's not in state
// typeEvent, then poke it. If the parent is in typeEvent it
// resStateEvent, then poke it. If the parent is in resStateEvent it
// means that an event is pending, so we'll be expecting a poke
// back soon, so we can safely discard the extra parent poke...
// TODO: implement a stateLT (less than) to tell if something
// happens earlier in the state cycle and that doesn't wrap nil
if x >= y && (s != typeEvent && s != typeApplying) {
if x >= y && (s != resStateEvent && s != resStateCheckApply) {
if DEBUG {
log.Printf("%v[%v]: BackPoke: %v[%v]", v.GetType(), v.GetName(), n.GetType(), n.GetName())
log.Printf("%v[%v]: BackPoke: %v[%v]", v.GetRes(), v.GetName(), n.GetRes(), n.GetName())
}
n.SendEvent(eventBackPoke, false, false) // XXX: can this be switched to sync?
} else {
if DEBUG {
log.Printf("%v[%v]: BackPoke: %v[%v]: Skipped!", v.GetType(), v.GetName(), n.GetType(), n.GetName())
log.Printf("%v[%v]: BackPoke: %v[%v]: Skipped!", v.GetRes(), v.GetName(), n.GetRes(), n.GetName())
}
}
}
}
// push an event into the message queue for a particular type vertex
func (obj *BaseType) SendEvent(event eventName, sync bool, activity bool) bool {
// push an event into the message queue for a particular vertex
func (obj *BaseRes) SendEvent(event eventName, sync bool, activity bool) bool {
// TODO: isn't this race-y ?
if !obj.IsWatching() { // element has already exited
return false // if we don't return, we'll block on the send
@@ -257,7 +257,7 @@ func (obj *BaseType) SendEvent(event eventName, sync bool, activity bool) bool {
// process events when a select gets one, this handles the pause code too!
// the return values specify if we should exit and poke respectively
func (obj *BaseType) ReadEvent(event *Event) (exit, poke bool) {
func (obj *BaseRes) ReadEvent(event *Event) (exit, poke bool) {
event.ACK()
switch event.Name {
case eventStart:
@@ -283,7 +283,7 @@ func (obj *BaseType) ReadEvent(event *Event) (exit, poke bool) {
return false, false // don't poke on unpause!
} else {
// if we get a poke event here, it's a bug!
log.Fatalf("%v[%v]: Unknown event: %v, while paused!", obj.GetType(), obj.GetName(), e)
log.Fatalf("%v[%v]: Unknown event: %v, while paused!", obj.GetRes(), obj.GetName(), e)
}
}
@@ -295,17 +295,17 @@ func (obj *BaseType) ReadEvent(event *Event) (exit, poke bool) {
// useful for using as: return CleanState() in the StateOK functions when there
// are multiple `true` return exits
func (obj *BaseType) CleanState() bool {
func (obj *BaseRes) CleanState() bool {
obj.isStateOK = true
return true
}
// XXX: rename this function
func Process(obj Type) {
func Process(obj Res) {
if DEBUG {
log.Printf("%v[%v]: Process()", obj.GetType(), obj.GetName())
log.Printf("%v[%v]: Process()", obj.GetRes(), obj.GetName())
}
obj.SetState(typeEvent)
obj.SetState(resStateEvent)
var ok = true
var apply = false // did we run an apply?
// is it okay to run dependency wise right now?
@@ -313,15 +313,15 @@ func Process(obj Type) {
// us back and we will run if needed then!
if obj.OKTimestamp() {
if DEBUG {
log.Printf("%v[%v]: OKTimestamp(%v)", obj.GetType(), obj.GetName(), obj.GetTimestamp())
log.Printf("%v[%v]: OKTimestamp(%v)", obj.GetRes(), obj.GetName(), obj.GetTimestamp())
}
if !obj.StateOK() { // TODO: can we rename this to something better?
if DEBUG {
log.Printf("%v[%v]: !StateOK()", obj.GetType(), obj.GetName())
log.Printf("%v[%v]: !StateOK()", obj.GetRes(), obj.GetName())
}
// throw an error if apply fails...
// if this fails, don't UpdateTimestamp()
obj.SetState(typeApplying)
obj.SetState(resStateCheckApply)
if !obj.Apply() { // check for error
ok = false
} else {
@@ -333,7 +333,7 @@ func Process(obj Type) {
// update this timestamp *before* we poke or the poked
// nodes might fail due to having a too old timestamp!
obj.UpdateTimestamp() // this was touched...
obj.SetState(typePoking) // can't cancel parent poke
obj.SetState(resStatePoking) // can't cancel parent poke
obj.Poke(apply)
}
// poke at our pre-req's instead since they need to refresh/run...
@@ -343,11 +343,11 @@ func Process(obj Type) {
}
}
func (obj *NoopType) GetType() string {
func (obj *NoopRes) GetRes() string {
return "Noop"
}
func (obj *NoopType) Watch() {
func (obj *NoopRes) Watch() {
if obj.IsWatching() {
return
}
@@ -358,17 +358,17 @@ func (obj *NoopType) Watch() {
var send = false // send event?
var exit = false
for {
obj.SetState(typeWatching) // reset
obj.SetState(resStateWatching) // reset
select {
case event := <-obj.events:
obj.SetConvergedState(typeConvergedNil)
obj.SetConvergedState(resConvergedNil)
// we avoid sending events on unpause
if exit, send = obj.ReadEvent(&event); exit {
return // exit
}
case _ = <-TimeAfterOrBlock(obj.ctimeout):
obj.SetConvergedState(typeConvergedTimeout)
obj.SetConvergedState(resConvergedTimeout)
obj.converged <- true
continue
}
@@ -383,21 +383,21 @@ func (obj *NoopType) Watch() {
}
}
func (obj *NoopType) StateOK() bool {
func (obj *NoopRes) StateOK() bool {
return true // never needs updating
}
func (obj *NoopType) Apply() bool {
log.Printf("%v[%v]: Apply", obj.GetType(), obj.GetName())
func (obj *NoopRes) Apply() bool {
log.Printf("%v[%v]: Apply", obj.GetRes(), obj.GetName())
return true
}
func (obj *NoopType) Compare(typ Type) bool {
switch typ.(type) {
// we can only compare NoopType to others of the same type
case *NoopType:
typ := typ.(*NoopType)
if obj.Name != typ.Name {
func (obj *NoopRes) Compare(res Res) bool {
switch res.(type) {
// we can only compare NoopRes to others of the same resource
case *NoopRes:
res := res.(*NoopRes)
if obj.Name != res.Name {
return false
}
default:

View File

@@ -27,15 +27,15 @@ import (
"log"
)
type ServiceType struct {
BaseType `yaml:",inline"`
type SvcRes struct {
BaseRes `yaml:",inline"`
State string `yaml:"state"` // state: running, stopped
Startup string `yaml:"startup"` // enabled, disabled, undefined
}
func NewServiceType(name, state, startup string) *ServiceType {
return &ServiceType{
BaseType: BaseType{
func NewSvcRes(name, state, startup string) *SvcRes {
return &SvcRes{
BaseRes: BaseRes{
Name: name,
events: make(chan Event),
vertex: nil,
@@ -45,19 +45,19 @@ func NewServiceType(name, state, startup string) *ServiceType {
}
}
func (obj *ServiceType) GetType() string {
return "Service"
func (obj *SvcRes) GetRes() string {
return "Svc"
}
// Service watcher
func (obj *ServiceType) Watch() {
func (obj *SvcRes) Watch() {
if obj.IsWatching() {
return
}
obj.SetWatching(true)
defer obj.SetWatching(false)
// obj.Name: service name
// obj.Name: svc name
//vertex := obj.GetVertex() // stored with SetVertex
if !util.IsRunningSystemd() {
log.Fatal("Systemd is not running.")
@@ -80,11 +80,11 @@ func (obj *ServiceType) Watch() {
buschan := make(chan *dbus.Signal, 10)
bus.Signal(buschan)
var service = fmt.Sprintf("%v.service", obj.Name) // systemd name
var svc = fmt.Sprintf("%v.service", obj.Name) // systemd name
var send = false // send event?
var exit = false
var dirty = false
var invalid = false // does the service exist or not?
var invalid = false // does the svc exist or not?
var previous bool // previous invalid value
set := conn.NewSubscriptionSet() // no error should be returned
subChannel, subErrors := set.Subscribe()
@@ -97,8 +97,8 @@ func (obj *ServiceType) Watch() {
previous = invalid
invalid = false
// firstly, does service even exist or not?
loadstate, err := conn.GetUnitProperty(service, "LoadState")
// firstly, does svc even exist or not?
loadstate, err := conn.GetUnitProperty(svc, "LoadState")
if err != nil {
log.Printf("Failed to get property: %v", err)
invalid = true
@@ -107,7 +107,7 @@ func (obj *ServiceType) Watch() {
if !invalid {
var notFound = (loadstate.Value == dbus.MakeVariant("not-found"))
if notFound { // XXX: in the loop we'll handle changes better...
log.Printf("Failed to find service: %v", service)
log.Printf("Failed to find svc: %v", svc)
invalid = true // XXX ?
}
}
@@ -118,21 +118,21 @@ func (obj *ServiceType) Watch() {
}
if invalid {
log.Printf("Waiting for: %v", service) // waiting for service to appear...
log.Printf("Waiting for: %v", svc) // waiting for svc to appear...
if activeSet {
activeSet = false
set.Remove(service) // no return value should ever occur
set.Remove(svc) // no return value should ever occur
}
obj.SetState(typeWatching) // reset
obj.SetState(resStateWatching) // reset
select {
case _ = <-buschan: // XXX wait for new units event to unstick
obj.SetConvergedState(typeConvergedNil)
obj.SetConvergedState(resConvergedNil)
// loop so that we can see the changed invalid signal
log.Printf("Service[%v]->DaemonReload()", service)
log.Printf("Svc[%v]->DaemonReload()", svc)
case event := <-obj.events:
obj.SetConvergedState(typeConvergedNil)
obj.SetConvergedState(resConvergedNil)
if exit, send = obj.ReadEvent(&event); exit {
return // exit
}
@@ -141,47 +141,47 @@ func (obj *ServiceType) Watch() {
}
case _ = <-TimeAfterOrBlock(obj.ctimeout):
obj.SetConvergedState(typeConvergedTimeout)
obj.SetConvergedState(resConvergedTimeout)
obj.converged <- true
continue
}
} else {
if !activeSet {
activeSet = true
set.Add(service) // no return value should ever occur
set.Add(svc) // no return value should ever occur
}
log.Printf("Watching: %v", service) // attempting to watch...
obj.SetState(typeWatching) // reset
log.Printf("Watching: %v", svc) // attempting to watch...
obj.SetState(resStateWatching) // reset
select {
case event := <-subChannel:
log.Printf("Service event: %+v", event)
log.Printf("Svc event: %+v", event)
// NOTE: the value returned is a map for some reason...
if event[service] != nil {
// event[service].ActiveState is not nil
if event[service].ActiveState == "active" {
log.Printf("Service[%v]->Started()", service)
} else if event[service].ActiveState == "inactive" {
log.Printf("Service[%v]->Stopped!()", service)
if event[svc] != nil {
// event[svc].ActiveState is not nil
if event[svc].ActiveState == "active" {
log.Printf("Svc[%v]->Started()", svc)
} else if event[svc].ActiveState == "inactive" {
log.Printf("Svc[%v]->Stopped!()", svc)
} else {
log.Fatal("Unknown service state: ", event[service].ActiveState)
log.Fatal("Unknown svc state: ", event[svc].ActiveState)
}
} else {
// service stopped (and ActiveState is nil...)
log.Printf("Service[%v]->Stopped", service)
// svc stopped (and ActiveState is nil...)
log.Printf("Svc[%v]->Stopped", svc)
}
send = true
dirty = true
case err := <-subErrors:
obj.SetConvergedState(typeConvergedNil) // XXX ?
obj.SetConvergedState(resConvergedNil) // XXX ?
log.Println("error:", err)
log.Fatal(err)
//vertex.events <- fmt.Sprintf("service: %v", "error") // XXX: how should we handle errors?
//vertex.events <- fmt.Sprintf("svc: %v", "error") // XXX: how should we handle errors?
case event := <-obj.events:
obj.SetConvergedState(typeConvergedNil)
obj.SetConvergedState(resConvergedNil)
if exit, send = obj.ReadEvent(&event); exit {
return // exit
}
@@ -203,7 +203,7 @@ func (obj *ServiceType) Watch() {
}
}
func (obj *ServiceType) StateOK() bool {
func (obj *SvcRes) StateOK() bool {
if obj.isStateOK { // cache the state
return true
}
@@ -218,9 +218,9 @@ func (obj *ServiceType) StateOK() bool {
}
defer conn.Close()
var service = fmt.Sprintf("%v.service", obj.Name) // systemd name
var svc = fmt.Sprintf("%v.service", obj.Name) // systemd name
loadstate, err := conn.GetUnitProperty(service, "LoadState")
loadstate, err := conn.GetUnitProperty(svc, "LoadState")
if err != nil {
log.Printf("Failed to get load state: %v", err)
return false
@@ -229,14 +229,14 @@ func (obj *ServiceType) StateOK() bool {
// NOTE: we have to compare variants with other variants, they are really strings...
var notFound = (loadstate.Value == dbus.MakeVariant("not-found"))
if notFound {
log.Printf("Failed to find service: %v", service)
log.Printf("Failed to find svc: %v", svc)
return false
}
// XXX: check service "enabled at boot" or not status...
// XXX: check svc "enabled at boot" or not status...
//conn.GetUnitProperties(service)
activestate, err := conn.GetUnitProperty(service, "ActiveState")
//conn.GetUnitProperties(svc)
activestate, err := conn.GetUnitProperty(svc, "ActiveState")
if err != nil {
log.Fatal("Failed to get active state: ", err)
}
@@ -258,8 +258,8 @@ func (obj *ServiceType) StateOK() bool {
return true // all is good, no state change needed
}
func (obj *ServiceType) Apply() bool {
log.Printf("%v[%v]: Apply", obj.GetType(), obj.GetName())
func (obj *SvcRes) Apply() bool {
log.Printf("%v[%v]: Apply", obj.GetRes(), obj.GetName())
if !util.IsRunningSystemd() {
log.Fatal("Systemd is not running.")
@@ -271,8 +271,8 @@ func (obj *ServiceType) Apply() bool {
}
defer conn.Close()
var service = fmt.Sprintf("%v.service", obj.Name) // systemd name
var files = []string{service} // the service represented in a list
var svc = fmt.Sprintf("%v.service", obj.Name) // systemd name
var files = []string{svc} // the svc represented in a list
if obj.Startup == "enabled" {
_, _, err = conn.EnableUnitFiles(files, false, true)
@@ -289,13 +289,13 @@ func (obj *ServiceType) Apply() bool {
result := make(chan string, 1) // catch result information
if obj.State == "running" {
_, err := conn.StartUnit(service, "fail", result)
_, err := conn.StartUnit(svc, "fail", result)
if err != nil {
log.Fatal("Failed to start unit: ", err)
return false
}
} else if obj.State == "stopped" {
_, err = conn.StopUnit(service, "fail", result)
_, err = conn.StopUnit(svc, "fail", result)
if err != nil {
log.Fatal("Failed to stop unit: ", err)
return false
@@ -319,17 +319,17 @@ func (obj *ServiceType) Apply() bool {
return true
}
func (obj *ServiceType) Compare(typ Type) bool {
switch typ.(type) {
case *ServiceType:
typ := typ.(*ServiceType)
if obj.Name != typ.Name {
func (obj *SvcRes) Compare(res Res) bool {
switch res.(type) {
case *SvcRes:
res := res.(*SvcRes)
if obj.Name != res.Name {
return false
}
if obj.State != typ.State {
if obj.State != res.State {
return false
}
if obj.Startup != typ.Startup {
if obj.Startup != res.Startup {
return false
}
default:

View File

@@ -1,6 +1,6 @@
---
graph: mygraph
types:
resources:
noop:
- name: noop1
file:
@@ -27,15 +27,15 @@ types:
edges:
- name: e1
from:
type: file
res: file
name: file1
to:
type: file
res: file
name: file2
- name: e2
from:
type: file
res: file
name: file2
to:
type: file
res: file
name: file3

View File

@@ -1,6 +1,6 @@
---
graph: mygraph
types:
resources:
file:
- name: file1a
path: "/tmp/mgmt/mgmtA/f1a"
@@ -23,6 +23,6 @@ types:
i am f4, exported from host A
state: exists
collect:
- type: file
- res: file
pattern: "/tmp/mgmt/mgmtA/"
edges: []

View File

@@ -1,6 +1,6 @@
---
graph: mygraph
types:
resources:
file:
- name: file1b
path: "/tmp/mgmt/mgmtB/f1b"
@@ -23,6 +23,6 @@ types:
i am f4, exported from host B
state: exists
collect:
- type: file
- res: file
pattern: "/tmp/mgmt/mgmtB/"
edges: []

View File

@@ -1,6 +1,6 @@
---
graph: mygraph
types:
resources:
file:
- name: file1c
path: "/tmp/mgmt/mgmtC/f1c"
@@ -23,6 +23,6 @@ types:
i am f4, exported from host C
state: exists
collect:
- type: file
- res: file
pattern: "/tmp/mgmt/mgmtC/"
edges: []

View File

@@ -1,7 +1,7 @@
---
graph: mygraph
comment: simple exec fan in example to demonstrate optimization)
types:
resources:
exec:
- name: exec1
cmd: sleep 10s
@@ -56,22 +56,22 @@ types:
edges:
- name: e1
from:
type: exec
res: exec
name: exec1
to:
type: exec
res: exec
name: exec5
- name: e2
from:
type: exec
res: exec
name: exec2
to:
type: exec
res: exec
name: exec5
- name: e3
from:
type: exec
res: exec
name: exec3
to:
type: exec
res: exec
name: exec5

View File

@@ -1,7 +1,7 @@
---
graph: mygraph
comment: simple exec fan in to fan out example to demonstrate optimization
types:
resources:
exec:
- name: exec1
cmd: sleep 10s
@@ -86,43 +86,43 @@ types:
edges:
- name: e1
from:
type: exec
res: exec
name: exec1
to:
type: exec
res: exec
name: exec4
- name: e2
from:
type: exec
res: exec
name: exec2
to:
type: exec
res: exec
name: exec4
- name: e3
from:
type: exec
res: exec
name: exec3
to:
type: exec
res: exec
name: exec4
- name: e4
from:
type: exec
res: exec
name: exec4
to:
type: exec
res: exec
name: exec5
- name: e5
from:
type: exec
res: exec
name: exec4
to:
type: exec
res: exec
name: exec6
- name: e6
from:
type: exec
res: exec
name: exec4
to:
type: exec
res: exec
name: exec7