/** * 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. */ package org.apache.aurora.scheduler.events; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.time.Instant; import com.google.common.eventbus.Subscribe; import com.google.inject.Inject; import org.apache.aurora.scheduler.events.PubsubEvent.EventSubscriber; import org.apache.aurora.scheduler.events.PubsubEvent.TaskStateChange; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Watches TaskStateChanges and send events to configured endpoint. */ public class Webhook implements EventSubscriber { private static final Logger LOG = LoggerFactory.getLogger(Webhook.class); private final WebhookInfo webhookInfo; private final CloseableHttpClient httpClient; @Inject Webhook(CloseableHttpClient httpClient, WebhookInfo webhookInfo) { this.webhookInfo = webhookInfo; this.httpClient = httpClient; LOG.info("Webhook enabled with info" + this.webhookInfo); } private HttpPost createPostRequest(TaskStateChange stateChange) throws UnsupportedEncodingException { String eventJson = stateChange.toJson(); HttpPost post = new HttpPost(); post.setURI(webhookInfo.getTargetURI()); post.setHeader("Timestamp", Long.toString(Instant.now().toEpochMilli())); post.setEntity(new StringEntity(eventJson)); webhookInfo.getHeaders().entrySet().forEach( e -> post.setHeader(e.getKey(), e.getValue())); return post; } /** * Watches all TaskStateChanges and send them best effort to a configured endpoint. * <p> * This is used to expose an external event bus. * * @param stateChange State change notification. */ @Subscribe public void taskChangedState(TaskStateChange stateChange) { LOG.debug("Got an event: {}", stateChange); // Old state is not present because a scheduler just failed over. In that case we do not want to // resend the entire state. if (stateChange.getOldState().isPresent()) { try { HttpPost post = createPostRequest(stateChange); // Using try-with-resources on closeable and following // https://hc.apache.org/httpcomponents-client-4.5.x/quickstart.html to make sure stream is // closed after we get back a response to not leak http connections. try (CloseableHttpResponse httpResponse = httpClient.execute(post)) { HttpEntity entity = httpResponse.getEntity(); EntityUtils.consumeQuietly(entity); } catch (IOException exp) { LOG.error("Error sending a Webhook event", exp); } } catch (UnsupportedEncodingException exp) { LOG.error("HttpPost exception when creating an HTTP Post request", exp); } } } }