github.com/taylorchu/nomad@v0.5.3-rc1.0.20170407200202-db11e7dd7b55/client/util.go (about) 1 package client 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "math/rand" 8 "os" 9 "path/filepath" 10 11 "github.com/hashicorp/nomad/nomad/structs" 12 ) 13 14 type allocTuple struct { 15 exist, updated *structs.Allocation 16 } 17 18 // diffResult is used to return the sets that result from a diff 19 type diffResult struct { 20 added []*structs.Allocation 21 removed []*structs.Allocation 22 updated []allocTuple 23 ignore []*structs.Allocation 24 } 25 26 func (d *diffResult) GoString() string { 27 return fmt.Sprintf("allocs: (added %d) (removed %d) (updated %d) (ignore %d)", 28 len(d.added), len(d.removed), len(d.updated), len(d.ignore)) 29 } 30 31 // diffAllocs is used to diff the existing and updated allocations 32 // to see what has happened. 33 func diffAllocs(existing []*structs.Allocation, allocs *allocUpdates) *diffResult { 34 // Scan the existing allocations 35 result := &diffResult{} 36 existIdx := make(map[string]struct{}) 37 for _, exist := range existing { 38 // Mark this as existing 39 existIdx[exist.ID] = struct{}{} 40 41 // Check if the alloc was updated or filtered because an update wasn't 42 // needed. 43 alloc, pulled := allocs.pulled[exist.ID] 44 _, filtered := allocs.filtered[exist.ID] 45 46 // If not updated or filtered, removed 47 if !pulled && !filtered { 48 result.removed = append(result.removed, exist) 49 continue 50 } 51 52 // Check for an update 53 if pulled && alloc.AllocModifyIndex > exist.AllocModifyIndex { 54 result.updated = append(result.updated, allocTuple{exist, alloc}) 55 continue 56 } 57 58 // Ignore this 59 result.ignore = append(result.ignore, exist) 60 } 61 62 // Scan the updated allocations for any that are new 63 for id, pulled := range allocs.pulled { 64 if _, ok := existIdx[id]; !ok { 65 result.added = append(result.added, pulled) 66 } 67 } 68 return result 69 } 70 71 // shuffleStrings randomly shuffles the list of strings 72 func shuffleStrings(list []string) { 73 for i := range list { 74 j := rand.Intn(i + 1) 75 list[i], list[j] = list[j], list[i] 76 } 77 } 78 79 // persistState is used to help with saving state 80 func persistState(path string, data interface{}) error { 81 buf, err := json.Marshal(data) 82 if err != nil { 83 return fmt.Errorf("failed to encode state: %v", err) 84 } 85 if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { 86 return fmt.Errorf("failed to make dirs for %s: %v", path, err) 87 } 88 tmpPath := path + ".tmp" 89 if err := ioutil.WriteFile(tmpPath, buf, 0600); err != nil { 90 return fmt.Errorf("failed to save state to tmp: %v", err) 91 } 92 if err := os.Rename(tmpPath, path); err != nil { 93 return fmt.Errorf("failed to rename tmp to path: %v", err) 94 } 95 96 // Sanity check since users have reported empty state files on disk 97 if stat, err := os.Stat(path); err != nil { 98 return fmt.Errorf("unable to stat state file %s: %v", path, err) 99 } else if stat.Size() == 0 { 100 return fmt.Errorf("persisted invalid state file %s; see https://github.com/hashicorp/nomad/issues/1367", path) 101 } 102 return nil 103 } 104 105 // restoreState is used to read back in the persisted state 106 func restoreState(path string, data interface{}) error { 107 buf, err := ioutil.ReadFile(path) 108 if err != nil { 109 if os.IsNotExist(err) { 110 return nil 111 } 112 return fmt.Errorf("failed to read state: %v", err) 113 } 114 if err := json.Unmarshal(buf, data); err != nil { 115 return fmt.Errorf("failed to decode state: %v", err) 116 } 117 return nil 118 }