blob: 4ea3508e090c1aff46be2ee34e45ff8c26b56393 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.jclouds.openstack.nova.v2_0.options;
import static com.google.common.base.Objects.equal;
import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.io.BaseEncoding.base64;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
import org.jclouds.http.HttpRequest;
import org.jclouds.openstack.nova.v2_0.domain.BlockDeviceMapping;
import org.jclouds.openstack.nova.v2_0.domain.Network;
import org.jclouds.rest.MapBinder;
import org.jclouds.rest.binders.BindToJsonPayload;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.MoreObjects.ToStringHelper;
import com.google.common.collect.ForwardingObject;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
public class CreateServerOptions implements MapBinder {
@Inject
private BindToJsonPayload jsonBinder;
static class File {
private final String path;
private final String contents;
public File(String path, byte[] contents) {
this.path = checkNotNull(path, "path");
this.contents = base64().encode(checkNotNull(contents, "contents"));
checkArgument(
path.getBytes().length < 255,
String.format("maximum length of path is 255 bytes. Path specified %s is %d bytes", path,
path.getBytes().length));
}
public String getContents() {
return contents;
}
public String getPath() {
return path;
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object instanceof File) {
final File other = File.class.cast(object);
return equal(path, other.path);
} else {
return false;
}
}
@Override
public int hashCode() {
return Objects.hashCode(path);
}
@Override
public String toString() {
return toStringHelper("file").add("path", path).toString();
}
}
private String keyName;
private String adminPass;
private Set<String> securityGroupNames = ImmutableSet.of();
private Map<String, String> metadata = ImmutableMap.of();
private List<File> personality = Lists.newArrayList();
private byte[] userData;
private String diskConfig;
private List<String> networks = ImmutableList.of();
private List<Network> novaNetworks = ImmutableList.of();
private String availabilityZone;
private boolean configDrive;
private Set<BlockDeviceMapping> blockDeviceMappings = ImmutableSet.of();
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object instanceof CreateServerOptions) {
final CreateServerOptions other = CreateServerOptions.class.cast(object);
return equal(keyName, other.keyName) && equal(adminPass, other.adminPass)
&& equal(securityGroupNames, other.securityGroupNames) && equal(metadata, other.metadata)
&& equal(personality, other.personality)
&& equal(diskConfig, other.diskConfig)
&& equal(networks, other.networks)
&& equal(availabilityZone, other.availabilityZone)
&& equal(configDrive, other.configDrive)
&& equal(blockDeviceMappings, other.blockDeviceMappings);
} else {
return false;
}
}
@Override
public int hashCode() {
return Objects.hashCode(keyName, adminPass, securityGroupNames, metadata, personality, networks, availabilityZone,
configDrive, blockDeviceMappings);
}
protected String formatPossiblyGzipped(final byte [] data) {
if (data.length > 10 && data[0] == 31 && data[1] == -117) {
return String.format("<gzipped data (%d bytes)>", data.length);
}
return new String(data);
}
protected ToStringHelper string() {
ToStringHelper toString = MoreObjects.toStringHelper(this);
toString.add("keyName", keyName);
if (!securityGroupNames.isEmpty())
toString.add("securityGroupNames", securityGroupNames);
if (!metadata.isEmpty())
toString.add("metadata", metadata);
if (!personality.isEmpty())
toString.add("personality", personality);
if (adminPass != null)
toString.add("adminPassPresent", true);
if (diskConfig != null)
toString.add("diskConfig", diskConfig);
toString.add("userData", userData == null ? null : formatPossiblyGzipped(userData));
if (!networks.isEmpty())
toString.add("networks", networks);
toString.add("availabilityZone", availabilityZone == null ? null : availabilityZone);
toString.add("configDrive", configDrive);
if (!blockDeviceMappings.isEmpty())
toString.add("blockDeviceMappings", blockDeviceMappings);
return toString;
}
@Override
public String toString() {
return string().toString();
}
static class ServerRequest {
final String name;
final String imageRef;
final String flavorRef;
@Named("availability_zone")
String availabilityZone;
String adminPass;
Map<String, String> metadata;
List<File> personality;
String key_name;
@Named("security_groups")
Set<NamedThingy> securityGroupNames;
String user_data;
@Named("OS-DCF:diskConfig")
String diskConfig;
List<Map<String, String>> networks;
@Named("config_drive")
String configDrive;
@Named("block_device_mapping_v2")
Set<BlockDeviceMapping> blockDeviceMappings;
private ServerRequest(String name, String imageRef, String flavorRef) {
this.name = name;
this.imageRef = imageRef;
this.flavorRef = flavorRef;
}
}
@Override
public <R extends HttpRequest> R bindToRequest(R request, Map<String, Object> postParams) {
ServerRequest server = new ServerRequest(checkNotNull(postParams.get("name"), "name parameter not present").toString(),
checkNotNull(postParams.get("imageRef"), "imageRef parameter not present").toString(),
checkNotNull(postParams.get("flavorRef"), "flavorRef parameter not present").toString());
if (!metadata.isEmpty())
server.metadata = metadata;
if (!personality.isEmpty())
server.personality = personality;
if (keyName != null)
server.key_name = keyName;
if (availabilityZone != null)
server.availabilityZone = availabilityZone;
if (userData != null)
server.user_data = base64().encode(userData);
if (configDrive)
server.configDrive = "true";
if (!securityGroupNames.isEmpty()) {
server.securityGroupNames = Sets.newLinkedHashSet();
for (String groupName : securityGroupNames) {
server.securityGroupNames.add(new NamedThingy(groupName));
}
}
if (adminPass != null) {
server.adminPass = adminPass;
}
if (diskConfig != null) {
server.diskConfig = diskConfig;
}
if (!networks.isEmpty() || !novaNetworks.isEmpty()) {
server.networks = Lists.newArrayList(); // ensures ordering is preserved - helps testing and more intuitive for users.
for (Network network : novaNetworks) {
// Avoid serializing null values, which are common here.
ImmutableMap.Builder<String, String> networkMap = new ImmutableMap.Builder<String, String>();
if (network.getNetworkUuid() != null) {
networkMap.put("uuid", network.getNetworkUuid());
}
if (network.getPortUuid() != null) {
networkMap.put("port", network.getPortUuid());
}
if (network.getFixedIp() != null) {
networkMap.put("fixed_ip", network.getFixedIp());
}
server.networks.add(networkMap.build());
}
for (String network : networks) {
server.networks.add(ImmutableMap.of("uuid", network));
}
}
if (!blockDeviceMappings.isEmpty()) {
server.blockDeviceMappings = blockDeviceMappings;
}
return bindToRequest(request, (Object) ImmutableMap.of("server", server));
}
private static class NamedThingy extends ForwardingObject {
private String name;
private NamedThingy(String name) {
this.name = name;
}
@Override
protected Object delegate() {
return name;
}
}
/**
* You may further customize a cloud server by injecting data into the file
* system of the cloud server itself. This is useful, for example, for
* inserting ssh keys, setting configuration files, or storing data that you
* want to retrieve from within the instance itself. It is intended to
* provide a minimal amount of launch-time personalization. If significant
* customization is required, a custom image should be created. The max size
* of the file path data is 255 bytes while the max size of the file contents
* is determined by provider quotas(default size is 10KB). Note that the file
* contents should be encoded as a Base64 string and the size limit refers to
* the number of bytes in the decoded data not the number of characters in the
* encoded data. The maximum number of file path/content pairs that can be supplied
* is determined by provider quotas(default is 5). Any existing files that match
* the specified file will be renamed to include the extension bak followed by a
* time stamp.
* For example, the file /etc/passwd will be backed
* up as /etc/passwd.bak.1246036261.5785. All files will have root and the
* root group as owner and group owner, respectively and will allow user and
* group read access only (-r--r-----).
*/
public CreateServerOptions writeFileToPath(byte[] contents, String path) {
personality.add(new File(path, contents));
return this;
}
public CreateServerOptions adminPass(String adminPass) {
checkNotNull(adminPass, "adminPass");
this.adminPass = adminPass;
return this;
}
/**
* Custom cloud server metadata can also be supplied at launch time. This
* metadata is stored in the API system where it is retrievable by querying
* the API for server status. The maximum size of the metadata key and value
* is each 255 bytes.
*/
public CreateServerOptions metadata(Map<String, String> metadata) {
checkNotNull(metadata, "metadata");
for (Entry<String, String> entry : metadata.entrySet()) {
checkArgument(
entry.getKey().getBytes().length < 255,
String.format("maximum length of metadata key is 255 bytes. Key specified %s is %d bytes",
entry.getKey(), entry.getKey().getBytes().length));
checkArgument(entry.getKey().getBytes().length < 255, String.format(
"maximum length of metadata value is 255 bytes. Value specified for %s (%s) is %d bytes",
entry.getKey(), entry.getValue(), entry.getValue().getBytes().length));
}
this.metadata = ImmutableMap.copyOf(metadata);
return this;
}
/**
* Custom user-data can be also be supplied at launch time.
* It is retrievable by the instance and is often used for launch-time configuration
* by instance scripts.
* Pass userData unencoded, as the value will be base64 encoded automatically.
*/
public CreateServerOptions userData(byte[] userData) {
this.userData = userData;
return this;
}
/**
* Set to true to use a config drive for metadata.
* This is a separate configuration drive that can be used separately from the metadata service.
* This needs to be set to "true" when trying to use user data for cloud-init.
* @see http://docs.openstack.org/grizzly/openstack-compute/admin/content/config-drive.html
*/
public CreateServerOptions configDrive(boolean configDrive) {
this.configDrive = configDrive;
return this;
}
/**
* A keypair name can be defined when creating a server. This key will be
* linked to the server and used to SSH connect to the machine
* @see #getKeyPairName()
*/
public CreateServerOptions keyPairName(String keyName) {
this.keyName = keyName;
return this;
}
/**
* @see #getAvailabilityZone()
*/
public CreateServerOptions availabilityZone(String availabilityZone) {
this.availabilityZone = availabilityZone;
return this;
}
/**
* @see #getSecurityGroupNames()
*/
public CreateServerOptions securityGroupNames(String... securityGroupNames) {
return securityGroupNames(ImmutableSet.copyOf(checkNotNull(securityGroupNames, "securityGroupNames")));
}
public CreateServerOptions securityGroupNames(Iterable<String> securityGroupNames) {
for (String groupName : checkNotNull(securityGroupNames, "securityGroupNames"))
checkNotNull(emptyToNull(groupName), "all security groups must be non-empty");
this.securityGroupNames = ImmutableSet.copyOf(securityGroupNames);
return this;
}
/**
* @see #getDiskConfig()
*/
public CreateServerOptions diskConfig(String diskConfig) {
this.diskConfig = diskConfig;
return this;
}
/**
* @see #getNetworks()
*/
public CreateServerOptions networks(Iterable<String> networks) {
this.networks = ImmutableList.copyOf(networks);
return this;
}
/**
* @see #getNetworks()
* Overwrites networks supplied by {@link #networks(Iterable)}
*/
public CreateServerOptions novaNetworks(Iterable<Network> networks) {
this.novaNetworks = ImmutableList.copyOf(networks);
return this;
}
/**
* @see #getNetworks()
*/
public CreateServerOptions networks(String... networks) {
return networks(ImmutableList.copyOf(networks));
}
/**
* @see #getBlockDeviceMappings()
*/
public CreateServerOptions blockDeviceMappings(Set<BlockDeviceMapping> blockDeviceMappings) {
this.blockDeviceMappings = ImmutableSet.copyOf(blockDeviceMappings);
return this;
}
/**
* A keypair name can be defined when creating a server. This key will be
* linked to the server and used to SSH connect to the machine
*/
public String getKeyPairName() {
return keyName;
}
public String getAvailabilityZone() {
return availabilityZone;
}
/**
* Security groups the user specified to run servers with.
* <p/>
* <h3>Note</h3>
* <p/>
* This requires that {@link NovaApi#getSecurityGroupExtensionApi(String)} to return
* {@link Optional#isPresent present}
*/
public Set<String> getSecurityGroupNames() {
return securityGroupNames;
}
/**
* Get custom networks specified for the server.
*
* @return A list of uuids defined by Neutron (previously Quantum)
* @see <a href="https://wiki.openstack.org/wiki/Neutron/APIv2-specification#Network">Neutron Networks<a/>
*/
public List<String> getNetworks() {
return networks;
}
/**
* Get custom networks specified for the server.
*
* @return A list of uuids defined by Neutron (previously Quantum)
* @see <a href="https://wiki.openstack.org/wiki/Neutron/APIv2-specification#Network">Neutron Networks<a/>
*/
public List<Network> getNovaNetworks() {
return novaNetworks;
}
/**
* When you create a server from an image with the diskConfig value set to
* {@link Server#DISK_CONFIG_AUTO}, the server is built with a single partition that is expanded to
* the disk size of the flavor selected. When you set the diskConfig attribute to
* {@link Server#DISK_CONFIG_MANUAL}, the server is built by using the partition scheme and file
* system that is in the source image.
* <p/>
* If the target flavor disk is larger, remaining disk space is left unpartitioned. A server inherits the diskConfig
* attribute from the image from which it is created. However, you can override the diskConfig value when you create
* a server. This field is only present if the Disk Config extension is installed in your OpenStack deployment.
*/
public String getDiskConfig() {
return diskConfig;
}
/**
* Determines if a configuration drive will be attached to the server or not.
* This can be used for cloud-init or other configuration purposes.
*/
public boolean getConfigDrive() {
return configDrive;
}
/**
* Block devices that should be attached to the instance at boot time.
*/
public Set<BlockDeviceMapping> getBlockDeviceMappings() {
return blockDeviceMappings;
}
public static class Builder {
/**
* @see CreateServerOptions#writeFileToPath(byte[], String)
*/
public static CreateServerOptions writeFileToPath(byte[] contents, String path) {
CreateServerOptions options = new CreateServerOptions();
return options.writeFileToPath(contents, path);
}
/**
* @see CreateServerOptions#adminPass(String)
*/
public static CreateServerOptions adminPass(String adminPass) {
CreateServerOptions options = new CreateServerOptions();
return options.adminPass(adminPass);
}
/**
* @see CreateServerOptions#metadata(Map<String, String>)
*/
public static CreateServerOptions metadata(Map<String, String> metadata) {
CreateServerOptions options = new CreateServerOptions();
return options.metadata(metadata);
}
/**
* @see CreateServerOptions#keyPairName(String)
*/
public static CreateServerOptions keyPairName(String keyName) {
CreateServerOptions options = new CreateServerOptions();
return options.keyPairName(keyName);
}
/**
* @see CreateServerOptions#securityGroupNames(String...)
*/
public static CreateServerOptions securityGroupNames(String... groupNames) {
CreateServerOptions options = new CreateServerOptions();
if (new CreateServerOptions().securityGroupNames(groupNames) == CreateServerOptions.class.cast(options.securityGroupNames(groupNames)))
System.out.println("They are fucking equal, dump the cast!!!");
return CreateServerOptions.class.cast(options.securityGroupNames(groupNames));
}
/**
* @see CreateServerOptions#securityGroupNames(Iterable)
*/
public static CreateServerOptions securityGroupNames(Iterable<String> groupNames) {
return CreateServerOptions.class.cast(new CreateServerOptions().securityGroupNames(groupNames));
}
/**
* @see CreateServerOptions#diskConfig(String)
*/
public static CreateServerOptions diskConfig(String diskConfig) {
return CreateServerOptions.class.cast(new CreateServerOptions().diskConfig(diskConfig));
}
/**
* @see CreateServerOptions#networks(String...)
*/
public static CreateServerOptions networks(String... networks) {
return CreateServerOptions.class.cast(new CreateServerOptions().networks(networks));
}
/**
* @see CreateServerOptions#networks(Iterable)
*/
public static CreateServerOptions networks(Iterable<String> networks) {
return CreateServerOptions.class.cast(new CreateServerOptions().networks(networks));
}
/**
* @see CreateServerOptions#novaNetworks(Iterable)
*/
public static CreateServerOptions novaNetworks(Iterable<Network> networks) {
return CreateServerOptions.class.cast(new CreateServerOptions().novaNetworks(networks));
}
/**
* @see CreateServerOptions#availabilityZone(String)
*/
public static CreateServerOptions availabilityZone(String availabilityZone) {
return new CreateServerOptions().availabilityZone(availabilityZone);
}
/**
* @see CreateServerOptions#blockDeviceMappings(Set)
*/
public static CreateServerOptions blockDeviceMappings(Set<BlockDeviceMapping> blockDeviceMappings) {
return new CreateServerOptions().blockDeviceMappings(blockDeviceMappings);
}
}
@Override
public <R extends HttpRequest> R bindToRequest(R request, Object input) {
return jsonBinder.bindToRequest(request, input);
}
}