diff --git a/README.md b/README.md
index 999fd24..63ccdd6 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,7 @@ You can specify idle time on the agent cloud profile, after which the instance s
| *security_group* | true | [Security group](https://docs.openstack.org/nova/latest/admin/security-groups.html), ex: `default` |
| *key_pair* | false | [Key pair](https://docs.openstack.org/horizon/latest/user/configure-access-and-security-for-instances.html), ex: `my-key` ; required for SSH connection on created instances (like TeamCity Agent Push feature) |
| *auto_floating_ip* | false | Boolean (`false` by default) for [floating ip](https://docs.openstack.org/ocata/user-guide/cli-manage-ip-addresses.html) association ; first from pool used |
+| *volume* | false | [Volume](https://docs.openstack.org/cinder/latest/cli/cli-manage-volumes.html) to attach (name and device separated by comma), ex: `some-volume,/dev/vdc`
| *user_script* | false | Script executed on instance start |
| *availability_zone* | false | Region for server instance (if not the global configured)
@@ -115,6 +116,8 @@ openstack-test-teamcity-plugin:
network: networkProviderName
security_group: default
key_pair: yourKey
+ auto_floating_ip: true
+ volume: some-volume,/dev/vdc
```
```
diff --git a/cloud-openstack-server/pom.xml b/cloud-openstack-server/pom.xml
index 2c2f959..5cad4d1 100644
--- a/cloud-openstack-server/pom.xml
+++ b/cloud-openstack-server/pom.xml
@@ -72,6 +72,11 @@
openstack-neutron
${jclouds.version}
+
+ org.apache.jclouds.api
+ openstack-cinder
+ ${jclouds.version}
+
org.jetbrains.teamcity
diff --git a/cloud-openstack-server/src/main/java/jetbrains/buildServer/clouds/openstack/CreateImageOptions.java b/cloud-openstack-server/src/main/java/jetbrains/buildServer/clouds/openstack/CreateImageOptions.java
new file mode 100644
index 0000000..3028228
--- /dev/null
+++ b/cloud-openstack-server/src/main/java/jetbrains/buildServer/clouds/openstack/CreateImageOptions.java
@@ -0,0 +1,157 @@
+
+package jetbrains.buildServer.clouds.openstack;
+
+import java.util.concurrent.ScheduledExecutorService;
+
+import org.jclouds.openstack.nova.v2_0.options.CreateServerOptions;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import jetbrains.buildServer.serverSide.ServerPaths;
+import jetbrains.buildServer.util.StringUtil;
+
+public class CreateImageOptions {
+
+ @NotNull
+ private OpenstackApi openstackApi;
+ @NotNull
+ private String imageId;
+ @NotNull
+ private String imageName;
+ @NotNull
+ private String openstackImageName;
+ @NotNull
+ private String flavorName;
+ @Nullable
+ private String volumeName;
+ @Nullable
+ private String volumeDevice;
+ @NotNull
+ private boolean autoFloatingIp;
+ @NotNull
+ private CreateServerOptions createServerOptions;
+ @Nullable
+ private String userScriptPath;
+ @NotNull
+ private ServerPaths serverPaths;
+ @NotNull
+ private ScheduledExecutorService scheduledExecutorService;
+
+ protected CreateImageOptions openstackApi(@NotNull final OpenstackApi openstackApi) {
+ this.openstackApi = openstackApi;
+ return this;
+ }
+
+ protected CreateImageOptions imageId(@NotNull final String imageId) {
+ this.imageId = imageId;
+ return this;
+ }
+
+ protected CreateImageOptions imageName(@NotNull final String imageName) {
+ this.imageName = imageName;
+ return this;
+ }
+
+ protected CreateImageOptions openstackImageName(@NotNull final String openstackImageName) {
+ this.openstackImageName = openstackImageName;
+ return this;
+ }
+
+ protected CreateImageOptions flavorName(@NotNull final String flavorName) {
+ this.flavorName = flavorName;
+ return this;
+ }
+
+ /**
+ * Volume should be "volumeName,volumeDevice"
+ *
+ * @param volume volume name and volume device
+ * @return CreateImageOptions
+ */
+ protected CreateImageOptions volume(@Nullable final String volume) {
+ if (StringUtil.isNotEmpty(volume)) {
+ String[] volumeNameDevice = volume.split(",");
+ if (volumeNameDevice.length > 0) {
+ this.volumeName = volumeNameDevice[0].trim();
+ }
+ if (volumeNameDevice.length > 1) {
+ this.volumeDevice = volumeNameDevice[1].trim();
+ }
+ }
+ return this;
+ }
+
+ protected CreateImageOptions autoFloatingIp(@NotNull final boolean autoFloatingIp) {
+ this.autoFloatingIp = autoFloatingIp;
+ return this;
+ }
+
+ protected CreateImageOptions userScriptPath(@Nullable final String userScriptPath) {
+ this.userScriptPath = userScriptPath;
+ return this;
+ }
+
+ protected CreateImageOptions createServerOptions(@NotNull final CreateServerOptions createServerOptions) {
+ this.createServerOptions = createServerOptions;
+ return this;
+ }
+
+ protected CreateImageOptions serverPaths(@NotNull final ServerPaths serverPaths) {
+ this.serverPaths = serverPaths;
+ return this;
+ }
+
+ protected CreateImageOptions scheduledExecutorService(@NotNull final ScheduledExecutorService scheduledExecutorService) {
+ this.scheduledExecutorService = scheduledExecutorService;
+ return this;
+ }
+
+ protected OpenstackApi getOpenstackApi() {
+ return openstackApi;
+ }
+
+ protected String getImageId() {
+ return imageId;
+ }
+
+ protected String getImageName() {
+ return imageName;
+ }
+
+ protected String getOpenstackImageName() {
+ return openstackImageName;
+ }
+
+ protected String getFlavorName() {
+ return flavorName;
+ }
+
+ protected String getVolumeName() {
+ return volumeName;
+ }
+
+ protected String getVolumeDevice() {
+ return volumeDevice;
+ }
+
+ protected boolean isAutoFloatingIp() {
+ return autoFloatingIp;
+ }
+
+ protected CreateServerOptions getCreateServerOptions() {
+ return createServerOptions;
+ }
+
+ protected String getUserScriptPath() {
+ return userScriptPath;
+ }
+
+ protected ServerPaths getServerPaths() {
+ return serverPaths;
+ }
+
+ protected ScheduledExecutorService getScheduledExecutorService() {
+ return scheduledExecutorService;
+ }
+
+}
diff --git a/cloud-openstack-server/src/main/java/jetbrains/buildServer/clouds/openstack/OpenstackApi.java b/cloud-openstack-server/src/main/java/jetbrains/buildServer/clouds/openstack/OpenstackApi.java
index 00c6e8c..2ed376a 100644
--- a/cloud-openstack-server/src/main/java/jetbrains/buildServer/clouds/openstack/OpenstackApi.java
+++ b/cloud-openstack-server/src/main/java/jetbrains/buildServer/clouds/openstack/OpenstackApi.java
@@ -5,6 +5,9 @@
import org.jclouds.ContextBuilder;
import org.jclouds.location.reference.LocationConstants;
+import org.jclouds.openstack.cinder.v1.CinderApi;
+import org.jclouds.openstack.cinder.v1.CinderApiMetadata;
+import org.jclouds.openstack.cinder.v1.domain.Volume;
import org.jclouds.openstack.keystone.config.KeystoneProperties;
import org.jclouds.openstack.neutron.v2.NeutronApi;
import org.jclouds.openstack.neutron.v2.NeutronApiMetadata;
@@ -14,7 +17,11 @@
import org.jclouds.openstack.nova.v2_0.NovaApiMetadata;
import org.jclouds.openstack.nova.v2_0.domain.Flavor;
import org.jclouds.openstack.nova.v2_0.domain.Image;
-import org.jclouds.openstack.nova.v2_0.features.ServerApi;
+import org.jclouds.openstack.nova.v2_0.domain.Server;
+import org.jclouds.openstack.nova.v2_0.domain.ServerCreated;
+import org.jclouds.openstack.nova.v2_0.options.CreateServerOptions;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import jetbrains.buildServer.util.StringUtil;
@@ -24,6 +31,7 @@ public class OpenstackApi {
private final NeutronApi neutronApi;
private final NovaApi novaApi;
+ private final CinderApi cinderApi;
public OpenstackApi(String endpointUrl, String identity, String password, String region) {
@@ -53,9 +61,34 @@ public OpenstackApi(String endpointUrl, String identity, String password, String
novaApi = ContextBuilder.newBuilder(new NovaApiMetadata()).endpoint(endpointUrl).credentials(identityObject.getCredendials(), password)
.overrides(overrides).buildApi(NovaApi.class);
+
+ cinderApi = ContextBuilder.newBuilder(new CinderApiMetadata()).endpoint(endpointUrl).credentials(identityObject.getCredendials(), password)
+ .overrides(overrides).buildApi(CinderApi.class);
+
+ }
+
+ @Nullable
+ public Server getServer(@NotNull final String id) {
+ return novaApi.getServerApi(region).get(id);
+ }
+
+ @NotNull
+ public ServerCreated createServer(@NotNull final String name, @NotNull final String imageId, @NotNull final String flavorId,
+ @NotNull final CreateServerOptions options) {
+ return novaApi.getServerApi(region).create(name, imageId, flavorId, options);
+
}
- public String getImageIdByName(String name) {
+ public void deleteServer(@NotNull final String id) {
+ novaApi.getServerApi(region).delete(id);
+ }
+
+ public void attachVolumeToServer(@NotNull final String serverId, @NotNull final String volumeId, @NotNull final String volumeDevice) {
+ novaApi.getVolumeAttachmentApi(region).get().attachVolumeToServerAsDevice(volumeId, serverId, volumeDevice);
+ }
+
+ @Nullable
+ public String getImageIdByName(@NotNull final String name) {
List extends Image> images = novaApi.getImageApi(region).listInDetail().concat().toList();
for (Image image : images) {
if (image.getName().equals(name))
@@ -64,7 +97,8 @@ public String getImageIdByName(String name) {
return null;
}
- public String getFlavorIdByName(String name) {
+ @Nullable
+ public String getFlavorIdByName(@NotNull final String name) {
List extends Flavor> flavors = novaApi.getFlavorApi(region).listInDetail().concat().toList();
for (Flavor flavor : flavors) {
if (flavor.getName().equals(name))
@@ -73,7 +107,8 @@ public String getFlavorIdByName(String name) {
return null;
}
- public String getNetworkIdByName(String name) {
+ @Nullable
+ public String getNetworkIdByName(@NotNull final String name) {
List extends Network> networks = neutronApi.getNetworkApi(region).list().concat().toList();
for (Network network : networks) {
if (network.getName().equals(name))
@@ -82,14 +117,21 @@ public String getNetworkIdByName(String name) {
return null;
}
- public ServerApi getNovaServerApi() {
- return novaApi.getServerApi(region);
+ @Nullable
+ public String getVolumeIdByName(@NotNull final String name) {
+ List extends Volume> volumes = cinderApi.getVolumeApi(region).list().toList();
+ for (Volume volume : volumes) {
+ if (volume.getName().equals(name))
+ return volume.getId();
+ }
+ return null;
}
- public void associateFloatingIp(String serverId, String ip) {
+ public void associateFloatingIp(@NotNull final String serverId, @NotNull final String ip) {
novaApi.getFloatingIPApi(region).get().addToServer(ip, serverId);
}
+ @Nullable
public String getFloatingIpAvailable() {
for (FloatingIP ip : neutronApi.getFloatingIPApi(region).list().concat().toList()) {
if (StringUtil.isEmpty(ip.getFixedIpAddress())) {
@@ -105,7 +147,7 @@ public String getFloatingIpAvailable() {
* @param url endpoint
* @return 2 or 3
*/
- protected static String getKeystoneVersion(String url) {
+ protected static String getKeystoneVersion(@NotNull final String url) {
final String def = "3";
if (StringUtil.isEmpty(url)) {
return def;
diff --git a/cloud-openstack-server/src/main/java/jetbrains/buildServer/clouds/openstack/OpenstackCloudClient.java b/cloud-openstack-server/src/main/java/jetbrains/buildServer/clouds/openstack/OpenstackCloudClient.java
index f1e7a69..0790569 100644
--- a/cloud-openstack-server/src/main/java/jetbrains/buildServer/clouds/openstack/OpenstackCloudClient.java
+++ b/cloud-openstack-server/src/main/java/jetbrains/buildServer/clouds/openstack/OpenstackCloudClient.java
@@ -78,6 +78,7 @@ public OpenstackCloudClient(@NotNull final CloudClientParameters params, @NotNul
final String networkName = StringUtil.trim(entry.getValue().get("network"));
final String securityGroupName = StringUtil.trim(entry.getValue().get("security_group"));
final String keyPair = StringUtil.trim(entry.getValue().get("key_pair"));
+ final String volume = StringUtil.trim(entry.getValue().get("volume"));
final String userScriptPath = entry.getValue().get("user_script");
Boolean autoFloatingIp = (Boolean) (Object) entry.getValue().get("auto_floating_ip"); // Evil, but Yaml parse Boolean only for this
autoFloatingIp = ObjectUtils.chooseNotNull(autoFloatingIp, false); // Can be null if not defined
@@ -91,11 +92,13 @@ public OpenstackCloudClient(@NotNull final CloudClientParameters params, @NotNul
}
LOG.debug(String.format(
- "Adding cloud image: imageName=%s, openstackImageName=%s, flavorName=%s, networkName=%s, networkId=%s, securityGroupName=%s, keyPair=%s, floatingIp=%s",
- imageName, openstackImageName, flavorName, networkName, networkId, securityGroupName, keyPair, autoFloatingIp));
+ "Adding cloud image: imageName=%s, openstackImageName=%s, flavorName=%s, networkName=%s, networkId=%s, securityGroupName=%s, keyPair=%s, floatingIp=%s, volume=%s",
+ imageName, openstackImageName, flavorName, networkName, networkId, securityGroupName, keyPair, autoFloatingIp, volume));
- final OpenstackCloudImage image = new OpenstackCloudImage(openstackApi, imageIdGenerator.next(), imageName, openstackImageName,
- flavorName, autoFloatingIp, options, userScriptPath, serverPaths, factory.createExecutorService(imageName));
+ final OpenstackCloudImage image = new OpenstackCloudImage(new CreateImageOptions().openstackApi(openstackApi)
+ .imageId(imageIdGenerator.next()).imageName(imageName).openstackImageName(openstackImageName).flavorName(flavorName)
+ .volume(volume).autoFloatingIp(autoFloatingIp).userScriptPath(userScriptPath).serverPaths(serverPaths)
+ .createServerOptions(options).scheduledExecutorService(factory.createExecutorService(imageName)));
cloudImages.add(image);
diff --git a/cloud-openstack-server/src/main/java/jetbrains/buildServer/clouds/openstack/OpenstackCloudImage.java b/cloud-openstack-server/src/main/java/jetbrains/buildServer/clouds/openstack/OpenstackCloudImage.java
index 4e8c36b..42ee361 100644
--- a/cloud-openstack-server/src/main/java/jetbrains/buildServer/clouds/openstack/OpenstackCloudImage.java
+++ b/cloud-openstack-server/src/main/java/jetbrains/buildServer/clouds/openstack/OpenstackCloudImage.java
@@ -7,7 +7,6 @@
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
-import org.jclouds.openstack.nova.v2_0.features.ServerApi;
import org.jclouds.openstack.nova.v2_0.options.CreateServerOptions;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -32,6 +31,10 @@ public class OpenstackCloudImage implements CloudImage {
private final String openstackImageName;
@NotNull
private final String flavorName;
+ @Nullable
+ private final String volumeName;
+ @Nullable
+ private final String volumeDevice;
@NotNull
private final boolean autoFloatingIp;
@NotNull
@@ -50,20 +53,19 @@ public class OpenstackCloudImage implements CloudImage {
@Nullable
private final CloudErrorInfo errorInfo;
- public OpenstackCloudImage(@NotNull final OpenstackApi openstackApi, @NotNull final String imageId, @NotNull final String imageName,
- @NotNull final String openstackImageName, @NotNull final String flavorId, @NotNull boolean autoFloatingIp,
- @NotNull final CreateServerOptions options, @Nullable final String userScriptPath, @NotNull final ServerPaths serverPaths,
- @NotNull final ScheduledExecutorService executor) {
- this.openstackApi = openstackApi;
- this.imageId = imageId;
- this.imageName = imageName;
- this.openstackImageName = openstackImageName;
- this.flavorName = flavorId;
- this.autoFloatingIp = autoFloatingIp;
- this.options = options;
- this.userScriptPath = userScriptPath;
- this.serverPaths = serverPaths;
- this.executor = executor;
+ public OpenstackCloudImage(@NotNull final CreateImageOptions cio) {
+ this.openstackApi = cio.getOpenstackApi();
+ this.imageId = cio.getImageId();
+ this.imageName = cio.getImageName();
+ this.openstackImageName = cio.getOpenstackImageName();
+ this.flavorName = cio.getFlavorName();
+ this.volumeName = cio.getVolumeName();
+ this.volumeDevice = cio.getVolumeDevice();
+ this.autoFloatingIp = cio.isAutoFloatingIp();
+ this.userScriptPath = cio.getUserScriptPath();
+ this.serverPaths = cio.getServerPaths();
+ this.options = cio.getCreateServerOptions();
+ this.executor = cio.getScheduledExecutorService();
this.errorInfo = null; // FIXME: need to use this, really.
@@ -76,21 +78,48 @@ public OpenstackCloudImage(@NotNull final OpenstackApi openstackApi, @NotNull fi
}, true), 3, 3, TimeUnit.SECONDS);
}
+ private void forgetInstance(@NotNull final OpenstackCloudInstance instance) {
+ instances.remove(instance.getInstanceId());
+ }
+
+ @Nullable
+ public OpenstackCloudInstance findInstanceById(@NotNull final String instanceId) {
+ return instances.get(instanceId);
+ }
+
@NotNull
- public ServerApi getNovaServerApi() {
- return openstackApi.getNovaServerApi();
+ public Collection extends OpenstackCloudInstance> getInstances() {
+ return Collections.unmodifiableCollection(instances.values());
}
- public String getFloatingIpAvailable() {
- return openstackApi.getFloatingIpAvailable();
+ @Nullable
+ @Override
+ public Integer getAgentPoolId() {
+ return null;
}
- public void associateFloatingIp(String serverId, String ip) {
- openstackApi.associateFloatingIp(serverId, ip);
+ @Nullable
+ public CloudErrorInfo getErrorInfo() {
+ return errorInfo;
}
- private void forgetInstance(@NotNull final OpenstackCloudInstance instance) {
- instances.remove(instance.getInstanceId());
+ @NotNull
+ public synchronized OpenstackCloudInstance startNewInstance(@NotNull final CloudInstanceUserData data) {
+ final String instanceId = instanceIdGenerator.next();
+ final OpenstackCloudInstance instance = new OpenstackCloudInstance(this, instanceId, openstackApi, serverPaths, executor);
+
+ instances.put(instanceId, instance);
+ instance.start(data);
+
+ return instance;
+ }
+
+ void dispose() {
+ for (final OpenstackCloudInstance instance : instances.values()) {
+ instance.terminate();
+ }
+ instances.clear();
+ executor.shutdown();
}
@NotNull
@@ -138,43 +167,13 @@ public String getUserScriptPath() {
return this.userScriptPath;
}
- @NotNull
- public Collection extends OpenstackCloudInstance> getInstances() {
- return Collections.unmodifiableCollection(instances.values());
- }
-
- @Nullable
- public OpenstackCloudInstance findInstanceById(@NotNull final String instanceId) {
- return instances.get(instanceId);
- }
-
@Nullable
- @Override
- public Integer getAgentPoolId() {
- return null;
+ public String getVolumeName() {
+ return this.volumeName;
}
@Nullable
- public CloudErrorInfo getErrorInfo() {
- return errorInfo;
- }
-
- @NotNull
- public synchronized OpenstackCloudInstance startNewInstance(@NotNull final CloudInstanceUserData data) {
- final String instanceId = instanceIdGenerator.next();
- final OpenstackCloudInstance instance = new OpenstackCloudInstance(this, instanceId, serverPaths, executor);
-
- instances.put(instanceId, instance);
- instance.start(data);
-
- return instance;
- }
-
- void dispose() {
- for (final OpenstackCloudInstance instance : instances.values()) {
- instance.terminate();
- }
- instances.clear();
- executor.shutdown();
+ public String getVolumeDevice() {
+ return this.volumeDevice;
}
}
diff --git a/cloud-openstack-server/src/main/java/jetbrains/buildServer/clouds/openstack/OpenstackCloudInstance.java b/cloud-openstack-server/src/main/java/jetbrains/buildServer/clouds/openstack/OpenstackCloudInstance.java
index 8606b30..5303d71 100644
--- a/cloud-openstack-server/src/main/java/jetbrains/buildServer/clouds/openstack/OpenstackCloudInstance.java
+++ b/cloud-openstack-server/src/main/java/jetbrains/buildServer/clouds/openstack/OpenstackCloudInstance.java
@@ -9,12 +9,12 @@
import java.util.concurrent.atomic.AtomicReference;
import org.jclouds.openstack.nova.v2_0.domain.Server;
+import org.jclouds.openstack.nova.v2_0.domain.Server.Status;
import org.jclouds.openstack.nova.v2_0.domain.ServerCreated;
import org.jclouds.openstack.nova.v2_0.options.CreateServerOptions;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import com.google.common.base.Strings;
import com.intellij.openapi.diagnostic.Logger;
import jetbrains.buildServer.clouds.CloudConstants;
@@ -39,6 +39,8 @@ public class OpenstackCloudInstance implements CloudInstance {
@NotNull
private final OpenstackCloudImage cloudImage;
@NotNull
+ private final OpenstackApi openstackApi;
+ @NotNull
private final Date startDate;
@Nullable
private volatile CloudErrorInfo errorInfo;
@@ -46,12 +48,15 @@ public class OpenstackCloudInstance implements CloudInstance {
private ServerCreated serverCreated;
@NotNull
private final ScheduledExecutorService executor;
+
+ @Nullable
private String ip;
private final AtomicReference status = new AtomicReference<>(InstanceStatus.SCHEDULED_TO_START);
- public OpenstackCloudInstance(@NotNull final OpenstackCloudImage image, @NotNull final String instanceId, @NotNull ServerPaths serverPaths,
- @NotNull ScheduledExecutorService executor) {
+ public OpenstackCloudInstance(@NotNull final OpenstackCloudImage image, @NotNull final String instanceId,
+ @NotNull final OpenstackApi openstackApi, @NotNull ServerPaths serverPaths, @NotNull ScheduledExecutorService executor) {
+ this.openstackApi = openstackApi;
this.cloudImage = image;
this.instanceId = instanceId;
this.serverPaths = serverPaths;
@@ -63,7 +68,7 @@ public OpenstackCloudInstance(@NotNull final OpenstackCloudImage image, @NotNull
public synchronized void updateStatus() {
LOG.debug(String.format("Pinging %s for status", getName()));
if (serverCreated != null) {
- Server server = cloudImage.getNovaServerApi().get(serverCreated.getId());
+ Server server = openstackApi.getServer(serverCreated.getId());
if (server != null) {
Server.Status currentStatus = server.getStatus();
LOG.debug(String.format("Getting instance status from openstack for %s, result is %s", getName(), currentStatus));
@@ -137,6 +142,7 @@ public Date getStartedTime() {
return startDate;
}
+ @Nullable
public String getNetworkIdentity() {
return ip;
}
@@ -167,7 +173,7 @@ public void terminate() {
setStatus(InstanceStatus.SCHEDULED_TO_STOP);
try {
if (serverCreated != null) {
- cloudImage.getNovaServerApi().delete(serverCreated.getId());
+ openstackApi.deleteServer(serverCreated.getId());
setStatus(InstanceStatus.STOPPING);
}
} catch (final Exception e) {
@@ -189,69 +195,117 @@ public StartAgentCommand(@NotNull final CloudInstanceUserData data) {
this.userData = data;
}
- private byte[] readUserScriptFile(File userScriptFile) throws IOException {
- try {
- String userScript = FileUtil.readText(userScriptFile);
- // this is userScript actually, but CreateServerOptionscalls it userData
- return userScript.trim().getBytes(StandardCharsets.UTF_8);
- } catch (IOException e) {
- throw new IOException(String.format("Error in reading user script: %s", e.getMessage()), e);
- }
- }
-
public void run() {
try {
- String floatingIp = null;
- if (cloudImage.isAutoFloatingIp()) {
- // Floating ip should be in meta-data before instance start
- // If multiple instances start in parallel, perhaps same ip could be retrieved
- // So an ip reservation mechanism should implemented in this case
- LOG.debug("Retrieve floating ip for future instance association");
- floatingIp = cloudImage.getFloatingIpAvailable();
- if (StringUtil.isEmpty(floatingIp)) {
- throw new OpenstackException("Floating ip could not be found, cancel instance start");
- }
- LOG.debug(String.format("Floating ip: %s", floatingIp));
- userData.addAgentConfigurationParameter(OpenstackCloudParameters.AGENT_CLOUD_IP, floatingIp);
- }
+ // If floating ip, retrieve one and store in meta-data
+ String floatingIp = retrieveAndStoreInMetaDataFloatingIp();
- String openstackImageId = cloudImage.getOpenstackImageId();
- String flavorId = cloudImage.getFlavorId();
CreateServerOptions options = cloudImage.getImageOptions();
options.metadata(userData.getCustomAgentConfigurationParameters());
- // TODO: that code should be in OpenstackCloudImage but as we make it possible to change userScript without touching teamcity, that
- // hack takes place, sorry
- String userScriptPath = cloudImage.getUserScriptPath();
- if (!Strings.isNullOrEmpty(userScriptPath)) {
- File pluginData = serverPaths.getPluginDataDirectory();
- File userScriptFile = new File(new File(pluginData, OpenstackCloudParameters.PLUGIN_SHORT_NAME), userScriptPath);
- options.userData(readUserScriptFile(userScriptFile)).configDrive(true);
- }
+ // If user script, read it and store in meta-data
+ retrieveAndStoreInMetaDataUserScriptFile(options);
- LOG.debug(String.format("Creating openstack instance with flavorId=%s, imageId=%s, options=%s", flavorId, openstackImageId, options));
- serverCreated = cloudImage.getNovaServerApi().create(getName(), openstackImageId, flavorId, options);
-
- if (cloudImage.isAutoFloatingIp()) {
- LOG.debug(String.format("Associating floating ip to serverId %s", serverCreated.getId()));
- // Associating floating IP. Require fixed IP so wait until found
- final long maxWait = 120000;
- final long beginWait = System.currentTimeMillis();
- while (cloudImage.getNovaServerApi().get(serverCreated.getId()).getAddresses().isEmpty()) {
- if (System.currentTimeMillis() > (beginWait + maxWait)) {
- throw new OpenstackException(String.format("Waiting fixed ip fails, taking more than %s ms", maxWait));
- }
- LOG.debug(String.format("(Waiting fixed ip before floating ip association on serverId: %s)", serverCreated.getId()));
- Thread.sleep(1000);
- }
- cloudImage.associateFloatingIp(serverCreated.getId(), floatingIp);
- ip = floatingIp;
- }
+ LOG.debug(String.format("Creating openstack instance with flavorId=%s, imageId=%s, options=%s", cloudImage.getFlavorId(),
+ cloudImage.getOpenstackImageId(), options));
+ serverCreated = openstackApi.createServer(getName(), cloudImage.getOpenstackImageId(), cloudImage.getFlavorId(), options);
+
+ // Attach volume
+ attachVolume();
+
+ // Associate floating ip
+ ip = associateFloatingIp(floatingIp);
setStatus(InstanceStatus.STARTING);
} catch (final Exception e) {
processError(e);
}
}
+
+ private String associateFloatingIp(String floatingIp) throws InterruptedException, OpenstackException {
+ if (!cloudImage.isAutoFloatingIp()) {
+ return null;
+ }
+ LOG.debug(String.format("Associating floating ip to serverId %s", serverCreated.getId()));
+ // Associating floating IP. Require fixed IP so wait until found
+ final long maxWait = 120000;
+ final long beginWait = System.currentTimeMillis();
+ while (openstackApi.getServer(serverCreated.getId()).getAddresses().isEmpty()) {
+ if (System.currentTimeMillis() > (beginWait + maxWait)) {
+ throw new OpenstackException(String.format("Waiting fixed ip fails, taking more than %s ms", maxWait));
+ }
+ LOG.debug(String.format("(Waiting fixed ip before floating ip association on serverId: %s)", serverCreated.getId()));
+ Thread.sleep(1000);
+ }
+ openstackApi.associateFloatingIp(serverCreated.getId(), floatingIp); // NOSONAR : Floating IP can't be null here
+ return floatingIp;
+ }
+
+ private void attachVolume() throws OpenstackException, InterruptedException {
+ String volumeName = cloudImage.getVolumeName();
+ if (StringUtil.isEmpty(volumeName)) {
+ return;
+ }
+ String volumeDevice = cloudImage.getVolumeDevice();
+ LOG.debug(String.format("Attach volume '%s' (with device '%s') to serverId %s", volumeName, volumeDevice, serverCreated.getId()));
+ String volumeId = openstackApi.getVolumeIdByName(volumeName);
+ if (StringUtil.isEmpty(volumeId)) {
+ throw new OpenstackException(String.format("Volume Id can't be retrieved for volume name: %s", volumeName));
+ }
+ if (StringUtil.isEmpty(volumeDevice)) {
+ throw new OpenstackException(String.format("Volume device can't be empty for volume name: %s", volumeName));
+ }
+ while (!Status.ACTIVE.equals(openstackApi.getServer(serverCreated.getId()).getStatus())) {
+ final long maxWait = 120000;
+ final long beginWait = System.currentTimeMillis();
+ if (System.currentTimeMillis() > (beginWait + maxWait)) {
+ throw new OpenstackException(String.format("Waiting fixed ip fails, taking more than %s ms", maxWait));
+ }
+ LOG.debug(String.format("(Waiting instance available before volume attachment on serverId: %s)", serverCreated.getId()));
+ Thread.sleep(1000);
+ }
+ openstackApi.attachVolumeToServer(serverCreated.getId(), volumeId, volumeDevice);
+ }
+
+ private String retrieveAndStoreInMetaDataFloatingIp() throws OpenstackException {
+ if (!cloudImage.isAutoFloatingIp()) {
+ return null;
+ }
+ // Floating ip should be in meta-data before instance start
+ // If multiple instances start in parallel, perhaps same ip could be retrieved
+ // So an ip reservation mechanism should implemented in this case
+ LOG.debug("Retrieve floating ip for future instance association");
+ String floatingIp = openstackApi.getFloatingIpAvailable();
+ if (StringUtil.isEmpty(floatingIp)) {
+ throw new OpenstackException("Floating ip could not be found, cancel instance start");
+ }
+ LOG.debug(String.format("Floating ip: %s", floatingIp));
+ userData.addAgentConfigurationParameter(OpenstackCloudParameters.AGENT_CLOUD_IP, floatingIp);
+ return floatingIp;
+
+ }
+
+ private void retrieveAndStoreInMetaDataUserScriptFile(CreateServerOptions options) throws IOException {
+ // TODO: that code should be in OpenstackCloudImage but as we make it possible to change userScript without touching teamcity, that
+ // hack takes place, sorry
+ String userScriptPath = cloudImage.getUserScriptPath();
+ if (StringUtil.isEmpty(userScriptPath)) {
+ return;
+ }
+ LOG.debug(String.format("Read user script: %s", userScriptPath));
+ File pluginData = serverPaths.getPluginDataDirectory();
+ File userScriptFile = new File(new File(pluginData, OpenstackCloudParameters.PLUGIN_SHORT_NAME), userScriptPath);
+ options.userData(readUserScriptFile(userScriptFile)).configDrive(true);
+ }
+
+ private byte[] readUserScriptFile(File userScriptFile) throws IOException {
+ try {
+ String userScript = FileUtil.readText(userScriptFile);
+ // this is userScript actually, but CreateServerOptionscalls it userData
+ return userScript.trim().getBytes(StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new IOException(String.format("Error in reading user script: %s", e.getMessage()), e);
+ }
+ }
}
}