/*
* Copyright (c) 2012 Intellectual Reserve, 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.
*
*/
package cf.component;
import cf.nats.CfNats;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import cf.nats.Publication;
import cf.nats.PublicationHandler;
import cf.nats.message.ComponentAnnounce;
import cf.nats.message.ComponentDiscover;
import org.apache.commons.codec.binary.Base64;
import cf.component.http.JsonTextResponseRequestHandler;
import cf.component.http.RequestException;
import cf.component.http.SimpleHttpServer;
import cf.component.util.Common;
import cf.component.util.ProcessUtils;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Mike Heath
*/
public class VcapComponent {
private final CfNats nats;
private final String type;
private final long startTime = System.currentTimeMillis();
private final String uuid = Common.generateUniqueId();
private final String username = Common.generateCredential();
private final String password = Common.generateCredential();
private final List<VarzUpdater> varzUpdaters;
private final ObjectMapper mapper = new ObjectMapper();
public VcapComponent(CfNats nats, SimpleHttpServer httpServer, String type, List<VarzUpdater> varzUpdaters) {
this.nats = nats;
this.type = type;
this.varzUpdaters = varzUpdaters;
nats.subscribe(ComponentDiscover.class, new PublicationHandler<ComponentDiscover, ComponentAnnounce>() {
@Override
public void onMessage(Publication<ComponentDiscover, ComponentAnnounce> publication) {
publication.reply(buildComponentAnnounce());
}
});
nats.publish(buildComponentAnnounce());
httpServer.addHandler(Pattern.compile("/healthz"), new AuthenticatedJsonTextResponseRequestHandler() {
@Override
public String handleAuthenticatedRequest(HttpRequest request, Matcher uriMatcher, ByteBuf body) throws RequestException {
if (!request.getMethod().equals(HttpMethod.GET)) {
throw new RequestException(HttpResponseStatus.METHOD_NOT_ALLOWED);
}
return "ok\n";
}
});
httpServer.addHandler(Pattern.compile("/varz"), new AuthenticatedJsonTextResponseRequestHandler() {
@Override
protected String handleAuthenticatedRequest(HttpRequest request, Matcher uriMatcher, ByteBuf body) throws RequestException {
if (!request.getMethod().equals(HttpMethod.GET)) {
throw new RequestException(HttpResponseStatus.METHOD_NOT_ALLOWED);
}
final Varz varz = buildVarz();
try {
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(varz);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}
protected Varz buildVarz() {
ProcessUtils.ProcessStats processStats;
try {
processStats = ProcessUtils.getProcessStats();
} catch (IOException e) {
processStats = null;
}
Varz varz = new Varz(
type,
0,
uuid,
getLocalAddress(),
Arrays.asList(username, password),
formatStartTime(),
formatUptime(),
Runtime.getRuntime().availableProcessors(),
(processStats == null) ? 0l : processStats.residentSetSize,
(processStats == null) ? 0f : processStats.cpuUtilization
);
if (varzUpdaters != null) {
for (VarzUpdater updater : varzUpdaters) {
varz = updater.update(varz);
}
}
return varz;
}
protected ComponentAnnounce buildComponentAnnounce() {
return new ComponentAnnounce(
type,
0, // What is the index?
uuid,
getLocalAddress(),
Arrays.asList(username, password),
formatStartTime(),
formatUptime());
}
protected String getLocalAddress() {
// Use the address Nats is using to increase the likelihood that the host that we advertise can be routed to from other hosts.
// TODO Instead of getting the address from NATS, use the NATS host address to open a socket and use the local address
//final InetSocketAddress localAddress = (InetSocketAddress) nats.getNats().getConnectionStatus().getLocalAddress();
//return localAddress.getHostString();
return null;
}
private String formatStartTime() {
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date(startTime));
}
private String formatUptime() {
long delta = System.currentTimeMillis() - startTime;
delta /= 1000; // Drop the milliseconds
final long days = delta / TimeUnit.DAYS.toSeconds(1);
delta -= TimeUnit.DAYS.toSeconds(days);
final long hours = delta / TimeUnit.HOURS.toSeconds(1);
delta -= TimeUnit.HOURS.toSeconds(hours);
final long minutes = delta / TimeUnit.MINUTES.toSeconds(1);
delta -= TimeUnit.MINUTES.toSeconds(1);
final long seconds = delta;
return String.format("%dd:%dh:%dm:%ds", days, hours, minutes, seconds);
}
private abstract class AuthenticatedJsonTextResponseRequestHandler extends JsonTextResponseRequestHandler {
@Override
public String handle(HttpRequest request, Matcher uriMatcher, ByteBuf body) throws RequestException {
final String encodedAuthorization = request.headers().get(HttpHeaders.Names.AUTHORIZATION);
if (encodedAuthorization == null) {
throw new RequestException(HttpResponseStatus.UNAUTHORIZED);
}
final String authorization = new String(Base64.decodeBase64(encodedAuthorization));
final String[] credentials = authorization.split(":");
if (credentials.length != 2) {
throw new RequestException(HttpResponseStatus.UNAUTHORIZED);
}
if (!username.equals(credentials[0]) && !password.equals(password)) {
throw new RequestException(HttpResponseStatus.UNAUTHORIZED);
}
return handleAuthenticatedRequest(request, uriMatcher, body);
}
protected abstract String handleAuthenticatedRequest(HttpRequest request, Matcher uriMatcher, ByteBuf body) throws RequestException;
}
}