Skip to content

Commit df21318

Browse files
committed
1. Setup Dns zone schema
2. added relevant changes in dao and vo 3. worked on creatednszone, integration with mgr 4. powerdns create zone api call
1 parent 9911c28 commit df21318

12 files changed

Lines changed: 293 additions & 54 deletions

File tree

api/src/main/java/org/apache/cloudstack/api/command/user/dns/CreateDnsZoneCmd.java

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
2424
public class CreateDnsZoneCmd extends BaseAsyncCreateCmd {
2525

26-
private static final String s_name = "creatednszoneresponse";
26+
private static final String COMMAND_RESPONSE_NAME = "creatednszoneresponse";
2727

2828
@Inject
2929
DnsProviderManager dnsProviderManager;
@@ -48,8 +48,8 @@ public class CreateDnsZoneCmd extends BaseAsyncCreateCmd {
4848
description = "The type of zone (Public, Private). Defaults to Public.")
4949
private String type;
5050

51-
// Standard CloudStack ownership parameters (account/domain) are handled
52-
// automatically by the BaseCmd parent if we access them via getEntityOwnerId()
51+
@Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "Display text for the zone")
52+
private String description;
5353

5454
/////////////////////////////////////////////////////
5555
/////////////////// Accessors ///////////////////////
@@ -71,16 +71,18 @@ public String getType() {
7171
return type;
7272
}
7373

74+
public String getDescription() {
75+
return description;
76+
}
77+
7478
/////////////////////////////////////////////////////
7579
/////////////// Implementation //////////////////////
7680
/////////////////////////////////////////////////////
7781

7882
@Override
7983
public void create() throws ResourceAllocationException {
80-
// Phase 1: DB Persist
81-
// The manager should create the DnsZoneVO in 'Allocating' state
8284
try {
83-
DnsZone zone = dnsProviderManager.allocDnsZone(this);
85+
DnsZone zone = dnsProviderManager.allocateDnsZone(this);
8486
if (zone != null) {
8587
setEntityId(zone.getId());
8688
setEntityUuid(zone.getUuid());
@@ -94,12 +96,8 @@ public void create() throws ResourceAllocationException {
9496

9597
@Override
9698
public void execute() {
97-
// Phase 2: Action (Call Plugin)
98-
// The manager should retrieve the zone by ID, call the plugin, and update state to 'Ready'
9999
try {
100-
// Note: We use getEntityId() which was set in the create() phase
101100
DnsZone result = dnsProviderManager.provisionDnsZone(getEntityId());
102-
103101
if (result != null) {
104102
DnsZoneResponse response = dnsProviderManager.createDnsZoneResponse(result);
105103
response.setResponseName(getCommandName());
@@ -114,7 +112,7 @@ public void execute() {
114112

115113
@Override
116114
public String getCommandName() {
117-
return s_name;
115+
return COMMAND_RESPONSE_NAME;
118116
}
119117

120118
@Override
@@ -124,7 +122,7 @@ public long getEntityOwnerId() {
124122

125123
@Override
126124
public String getEventType() {
127-
return EventTypes.EVENT_DNS_ZONE_CREATE; // You must add this constant to EventTypes.java
125+
return EventTypes.EVENT_DNS_ZONE_CREATE;
128126
}
129127

130128
@Override

api/src/main/java/org/apache/cloudstack/api/response/DnsZoneResponse.java

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,35 +28,67 @@
2828
@EntityReference(value = DnsZone.class)
2929
public class DnsZoneResponse extends BaseResponse {
3030
@SerializedName(ApiConstants.ID)
31-
@Param(description = "the ID of the DNS zone")
31+
@Param(description = "ID of the DNS zone")
3232
private String id;
3333

3434
@SerializedName(ApiConstants.NAME)
35-
@Param(description = "the name of the DNS zone")
35+
@Param(description = "Name of the DNS zone")
3636
private String name;
3737

3838
@SerializedName("dnsserverid")
39-
@Param(description = "the ID of the DNS server this zone belongs to")
40-
private String dnsServerId;
39+
@Param(description = "ID of the DNS server this zone belongs to")
40+
private Long dnsServerId;
4141

4242
@SerializedName("dnsservername")
43-
@Param(description = "the name of the DNS server this zone belongs to")
43+
@Param(description = "Name of the DNS server this zone belongs to")
4444
private String dnsServerName;
4545

4646
@SerializedName(ApiConstants.NETWORK_ID)
47-
@Param(description = "the ID of the network this zone is associated with")
47+
@Param(description = "ID of the network this zone is associated with")
4848
private String networkId;
4949

5050
@SerializedName(ApiConstants.NETWORK_NAME)
51-
@Param(description = "the name of the network this zone is associated with")
51+
@Param(description = "Name of the network this zone is associated with")
5252
private String networkName;
5353

5454
@SerializedName(ApiConstants.TYPE)
55-
@Param(description = "the type of the zone (Public/Private)")
56-
private String type;
55+
@Param(description = "The type of the zone (Public/Private)")
56+
private DnsZone.ZoneType type;
57+
58+
@SerializedName(ApiConstants.STATE)
59+
@Param(description = "The state of the zone (Active/Inactive)")
60+
private DnsZone.State state;
5761

5862
public DnsZoneResponse() {
5963
super();
6064
setObjectName("dnszone");
6165
}
66+
67+
public void setName(String name) {
68+
this.name = name;
69+
}
70+
71+
public void setDnsServerId(Long dnsServerId) {
72+
this.dnsServerId = dnsServerId;
73+
}
74+
75+
public void setDnsServerName(String dnsServerName) {
76+
this.dnsServerName = dnsServerName;
77+
}
78+
79+
public void setNetworkId(String networkId) {
80+
this.networkId = networkId;
81+
}
82+
83+
public void setNetworkName(String networkName) {
84+
this.networkName = networkName;
85+
}
86+
87+
public void setType(DnsZone.ZoneType type) {
88+
this.type = type;
89+
}
90+
91+
public void setState(DnsZone.State state) {
92+
this.state = state;
93+
}
6294
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,9 @@ public interface DnsProvider extends Adapter {
2828
boolean validate(DnsServer server) throws Exception;
2929

3030
// Zone Operations
31-
boolean createZone(DnsServer server, DnsZone zone);
31+
boolean provisionZone(DnsServer server, DnsZone zone) throws Exception;
3232
boolean deleteZone(DnsServer server, DnsZone zone);
3333

34-
3534
DnsRecord createRecord(DnsServer server, DnsZone zone, DnsRecord record);
3635
boolean updateRecord(DnsServer server, DnsZone zone, DnsRecord record);
3736
boolean deleteRecord(DnsServer server, DnsZone zone, DnsRecord record);

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@ public interface DnsProviderManager extends Manager, PluggableService {
6060

6161
List<String> listProviderNames();
6262

63-
// Allocates the DB row (State: Allocating)
64-
DnsZone allocDnsZone(CreateDnsZoneCmd cmd);
63+
// Allocates the DB row (State: Inactive)
64+
DnsZone allocateDnsZone(CreateDnsZoneCmd cmd);
6565

66-
// Calls the Plugin (State: Allocating -> Ready/Error)
66+
// Calls the Plugin (State: Inactive -> Active)
6767
DnsZone provisionDnsZone(long zoneId);
6868

6969
// Helper to create the response object

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@
1919

2020
import java.util.List;
2121

22+
import org.apache.cloudstack.acl.ControlledEntity;
2223
import org.apache.cloudstack.api.Identity;
2324
import org.apache.cloudstack.api.InternalIdentity;
2425

25-
public interface DnsZone extends InternalIdentity, Identity {
26+
public interface DnsZone extends InternalIdentity, Identity, ControlledEntity {
2627
enum ZoneType {
2728
Public, Private
2829
}
@@ -38,5 +39,9 @@ enum State {
3839

3940
ZoneType getType();
4041

42+
String getDescription();
43+
4144
List<Long> getAssociatedNetworks();
45+
46+
State getState();
4247
}

engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ CREATE TABLE IF NOT EXISTS `cloud`.`webhook_filter` (
5858
-- 1. DNS Server Table (Stores DNS Server Configurations)
5959
CREATE TABLE `cloud`.`dns_server` (
6060
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id of the dns server',
61-
`uuid` varchar(255) COMMENT 'uuid of the dns server',
61+
`uuid` varchar(40) COMMENT 'uuid of the dns server',
6262
`name` varchar(255) NOT NULL COMMENT 'display name of the dns server',
6363
`provider_type` varchar(255) NOT NULL COMMENT 'Provider type such as PowerDns',
6464
`url` varchar(1024) NOT NULL COMMENT 'dns server url',
@@ -79,16 +79,25 @@ CREATE TABLE `cloud`.`dns_server` (
7979
-- 2. DNS Zone Table (Stores DNS Zone Metadata)
8080
CREATE TABLE `cloud`.`dns_zone` (
8181
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id of the dns zone',
82-
`uuid` varchar(255) COMMENT 'uuid of the dns zone',
82+
`uuid` varchar(40) COMMENT 'uuid of the dns zone',
8383
`name` varchar(255) NOT NULL COMMENT 'dns zone name (e.g. example.com)',
8484
`dns_server_id` bigint unsigned NOT NULL COMMENT 'fk to dns_server.id',
8585
`external_reference` VARCHAR(255) COMMENT 'id of external provider resource',
86+
`domain_id` bigint unsigned COMMENT 'for domain-specific ownership',
87+
`account_id` bigint unsigned COMMENT 'account id. foreign key to account table',
88+
`description` varchar(1024) DEFAULT NULL,
89+
`type` ENUM('Private', 'Public') NOT NULL DEFAULT 'Public',
8690
`state` ENUM('Active', 'Inactive') NOT NULL DEFAULT 'Inactive',
8791
`created` datetime NOT NULL COMMENT 'date created',
8892
`removed` datetime DEFAULT NULL COMMENT 'Date removed (soft delete)',
8993
PRIMARY KEY (`id`),
94+
CONSTRAINT `uc_dns_zone__uuid` UNIQUE (`uuid`),
95+
CONSTRAINT `uc_dns_zone__name_server_type` UNIQUE (`name`, `dns_server_id`, `type`),
9096
KEY `i_dns_zone__dns_server` (`dns_server_id`),
91-
CONSTRAINT `fk_dns_zone__dns_server_id` FOREIGN KEY (`dns_server_id`) REFERENCES `dns_server` (`id`) ON DELETE CASCADE
97+
KEY `i_dns_zone__account_id` (`account_id`),
98+
CONSTRAINT `fk_dns_zone__dns_server_id` FOREIGN KEY (`dns_server_id`) REFERENCES `dns_server` (`id`) ON DELETE CASCADE,
99+
CONSTRAINT `fk_dns_zone__account_id` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`) ON DELETE CASCADE,
100+
CONSTRAINT `fk_dns_zone__domain_id` FOREIGN KEY (`domain_id`) REFERENCES `domain` (`id`) ON DELETE CASCADE
92101
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
93102

94103
-- 3. DNS Zone Network Map (One-to-Many Link)

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

Lines changed: 80 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,14 @@
1818
package org.apache.cloudstack.dns.powerdns;
1919

2020
import java.io.IOException;
21+
import java.util.List;
2122

2223
import org.apache.http.HttpStatus;
2324
import org.apache.http.client.config.RequestConfig;
2425
import org.apache.http.client.methods.CloseableHttpResponse;
2526
import org.apache.http.client.methods.HttpGet;
27+
import org.apache.http.client.methods.HttpPost;
28+
import org.apache.http.entity.StringEntity;
2629
import org.apache.http.impl.client.CloseableHttpClient;
2730
import org.apache.http.impl.client.HttpClientBuilder;
2831
import org.apache.http.util.EntityUtils;
@@ -32,6 +35,8 @@
3235
import com.cloud.utils.exception.CloudRuntimeException;
3336
import com.fasterxml.jackson.databind.JsonNode;
3437
import com.fasterxml.jackson.databind.ObjectMapper;
38+
import com.fasterxml.jackson.databind.node.ArrayNode;
39+
import com.fasterxml.jackson.databind.node.ObjectNode;
3540

3641
public class PowerDnsClient implements AutoCloseable {
3742
public static final Logger logger = LoggerFactory.getLogger(PowerDnsClient.class);
@@ -40,19 +45,7 @@ public class PowerDnsClient implements AutoCloseable {
4045
private final CloseableHttpClient httpClient;
4146

4247
public void validate(String baseUrl, String apiKey) {
43-
String normalizedUrl = baseUrl.trim();
44-
45-
if (!normalizedUrl.startsWith("http://") && !normalizedUrl.startsWith("https://")) {
46-
normalizedUrl = "http://" + normalizedUrl; // default to HTTP
47-
}
48-
49-
if (normalizedUrl.endsWith("/")) {
50-
normalizedUrl = normalizedUrl.substring(0, normalizedUrl.length() - 1);
51-
}
52-
53-
54-
String checkUrl = normalizedUrl + "/api/v1/servers";
55-
48+
String checkUrl = buildApiUrl(baseUrl, "/api/v1/servers");
5649
HttpGet request = new HttpGet(checkUrl);
5750
request.addHeader("X-API-Key", apiKey);
5851
request.addHeader("Accept", "application/json");
@@ -94,6 +87,62 @@ public void validate(String baseUrl, String apiKey) {
9487
}
9588
}
9689

90+
public void createZone(String baseUrl, String apiKey, String zoneName, List<String> nameservers) {
91+
String url = buildApiUrl(baseUrl, "/servers/localhost/zones");
92+
ObjectNode json = MAPPER.createObjectNode();
93+
json.put("name", zoneName.endsWith(".") ? zoneName : zoneName + ".");
94+
json.put("kind", "Native");
95+
json.put("dnssec", false);
96+
97+
if (nameservers != null && !nameservers.isEmpty()) {
98+
ArrayNode nsArray = json.putArray("nameservers");
99+
for (String ns : nameservers) {
100+
nsArray.add(ns.endsWith(".") ? ns : ns + ".");
101+
}
102+
}
103+
104+
logger.debug("Creating PowerDNS zone: {} using URL: {}", zoneName, url);
105+
106+
HttpPost request = new HttpPost(url);
107+
request.addHeader("X-API-Key", apiKey);
108+
request.addHeader("Content-Type", "application/json");
109+
request.addHeader("Accept", "application/json");
110+
111+
try {
112+
request.setEntity(new StringEntity(json.toString()));
113+
114+
try (CloseableHttpResponse response = httpClient.execute(request)) {
115+
116+
int statusCode = response.getStatusLine().getStatusCode();
117+
String body = response.getEntity() != null
118+
? EntityUtils.toString(response.getEntity())
119+
: null;
120+
121+
if (statusCode == HttpStatus.SC_CREATED) {
122+
logger.debug("Zone {} created successfully", zoneName);
123+
return;
124+
}
125+
126+
if (statusCode == HttpStatus.SC_CONFLICT) {
127+
throw new CloudRuntimeException("Zone already exists: " + zoneName);
128+
}
129+
130+
if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
131+
statusCode == HttpStatus.SC_FORBIDDEN) {
132+
throw new CloudRuntimeException("Invalid PowerDNS API key");
133+
}
134+
135+
logger.debug("Unexpected PowerDNS response: HTTP {} Body: {}", statusCode, body);
136+
137+
throw new CloudRuntimeException(String.format("Failed to create zone %s (HTTP %d)", zoneName, statusCode));
138+
}
139+
140+
} catch (IOException e) {
141+
throw new CloudRuntimeException("Error while creating PowerDNS zone " + zoneName, e);
142+
}
143+
}
144+
145+
97146
public PowerDnsClient() {
98147
RequestConfig config = RequestConfig.custom()
99148
.setConnectTimeout(TIMEOUT_MS)
@@ -107,6 +156,24 @@ public PowerDnsClient() {
107156
.build();
108157
}
109158

159+
private String normalizeBaseUrl(String baseUrl) {
160+
if (baseUrl == null) {
161+
throw new IllegalArgumentException("PowerDNS base URL cannot be null");
162+
}
163+
String normalizedUrl = baseUrl.trim();
164+
if (!normalizedUrl.startsWith("http://") && !normalizedUrl.startsWith("https://")) {
165+
normalizedUrl = "http://" + normalizedUrl;
166+
}
167+
if (normalizedUrl.endsWith("/")) {
168+
normalizedUrl = normalizedUrl.substring(0, normalizedUrl.length() - 1);
169+
}
170+
return normalizedUrl;
171+
}
172+
173+
private String buildApiUrl(String baseUrl, String path) {
174+
return normalizeBaseUrl(baseUrl) + "/api/v1" + path;
175+
}
176+
110177
@Override
111178
public void close() {
112179
try {

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,20 @@ public boolean validate(DnsServer server) {
5151
}
5252

5353
@Override
54-
public boolean createZone(DnsServer server, DnsZone zone) {
55-
return false;
54+
public boolean provisionZone(DnsServer server, DnsZone zone) throws Exception {
55+
if (StringUtils.isBlank(zone.getName())) {
56+
throw new IllegalArgumentException("Zone name cannot be empty");
57+
}
58+
59+
if (StringUtils.isBlank(server.getUrl())) {
60+
throw new IllegalArgumentException("PowerDNS API URL cannot be empty");
61+
}
62+
63+
if (StringUtils.isBlank(server.getApiKey())) {
64+
throw new IllegalArgumentException("PowerDNS API key cannot be empty");
65+
}
66+
client.createZone(server.getUrl(), server.getApiKey(), zone.getName(), null);
67+
return true;
5668
}
5769

5870
@Override

0 commit comments

Comments
 (0)