/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package util.support; import java.io.*; import java.net.URI; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Properties; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import com.emc.storageos.model.tenant.TenantOrgRestRep; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import controllers.security.Security; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.CloseShieldOutputStream; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.UnhandledException; import org.apache.commons.lang.text.StrBuilder; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import play.Logger; import play.Play; import play.jobs.Job; import play.mvc.Http; import util.ConfigPropertyUtils; import util.MonitorUtils; import com.emc.storageos.db.client.model.uimodels.OrderStatus; import com.emc.storageos.db.client.util.OrderTextCreator; import com.emc.vipr.client.ViPRCatalogClient2; import com.emc.vipr.client.ViPRSystemClient; import com.emc.vipr.client.core.filters.DefaultResourceFilter; import com.emc.vipr.client.core.search.SearchBuilder; import com.emc.vipr.model.catalog.OrderRestRep; import com.emc.vipr.model.sys.healthmonitor.HealthRestRep; import com.emc.vipr.model.sys.healthmonitor.NodeHealth; import com.emc.vipr.model.sys.healthmonitor.StatsRestRep; import com.google.common.collect.Sets; import util.TenantUtils; public class SupportPackageCreator { private static final String TIMESTAMP = "ddMMyy-HHmm"; private static final String VIPR_LOG_DATE_FORMAT = "yyyy-MM-dd_HH:mm:ss"; private static final Integer LOG_MINTUES_PREVIOUSLY = 60; private static final Integer ORDER_EARLIEST_START_DATE = 30; public enum OrderTypes { NONE, ERROR, ALL } // Logging Info private List<String> logNames; private List<String> nodeIds; private String startTime = getDefaultStartTime(); private String endTime = ""; private String msgRegex = null; private Integer logSeverity = 5; // WARN private OrderTypes orderTypes = OrderTypes.NONE; private Http.Request request; private ViPRSystemClient client; private String tenantId; private ViPRCatalogClient2 catalogClient; private List<URI> tenantIds;//if is set, means need to get orders for these tenants public SupportPackageCreator(Http.Request request, ViPRSystemClient client, String tenantId, ViPRCatalogClient2 catalogClient) { this.request = request; this.client = Objects.requireNonNull(client); this.tenantId = tenantId; this.catalogClient = Objects.requireNonNull(catalogClient); } public void setEndTime(String endTime) { this.endTime = endTime; } public void setLogNames(List<String> logNames) { this.logNames = logNames; } public void setNodeIds(List<String> nodeIds) { this.nodeIds = nodeIds; } public void setLogSeverity(Integer logSeverity) { this.logSeverity = logSeverity; } public void setMsgRegex(String msgRegex) { this.msgRegex = msgRegex; } public void setStartTime(String startTime) { this.startTime = startTime; } public void setStartTimeWithRestriction(String startTime) { long restrictEarliestTimestamp = getTimestampOfDaysAgo(ORDER_EARLIEST_START_DATE); this.startTime = restrictEarliestTimestamp > Long.parseLong(startTime) ? String.valueOf(restrictEarliestTimestamp) : startTime; } public void setOrderTypes(OrderTypes orderTypes) { this.orderTypes = orderTypes; } public void setTenantIds(List<URI> tenantIds) { this.tenantIds = tenantIds; } private String getDefaultStartTime() { DateTime currentTimeInUTC = new DateTime(DateTimeZone.UTC); DateTime startTimeInUTC = currentTimeInUTC.minusMinutes(LOG_MINTUES_PREVIOUSLY); DateTimeFormatter fmt = DateTimeFormat.forPattern(VIPR_LOG_DATE_FORMAT); return fmt.print(startTimeInUTC); } private long getTimestampOfDaysAgo(int days) { DateTime currentTimeInUTC = new DateTime(DateTimeZone.UTC); return currentTimeInUTC.minusDays(days).getMillis(); } public static String formatTimestamp(Calendar cal) { final SimpleDateFormat TIME = new SimpleDateFormat(TIMESTAMP); return cal != null ? TIME.format(cal.getTime()) : "UNKNOWN"; } private ViPRSystemClient api() { return client; } private ViPRCatalogClient2 catalogApi() { return catalogClient; } private Properties getConfig() { Properties props = new Properties(); props.putAll(ConfigPropertyUtils.getPropertiesFromCoordinator()); return props; } private String getMonitorHealthXml() { HealthRestRep health = api().health().getHealth(); return marshall(health); } private String getMonitorStatsXml() { StatsRestRep stats = api().health().getStats(); return marshall(stats); } private String marshall(Object obj) { try { StringWriter writer = new StringWriter(); JAXBContext context = JAXBContext.newInstance(obj.getClass()); context.createMarshaller().marshal(obj, writer); return writer.toString(); } catch (JAXBException e) { throw new UnhandledException(e); } } private String getBrowserInfo() { StrBuilder sb = new StrBuilder(); if (request != null) { for (Map.Entry<String, Http.Header> header : request.headers.entrySet()) { sb.append(header.getKey()).append(": ").append(header.getValue().values).append("\n"); } } return sb.toString(); } private List<OrderRestRep> getOrders() { if ((orderTypes == OrderTypes.ALL) || (orderTypes == OrderTypes.ERROR)) { List<OrderRestRep> orders = Lists.newArrayList(); if (tenantIds != null) { for (URI tenantId : tenantIds) { SearchBuilder<OrderRestRep> search = catalogApi().orders().search().byTimeRange( this.startTime, this.endTime, tenantId); if (orderTypes == OrderTypes.ERROR) { search.filter(new FailedOrderFilter()); } List<OrderRestRep> tenantOrders = search.run(); Logger.info("Found %s Orders for tenantId %s", tenantOrders.size(), tenantId); orders.addAll(tenantOrders); } } else { SearchBuilder<OrderRestRep> search = catalogApi().orders().search().byTimeRange( this.startTime, this.endTime); if (orderTypes == OrderTypes.ERROR) { search.filter(new FailedOrderFilter()); } orders = search.run(); Logger.info("Found %s Orders", orders.size()); } return orders; } else { return Collections.emptyList(); } } private Map<String,String> getSelectedNodeIds() { Map<String,String> activeNodeIds = Maps.newTreeMap(); for (NodeHealth activeNode : MonitorUtils.getNodeHealth(api())) { if (!StringUtils.containsIgnoreCase(activeNode.getStatus(), "unavailable") || Play.mode.isDev()) { activeNodeIds.put(activeNode.getNodeId(),activeNode.getNodeName()); } } Map<String,String> selectedNodeIds = Maps.newTreeMap(); if ((nodeIds == null) || nodeIds.isEmpty()) { selectedNodeIds.putAll(activeNodeIds); } else { for (String node :nodeIds) { if (activeNodeIds.containsKey(node)) selectedNodeIds.put(node, activeNodeIds.get(node)); } } return selectedNodeIds; } public CreateSupportPackageJob createJob(OutputStream out) { return new CreateSupportPackageJob(out, this); } public void writeTo(OutputStream out) throws IOException { ZipOutputStream zip = new ZipOutputStream(out); try { writeConfig(zip); writeSystemInfo(zip); writeOrders(zip); writeLogs(zip); zip.flush(); } finally { zip.close(); } } private OutputStream nextEntry(ZipOutputStream zip, String path) throws IOException { Logger.debug("Adding entry: %s", path); ZipEntry entry = new ZipEntry(path); zip.putNextEntry(entry); return new CloseShieldOutputStream(zip); } private void addBinaryEntry(ZipOutputStream zip, String path, byte[] data) throws IOException { Logger.debug("Adding entry: %s", path); ZipEntry entry = new ZipEntry(path); entry.setSize(data.length); zip.putNextEntry(entry); zip.write(data); zip.closeEntry(); zip.flush(); } private void addStringEntry(ZipOutputStream zip, String path, String data) throws IOException { addBinaryEntry(zip, path, data.getBytes("UTF-8")); } private void writeConfig(ZipOutputStream zip) throws IOException { Properties config = getConfig(); config.store(nextEntry(zip, "info/config.properties"), ""); } private void writeSystemInfo(ZipOutputStream zip) throws IOException { addStringEntry(zip, "info/MonitorHealth.xml", getMonitorHealthXml()); addStringEntry(zip, "info/MonitorStats.xml", getMonitorStatsXml()); addStringEntry(zip, "info/BrowserInfo.txt", getBrowserInfo()); } private void writeOrders(ZipOutputStream zip) throws IOException { for (OrderRestRep order : getOrders()) { writeOrder(zip, order); } } private void writeOrder(ZipOutputStream zip, OrderRestRep order) throws IOException { String path = String.format("orders/%s", OrderTextCreator.genereateOrderFileName(order)); addStringEntry(zip, path, getOrderTextCreator(order).getText()); Logger.debug("Written Order " + order.getId() + " to archive"); } private OrderTextCreator getOrderTextCreator(OrderRestRep order) { ViPRCatalogClient2 client = catalogApi(); OrderTextCreator creator = new OrderTextCreator(); creator.setOrder(order); creator.setService(client.services().get(order.getCatalogService())); creator.setState(client.orders().getExecutionState(order.getId())); creator.setLogs(client.orders().getLogs(order.getId())); creator.setExeLogs(client.orders().getExecutionLogs(order.getId())); return creator; } private void writeLogs(ZipOutputStream zip) throws IOException { if (logNames != null) { // Ensure no duplicate log names Set<String> selectedLogNames = Sets.newLinkedHashSet(logNames); Map<String,String> selectedNodeIds = getSelectedNodeIds(); for (String nodeId : selectedNodeIds.keySet()) { String nodeName = selectedNodeIds.get(nodeId); for (String logName : selectedLogNames) { writeLog(zip, nodeId, nodeName, logName); } } } } private void writeLog(ZipOutputStream zip, String nodeId, String nodeName, String logName) throws IOException { Set<String> nodeIds = Collections.singleton(nodeId); Set<String> logNames = Collections.singleton(logName); OutputStream stream = null; InputStream in = api().logs().getAsText(nodeIds, null, logNames, logSeverity, startTime, endTime, msgRegex, null); long size = 1024*1024*300L; int partId = 2; long writeSize = 0L; String line = null; String path = String.format("logs/%s_%s_%s.log", logName, nodeId, nodeName); stream = nextEntry(zip, path); BufferedReader br = new BufferedReader(new InputStreamReader(in)); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(stream)); try { while ((line = br.readLine()) != null) { bw.write(line); bw.newLine(); writeSize = writeSize + line.length(); if (writeSize > size) { bw.close(); path = String.format("logs/%s_%s_%s_%d.log", logName, nodeId, nodeName,partId); stream = nextEntry(zip, path); bw = new BufferedWriter(new OutputStreamWriter(stream)); writeSize = 0L; partId++; } } } finally { br.close(); IOUtils.closeQuietly(bw); } } /** * Filter for failed orders. */ private static class FailedOrderFilter extends DefaultResourceFilter<OrderRestRep> { @Override public boolean accept(OrderRestRep item) { return StringUtils.equals(OrderStatus.ERROR.name(), item.getOrderStatus()); } } /** * Job that runs to generate a support package. * * @author jonnymiller */ public static class CreateSupportPackageJob extends Job { private OutputStream out; private SupportPackageCreator supportPackage; public CreateSupportPackageJob(OutputStream out, SupportPackageCreator supportPackage) { this.out = out; this.supportPackage = supportPackage; } @Override public void doJob() throws Exception { supportPackage.writeTo(out); } } }