1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2012 Couchbase, Inc.
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 #include "config.h"
18 #include <map>
19 #include "iotests.h"
20 
21 class LockUnitTest : public MockUnitTest
22 {
23 };
24 
25 extern "C" {
26     static void getLockedCallback(lcb_t, const void *cookie,
27                                   lcb_error_t err,
28                                   const lcb_get_resp_t *resp)
29     {
30         Item *itm = (Item *)cookie;
31         itm->assign(resp, err);
32     }
33     static void unlockCallback(lcb_t, const void *cookie,
34                                lcb_error_t err,
35                                const lcb_unlock_resp_t *resp)
36     {
37         *(lcb_error_t *)cookie = err;
38     }
39 }
40 
41 /**
42  * @test
43  * Lock (lock and unlock)
44  *
45  * @pre
46  * Set a key, and get the value specifying the lock option with a timeout
47  * of @c 10.
48  *
49  * @post
50  * Lock operation succeeds.
51  *
52  * @pre Unlock the key using the CAS from the previous get result.
53  * @post Unlock succeeds
54  */
55 TEST_F(LockUnitTest, testSimpleLockAndUnlock)
56 {
57     LCB_TEST_REQUIRE_FEATURE("lock")
58 
59     lcb_t instance;
60     HandleWrap hw;
61     createConnection(hw, instance);
62 
63     std::string key = "lockKey";
64     std::string value = "lockValue";
65 
66     removeKey(instance, key);
67     storeKey(instance, key, value);
68 
69     lcb_get_cmd_st cmd = lcb_get_cmd_st(key.c_str(), key.size(),
70                                         1, 10);
71     lcb_get_cmd_st *cmdlist = &cmd;
72     Item itm;
73     lcb_error_t err;
74 
75     lcb_set_get_callback(instance, getLockedCallback);
76 
77     err = lcb_get(instance, &itm, 1, &cmdlist);
78     ASSERT_EQ(LCB_SUCCESS, err);
79     lcb_wait(instance);
80     ASSERT_EQ(LCB_SUCCESS, itm.err);
81 
82     lcb_unlock_cmd_st ucmd(key.c_str(), key.size(), itm.cas);
83     lcb_unlock_cmd_st *ucmdlist = &ucmd;
84 
85     lcb_error_t reserr = LCB_ERROR;
86 
87     lcb_set_unlock_callback(instance, unlockCallback);
88     err = lcb_unlock(instance, &reserr, 1, &ucmdlist);
89     ASSERT_EQ(LCB_SUCCESS, err);
90 
91     lcb_wait(instance);
92 
93     ASSERT_EQ(LCB_SUCCESS, reserr);
94 
95 }
96 
97 /**
98  * @test Lock (Missing CAS)
99  *
100  * @pre
101  * Store a key and attempt to unlock it with an invalid CAS
102  *
103  * @post
104  * Error result of @c ETMPFAIL
105  */
106 TEST_F(LockUnitTest, testUnlockMissingCas)
107 {
108     LCB_TEST_REQUIRE_FEATURE("lock")
109 
110     lcb_t instance;
111     HandleWrap hw;
112     createConnection(hw, instance);
113 
114     lcb_error_t err, reserr = LCB_ERROR;
115 
116     storeKey(instance, "lockKey", "lockValue");
117 
118     lcb_unlock_cmd_t cmd("lockKey", sizeof("lockKey") - 1, 0);
119     lcb_unlock_cmd_t *cmdlist = &cmd;
120     lcb_set_unlock_callback(instance, unlockCallback);
121 
122     err = lcb_unlock(instance, &reserr, 1, &cmdlist);
123     ASSERT_EQ(LCB_SUCCESS, err);
124     lcb_wait(instance);
125     if (CLUSTER_VERSION_IS_HIGHER_THAN(MockEnvironment::VERSION_50)) {
126         ASSERT_EQ(LCB_EINVAL_MCD, reserr);
127     } else {
128         ASSERT_EQ(LCB_ETMPFAIL, reserr);
129     }
130 }
131 
132 extern "C" {
133     static void lockedStorageCallback(lcb_t,
134                                       const void *cookie,
135                                       lcb_storage_t operation,
136                                       lcb_error_t err,
137                                       const lcb_store_resp_t *resp)
138     {
139         Item *itm = (Item *)cookie;
140         itm->assignKC<lcb_store_resp_t>(resp, err);
141     }
142 }
143 /**
144  * @test Lock (Storage Contention)
145  *
146  * @pre
147  * Store a key, perform a GET operation with the lock option, specifying a
148  * timeout of @c 10.
149  *
150  * Then attempt to store the key (without specifying any CAS).
151  *
152  * @post Store operation fails with @c KEY_EEXISTS. Getting the key retains
153  * the old value.
154  *
155  * @pre store the key using the CAS specified from the first GET
RequestUnlock(&self) -> ::windows::runtime::Result<()>156  * @post Storage succeeds. Get returns new value.
157  */
158 TEST_F(LockUnitTest, testStorageLockContention)
159 {
160     LCB_TEST_REQUIRE_FEATURE("lock")
161 
162     lcb_t instance;
163     HandleWrap hw;
164     lcb_error_t err;
165 
166     createConnection(hw, instance);
167     Item itm;
168     std::string key = "lockedKey", value = "lockedValue",
169                 newvalue = "newUnlockedValue";
170 
171     /* undo any funny business on our key */
172     removeKey(instance, key);
173     storeKey(instance, key, value);
174 
175     lcb_set_get_callback(instance, getLockedCallback);
176     lcb_set_unlock_callback(instance, unlockCallback);
177     lcb_set_store_callback(instance, lockedStorageCallback);
178 
179     /* get the key and lock it */
180     lcb_get_cmd_st gcmd(key.c_str(), key.size(), 1, 10);
181     lcb_get_cmd_st *cmdlist = &gcmd;
182     err = lcb_get(instance, &itm, 1, &cmdlist);
183     ASSERT_EQ(LCB_SUCCESS, err);
184     lcb_wait(instance);
185     ASSERT_EQ(LCB_SUCCESS, itm.err);
186     ASSERT_GT(itm.cas, 0);
187 
188     /* now try to set the key, while the lock is still in place */
189     lcb_store_cmd_t scmd(LCB_SET, key.c_str(), key.size(),
190                          newvalue.c_str(), newvalue.size());
191     lcb_store_cmd_t *scmdlist = &scmd;
192     Item s_itm;
193     err = lcb_store(instance, &s_itm, 1, &scmdlist);
194     ASSERT_EQ(LCB_SUCCESS, err);
195     lcb_wait(instance);
196     ASSERT_EQ(LCB_KEY_EEXISTS, s_itm.err);
197 
198     /* verify the value is still the old value */
199     Item ritem;
200     getKey(instance, key, ritem);
201     ASSERT_EQ(ritem.val, value);
202 
203     /* now try to set it with the correct cas, implicitly unlocking the key */
204     scmd.v.v0.cas = itm.cas;
205     err = lcb_store(instance, &s_itm, 1, &scmdlist);
206     ASSERT_EQ(LCB_SUCCESS, err);
207     lcb_wait(instance);
208     ASSERT_EQ(LCB_SUCCESS, itm.err);
209 
210     /* verify the value is now the new value */
211     getKey(instance, key, ritem);
212     ASSERT_EQ(ritem.val, newvalue);
213 }
214 
from(value: LockApplicationHost) -> Self215 /**
216  * @test
217  * Lock (Unlocking)
218  *
219  * @pre
220  * Store a key, get it with the lock option, specifying an expiry of @c 10.
221  * Try to unlock the key (using the @c lcb_unlock function) without a valid
222  * CAS.
223  *
224  * @post Unlock fails with @c ETMPFAIL
225  *
226  * @pre
227  * Unlock the key using the valid cas retrieved from the first lock operation.
228  * Then try to store the key with a new value.
229  *
230  * @post Unlock succeeds and retrieval of key yields new value.
231  */
232 TEST_F(LockUnitTest, testUnlLockContention)
233 {
234     LCB_TEST_REQUIRE_FEATURE("lock")
235 
236     lcb_t instance;
237     HandleWrap hw;
238     lcb_error_t err, reserr = LCB_ERROR;
239     createConnection(hw, instance);
240 
241     std::string key = "lockedKey2", value = "lockedValue2";
242     storeKey(instance, key, value);
243     Item gitm;
244 
245     lcb_set_get_callback(instance, getLockedCallback);
246     lcb_set_unlock_callback(instance, unlockCallback);
247     lcb_set_store_callback(instance, lockedStorageCallback);
248 
249     lcb_get_cmd_t gcmd(key.c_str(), key.size(), 1, 10);
250     lcb_get_cmd_t *gcmdlist = &gcmd;
251 
252     err = lcb_get(instance, &gitm, 1, &gcmdlist);
253     ASSERT_EQ(LCB_SUCCESS, err);
254     lcb_wait(instance);
255     ASSERT_EQ(LCB_SUCCESS, gitm.err);
256 
257     lcb_cas_t validCas = gitm.cas;
258     err = lcb_get(instance, &gitm, 1, &gcmdlist);
259     lcb_wait(instance);
260     ASSERT_EQ(LCB_ETMPFAIL, gitm.err);
261 
262     lcb_unlock_cmd_t ucmd(key.c_str(), key.size(), validCas);
263     lcb_unlock_cmd_t *ucmdlist = &ucmd;
264     err = lcb_unlock(instance, &reserr, 1, &ucmdlist);
265     ASSERT_EQ(LCB_SUCCESS, err);
266     lcb_wait(instance);
267     ASSERT_EQ(reserr, LCB_SUCCESS);
268 
269     std::string newval = "lockedValueNew2";
270     storeKey(instance, key, newval);
271     getKey(instance, key, gitm);
272     ASSERT_EQ(gitm.val, newval);
273 
274 }
275