github.com/Cloud-Foundations/Dominator@v0.3.4/cmd/installer/main.go (about)

     1  // +build linux
     2  
     3  package main
     4  
     5  import (
     6  	"flag"
     7  	"fmt"
     8  	stdlog "log"
     9  	"os"
    10  	"path/filepath"
    11  	"sync"
    12  	"syscall"
    13  	"time"
    14  
    15  	"github.com/Cloud-Foundations/Dominator/lib/constants"
    16  	"github.com/Cloud-Foundations/Dominator/lib/flags/loadflags"
    17  	"github.com/Cloud-Foundations/Dominator/lib/fsutil"
    18  	"github.com/Cloud-Foundations/Dominator/lib/log"
    19  	"github.com/Cloud-Foundations/Dominator/lib/log/debuglogger"
    20  	"github.com/Cloud-Foundations/Dominator/lib/logbuf"
    21  	"github.com/Cloud-Foundations/Dominator/lib/srpc"
    22  	"github.com/Cloud-Foundations/Dominator/lib/srpc/setupserver"
    23  	"github.com/Cloud-Foundations/tricorder/go/tricorder"
    24  )
    25  
    26  const logfile = "/var/log/installer/latest"
    27  
    28  type flusher interface {
    29  	Flush() error
    30  }
    31  
    32  type Rebooter interface {
    33  	Reboot() error
    34  	String() string
    35  }
    36  
    37  var (
    38  	dryRun = flag.Bool("dryRun", ifUnprivileged(),
    39  		"If true, do not make changes")
    40  	mountPoint = flag.String("mountPoint", "/mnt",
    41  		"Mount point for new root file-system")
    42  	objectsDirectory = flag.String("objectsDirectory", "/objects",
    43  		"Directory where cached objects will be written")
    44  	logDebugLevel = flag.Int("logDebugLevel", -1, "Debug log level")
    45  	portNum       = flag.Uint("portNum", constants.InstallerPortNumber,
    46  		"Port number to allocate and listen on for HTTP/RPC")
    47  	procDirectory = flag.String("procDirectory", "/proc",
    48  		"Directory where procfs is mounted")
    49  	skipNetwork = flag.Bool("skipNetwork", false,
    50  		"If true, do not update target network configuration")
    51  	skipStorage = flag.Bool("skipStorage", false,
    52  		"If true, do not update storage")
    53  	sysfsDirectory = flag.String("sysfsDirectory", "/sys",
    54  		"Directory where sysfs is mounted")
    55  	tftpDirectory = flag.String("tftpDirectory", "/tftpdata",
    56  		"Directory containing (possibly injected) TFTP data")
    57  	tmpRoot = flag.String("tmpRoot", "/tmproot",
    58  		"Mount point for temporary (tmpfs) root file-system")
    59  )
    60  
    61  func copyLogs(logFlusher flusher) error {
    62  	logFlusher.Flush()
    63  	logdir := filepath.Join(*mountPoint, "var", "log", "installer")
    64  	return fsutil.CopyFile(filepath.Join(logdir, "log"), logfile,
    65  		fsutil.PublicFilePerms)
    66  }
    67  
    68  func createLogger() (*logbuf.LogBuffer, log.DebugLogger) {
    69  	os.MkdirAll("/var/log/installer", fsutil.DirPerms)
    70  	options := logbuf.GetStandardOptions()
    71  	options.AlsoLogToStderr = true
    72  	logBuffer := logbuf.NewWithOptions(options)
    73  	logger := debuglogger.New(stdlog.New(logBuffer, "", 0))
    74  	logger.SetLevel(int16(*logDebugLevel))
    75  	srpc.SetDefaultLogger(logger)
    76  	return logBuffer, logger
    77  }
    78  
    79  func ifUnprivileged() bool {
    80  	if os.Geteuid() != 0 {
    81  		return true
    82  	}
    83  	return false
    84  }
    85  
    86  func install(updateHwClock bool, logFlusher flusher,
    87  	logger log.DebugLogger) (Rebooter, error) {
    88  	var rebooter Rebooter
    89  	machineInfo, interfaces, err := configureLocalNetwork(logger)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  	if !*skipStorage {
    94  		rebooter, err = configureStorage(*machineInfo, logger)
    95  		if err != nil {
    96  			return nil, err
    97  		}
    98  		if !*dryRun && updateHwClock {
    99  			if err := run("hwclock", *tmpRoot, logger, "-w"); err != nil {
   100  				logger.Printf("Error updating hardware clock: %s\n", err)
   101  			} else {
   102  				logger.Println("Updated hardware clock from system clock")
   103  			}
   104  		}
   105  	}
   106  	if !*skipNetwork {
   107  		err := configureNetwork(*machineInfo, interfaces, logger)
   108  		if err != nil {
   109  			return nil, err
   110  		}
   111  	}
   112  	if err := copyLogs(logFlusher); err != nil {
   113  		return nil, fmt.Errorf("error copying logs: %s", err)
   114  	}
   115  	if err := unmountStorage(logger); err != nil {
   116  		return nil, fmt.Errorf("error unmounting: %s", err)
   117  	}
   118  	return rebooter, nil
   119  }
   120  
   121  func printAndWait(initialTimeoutString, waitTimeoutString string,
   122  	waitGroup *sync.WaitGroup, rebooterName string, logger log.Logger) {
   123  	initialTimeout, _ := time.ParseDuration(initialTimeoutString)
   124  	if initialTimeout < time.Second {
   125  		initialTimeout = time.Second
   126  		initialTimeoutString = "1s"
   127  	}
   128  	logger.Printf("waiting %s before rebooting with %s rebooter\n",
   129  		initialTimeoutString, rebooterName)
   130  	time.Sleep(initialTimeout - time.Second)
   131  	waitChannel := make(chan struct{})
   132  	go func() {
   133  		waitGroup.Wait()
   134  		waitChannel <- struct{}{}
   135  	}()
   136  	timer := time.NewTimer(time.Second)
   137  	select {
   138  	case <-timer.C:
   139  	case <-waitChannel:
   140  		return
   141  	}
   142  	logger.Printf(
   143  		"waiting %s for remote shells to terminate before rebooting\n",
   144  		waitTimeoutString)
   145  	waitTimeout, _ := time.ParseDuration(waitTimeoutString)
   146  	timer.Reset(waitTimeout)
   147  	select {
   148  	case <-timer.C:
   149  	case <-waitChannel:
   150  	}
   151  }
   152  
   153  func doMain() error {
   154  	var timeLogMessage string
   155  	if fi, err := os.Stat("/build-timestamp"); err != nil {
   156  		return err
   157  	} else {
   158  		now := time.Now()
   159  		if fi.ModTime().After(now) {
   160  			timeval := syscall.Timeval{Sec: fi.ModTime().Unix()}
   161  			if err := syscall.Settimeofday(&timeval); err != nil {
   162  				return err
   163  			}
   164  			timeLogMessage = fmt.Sprintf("System time: %s is earlier than build time: %s.\nAdvancing to build time",
   165  				now, fi.ModTime())
   166  		}
   167  	}
   168  	if err := loadflags.LoadForDaemon("installer"); err != nil {
   169  		return err
   170  	}
   171  	flag.Parse()
   172  	tricorder.RegisterFlags()
   173  	logBuffer, logger := createLogger()
   174  	defer logBuffer.Flush()
   175  	if timeLogMessage != "" {
   176  		logger.Println(timeLogMessage)
   177  	}
   178  	go runShellOnConsole(logger)
   179  	AddHtmlWriter(logBuffer)
   180  	params := setupserver.Params{Logger: logger}
   181  	if err := setupserver.SetupTlsWithParams(params); err != nil {
   182  		logger.Println(err)
   183  	}
   184  	waitGroup := &sync.WaitGroup{}
   185  	if newLogger, err := startServer(*portNum, waitGroup, logger); err != nil {
   186  		logger.Printf("cannot start server: %s\n", err)
   187  	} else {
   188  		logger = newLogger
   189  	}
   190  	rebooter, err := install(timeLogMessage != "", logBuffer, logger)
   191  	rebooterName := "default"
   192  	if rebooter != nil {
   193  		rebooterName = rebooter.String()
   194  	}
   195  	if err != nil {
   196  		logger.Println(err)
   197  		printAndWait("5m", "1h", waitGroup, rebooterName, logger)
   198  	} else {
   199  		printAndWait("5s", "5m", waitGroup, rebooterName, logger)
   200  	}
   201  	syscall.Sync()
   202  	if rebooter != nil {
   203  		if err := rebooter.Reboot(); err != nil {
   204  			logger.Printf("error calling %s rebooter: %s\n", rebooterName, err)
   205  			logger.Println("falling back to default rebooter after 5 minutes")
   206  			time.Sleep(time.Minute * 5)
   207  		}
   208  	}
   209  	if err := syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART); err != nil {
   210  		logger.Fatalf("error rebooting: %s\n", err)
   211  	}
   212  	return nil
   213  }
   214  
   215  func main() {
   216  	if err := doMain(); err != nil {
   217  		fmt.Fprintln(os.Stderr, err)
   218  		os.Exit(1)
   219  	}
   220  }