Skip to content

Commit 7b9fc0e

Browse files
committed
wip: dns provider framework
1 parent 408e8c0 commit 7b9fc0e

22 files changed

Lines changed: 1211 additions & 1 deletion

api/src/main/java/com/cloud/event/EventTypes.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
import org.apache.cloudstack.backup.BackupRepositoryService;
3131
import org.apache.cloudstack.config.Configuration;
3232
import org.apache.cloudstack.datacenter.DataCenterIpv4GuestSubnet;
33+
import org.apache.cloudstack.dns.DnsRecord;
34+
import org.apache.cloudstack.dns.DnsServer;
35+
import org.apache.cloudstack.dns.DnsZone;
3336
import org.apache.cloudstack.extension.Extension;
3437
import org.apache.cloudstack.extension.ExtensionCustomAction;
3538
import org.apache.cloudstack.gpu.GpuCard;
@@ -859,6 +862,14 @@ public class EventTypes {
859862
public static final String EVENT_BACKUP_REPOSITORY_ADD = "BACKUP.REPOSITORY.ADD";
860863
public static final String EVENT_BACKUP_REPOSITORY_UPDATE = "BACKUP.REPOSITORY.UPDATE";
861864

865+
// DNS Framework Events
866+
public static final String EVENT_DNS_SERVER_ADD = "DNS.SERVER.ADD";
867+
public static final String EVENT_DNS_SERVER_DELETE = "DNS.SERVER.DELETE";
868+
public static final String EVENT_DNS_ZONE_CREATE = "DNS.ZONE.CREATE";
869+
public static final String EVENT_DNS_ZONE_DELETE = "DNS.ZONE.DELETE";
870+
public static final String EVENT_DNS_RECORD_CREATE = "DNS.RECORD.CREATE";
871+
public static final String EVENT_DNS_RECORD_DELETE = "DNS.RECORD.DELETE";
872+
862873
static {
863874

864875
// TODO: need a way to force author adding event types to declare the entity details as well, with out braking
@@ -1397,6 +1408,15 @@ public class EventTypes {
13971408
// Backup Repository
13981409
entityEventDetails.put(EVENT_BACKUP_REPOSITORY_ADD, BackupRepositoryService.class);
13991410
entityEventDetails.put(EVENT_BACKUP_REPOSITORY_UPDATE, BackupRepositoryService.class);
1411+
1412+
// DNS Framework Events
1413+
entityEventDetails.put(EVENT_DNS_SERVER_ADD, DnsServer.class);
1414+
entityEventDetails.put(EVENT_DNS_SERVER_DELETE, DnsServer.class);
1415+
entityEventDetails.put(EVENT_DNS_ZONE_CREATE, DnsZone.class);
1416+
entityEventDetails.put(EVENT_DNS_ZONE_DELETE, DnsZone.class);
1417+
entityEventDetails.put(EVENT_DNS_RECORD_CREATE, DnsRecord.class);
1418+
entityEventDetails.put(EVENT_DNS_RECORD_DELETE, DnsRecord.class);
1419+
14001420
}
14011421

14021422
public static boolean isNetworkEvent(String eventType) {

api/src/main/java/org/apache/cloudstack/api/BaseCmd.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.apache.cloudstack.alert.AlertService;
4141
import org.apache.cloudstack.annotation.AnnotationService;
4242
import org.apache.cloudstack.context.CallContext;
43+
import org.apache.cloudstack.dns.DnsProviderManager;
4344
import org.apache.cloudstack.gpu.GpuService;
4445
import org.apache.cloudstack.network.RoutedIpv4Manager;
4546
import org.apache.cloudstack.network.lb.ApplicationLoadBalancerService;
@@ -230,6 +231,9 @@ public static enum CommandType {
230231
@Inject
231232
public RoutedIpv4Manager routedIpv4Manager;
232233

234+
@Inject
235+
public DnsProviderManager dnsProviderManager;
236+
233237
public abstract void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException,
234238
ResourceAllocationException, NetworkRuleConflictException;
235239

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.apache.cloudstack.api.command.user.dns;
19+
20+
import javax.inject.Inject;
21+
22+
import org.apache.cloudstack.api.APICommand;
23+
import org.apache.cloudstack.api.ApiConstants;
24+
import org.apache.cloudstack.api.BaseCmd;
25+
import org.apache.cloudstack.api.Parameter;
26+
import org.apache.cloudstack.api.response.DnsServerResponse;
27+
import org.apache.cloudstack.context.CallContext;
28+
import org.apache.cloudstack.dns.DnsProviderManager;
29+
import org.apache.cloudstack.dns.DnsServer;
30+
31+
@APICommand(name = "addDnsServer", description = "Adds a new external DNS server",
32+
responseObject = DnsServerResponse.class, requestHasSensitiveInfo = true)
33+
public class AddDnsServerCmd extends BaseCmd {
34+
35+
@Inject
36+
DnsProviderManager dnsProviderManager;
37+
38+
/////////////////////////////////////////////////////
39+
//////////////// API parameters /////////////////////
40+
/////////////////////////////////////////////////////
41+
///
42+
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "Name of the DNS server")
43+
private String name;
44+
45+
@Parameter(name = ApiConstants.URL, type = CommandType.STRING, required = true, description = "API URL of the provider")
46+
private String url;
47+
48+
@Parameter(name = "provider", type = CommandType.STRING, required = true, description = "Provider type (e.g., PowerDNS)")
49+
private String provider;
50+
51+
@Parameter(name = ApiConstants.USERNAME, type = CommandType.STRING, description = "API Username")
52+
private String username;
53+
54+
@Parameter(name = ApiConstants.PASSWORD, type = CommandType.STRING, description = "API Password or Token")
55+
private String password;
56+
57+
/////////////////////////////////////////////////////
58+
/////////////////// Accessors ///////////////////////
59+
/////////////////////////////////////////////////////
60+
61+
public String getName() { return name; }
62+
public String getUrl() { return url; }
63+
public String getProvider() { return provider; }
64+
public String getUsername() { return username; }
65+
public String getPassword() { return password; }
66+
67+
@Override
68+
public void execute() {
69+
DnsServer server = dnsProviderManager.addDnsServer(this);
70+
DnsServerResponse response = dnsProviderManager.createDnsServerResponse(server);
71+
response.setResponseName(getCommandName());
72+
setResponseObject(response);
73+
}
74+
75+
@Override
76+
public long getEntityOwnerId() {
77+
return CallContext.current().getCallingAccount().getId();
78+
}
79+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package org.apache.cloudstack.api.command.user.dns;
2+
3+
import org.apache.cloudstack.api.APICommand;
4+
import org.apache.cloudstack.api.ApiConstants;
5+
import org.apache.cloudstack.api.ApiErrorCode;
6+
import org.apache.cloudstack.api.BaseAsyncCmd;
7+
import org.apache.cloudstack.api.Parameter;
8+
import org.apache.cloudstack.api.ServerApiException;
9+
import org.apache.cloudstack.api.response.DnsRecordResponse;
10+
import org.apache.cloudstack.api.response.DnsZoneResponse;
11+
import org.apache.cloudstack.context.CallContext;
12+
13+
import com.cloud.event.EventTypes;
14+
15+
@APICommand(name = "createDnsRecord", description = "Creates a DNS record directly on the provider",
16+
responseObject = DnsRecordResponse.class)
17+
public class CreateDnsRecordCmd extends BaseAsyncCmd {
18+
19+
@Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = DnsZoneResponse.class, required = true)
20+
private Long zoneId;
21+
22+
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "Record name")
23+
private String name;
24+
25+
@Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, required = true, description = "Record type (A, CNAME)")
26+
private String type;
27+
28+
@Parameter(name = "content", type = CommandType.STRING, required = true, description = "IP or target")
29+
private String content;
30+
31+
@Parameter(name = "ttl", type = CommandType.INTEGER, description = "Time to live")
32+
private Integer ttl;
33+
34+
// Getters
35+
public Long getZoneId() { return zoneId; }
36+
public String getName() { return name; }
37+
public String getType() { return type; }
38+
public String getContent() { return content; }
39+
public Integer getTtl() { return (ttl == null) ? 3600 : ttl; }
40+
41+
@Override
42+
public void execute() {
43+
try {
44+
DnsRecordResponse response = dnsProviderManager.createDnsRecord(this);
45+
response.setResponseName(getCommandName());
46+
setResponseObject(response);
47+
} catch (Exception e) {
48+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create DNS Record: " + e.getMessage());
49+
}
50+
}
51+
52+
@Override
53+
public long getEntityOwnerId() { return CallContext.current().getCallingAccount().getId(); }
54+
55+
@Override
56+
public String getEventType() { return EventTypes.EVENT_DNS_RECORD_CREATE; }
57+
58+
@Override
59+
public String getEventDescription() { return "Creating DNS Record: " + getName(); }
60+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package org.apache.cloudstack.api.command.user.dns;
2+
3+
import javax.inject.Inject;
4+
5+
import org.apache.cloudstack.api.APICommand;
6+
import org.apache.cloudstack.api.ApiConstants;
7+
import org.apache.cloudstack.api.ApiErrorCode;
8+
import org.apache.cloudstack.api.BaseAsyncCreateCmd;
9+
import org.apache.cloudstack.api.Parameter;
10+
import org.apache.cloudstack.api.ServerApiException;
11+
import org.apache.cloudstack.api.response.DnsServerResponse;
12+
import org.apache.cloudstack.api.response.DnsZoneResponse;
13+
import org.apache.cloudstack.api.response.NetworkResponse;
14+
import org.apache.cloudstack.context.CallContext;
15+
import org.apache.cloudstack.dns.DnsProviderManager;
16+
import org.apache.cloudstack.dns.DnsZone;
17+
18+
import com.cloud.event.EventTypes;
19+
import com.cloud.exception.ResourceAllocationException;
20+
21+
@APICommand(name = "createDnsZone", description = "Creates a new DNS Zone on a specific server",
22+
responseObject = DnsZoneResponse.class,
23+
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
24+
public class CreateDnsZoneCmd extends BaseAsyncCreateCmd {
25+
26+
private static final String s_name = "creatednszoneresponse";
27+
28+
@Inject
29+
DnsProviderManager dnsProviderManager;
30+
31+
/////////////////////////////////////////////////////
32+
//////////////// API Parameters /////////////////////
33+
/////////////////////////////////////////////////////
34+
35+
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true,
36+
description = "The name of the DNS zone (e.g. example.com)")
37+
private String name;
38+
39+
@Parameter(name = "dnsserverid", type = CommandType.UUID, entityType = DnsServerResponse.class,
40+
required = true, description = "The ID of the DNS server to host this zone")
41+
private Long dnsServerId;
42+
43+
@Parameter(name = ApiConstants.NETWORK_ID, type = CommandType.UUID, entityType = NetworkResponse.class,
44+
description = "Optional: The Guest Network to associate with this zone for auto-registration")
45+
private Long networkId;
46+
47+
@Parameter(name = ApiConstants.TYPE, type = CommandType.STRING,
48+
description = "The type of zone (Public, Private). Defaults to Public.")
49+
private String type;
50+
51+
// Standard CloudStack ownership parameters (account/domain) are handled
52+
// automatically by the BaseCmd parent if we access them via getEntityOwnerId()
53+
54+
/////////////////////////////////////////////////////
55+
/////////////////// Accessors ///////////////////////
56+
/////////////////////////////////////////////////////
57+
58+
public String getName() {
59+
return name;
60+
}
61+
62+
public Long getDnsServerId() {
63+
return dnsServerId;
64+
}
65+
66+
public Long getNetworkId() {
67+
return networkId;
68+
}
69+
70+
public String getType() {
71+
return type;
72+
}
73+
74+
/////////////////////////////////////////////////////
75+
/////////////// Implementation //////////////////////
76+
/////////////////////////////////////////////////////
77+
78+
@Override
79+
public void create() throws ResourceAllocationException {
80+
// Phase 1: DB Persist
81+
// The manager should create the DnsZoneVO in 'Allocating' state
82+
try {
83+
DnsZone zone = dnsProviderManager.allocDnsZone(this);
84+
if (zone != null) {
85+
setEntityId(zone.getId());
86+
setEntityUuid(zone.getUuid());
87+
} else {
88+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create DNS Zone entity");
89+
}
90+
} catch (Exception e) {
91+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to allocate DNS Zone: " + e.getMessage());
92+
}
93+
}
94+
95+
@Override
96+
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'
99+
try {
100+
// Note: We use getEntityId() which was set in the create() phase
101+
DnsZone result = dnsProviderManager.provisionDnsZone(getEntityId());
102+
103+
if (result != null) {
104+
DnsZoneResponse response = dnsProviderManager.createDnsZoneResponse(result);
105+
response.setResponseName(getCommandName());
106+
setResponseObject(response);
107+
} else {
108+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to provision DNS Zone on external provider");
109+
}
110+
} catch (Exception e) {
111+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to provision DNS Zone: " + e.getMessage());
112+
}
113+
}
114+
115+
@Override
116+
public String getCommandName() {
117+
return s_name;
118+
}
119+
120+
@Override
121+
public long getEntityOwnerId() {
122+
return CallContext.current().getCallingAccount().getId();
123+
}
124+
125+
@Override
126+
public String getEventType() {
127+
return EventTypes.EVENT_DNS_ZONE_CREATE; // You must add this constant to EventTypes.java
128+
}
129+
130+
@Override
131+
public String getEventDescription() {
132+
return "creating DNS zone: " + getName();
133+
}
134+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package org.apache.cloudstack.api.command.user.dns;
2+
3+
import org.apache.cloudstack.api.APICommand;
4+
import org.apache.cloudstack.api.ApiConstants;
5+
import org.apache.cloudstack.api.ApiErrorCode;
6+
import org.apache.cloudstack.api.BaseAsyncCmd;
7+
import org.apache.cloudstack.api.Parameter;
8+
import org.apache.cloudstack.api.ServerApiException;
9+
import org.apache.cloudstack.api.response.DnsZoneResponse;
10+
import org.apache.cloudstack.api.response.SuccessResponse;
11+
import org.apache.cloudstack.context.CallContext;
12+
13+
import com.cloud.event.EventTypes;
14+
15+
@APICommand(name = "deleteDnsRecord", description = "Deletes a DNS record from the external provider",
16+
responseObject = SuccessResponse.class)
17+
public class DeleteDnsRecordCmd extends BaseAsyncCmd {
18+
19+
@Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = DnsZoneResponse.class,
20+
required = true, description = "The ID of the DNS zone")
21+
private Long zoneId;
22+
23+
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true)
24+
private String name;
25+
26+
@Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, required = true)
27+
private String type;
28+
29+
// Getters
30+
public Long getZoneId() { return zoneId; }
31+
public String getName() { return name; }
32+
public String getType() { return type; }
33+
34+
@Override
35+
public void execute() {
36+
try {
37+
boolean result = dnsProviderManager.deleteDnsRecord(this);
38+
if (result) {
39+
SuccessResponse response = new SuccessResponse(getCommandName());
40+
setResponseObject(response);
41+
} else {
42+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete DNS Record");
43+
}
44+
} catch (Exception e) {
45+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Error deleting DNS Record: " + e.getMessage());
46+
}
47+
}
48+
49+
@Override
50+
public long getEntityOwnerId() { return CallContext.current().getCallingAccount().getId(); }
51+
52+
@Override
53+
public String getEventType() { return EventTypes.EVENT_DNS_RECORD_DELETE; }
54+
55+
@Override
56+
public String getEventDescription() { return "Deleting DNS Record: " + getName(); }
57+
}

0 commit comments

Comments
 (0)