github.com/Cloud-Foundations/Dominator@v0.3.4/cmd/mdbd/daemon.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "encoding/gob" 6 "errors" 7 "os" 8 "path" 9 "reflect" 10 "regexp" 11 "runtime" 12 "sort" 13 "syscall" 14 "time" 15 16 "github.com/Cloud-Foundations/Dominator/lib/json" 17 "github.com/Cloud-Foundations/Dominator/lib/log" 18 "github.com/Cloud-Foundations/Dominator/lib/mdb" 19 "github.com/Cloud-Foundations/tricorder/go/tricorder" 20 "github.com/Cloud-Foundations/tricorder/go/tricorder/units" 21 ) 22 23 var ( 24 latencyBucketer = tricorder.NewGeometricBucketer(0.1, 100e3) 25 loadCpuTimeDistribution *tricorder.CumulativeDistribution 26 loadTimeDistribution *tricorder.CumulativeDistribution 27 ) 28 29 type genericEncoder interface { 30 Encode(v interface{}) error 31 } 32 33 func init() { 34 loadCpuTimeDistribution = latencyBucketer.NewCumulativeDistribution() 35 if err := tricorder.RegisterMetric("/load-cpu-time", loadCpuTimeDistribution, 36 units.Millisecond, "load CPU time durations"); err != nil { 37 panic(err) 38 } 39 loadTimeDistribution = latencyBucketer.NewCumulativeDistribution() 40 if err := tricorder.RegisterMetric("/load-time", loadTimeDistribution, 41 units.Millisecond, "load durations"); err != nil { 42 panic(err) 43 } 44 } 45 46 func runDaemon(generators []generator, eventChannel <-chan struct{}, 47 mdbFileName string, hostnameRegex string, 48 datacentre string, fetchInterval uint, updateFunc func(old, new *mdb.Mdb), 49 logger log.DebugLogger, debug bool) { 50 var prevMdb *mdb.Mdb 51 var hostnameRE *regexp.Regexp 52 var err error 53 if hostnameRegex != ".*" { 54 hostnameRE, err = regexp.Compile("^" + hostnameRegex) 55 if err != nil { 56 logger.Println(err) 57 os.Exit(1) 58 } 59 } 60 var cycleStopTime time.Time 61 fetchIntervalDuration := time.Duration(fetchInterval) * time.Second 62 intervalTimer := time.NewTimer(fetchIntervalDuration) 63 for ; ; sleepUntil(eventChannel, intervalTimer, cycleStopTime) { 64 cycleStopTime = time.Now().Add(fetchIntervalDuration) 65 newMdb, err := loadFromAll(generators, datacentre, logger) 66 if err != nil { 67 logger.Println(err) 68 continue 69 } 70 newMdb = selectHosts(newMdb, hostnameRE) 71 sort.Sort(newMdb) 72 if newMdbIsDifferent(prevMdb, newMdb) { 73 updateFunc(prevMdb, newMdb) 74 if err := writeMdb(newMdb, mdbFileName); err != nil { 75 logger.Println(err) 76 } else { 77 if debug { 78 logger.Printf("Wrote new MDB data, %d machines\n", 79 len(newMdb.Machines)) 80 } 81 prevMdb = newMdb 82 } 83 } else if debug { 84 logger.Printf("Refreshed MDB data, same %d machines\n", 85 len(newMdb.Machines)) 86 } 87 } 88 } 89 90 func sleepUntil(eventChannel <-chan struct{}, intervalTimer *time.Timer, 91 wakeTime time.Time) { 92 runtime.GC() // An opportune time to take out the garbage. 93 sleepTime := wakeTime.Sub(time.Now()) 94 if sleepTime < time.Second { 95 sleepTime = time.Second 96 } 97 intervalTimer.Reset(sleepTime) 98 select { 99 case <-eventChannel: 100 case <-intervalTimer.C: 101 } 102 } 103 104 func loadFromAll(generators []generator, datacentre string, 105 logger log.DebugLogger) (*mdb.Mdb, error) { 106 machineMap := make(map[string]mdb.Machine) 107 var variables map[string]string 108 startTime := time.Now() 109 var rusageStart, rusageStop syscall.Rusage 110 syscall.Getrusage(syscall.RUSAGE_SELF, &rusageStart) 111 for _, gen := range generators { 112 mdb, err := gen.Generate(datacentre, logger) 113 if err != nil { 114 return nil, err 115 } 116 for _, machine := range mdb.Machines { 117 if oldMachine, ok := machineMap[machine.Hostname]; ok { 118 oldMachine.UpdateFrom(machine) 119 machineMap[machine.Hostname] = oldMachine 120 } else { 121 machineMap[machine.Hostname] = machine 122 } 123 } 124 if vGen, ok := gen.(variablesGetter); ok { 125 if _variables, err := vGen.GetVariables(); err != nil { 126 return nil, err 127 } else { 128 variables = _variables 129 } 130 } 131 } 132 var newMdb mdb.Mdb 133 for _, machine := range machineMap { 134 processMachine(&machine, variables) 135 newMdb.Machines = append(newMdb.Machines, machine) 136 } 137 syscall.Getrusage(syscall.RUSAGE_SELF, &rusageStop) 138 loadTimeDistribution.Add(time.Since(startTime)) 139 loadCpuTimeDistribution.Add(time.Duration( 140 rusageStop.Utime.Sec)*time.Second + 141 time.Duration(rusageStop.Utime.Usec)*time.Microsecond - 142 time.Duration(rusageStart.Utime.Sec)*time.Second - 143 time.Duration(rusageStart.Utime.Usec)*time.Microsecond) 144 return &newMdb, nil 145 } 146 147 func processMachine(machine *mdb.Machine, variables map[string]string) { 148 if len(variables) < 1 { 149 return 150 } 151 machine.RequiredImage = processValue(machine.RequiredImage, variables) 152 machine.PlannedImage = processValue(machine.PlannedImage, variables) 153 machine.Tags = machine.Tags.Copy() 154 for key, value := range machine.Tags { 155 machine.Tags[key] = processValue(value, variables) 156 } 157 } 158 159 func processValue(value string, variables map[string]string) string { 160 if len(value) < 2 { 161 return value 162 } 163 if value[0] == '$' { 164 if newValue, ok := variables[value[1:]]; ok { 165 return newValue 166 } 167 } 168 return value 169 } 170 171 func selectHosts(inMdb *mdb.Mdb, hostnameRE *regexp.Regexp) *mdb.Mdb { 172 if hostnameRE == nil { 173 return inMdb 174 } 175 var outMdb mdb.Mdb 176 for _, machine := range inMdb.Machines { 177 if hostnameRE.MatchString(machine.Hostname) { 178 outMdb.Machines = append(outMdb.Machines, machine) 179 } 180 } 181 return &outMdb 182 } 183 184 func newMdbIsDifferent(prevMdb, newMdb *mdb.Mdb) bool { 185 return !reflect.DeepEqual(prevMdb, newMdb) 186 } 187 188 func writeMdb(mdb *mdb.Mdb, mdbFileName string) error { 189 tmpFileName := mdbFileName + "~" 190 file, err := os.Create(tmpFileName) 191 if err != nil { 192 return errors.New("error opening file " + err.Error()) 193 } 194 defer os.Remove(tmpFileName) 195 defer file.Close() 196 writer := bufio.NewWriter(file) 197 switch path.Ext(mdbFileName) { 198 case ".gob": 199 if err := gob.NewEncoder(writer).Encode(mdb.Machines); err != nil { 200 return err 201 } 202 default: 203 if err := json.WriteWithIndent(writer, " ", 204 mdb.Machines); err != nil { 205 return err 206 } 207 } 208 if err := writer.Flush(); err != nil { 209 return err 210 } 211 return os.Rename(tmpFileName, mdbFileName) 212 }