branch_utility.py revision 7d4cd473f85ac64c3747c96c277f9e506a0d2246
1# Copyright (c) 2012 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
5import json
6import logging
7import operator
8
9from appengine_url_fetcher import AppEngineUrlFetcher
10import url_constants
11
12class ChannelInfo(object):
13  def __init__(self, channel, branch, version):
14    self.channel = channel
15    self.branch = branch
16    self.version = version
17
18class BranchUtility(object):
19  def __init__(self, fetch_url, history_url, fetcher, object_store_creator):
20    self._fetcher = fetcher
21    # BranchUtility is obviously cross-channel, so set the channel to None.
22    self._branch_object_store = object_store_creator.Create(BranchUtility,
23                                                            category='branch',
24                                                            channel=None)
25    self._version_object_store = object_store_creator.Create(BranchUtility,
26                                                             category='version',
27                                                             channel=None)
28    self._fetch_result = self._fetcher.FetchAsync(fetch_url)
29    self._history_result = self._fetcher.FetchAsync(history_url)
30
31  @staticmethod
32  def GetAllChannelNames():
33    return ('stable', 'beta', 'dev', 'trunk')
34
35  @staticmethod
36  def NewestChannel(channels):
37    for channel in reversed(BranchUtility.GetAllChannelNames()):
38      if channel in channels:
39        return channel
40
41  @staticmethod
42  def Create(object_store_creator):
43    return BranchUtility(url_constants.OMAHA_PROXY_URL,
44                         url_constants.OMAHA_DEV_HISTORY,
45                         AppEngineUrlFetcher(),
46                         object_store_creator)
47
48  @staticmethod
49  def SplitChannelNameFromPath(path):
50    '''Splits the channel name out of |path|, returning the tuple
51    (channel_name, real_path). If the channel cannot be determined then returns
52    (None, path).
53    '''
54    if '/' in path:
55      first, second = path.split('/', 1)
56    else:
57      first, second = (path, '')
58    if first in BranchUtility.GetAllChannelNames():
59      return (first, second)
60    return (None, path)
61
62  def GetAllBranchNumbers(self):
63    return ((channel, self.GetChannelInfo(channel).branch)
64            for channel in BranchUtility.GetAllChannelNames())
65
66  def GetAllVersionNumbers(self):
67    return (self.GetChannelInfo(channel).version
68            for channel in BranchUtility.GetAllChannelNames())
69
70  def GetAllChannelInfo(self):
71    return (self.GetChannelInfo(channel)
72            for channel in BranchUtility.GetAllChannelNames())
73
74
75  def GetChannelInfo(self, channel):
76    return ChannelInfo(channel,
77                       self._ExtractFromVersionJson(channel, 'branch'),
78                       self._ExtractFromVersionJson(channel, 'version'))
79
80  def _ExtractFromVersionJson(self, channel_name, data_type):
81    '''Returns the branch or version number for a channel name.
82    '''
83    if channel_name == 'trunk':
84      return 'trunk'
85
86    if data_type == 'branch':
87      object_store = self._branch_object_store
88    elif data_type == 'version':
89      object_store = self._version_object_store
90
91    data = object_store.Get(channel_name).Get()
92    if data is not None:
93      return data
94
95    try:
96      version_json = json.loads(self._fetch_result.Get().content)
97    except Exception as e:
98      # This can happen if omahaproxy is misbehaving, which we've seen before.
99      # Quick hack fix: just serve from trunk until it's fixed.
100      logging.error('Failed to fetch or parse branch from omahaproxy: %s! '
101                    'Falling back to "trunk".' % e)
102      return 'trunk'
103
104    numbers = {}
105    for entry in version_json:
106      if entry['os'] not in ['win', 'linux', 'mac', 'cros']:
107        continue
108      for version in entry['versions']:
109        if version['channel'] != channel_name:
110          continue
111        if data_type == 'branch':
112          number = version['version'].split('.')[2]
113        elif data_type == 'version':
114          number = version['version'].split('.')[0]
115        if number not in numbers:
116          numbers[number] = 0
117        else:
118          numbers[number] += 1
119
120    sorted_numbers = sorted(numbers.iteritems(),
121                            None,
122                            operator.itemgetter(1),
123                            True)
124    object_store.Set(channel_name, int(sorted_numbers[0][0]))
125    return int(sorted_numbers[0][0])
126
127  def GetBranchForVersion(self, version):
128    '''Returns the most recent branch for a given chrome version number using
129    data stored on omahaproxy (see url_constants).
130    '''
131    if version == 'trunk':
132      return 'trunk'
133
134    branch = self._branch_object_store.Get(version).Get()
135    if branch is not None:
136      return branch
137
138    version_json = json.loads(self._history_result.Get().content)
139    for entry in version_json['events']:
140      # Here, entry['title'] looks like: 'title - version#.#.branch#.#'
141      version_title = entry['title'].split(' - ')[1].split('.')
142      if version_title[0] == str(version):
143        self._branch_object_store.Set(str(version), version_title[2])
144        return int(version_title[2])
145
146    raise ValueError(
147        'The branch for %s could not be found.' % version)
148
149  def GetLatestVersionNumber(self):
150    '''Returns the most recent version number found using data stored on
151    omahaproxy.
152    '''
153    latest_version = self._version_object_store.Get('latest').Get()
154    if latest_version is not None:
155      return latest_version
156
157    version_json = json.loads(self._history_result.Get().content)
158    latest_version = 0
159    for entry in version_json['events']:
160      version_title = entry['title'].split(' - ')[1].split('.')
161      version = int(version_title[0])
162      if version > latest_version:
163        latest_version = version
164
165    self._version_object_store.Set('latest', latest_version)
166    return latest_version
167