Belle II Software  release-05-01-25
TestingPayloadStorage.cc
1 /**************************************************************************
2  * BASF2 (Belle Analysis Framework 2) *
3  * Copyright(C) 2019 - Belle II Collaboration *
4  * *
5  * Author: The Belle II Collaboration *
6  * Contributors: Martin Ritter *
7  * *
8  * This software is provided "as is" without any warranty. *
9  **************************************************************************/
10 
11 #include <framework/database/TestingPayloadStorage.h>
12 #include <framework/dataobjects/EventMetaData.h>
13 #include <framework/logging/Logger.h>
14 #include <framework/utilities/FileSystem.h>
15 #include <framework/utilities/ScopeGuard.h>
16 
17 #include <TDirectory.h>
18 #include <TFile.h>
19 #include <TObject.h>
20 
21 #include <boost/filesystem.hpp>
22 #include <boost/algorithm/string.hpp>
23 
24 namespace fs = boost::filesystem;
25 
26 namespace Belle2::Conditions {
27  TestingPayloadStorage::TestingPayloadStorage(const std::string& filename):
28  m_filename{filename}, m_absoluteFilename{fs::absolute(m_filename).string()},
29  m_payloadDir{fs::path(m_absoluteFilename).parent_path().string()}
30  {}
31 
32  bool TestingPayloadStorage::get(const EventMetaData& event, PayloadMetadata& info)
33  {
34  // Read database file on first access
36  m_initialized = true;
37  // And then let's look for a payload with the name
38  auto it = m_payloads.find(info.name);
39  if (it == m_payloads.end()) return false;
40  bool found{false};
41  // and check all of them for what is the highest revision containing the event
42  for (const auto& [revision, iov] : it->second) {
43  if (iov.contains(event) and info.revision < revision) {
44  info.revision = revision;
45  info.globaltag = "temp://" + m_absoluteFilename;
46  info.baseUrl = "";
47  info.payloadUrl = "";
48  info.filename = payloadFilename(m_payloadDir, info.name, info.revision);
49  info.iov = iov;
50  if (!FileSystem::fileExists(info.filename)) {
51  B2FATAL("Could not find payload file specified in testing payload storage" << LogVar("storage filen", m_filename)
52  << LogVar("name", info.name) << LogVar("local revision", info.revision)
53  << LogVar("payload filename", info.filename));
54  }
55  info.checksum = FileSystem::calculateMD5(info.filename);
56  found = true;
57  }
58  }
59  if (found) {
60  B2WARNING("Conditions: Temporary testing payload used for processing" << LogVar("storage file", m_filename)
61  << LogVar("name", info.name) << LogVar("revision", info.revision) << LogVar("iov", info.iov));
62  }
63  // Did we find something? did we?
64  return found;
65  }
66 
68  {
69  m_payloads.clear();
70  if (!fs::is_regular_file(m_absoluteFilename)) {
71  B2WARNING("Given testing payload storage file doesn't exist or is not a regular file" << LogVar("storage file", m_filename));
72  return;
73  }
74  // read and parse the database content
75  std::ifstream file(m_absoluteFilename.c_str());
76  if (!file.is_open()) {
77  B2FATAL("Opening of testing payload storage file failed" << LogVar("storage file", m_filename) << LogVar("error", strerror(errno)));
78  }
79  int lineno{0};
80  try {
81  while (!file.eof()) {
82  // read the file line by line
83  std::string line;
84  std::getline(file, line);
85  ++lineno;
86  // and remove comments from the line
87  size_t commentChar = line.find('#');
88  if (commentChar != std::string::npos) {
89  line = line.substr(0, commentChar);
90  }
91  // trim whitespace on each side
92  boost::algorithm::trim(line);
93  // if nothing is left skip the line
94  if (line.empty()) continue;
95  // otherwise read name, revision and iov from the line
96  std::string name;
97  std::string revisionStr;
98  IntervalOfValidity iov;
99  try {
100  std::stringstream(line) >> name >> revisionStr >> iov;
101  } catch (std::runtime_error& e) {
102  throw std::runtime_error("line must be of the form 'dbstore/<payloadname> <revision> <firstExp>,<firstRun>,<finalExp>,<finalRun>'");
103  }
104  int revision{ -1};
105  try {
106  revision = stoi(revisionStr);
107  } catch (std::invalid_argument& e) {
108  throw std::runtime_error("revision must be an integer");
109  }
110  // parse name
111  size_t pos = name.find('/');
112  if (pos == std::string::npos) {
113  throw std::runtime_error("payload name must be of the form dbstore/<payloadname>");
114  }
115  std::string module = name.substr(pos + 1, name.length());
116  // and add to map of payloads
117  B2DEBUG(39, "Found testing payload" << LogVar("storage file", m_filename) << LogVar("name", module) << LogVar("revision",
118  revision) << LogVar("iov", iov));
119  m_payloads[module].emplace_back(revision, iov);
120  }
121  } catch (std::exception& e) {
122  B2FATAL("Problem reading testing payloads storage" << LogVar("storage file", m_filename)
123  << LogVar("line", lineno) << LogVar("error", e.what()));
124  }
125  }
126 
127  std::string TestingPayloadStorage::payloadFilename(const std::string& path, const std::string& name,
128  int revision)
129  {
130  std::stringstream result;
131  if (!path.empty()) result << path << '/';
132  result << "dbstore_" << name << "_rev_" << revision << ".root";
133  return result.str();
134  }
135 
136  bool TestingPayloadStorage::storeData(const std::string& name, TObject* object, const IntervalOfValidity& iov)
137  {
138  return store(name, iov, [this, &object, &name](const std::string & filename) {
139  return writePayload(filename, name, object);
140  });
141  }
142 
143  bool TestingPayloadStorage::storePayload(const std::string& name, const std::string& fileName, const IntervalOfValidity& iov)
144  {
145  // resolve all symbolic links to make sure we point to the real file
146  boost::filesystem::path resolved = boost::filesystem::canonical(fileName);
147  if (not boost::filesystem::is_regular_file(resolved)) {
148  B2ERROR("Problem creating testing payload: Given payload storage file doesn't exist" << LogVar("storage file", fileName));
149  return false;
150  }
151  return store(name, iov, [&resolved](const std::string & destination) {
152  // copy payload file to payload directory and rename it to follow the file name convention
153  boost::filesystem::copy(resolved, destination);
154  return true;
155  });
156  }
157 
158  bool TestingPayloadStorage::store(const std::string& name, const IntervalOfValidity& iov,
159  const std::function<bool(const std::string&)>& writer)
160  {
161  if (iov.empty()) {
162  B2ERROR("IoV is empty, refusing to store object in testing payload storage"
163  "Please provide a valid experiment/run range for the data, for example "
164  "using IntervalOfValidity::always() to store data which is always valid"
165  << LogVar("name", name));
166  return false;
167  }
168 
169  if (!fs::exists(m_payloadDir)) {
170  fs::create_directories(m_payloadDir);
171  }
172  // get lock for write access to database file
174  if (!lock.lock()) {
175  B2ERROR("Locking of testing payload storage file failed, cannot create payload"
176  << LogVar("storage file", m_filename));
177  return false;
178  }
179  std::ofstream file(m_absoluteFilename.c_str(), std::ios::app);
180  if (!file.is_open()) {
181  B2ERROR("Could not open testing payload storage file for writing" << LogVar("storage file", m_filename));
182  }
183 
184  // Find the next free revision number
185  for (int revision = 1; revision < INT_MAX; ++revision) {
186  auto filename = payloadFilename(m_payloadDir, name, revision);
187  // FIXME: This could be a race condition, we check for existence and then
188  // create which could fail if two processes try this at the same time and
189  // thus overwrite the files of each other. We could instead check if an
190  // `open(filename.c_str(), O_CREAT|O_EXCL)` is successful in which the
191  // file is ours however I'm a bit sceptical if this will work on SL6. But
192  // since we locked the database file and no longer support the payloads be
193  // in a different directory then the text files this almost fine. However
194  // there could still be multiple text files in the same directory so still
195  // a slight chance for race conditions. Or locking could just not work on
196  // some file systems, for example misconfigured NFS
197  if (FileSystem::fileExists(filename)) continue;
198  // free revision found, try to save
199  if (!writer(filename)) return false;
200  // Ok, now we need to add it to the database file
201  file << "dbstore/" << name << " " << revision << " " << iov << std::endl;
202  B2DEBUG(32, "Storing testing payload" << LogVar("storage file", m_filename) << LogVar("name", name)
203  << LogVar("local revision", revision) << LogVar("iov", iov));
204  // And make sure we reread the file on next request to payloads
205  m_initialized = false;
206  return true;
207  }
208  B2ERROR("Could not find a suitable revision to create payload" << LogVar("storage file", m_filename) << LogVar("name", name));
209  return false;
210  }
211 
212  bool TestingPayloadStorage::writePayload(const std::string& fileName, const std::string& name, const TObject* object)
213  {
214  // Save the current gDirectory
215  TDirectory::TContext saveDir;
216  // And create a reproducible TFile: one that has the same checksum every time it's created as long as the content is the same
217  std::unique_ptr<TFile> file{TFile::Open((fileName + "?reproducible=PayloadFile").c_str(), "RECREATE")};
218  if (!file || !file->IsOpen()) {
219  B2ERROR("Could not open payload file for writing." << LogVar("filename", m_filename));
220  return false;
221  }
222  // Write the payload and maybe the iov
223  object->Write(name.c_str(), TObject::kSingleKey);
224  // Done, let's go
225  file->Close();
226  return true;
227  }
228 
229 } // Belle2::Conditions namespace
Belle2::IntervalOfValidity
A class that describes the interval of experiments/runs for which an object in the database is valid.
Definition: IntervalOfValidity.h:35
prepareAsicCrosstalkSimDB.e
e
aux.
Definition: prepareAsicCrosstalkSimDB.py:53
Belle2::Conditions::TestingPayloadStorage::m_initialized
bool m_initialized
Remember whether we read the file already.
Definition: TestingPayloadStorage.h:80
Belle2::Conditions::TestingPayloadStorage::m_filename
std::string m_filename
Storage file where to look for payloads.
Definition: TestingPayloadStorage.h:72
Belle2::Conditions::TestingPayloadStorage::read
void read()
Read the given storage file, done lazily on first access to get() after construction or call to reset...
Definition: TestingPayloadStorage.cc:75
Belle2::FileSystem::calculateMD5
static std::string calculateMD5(const std::string &filename)
Calculate the MD5 checksum of a given file.
Definition: FileSystem.cc:79
Belle2::Conditions::TestingPayloadStorage::m_payloadDir
std::string m_payloadDir
Directory containing the storage file as absolute file name.
Definition: TestingPayloadStorage.h:78
Belle2::Conditions::TestingPayloadStorage::get
bool get(const EventMetaData &event, PayloadMetadata &info)
Try to fill the PayloadMetaData for the given EventMetaData, return true on success,...
Definition: TestingPayloadStorage.cc:40
Belle2::Conditions::TestingPayloadStorage::storeData
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.
Definition: TestingPayloadStorage.cc:144
LogVar
Class to store variables with their name which were sent to the logging service.
Definition: LogVariableStream.h:24
Belle2::FileSystem::fileExists
static bool fileExists(const std::string &filename)
Check if the file with given filename exists.
Definition: FileSystem.cc:33
Belle2::FileSystem::Lock
Helper class for locking a file.
Definition: FileSystem.h:107
alignment.constraints_generator.filename
filename
File name.
Definition: constraints_generator.py:224
Belle2::Conditions::TestingPayloadStorage::writePayload
bool writePayload(const std::string &fileName, const std::string &name, const TObject *object)
Write a payload file from the given object and name.
Definition: TestingPayloadStorage.cc:220
Belle2::Conditions::TestingPayloadStorage::m_absoluteFilename
std::string m_absoluteFilename
Storage file where to look for payloads converted to an absolute path to be robust against directory ...
Definition: TestingPayloadStorage.h:74
Belle2::Conditions::TestingPayloadStorage::store
bool store(const std::string &name, const IntervalOfValidity &iov, const std::function< bool(const std::string &)> &writer)
Try to store a new payload with the given name and interval of validity.
Definition: TestingPayloadStorage.cc:166
Belle2::Conditions::TestingPayloadStorage::payloadFilename
static std::string payloadFilename(const std::string &path, const std::string &name, int revision)
Build the filename for a new payload with a given name and revision in a directory.
Definition: TestingPayloadStorage.cc:135
Belle2::Conditions::TestingPayloadStorage::m_payloads
std::unordered_map< std::string, std::vector< std::tuple< size_t, IntervalOfValidity > > > m_payloads
Map of known payloads to a list of known revisions and their interval of validity.
Definition: TestingPayloadStorage.h:70
Belle2::Conditions::TestingPayloadStorage::TestingPayloadStorage
TestingPayloadStorage(const std::string &filename)
Create a new instance to work on a given filename.
Definition: TestingPayloadStorage.cc:35
Belle2::Conditions::TestingPayloadStorage::storePayload
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.
Definition: TestingPayloadStorage.cc:151