fix automatic dns record registration based on vm and nic lifecycle

This commit is contained in:
Manoj Kumar 2026-04-09 19:06:53 +05:30
parent 8d10ae1094
commit 356781972e
No known key found for this signature in database
GPG Key ID: E952B7234D2C6F88
9 changed files with 539 additions and 212 deletions

View File

@ -75,7 +75,6 @@ public interface DnsProviderManager extends Manager, PluggableService {
boolean disassociateZoneFromNetwork(DisassociateDnsZoneFromNetworkCmd cmd);
void addDnsRecordForVM(VirtualMachine instance, Network network, Nic nic);
void deleteDnsRecordForVM(VirtualMachine instance, Network network, Nic nic);
void checkDnsServerPermission(Account caller, DnsServer dnsServer);

View File

@ -317,5 +317,6 @@
<bean id="dnsZoneNetworkMapDao" class="org.apache.cloudstack.dns.dao.DnsZoneNetworkMapDaoImpl" />
<bean id="dnsServerJoinDao" class="org.apache.cloudstack.dns.dao.DnsServerJoinDaoImpl" />
<bean id="dnsZoneJoinDao" class="org.apache.cloudstack.dns.dao.DnsZoneJoinDaoImpl" />
<bean id="dnsNicJoinDao" class="org.apache.cloudstack.dns.dao.DnsNicJoinDaoImpl" />
</beans>

View File

@ -124,7 +124,7 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.nics','enabled', 'TINYINT(1) NOT NUL
-- ======================================================================
-- DNS Server Table (Stores DNS Server Configurations)
CREATE TABLE `cloud`.`dns_server` (
CREATE TABLE IF NOT EXISTS `cloud`.`dns_server` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id of the dns server',
`uuid` varchar(40) COMMENT 'uuid of the dns server',
`name` varchar(255) NOT NULL COMMENT 'display name of the dns server',
@ -148,7 +148,7 @@ CREATE TABLE `cloud`.`dns_server` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- DNS Zone Table (Stores DNS Zone Metadata)
CREATE TABLE `cloud`.`dns_zone` (
CREATE TABLE IF NOT EXISTS `cloud`.`dns_zone` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id of the dns zone',
`uuid` varchar(40) COMMENT 'uuid of the dns zone',
`name` varchar(255) NOT NULL COMMENT 'dns zone name (e.g. example.com)',
@ -171,7 +171,7 @@ CREATE TABLE `cloud`.`dns_zone` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- DNS Zone Network Map (One-to-Many Link)
CREATE TABLE `cloud`.`dns_zone_network_map` (
CREATE TABLE IF NOT EXISTS `cloud`.`dns_zone_network_map` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id of the dns zone to network mapping',
`uuid` varchar(40),
`dns_zone_id` bigint(20) unsigned NOT NULL,

View File

@ -0,0 +1,40 @@
-- Licensed to the Apache Software Foundation (ASF) under one
-- or more contributor license agreements. See the NOTICE file
-- distributed with this work for additional information
-- regarding copyright ownership. The ASF licenses this file
-- to you 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.
-- VIEW `cloud`.`dns_nic_view`;
DROP VIEW IF EXISTS `cloud`.`dns_nic_view`;
CREATE VIEW `cloud`.`dns_nic_view` AS
SELECT
n.id AS id,
n.uuid AS uuid,
n.instance_id AS instance_id,
n.network_id AS network_id,
n.ip4_address AS ip4_address,
n.ip6_address AS ip6_address,
n.removed AS removed,
nd.value AS nic_dns_url,
map.dns_zone_id AS dns_zone_id,
map.sub_domain AS sub_domain
FROM
`cloud`.`nics` n
INNER JOIN
`cloud`.`dns_zone_network_map` map ON n.network_id = map.network_id
LEFT JOIN
`cloud`.`nic_details` nd ON n.id = nd.nic_id AND nd.name = 'nicdnsrecord'
WHERE
map.removed IS NULL;

View File

@ -56,6 +56,7 @@ import org.apache.cloudstack.api.response.DnsZoneNetworkMapResponse;
import org.apache.cloudstack.api.response.DnsZoneResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.dns.dao.DnsNicJoinDao;
import org.apache.cloudstack.dns.dao.DnsServerDao;
import org.apache.cloudstack.dns.dao.DnsServerJoinDao;
import org.apache.cloudstack.dns.dao.DnsZoneDao;
@ -63,7 +64,9 @@ import org.apache.cloudstack.dns.dao.DnsZoneJoinDao;
import org.apache.cloudstack.dns.dao.DnsZoneNetworkMapDao;
import org.apache.cloudstack.dns.exception.DnsConflictException;
import org.apache.cloudstack.dns.exception.DnsNotFoundException;
import org.apache.cloudstack.dns.exception.DnsProviderException;
import org.apache.cloudstack.dns.exception.DnsTransportException;
import org.apache.cloudstack.dns.vo.DnsNicJoinVO;
import org.apache.cloudstack.dns.vo.DnsServerJoinVO;
import org.apache.cloudstack.dns.vo.DnsServerVO;
import org.apache.cloudstack.dns.vo.DnsZoneJoinVO;
@ -73,7 +76,6 @@ 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.commons.lang3.RandomStringUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.stereotype.Component;
@ -95,11 +97,11 @@ import com.cloud.utils.component.PluggableService;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallback;
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.NicVO;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachineManager;
import com.cloud.vm.dao.NicDao;
@ -138,6 +140,8 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
MessageBus messageBus;
@Inject
VMInstanceDao vmInstanceDao;
@Inject
DnsNicJoinDao dnsNicJoinDao;
private DnsProvider getProviderByType(DnsProviderType type) {
if (type == null) {
@ -701,57 +705,6 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
return dnsZoneNetworkMapDao.remove(mapping.getId());
}
@Override
public void addDnsRecordForVM(VirtualMachine instance, Network network, Nic nic) {
DnsZoneNetworkMapVO dnsZoneNetworkMap = dnsZoneNetworkMapDao.findByNetworkId(network.getId());
if (dnsZoneNetworkMap == null) {
logger.warn("No DNS zone is mapped to this network. Please associate a zone first.");
return;
}
DnsZoneVO dnsZone = dnsZoneDao.findById(dnsZoneNetworkMap.getDnsZoneId());
if (dnsZone == null || dnsZone.getState() != DnsZone.State.Active) {
logger.warn("DNS zone is not available for DNS record setup");
return;
}
DnsServerVO server = dnsServerDao.findById(dnsZone.getDnsServerId());
if (server == null) {
logger.warn("DNS server is not found to process DNS record for Instance: {}", instance.getInstanceName());
return;
}
String recordName = finalizeDnsRecordNameForVm(instance, dnsZoneNetworkMap, server, dnsZone);
String dnsRecordUrl = processDnsRecordInProvider(recordName, instance, server, dnsZone, nic, true);
if (Strings.isBlank(dnsRecordUrl)) {
logger.error("Failed to add DNS record in provider for Instance: {}", instance.getInstanceName());
return;
}
nicDetailsDao.addDetail(nic.getId(), ApiConstants.NIC_DNS_RECORD, dnsRecordUrl, true);
}
private String finalizeDnsRecordNameForVm(VirtualMachine instance, DnsZoneNetworkMapVO dnsZoneNetworkMap, DnsServerVO server, DnsZoneVO dnsZone) {
String recordName;
// Construct FQDN Prefix (e.g., "hostname.dnsZoneName" or "hostname.subdomain.dnsZoneName")
try {
List<String> parts = new ArrayList<>();
parts.add(instance.getHostName());
if (StringUtils.isNotBlank(dnsZoneNetworkMap.getSubDomain())) {
parts.add(dnsZoneNetworkMap.getSubDomain());
}
parts.add(dnsZone.getName());
recordName = String.join(".", parts);
DnsProvider provider = getProviderByType(server.getProviderType());
boolean dnsRecordExist = provider.dnsRecordExists(server, dnsZone, recordName, DnsRecord.RecordType.A.toString());
if (dnsRecordExist) {
String randomPrefix = RandomStringUtils.randomAlphanumeric(3).toLowerCase();
recordName = randomPrefix + "-" + recordName;
}
} catch (Exception ex) {
logger.error("Failed while constructing DNS record name for Instance: {} ", instance.getInstanceName(), ex);
throw new CloudRuntimeException("Error occurred during DNS record registration for Instance: " + instance.getInstanceName());
}
return recordName;
}
@Override
public void deleteDnsRecordForVM(VirtualMachine instance, Network network, Nic nic) {
String instanceName = instance.getInstanceName();
@ -918,11 +871,11 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
long instanceId = (long) event.get(ApiConstants.INSTANCE_ID);
switch (newState) {
case Running:
handleVmEvent(instanceId, true);
handleVmRunningState(instanceId);
break;
case Stopped:
case Destroyed:
handleVmEvent(instanceId, false);
handleVmStopAndDestroy(instanceId);
break;
default:
logger.warn("Ignoring lifecycle event for Instance ID: {}, unsupported state={}", instanceId, newState);
@ -950,9 +903,9 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
return;
}
if (EVENT_NIC_CREATE.equals(eventType)) {
handleNicEvent(nicId, instanceId, true);
handleNicPlug(instanceId, nicId);
} else if (EVENT_NIC_DELETE.equals(eventType)) {
handleNicEvent(nicId, instanceId, false);
handleNicUnplug(instanceId, nicId);
} else {
logger.warn("Ignoring lifecycle event for NIC with ID: {}, unsupported eventType={}", nicId, eventType);
}
@ -993,49 +946,6 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
}
}
private void handleNicEvent(long nicId, long instanceId, boolean isAddDnsRecord) {
VMInstanceVO vmInstanceVO = vmInstanceDao.findById(instanceId);
if (vmInstanceVO == null) {
logger.error("Unable to find Instance with ID: {}", instanceId);
return;
}
Nic nic = nicDao.findByIdIncludingRemoved(nicId);
if (nic == null) {
logger.error("NIC is not found for the ID: {}", nicId);
return;
}
Network network = networkDao.findById(nic.getNetworkId());
if (network == null || !Network.GuestType.Shared.equals(network.getGuestType())) {
logger.warn("Network is not eligible for DNS record registration");
return;
}
processEventForDnsRecord(vmInstanceVO, network, nic, isAddDnsRecord);
}
private void handleVmEvent(long instanceId, boolean isAddDnsRecord) {
VMInstanceVO vmInstanceVO = vmInstanceDao.findByIdIncludingRemoved(instanceId);
if (vmInstanceVO == null) {
logger.error("Unable to find Instance with ID: {}", instanceId);
return;
}
List<NicVO> vmNics = nicDao.listByVmIdIncludingRemoved(vmInstanceVO.getId());
for (NicVO nic : vmNics) {
Network network = networkDao.findById(nic.getNetworkId());
if (network == null || !Network.GuestType.Shared.equals(network.getGuestType())) {
continue;
}
processEventForDnsRecord(vmInstanceVO, network, nic, isAddDnsRecord);
}
}
void processEventForDnsRecord(VMInstanceVO vmInstanceVO, Network network, Nic nic, boolean isAddDnsRecord) {
if (isAddDnsRecord) {
addDnsRecordForVM(vmInstanceVO, network, nic);
} else {
deleteDnsRecordForVM(vmInstanceVO, network, nic);
}
}
void publishDnsRecordEventMessageBus(String dnsRecord, DnsRecord.RecordType recordType, Long accountId,
String eventType, List<String> contents) {
@ -1056,4 +966,215 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
logger.error("Failed to publish {} event for DNS record: {}", eventType, dnsRecord, ex);
}
}
void handleVmRunningState(long instanceId) {
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)) {
logger.debug("No active DNS zone associated to NICs");
return;
}
Map<Long, Map<String, List<DnsNicJoinVO>>> dnsZoneRecordNicMap = new HashMap<>();
for (DnsNicJoinVO nic : mappedNics) {
DnsZoneVO targetZone = dnsZoneDao.findById(nic.getDnsZoneId());
if (targetZone == null) {
continue;
}
String targetDnsRecordUrl = prepareDnsRecordUrl(instance.getHostName(), nic.getSubDomain(), targetZone.getName());
dnsZoneRecordNicMap.computeIfAbsent(targetZone.getId(), k -> new HashMap<>())
.computeIfAbsent(targetDnsRecordUrl, k -> new ArrayList<>())
.add(nic);
}
for (Map.Entry<Long, Map<String, List<DnsNicJoinVO>>> zoneEntry : dnsZoneRecordNicMap.entrySet()) {
long targetZoneId = zoneEntry.getKey();
for (Map.Entry<String, List<DnsNicJoinVO>> dnsUrlEntry : zoneEntry.getValue().entrySet()) {
String dnsRecordUrl = dnsUrlEntry.getKey();
List<DnsNicJoinVO> nicsForThisFqdn = dnsUrlEntry.getValue();
try {
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());
return;
}
for (DnsNicJoinVO nic : nicsForThisFqdn) {
nicDetailsDao.addDetail(nic.getId(), ApiConstants.NIC_DNS_RECORD, dnsRecordUrl, true);
}
syncDnsState(instanceId, dnsRecordUrl, targetZoneId);
}
});
} catch (DnsProviderException ex) {
logger.error("Failed to register DNS record: {} in provider for Instance: {}", dnsRecordUrl, instance.getUuid(), ex);
} catch (Exception ex) {
logger.warn("Failed to register DNS record: {} for Instance: {}", dnsRecordUrl, instance.getUuid(), ex);
}
}
}
}
void handleVmStopAndDestroy(long instanceId) {
List<DnsNicJoinVO> historicalNics = dnsNicJoinDao.listIncludingRemovedByVmId(instanceId);
if (CollectionUtils.isEmpty(historicalNics)) {
return;
}
Map<Long, Map<String, List<DnsNicJoinVO>>> groupByDnsZone = new HashMap<>();
for (DnsNicJoinVO nic : historicalNics) {
// If the DNS record url is null, it means this NIC was never registered in nic_details
if (nic.getNicDnsUrl() == null) {
continue;
}
groupByDnsZone
.computeIfAbsent(nic.getDnsZoneId(), k -> new HashMap<>())
.computeIfAbsent(nic.getNicDnsUrl(), k -> new ArrayList<>())
.add(nic);
}
for (Map.Entry<Long, Map<String, List<DnsNicJoinVO>>> zoneEntry : groupByDnsZone.entrySet()) {
long targetZoneId = zoneEntry.getKey();
for (Map.Entry<String, List<DnsNicJoinVO>> dnsRecordEntry : zoneEntry.getValue().entrySet()) {
String dnsRecordUrl = dnsRecordEntry.getKey();
List<DnsNicJoinVO> nicsForDnsUrl = dnsRecordEntry.getValue();
try {
Transaction.execute(new TransactionCallbackWithExceptionNoReturn<DnsProviderException>() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) throws DnsProviderException {
for (DnsNicJoinVO nic : nicsForDnsUrl) {
nicDetailsDao.removeDetail(nic.getId(), ApiConstants.NIC_DNS_RECORD);
}
// 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);
}
});
logger.debug("Successfully cleaned up DNS record: {} for Instance with ID: {}", dnsRecordUrl, instanceId);
} catch (DnsProviderException ex) {
logger.error("Failed to cleanup DNS record: {} in provider", dnsRecordUrl, ex);
} catch (Exception ex) {
logger.error("Failed DNS record: {} cleanup for Instance", dnsRecordUrl, ex);
}
}
}
}
void handleNicPlug(long instanceId, long nicId) {
VirtualMachine instance = vmInstanceDao.findById(instanceId);
if (instance == null || instance.getState() != VirtualMachine.State.Running) {
return;
}
DnsNicJoinVO nic = dnsNicJoinDao.findById(nicId);
if (nic == null) {
logger.debug("NIC with ID: {} doesn't have DNS zone associated", nicId);
return;
}
DnsZoneVO targetZone = dnsZoneDao.findById(nic.getDnsZoneId());
if (targetZone == null) {
return;
}
String dnsRecordUrl = prepareDnsRecordUrl(instance.getHostName(), nic.getSubDomain(), targetZone.getName());
try {
Transaction.execute(new TransactionCallbackWithExceptionNoReturn<DnsProviderException>() {
@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());
return;
}
nicDetailsDao.addDetail(nicId, ApiConstants.NIC_DNS_RECORD, dnsRecordUrl, true);
syncDnsState(instanceId, dnsRecordUrl, targetZone.getId());
logger.debug("Successfully synced DNS on NIC Plug for: {}", dnsRecordUrl);
}
});
} catch (DnsProviderException ex) {
logger.error("Failed to register DNS record: {} in provider for Instance: {}", dnsRecordUrl, instance.getUuid(), ex);
} catch (Exception ex) {
logger.warn("Failed to register DNS record: {} for Instance: {}", dnsRecordUrl, instance.getUuid(), ex);
}
}
void handleNicUnplug(long instanceId, long nicId) {
DnsNicJoinVO nic = dnsNicJoinDao.findByIdIncludingRemoved(nicId);
if (nic == null || nic.getNicDnsUrl() == null) {
return;
}
String dnsRecordUrl = nic.getNicDnsUrl();
long dnsZoneId = nic.getDnsZoneId();
try {
Transaction.execute(new TransactionCallbackWithExceptionNoReturn<DnsProviderException>() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) throws DnsProviderException {
nicDetailsDao.removeDetail(nicId, ApiConstants.NIC_DNS_RECORD);
syncDnsState(instanceId, dnsRecordUrl, dnsZoneId);
logger.debug("Successfully synced DNS record: {} on NIC unplug", dnsRecordUrl);
}
});
} catch (DnsProviderException ex) {
logger.error("Failed to sync DNS record: {} on NIC unplug in provider", dnsRecordUrl);
} catch (Exception ex) {
logger.error("Failed to sync DNS record: {} on NIC unplug ", dnsRecordUrl);
}
}
String prepareDnsRecordUrl(String hostName, String subDomain, String dnsZoneName) {
List<String> parts = new ArrayList<>();
parts.add(hostName);
if (StringUtils.isNotBlank(subDomain)) {
parts.add(subDomain.trim());
}
parts.add(dnsZoneName);
return String.join(".", parts);
}
public void syncDnsState(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);
return;
}
DnsServerVO dnsServer = dnsServerDao.findById(dnsZone.getDnsServerId());
List<DnsNicJoinVO> activeNics = dnsNicJoinDao.listActiveByVmIdZoneAndDnsRecord(instanceId, dnsZoneId, dnsRecordUrl);
List<String> ipv4s = new ArrayList<>();
List<String> ipv6s = new ArrayList<>();
for (DnsNicJoinVO nic : activeNics) {
if (nic.getIp4Address() != null && !nic.getIp4Address().isEmpty()) {
ipv4s.add(nic.getIp4Address());
}
if (nic.getIp6Address() != null && !nic.getIp6Address().isEmpty()) {
ipv6s.add(nic.getIp6Address());
}
}
logger.debug("Syncing DNS for: {}. Found: {} IPv4s and {} IPv6s.", dnsRecordUrl, ipv4s.size(), ipv6s.size());
DnsProvider provider = getProviderByType(dnsServer.getProviderType());
DnsRecord recordIpv4 = new DnsRecord(dnsRecordUrl, DnsRecord.RecordType.A, null, 3600);
// push A records into Provider
if (ipv4s.isEmpty()) {
provider.deleteRecord(dnsServer, dnsZone, recordIpv4);
} else {
recordIpv4.setContents(ipv4s);
provider.addRecord(dnsServer, dnsZone, recordIpv4);
}
// push AAAA records into Provider
DnsRecord recordIpv6 = new DnsRecord(dnsRecordUrl, DnsRecord.RecordType.AAAA, null, 3600);
if (ipv6s.isEmpty()) {
provider.deleteRecord(dnsServer, dnsZone, recordIpv6);
} else {
recordIpv6.setContents(ipv6s);
provider.addRecord(dnsServer, dnsZone, recordIpv6);
}
}
}

View File

@ -0,0 +1,58 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 org.apache.cloudstack.dns.dao;
import java.util.List;
import org.apache.cloudstack.dns.vo.DnsNicJoinVO;
import com.cloud.utils.db.GenericDao;
public interface DnsNicJoinDao extends GenericDao<DnsNicJoinVO, Long> {
/**
* Used for Collision Checks.
* @param dnsRecordUrl
* @param dnsZoneId
* @return active records to see who currently owns the dnsRecordUrl.
*/
DnsNicJoinVO findActiveByDnsRecordAndZone(String dnsRecordUrl, long dnsZoneId);
/**
* Used to sync DNS record url based on available ips for vmId in the dnsZone
* @param vmId
* @param dnsZoneId
* @param dnsRecordUrl
* @return list of active nics using the dnsRecordUrl, supports null vmId for dnsZone wide query
*/
List<DnsNicJoinVO> listActiveByVmIdZoneAndDnsRecord(Long vmId, long dnsZoneId, String dnsRecordUrl);
/**
* Used for VM Start/Running
* @param vmId
* @return records associated to vmId
*/
List<DnsNicJoinVO> listActiveByVmId(long vmId);
/**
* Used by Instance Destroy/Stop or NIC delete
* @param vmId
* @return records with soft-delete
*/
List<DnsNicJoinVO> listIncludingRemovedByVmId(long vmId);
}

View File

@ -0,0 +1,91 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 org.apache.cloudstack.dns.dao;
import java.util.List;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.dns.vo.DnsNicJoinVO;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
public class DnsNicJoinDaoImpl extends GenericDaoBase<DnsNicJoinVO, Long> implements DnsNicJoinDao {
private final SearchBuilder<DnsNicJoinVO> activeDnsRecordZoneSearch;
private final SearchBuilder<DnsNicJoinVO> activeVmZoneDnsRecordSearch; // Route for null vmId
private final SearchBuilder<DnsNicJoinVO> activeVmSearch;
public DnsNicJoinDaoImpl() {
activeDnsRecordZoneSearch = createSearchBuilder();
activeDnsRecordZoneSearch.and(ApiConstants.NIC_DNS_RECORD, activeDnsRecordZoneSearch.entity().getNicDnsUrl(), SearchCriteria.Op.EQ);
activeDnsRecordZoneSearch.and(ApiConstants.DNS_ZONE_ID, activeDnsRecordZoneSearch.entity().getDnsZoneId(), SearchCriteria.Op.EQ);
activeDnsRecordZoneSearch.and(ApiConstants.REMOVED, activeDnsRecordZoneSearch.entity().getRemoved(), SearchCriteria.Op.NULL);
activeDnsRecordZoneSearch.done();
activeVmZoneDnsRecordSearch = createSearchBuilder();
activeVmZoneDnsRecordSearch.and(ApiConstants.INSTANCE_ID, activeVmZoneDnsRecordSearch.entity().getInstanceId(), SearchCriteria.Op.EQ);
activeVmZoneDnsRecordSearch.and(ApiConstants.NIC_DNS_RECORD, activeVmZoneDnsRecordSearch.entity().getNicDnsUrl(), SearchCriteria.Op.EQ);
activeVmZoneDnsRecordSearch.and(ApiConstants.DNS_ZONE_ID, activeVmZoneDnsRecordSearch.entity().getDnsZoneId(), SearchCriteria.Op.EQ);
activeVmZoneDnsRecordSearch.and(ApiConstants.REMOVED, activeVmZoneDnsRecordSearch.entity().getRemoved(), SearchCriteria.Op.NULL);
activeVmZoneDnsRecordSearch.done();
activeVmSearch = createSearchBuilder();
activeVmSearch.and(ApiConstants.INSTANCE_ID, activeVmSearch.entity().getInstanceId(), SearchCriteria.Op.EQ);
activeVmSearch.done();
}
@Override
public DnsNicJoinVO findActiveByDnsRecordAndZone(String dnsRecordUrl, long dnsZoneId) {
SearchCriteria<DnsNicJoinVO> sc = activeDnsRecordZoneSearch.create();
sc.setParameters(ApiConstants.NIC_DNS_RECORD, dnsRecordUrl);
sc.setParameters(ApiConstants.DNS_ZONE_ID, dnsZoneId);
return findOneBy(sc);
}
@Override
public List<DnsNicJoinVO> listActiveByVmIdZoneAndDnsRecord(Long vmId, long dnsZoneId, String dnsRecordUrl) {
if (vmId != null) {
SearchCriteria<DnsNicJoinVO> sc = activeDnsRecordZoneSearch.create();
sc.setParameters(ApiConstants.INSTANCE_ID, vmId);
sc.setParameters(ApiConstants.DNS_ZONE_ID, dnsZoneId);
sc.setParameters(ApiConstants.NIC_DNS_RECORD, dnsRecordUrl);
return listBy(sc);
} else {
SearchCriteria<DnsNicJoinVO> sc = activeDnsRecordZoneSearch.create();
sc.setParameters(ApiConstants.NIC_DNS_RECORD, dnsRecordUrl);
sc.setParameters(ApiConstants.DNS_ZONE_ID, dnsZoneId);
return listBy(sc);
}
}
@Override
public List<DnsNicJoinVO> listActiveByVmId(long vmId) {
SearchCriteria<DnsNicJoinVO> sc = activeVmSearch.create();
sc.setParameters(ApiConstants.INSTANCE_ID, vmId);
return listBy(sc);
}
@Override
public List<DnsNicJoinVO> listIncludingRemovedByVmId(long vmId) {
SearchCriteria<DnsNicJoinVO> sc = activeVmSearch.create();
sc.setParameters(ApiConstants.INSTANCE_ID, vmId);
return listIncludingRemovedBy(sc);
}
}

View File

@ -0,0 +1,115 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.cloudstack.dns.vo;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
import com.cloud.api.query.vo.BaseViewVO;
@Entity
@Table(name = "dns_nic_view")
public class DnsNicJoinVO extends BaseViewVO implements InternalIdentity, Identity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private long id;
@Column(name = "uuid")
private String uuid;
@Column(name = "instance_id")
private long instanceId;
@Column(name = "network_id")
private long networkId;
@Column(name = "ip4_address")
private String ip4Address;
@Column(name = "ip6_address")
private String ip6Address;
@Column(name = "nic_dns_url")
private String nicDnsUrl;
@Column(name = "dns_zone_id")
private long dnsZoneId;
@Column(name = "sub_domain")
private String subDomain;
@Column(name = "removed")
private Date removed;
public DnsNicJoinVO() {
}
@Override
public long getId() {
return id;
}
@Override
public String getUuid() {
return uuid;
}
public long getInstanceId() {
return instanceId;
}
public long getNetworkId() {
return networkId;
}
public long getDnsZoneId() {
return dnsZoneId;
}
public String getSubDomain() {
return subDomain;
}
public String getNicDnsUrl() {
return nicDnsUrl;
}
public String getIp4Address() {
return ip4Address;
}
public String getIp6Address() {
return ip6Address;
}
public Date getRemoved() {
return removed;
}
}

View File

@ -52,6 +52,7 @@ import org.apache.cloudstack.api.response.DnsZoneNetworkMapResponse;
import org.apache.cloudstack.api.response.DnsZoneResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.dns.dao.DnsNicJoinDao;
import org.apache.cloudstack.dns.dao.DnsServerDao;
import org.apache.cloudstack.dns.dao.DnsServerJoinDao;
import org.apache.cloudstack.dns.dao.DnsZoneDao;
@ -59,7 +60,6 @@ import org.apache.cloudstack.dns.dao.DnsZoneJoinDao;
import org.apache.cloudstack.dns.dao.DnsZoneNetworkMapDao;
import org.apache.cloudstack.dns.exception.DnsConflictException;
import org.apache.cloudstack.dns.exception.DnsNotFoundException;
import org.apache.cloudstack.dns.exception.DnsProviderException;
import org.apache.cloudstack.dns.exception.DnsTransportException;
import org.apache.cloudstack.dns.vo.DnsServerJoinVO;
import org.apache.cloudstack.dns.vo.DnsServerVO;
@ -130,6 +130,8 @@ public class DnsProviderManagerImplTest {
@Mock
NicDetailsDao nicDetailsDao;
@Mock
DnsNicJoinDao dnsNicJoinDao;
@Mock
MessageBus messageBus;
@Mock
VMInstanceDao vmInstanceDao;
@ -571,51 +573,6 @@ public class DnsProviderManagerImplTest {
manager.checkDnsZonePermission(callerMock, zoneVO);
}
@Test
public void testAddDnsRecordForVMNoNetworkMapping() throws DnsProviderException {
Network network = mock(Network.class);
NicVO nic = mock(NicVO.class);
VMInstanceVO vm = mock(VMInstanceVO.class);
when(dnsZoneNetworkMapDao.findByNetworkId(anyLong())).thenReturn(null);
when(network.getId()).thenReturn(NETWORK_ID);
manager.addDnsRecordForVM(vm, network, nic);
verify(dnsProviderMock, never()).addRecord(any(), any(), any());
}
@Test
public void testAddDnsRecordForVMInactiveZone() {
Network network = mock(Network.class);
NicVO nic = mock(NicVO.class);
VMInstanceVO vm = mock(VMInstanceVO.class);
DnsZoneNetworkMapVO mapping = mock(DnsZoneNetworkMapVO.class);
when(network.getId()).thenReturn(NETWORK_ID);
when(dnsZoneNetworkMapDao.findByNetworkId(NETWORK_ID)).thenReturn(mapping);
when(mapping.getDnsZoneId()).thenReturn(ZONE_ID);
DnsZoneVO inactiveZone = Mockito.spy(new DnsZoneVO("ex.com", DnsZone.ZoneType.Public, SERVER_ID, ACCOUNT_ID, DOMAIN_ID, ""));
// state defaults to Inactive
when(dnsZoneDao.findById(ZONE_ID)).thenReturn(inactiveZone);
manager.addDnsRecordForVM(vm, network, nic);
verify(dnsServerDao, never()).findById(anyLong());
}
@Test
public void testAddDnsRecordForVMServerMissing() {
Network network = mock(Network.class);
NicVO nic = mock(NicVO.class);
VMInstanceVO vm = mock(VMInstanceVO.class);
DnsZoneNetworkMapVO mapping = mock(DnsZoneNetworkMapVO.class);
when(network.getId()).thenReturn(NETWORK_ID);
when(dnsZoneNetworkMapDao.findByNetworkId(NETWORK_ID)).thenReturn(mapping);
when(mapping.getDnsZoneId()).thenReturn(ZONE_ID);
DnsZoneVO activeZone = Mockito.spy(new DnsZoneVO("ex.com", DnsZone.ZoneType.Public, SERVER_ID, ACCOUNT_ID, DOMAIN_ID, ""));
activeZone.setState(DnsZone.State.Active);
when(dnsZoneDao.findById(ZONE_ID)).thenReturn(activeZone);
when(dnsServerDao.findById(SERVER_ID)).thenReturn(null);
when(vm.getInstanceName()).thenReturn("vm-1");
manager.addDnsRecordForVM(vm, network, nic);
verify(nicDetailsDao, never()).addDetail(anyLong(), anyString(), anyString(), eq(true));
}
@Test
public void testDeleteDnsRecordForVMNoNicDetail() {
Network network = mock(Network.class);
@ -642,32 +599,6 @@ public class DnsProviderManagerImplTest {
verify(dnsZoneNetworkMapDao, never()).findByNetworkId(anyLong());
}
@Test
public void testProcessEventForDnsRecordAdd() throws Exception {
Network network = mock(Network.class);
NicVO nic = mock(NicVO.class);
VMInstanceVO vm = mock(VMInstanceVO.class);
when(dnsZoneNetworkMapDao.findByNetworkId(anyLong())).thenReturn(null);
when(network.getId()).thenReturn(NETWORK_ID);
manager.processEventForDnsRecord(vm, network, nic, true);
// addDnsRecordForVM was called returns early because no mapping
verify(dnsZoneNetworkMapDao, times(1)).findByNetworkId(NETWORK_ID);
}
@Test
public void testProcessEventForDnsRecordDelete() {
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.processEventForDnsRecord(vm, network, nic, false);
verify(nicDetailsDao, times(1)).findDetail(50L, "nicdnsrecord");
}
@Test
public void testGetCommandsReturnsNonEmptyList() {
List<Class<?>> commands = manager.getCommands();
@ -820,31 +751,6 @@ public class DnsProviderManagerImplTest {
verify(messageBus, times(3)).subscribe(anyString(), any());
}
@Test
public void testHandleVmEventAndNicEvent() throws Exception {
VMInstanceVO vm = mock(VMInstanceVO.class);
NicVO nic = mock(NicVO.class);
NetworkVO network = mock(NetworkVO.class);
when(network.getId()).thenReturn(NETWORK_ID);
when(vmInstanceDao.findById(10L)).thenReturn(vm);
when(nicDao.findByIdIncludingRemoved(50L)).thenReturn(nic);
when(nic.getNetworkId()).thenReturn(NETWORK_ID);
when(networkDao.findById(NETWORK_ID)).thenReturn(network);
when(network.getGuestType()).thenReturn(Network.GuestType.Shared);
when(dnsZoneNetworkMapDao.findByNetworkId(NETWORK_ID)).thenReturn(null);
org.springframework.test.util.ReflectionTestUtils.invokeMethod(manager, "handleNicEvent", 50L, 10L, true);
verify(dnsZoneNetworkMapDao, times(1)).findByNetworkId(NETWORK_ID);
when(vmInstanceDao.findByIdIncludingRemoved(10L)).thenReturn(vm);
when(vm.getId()).thenReturn(10L);
when(nicDao.listByVmIdIncludingRemoved(10L)).thenReturn(Collections.singletonList(nic));
org.springframework.test.util.ReflectionTestUtils.invokeMethod(manager, "handleVmEvent", 10L, true);
verify(dnsZoneNetworkMapDao, times(2)).findByNetworkId(NETWORK_ID);
}
@Test
public void testAddDnsServerSuccess() throws Exception {
org.apache.cloudstack.api.command.user.dns.AddDnsServerCmd cmd = mock(org.apache.cloudstack.api.command.user.dns.AddDnsServerCmd.class);
@ -1011,10 +917,10 @@ public class DnsProviderManagerImplTest {
event.put(org.apache.cloudstack.api.ApiConstants.INSTANCE_ID, 12L);
// Expect handleVmEvent to be called, which accesses vmInstanceDao.findByIdIncludingRemoved
when(vmInstanceDao.findByIdIncludingRemoved(12L)).thenReturn(null);
when(vmInstanceDao.findById(12L)).thenReturn(null);
subscriber.onPublishMessage("sender", "subject", event);
verify(vmInstanceDao, times(1)).findByIdIncludingRemoved(12L);
verify(vmInstanceDao, times(1)).findById(12L);
}
@Test
@ -1024,11 +930,9 @@ public class DnsProviderManagerImplTest {
event.put(org.apache.cloudstack.api.ApiConstants.OLD_STATE, com.cloud.vm.VirtualMachine.State.Running);
event.put(org.apache.cloudstack.api.ApiConstants.NEW_STATE, com.cloud.vm.VirtualMachine.State.Stopped);
event.put(org.apache.cloudstack.api.ApiConstants.INSTANCE_ID, 15L);
when(vmInstanceDao.findByIdIncludingRemoved(15L)).thenReturn(null);
when(dnsNicJoinDao.listIncludingRemovedByVmId(15L)).thenReturn(null);
subscriber.onPublishMessage("sender", "subject", event);
verify(vmInstanceDao, times(1)).findByIdIncludingRemoved(15L);
verify(dnsNicJoinDao, times(1)).listIncludingRemovedByVmId(15L);
}
@Test
@ -1073,11 +977,9 @@ public class DnsProviderManagerImplTest {
event.put(org.apache.cloudstack.api.ApiConstants.EVENT_TYPE, com.cloud.event.EventTypes.EVENT_NIC_DELETE);
event.put(org.apache.cloudstack.api.ApiConstants.NIC_ID, 101L);
event.put(org.apache.cloudstack.api.ApiConstants.INSTANCE_ID, 201L);
when(vmInstanceDao.findById(201L)).thenReturn(null);
when(dnsNicJoinDao.findByIdIncludingRemoved(101L)).thenReturn(null);
subscriber.onPublishMessage("sender", "subject", event);
verify(vmInstanceDao, times(1)).findById(201L);
verify(dnsNicJoinDao, times(1)).findByIdIncludingRemoved(101L);
}
@Test