/** * Copyright 2015 StreamSets Inc. * * Licensed under the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. */ package com.streamsets.datacollector.updatechecker; import com.google.common.annotations.VisibleForTesting; import com.streamsets.datacollector.config.PipelineConfiguration; import com.streamsets.datacollector.config.StageConfiguration; import com.streamsets.datacollector.execution.PipelineState; import com.streamsets.datacollector.execution.PipelineStatus; import com.streamsets.datacollector.execution.Runner; import com.streamsets.datacollector.json.ObjectMapperFactory; import com.streamsets.datacollector.main.DataCollectorBuildInfo; import com.streamsets.datacollector.main.RuntimeInfo; import com.streamsets.datacollector.store.PipelineStoreException; import com.streamsets.datacollector.util.Configuration; import com.streamsets.pipeline.api.impl.Utils; import org.apache.commons.codec.binary.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.security.MessageDigest; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; public class UpdateChecker implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(UpdateChecker.class); public static final String URL_KEY = "streamsets.updatecheck.url"; public static final String URL_DEFAULT = "https://streamsets.com/rest/v1/updatecheck"; static final String APPLICATION_JSON_MIME = "application/json"; private final RuntimeInfo runtimeInfo; private final Runner runner; private URL url = null; private volatile Map updateInfo; private final PipelineConfiguration pipelineConf; public UpdateChecker(RuntimeInfo runtimeInfo, Configuration configuration, PipelineConfiguration pipelineConf, Runner runner) { this.pipelineConf = pipelineConf; this.runtimeInfo = runtimeInfo; this.runner = runner; String url = configuration.get(URL_KEY, URL_DEFAULT); try { this.url = new URL(url); } catch (Exception ex) { LOG.trace("Invalid update check URL '{}': {}", url, ex.toString(), ex); } } URL getUrl() { return url; } static String getSha256(String id) { try { MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(id.getBytes("UTF-8")); return Base64.encodeBase64String(md.digest()); } catch (Exception ex) { return "<UNKNOWN>"; } } @SuppressWarnings("unchecked") @VisibleForTesting Map getUploadInfo() { Map uploadInfo = null; if (pipelineConf != null) { List stages = new ArrayList(); // Stats aggregator target stage Map stage = new LinkedHashMap(); if(pipelineConf.getStatsAggregatorStage() != null) { stage.put("name", pipelineConf.getStatsAggregatorStage().getStageName()); stage.put("version", pipelineConf.getStatsAggregatorStage().getStageVersion()); stage.put("library", pipelineConf.getStatsAggregatorStage().getLibrary()); stages.add(stage); } // error stage stage = new LinkedHashMap(); stage.put("name", pipelineConf.getErrorStage().getStageName()); stage.put("version", pipelineConf.getErrorStage().getStageVersion()); stage.put("library", pipelineConf.getErrorStage().getLibrary()); stages.add(stage); // pipeline stages for (StageConfiguration stageConf : pipelineConf.getStages()) { stage = new LinkedHashMap(); stage.put("name", stageConf.getStageName()); stage.put("version", stageConf.getStageVersion()); stage.put("library", stageConf.getLibrary()); stages.add(stage); } uploadInfo = new LinkedHashMap(); uploadInfo.put("sdc.sha256", getSha256(runner.getToken())); uploadInfo.put("sdc.buildInfo", new DataCollectorBuildInfo()); uploadInfo.put("sdc.stages", stages); } return uploadInfo; } @Override public void run() { updateInfo = null; PipelineState ps; try { ps = runner.getState(); } catch (PipelineStoreException e) { LOG.warn(Utils.format("Cannot get pipeline state: '{}'", e.toString()), e); return; } if (ps.getStatus() == PipelineStatus.RUNNING) { if (url != null) { Map uploadInfo = getUploadInfo(); if (uploadInfo != null) { HttpURLConnection conn = null; try { conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(2000); conn.setReadTimeout(2000); conn.setDoOutput(true); conn.setDoInput(true); conn.setRequestProperty("content-type", APPLICATION_JSON_MIME); try(OutputStream outputStream = conn.getOutputStream()) { ObjectMapperFactory.getOneLine().writeValue(outputStream, uploadInfo); } if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { String responseContentType = conn.getHeaderField("content-type"); if (APPLICATION_JSON_MIME.equals(responseContentType)) { try(InputStream inputStream = conn.getInputStream()) { updateInfo = ObjectMapperFactory.get().readValue(inputStream, Map.class); } } else { LOG.trace("Got invalid content-type '{}' from from update-check server", responseContentType); } } else { LOG.trace("Got '{} : {}' from update-check server", conn.getResponseCode(), conn.getResponseMessage()); } } catch (Exception ex) { LOG.trace("Could not do an update check: {}", ex.toString(), ex); } finally { if (conn != null) { conn.disconnect(); } } } } } } public Map getUpdateInfo() { return updateInfo; } }