github.com/yasker/longhorn-engine@v0.0.0-20160621014712-6ed6cfca0729/backend/remote/remote.go (about)

     1  package remote
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"net"
    10  	"net/http"
    11  	"strconv"
    12  	"time"
    13  
    14  	"github.com/Sirupsen/logrus"
    15  	"github.com/rancher/longhorn/replica/rest"
    16  	"github.com/rancher/longhorn/rpc"
    17  	"github.com/rancher/longhorn/types"
    18  	"github.com/rancher/longhorn/util"
    19  	journal "github.com/rancher/sparse-tools/stats"
    20  )
    21  
    22  var (
    23  	pingRetries = 6
    24  	pingTimeout = 20 * time.Second
    25  	pingInveral = 2 * time.Second
    26  
    27  	timeout        = 30 * time.Second
    28  	requestBuffer  = 1024
    29  	ErrPingTimeout = errors.New("Ping timeout")
    30  )
    31  
    32  func New() types.BackendFactory {
    33  	return &Factory{}
    34  }
    35  
    36  type Factory struct {
    37  }
    38  
    39  type Remote struct {
    40  	types.ReaderWriterAt
    41  	name       string
    42  	pingURL    string
    43  	replicaURL string
    44  	httpClient *http.Client
    45  	closeChan  chan struct{}
    46  }
    47  
    48  func (r *Remote) Close() error {
    49  	r.closeChan <- struct{}{}
    50  	logrus.Infof("Closing: %s", r.name)
    51  	return r.doAction("close", "")
    52  }
    53  
    54  func (r *Remote) open() error {
    55  	logrus.Infof("Opening: %s", r.name)
    56  	return r.doAction("open", "")
    57  }
    58  
    59  func (r *Remote) Snapshot(name string) error {
    60  	logrus.Infof("Snapshot: %s %s", r.name, name)
    61  	return r.doAction("snapshot", name)
    62  }
    63  
    64  func (r *Remote) doAction(action, name string) error {
    65  	body := io.Reader(nil)
    66  	if name != "" {
    67  		buffer := &bytes.Buffer{}
    68  		if err := json.NewEncoder(buffer).Encode(&map[string]string{"name": name}); err != nil {
    69  			return err
    70  		}
    71  		body = buffer
    72  	}
    73  
    74  	req, err := http.NewRequest("POST", r.replicaURL+"?action="+action, body)
    75  	if err != nil {
    76  		return err
    77  	}
    78  
    79  	if name != "" {
    80  		req.Header.Add("Content-Type", "application/json")
    81  	}
    82  
    83  	resp, err := r.httpClient.Do(req)
    84  	if err != nil {
    85  		return err
    86  	}
    87  	defer resp.Body.Close()
    88  
    89  	if resp.StatusCode != http.StatusOK {
    90  		return fmt.Errorf("Bad status: %d %s", resp.StatusCode, resp.Status)
    91  	}
    92  
    93  	return nil
    94  }
    95  
    96  func (r *Remote) Size() (int64, error) {
    97  	replica, err := r.info()
    98  	if err != nil {
    99  		return 0, err
   100  	}
   101  	return strconv.ParseInt(replica.Size, 10, 0)
   102  }
   103  
   104  func (r *Remote) SectorSize() (int64, error) {
   105  	replica, err := r.info()
   106  	if err != nil {
   107  		return 0, err
   108  	}
   109  	return replica.SectorSize, nil
   110  }
   111  
   112  func (r *Remote) info() (rest.Replica, error) {
   113  	var replica rest.Replica
   114  	req, err := http.NewRequest("GET", r.replicaURL, nil)
   115  	if err != nil {
   116  		return replica, err
   117  	}
   118  
   119  	resp, err := r.httpClient.Do(req)
   120  	if err != nil {
   121  		return replica, err
   122  	}
   123  	defer resp.Body.Close()
   124  
   125  	if resp.StatusCode != http.StatusOK {
   126  		return replica, fmt.Errorf("Bad status: %d %s", resp.StatusCode, resp.Status)
   127  	}
   128  
   129  	err = json.NewDecoder(resp.Body).Decode(&replica)
   130  	return replica, err
   131  }
   132  
   133  func (rf *Factory) Create(address string) (types.Backend, error) {
   134  	logrus.Infof("Connecting to remote: %s", address)
   135  
   136  	controlAddress, dataAddress, _, err := util.ParseAddresses(address)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	r := &Remote{
   142  		name:       address,
   143  		replicaURL: fmt.Sprintf("http://%s/v1/replicas/1", controlAddress),
   144  		pingURL:    fmt.Sprintf("http://%s/ping", controlAddress),
   145  		httpClient: &http.Client{
   146  			Timeout: timeout,
   147  		},
   148  		closeChan: make(chan struct{}, 1),
   149  	}
   150  
   151  	replica, err := r.info()
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	if replica.State != "closed" {
   157  		return nil, fmt.Errorf("Replica must be closed, Can not add in state: %s", replica.State)
   158  	}
   159  
   160  	conn, err := net.Dial("tcp", dataAddress)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	rpc := rpc.NewClient(conn)
   166  	r.ReaderWriterAt = rpc
   167  
   168  	if err := r.open(); err != nil {
   169  		return nil, err
   170  	}
   171  
   172  	go r.monitorPing(rpc)
   173  
   174  	return r, nil
   175  }
   176  
   177  func (r *Remote) monitorPing(client *rpc.Client) error {
   178  	ticker := time.NewTicker(pingInveral)
   179  	defer ticker.Stop()
   180  
   181  	retry := 0
   182  	for {
   183  		select {
   184  		case <-r.closeChan:
   185  			return nil
   186  		case <-ticker.C:
   187  			if err := r.Ping(client); err == nil {
   188  				retry = 0 // reset on success
   189  			} else {
   190  				if retry < pingRetries {
   191  					retry++
   192  					logrus.Errorf("Ping retry %v on replica %v; error: %v", retry, r.replicaURL, err)
   193  					journal.PrintLimited(1000) //flush automatically upon retry
   194  				} else {
   195  					logrus.Errorf("Failed to get ping response from replica %v; error: %v", r.replicaURL, err)
   196  					journal.PrintLimited(1000) //flush automatically upon error
   197  					client.SetError(err)
   198  					return err
   199  				}
   200  			}
   201  		}
   202  	}
   203  }
   204  
   205  func (r *Remote) Ping(client *rpc.Client) error {
   206  	ret := make(chan error, 1)
   207  	// use replica data addr for ping target tracking
   208  	opID := journal.InsertPendingOp(time.Now(), client.TargetID(), journal.OpPing, 0)
   209  
   210  	go func() {
   211  		resp, err := r.httpClient.Get(r.pingURL)
   212  		if err != nil {
   213  			ret <- err
   214  			return
   215  		}
   216  		defer resp.Body.Close()
   217  		if resp.StatusCode != 200 {
   218  			ret <- fmt.Errorf("Non-200 response %d from ping to %s", resp.StatusCode, r.name)
   219  			return
   220  		}
   221  		ret <- nil
   222  	}()
   223  
   224  	select {
   225  	case err := <-ret:
   226  		journal.RemovePendingOp(opID, err == nil)
   227  		return err
   228  	case <-time.After(pingTimeout):
   229  		journal.RemovePendingOp(opID, false)
   230  		return ErrPingTimeout
   231  	}
   232  }