package io.fathom.cloud.image.api.os.resources;
import io.fathom.cloud.Clock;
import io.fathom.cloud.CloudException;
import io.fathom.cloud.blobs.BlobData;
import io.fathom.cloud.image.ImageDataService;
import io.fathom.cloud.image.ImageServiceImpl;
import io.fathom.cloud.image.ImageServiceImpl.ImageImpl;
import io.fathom.cloud.image.api.os.model.ImageListResponse;
import io.fathom.cloud.image.api.os.model.WrappedImage;
import io.fathom.cloud.protobuf.CloudCommons.KeyValueData;
import io.fathom.cloud.protobuf.ImageModel.ImageData;
import io.fathom.cloud.protobuf.ImageModel.ImageLocation;
import io.fathom.cloud.services.ImageService;
import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Map;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fathomdb.utils.Hex;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.inject.persist.Transactional;
import com.google.protobuf.ByteString;
@Path("/openstack/images/v1/images")
@Transactional
public class ImagesV1Endpoint extends ImageResourceBase {
private static final Logger log = LoggerFactory.getLogger(ImagesV1Endpoint.class);
@Inject
ImageServiceImpl imageService;
@Inject
ImageDataService imageDataService;
private ImageData findImage(String id) throws CloudException {
ImageImpl image = imageService.findImage(getProject(), Long.valueOf(id));
if (image == null) {
return null;
}
return image.getData();
}
// @PUT
// @Path("{imageId}/file")
// public Response updateContent(File file) throws CloudException,
// IOException {
// ImageData image = findImage();
// if (image == null) {
// throw new WebApplicationException(Status.NOT_FOUND);
// }
//
// BlobData data = BlobData.build(file);
//
// String stored = imageDataService.storeImageFile(image, data);
//
// ImageLocation.Builder b = ImageLocation.newBuilder();
// b.setStored(stored);
//
// imageService.setImageLocation(image.getId(), data.size(),
// data.getHash(), b.build());
//
// return Response.status(Status.NO_CONTENT).build();
// }
//
// private ImageData findImage() throws CloudException {
// return imageService.findImage(Long.valueOf(imageId));
// }
private static final String IMAGE_META_PREFIX = "x-image-meta-";
@POST
@Consumes({ "application/octet-stream" })
public Response createImage(File file) throws CloudException, IOException {
if (file == null) {
throw new IllegalArgumentException("No content supplied");
}
ImageImpl image;
{
Map<String, String> metadata = extractHeaders();
if (metadata.containsKey("size")) {
long size = Long.valueOf(metadata.get(ImageService.METADATA_KEY_SIZE));
if (size != file.length()) {
throw new IllegalArgumentException();
}
}
metadata.put("size", "" + Long.valueOf(file.length()));
image = imageService.createImage(getProject().getId(), metadata);
}
{
BlobData data = BlobData.build(file);
image = imageService.uploadData(image, data);
}
WrappedImage result = new WrappedImage();
result.image = toModel(image.getData());
return Response.status(Status.CREATED).entity(result).type(MediaType.APPLICATION_JSON_TYPE).build();
}
private Map<String, String> extractHeaders() {
Map<String, String> metadata = Maps.newHashMap();
Enumeration<String> headerNames = httpRequest.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
// Header names are case-insensitive
String normalized = headerName.toLowerCase();
if (normalized.startsWith(IMAGE_META_PREFIX)) {
String key = headerName.substring(IMAGE_META_PREFIX.length());
String value = httpRequest.getHeader(headerName);
metadata.put(key, value);
}
}
return metadata;
}
@GET
@Path("detail")
@Produces({ JSON })
public ImageListResponse doImageDetailsGet() throws CloudException {
ImageListResponse response = new ImageListResponse();
response.images = Lists.newArrayList();
ImageFilter filter = new ImageFilter();
filter.name = httpRequest.getParameter("name");
for (ImageService.Image data : imageService.listImages(getProject(), filter)) {
response.images.add(toModel(data));
}
return response;
}
@DELETE
@Path("{id}")
public Response deleteImage(@PathParam("id") String id) throws CloudException, IOException {
ImageData image = findImage(id);
if (image == null) {
throw new WebApplicationException(Status.NOT_FOUND);
}
imageService.deleteImage(getProject(), image.getId());
return Response.status(Status.NO_CONTENT).build();
}
@PUT
@Path("{id}")
public Response updateImage(@PathParam("id") String id, File content) throws CloudException, IOException {
if (content != null && content.length() != 0) {
throw new UnsupportedOperationException();
}
ImageData image = findImage(id);
if (image == null) {
throw new WebApplicationException(Status.NOT_FOUND);
}
// TODO: Some
// -H 'x-image-meta-protected: True'
// -H 'x-glance-registry-purge-props: false'
Map<String, String> metadata = extractHeaders();
image = imageService.updateImage(getProject(), image.getId(), metadata);
// ResponseBuilder response = Response.ok();
// setHttpHeaders(image, response);
// return response.build();
WrappedImage result = new WrappedImage();
result.image = toModel(image);
return Response.status(Status.CREATED).entity(result).type(MediaType.APPLICATION_JSON_TYPE).build();
}
@HEAD
@Path("{id}")
public Response getImageInfo(@PathParam("id") String id) throws CloudException, IOException {
ImageData image = findImage(id);
if (image == null) {
throw new WebApplicationException(Status.NOT_FOUND);
}
ResponseBuilder response = Response.ok();
setHttpHeaders(image, response);
return response.build();
}
@GET
@Path("{id}")
public Response getImage(@PathParam("id") String id) throws CloudException, IOException {
ImageData image = findImage(id);
if (image == null) {
throw new WebApplicationException(Status.NOT_FOUND);
}
ResponseBuilder response;
ImageLocation imageLocation = image.getLocation();
if (imageLocation.hasStored()) {
String cookie = imageLocation.getStored();
BlobData blob = imageDataService.getImageFile(cookie);
response = Response.ok().entity(blob.asEntity());
ByteString md5 = blob.getHash();
response.header("Content-MD5", Hex.toHex(md5.toByteArray()));
} else {
response = Response.status(Status.NO_CONTENT);
}
setHttpHeaders(image, response);
return response.build();
}
private void setHttpHeaders(ImageData image, ResponseBuilder response) {
// TODO: We could fix this up to be less hacky, but this works for now
response.header("x-image-meta-id", "" + image.getId());
response.header("x-image-meta-uri", imageService.getUrl(httpRequest, image.getId()));
if (image.hasName()) {
response.header("x-image-meta-name", image.getName());
}
if (image.hasDiskFormat()) {
response.header("x-image-meta-disk_format", image.getDiskFormat());
}
if (image.hasContainerFormat()) {
response.header("x-image-meta-container_format", image.getContainerFormat());
}
if (image.hasImageSize()) {
response.header("x-image-meta-size", "" + image.getImageSize());
}
if (image.hasImageChecksum()) {
response.header("x-image-meta-checksum", Hex.toHex(image.getImageChecksum().toByteArray()));
}
if (image.hasCreatedAt()) {
response.header("x-image-meta-created_at", Clock.toDate(image.getCreatedAt()));
}
if (image.hasUpdatedAt()) {
response.header("x-image-meta-updated_at", Clock.toDate(image.getUpdatedAt()));
}
if (image.hasDeletedAt()) {
response.header("x-image-meta-deleted_at", Clock.toDate(image.getDeletedAt()));
}
String status = "queued";
if (image.hasImageState()) {
status = image.getImageState().toString().toLowerCase();
}
response.header("x-image-meta-status", status);
if (image.hasIsPublic()) {
response.header("x-image-meta-is_public", Boolean.toString(image.getIsPublic()));
}
response.header("x-image-meta-protected", image.getIsProtected());
response.header("x-image-meta-owner", "" + image.getOwnerProject());
if (image.hasAttributes()) {
for (KeyValueData kv : image.getAttributes().getUserAttributesList()) {
response.header("x-image-meta-property-" + kv.getKey(), kv.getValue());
}
}
// x-image-meta-is-public true
// x-image-meta-min-ram 256
// x-image-meta-min-disk 0
// x-image-meta-owner null
// x-image-meta-property-distro Ubuntu 10.04 LTS
}
}