Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7b9fc0e
wip: dns provider framework
sudo87 Feb 4, 2026
b2d597c
add db schema, vo and dao classes
sudo87 Feb 10, 2026
6ef7f9b
add powerdns module in plugins
sudo87 Feb 11, 2026
9911c28
Changes done to AddDnsServer, ListDnsServer, DeleteDnsServer and Upda…
sudo87 Feb 12, 2026
df21318
1. Setup Dns zone schema
sudo87 Feb 13, 2026
f29b8be
following dns zone apis are integrated:
sudo87 Feb 15, 2026
9b77c37
Tested following flow:
sudo87 Feb 16, 2026
c5972ae
Add associate and disassociate zone to network APIs
sudo87 Feb 16, 2026
99f8c7d
following things are done:
sudo87 Feb 17, 2026
e011ce1
add missing license, cleanup, log std
sudo87 Feb 18, 2026
4a9f66d
following changes are done:
sudo87 Feb 19, 2026
c64cf81
include:
sudo87 Feb 20, 2026
4df11a4
normalize dns zone and record in svc layer, always use dotless data i…
sudo87 Feb 21, 2026
857436e
enable apis for all roles
sudo87 Feb 21, 2026
582b687
add ACL annotation, entitytype, minor cleanup
sudo87 Feb 21, 2026
add7763
acl related changes, fixes, mistakes
sudo87 Feb 25, 2026
1fe79bd
fixes related to acl, dao
sudo87 Feb 25, 2026
6ca9d5a
add views for dns_server and dns_zone
sudo87 Feb 26, 2026
1c1eef3
Add Listener for VM lifecycle to add dnsrecords for associated dns zone
sudo87 Mar 2, 2026
caf8533
1. implement event processing for vm start/stop/destroy, nic create/d…
sudo87 Mar 3, 2026
856158c
starting on ui work
sudo87 Mar 3, 2026
928db65
resolve merge conflict with main
sudo87 Mar 3, 2026
369bb16
fix lint issue
sudo87 Mar 3, 2026
0df50ce
fix list dnsservers api, ui screens for dns servers, generate events
sudo87 Mar 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion api/src/main/java/com/cloud/configuration/Resource.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ enum ResourceType { // All storage type resources are allocated_storage and not
backup_storage("backup_storage", 13),
bucket("bucket", 14),
object_storage("object_storage", 15),
gpu("gpu", 16);
gpu("gpu", 16),
dns_zone("dns_zone", 17);

private String name;
private int ordinal;
Expand Down
22 changes: 22 additions & 0 deletions api/src/main/java/com/cloud/event/EventTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
import org.apache.cloudstack.backup.BackupRepositoryService;
import org.apache.cloudstack.config.Configuration;
import org.apache.cloudstack.datacenter.DataCenterIpv4GuestSubnet;
import org.apache.cloudstack.dns.DnsRecord;
import org.apache.cloudstack.dns.DnsServer;
import org.apache.cloudstack.dns.DnsZone;
import org.apache.cloudstack.extension.Extension;
import org.apache.cloudstack.extension.ExtensionCustomAction;
import org.apache.cloudstack.gpu.GpuCard;
Expand Down Expand Up @@ -859,6 +862,16 @@ public class EventTypes {
public static final String EVENT_BACKUP_REPOSITORY_ADD = "BACKUP.REPOSITORY.ADD";
public static final String EVENT_BACKUP_REPOSITORY_UPDATE = "BACKUP.REPOSITORY.UPDATE";

// DNS Framework Events
public static final String EVENT_DNS_SERVER_ADD = "DNS.SERVER.ADD";
public static final String EVENT_DNS_SERVER_UPDATE = "DNS.SERVER.UPDATE";
public static final String EVENT_DNS_SERVER_DELETE = "DNS.SERVER.DELETE";
public static final String EVENT_DNS_ZONE_CREATE = "DNS.ZONE.CREATE";
public static final String EVENT_DNS_ZONE_UPDATE = "DNS.ZONE.UPDATE";
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";

static {

// TODO: need a way to force author adding event types to declare the entity details as well, with out braking
Expand Down Expand Up @@ -1397,6 +1410,15 @@ public class EventTypes {
// Backup Repository
entityEventDetails.put(EVENT_BACKUP_REPOSITORY_ADD, BackupRepositoryService.class);
entityEventDetails.put(EVENT_BACKUP_REPOSITORY_UPDATE, BackupRepositoryService.class);

// DNS Framework Events
entityEventDetails.put(EVENT_DNS_SERVER_ADD, DnsServer.class);
entityEventDetails.put(EVENT_DNS_SERVER_DELETE, DnsServer.class);
entityEventDetails.put(EVENT_DNS_ZONE_CREATE, DnsZone.class);
entityEventDetails.put(EVENT_DNS_ZONE_DELETE, DnsZone.class);
entityEventDetails.put(EVENT_DNS_RECORD_CREATE, DnsRecord.class);
entityEventDetails.put(EVENT_DNS_RECORD_DELETE, DnsRecord.class);

}

public static boolean isNetworkEvent(String eventType) {
Expand Down
9 changes: 6 additions & 3 deletions api/src/main/java/com/cloud/user/AccountService.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@
import java.util.List;
import java.util.Map;

import com.cloud.utils.Pair;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd;
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.RegisterUserKeyCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
import org.apache.cloudstack.backup.BackupOffering;
import org.apache.cloudstack.dns.DnsServer;

import com.cloud.dc.DataCenter;
import com.cloud.domain.Domain;
Expand All @@ -35,8 +37,7 @@
import com.cloud.offering.DiskOffering;
import com.cloud.offering.NetworkOffering;
import com.cloud.offering.ServiceOffering;
import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
import org.apache.cloudstack.backup.BackupOffering;
import com.cloud.utils.Pair;

public interface AccountService {

Expand Down Expand Up @@ -119,6 +120,8 @@ User createUser(String userName, String password, String firstName, String lastN

void checkAccess(Account account, BackupOffering bof) throws PermissionDeniedException;

void checkAccess(Account account, DnsServer dnsServer) throws PermissionDeniedException;

void checkAccess(User user, ControlledEntity entity);

void checkAccess(Account account, AccessType accessType, boolean sameOwner, String apiName, ControlledEntity... entities) throws PermissionDeniedException;
Expand Down
2 changes: 2 additions & 0 deletions api/src/main/java/com/cloud/user/ResourceLimitService.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ public interface ResourceLimitService {
"The default maximum number of GPU devices that can be used for a domain", false);
static final ConfigKey<Long> DefaultMaxProjectGpus = new ConfigKey<>("Project Defaults",Long.class,"max.project.gpus","20",
"The default maximum number of GPU devices that can be used for a project", false);
ConfigKey<Long> DefaultMaxDnsAccounts = new ConfigKey<>("Account Defaults",Long.class, "max.account.dns_zones","10",
"The default maximum number of DNS zones that can be created by an Account", true);

static final List<ResourceType> HostTagsSupportingTypes = List.of(ResourceType.user_vm, ResourceType.cpu, ResourceType.memory, ResourceType.gpu);
static final List<ResourceType> StorageTagsSupportingTypes = List.of(ResourceType.volume, ResourceType.primary_storage);
Expand Down
23 changes: 23 additions & 0 deletions api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -1334,6 +1334,29 @@ public class ApiConstants {
public static final String OBJECT_STORAGE_LIMIT = "objectstoragelimit";
public static final String OBJECT_STORAGE_TOTAL = "objectstoragetotal";

// DNS provider related
public static final String NAME_SERVERS = "nameservers";
public static final String DNS_USER_NAME = "dnsusername";
public static final String CREDENTIALS = "credentials";
public static final String DNS_ZONE_ID = "dnszoneid";
public static final String DNS_SERVER_ID = "dnsserverid";
public static final String CONTENT = "content";
public static final String CONTENTS = "contents";
public static final String PUBLIC_DOMAIN_SUFFIX = "publicdomainsuffix";
public static final String AUTHORITATIVE = "authoritative";
public static final String KIND = "kind";
public static final String DNS_SEC = "dnssec";
public static final String TTL = "ttl";
public static final String CHANGE_TYPE = "changetype";
public static final String RECORDS = "records";
public static final String RR_SETS = "rrsets";
public static final String X_API_KEY = "X-API-Key";
public static final String DISABLED = "disabled";
public static final String CONTENT_TYPE = "Content-Type";
public static final String NATIVE_ZONE = "Native";
public static final String NIC_DNS_RECORD = "nicdnsrecord";


public static final String PARAMETER_DESCRIPTION_ACTIVATION_RULE = "Quota tariff's activation rule. It can receive a JS script that results in either " +
"a boolean or a numeric value: if it results in a boolean value, the tariff value will be applied according to the result; if it results in a numeric value, the " +
"numeric value will be applied; if the result is neither a boolean nor a numeric value, the tariff will not be applied. If the rule is not informed, the tariff " +
Expand Down
4 changes: 4 additions & 0 deletions api/src/main/java/org/apache/cloudstack/api/BaseCmd.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.apache.cloudstack.alert.AlertService;
import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.dns.DnsProviderManager;
import org.apache.cloudstack.gpu.GpuService;
import org.apache.cloudstack.network.RoutedIpv4Manager;
import org.apache.cloudstack.network.lb.ApplicationLoadBalancerService;
Expand Down Expand Up @@ -230,6 +231,9 @@ public static enum CommandType {
@Inject
public RoutedIpv4Manager routedIpv4Manager;

@Inject
public DnsProviderManager dnsProviderManager;

public abstract void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException,
ResourceAllocationException, NetworkRuleConflictException;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// 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.api.command.user.dns;

import java.util.List;

import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.DnsServerResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.dns.DnsProviderType;
import org.apache.cloudstack.dns.DnsServer;
import org.apache.commons.lang3.BooleanUtils;

import com.cloud.exception.InvalidParameterValueException;
import com.cloud.utils.EnumUtils;

@APICommand(name = "addDnsServer",
description = "Adds a new external DNS server",
responseObject = DnsServerResponse.class,
entityType = {DnsServer.class},
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false,
since = "4.23.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class AddDnsServerCmd extends BaseCmd {

/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
///
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "Name of the DNS server")
private String name;

@Parameter(name = ApiConstants.URL, type = CommandType.STRING, required = true, description = "API URL of the provider")
private String url;

@Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING, required = true, description = "Provider type (e.g., PowerDNS)")
private String provider;

@Parameter(name = ApiConstants.DNS_USER_NAME, type = CommandType.STRING,
description = "Username or email associated with the external DNS provider account (used for authentication)")
private String dnsUserName;

@Parameter(name = ApiConstants.CREDENTIALS, required = true, type = CommandType.STRING, description = "API key or credentials for the external provider")
private String credentials;

@Parameter(name = ApiConstants.PORT, type = CommandType.INTEGER, description = "Port number of the external DNS server")
private Integer port;

@Parameter(name = ApiConstants.IS_PUBLIC, type = CommandType.BOOLEAN, description = "Whether the DNS server is publicly accessible by other accounts")
private Boolean isPublic;

@Parameter(name = ApiConstants.PUBLIC_DOMAIN_SUFFIX, type = CommandType.STRING, description = "The domain suffix used for public access (e.g. public.example.com)")
private String publicDomainSuffix;

@Parameter(name = ApiConstants.NAME_SERVERS, type = CommandType.LIST, collectionType = CommandType.STRING,
required = true, description = "Comma separated list of name servers")
private List<String> nameServers;

@Parameter(name = "externalserverid", type = CommandType.STRING, description = "External server id or hostname for the DNS server, e.g., 'localhost' for PowerDNS")
private String externalServerId;

/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////

public String getName() { return name; }

public String getUrl() { return url; }

public String getCredentials() {
return credentials;
}

public Integer getPort() {
return port;
}

public Boolean isPublic() {
return BooleanUtils.isTrue(isPublic);
}

public String getPublicDomainSuffix() {
return publicDomainSuffix;
}

public List<String> getNameServers() {
return nameServers;
}

public DnsProviderType getProvider() {
DnsProviderType dnsProviderType = EnumUtils.getEnumIgnoreCase(DnsProviderType.class, provider, DnsProviderType.PowerDNS);
if (dnsProviderType == null) {
throw new InvalidParameterValueException(String.format("Invalid value passed for provider type, valid values are: %s",
EnumUtils.listValues(DnsProviderType.values())));
}
return dnsProviderType;
}

@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}

@Override
public void execute() {
try {
DnsServer server = dnsProviderManager.addDnsServer(this);
if (server != null) {
DnsServerResponse response = dnsProviderManager.createDnsServerResponse(server);
response.setResponseName(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to add DNS server");
}
} catch (Exception ex) {
logger.error("Failed to add DNS server", ex);
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage());
}
}

public String getExternalServerId() {
return externalServerId;
}

public String getDnsUserName() {
return dnsUserName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// 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.api.command.user.dns;

import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.DnsZoneNetworkMapResponse;
import org.apache.cloudstack.api.response.DnsZoneResponse;
import org.apache.cloudstack.api.response.NetworkResponse;
import org.apache.cloudstack.dns.DnsZone;

import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.NetworkRuleConflictException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.user.Account;

@APICommand(name = "associateDnsZoneToNetwork",
description = "Associates a DNS Zone with a Network for VM auto-registration",
responseObject = DnsZoneNetworkMapResponse.class,
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false,
since = "4.23.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class AssociateDnsZoneToNetworkCmd extends BaseCmd {

@Parameter(name = ApiConstants.DNS_ZONE_ID, type = CommandType.UUID, entityType = DnsZoneResponse.class,
required = true, description = "The ID of the DNS zone")
private Long dnsZoneId;

@ACL(accessType = SecurityChecker.AccessType.OperateEntry)
@Parameter(name = ApiConstants.NETWORK_ID, type = CommandType.UUID, entityType = NetworkResponse.class,
required = true, description = "The ID of the network")
private Long networkId;

@Parameter(name = "subdomain", type = CommandType.STRING,
description = "Optional subdomain to append (e.g., 'dev' creates vm1.dev.example.com)")
private String subDomain;

@Override
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
try {
DnsZoneNetworkMapResponse response = dnsProviderManager.associateZoneToNetwork(this);
response.setResponseName(getCommandName());
setResponseObject(response);
} catch (Exception e) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
}
}

@Override
public long getEntityOwnerId() {
DnsZone zone = _entityMgr.findById(DnsZone.class, dnsZoneId);
if (zone != null) {
return zone.getAccountId();
}
return Account.ACCOUNT_ID_SYSTEM;
}

public Long getDnsZoneId() {
return dnsZoneId;
}

public Long getNetworkId() {
return networkId;
}

public String getSubDomain() {
return subDomain;
}
}
Loading
Loading