blob: e299a6847010ed064a2f58e745e9b349e86f3a4d [file] [log] [blame]
David K. Bainbridgeefa951d2016-05-26 10:54:25 -07001package main
2
3import (
4 "encoding/json"
5 "github.com/fzzy/radix/redis"
David K. Bainbridge19b8d272016-05-26 21:20:43 -07006 consul "github.com/hashicorp/consul/api"
David K. Bainbridgeefa951d2016-05-26 10:54:25 -07007 "log"
8 "net/url"
9 "os"
David K. Bainbridge19b8d272016-05-26 21:20:43 -070010 "strings"
David K. Bainbridgeefa951d2016-05-26 10:54:25 -070011)
12
13type ProvisionState int8
14
15const (
16 Unprovisioned ProvisionState = iota
17 ProvisionError
18 Provisioning
19 Provisioned
20)
21
22func (s *ProvisionState) String() string {
23 switch *s {
24 case Unprovisioned:
25 return "UNPROVISIONED"
26 case ProvisionError:
27 return "PROVISIONERROR"
28 case Provisioning:
29 return "PROVISIONING"
30 case Provisioned:
31 return "PROVISIONED"
32 default:
33 return "UNKNOWN"
34 }
35}
36
37// TrackerRecord state kept for each node to be provisioned
38type TrackerRecord struct {
39 State ProvisionState
40
41 // Timeestamp maintains the time the node started provisioning, eventually will be used to time out
42 // provisinion states
43 Timestamp int64
44}
45
46// Tracker used to track if a node has been post deployed provisioned
47type Tracker interface {
48 Get(key string) (*TrackerRecord, error)
49 Set(key string, record *TrackerRecord) error
50 Clear(key string) error
51}
52
David K. Bainbridge19b8d272016-05-26 21:20:43 -070053type ConsulTracker struct {
54 client *consul.Client
55 kv *consul.KV
56}
57
58func (c *ConsulTracker) Get(key string) (*TrackerRecord, error) {
59 pair, _, err := c.kv.Get(key, nil)
60 if err != nil {
61 return nil, err
62 }
63
64 if pair == nil {
65 var record TrackerRecord
66 record.State = Unprovisioned
67 return &record, nil
68 }
69
70 var record TrackerRecord
71 err = json.Unmarshal([]byte(pair.Value), &record)
72 if err != nil {
73 return nil, err
74 }
75 return &record, nil
76}
77
78func (c *ConsulTracker) Set(key string, record *TrackerRecord) error {
79 data, err := json.Marshal(record)
80 if err != nil {
81 return err
82 }
83 pair := &consul.KVPair{Key: key, Value: data}
84 _, err = c.kv.Put(pair, nil)
85 return err
86}
87
88func (c *ConsulTracker) Clear(key string) error {
89 _, err := c.kv.Delete(key, nil)
90 return err
91}
92
David K. Bainbridgeefa951d2016-05-26 10:54:25 -070093// RedisTracker redis implementation of the tracker interface
94type RedisTracker struct {
95 client *redis.Client
96}
97
98func (t *RedisTracker) Get(key string) (*TrackerRecord, error) {
99 reply := t.client.Cmd("get", key)
100 if reply.Err != nil {
101 return nil, reply.Err
102 }
103 if reply.Type == redis.NilReply {
104 var record TrackerRecord
105 record.State = Unprovisioned
106 return &record, nil
107 }
108
109 value, err := reply.Str()
110 if err != nil {
111 return nil, err
112 }
113 var record TrackerRecord
114 err = json.Unmarshal([]byte(value), &record)
115 if err != nil {
116 return nil, err
117 }
118 return &record, nil
119}
120
121func (t *RedisTracker) Set(key string, record *TrackerRecord) error {
David K. Bainbridge19b8d272016-05-26 21:20:43 -0700122 data, err := json.Marshal(record)
123 if err != nil {
124 return err
125 }
126 reply := t.client.Cmd("set", key, data)
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700127 return reply.Err
128}
129
130func (t *RedisTracker) Clear(key string) error {
131 reply := t.client.Cmd("del", key)
132 return reply.Err
133}
134
135// MemoryTracker in memory implementation of the tracker interface
136type MemoryTracker struct {
137 data map[string]TrackerRecord
138}
139
140func (m *MemoryTracker) Get(key string) (*TrackerRecord, error) {
141 if value, ok := m.data[key]; ok {
142 return &value, nil
143 }
144 var record TrackerRecord
145 record.State = Unprovisioned
146 return &record, nil
147}
148
149func (m *MemoryTracker) Set(key string, record *TrackerRecord) error {
150 m.data[key] = *record
151 return nil
152}
153
154func (m *MemoryTracker) Clear(key string) error {
155 delete(m.data, key)
156 return nil
157}
158
159// NetTracker constructs an implemetation of the Tracker interface. Which implementation selected
160// depends on the environment. If a link to a redis instance is defined then this will
161// be used, else an in memory version will be used.
162func NewTracker() Tracker {
David K. Bainbridge19b8d272016-05-26 21:20:43 -0700163 driver := os.Getenv("AUTODB_DRIVER")
164 if driver == "" {
165 log.Printf("[info] No driver specified, defaulting to in memeory persistence driver")
166 driver = "MEMORY"
167 }
168
169 switch strings.ToUpper(driver) {
170 case "REDIS":
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700171 tracker := new(RedisTracker)
172 if spec := os.Getenv("AUTODB_PORT"); spec != "" {
173 port, err := url.Parse(spec)
174 checkError(err, "[error] unable to lookup to redis database : %s", err)
175 tracker.client, err = redis.Dial(port.Scheme, port.Host)
176 checkError(err, "[error] unable to connect to redis database : '%s' : %s", port, err)
177 log.Println("[info] Using REDIS to track provisioning status of nodes")
178 return tracker
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700179 }
David K. Bainbridge19b8d272016-05-26 21:20:43 -0700180 log.Fatalf("[error] No connection specified to REDIS server")
181 case "CONSUL":
182 var err error
183 config := consul.Config{
184 Address: "autodb:8500",
185 Scheme: "http",
186 }
187 tracker := new(ConsulTracker)
188 tracker.client, err = consul.NewClient(&config)
189 checkError(err, "[error] unable to connect to redis server : 'autodb:8500' : %s", err)
190 log.Println("[info] Using Consul to track provisioning status of nodes")
191 tracker.kv = tracker.client.KV()
192 return tracker
193 case "MEMORY":
194 tracker := new(MemoryTracker)
195 tracker.data = make(map[string]TrackerRecord)
196 log.Println("[info] Using memory based structures to track provisioning status of nodes")
197 return tracker
198 default:
199 log.Fatalf("[error] Unknown persistance driver specified, '%s'", driver)
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700200 }
David K. Bainbridge19b8d272016-05-26 21:20:43 -0700201 return nil
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700202}