Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #15 - Cinder volume attachment #32

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(",");
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug Bug: A "NullPointerException" could be thrown; "volume" is nullable here. (squid:S2259)

See it in SonarCloud

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