github.com/kardianos/nomad@v0.1.3-0.20151022182107-b13df73ee850/command/agent/scada.go (about)

     1  package agent
     2  
     3  import (
     4  	"crypto/tls"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  	"os"
    10  	"strconv"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/hashicorp/scada-client"
    15  )
    16  
    17  const (
    18  	// providerService is the service name we use
    19  	providerService = "nomad"
    20  
    21  	// resourceType is the type of resource we represent
    22  	// when connecting to SCADA
    23  	resourceType = "nomad-cluster"
    24  )
    25  
    26  // ProviderService returns the service information for the provider
    27  func ProviderService(c *Config) *client.ProviderService {
    28  	return &client.ProviderService{
    29  		Service:        providerService,
    30  		ServiceVersion: fmt.Sprintf("%s%s", c.Version, c.VersionPrerelease),
    31  		Capabilities: map[string]int{
    32  			"http": 1,
    33  		},
    34  		Meta: map[string]string{
    35  			"auto-join":  strconv.FormatBool(c.Atlas.Join),
    36  			"region":     c.Region,
    37  			"datacenter": c.Datacenter,
    38  			"client":     strconv.FormatBool(c.Client != nil && c.Client.Enabled),
    39  			"server":     strconv.FormatBool(c.Server != nil && c.Server.Enabled),
    40  		},
    41  		ResourceType: resourceType,
    42  	}
    43  }
    44  
    45  // ProviderConfig returns the configuration for the SCADA provider
    46  func ProviderConfig(c *Config) *client.ProviderConfig {
    47  	return &client.ProviderConfig{
    48  		Service: ProviderService(c),
    49  		Handlers: map[string]client.CapabilityProvider{
    50  			"http": nil,
    51  		},
    52  		Endpoint:      c.Atlas.Endpoint,
    53  		ResourceGroup: c.Atlas.Infrastructure,
    54  		Token:         c.Atlas.Token,
    55  	}
    56  }
    57  
    58  // NewProvider creates a new SCADA provider using the
    59  // given configuration. Requests for the HTTP capability
    60  // are passed off to the listener that is returned.
    61  func NewProvider(c *Config, logOutput io.Writer) (*client.Provider, net.Listener, error) {
    62  	// Get the configuration of the provider
    63  	config := ProviderConfig(c)
    64  	config.LogOutput = logOutput
    65  
    66  	// SCADA_INSECURE env variable is used for testing to disable
    67  	// TLS certificate verification.
    68  	if os.Getenv("SCADA_INSECURE") != "" {
    69  		config.TLSConfig = &tls.Config{
    70  			InsecureSkipVerify: true,
    71  		}
    72  	}
    73  
    74  	// Create an HTTP listener and handler
    75  	list := newScadaListener(c.Atlas.Infrastructure)
    76  	config.Handlers["http"] = func(capability string, meta map[string]string,
    77  		conn io.ReadWriteCloser) error {
    78  		return list.PushRWC(conn)
    79  	}
    80  
    81  	// Create the provider
    82  	provider, err := client.NewProvider(config)
    83  	if err != nil {
    84  		list.Close()
    85  		return nil, nil, err
    86  	}
    87  	return provider, list, nil
    88  }
    89  
    90  // scadaListener is used to return a net.Listener for
    91  // incoming SCADA connections
    92  type scadaListener struct {
    93  	addr    *scadaAddr
    94  	pending chan net.Conn
    95  
    96  	closed   bool
    97  	closedCh chan struct{}
    98  	l        sync.Mutex
    99  }
   100  
   101  // newScadaListener returns a new listener
   102  func newScadaListener(infra string) *scadaListener {
   103  	l := &scadaListener{
   104  		addr:     &scadaAddr{infra},
   105  		pending:  make(chan net.Conn),
   106  		closedCh: make(chan struct{}),
   107  	}
   108  	return l
   109  }
   110  
   111  // PushRWC is used to push a io.ReadWriteCloser as a net.Conn
   112  func (s *scadaListener) PushRWC(conn io.ReadWriteCloser) error {
   113  	// Check if this already implements net.Conn
   114  	if nc, ok := conn.(net.Conn); ok {
   115  		return s.Push(nc)
   116  	}
   117  
   118  	// Wrap to implement the interface
   119  	wrapped := &scadaRWC{conn, s.addr}
   120  	return s.Push(wrapped)
   121  }
   122  
   123  // Push is used to add a connection to the queu
   124  func (s *scadaListener) Push(conn net.Conn) error {
   125  	select {
   126  	case s.pending <- conn:
   127  		return nil
   128  	case <-time.After(time.Second):
   129  		return fmt.Errorf("accept timed out")
   130  	case <-s.closedCh:
   131  		return fmt.Errorf("scada listener closed")
   132  	}
   133  }
   134  
   135  func (s *scadaListener) Accept() (net.Conn, error) {
   136  	select {
   137  	case conn := <-s.pending:
   138  		return conn, nil
   139  	case <-s.closedCh:
   140  		return nil, fmt.Errorf("scada listener closed")
   141  	}
   142  }
   143  
   144  func (s *scadaListener) Close() error {
   145  	s.l.Lock()
   146  	defer s.l.Unlock()
   147  	if s.closed {
   148  		return nil
   149  	}
   150  	s.closed = true
   151  	close(s.closedCh)
   152  	return nil
   153  }
   154  
   155  func (s *scadaListener) Addr() net.Addr {
   156  	return s.addr
   157  }
   158  
   159  // scadaAddr is used to return a net.Addr for SCADA
   160  type scadaAddr struct {
   161  	infra string
   162  }
   163  
   164  func (s *scadaAddr) Network() string {
   165  	return "SCADA"
   166  }
   167  
   168  func (s *scadaAddr) String() string {
   169  	return fmt.Sprintf("SCADA::Atlas::%s", s.infra)
   170  }
   171  
   172  type scadaRWC struct {
   173  	io.ReadWriteCloser
   174  	addr *scadaAddr
   175  }
   176  
   177  func (s *scadaRWC) LocalAddr() net.Addr {
   178  	return s.addr
   179  }
   180  
   181  func (s *scadaRWC) RemoteAddr() net.Addr {
   182  	return s.addr
   183  }
   184  
   185  func (s *scadaRWC) SetDeadline(t time.Time) error {
   186  	return errors.New("SCADA.Conn does not support deadlines")
   187  }
   188  
   189  func (s *scadaRWC) SetReadDeadline(t time.Time) error {
   190  	return errors.New("SCADA.Conn does not support deadlines")
   191  }
   192  
   193  func (s *scadaRWC) SetWriteDeadline(t time.Time) error {
   194  	return errors.New("SCADA.Conn does not support deadlines")
   195  }