未验证 提交 722ff407 编写于 作者: D Daniel Beck 提交者: GitHub

[JEP-214] Telemetry with initial escape hatch implementation (#3604)

上级 07748327
/*
* The MIT License
*
* Copyright (c) 2018, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.telemetry;
import hudson.Extension;
import hudson.model.Describable;
import hudson.model.Descriptor;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import java.util.UUID;
/**
* This class stores a UUID identifying this instance for telemetry reporting to allow deduplication or merging of submitted records.
*
* We're not using anything derived from instance identity so we cannot connect an instance's public appearance with its submissions.
*
* This really only uses Descriptor/Describable to get a Saveable implementation for free.
*/
@Extension
@Restricted(NoExternalUse.class)
public class Correlator extends Descriptor<Correlator> implements Describable<Correlator> {
private String correlationId;
public Correlator() {
super(Correlator.class);
load();
if (correlationId == null) {
correlationId = UUID.randomUUID().toString();
save();
}
}
public String getCorrelationId() {
return correlationId;
}
@Override
public Descriptor<Correlator> getDescriptor() {
return this;
}
}
\ No newline at end of file
/*
* The MIT License
*
* Copyright (c) 2018, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.telemetry;
import com.google.common.annotations.VisibleForTesting;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.ProxyConfiguration;
import hudson.model.AsyncPeriodicWork;
import hudson.model.TaskListener;
import hudson.model.UsageStatistics;
import jenkins.model.Jenkins;
import jenkins.util.SystemProperties;
import net.sf.json.JSONObject;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Extension point for collecting JEP-214 telemetry.
*
* @see <a href="https://github.com/jenkinsci/jep/tree/master/jep/214">JEP-214</a>
*
* @since TODO
*/
public abstract class Telemetry implements ExtensionPoint {
// https://webhook.site is a nice stand-in for this during development; just needs to end in ? to submit the ID as query parameter
@Restricted(NoExternalUse.class)
@VisibleForTesting
static String ENDPOINT = SystemProperties.getString(Telemetry.class.getName() + ".endpoint", "https://uplink.jenkins.io/events");
private static final Logger LOGGER = Logger.getLogger(Telemetry.class.getName());
/**
* ID of this collector, typically a basic alphanumeric string (and _- characters).
*
* Good IDs are globally unique and human readable (i.e. no UUIDs).
*
* For a periodically updated list of all public implementations, see https://jenkins.io/doc/developer/extensions/jenkins-core/#telemetry
*
* @return ID of the collector, never null or empty
*/
@Nonnull
public abstract String getId();
/**
* User friendly display name for this telemetry collector, ideally localized.
*
* @return display name, never null or empty
*/
@Nonnull
public abstract String getDisplayName();
/**
* Start date for the collection.
* Will be checked in Jenkins to not collect outside the defined time span.
* This does not have to be precise enough for time zones to be a consideration.
*
* @return collection start date
*/
@Nonnull
public abstract LocalDate getStart();
/**
* End date for the collection.
* Will be checked in Jenkins to not collect outside the defined time span.
* This does not have to be precise enough for time zones to be a consideration.
*
* @return collection end date
*/
@Nonnull
public abstract LocalDate getEnd();
/**
* Returns the content to be sent to the telemetry service.
*
* This method is called periodically, once per content submission.
*
* @return
*/
@Nonnull
public abstract JSONObject createContent();
public static ExtensionList<Telemetry> all() {
return ExtensionList.lookup(Telemetry.class);
}
@Extension
public static class TelemetryReporter extends AsyncPeriodicWork {
public TelemetryReporter() {
super("telemetry collection");
}
@Override
public long getRecurrencePeriod() {
return TimeUnit.HOURS.toMillis(24);
}
@Override
protected void execute(TaskListener listener) throws IOException, InterruptedException {
if (UsageStatistics.DISABLED || !Jenkins.getInstance().isUsageStatisticsCollected()) {
LOGGER.info("Collection of anonymous usage statistics is disabled, skipping telemetry collection and submission");
return;
}
Telemetry.all().forEach(telemetry -> {
if (telemetry.getStart().isAfter(LocalDate.now())) {
LOGGER.config("Skipping telemetry for '" + telemetry.getId() + "' as it is configured to start later");
return;
}
if (telemetry.getEnd().isBefore(LocalDate.now())) {
LOGGER.config("Skipping telemetry for '" + telemetry.getId() + "' as it is configured to end in the past");
return;
}
JSONObject data = new JSONObject();
try {
data = telemetry.createContent();
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Failed to build telemetry content for: '" + telemetry.getId() + "'", e);
}
JSONObject wrappedData = new JSONObject();
wrappedData.put("type", telemetry.getId());
wrappedData.put("payload", data);
wrappedData.put("correlator", ExtensionList.lookupSingleton(Correlator.class).getCorrelationId());
try {
URL url = new URL(ENDPOINT);
URLConnection conn = ProxyConfiguration.open(url);
if (!(conn instanceof HttpURLConnection)) {
LOGGER.config("URL did not result in an HttpURLConnection: " + ENDPOINT);
return;
}
HttpURLConnection http = (HttpURLConnection) conn;
http.setRequestProperty("Content-Type", "application/json; charset=utf-8");
http.setDoOutput(true);
String body = wrappedData.toString();
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("Submitting JSON: " + body);
}
try (OutputStream out = http.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) {
writer.append(body);
}
LOGGER.config("Telemetry submission received response '" + http.getResponseCode() + " " + http.getResponseMessage() + "' for: " + telemetry.getId());
} catch (MalformedURLException e) {
LOGGER.config("Malformed endpoint URL: " + ENDPOINT + " for telemetry: " + telemetry.getId());
} catch (IOException e) {
// deliberately low visibility, as temporary infra problems aren't a big deal and we'd
// rather have some unsuccessful submissions than admins opting out to clean up logs
LOGGER.log(Level.CONFIG, "Failed to submit telemetry: " + telemetry.getId() + " to: " + ENDPOINT, e);
}
});
}
}
}
/*
* The MIT License
*
* Copyright (c) 2018, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.telemetry.impl;
import hudson.Extension;
import jenkins.model.Jenkins;
import jenkins.telemetry.Telemetry;
import jenkins.util.SystemProperties;
import net.sf.json.JSONObject;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import javax.annotation.Nonnull;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
import java.util.TreeMap;
/**
* Telemetry implementation gathering information about system properties.
*/
@Extension
@Restricted(NoExternalUse.class)
public class SecuritySystemProperties extends Telemetry {
@Nonnull
@Override
public String getId() {
return "security-system-properties";
}
@Nonnull
@Override
public LocalDate getStart() {
return LocalDate.of(2018, 9, 1);
}
@Nonnull
@Override
public LocalDate getEnd() {
return LocalDate.of(2018, 12, 1);
}
@Nonnull
@Override
public String getDisplayName() {
return "Use of Security-related Java system properties";
}
@Nonnull
@Override
public JSONObject createContent() {
Map<String, String> security = new TreeMap<>();
putBoolean(security, "hudson.ConsoleNote.INSECURE", false);
putBoolean(security, "hudson.model.ParametersAction.keepUndefinedParameters", false);
putBoolean(security, "hudson.model.User.allowNonExistentUserToLogin", false);
putBoolean(security, "hudson.model.User.allowUserCreationViaUrl", false);
putBoolean(security, "hudson.model.User.SECURITY_243_FULL_DEFENSE", true);
putBoolean(security, "hudson.remoting.URLDeserializationHelper.avoidUrlWrapping", false);
putBoolean(security, "jenkins.security.ClassFilterImpl.SUPPRESS_WHITELIST", false);
putBoolean(security, "jenkins.security.ClassFilterImpl.SUPPRESS_ALL", false);
putStringInfo(security, "hudson.model.ParametersAction.safeParameters");
putStringInfo(security, "hudson.model.DirectoryBrowserSupport.CSP");
putStringInfo(security, "hudson.security.HudsonPrivateSecurityRealm.ID_REGEX");
Map<String, Object> info = new TreeMap<>();
info.put("core", Jenkins.getVersion().toString());
info.put("clientDate", clientDateString());
info.put("properties", security);
return JSONObject.fromObject(info);
}
private static String clientDateString() {
TimeZone tz = TimeZone.getTimeZone("UTC");
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");
df.setTimeZone(tz); // strip timezone
return df.format(new Date());
}
private static void putBoolean(Map<String, String> propertiesMap, String systemProperty, boolean defaultValue) {
propertiesMap.put(systemProperty, Boolean.toString(SystemProperties.getBoolean(systemProperty, defaultValue)));
}
private static void putStringInfo(Map<String, String> propertiesMap, String systemProperty) {
String reportedValue = "null";
String value = SystemProperties.getString(systemProperty);
if (value != null) {
reportedValue = Integer.toString(value.length());
}
propertiesMap.put(systemProperty, reportedValue);
}
}
<div>
Knowing how Jenkins is used is a tremendous help in guiding the direction of the development, especially
for open-source projects where users are inherently hard to track down. When this option is enabled,
Jenkins periodically send information about your usage of Jenkins.
<p>
Specifically, it contains the following information:
<ul>
<li>Your Jenkins version
<li>For your master and each agent, OS type, and # of executors
<li>Plugins that are installed and their versions
<li>Number of jobs per each job type in your Jenkins
</ul>
<p>
The information doesn't contain anything that identifies you or allows us to contact you
(except information inherently revealed by HTTP, such as the IP address). Tabulated
data of these usage statistics submissions will be shared with the community.
</div>
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<div>
For any project, it's critical to know how the software is used, but tracking usage data is inherently difficult in open-source projects.
Anonymous usage statistics address this need.
When enabled, Jenkins periodically sends information to the Jenkins project.
The Jenkins project uses this information to set development priorities.
</div>
<h1>General usage statistics</h1>
<div>
<p>Jenkins reports the following general usage statistics:</p>
<ul>
<li>Your Jenkins version</li>
<li>For your master and each agent, the OS type and number of executors</li>
<li>Installed plugins and their versions</li>
<li>Number of items (like jobs) of each item type</li>
</ul>
<p>
This <b>does not</b> report any personally identifiable information. The only information reported by Jenkins is information inherently revealed by the HTTP protocol, such as the IP address.
These usage statistics are aggregated, updated monthly, and published to <a href="https://stats.jenkins.io">stats.jenkins.io</a>
</p>
</div>
<h1>Telemetry collection</h1>
<div>
<p>
In addition to the general usage statistics listed above, the Jenkins project collects telemetry data from specific trials to inform future development.
Each trial has a specific purpose and a defined end date, after which collection stops, independent of the installed versions of Jenkins or plugins.
Once a trial is complete, the trial results may be aggregated and shared with the developer community.
</p>
<p>
The following trials are currently set up on this instance:
</p>
<j:invokeStatic className="jenkins.telemetry.Telemetry" method="all" var="collectors"/>
<dl>
<j:forEach items="${collectors}" var="collector">
<dt>${collector.displayName}</dt>
<dd>
<st:include from="${collector}" optional="true" page="description.jelly"/>
<p>
Start date: ${collector.start}
End date: ${collector.end}
</p>
</dd>
</j:forEach>
</dl>
</div>
</j:jelly>
<div>
От голяма помощ е да знаем как Jenkins се ползва. Това може да определи
посоката на разработка, което иначе е трудно, защото няма как да се проследяват
потребителите на проект с отворен код. Като изберете тази опция Jenkins
периодично ще изпраща анонимни данни за използването.
<p>
Това е пълното описание на включената информация:
<ul>
<li>версията на jenkins;
<li>операционната система и броя изпълнявани изграждания от основния и подчинените компютри;
<li>списък с инсталираните приставки и версиите им;
<li>броят задачи за всеки вид задача в инсталацията на Jenkins
</ul>
<p>
Информацията не съдържа нищо, които да ви идентифицира или да позволява да се свържем
с вас (с изключение на информацията указана поради естеството на HTTP, като IP адреси).
Тези данни ще бъдат споделени с общността в табличен вид.
</div>
<div>
Comprendre comment Jenkins est utilisé est d'une grande aide pour guider les développements,
particulièrement pour les projets open-source où les utilisateurs sont typiquement difficiles
à connaitre. Quand cette option est activée, Jenkins envoie périodiquement des informations sur votre
utilisation de Jenkins.
<p>
Plus précisément, il envoie les informations suivantes:
<ul>
<li>Votre version de Jenkins
<li>Pour le Jenkins maître et pour chaque esclave, le type d'OS et le nombre d'exécuteurs
<li>Les plugins qui sont installés et leurs versions
<li>Le nombre de jobs pour chaque type de job dans votre installation de Jenkins
</ul>
<p>
Cette information ne contient rien qui vous identifie ou qui nous permette de vous contacter
(hors certaines informations intrinsèques à HTTP, comme votre adresse IP).
Les données compilées de ces statistiques d'utilisation seront partagées avec la communauté.
<p>
N'hésitez pas à lire <a href="http://jenkins.361315.n4.nabble.com/request-for-comments-tracking-usage-metrics-td375557.html">
la discussion sur ce mécanisme</a> et à contribuer aux débats.
</div>
<div>
Sapere come viene utilizzato Jenkins aiuta notevolmente a guidare la
direzione dello sviluppo, specialmente nel caso dei progetti open source
in cui è inerentemente difficile rintracciare gli utenti. Quando
quest'opzione è abilitata, Jenkins invierà periodicamente informazioni
sull'utilizzo di Jenkins.
<p>
Specificamente, tali informazioni conterranno quanto segue:
<ul>
<li>La versione di Jenkins
<li>Per il master e ogni agente, il tipo di sistema operativo e il numero di esecutori
<li>I plugin installati e le rispettive versioni
<li>Il numero di processi per ogni tipo di processo in Jenkins
</ul>
<p>
Le informazioni non conterranno nulla che possa identificare l'utente o
consentirci di contattare l'utente (ad eccezione delle informazioni rivelate
naturalmente dal protocollo HTTP, come l'indirizzo IP). I dati tabulati
relativi agli invii di queste statistiche di utilizzo saranno condivisi con
la comunità.
</div>
<div>
Jenkinsがどのように使用されているか把握することは、特にユーザーを特定できないオープンソースプロジェクトにとって、
開発の方向性を決定する上で非常に役に立ちます。このオプションを有効にすると、Jenkinsの利用状況を定期的に送信します。
<p>
送信する情報は次の情報を含んでいます。
<ul>
<li>Jenkinsのバージョン
<li>マスターと各スレーブの、OSとエグゼキューターの数
<li>インストールされているプラグインとそのバージョン
<li>ジョブのタイプ毎のジョブ数
</ul>
<p>
情報には、あなたを特定できる、もしくはあなたに連絡できるようなものは含みません(ただし、IPアドレスのようなHTTP関連の情報は除きます)。
送信された利用状況の一覧データは、コミュニティで共有されます。
<p>
<a href="http://jenkins.361315.n4.nabble.com/request-for-comments-tracking-usage-metrics-td375557.html">
コミュニティでのこの仕組みに関するディスカッション</a>を参考にしてください。ご意見はこちらにお願いします。
</div>
<div>
Saber como Jenkins é utilizado é uma tremenda ajuda guiando-nos no desenvolvimento, especificamente para
projetos de código livre onde os usuários são inerentemente difíceis de rastrear. Ao ativar esta opção,
Jenkins periodicamente enviará informações sobre o seu uso.
<p>
Especificamente, as informações enviadas serão:
<ul>
<li>A versão do seu Jenkins
<li>Para cada master e cada slave, o tipo de Sistema Operacional, e número de executores
<li>Os plugins instalados e suas versões
<li>Número de jobs por tipo de job no seu Jenkins
</ul>
<p>
Essas informaçãos não contem nada o identifique ou nos permita contatá-lo
(exceto pelas informações reveladas via HTTP, tal como Endereço IP).
Dados das estatísticas de uso submetidas são compartilhados com a comunidade.
</div>
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
For many security fixes, the Jenkins security team adds hidden options, sometimes called <em>escape hatches</em>, to disable the security fix.
This hedges against severe regressions not caught in testing, or unexpected legitimate use cases.
It is unknown to the team which of these options are actually in use, so this trial collects anonymous information about the values of (typically boolean) security-related Java system properties.
</j:jelly>
\ No newline at end of file
package jenkins.telemetry;
import hudson.ExtensionList;
import hudson.model.UnprotectedRootAction;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import hudson.security.csrf.CrumbExclusion;
import net.sf.json.JSONObject;
import org.apache.commons.io.IOUtils;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.LoggerRule;
import org.jvnet.hudson.test.TestExtension;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
public class TelemetryTest {
@Rule
public JenkinsRule j = new JenkinsRule();
@Rule
public LoggerRule logger = new LoggerRule().record(Telemetry.class, Level.ALL).capture(100);
private static int counter = 0;
@Test
public void testSubmission() throws Exception {
j.jenkins.setNoUsageStatistics(false); // tests usually don't submit this, but we need this
assertEquals("no requests received", 0, counter);
Telemetry.ENDPOINT = j.getURL().toString() + "uplink/events";
ExtensionList.lookupSingleton(Telemetry.TelemetryReporter.class).doRun();
do {
Thread.sleep(250);
} while (counter == 0); // this might end up being flaky due to 1 to many active telemetry trials
assertThat(logger.getMessages(), hasItem("Telemetry submission received response '200 OK' for: test-data"));
assertThat(logger.getMessages(), hasItem("Skipping telemetry for 'future' as it is configured to start later"));
assertThat(logger.getMessages(), hasItem("Skipping telemetry for 'past' as it is configured to end in the past"));
assertThat(types, hasItem("test-data"));
assertThat(types, not(hasItem("future")));
assertThat(types, not(hasItem("past")));
assertThat(correlators.size(), is(1));
assertTrue("at least one request received", counter > 0); // TestTelemetry plus whatever real impls exist
}
@TestExtension
public static class DisabledFutureTelemetry extends Telemetry {
@Nonnull
@Override
public String getId() {
return "future";
}
@Nonnull
@Override
public String getDisplayName() {
return "future";
}
@Nonnull
@Override
public LocalDate getStart() {
return LocalDate.now().plus(1, ChronoUnit.DAYS);
}
@Nonnull
@Override
public LocalDate getEnd() {
return LocalDate.MAX;
}
@Nonnull
@Override
public JSONObject createContent() {
return new JSONObject();
}
}
@TestExtension
public static class DisabledPastTelemetry extends Telemetry {
@Nonnull
@Override
public String getId() {
return "past";
}
@Nonnull
@Override
public String getDisplayName() {
return "past";
}
@Nonnull
@Override
public LocalDate getStart() {
return LocalDate.MIN;
}
@Nonnull
@Override
public LocalDate getEnd() {
return LocalDate.now().minus(1, ChronoUnit.DAYS);
}
@Nonnull
@Override
public JSONObject createContent() {
return new JSONObject();
}
}
@TestExtension
public static class TestTelemetry extends Telemetry {
@Nonnull
@Override
public String getId() {
return "test-data";
}
@Nonnull
@Override
public String getDisplayName() {
return "test-data";
}
@Nonnull
@Override
public LocalDate getStart() {
return LocalDate.MIN;
}
@Nonnull
@Override
public LocalDate getEnd() {
return LocalDate.MAX;
}
@Nonnull
@Override
public JSONObject createContent() {
return new JSONObject();
}
}
@TestExtension
public static class NoCrumb extends CrumbExclusion {
@Override
public boolean process(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String pathInfo = request.getPathInfo();
if (pathInfo != null && pathInfo.startsWith("/uplink")) {
chain.doFilter(request, response);
return true;
}
return false;
}
}
private static Set<String> correlators = new HashSet<>();
private static Set<String> types = new HashSet<>();
@TestExtension
public static class TelemetryReceiver implements UnprotectedRootAction {
public void doEvents(StaplerRequest request, StaplerResponse response) throws IOException {
StringWriter sw = new StringWriter();
IOUtils.copy(request.getInputStream(), sw, StandardCharsets.UTF_8);
JSONObject json = JSONObject.fromObject(sw.toString());
correlators.add(json.getString("correlator"));
types.add(json.getString("type"));
counter++;
}
@CheckForNull
@Override
public String getIconFileName() {
return null;
}
@CheckForNull
@Override
public String getDisplayName() {
return null;
}
@CheckForNull
@Override
public String getUrlName() {
return "uplink";
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册