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