CMS 3D CMS Logo

uploadConditions.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 """
3 
4 Joshua Dawes - CERN, CMS - The University of Manchester
5 
6 Upload script wrapper - controls the automatic update system.
7 
8 Note: the name of the file follows a different convention to the others because it should be the same as the current upload script name.
9 
10 Takes user arguments and passes them to the main upload module CondDBFW.uploads, once the correct version exists.
11 
12 1. Ask the server corresponding to the database we're uploading to which version of CondDBFW it has (query the /conddbfw_version/ url).
13 2. Decide which directory that we can write to - either the current local directory, or /tmp/random_string/.
14 3. Pull the commit returned from the server into the directory from step 2.
15 4. Invoke the CondDBFW.uploads module with the arguments given to this script.
16 
17 """
18 
19 import pycurl
20 from StringIO import StringIO
21 import traceback
22 import sys
23 import os
24 import json
25 import subprocess
26 import argparse
27 import netrc
28 import shutil
29 import getpass
30 
32  """
33  Queries the server-side for the commit hash it is currently using.
34  Note: this is the commit hash used by /data/services/common/CondDBFW on the server-side.
35  """
36  request = pycurl.Curl()
37  request.setopt(request.CONNECTTIMEOUT, 60)
38  user_agent = "User-Agent: ConditionWebServices/1.0 python/%d.%d.%d PycURL/%s" % (sys.version_info[ :3 ] + (pycurl.version_info()[1],))
39  request.setopt(request.USERAGENT, user_agent)
40  # we don't need to verify who signed the certificate or who the host is
41  request.setopt(request.SSL_VERIFYPEER, 0)
42  request.setopt(request.SSL_VERIFYHOST, 0)
43  response_buffer = StringIO()
44  request.setopt(request.WRITEFUNCTION, response_buffer.write)
45  request.setopt(request.URL, url + "conddbfw_version/")
46  request.perform()
47  return json.loads(response_buffer.getvalue())
48 
50  """
51  Gets the commit hash used by the local repository CondDBFW/.git/.
52  """
53  directory = os.path.abspath("CondDBFW")
54 
55  # get the commit hash of the code in `directory`
56  # by reading the .commit_hash file
57  try:
58  commit_hash_file_handle = open(os.path.join(directory, ".commit_hash"), "r")
59  commit_hash = commit_hash_file_handle.read().strip()
60 
61  # validate length of the commit hash
62  if len(commit_hash) != 40:
63  print("Commit hash found is not valid. Must be 40 characters long.")
64  exit()
65 
66  #commit_hash = run_in_shell("git --git-dir=%s rev-parse HEAD" % (os.path.join(directory, ".git")), shell=True).strip()
67 
68  return commit_hash
69  except Exception:
70  return None
71 
72 def get_directory_to_pull_to(default_directory, commit_hash):
73  """
74  Finds out which directory we can safely use - either CondDBFW/ or a temporary directory.
75  """
76  # try to write a file (and then delete it)
77  try:
78  handle = open(os.path.join(default_directory, "test_file"), "w")
79  handle.write("test")
80  handle.close()
81  os.remove(os.path.join(default_directory, "test_file"))
82  sys.path.insert(0, default_directory)
83  return default_directory
84  except IOError as io:
85  # cannot write to default directory, so set up a directory in /tmp/
86  new_path = os.path.join("tmp", commit_hash[0:10])
87  if not(os.path.exists(new_path)):
88  os.mkdir(new_path)
89  sys.path.insert(0, new_path)
90  return new_path
91  else:
92  # for now, fail
93  exit("Can't find anywhere to pull the new code base to.")
94 
95 horizontal_rule = "="*60
96 
97 def pull_code_from_git(target_directory, repository_url, hash):
98  """
99  Pulls CondDBFW from the git repository specified by the upload server.
100  """
101  # make directory
102  target = os.path.abspath(target_directory)
103  sys.path.append(target)
104  conddbfw_directory = os.path.join(target, "CondDBFW")
105  git_directory = os.path.join(conddbfw_directory, ".git")
106  if not(os.path.exists(conddbfw_directory)):
107  os.mkdir(conddbfw_directory)
108  else:
109  # if the directory exists, it may contain things - prompt the user
110  force_pull = str(raw_input("CondDBFW directory isn't empty - empty it, and update to new version? [y/n] "))
111  if force_pull == "y":
112  # empty directory and delete it
113  run_in_shell("rm -rf CondDBFW", shell=True)
114  # remake the directory - it will be empty
115  os.mkdir(conddbfw_directory)
116 
117  print("Pulling code back from repository...")
118  print(horizontal_rule)
119 
120  run_in_shell("git --git-dir=%s clone %s CondDBFW" % (git_directory, repository_url), shell=True)
121  # --force makes sure we ignore any conflicts that
122  # could occur and overwrite everything in the checkout
123  run_in_shell("cd %s && git checkout --force -b version_used %s" % (conddbfw_directory, hash), shell=True)
124 
125  # write the hash to a file in the CondDBFW directory so we can delete the git repository
126  hash_file_handle = open(os.path.join(conddbfw_directory, ".commit_hash"), "w")
127  hash_file_handle.write(hash)
128  hash_file_handle.close()
129 
130  # can now delete .git directory
131  shutil.rmtree(git_directory)
132 
133  print(horizontal_rule)
134  print("Creating local log directories (if required)...")
135  if not(os.path.exists(os.path.join(target, "upload_logs"))):
136  os.mkdir(os.path.join(target, "upload_logs"))
137  if not(os.path.exists(os.path.join(target, "server_side_logs"))):
138  os.mkdir(os.path.join(target, "server_side_logs"))
139  print("Finished with log directories.")
140  print("Update of CondDBFW complete.")
141 
142  print(horizontal_rule)
143 
144  return True
145 
146 def run_in_shell(*popenargs, **kwargs):
147  """
148  Runs string-based commands in the shell and returns the result.
149  """
150  out = subprocess.PIPE if kwargs.get("stdout") == None else kwargs.get("stdout")
151  new_kwargs = kwargs
152  if new_kwargs.get("stdout"):
153  del new_kwargs["stdout"]
154  process = subprocess.Popen(*popenargs, stdout=out, **new_kwargs)
155  stdout = process.communicate()[0]
156  returnCode = process.returncode
157  cmd = kwargs.get('args')
158  if cmd is None:
159  cmd = popenargs[0]
160  if returnCode:
161  raise subprocess.CalledProcessError(returnCode, cmd)
162  return stdout
163 
164 def run_upload(**parameters):
165  """
166  Imports CondDBFW.uploads and runs the upload with the upload metadata obtained.
167  """
168  try:
169  import CondDBFW.uploads as uploads
170  except Exception as e:
171  traceback.print_exc()
172  exit("CondDBFW or one of its dependencies could not be imported.\n"\
173  + "If the CondDBFW directory exists, you are likely not in a CMSSW environment.")
174  # we have CondDBFW, so just call the module with the parameters given in the command line
175  uploader = uploads.uploader(**parameters)
176  result = uploader.upload()
177 
179  # read in command line arguments, and build metadata dictionary from them
180  parser = argparse.ArgumentParser(prog="cmsDbUpload client", description="CMS Conditions Upload Script in CondDBFW.")
181 
182  parser.add_argument("--sourceDB", type=str, help="DB to find Tags, IOVs + Payloads in.", required=False)
183 
184  # metadata arguments
185  parser.add_argument("--inputTag", type=str,\
186  help="Tag to take IOVs + Payloads from in --sourceDB.", required=False)
187  parser.add_argument("--destinationTag", type=str,\
188  help="Tag to copy IOVs + Payloads to in --destDB.", required=False)
189  parser.add_argument("--destinationDatabase", type=str,\
190  help="Database to copy IOVs + Payloads to.", required=False)
191  parser.add_argument("--since", type=int,\
192  help="Since to take IOVs from.", required=False)
193  parser.add_argument("--userText", type=str,\
194  help="Description of --destTag (can be empty).")
195 
196  # non-metadata arguments
197  parser.add_argument("--metadataFile", "-m", type=str, help="Metadata file to take metadata from.", required=False)
198 
199  parser.add_argument("--debug", required=False, action="store_true")
200  parser.add_argument("--verbose", required=False, action="store_true")
201  parser.add_argument("--testing", required=False, action="store_true")
202  parser.add_argument("--fcsr-filter", type=str, help="Synchronization to take FCSR from for local filtering of IOVs.", required=False)
203 
204  parser.add_argument("--netrc", required=False)
205 
206  parser.add_argument("--hashToUse", required=False)
207 
208  parser.add_argument("--server", required=False)
209 
210  parser.add_argument("--review-options", required=False, action="store_true")
211 
212  command_line_data = parser.parse_args()
213 
214  # default is the production server, which can point to either database anyway
215  server_alias_to_url = {
216  "prep" : "https://cms-conddb-dev.cern.ch/cmsDbCondUpload/",
217  "prod" : "https://cms-conddb.cern.ch/cmsDbCondUpload/",
218  None : "https://cms-conddb.cern.ch/cmsDbCondUpload/"
219  }
220 
221  # if prep, prod or None were given, convert to URLs in dictionary server_alias_to_url
222  # if not, assume a URL has been given and use this instead
223  if command_line_data.server in server_alias_to_url.keys():
224  command_line_data.server = server_alias_to_url[command_line_data.server]
225 
226  # use netrc to get username and password
227  try:
228  netrc_file = command_line_data.netrc
229  netrc_authenticators = netrc.netrc(netrc_file).authenticators("ConditionUploader")
230  if netrc_authenticators == None:
231  print("Your netrc file must contain the key 'ConditionUploader'.")
232  manual_input = raw_input("Do you want to try to type your credentials? ")
233  if manual_input == "y":
234  # ask for username and password
235  username = raw_input("Username: ")
236  password = getpass.getpass("Password: ")
237  else:
238  exit()
239  else:
240  print("Read your credentials from ~/.netrc. If you want to use a different file, supply its name with the --netrc argument.")
241  username = netrc_authenticators[0]
242  password = netrc_authenticators[2]
243  except:
244  print("Couldn't obtain your credentials (either from netrc or manual input).")
245  exit()
246 
247  command_line_data.username = username
248  command_line_data.password = password
249  # this will be used as the final destinationTags value by all input methods
250  # apart from the metadata file
251  command_line_data.destinationTags = {command_line_data.destinationTag:{}}
252 
253  """
254  Construct metadata_dictionary:
255  Currently, this is 3 cases:
256 
257  1) An IOV is being appended to an existing Tag with an existing Payload.
258  In this case, we just take all data from the command line.
259 
260  2) No metadata file is given, so we assume that ALL upload metadata is coming from the command line.
261 
262  3) A metadata file is given, hence we parse the file, and then iterate through command line arguments
263  since these override the options set in the metadata file.
264 
265  """
266  if command_line_data.hashToUse != None:
267  command_line_data.userText = ""
268  metadata_dictionary = command_line_data.__dict__
269  elif command_line_data.metadataFile == None:
270  command_line_data.userText = command_line_data.userText\
271  if command_line_data.userText != None\
272  else str(raw_input("Tag's description [can be empty]:"))
273  metadata_dictionary = command_line_data.__dict__
274  else:
275  metadata_dictionary = json.loads("".join(open(os.path.abspath(command_line_data.metadataFile), "r").readlines()))
276  metadata_dictionary["username"] = username
277  metadata_dictionary["password"] = password
278  metadata_dictionary["userText"] = metadata_dictionary.get("userText")\
279  if metadata_dictionary.get("userText") != None\
280  else str(raw_input("Tag's description [can be empty]:"))
281  # set the server to use to be the default one
282  metadata_dictionary["server"] = server_alias_to_url[None]
283 
284  # go through command line options and, if they are set, overwrite entries
285  for (option_name, option_value) in command_line_data.__dict__.items():
286  # if the metadata_dictionary sets this, overwrite it
287  if option_name != "destinationTags":
288  if option_value != None or (option_value == None and not(option_name in metadata_dictionary.keys())):
289  # if option_value has a value, override the metadata file entry
290  # or if option_value is None but the metadata file doesn't give a value,
291  # set the entry to None as well
292  metadata_dictionary[option_name] = option_value
293  else:
294  if option_value != {None:{}}:
295  metadata_dictionary["destinationTags"] = {option_value:{}}
296  elif option_value == {None:{}} and not("destinationTags" in metadata_dictionary.keys()):
297  metadata_dictionary["destinationTags"] = {None:{}}
298 
299  if command_line_data.review_options:
300  defaults = {
301  "since" : "Since of first IOV",
302  "userText" : "Populated by upload process",
303  "netrc" : "None given",
304  "fcsr_filter" : "Don't apply",
305  "hashToUse" : "Using local SQLite file instead"
306  }
307  print("Configuration to use for the upload:")
308  for key in metadata_dictionary:
309  if not(key) in ["username", "password", "destinationTag"]:
310  value_to_print = metadata_dictionary[key] if metadata_dictionary[key] != None else defaults[key]
311  print("\t%s : %s" % (key, value_to_print))
312 
313  if raw_input("\nDo you want to continue? [y/n] ") != "y":
314  exit()
315 
316  return metadata_dictionary
317 
318 if __name__ == "__main__":
319 
320  upload_metadata = parse_arguments()
321 
322  # upload_metadata should be used to decide the service url
323  final_service_url = upload_metadata["server"]
324 
325  conddbfw_version = get_version_info(final_service_url)
326  local_version = get_local_commit_hash()
327 
328  """
329  Todo - case where we don't have write permission in the current directory (local_version == None and hashes don't match)
330  """
331  # target_directory is only used if we don't find a version of CondDBFW locally,
332  # but is set here so we can access it later if we need to delete a temporary directory
333  target_directory = ""
334  # check if we have a persistent local version of CondDBFW
335  if local_version != None:
336  if conddbfw_version["hash"] == local_version:
337  # no update is required, pass for now
338  print("No change of version of CondDBFW is required - performing the upload.")
339  # add CondDBFW to the system paths (local_version != None, so we know it's in this directory)
340  sys.path.append(os.path.abspath(os.getcwd()))
341  elif conddbfw_version["hash"] != local_version:
342  # this is the case where CondDBFW is in the directory working_dir/CondDBFW, but there is an update available
343  # CondDBFW isn't in this directory, and the local commit hash doesn't match the latest one on the server
344  print("The server uses a different version of CondDBFW - changing to commit '%s' of CondDBFW." % conddbfw_version["hash"])
345  shell_response = pull_code_from_git(os.getcwd(), conddbfw_version["repo"], conddbfw_version["hash"])
346  else:
347  # no CondDBFW version - we should pull the code and start from scratch
348  # we can't look for temporary versions of it in /tmp/, since we can't guess the hash used to make the directory name
349  print("No CondDBFW version found locally - pulling one.")
350  target_directory = get_directory_to_pull_to(os.getcwd(), conddbfw_version["hash"])
351  shell_response = pull_code_from_git(target_directory, conddbfw_version["repo"], conddbfw_version["hash"])
352 
353  import CondDBFW.data_sources as data_sources
354 
355  upload_metadata["sqlite_file"] = upload_metadata.get("sourceDB")
356 
357  # make new dictionary, and copy over everything except "metadata_source"
358  upload_metadata_argument = {}
359  for (key, value) in upload_metadata.items():
360  if key != "metadata_source":
361  upload_metadata_argument[key] = value
362 
363  upload_metadata["metadata_source"] = data_sources.json_data_node.make(upload_metadata_argument)
364 
365  # pass dictionary as arguments to match keywords - the constructor has a **kwargs parameter to deal with stray arguments
366  run_upload(**upload_metadata)
367 
368  # if the directory was temporary, delete it
369  if "tmp" in target_directory:
370  print(horizontal_rule)
371  print("Removing directory %s..." % target_directory)
372  try:
373  run_in_shell("rm -rf %s" % target_directory, shell=True)
374  except Exception as e:
375  print("Couldn't delete the directory %s - try to manually delete it." % target_directory)
std::string print(const Track &, edm::Verbosity=edm::Concise)
Track print utility.
Definition: print.cc:10
def get_directory_to_pull_to(default_directory, commit_hash)
def get_version_info(url)
def run_in_shell(popenargs, kwargs)
def run_upload(parameters)
static std::string join(char **cmd)
Definition: RemoteFile.cc:18
def pull_code_from_git(target_directory, repository_url, hash)