
This adds two new commands: hummingbird moveparts [old ring.gz] [new ring.gz] After a rebalance, moveparts will diff the two rings and instruct the object replicators (via HTTP call) to execute jobs that bulk move any re-assigned partitions. hummingbird restoredevice [ip] [device-name] restoredevice will instruct peers of the selected device to sync over their copies of any partitions that should be on that drive. It can be used to speed up restoring a drive after it's been swapped. They should both run until all of the jobs have been taken up by the replicator, so a large job might take a while. This means the replicator has a semi-real HTTP server now, and not just a debug port. It binds to the replication object server's port + 500, which is totally arbitrary and open to bikeshedding. Change-Id: I4bbb2719657eb84c5191e053a1654e986511bb95
162 lines
3.5 KiB
Go
162 lines
3.5 KiB
Go
// Copyright (c) 2015 Rackspace
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
// implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package objectserver
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"time"
|
|
)
|
|
|
|
var RepUnmountedError = fmt.Errorf("Device unmounted")
|
|
var repDialer = (&net.Dialer{Timeout: 5 * time.Second, KeepAlive: 30 * time.Second}).Dial
|
|
|
|
const repITimeout = time.Minute * 10
|
|
const repOTimeout = time.Minute
|
|
|
|
type BeginReplicationRequest struct {
|
|
Device string
|
|
Partition string
|
|
NeedHashes bool
|
|
}
|
|
|
|
type BeginReplicationResponse struct {
|
|
Hashes map[string]string
|
|
}
|
|
|
|
type SyncFileRequest struct {
|
|
Path string
|
|
Xattrs string
|
|
Size int64
|
|
Done bool
|
|
}
|
|
|
|
type SyncFileResponse struct {
|
|
Exists bool
|
|
NewerExists bool
|
|
GoAhead bool
|
|
Msg string
|
|
}
|
|
|
|
type FileUploadResponse struct {
|
|
Success bool
|
|
Msg string
|
|
}
|
|
|
|
type RepConn struct {
|
|
rw *bufio.ReadWriter
|
|
c net.Conn
|
|
Disconnected bool
|
|
}
|
|
|
|
func (r *RepConn) SendMessage(v interface{}) error {
|
|
r.c.SetDeadline(time.Now().Add(repOTimeout))
|
|
jsoned, err := json.Marshal(v)
|
|
if err != nil {
|
|
r.Close()
|
|
return err
|
|
}
|
|
if err := binary.Write(r.rw, binary.BigEndian, uint32(len(jsoned))); err != nil {
|
|
r.Close()
|
|
return err
|
|
}
|
|
if _, err := r.rw.Write(jsoned); err != nil {
|
|
r.Close()
|
|
return err
|
|
}
|
|
if err := r.rw.Flush(); err != nil {
|
|
r.Close()
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *RepConn) RecvMessage(v interface{}) (err error) {
|
|
r.c.SetDeadline(time.Now().Add(repITimeout))
|
|
var length uint32
|
|
if err = binary.Read(r.rw, binary.BigEndian, &length); err != nil {
|
|
r.Close()
|
|
return
|
|
}
|
|
data := make([]byte, length)
|
|
if _, err = io.ReadFull(r.rw, data); err != nil {
|
|
r.Close()
|
|
return
|
|
}
|
|
if err = json.Unmarshal(data, v); err != nil {
|
|
r.Close()
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func (r *RepConn) Write(data []byte) (l int, err error) {
|
|
r.c.SetDeadline(time.Now().Add(repOTimeout))
|
|
if l, err = r.rw.Write(data); err != nil {
|
|
r.Close()
|
|
}
|
|
return
|
|
}
|
|
|
|
func (r *RepConn) Flush() (err error) {
|
|
r.c.SetDeadline(time.Now().Add(repOTimeout))
|
|
if err = r.rw.Flush(); err != nil {
|
|
r.Close()
|
|
}
|
|
return
|
|
}
|
|
|
|
func (r *RepConn) Read(data []byte) (l int, err error) {
|
|
r.c.SetDeadline(time.Now().Add(repITimeout))
|
|
if l, err = io.ReadFull(r.rw, data); err != nil {
|
|
r.Close()
|
|
}
|
|
return
|
|
}
|
|
|
|
func (r *RepConn) Close() {
|
|
r.Disconnected = true
|
|
r.c.Close()
|
|
}
|
|
|
|
func NewRepConn(ip string, port int, device string, partition string) (*RepConn, error) {
|
|
url := fmt.Sprintf("http://%s:%d/%s/%s", ip, port, device, partition)
|
|
req, err := http.NewRequest("REPCONN", url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
conn, err := repDialer("tcp", req.URL.Host)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
hc := httputil.NewClientConn(conn, nil)
|
|
resp, err := hc.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resp.StatusCode/100 != 2 {
|
|
return nil, RepUnmountedError
|
|
}
|
|
newc, _ := hc.Hijack()
|
|
return &RepConn{rw: bufio.NewReadWriter(bufio.NewReader(newc), bufio.NewWriter(newc)), c: newc}, nil
|
|
}
|