package io.fathom.cloud.compute.api.os.resources;
import io.fathom.cloud.CloudException;
import io.fathom.cloud.compute.actions.StartInstancesAction;
import io.fathom.cloud.compute.actions.StopInstancesAction;
import io.fathom.cloud.compute.api.os.model.Address;
import io.fathom.cloud.compute.api.os.model.Addresses;
import io.fathom.cloud.compute.api.os.model.Flavor;
import io.fathom.cloud.compute.api.os.model.Image;
import io.fathom.cloud.compute.api.os.model.SecurityGroup;
import io.fathom.cloud.compute.api.os.model.SecurityGroupList;
import io.fathom.cloud.compute.api.os.model.Server;
import io.fathom.cloud.compute.api.os.model.ServerList;
import io.fathom.cloud.compute.api.os.model.VolumeAttachments;
import io.fathom.cloud.compute.api.os.model.WrappedServer;
import io.fathom.cloud.compute.api.os.model.actions.AddFloatingIpRequest;
import io.fathom.cloud.compute.api.os.model.actions.AddSecurityGroupRequest;
import io.fathom.cloud.compute.api.os.model.actions.CreateImageRequest;
import io.fathom.cloud.compute.api.os.model.actions.RemoveFloatingIpRequest;
import io.fathom.cloud.compute.api.os.model.actions.RemoveSecurityGroupRequest;
import io.fathom.cloud.compute.networks.IpRanges;
import io.fathom.cloud.compute.services.ComputeServices;
import io.fathom.cloud.compute.services.Flavors;
import io.fathom.cloud.compute.services.Instances;
import io.fathom.cloud.compute.services.IpPools;
import io.fathom.cloud.compute.services.MetadataServices;
import io.fathom.cloud.compute.services.SecurityGroups;
import io.fathom.cloud.compute.services.SshKeyPairs;
import io.fathom.cloud.compute.state.ComputeRepository;
import io.fathom.cloud.protobuf.CloudModel.FlavorData;
import io.fathom.cloud.protobuf.CloudModel.InstanceData;
import io.fathom.cloud.protobuf.CloudModel.KeyPairData;
import io.fathom.cloud.protobuf.CloudModel.NetworkAddressData;
import io.fathom.cloud.protobuf.CloudModel.ReservationData;
import io.fathom.cloud.protobuf.CloudModel.SecurityGroupData;
import io.fathom.cloud.server.auth.Auth;
import io.fathom.cloud.server.model.Project;
import io.fathom.cloud.services.ImageService;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
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.HttpHeaders;
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.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.net.InetAddresses;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.inject.persist.Transactional;
@Path("/openstack/compute/{project}/servers")
@Transactional
public class ServersResource extends ComputeResourceBase {
private static final Logger log = LoggerFactory.getLogger(ServersResource.class);
@Inject
SecurityGroups securityGroups;
@Inject
Instances instances;
@Inject
Flavors flavors;
@Inject
Provider<StartInstancesAction> startInstancesActionProvider;
@Inject
Provider<StopInstancesAction> stopInstancesActionProvider;
@Inject
ComputeRepository instanceStateStore;
@Inject
ComputeServices computeServices;
@Inject
ImageService imageService;
@Inject
SshKeyPairs keypairs;
@Inject
Gson gson;
@Inject
IpPools ipPools;
@GET
@Path("{id}")
public WrappedServer doServerGet(@PathParam("id") String id) throws CloudException {
InstanceData instance = getInstance(id);
WrappedServer response = new WrappedServer();
ReservationData reservation = getReservation(instance.getReservationId());
response.server = toModel(reservation, instance, true);
return response;
}
@PUT
@Path("{id}")
public WrappedServer doServerPut(@PathParam("id") String id, WrappedServer request) throws CloudException {
InstanceData instance = getInstance(id);
if (request.server == null) {
request.server = new Server();
}
if (request.server.name != null && !Objects.equal(instance.getName(), request.server.name)) {
instance = instances.updateInstance(getProject(), instance.getId(), request.server.name);
}
WrappedServer response = new WrappedServer();
ReservationData reservation = getReservation(instance.getReservationId());
response.server = toModel(reservation, instance, true);
return response;
}
@POST
@Path("{id}/action")
public Response doAction(@PathParam("id") String id, JsonObject jsonRequest) throws CloudException, IOException {
InstanceData instance = getInstance(id);
for (Entry<String, JsonElement> entry : jsonRequest.entrySet()) {
String key = entry.getKey();
if (key.equals("addFloatingIp")) {
AddFloatingIpRequest request = gson.fromJson(entry.getValue(), AddFloatingIpRequest.class);
return addFloatingIp(instance, request);
} else if (key.equals("removeFloatingIp")) {
RemoveFloatingIpRequest request = gson.fromJson(entry.getValue(), RemoveFloatingIpRequest.class);
return removeFloatingIp(instance, request);
} else if (key.equals("addSecurityGroup")) {
AddSecurityGroupRequest request = gson.fromJson(entry.getValue(), AddSecurityGroupRequest.class);
return addRemoveSecurityGroup(instance, request.name, false);
} else if (key.equals("removeSecurityGroup")) {
RemoveSecurityGroupRequest request = gson.fromJson(entry.getValue(), RemoveSecurityGroupRequest.class);
return addRemoveSecurityGroup(instance, request.name, true);
} else if (key.equals("createImage")) {
CreateImageRequest request = gson.fromJson(entry.getValue(), CreateImageRequest.class);
return createImage(instance, request);
} else {
throw new IllegalArgumentException("Unknown action: " + key);
}
}
throw new IllegalArgumentException();
}
private Response addRemoveSecurityGroup(InstanceData instance, String name, boolean remove) throws CloudException {
Project project = getProject();
Long id = Long.valueOf(name);
SecurityGroupData sg = securityGroups.find(project, id);
// SecurityGroupData sg = securityGroups.find(project, request.name);
notFoundIfNull(sg);
securityGroups.addRemoveSecurityGroup(project, instance.getId(), sg, remove);
return Response.accepted().build();
}
private Response createImage(InstanceData instance, CreateImageRequest request) throws IOException, CloudException {
Project project = getProject();
ImageService.Image image = computeServices.createImage(project, instance, request);
String location = imageService.getUrl(httpRequest, image.getId());
return Response.ok().header(HttpHeaders.LOCATION, location).build();
}
private Response removeFloatingIp(InstanceData instance, RemoveFloatingIpRequest request) throws CloudException {
Project project = getProject();
ipPools.detachFloatingIp(project, instance, request);
return Response.ok().build();
}
protected Response addFloatingIp(InstanceData instance, AddFloatingIpRequest request) throws CloudException {
Project project = getProject();
ipPools.attachFloatingIp(project, instance, request);
return Response.ok().build();
}
@DELETE
@Path("{id}")
public Response shutdownServer(@PathParam("id") String id) throws CloudException {
InstanceData instance = getInstance(id);
StopInstancesAction action = stopInstancesActionProvider.get();
action.project = getProject();
action.instances = Lists.newArrayList();
action.instances.add(instance);
action.go();
ResponseBuilder response = Response.noContent();
return response.build();
}
@GET
@Path("{id}/os-volume_attachments")
public VolumeAttachments listVolumeAttachments(@PathParam("id") String id) throws CloudException {
VolumeAttachments response = new VolumeAttachments();
response.volumeAttachments = Lists.newArrayList();
log.warn("os-volume_attachments is stub-implemented");
return response;
}
@GET
@Path("{id}/os-security-groups")
public SecurityGroupList listSecurityGroups(@PathParam("id") String id) throws CloudException {
Project project = getProject();
InstanceData instance = getInstance(id);
SecurityGroupList response = new SecurityGroupList();
response.securityGroups = Lists.newArrayList();
for (long sgId : instance.getSecurityGroupIdList()) {
SecurityGroupData data = securityGroups.find(getProject(), sgId);
if (data == null) {
log.warn("Cannot find sg: {}", sgId);
continue;
}
SecurityGroup model = SecurityGroupsResource.toModel(project, data, true);
response.securityGroups.add(model);
}
return response;
}
private InstanceData getInstance(String id) throws CloudException {
InstanceData instance = instanceStateStore.getInstances(getProject().getId()).find(Long.valueOf(id));
if (instance == null) {
throw new WebApplicationException(Status.NOT_FOUND);
}
return instance;
}
private ReservationData getReservation(long id) throws CloudException {
ReservationData r = instanceStateStore.getReservations(getProject()).find(id);
if (r == null) {
throw new WebApplicationException(Status.NOT_FOUND);
}
return r;
}
private ServerList listServers(boolean details) throws CloudException {
Auth auth = getAuth();
boolean allTenants = httpRequest.getParameter("all_tenants") != null;
Project filterProject = getProject();
if (allTenants) {
filterProject = null;
}
List<InstanceData> instances = computeServices.listInstances(auth, filterProject);
Map<Long, ReservationData> reservations = Maps.newHashMap();
ServerList response = new ServerList();
response.servers = Lists.newArrayList();
for (InstanceData instance : instances) {
ReservationData reservation = reservations.get(instance.getReservationId());
if (reservation == null) {
reservation = getReservation(instance.getReservationId());
reservations.put(instance.getReservationId(), reservation);
}
response.servers.add(toModel(reservation, instance, details));
}
return response;
}
@GET
@Path("detail")
public ServerList listDetails() throws CloudException {
return listServers(true);
}
@GET
public ServerList list() throws CloudException {
return listServers(false);
}
@POST
@Produces({ JSON })
public Response launchServer(WrappedServer request) throws CloudException {
StartInstancesAction action = startInstancesActionProvider.get();
// action.user = getUser();
action.project = getProject();
action.auth = getAuth();
FlavorData flavor;
{
long flavorId = OpenstackIds.toFlavorId(request.server.flavorRef);
flavor = flavors.find(flavorId);
if (flavor == null) {
throw new IllegalArgumentException();
}
}
action.minCount = request.server.minCount;
if (action.minCount == 0) {
action.minCount = 1;
}
action.maxCount = request.server.maxCount;
if (action.maxCount == 0) {
action.maxCount = 1;
}
if (action.maxCount != 1) {
// Not clear what the response should be in this case...
throw new UnsupportedOperationException();
}
ImageService.Image image;
{
long imageId = OpenstackIds.toImageId(request.server.imageRef);
image = imageService.findImage(getProject(), imageId);
if (image == null) {
throw new IllegalArgumentException();
}
}
{
ReservationData.Builder reservation = ReservationData.newBuilder();
// TODO: Copy image?
reservation.setImageId(image.getId());
action.reservationTemplate = reservation.build();
}
{
InstanceData.Builder instance = InstanceData.newBuilder();
instance.setName(request.server.name);
if (request.server.keyName != null) {
KeyPairData keypair = keypairs.findKeyPair(getProject(), request.server.keyName);
if (keypair == null) {
throw new IllegalArgumentException();
}
instance.setKeyPair(keypair);
}
instance.setImageId(image.getId());
instance.setFlavor(flavor);
if (request.server.securityGroups != null && !request.server.securityGroups.isEmpty()) {
SecurityGroupDictionary dictionary = new SecurityGroupDictionary(securityGroups.list(getProject()));
for (SecurityGroup securityGroup : request.server.securityGroups) {
String name = securityGroup.name;
SecurityGroupData data = dictionary.getByName(name);
if (data == null) {
throw new IllegalArgumentException("Security group not found: " + name);
}
instance.addSecurityGroupId(data.getId());
}
}
action.instanceTemplate = instance.build();
}
StartInstancesAction.Result result = action.go();
WrappedServer response = new WrappedServer();
if (result.instances.size() != 1) {
throw new IllegalStateException();
}
Map<Long, ReservationData> reservations = Maps.newHashMap();
for (InstanceData instance : result.instances) {
ReservationData reservation = reservations.get(instance.getReservationId());
if (reservation == null) {
reservation = getReservation(instance.getReservationId());
reservations.put(instance.getReservationId(), reservation);
}
response.server = toModel(reservation, instance, false);
// TODO: adminPass??
}
return Response.status(Status.ACCEPTED).entity(response).build();
}
private Server toModel(ReservationData reservation, InstanceData instance, boolean details) throws CloudException {
Server server = new Server();
server.id = "" + instance.getId();
server.links = Lists.newArrayList();
server.name = instance.getName();
FlavorData flavor;
if (instance.hasFlavor()) {
flavor = instance.getFlavor();
} else {
// TODO: I don't think any of these escaped publicly... remove.
// Most clients freak out if we don't have a flavor...
log.warn("No flavor associated with instance: {}", instance);
flavor = flavors.list().get(0);
}
if (flavor != null) {
server.flavor = new Flavor();
server.flavor.id = "" + flavor.getId();
}
server.tenantId = "" + instance.getProjectId();
if (details) {
server.progress = 0;
// server.extensionTaskState = "";
server.extensionPowerState = 1;
server.created = new Date(instance.getLaunchTime());
server.addresses = new Addresses();
server.addresses.privateAddresses = Lists.newArrayList();
server.addresses.publicAddresses = Lists.newArrayList();
server.image = new Image();
server.image.id = "" + instance.getImageId();
server.metadata = MetadataServices.toMap(instance.getMetadata());
if (instance.hasNetwork()) {
InetAddress bestIpv4 = null;
InetAddress bestIpv6 = null;
for (NetworkAddressData addressData : instance.getNetwork().getAddressesList()) {
String addressString = addressData.getIp();
InetAddress address = InetAddresses.forString(addressString);
Address xml = new Address();
xml.address = addressString;
boolean publicAddress = true;
if (address instanceof Inet4Address) {
xml.version = 4;
if (bestIpv4 == null) {
bestIpv4 = address;
}
} else {
xml.version = 6;
if (bestIpv6 == null) {
bestIpv6 = address;
}
}
if (publicAddress) {
server.addresses.publicAddresses.add(xml);
} else {
server.addresses.privateAddresses.add(xml);
}
}
if (bestIpv4 != null) {
boolean publishIpv4 = true;
if (!IpRanges.isPublic(bestIpv4) && bestIpv6 != null) {
publishIpv4 = false;
}
if (publishIpv4) {
server.accessIPv4 = InetAddresses.toAddrString(bestIpv4);
}
}
if (bestIpv6 != null) {
server.accessIPv6 = InetAddresses.toAddrString(bestIpv6);
}
}
switch (instance.getInstanceState()) {
case TERMINATED:
case STOPPED:
server.status = "DELETED";
break;
case RUNNING:
case SHUTTING_DOWN:
case STOPPING:
server.status = "ACTIVE";
break;
case PENDING:
server.status = "ERROR";
// server.status = "BUILD";
break;
default:
log.warn("Unhandled state {}", instance.getInstanceState());
server.status = "UNKNOWN";
break;
}
// "accessIPv4": "",
// "accessIPv6": "",
// "addresses": {
// "private": [
// {
// "addr": "192.168.0.3",
// "version": 4
// }
// ]
// },
// "created": "2012-09-07T16:56:37Z",
// "flavor": {
// "id": "1",
// "links": [
// {
// "href": "http://openstack.example.com/openstack/flavors/1",
// "rel": "bookmark"
// }
// ]
// },
// "hostId":
// "16d193736a5cfdb60c697ca27ad071d6126fa13baeb670fc9d10645e",
// "id": "05184ba3-00ba-4fbc-b7a2-03b62b884931",
// "image": {
// "id": "70a599e0-31e7-49b7-b260-868f441e862b",
// "links": [
// {
// "href":
// "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b",
// "rel": "bookmark"
// }
// ]
// },
// "links": [
// {
// "href":
// "http://openstack.example.com/v2/openstack/servers/05184ba3-00ba-4fbc-b7a2-03b62b884931",
// "rel": "self"
// },
// {
// "href":
// "http://openstack.example.com/openstack/servers/05184ba3-00ba-4fbc-b7a2-03b62b884931",
// "rel": "bookmark"
// }
// ],
// "metadata": {
// "My Server Name": "Apache1"
// },
// "name": "new-server-test",
// "progress": 0,
// "status": "ACTIVE",
// "tenant_id": "openstack",
// "updated": "2012-09-07T16:56:37Z",
// "user_id": "fake"
}
return server;
}
}