| OLD | NEW |
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | 1 # Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import json | 5 import json |
| 6 import logging | 6 import logging |
| 7 import os | 7 import os |
| 8 import re | 8 import re |
| 9 import shutil | 9 import shutil |
| 10 import tempfile | 10 import tempfile |
| 11 | 11 |
| 12 from telemetry import page as page_module | 12 from telemetry import page as page_module |
| 13 from telemetry.util import cloud_storage | 13 from telemetry.util import cloud_storage |
| 14 | 14 |
| 15 | 15 |
| 16 def AssertValidCloudStorageBucket(bucket): | 16 def AssertValidCloudStorageBucket(bucket): |
| 17 is_valid = bucket in (None, | 17 is_valid = bucket in (None, |
| 18 cloud_storage.PUBLIC_BUCKET, | 18 cloud_storage.PUBLIC_BUCKET, |
| 19 cloud_storage.PARTNER_BUCKET, | 19 cloud_storage.PARTNER_BUCKET, |
| 20 cloud_storage.INTERNAL_BUCKET) | 20 cloud_storage.INTERNAL_BUCKET) |
| 21 if not is_valid: | 21 if not is_valid: |
| 22 raise ValueError("Cloud storage privacy bucket %s is invalid" % bucket) | 22 raise ValueError("Cloud storage privacy bucket %s is invalid" % bucket) |
| 23 | 23 |
| 24 | 24 |
| 25 # TODO(chrishenry): Rename this (and module) to wpr_archive_info.WprArchiveInfo | 25 class WprArchiveInfo(object): |
| 26 # and move to telemetry.user_story or telemetry.wpr or telemetry.core. | |
| 27 class PageSetArchiveInfo(object): | |
| 28 def __init__(self, file_path, data, bucket, ignore_archive=False): | 26 def __init__(self, file_path, data, bucket, ignore_archive=False): |
| 29 AssertValidCloudStorageBucket(bucket) | 27 AssertValidCloudStorageBucket(bucket) |
| 30 self._file_path = file_path | 28 self._file_path = file_path |
| 31 self._base_dir = os.path.dirname(file_path) | 29 self._base_dir = os.path.dirname(file_path) |
| 32 self._bucket = bucket | 30 self._bucket = bucket |
| 33 | 31 |
| 34 # Ensure directory exists. | 32 # Ensure directory exists. |
| 35 if not os.path.exists(self._base_dir): | 33 if not os.path.exists(self._base_dir): |
| 36 os.makedirs(self._base_dir) | 34 os.makedirs(self._base_dir) |
| 37 | 35 |
| 38 # TODO(aiolos): We should take this out of init if we reduce the number of | 36 # TODO(aiolos): We should take this out of init if we reduce the number of |
| 39 # supported code paths/configs using archive_info when switching over to | 37 # supported code paths/configs using archive_info when switching over to |
| 40 # user_stories. | 38 # user_stories. |
| 41 # Download all .wpr files. | 39 # Download all .wpr files. |
| 42 if not ignore_archive: | 40 if not ignore_archive: |
| 43 if not self._bucket: | 41 if not self._bucket: |
| 44 logging.warning('page_set in %s has no bucket specified, and cannot be' | 42 logging.warning('User story set in %s has no bucket specified, and ' |
| 45 'downloaded from cloud_storage.', file_path) | 43 'cannot be downloaded from cloud_storage.', file_path) |
| 46 else: | 44 else: |
| 47 for archive_path in data['archives']: | 45 for archive_path in data['archives']: |
| 48 archive_path = self._WprFileNameToPath(archive_path) | 46 archive_path = self._WprFileNameToPath(archive_path) |
| 49 try: | 47 try: |
| 50 cloud_storage.GetIfChanged(archive_path, bucket) | 48 cloud_storage.GetIfChanged(archive_path, bucket) |
| 51 except (cloud_storage.CredentialsError, | 49 except (cloud_storage.CredentialsError, |
| 52 cloud_storage.PermissionError): | 50 cloud_storage.PermissionError): |
| 53 if os.path.exists(archive_path): | 51 if os.path.exists(archive_path): |
| 54 # If the archive exists, assume the user recorded their own and | 52 # If the archive exists, assume the user recorded their own and |
| 55 # simply warn. | 53 # simply warn. |
| 56 logging.warning('Need credentials to update WPR archive: %s', | 54 logging.warning('Need credentials to update WPR archive: %s', |
| 57 archive_path) | 55 archive_path) |
| 58 else: | 56 else: |
| 59 logging.error("You either aren't authenticated or don't have " | 57 logging.error("You either aren't authenticated or don't have " |
| 60 "permission to use the archives for this page set." | 58 "permission to use the archives for this page set." |
| 61 "\nYou may need to run gsutil config") | 59 "\nYou may need to run gsutil config") |
| 62 raise | 60 raise |
| 63 | 61 |
| 64 # Map from the relative path (as it appears in the metadata file) of the | 62 # Map from the relative path (as it appears in the metadata file) of the |
| 65 # .wpr file to a list of page names it supports. | 63 # .wpr file to a list of user story names it supports. |
| 66 self._wpr_file_to_page_names = data['archives'] | 64 self._wpr_file_to_user_story_names = data['archives'] |
| 67 | 65 |
| 68 # Map from the page name to a relative path (as it appears in the metadata | 66 # Map from the user_story name to a relative path (as it appears |
| 69 # file) of the .wpr file. | 67 # in the metadata file) of the .wpr file. |
| 70 self._page_name_to_wpr_file = dict() | 68 self._user_story_name_to_wpr_file = dict() |
| 71 # Find out the wpr file names for each page. | 69 # Find out the wpr file names for each user_story. |
| 72 for wpr_file in data['archives']: | 70 for wpr_file in data['archives']: |
| 73 page_names = data['archives'][wpr_file] | 71 user_story_names = data['archives'][wpr_file] |
| 74 for page_name in page_names: | 72 for user_story_name in user_story_names: |
| 75 self._page_name_to_wpr_file[page_name] = wpr_file | 73 self._user_story_name_to_wpr_file[user_story_name] = wpr_file |
| 76 self.temp_target_wpr_file_path = None | 74 self.temp_target_wpr_file_path = None |
| 77 | 75 |
| 78 @classmethod | 76 @classmethod |
| 79 def FromFile(cls, file_path, bucket, ignore_archive=False): | 77 def FromFile(cls, file_path, bucket, ignore_archive=False): |
| 80 if os.path.exists(file_path): | 78 if os.path.exists(file_path): |
| 81 with open(file_path, 'r') as f: | 79 with open(file_path, 'r') as f: |
| 82 data = json.load(f) | 80 data = json.load(f) |
| 83 return cls(file_path, data, bucket, ignore_archive=ignore_archive) | 81 return cls(file_path, data, bucket, ignore_archive=ignore_archive) |
| 84 return cls(file_path, {'archives': {}}, bucket, | 82 return cls(file_path, {'archives': {}}, bucket, |
| 85 ignore_archive=ignore_archive) | 83 ignore_archive=ignore_archive) |
| 86 | 84 |
| 87 def WprFilePathForUserStory(self, story): | 85 def WprFilePathForUserStory(self, story): |
| 88 if self.temp_target_wpr_file_path: | 86 if self.temp_target_wpr_file_path: |
| 89 return self.temp_target_wpr_file_path | 87 return self.temp_target_wpr_file_path |
| 90 wpr_file = self._page_name_to_wpr_file.get(story.display_name, None) | 88 wpr_file = self._user_story_name_to_wpr_file.get(story.display_name, None) |
| 91 if wpr_file is None and isinstance(story, page_module.Page): | 89 if wpr_file is None and isinstance(story, page_module.Page): |
| 92 # Some old page sets always use the URL to identify a page rather than the | 90 # Some old pages always use the URL to identify a page rather than the |
| 93 # display_name, so try to look for that. | 91 # display_name, so try to look for that. |
| 94 wpr_file = self._page_name_to_wpr_file.get(story.url, None) | 92 wpr_file = self._user_story_name_to_wpr_file.get(story.url, None) |
| 95 if wpr_file: | 93 if wpr_file: |
| 96 return self._WprFileNameToPath(wpr_file) | 94 return self._WprFileNameToPath(wpr_file) |
| 97 return None | 95 return None |
| 98 | 96 |
| 99 def AddNewTemporaryRecording(self, temp_wpr_file_path=None): | 97 def AddNewTemporaryRecording(self, temp_wpr_file_path=None): |
| 100 if temp_wpr_file_path is None: | 98 if temp_wpr_file_path is None: |
| 101 temp_wpr_file_handle, temp_wpr_file_path = tempfile.mkstemp() | 99 temp_wpr_file_handle, temp_wpr_file_path = tempfile.mkstemp() |
| 102 os.close(temp_wpr_file_handle) | 100 os.close(temp_wpr_file_handle) |
| 103 self.temp_target_wpr_file_path = temp_wpr_file_path | 101 self.temp_target_wpr_file_path = temp_wpr_file_path |
| 104 | 102 |
| 105 def AddRecordedPages(self, pages, upload_to_cloud_storage=False): | 103 def AddRecordedUserStories(self, user_stories, upload_to_cloud_storage=False): |
| 106 if not pages: | 104 if not user_stories: |
| 107 os.remove(self.temp_target_wpr_file_path) | 105 os.remove(self.temp_target_wpr_file_path) |
| 108 return | 106 return |
| 109 | 107 |
| 110 (target_wpr_file, target_wpr_file_path) = self._NextWprFileName() | 108 (target_wpr_file, target_wpr_file_path) = self._NextWprFileName() |
| 111 for page in pages: | 109 for user_story in user_stories: |
| 112 self._SetWprFileForPage(page.display_name, target_wpr_file) | 110 self._SetWprFileForUserStory(user_story.display_name, target_wpr_file) |
| 113 shutil.move(self.temp_target_wpr_file_path, target_wpr_file_path) | 111 shutil.move(self.temp_target_wpr_file_path, target_wpr_file_path) |
| 114 | 112 |
| 115 # Update the hash file. | 113 # Update the hash file. |
| 116 with open(target_wpr_file_path + '.sha1', 'wb') as f: | 114 with open(target_wpr_file_path + '.sha1', 'wb') as f: |
| 117 f.write(cloud_storage.CalculateHash(target_wpr_file_path)) | 115 f.write(cloud_storage.CalculateHash(target_wpr_file_path)) |
| 118 f.flush() | 116 f.flush() |
| 119 | 117 |
| 120 self._WriteToFile() | 118 self._WriteToFile() |
| 121 self._DeleteAbandonedWprFiles() | 119 self._DeleteAbandonedWprFiles() |
| 122 | 120 |
| 123 # Upload to cloud storage | 121 # Upload to cloud storage |
| 124 if upload_to_cloud_storage: | 122 if upload_to_cloud_storage: |
| 125 if not self._bucket: | 123 if not self._bucket: |
| 126 logging.warning('PageSet must have bucket specified to upload pages to' | 124 logging.warning('UserStorySet must have bucket specified to upload ' |
| 127 ' cloud storage.') | 125 'user stories to cloud storage.') |
| 128 return | 126 return |
| 129 try: | 127 try: |
| 130 cloud_storage.Insert(self._bucket, target_wpr_file, | 128 cloud_storage.Insert(self._bucket, target_wpr_file, |
| 131 target_wpr_file_path) | 129 target_wpr_file_path) |
| 132 except cloud_storage.CloudStorageError, e: | 130 except cloud_storage.CloudStorageError, e: |
| 133 logging.warning('Failed to upload wpr file %s to cloud storage. ' | 131 logging.warning('Failed to upload wpr file %s to cloud storage. ' |
| 134 'Error:%s' % target_wpr_file_path, e) | 132 'Error:%s' % target_wpr_file_path, e) |
| 135 | 133 |
| 136 def _DeleteAbandonedWprFiles(self): | 134 def _DeleteAbandonedWprFiles(self): |
| 137 # Update the metadata so that the abandoned wpr files don't have empty page | 135 # Update the metadata so that the abandoned wpr files don't have |
| 138 # name arrays. | 136 # empty user story name arrays. |
| 139 abandoned_wpr_files = self._AbandonedWprFiles() | 137 abandoned_wpr_files = self._AbandonedWprFiles() |
| 140 for wpr_file in abandoned_wpr_files: | 138 for wpr_file in abandoned_wpr_files: |
| 141 del self._wpr_file_to_page_names[wpr_file] | 139 del self._wpr_file_to_user_story_names[wpr_file] |
| 142 # Don't fail if we're unable to delete some of the files. | 140 # Don't fail if we're unable to delete some of the files. |
| 143 wpr_file_path = self._WprFileNameToPath(wpr_file) | 141 wpr_file_path = self._WprFileNameToPath(wpr_file) |
| 144 try: | 142 try: |
| 145 os.remove(wpr_file_path) | 143 os.remove(wpr_file_path) |
| 146 except Exception: | 144 except Exception: |
| 147 logging.warning('Failed to delete file: %s' % wpr_file_path) | 145 logging.warning('Failed to delete file: %s' % wpr_file_path) |
| 148 | 146 |
| 149 def _AbandonedWprFiles(self): | 147 def _AbandonedWprFiles(self): |
| 150 abandoned_wpr_files = [] | 148 abandoned_wpr_files = [] |
| 151 for wpr_file, page_names in self._wpr_file_to_page_names.iteritems(): | 149 for wpr_file, user_story_names in ( |
| 152 if not page_names: | 150 self._wpr_file_to_user_story_names.iteritems()): |
| 151 if not user_story_names: |
| 153 abandoned_wpr_files.append(wpr_file) | 152 abandoned_wpr_files.append(wpr_file) |
| 154 return abandoned_wpr_files | 153 return abandoned_wpr_files |
| 155 | 154 |
| 156 def _WriteToFile(self): | 155 def _WriteToFile(self): |
| 157 """Writes the metadata into the file passed as constructor parameter.""" | 156 """Writes the metadata into the file passed as constructor parameter.""" |
| 158 metadata = dict() | 157 metadata = dict() |
| 159 metadata['description'] = ( | 158 metadata['description'] = ( |
| 160 'Describes the Web Page Replay archives for a page set. Don\'t edit by ' | 159 'Describes the Web Page Replay archives for a user story set. ' |
| 161 'hand! Use record_wpr for updating.') | 160 'Don\'t edit by hand! Use record_wpr for updating.') |
| 162 metadata['archives'] = self._wpr_file_to_page_names.copy() | 161 metadata['archives'] = self._wpr_file_to_user_story_names.copy() |
| 163 # Don't write data for abandoned archives. | 162 # Don't write data for abandoned archives. |
| 164 abandoned_wpr_files = self._AbandonedWprFiles() | 163 abandoned_wpr_files = self._AbandonedWprFiles() |
| 165 for wpr_file in abandoned_wpr_files: | 164 for wpr_file in abandoned_wpr_files: |
| 166 del metadata['archives'][wpr_file] | 165 del metadata['archives'][wpr_file] |
| 167 | 166 |
| 168 with open(self._file_path, 'w') as f: | 167 with open(self._file_path, 'w') as f: |
| 169 json.dump(metadata, f, indent=4) | 168 json.dump(metadata, f, indent=4) |
| 170 f.flush() | 169 f.flush() |
| 171 | 170 |
| 172 def _WprFileNameToPath(self, wpr_file): | 171 def _WprFileNameToPath(self, wpr_file): |
| 173 return os.path.abspath(os.path.join(self._base_dir, wpr_file)) | 172 return os.path.abspath(os.path.join(self._base_dir, wpr_file)) |
| 174 | 173 |
| 175 def _NextWprFileName(self): | 174 def _NextWprFileName(self): |
| 176 """Creates a new file name for a wpr archive file.""" | 175 """Creates a new file name for a wpr archive file.""" |
| 177 # The names are of the format "some_thing_number.wpr". Read the numbers. | 176 # The names are of the format "some_thing_number.wpr". Read the numbers. |
| 178 highest_number = -1 | 177 highest_number = -1 |
| 179 base = None | 178 base = None |
| 180 for wpr_file in self._wpr_file_to_page_names: | 179 for wpr_file in self._wpr_file_to_user_story_names: |
| 181 match = re.match(r'(?P<BASE>.*)_(?P<NUMBER>[0-9]+)\.wpr', wpr_file) | 180 match = re.match(r'(?P<BASE>.*)_(?P<NUMBER>[0-9]+)\.wpr', wpr_file) |
| 182 if not match: | 181 if not match: |
| 183 raise Exception('Illegal wpr file name ' + wpr_file) | 182 raise Exception('Illegal wpr file name ' + wpr_file) |
| 184 highest_number = max(int(match.groupdict()['NUMBER']), highest_number) | 183 highest_number = max(int(match.groupdict()['NUMBER']), highest_number) |
| 185 if base and match.groupdict()['BASE'] != base: | 184 if base and match.groupdict()['BASE'] != base: |
| 186 raise Exception('Illegal wpr file name ' + wpr_file + | 185 raise Exception('Illegal wpr file name ' + wpr_file + |
| 187 ', doesn\'t begin with ' + base) | 186 ', doesn\'t begin with ' + base) |
| 188 base = match.groupdict()['BASE'] | 187 base = match.groupdict()['BASE'] |
| 189 if not base: | 188 if not base: |
| 190 # If we're creating a completely new info file, use the base name of the | 189 # If we're creating a completely new info file, use the base name of the |
| 191 # page set file. | 190 # user story set file. |
| 192 base = os.path.splitext(os.path.basename(self._file_path))[0] | 191 base = os.path.splitext(os.path.basename(self._file_path))[0] |
| 193 new_filename = '%s_%03d.wpr' % (base, highest_number + 1) | 192 new_filename = '%s_%03d.wpr' % (base, highest_number + 1) |
| 194 return new_filename, self._WprFileNameToPath(new_filename) | 193 return new_filename, self._WprFileNameToPath(new_filename) |
| 195 | 194 |
| 196 def _SetWprFileForPage(self, page_name, wpr_file): | 195 def _SetWprFileForUserStory(self, user_story_name, wpr_file): |
| 197 """For modifying the metadata when we're going to record a new archive.""" | 196 """For modifying the metadata when we're going to record a new archive.""" |
| 198 old_wpr_file = self._page_name_to_wpr_file.get(page_name, None) | 197 old_wpr_file = self._user_story_name_to_wpr_file.get(user_story_name, None) |
| 199 if old_wpr_file: | 198 if old_wpr_file: |
| 200 self._wpr_file_to_page_names[old_wpr_file].remove(page_name) | 199 self._wpr_file_to_user_story_names[old_wpr_file].remove(user_story_name) |
| 201 self._page_name_to_wpr_file[page_name] = wpr_file | 200 self._user_story_name_to_wpr_file[user_story_name] = wpr_file |
| 202 if wpr_file not in self._wpr_file_to_page_names: | 201 if wpr_file not in self._wpr_file_to_user_story_names: |
| 203 self._wpr_file_to_page_names[wpr_file] = [] | 202 self._wpr_file_to_user_story_names[wpr_file] = [] |
| 204 self._wpr_file_to_page_names[wpr_file].append(page_name) | 203 self._wpr_file_to_user_story_names[wpr_file].append(user_story_name) |
| OLD | NEW |