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  }