Skip to content

Commit 0166076

Browse files
committed
fix for worker vm static IP
Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
1 parent 953973d commit 0166076

6 files changed

Lines changed: 305 additions & 38 deletions

File tree

plugins/integrations/veeam-control-service/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,10 @@
5757
<artifactId>jackson-dataformat-xml</artifactId>
5858
<version>${cs.jackson.version}</version>
5959
</dependency>
60+
<dependency>
61+
<groupId>com.fasterxml.jackson.dataformat</groupId>
62+
<artifactId>jackson-dataformat-yaml</artifactId>
63+
<version>${cs.jackson.version}</version>
64+
</dependency>
6065
</dependencies>
6166
</project>

plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/adapter/ServerAdapter.java

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.List;
2828
import java.util.Map;
2929
import java.util.Objects;
30+
import java.util.Optional;
3031
import java.util.Set;
3132
import java.util.UUID;
3233
import java.util.concurrent.TimeUnit;
@@ -133,6 +134,7 @@
133134
import org.apache.cloudstack.veeam.api.dto.Vm;
134135
import org.apache.cloudstack.veeam.api.dto.VmAction;
135136
import org.apache.cloudstack.veeam.api.dto.VnicProfile;
137+
import org.apache.cloudstack.veeam.utils.CloudConfigUtil;
136138
import org.apache.commons.collections.CollectionUtils;
137139
import org.apache.commons.collections.MapUtils;
138140
import org.apache.commons.lang3.ObjectUtils;
@@ -231,6 +233,7 @@ public class ServerAdapter extends ManagerBase {
231233
private static final String VM_TAG_KEY = "veeam_tag";
232234
private static final String WORKER_VM_GUEST_CPU_MODE = "host-passthrough";
233235
private static final String WORKER_VM_GUEST_OS = "Rocky Linux 9";
236+
private static final String WORKER_VM_IP_DETAIL = "worker.vm.ip";
234237
private static final String RESTORE_CONFIG = "restore.config";
235238

236239
@Inject
@@ -802,6 +805,19 @@ protected static Map<String, String> getDetailsForInstanceCreation(String userda
802805
return details;
803806
}
804807

808+
protected void saveInstanceAdditionalDetails(Vm request, UserVm vm) {
809+
if (request.isWorkerVm()) {
810+
Optional<String> ip = CloudConfigUtil.extractIpv4Address(
811+
request.getInitialization().getCustomScript());
812+
if (ip.isPresent() && StringUtils.isNotBlank(ip.get())) {
813+
logger.debug("Saving worker VM IP {} in details for {}", ip.get(), vm);
814+
vmInstanceDetailsDao.addDetail(vm.getId(), WORKER_VM_IP_DETAIL, ip.get(), false);
815+
}
816+
return;
817+
}
818+
saveInstanceRestoreConfig(request, vm);
819+
}
820+
805821
protected void saveInstanceRestoreConfig(Vm request, UserVm vm) {
806822
if (StringUtils.isBlank(request.getAccountId())) {
807823
return;
@@ -813,13 +829,18 @@ protected void saveInstanceRestoreConfig(Vm request, UserVm vm) {
813829
if (StringUtils.isBlank(restoreConfig)) {
814830
return;
815831
}
832+
logger.debug("Saving restore config: {} in details for {}", restoreConfig, vm);
816833
vmInstanceDetailsDao.addDetail(vm.getId(), RESTORE_CONFIG, restoreConfig, false);
817834
}
818835

819836
protected void removeInstanceRestoreConfig(UserVm vm) {
820837
vmInstanceDetailsDao.removeDetail(vm.getId(), RESTORE_CONFIG);
821838
}
822839

840+
protected void removeInstanceWorkerVmIp(UserVm vm) {
841+
vmInstanceDetailsDao.removeDetail(vm.getId(), WORKER_VM_IP_DETAIL);
842+
}
843+
823844
protected void processInstanceRestoreConfigIfNeeded(UserVm userVm, Volume volume) {
824845
VMInstanceDetailVO detail = vmInstanceDetailsDao.findDetail(userVm.getId(), RESTORE_CONFIG);
825846
if (detail == null) {
@@ -853,11 +874,11 @@ protected void processInstanceRestoreConfigIfNeeded(UserVm userVm, Volume volume
853874
sharedFSService.updateSharedFSPostRestore(sharedFS.getId(), volume.getId());
854875
}
855876

856-
protected Pair<String, String> getValidatedInstanceNicDetails(final VMInstanceDetailVO detail, final NetworkVO network) {
857-
if (ObjectUtils.anyNull(detail, network) || StringUtils.isBlank(detail.getValue())) {
877+
protected Pair<String, String> getValidatedInstanceNicDetails(final String config, final NetworkVO network) {
878+
if (ObjectUtils.anyNull(config, network) || StringUtils.isBlank(config)) {
858879
return new Pair<>(null, null);
859880
}
860-
Pair<String, String> result = OvfXmlUtil.getVmNicDetailFromStoredConfig(detail.getValue(), network.getUuid(), logger);
881+
Pair<String, String> result = OvfXmlUtil.getVmNicDetailFromStoredConfig(config, network.getUuid(), logger);
861882
String mac = StringUtils.trimToNull(result.first());
862883
String ip4Address = StringUtils.trimToNull(result.second());
863884
NicVO nic = null;
@@ -888,11 +909,10 @@ protected Pair<String, String> getValidatedInstanceNicDetails(final VMInstanceDe
888909
return new Pair<>(mac, ip4Address);
889910
}
890911

891-
protected void updateInstanceSecurityGroupsIfNeeded(final UserVmVO vmVo, final VMInstanceDetailVO detail, final NetworkVO network) {
892-
if (ObjectUtils.anyNull(detail, network) || StringUtils.isBlank(detail.getValue())) {
912+
protected void updateInstanceSecurityGroupsIfNeeded(final UserVmVO vmVo, final String config, final NetworkVO network) {
913+
if (ObjectUtils.anyNull(config, network) || StringUtils.isBlank(config)) {
893914
return;
894915
}
895-
String config = detail.getValue();
896916
Vm vm = OvfXmlUtil.parseVmRestoreConfig(config, logger);
897917
if (CollectionUtils.isEmpty(vm.getSecurityGroupIds())) {
898918
return;
@@ -1295,13 +1315,13 @@ public Vm createInstance(Vm request) {
12951315
if (request.getTemplate() != null && StringUtils.isNotEmpty(request.getTemplate().getId())) {
12961316
templateUuid = request.getTemplate().getId();
12971317
}
1298-
GuestOS guestOs = getGuestOsForInstance(request, StringUtils.isNotEmpty(userdata));
1318+
GuestOS guestOs = getGuestOsForInstance(request, request.isWorkerVm());
12991319
String instanceType = getValidatedInstanceType(request);
13001320
Pair<Vm, UserVm> result = createInstance(zone, clusterId, owner, ownerDetails.first(), ownerDetails.second(),
13011321
ownerDetails.third(), name, displayName, serviceOfferingUuid, cpu, memoryMB, templateUuid, guestOs,
13021322
userdata, bootOptions.first(), bootOptions.second(), request.getAffinityGroupId(),
13031323
request.getUserDataId(), request.getSshKeyPairNames(), instanceType, request.getDetails());
1304-
saveInstanceRestoreConfig(request, result.second());
1324+
saveInstanceAdditionalDetails(request, result.second());
13051325
return result.first();
13061326
}
13071327

@@ -1665,8 +1685,15 @@ public Nic attachInstanceNic(final String vmUuid, final Nic request) {
16651685
accountCannotAccessNetwork(networkVO, vmVo.getAccountId())) {
16661686
assignVmToAccount(vmVo, networkVO.getAccountId());
16671687
}
1668-
VMInstanceDetailVO detail = vmInstanceDetailsDao.findDetail(vmVo.getId(), RESTORE_CONFIG);
1669-
Pair<String, String> nicDetails = getValidatedInstanceNicDetails(detail, networkVO);
1688+
Map<String, String> details = vmInstanceDetailsDao.listDetailsKeyPairs(
1689+
vmVo.getId(), List.of(RESTORE_CONFIG, WORKER_VM_IP_DETAIL));
1690+
String restoreConfig = details.get(RESTORE_CONFIG);
1691+
Pair<String, String> nicDetails = getValidatedInstanceNicDetails(restoreConfig, networkVO);
1692+
String workerVmIp = details.get(WORKER_VM_IP_DETAIL);
1693+
if (StringUtils.isNotBlank(workerVmIp)) {
1694+
nicDetails = new Pair<>(null, workerVmIp);
1695+
removeInstanceWorkerVmIp(vmVo);
1696+
}
16701697
AddNicToVMCmd cmd = new AddNicToVMCmd();
16711698
ComponentContext.inject(cmd);
16721699
cmd.setVmId(vmVo.getId());
@@ -1681,7 +1708,7 @@ public Nic attachInstanceNic(final String vmUuid, final Nic request) {
16811708
if (nic == null) {
16821709
throw new CloudRuntimeException("Failed to attach NIC to VM");
16831710
}
1684-
updateInstanceSecurityGroupsIfNeeded(vmVo, detail, networkVO);
1711+
updateInstanceSecurityGroupsIfNeeded(vmVo, restoreConfig, networkVO);
16851712
return NicVOToNicConverter.toNic(nic, vmUuid, this::getNetworkById);
16861713
}
16871714

plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Vm.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,7 @@ public static class Initialization {
571571

572572
private String customScript;
573573
private Configuration configuration;
574+
private Boolean regenerateIds;
574575

575576
public String getCustomScript() {
576577
return customScript;
@@ -588,6 +589,14 @@ public void setConfiguration(Configuration configuration) {
588589
this.configuration = configuration;
589590
}
590591

592+
public Boolean isRegenerateIds() {
593+
return regenerateIds;
594+
}
595+
596+
public void setRegenerateIds(boolean regenerateIds) {
597+
this.regenerateIds = regenerateIds;
598+
}
599+
591600
@JsonInclude(JsonInclude.Include.NON_NULL)
592601
public static class Configuration {
593602

@@ -612,6 +621,14 @@ public void setType(String type) {
612621
}
613622
}
614623

624+
/**
625+
* Assumes that any request carrying a non-empty custom script represents a worker VM.
626+
*/
627+
@JsonIgnore
628+
public boolean isWorkerVm() {
629+
return initialization != null && StringUtils.isNotEmpty(initialization.getCustomScript());
630+
}
631+
615632
public static Vm of(String href, String id) {
616633
return withHrefAndId(new Vm(), href, id);
617634
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.apache.cloudstack.veeam.utils;
19+
20+
import java.util.List;
21+
import java.util.Map;
22+
import java.util.Optional;
23+
24+
import org.apache.commons.lang3.StringUtils;
25+
26+
import com.cloud.utils.net.NetUtils;
27+
import com.fasterxml.jackson.databind.ObjectMapper;
28+
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
29+
30+
public final class CloudConfigUtil {
31+
32+
private static final String NETWORK_CONFIG_PATH = "network_config.yml";
33+
private static final ObjectMapper YAML_MAPPER = new ObjectMapper(new YAMLFactory());
34+
35+
private CloudConfigUtil() {
36+
}
37+
38+
public static Optional<String> extractIpv4Address(String userData) {
39+
if (userData == null || userData.isBlank()) {
40+
return Optional.empty();
41+
}
42+
43+
String normalizedUserData = normalize(userData);
44+
45+
Map<String, Object> cloudConfig = parseYamlMap(normalizedUserData);
46+
if (cloudConfig == null) {
47+
return Optional.empty();
48+
}
49+
50+
Object writeFilesObj = cloudConfig.get("write_files");
51+
if (!(writeFilesObj instanceof List)) {
52+
return Optional.empty();
53+
}
54+
List<?> writeFiles = (List<?>) writeFilesObj;
55+
56+
for (Object item : writeFiles) {
57+
if (!(item instanceof Map)) {
58+
continue;
59+
}
60+
Map<?, ?> fileEntry = (Map<?, ?>) item;
61+
62+
Object pathObj = fileEntry.get("path");
63+
Object contentObj = fileEntry.get("content");
64+
String configPath = String.valueOf(pathObj);
65+
66+
if (StringUtils.isBlank(configPath) || !configPath.endsWith(NETWORK_CONFIG_PATH)) {
67+
continue;
68+
}
69+
70+
if (contentObj == null) {
71+
return Optional.empty();
72+
}
73+
74+
return extractIpv4FromNetworkConfig(String.valueOf(contentObj));
75+
}
76+
77+
return Optional.empty();
78+
}
79+
80+
private static Optional<String> extractIpv4FromNetworkConfig(String networkConfigYaml) {
81+
Map<String, Object> networkConfig = parseYamlMap(networkConfigYaml);
82+
if (networkConfig == null) {
83+
return Optional.empty();
84+
}
85+
86+
Object interfacesObj = networkConfig.get("interfaces");
87+
if (!(interfacesObj instanceof List)) {
88+
return Optional.empty();
89+
}
90+
List<?> interfaces = (List<?>) interfacesObj;
91+
92+
for (Object item : interfaces) {
93+
if (!(item instanceof Map)) {
94+
continue;
95+
}
96+
Map<?, ?> iface = (Map<?, ?>) item;
97+
98+
Object ipv4Obj = iface.get("ipv4");
99+
if (!(ipv4Obj instanceof Map)) {
100+
continue;
101+
}
102+
Map<?, ?> ipv4 = (Map<?, ?>) ipv4Obj;
103+
104+
Object enabledObj = ipv4.get("enabled");
105+
if (Boolean.FALSE.equals(enabledObj)) {
106+
continue;
107+
}
108+
109+
Object dhcpObj = ipv4.get("dhcp");
110+
if (Boolean.TRUE.equals(dhcpObj)) {
111+
continue;
112+
}
113+
114+
Object addressObj = ipv4.get("address");
115+
if (!(addressObj instanceof List)) {
116+
continue;
117+
}
118+
List<?> addresses = (List<?>) addressObj;
119+
120+
for (Object addressItem : addresses) {
121+
if (!(addressItem instanceof Map)) {
122+
continue;
123+
}
124+
Map<?, ?> address = (Map<?, ?>) addressItem;
125+
126+
Object ipObj = address.get("ip");
127+
if (ipObj != null && NetUtils.isIpv4(String.valueOf(ipObj))) {
128+
return Optional.of(String.valueOf(ipObj));
129+
}
130+
}
131+
}
132+
133+
return Optional.empty();
134+
}
135+
136+
@SuppressWarnings("unchecked")
137+
private static Map<String, Object> parseYamlMap(String yamlContent) {
138+
try {
139+
Object parsed = YAML_MAPPER.readValue(yamlContent, Object.class);
140+
if (parsed instanceof Map) {
141+
return (Map<String, Object>) parsed;
142+
}
143+
} catch (Exception ignored) {
144+
// Invalid or unsupported YAML
145+
}
146+
return null;
147+
}
148+
149+
private static String normalize(String data) {
150+
return data
151+
.replace("\\r\\n", "\n")
152+
.replace("\\n", "\n")
153+
.replace("\r\n", "\n")
154+
.replace("\r", "\n");
155+
}
156+
}

0 commit comments

Comments
 (0)