github.com/yasker/longhorn-engine@v0.0.0-20160621014712-6ed6cfca0729/agent/controller/rest/backup.go (about)

     1  package rest
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"os"
    10  	"os/exec"
    11  	"sync"
    12  
    13  	"github.com/Sirupsen/logrus"
    14  	"github.com/gorilla/mux"
    15  
    16  	"github.com/rancher/go-rancher-metadata/metadata"
    17  	"github.com/rancher/go-rancher/api"
    18  	"github.com/rancher/longhorn/agent/controller"
    19  	"github.com/rancher/longhorn/agent/replica/rest"
    20  	"regexp"
    21  )
    22  
    23  // TODO Add logic to purge old entries from these maps
    24  var restoreMutex = &sync.RWMutex{}
    25  var restoreMap = make(map[string]*status)
    26  
    27  var backupMutex = &sync.RWMutex{}
    28  var backupMap = make(map[string]*status)
    29  
    30  var doesntExistRegex = regexp.MustCompile("cannot find.*in objectstore")
    31  
    32  func (s *Server) CreateBackup(rw http.ResponseWriter, req *http.Request) error {
    33  	logrus.Infof("Creating backup")
    34  
    35  	apiContext := api.GetApiContext(req)
    36  	id := mux.Vars(req)["id"]
    37  
    38  	snapshot, err := s.getSnapshot(apiContext, id)
    39  	if err != nil {
    40  		return err
    41  	}
    42  
    43  	if snapshot == nil {
    44  		rw.WriteHeader(http.StatusNotFound)
    45  		return nil
    46  	}
    47  
    48  	var input backupInput
    49  	if err := apiContext.Read(&input); err != nil {
    50  		return err
    51  	}
    52  
    53  	if input.UUID == "" {
    54  		rw.WriteHeader(http.StatusBadRequest)
    55  		return nil
    56  	}
    57  
    58  	if status := checkStatus(input.UUID, backupMap, backupMutex); status != nil {
    59  		logrus.Infof("Found status %v for id %v for backup request.", status, input.UUID)
    60  		return apiContext.WriteResource(status)
    61  	}
    62  
    63  	if err := prepareBackupTarget(input.BackupTarget); err != nil {
    64  		return err
    65  	}
    66  
    67  	destination := fmt.Sprintf("vfs:///var/lib/rancher/longhorn/backups/%v/%v", input.BackupTarget.Name, input.BackupTarget.UUID)
    68  	status, err := backup(input.UUID, snapshot.Id, destination)
    69  	if err != nil {
    70  		return err
    71  	}
    72  
    73  	return apiContext.WriteResource(status)
    74  }
    75  
    76  func (s *Server) RemoveBackup(rw http.ResponseWriter, req *http.Request) error {
    77  	apiContext := api.GetApiContext(req)
    78  
    79  	var input locationInput
    80  	if err := apiContext.Read(&input); err != nil {
    81  		return err
    82  	}
    83  	logrus.Infof("Removing backup %#v", input)
    84  
    85  	if input.Location == "" {
    86  		rw.WriteHeader(http.StatusBadRequest)
    87  		return nil
    88  	}
    89  
    90  	if err := prepareBackupTarget(input.BackupTarget); err != nil {
    91  		return err
    92  	}
    93  
    94  	exists, err := backupExists(input)
    95  	if err != nil {
    96  		return fmt.Errorf("Error while determining if backup exists: %v", err)
    97  	}
    98  
    99  	if !exists {
   100  		logrus.Infof("Backup [%v] doesn't exist. Nothing to remove.", input.Location)
   101  		rw.WriteHeader(http.StatusNoContent)
   102  		return nil
   103  	}
   104  
   105  	cmd := exec.Command("longhorn", "backup", "rm", input.Location)
   106  	cmd.Stderr = os.Stderr
   107  	cmd.Stdout = os.Stdout
   108  	logrus.Infof("Running %v", cmd.Args)
   109  	if err := cmd.Run(); err != nil {
   110  		return err
   111  	}
   112  
   113  	rw.WriteHeader(http.StatusNoContent)
   114  	return nil
   115  }
   116  
   117  func (s *Server) RestoreFromBackup(rw http.ResponseWriter, req *http.Request) error {
   118  	logrus.Infof("Restoring from backup")
   119  
   120  	apiContext := api.GetApiContext(req)
   121  	id := mux.Vars(req)["id"]
   122  
   123  	if id != "1" {
   124  		rw.WriteHeader(http.StatusNotFound)
   125  		return nil
   126  	}
   127  
   128  	var input locationInput
   129  	if err := apiContext.Read(&input); err != nil {
   130  		return err
   131  	}
   132  
   133  	if input.Location == "" || input.UUID == "" {
   134  		rw.WriteHeader(http.StatusBadRequest)
   135  		return nil
   136  	}
   137  
   138  	if status := checkStatus(input.UUID, restoreMap, restoreMutex); status != nil {
   139  		logrus.Infof("Found status %v for id %v for restore request.", status, input.UUID)
   140  		return apiContext.WriteResource(status)
   141  	}
   142  
   143  	if err := prepareBackupTarget(input.BackupTarget); err != nil {
   144  		return err
   145  	}
   146  
   147  	restoreStatus, err := restore(input.UUID, input.Location)
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	return apiContext.WriteResource(restoreStatus)
   153  }
   154  
   155  func restore(uuid, location string) (*status, error) {
   156  	cmd := exec.Command("longhorn", "backup", "restore", location)
   157  	logrus.Infof("Running restore command: %v", cmd.Args)
   158  	return doStatusBackedCommand(uuid, "restorestatus", cmd, restoreMap, restoreMutex)
   159  }
   160  
   161  func backup(uuid, snapshot, destination string) (*status, error) {
   162  	cmd := exec.Command("longhorn", "backup", "create", snapshot, "--dest", destination)
   163  	logrus.Infof("Running backup command: %v", cmd.Args)
   164  	return doStatusBackedCommand(uuid, "backupstatus", cmd, backupMap, backupMutex)
   165  }
   166  
   167  func doStatusBackedCommand(id, resourceType string, command *exec.Cmd, statusMap map[string]*status, statusMutex *sync.RWMutex) (*status, error) {
   168  	output := new(bytes.Buffer)
   169  	command.Stdout = output
   170  	command.Stderr = os.Stderr
   171  	err := command.Start()
   172  	if err != nil {
   173  		return &status{}, err
   174  	}
   175  
   176  	statusMutex.Lock()
   177  	defer statusMutex.Unlock()
   178  	status := newStatus(id, "running", "", resourceType)
   179  	statusMap[id] = status
   180  
   181  	go func(id string, c *exec.Cmd) {
   182  		var message string
   183  		var state string
   184  
   185  		err := c.Wait()
   186  		if err != nil {
   187  			logrus.Errorf("Error running command %v: %v", command.Args, err)
   188  			state = "error"
   189  			message = fmt.Sprintf("Error: %v", err)
   190  		} else {
   191  			logrus.Infof("Command %v completed successfuly.", command.Args)
   192  			state = "done"
   193  			message = output.String()
   194  		}
   195  
   196  		statusMutex.Lock()
   197  		defer statusMutex.Unlock()
   198  		status, ok := statusMap[id]
   199  		if !ok {
   200  			status = newStatus(id, "", "", resourceType)
   201  		}
   202  
   203  		status.State = state
   204  		status.Message = message
   205  		statusMap[id] = status
   206  	}(id, command)
   207  
   208  	return status, nil
   209  }
   210  
   211  func (s *Server) GetBackupStatus(rw http.ResponseWriter, req *http.Request) error {
   212  	logrus.Infof("Getting backup status")
   213  	return getStatus(backupMap, backupMutex, rw, req)
   214  }
   215  
   216  func (s *Server) GetRestoreStatus(rw http.ResponseWriter, req *http.Request) error {
   217  	logrus.Infof("Getting restore status")
   218  	return getStatus(restoreMap, restoreMutex, rw, req)
   219  }
   220  
   221  func checkStatus(id string, statusMap map[string]*status, statusMutex *sync.RWMutex) *status {
   222  	statusMutex.RLock()
   223  	defer statusMutex.RUnlock()
   224  	return statusMap[id]
   225  }
   226  
   227  func getStatus(statusMap map[string]*status, statusMutex *sync.RWMutex, rw http.ResponseWriter, req *http.Request) error {
   228  	apiContext := api.GetApiContext(req)
   229  	id := mux.Vars(req)["id"]
   230  
   231  	statusMutex.RLock()
   232  	defer statusMutex.RUnlock()
   233  
   234  	status, ok := statusMap[id]
   235  	if !ok {
   236  		rw.WriteHeader(http.StatusNotFound)
   237  		return nil
   238  	}
   239  
   240  	return apiContext.WriteResource(status)
   241  }
   242  
   243  func backupExists(input locationInput) (bool, error) {
   244  	cmd := exec.Command("longhorn", "backup", "inspect", input.Location)
   245  	stderr := new(bytes.Buffer)
   246  	cmd.Stderr = stderr
   247  	cmd.Stdout = os.Stdout
   248  	logrus.Infof("Running %v", cmd.Args)
   249  	if err := cmd.Run(); err != nil {
   250  		errOutput := stderr.String()
   251  		if doesntExistRegex.MatchString(errOutput) {
   252  			return false, nil
   253  		}
   254  		logrus.Errorf("Backup inspect error output: %v", errOutput)
   255  		return false, err
   256  	}
   257  
   258  	return true, nil
   259  }
   260  
   261  func prepareBackupTarget(target rest.BackupTarget) error {
   262  	replicas, err := replicRestEndpoints()
   263  	if err != nil {
   264  		return fmt.Errorf("Error getting replica endpoints: %v", err)
   265  	}
   266  
   267  	b, err := json.Marshal(target)
   268  	if err != nil {
   269  		return err
   270  	}
   271  
   272  	// This could be optimized with some goroutines and a waitgroup
   273  	for _, r := range replicas {
   274  		url := r + "/backuptargets"
   275  		resp, err := http.Post(url, "application/json", bytes.NewBuffer(b))
   276  		if err != nil {
   277  			return err
   278  		}
   279  
   280  		if resp.StatusCode >= 300 {
   281  			content, _ := ioutil.ReadAll(resp.Body)
   282  			resp.Body.Close()
   283  			return fmt.Errorf("Bad response preparing mount for %v: %v %v - %s", r, resp.StatusCode, resp.Status, content)
   284  		}
   285  		resp.Body.Close()
   286  	}
   287  
   288  	return nil
   289  }
   290  
   291  func replicRestEndpoints() ([]string, error) {
   292  	client, err := metadata.NewClientAndWait(controller.MetadataURL)
   293  	if err != nil {
   294  		return nil, err
   295  	}
   296  	service, err := client.GetSelfServiceByName("replica")
   297  	if err != nil {
   298  		return nil, err
   299  	}
   300  
   301  	containers := map[string]metadata.Container{}
   302  	for _, container := range service.Containers {
   303  		if c, ok := containers[container.Name]; !ok {
   304  			containers[container.Name] = container
   305  		} else if container.CreateIndex > c.CreateIndex {
   306  			containers[container.Name] = container
   307  		}
   308  	}
   309  
   310  	result := []string{}
   311  	for _, container := range containers {
   312  		endpoint := fmt.Sprintf("http://%s/v1", container.PrimaryIp)
   313  		result = append(result, endpoint)
   314  	}
   315  
   316  	return result, nil
   317  }