Skip to content

Commit

Permalink
Support placement groups
Browse files Browse the repository at this point in the history
Closes #63
  • Loading branch information
rkosegi committed Nov 30, 2022
1 parent a083148 commit 27ae461
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 1 deletion.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ jenkins:
image: name=jenkins
mode: NORMAL
numExecutors: 1
placementGroup: "key1=value1&key2=value2"
connector:
root:
sshCredentialsId: 'ssh-private-key'
Expand All @@ -199,6 +200,7 @@ jenkins:
network: subsystem=cd
labelStr: java
numExecutors: 3
placementGroup: "1000656"
connectivity: "public-only"
connector:
root:
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@
<dependency>
<groupId>cloud.dnation.integration</groupId>
<artifactId>hetzner-cloud-client-java</artifactId>
<version>1.1.0</version>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import cloud.dnation.hetznerclient.GetLocationsResponse;
import cloud.dnation.hetznerclient.GetNetworkByIdResponse;
import cloud.dnation.hetznerclient.GetNetworksBySelectorResponse;
import cloud.dnation.hetznerclient.GetPlacementGroupByIdResponse;
import cloud.dnation.hetznerclient.GetPlacementGroupsResponse;
import cloud.dnation.hetznerclient.GetServerTypesResponse;
import cloud.dnation.hetznerclient.HetznerApi;
import com.google.common.base.Preconditions;
Expand Down Expand Up @@ -136,6 +138,28 @@ static ValidationResult verifyNetwork(String network, String credentialsId) {
}, credentialsId);
}

static ValidationResult verifyPlacementGroup(String placementGroup, String credentialsId) {
if (Strings.isNullOrEmpty(placementGroup)) {
return new ValidationResult(false, "Placement group expression is empty");
}
return validateWithClient(api -> {
if (Helper.isLabelExpression(placementGroup)) {
final GetPlacementGroupsResponse result = api.getPlacementGroups(placementGroup).execute().body();
Preconditions.checkArgument(result.getPlacementGroups().size() == 1,
"Expected exactly one result, got %s", result.getPlacementGroups().size());
return new ValidationResult(true, "Found: " +
result.getPlacementGroups().get(0).getName() + " " +
result.getPlacementGroups().get(0).getId());
} else if (Helper.isPossiblyInteger(placementGroup)) {
final GetPlacementGroupByIdResponse result = api.getPlacementGroupById(Integer.parseInt(placementGroup)).execute().body();
return new ValidationResult(true, "Found: " +
result.getPlacementGroup().getName() + " " + result.getPlacementGroup().getId());
} else {
return new ValidationResult(false, "Placement group expression unsupported : " + placementGroup);
}
}, credentialsId);
}

/**
* Attempt to validate given server type name.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import cloud.dnation.hetznerclient.CreateSshKeyResponse;
import cloud.dnation.hetznerclient.GetImagesBySelectorResponse;
import cloud.dnation.hetznerclient.GetNetworksBySelectorResponse;
import cloud.dnation.hetznerclient.GetPlacementGroupsResponse;
import cloud.dnation.hetznerclient.GetServerByIdResponse;
import cloud.dnation.hetznerclient.GetServersBySelectorResponse;
import cloud.dnation.hetznerclient.GetSshKeysBySelectorResponse;
Expand Down Expand Up @@ -136,6 +137,21 @@ private int getNetworkIdForLabelExpression(String labelExpression) throws IOExce
GetNetworksBySelectorResponse::getNetworks);
}

/**
* Attempt to obtain placement group based on label expression.
* It's expected that provided label expression resolves to single placement group.
*
* @param labelExpression label expression used to filter placement group
* @return placement group ID
* @throws IOException if fails to make API call
* @throws IllegalStateException if there was invalid response from API server
* @throws IllegalArgumentException if label expression didn't yield single placement group
*/
private int getPlacementGroupForLabelExpression(String labelExpression) throws IOException {
return searchResourceByLabelExpression(labelExpression, proxy()::getPlacementGroups,
GetPlacementGroupsResponse::getPlacementGroups);
}

private <R extends AbstractSearchResponse, I extends IdentifiableResource> int searchResourceByLabelExpression(
String labelExpression,
Function<String, Call<R>> searchFunction,
Expand Down Expand Up @@ -254,6 +270,14 @@ public HetznerServerInfo createServer(HetznerServerAgent agent) {
createServerRequest.setNetworks(Lists.newArrayList(networkId));
}
}
final String placementGroup = agent.getTemplate().getPlacementGroup();
if (!Strings.isNullOrEmpty(placementGroup)) {
if(Helper.isPossiblyInteger(placementGroup)) {
createServerRequest.setPlacementGroup(Integer.parseInt(placementGroup));
} else {
createServerRequest.setPlacementGroup(getPlacementGroupForLabelExpression(placementGroup));
}
}
createServerRequest.setServerType(agent.getTemplate().getServerType());
createServerRequest.setImage(imageId);
createServerRequest.setName(agent.getNodeName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import static cloud.dnation.jenkins.plugins.hetzner.ConfigurationValidator.verifyImage;
import static cloud.dnation.jenkins.plugins.hetzner.ConfigurationValidator.verifyLocation;
import static cloud.dnation.jenkins.plugins.hetzner.ConfigurationValidator.verifyNetwork;
import static cloud.dnation.jenkins.plugins.hetzner.ConfigurationValidator.verifyPlacementGroup;
import static cloud.dnation.jenkins.plugins.hetzner.ConfigurationValidator.verifyServerType;
import static cloud.dnation.jenkins.plugins.hetzner.Helper.getStringOrDefault;
import static cloud.dnation.jenkins.plugins.hetzner.HetznerConstants.DEFAULT_REMOTE_FS;
Expand Down Expand Up @@ -89,6 +90,10 @@ public class HetznerServerTemplate extends AbstractDescribableImpl<HetznerServer
@Getter
private String remoteFs;

@Setter(onMethod = @__({@DataBoundSetter}))
@Getter
private String placementGroup;

@Setter(onMethod = @__({@DataBoundSetter}))
@Getter
private String jvmOpts;
Expand Down Expand Up @@ -153,6 +158,9 @@ protected Object readResolve() {
if (connectivity == null ) {
connectivity = HetznerConstants.DEFAULT_CONNECTIVITY;
}
if (placementGroup == null) {
placementGroup = "";
}
return this;
}

Expand Down Expand Up @@ -205,6 +213,13 @@ public FormValidation doVerifyNetwork(@QueryParameter String network,
return verifyNetwork(network, credentialsId).toFormValidation();
}

@Restricted(NoExternalUse.class)
@RequirePOST
public FormValidation doVerifyPlacementGroup(@QueryParameter String placementGroup,
@QueryParameter String credentialsId) {
return verifyPlacementGroup(placementGroup, credentialsId).toFormValidation();
}

@Restricted(NoExternalUse.class)
@RequirePOST
public FormValidation doVerifyServerType(@QueryParameter String serverType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@
<f:entry title="${%Number of Executors}" field="numExecutors">
<f:textbox default="1"/>
</f:entry>
<f:entry title="${%Placement group}" default="" field="placementGroup">
<f:textbox/>
<f:validateButton title="Verify placement group" progress="${%Testing...}" method="verifyPlacementGroup"
with="placementGroup,credentialsId"/>
</f:entry>
<f:dropdownDescriptorSelector field="shutdownPolicy" title="Shutdown policy"/>

<f:dropdownDescriptorSelector field="primaryIp" title="Primary IP" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!--
Copyright 2022 https://dnation.cloud
Licensed 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.
-->
<div>
ID of <a href="https://docs.hetzner.com/cloud/placement-groups/overview/">placement group</a>
or label expression that resolves to single placement group
To obtain list of all placement groups, use following <code>curl</code> command:
<p>
<pre>
curl -H "Authorization: Bearer $API_TOKEN" https://api.hetzner.cloud/v1/placement_groups
</pre>
<p></p>
<a href="https://docs.hetzner.cloud/#placement-groups">API documentation</a>

</div>

0 comments on commit 27ae461

Please sign in to comment.