forked from InVisionApp/go-health
-
Notifications
You must be signed in to change notification settings - Fork 0
/
memcached.go
170 lines (143 loc) · 4.47 KB
/
memcached.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package memcachechk
import (
"bytes"
"fmt"
"net"
"net/url"
"github.com/bradfitz/gomemcache/memcache"
)
const (
// MemcachedDefaultSetValue will be used if the "Set" check method is enabled
// and "MemcachedSetOptions.Value" is _not_ set.
MemcachedDefaultSetValue = "go-health/memcached-check"
)
// MongoConfig is used for configuring the go-mongo check.
//
// "Url" is _required_; memcached connection url, format is "10.0.0.1:11011". Port (:11011) is mandatory
// "Timeout" defines timeout for socket write/read (useful for servers hosted on different machine)
// "Ping" is optional; Ping establishes tcp connection to memcached server.
type MemcachedConfig struct {
Url string
Timeout int32
Ping bool
Set *MemcachedSetOptions
Get *MemcachedGetOptions
}
type MemcachedClient interface {
Get(key string) (item *memcache.Item, err error)
Set(item *memcache.Item) error
}
type Memcached struct {
Config *MemcachedConfig
wrapper *MemcachedClientWrapper
}
// MemcachedSetOptions contains attributes that can alter the behavior of the memcached
// "SET" check.
//
// "Key" is _required_; the name of the key we are attempting to "SET".
//
// "Value" is optional; what the value should hold; if not set, it will be set
// to "MemcachedDefaultSetValue".
//
// "Expiration" is optional; if set, a TTL will be attached to the key.
type MemcachedSetOptions struct {
Key string
Value string
Expiration int32
}
// MemcachedGetOptions contains attributes that can alter the behavior of the memcached
// "GET" check.
//
// "Key" is _required_; the name of the key that we are attempting to "GET".
//
// "Expect" is optional; optionally verify that the value for the key matches
// the Expect value.
//
// "NoErrorMissingKey" is optional; by default, the "GET" check will error if
// the key we are fetching does not exist; flip this bool if that is normal/expected/ok.
type MemcachedGetOptions struct {
Key string
Expect []byte
NoErrorMissingKey bool
}
func NewMemcached(cfg *MemcachedConfig) (*Memcached, error) {
// validate settings
if err := validateMemcachedConfig(cfg); err != nil {
return nil, fmt.Errorf("unable to validate memcached config: %v", err)
}
mcWrapper := &MemcachedClientWrapper{memcache.New(cfg.Url)}
return &Memcached{
Config: cfg,
wrapper: mcWrapper,
}, nil
}
func (mc *Memcached) Status() (interface{}, error) {
if mc.Config.Ping {
if _, err := net.Dial("tcp", mc.Config.Url); err != nil {
return nil, fmt.Errorf("Ping failed: %v", err)
}
}
if mc.Config.Set != nil {
err := mc.wrapper.GetClient().Set(&memcache.Item{Key: mc.Config.Set.Key, Value: []byte(mc.Config.Set.Value), Expiration: mc.Config.Set.Expiration})
if err != nil {
return nil, fmt.Errorf("Unable to complete set: %v", err)
}
}
if mc.Config.Get != nil {
val, err := mc.wrapper.GetClient().Get(mc.Config.Get.Key)
if err != nil {
if err == memcache.ErrCacheMiss {
if !mc.Config.Get.NoErrorMissingKey {
return nil, fmt.Errorf("Unable to complete get: '%v' not found", mc.Config.Get.Key)
}
} else {
return nil, fmt.Errorf("Unable to complete get: %v", err)
}
}
if mc.Config.Get.Expect != nil {
if !bytes.Equal(mc.Config.Get.Expect, val.Value) {
return nil, fmt.Errorf("Unable to complete get: returned value '%v' does not match expected value '%v'",
val, mc.Config.Get.Expect)
}
}
}
return nil, nil
}
func validateMemcachedConfig(cfg *MemcachedConfig) error {
if cfg == nil {
return fmt.Errorf("Main config cannot be nil")
}
if cfg.Url == "" {
return fmt.Errorf("Url string must be set in config")
}
if _, err := url.Parse(cfg.Url); err != nil {
return fmt.Errorf("Unable to parse URL: %v", err)
}
// At least one check method must be set
if !cfg.Ping && cfg.Set == nil && cfg.Get == nil {
return fmt.Errorf("At minimum, either cfg.Ping, cfg.Set or cfg.Get must be set")
}
// If .Set is set, verify that at minimum .Key is set
if cfg.Set != nil {
if cfg.Set.Key == "" {
return fmt.Errorf("If cfg.Set is used, cfg.Set.Key must be set")
}
if cfg.Set.Value == "" {
cfg.Set.Value = MemcachedDefaultSetValue
}
}
// If .Get is set, verify that at minimum .Key is set
if cfg.Get != nil {
if cfg.Get.Key == "" {
return fmt.Errorf("If cfg.Get is used, cfg.Get.Key must be set")
}
}
return nil
}
// Used to simplify testing routines
type MemcachedClientWrapper struct {
MemcachedClient
}
func (mcw MemcachedClientWrapper) GetClient() MemcachedClient {
return mcw.MemcachedClient
}