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 }