1# Copyright (C) 2010 Google Inc. All rights reserved.
2#
3# Redistribution and use in source and binary forms, with or without
4# modification, are permitted provided that the following conditions are
5# met:
6#
7#     * Redistributions of source code must retain the above copyright
8# notice, this list of conditions and the following disclaimer.
9#     * Redistributions in binary form must reproduce the above
10# copyright notice, this list of conditions and the following disclaimer
11# in the documentation and/or other materials provided with the
12# distribution.
13#     * Neither the name of Google Inc. nor the names of its
14# contributors may be used to endorse or promote products derived from
15# this software without specific prior written permission.
16#
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29import logging
30import urllib
31
32from google.appengine.api import users
33from google.appengine.ext import webapp
34from google.appengine.ext.webapp import template
35
36from model.jsonresults import JsonResults
37from model.testfile import TestFile
38
39PARAM_MASTER = "master"
40PARAM_BUILDER = "builder"
41PARAM_DIR = "dir"
42PARAM_FILE = "file"
43PARAM_NAME = "name"
44PARAM_KEY = "key"
45PARAM_TEST_TYPE = "testtype"
46PARAM_INCREMENTAL = "incremental"
47PARAM_TEST_LIST_JSON = "testlistjson"
48
49
50class DeleteFile(webapp.RequestHandler):
51    """Delete test file for a given builder and name from datastore."""
52
53    def get(self):
54        key = self.request.get(PARAM_KEY)
55        master = self.request.get(PARAM_MASTER)
56        builder = self.request.get(PARAM_BUILDER)
57        test_type = self.request.get(PARAM_TEST_TYPE)
58        name = self.request.get(PARAM_NAME)
59
60        logging.debug(
61            "Deleting File, master: %s, builder: %s, test_type: %s, name: %s, key: %s.",
62            master, builder, test_type, name, key)
63
64        TestFile.delete_file(key, master, builder, test_type, name, 100)
65
66        # Display file list after deleting the file.
67        self.redirect("/testfile?master=%s&builder=%s&testtype=%s&name=%s"
68            % (master, builder, test_type, name))
69
70
71class GetFile(webapp.RequestHandler):
72    """Get file content or list of files for given builder and name."""
73
74    def _get_file_list(self, master, builder, test_type, name):
75        """Get and display a list of files that matches builder and file name.
76
77        Args:
78            builder: builder name
79            test_type: type of the test
80            name: file name
81        """
82
83        files = TestFile.get_files(
84            master, builder, test_type, name, load_data=False, limit=100)
85        if not files:
86            logging.info("File not found, master: %s, builder: %s, test_type: %s, name: %s.",
87                         master, builder, test_type, name)
88            self.response.out.write("File not found")
89            return
90
91        template_values = {
92            "admin": users.is_current_user_admin(),
93            "master": master,
94            "builder": builder,
95            "test_type": test_type,
96            "name": name,
97            "files": files,
98        }
99        self.response.out.write(template.render("templates/showfilelist.html",
100                                                template_values))
101
102    def _get_file_content(self, master, builder, test_type, name):
103        """Return content of the file that matches builder and file name.
104
105        Args:
106            builder: builder name
107            test_type: type of the test
108            name: file name
109        """
110
111        files = TestFile.get_files(
112            master, builder, test_type, name, load_data=True, limit=1)
113        if not files:
114            logging.info("File not found, master %s, builder: %s, test_type: %s, name: %s.",
115                         master, builder, test_type, name)
116            return None
117
118        return files[0].data
119
120    def _get_test_list_json(self, master, builder, test_type):
121        """Return json file with test name list only, do not include test
122           results and other non-test-data .
123
124        Args:
125            builder: builder name.
126            test_type: type of test results.
127        """
128
129        json = self._get_file_content(master, builder, test_type, "results.json")
130        if not json:
131            return None
132
133        return JsonResults.get_test_list(builder, json)
134
135    def get(self):
136        master = self.request.get(PARAM_MASTER)
137        builder = self.request.get(PARAM_BUILDER)
138        test_type = self.request.get(PARAM_TEST_TYPE)
139        name = self.request.get(PARAM_NAME)
140        dir = self.request.get(PARAM_DIR)
141        test_list_json = self.request.get(PARAM_TEST_LIST_JSON)
142
143        logging.debug(
144            "Getting files, master %s, builder: %s, test_type: %s, name: %s.",
145            master, builder, test_type, name)
146
147        # If parameter "dir" is specified or there is no builder or filename
148        # specified in the request, return list of files, otherwise, return
149        # file content.
150        if dir or not builder or not name:
151            return self._get_file_list(master, builder, test_type, name)
152
153        if name == "results.json" and test_list_json:
154            json = self._get_test_list_json(master, builder, test_type)
155        else:
156            json = self._get_file_content(master, builder, test_type, name)
157
158        if json:
159            self.response.headers["Content-Type"] = "text/plain; charset=utf-8"
160            self.response.out.write(json)
161        else:
162            self.error(404)
163
164class Upload(webapp.RequestHandler):
165    """Upload test results file to datastore."""
166
167    def post(self):
168        file_params = self.request.POST.getall(PARAM_FILE)
169        if not file_params:
170            self.response.out.write("FAIL: missing upload file field.")
171            return
172
173        builder = self.request.get(PARAM_BUILDER)
174        if not builder:
175            self.response.out.write("FAIL: missing builder parameter.")
176            return
177
178        master = self.request.get(PARAM_MASTER)
179        test_type = self.request.get(PARAM_TEST_TYPE)
180        incremental = self.request.get(PARAM_INCREMENTAL)
181
182        logging.debug(
183            "Processing upload request, master: %s, builder: %s, test_type: %s.",
184            master, builder, test_type)
185
186        # There are two possible types of each file_params in the request:
187        # one file item or a list of file items.
188        # Normalize file_params to a file item list.
189        files = []
190        logging.debug("test: %s, type:%s", file_params, type(file_params))
191        for item in file_params:
192            if not isinstance(item, list) and not isinstance(item, tuple):
193                item = [item]
194            files.extend(item)
195
196        errors = []
197        for file in files:
198            filename = file.filename.lower()
199            if ((incremental and filename == "results.json") or
200                (filename == "incremental_results.json")):
201                # Merge incremental json results.
202                update_succeeded = JsonResults.update(master, builder, test_type, file.value)
203            else:
204                update_succeeded = TestFile.update(
205                    master, builder, test_type, file.filename, file.value)
206
207            if not update_succeeded:
208                errors.append(
209                    "Upload failed, master: %s, builder: %s, test_type: %s, name: %s." %
210                    (master, builder, test_type, file.filename))
211
212        if errors:
213            messages = "FAIL: " + "; ".join(errors)
214            logging.warning(messages)
215            self.response.set_status(500, messages)
216            self.response.out.write("FAIL")
217        else:
218            self.response.set_status(200)
219            self.response.out.write("OK")
220
221
222class UploadForm(webapp.RequestHandler):
223    """Show a form so user can upload a file."""
224
225    def get(self):
226        template_values = {
227            "upload_url": "/testfile/upload",
228        }
229        self.response.out.write(template.render("templates/uploadform.html",
230                                                template_values))
231