-
Notifications
You must be signed in to change notification settings - Fork 1
/
lock.go
143 lines (122 loc) · 3.69 KB
/
lock.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
package kubelock
import (
"context"
"encoding/json"
"time"
"github.com/giantswarm/microerror"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/dynamic"
)
type lock struct {
resource dynamic.ResourceInterface
lockName string
}
func (l *lock) Acquire(ctx context.Context, name string, options AcquireOptions) error {
options = defaultedAcquireOptions(options)
obj, err := l.resource.Get(ctx, name, metav1.GetOptions{})
if err != nil {
return microerror.Mask(err)
}
// Check if there is non expired lock acquired and error if so.
{
data, ok, err := l.data(obj)
if err != nil {
return microerror.Mask(err)
}
if ok && !isExpired(data) {
if data.Owner == options.Owner {
return microerror.Maskf(alreadyExistsError, "lock %#q on %#q owned by %#q already acquired at %s with TTL %s", l.lockName, obj.GetSelfLink(), data.Owner, data.CreatedAt.Format(time.RFC3339), data.TTL)
} else {
return microerror.Maskf(ownerMismatchError, "lock %#q on %#q owned by %#q already acquired at %s with TTL %s", l.lockName, obj.GetSelfLink(), data.Owner, data.CreatedAt.Format(time.RFC3339), data.TTL)
}
}
}
var data []byte
{
d := lockData{
Owner: options.Owner,
CreatedAt: time.Now().UTC(),
TTL: options.TTL,
}
data, err = json.Marshal(d)
if err != nil {
return microerror.Mask(err)
}
}
// Update object annotations.
{
ann := obj.GetAnnotations()
if ann == nil {
ann = map[string]string{}
}
ann[lockAnnotation(l.lockName)] = string(data)
obj.SetAnnotations(ann)
}
// Update object.
{
_, err := l.resource.Update(ctx, obj, metav1.UpdateOptions{})
if err != nil {
return microerror.Mask(err)
}
}
return nil
}
func (l *lock) Release(ctx context.Context, name string, options ReleaseOptions) error {
options = defaultedReleaseOptions(options)
obj, err := l.resource.Get(ctx, name, metav1.GetOptions{})
if err != nil {
return microerror.Mask(err)
}
// Check if the lock exists and fail if it doesn't.
{
data, ok, err := l.data(obj)
if err != nil {
return microerror.Mask(err)
}
// Lock exists and it's expired and owner matches.
if ok && isExpired(data) && data.Owner == options.Owner {
return microerror.Maskf(notFoundError, "lock %#q on %#q owned by %#q is expired", l.lockName, obj.GetSelfLink(), options.Owner)
}
// Lock doesn't exist.
if !ok {
return microerror.Maskf(notFoundError, "lock %#q on %#q not found", l.lockName, obj.GetSelfLink())
}
// Lock exists and it's expired and owner doesn't match.
if isExpired(data) {
return microerror.Maskf(notFoundError, "lock %#q on %#q is expired and it is not owned by %#q but %#q", l.lockName, obj.GetSelfLink(), options.Owner, data.Owner)
}
// Lock exists, it isn't expired and owner doesn't match. Note
// that in this case different error is returned.
if data.Owner != options.Owner {
return microerror.Maskf(ownerMismatchError, "lock %#q on %#q is not owned by %#q but %#q", l.lockName, obj.GetSelfLink(), options.Owner, data.Owner)
}
}
// Update object annotations.
{
ann := obj.GetAnnotations()
delete(ann, lockAnnotation(l.lockName))
obj.SetAnnotations(ann)
}
// Update object.
{
_, err := l.resource.Update(ctx, obj, metav1.UpdateOptions{})
if err != nil {
return microerror.Mask(err)
}
}
return nil
}
func (l *lock) data(obj *unstructured.Unstructured) (lockData, bool, error) {
ann := obj.GetAnnotations()
stringData, ok := ann[lockAnnotation(l.lockName)]
if !ok {
return lockData{}, false, nil
}
var data lockData
err := json.Unmarshal([]byte(stringData), &data)
if err != nil {
return lockData{}, false, microerror.Mask(err)
}
return data, true, nil
}