github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/sub/rpcd/update.go (about) 1 package rpcd 2 3 import ( 4 "bufio" 5 "encoding/json" 6 "errors" 7 "flag" 8 "fmt" 9 "os" 10 "os/exec" 11 "time" 12 13 jsonlib "github.com/Cloud-Foundations/Dominator/lib/json" 14 "github.com/Cloud-Foundations/Dominator/lib/log" 15 "github.com/Cloud-Foundations/Dominator/lib/srpc" 16 "github.com/Cloud-Foundations/Dominator/lib/triggers" 17 "github.com/Cloud-Foundations/Dominator/proto/sub" 18 "github.com/Cloud-Foundations/Dominator/sub/lib" 19 ) 20 21 var ( 22 readOnly = flag.Bool("readOnly", false, 23 "If true, refuse all Fetch and Update requests. For debugging only") 24 disableUpdates = flag.Bool("disableUpdates", false, 25 "If true, refuse all Update requests. For debugging only") 26 disableTriggers = flag.Bool("disableTriggers", false, 27 "If true, do not run any triggers. For debugging only") 28 ) 29 30 func (t *rpcType) Update(conn *srpc.Conn, request sub.UpdateRequest, 31 reply *sub.UpdateResponse) error { 32 if err := t.getUpdateLock(); err != nil { 33 t.logger.Println(err) 34 return err 35 } 36 t.logger.Printf("Update()\n") 37 fs := t.fileSystemHistory.FileSystem() 38 if request.Wait { 39 return t.updateAndUnlock(request, fs.RootDirectoryName()) 40 } 41 go t.updateAndUnlock(request, fs.RootDirectoryName()) 42 return nil 43 } 44 45 func (t *rpcType) getUpdateLock() error { 46 if *readOnly || *disableUpdates { 47 return errors.New("Update() rejected due to read-only mode") 48 } 49 fs := t.fileSystemHistory.FileSystem() 50 if fs == nil { 51 return errors.New("No file-system history yet") 52 } 53 t.rwLock.Lock() 54 defer t.rwLock.Unlock() 55 if t.fetchInProgress { 56 return errors.New("Fetch() in progress") 57 } 58 if t.updateInProgress { 59 return errors.New("Update() already in progress") 60 } 61 t.updateInProgress = true 62 t.lastUpdateError = nil 63 return nil 64 } 65 66 func (t *rpcType) updateAndUnlock(request sub.UpdateRequest, 67 rootDirectoryName string) error { 68 defer t.clearUpdateInProgress() 69 defer t.scannerConfiguration.BoostCpuLimit(t.logger) 70 t.disableScannerFunc(true) 71 defer t.disableScannerFunc(false) 72 startTime := time.Now() 73 oldTriggers := &triggers.MergeableTriggers{} 74 file, err := os.Open(t.oldTriggersFilename) 75 if err == nil { 76 decoder := json.NewDecoder(file) 77 var trig triggers.Triggers 78 err = decoder.Decode(&trig.Triggers) 79 file.Close() 80 if err == nil { 81 oldTriggers.Merge(&trig) 82 } else { 83 t.logger.Printf("Error decoding old triggers: %s", err.Error()) 84 } 85 } 86 if request.Triggers != nil { 87 // Merge new triggers into old triggers. This supports initial 88 // Domination of a machine and when the old triggers are incomplete. 89 oldTriggers.Merge(request.Triggers) 90 file, err = os.Create(t.oldTriggersFilename) 91 if err == nil { 92 writer := bufio.NewWriter(file) 93 if err := jsonlib.WriteWithIndent(writer, " ", 94 request.Triggers.Triggers); err != nil { 95 t.logger.Printf("Error marshaling triggers: %s", err) 96 } 97 writer.Flush() 98 file.Close() 99 } 100 } 101 hadTriggerFailures, fsChangeDuration, lastUpdateError := lib.Update( 102 request, rootDirectoryName, t.objectsDir, oldTriggers.ExportTriggers(), 103 t.scannerConfiguration.ScanFilter, runTriggers, t.logger) 104 t.lastUpdateHadTriggerFailures = hadTriggerFailures 105 t.lastUpdateError = lastUpdateError 106 timeTaken := time.Since(startTime) 107 if t.lastUpdateError != nil { 108 t.logger.Printf("Update(): last error: %s\n", t.lastUpdateError) 109 } else { 110 t.rwLock.Lock() 111 t.lastSuccessfulImageName = request.ImageName 112 t.rwLock.Unlock() 113 } 114 t.logger.Printf("Update() completed in %s (change window: %s)\n", 115 timeTaken, fsChangeDuration) 116 return t.lastUpdateError 117 } 118 119 func (t *rpcType) clearUpdateInProgress() { 120 t.rwLock.Lock() 121 defer t.rwLock.Unlock() 122 t.updateInProgress = false 123 } 124 125 // Returns true if there were failures. 126 func runTriggers(triggers []*triggers.Trigger, action string, 127 logger log.Logger) bool { 128 doReboot := false 129 hadFailures := false 130 needRestart := false 131 logPrefix := "" 132 if *disableTriggers { 133 logPrefix = "Disabled: " 134 } 135 ppid := fmt.Sprint(os.Getppid()) 136 for _, trigger := range triggers { 137 if trigger.DoReboot && action == "start" { 138 doReboot = true 139 break 140 } 141 } 142 for _, trigger := range triggers { 143 if trigger.Service == "subd" { 144 // Never kill myself, just restart. 145 if action == "start" { 146 needRestart = true 147 } 148 continue 149 } 150 logger.Printf("%sAction: service %s %s\n", 151 logPrefix, trigger.Service, action) 152 if *disableTriggers { 153 continue 154 } 155 if !runCommand(logger, 156 "run-in-mntns", ppid, "service", trigger.Service, action) { 157 hadFailures = true 158 if trigger.DoReboot && action == "start" { 159 doReboot = false 160 } 161 } 162 } 163 if doReboot { 164 logger.Print(logPrefix, "Rebooting") 165 if *disableTriggers { 166 return hadFailures 167 } 168 if !runCommand(logger, "reboot") { 169 hadFailures = true 170 } 171 return hadFailures 172 } else if needRestart { 173 logger.Printf("%sAction: service subd restart\n", logPrefix) 174 if !runCommand(logger, 175 "run-in-mntns", ppid, "service", "subd", "restart") { 176 hadFailures = true 177 } 178 } 179 return hadFailures 180 } 181 182 // Returns true on success, else false. 183 func runCommand(logger log.Logger, name string, args ...string) bool { 184 cmd := exec.Command(name, args...) 185 if logs, err := cmd.CombinedOutput(); err != nil { 186 errMsg := "error running: " + name 187 for _, arg := range args { 188 errMsg += " " + arg 189 } 190 errMsg += ": " + err.Error() 191 logger.Printf("error running: %s\n", errMsg) 192 logger.Println(string(logs)) 193 return false 194 } 195 return true 196 }