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  }