| OLD | NEW |
| (Empty) | |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. |
| 4 |
| 5 """Get information about a buildbot master and manipulate it.""" |
| 6 |
| 7 |
| 8 import errno |
| 9 import os |
| 10 import json |
| 11 import re |
| 12 import requests |
| 13 import simplejson |
| 14 import subprocess |
| 15 import sys |
| 16 |
| 17 from dateutil.parser import parse |
| 18 |
| 19 from infra.libs.time_functions import timestamp |
| 20 |
| 21 ######## Getting information about the master. |
| 22 |
| 23 |
| 24 # Derived from http://stackoverflow.com/a/6940314. |
| 25 def _pid_is_alive(pid): # pragma: no cover |
| 26 """Determine if the given pid is still running. |
| 27 |
| 28 Sending a signal of 0 (the 'null signal') does all the checks to send a signal |
| 29 without actually sending a signal. Thus it works as a check to see if the pid |
| 30 is valid. See http://goo.gl/6M1BoF for details. |
| 31 """ |
| 32 if sys.platform.startswith('win'): |
| 33 raise NotImplementedError |
| 34 |
| 35 if pid <= 0: |
| 36 return False |
| 37 try: |
| 38 os.kill(pid, 0) |
| 39 except OSError as err: |
| 40 if err.errno == errno.ESRCH: |
| 41 return False |
| 42 elif err.errno == errno.EPERM: |
| 43 return True |
| 44 else: |
| 45 raise |
| 46 else: |
| 47 return True |
| 48 |
| 49 |
| 50 def buildbot_is_running(directory): |
| 51 """Determine if the twisted.pid in a directory is running.""" |
| 52 pidfile = os.path.join(directory, 'twistd.pid') |
| 53 if not os.path.exists(pidfile): |
| 54 return False |
| 55 with open(pidfile) as f: |
| 56 pid = int(f.read().strip()) |
| 57 return _pid_is_alive(pid) |
| 58 |
| 59 |
| 60 def _get_last_action(directory, action): |
| 61 """Get the last time the given action was run in the actions.log.""" |
| 62 actions_log_file = os.path.join(directory, 'actions.log') |
| 63 if not os.path.exists(actions_log_file): |
| 64 return None |
| 65 with open(actions_log_file) as f: |
| 66 for line in reversed(list(f)): |
| 67 if action in line: |
| 68 unparsed_date = line.split(action)[0][len('**'):] |
| 69 return timestamp.utctimestamp(parse(unparsed_date)) |
| 70 return None |
| 71 |
| 72 |
| 73 |
| 74 def get_last_boot(directory): |
| 75 """Determine the last time the master was started.""" |
| 76 return max( |
| 77 _get_last_action(directory, 'make restart'), |
| 78 _get_last_action(directory, 'make start')) |
| 79 |
| 80 |
| 81 def get_last_no_new_builds(directory): |
| 82 """Determine the last time a *current* make no-new-builds was called. |
| 83 |
| 84 If a 'make start' or 'make restart' was issued after the no-new-builds, it is |
| 85 not valid (since the make start or make restart nullified the no-new-builds). |
| 86 """ |
| 87 last_boot = get_last_boot(directory) |
| 88 last_no_new_builds = _get_last_action(directory, 'make no-new-builds') |
| 89 if not last_no_new_builds: |
| 90 return None |
| 91 if last_boot > last_no_new_builds: |
| 92 return None |
| 93 return last_no_new_builds |
| 94 |
| 95 |
| 96 def _get_mastermap_data(directory): |
| 97 """Get mastermap JSON from a master directory.""" |
| 98 |
| 99 build_dir = os.path.join(directory, os.pardir, os.pardir, os.pardir, 'build') |
| 100 runit = os.path.join(build_dir, 'scripts', 'tools', 'runit.py') |
| 101 build_internal_dir = os.path.join(build_dir, os.pardir, 'build_internal') |
| 102 |
| 103 script_path = os.path.join(build_dir, 'scripts', 'tools', 'mastermap.py') |
| 104 if os.path.exists(build_internal_dir): |
| 105 script_path = os.path.join( |
| 106 build_internal_dir, 'scripts', 'tools', 'mastermap_internal.py') |
| 107 |
| 108 script_data = json.loads(subprocess.check_output( |
| 109 [runit, script_path, '-f', 'json'])) |
| 110 |
| 111 short_dirname = os.path.basename(directory) |
| 112 matches = [x for x in script_data if x.get('dirname') == short_dirname] |
| 113 assert len(matches) < 2, ( |
| 114 'Multiple masters were found with the same master directory.') |
| 115 if matches: |
| 116 return matches[0] |
| 117 return None |
| 118 |
| 119 |
| 120 def _get_master_web_port(directory): |
| 121 """Determine the web port of the master running in the given directory.""" |
| 122 mastermap_data = _get_mastermap_data(directory) |
| 123 if mastermap_data: |
| 124 return mastermap_data['port'] |
| 125 return None |
| 126 |
| 127 |
| 128 def get_accepting_builds(directory, timeout=30): |
| 129 """Determine whether the master is accepting new builds or not. |
| 130 |
| 131 *** This only works for masters running on localhost.*** |
| 132 """ |
| 133 port = _get_master_web_port(directory) |
| 134 if port is not None: |
| 135 try: |
| 136 res = requests.get( |
| 137 'http://localhost:%d/json/accepting_builds' % port, |
| 138 timeout=timeout) |
| 139 if res.status_code == 200: |
| 140 try: |
| 141 return res.json().get('accepting_builds') |
| 142 except simplejson.scanner.JSONDecodeError: |
| 143 pass |
| 144 except requests.exceptions.Timeout: |
| 145 pass |
| 146 return None |
| 147 |
| 148 |
| 149 ######## Performing actions on the master. |
| 150 |
| 151 |
| 152 GclientSync, MakeStop, MakeWait, MakeStart, MakeNoNewBuilds = range(5) |
| 153 |
| 154 |
| 155 def convert_action_items_to_cli( |
| 156 action_items, directory, enable_gclient=False): |
| 157 |
| 158 def cmd_dict(cmd, lockfile_prefix): |
| 159 # Using the same lockfile prefix for two actions will make them mutually |
| 160 # exclusive. So setting 'make' for all the make commands tries to prevent |
| 161 # two make actions happening at once in the same master directory. |
| 162 lockfile = '%s_%s' % ( |
| 163 lockfile_prefix, |
| 164 re.sub('[^\w]', '_', directory.lower())) |
| 165 return { |
| 166 'cwd': directory, |
| 167 'cmd': cmd, |
| 168 'lockfile': lockfile, |
| 169 } |
| 170 |
| 171 for action_item in action_items: |
| 172 if action_item == GclientSync: |
| 173 if enable_gclient: |
| 174 yield cmd_dict( |
| 175 ['gclient', 'sync', '--reset', '--force', '--auto_rebase'], |
| 176 'gclient') |
| 177 elif action_item == MakeStop: |
| 178 yield cmd_dict(['make', 'stop'], 'make') |
| 179 elif action_item == MakeWait: |
| 180 yield cmd_dict(['make', 'wait'], 'make') |
| 181 elif action_item == MakeStart: |
| 182 yield cmd_dict(['make', 'start'], 'make') |
| 183 elif action_item == MakeNoNewBuilds: |
| 184 yield cmd_dict(['make', 'no-new-builds'], 'make') |
| 185 else: |
| 186 raise ValueError('Invalid action item %s' % action_item) |
| OLD | NEW |