diff options
author | Young Gyu Park <younggyu@google.com> | 2018-02-27 12:32:50 +0900 |
---|---|---|
committer | Young Gyu Park <younggyu@google.com> | 2018-03-12 17:49:54 +0900 |
commit | 0af022a827becb32bb6132bd505efffd350de242 (patch) | |
tree | 92664185a61da856c180c36855ed970bdf115afc /src/main | |
parent | 6a63048585642a823cc7d0a339146e6cdd670277 (diff) | |
download | dashboard-0af022a827becb32bb6132bd505efffd350de242.tar.gz |
GCS zip log file viewer by unarchiving the file.
Test: Tested with browser on GAE(go/vts-web-staging/show_gcs_log)
Bug: 73900965
Change-Id: I65d2ea816a8327e333f2e818e2af19a0989e6b63
Diffstat (limited to 'src/main')
-rw-r--r-- | src/main/java/com/android/vts/servlet/BaseServlet.java | 2 | ||||
-rw-r--r-- | src/main/java/com/android/vts/servlet/ShowGcsLogServlet.java | 212 | ||||
-rw-r--r-- | src/main/webapp/WEB-INF/appengine-web.xml | 1 | ||||
-rw-r--r-- | src/main/webapp/WEB-INF/jsp/error_msg.jsp | 38 | ||||
-rw-r--r-- | src/main/webapp/WEB-INF/jsp/show_gcs_log.jsp | 73 |
5 files changed, 260 insertions, 66 deletions
diff --git a/src/main/java/com/android/vts/servlet/BaseServlet.java b/src/main/java/com/android/vts/servlet/BaseServlet.java index a20cd62..93652b4 100644 --- a/src/main/java/com/android/vts/servlet/BaseServlet.java +++ b/src/main/java/com/android/vts/servlet/BaseServlet.java @@ -36,6 +36,8 @@ import javax.servlet.http.HttpSession; public abstract class BaseServlet extends HttpServlet { protected final Logger logger = Logger.getLogger(getClass().getName()); + protected String ERROR_MESSAGE_JSP = "WEB-INF/jsp/error_msg.jsp"; + // Environment variables protected static final String GERRIT_URI = System.getProperty("GERRIT_URI"); protected static final String GERRIT_SCOPE = System.getProperty("GERRIT_SCOPE"); diff --git a/src/main/java/com/android/vts/servlet/ShowGcsLogServlet.java b/src/main/java/com/android/vts/servlet/ShowGcsLogServlet.java index 337494f..583803d 100644 --- a/src/main/java/com/android/vts/servlet/ShowGcsLogServlet.java +++ b/src/main/java/com/android/vts/servlet/ShowGcsLogServlet.java @@ -16,25 +16,33 @@ package com.android.vts.servlet; +import com.google.appengine.api.memcache.ErrorHandlers; +import com.google.appengine.api.memcache.MemcacheService; +import com.google.appengine.api.memcache.MemcacheServiceFactory; import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.cloud.storage.Blob; import com.google.cloud.storage.Bucket; import com.google.cloud.storage.Storage; import com.google.cloud.storage.Storage.BlobListOption; import com.google.cloud.storage.StorageOptions; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.logging.Level; +import com.google.gson.Gson; +import org.apache.commons.io.IOUtils; + import javax.servlet.RequestDispatcher; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.logging.Level; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; /** * A GCS log servlet read log zip file from Google Cloud Storage bucket and show the content in it @@ -45,36 +53,47 @@ public class ShowGcsLogServlet extends BaseServlet { private static final String GCS_LOG_JSP = "WEB-INF/jsp/show_gcs_log.jsp"; + /** Google Cloud Storage project ID */ private static final String GCS_PROJECT_ID = System.getProperty("GCS_PROJECT_ID"); + /** Google Cloud Storage project's key file to access the storage */ private static final String GCS_KEY_FILE = System.getProperty("GCS_KEY_FILE"); + /** Google Cloud Storage project's default bucket name for vtslab log files */ private static final String GCS_BUCKET_NAME = System.getProperty("GCS_BUCKET_NAME"); /** * This is the key file to access vtslab-gcs project. It will allow the dashboard to have a full - * controll of the bucket. + * control of the bucket. */ private InputStream keyFileInputStream; /** This is the instance of java google storage library */ private Storage storage; + /** This is the instance of App Engine memcache service java library */ + private MemcacheService syncCache = MemcacheServiceFactory.getMemcacheService(); + @Override public void init(ServletConfig cfg) throws ServletException { super.init(cfg); keyFileInputStream = this.getServletContext().getResourceAsStream("/WEB-INF/keys/" + GCS_KEY_FILE); - try { - storage = - StorageOptions.newBuilder() - .setProjectId(GCS_PROJECT_ID) - .setCredentials( - ServiceAccountCredentials.fromStream(keyFileInputStream)) - .build() - .getService(); - } catch (IOException e) { - logger.log(Level.SEVERE, "Error on creating storage instance!"); + if (keyFileInputStream == null) { + logger.log(Level.SEVERE, "Error GCS key file is not exiting. Check key file!"); + } else { + try { + storage = + StorageOptions.newBuilder() + .setProjectId(GCS_PROJECT_ID) + .setCredentials( + ServiceAccountCredentials.fromStream(keyFileInputStream)) + .build() + .getService(); + } catch (IOException e) { + logger.log(Level.SEVERE, "Error on creating storage instance!"); + } } + syncCache.setErrorHandler(ErrorHandlers.getConsistentLogAndContinue(Level.INFO)); } @Override @@ -90,60 +109,127 @@ public class ShowGcsLogServlet extends BaseServlet { @Override public void doGetHandler(HttpServletRequest request, HttpServletResponse response) throws IOException { + if (keyFileInputStream == null) { + request.setAttribute("error_title", "GCS Key file Error"); + request.setAttribute("error_message", "The GCS Key file is not existed!"); + RequestDispatcher dispatcher = request.getRequestDispatcher(ERROR_MESSAGE_JSP); + try { + dispatcher.forward(request, response); + } catch (ServletException e) { + logger.log(Level.SEVERE, "Servlet Excpetion caught : ", e); + } + } else { - String path = request.getParameter("path") == null ? "" : request.getParameter("path"); - Path pathInfo = Paths.get(path); - - Bucket vtsReportBucket = storage.get(GCS_BUCKET_NAME); + String action = + request.getParameter("action") == null + ? "read" + : request.getParameter("action"); + String path = request.getParameter("path") == null ? "/" : request.getParameter("path"); + String entry = + request.getParameter("entry") == null ? "" : request.getParameter("entry"); + Path pathInfo = Paths.get(path); + + Bucket vtsReportBucket = storage.get(GCS_BUCKET_NAME); + + List<String> dirList = new ArrayList<>(); + List<String> fileList = new ArrayList<>(); + List<String> entryList = new ArrayList<>(); + Map<String, Object> resultMap = new HashMap<>(); + String entryContent = ""; + + if (pathInfo.toString().endsWith(".zip")) { + + Blob blobFile = (Blob) this.syncCache.get(path.toString()); + if (blobFile == null) { + blobFile = vtsReportBucket.get(path); + this.syncCache.put(path.toString(), blobFile); + } - List<String> dirList = new ArrayList<>(); - List<String> fileList = new ArrayList<>(); - if (pathInfo.endsWith(".zip")) { - Blob blobFile = vtsReportBucket.get(path); - } else { + if (action.equalsIgnoreCase("read")) { + InputStream blobInputStream = new ByteArrayInputStream(blobFile.getContent()); + ZipInputStream zipInputStream = new ZipInputStream(blobInputStream); + + ZipEntry zipEntry; + while ((zipEntry = zipInputStream.getNextEntry()) != null) { + if (zipEntry.isDirectory()) { + + } else { + if (entry.length() > 0) { + System.out.println("param entry => " + entry); + if (zipEntry.getName().equals(entry)) { + System.out.println("matched !!!! " + zipEntry.getName()); + entryContent = + IOUtils.toString( + zipInputStream, StandardCharsets.UTF_8.name()); + } + } else { + entryList.add(zipEntry.getName()); + } + } + } + resultMap.put("entryList", entryList); + resultMap.put("entryContent", entryContent); + + String json = new Gson().toJson(resultMap); + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write(json); + } else { + response.setContentType("application/octet-stream"); + response.setContentLength(blobFile.getSize().intValue()); + response.setHeader( + "Content-Disposition", + "attachment; filename=\"" + pathInfo.getFileName() + "\""); - logger.log(Level.INFO, "path info => " + pathInfo); - logger.log(Level.INFO, "path name count => " + pathInfo.getNameCount()); + response.getOutputStream().write(blobFile.getContent()); + } - BlobListOption[] listOptions; - if (pathInfo.getNameCount() == 0) { - listOptions = new BlobListOption[] {BlobListOption.currentDirectory()}; } else { - if (pathInfo.getNameCount() <= 1) { - dirList.add("/"); + + logger.log(Level.INFO, "path info => " + pathInfo); + logger.log(Level.INFO, "path name count => " + pathInfo.getNameCount()); + + BlobListOption[] listOptions; + if (pathInfo.getNameCount() == 0) { + listOptions = new BlobListOption[] {BlobListOption.currentDirectory()}; } else { - dirList.add(pathInfo.getParent().toString()); + if (pathInfo.getNameCount() <= 1) { + dirList.add("/"); + } else { + dirList.add(pathInfo.getParent().toString()); + } + listOptions = + new BlobListOption[] { + BlobListOption.currentDirectory(), + BlobListOption.prefix(pathInfo.toString() + "/") + }; } - listOptions = - new BlobListOption[] { - BlobListOption.currentDirectory(), - BlobListOption.prefix(pathInfo.toString() + "/") - }; - } - Iterator<Blob> blobIterator = vtsReportBucket.list(listOptions).iterateAll(); - while (blobIterator.hasNext()) { - Blob blob = blobIterator.next(); - logger.log(Level.INFO, "blob name => " + blob); - if (blob.isDirectory()) { - logger.log(Level.INFO, "directory name => " + blob.getName()); - dirList.add(blob.getName()); - } else { - logger.log(Level.INFO, "file name => " + blob.getName()); - fileList.add(blob.getName()); + Iterator<Blob> blobIterator = vtsReportBucket.list(listOptions).iterateAll(); + while (blobIterator.hasNext()) { + Blob blob = blobIterator.next(); + logger.log(Level.INFO, "blob name => " + blob); + if (blob.isDirectory()) { + logger.log(Level.INFO, "directory name => " + blob.getName()); + dirList.add(blob.getName()); + } else { + logger.log(Level.INFO, "file name => " + blob.getName()); + fileList.add(blob.getName()); + } } - } - } - response.setStatus(HttpServletResponse.SC_OK); - request.setAttribute("dirList", dirList); - request.setAttribute("fileList", fileList); - request.setAttribute("path", path); - RequestDispatcher dispatcher = request.getRequestDispatcher(GCS_LOG_JSP); - try { - dispatcher.forward(request, response); - } catch (ServletException e) { - logger.log(Level.SEVERE, "Servlet Excpetion caught : ", e); + response.setStatus(HttpServletResponse.SC_OK); + request.setAttribute("entryList", entryList); + request.setAttribute("dirList", dirList); + request.setAttribute("fileList", fileList); + request.setAttribute("path", path); + RequestDispatcher dispatcher = request.getRequestDispatcher(GCS_LOG_JSP); + try { + dispatcher.forward(request, response); + } catch (ServletException e) { + logger.log(Level.SEVERE, "Servlet Excpetion caught : ", e); + } + } } } } diff --git a/src/main/webapp/WEB-INF/appengine-web.xml b/src/main/webapp/WEB-INF/appengine-web.xml index 4023fb1..fed561d 100644 --- a/src/main/webapp/WEB-INF/appengine-web.xml +++ b/src/main/webapp/WEB-INF/appengine-web.xml @@ -16,6 +16,7 @@ <threadsafe>true</threadsafe> <sessions-enabled>true</sessions-enabled> <runtime>java8</runtime> + <instance-class>F4_1G</instance-class> <system-properties> <property name="EMAIL_DOMAIN" value="${appengine.emailDomain}" /> diff --git a/src/main/webapp/WEB-INF/jsp/error_msg.jsp b/src/main/webapp/WEB-INF/jsp/error_msg.jsp new file mode 100644 index 0000000..13e6657 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/error_msg.jsp @@ -0,0 +1,38 @@ +<%-- + ~ Copyright (c) 2018 Google Inc. All Rights Reserved. + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); you + ~ may not use this file except in compliance with the License. You may + ~ obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + ~ implied. See the License for the specific language governing + ~ permissions and limitations under the License. + --%> +<%@ page contentType='text/html;charset=UTF-8' language='java' %> +<%@ taglib prefix='fn' uri='http://java.sun.com/jsp/jstl/functions' %> +<%@ taglib prefix='c' uri='http://java.sun.com/jsp/jstl/core'%> + +<html> +<link rel='stylesheet' href='/css/dashboard_main.css'> +<%@ include file='header.jsp' %> +<body> + +<div class='container wide'> + + <div class="card-panel"> + <span class="red-text text-darken-2"> + <h3> <c:out value="${error_title}"></c:out> </h3> + <br/> + <c:out value="${error_message}"></c:out> + </span> + </div> + +</div> +<%@ include file='footer.jsp' %> +</body> +</html> diff --git a/src/main/webapp/WEB-INF/jsp/show_gcs_log.jsp b/src/main/webapp/WEB-INF/jsp/show_gcs_log.jsp index 5c175ea..8bdf416 100644 --- a/src/main/webapp/WEB-INF/jsp/show_gcs_log.jsp +++ b/src/main/webapp/WEB-INF/jsp/show_gcs_log.jsp @@ -27,9 +27,42 @@ <script src='js/plan_runs.js'></script> <script src='js/search_header.js'></script> <script type='text/javascript'> - var search; $(document).ready(function() { - + var paraMap = {'path': '', 'entry': ''}; + $('.modal').modal({ + dismissible: true, // Modal can be dismissed by clicking outside of the modal + opacity: .99, // Opacity of modal background + inDuration: 300, // Transition in duration + outDuration: 200, // Transition out duration + startingTop: '4%', // Starting top style attribute + endingTop: '10%', // Ending top style attribute + ready: function(modal, trigger) { // Callback for Modal open. Modal and trigger parameters available. + if ($(trigger).attr("href") == "#logEntryListModal") { + $(modal).find('#modal-entry-list').find("li").remove(); + paraMap['path'] = trigger.text().trim(); + var fileName = paraMap['path'].substring(paraMap['path'].lastIndexOf('/')+1); // Getting filename + $(modal).find('#entry-list-modal-title').text(fileName); // Set file name for modal window + var url = "${requestScope['javax.servlet.forward.servlet_path']}?path=" + paraMap['path']; + $(modal).find('#downloadLink').prop('href', url + "&action=download"); + $.get( url, function(data) { + var entryList = $(modal).find('#modal-entry-list'); + $(data.entryList).each(function( index, element ) { + entryList.append("<li><a href='#logEntryViewModal'>" + element + "</a></li>"); + }); + }); + } else { + paraMap['entry'] = trigger.text().trim(); + $(modal).find('#entry-view-modal-title').text(paraMap['entry']); + var entryUrl = "${requestScope['javax.servlet.forward.servlet_path']}?path=" + paraMap['path'] + "&entry=" + paraMap['entry']; + $.get( entryUrl, function(data) { + $(modal).find('#entry-view-modal-content').text(data.entryContent); + }); + } + }, + complete: function() { + console.log("modal closed!"); + } // Callback for Modal close + }); }); </script> @@ -55,7 +88,7 @@ <h3>File List</h3> <c:forEach varStatus="fileLoop" var="fileName" items="${fileList}"> <p> - <a href="${requestScope['javax.servlet.forward.servlet_path']}?path=${fileName}"> + <a href="#logEntryListModal"> <c:out value="${fileName}"></c:out> </a> </p> @@ -64,6 +97,40 @@ </c:forEach> </div> </div> + <%@ include file="footer.jsp" %> + + <!-- Modal For Zip file entries --> + <div id="logEntryListModal" class="modal"> + <div class="modal-content"> + <h4 id="entry-list-modal-title" class="truncate"></h4> + <div id="entry-list-modal-content"> + <ul id="modal-entry-list"></ul> + </div> + </div> + <div class="modal-footer"> + <div class="row"> + <div class="col s3 offset-s6"> + <a href="#!" id="downloadLink" class="modal-action modal-close waves-effect waves-green btn">Download</a> + </div> + <div class="col s3"> + <a href="#!" class="modal-action modal-close waves-effect waves-green btn">Close</a> + </div> + </div> + </div> + </div> + + <!-- Modal For Zip file entry's content --> + <div id="logEntryViewModal" class="modal modal-fixed-footer"> + <div class="modal-content"> + <h4 id="entry-view-modal-title" class="truncate"></h4> + <div id="entry-view-modal-content"> + + </div> + </div> + <div class="modal-footer"> + <a href="#!" class="modal-action modal-close waves-effect waves-green btn">Close</a> + </div> + </div> </body> </html> |