blob: 147556b10de70539a5b604553afde1512868729b [file] [log] [blame]
# Copyright 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Manage tree status."""
import httplib
import json
import logging
import os
import socket
import urllib
import urllib2
from chromite.cbuildbot import constants
from chromite.lib import osutils
from chromite.lib import timeout_util
CROS_TREE_STATUS_URL = 'https://chromiumos-status.appspot.com'
CROS_TREE_STATUS_JSON_URL = '%s/current?format=json' % CROS_TREE_STATUS_URL
CROS_TREE_STATUS_UPDATE_URL = '%s/status' % CROS_TREE_STATUS_URL
_USER_NAME = 'buildbot@chromium.org'
_PASSWORD_PATH = '/home/chrome-bot/.status_password'
class PasswordFileDoesNotExist(Exception):
"""Raised when password file does not exist."""
class InvalidTreeStatus(Exception):
"""Raised when user wants to set an invalid tree status."""
def _GetStatus(status_url):
"""Polls |status_url| and returns the retrieved tree status.
This function gets a JSON response from |status_url|, and returns the
value associated with the 'general_state' key, if one exists and the
http request was successful.
Returns:
The tree status, as a string, if it was successfully retrieved. Otherwise
None.
"""
try:
# Check for successful response code.
response = urllib.urlopen(status_url)
if response.getcode() == 200:
data = json.load(response)
if data.has_key('general_state'):
return data['general_state']
# We remain robust against IOError's.
except IOError as e:
logging.error('Could not reach %s: %r', status_url, e)
def WaitForTreeStatus(status_url=None, period=1, timeout=1, throttled_ok=False):
"""Wait for tree status to be open (or throttled, if |throttled_ok|).
Args:
status_url: The status url to check i.e.
'https://status.appspot.com/current?format=json'
period: How often to poll for status updates.
timeout: How long to wait until a tree status is discovered.
throttled_ok: is TREE_THROTTLED an acceptable status?
Returns:
The most recent tree status, either constants.TREE_OPEN or
constants.TREE_THROTTLED (if |throttled_ok|)
Raises:
timeout_util.TimeoutError if timeout expired before tree reached
acceptable status.
"""
if not status_url:
status_url = CROS_TREE_STATUS_JSON_URL
acceptable_states = set([constants.TREE_OPEN])
verb = 'open'
if throttled_ok:
acceptable_states.add(constants.TREE_THROTTLED)
verb = 'not be closed'
timeout = max(timeout, 1)
def _LogMessage(minutes_left):
logging.info('Waiting for the tree to %s (%d minutes left)...', verb,
minutes_left)
def _get_status():
return _GetStatus(status_url)
return timeout_util.WaitForReturnValue(
acceptable_states, _get_status, timeout=timeout,
period=period, side_effect_func=_LogMessage)
def IsTreeOpen(status_url=None, period=1, timeout=1, throttled_ok=False):
"""Wait for tree status to be open (or throttled, if |throttled_ok|).
Args:
status_url: The status url to check i.e.
'https://status.appspot.com/current?format=json'
period: How often to poll for status updates.
timeout: How long to wait until a tree status is discovered.
throttled_ok: Does TREE_THROTTLED count as open?
Returns:
True if the tree is open (or throttled, if |throttled_ok|). False if
timeout expired before tree reached acceptable status.
"""
if not status_url:
status_url = CROS_TREE_STATUS_JSON_URL
try:
WaitForTreeStatus(status_url=status_url, period=period, timeout=timeout,
throttled_ok=throttled_ok)
except timeout_util.TimeoutError:
return False
return True
def _GetPassword():
"""Returns the password for updating tree status."""
if not os.path.exists(_PASSWORD_PATH):
raise PasswordFileDoesNotExist(
'Unable to retrieve password. %s does not exist',
_PASSWORD_PATH)
return osutils.ReadFile(_PASSWORD_PATH).strip()
def _UpdateTreeStatus(status_url, message):
"""Updates the tree status to |message|.
Args:
status_url: The tree status URL.
message: The tree status text to post .
"""
password = _GetPassword()
params = urllib.urlencode({
'message': message,
'username': _USER_NAME,
'password': password,
})
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
req = urllib2.Request(status_url, data=params, headers=headers)
try:
urllib2.urlopen(req)
except (urllib2.URLError, httplib.HTTPException, socket.error) as e:
logging.error('Unable to update tree status: %s', e)
raise e
else:
logging.info('Updated tree status to %s', message)
def UpdateTreeStatus(status, message, status_url=None):
"""Updates the tree status to |status| with additional |message|.
Args:
status: A status in constants.VALID_TREE_STATUSES.
message: A string to display as part of the tree status.
status_url: The tree status URL.
"""
if status_url is None:
status_url = CROS_TREE_STATUS_UPDATE_URL
if status not in constants.VALID_TREE_STATUSES:
raise InvalidTreeStatus('%s is not a valid tree status.' % status)
status_text = 'Tree is %(status)s (cbuildbot: %(message)s)' % {
'status': status,
'message': message}
_UpdateTreeStatus(status_url, status_text)