Skip to content

Commit

Permalink
Fix #15 - Cinder volume attachment
Browse files Browse the repository at this point in the history
  • Loading branch information
axel3rd committed Aug 3, 2018
1 parent 97b6990 commit 7391e3c
Show file tree
Hide file tree
Showing 7 changed files with 388 additions and 125 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
```

```
Expand Down
5 changes: 5 additions & 0 deletions cloud-openstack-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@
<artifactId>openstack-neutron</artifactId>
<version>${jclouds.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jclouds.api</groupId>
<artifactId>openstack-cinder</artifactId>
<version>${jclouds.version}</version>
</dependency>

<dependency>
<groupId>org.jetbrains.teamcity</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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) {

Expand Down Expand Up @@ -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))
Expand All @@ -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))
Expand All @@ -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))
Expand All @@ -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())) {
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);

Expand Down
Loading

0 comments on commit 7391e3c

Please sign in to comment.