GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/publish/repository_session.cc
Date: 2024-04-28 02:33:07
Exec Total Coverage
Lines: 0 189 0.0%
Branches: 0 345 0.0%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5 #include "cvmfs_config.h"
6 #include "publish/repository.h"
7
8 #include <fcntl.h>
9 #include <unistd.h>
10
11 #include <cassert>
12 #include <string>
13
14 #include "backoff.h"
15 #include "catalog_mgr_ro.h"
16 #include "crypto/hash.h"
17 #include "directory_entry.h"
18 #include "duplex_curl.h"
19 #include "gateway_util.h"
20 #include "json_document.h"
21 #include "publish/except.h"
22 #include "ssl.h"
23 #include "upload.h"
24 #include "util/logging.h"
25 #include "util/pointer.h"
26 #include "util/posix.h"
27 #include "util/string.h"
28
29 namespace {
30
31 struct CurlBuffer {
32 std::string data;
33 };
34
35 enum LeaseReply {
36 kLeaseReplySuccess,
37 kLeaseReplyBusy,
38 kLeaseReplyFailure
39 };
40
41 static CURL* PrepareCurl(const std::string& method) {
42 const char* user_agent_string = "cvmfs/" VERSION;
43
44 CURL* h_curl = curl_easy_init();
45 assert(h_curl != NULL);
46
47 curl_easy_setopt(h_curl, CURLOPT_NOPROGRESS, 1L);
48 curl_easy_setopt(h_curl, CURLOPT_USERAGENT, user_agent_string);
49 curl_easy_setopt(h_curl, CURLOPT_MAXREDIRS, 50L);
50 curl_easy_setopt(h_curl, CURLOPT_CUSTOMREQUEST, method.c_str());
51
52 return h_curl;
53 }
54
55 static size_t RecvCB(void* buffer, size_t size, size_t nmemb, void* userp) {
56 CurlBuffer* my_buffer = static_cast<CurlBuffer*>(userp);
57
58 if (size * nmemb < 1) {
59 return 0;
60 }
61
62 my_buffer->data = static_cast<char*>(buffer);
63
64 return my_buffer->data.size();
65 }
66
67 static void MakeAcquireRequest(
68 const gateway::GatewayKey &key,
69 const std::string& repo_path,
70 const std::string& repo_service_url,
71 int llvl,
72 CurlBuffer* buffer)
73 {
74 CURLcode ret = static_cast<CURLcode>(0);
75
76 CURL* h_curl = PrepareCurl("POST");
77
78 const std::string payload = "{\"path\" : \"" + repo_path +
79 "\", \"api_version\" : \"" +
80 StringifyInt(gateway::APIVersion()) + "\", " +
81 "\"hostname\" : \"" + GetHostname() + "\"}";
82
83 shash::Any hmac(shash::kSha1);
84 shash::HmacString(key.secret(), payload, &hmac);
85 SslCertificateStore cs;
86 cs.UseSystemCertificatePath();
87 cs.ApplySslCertificatePath(h_curl);
88
89 const std::string header_str =
90 std::string("Authorization: ") + key.id() + " " +
91 Base64(hmac.ToString(false));
92 struct curl_slist* auth_header = NULL;
93 auth_header = curl_slist_append(auth_header, header_str.c_str());
94 curl_easy_setopt(h_curl, CURLOPT_HTTPHEADER, auth_header);
95
96 // Make request to acquire lease from repo services
97 curl_easy_setopt(h_curl, CURLOPT_URL, (repo_service_url + "/leases").c_str());
98 curl_easy_setopt(h_curl, CURLOPT_POSTFIELDSIZE_LARGE,
99 static_cast<curl_off_t>(payload.length()));
100 curl_easy_setopt(h_curl, CURLOPT_POSTFIELDS, payload.c_str());
101 curl_easy_setopt(h_curl, CURLOPT_WRITEFUNCTION, RecvCB);
102 curl_easy_setopt(h_curl, CURLOPT_WRITEDATA, buffer);
103
104 ret = curl_easy_perform(h_curl);
105 curl_easy_cleanup(h_curl);
106 if (ret != CURLE_OK) {
107 LogCvmfs(kLogUploadGateway, llvl | kLogStderr,
108 "Make lease acquire request failed: %d. Reply: %s", ret,
109 buffer->data.c_str());
110 throw publish::EPublish("cannot acquire lease",
111 publish::EPublish::kFailLeaseHttp);
112 }
113 }
114
115 // TODO(jblomer): This should eventually also handle the POST request for
116 // committing a transaction
117 static void MakeDropRequest(
118 const gateway::GatewayKey &key,
119 const std::string &session_token,
120 const std::string &repo_service_url,
121 int llvl,
122 CurlBuffer *reply)
123 {
124 CURLcode ret = static_cast<CURLcode>(0);
125
126 CURL *h_curl = PrepareCurl("DELETE");
127
128 shash::Any hmac(shash::kSha1);
129 shash::HmacString(key.secret(), session_token, &hmac);
130 SslCertificateStore cs;
131 cs.UseSystemCertificatePath();
132 cs.ApplySslCertificatePath(h_curl);
133
134 const std::string header_str =
135 std::string("Authorization: ") + key.id() + " " +
136 Base64(hmac.ToString(false));
137 struct curl_slist *auth_header = NULL;
138 auth_header = curl_slist_append(auth_header, header_str.c_str());
139 curl_easy_setopt(h_curl, CURLOPT_HTTPHEADER, auth_header);
140
141 curl_easy_setopt(h_curl, CURLOPT_URL,
142 (repo_service_url + "/leases/" + session_token).c_str());
143 curl_easy_setopt(h_curl, CURLOPT_POSTFIELDSIZE_LARGE,
144 static_cast<curl_off_t>(0));
145 curl_easy_setopt(h_curl, CURLOPT_POSTFIELDS, NULL);
146 curl_easy_setopt(h_curl, CURLOPT_WRITEFUNCTION, RecvCB);
147 curl_easy_setopt(h_curl, CURLOPT_WRITEDATA, reply);
148
149 ret = curl_easy_perform(h_curl);
150 curl_easy_cleanup(h_curl);
151 if (ret != CURLE_OK) {
152 LogCvmfs(kLogUploadGateway, llvl | kLogStderr,
153 "Make lease drop request failed: %d. Reply: '%s'",
154 ret, reply->data.c_str());
155 throw publish::EPublish("cannot drop lease",
156 publish::EPublish::kFailLeaseHttp);
157 }
158 }
159
160 static LeaseReply ParseAcquireReply(
161 const CurlBuffer &buffer,
162 std::string *session_token,
163 int llvl)
164 {
165 if (buffer.data.size() == 0 || session_token == NULL) {
166 return kLeaseReplyFailure;
167 }
168
169 const UniquePtr<JsonDocument> reply(JsonDocument::Create(buffer.data));
170 if (!reply.IsValid() || !reply->IsValid()) {
171 return kLeaseReplyFailure;
172 }
173
174 const JSON *result =
175 JsonDocument::SearchInObject(reply->root(), "status", JSON_STRING);
176 if (result != NULL) {
177 const std::string status = result->string_value;
178 if (status == "ok") {
179 LogCvmfs(kLogCvmfs, llvl | kLogStdout, "Gateway reply: ok");
180 const JSON *token = JsonDocument::SearchInObject(
181 reply->root(), "session_token", JSON_STRING);
182 if (token != NULL) {
183 LogCvmfs(kLogCvmfs, kLogDebug, "Session token: %s",
184 token->string_value);
185 *session_token = token->string_value;
186 return kLeaseReplySuccess;
187 }
188 } else if (status == "path_busy") {
189 const JSON *time_remaining = JsonDocument::SearchInObject(
190 reply->root(), "time_remaining", JSON_STRING);
191 LogCvmfs(kLogCvmfs, llvl | kLogStdout,
192 "Path busy. Time remaining = %s", (time_remaining != NULL) ?
193 time_remaining->string_value : "UNKNOWN");
194 return kLeaseReplyBusy;
195 } else if (status == "error") {
196 const JSON *reason =
197 JsonDocument::SearchInObject(reply->root(), "reason", JSON_STRING);
198 LogCvmfs(kLogCvmfs, llvl | kLogStdout, "Error: '%s'",
199 (reason != NULL) ? reason->string_value : "");
200 } else {
201 LogCvmfs(kLogCvmfs, llvl | kLogStdout, "Unknown reply. Status: %s",
202 status.c_str());
203 }
204 }
205
206 return kLeaseReplyFailure;
207 }
208
209
210 static LeaseReply ParseDropReply(const CurlBuffer &buffer, int llvl) {
211 if (buffer.data.size() == 0) {
212 return kLeaseReplyFailure;
213 }
214
215 const UniquePtr<const JsonDocument> reply(JsonDocument::Create(buffer.data));
216 if (!reply.IsValid() || !reply->IsValid()) {
217 return kLeaseReplyFailure;
218 }
219
220 const JSON *result =
221 JsonDocument::SearchInObject(reply->root(), "status", JSON_STRING);
222 if (result != NULL) {
223 const std::string status = result->string_value;
224 if (status == "ok") {
225 LogCvmfs(kLogCvmfs, llvl | kLogStdout, "Gateway reply: ok");
226 return kLeaseReplySuccess;
227 } else if (status == "invalid_token") {
228 LogCvmfs(kLogCvmfs, llvl | kLogStdout, "Error: invalid session token");
229 } else if (status == "error") {
230 const JSON *reason =
231 JsonDocument::SearchInObject(reply->root(), "reason", JSON_STRING);
232 LogCvmfs(kLogCvmfs, llvl | kLogStdout, "Error from gateway: '%s'",
233 (reason != NULL) ? reason->string_value : "");
234 } else {
235 LogCvmfs(kLogCvmfs, llvl | kLogStdout, "Unknown reply. Status: %s",
236 status.c_str());
237 }
238 }
239
240 return kLeaseReplyFailure;
241 }
242
243 } // anonymous namespace
244
245 namespace publish {
246
247 Publisher::Session::Session(const Settings &settings_session)
248 : settings_(settings_session)
249 , keep_alive_(false)
250 // TODO(jblomer): it would be better to actually read & validate the token
251 , has_lease_(FileExists(settings_.token_path))
252 {
253 }
254
255
256 Publisher::Session::Session(const SettingsPublisher &settings_publisher,
257 int llvl)
258 {
259 keep_alive_ = false;
260 if (settings_publisher.storage().type() != upload::SpoolerDefinition::Gateway)
261 {
262 has_lease_ = true;
263 return;
264 }
265
266 settings_.service_endpoint = settings_publisher.storage().endpoint();
267 settings_.repo_path = settings_publisher.fqrn() + "/" +
268 settings_publisher.transaction().lease_path();
269 settings_.gw_key_path = settings_publisher.keychain().gw_key_path();
270 settings_.token_path =
271 settings_publisher.transaction().spool_area().gw_session_token();
272 settings_.llvl = llvl;
273
274 // TODO(jblomer): it would be better to actually read & validate the token
275 has_lease_ = FileExists(settings_.token_path);
276 // If a lease is already present, we don't want to remove it automatically
277 keep_alive_ = has_lease_;
278 }
279
280
281 void Publisher::Session::SetKeepAlive(bool value) {
282 keep_alive_ = value;
283 }
284
285
286 void Publisher::Session::Acquire() {
287 if (has_lease_)
288 return;
289
290 gateway::GatewayKey gw_key = gateway::ReadGatewayKey(settings_.gw_key_path);
291 if (!gw_key.IsValid()) {
292 throw EPublish("cannot read gateway key: " + settings_.gw_key_path,
293 EPublish::kFailGatewayKey);
294 }
295 CurlBuffer buffer;
296 MakeAcquireRequest(gw_key, settings_.repo_path, settings_.service_endpoint,
297 settings_.llvl, &buffer);
298
299 std::string session_token;
300 LeaseReply rep = ParseAcquireReply(buffer, &session_token, settings_.llvl);
301 switch (rep) {
302 case kLeaseReplySuccess:
303 {
304 has_lease_ = true;
305 bool rvb = SafeWriteToFile(
306 session_token,
307 settings_.token_path,
308 0600);
309 if (!rvb) {
310 throw EPublish("cannot write session token: " + settings_.token_path);
311 }
312 }
313 break;
314 case kLeaseReplyBusy:
315 throw EPublish("lease path busy", EPublish::kFailLeaseBusy);
316 break;
317 case kLeaseReplyFailure:
318 default:
319 throw EPublish("cannot parse session token", EPublish::kFailLeaseBody);
320 }
321 }
322
323 void Publisher::Session::Drop() {
324 if (!has_lease_)
325 return;
326 // TODO(jblomer): there might be a better way to distinguish between the
327 // nop-session and a real session
328 if (settings_.service_endpoint.empty())
329 return;
330
331 std::string token;
332 int fd_token = open(settings_.token_path.c_str(), O_RDONLY);
333 bool rvb = SafeReadToString(fd_token, &token);
334 close(fd_token);
335 if (!rvb) {
336 throw EPublish("cannot read session token: " + settings_.token_path,
337 EPublish::kFailGatewayKey);
338 }
339 gateway::GatewayKey gw_key = gateway::ReadGatewayKey(settings_.gw_key_path);
340 if (!gw_key.IsValid()) {
341 throw EPublish("cannot read gateway key: " + settings_.gw_key_path,
342 EPublish::kFailGatewayKey);
343 }
344
345 CurlBuffer buffer;
346 MakeDropRequest(gw_key, token, settings_.service_endpoint, settings_.llvl,
347 &buffer);
348 LeaseReply rep = ParseDropReply(buffer, settings_.llvl);
349 int rvi = 0;
350 switch (rep) {
351 case kLeaseReplySuccess:
352 has_lease_ = false;
353 rvi = unlink(settings_.token_path.c_str());
354 if (rvi != 0)
355 throw EPublish("cannot delete session token " + settings_.token_path);
356 break;
357 case kLeaseReplyFailure:
358 default:
359 throw EPublish("gateway doesn't recognize the lease or cannot drop it",
360 EPublish::kFailLeaseBody);
361 }
362 }
363
364 Publisher::Session::~Session() {
365 if (keep_alive_)
366 return;
367
368 Drop();
369 }
370
371 } // namespace publish
372