Belle II Software  release-06-01-15
TestingPayloadStorage.cc
1 /**************************************************************************
2  * basf2 (Belle II Analysis Software Framework) *
3  * Author: The Belle II Collaboration *
4  * *
5  * See git log for contributors and copyright holders. *
6  * This file is licensed under LGPL-3.0, see LICENSE.md. *
7  **************************************************************************/
8 
9 #include <framework/database/TestingPayloadStorage.h>
10 #include <framework/dataobjects/EventMetaData.h>
11 #include <framework/logging/Logger.h>
12 #include <framework/utilities/FileSystem.h>
13 #include <framework/utilities/ScopeGuard.h>
14 
15 #include <TDirectory.h>
16 #include <TFile.h>
17 #include <TObject.h>
18 
19 #include <boost/filesystem.hpp>
20 #include <boost/algorithm/string.hpp>
21 
22 namespace fs = boost::filesystem;
23 
24 namespace Belle2::Conditions {
25  TestingPayloadStorage::TestingPayloadStorage(const std::string& filename):
26  m_filename{filename}, m_absoluteFilename{fs::absolute(m_filename).string()},
27  m_payloadDir{fs::path(m_absoluteFilename).parent_path().string()}
28  {}
29 
31  {
32  // Read database file on first access
33  if (!m_initialized) read();
34  m_initialized = true;
35  // And then let's look for a payload with the name
36  auto it = m_payloads.find(info.name);
37  if (it == m_payloads.end()) return false;
38  bool found{false};
39  // and check all of them for what is the highest revision containing the event
40  for (const auto& [revision, iov] : it->second) {
41  if (iov.contains(event)) {
42  info.revision = 0;
43  info.globaltag = "temp://" + m_absoluteFilename;
44  info.baseUrl = "";
45  info.payloadUrl = "";
46  info.filename = payloadFilename(m_payloadDir, info.name, revision);
47  info.iov = iov;
48  if (!FileSystem::fileExists(info.filename)) {
49  B2FATAL("Could not find payload file specified in testing payload storage" << LogVar("storage filen", m_filename)
50  << LogVar("name", info.name) << LogVar("local revision", info.revision)
51  << LogVar("payload filename", info.filename));
52  }
53  info.checksum = FileSystem::calculateMD5(info.filename);
54  found = true;
55  break;
56  }
57  }
58  if (found) {
59  B2WARNING("Conditions: Temporary testing payload used for processing" << LogVar("storage file", m_filename)
60  << LogVar("name", info.name) << LogVar("revision", info.revision) << LogVar("iov", info.iov));
61  }
62  // Did we find something? did we?
63  return found;
64  }
65 
67  {
68  m_payloads.clear();
69  if (!fs::is_regular_file(m_absoluteFilename)) {
70  B2WARNING("Given testing payload storage file doesn't exist or is not a regular file" << LogVar("storage file", m_filename));
71  return;
72  }
73  // read and parse the database content
74  std::ifstream file(m_absoluteFilename.c_str());
75  if (!file.is_open()) {
76  B2FATAL("Opening of testing payload storage file failed" << LogVar("storage file", m_filename) << LogVar("error", strerror(errno)));
77  }
78  int lineno{0};
79  try {
80  while (!file.eof()) {
81  // read the file line by line
82  std::string line;
83  std::getline(file, line);
84  ++lineno;
85  // and remove comments from the line
86  size_t commentChar = line.find('#');
87  if (commentChar != std::string::npos) {
88  line = line.substr(0, commentChar);
89  }
90  // trim whitespace on each side
91  boost::algorithm::trim(line);
92  // if nothing is left skip the line
93  if (line.empty()) continue;
94  // otherwise read name, revision and iov from the line
95  std::string name;
96  std::string revision;
98  try {
99  std::stringstream(line) >> name >> revision >> iov;
100  } catch (std::runtime_error& e) {
101  throw std::runtime_error("line must be of the form 'dbstore/<payloadname> <revision> <firstExp>,<firstRun>,<finalExp>,<finalRun>'");
102  }
103  // parse name
104  size_t pos = name.find('/');
105  if (pos == std::string::npos) {
106  throw std::runtime_error("payload name must be of the form dbstore/<payloadname>");
107  }
108  std::string module = name.substr(pos + 1, name.length());
109  // and add to map of payloads
110  B2DEBUG(39, "Found testing payload" << LogVar("storage file", m_filename) << LogVar("name", module)
111  << LogVar("revision/md5", revision) << LogVar("iov", iov));
112  m_payloads[module].emplace_back(revision, iov);
113  }
114  } catch (std::exception& e) {
115  B2FATAL("Problem reading testing payloads storage" << LogVar("storage file", m_filename)
116  << LogVar("line", lineno) << LogVar("error", e.what()));
117  }
118  // and reverse all the payloads so the last ones in the file have highest priority
119  for (auto& [name, payloads] : m_payloads) {
120  std::reverse(payloads.begin(), payloads.end());
121  }
122  }
123 
124  std::string TestingPayloadStorage::payloadFilename(const std::string& path, const std::string& name,
125  const std::string& revision)
126  {
127  std::stringstream result;
128  if (!path.empty()) result << path << '/';
129  result << "dbstore_" << name << "_rev_" << revision << ".root";
130  return result.str();
131  }
132 
133  bool TestingPayloadStorage::storeData(const std::string& name, TObject* object, const IntervalOfValidity& iov)
134  {
135  return store(name, iov, "", [this, &object, &name](const std::string & filename) {
136  return writePayload(filename, name, object);
137  });
138  }
139 
140  bool TestingPayloadStorage::storePayload(const std::string& name, const std::string& fileName, const IntervalOfValidity& iov)
141  {
142  // resolve all symbolic links to make sure we point to the real file
143  fs::path resolved = fs::canonical(fileName);
144  if (not fs::is_regular_file(resolved)) {
145  B2ERROR("Problem creating testing payload: Given payload storage file doesn't exist" << LogVar("storage file", fileName));
146  return false;
147  }
148  return store(name, iov, resolved.string(), [&resolved](const std::string & destination) {
149  // copy payload file to payload directory and rename it to follow the file name convention
150  fs::copy_file(resolved, destination, fs::copy_option::overwrite_if_exists);
151  return true;
152  });
153  }
154 
155  bool TestingPayloadStorage::store(const std::string& name, const IntervalOfValidity& iov,
156  const std::string& source, const std::function<bool(const std::string&)>& writer)
157  {
158  if (iov.empty()) {
159  B2ERROR("IoV is empty, refusing to store object in testing payload storage"
160  "Please provide a valid experiment/run range for the data, for example "
161  "using IntervalOfValidity::always() to store data which is always valid"
162  << LogVar("name", name));
163  return false;
164  }
165 
166  if (!fs::exists(m_payloadDir)) {
167  fs::create_directories(m_payloadDir);
168  }
169 
170  // create a temporary file if we don't have a source file yet
171  fs::path sourcefile{source};
172  if (source.empty()) {
173  while (true) {
174  sourcefile = fs::path(m_payloadDir) / fs::unique_path();
175  auto fd = open(sourcefile.c_str(), O_CREAT | O_EXCL);
176  if (fd >= 0) {
177  close(fd);
178  break;
179  }
180  if (errno != EEXIST && errno != EINTR) {
181  B2ERROR("Cannot create payload file:" << strerror(errno));
182  return false;
183  }
184  B2DEBUG(35, "first try to create tempfile failed, trying again");
185  }
186  if (!writer(sourcefile.string())) return false;
187  }
188  // If we created a temporary file we want to delete it again so we'd like to
189  // use a scope guard to do so. However we need it in this scope so we need
190  // to create one in any case and release it if we didn't create a temporary
191  // file
192  ScopeGuard delete_srcfile([&sourcefile] {fs::remove(sourcefile);});
193  if (!source.empty()) delete_srcfile.release();
194 
195  std::string md5 = FileSystem::calculateMD5(sourcefile.string());
196 
197  // Ok, now we have the file and it's md5 sum so let's get a write lock to the database file
198  // to avoid race conditions when creating files and writing the info in the text file.
200  if (!lock.lock()) {
201  B2ERROR("Locking of testing payload storage file failed, cannot create payload"
202  << LogVar("storage file", m_filename));
203  return false;
204  }
205  std::ofstream file(m_absoluteFilename.c_str(), std::ios::app);
206  if (!file.is_open()) {
207  B2ERROR("Could not open testing payload storage file for writing" << LogVar("storage file", m_filename));
208  }
209 
210  // So let's try renaming our temporary payload file to final destination
211  // We start with a 5 digit hash and expand if there's a collision
212  std::string revision;
213  bool found = false;
214  for (int i = 6; i <= 32; ++i) {
215  revision = md5.substr(0, i);
216  auto filename = payloadFilename(m_payloadDir, name, revision);
217  if (FileSystem::fileExists(filename)) {
218  if (md5 != FileSystem::calculateMD5(filename)) continue;
219  } else {
220  if (source.empty()) {
221  fs::rename(sourcefile, filename);
222  delete_srcfile.release();
223  } else {
224  fs::copy_file(source, filename, fs::copy_option::overwrite_if_exists);
225  }
226  }
227  found = true;
228  break;
229  }
230  if (!found) {
231  B2ERROR("Cannot create payload file: checksum mistmatch for existing files");
232  return false;
233  }
234  // Ok, add to the text file
235  file << "dbstore/" << name << " " << revision << " " << iov << std::endl;
236  B2DEBUG(32, "Storing testing payload" << LogVar("storage file", m_filename) << LogVar("name", name)
237  << LogVar("local revision", revision) << LogVar("iov", iov));
238  // And make sure we reread the file on next request to payloads
239  m_initialized = false;
240  return true;
241  }
242 
243  bool TestingPayloadStorage::writePayload(const std::string& fileName, const std::string& name, const TObject* object)
244  {
245  // Save the current gDirectory
246  TDirectory::TContext saveDir;
247  // And create a reproducible TFile: one that has the same checksum every time it's created as long as the content is the same
248  std::unique_ptr<TFile> file{TFile::Open((fileName + "?reproducible=PayloadFile").c_str(), "RECREATE")};
249  if (!file || !file->IsOpen()) {
250  B2ERROR("Could not open payload file for writing." << LogVar("filename", m_filename));
251  return false;
252  }
253  // Write the payload
254  object->Write(name.c_str(), TObject::kSingleKey);
255  // Done, let's go
256  file->Close();
257  return true;
258  }
259 
260 } // Belle2::Conditions namespace
bool storePayload(const std::string &name, const std::string &fileName, const IntervalOfValidity &iov)
Store an existing file as payload with given name and interval of validity.
bool m_initialized
Remember whether we read the file already.
std::string m_payloadDir
Directory containing the storage file as absolute file name.
bool writePayload(const std::string &fileName, const std::string &name, const TObject *object)
Write a payload file from the given object and name.
static std::string payloadFilename(const std::string &path, const std::string &name, const std::string &revision)
Build the filename for a new payload with a given name and revision in a directory.
std::unordered_map< std::string, std::vector< std::tuple< std::string, IntervalOfValidity > > > m_payloads
Map of known payloads to a list of known revisions and their interval of validity.
void read()
Read the given storage file, done lazily on first access to get() after construction or call to reset...
bool storeData(const std::string &name, TObject *object, const IntervalOfValidity &iov)
Store a TObject instance as a payload with given name and interval of validity.
bool store(const std::string &name, const IntervalOfValidity &iov, const std::string &source, const std::function< bool(const std::string &)> &writer)
Try to store a new payload with the given name and interval of validity.
bool get(const EventMetaData &event, PayloadMetadata &info)
Try to fill the PayloadMetaData for the given EventMetaData, return true on success,...
std::string m_filename
Storage file where to look for payloads.
std::string m_absoluteFilename
Storage file where to look for payloads converted to an absolute path to be robust against directory ...
TestingPayloadStorage(const std::string &filename)
Create a new instance to work on a given filename.
Store event, run, and experiment numbers.
Definition: EventMetaData.h:33
Helper class for locking a file.
Definition: FileSystem.h:97
bool lock(int timeout=300, bool ignoreErrors=false)
Try to lock the file.
Definition: FileSystem.cc:183
static std::string calculateMD5(const std::string &filename)
Calculate the MD5 checksum of a given file.
Definition: FileSystem.cc:77
static bool fileExists(const std::string &filename)
Check if the file with given filename exists.
Definition: FileSystem.cc:31
A class that describes the interval of experiments/runs for which an object in the database is valid.
Simple ScopeGuard to execute a function at the end of the object lifetime.
Definition: ScopeGuard.h:36
void release()
Release the guard without calling the cleanup function.
Definition: ScopeGuard.h:56
Class to store variables with their name which were sent to the logging service.
Simple struct to group all information necessary for a single payload.