Skip to content

Commit 8d10ae1

Browse files
committed
Following changes are done:
1. new messageSubscriber to handle dns record create/delete event 2. instance dns record registration uses hostname, prepend random 3 char if conflict
1 parent 9d4e141 commit 8d10ae1

File tree

6 files changed

+116
-21
lines changed

6 files changed

+116
-21
lines changed

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,6 +1354,7 @@ public class ApiConstants {
13541354
public static final String CREDENTIALS = "credentials";
13551355
public static final String DNS_ZONE_ID = "dnszoneid";
13561356
public static final String DNS_ZONE = "dnszone";
1357+
public static final String DNS_RECORD = "dnsrecord";
13571358
public static final String DNS_SUB_DOMAIN = "dnssubdomain";
13581359
public static final String DNS_SERVER_ID = "dnsserverid";
13591360
public static final String CONTENT = "content";

api/src/main/java/org/apache/cloudstack/dns/DnsProvider.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
public interface DnsProvider extends Adapter {
2727

2828
interface Topics {
29-
String DNS_RECORD_DELETE = "dns.record.delete";
29+
String DNS_RECORD_LIFECYCLE = "dns.record.lifecycle";
3030
}
3131

3232
DnsProviderType getProviderType();
@@ -45,4 +45,5 @@ interface Topics {
4545
List<DnsRecord> listRecords(DnsServer server, DnsZone zone) throws DnsProviderException;
4646
String updateRecord(DnsServer server, DnsZone zone, DnsRecord record) throws DnsProviderException;
4747
String deleteRecord(DnsServer server, DnsZone zone, DnsRecord record) throws DnsProviderException;
48+
boolean dnsRecordExists(DnsServer server, DnsZone zone, String recordName, String recordType) throws DnsProviderException;
4849
}

plugins/dns/powerdns/src/main/java/org/apache/cloudstack/dns/powerdns/PowerDnsClient.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,26 @@ public Iterable<JsonNode> listRecords(String baseUrl, Integer port, String apiKe
240240
return rrsets.isArray() ? rrsets : Collections.emptyList();
241241
}
242242

243+
public boolean dnsRecordExists(String baseUrl, Integer port, String apiKey,
244+
String externalServerId, String zoneName,
245+
String recordName, String type) throws DnsProviderException {
246+
247+
validateServerId(baseUrl, port, apiKey, externalServerId);
248+
String normalizedZone = normalizeZone(zoneName);
249+
String normalizedRecord = normalizeRecordName(recordName, normalizedZone);
250+
String encodedZone = URLEncoder.encode(normalizedZone, StandardCharsets.UTF_8);
251+
String urlPath = "/servers/" + externalServerId + "/zones/" + encodedZone +
252+
"?rrset_name=" + URLEncoder.encode(normalizedRecord, StandardCharsets.UTF_8) +
253+
"&rrset_type=" + type.toUpperCase();
254+
HttpGet request = new HttpGet(buildUrl(baseUrl, port, urlPath));
255+
JsonNode zoneNode = execute(request, apiKey, 200);
256+
if (zoneNode == null || !zoneNode.has(ApiConstants.RR_SETS)) {
257+
return false;
258+
}
259+
JsonNode rrsets = zoneNode.path(ApiConstants.RR_SETS);
260+
return rrsets.isArray() && !rrsets.isEmpty();
261+
}
262+
243263
private JsonNode execute(HttpUriRequest request, String apiKey, int... expectedStatus) throws DnsProviderException {
244264
request.addHeader(ApiConstants.X_API_KEY, apiKey);
245265
request.addHeader("Accept", "application/json");

plugins/dns/powerdns/src/main/java/org/apache/cloudstack/dns/powerdns/PowerDnsProvider.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,11 @@ public List<DnsRecord> listRecords(DnsServer server, DnsZone zone) throws DnsPro
153153
return records;
154154
}
155155

156+
public boolean dnsRecordExists(DnsServer server, DnsZone zone, String recordName, String recordType) throws DnsProviderException {
157+
return client.dnsRecordExists(server.getUrl(), server.getPort(), server.getApiKey(),
158+
server.getExternalServerId(), zone.getName(), recordName, recordType);
159+
}
160+
156161
void validateRequiredServerAndZoneFields(DnsServer server, DnsZone zone) {
157162
validateRequiredServerFields(server);
158163
if (StringUtils.isBlank(zone.getName())) {

server/src/main/java/org/apache/cloudstack/dns/DnsProviderManagerImpl.java

Lines changed: 87 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,19 @@
1717

1818
package org.apache.cloudstack.dns;
1919

20+
import static com.cloud.event.EventTypes.EVENT_DNS_RECORD_CREATE;
21+
import static com.cloud.event.EventTypes.EVENT_DNS_RECORD_DELETE;
2022
import static com.cloud.event.EventTypes.EVENT_NIC_CREATE;
2123
import static com.cloud.event.EventTypes.EVENT_NIC_DELETE;
2224

2325
import java.util.ArrayList;
2426
import java.util.Collections;
27+
import java.util.HashMap;
2528
import java.util.List;
2629
import java.util.Map;
2730
import java.util.Objects;
2831
import java.util.Set;
32+
import java.util.UUID;
2933
import java.util.stream.Collectors;
3034

3135
import javax.inject.Inject;
@@ -69,6 +73,7 @@
6973
import org.apache.cloudstack.framework.messagebus.MessageSubscriber;
7074
import org.apache.cloudstack.framework.messagebus.PublishScope;
7175
import org.apache.commons.collections.CollectionUtils;
76+
import org.apache.commons.lang3.RandomStringUtils;
7277
import org.apache.logging.log4j.util.Strings;
7378
import org.springframework.stereotype.Component;
7479

@@ -457,6 +462,7 @@ public DnsRecordResponse createDnsRecord(CreateDnsRecordCmd cmd) {
457462
DnsProvider provider = getProviderByType(server.getProviderType());
458463
String normalizedRecordName = provider.addRecord(server, dnsZone, record);
459464
record.setName(normalizedRecordName);
465+
publishDnsRecordEventMessageBus(recordName, type, caller.getAccountId(), EVENT_DNS_RECORD_CREATE, normalizedContents);
460466
return createDnsRecordResponse(record);
461467
} catch (Exception ex) {
462468
logger.error("Failed to add DNS record via provider", ex);
@@ -474,15 +480,14 @@ public boolean deleteDnsRecord(DeleteDnsRecordCmd cmd) {
474480
Account caller = CallContext.current().getCallingAccount();
475481
accountMgr.checkAccess(caller, null, true, zone);
476482
DnsServerVO server = dnsServerDao.findById(zone.getDnsServerId());
483+
DnsRecord.RecordType recordType = cmd.getType();
477484
try {
478485
DnsRecord record = new DnsRecord();
479486
record.setName(cmd.getName());
480-
record.setType(cmd.getType());
487+
record.setType(recordType);
481488
DnsProvider provider = getProviderByType(server.getProviderType());
482489
String deletedDnsRecord = provider.deleteRecord(server, zone, record);
483-
if (deletedDnsRecord != null) {
484-
messageBus.publish(_name, DnsProvider.Topics.DNS_RECORD_DELETE, PublishScope.GLOBAL, deletedDnsRecord);
485-
}
490+
publishDnsRecordEventMessageBus(deletedDnsRecord, recordType, caller.getAccountId(), EVENT_DNS_RECORD_DELETE, null);
486491
return deletedDnsRecord != null;
487492
} catch (Exception ex) {
488493
logger.error("Failed to delete DNS record via provider", ex);
@@ -713,11 +718,7 @@ public void addDnsRecordForVM(VirtualMachine instance, Network network, Nic nic)
713718
logger.warn("DNS server is not found to process DNS record for Instance: {}", instance.getInstanceName());
714719
return;
715720
}
716-
// Construct FQDN Prefix (e.g., "instance-id.dnsZoneName" or "instance-id.subdomain.dnsZoneName")
717-
String recordName = String.valueOf(instance.getInstanceName());
718-
if (StringUtils.isNotBlank(dnsZoneNetworkMap.getSubDomain())) {
719-
recordName = String.join(".", recordName, dnsZoneNetworkMap.getSubDomain(), dnsZone.getName());
720-
}
721+
String recordName = finalizeDnsRecordNameForVm(instance, dnsZoneNetworkMap, server, dnsZone);
721722
String dnsRecordUrl = processDnsRecordInProvider(recordName, instance, server, dnsZone, nic, true);
722723
if (Strings.isBlank(dnsRecordUrl)) {
723724
logger.error("Failed to add DNS record in provider for Instance: {}", instance.getInstanceName());
@@ -726,6 +727,31 @@ public void addDnsRecordForVM(VirtualMachine instance, Network network, Nic nic)
726727
nicDetailsDao.addDetail(nic.getId(), ApiConstants.NIC_DNS_RECORD, dnsRecordUrl, true);
727728
}
728729

730+
private String finalizeDnsRecordNameForVm(VirtualMachine instance, DnsZoneNetworkMapVO dnsZoneNetworkMap, DnsServerVO server, DnsZoneVO dnsZone) {
731+
String recordName;
732+
// Construct FQDN Prefix (e.g., "hostname.dnsZoneName" or "hostname.subdomain.dnsZoneName")
733+
try {
734+
List<String> parts = new ArrayList<>();
735+
parts.add(instance.getHostName());
736+
if (StringUtils.isNotBlank(dnsZoneNetworkMap.getSubDomain())) {
737+
parts.add(dnsZoneNetworkMap.getSubDomain());
738+
}
739+
parts.add(dnsZone.getName());
740+
recordName = String.join(".", parts);
741+
742+
DnsProvider provider = getProviderByType(server.getProviderType());
743+
boolean dnsRecordExist = provider.dnsRecordExists(server, dnsZone, recordName, DnsRecord.RecordType.A.toString());
744+
if (dnsRecordExist) {
745+
String randomPrefix = RandomStringUtils.randomAlphanumeric(3).toLowerCase();
746+
recordName = randomPrefix + "-" + recordName;
747+
}
748+
} catch (Exception ex) {
749+
logger.error("Failed while constructing DNS record name for Instance: {} ", instance.getInstanceName(), ex);
750+
throw new CloudRuntimeException("Error occurred during DNS record registration for Instance: " + instance.getInstanceName());
751+
}
752+
return recordName;
753+
}
754+
729755
@Override
730756
public void deleteDnsRecordForVM(VirtualMachine instance, Network network, Nic nic) {
731757
String instanceName = instance.getInstanceName();
@@ -836,14 +862,7 @@ public boolean start() {
836862
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
837863
messageBus.subscribe(VirtualMachineManager.Topics.VM_LIFECYCLE, new VmLifecycleSubscriber());
838864
messageBus.subscribe(Nic.Topics.NIC_LIFECYCLE, new NicLifecycleSubscriber());
839-
messageBus.subscribe(DnsProvider.Topics.DNS_RECORD_DELETE, (senderAddress, subject, args) -> {
840-
try {
841-
String deletedDnsRecord = (String) args;
842-
nicDetailsDao.removeDetailsForValuesIn(ApiConstants.NIC_DNS_RECORD, Collections.singletonList(deletedDnsRecord));
843-
} catch (Exception ex) {
844-
logger.error("Failed to process DNS record deletion event", ex);
845-
}
846-
});
865+
messageBus.subscribe(DnsProvider.Topics.DNS_RECORD_LIFECYCLE, new DnsRecordLifecycleSubscriber());
847866
return true;
848867
}
849868

@@ -944,6 +963,36 @@ public void onPublishMessage(String senderAddress, String subject, Object args)
944963
}
945964
}
946965

966+
class DnsRecordLifecycleSubscriber implements MessageSubscriber {
967+
@Override
968+
public void onPublishMessage(String senderAddress, String subject, Object args) {
969+
try {
970+
logger.trace("DNS record lifecycle event: {}, {}, {}", senderAddress, subject, args);
971+
972+
@SuppressWarnings("unchecked")
973+
Map<String, Object> event = (Map<String, Object>) args;
974+
String eventType = (String) event.get(ApiConstants.EVENT_TYPE);
975+
String dnsRecord = (String) event.get(ApiConstants.DNS_RECORD);
976+
if (EVENT_DNS_RECORD_CREATE.equalsIgnoreCase(eventType)) {
977+
@SuppressWarnings("unchecked")
978+
List<String> contents = (List<String>) event.get(ApiConstants.CONTENTS);
979+
if (CollectionUtils.isNotEmpty(contents)) {
980+
for (String ipAddress : contents) {
981+
Nic nic = nicDao.findByIpAddressAndVmType(ipAddress, VirtualMachine.Type.User);
982+
if (nic != null) {
983+
nicDetailsDao.addDetail(nic.getId(), ApiConstants.NIC_DNS_RECORD, dnsRecord, true);
984+
}
985+
}
986+
}
987+
} else if (EVENT_DNS_RECORD_DELETE.equalsIgnoreCase(eventType)) {
988+
nicDetailsDao.removeDetailsForValuesIn(ApiConstants.NIC_DNS_RECORD, Collections.singletonList(dnsRecord));
989+
}
990+
} catch (Exception ex) {
991+
logger.error("Failed to process DNS record lifecycle event", ex);
992+
}
993+
}
994+
}
995+
947996
private void handleNicEvent(long nicId, long instanceId, boolean isAddDnsRecord) {
948997
VMInstanceVO vmInstanceVO = vmInstanceDao.findById(instanceId);
949998
if (vmInstanceVO == null) {
@@ -986,4 +1035,25 @@ void processEventForDnsRecord(VMInstanceVO vmInstanceVO, Network network, Nic ni
9861035
deleteDnsRecordForVM(vmInstanceVO, network, nic);
9871036
}
9881037
}
1038+
1039+
void publishDnsRecordEventMessageBus(String dnsRecord, DnsRecord.RecordType recordType, Long accountId,
1040+
String eventType, List<String> contents) {
1041+
1042+
// Only publish for A or AAAA records and non-null record name
1043+
if ((recordType != DnsRecord.RecordType.A && recordType != DnsRecord.RecordType.AAAA) || dnsRecord == null) {
1044+
return;
1045+
}
1046+
try {
1047+
Map<String, Object> event = new HashMap<>();
1048+
event.put(ApiConstants.EVENT_ID, UUID.randomUUID().toString());
1049+
event.put(ApiConstants.DNS_RECORD, dnsRecord);
1050+
event.put(ApiConstants.ACCOUNT_ID, accountId);
1051+
event.put(ApiConstants.EVENT_TYPE, eventType);
1052+
event.put(ApiConstants.CONTENTS, contents != null ? contents : Collections.emptyList());
1053+
event.put(ApiConstants.TIME_STAMP, System.currentTimeMillis());
1054+
messageBus.publish(_name, DnsProvider.Topics.DNS_RECORD_LIFECYCLE, PublishScope.GLOBAL, event);
1055+
} catch (Exception ex) {
1056+
logger.error("Failed to publish {} event for DNS record: {}", eventType, dnsRecord, ex);
1057+
}
1058+
}
9891059
}

ui/src/views/network/dns/DnsRecordsTab.vue

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,7 @@ export default {
187187
const params = {
188188
dnszoneid: this.resource.id,
189189
name: record.name,
190-
type: record.type,
191-
contents: record.contents.join(','),
192-
ttl: record.ttl
190+
type: record.type
193191
}
194192
console.log('DeleteDnsRecord params', params)
195193

0 commit comments

Comments
 (0)