1package ibclient
2
3import (
4	"fmt"
5	"math/rand"
6	"time"
7
8	"github.com/sirupsen/logrus"
9)
10
11const (
12	timeout     int32  = 60 // in seconds
13	freeLockVal string = "Available"
14)
15
16type Lock interface {
17	Lock() error
18	UnLock(force bool) error
19}
20
21type NetworkViewLock struct {
22	Name          string
23	ObjMgr        *ObjectManager
24	LockEA        string
25	LockTimeoutEA string
26}
27
28func (l *NetworkViewLock) createLockRequest() *MultiRequest {
29
30	req := NewMultiRequest(
31		[]*RequestBody{
32			&RequestBody{
33				Method: "GET",
34				Object: "networkview",
35				Data: map[string]interface{}{
36					"name":         l.Name,
37					"*" + l.LockEA: freeLockVal,
38				},
39				Args: map[string]string{
40					"_return_fields": "extattrs",
41				},
42				AssignState: map[string]string{
43					"NET_VIEW_REF": "_ref",
44				},
45				Discard: true,
46			},
47			&RequestBody{
48				Method: "PUT",
49				Object: "##STATE:NET_VIEW_REF:##",
50				Data: map[string]interface{}{
51					"extattrs+": map[string]interface{}{
52						l.LockEA: map[string]string{
53							"value": l.ObjMgr.tenantID,
54						},
55						l.LockTimeoutEA: map[string]int32{
56							"value": int32(time.Now().Unix()),
57						},
58					},
59				},
60				EnableSubstitution: true,
61				Discard:            true,
62			},
63			&RequestBody{
64				Method: "GET",
65				Object: "##STATE:NET_VIEW_REF:##",
66				Args: map[string]string{
67					"_return_fields": "extattrs",
68				},
69				AssignState: map[string]string{
70					"DOCKER-ID": "*" + l.LockEA,
71				},
72				EnableSubstitution: true,
73				Discard:            true,
74			},
75			&RequestBody{
76				Method: "STATE:DISPLAY",
77			},
78		},
79	)
80
81	return req
82}
83
84func (l *NetworkViewLock) createUnlockRequest(force bool) *MultiRequest {
85
86	getData := map[string]interface{}{"name": l.Name}
87	if !force {
88		getData["*"+l.LockEA] = l.ObjMgr.tenantID
89	}
90
91	req := NewMultiRequest(
92		[]*RequestBody{
93			&RequestBody{
94				Method: "GET",
95				Object: "networkview",
96				Data:   getData,
97				Args: map[string]string{
98					"_return_fields": "extattrs",
99				},
100				AssignState: map[string]string{
101					"NET_VIEW_REF": "_ref",
102				},
103				Discard: true,
104			},
105			&RequestBody{
106				Method: "PUT",
107				Object: "##STATE:NET_VIEW_REF:##",
108				Data: map[string]interface{}{
109					"extattrs+": map[string]interface{}{
110						l.LockEA: map[string]string{
111							"value": freeLockVal,
112						},
113					},
114				},
115				EnableSubstitution: true,
116				Discard:            true,
117			},
118			&RequestBody{
119				Method: "PUT",
120				Object: "##STATE:NET_VIEW_REF:##",
121				Data: map[string]interface{}{
122					"extattrs-": map[string]interface{}{
123						l.LockTimeoutEA: map[string]interface{}{},
124					},
125				},
126				EnableSubstitution: true,
127				Discard:            true,
128			},
129			&RequestBody{
130				Method: "GET",
131				Object: "##STATE:NET_VIEW_REF:##",
132				Args: map[string]string{
133					"_return_fields": "extattrs",
134				},
135				AssignState: map[string]string{
136					"DOCKER-ID": "*" + l.LockEA,
137				},
138				EnableSubstitution: true,
139				Discard:            true,
140			},
141			&RequestBody{
142				Method: "STATE:DISPLAY",
143			},
144		},
145	)
146
147	return req
148}
149
150func (l *NetworkViewLock) getLock() bool {
151	logrus.Debugf("Creating lock on network niew %s\n", l.Name)
152	req := l.createLockRequest()
153	res, err := l.ObjMgr.CreateMultiObject(req)
154
155	if err != nil {
156		logrus.Debugf("Failed to create lock on network view %s: %s\n", l.Name, err)
157
158		//Check for Lock Timeout
159		nw, err := l.ObjMgr.GetNetworkView(l.Name)
160		if err != nil {
161			logrus.Debugf("Failed to get the network view object for %s : %s\n", l.Name, err)
162			return false
163		}
164
165		if t, ok := nw.Ea[l.LockTimeoutEA]; ok {
166			if int32(time.Now().Unix())-int32(t.(int)) > timeout {
167				logrus.Debugln("Lock is timed out. Forcefully acquiring it.")
168				//remove the lock forcefully and acquire it
169				l.UnLock(true)
170				// try to get lock again
171				return l.getLock()
172			}
173		}
174		return false
175	}
176
177	dockerID := res[0]["DOCKER-ID"]
178	if dockerID == l.ObjMgr.tenantID {
179		logrus.Debugln("Got the lock !!!")
180		return true
181	}
182
183	return false
184}
185
186func (l *NetworkViewLock) Lock() error {
187
188	// verify if network view exists and has EA for the lock
189	nw, err := l.ObjMgr.GetNetworkView(l.Name)
190	if err != nil {
191		msg := fmt.Sprintf("Failed to get the network view object for %s : %s\n", l.Name, err)
192		logrus.Debugf(msg)
193		return fmt.Errorf(msg)
194	}
195
196	if _, ok := nw.Ea[l.LockEA]; !ok {
197		err = l.ObjMgr.UpdateNetworkViewEA(nw.Ref, EA{l.LockEA: freeLockVal}, nil)
198		if err != nil {
199			return fmt.Errorf("Failed to Update Network view with Lock EA")
200		}
201	}
202
203	retryCount := 0
204	for {
205		// Get lock on the network view
206		lock := l.getLock()
207		if lock == true {
208			// Got the lock.
209			logrus.Debugf("Got the lock on Network View %s\n", l.Name)
210			return nil
211		}
212
213		// Lock is held by some other agent. Wait for some time and retry it again
214		if retryCount >= 10 {
215			return fmt.Errorf("Failed to get Lock on Network View %s", l.Name)
216		}
217
218		retryCount++
219		logrus.Debugf("Lock on Network View %s not free. Retrying again %d out of 10.\n", l.Name, retryCount)
220		// sleep for random time (between 1 - 10 seconds) to reduce collisions
221		time.Sleep(time.Duration(rand.Intn(9)+1) * time.Second)
222		continue
223	}
224}
225
226func (l *NetworkViewLock) UnLock(force bool) error {
227	// To unlock set the Docker-Plugin-Lock EA of network view to Available and
228	// remove the Docker-Plugin-Lock-Time EA
229	req := l.createUnlockRequest(force)
230	res, err := l.ObjMgr.CreateMultiObject(req)
231
232	if err != nil {
233		msg := fmt.Sprintf("Failed to release lock from Network View %s: %s\n", l.Name, err)
234		logrus.Errorf(msg)
235		return fmt.Errorf(msg)
236	}
237
238	dockerID := res[0]["DOCKER-ID"]
239	if dockerID == freeLockVal {
240		logrus.Debugln("Removed the lock!")
241		return nil
242	}
243
244	msg := fmt.Sprintf("Failed to release lock from Network View %s\n", l.Name)
245	logrus.Errorf(msg)
246	return fmt.Errorf(msg)
247}
248