blob: e299a6847010ed064a2f58e745e9b349e86f3a4d [file] [log] [blame]
package main
import (
"encoding/json"
"github.com/fzzy/radix/redis"
consul "github.com/hashicorp/consul/api"
"log"
"net/url"
"os"
"strings"
)
type ProvisionState int8
const (
Unprovisioned ProvisionState = iota
ProvisionError
Provisioning
Provisioned
)
func (s *ProvisionState) String() string {
switch *s {
case Unprovisioned:
return "UNPROVISIONED"
case ProvisionError:
return "PROVISIONERROR"
case Provisioning:
return "PROVISIONING"
case Provisioned:
return "PROVISIONED"
default:
return "UNKNOWN"
}
}
// TrackerRecord state kept for each node to be provisioned
type TrackerRecord struct {
State ProvisionState
// Timeestamp maintains the time the node started provisioning, eventually will be used to time out
// provisinion states
Timestamp int64
}
// Tracker used to track if a node has been post deployed provisioned
type Tracker interface {
Get(key string) (*TrackerRecord, error)
Set(key string, record *TrackerRecord) error
Clear(key string) error
}
type ConsulTracker struct {
client *consul.Client
kv *consul.KV
}
func (c *ConsulTracker) Get(key string) (*TrackerRecord, error) {
pair, _, err := c.kv.Get(key, nil)
if err != nil {
return nil, err
}
if pair == nil {
var record TrackerRecord
record.State = Unprovisioned
return &record, nil
}
var record TrackerRecord
err = json.Unmarshal([]byte(pair.Value), &record)
if err != nil {
return nil, err
}
return &record, nil
}
func (c *ConsulTracker) Set(key string, record *TrackerRecord) error {
data, err := json.Marshal(record)
if err != nil {
return err
}
pair := &consul.KVPair{Key: key, Value: data}
_, err = c.kv.Put(pair, nil)
return err
}
func (c *ConsulTracker) Clear(key string) error {
_, err := c.kv.Delete(key, nil)
return err
}
// RedisTracker redis implementation of the tracker interface
type RedisTracker struct {
client *redis.Client
}
func (t *RedisTracker) Get(key string) (*TrackerRecord, error) {
reply := t.client.Cmd("get", key)
if reply.Err != nil {
return nil, reply.Err
}
if reply.Type == redis.NilReply {
var record TrackerRecord
record.State = Unprovisioned
return &record, nil
}
value, err := reply.Str()
if err != nil {
return nil, err
}
var record TrackerRecord
err = json.Unmarshal([]byte(value), &record)
if err != nil {
return nil, err
}
return &record, nil
}
func (t *RedisTracker) Set(key string, record *TrackerRecord) error {
data, err := json.Marshal(record)
if err != nil {
return err
}
reply := t.client.Cmd("set", key, data)
return reply.Err
}
func (t *RedisTracker) Clear(key string) error {
reply := t.client.Cmd("del", key)
return reply.Err
}
// MemoryTracker in memory implementation of the tracker interface
type MemoryTracker struct {
data map[string]TrackerRecord
}
func (m *MemoryTracker) Get(key string) (*TrackerRecord, error) {
if value, ok := m.data[key]; ok {
return &value, nil
}
var record TrackerRecord
record.State = Unprovisioned
return &record, nil
}
func (m *MemoryTracker) Set(key string, record *TrackerRecord) error {
m.data[key] = *record
return nil
}
func (m *MemoryTracker) Clear(key string) error {
delete(m.data, key)
return nil
}
// NetTracker constructs an implemetation of the Tracker interface. Which implementation selected
// depends on the environment. If a link to a redis instance is defined then this will
// be used, else an in memory version will be used.
func NewTracker() Tracker {
driver := os.Getenv("AUTODB_DRIVER")
if driver == "" {
log.Printf("[info] No driver specified, defaulting to in memeory persistence driver")
driver = "MEMORY"
}
switch strings.ToUpper(driver) {
case "REDIS":
tracker := new(RedisTracker)
if spec := os.Getenv("AUTODB_PORT"); spec != "" {
port, err := url.Parse(spec)
checkError(err, "[error] unable to lookup to redis database : %s", err)
tracker.client, err = redis.Dial(port.Scheme, port.Host)
checkError(err, "[error] unable to connect to redis database : '%s' : %s", port, err)
log.Println("[info] Using REDIS to track provisioning status of nodes")
return tracker
}
log.Fatalf("[error] No connection specified to REDIS server")
case "CONSUL":
var err error
config := consul.Config{
Address: "autodb:8500",
Scheme: "http",
}
tracker := new(ConsulTracker)
tracker.client, err = consul.NewClient(&config)
checkError(err, "[error] unable to connect to redis server : 'autodb:8500' : %s", err)
log.Println("[info] Using Consul to track provisioning status of nodes")
tracker.kv = tracker.client.KV()
return tracker
case "MEMORY":
tracker := new(MemoryTracker)
tracker.data = make(map[string]TrackerRecord)
log.Println("[info] Using memory based structures to track provisioning status of nodes")
return tracker
default:
log.Fatalf("[error] Unknown persistance driver specified, '%s'", driver)
}
return nil
}