Codebase list golang-github-hashicorp-scada-client / b66405f
Imported Upstream version 0.0~git20160601.0.6e89678 Dmitry Smirnov 7 years ago
2 changed file(s) with 354 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 package scada
1
2 import (
3 "crypto/tls"
4 "errors"
5 "fmt"
6 "io"
7 "net"
8 "os"
9 "sync"
10 "time"
11
12 sc "github.com/hashicorp/scada-client"
13 )
14
15 // Provider wraps scada-client.Provider to allow most applications to only pull
16 // in this package
17 type Provider struct {
18 *sc.Provider
19 }
20
21 type AtlasConfig struct {
22 // Endpoint is the SCADA endpoint used for Atlas integration. If empty, the
23 // defaults from the provider are used.
24 Endpoint string `mapstructure:"endpoint"`
25
26 // The name of the infrastructure we belong to, e.g. "hashicorp/prod"
27 Infrastructure string `mapstructure:"infrastructure"`
28
29 // The Atlas authentication token
30 Token string `mapstructure:"token" json:"-"`
31 }
32
33 // Config holds the high-level information used to instantiate a SCADA provider
34 // and listener
35 type Config struct {
36 // The service name to use
37 Service string
38
39 // The version of the service
40 Version string
41
42 // The type of resource we represent
43 ResourceType string
44
45 // Metadata to send to along with the service information
46 Meta map[string]string
47
48 // If set, TLS certificate verification will be skipped. The value of the
49 // SCADA_INSECURE environment variable will be considered if this is false.
50 // If using SCADA_INSECURE, any non-empty value will trigger insecure mode.
51 Insecure bool
52
53 // Holds Atlas configuration
54 Atlas AtlasConfig
55 }
56
57 // ProviderService returns the service information for the provider
58 func providerService(c *Config) *sc.ProviderService {
59 ret := &sc.ProviderService{
60 Service: c.Service,
61 ServiceVersion: c.Version,
62 Capabilities: map[string]int{},
63 Meta: c.Meta,
64 ResourceType: c.ResourceType,
65 }
66
67 return ret
68 }
69
70 // providerConfig returns the configuration for the SCADA provider
71 func providerConfig(c *Config) *sc.ProviderConfig {
72 ret := &sc.ProviderConfig{
73 Service: providerService(c),
74 Handlers: map[string]sc.CapabilityProvider{},
75 Endpoint: c.Atlas.Endpoint,
76 ResourceGroup: c.Atlas.Infrastructure,
77 Token: c.Atlas.Token,
78 }
79
80 // SCADA_INSECURE env variable is used for testing to disable TLS
81 // certificate verification.
82 insecure := c.Insecure
83 if !insecure {
84 if os.Getenv("SCADA_INSECURE") != "" {
85 insecure = true
86 }
87 }
88 if insecure {
89 ret.TLSConfig = &tls.Config{
90 InsecureSkipVerify: true,
91 }
92 }
93
94 return ret
95 }
96
97 // NewProvider creates a new SCADA provider using the given configuration.
98 // Requests for the HTTP capability are passed off to the listener that is
99 // returned.
100 func NewHTTPProvider(c *Config, logOutput io.Writer) (*Provider, net.Listener, error) {
101 // Get the configuration of the provider
102 config := providerConfig(c)
103 config.LogOutput = logOutput
104
105 // Set the HTTP capability
106 config.Service.Capabilities["http"] = 1
107
108 // Create an HTTP listener and handler
109 list := newScadaListener(c.Atlas.Infrastructure)
110 config.Handlers["http"] = func(capability string, meta map[string]string,
111 conn io.ReadWriteCloser) error {
112 return list.PushRWC(conn)
113 }
114
115 // Create the provider
116 provider, err := sc.NewProvider(config)
117 if err != nil {
118 list.Close()
119 return nil, nil, err
120 }
121
122 return &Provider{provider}, list, nil
123 }
124
125 // scadaListener is used to return a net.Listener for
126 // incoming SCADA connections
127 type scadaListener struct {
128 addr *scadaAddr
129 pending chan net.Conn
130
131 closed bool
132 closedCh chan struct{}
133 l sync.Mutex
134 }
135
136 // newScadaListener returns a new listener
137 func newScadaListener(infra string) *scadaListener {
138 l := &scadaListener{
139 addr: &scadaAddr{infra},
140 pending: make(chan net.Conn),
141 closedCh: make(chan struct{}),
142 }
143 return l
144 }
145
146 // PushRWC is used to push a io.ReadWriteCloser as a net.Conn
147 func (s *scadaListener) PushRWC(conn io.ReadWriteCloser) error {
148 // Check if this already implements net.Conn
149 if nc, ok := conn.(net.Conn); ok {
150 return s.Push(nc)
151 }
152
153 // Wrap to implement the interface
154 wrapped := &scadaRWC{conn, s.addr}
155 return s.Push(wrapped)
156 }
157
158 // Push is used to add a connection to the queu
159 func (s *scadaListener) Push(conn net.Conn) error {
160 select {
161 case s.pending <- conn:
162 return nil
163 case <-time.After(time.Second):
164 return fmt.Errorf("accept timed out")
165 case <-s.closedCh:
166 return fmt.Errorf("scada listener closed")
167 }
168 }
169
170 func (s *scadaListener) Accept() (net.Conn, error) {
171 select {
172 case conn := <-s.pending:
173 return conn, nil
174 case <-s.closedCh:
175 return nil, fmt.Errorf("scada listener closed")
176 }
177 }
178
179 func (s *scadaListener) Close() error {
180 s.l.Lock()
181 defer s.l.Unlock()
182 if s.closed {
183 return nil
184 }
185 s.closed = true
186 close(s.closedCh)
187 return nil
188 }
189
190 func (s *scadaListener) Addr() net.Addr {
191 return s.addr
192 }
193
194 // scadaAddr is used to return a net.Addr for SCADA
195 type scadaAddr struct {
196 infra string
197 }
198
199 func (s *scadaAddr) Network() string {
200 return "SCADA"
201 }
202
203 func (s *scadaAddr) String() string {
204 return fmt.Sprintf("SCADA::Atlas::%s", s.infra)
205 }
206
207 type scadaRWC struct {
208 io.ReadWriteCloser
209 addr *scadaAddr
210 }
211
212 func (s *scadaRWC) LocalAddr() net.Addr {
213 return s.addr
214 }
215
216 func (s *scadaRWC) RemoteAddr() net.Addr {
217 return s.addr
218 }
219
220 func (s *scadaRWC) SetDeadline(t time.Time) error {
221 return errors.New("SCADA.Conn does not support deadlines")
222 }
223
224 func (s *scadaRWC) SetReadDeadline(t time.Time) error {
225 return errors.New("SCADA.Conn does not support deadlines")
226 }
227
228 func (s *scadaRWC) SetWriteDeadline(t time.Time) error {
229 return errors.New("SCADA.Conn does not support deadlines")
230 }
0 package scada
1
2 import (
3 "net"
4 "reflect"
5 "testing"
6
7 "github.com/hashicorp/scada-client"
8 )
9
10 func TestProviderService(t *testing.T) {
11 conf := &Config{
12 Version: "0.5.0rc1",
13 Service: "nomad",
14 ResourceType: "nomad-cluster",
15 Meta: map[string]string{
16 "auto-join": "true",
17 "region": "global",
18 "datacenter": "dc1",
19 "client": "false",
20 "server": "true",
21 },
22 }
23
24 ps := providerService(conf)
25
26 expect := &client.ProviderService{
27 Service: "nomad",
28 ServiceVersion: "0.5.0rc1",
29 Capabilities: map[string]int{},
30 Meta: map[string]string{
31 "auto-join": "true",
32 "region": "global",
33 "datacenter": "dc1",
34 "client": "false",
35 "server": "true",
36 },
37 ResourceType: "nomad-cluster",
38 }
39
40 if !reflect.DeepEqual(ps, expect) {
41 t.Fatalf("bad: %v", ps)
42 }
43 }
44
45 func TestProviderConfig(t *testing.T) {
46 conf := &Config{
47 Version: "0.5.0rc1",
48 Service: "nomad",
49 ResourceType: "nomad-cluster",
50 Meta: map[string]string{
51 "auto-join": "true",
52 "region": "global",
53 "datacenter": "dc1",
54 "client": "false",
55 "server": "true",
56 },
57 }
58
59 conf.Atlas = AtlasConfig{
60 Infrastructure: "armon/test",
61 Token: "foobarbaz",
62 Endpoint: "foo.bar:1111",
63 }
64
65 pc := providerConfig(conf)
66
67 expect := &client.ProviderConfig{
68 Service: &client.ProviderService{
69 Service: "nomad",
70 ServiceVersion: "0.5.0rc1",
71 Capabilities: map[string]int{},
72 Meta: map[string]string{
73 "auto-join": "true",
74 "region": "global",
75 "datacenter": "dc1",
76 "client": "false",
77 "server": "true",
78 },
79 ResourceType: "nomad-cluster",
80 },
81 Handlers: map[string]client.CapabilityProvider{},
82 Endpoint: "foo.bar:1111",
83 ResourceGroup: "armon/test",
84 Token: "foobarbaz",
85 }
86
87 if !reflect.DeepEqual(pc, expect) {
88 t.Fatalf("bad: %v", pc)
89 }
90 }
91
92 func TestSCADAListener(t *testing.T) {
93 list := newScadaListener("armon/test")
94 defer list.Close()
95
96 var raw interface{} = list
97 _, ok := raw.(net.Listener)
98 if !ok {
99 t.Fatalf("bad")
100 }
101
102 a, b := net.Pipe()
103 defer a.Close()
104 defer b.Close()
105
106 go list.Push(a)
107 out, err := list.Accept()
108 if err != nil {
109 t.Fatalf("err: %v", err)
110 }
111 if out != a {
112 t.Fatalf("bad")
113 }
114 }
115
116 func TestSCADAAddr(t *testing.T) {
117 var addr interface{} = &scadaAddr{"armon/test"}
118 _, ok := addr.(net.Addr)
119 if !ok {
120 t.Fatalf("bad")
121 }
122 }