mirror of https://github.com/apache/cloudstack.git
Add Listener for VM lifecycle to add dnsrecords for associated dns zone
This commit is contained in:
parent
6ca9d5ace8
commit
1c1eef3cc7
|
|
@ -37,9 +37,12 @@ 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 {
|
||||
|
||||
|
|
@ -73,4 +76,6 @@ public interface DnsProviderManager extends Manager, PluggableService {
|
|||
boolean disassociateZoneFromNetwork(DisassociateDnsZoneFromNetworkCmd cmd);
|
||||
|
||||
void checkDnsServerPermissions(Account caller, DnsServer server);
|
||||
|
||||
boolean processDnsRecordForInstance(VirtualMachine instance, Network network, Nic nic, boolean isAdd);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
# 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.
|
||||
name=inmemory
|
||||
parent=event
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<!--
|
||||
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.
|
||||
-->
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
|
||||
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context-3.0.xsd"
|
||||
>
|
||||
|
||||
<bean id="inMemoryEventBus" class="org.apache.cloudstack.mom.inmemory.InMemoryEventBus">
|
||||
<property name="name" value="inMemoryEventBus"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
|
|
@ -62,6 +62,7 @@ import org.springframework.stereotype.Component;
|
|||
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.projects.Project;
|
||||
|
|
@ -76,8 +77,8 @@ import com.cloud.utils.db.Filter;
|
|||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.vm.NicVO;
|
||||
import com.cloud.vm.UserVmVO;
|
||||
import com.cloud.vm.Nic;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import com.cloud.vm.dao.NicDao;
|
||||
import com.cloud.vm.dao.UserVmDao;
|
||||
|
||||
|
|
@ -671,50 +672,21 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to handle both Register and Remove logic for Instance
|
||||
*/
|
||||
private boolean processDnsRecordForInstance(Long instanceId, Long networkId, boolean isAdd) {
|
||||
// 1. Fetch VM and verify access
|
||||
UserVmVO instance = userVmDao.findById(instanceId);
|
||||
if (instance == null) {
|
||||
throw new InvalidParameterValueException("Provided Instance not found.");
|
||||
}
|
||||
accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, instance);
|
||||
|
||||
// 2. Resolve the NIC and Network
|
||||
NicVO nic;
|
||||
if (networkId != null) {
|
||||
nic = nicDao.findByNtwkIdAndInstanceId(networkId, instance.getId());
|
||||
} else {
|
||||
nic = nicDao.findDefaultNicForVM(instance.getId());
|
||||
networkId = nic != null ? nic.getNetworkId() : null;
|
||||
}
|
||||
|
||||
// networkId may not be of Shared network type
|
||||
// there might be multiple shared networks
|
||||
// possible to have dns record for secondary ip
|
||||
|
||||
if (nic == null) {
|
||||
throw new CloudRuntimeException("No valid NIC found for this Instance on the specified Network.");
|
||||
}
|
||||
|
||||
// 3. Find if this network is linked to any DNS Zones
|
||||
@Override
|
||||
public boolean processDnsRecordForInstance(VirtualMachine instance, Network network, Nic nic, boolean isAdd) {
|
||||
long networkId = network.getId();
|
||||
List<DnsZoneNetworkMapVO> mappings = dnsZoneNetworkMapDao.listByNetworkId(networkId);
|
||||
if (mappings == null || mappings.isEmpty()) {
|
||||
throw new CloudRuntimeException("No DNS zones are mapped to this network. Please associate a zone first.");
|
||||
logger.warn("No DNS zones are mapped to this network. Please associate a zone first.");
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean atLeastOneSuccess = false;
|
||||
// 4. Iterate over mapped zones and push the record
|
||||
for (DnsZoneNetworkMapVO map : mappings) {
|
||||
DnsZoneVO zone = dnsZoneDao.findById(map.getDnsZoneId());
|
||||
if (zone == null || zone.getState() != DnsZone.State.Active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DnsServerVO server = dnsServerDao.findById(zone.getDnsServerId());
|
||||
|
||||
// Construct FQDN Prefix (e.g., "instance-id" or "instance-id.subdomain")
|
||||
String recordName = String.valueOf(instance.getInstanceName());
|
||||
if (StringUtils.isNotBlank(map.getSubDomain())) {
|
||||
|
|
@ -753,11 +725,13 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
|
|||
zone.getName(),
|
||||
ex
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!atLeastOneSuccess) {
|
||||
throw new CloudRuntimeException("Failed to process DNS records. Ensure the Instance has a valid IP address.");
|
||||
logger.error("Failed to process DNS records. Ensure the Instance has a valid IP address.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,193 @@
|
|||
//
|
||||
// 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;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.framework.events.Event;
|
||||
import org.apache.cloudstack.framework.events.EventBus;
|
||||
import org.apache.cloudstack.framework.events.EventBusException;
|
||||
import org.apache.cloudstack.framework.events.EventSubscriber;
|
||||
import org.apache.cloudstack.framework.events.EventTopic;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.cloud.event.EventTypes;
|
||||
import com.cloud.network.Network;
|
||||
import com.cloud.network.dao.NetworkDao;
|
||||
import com.cloud.utils.StringUtils;
|
||||
import com.cloud.utils.component.ManagerBase;
|
||||
import com.cloud.vm.Nic;
|
||||
import com.cloud.vm.NicVO;
|
||||
import com.cloud.vm.VMInstanceVO;
|
||||
import com.cloud.vm.dao.NicDao;
|
||||
import com.cloud.vm.dao.VMInstanceDao;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
@Component
|
||||
public class DnsVmLifecycleListener extends ManagerBase implements EventSubscriber {
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
@Inject
|
||||
private EventBus eventBus = null;
|
||||
|
||||
@Inject
|
||||
VMInstanceDao vmInstanceDao;
|
||||
@Inject
|
||||
NetworkDao networkDao;
|
||||
@Inject
|
||||
NicDao nicDao;
|
||||
@Inject
|
||||
DnsProviderManager providerManager;
|
||||
|
||||
@Override
|
||||
public boolean configure(final String name, final Map<String, Object> params) {
|
||||
if (eventBus == null) {
|
||||
logger.info("EventBus is not available; DNS Instance lifecycle listener will not subscribe to events");
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
eventBus.subscribe(new EventTopic(null, EventTypes.EVENT_VM_CREATE, null, null, null), this);
|
||||
eventBus.subscribe(new EventTopic(null, EventTypes.EVENT_VM_STOP, null, null, null), this);
|
||||
eventBus.subscribe(new EventTopic(null, EventTypes.EVENT_VM_DESTROY, null, null, null), this);
|
||||
eventBus.subscribe(new EventTopic(null, EventTypes.EVENT_NIC_CREATE, null, null, null), this);
|
||||
eventBus.subscribe(new EventTopic(null, EventTypes.EVENT_NIC_DELETE, null, null, null), this);
|
||||
} catch (EventBusException ex) {
|
||||
logger.error("Failed to subscribe DnsVmLifecycleListener to EventBus", ex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(Event event) {
|
||||
logger.debug("Received EventBus event: {}", event);
|
||||
JsonNode descJson = parseEventDescription(event);
|
||||
if (!isEventCompleted(descJson)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String eventType = event.getEventType();
|
||||
String resourceUuid = event.getResourceUUID();
|
||||
logger.debug("Processing Event: {}", event);
|
||||
try {
|
||||
switch (eventType) {
|
||||
case EventTypes.EVENT_VM_CREATE:
|
||||
case EventTypes.EVENT_VM_START:
|
||||
handleVmEvent(resourceUuid, true);
|
||||
break;
|
||||
case EventTypes.EVENT_VM_STOP:
|
||||
case EventTypes.EVENT_VM_DESTROY:
|
||||
handleVmEvent(resourceUuid, false);
|
||||
break;
|
||||
case EventTypes.EVENT_NIC_CREATE:
|
||||
handleNicEvent(descJson, true);
|
||||
break;
|
||||
case EventTypes.EVENT_NIC_DELETE:
|
||||
handleNicEvent(descJson, false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logger.error("Failed to process DNS lifecycle event: type={}, resourceUuid={}",
|
||||
eventType, event.getResourceUUID(), ex);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void handleNicEvent(JsonNode eventDesc, boolean isAddDnsRecord) {
|
||||
JsonNode nicUuid = eventDesc.get("Nic");
|
||||
JsonNode vmUuid = eventDesc.get("VirtualMachine");
|
||||
JsonNode networkUuid = eventDesc.get("Network");
|
||||
if (nicUuid == null || nicUuid.isNull() || vmUuid == null || vmUuid.isNull() || networkUuid == null || networkUuid.isNull()) {
|
||||
logger.warn("Event has missing data to work on: {}", eventDesc);
|
||||
return;
|
||||
}
|
||||
VMInstanceVO vmInstanceVO = vmInstanceDao.findByUuid(vmUuid.asText());
|
||||
if (vmInstanceVO == null) {
|
||||
logger.error("Unable to find Instance with ID: {}", vmUuid);
|
||||
return;
|
||||
}
|
||||
|
||||
Network network = networkDao.findByUuid(networkUuid.asText());
|
||||
if (network == null || !Network.GuestType.Shared.equals(network.getGuestType())) {
|
||||
logger.warn("Network is not eligible for DNS record registration");
|
||||
return;
|
||||
}
|
||||
Nic nic = nicDao.findByUuid(nicUuid.asText());
|
||||
if (nic == null) {
|
||||
logger.error("NIC is not found for the ID: {}", nicUuid);
|
||||
}
|
||||
|
||||
boolean dnsRecordAdded = providerManager.processDnsRecordForInstance(vmInstanceVO, network, nic, isAddDnsRecord);
|
||||
if (!dnsRecordAdded) {
|
||||
logger.error("Failure {} DNS record for Instance: {} for Network with ID: {}",
|
||||
isAddDnsRecord ? "adding" : "removing", vmUuid, networkUuid);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleVmEvent(String vmUuid, boolean isAddDnsRecord) {
|
||||
VMInstanceVO vmInstanceVO = vmInstanceDao.findByUuid(vmUuid);
|
||||
if (vmInstanceVO == null) {
|
||||
logger.error("Unable to find Instance with ID: {}", vmUuid);
|
||||
return;
|
||||
}
|
||||
List<NicVO> vmNics = nicDao.listByVmId(vmInstanceVO.getId());
|
||||
for (NicVO nic : vmNics) {
|
||||
Network network = networkDao.findById(nic.getNetworkId());
|
||||
if (Network.GuestType.Shared.equals(network.getGuestType())) {
|
||||
boolean dnsRecordAdded = providerManager.processDnsRecordForInstance(vmInstanceVO, network, nic, isAddDnsRecord);
|
||||
if (!dnsRecordAdded) {
|
||||
logger.error("Failure {} DNS record for Instance: {} for Network with ID: {}",
|
||||
isAddDnsRecord ? "adding" : "removing", vmUuid, network.getUuid());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private JsonNode parseEventDescription(Event event) {
|
||||
String rawDescription = event.getDescription();
|
||||
if (StringUtils.isBlank(rawDescription)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return OBJECT_MAPPER.readTree(rawDescription);
|
||||
} catch (Exception ex) {
|
||||
logger.warn("parseEventDescription: failed to parse description for event [{}]: {}",
|
||||
event.getEventType(), ex.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEventCompleted(JsonNode descJson) {
|
||||
if (descJson == null) {
|
||||
return false;
|
||||
}
|
||||
JsonNode statusNode = descJson.get(ApiConstants.STATUS);
|
||||
if (statusNode == null || statusNode.isNull()) {
|
||||
return false;
|
||||
}
|
||||
return ApiConstants.COMPLETED.equalsIgnoreCase(statusNode.asText());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
|
||||
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context-3.0.xsd"
|
||||
>
|
||||
|
||||
<bean id="eventNotificationBus" class="org.apache.cloudstack.mom.inmemory.InMemoryEventBus">
|
||||
<property name="name" value="eventNotificationBus"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
|
|
@ -402,4 +402,5 @@
|
|||
<bean id="dnsProviderManager" class="org.apache.cloudstack.dns.DnsProviderManagerImpl" >
|
||||
<property name="dnsProviders" value="#{dnsProvidersRegistry.registered}" />
|
||||
</bean>
|
||||
<bean id="dnsVmLifecycleListener" class="org.apache.cloudstack.dns.DnsVmLifecycleListener" />
|
||||
</beans>
|
||||
|
|
|
|||
Loading…
Reference in New Issue