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 }