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

Add experimental support for IPv6 #78

Merged
merged 1 commit into from
Apr 29, 2024
Merged
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
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ Following attributes are **required** for each server template:
In both cases, selection of IP address can be specified as one of
- `Connect using private IPv4 address if available, otherwise using public IPv4 address`
- `Connect using public IPv4 address only`
- `Connect using public IPv6 address only`

- `Labels` - Labels that identifies jobs that could run on node created from this template.
Multiple values can be specified when separated by space.
When no labels are specified and usage mode is set to <strong>Use this node as much as possible</strong>,
Expand Down Expand Up @@ -138,9 +140,13 @@ These additional attributes can be specified, but are not required:
If no address is available or any error occurs, problem is logged, but provisioning of agent will continue without Primary IP being allocated.

- `Connectivity` - defines how network connectivity will be configured on newly created server
- `Only private networking will be used` - network ID or labels expression must also be provided
- `Only public networking will be allocated` - public IP address will be allocated to the server
- `Configure both private and public networking`
- `Only private networking will be used` - network ID or labels expression must be provided
- `Only public networking will be allocated` - public IPv4/IPv6 addresses will be allocated to the server
- `Only public IPv6 networking will be allocated` - public IPv6 address will be allocated to the server
- `Configure both private and public networking` - public IPv4/IPv6 addresses will be allocated. Network ID or labels expression must be provided.
- `Configure both private and public IPv6 networking` - public IPv6 address will be allocated. Network ID or labels expression must be provided.

Make sure this field is aligned with `Connection method`.

- `Automount volumes` - Auto-mount volumes after attach.

Expand Down
Binary file modified docs/server-detail.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@
<dependency>
<groupId>cloud.dnation.integration</groupId>
<artifactId>hetzner-cloud-client-java</artifactId>
<version>1.7.0</version>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import hudson.util.Secret;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import retrofit2.Call;
import retrofit2.Response;
Expand All @@ -56,6 +57,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -231,6 +233,55 @@
}
}

@SneakyThrows
private void configurePrivateNetwork(CreateServerRequest req, String network) {
if (!Strings.isNullOrEmpty(network)) {
final long networkId;
if (Helper.isPossiblyLong(network)) {
networkId = Long.parseLong(network);
} else {
networkId = getNetworkIdForLabelExpression(network);
}
req.setNetworks(Lists.newArrayList(networkId));
}
}

Check warning on line 247 in src/main/java/cloud/dnation/jenkins/plugins/hetzner/HetznerCloudResourceManager.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 236-247 are not covered by tests

@VisibleForTesting
static void customizeNetworking(ConnectivityType ct, CreateServerRequest req, String network,
BiConsumer<CreateServerRequest, String> privateNetConfig) throws IOException {
req.setPublicNet(new PublicNetRequest());
req.getPublicNet().setEnableIpv4(false);
req.getPublicNet().setEnableIpv6(false);
switch (ct) {

Check warning on line 255 in src/main/java/cloud/dnation/jenkins/plugins/hetzner/HetznerCloudResourceManager.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 255 is only partially covered, one branch is missing
case PRIVATE:
privateNetConfig.accept(req, network);
break;

case BOTH:
req.getPublicNet().setEnableIpv4(true);
req.getPublicNet().setEnableIpv6(true);
privateNetConfig.accept(req, network);
break;

case BOTH_V6:
req.getPublicNet().setEnableIpv6(true);
privateNetConfig.accept(req, network);
break;

case PUBLIC:
req.getPublicNet().setEnableIpv4(true);
req.getPublicNet().setEnableIpv6(true);
break;

case PUBLIC_V6:
req.getPublicNet().setEnableIpv6(true);
break;

default:
throw new IllegalArgumentException("Unknown connectivity type: " + ct);

Check warning on line 281 in src/main/java/cloud/dnation/jenkins/plugins/hetzner/HetznerCloudResourceManager.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 281 is not covered by tests
}
}

/**
* Create new server instance.
*
Expand Down Expand Up @@ -258,47 +309,33 @@
createServerRequest.setVolumes(Helper.idList(agent.getTemplate().getVolumeIds()));
}
final ConnectivityType ct = agent.getTemplate().getConnectivity().getType();
if (ct == ConnectivityType.BOTH || ct == ConnectivityType.PRIVATE) {
if (!Strings.isNullOrEmpty(agent.getTemplate().getNetwork())) {
final long networkId;
if (Helper.isPossiblyLong(agent.getTemplate().getNetwork())) {
networkId = Long.parseLong(agent.getTemplate().getNetwork());
} else {
networkId = getNetworkIdForLabelExpression(agent.getTemplate().getNetwork());
}
if (ct == ConnectivityType.PRIVATE) {
final PublicNetRequest pn = new PublicNetRequest();
pn.setEnableIpv4(false);
pn.setEnableIpv6(false);
createServerRequest.setPublicNet(pn);
}
createServerRequest.setNetworks(Lists.newArrayList(networkId));
}
}
customizeNetworking(ct, createServerRequest, agent.getTemplate().getNetwork(),
this::configurePrivateNetwork);
final String placementGroup = agent.getTemplate().getPlacementGroup();
if (!Strings.isNullOrEmpty(placementGroup)) {
if(Helper.isPossiblyLong(placementGroup)) {
createServerRequest.setPlacementGroup(Long.parseLong(placementGroup));
} else {
createServerRequest.setPlacementGroup(getPlacementGroupForLabelExpression(placementGroup));
}
}
if (!Strings.isNullOrEmpty(agent.getTemplate().getUserData())) {
createServerRequest.setUserData(agent.getTemplate().getUserData());
}
createServerRequest.setServerType(agent.getTemplate().getServerType());
createServerRequest.setImage(imageId);
createServerRequest.setName(agent.getNodeName());
createServerRequest.setSshKeys(Collections.singletonList(sshKey.getName()));
if (agent.getTemplate().getLocation().contains("-")) {
createServerRequest.setDatacenter(agent.getTemplate().getLocation());
} else {
createServerRequest.setLocation(agent.getTemplate().getLocation());
}
createServerRequest.setLabels(createLabelsForServer(agent.getTemplate().getCloud().name));
if (ct == ConnectivityType.BOTH || ct == ConnectivityType.PUBLIC) {
if (ct == ConnectivityType.BOTH || ct == ConnectivityType.PUBLIC_V6 || ct == ConnectivityType.PUBLIC) {
Optional.of(agent.getTemplate().getPrimaryIp()).ifPresent(ip -> ip.apply(proxy(), createServerRequest));
}
log.debug("Calling API to create server resource : {}", createServerRequest);

Check warning on line 338 in src/main/java/cloud/dnation/jenkins/plugins/hetzner/HetznerCloudResourceManager.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 312-338 are not covered by tests
final Response<CreateServerResponse> createServerResponse = proxy().createServer(createServerRequest)
.execute();
final HetznerServerInfo info = new HetznerServerInfo(sshKey);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2024 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.
*/
package cloud.dnation.jenkins.plugins.hetzner.connect;

import cloud.dnation.jenkins.plugins.hetzner.Messages;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.Descriptor;
import lombok.NoArgsConstructor;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;

@NoArgsConstructor(onConstructor = @__({@DataBoundConstructor}))
public class BothV6 extends AbstractConnectivity{
public ConnectivityType getType() {
return ConnectivityType.BOTH_V6;

Check warning on line 29 in src/main/java/cloud/dnation/jenkins/plugins/hetzner/connect/BothV6.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 26-29 are not covered by tests
}

@Extension
@Symbol("both-v6")
public static final class DescriptorImpl extends Descriptor<AbstractConnectivity> {
@NonNull
@Override
public String getDisplayName() {
return Messages.connectivity_bothV6();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@
public enum ConnectivityType {
PRIVATE,
PUBLIC,
BOTH
PUBLIC_V6,
BOTH,
BOTH_V6
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2024 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.
*/
package cloud.dnation.jenkins.plugins.hetzner.connect;

import cloud.dnation.jenkins.plugins.hetzner.Messages;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.Descriptor;
import lombok.NoArgsConstructor;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;

@NoArgsConstructor(onConstructor = @__({@DataBoundConstructor}))
public class PublicV6Only extends AbstractConnectivity {
public ConnectivityType getType() {
return ConnectivityType.PUBLIC_V6;

Check warning on line 29 in src/main/java/cloud/dnation/jenkins/plugins/hetzner/connect/PublicV6Only.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 26-29 are not covered by tests
}

@Extension
@Symbol("publicV6-only")
public static final class DescriptorImpl extends Descriptor<AbstractConnectivity> {
@NonNull
@Override
public String getDisplayName() {
return Messages.connectivity_publicV6_only();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2024 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.
*/
package cloud.dnation.jenkins.plugins.hetzner.launcher;

import cloud.dnation.hetznerclient.ServerDetail;
import cloud.dnation.jenkins.plugins.hetzner.Messages;
import com.google.common.base.Strings;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.Descriptor;
import lombok.NoArgsConstructor;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;

@NoArgsConstructor(onConstructor = @__({@DataBoundConstructor}))
public class PublicV6AddressOnly extends AbstractConnectionMethod {
@Override
public String getAddress(ServerDetail server) {
if (server.getPublicNet().getIpv6() == null || Strings.isNullOrEmpty(server.getPublicNet().getIpv6().getIp())) {

Check warning on line 32 in src/main/java/cloud/dnation/jenkins/plugins/hetzner/launcher/PublicV6AddressOnly.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 32 is only partially covered, one branch is missing
throw new IllegalArgumentException("Connection method requires IPv6 address");
}
// value returned by API ends with "::/64" so replace it with "1"
return server.getPublicNet().getIpv6().getIp().replaceFirst("::/64$", "::1");
}

@Extension
@Symbol("publicV6")
public static final class DescriptorImpl extends Descriptor<AbstractConnectionMethod> {
@NonNull
@Override
public String getDisplayName() {
return Messages.connection_method_publicV6();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@
<th>Public IPv4 address</th>
<td>${instance.publicNet.ipv4.ip}</td>
</tr>
<tr>
<th>Public IPv6 address</th>
<td>${instance.publicNet.ipv6.ip}</td>
</tr>
<j:choose>
<j:when test="${instance.privateNet != null and instance.privateNet.size() == 1}">
<tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ primaryip.bylabelselector.failing=Allocate primary IPv4 using label selector, fa
primaryip.bylabelselector.ignoring=Allocate primary IPv4 using label selector, ignore any error
connection.method.default=Connect using private IPv4 address if available, otherwise using public IPv4 address
connection.method.public=Connect using public IPv4 address only
connection.method.publicV6=Connect using public IPv6 address only
connectivity.private-only=Only private networking will be used. Network ID or label expression must be provided as well.
connectivity.public-only=Only public networking will be allocated
connectivity.both=Configure both private and public networking. Additional constrains may apply
connectivity.publicV6-only=Only public IPv6 networking will be allocated
connectivity.both=Configure both private and public networking. Additional constrains may apply
connectivity.bothV6=Configure both private network and public IPv6. Additional constrains may apply
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2024 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.
*/
package cloud.dnation.jenkins.plugins.hetzner;

import cloud.dnation.hetznerclient.CreateServerRequest;
import cloud.dnation.jenkins.plugins.hetzner.connect.ConnectivityType;
import org.junit.Test;

import java.io.IOException;

import static cloud.dnation.jenkins.plugins.hetzner.HetznerCloudResourceManager.customizeNetworking;
import static org.junit.Assert.assertEquals;

public class HetznerCloudResourceManagerTest {
@Test
public void testCustomizeNetworking() throws IOException {
CreateServerRequest req;

req = new CreateServerRequest();
customizeNetworking(ConnectivityType.BOTH, req, "", (s1, s2) -> {
});
assertEquals(true, req.getPublicNet().getEnableIpv4());
assertEquals(true, req.getPublicNet().getEnableIpv6());

req = new CreateServerRequest();
customizeNetworking(ConnectivityType.BOTH_V6, req, "", (s1, s2) -> {
});
assertEquals(false, req.getPublicNet().getEnableIpv4());
assertEquals(true, req.getPublicNet().getEnableIpv6());

req = new CreateServerRequest();
customizeNetworking(ConnectivityType.PUBLIC, req, "", (s1, s2) -> {
});
assertEquals(true, req.getPublicNet().getEnableIpv4());
assertEquals(true, req.getPublicNet().getEnableIpv6());

req = new CreateServerRequest();
customizeNetworking(ConnectivityType.PUBLIC_V6, req, "", (s1, s2) -> {
});
assertEquals(false, req.getPublicNet().getEnableIpv4());
assertEquals(true, req.getPublicNet().getEnableIpv6());

req = new CreateServerRequest();
customizeNetworking(ConnectivityType.PRIVATE, req, "", (s1, s2) -> {
});
assertEquals(false, req.getPublicNet().getEnableIpv4());
assertEquals(false, req.getPublicNet().getEnableIpv6());
}
}
Loading