1# Copyright 2013-2019 The Distro Tracker Developers 

2# See the COPYRIGHT file at the top-level directory of this distribution and 

3# at https://deb.li/DTAuthors 

4# 

5# This file is part of Distro Tracker. It is subject to the license terms 

6# in the LICENSE file found in the top-level directory of this 

7# distribution and at https://deb.li/DTLicense. No part of Distro Tracker, 

8# including this file, may be copied, modified, propagated, or distributed 

9# except according to the terms contained in the LICENSE file. 

10""" 

11The Distro-Tracker-specific tasks for :mod:`distro_tracker.debci_status` app. 

12""" 

13 

14import collections 

15import datetime 

16import json 

17import logging 

18import os.path 

19 

20from django.conf import settings 

21from django.db import transaction 

22 

23from distro_tracker.core.models import ( 

24 ActionItem, 

25 ActionItemType, 

26 PackageData, 

27 Repository, 

28 SourcePackageName 

29) 

30from distro_tracker.core.tasks import BaseTask 

31from distro_tracker.core.tasks.mixins import PackageTagging 

32from distro_tracker.core.tasks.schedulers import IntervalScheduler 

33from distro_tracker.core.utils.http import get_resource_text 

34 

35logger = logging.getLogger(__name__) 

36 

37 

38class UpdateDebciStatusTask(BaseTask): 

39 """ 

40 Updates packages' debci status. 

41 """ 

42 

43 class Scheduler(IntervalScheduler): 

44 interval = 3600 

45 

46 ACTION_ITEM_TYPE_NAME = 'debci-failed-tests' 

47 ITEM_DESCRIPTION = ( 

48 '<a href="{base_url}">Debci</a> reports ' + 

49 '<a href="{debci_url}">failed tests</a> ' 

50 ) 

51 ITEM_FULL_DESCRIPTION_TEMPLATE = 'debci_status/debci-action-item.html' 

52 

53 def initialize(self, *args, **kwargs): 

54 super().initialize(*args, **kwargs) 

55 self.debci_action_item_type = ActionItemType.objects.create_or_update( 

56 type_name=self.ACTION_ITEM_TYPE_NAME, 

57 full_description_template=self.ITEM_FULL_DESCRIPTION_TEMPLATE) 

58 

59 @property 

60 def base_url(self): 

61 return getattr(settings, 'DISTRO_TRACKER_DEBCI_URL') 

62 

63 def get_debci_status(self, repo): 

64 url = self.base_url + '/data/status/' + \ 

65 repo + '/amd64/packages.json' 

66 

67 content = get_resource_text(url, ignore_http_error=404) 

68 if content is None: 

69 logger.warning("Tried to fetch non-existing debci URL: %s", url) 

70 return [] 

71 

72 debci_status = json.loads(content) 

73 return debci_status 

74 

75 def __get_debci_dir(self, package_name): 

76 if package_name[:3] == 'lib': 

77 debci_dir = package_name[:4] 

78 else: 

79 debci_dir = package_name[:1] 

80 

81 return os.path.join(debci_dir, package_name) 

82 

83 def __get_debci_url_main(self, package_name): 

84 return os.path.join(self.base_url, 'packages', 

85 self.__get_debci_dir(package_name)) 

86 

87 def __get_debci_url_logfile(self, package_name, repo): 

88 return os.path.join(self.base_url, 

89 'data/packages/' + repo + '/amd64', 

90 self.__get_debci_dir(package_name), 

91 'latest-autopkgtest/log.gz') 

92 

93 def update_action_item(self, package, debci_statuses): 

94 """ 

95 Updates the :class:`ActionItem` for the given package based on the 

96 :class:`DebciStatus <distro_tracker.debci_status.DebciStatus` 

97 If the package has test failures an :class:`ActionItem` is created. 

98 """ 

99 debci_action_item = package.get_action_item_for_type( 

100 self.debci_action_item_type.type_name) 

101 if 'fail' not in [s['result']['status'] for s in debci_statuses]: 

102 if debci_action_item: 

103 debci_action_item.delete() 

104 return 

105 

106 if debci_action_item is None: 

107 debci_action_item = ActionItem( 

108 package=package, 

109 item_type=self.debci_action_item_type, 

110 severity=ActionItem.SEVERITY_HIGH) 

111 

112 package_name = package.name 

113 url = self.__get_debci_url_main(package_name) 

114 

115 debci_action_item.short_description = self.ITEM_DESCRIPTION.format( 

116 debci_url=url, 

117 base_url=self.base_url) 

118 

119 debci_action_item.extra_data = [] 

120 for debci_status in debci_statuses: 

121 repo_codename = debci_status['repository'] 

122 

123 log = self.__get_debci_url_logfile(package_name, repo_codename) 

124 

125 result = debci_status['result'] 

126 try: 

127 duration = str(datetime.timedelta( 

128 seconds=int(result['duration_seconds']))) 

129 except (TypeError, KeyError): 

130 duration = 'an unknown time' 

131 

132 debci_action_item.extra_data.append({ 

133 'duration': duration, 

134 'previous_status': result.get('previous_status', 'unknown'), 

135 'status': result['status'], 

136 'date': result['date'], 

137 'base_url': self.base_url, 

138 'url': url, 

139 'log': log, 

140 'repository': repo_codename 

141 }) 

142 

143 debci_action_item.save() 

144 

145 def execute_main(self): 

146 all_debci_status = collections.defaultdict(lambda: {}) 

147 

148 repos = getattr(settings, 'DISTRO_TRACKER_DEBCI_REPOSITORIES', None) 

149 if repos is None: 

150 repos = Repository.objects.all().values_list('suite', flat=True) 

151 

152 for repo_codename in repos: 

153 for status in self.get_debci_status(repo_codename): 

154 if status and 'package' in status: 

155 package = status['package'] 

156 all_debci_status[package][repo_codename] = status 

157 

158 # import pprint 

159 # pprint.pprint(all_debci_status) 

160 with transaction.atomic(): 

161 # Delete obsolete data 

162 PackageData.objects.filter(key='debci').delete() 

163 packages = [] 

164 infos = [] 

165 for package_name in all_debci_status: 

166 try: 

167 package = SourcePackageName.objects.get( 

168 name=package_name) 

169 packages.append(package) 

170 except SourcePackageName.DoesNotExist: 

171 continue 

172 

173 value = [] 

174 items = all_debci_status[package_name].items() 

175 for repo_codename, result in items: 

176 url = self.__get_debci_url_main(package_name) 

177 value.append({'result': result, 

178 'repository': repo_codename, 

179 'url': url}) 

180 

181 infos.append( 

182 PackageData( 

183 package=package, 

184 key='debci', 

185 value=value 

186 ) 

187 ) 

188 

189 self.update_action_item(package, value) 

190 

191 PackageData.objects.bulk_create(infos) 

192 ActionItem.objects.delete_obsolete_items( 

193 [self.debci_action_item_type], packages) 

194 

195 

196class TagPackagesWithDebciFailures(BaseTask, PackageTagging): 

197 """ 

198 Performs an update of 'debci-failures' tag for packages. 

199 """ 

200 

201 class Scheduler(IntervalScheduler): 

202 interval = 3600 

203 

204 TAG_NAME = 'tag:debci-failures' 

205 TAG_DISPLAY_NAME = 'debci failures' 

206 TAG_COLOR_TYPE = 'danger' 

207 TAG_DESCRIPTION = 'The package has test failures' 

208 TAG_TABLE_TITLE = 'Packages with test failures' 

209 

210 def packages_to_tag(self): 

211 try: 

212 action_type = ActionItemType.objects.get( 

213 type_name='debci-failed-tests') 

214 except ActionItemType.DoesNotExist: 

215 return [] 

216 

217 items = action_type.action_items.all().prefetch_related('package') 

218 return [item.package for item in items]