github.com/yasker/longhorn-engine@v0.0.0-20160621014712-6ed6cfca0729/replica/client/client.go (about) 1 package client 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "net/url" 10 "strconv" 11 "strings" 12 "time" 13 14 "github.com/Sirupsen/logrus" 15 "github.com/rancher/longhorn/replica/rest" 16 "github.com/rancher/longhorn/sync/agent" 17 ) 18 19 type ReplicaClient struct { 20 address string 21 syncAgent string 22 host string 23 httpClient *http.Client 24 } 25 26 func NewReplicaClient(address string) (*ReplicaClient, error) { 27 if strings.HasPrefix(address, "tcp://") { 28 address = address[6:] 29 } 30 31 if !strings.HasPrefix(address, "http") { 32 address = "http://" + address 33 } 34 35 if !strings.HasSuffix(address, "/v1") { 36 address += "/v1" 37 } 38 39 u, err := url.Parse(address) 40 if err != nil { 41 return nil, err 42 } 43 44 parts := strings.Split(u.Host, ":") 45 if len(parts) < 2 { 46 return nil, fmt.Errorf("Invalid address %s, must have a port in it", address) 47 } 48 49 port, err := strconv.Atoi(parts[1]) 50 if err != nil { 51 return nil, err 52 } 53 54 syncAgent := strings.Replace(address, fmt.Sprintf(":%d", port), fmt.Sprintf(":%d", port+2), -1) 55 56 timeout := time.Duration(30 * time.Second) 57 client := &http.Client{ 58 Timeout: timeout, 59 } 60 61 return &ReplicaClient{ 62 host: parts[0], 63 address: address, 64 syncAgent: syncAgent, 65 httpClient: client, 66 }, nil 67 } 68 69 func (c *ReplicaClient) Create(size string) error { 70 r, err := c.GetReplica() 71 if err != nil { 72 return err 73 } 74 75 return c.post(r.Actions["create"], rest.CreateInput{ 76 Size: size, 77 }, nil) 78 } 79 80 func (c *ReplicaClient) Revert(name string) error { 81 r, err := c.GetReplica() 82 if err != nil { 83 return err 84 } 85 86 return c.post(r.Actions["revert"], rest.RevertInput{ 87 Name: name, 88 }, nil) 89 } 90 91 func (c *ReplicaClient) Close() error { 92 r, err := c.GetReplica() 93 if err != nil { 94 return err 95 } 96 97 return c.post(r.Actions["close"], nil, nil) 98 } 99 100 func (c *ReplicaClient) SetRebuilding(rebuilding bool) error { 101 r, err := c.GetReplica() 102 if err != nil { 103 return err 104 } 105 106 return c.post(r.Actions["setrebuilding"], &rest.RebuildingInput{ 107 Rebuilding: rebuilding, 108 }, nil) 109 } 110 111 func (c *ReplicaClient) RemoveDisk(disk string, markOnly bool) error { 112 r, err := c.GetReplica() 113 if err != nil { 114 return err 115 } 116 117 return c.post(r.Actions["removedisk"], &rest.RemoveDiskInput{ 118 Name: disk, 119 MarkOnly: markOnly, 120 }, nil) 121 } 122 123 func (c *ReplicaClient) PrepareRemoveDisk(disk string) (rest.PrepareRemoveDiskOutput, error) { 124 var output rest.PrepareRemoveDiskOutput 125 r, err := c.GetReplica() 126 if err != nil { 127 return output, err 128 } 129 130 err = c.post(r.Actions["prepareremovedisk"], &rest.PrepareRemoveDiskInput{ 131 Name: disk, 132 }, &output) 133 return output, err 134 } 135 136 func (c *ReplicaClient) OpenReplica() error { 137 r, err := c.GetReplica() 138 if err != nil { 139 return err 140 } 141 142 return c.post(r.Actions["open"], nil, nil) 143 } 144 145 func (c *ReplicaClient) GetReplica() (rest.Replica, error) { 146 var replica rest.Replica 147 148 err := c.get(c.address+"/replicas/1", &replica) 149 return replica, err 150 } 151 152 func (c *ReplicaClient) ReloadReplica() (rest.Replica, error) { 153 var replica rest.Replica 154 155 err := c.post(c.address+"/replicas/1?action=reload", map[string]string{}, &replica) 156 return replica, err 157 } 158 159 func (c *ReplicaClient) LaunchReceiver() (string, int, error) { 160 var running agent.Process 161 err := c.post(c.syncAgent+"/processes", &agent.Process{ 162 ProcessType: "sync", 163 }, &running) 164 if err != nil { 165 return "", 0, err 166 } 167 168 return c.host, running.Port, nil 169 } 170 171 func (c *ReplicaClient) Coalesce(from, to string) error { 172 var running agent.Process 173 err := c.post(c.syncAgent+"/processes", &agent.Process{ 174 ProcessType: "fold", 175 SrcFile: from, 176 DestFile: to, 177 }, &running) 178 if err != nil { 179 return err 180 } 181 182 start := 250 * time.Millisecond 183 for { 184 err := c.get(running.Links["self"], &running) 185 if err != nil { 186 return err 187 } 188 189 switch running.ExitCode { 190 case -2: 191 time.Sleep(start) 192 start = start * 2 193 if start > 1*time.Second { 194 start = 1 * time.Second 195 } 196 case 0: 197 return nil 198 default: 199 return fmt.Errorf("ExitCode: %d", running.ExitCode) 200 } 201 } 202 } 203 204 func (c *ReplicaClient) SendFile(from, to, host string, port int) error { 205 var running agent.Process 206 err := c.post(c.syncAgent+"/processes", &agent.Process{ 207 ProcessType: "sync", 208 Host: host, 209 SrcFile: from, 210 DestFile: to, 211 Port: port, 212 }, &running) 213 if err != nil { 214 return err 215 } 216 217 start := 250 * time.Millisecond 218 for { 219 err := c.get(running.Links["self"], &running) 220 if err != nil { 221 return err 222 } 223 224 switch running.ExitCode { 225 case -2: 226 time.Sleep(start) 227 start = start * 2 228 if start > 1*time.Second { 229 start = 1 * time.Second 230 } 231 case 0: 232 return nil 233 default: 234 return fmt.Errorf("ExitCode: %d", running.ExitCode) 235 } 236 } 237 } 238 239 func (c *ReplicaClient) CreateBackup(snapshot, dest, volume string) (string, error) { 240 var running agent.Process 241 err := c.post(c.syncAgent+"/processes", &agent.Process{ 242 ProcessType: "backup", 243 SrcFile: snapshot, 244 DestFile: dest, 245 Host: volume, 246 }, &running) 247 if err != nil { 248 return "", err 249 } 250 251 start := 250 * time.Millisecond 252 for { 253 err := c.get(running.Links["self"], &running) 254 if err != nil { 255 return "", err 256 } 257 258 switch running.ExitCode { 259 case -2: 260 time.Sleep(start) 261 start = start * 2 262 if start > 1*time.Second { 263 start = 1 * time.Second 264 } 265 case 0: 266 return running.Output, nil 267 default: 268 return "", fmt.Errorf("ExitCode: %d, output: %v", 269 running.ExitCode, running.Output) 270 } 271 } 272 } 273 274 func (c *ReplicaClient) RmBackup(backup string) error { 275 var running agent.Process 276 err := c.post(c.syncAgent+"/processes", &agent.Process{ 277 ProcessType: "rmbackup", 278 SrcFile: backup, 279 }, &running) 280 if err != nil { 281 return err 282 } 283 284 start := 250 * time.Millisecond 285 for { 286 err := c.get(running.Links["self"], &running) 287 if err != nil { 288 return err 289 } 290 291 switch running.ExitCode { 292 case -2: 293 time.Sleep(start) 294 start = start * 2 295 if start > 1*time.Second { 296 start = 1 * time.Second 297 } 298 case 0: 299 return nil 300 default: 301 return fmt.Errorf("ExitCode: %d, output: %v", 302 running.ExitCode, running.Output) 303 } 304 } 305 } 306 307 func (c *ReplicaClient) RestoreBackup(backup, snapshotFile string) error { 308 var running agent.Process 309 err := c.post(c.syncAgent+"/processes", &agent.Process{ 310 ProcessType: "restore", 311 SrcFile: backup, 312 DestFile: snapshotFile, 313 }, &running) 314 if err != nil { 315 return err 316 } 317 318 start := 250 * time.Millisecond 319 for { 320 err := c.get(running.Links["self"], &running) 321 if err != nil { 322 return err 323 } 324 325 switch running.ExitCode { 326 case -2: 327 time.Sleep(start) 328 start = start * 2 329 if start > 1*time.Second { 330 start = 1 * time.Second 331 } 332 case 0: 333 return nil 334 default: 335 return fmt.Errorf("ExitCode: %d, output: %v", 336 running.ExitCode, running.Output) 337 } 338 } 339 } 340 341 func (c *ReplicaClient) InspectBackup(backup string) (string, error) { 342 var running agent.Process 343 err := c.post(c.syncAgent+"/processes", &agent.Process{ 344 ProcessType: "inspectbackup", 345 SrcFile: backup, 346 }, &running) 347 if err != nil { 348 return "", err 349 } 350 351 start := 250 * time.Millisecond 352 for { 353 err := c.get(running.Links["self"], &running) 354 if err != nil { 355 return "", err 356 } 357 358 switch running.ExitCode { 359 case -2: 360 time.Sleep(start) 361 start = start * 2 362 if start > 1*time.Second { 363 start = 1 * time.Second 364 } 365 case 0: 366 return running.Output, nil 367 default: 368 return "", fmt.Errorf("ExitCode: %d, output: %v", 369 running.ExitCode, running.Output) 370 } 371 } 372 } 373 374 func (c *ReplicaClient) get(url string, obj interface{}) error { 375 if !strings.HasPrefix(url, "http") { 376 url = c.address + url 377 } 378 379 resp, err := c.httpClient.Get(url) 380 if err != nil { 381 return err 382 } 383 defer resp.Body.Close() 384 385 return json.NewDecoder(resp.Body).Decode(obj) 386 } 387 388 func (c *ReplicaClient) post(path string, req, resp interface{}) error { 389 b, err := json.Marshal(req) 390 if err != nil { 391 return err 392 } 393 394 bodyType := "application/json" 395 url := path 396 if !strings.HasPrefix(url, "http") { 397 url = c.address + path 398 } 399 400 logrus.Debugf("POST %s", url) 401 402 httpResp, err := c.httpClient.Post(url, bodyType, bytes.NewBuffer(b)) 403 if err != nil { 404 return err 405 } 406 defer httpResp.Body.Close() 407 408 if httpResp.StatusCode >= 300 { 409 content, _ := ioutil.ReadAll(httpResp.Body) 410 return fmt.Errorf("Bad response: %d %s: %s", httpResp.StatusCode, httpResp.Status, content) 411 } 412 413 if resp == nil { 414 return nil 415 } 416 417 return json.NewDecoder(httpResp.Body).Decode(resp) 418 } 419 420 func (c *ReplicaClient) HardLink(from, to string) error { 421 var running agent.Process 422 err := c.post(c.syncAgent+"/processes", &agent.Process{ 423 ProcessType: "hardlink", 424 SrcFile: from, 425 DestFile: to, 426 }, &running) 427 if err != nil { 428 return err 429 } 430 431 start := 250 * time.Millisecond 432 for { 433 err := c.get(running.Links["self"], &running) 434 if err != nil { 435 return err 436 } 437 438 switch running.ExitCode { 439 case -2: 440 time.Sleep(start) 441 start = start * 2 442 if start > 1*time.Second { 443 start = 1 * time.Second 444 } 445 case 0: 446 return nil 447 default: 448 return fmt.Errorf("ExitCode: %d", running.ExitCode) 449 } 450 } 451 }