github.com/psiphon-labs/goarista@v0.0.0-20160825065156-d002785f4c67/lanz/client.go (about)

     1  // Copyright (C) 2016  Arista Networks, Inc.
     2  // Use of this source code is governed by the Apache License 2.0
     3  // that can be found in the COPYING file.
     4  
     5  // Package lanz implements a LANZ client that will listen to notofications from LANZ streaming
     6  // server and will decode them and send them as a protobuf over a channel to a receiver.
     7  package lanz
     8  
     9  import (
    10  	"bufio"
    11  	"encoding/binary"
    12  	"io"
    13  	"net"
    14  	"time"
    15  
    16  	pb "github.com/aristanetworks/goarista/lanz/proto"
    17  
    18  	"github.com/aristanetworks/glog"
    19  	"github.com/golang/protobuf/proto"
    20  )
    21  
    22  const (
    23  	defaultConnectTimeout = 10 * time.Second
    24  	defaultConnectBackoff = 30 * time.Second
    25  )
    26  
    27  // Client is the LANZ client interface.
    28  type Client interface {
    29  	// Run is the main loop of the client.
    30  	// It connects to the LANZ server and reads the notifications, decodes them
    31  	// and sends them to the channel.
    32  	// In case of disconnect, it will reconnect automatically.
    33  	Run(ch chan<- *pb.LanzRecord)
    34  	// Stops the client.
    35  	Stop()
    36  }
    37  
    38  // ConnectReadCloser extends the io.ReadCloser interface with a Connect method.
    39  type ConnectReadCloser interface {
    40  	io.ReadCloser
    41  	// Connect connects to the address, returning an error if it fails.
    42  	Connect() error
    43  }
    44  
    45  type client struct {
    46  	addr     string
    47  	stopping bool
    48  	timeout  time.Duration
    49  	backoff  time.Duration
    50  	conn     ConnectReadCloser
    51  }
    52  
    53  // New creates a new client with default TCP connection to the LANZ server.
    54  func New(opts ...Option) Client {
    55  	c := &client{
    56  		stopping: false,
    57  		timeout:  defaultConnectTimeout,
    58  		backoff:  defaultConnectBackoff,
    59  	}
    60  
    61  	for _, opt := range opts {
    62  		opt(c)
    63  	}
    64  
    65  	if c.conn == nil {
    66  		if c.addr == "" {
    67  			panic("Neither address, nor connector specified")
    68  		}
    69  		c.conn = &netConnector{
    70  			addr:    c.addr,
    71  			timeout: c.timeout,
    72  			backoff: c.backoff,
    73  		}
    74  	}
    75  
    76  	return c
    77  }
    78  
    79  func (c *client) Run(ch chan<- *pb.LanzRecord) {
    80  	for !c.stopping {
    81  		if err := c.conn.Connect(); err != nil && !c.stopping {
    82  			glog.V(1).Infof("Can't connect to LANZ server: %v", err)
    83  			time.Sleep(c.backoff)
    84  			continue
    85  		}
    86  		glog.V(1).Infof("Connected successfully to LANZ server: %v", c.addr)
    87  		if err := c.read(bufio.NewReader(c.conn), ch); err != nil && !c.stopping {
    88  			if err != io.EOF && err != io.ErrUnexpectedEOF {
    89  				glog.Errorf("Error receiving LANZ events: %v", err)
    90  			}
    91  			c.conn.Close()
    92  			time.Sleep(c.backoff)
    93  		}
    94  	}
    95  
    96  	close(ch)
    97  }
    98  
    99  func (c *client) read(r *bufio.Reader, ch chan<- *pb.LanzRecord) error {
   100  	for !c.stopping {
   101  		len, err := binary.ReadUvarint(r)
   102  		if err != nil {
   103  			return err
   104  		}
   105  
   106  		buf := make([]byte, len)
   107  		if _, err = io.ReadFull(r, buf); err != nil {
   108  			return err
   109  		}
   110  
   111  		rec := &pb.LanzRecord{}
   112  		if err = proto.Unmarshal(buf, rec); err != nil {
   113  			return err
   114  		}
   115  
   116  		ch <- rec
   117  	}
   118  
   119  	return nil
   120  }
   121  
   122  func (c *client) Stop() {
   123  	if c.stopping {
   124  		return
   125  	}
   126  
   127  	c.stopping = true
   128  	c.conn.Close()
   129  }
   130  
   131  type netConnector struct {
   132  	net.Conn
   133  	addr    string
   134  	timeout time.Duration
   135  	backoff time.Duration
   136  }
   137  
   138  func (c *netConnector) Connect() (err error) {
   139  	c.Conn, err = net.DialTimeout("tcp", c.addr, c.timeout)
   140  	if err != nil {
   141  	}
   142  	return
   143  }