Пример #1
0
// execPtOsc shells out and uses pt-osc to actually run a migration.
func (runner *runner) execPtOsc(currentMigration *migration.Migration,
	ptOscOptionGenerator commandOptionGenerator, copyPercentChan chan int, unstageDone bool) (bool, error) {

	// unstageDone = whether or not unstagedMigrationsWaitGroup has already had .Done() called on it
	defer func() {
		if !unstageDone {
			unstagedMigrationsWaitGroup.Done()
		}
	}()
	canceled := false

	var fileRoutineWaitGroup sync.WaitGroup

	err := runner.createAndRetrieveFiles(currentMigration)
	if err != nil {
		return canceled, err
	}

	ptOscLogFile, ptOscLogWriter, err := setupLogWriter(currentMigration.LogFile)
	if err != nil {
		glog.Errorf("mig_id=%d: error creating pt-osc log file '%s' (error: %s)", currentMigration.Id, currentMigration.LogFile, err)
		return canceled, ErrPtOscExec
	}
	defer ptOscLogFile.Close()

	// generate the pt-osc command to run
	commandOptions := ptOscOptionGenerator(currentMigration)
	glog.Infof("mig_id=%d: Running %s %v", currentMigration.Id, runner.PtOscPath, strings.Join(commandOptions, " "))
	cmd := exec.Command(runner.PtOscPath, commandOptions...)

	// capture stdout and stderr of the command
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		glog.Errorf("mig_id=%d: error getting stdout pipe for pt-osc exec (error: %s)", currentMigration.Id, err)
		return canceled, ErrPtOscExec
	}
	stderr, err := cmd.StderrPipe()
	if err != nil {
		glog.Errorf("mig_id=%d: error getting stderr pipe for pt-osc exec (error: %s)", currentMigration.Id, err)
		return canceled, ErrPtOscExec
	}

	// start the pt-osc comand
	if err := cmd.Start(); err != nil {
		glog.Errorf("mig_id=%d: error starting pt-osc exec (error: %s)", currentMigration.Id, err)
		return canceled, ErrPtOscExec
	}

	// setup a channel and goroutine for logging output of stdout/stderr
	ptOscLogChan := make(chan string)
	fileRoutineWaitGroup.Add(1)
	fileSyncWaitGroup.Add(1)
	go runner.writeToPtOscLog(ptOscLogWriter, ptOscLogChan, writeLineToPtOscLog, currentMigration.Id, &fileRoutineWaitGroup)

	// setup a goroutine for sending statefiles to the api
	ptOscStateFileChan := make(chan string)
	fileRoutineWaitGroup.Add(1)
	fileSyncWaitGroup.Add(1)
	go runner.syncStateFile(currentMigration, ptOscStateFileChan, &fileRoutineWaitGroup)

	// setup goroutines for watching stdout/stderr of the command
	stdoutErrChan := make(chan error)
	stderrErrChan := make(chan error)
	go currentMigration.WatchMigrationStdout(stdout, stdoutErrChan, ptOscLogChan)
	if currentMigration.Status == migration.RunMigrationStatus {
		// setup a goroutine to continually update the % copied of the migration
		fileRoutineWaitGroup.Add(1)
		fileSyncWaitGroup.Add(1)
		go runner.updateMigrationCopyPercentage(currentMigration, copyPercentChan, &fileRoutineWaitGroup)
		go currentMigration.WatchMigrationCopyStderr(stderr, copyPercentChan, stderrErrChan, ptOscLogChan)
	} else {
		go currentMigration.WatchMigrationStderr(stderr, stderrErrChan, ptOscLogChan)
	}

	// save the pid of the pt-osc process
	currentMigration.Pid = cmd.Process.Pid
	glog.Infof("mig_id=%d: pt-osc pid for status %d is %d.", currentMigration.Id, currentMigration.Status, currentMigration.Pid)

	// add the migration id and pid to the running migration map
	runningMigMutex.Lock()
	runningMigrations[currentMigration.Id] = currentMigration.Pid
	runningMigMutex.Unlock()
	if !unstageDone {
		unstageDone = true
		unstagedMigrationsWaitGroup.Done()
	}

	// wait for both stdout and stderr error channels to receive a signal
	stdoutErr := <-stdoutErrChan
	stderrErr := <-stderrErrChan
	close(ptOscLogChan)
	close(ptOscStateFileChan)

	// remove the migration id from the running migration map
	runningMigMutex.Lock()
	delete(runningMigrations, currentMigration.Id)
	runningMigMutex.Unlock()

	// get the exit status of the command. if it was sent a SIGKILL (most likely
	// by another goroutine) we want to know because we will treat it differently
	failed := false
	if err := cmd.Wait(); err != nil {
		if exiterr, ok := err.(*exec.ExitError); ok {
			if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
				exitSignal := status.Signal().String()
				glog.Infof("mig_id=%d: exit signal was %s.", currentMigration.Id, exitSignal)
				if (exitSignal == SIGKILLSignal) && (currentMigration.Status == migration.RunMigrationStatus) {
					// was killed
					glog.Infof("mig_id=%d: migration must have been canceled", currentMigration.Id)
					canceled = true
				} else if currentMigration.Status == migration.RunMigrationStatus {
					// died for an unexpected reason
					glog.Infof("mig_id=%d: migration died for an unexpected reason", currentMigration.Id)
					failed = true
				}
			}
		}
	} else {
		if (stderrErr == nil) && (currentMigration.Status == migration.RunMigrationStatus) {
			// wasn't killed. copy completed 100%
			glog.Infof("mig_id=%d: updating migration with copy percentage of 100", currentMigration.Id)
			copyPercentChan <- 100
		}
	}

	if copyPercentChan != nil {
		glog.Infof("Closing copy percent channel")
		close(copyPercentChan)
	}

	fileRoutineWaitGroup.Wait() // wait for go routines to finish

	// favor returning error from unexpected failure, then error from stderr,
	// and lastly error from stdout
	if failed {
		return canceled, ErrUnexpectedExit
	} else if stderrErr != nil {
		return canceled, stderrErr
	} else if stdoutErr != nil {
		return canceled, stdoutErr
	}

	return canceled, nil
}