1. handle vm hostname change for dns registration
2. generate event for dns name collision
3. remove dns record lifecycle subscriber
This commit is contained in:
Manoj Kumar 2026-04-10 12:35:48 +05:30
parent 356781972e
commit 883dc32abf
No known key found for this signature in database
GPG Key ID: E952B7234D2C6F88
8 changed files with 159 additions and 209 deletions

View File

@ -877,6 +877,7 @@ public class EventTypes {
public static final String EVENT_DNS_ZONE_DELETE = "DNS.ZONE.DELETE";
public static final String EVENT_DNS_RECORD_CREATE = "DNS.RECORD.CREATE";
public static final String EVENT_DNS_RECORD_DELETE = "DNS.RECORD.DELETE";
public static final String EVENT_DNS_NAME_COLLISION = "DNS.NAME.COLLISION";
static {

View File

@ -1376,6 +1376,7 @@ public class ApiConstants {
public static final String INSTANCE_ID = "instanceId";
public static final String OLD_STATE = "oldState";
public static final String NEW_STATE = "newState";
public static final String OLD_HOST_NAME = "oldHostName";
public static final String PARAMETER_DESCRIPTION_ACTIVATION_RULE = "Quota tariff's activation rule. It can receive a JS script that results in either " +

View File

@ -24,11 +24,6 @@ import org.apache.cloudstack.dns.exception.DnsProviderException;
import com.cloud.utils.component.Adapter;
public interface DnsProvider extends Adapter {
interface Topics {
String DNS_RECORD_LIFECYCLE = "dns.record.lifecycle";
}
DnsProviderType getProviderType();
// Validates connectivity to the server

View File

@ -37,12 +37,9 @@ import org.apache.cloudstack.api.response.DnsZoneNetworkMapResponse;
import org.apache.cloudstack.api.response.DnsZoneResponse;
import org.apache.cloudstack.api.response.ListResponse;
import com.cloud.network.Network;
import com.cloud.user.Account;
import com.cloud.utils.component.Manager;
import com.cloud.utils.component.PluggableService;
import com.cloud.vm.Nic;
import com.cloud.vm.VirtualMachine;
public interface DnsProviderManager extends Manager, PluggableService {
@ -75,8 +72,6 @@ public interface DnsProviderManager extends Manager, PluggableService {
boolean disassociateZoneFromNetwork(DisassociateDnsZoneFromNetworkCmd cmd);
void deleteDnsRecordForVM(VirtualMachine instance, Network network, Nic nic);
void checkDnsServerPermission(Account caller, DnsServer dnsServer);
void checkDnsZonePermission(Account caller, DnsZone dnsZone);

View File

@ -112,7 +112,8 @@ public interface VirtualMachineManager extends Manager {
interface Topics {
String VM_POWER_STATE = "vm.powerstate";
String VM_LIFECYCLE = "vm.lifecycle";
String VM_LIFECYCLE_STATE = "vm.lifecycle.state";
String VM_ACTION = "vm.action";
}
/**

View File

@ -18,10 +18,12 @@ package com.cloud.vm;
import static com.cloud.event.EventTypes.EVENT_NIC_CREATE;
import static com.cloud.event.EventTypes.EVENT_NIC_DELETE;
import static com.cloud.event.EventTypes.EVENT_VM_UPDATE;
import static com.cloud.hypervisor.Hypervisor.HypervisorType.Functionality;
import static com.cloud.storage.Volume.IOPS_LIMIT;
import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
import static com.cloud.vm.VirtualMachineManager.Topics.VM_LIFECYCLE;
import static com.cloud.vm.VirtualMachineManager.Topics.VM_ACTION;
import static com.cloud.vm.VirtualMachineManager.Topics.VM_LIFECYCLE_STATE;
import static org.apache.cloudstack.api.ApiConstants.MAX_IOPS;
import static org.apache.cloudstack.api.ApiConstants.MIN_IOPS;
@ -163,6 +165,7 @@ import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.logging.log4j.util.Strings;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
@ -1554,7 +1557,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
event.put(ApiConstants.OLD_STATE, oldState != null ? oldState : State.Unknown);
event.put(ApiConstants.NEW_STATE, newState);
event.put(ApiConstants.TIME_STAMP, System.currentTimeMillis());
messageBus.publish(_name, VM_LIFECYCLE, PublishScope.GLOBAL, event);
messageBus.publish(_name, VM_LIFECYCLE_STATE, PublishScope.GLOBAL, event);
} catch (Exception ex) {
logger.warn("Failed to publish lifecycle event for Instance: {}", instance.getUuid(), ex);
}
@ -1576,6 +1579,24 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
}
private void publishVmHostNameUpdateMessageBus(long instanceId, String oldHostName, String hostName) {
if (Strings.isBlank(hostName) || oldHostName.equalsIgnoreCase(hostName)) {
return;
}
try {
Map<String, Object> event = new HashMap<>();
event.put(ApiConstants.EVENT_ID, UUID.randomUUID().toString());
event.put(ApiConstants.INSTANCE_ID, instanceId);
event.put(ApiConstants.OLD_HOST_NAME, oldHostName);
event.put(ApiConstants.HOST_NAME, hostName);
event.put(ApiConstants.EVENT_TYPE, EVENT_VM_UPDATE);
event.put(ApiConstants.TIME_STAMP, System.currentTimeMillis());
messageBus.publish(_name, VM_ACTION, PublishScope.GLOBAL, event);
} catch (Exception ex) {
logger.error("Failed to publish Instance action event for ID: {}", instanceId, ex);
}
}
/**
* Set NIC as default if VM has no default NIC
* @param vmInstance VM instance to be checked
@ -2964,7 +2985,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_UPDATE, eventDescription = "updating Vm")
@ActionEvent(eventType = EVENT_VM_UPDATE, eventDescription = "updating Vm")
public UserVm updateVirtualMachine(UpdateVMCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException {
validateInputsAndPermissionForUpdateVirtualMachineCommand(cmd);
@ -3340,6 +3361,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
if (State.Running == vm.getState()) {
publishVmHostNameUpdateMessageBus(vm.getId(), vm.getHostName(), hostName);
updateDns(vm, hostName);
}

View File

@ -17,24 +17,23 @@
package org.apache.cloudstack.dns;
import static com.cloud.event.EventTypes.EVENT_DNS_RECORD_CREATE;
import static com.cloud.event.EventTypes.EVENT_DNS_RECORD_DELETE;
import static com.cloud.event.EventTypes.EVENT_NIC_CREATE;
import static com.cloud.event.EventTypes.EVENT_NIC_DELETE;
import static com.cloud.event.EventTypes.EVENT_VM_UPDATE;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.command.user.dns.AddDnsServerCmd;
import org.apache.cloudstack.api.command.user.dns.AssociateDnsZoneToNetworkCmd;
@ -74,21 +73,21 @@ import org.apache.cloudstack.dns.vo.DnsZoneNetworkMapVO;
import org.apache.cloudstack.dns.vo.DnsZoneVO;
import org.apache.cloudstack.framework.messagebus.MessageBus;
import org.apache.cloudstack.framework.messagebus.MessageSubscriber;
import org.apache.cloudstack.framework.messagebus.PublishScope;
import org.apache.commons.collections.CollectionUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.stereotype.Component;
import com.cloud.domain.Domain;
import com.cloud.domain.dao.DomainDao;
import com.cloud.event.ActionEvent;
import com.cloud.event.ActionEventUtils;
import com.cloud.event.EventTypes;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.network.Network;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.User;
import com.cloud.user.dao.AccountDao;
import com.cloud.utils.Pair;
import com.cloud.utils.StringUtils;
@ -101,7 +100,6 @@ import com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn;
import com.cloud.utils.db.TransactionStatus;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.Nic;
import com.cloud.vm.NicDetailVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachineManager;
import com.cloud.vm.dao.NicDao;
@ -460,13 +458,20 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
}
try {
DnsRecord.RecordType type = cmd.getType();
DnsProvider provider = getProviderByType(server.getProviderType());
if (provider.dnsRecordExists(server, dnsZone, recordName, type.name())) {
throw new CloudRuntimeException(String.format(
"DNS record '%s' of type '%s' already exists in DNS zone '%s' (provider: %s). Overwriting is not permitted.",
recordName, type.name(), dnsZone.getName(), provider.getName()
));
}
List<String> normalizedContents = cmd.getContents().stream()
.map(value -> DnsProviderUtil.normalizeDnsRecordValue(value, type)).collect(Collectors.toList());
DnsRecord record = new DnsRecord(recordName, type, normalizedContents, cmd.getTtl());
DnsProvider provider = getProviderByType(server.getProviderType());
String normalizedRecordName = provider.addRecord(server, dnsZone, record);
record.setName(normalizedRecordName);
publishDnsRecordEventMessageBus(recordName, type, caller.getAccountId(), EVENT_DNS_RECORD_CREATE, normalizedContents);
return createDnsRecordResponse(record);
} catch (Exception ex) {
logger.error("Failed to add DNS record via provider", ex);
@ -477,21 +482,20 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
@Override
@ActionEvent(eventType = EventTypes.EVENT_DNS_RECORD_DELETE, eventDescription = "Deleting DNS Record")
public boolean deleteDnsRecord(DeleteDnsRecordCmd cmd) {
DnsZoneVO zone = dnsZoneDao.findById(cmd.getDnsZoneId());
if (zone == null) {
DnsZoneVO dnsZone = dnsZoneDao.findById(cmd.getDnsZoneId());
if (dnsZone == null) {
throw new InvalidParameterValueException("DNS zone not found.");
}
Account caller = CallContext.current().getCallingAccount();
accountMgr.checkAccess(caller, null, true, zone);
DnsServerVO server = dnsServerDao.findById(zone.getDnsServerId());
accountMgr.checkAccess(caller, null, true, dnsZone);
DnsServerVO server = dnsServerDao.findById(dnsZone.getDnsServerId());
DnsRecord.RecordType recordType = cmd.getType();
try {
DnsRecord record = new DnsRecord();
record.setName(cmd.getName());
record.setType(recordType);
DnsProvider provider = getProviderByType(server.getProviderType());
String deletedDnsRecord = provider.deleteRecord(server, zone, record);
publishDnsRecordEventMessageBus(deletedDnsRecord, recordType, caller.getAccountId(), EVENT_DNS_RECORD_DELETE, null);
String deletedDnsRecord = provider.deleteRecord(server, dnsZone, record);
return deletedDnsRecord != null;
} catch (Exception ex) {
logger.error("Failed to delete DNS record via provider", ex);
@ -705,77 +709,6 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
return dnsZoneNetworkMapDao.remove(mapping.getId());
}
@Override
public void deleteDnsRecordForVM(VirtualMachine instance, Network network, Nic nic) {
String instanceName = instance.getInstanceName();
NicDetailVO nicDetailVO = nicDetailsDao.findDetail(nic.getId(), ApiConstants.NIC_DNS_RECORD);
if (nicDetailVO == null || Strings.isBlank(nicDetailVO.getValue())) {
logger.debug("No DNS record found for Instance: {}", instance.getInstanceName());
return;
}
String dnsRecord = nicDetailVO.getValue();
try {
DnsZoneNetworkMapVO dnsZoneNetworkMap = dnsZoneNetworkMapDao.findByNetworkId(network.getId());
DnsZoneVO dnsZone = null;
DnsServerVO dnsServer = null;
if (dnsZoneNetworkMap != null) {
dnsZone = dnsZoneDao.findById(dnsZoneNetworkMap.getDnsZoneId());
}
if (dnsZone != null) {
dnsServer = dnsServerDao.findById(dnsZone.getDnsServerId());
}
if (dnsServer != null) {
processDnsRecordInProvider(dnsRecord, instance, dnsServer, dnsZone, nic, false);
} else {
logger.warn("Skipping deletion of DNS record: {} from provider for Instance: {}.", dnsRecord, instanceName);
}
} catch (Exception ex) {
logger.error("Failed deleting DNS record: {} for Instance: {}, proceeding with DB cleanup.", dnsRecord, instanceName);
} finally {
nicDetailsDao.removeDetail(nic.getId(), ApiConstants.NIC_DNS_RECORD);
logger.debug("Removed DNS record from DB for Instance: {}, NIC ID: {}", instanceName, nic.getUuid());
}
}
private String processDnsRecordInProvider(String recordName, VirtualMachine instance, DnsServer server, DnsZone dnsZone,
Nic nic, boolean isAdd) {
try {
DnsProvider provider = getProviderByType(server.getProviderType());
// Handle IPv4 (A Record)
String ipv4DnsRecord = null;
if (nic.getIPv4Address() != null) {
DnsRecord recordA = new DnsRecord(recordName, DnsRecord.RecordType.A, Collections.singletonList(nic.getIPv4Address()), 3600);
if (isAdd) {
ipv4DnsRecord = provider.addRecord(server, dnsZone, recordA);
} else {
ipv4DnsRecord = provider.deleteRecord(server, dnsZone, recordA);
}
}
// Handle IPv6 (AAAA Record) if it exists
String ipv6DnsRecord = null;
if (nic.getIPv6Address() != null) {
DnsRecord recordAAAA = new DnsRecord(recordName, DnsRecord.RecordType.AAAA, Collections.singletonList(nic.getIPv6Address()), 3600);
if (isAdd) {
ipv6DnsRecord = provider.addRecord(server, dnsZone, recordAAAA);
} else {
ipv6DnsRecord = provider.deleteRecord(server, dnsZone, recordAAAA);
}
}
return ipv4DnsRecord != null ? ipv4DnsRecord : ipv6DnsRecord;
} catch (Exception ex) {
logger.error(
"Failed to {} DNS record for Instance {} in zone {}",
isAdd ? "register" : "remove",
instance.getInstanceName(),
dnsZone.getName(),
ex
);
}
return null;
}
@Override
public void checkDnsServerPermission(Account caller, DnsServer dnsServer) throws PermissionDeniedException {
if (caller.getId() == dnsServer.getAccountId()) {
@ -813,9 +746,9 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
@Override
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
messageBus.subscribe(VirtualMachineManager.Topics.VM_LIFECYCLE, new VmLifecycleSubscriber());
messageBus.subscribe(VirtualMachineManager.Topics.VM_LIFECYCLE_STATE, new VmLifecycleSubscriber());
messageBus.subscribe(Nic.Topics.NIC_LIFECYCLE, new NicLifecycleSubscriber());
messageBus.subscribe(DnsProvider.Topics.DNS_RECORD_LIFECYCLE, new DnsRecordLifecycleSubscriber());
messageBus.subscribe(VirtualMachineManager.Topics.VM_ACTION, new VmRenameActionSubscriber());
return true;
}
@ -916,57 +849,33 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
}
}
class DnsRecordLifecycleSubscriber implements MessageSubscriber {
class VmRenameActionSubscriber implements MessageSubscriber {
@Override
public void onPublishMessage(String senderAddress, String subject, Object args) {
try {
logger.trace("DNS record lifecycle event: {}, {}, {}", senderAddress, subject, args);
logger.trace("VM action event: {}, {}, {}", senderAddress, subject, args);
@SuppressWarnings("unchecked")
Map<String, Object> event = (Map<String, Object>) args;
String eventType = (String) event.get(ApiConstants.EVENT_TYPE);
String dnsRecord = (String) event.get(ApiConstants.DNS_RECORD);
if (EVENT_DNS_RECORD_CREATE.equalsIgnoreCase(eventType)) {
@SuppressWarnings("unchecked")
List<String> contents = (List<String>) event.get(ApiConstants.CONTENTS);
if (CollectionUtils.isNotEmpty(contents)) {
for (String ipAddress : contents) {
Nic nic = nicDao.findByIpAddressAndVmType(ipAddress, VirtualMachine.Type.User);
if (nic != null) {
nicDetailsDao.addDetail(nic.getId(), ApiConstants.NIC_DNS_RECORD, dnsRecord, true);
}
}
}
} else if (EVENT_DNS_RECORD_DELETE.equalsIgnoreCase(eventType)) {
nicDetailsDao.removeDetailsForValuesIn(ApiConstants.NIC_DNS_RECORD, Collections.singletonList(dnsRecord));
if (!eventType.equalsIgnoreCase(EVENT_VM_UPDATE)) {
return;
}
String newHostName = (String) event.get(ApiConstants.HOST_NAME);
String oldHostName = (String) event.get(ApiConstants.OLD_HOST_NAME);
if (oldHostName.equalsIgnoreCase(newHostName)) {
logger.debug("Instance hostname is unchanged, skip event processing");
return;
}
long instanceId = (long) event.get(ApiConstants.INSTANCE_ID);
handleVmHostnameChanged(instanceId, newHostName);
} catch (Exception ex) {
logger.error("Failed to process DNS record lifecycle event", ex);
logger.error("Failed to process Instance action event", ex);
}
}
}
void publishDnsRecordEventMessageBus(String dnsRecord, DnsRecord.RecordType recordType, Long accountId,
String eventType, List<String> contents) {
// Only publish for A or AAAA records and non-null record name
if ((recordType != DnsRecord.RecordType.A && recordType != DnsRecord.RecordType.AAAA) || dnsRecord == null) {
return;
}
try {
Map<String, Object> event = new HashMap<>();
event.put(ApiConstants.EVENT_ID, UUID.randomUUID().toString());
event.put(ApiConstants.DNS_RECORD, dnsRecord);
event.put(ApiConstants.ACCOUNT_ID, accountId);
event.put(ApiConstants.EVENT_TYPE, eventType);
event.put(ApiConstants.CONTENTS, contents != null ? contents : Collections.emptyList());
event.put(ApiConstants.TIME_STAMP, System.currentTimeMillis());
messageBus.publish(_name, DnsProvider.Topics.DNS_RECORD_LIFECYCLE, PublishScope.GLOBAL, event);
} catch (Exception ex) {
logger.error("Failed to publish {} event for DNS record: {}", eventType, dnsRecord, ex);
}
}
void handleVmRunningState(long instanceId) {
VirtualMachine instance = vmInstanceDao.findById(instanceId);
if (instance == null) {
@ -999,16 +908,13 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
Transaction.execute(new TransactionCallbackWithExceptionNoReturn<DnsProviderException>() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) throws DnsProviderException{
DnsNicJoinVO existing = dnsNicJoinDao.findActiveByDnsRecordAndZone(dnsRecordUrl, targetZoneId);
if (existing != null && existing.getInstanceId() != instanceId) {
logger.error("DNS Collision: Cannot register DNS record: {}. Already owned by Instance: {}.",
dnsRecordUrl, existing.getInstanceId());
if (isDnsCollision(dnsRecordUrl, targetZoneId, instanceId)) {
return;
}
for (DnsNicJoinVO nic : nicsForThisFqdn) {
nicDetailsDao.addDetail(nic.getId(), ApiConstants.NIC_DNS_RECORD, dnsRecordUrl, true);
}
syncDnsState(instanceId, dnsRecordUrl, targetZoneId);
syncDnsRecordsState(instanceId, dnsRecordUrl, targetZoneId);
}
});
} catch (DnsProviderException ex) {
@ -1052,7 +958,7 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
}
// Because we just deleted the nic_details, the sync method will naturally
// find 0 active IPs for this VM/FQDN combo and issue a clean DELETE to PowerDNS.
syncDnsState(instanceId, dnsRecordUrl, targetZoneId);
syncDnsRecordsState(instanceId, dnsRecordUrl, targetZoneId);
}
});
logger.debug("Successfully cleaned up DNS record: {} for Instance with ID: {}", dnsRecordUrl, instanceId);
@ -1065,6 +971,78 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
}
}
void handleVmHostnameChanged(long instanceId, String newHostName) {
VirtualMachine instance = vmInstanceDao.findById(instanceId);
if (instance == null) {
logger.debug("Instance is not found for the given ID: {}", instanceId);
return;
}
List<DnsNicJoinVO> mappedNics = dnsNicJoinDao.listActiveByVmId(instanceId);
if (CollectionUtils.isEmpty(mappedNics)) {
return;
}
Map<Long, Map<String, List<DnsNicJoinVO>>> dnsZoneNewRecordNicMap = new HashMap<>();
for (DnsNicJoinVO nic : mappedNics) {
DnsZoneVO targetZone = dnsZoneDao.findById(nic.getDnsZoneId());
if (targetZone == null) {
continue;
}
String oldDnsRecordUrl = nic.getNicDnsUrl();
String newDnsRecordUrl = prepareDnsRecordUrl(newHostName, nic.getSubDomain(), targetZone.getName());
if (newDnsRecordUrl.equals(oldDnsRecordUrl)) {
continue;
}
dnsZoneNewRecordNicMap.computeIfAbsent(targetZone.getId(), k -> new HashMap<>())
.computeIfAbsent(newDnsRecordUrl, k -> new ArrayList<>())
.add(nic);
}
for (Map.Entry<Long, Map<String, List<DnsNicJoinVO>>> zoneEntry : dnsZoneNewRecordNicMap.entrySet()) {
long targetZoneId = zoneEntry.getKey();
for (Map.Entry<String, List<DnsNicJoinVO>> newUrlEntry : zoneEntry.getValue().entrySet()) {
String newDnsRecordUrl = newUrlEntry.getKey();
List<DnsNicJoinVO> nicsForThisFqdn = newUrlEntry.getValue();
try {
Transaction.execute(new TransactionCallbackWithExceptionNoReturn<DnsProviderException>() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) throws DnsProviderException {
if (isDnsCollision(newDnsRecordUrl, targetZoneId, instanceId)) {
return;
}
Set<String> oldDnsRecordUrls = new HashSet<>();
for (DnsNicJoinVO nic : nicsForThisFqdn) {
if (nic.getNicDnsUrl() != null) {
oldDnsRecordUrls.add(nic.getNicDnsUrl());
}
nicDetailsDao.addDetail(nic.getId(), ApiConstants.NIC_DNS_RECORD, newDnsRecordUrl, true);
}
// NICs for the old URL and cleanly send a DELETE API call to PowerDNS!
for (String oldUrl : oldDnsRecordUrls) {
syncDnsRecordsState(instanceId, oldUrl, targetZoneId);
}
// This sync call finds the newly written intent and sends an ADD/REPLACE call.
syncDnsRecordsState(instanceId, newDnsRecordUrl, targetZoneId);
}
});
logger.debug("Successfully handled DNS Rename to: {}", newDnsRecordUrl);
} catch (Exception ex) {
logger.error("Failed to process VM Rename for Instance: {}", instance.getUuid(), ex);
throw new CloudRuntimeException(String.format("DNS API Sync Failed for Rename to %s", newDnsRecordUrl), ex);
}
}
}
}
void handleNicPlug(long instanceId, long nicId) {
VirtualMachine instance = vmInstanceDao.findById(instanceId);
if (instance == null || instance.getState() != VirtualMachine.State.Running) {
@ -1087,14 +1065,11 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
@Override
public void doInTransactionWithoutResult(TransactionStatus status) throws DnsProviderException {
DnsNicJoinVO existing = dnsNicJoinDao.findActiveByDnsRecordAndZone(dnsRecordUrl, targetZone.getId());
if (existing != null && existing.getInstanceId() != instanceId) {
logger.error("DNS Collision: Cannot register DNS record: {}. Already owned by Instance: {}.",
dnsRecordUrl, existing.getInstanceId());
if (isDnsCollision(dnsRecordUrl, targetZone.getId(), instanceId)) {
return;
}
nicDetailsDao.addDetail(nicId, ApiConstants.NIC_DNS_RECORD, dnsRecordUrl, true);
syncDnsState(instanceId, dnsRecordUrl, targetZone.getId());
syncDnsRecordsState(instanceId, dnsRecordUrl, targetZone.getId());
logger.debug("Successfully synced DNS on NIC Plug for: {}", dnsRecordUrl);
}
});
@ -1118,7 +1093,7 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
@Override
public void doInTransactionWithoutResult(TransactionStatus status) throws DnsProviderException {
nicDetailsDao.removeDetail(nicId, ApiConstants.NIC_DNS_RECORD);
syncDnsState(instanceId, dnsRecordUrl, dnsZoneId);
syncDnsRecordsState(instanceId, dnsRecordUrl, dnsZoneId);
logger.debug("Successfully synced DNS record: {} on NIC unplug", dnsRecordUrl);
}
});
@ -1139,7 +1114,22 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
return String.join(".", parts);
}
public void syncDnsState(Long instanceId, String dnsRecordUrl, long dnsZoneId) throws DnsProviderException {
private boolean isDnsCollision(String dnsRecordUrl, long targetZoneId, long instanceId) {
DnsNicJoinVO existing = dnsNicJoinDao.findActiveByDnsRecordAndZone(dnsRecordUrl, targetZoneId);
if (existing != null && existing.getInstanceId() != instanceId) {
logger.error("DNS collision: cannot register DNS record: {}. Already owned by Instance: {}.",
dnsRecordUrl, existing.getInstanceId());
String description = String.format("Instance hostname change resulted in a DNS collision. " +
"The requested DNS record '%s' is already in use.", dnsRecordUrl);
ActionEventUtils.onActionEvent(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM, Domain.ROOT_DOMAIN,
EventTypes.EVENT_DNS_NAME_COLLISION, description, instanceId, ApiCommandResourceType.VirtualMachine.toString());
return true;
}
return false;
}
public void syncDnsRecordsState(Long instanceId, String dnsRecordUrl, long dnsZoneId) throws DnsProviderException {
DnsZone dnsZone = dnsZoneDao.findById(dnsZoneId);
if (dnsZone == null) {
logger.error("DNS zone not found for the provided ID: {}", dnsZoneId);

View File

@ -81,7 +81,6 @@ import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.domain.dao.DomainDao;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.network.Network;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.user.Account;
@ -90,9 +89,6 @@ import com.cloud.user.AccountVO;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallback;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.NicDetailVO;
import com.cloud.vm.NicVO;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.dao.NicDao;
import com.cloud.vm.dao.NicDetailsDao;
import com.cloud.vm.dao.VMInstanceDao;
@ -573,32 +569,6 @@ public class DnsProviderManagerImplTest {
manager.checkDnsZonePermission(callerMock, zoneVO);
}
@Test
public void testDeleteDnsRecordForVMNoNicDetail() {
Network network = mock(Network.class);
NicVO nic = mock(NicVO.class);
VMInstanceVO vm = mock(VMInstanceVO.class);
when(nic.getId()).thenReturn(50L);
when(vm.getInstanceName()).thenReturn("vm-1");
when(nicDetailsDao.findDetail(50L, "nicdnsrecord")).thenReturn(null);
manager.deleteDnsRecordForVM(vm, network, nic);
verify(dnsZoneNetworkMapDao, never()).findByNetworkId(anyLong());
}
@Test
public void testDeleteDnsRecordForVMNicDetailBlankValue() {
Network network = mock(Network.class);
NicVO nic = mock(NicVO.class);
VMInstanceVO vm = mock(VMInstanceVO.class);
NicDetailVO detail = mock(NicDetailVO.class);
when(nic.getId()).thenReturn(50L);
when(vm.getInstanceName()).thenReturn("vm-1");
when(nicDetailsDao.findDetail(50L, "nicdnsrecord")).thenReturn(detail);
when(detail.getValue()).thenReturn(" ");
manager.deleteDnsRecordForVM(vm, network, nic);
verify(dnsZoneNetworkMapDao, never()).findByNetworkId(anyLong());
}
@Test
public void testGetCommandsReturnsNonEmptyList() {
List<Class<?>> commands = manager.getCommands();
@ -720,31 +690,6 @@ public class DnsProviderManagerImplTest {
verify(dnsProviderMock).deleteRecord(any(), any(), any());
}
@Test
public void testDeleteDnsRecordForVMSuccess() throws Exception {
Network network = mock(Network.class);
NicVO nic = mock(NicVO.class);
when(nic.getIPv4Address()).thenReturn("1.2.3.4");
VMInstanceVO vm = mock(VMInstanceVO.class);
NicDetailVO detail = mock(NicDetailVO.class);
when(nic.getId()).thenReturn(50L);
when(vm.getInstanceName()).thenReturn("vm-1");
when(nicDetailsDao.findDetail(50L, "nicdnsrecord")).thenReturn(detail);
when(detail.getValue()).thenReturn("vm-1.ex.com");
DnsZoneNetworkMapVO mapping = mock(DnsZoneNetworkMapVO.class);
when(network.getId()).thenReturn(NETWORK_ID);
when(dnsZoneNetworkMapDao.findByNetworkId(NETWORK_ID)).thenReturn(mapping);
when(mapping.getDnsZoneId()).thenReturn(ZONE_ID);
when(dnsZoneDao.findById(ZONE_ID)).thenReturn(zoneVO);
when(dnsServerDao.findById(anyLong())).thenReturn(serverVO);
when(dnsProviderMock.deleteRecord(any(), any(), any())).thenReturn("vm-1.ex.com");
manager.deleteDnsRecordForVM(vm, network, nic);
verify(dnsProviderMock).deleteRecord(any(), any(), any());
verify(nicDetailsDao).removeDetail(50L, "nicdnsrecord");
}
@Test
public void testConfigure() throws Exception {
assertTrue(manager.configure("dnsProviderManagerImpl", Collections.emptyMap()));