This commit is contained in:
Manoj Kumar 2026-05-12 07:07:35 +00:00 committed by GitHub
commit edc9f2334a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
131 changed files with 14391 additions and 17 deletions

View File

@ -30,6 +30,9 @@ import org.apache.cloudstack.api.response.ZoneResponse;
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;
@ -865,6 +868,17 @@ 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";
public static final String EVENT_DNS_NAME_COLLISION = "DNS.NAME.COLLISION";
static {
// TODO: need a way to force author adding event types to declare the entity details as well, with out braking
@ -1406,6 +1420,17 @@ 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_UPDATE, DnsServer.class);
entityEventDetails.put(EVENT_DNS_SERVER_DELETE, DnsServer.class);
entityEventDetails.put(EVENT_DNS_ZONE_CREATE, DnsZone.class);
entityEventDetails.put(EVENT_DNS_ZONE_UPDATE, 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) {

View File

@ -33,6 +33,11 @@ import com.cloud.utils.fsm.StateMachine;
* Nic represents one nic on the VM.
*/
public interface Nic extends Identity, InternalIdentity {
interface Topics {
String NIC_LIFECYCLE = "nic.lifecycle";
}
enum Event {
ReservationRequested, ReleaseRequested, CancelRequested, OperationCompleted, OperationFailed,
}

View File

@ -1363,6 +1363,38 @@ public class ApiConstants {
" a VR, CloudStack will use the same MAC address for the public NIC of all VRs. Otherwise, if \"false\", new public NICs will always have " +
" a new MAC address.";
// DNS provider related
public static final String NAME_SERVERS = "nameservers";
public static final String DNS_USER_NAME = "dnsusername";
public static final String DNS_API_KEY = "dnsapikey";
public static final String DNS_ZONE_ID = "dnszoneid";
public static final String DNS_ZONE = "dnszone";
public static final String DNS_RECORD = "dnsrecord";
public static final String DNS_SUB_DOMAIN = "dnssubdomain";
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_NAME = "nicdnsname";
public static final String TIME_STAMP = "timestamp";
public static final String INSTANCE_ID = "instanceId";
public static final String OLD_STATE = "oldState";
public static final String NEW_STATE = "newState";
public static final String OLD_HOST_NAME = "oldHostName";
public static final String EXISTING = "existing";
public static final String UNMANAGE = "unmanage";
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 " +

View File

@ -40,6 +40,7 @@ import org.apache.cloudstack.affinity.AffinityGroupService;
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;
@ -232,6 +233,9 @@ public abstract class BaseCmd {
@Inject
public RoutedIpv4Manager routedIpv4Manager;
@Inject
public DnsProviderManager dnsProviderManager;
public abstract void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException,
ResourceAllocationException, NetworkRuleConflictException;

View File

@ -0,0 +1,169 @@
// 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.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.collections.MapUtils;
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 DNS provider account (used for authentication)")
private String dnsUserName;
@Parameter(name = ApiConstants.DNS_API_KEY, required = true, type = CommandType.STRING, description = "API key or token for the DNS provider")
private String dnsApiKey;
@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 this DNS server can be used by accounts other than the owner to create and manage DNS zones")
private Boolean isPublic;
@Parameter(name = ApiConstants.PUBLIC_DOMAIN_SUFFIX, type = CommandType.STRING,
description = "Domain suffix that restricts DNS zones created by non-owner accounts to subdomains of this " +
"suffix (for example, sub.example.com under 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; used to create NS records for the DNS Zone (for example, ns1.example.com, ns2.example.com)")
private List<String> nameServers;
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, description = "Details in key/value pairs using " +
"format details[i].keyname=keyvalue. Example: details[0].pdnsServerId=localhost")
protected Map details;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public String getName() { return name; }
public String getUrl() { return url; }
public String getDnsApiKey() {
return dnsApiKey;
}
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 getDnsUserName() {
return dnsUserName;
}
public Map<String, String> getDetails() {
Map<String, String> detailsMap = new HashMap<>();
if (MapUtils.isNotEmpty(details)) {
Collection<?> props = details.values();
for (Object prop : props) {
HashMap<String, String> detail = (HashMap<String, String>) prop;
for (Map.Entry<String, String> entry: detail.entrySet()) {
detailsMap.put(entry.getKey(),entry.getValue());
}
}
}
return detailsMap;
}
}

View File

@ -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 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.network.Network;
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() {
Network network = _entityMgr.findById(Network.class, networkId);
if (network != null) {
return network.getAccountId();
}
return Account.ACCOUNT_ID_SYSTEM;
}
public Long getDnsZoneId() {
return dnsZoneId;
}
public Long getNetworkId() {
return networkId;
}
public String getSubDomain() {
return subDomain;
}
}

View File

@ -0,0 +1,100 @@
// 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.ACL;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.DnsRecordResponse;
import org.apache.cloudstack.api.response.DnsZoneResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.dns.DnsRecord;
import com.cloud.event.EventTypes;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.utils.EnumUtils;
@APICommand(name = "createDnsRecord",
description = "Creates a DNS record directly on the provider",
responseObject = DnsRecordResponse.class,
entityType = {DnsRecord.class},
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
since = "4.23.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class CreateDnsRecordCmd extends BaseAsyncCmd {
@ACL
@Parameter(name = ApiConstants.DNS_ZONE_ID, type = CommandType.UUID, entityType = DnsZoneResponse.class, required = true,
description = "ID of the DNS zone")
private Long dnsZoneId;
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "DNS record name")
private String name;
@Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, required = true, description = "DNS record type (e.g., A, AAAA, CNAME, MX, TXT, etc.)")
private String type;
@Parameter(name = ApiConstants.CONTENTS, type = CommandType.LIST, collectionType = CommandType.STRING, required = true,
description = "The content of the record (IP address for A/AAAA, FQDN for CNAME/NS, quoted string for TXT, etc.)")
private List<String> contents;
@Parameter(name = "ttl", type = CommandType.INTEGER, description = "Time to live")
private Integer ttl;
// Getters
public Long getDnsZoneId() { return dnsZoneId; }
public String getName() { return name; }
public List<String> getContents() { return contents; }
public Integer getTtl() { return (ttl == null) ? 3600 : ttl; }
public DnsRecord.RecordType getType() {
DnsRecord.RecordType dnsRecordType = EnumUtils.getEnumIgnoreCase(DnsRecord.RecordType.class, type);
if (dnsRecordType == null) {
throw new InvalidParameterValueException("Invalid value passed for record type, valid values are: " + EnumUtils.listValues(DnsRecord.RecordType.values()));
}
return dnsRecordType;
}
@Override
public void execute() {
try {
DnsRecordResponse response = dnsProviderManager.createDnsRecord(this);
response.setResponseName(getCommandName());
setResponseObject(response);
} catch (Exception e) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create DNS Record: " + e.getMessage());
}
}
@Override
public long getEntityOwnerId() { return CallContext.current().getCallingAccount().getId(); }
@Override
public String getEventType() { return EventTypes.EVENT_DNS_RECORD_CREATE; }
@Override
public String getEventDescription() { return "Creating DNS Record: " + getName(); }
}

View File

@ -0,0 +1,154 @@
// 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.Arrays;
import org.apache.cloudstack.acl.RoleType;
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.BaseAsyncCreateCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.DnsServerResponse;
import org.apache.cloudstack.api.response.DnsZoneResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.dns.DnsZone;
import org.apache.commons.lang3.StringUtils;
import com.cloud.event.EventTypes;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.utils.EnumUtils;
@APICommand(name = "createDnsZone",
description = "Creates a new DNS Zone on a specific server",
responseObject = DnsZoneResponse.class,
entityType = {DnsZone.class},
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
since = "4.23.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class CreateDnsZoneCmd extends BaseAsyncCreateCmd {
/////////////////////////////////////////////////////
//////////////// API Parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true,
description = "The name of the DNS zone (e.g. example.com)")
private String name;
@ACL
@Parameter(name = ApiConstants.DNS_SERVER_ID, type = CommandType.UUID, entityType = DnsServerResponse.class,
required = true, description = "The ID of the DNS server to host this zone")
private Long dnsServerId;
@Parameter(name = ApiConstants.TYPE, type = CommandType.STRING,
description = "The type of zone (Public, Private). Defaults to Public.")
private String type;
@Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "The description of the DNS zone")
private String description;
@Parameter(name = ApiConstants.EXISTING, type = CommandType.BOOLEAN, entityType = DnsZoneResponse.class,
description = "If true, imports an existing DNS zone from the DNS provider into CloudStack. " +
"If false, creates the zone in the DNS provider and registers it in CloudStack. Default is false")
private Boolean existing = false;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public String getName() {
return name;
}
public Long getDnsServerId() {
return dnsServerId;
}
public DnsZone.ZoneType getType() {
if (StringUtils.isBlank(type)) {
return DnsZone.ZoneType.Public;
}
DnsZone.ZoneType zoneType = EnumUtils.getEnumIgnoreCase(DnsZone.ZoneType.class, type);
if (zoneType == null) {
throw new IllegalArgumentException("Invalid type value, supported values are: " + Arrays.toString(DnsZone.ZoneType.values()));
}
return zoneType;
}
public String getDescription() {
return description;
}
/////////////////////////////////////////////////////
/////////////// Implementation //////////////////////
/////////////////////////////////////////////////////
@Override
public void create() throws ResourceAllocationException {
try {
DnsZone zone = dnsProviderManager.allocateDnsZone(this);
if (zone != null) {
setEntityId(zone.getId());
setEntityUuid(zone.getUuid());
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create DNS Zone entity");
}
} catch (Exception e) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to allocate DNS Zone: " + e.getMessage());
}
}
@Override
public void execute() {
try {
DnsZone result = dnsProviderManager.provisionDnsZone(getEntityId(), isExistingZone());
if (result != null) {
DnsZoneResponse response = dnsProviderManager.createDnsZoneResponse(result);
response.setResponseName(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to provision DNS Zone on external provider");
}
} catch (Exception e) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to provision DNS Zone: " + e.getMessage());
}
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
@Override
public String getEventType() {
return EventTypes.EVENT_DNS_ZONE_CREATE;
}
@Override
public String getEventDescription() {
return "creating DNS zone: " + getName();
}
public Boolean isExistingZone() {
return Boolean.TRUE.equals(existing);
}
}

View File

@ -0,0 +1,92 @@
// 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.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.DnsZoneResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.dns.DnsRecord;
import com.cloud.event.EventTypes;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.utils.EnumUtils;
@APICommand(name = "deleteDnsRecord",
description = "Deletes a DNS record from the external provider",
responseObject = SuccessResponse.class,
entityType = {DnsRecord.class},
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
since = "4.23.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class DeleteDnsRecordCmd extends BaseAsyncCmd {
@ACL(accessType = SecurityChecker.AccessType.OperateEntry)
@Parameter(name = ApiConstants.DNS_ZONE_ID, type = CommandType.UUID, entityType = DnsZoneResponse.class,
required = true, description = "The ID of the DNS zone")
private Long dnsZoneId;
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true)
private String name;
@Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, required = true)
private String type;
// Getters
public DnsRecord.RecordType getType() {
DnsRecord.RecordType dnsRecordType = EnumUtils.getEnumIgnoreCase(DnsRecord.RecordType.class, type);
if (dnsRecordType == null) {
throw new InvalidParameterValueException("Invalid value passed for record type, valid values are: " + EnumUtils.listValues(DnsRecord.RecordType.values()));
}
return dnsRecordType;
}
public Long getDnsZoneId() { return dnsZoneId; }
public String getName() { return name; }
@Override
public void execute() {
try {
boolean result = dnsProviderManager.deleteDnsRecord(this);
if (result) {
SuccessResponse response = new SuccessResponse(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete DNS Record");
}
} catch (Exception e) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Error deleting DNS Record: " + e.getMessage());
}
}
@Override
public long getEntityOwnerId() { return CallContext.current().getCallingAccount().getId(); }
@Override
public String getEventType() { return EventTypes.EVENT_DNS_RECORD_DELETE; }
@Override
public String getEventDescription() { return "Deleting DNS Record: " + getName(); }
}

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.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.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.DnsServerResponse;
import org.apache.cloudstack.api.response.DnsZoneResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.dns.DnsServer;
import com.cloud.event.EventTypes;
import com.cloud.user.Account;
@APICommand(name = "deleteDnsServer",
description = "Removes a DNS server integration",
responseObject = SuccessResponse.class,
entityType = {DnsServer.class},
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
since = "4.23.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class DeleteDnsServerCmd extends BaseAsyncCmd {
/////////////////////////////////////////////////////
//////////////// API Parameters /////////////////////
/////////////////////////////////////////////////////
@ACL(accessType = SecurityChecker.AccessType.OperateEntry)
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = DnsServerResponse.class,
required = true, description = "the ID of the DNS server")
private Long id;
@Parameter(name = ApiConstants.CLEANUP, type = CommandType.BOOLEAN,
entityType = DnsZoneResponse.class, description = "If true, all associated DNS zones will be cleaned up " +
"when the server is removed. Default: true")
private Boolean cleanup = true;
@Parameter(name = ApiConstants.UNMANAGE, type = CommandType.BOOLEAN, entityType = DnsZoneResponse.class,
description = "If true, the DNS zone is only removed from CloudStack (unmanaged); if false, it is removed " +
"from both CloudStack and the DNS provider. Default: false")
private Boolean unmanage = false;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
/////////////////////////////////////////////////////
/////////////// Implementation //////////////////////
/////////////////////////////////////////////////////
@Override
public void execute() {
try {
boolean result = dnsProviderManager.deleteDnsServer(this);
if (result) {
SuccessResponse response = new SuccessResponse(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete DNS server");
}
} catch (Exception e) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete DNS server: " + e.getMessage());
}
}
@Override
public long getEntityOwnerId() {
DnsServer server = _entityMgr.findById(DnsServer.class, id);
if (server != null) {
return server.getAccountId();
}
// If server not found, return System to fail safely (or let manager handle 404)
return Account.ACCOUNT_ID_SYSTEM;
}
@Override
public String getEventType() { return EventTypes.EVENT_DNS_SERVER_DELETE; }
@Override
public String getEventDescription() { return "Deleting DNS server ID: " + getId(); }
public Boolean getCleanup() {
return Boolean.TRUE.equals(cleanup);
}
public Boolean isUnmanage() {
return Boolean.TRUE.equals(unmanage);
}
}

View File

@ -0,0 +1,109 @@
// 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.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.DnsZoneResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.dns.DnsZone;
import com.cloud.event.EventTypes;
import com.cloud.user.Account;
@APICommand(name = "deleteDnsZone",
description = "Removes a DNS Zone from CloudStack and the external provider",
responseObject = SuccessResponse.class,
entityType = {DnsZone.class},
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
since = "4.23.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class DeleteDnsZoneCmd extends BaseAsyncCmd {
/////////////////////////////////////////////////////
//////////////// API Parameters /////////////////////
/////////////////////////////////////////////////////
@ACL(accessType = SecurityChecker.AccessType.OperateEntry)
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = DnsZoneResponse.class, required = true,
description = "The ID of the DNS zone")
private Long id;
@Parameter(name = ApiConstants.UNMANAGE, type = CommandType.BOOLEAN, entityType = DnsZoneResponse.class,
description = "If true, removes the DNS zone only from CloudStack; if false, removes it from " +
"both CloudStack and the DNS provider. Default: false")
private Boolean unmanage = false;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
/////////////////////////////////////////////////////
/////////////// API Implementation //////////////////////
/////////////////////////////////////////////////////
@Override
public void execute() {
try {
boolean result = dnsProviderManager.deleteDnsZone(getId(), isUnmanage());
if (result) {
SuccessResponse response = new SuccessResponse(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete DNS Zone");
}
} catch (Exception e) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
}
}
@Override
public long getEntityOwnerId() {
DnsZone zone = _entityMgr.findById(DnsZone.class, id);
if (zone != null) {
return zone.getAccountId();
}
// Fallback or System if not found (likely to fail in execute() anyway)
return Account.ACCOUNT_ID_SYSTEM;
}
@Override
public String getEventType() {
return EventTypes.EVENT_DNS_ZONE_DELETE;
}
@Override
public String getEventDescription() {
return "Deleting DNS Zone ID: " + getId();
}
public Boolean isUnmanage() {
return Boolean.TRUE.equals(unmanage);
}
}

View File

@ -0,0 +1,80 @@
// 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.NetworkResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
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.network.Network;
import com.cloud.user.Account;
@APICommand(name = "disassociateDnsZoneFromNetwork",
description = "Removes the association between a DNS Zone and a Network",
responseObject = SuccessResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
since = "4.23.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class DisassociateDnsZoneFromNetworkCmd extends BaseCmd {
@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;
@Override
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
try {
boolean result = dnsProviderManager.disassociateZoneFromNetwork(this);
if (result) {
SuccessResponse response = new SuccessResponse(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to disassociate DNS zone from network.");
}
} catch (Exception e) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
}
}
@Override
public long getEntityOwnerId() {
Network network = _entityMgr.findById(Network.class, networkId);
if (network != null) {
return network.getAccountId();
}
return Account.ACCOUNT_ID_SYSTEM;
}
public Long getNetworkId() {
return networkId;
}
}

View File

@ -0,0 +1,53 @@
// 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.ArrayList;
import java.util.List;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.BaseListCmd;
import org.apache.cloudstack.api.response.DnsProviderResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.dns.DnsProvider;
@APICommand(name = "listDnsProviders",
description = "Lists available DNS plugin providers",
responseObject = DnsProviderResponse.class,
entityType = {DnsProvider.class},
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
since = "4.23.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class ListDnsProvidersCmd extends BaseListCmd {
@Override
public void execute() {
List<String> providers = dnsProviderManager.listProviderNames();
ListResponse<DnsProviderResponse> response = new ListResponse<>();
List<DnsProviderResponse> responses = new ArrayList<>();
for (String name : providers) {
DnsProviderResponse resp = new DnsProviderResponse(name);
resp.setName(name);
responses.add(resp);
}
response.setResponses(responses);
response.setResponseName(getCommandName());
setResponseObject(response);
}
}

View File

@ -0,0 +1,54 @@
// 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.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseListCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.DnsRecordResponse;
import org.apache.cloudstack.api.response.DnsZoneResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.dns.DnsRecord;
@APICommand(name = "listDnsRecords",
description = "Lists DNS records from the external provider",
responseObject = DnsRecordResponse.class,
entityType = {DnsRecord.class},
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
since = "4.23.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class ListDnsRecordsCmd extends BaseListCmd {
@Parameter(name = ApiConstants.DNS_ZONE_ID, type = CommandType.UUID, entityType = DnsZoneResponse.class, required = true,
description = "ID of the DNS zone to list records from")
private Long dnsZoneId;
public Long getDnsZoneId() {
return dnsZoneId;
}
@Override
public void execute() {
// The manager will fetch live data from the plugin
ListResponse<DnsRecordResponse> response = dnsProviderManager.listDnsRecords(this);
response.setResponseName(getCommandName());
setResponseObject(response);
}
}

View File

@ -0,0 +1,82 @@
// 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.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseListAccountResourcesCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.DnsServerResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.dns.DnsProviderType;
import org.apache.cloudstack.dns.DnsServer;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.utils.EnumUtils;
@APICommand(name = "listDnsServers",
description = "Lists DNS servers owned by the account.",
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 ListDnsServersCmd extends BaseListAccountResourcesCmd {
/////////////////////////////////////////////////////
//////////////// API Parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = DnsServerResponse.class,
description = "the ID of the DNS server")
private Long id;
@Parameter(name = ApiConstants.PROVIDER_TYPE, type = CommandType.STRING,
description = "filter by provider type (e.g. PowerDNS, Cloudflare)")
private String providerType;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public DnsProviderType getProviderType() {
DnsProviderType dnsProviderType = EnumUtils.getEnumIgnoreCase(DnsProviderType.class, providerType, 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;
}
/////////////////////////////////////////////////////
/////////////// Implementation //////////////////////
/////////////////////////////////////////////////////
@Override
public void execute() {
ListResponse<DnsServerResponse> response = dnsProviderManager.listDnsServers(this);
response.setResponseName(getCommandName());
response.setObjectName("dnsserver");
setResponseObject(response);
}
}

View File

@ -0,0 +1,63 @@
// 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.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseListCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.DnsServerResponse;
import org.apache.cloudstack.api.response.DnsZoneResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.dns.DnsZone;
@APICommand(name = "listDnsZones",
description = "Lists DNS zones.", responseObject = DnsZoneResponse.class,
entityType = {DnsZone.class},
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
since = "4.23.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class ListDnsZonesCmd extends BaseListCmd {
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = DnsZoneResponse.class,
description = "List DNS zone by ID")
private Long id;
@Parameter(name = "dnsserverid", type = CommandType.UUID, entityType = DnsServerResponse.class,
description = "List DNS zones belonging to a specific DNS server")
private Long dnsServerId;
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "List by zone name")
private String name;
public Long getId() { return id; }
public Long getDnsServerId() { return dnsServerId; }
public String getName() { return name; }
@Override
public void execute() {
ListResponse<DnsZoneResponse> response = dnsProviderManager.listDnsZones(this);
response.setResponseName(getCommandName());
setResponseObject(response);
}
}

View File

@ -0,0 +1,151 @@
// 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.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.DnsServerResponse;
import org.apache.cloudstack.dns.DnsServer;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import com.cloud.user.Account;
import com.cloud.utils.EnumUtils;
@APICommand(name = "updateDnsServer",
description = "Update 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 UpdateDnsServerCmd extends BaseCmd {
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@ACL(accessType = SecurityChecker.AccessType.OperateEntry)
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = DnsServerResponse.class,
required = true, description = "The ID of the DNS server to update")
private Long id;
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "Name of the DNS server")
private String name;
@Parameter(name = ApiConstants.URL, type = CommandType.STRING, description = "API URL of the provider")
private String url;
@Parameter(name = ApiConstants.DNS_API_KEY, type = CommandType.STRING, description = "API Key or Credentials for the external provider")
private String dnsApiKey;
@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 this DNS server can be used by accounts other than the owner to create and manage DNS zones")
private Boolean isPublic;
@Parameter(name = ApiConstants.PUBLIC_DOMAIN_SUFFIX, type = CommandType.STRING,
description = "Domain suffix that restricts DNS zones created by non-owner accounts to subdomains of this " +
"suffix (for example, sub.example.com under example.com)")
private String publicDomainSuffix;
@Parameter(name = ApiConstants.NAME_SERVERS, type = CommandType.LIST, collectionType = CommandType.STRING,
description = "Comma separated list of name servers; used to create NS records for the DNS Zone (for example, ns1.example.com, ns2.example.com)")
private List<String> nameServers;
@Parameter(name = ApiConstants.STATE, type = CommandType.STRING, description = "Update state for the DNS server (Enabled, Disabled)")
private String state;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() { return id; }
public String getName() { return name; }
public String getUrl() { return url; }
public String getDnsApiKey() {
return dnsApiKey;
}
public Integer getPort() {
return port;
}
public Boolean isPublic() {
return BooleanUtils.isTrue(isPublic);
}
public String getPublicDomainSuffix() {
return publicDomainSuffix;
}
public String getNameServers() {
if (nameServers == null) {
return null;
}
return StringUtils.join(nameServers.stream()
.filter(StringUtils::isNotBlank)
.map(StringUtils::trim)
.toArray(String[]::new), ",");
}
@Override
public long getEntityOwnerId() {
DnsServer server = _entityMgr.findById(DnsServer.class, id);
if (server != null) {
return server.getAccountId();
}
// If server not found, return System to fail safely (or let manager handle 404)
return Account.ACCOUNT_ID_SYSTEM;
}
@Override
public void execute() {
try {
DnsServer server = dnsProviderManager.updateDnsServer(this);
if (server != null) {
DnsServerResponse response = dnsProviderManager.createDnsServerResponse(server);
response.setResponseName(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update DNS server");
}
} catch (Exception ex) {
logger.error("Failed to add update server", ex);
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage());
}
}
public DnsServer.State getState() {
if (StringUtils.isBlank(state)) {
return null;
}
DnsServer.State dnsState = EnumUtils.getEnumIgnoreCase(DnsServer.State.class, state);
if (dnsState == null) {
throw new IllegalArgumentException("Invalid state value: " + state);
}
return dnsState;
}
}

View File

@ -0,0 +1,95 @@
// 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.DnsZoneResponse;
import org.apache.cloudstack.dns.DnsZone;
import com.cloud.user.Account;
@APICommand(name = "updateDnsZone",
description = "Updates a DNS Zone's metadata",
responseObject = DnsZoneResponse.class,
entityType = {DnsZone.class},
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
since = "4.23.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class UpdateDnsZoneCmd extends BaseCmd {
/////////////////////////////////////////////////////
//////////////// API Parameters /////////////////////
/////////////////////////////////////////////////////
@ACL(accessType = SecurityChecker.AccessType.OperateEntry)
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = DnsZoneResponse.class,
required = true, description = "The ID of the DNS zone")
private Long id;
@Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "The description of the DNS zone to be updated")
private String description;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public String getDescription() {
return description;
}
public Long getId() {
return id;
}
/////////////////////////////////////////////////////
/////////////// Implementation //////////////////////
/////////////////////////////////////////////////////
@Override
public void execute() {
try {
DnsZone result = dnsProviderManager.updateDnsZone(this);
if (result != null) {
DnsZoneResponse response = dnsProviderManager.createDnsZoneResponse(result);
response.setResponseName(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update DNS Zone on external provider");
}
} catch (Exception e) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update DNS Zone: " + e.getMessage());
}
}
@Override
public long getEntityOwnerId() {
DnsZone dnsZone = _entityMgr.findById(DnsZone.class, id);
if (dnsZone != null) {
return dnsZone.getAccountId();
}
return Account.ACCOUNT_ID_SYSTEM;
}
}

View File

@ -0,0 +1,45 @@
// 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.response;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
public class DnsProviderResponse extends BaseResponse {
@SerializedName(ApiConstants.NAME)
@Param(description = "The name of the DNS provider (e.g. PowerDNS, Cloudflare)")
private String name;
public DnsProviderResponse(String name) {
this.name = name;
setObjectName("dnsprovider"); // Sets the JSON wrapper name
}
// Accessors
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,57 @@
// 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.response;
import java.util.List;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import org.apache.cloudstack.dns.DnsRecord;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
public class DnsRecordResponse extends BaseResponse {
@SerializedName(ApiConstants.NAME)
@Param(description = "The record name (e.g., www.example.com.)")
private String name;
@SerializedName(ApiConstants.TYPE)
@Param(description = "The record type (e.g., A, CNAME, TXT)")
private DnsRecord.RecordType type;
@SerializedName("contents")
@Param(description = "The contents of the record (IP address or target)")
private List<String> contents;
@SerializedName("ttl")
@Param(description = "Time to live (TTL) in seconds")
private Integer ttl;
public DnsRecordResponse() {
super();
setObjectName("dnsrecord");
}
// Setters
public void setName(String name) { this.name = name; }
public void setType(DnsRecord.RecordType type) { this.type = type; }
public void setContent(List<String> contents) { this.contents = contents; }
public void setTtl(Integer ttl) { this.ttl = ttl; }
}

View File

@ -0,0 +1,133 @@
// 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.response;
import java.util.List;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import org.apache.cloudstack.api.EntityReference;
import org.apache.cloudstack.dns.DnsServer;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
@EntityReference(value = DnsServer.class)
public class DnsServerResponse extends BaseResponse {
@SerializedName(ApiConstants.ID)
@Param(description = "ID of the DNS server")
private String id;
@SerializedName(ApiConstants.NAME)
@Param(description = "Name of the DNS server")
private String name;
@SerializedName(ApiConstants.URL)
@Param(description = "URL of the DNS server API")
private String url;
@SerializedName(ApiConstants.PORT)
@Param(description = "The port of the DNS server")
private Integer port;
@SerializedName(ApiConstants.PROVIDER)
@Param(description = "The provider type of the DNS server")
private String provider;
@SerializedName(ApiConstants.IS_PUBLIC)
@Param(description = "Is the DNS server publicly available")
private Boolean isPublic;
@SerializedName(ApiConstants.PUBLIC_DOMAIN_SUFFIX)
@Param(description = "The public domain suffix for the DNS server")
private String publicDomainSuffix;
@SerializedName(ApiConstants.NAME_SERVERS)
@Param(description = "Name servers entries associated to DNS server")
private List<String> nameServers;
@SerializedName(ApiConstants.ACCOUNT)
@Param(description = "the account associated with the DNS server")
private String accountName;
@SerializedName(ApiConstants.DOMAIN_ID)
@Param(description = "the ID of the domain associated with the DNS server")
private String domainId;
@SerializedName(ApiConstants.DOMAIN)
@Param(description = "the name of the domain associated with the DNS server")
private String domainName;
@SerializedName(ApiConstants.STATE)
@Param(description = "The state of the account")
private String state;
public DnsServerResponse() {
super();
}
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setUrl(String url) {
this.url = url;
}
public void setProvider(String provider) {
this.provider = provider;
}
public void setPublic(Boolean value) {
isPublic = value;
}
public void setPort(Integer port) {
this.port = port;
}
public void setPublicDomainSuffix(String publicDomainSuffix) {
this.publicDomainSuffix = publicDomainSuffix;
}
public void setNameServers(List<String> nameServers) {
this.nameServers = nameServers;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
public void setDomainId(String domainId) {
this.domainId = domainId;
}
public void setDomainName(String domainName) {
this.domainName = domainName;
}
public void setState(String state) {
this.state = state;
}
}

View File

@ -0,0 +1,64 @@
// 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.response;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
public class DnsZoneNetworkMapResponse extends BaseResponse {
@SerializedName(ApiConstants.ID)
@Param(description = "The ID of the mapping")
private String id;
@SerializedName(ApiConstants.DNS_ZONE_ID)
@Param(description = "The ID of the DNS zone")
private String dnsZoneId;
@SerializedName(ApiConstants.NETWORK_ID)
@Param(description = "The ID of the Network")
private String networkId;
@SerializedName("subdomain")
@Param(description = "The sub domain name of the auto-registered DNS record")
private String subDomain;
public DnsZoneNetworkMapResponse() {
super();
setObjectName("dnszonenetwork");
}
// Setters
public void setId(String id) {
this.id = id;
}
public void setDnsZoneId(String dnsZoneId) {
this.dnsZoneId = dnsZoneId;
}
public void setNetworkId(String networkId) {
this.networkId = networkId;
}
public void setSubDomain(String subDomain) {
this.subDomain = subDomain;
}
}

View File

@ -0,0 +1,139 @@
// 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.response;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import org.apache.cloudstack.api.EntityReference;
import org.apache.cloudstack.dns.DnsZone;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
@EntityReference(value = DnsZone.class)
public class DnsZoneResponse extends BaseResponse {
@SerializedName(ApiConstants.ID)
@Param(description = "ID of the DNS zone")
private String id;
@SerializedName(ApiConstants.NAME)
@Param(description = "Name of the DNS zone")
private String name;
@SerializedName("dnsserverid")
@Param(description = "ID of the DNS server this zone belongs to")
private String dnsServerId;
@SerializedName("dnsservername")
@Param(description = "the name of the DNS server hosting this zone")
private String dnsServerName;
@SerializedName("dnsserveraccount")
@Param(description = "the account name of the DNS server owner")
private String dnsServerAccountName;
@SerializedName(ApiConstants.ACCOUNT)
@Param(description = "the account associated with the DNS zone")
private String accountName;
@SerializedName(ApiConstants.DOMAIN)
@Param(description = "the name of the domain associated with the DNS zone")
private String domainName;
@SerializedName(ApiConstants.DOMAIN_ID)
@Param(description = "the ID of the domain associated with the DNS server")
private String domainId;
@SerializedName(ApiConstants.NETWORK_ID)
@Param(description = "ID of the network this zone is associated with")
private String networkId;
@SerializedName(ApiConstants.NETWORK_NAME)
@Param(description = "Name of the network this zone is associated with")
private String networkName;
@SerializedName(ApiConstants.TYPE)
@Param(description = "The type of the zone (Public/Private)")
private DnsZone.ZoneType type;
@SerializedName(ApiConstants.STATE)
@Param(description = "The state of the zone (Active/Inactive)")
private DnsZone.State state;
@SerializedName(ApiConstants.DESCRIPTION)
@Param(description = "Description for the DNS zone")
private String description;
public DnsZoneResponse() {
super();
setObjectName("dnszone");
}
public void setName(String name) {
this.name = name;
}
public void setDnsServerId(String dnsServerId) {
this.dnsServerId = dnsServerId;
}
public void setNetworkId(String networkId) {
this.networkId = networkId;
}
public void setNetworkName(String networkName) {
this.networkName = networkName;
}
public void setType(DnsZone.ZoneType type) {
this.type = type;
}
public void setState(DnsZone.State state) {
this.state = state;
}
public void setId(String id) {
this.id = id;
}
public void setDescription(String description) {
this.description = description;
}
public void setDnsServerName(String dnsServerName) {
this.dnsServerName = dnsServerName;
}
public void setDnsServerAccountName(String dnsServerAccountName) {
this.dnsServerAccountName = dnsServerAccountName;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
public void setDomainName(String domainName) {
this.domainName = domainName;
}
public void setDomainId(String domainId) {
this.domainId = domainId;
}
}

View File

@ -335,6 +335,14 @@ public class NetworkResponse extends BaseResponseWithAssociatedNetwork implement
@Param(description = ApiConstants.PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, since = "4.23.0")
private Boolean keepMacAddressOnPublicNic;
@SerializedName(ApiConstants.DNS_ZONE)
@Param(description = "DNS zone associated to the network", since = "4.23.0")
private String dnsZone;
@SerializedName(ApiConstants.DNS_SUB_DOMAIN)
@Param(description = "DNS subdomain associated to the network", since = "4.23.0")
private String dnsSubdomain;
public NetworkResponse() {}
public Boolean getDisplayNetwork() {
@ -710,4 +718,12 @@ public class NetworkResponse extends BaseResponseWithAssociatedNetwork implement
public void setKeepMacAddressOnPublicNic(Boolean keepMacAddressOnPublicNic) {
this.keepMacAddressOnPublicNic = keepMacAddressOnPublicNic;
}
public void setDnsZone(String dnsZone) {
this.dnsZone = dnsZone;
}
public void setDnsSubdomain(String dnsSubdomain) {
this.dnsSubdomain = dnsSubdomain;
}
}

View File

@ -150,6 +150,10 @@ public class NicResponse extends BaseResponse {
@Param(description = "whether the NIC is enabled or not")
private Boolean isEnabled;
@SerializedName(ApiConstants.NIC_DNS_NAME)
@Param(description = "DNS name associated with this NIC's IP address")
private String nicDnsName;
public void setVmId(String vmId) {
this.vmId = vmId;
}
@ -428,4 +432,8 @@ public class NicResponse extends BaseResponse {
public void setEnabled(Boolean enabled) {
isEnabled = enabled;
}
public void setNicDnsName(String nicDnsName) {
this.nicDnsName = nicDnsName;
}
}

View File

@ -0,0 +1,45 @@
// 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 org.apache.cloudstack.dns.exception.DnsProviderException;
import com.cloud.utils.component.Adapter;
public interface DnsProvider extends Adapter {
DnsProviderType getProviderType();
// Validates connectivity to the server
void validate(DnsServer server) throws Exception;
String validateAndResolveServer(DnsServer server) throws Exception;
// Zone Operations
String provisionZone(DnsServer server, DnsZone zone) throws DnsProviderException;
void deleteZone(DnsServer server, DnsZone zone) throws DnsProviderException;
void updateZone(DnsServer server, DnsZone zone) throws DnsProviderException;
String addRecord(DnsServer server, DnsZone zone, DnsRecord record) throws DnsProviderException;
List<DnsRecord> listRecords(DnsServer server, DnsZone zone) throws DnsProviderException;
String updateRecord(DnsServer server, DnsZone zone, DnsRecord record) throws DnsProviderException;
String deleteRecord(DnsServer server, DnsZone zone, DnsRecord record) throws DnsProviderException;
boolean dnsRecordExists(DnsServer server, DnsZone zone, String recordName, String recordType) throws DnsProviderException;
boolean dnsZoneExists(DnsServer server, DnsZone zone);
}

View File

@ -0,0 +1,78 @@
// 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 org.apache.cloudstack.api.command.user.dns.AddDnsServerCmd;
import org.apache.cloudstack.api.command.user.dns.AssociateDnsZoneToNetworkCmd;
import org.apache.cloudstack.api.command.user.dns.CreateDnsRecordCmd;
import org.apache.cloudstack.api.command.user.dns.CreateDnsZoneCmd;
import org.apache.cloudstack.api.command.user.dns.DeleteDnsRecordCmd;
import org.apache.cloudstack.api.command.user.dns.DeleteDnsServerCmd;
import org.apache.cloudstack.api.command.user.dns.DisassociateDnsZoneFromNetworkCmd;
import org.apache.cloudstack.api.command.user.dns.ListDnsRecordsCmd;
import org.apache.cloudstack.api.command.user.dns.ListDnsServersCmd;
import org.apache.cloudstack.api.command.user.dns.ListDnsZonesCmd;
import org.apache.cloudstack.api.command.user.dns.UpdateDnsServerCmd;
import org.apache.cloudstack.api.command.user.dns.UpdateDnsZoneCmd;
import org.apache.cloudstack.api.response.DnsRecordResponse;
import org.apache.cloudstack.api.response.DnsServerResponse;
import org.apache.cloudstack.api.response.DnsZoneNetworkMapResponse;
import org.apache.cloudstack.api.response.DnsZoneResponse;
import org.apache.cloudstack.api.response.ListResponse;
import com.cloud.user.Account;
import com.cloud.utils.component.Manager;
import com.cloud.utils.component.PluggableService;
public interface DnsProviderManager extends Manager, PluggableService {
DnsServer addDnsServer(AddDnsServerCmd cmd);
ListResponse<DnsServerResponse> listDnsServers(ListDnsServersCmd cmd);
DnsServer updateDnsServer(UpdateDnsServerCmd cmd);
boolean deleteDnsServer(DeleteDnsServerCmd cmd);
DnsServerResponse createDnsServerResponse(DnsServer server);
// Allocates the DB row (State: Inactive)
DnsZone allocateDnsZone(CreateDnsZoneCmd cmd);
// Calls the Plugin (State: Inactive -> Active)
DnsZone provisionDnsZone(long zoneId, boolean isImport);
DnsZone updateDnsZone(UpdateDnsZoneCmd cmd);
boolean deleteDnsZone(Long id, boolean isUnmanage);
ListResponse<DnsZoneResponse> listDnsZones(ListDnsZonesCmd cmd);
DnsRecordResponse createDnsRecord(CreateDnsRecordCmd cmd);
boolean deleteDnsRecord(DeleteDnsRecordCmd cmd);
ListResponse<DnsRecordResponse> listDnsRecords(ListDnsRecordsCmd cmd);
List<String> listProviderNames();
// Helper to create the response object
DnsZoneResponse createDnsZoneResponse(DnsZone zone);
DnsRecordResponse createDnsRecordResponse(DnsRecord record);
DnsZoneNetworkMapResponse associateZoneToNetwork(AssociateDnsZoneToNetworkCmd cmd);
boolean disassociateZoneFromNetwork(DisassociateDnsZoneFromNetworkCmd cmd);
void checkDnsServerPermission(Account caller, DnsServer dnsServer);
void checkDnsZonePermission(Account caller, DnsZone dnsZone);
}

View File

@ -0,0 +1,23 @@
// 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;
public enum DnsProviderType {
PowerDNS;
// Cloudflare
}

View File

@ -0,0 +1,66 @@
// 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 com.cloud.utils.exception.CloudRuntimeException;
public class DnsRecord {
public enum RecordType {
A, AAAA, CNAME, MX, TXT, SRV, PTR, NS;
public static RecordType fromString(String type) {
if (type == null) return null;
try {
return RecordType.valueOf(type.toUpperCase());
} catch (IllegalArgumentException e) {
throw new CloudRuntimeException("Invalid DNS Record Type: " + type +
". Supported: " + java.util.Arrays.toString(values()));
}
}
}
private String name;
private RecordType type;
private List<String> contents;
private int ttl;
public DnsRecord() {}
public DnsRecord(String name, RecordType type, List<String> contents, int ttl) {
this.name = name;
this.type = type;
this.contents = contents;
this.ttl = ttl;
}
// Getters and Setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public RecordType getType() { return type; }
public void setType(RecordType type) { this.type = type; }
public List<String> getContents() { return contents; }
public void setContents(List<String> contents) { this.contents = contents; }
public int getTtl() { return ttl; }
public void setTtl(int ttl) { this.ttl = ttl; }
}

View File

@ -0,0 +1,62 @@
// 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.Date;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
public interface DnsServer extends InternalIdentity, Identity, ControlledEntity {
enum State {
Enabled, Disabled
};
String getName();
String getUrl();
DnsProviderType getProviderType();
List<String> getNameServers();
String getDnsApiKey();
long getAccountId();
boolean getPublicServer();
Date getCreated();
Date getRemoved();
String getPublicDomainSuffix();
Integer getPort();
Map<String, String> getDetails();
String getDetail(String name);
void setDetails(Map<String, String> details);
void appendDetails(String name, String value);
}

View File

@ -0,0 +1,47 @@
// 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 org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
public interface DnsZone extends InternalIdentity, Identity, ControlledEntity {
enum ZoneType {
Public, Private
}
enum State {
Active, Inactive
}
String getName();
long getDnsServerId();
long getAccountId();
ZoneType getType();
String getDescription();
List<Long> getAssociatedNetworks();
State getState();
}

View File

@ -0,0 +1,27 @@
// 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.exception;
/**
* Thrown when authentication to the DNS provider fails.
*/
public class DnsAuthenticationException extends DnsProviderException {
public DnsAuthenticationException(String message) {
super(message);
}
}

View File

@ -0,0 +1,27 @@
// 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.exception;
/**
* Thrown when attempting to create a zone or record that already exists.
*/
public class DnsConflictException extends DnsProviderException {
public DnsConflictException(String message) {
super(message);
}
}

View File

@ -0,0 +1,27 @@
// 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.exception;
/**
* Thrown when the requested zone or record does not exist.
*/
public class DnsNotFoundException extends DnsProviderException {
public DnsNotFoundException(String message) {
super(message);
}
}

View File

@ -0,0 +1,27 @@
// 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.exception;
/**
* Thrown for unexpected or unknown errors returned by the DNS provider.
*/
public class DnsOperationException extends DnsProviderException {
public DnsOperationException(String message) {
super(message);
}
}

View File

@ -0,0 +1,28 @@
// 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.exception;
public class DnsProviderException extends Exception {
public DnsProviderException(String message) {
super(message);
}
public DnsProviderException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,30 @@
// 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.exception;
import java.io.IOException;
/**
* Thrown when HTTP or network errors occur communicating with the DNS provider.
*/
public class DnsTransportException extends DnsProviderException {
public DnsTransportException(String message, IOException cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,134 @@
// 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.DnsServerResponse;
import org.apache.cloudstack.dns.DnsProviderType;
import org.apache.cloudstack.dns.DnsServer;
import org.junit.Test;
public class AddDnsServerCmdTest extends BaseDnsCmdTest {
private AddDnsServerCmd createCmd() throws Exception {
AddDnsServerCmd cmd = new AddDnsServerCmd();
setField(cmd, "dnsProviderManager", dnsProviderManager);
setField(cmd, "name", "test-dns");
setField(cmd, "url", "http://dns.example.com");
setField(cmd, "provider", "PowerDNS");
setField(cmd, "dnsApiKey", "api-key-123");
setField(cmd, "port", 8081);
setField(cmd, "isPublic", true);
setField(cmd, "publicDomainSuffix", "public.example.com");
setField(cmd, "nameServers", Arrays.asList("ns1.example.com", "ns2.example.com"));
setField(cmd, "dnsUserName", "admin@example.com");
return cmd;
}
@Test
public void testAccessors() throws Exception {
AddDnsServerCmd cmd = createCmd();
assertEquals("test-dns", cmd.getName());
assertEquals("http://dns.example.com", cmd.getUrl());
assertEquals("api-key-123", cmd.getDnsApiKey());
assertEquals(Integer.valueOf(8081), cmd.getPort());
assertTrue(cmd.isPublic());
assertEquals("public.example.com", cmd.getPublicDomainSuffix());
assertEquals(Arrays.asList("ns1.example.com", "ns2.example.com"), cmd.getNameServers());
assertEquals(DnsProviderType.PowerDNS, cmd.getProvider());
assertEquals("admin@example.com", cmd.getDnsUserName());
}
@Test
public void testIsPublicFalse() throws Exception {
AddDnsServerCmd cmd = createCmd();
setField(cmd, "isPublic", false);
assertFalse(cmd.isPublic());
}
@Test
public void testIsPublicNull() throws Exception {
AddDnsServerCmd cmd = createCmd();
setField(cmd, "isPublic", null);
assertFalse(cmd.isPublic());
}
@Test
public void testGetEntityOwnerId() throws Exception {
AddDnsServerCmd cmd = createCmd();
assertEquals(ACCOUNT_ID, cmd.getEntityOwnerId());
}
@Test
public void testGetProviderDefault() throws Exception {
AddDnsServerCmd cmd = createCmd();
setField(cmd, "provider", null);
assertEquals(DnsProviderType.PowerDNS, cmd.getProvider());
}
@Test
public void testGetProviderCaseInsensitive() throws Exception {
AddDnsServerCmd cmd = createCmd();
setField(cmd, "provider", "powerdns");
assertEquals(DnsProviderType.PowerDNS, cmd.getProvider());
}
@Test
public void testExecuteSuccess() throws Exception {
AddDnsServerCmd cmd = createCmd();
DnsServer mockServer = mock(DnsServer.class);
DnsServerResponse mockResponse = new DnsServerResponse();
mockResponse.setName("test-dns");
when(dnsProviderManager.addDnsServer(cmd)).thenReturn(mockServer);
when(dnsProviderManager.createDnsServerResponse(mockServer)).thenReturn(mockResponse);
cmd.execute();
DnsServerResponse response = (DnsServerResponse) cmd.getResponseObject();
assertNotNull(response);
assertEquals("adddnsserverresponse", response.getResponseName());
verify(dnsProviderManager).addDnsServer(cmd);
verify(dnsProviderManager).createDnsServerResponse(mockServer);
}
@Test(expected = ServerApiException.class)
public void testExecuteReturnsNull() throws Exception {
AddDnsServerCmd cmd = createCmd();
when(dnsProviderManager.addDnsServer(cmd)).thenReturn(null);
cmd.execute();
}
@Test(expected = ServerApiException.class)
public void testExecuteThrowsException() throws Exception {
AddDnsServerCmd cmd = createCmd();
when(dnsProviderManager.addDnsServer(cmd)).thenThrow(new RuntimeException("Connection refused"));
cmd.execute();
}
}

View File

@ -0,0 +1,97 @@
// 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.DnsZoneNetworkMapResponse;
import org.junit.Test;
import com.cloud.network.Network;
import com.cloud.user.Account;
public class AssociateDnsZoneToNetworkCmdTest extends BaseDnsCmdTest {
private static final long NETWORK_ID = 200L;
private AssociateDnsZoneToNetworkCmd createCmd() throws Exception {
AssociateDnsZoneToNetworkCmd cmd = new AssociateDnsZoneToNetworkCmd();
setField(cmd, "dnsProviderManager", dnsProviderManager);
setField(cmd, "_entityMgr", entityManager);
setField(cmd, "dnsZoneId", ENTITY_ID);
setField(cmd, "networkId", NETWORK_ID);
setField(cmd, "subDomain", "dev");
return cmd;
}
@Test
public void testAccessors() throws Exception {
AssociateDnsZoneToNetworkCmd cmd = createCmd();
assertEquals(Long.valueOf(ENTITY_ID), cmd.getDnsZoneId());
assertEquals(Long.valueOf(NETWORK_ID), cmd.getNetworkId());
assertEquals("dev", cmd.getSubDomain());
}
@Test
public void testGetEntityOwnerIdWithNetwork() throws Exception {
AssociateDnsZoneToNetworkCmd cmd = createCmd();
Network mockNetwork = mock(Network.class);
when(mockNetwork.getAccountId()).thenReturn(ACCOUNT_ID);
when(entityManager.findById(Network.class, NETWORK_ID))
.thenReturn(mockNetwork);
assertEquals(ACCOUNT_ID, cmd.getEntityOwnerId());
}
@Test
public void testGetEntityOwnerIdNetworkNotFound() throws Exception {
AssociateDnsZoneToNetworkCmd cmd = createCmd();
when(entityManager.findById(Network.class, NETWORK_ID))
.thenReturn(null);
assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId());
}
@Test
public void testExecuteSuccess() throws Exception {
AssociateDnsZoneToNetworkCmd cmd = createCmd();
DnsZoneNetworkMapResponse mockResponse =
new DnsZoneNetworkMapResponse();
when(dnsProviderManager.associateZoneToNetwork(cmd))
.thenReturn(mockResponse);
cmd.execute();
DnsZoneNetworkMapResponse response =
(DnsZoneNetworkMapResponse) cmd.getResponseObject();
assertNotNull(response);
assertEquals("associatednszonetonetworkresponse",
response.getResponseName());
}
@Test(expected = ServerApiException.class)
public void testExecuteThrowsException() throws Exception {
AssociateDnsZoneToNetworkCmd cmd = createCmd();
when(dnsProviderManager.associateZoneToNetwork(cmd))
.thenThrow(new RuntimeException("Error"));
cmd.execute();
}
}

View File

@ -0,0 +1,88 @@
// 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 static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.lang.reflect.Field;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.dns.DnsProviderManager;
import org.junit.After;
import org.junit.Before;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import com.cloud.user.Account;
import com.cloud.utils.db.EntityManager;
/**
* Shared setup for all DNS command unit tests.
*/
public abstract class BaseDnsCmdTest {
protected static final long ACCOUNT_ID = 42L;
protected static final long ENTITY_ID = 100L;
protected DnsProviderManager dnsProviderManager;
protected EntityManager entityManager;
protected Account callingAccount;
private MockedStatic<CallContext> callContextMock;
@Before
public void setUp() {
dnsProviderManager = mock(DnsProviderManager.class);
entityManager = mock(EntityManager.class);
callingAccount = mock(Account.class);
when(callingAccount.getId()).thenReturn(ACCOUNT_ID);
CallContext callContext = mock(CallContext.class);
when(callContext.getCallingAccount()).thenReturn(callingAccount);
callContextMock = Mockito.mockStatic(CallContext.class);
callContextMock.when(CallContext::current).thenReturn(callContext);
}
@After
public void tearDown() {
callContextMock.close();
}
/**
* Sets a private/inherited field value via reflection.
*/
protected void setField(Object target, String fieldName, Object value) throws Exception {
Field field = null;
Class<?> clazz = target.getClass();
while (clazz != null) {
try {
field = clazz.getDeclaredField(fieldName);
break;
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass();
}
}
if (field == null) {
throw new NoSuchFieldException(fieldName + " not found in hierarchy of " + target.getClass().getName());
}
field.setAccessible(true);
field.set(target, value);
}
}

View File

@ -0,0 +1,117 @@
// 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.DnsRecordResponse;
import org.apache.cloudstack.dns.DnsRecord;
import org.junit.Test;
import com.cloud.event.EventTypes;
import com.cloud.exception.InvalidParameterValueException;
public class CreateDnsRecordCmdTest extends BaseDnsCmdTest {
private CreateDnsRecordCmd createCmd() throws Exception {
CreateDnsRecordCmd cmd = new CreateDnsRecordCmd();
setField(cmd, "dnsProviderManager", dnsProviderManager);
setField(cmd, "dnsZoneId", ENTITY_ID);
setField(cmd, "name", "www");
setField(cmd, "type", "A");
setField(cmd, "contents", Arrays.asList("192.168.1.1"));
setField(cmd, "ttl", 7200);
return cmd;
}
@Test
public void testAccessors() throws Exception {
CreateDnsRecordCmd cmd = createCmd();
assertEquals(Long.valueOf(ENTITY_ID), cmd.getDnsZoneId());
assertEquals("www", cmd.getName());
assertEquals(DnsRecord.RecordType.A, cmd.getType());
assertEquals(Arrays.asList("192.168.1.1"), cmd.getContents());
assertEquals(Integer.valueOf(7200), cmd.getTtl());
}
@Test
public void testGetTtlDefault() throws Exception {
CreateDnsRecordCmd cmd = createCmd();
setField(cmd, "ttl", null);
assertEquals(Integer.valueOf(3600), cmd.getTtl());
}
@Test
public void testGetTypeCname() throws Exception {
CreateDnsRecordCmd cmd = createCmd();
setField(cmd, "type", "CNAME");
assertEquals(DnsRecord.RecordType.CNAME, cmd.getType());
}
@Test(expected = InvalidParameterValueException.class)
public void testGetTypeInvalid() throws Exception {
CreateDnsRecordCmd cmd = createCmd();
setField(cmd, "type", "INVALID");
cmd.getType();
}
@Test
public void testGetEntityOwnerId() throws Exception {
CreateDnsRecordCmd cmd = createCmd();
assertEquals(ACCOUNT_ID, cmd.getEntityOwnerId());
}
@Test
public void testEventType() throws Exception {
CreateDnsRecordCmd cmd = createCmd();
assertEquals(EventTypes.EVENT_DNS_RECORD_CREATE, cmd.getEventType());
}
@Test
public void testEventDescription() throws Exception {
CreateDnsRecordCmd cmd = createCmd();
assertEquals("Creating DNS Record: www", cmd.getEventDescription());
}
@Test
public void testExecuteSuccess() throws Exception {
CreateDnsRecordCmd cmd = createCmd();
DnsRecordResponse mockResponse = new DnsRecordResponse();
mockResponse.setName("www");
when(dnsProviderManager.createDnsRecord(cmd)).thenReturn(mockResponse);
cmd.execute();
DnsRecordResponse response = (DnsRecordResponse) cmd.getResponseObject();
assertNotNull(response);
assertEquals("creatednsrecordresponse", response.getResponseName());
}
@Test(expected = ServerApiException.class)
public void testExecuteThrowsException() throws Exception {
CreateDnsRecordCmd cmd = createCmd();
when(dnsProviderManager.createDnsRecord(cmd)).thenThrow(new RuntimeException("Provider error"));
cmd.execute();
}
}

View File

@ -0,0 +1,157 @@
// 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.DnsZoneResponse;
import org.apache.cloudstack.dns.DnsZone;
import org.junit.Test;
import com.cloud.event.EventTypes;
public class CreateDnsZoneCmdTest extends BaseDnsCmdTest {
private CreateDnsZoneCmd createCmd() throws Exception {
CreateDnsZoneCmd cmd = new CreateDnsZoneCmd();
setField(cmd, "dnsProviderManager", dnsProviderManager);
setField(cmd, "name", "example.com");
setField(cmd, "dnsServerId", ENTITY_ID);
setField(cmd, "type", "Public");
setField(cmd, "description", "Test zone");
return cmd;
}
@Test
public void testAccessors() throws Exception {
CreateDnsZoneCmd cmd = createCmd();
assertEquals("example.com", cmd.getName());
assertEquals(Long.valueOf(ENTITY_ID), cmd.getDnsServerId());
assertEquals(DnsZone.ZoneType.Public, cmd.getType());
assertEquals("Test zone", cmd.getDescription());
}
@Test
public void testGetTypePrivate() throws Exception {
CreateDnsZoneCmd cmd = createCmd();
setField(cmd, "type", "Private");
assertEquals(DnsZone.ZoneType.Private, cmd.getType());
}
@Test
public void testGetTypeDefaultsToPublicWhenNull() throws Exception {
CreateDnsZoneCmd cmd = createCmd();
setField(cmd, "type", null);
assertEquals(DnsZone.ZoneType.Public, cmd.getType());
}
@Test
public void testGetTypeDefaultsToPublicWhenBlank() throws Exception {
CreateDnsZoneCmd cmd = createCmd();
setField(cmd, "type", "");
assertEquals(DnsZone.ZoneType.Public, cmd.getType());
}
@Test
public void testGetEntityOwnerId() throws Exception {
CreateDnsZoneCmd cmd = createCmd();
assertEquals(ACCOUNT_ID, cmd.getEntityOwnerId());
}
@Test
public void testEventType() throws Exception {
CreateDnsZoneCmd cmd = createCmd();
assertEquals(EventTypes.EVENT_DNS_ZONE_CREATE, cmd.getEventType());
}
@Test
public void testEventDescription() throws Exception {
CreateDnsZoneCmd cmd = createCmd();
assertEquals("creating DNS zone: example.com", cmd.getEventDescription());
}
@Test
public void testCreateSuccess() throws Exception {
CreateDnsZoneCmd cmd = createCmd();
DnsZone mockZone = mock(DnsZone.class);
when(mockZone.getId()).thenReturn(ENTITY_ID);
when(mockZone.getUuid()).thenReturn("uuid-123");
when(dnsProviderManager.allocateDnsZone(cmd)).thenReturn(mockZone);
cmd.create();
assertEquals(Long.valueOf(ENTITY_ID), cmd.getEntityId());
assertEquals("uuid-123", cmd.getEntityUuid());
}
@Test(expected = ServerApiException.class)
public void testCreateReturnsNull() throws Exception {
CreateDnsZoneCmd cmd = createCmd();
when(dnsProviderManager.allocateDnsZone(cmd)).thenReturn(null);
cmd.create();
}
@Test(expected = ServerApiException.class)
public void testCreateThrowsException() throws Exception {
CreateDnsZoneCmd cmd = createCmd();
when(dnsProviderManager.allocateDnsZone(cmd)).thenThrow(new RuntimeException("DB error"));
cmd.create();
}
@Test
public void testExecuteSuccess() throws Exception {
CreateDnsZoneCmd cmd = createCmd();
cmd.setEntityId(ENTITY_ID);
DnsZone mockZone = mock(DnsZone.class);
DnsZoneResponse mockResponse = new DnsZoneResponse();
mockResponse.setName("example.com");
when(dnsProviderManager.provisionDnsZone(ENTITY_ID, false)).thenReturn(mockZone);
when(dnsProviderManager.createDnsZoneResponse(mockZone)).thenReturn(mockResponse);
cmd.execute();
DnsZoneResponse response = (DnsZoneResponse) cmd.getResponseObject();
assertNotNull(response);
assertEquals("creatednszoneresponse", response.getResponseName());
}
@Test(expected = ServerApiException.class)
public void testExecuteReturnsNull() throws Exception {
CreateDnsZoneCmd cmd = createCmd();
cmd.setEntityId(ENTITY_ID);
when(dnsProviderManager.provisionDnsZone(ENTITY_ID, false)).thenReturn(null);
cmd.execute();
}
@Test(expected = ServerApiException.class)
public void testExecuteThrowsException() throws Exception {
CreateDnsZoneCmd cmd = createCmd();
cmd.setEntityId(ENTITY_ID);
when(dnsProviderManager.provisionDnsZone(ENTITY_ID, false)).thenThrow(new RuntimeException("Provider error"));
cmd.execute();
}
}

View File

@ -0,0 +1,102 @@
// 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.dns.DnsRecord;
import org.junit.Test;
import com.cloud.event.EventTypes;
import com.cloud.exception.InvalidParameterValueException;
public class DeleteDnsRecordCmdTest extends BaseDnsCmdTest {
private DeleteDnsRecordCmd createCmd() throws Exception {
DeleteDnsRecordCmd cmd = new DeleteDnsRecordCmd();
setField(cmd, "dnsProviderManager", dnsProviderManager);
setField(cmd, "dnsZoneId", ENTITY_ID);
setField(cmd, "name", "www");
setField(cmd, "type", "A");
return cmd;
}
@Test
public void testAccessors() throws Exception {
DeleteDnsRecordCmd cmd = createCmd();
assertEquals(Long.valueOf(ENTITY_ID), cmd.getDnsZoneId());
assertEquals("www", cmd.getName());
assertEquals(DnsRecord.RecordType.A, cmd.getType());
}
@Test(expected = InvalidParameterValueException.class)
public void testGetTypeInvalid() throws Exception {
DeleteDnsRecordCmd cmd = createCmd();
setField(cmd, "type", "BOGUS");
cmd.getType();
}
@Test
public void testGetEntityOwnerId() throws Exception {
DeleteDnsRecordCmd cmd = createCmd();
assertEquals(ACCOUNT_ID, cmd.getEntityOwnerId());
}
@Test
public void testEventType() throws Exception {
DeleteDnsRecordCmd cmd = createCmd();
assertEquals(EventTypes.EVENT_DNS_RECORD_DELETE, cmd.getEventType());
}
@Test
public void testEventDescription() throws Exception {
DeleteDnsRecordCmd cmd = createCmd();
assertEquals("Deleting DNS Record: www", cmd.getEventDescription());
}
@Test
public void testExecuteSuccess() throws Exception {
DeleteDnsRecordCmd cmd = createCmd();
when(dnsProviderManager.deleteDnsRecord(cmd)).thenReturn(true);
cmd.execute();
SuccessResponse response = (SuccessResponse) cmd.getResponseObject();
assertNotNull(response);
verify(dnsProviderManager).deleteDnsRecord(cmd);
}
@Test(expected = ServerApiException.class)
public void testExecuteReturnsFalse() throws Exception {
DeleteDnsRecordCmd cmd = createCmd();
when(dnsProviderManager.deleteDnsRecord(cmd)).thenReturn(false);
cmd.execute();
}
@Test(expected = ServerApiException.class)
public void testExecuteThrowsException() throws Exception {
DeleteDnsRecordCmd cmd = createCmd();
when(dnsProviderManager.deleteDnsRecord(cmd)).thenThrow(new RuntimeException("Error"));
cmd.execute();
}
}

View File

@ -0,0 +1,119 @@
// 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.dns.DnsServer;
import org.junit.Test;
import com.cloud.event.EventTypes;
import com.cloud.user.Account;
public class DeleteDnsServerCmdTest extends BaseDnsCmdTest {
private DeleteDnsServerCmd createCmd() throws Exception {
DeleteDnsServerCmd cmd = new DeleteDnsServerCmd();
setField(cmd, "dnsProviderManager", dnsProviderManager);
setField(cmd, "_entityMgr", entityManager);
setField(cmd, "id", ENTITY_ID);
return cmd;
}
@Test
public void testGetId() throws Exception {
DeleteDnsServerCmd cmd = createCmd();
assertEquals(Long.valueOf(ENTITY_ID), cmd.getId());
}
@Test
public void testGetCleanupDefault() throws Exception {
DeleteDnsServerCmd cmd = createCmd();
assertTrue(cmd.getCleanup());
}
@Test
public void testGetCleanupFalse() throws Exception {
DeleteDnsServerCmd cmd = createCmd();
setField(cmd, "cleanup", false);
assertFalse(cmd.getCleanup());
}
@Test
public void testGetEntityOwnerIdWithServer() throws Exception {
DeleteDnsServerCmd cmd = createCmd();
DnsServer mockServer = mock(DnsServer.class);
when(mockServer.getAccountId()).thenReturn(ACCOUNT_ID);
when(entityManager.findById(DnsServer.class, ENTITY_ID)).thenReturn(mockServer);
assertEquals(ACCOUNT_ID, cmd.getEntityOwnerId());
}
@Test
public void testGetEntityOwnerIdServerNotFound() throws Exception {
DeleteDnsServerCmd cmd = createCmd();
when(entityManager.findById(DnsServer.class, ENTITY_ID)).thenReturn(null);
assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId());
}
@Test
public void testEventType() throws Exception {
DeleteDnsServerCmd cmd = createCmd();
assertEquals(EventTypes.EVENT_DNS_SERVER_DELETE, cmd.getEventType());
}
@Test
public void testEventDescription() throws Exception {
DeleteDnsServerCmd cmd = createCmd();
assertEquals("Deleting DNS server ID: " + ENTITY_ID, cmd.getEventDescription());
}
@Test
public void testExecuteSuccess() throws Exception {
DeleteDnsServerCmd cmd = createCmd();
when(dnsProviderManager.deleteDnsServer(cmd)).thenReturn(true);
cmd.execute();
SuccessResponse response = (SuccessResponse) cmd.getResponseObject();
assertNotNull(response);
verify(dnsProviderManager).deleteDnsServer(cmd);
}
@Test(expected = ServerApiException.class)
public void testExecuteReturnsFalse() throws Exception {
DeleteDnsServerCmd cmd = createCmd();
when(dnsProviderManager.deleteDnsServer(cmd)).thenReturn(false);
cmd.execute();
}
@Test(expected = ServerApiException.class)
public void testExecuteThrowsException() throws Exception {
DeleteDnsServerCmd cmd = createCmd();
when(dnsProviderManager.deleteDnsServer(cmd)).thenThrow(new RuntimeException("Error"));
cmd.execute();
}
}

View File

@ -0,0 +1,104 @@
// 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.dns.DnsZone;
import org.junit.Test;
import com.cloud.event.EventTypes;
import com.cloud.user.Account;
public class DeleteDnsZoneCmdTest extends BaseDnsCmdTest {
private DeleteDnsZoneCmd createCmd() throws Exception {
DeleteDnsZoneCmd cmd = new DeleteDnsZoneCmd();
setField(cmd, "dnsProviderManager", dnsProviderManager);
setField(cmd, "_entityMgr", entityManager);
setField(cmd, "id", ENTITY_ID);
return cmd;
}
@Test
public void testGetId() throws Exception {
DeleteDnsZoneCmd cmd = createCmd();
assertEquals(Long.valueOf(ENTITY_ID), cmd.getId());
}
@Test
public void testGetEntityOwnerIdWithZone() throws Exception {
DeleteDnsZoneCmd cmd = createCmd();
DnsZone mockZone = mock(DnsZone.class);
when(mockZone.getAccountId()).thenReturn(ACCOUNT_ID);
when(entityManager.findById(DnsZone.class, ENTITY_ID)).thenReturn(mockZone);
assertEquals(ACCOUNT_ID, cmd.getEntityOwnerId());
}
@Test
public void testGetEntityOwnerIdZoneNotFound() throws Exception {
DeleteDnsZoneCmd cmd = createCmd();
when(entityManager.findById(DnsZone.class, ENTITY_ID)).thenReturn(null);
assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId());
}
@Test
public void testEventType() throws Exception {
DeleteDnsZoneCmd cmd = createCmd();
assertEquals(EventTypes.EVENT_DNS_ZONE_DELETE, cmd.getEventType());
}
@Test
public void testEventDescription() throws Exception {
DeleteDnsZoneCmd cmd = createCmd();
assertEquals("Deleting DNS Zone ID: " + ENTITY_ID, cmd.getEventDescription());
}
@Test
public void testExecuteSuccess() throws Exception {
DeleteDnsZoneCmd cmd = createCmd();
when(dnsProviderManager.deleteDnsZone(ENTITY_ID, false)).thenReturn(true);
cmd.execute();
SuccessResponse response = (SuccessResponse) cmd.getResponseObject();
assertNotNull(response);
verify(dnsProviderManager).deleteDnsZone(ENTITY_ID, false);
}
@Test(expected = ServerApiException.class)
public void testExecuteReturnsFalse() throws Exception {
DeleteDnsZoneCmd cmd = createCmd();
when(dnsProviderManager.deleteDnsZone(ENTITY_ID, false)).thenReturn(false);
cmd.execute();
}
@Test(expected = ServerApiException.class)
public void testExecuteThrowsException() throws Exception {
DeleteDnsZoneCmd cmd = createCmd();
when(dnsProviderManager.deleteDnsZone(ENTITY_ID, false)).thenThrow(new RuntimeException("Error"));
cmd.execute();
}
}

View File

@ -0,0 +1,100 @@
// 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.junit.Test;
import com.cloud.network.Network;
import com.cloud.user.Account;
public class DisassociateDnsZoneFromNetworkCmdTest extends BaseDnsCmdTest {
private static final long NETWORK_ID = 200L;
private DisassociateDnsZoneFromNetworkCmd createCmd() throws Exception {
DisassociateDnsZoneFromNetworkCmd cmd =
new DisassociateDnsZoneFromNetworkCmd();
setField(cmd, "dnsProviderManager", dnsProviderManager);
setField(cmd, "_entityMgr", entityManager);
setField(cmd, "networkId", NETWORK_ID);
return cmd;
}
@Test
public void testGetNetworkId() throws Exception {
DisassociateDnsZoneFromNetworkCmd cmd = createCmd();
assertEquals(Long.valueOf(NETWORK_ID), cmd.getNetworkId());
}
@Test
public void testGetEntityOwnerIdWithNetwork() throws Exception {
DisassociateDnsZoneFromNetworkCmd cmd = createCmd();
Network mockNetwork = mock(Network.class);
when(mockNetwork.getAccountId()).thenReturn(ACCOUNT_ID);
when(entityManager.findById(Network.class, NETWORK_ID))
.thenReturn(mockNetwork);
assertEquals(ACCOUNT_ID, cmd.getEntityOwnerId());
}
@Test
public void testGetEntityOwnerIdNetworkNotFound() throws Exception {
DisassociateDnsZoneFromNetworkCmd cmd = createCmd();
when(entityManager.findById(Network.class, NETWORK_ID))
.thenReturn(null);
assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId());
}
@Test
public void testExecuteSuccess() throws Exception {
DisassociateDnsZoneFromNetworkCmd cmd = createCmd();
when(dnsProviderManager.disassociateZoneFromNetwork(cmd))
.thenReturn(true);
cmd.execute();
SuccessResponse response =
(SuccessResponse) cmd.getResponseObject();
assertNotNull(response);
verify(dnsProviderManager).disassociateZoneFromNetwork(cmd);
}
@Test(expected = ServerApiException.class)
public void testExecuteReturnsFalse() throws Exception {
DisassociateDnsZoneFromNetworkCmd cmd = createCmd();
when(dnsProviderManager.disassociateZoneFromNetwork(cmd))
.thenReturn(false);
cmd.execute();
}
@Test(expected = ServerApiException.class)
public void testExecuteThrowsException() throws Exception {
DisassociateDnsZoneFromNetworkCmd cmd = createCmd();
when(dnsProviderManager.disassociateZoneFromNetwork(cmd))
.thenThrow(new RuntimeException("Error"));
cmd.execute();
}
}

View File

@ -0,0 +1,83 @@
// 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.Collections;
import org.apache.cloudstack.api.response.DnsProviderResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.junit.Test;
public class ListDnsProvidersCmdTest extends BaseDnsCmdTest {
private ListDnsProvidersCmd createCmd() throws Exception {
ListDnsProvidersCmd cmd = new ListDnsProvidersCmd();
setField(cmd, "dnsProviderManager", dnsProviderManager);
return cmd;
}
@Test
public void testExecute() throws Exception {
ListDnsProvidersCmd cmd = createCmd();
when(dnsProviderManager.listProviderNames()).thenReturn(Arrays.asList("PowerDNS"));
cmd.execute();
@SuppressWarnings("unchecked")
ListResponse<DnsProviderResponse> response =
(ListResponse<DnsProviderResponse>) cmd.getResponseObject();
assertNotNull(response);
assertEquals("listdnsprovidersresponse", response.getResponseName());
assertNotNull(response.getResponses());
assertEquals(1, response.getResponses().size());
}
@Test
public void testExecuteMultipleProviders() throws Exception {
ListDnsProvidersCmd cmd = createCmd();
when(dnsProviderManager.listProviderNames())
.thenReturn(Arrays.asList("PowerDNS", "Cloudflare"));
cmd.execute();
@SuppressWarnings("unchecked")
ListResponse<DnsProviderResponse> response =
(ListResponse<DnsProviderResponse>) cmd.getResponseObject();
assertNotNull(response);
assertEquals(2, response.getResponses().size());
}
@Test
public void testExecuteEmptyList() throws Exception {
ListDnsProvidersCmd cmd = createCmd();
when(dnsProviderManager.listProviderNames())
.thenReturn(Collections.emptyList());
cmd.execute();
@SuppressWarnings("unchecked")
ListResponse<DnsProviderResponse> response =
(ListResponse<DnsProviderResponse>) cmd.getResponseObject();
assertNotNull(response);
assertEquals(0, response.getResponses().size());
}
}

View File

@ -0,0 +1,56 @@
// 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.when;
import org.apache.cloudstack.api.response.DnsRecordResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.junit.Test;
public class ListDnsRecordsCmdTest extends BaseDnsCmdTest {
private ListDnsRecordsCmd createCmd() throws Exception {
ListDnsRecordsCmd cmd = new ListDnsRecordsCmd();
setField(cmd, "dnsProviderManager", dnsProviderManager);
setField(cmd, "dnsZoneId", ENTITY_ID);
return cmd;
}
@Test
public void testGetDnsZoneId() throws Exception {
ListDnsRecordsCmd cmd = createCmd();
assertEquals(Long.valueOf(ENTITY_ID), cmd.getDnsZoneId());
}
@Test
public void testExecute() throws Exception {
ListDnsRecordsCmd cmd = createCmd();
ListResponse<DnsRecordResponse> mockListResponse = new ListResponse<>();
when(dnsProviderManager.listDnsRecords(cmd)).thenReturn(mockListResponse);
cmd.execute();
@SuppressWarnings("unchecked")
ListResponse<DnsRecordResponse> response = (ListResponse<DnsRecordResponse>) cmd.getResponseObject();
assertNotNull(response);
assertEquals("listdnsrecordsresponse", response.getResponseName());
}
}

View File

@ -0,0 +1,67 @@
// 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.when;
import org.apache.cloudstack.api.response.DnsServerResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.dns.DnsProviderType;
import org.junit.Test;
public class ListDnsServersCmdTest extends BaseDnsCmdTest {
private ListDnsServersCmd createCmd() throws Exception {
ListDnsServersCmd cmd = new ListDnsServersCmd();
setField(cmd, "dnsProviderManager", dnsProviderManager);
setField(cmd, "id", ENTITY_ID);
setField(cmd, "providerType", "PowerDNS");
return cmd;
}
@Test
public void testAccessors() throws Exception {
ListDnsServersCmd cmd = createCmd();
assertEquals(Long.valueOf(ENTITY_ID), cmd.getId());
assertEquals(DnsProviderType.PowerDNS, cmd.getProviderType());
}
@Test
public void testGetProviderTypeNull() throws Exception {
ListDnsServersCmd cmd = createCmd();
setField(cmd, "providerType", null);
assertEquals(DnsProviderType.PowerDNS, cmd.getProviderType());
}
@Test
public void testExecute() throws Exception {
ListDnsServersCmd cmd = createCmd();
ListResponse<DnsServerResponse> mockListResponse = new ListResponse<>();
when(dnsProviderManager.listDnsServers(cmd)).thenReturn(mockListResponse);
cmd.execute();
@SuppressWarnings("unchecked")
ListResponse<DnsServerResponse> response = (ListResponse<DnsServerResponse>) cmd.getResponseObject();
assertNotNull(response);
assertEquals("listdnsserversresponse", response.getResponseName());
assertEquals("dnsserver", response.getObjectName());
}
}

View File

@ -0,0 +1,60 @@
// 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.when;
import org.apache.cloudstack.api.response.DnsZoneResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.junit.Test;
public class ListDnsZonesCmdTest extends BaseDnsCmdTest {
private ListDnsZonesCmd createCmd() throws Exception {
ListDnsZonesCmd cmd = new ListDnsZonesCmd();
setField(cmd, "dnsProviderManager", dnsProviderManager);
setField(cmd, "id", ENTITY_ID);
setField(cmd, "dnsServerId", 200L);
setField(cmd, "name", "example.com");
return cmd;
}
@Test
public void testAccessors() throws Exception {
ListDnsZonesCmd cmd = createCmd();
assertEquals(Long.valueOf(ENTITY_ID), cmd.getId());
assertEquals(Long.valueOf(200L), cmd.getDnsServerId());
assertEquals("example.com", cmd.getName());
}
@Test
public void testExecute() throws Exception {
ListDnsZonesCmd cmd = createCmd();
ListResponse<DnsZoneResponse> mockListResponse = new ListResponse<>();
when(dnsProviderManager.listDnsZones(cmd)).thenReturn(mockListResponse);
cmd.execute();
@SuppressWarnings("unchecked")
ListResponse<DnsZoneResponse> response = (ListResponse<DnsZoneResponse>) cmd.getResponseObject();
assertNotNull(response);
assertEquals("listdnszonesresponse", response.getResponseName());
}
}

View File

@ -0,0 +1,145 @@
// 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.DnsServerResponse;
import org.apache.cloudstack.dns.DnsServer;
import org.junit.Test;
import com.cloud.user.Account;
public class UpdateDnsServerCmdTest extends BaseDnsCmdTest {
private UpdateDnsServerCmd createCmd() throws Exception {
UpdateDnsServerCmd cmd = new UpdateDnsServerCmd();
setField(cmd, "dnsProviderManager", dnsProviderManager);
setField(cmd, "_entityMgr", entityManager);
setField(cmd, "id", ENTITY_ID);
setField(cmd, "name", "updated-dns");
setField(cmd, "url", "http://updated.dns.com");
setField(cmd, "dnsApiKey", "new-api-key");
setField(cmd, "port", 9090);
setField(cmd, "isPublic", true);
setField(cmd, "publicDomainSuffix", "updated.example.com");
setField(cmd, "nameServers", Arrays.asList("ns1.updated.com", "ns2.updated.com"));
setField(cmd, "state", "Enabled");
return cmd;
}
@Test
public void testAccessors() throws Exception {
UpdateDnsServerCmd cmd = createCmd();
assertEquals(Long.valueOf(ENTITY_ID), cmd.getId());
assertEquals("updated-dns", cmd.getName());
assertEquals("http://updated.dns.com", cmd.getUrl());
assertEquals("new-api-key", cmd.getDnsApiKey());
assertEquals(Integer.valueOf(9090), cmd.getPort());
assertTrue(cmd.isPublic());
assertEquals("updated.example.com", cmd.getPublicDomainSuffix());
assertEquals("ns1.updated.com,ns2.updated.com", cmd.getNameServers());
assertEquals(DnsServer.State.Enabled, cmd.getState());
}
@Test
public void testGetStateDisabled() throws Exception {
UpdateDnsServerCmd cmd = createCmd();
setField(cmd, "state", "Disabled");
assertEquals(DnsServer.State.Disabled, cmd.getState());
}
@Test
public void testGetStateNull() throws Exception {
UpdateDnsServerCmd cmd = createCmd();
setField(cmd, "state", null);
assertNull(cmd.getState());
}
@Test
public void testGetStateBlank() throws Exception {
UpdateDnsServerCmd cmd = createCmd();
setField(cmd, "state", "");
assertNull(cmd.getState());
}
@Test(expected = IllegalArgumentException.class)
public void testGetStateInvalid() throws Exception {
UpdateDnsServerCmd cmd = createCmd();
setField(cmd, "state", "InvalidState");
cmd.getState();
}
@Test
public void testGetEntityOwnerIdWithServer() throws Exception {
UpdateDnsServerCmd cmd = createCmd();
DnsServer mockServer = mock(DnsServer.class);
when(mockServer.getAccountId()).thenReturn(ACCOUNT_ID);
when(entityManager.findById(DnsServer.class, ENTITY_ID)).thenReturn(mockServer);
assertEquals(ACCOUNT_ID, cmd.getEntityOwnerId());
}
@Test
public void testGetEntityOwnerIdServerNotFound() throws Exception {
UpdateDnsServerCmd cmd = createCmd();
when(entityManager.findById(DnsServer.class, ENTITY_ID)).thenReturn(null);
assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId());
}
@Test
public void testExecuteSuccess() throws Exception {
UpdateDnsServerCmd cmd = createCmd();
DnsServer mockServer = mock(DnsServer.class);
DnsServerResponse mockResponse = new DnsServerResponse();
mockResponse.setName("updated-dns");
when(dnsProviderManager.updateDnsServer(cmd)).thenReturn(mockServer);
when(dnsProviderManager.createDnsServerResponse(mockServer)).thenReturn(mockResponse);
cmd.execute();
DnsServerResponse response = (DnsServerResponse) cmd.getResponseObject();
assertNotNull(response);
assertEquals("updatednsserverresponse", response.getResponseName());
}
@Test(expected = ServerApiException.class)
public void testExecuteReturnsNull() throws Exception {
UpdateDnsServerCmd cmd = createCmd();
when(dnsProviderManager.updateDnsServer(cmd)).thenReturn(null);
cmd.execute();
}
@Test(expected = ServerApiException.class)
public void testExecuteThrowsException() throws Exception {
UpdateDnsServerCmd cmd = createCmd();
when(dnsProviderManager.updateDnsServer(cmd)).thenThrow(new RuntimeException("Update failed"));
cmd.execute();
}
}

View File

@ -0,0 +1,96 @@
// 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.DnsZoneResponse;
import org.apache.cloudstack.dns.DnsZone;
import org.junit.Test;
import com.cloud.user.Account;
public class UpdateDnsZoneCmdTest extends BaseDnsCmdTest {
private UpdateDnsZoneCmd createCmd() throws Exception {
UpdateDnsZoneCmd cmd = new UpdateDnsZoneCmd();
setField(cmd, "dnsProviderManager", dnsProviderManager);
setField(cmd, "_entityMgr", entityManager);
setField(cmd, "id", ENTITY_ID);
setField(cmd, "description", "Updated description");
return cmd;
}
@Test
public void testAccessors() throws Exception {
UpdateDnsZoneCmd cmd = createCmd();
assertEquals(Long.valueOf(ENTITY_ID), cmd.getId());
assertEquals("Updated description", cmd.getDescription());
}
@Test
public void testGetEntityOwnerIdWhenZoneExists() throws Exception {
UpdateDnsZoneCmd cmd = createCmd();
DnsZone mockZone = mock(DnsZone.class);
when(mockZone.getAccountId()).thenReturn(ACCOUNT_ID);
when(entityManager.findById(DnsZone.class, ENTITY_ID)).thenReturn(mockZone);
assertEquals(ACCOUNT_ID, cmd.getEntityOwnerId());
}
@Test
public void testGetEntityOwnerIdWhenZoneNotFound() throws Exception {
UpdateDnsZoneCmd cmd = createCmd();
when(entityManager.findById(DnsZone.class, ENTITY_ID)).thenReturn(null);
assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId());
}
@Test
public void testExecuteSuccess() throws Exception {
UpdateDnsZoneCmd cmd = createCmd();
DnsZone mockZone = mock(DnsZone.class);
DnsZoneResponse mockResponse = new DnsZoneResponse();
mockResponse.setName("example.com");
when(dnsProviderManager.updateDnsZone(cmd)).thenReturn(mockZone);
when(dnsProviderManager.createDnsZoneResponse(mockZone)).thenReturn(mockResponse);
cmd.execute();
DnsZoneResponse response = (DnsZoneResponse) cmd.getResponseObject();
assertNotNull(response);
assertEquals("updatednszoneresponse", response.getResponseName());
}
@Test(expected = ServerApiException.class)
public void testExecuteReturnsNull() throws Exception {
UpdateDnsZoneCmd cmd = createCmd();
when(dnsProviderManager.updateDnsZone(cmd)).thenReturn(null);
cmd.execute();
}
@Test(expected = ServerApiException.class)
public void testExecuteThrowsException() throws Exception {
UpdateDnsZoneCmd cmd = createCmd();
when(dnsProviderManager.updateDnsZone(cmd)).thenThrow(new RuntimeException("Update failed"));
cmd.execute();
}
}

View File

@ -0,0 +1,105 @@
// 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import com.cloud.utils.exception.CloudRuntimeException;
public class DnsRecordTest {
@Test
public void testDefaultConstructor() {
DnsRecord record = new DnsRecord();
assertNull(record.getName());
assertNull(record.getType());
assertNull(record.getContents());
assertEquals(0, record.getTtl());
}
@Test
public void testParameterizedConstructor() {
List<String> contents = Arrays.asList("192.168.1.1");
DnsRecord record = new DnsRecord("www", DnsRecord.RecordType.A, contents, 3600);
assertEquals("www", record.getName());
assertEquals(DnsRecord.RecordType.A, record.getType());
assertEquals(contents, record.getContents());
assertEquals(3600, record.getTtl());
}
@Test
public void testSettersAndGetters() {
DnsRecord record = new DnsRecord();
List<String> contents = Arrays.asList("10.0.0.1", "10.0.0.2");
record.setName("mail");
record.setType(DnsRecord.RecordType.AAAA);
record.setContents(contents);
record.setTtl(7200);
assertEquals("mail", record.getName());
assertEquals(DnsRecord.RecordType.AAAA, record.getType());
assertEquals(contents, record.getContents());
assertEquals(7200, record.getTtl());
}
// RecordType.fromString tests
@Test
public void testFromStringValid() {
assertEquals(DnsRecord.RecordType.A, DnsRecord.RecordType.fromString("A"));
assertEquals(DnsRecord.RecordType.AAAA, DnsRecord.RecordType.fromString("AAAA"));
assertEquals(DnsRecord.RecordType.CNAME, DnsRecord.RecordType.fromString("CNAME"));
assertEquals(DnsRecord.RecordType.MX, DnsRecord.RecordType.fromString("MX"));
assertEquals(DnsRecord.RecordType.TXT, DnsRecord.RecordType.fromString("TXT"));
assertEquals(DnsRecord.RecordType.SRV, DnsRecord.RecordType.fromString("SRV"));
assertEquals(DnsRecord.RecordType.PTR, DnsRecord.RecordType.fromString("PTR"));
assertEquals(DnsRecord.RecordType.NS, DnsRecord.RecordType.fromString("NS"));
}
@Test
public void testFromStringCaseInsensitive() {
assertEquals(DnsRecord.RecordType.A, DnsRecord.RecordType.fromString("a"));
assertEquals(DnsRecord.RecordType.CNAME, DnsRecord.RecordType.fromString("cname"));
assertEquals(DnsRecord.RecordType.MX, DnsRecord.RecordType.fromString("mx"));
}
@Test
public void testFromStringNull() {
assertNull(DnsRecord.RecordType.fromString(null));
}
@Test(expected = CloudRuntimeException.class)
public void testFromStringInvalid() {
DnsRecord.RecordType.fromString("INVALID");
}
@Test
public void testRecordTypeValues() {
DnsRecord.RecordType[] values = DnsRecord.RecordType.values();
assertNotNull(values);
assertEquals(8, values.length);
}
}

View File

@ -662,6 +662,12 @@
<artifactId>cloud-utils</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-plugin-dns-powerdns</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>

View File

@ -366,4 +366,6 @@
<bean id="sharedFSProvidersRegistry" class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
</bean>
<bean id="dnsProvidersRegistry" class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry" />
</beans>

View File

@ -0,0 +1,21 @@
#
# 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=dns
parent=core

View File

@ -0,0 +1,31 @@
<!--
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
>
<bean class="org.apache.cloudstack.spring.lifecycle.registry.RegistryLifecycle">
<property name="registry" ref="dnsProvidersRegistry" />
<property name="typeClass" value="org.apache.cloudstack.dns.DnsProvider" />
</bean>
</beans>

View File

@ -112,6 +112,8 @@ public interface VirtualMachineManager extends Manager {
interface Topics {
String VM_POWER_STATE = "vm.powerstate";
String VM_LIFECYCLE_STATE = "vm.lifecycle.state";
String VM_ACTION = "vm.action";
}
/**

View File

@ -16,10 +16,14 @@
// under the License.
package com.cloud.vm.dao;
import java.util.Set;
import org.apache.cloudstack.resourcedetail.ResourceDetailsDao;
import com.cloud.utils.db.GenericDao;
import com.cloud.vm.NicDetailVO;
public interface NicDetailsDao extends GenericDao<NicDetailVO, Long>, ResourceDetailsDao<NicDetailVO> {
void removeDetailsForNicIds(String resourceName, Set<Long> nicIds);
}

View File

@ -17,17 +17,43 @@
package com.cloud.vm.dao;
import java.util.Set;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.vm.NicDetailVO;
@Component
public class NicDetailsDaoImpl extends ResourceDetailsDaoBase<NicDetailVO> implements NicDetailsDao {
private final SearchBuilder<NicDetailVO> ResourceIdNameSearch;
public NicDetailsDaoImpl() {
super();
ResourceIdNameSearch = createSearchBuilder();
ResourceIdNameSearch.and(ApiConstants.NAME, ResourceIdNameSearch.entity().getName(), SearchCriteria.Op.EQ);
ResourceIdNameSearch.and(ApiConstants.RESOURCE_ID, ResourceIdNameSearch.entity().getResourceId(), SearchCriteria.Op.IN);
ResourceIdNameSearch.done();
}
@Override
public void addDetail(long resourceId, String key, String value, boolean display) {
super.addDetail(new NicDetailVO(resourceId, key, value, display));
}
@Override
public void removeDetailsForNicIds(String resourceName, Set<Long> nicIds) {
if (CollectionUtils.isEmpty(nicIds)) {
return;
}
SearchCriteria<NicDetailVO> sc = ResourceIdNameSearch.create();
sc.setParameters(ApiConstants.NAME, resourceName);
sc.setParameters(ApiConstants.RESOURCE_ID, nicIds.toArray());
remove(sc);
}
}

View File

@ -310,4 +310,12 @@
<bean id="importVMTaskDaoImpl" class="com.cloud.vm.dao.ImportVMTaskDaoImpl" />
<bean id="apiKeyPairDaoImpl" class="org.apache.cloudstack.acl.dao.ApiKeyPairDaoImpl" />
<bean id="apiKeyPairPermissionsDaoImpl" class="org.apache.cloudstack.acl.dao.ApiKeyPairPermissionsDaoImpl" />
<bean id="dnsServerDao" class="org.apache.cloudstack.dns.dao.DnsServerDaoImpl" />
<bean id="dnsZoneDao" class="org.apache.cloudstack.dns.dao.DnsZoneDaoImpl" />
<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.NicDnsJoinDaoImpl" />
<bean id="dnsServerDetailsDao" class="org.apache.cloudstack.dns.dao.DnsServerDetailsDaoImpl" />
</beans>

View File

@ -131,3 +131,81 @@ CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_tariff_usage` (
-- Add the 'keep_mac_address_on_public_nic' column to the 'cloud.networks' and 'cloud.vpc' tables
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.networks', 'keep_mac_address_on_public_nic', 'TINYINT(1) NOT NULL DEFAULT 1');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc', 'keep_mac_address_on_public_nic', 'TINYINT(1) NOT NULL DEFAULT 1');
-- ======================================================================
-- DNS Framework Schema
-- ======================================================================
-- DNS Server Table (Stores DNS Server Configurations)
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',
`provider_type` varchar(255) NOT NULL COMMENT 'Provider type such as PowerDns',
`url` varchar(1024) NOT NULL COMMENT 'dns server url',
`dns_username` varchar(255) COMMENT 'username or email for dns server credentials',
`dns_api_key` varchar(255) NOT NULL COMMENT 'api key or token for the dns server ',
`port` int(11) DEFAULT NULL COMMENT 'optional dns server port',
`name_servers` varchar(1024) DEFAULT NULL COMMENT 'Comma separated list of name servers',
`is_public` tinyint(1) NOT NULL DEFAULT '0',
`public_domain_suffix` VARCHAR(255),
`state` ENUM('Enabled', 'Disabled') NOT NULL DEFAULT 'Disabled',
`domain_id` bigint unsigned COMMENT 'for domain-specific ownership',
`account_id` bigint(20) unsigned NOT NULL,
`created` datetime NOT NULL COMMENT 'date created',
`removed` datetime DEFAULT NULL COMMENT 'Date removed (soft delete)',
PRIMARY KEY (`id`),
KEY `i_dns_server__account_id` (`account_id`),
CONSTRAINT `fk_dns_server__account_id` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- DNS Server Details Table
CREATE TABLE IF NOT EXISTS `cloud`.`dns_server_details` (
`id` bigint unsigned UNIQUE NOT NULL AUTO_INCREMENT COMMENT 'id',
`dns_server_id` bigint unsigned NOT NULL COMMENT 'dns_server the detail is related to',
`name` varchar(255) NOT NULL COMMENT 'name of the detail',
`value` varchar(255) NOT NULL COMMENT 'value of the detail',
`display` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Should detail be displayed to the end user',
PRIMARY KEY (`id`),
CONSTRAINT `fk_dns_server_details__dns_server_id` FOREIGN KEY (`dns_server_id`) REFERENCES `dns_server`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- DNS Zone Table (Stores DNS Zone Metadata)
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)',
`dns_server_id` bigint unsigned NOT NULL COMMENT 'fk to dns_server.id',
`external_reference` VARCHAR(255) COMMENT 'id of external provider resource',
`domain_id` bigint unsigned COMMENT 'for domain-specific ownership',
`account_id` bigint unsigned COMMENT 'account id. foreign key to account table',
`description` varchar(1024) DEFAULT NULL,
`type` ENUM('Private', 'Public') NOT NULL DEFAULT 'Public',
`state` ENUM('Active', 'Inactive') NOT NULL DEFAULT 'Inactive',
`created` datetime NOT NULL COMMENT 'date created',
`removed` datetime DEFAULT NULL COMMENT 'Date removed (soft delete)',
PRIMARY KEY (`id`),
CONSTRAINT `uc_dns_zone__uuid` UNIQUE (`uuid`),
KEY `i_dns_zone__dns_server` (`dns_server_id`),
KEY `i_dns_zone__account_id` (`account_id`),
CONSTRAINT `fk_dns_zone__dns_server_id` FOREIGN KEY (`dns_server_id`) REFERENCES `dns_server` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_dns_zone__account_id` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_dns_zone__domain_id` FOREIGN KEY (`domain_id`) REFERENCES `domain` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- DNS Zone Network Map (One-to-Many Link)
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,
`network_id` bigint(20) unsigned NOT NULL COMMENT 'network to which dns zone is associated to',
`sub_domain` varchar(255) DEFAULT NULL COMMENT 'Subdomain for auto-registration',
`created` datetime NOT NULL COMMENT 'date created',
`removed` datetime DEFAULT NULL COMMENT 'Date removed (soft delete)',
PRIMARY KEY (`id`),
CONSTRAINT `uc_dns_zone__uuid` UNIQUE (`uuid`),
KEY `fk_dns_map__zone_id` (`dns_zone_id`),
KEY `fk_dns_map__network_id` (`network_id`),
CONSTRAINT `fk_dns_map__zone_id` FOREIGN KEY (`dns_zone_id`) REFERENCES `dns_zone` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_dns_map__network_id` FOREIGN KEY (`network_id`) REFERENCES `networks` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -0,0 +1,44 @@
-- 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_server_view`;
DROP VIEW IF EXISTS `cloud`.`dns_server_view`;
CREATE VIEW `cloud`.`dns_server_view` AS
SELECT
dns.id,
dns.uuid,
dns.name,
dns.provider_type,
dns.url,
dns.port,
dns.name_servers,
dns.is_public,
dns.public_domain_suffix,
dns.state,
dns.created,
dns.removed,
account.account_name account_name,
domain.name domain_name,
domain.uuid domain_uuid,
domain.path domain_path
FROM
`cloud`.`dns_server` dns
INNER JOIN
`cloud`.`account` account ON dns.account_id = account.id
INNER JOIN
`cloud`.`domain` domain ON dns.domain_id = domain.id;

View File

@ -0,0 +1,45 @@
-- 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_zone_view`;
DROP VIEW IF EXISTS `cloud`.`dns_zone_view`;
CREATE VIEW `cloud`.`dns_zone_view` AS
SELECT
zone.id,
zone.uuid,
zone.name,
zone.dns_server_id,
zone.state,
zone.description,
server.uuid dns_server_uuid,
server.name dns_server_name,
server_account.account_name dns_server_account_name,
account.account_name account_name,
domain.name domain_name,
domain.uuid domain_uuid,
domain.path domain_path
FROM
`cloud`.`dns_zone` zone
INNER JOIN
`cloud`.`dns_server` server ON zone.dns_server_id = server.id
INNER JOIN
`cloud`.`account` server_account ON server.account_id = server_account.id
INNER JOIN
`cloud`.`account` account ON zone.account_id = account.id
INNER JOIN
`cloud`.`domain` domain ON zone.domain_id = domain.id;

View File

@ -143,6 +143,7 @@ SELECT
`nics`.`broadcast_uri` AS `broadcast_uri`,
`nics`.`isolation_uri` AS `isolation_uri`,
`nics`.`enabled` AS `is_nic_enabled`,
`nic_details`.`value` AS `nic_dns_name`,
`vpc`.`id` AS `vpc_id`,
`vpc`.`uuid` AS `vpc_uuid`,
`networks`.`uuid` AS `network_uuid`,
@ -187,7 +188,7 @@ SELECT
`lease_expiry_action`.`value` AS `lease_expiry_action`,
`lease_action_execution`.`value` AS `lease_action_execution`
FROM
(((((((((((((((((((((((((((((((((((((`user_vm`
((((((((((((((((((((((((((((((((((((((`user_vm`
JOIN `vm_instance` ON (((`vm_instance`.`id` = `user_vm`.`id`)
AND ISNULL(`vm_instance`.`removed`))))
JOIN `account` ON ((`vm_instance`.`account_id` = `account`.`id`)))
@ -214,6 +215,7 @@ FROM
LEFT JOIN `user_data` ON ((`user_data`.`id` = `user_vm`.`user_data_id`)))
LEFT JOIN `nics` ON (((`vm_instance`.`id` = `nics`.`instance_id`)
AND ISNULL(`nics`.`removed`))))
LEFT JOIN `nic_details` ON ((`nic_details`.`nic_id` = `nics`.`id`) AND (`nic_details`.`name` = 'nicdnsname')))
LEFT JOIN `networks` ON ((`nics`.`network_id` = `networks`.`id`)))
LEFT JOIN `vpc` ON (((`networks`.`vpc_id` = `vpc`.`id`)
AND ISNULL(`vpc`.`removed`))))

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`.`nic_dns_view`;
DROP VIEW IF EXISTS `cloud`.`nic_dns_view`;
CREATE VIEW `cloud`.`nic_dns_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_name,
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 = 'nicdnsname'
WHERE
n.instance_id IS NOT NULL AND map.removed IS NULL;

View File

@ -0,0 +1,30 @@
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-plugin-dns-powerdns</artifactId>
<name>Apache CloudStack Plugin - PowerDNS</name>
<parent>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloudstack-plugins</artifactId>
<version>4.23.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
</project>

View File

@ -0,0 +1,390 @@
// 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.powerdns;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.dns.exception.DnsAuthenticationException;
import org.apache.cloudstack.dns.exception.DnsConflictException;
import org.apache.cloudstack.dns.exception.DnsNotFoundException;
import org.apache.cloudstack.dns.exception.DnsOperationException;
import org.apache.cloudstack.dns.exception.DnsProviderException;
import org.apache.cloudstack.dns.exception.DnsTransportException;
import org.apache.commons.collections.CollectionUtils;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.cloud.utils.StringUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class PowerDnsClient implements AutoCloseable {
public static final Logger logger = LoggerFactory.getLogger(PowerDnsClient.class);
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final int CONNECT_TIMEOUT_MS = 5_000;
private static final int SOCKET_TIMEOUT_MS = 10_000;
private static final int MAX_CONNECTIONS_TOTAL = 50;
private static final int MAX_CONNECTIONS_PER_ROUTE = 10;
private static final String API_PREFIX = "/api/v1";
public static final String DEFAULT_SERVER_NAME = "localhost";
private final CloseableHttpClient httpClient;
public PowerDnsClient() {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(MAX_CONNECTIONS_TOTAL);
connectionManager.setDefaultMaxPerRoute(MAX_CONNECTIONS_PER_ROUTE);
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(CONNECT_TIMEOUT_MS)
.setConnectionRequestTimeout(CONNECT_TIMEOUT_MS)
.setSocketTimeout(SOCKET_TIMEOUT_MS)
.build();
this.httpClient = HttpClientBuilder.create()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig)
.evictIdleConnections(30, TimeUnit.SECONDS)
.disableCookieManagement()
.build();
}
public String resolveServerId(String baseUrl, Integer port, String apiKey, String externalServerId) throws DnsProviderException {
if (StringUtils.isNotBlank(externalServerId)) {
return validateServerId(baseUrl, port, apiKey, externalServerId);
}
return discoverAuthoritativeServerId(baseUrl, port, apiKey);
}
public String validateServerId(String baseUrl, Integer port, String apiKey, String externalServerId) throws DnsProviderException {
String encodedServer = URLEncoder.encode(externalServerId, StandardCharsets.UTF_8);
HttpGet request = new HttpGet(buildUrl(baseUrl, port, "/servers/" + encodedServer));
JsonNode server = execute(request, apiKey, 200);
if (!ApiConstants.AUTHORITATIVE.equalsIgnoreCase(server.path("daemon_type").asText(null))) {
throw new DnsOperationException(String.format("Server %s is not authoritative type=%s", externalServerId,
server.path("daemon_type").asText(null)));
}
return externalServerId;
}
public String discoverAuthoritativeServerId(String baseUrl, Integer port, String apiKey) throws DnsProviderException {
String url = buildUrl(baseUrl, port , "/servers");
HttpGet request = new HttpGet(url);
JsonNode servers = execute(request, apiKey, 200);
if (servers == null || !servers.isArray() || servers.isEmpty()) {
throw new DnsOperationException("No servers returned by PowerDNS API");
}
String fallbackId = null;
for (JsonNode server : servers) {
String daemonType = server.path("daemon_type").asText(null);
if (!ApiConstants.AUTHORITATIVE.equalsIgnoreCase(daemonType)) {
continue;
}
String serverId = server.path(ApiConstants.ID).asText(null);
if (StringUtils.isBlank(serverId)) {
continue;
}
// Prefer localhost if present
if (DEFAULT_SERVER_NAME.equals(serverId)) {
return serverId;
}
if (fallbackId == null) {
fallbackId = serverId;
}
}
if (fallbackId != null) {
return fallbackId;
}
throw new DnsOperationException("No authoritative PowerDNS server found");
}
public String createZone(String baseUrl, Integer port, String apiKey, String externalServerId, String zoneName,
String zoneKind, boolean dnsSecFlag, List<String> nameServers) throws DnsProviderException {
validateServerId(baseUrl, port, apiKey, externalServerId);
String normalizedZone = normalizeZone(zoneName);
ObjectNode json = MAPPER.createObjectNode();
json.put(ApiConstants.NAME, normalizedZone);
json.put(ApiConstants.KIND, zoneKind);
json.put(ApiConstants.DNS_SEC, dnsSecFlag);
if (!CollectionUtils.isEmpty(nameServers)) {
ArrayNode nsArray = json.putArray(ApiConstants.NAME_SERVERS);
for (String ns : nameServers) {
nsArray.add(ns.endsWith(".") ? ns : ns + ".");
}
}
HttpPost request = new HttpPost(buildUrl(baseUrl, port, "/servers/" + externalServerId + "/zones"));
request.setEntity(new StringEntity(json.toString(), StandardCharsets.UTF_8));
JsonNode response = execute(request, apiKey, 201);
if (response == null) {
throw new DnsOperationException("Empty response from DNS server");
}
String zoneId = response.path(ApiConstants.ID).asText();
if (StringUtils.isBlank(zoneId)) {
throw new DnsOperationException("PowerDNS returned empty zone id");
}
return zoneId;
}
public void updateZone(String baseUrl, Integer port, String apiKey, String externalServerId, String zoneName,
String zoneKind, Boolean dnsSecFlag, List<String> nameServers) throws DnsProviderException {
validateServerId(baseUrl, port, apiKey, externalServerId);
String normalizedZone = normalizeZone(zoneName);
String encodedZone = URLEncoder.encode(normalizedZone, StandardCharsets.UTF_8);
String url = buildUrl(baseUrl, port,"/servers/" + externalServerId + "/zones/" + encodedZone);
ObjectNode json = MAPPER.createObjectNode();
if (dnsSecFlag != null) {
json.put(ApiConstants.DNS_SEC, dnsSecFlag);
}
if (StringUtils.isNotBlank(zoneKind)) {
json.put(ApiConstants.KIND, zoneKind);
}
if (!CollectionUtils.isEmpty(nameServers)) {
ArrayNode nsArray = json.putArray(ApiConstants.NAME_SERVERS);
for (String ns : nameServers) {
nsArray.add(ns.endsWith(".") ? ns : ns + ".");
}
}
HttpPut request = new HttpPut(url);
request.setEntity(new StringEntity(json.toString(), StandardCharsets.UTF_8));
execute(request, apiKey, 204);
}
public void deleteZone(String baseUrl, Integer port, String apiKey, String externalServerId, String zoneName) throws DnsProviderException {
validateServerId(baseUrl, port, apiKey, externalServerId);
String normalizedZone = normalizeZone(zoneName);
String encodedZone = URLEncoder.encode(normalizedZone, StandardCharsets.UTF_8);
HttpDelete request = new HttpDelete(buildUrl(baseUrl, port, "/servers/" + externalServerId + "/zones/" + encodedZone));
execute(request, apiKey, 204, 404);
}
public String modifyRecord(String baseUrl, Integer port, String apiKey, String externalServerId, String zoneName,
String recordName, String type, long ttl, List<String> contents, String changeType) throws DnsProviderException {
validateServerId(baseUrl, port, apiKey, externalServerId);
String normalizedZone = normalizeZone(zoneName);
String normalizedRecord = normalizeRecordName(recordName, normalizedZone);
ObjectNode root = MAPPER.createObjectNode();
ArrayNode rrsets = root.putArray(ApiConstants.RR_SETS);
ObjectNode rrset = rrsets.addObject();
rrset.put(ApiConstants.NAME, normalizedRecord);
rrset.put(ApiConstants.TYPE, type.toUpperCase());
rrset.put(ApiConstants.TTL, ttl);
rrset.put(ApiConstants.CHANGE_TYPE, changeType);
ArrayNode records = rrset.putArray(ApiConstants.RECORDS);
if (!CollectionUtils.isEmpty(contents)) {
for (String content : contents) {
ObjectNode record = records.addObject();
record.put(ApiConstants.CONTENT, content);
record.put(ApiConstants.DISABLED, false);
}
}
String encodedZone = URLEncoder.encode(normalizedZone, StandardCharsets.UTF_8);
HttpPatch request = new HttpPatch(buildUrl(baseUrl, port, "/servers/" + externalServerId + "/zones/" + encodedZone));
request.setEntity(new StringEntity(root.toString(), StandardCharsets.UTF_8));
execute(request, apiKey, 204);
return normalizedRecord.endsWith(".") ? normalizedRecord.substring(0, normalizedRecord.length() - 1) : normalizedRecord;
}
public Iterable<JsonNode> listRecords(String baseUrl, Integer port, String apiKey, String externalServerId, String zoneName) throws DnsProviderException {
validateServerId(baseUrl, port, apiKey, externalServerId);
String normalizedZone = normalizeZone(zoneName);
String encodedZone = URLEncoder.encode(normalizedZone, StandardCharsets.UTF_8);
HttpGet request = new HttpGet(buildUrl(baseUrl, port, "/servers/" + externalServerId + "/zones/" + encodedZone));
JsonNode zoneNode = execute(request, apiKey, 200);
if (zoneNode == null || !zoneNode.has(ApiConstants.RR_SETS)) {
return Collections.emptyList();
}
JsonNode rrsets = zoneNode.path(ApiConstants.RR_SETS);
return rrsets.isArray() ? rrsets : Collections.emptyList();
}
public boolean zoneExists(String baseUrl, Integer port, String apiKey, String externalServerId, String zoneName) {
try {
validateServerId(baseUrl, port, apiKey, externalServerId);
String normalizedZone = normalizeZone(zoneName);
String encodedZone = URLEncoder.encode(normalizedZone, StandardCharsets.UTF_8);
HttpGet request = new HttpGet(buildUrl(baseUrl, port, "/servers/" + externalServerId + "/zones/" + encodedZone));
execute(request, apiKey, 200);
return true;
} catch (DnsProviderException | IllegalArgumentException e) {
return false;
}
}
public boolean recordExists(String baseUrl, Integer port, String apiKey,
String externalServerId, String zoneName,
String recordName, String type) throws DnsProviderException {
validateServerId(baseUrl, port, apiKey, externalServerId);
String normalizedZone = normalizeZone(zoneName);
String normalizedRecord = normalizeRecordName(recordName, normalizedZone);
String encodedZone = URLEncoder.encode(normalizedZone, StandardCharsets.UTF_8);
String urlPath = "/servers/" + externalServerId + "/zones/" + encodedZone +
"?rrset_name=" + URLEncoder.encode(normalizedRecord, StandardCharsets.UTF_8) +
"&rrset_type=" + type.toUpperCase();
HttpGet request = new HttpGet(buildUrl(baseUrl, port, urlPath));
JsonNode zoneNode = execute(request, apiKey, 200);
if (zoneNode == null || !zoneNode.has(ApiConstants.RR_SETS)) {
return false;
}
JsonNode rrsets = zoneNode.path(ApiConstants.RR_SETS);
return rrsets.isArray() && !rrsets.isEmpty();
}
private JsonNode execute(HttpUriRequest request, String apiKey, int... expectedStatus) throws DnsProviderException {
request.addHeader(ApiConstants.X_API_KEY, apiKey);
request.addHeader("Accept", "application/json");
request.addHeader(ApiConstants.CONTENT_TYPE, "application/json");
try (CloseableHttpResponse response = httpClient.execute(request)) {
int status = response.getStatusLine().getStatusCode();
String body = response.getEntity() != null ? EntityUtils.toString(response.getEntity()) : null;
for (int expected : expectedStatus) {
if (status == expected) {
if (body != null && !body.isEmpty()) {
return MAPPER.readTree(body);
} else {
return null;
}
}
}
if (status == 404) {
throw new DnsNotFoundException("Resource not found: " + body);
} else if (status == 401 || status == 403) {
throw new DnsAuthenticationException("Invalid API key");
} else if (status == 409) {
throw new DnsConflictException("Conflict: " + body);
}
throw new DnsOperationException("Unexpected PowerDNS response: HTTP " + status + " Body: " + body);
} catch (IOException ex) {
throw new DnsTransportException("Error communicating with PowerDNS", ex);
}
}
private String buildUrl(String baseUrl, Integer port, String path) {
String fullUrl = normalizeBaseUrl(baseUrl);
if (port != null && port > 0) {
try {
URI uri = new URI(fullUrl);
if (uri.getPort() == -1) {
fullUrl = fullUrl + ":" + port;
}
} catch (URISyntaxException e) {
fullUrl = fullUrl + ":" + port;
}
}
if (!path.startsWith("/")) {
path = "/" + path;
}
return fullUrl + API_PREFIX + path;
}
private String normalizeBaseUrl(String baseUrl) {
if (baseUrl == null) {
throw new IllegalArgumentException("Base URL cannot be null");
}
String url = baseUrl.trim();
if (!url.startsWith("http://") && !url.startsWith("https://")) {
url = "http://" + url;
}
if (url.endsWith("/")) {
url = url.substring(0, url.length() - 1);
}
return url;
}
private String normalizeZone(String zoneName) {
if (StringUtils.isBlank(zoneName)) {
throw new IllegalArgumentException("Zone name must not be null or empty");
}
String zone = zoneName.trim().toLowerCase();
if (!zone.endsWith(".")) {
zone = zone + ".";
}
if (zone.length() < 2) {
throw new IllegalArgumentException("Zone name is too short");
}
return zone;
}
String normalizeRecordName(String recordName, String zoneName) {
if (recordName == null) {
throw new IllegalArgumentException("Record name must not be null");
}
String normalizedZone = normalizeZone(zoneName);
String name = recordName.trim().toLowerCase();
// Apex of the zone
if (name.equals("@") || name.isEmpty()) {
return normalizedZone;
}
String zoneWithoutDot = normalizedZone.substring(0, normalizedZone.length() - 1);
// Already absolute (ends with dot)
if (name.endsWith(".")) {
// Check if the record belongs to the zone
if (!name.equals(normalizedZone) && !name.endsWith("." + zoneWithoutDot + ".")) {
throw new IllegalArgumentException(
String.format("Record '%s' does not belong to zone '%s'", recordName, zoneName)
);
}
return name;
}
if (name.contains(".")) {
return name + ".";
}
// Relative name append zone
return name + "." + normalizedZone;
}
@Override
public void close() {
try {
httpClient.close();
} catch (IOException e) {
logger.warn("Failed to close PowerDNS HTTP client", e);
}
}
}

View File

@ -0,0 +1,202 @@
// 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.powerdns;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.dns.DnsProvider;
import org.apache.cloudstack.dns.DnsProviderType;
import org.apache.cloudstack.dns.DnsRecord;
import org.apache.cloudstack.dns.DnsServer;
import org.apache.cloudstack.dns.DnsZone;
import org.apache.cloudstack.dns.exception.DnsProviderException;
import org.apache.logging.log4j.util.Strings;
import com.cloud.utils.StringUtils;
import com.cloud.utils.component.AdapterBase;
import com.fasterxml.jackson.databind.JsonNode;
public class PowerDnsProvider extends AdapterBase implements DnsProvider {
private PowerDnsClient client;
static String PDNS_SERVER_ID = "pdnsServerId";
@Override
public DnsProviderType getProviderType() {
return DnsProviderType.PowerDNS;
}
public void validate(DnsServer server) throws DnsProviderException {
validateRequiredServerFields(server);
client.validateServerId(server.getUrl(), server.getPort(), server.getDnsApiKey(), server.getDetail(PDNS_SERVER_ID));
}
@Override
public String validateAndResolveServer(DnsServer server) throws Exception {
validateRequiredServerFields(server);
String resolvedDnsServerId = client.resolveServerId(server.getUrl(), server.getPort(), server.getDnsApiKey(), server.getDetail(PDNS_SERVER_ID));
if (Strings.isNotBlank(resolvedDnsServerId)) {
server.appendDetails(PDNS_SERVER_ID, resolvedDnsServerId);
}
return resolvedDnsServerId;
}
@Override
public String provisionZone(DnsServer server, DnsZone zone) throws DnsProviderException {
validateRequiredServerAndZoneFields(server, zone);
return client.createZone(
server.getUrl(),
server.getPort(),
server.getDnsApiKey(),
server.getDetail(PDNS_SERVER_ID),
zone.getName(),
ApiConstants.NATIVE_ZONE, false, server.getNameServers()
);
}
@Override
public void deleteZone(DnsServer server, DnsZone zone) throws DnsProviderException {
validateRequiredServerAndZoneFields(server, zone);
client.deleteZone(server.getUrl(), server.getPort(), server.getDnsApiKey(), server.getDetail(PDNS_SERVER_ID), zone.getName());
}
@Override
public void updateZone(DnsServer server, DnsZone zone) throws DnsProviderException {
validateRequiredServerAndZoneFields(server, zone);
client.updateZone(
server.getUrl(),
server.getPort(),
server.getDnsApiKey(),
server.getDetail(PDNS_SERVER_ID),
zone.getName(), ApiConstants.NATIVE_ZONE, false, server.getNameServers());
}
public enum ChangeType {
REPLACE, DELETE
}
@Override
public String addRecord(DnsServer server, DnsZone zone, DnsRecord record) throws DnsProviderException {
validateRequiredServerAndZoneFields(server, zone);
return applyRecord(
server.getUrl(),
server.getPort(),
server.getDnsApiKey(),
server.getDetail(PDNS_SERVER_ID),
zone.getName(), record, ChangeType.REPLACE);
}
@Override
public String updateRecord(DnsServer server, DnsZone zone, DnsRecord record) throws DnsProviderException {
validateRequiredServerAndZoneFields(server, zone);
return addRecord(server, zone, record);
}
@Override
public String deleteRecord(DnsServer server, DnsZone zone, DnsRecord record) throws DnsProviderException {
validateRequiredServerAndZoneFields(server, zone);
return applyRecord(server.getUrl(),
server.getPort(),
server.getDnsApiKey(),
server.getDetail(PDNS_SERVER_ID),
zone.getName(), record, ChangeType.DELETE);
}
public String applyRecord(String serverUrl, Integer port, String apiKey, String externalServerId, String zoneName,
DnsRecord record, ChangeType changeType) throws DnsProviderException {
return client.modifyRecord(serverUrl, port, apiKey, externalServerId, zoneName, record.getName(),
record.getType().name(), record.getTtl(), record.getContents(), changeType.name());
}
@Override
public List<DnsRecord> listRecords(DnsServer server, DnsZone zone) throws DnsProviderException {
validateRequiredServerAndZoneFields(server, zone);
List<DnsRecord> records = new ArrayList<>();
Iterable<JsonNode> rrsetNodes = client.listRecords(server.getUrl(), server.getPort(), server.getDnsApiKey(),
server.getDetail(PDNS_SERVER_ID), zone.getName());
for (JsonNode rrset : rrsetNodes) {
String name = rrset.path(ApiConstants.NAME).asText();
String typeStr = rrset.path(ApiConstants.TYPE).asText();
int ttl = rrset.path(ApiConstants.TTL).asInt(0);
if (!"SOA".equalsIgnoreCase(typeStr)) {
try {
List<String> contents = new ArrayList<>();
JsonNode recordsNode = rrset.path(ApiConstants.RECORDS);
if (recordsNode.isArray()) {
for (JsonNode rec : recordsNode) {
String content = rec.path(ApiConstants.CONTENT).asText();
if (!content.isEmpty()) {
contents.add(content);
}
}
}
records.add(new DnsRecord(name, DnsRecord.RecordType.valueOf(typeStr), contents, ttl));
} catch (Exception ignored) {
// Skip unsupported record types
}
}
}
return records;
}
public boolean dnsRecordExists(DnsServer server, DnsZone zone, String recordName, String recordType) throws DnsProviderException {
return client.recordExists(server.getUrl(), server.getPort(), server.getDnsApiKey(),
server.getDetail(PDNS_SERVER_ID), zone.getName(), recordName, recordType);
}
@Override
public boolean dnsZoneExists(DnsServer server, DnsZone zone) {
return client.zoneExists(server.getUrl(), server.getPort(), server.getDnsApiKey(), server.getDetail(PDNS_SERVER_ID), zone.getName());
}
void validateRequiredServerAndZoneFields(DnsServer server, DnsZone zone) {
validateRequiredServerFields(server);
if (StringUtils.isBlank(zone.getName())) {
throw new IllegalArgumentException("Zone name cannot be empty");
}
}
void validateRequiredServerFields(DnsServer server) {
if (StringUtils.isBlank(server.getUrl())) {
throw new IllegalArgumentException("PowerDNS API URL cannot be empty");
}
if (StringUtils.isBlank(server.getDnsApiKey())) {
throw new IllegalArgumentException("PowerDNS API key cannot be empty");
}
}
@Override
public boolean configure(String name, Map<String, Object> params) {
if (client == null) {
client = new PowerDnsClient();
}
return true;
}
@Override
public boolean stop() {
if (client != null) {
client.close();
}
return true;
}
}

View File

@ -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=powerdns
parent=dns

View File

@ -0,0 +1,31 @@
<!--
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="powerDnsProvider" class="org.apache.cloudstack.dns.powerdns.PowerDnsProvider" />
</beans>

View File

@ -0,0 +1,101 @@
// 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 static org.apache.cloudstack.dns.DnsProviderUtil.appendPublicSuffixToZone;
import static org.apache.cloudstack.dns.DnsProviderUtil.normalizeDomainForDb;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.util.Arrays;
import java.util.Collection;
import org.apache.logging.log4j.util.Strings;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class DnsProviderUtilTest {
private final String userZoneName;
private final String publicSuffix;
private final String expectedResult;
private final boolean expectException;
public DnsProviderUtilTest(String userZoneName,
String publicSuffix,
String expectedResult,
boolean expectException) {
this.userZoneName = userZoneName;
this.publicSuffix = publicSuffix;
this.expectedResult = expectedResult;
this.expectException = expectException;
}
@Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{
{"tenant1.com", "example.com", "tenant1.example.com", false},
{"dev.tenant2.com", "example.com", "dev.tenant2.example.com", false},
{"tenant3.example.com", "example.com", "tenant3.example.com", false},
{"Tenant1.CoM", "ExAmple.CoM", "tenant1.example.com", false},
{"tenant1.com.", "example.com.", "tenant1.example.com", false},
{"tenant1.com", "", "tenant1.com", false},
{"tenant1.com", null, "tenant1.com", false},
{"test.abc.com", "abc.com", "test.abc.com", false},
{"sub.test.abc.com", "abc.com", "sub.test.abc.com", false},
{"test.ai.abc.com", "abc.com", "test.ai.abc.com", false},
{"deep.sub.abc.com", "abc.com", "deep.sub.abc.com", false},
{"abc.com", "xyz.com", "abc.xyz.com", false},
{"test.xyz.com", "xyz.com", "test.xyz.com", false},
{"test.com.xyz.com", "xyz.com", "test.com.xyz.com", false},
{"tenant", "example.com", null, true}, // single label
{"test", "abc.com", null, true},
{"example.com.", "example.com", null, true},
{"example.com", "example.com", null, true}, // root level forbidden
{"abc.com", "abc.com", null, true}, // root level forbidden
{"tenant1.org", "example.com", null, true}, // TLD mismatch
{"test.ai", "abc.com", null, true}, // TLD mismatch
{null, "example.com", null, true},
});
}
@Test
public void testAppendPublicSuffix() {
if (expectException) {
try {
executeAppendSuffixTest(userZoneName, publicSuffix);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException ignored) {
// noop
}
} else {
String result;
if (Strings.isNotBlank(publicSuffix)) {
result = executeAppendSuffixTest(userZoneName, publicSuffix);
} else {
result = appendPublicSuffixToZone(normalizeDomainForDb(userZoneName), publicSuffix);
}
assertEquals(expectedResult, result);
}
}
String executeAppendSuffixTest(String zoneName, String domainSuffix) {
return appendPublicSuffixToZone(normalizeDomainForDb(zoneName), domainSuffix);
}
}

View File

@ -0,0 +1,197 @@
// 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 static org.apache.cloudstack.dns.DnsRecord.RecordType.A;
import static org.apache.cloudstack.dns.DnsRecord.RecordType.AAAA;
import static org.apache.cloudstack.dns.DnsRecord.RecordType.CNAME;
import static org.apache.cloudstack.dns.DnsRecord.RecordType.MX;
import static org.apache.cloudstack.dns.DnsRecord.RecordType.NS;
import static org.apache.cloudstack.dns.DnsRecord.RecordType.PTR;
import static org.apache.cloudstack.dns.DnsRecord.RecordType.SRV;
import static org.apache.cloudstack.dns.DnsRecord.RecordType.TXT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.util.Arrays;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class NormalizeDnsRecordValueTest {
private final String description;
private final String input;
private final DnsRecord.RecordType recordType;
private final String expected;
private final boolean expectException;
public NormalizeDnsRecordValueTest(String description, String input,
DnsRecord.RecordType recordType,
String expected, boolean expectException) {
this.description = description;
this.input = input;
this.recordType = recordType;
this.expected = expected;
this.expectException = expectException;
}
@Parameterized.Parameters(name = "{0}")
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {
// ----------------------------------------------------------------
// Guard: blank/null value all record types should throw
// ----------------------------------------------------------------
{"null value, A record", null, A, null, true},
{"empty value, A record", "", A, null, true},
{"blank value, A record", " ", A, null, true},
{"null value, AAAA record", null, AAAA, null, true},
{"null value, CNAME record", null, CNAME, null, true},
{"null value, MX record", null, MX, null, true},
{"null value, TXT record", null, TXT, null, true},
{"null value, SRV record", null, SRV, null, true},
{"null value, NS record", null, NS, null, true},
{"null value, PTR record", null, PTR, null, true},
// ----------------------------------------------------------------
// A record
// ----------------------------------------------------------------
{"A: valid IPv4", "93.184.216.34", A, "93.184.216.34", false},
{"A: valid IPv4 with whitespace", " 93.184.216.34 ", A, "93.184.216.34", false},
{"A: loopback", "127.0.0.1", A, "127.0.0.1", false},
{"A: all-zeros", "0.0.0.0", A, "0.0.0.0", false},
{"A: broadcast", "255.255.255.255", A, "255.255.255.255", false},
{"A: private 10.x", "10.0.0.1", A, "10.0.0.1", false},
{"A: private 192.168.x", "192.168.1.1", A, "192.168.1.1", false},
{"A: IPv6 rejected", "2001:db8::1", A, null, true},
{"A: domain rejected", "example.com", A, null, true},
{"A: partial IP rejected", "192.168.1", A, null, true},
{"A: trailing dot rejected", "93.184.216.34.", A, null, true},
{"A: octet out of range rejected", "256.0.0.1", A, null, true},
// ----------------------------------------------------------------
// AAAA record
// ----------------------------------------------------------------
{"AAAA: full IPv6", "2001:0db8:0000:0000:0000:0000:0000:0001", AAAA,
"2001:0db8:0000:0000:0000:0000:0000:0001", false},
{"AAAA: compressed IPv6", "2001:db8::1", AAAA, "2001:db8::1", false},
{"AAAA: loopback", "::1", AAAA, "::1", false},
{"AAAA: all zeros", "::", AAAA, "::", false},
{"AAAA: whitespace", " 2001:db8::1 ", AAAA, "2001:db8::1", false},
{"AAAA: IPv4 rejected", "93.184.216.34", AAAA, null, true},
{"AAAA: domain rejected", "example.com", AAAA, null, true},
{"AAAA: invalid hex rejected", "2001:db8::xyz", AAAA, null, true},
// ----------------------------------------------------------------
// CNAME record
// ----------------------------------------------------------------
{"CNAME: basic", "target.example.com", CNAME, "target.example.com.", false},
{"CNAME: uppercase", "TARGET.EXAMPLE.COM", CNAME, "target.example.com.", false},
{"CNAME: trailing dot", "target.example.com.", CNAME, "target.example.com.", false},
{"CNAME: whitespace", " target.example.com ", CNAME, "target.example.com.", false},
{"CNAME: subdomain", "sub.target.example.com", CNAME, "sub.target.example.com.", false},
{"CNAME: IP rejected", "192.168.1.1", CNAME, null, true},
{"CNAME: invalid label", "-bad.example.com", CNAME, null, true},
// ----------------------------------------------------------------
// NS record
// ----------------------------------------------------------------
{"NS: basic", "ns1.example.com", NS, "ns1.example.com.", false},
{"NS: uppercase", "NS1.EXAMPLE.COM", NS, "ns1.example.com.", false},
{"NS: trailing dot", "ns1.example.com.", NS, "ns1.example.com.", false},
{"NS: subdomain", "ns1.sub.example.com", NS, "ns1.sub.example.com.", false},
{"NS: IP rejected", "8.8.8.8", NS, null, true},
{"NS: invalid label", "ns1-.example.com", NS, null, true},
// ----------------------------------------------------------------
// PTR record
// ----------------------------------------------------------------
{"PTR: basic", "host.example.com", PTR, "host.example.com.", false},
{"PTR: in-addr.arpa", "1.168.192.in-addr.arpa", PTR, "1.168.192.in-addr.arpa.", false},
{"PTR: uppercase", "HOST.EXAMPLE.COM", PTR, "host.example.com.", false},
{"PTR: trailing dot", "host.example.com.", PTR, "host.example.com.", false},
{"PTR: IP rejected", "192.168.1.1", PTR, null, true},
{"PTR: invalid label", "-host.example.com", PTR, null, true},
// ----------------------------------------------------------------
// MX record
// ----------------------------------------------------------------
{"MX: standard", "10 mail.example.com", MX, "10 mail.example.com.", false},
{"MX: zero priority", "0 mail.example.com", MX, "0 mail.example.com.", false},
{"MX: max priority", "65535 mail.example.com", MX, "65535 mail.example.com.", false},
{"MX: uppercase", "10 MAIL.EXAMPLE.COM", MX, "10 mail.example.com.", false},
{"MX: trailing dot", "10 mail.example.com.", MX, "10 mail.example.com.", false},
{"MX: extra whitespace", "10 mail.example.com", MX, "10 mail.example.com.", false},
{"MX: missing domain", "10", MX, null, true},
{"MX: priority out of range", "65536 mail.example.com", MX, null, true},
{"MX: non-numeric priority", "abc mail.example.com", MX, null, true},
{"MX: IP rejected", "10 192.168.1.1", MX, null, true},
// ----------------------------------------------------------------
// SRV record
// ----------------------------------------------------------------
{"SRV: standard", "10 20 443 target.example.com", SRV, "10 20 443 target.example.com.", false},
{"SRV: zeros", "0 0 1 target.example.com", SRV, "0 0 1 target.example.com.", false},
{"SRV: max values", "65535 65535 65535 target.example.com", SRV, "65535 65535 65535 target.example.com.", false},
{"SRV: uppercase", "10 20 443 TARGET.EXAMPLE.COM", SRV, "10 20 443 target.example.com.", false},
{"SRV: trailing dot", "10 20 443 target.example.com.", SRV, "10 20 443 target.example.com.", false},
{"SRV: missing target", "10 20 443", SRV, null, true},
{"SRV: port 0", "10 20 0 target.example.com", SRV, null, true},
{"SRV: priority out of range", "65536 20 443 target.example.com", SRV, null, true},
{"SRV: IP rejected", "10 20 443 192.168.1.1", SRV, null, true},
// ----------------------------------------------------------------
// TXT record
// ----------------------------------------------------------------
{"TXT: trim", " hello world ", TXT, "hello world", false},
{"TXT: already clean", "v=spf1 include:example.com ~all", TXT, "v=spf1 include:example.com ~all", false},
{"TXT: special chars", "v=DKIM1; k=rsa; p=MIGf", TXT, "v=DKIM1; k=rsa; p=MIGf", false},
{"TXT: unicode", "héllo wörld", TXT, "héllo wörld", false},
{"TXT: multiple spaces", "key=value with spaces", TXT, "key=value with spaces", false},
{"TXT: quoted", "\"quoted value\"", TXT, "\"quoted value\"", false},
{"TXT: blank", " ", TXT, null, true},
{"TXT: newline", "\n", TXT, null, true},
});
}
@Test
public void testNormalizeDnsRecordValue() {
if (expectException) {
try {
DnsProviderUtil.normalizeDnsRecordValue(input, recordType);
fail("Expected IllegalArgumentException for [" + description + "] input='" + input + "'");
} catch (IllegalArgumentException ignored) {}
} else {
String result = DnsProviderUtil.normalizeDnsRecordValue(input, recordType);
assertEquals(description, expected, result);
}
}
}

View File

@ -0,0 +1,432 @@
// 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.powerdns;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import org.apache.cloudstack.dns.exception.DnsOperationException;
import org.apache.cloudstack.dns.exception.DnsAuthenticationException;
import org.apache.cloudstack.dns.exception.DnsConflictException;
import org.apache.cloudstack.dns.exception.DnsNotFoundException;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.test.util.ReflectionTestUtils;
import com.fasterxml.jackson.databind.JsonNode;
@RunWith(MockitoJUnitRunner.class)
public class PowerDnsClientTest {
PowerDnsClient client;
CloseableHttpClient httpClientMock;
@Before
public void setUp() {
client = new PowerDnsClient();
httpClientMock = mock(CloseableHttpClient.class);
ReflectionTestUtils.setField(client, "httpClient", httpClientMock);
}
private CloseableHttpResponse createResponse(int statusCode, String jsonBody) {
CloseableHttpResponse responseMock = mock(CloseableHttpResponse.class);
StatusLine statusLineMock = mock(StatusLine.class);
when(responseMock.getStatusLine()).thenReturn(statusLineMock);
when(statusLineMock.getStatusCode()).thenReturn(statusCode);
if (jsonBody != null) {
when(responseMock.getEntity()).thenReturn(new StringEntity(jsonBody, StandardCharsets.UTF_8));
}
return responseMock;
}
private void mockHttpResponse(int statusCode, String jsonBody) throws IOException {
CloseableHttpResponse response = createResponse(statusCode, jsonBody);
when(httpClientMock.execute(any(HttpUriRequest.class))).thenReturn(response);
}
@Test
public void testNormalizeApexRecord() {
String result = client.normalizeRecordName("@", "example.com");
assertEquals("example.com.", result);
result = client.normalizeRecordName("", "example.com");
assertEquals("example.com.", result);
}
@Test
public void testNormalizeRelativeRecord() {
String result = client.normalizeRecordName("www", "example.com");
assertEquals("www.example.com.", result);
result = client.normalizeRecordName("WWW", "example.com"); // test case-insensitive
assertEquals("www.example.com.", result);
}
@Test
public void testNormalizeAbsoluteRecordWithinZone() {
String result = client.normalizeRecordName("www.example.com.", "example.com");
assertEquals("www.example.com.", result);
}
@Test(expected = IllegalArgumentException.class)
public void testNormalizeAbsoluteRecordOutsideZoneThrows() {
client.normalizeRecordName("other.com.", "example.com");
}
@Test
public void testNormalizeDottedNameWithoutTrailingDot() {
String result = client.normalizeRecordName("api.test.com", "example.com");
assertEquals("api.test.com.", result);
}
@Test
public void testNormalizeRelativeSubdomain() {
String result = client.normalizeRecordName("mail", "example.com");
assertEquals("mail.example.com.", result);
}
@Test(expected = IllegalArgumentException.class)
public void testNormalizeNullRecordNameThrows() {
client.normalizeRecordName(null, "example.com");
}
@Test
public void testNormalizeZoneNormalization() {
String result = client.normalizeRecordName("www", "Example.Com");
assertEquals("www.example.com.", result);
result = client.normalizeRecordName("www", "example.com.");
assertEquals("www.example.com.", result);
}
@Test
public void testDiscoverAuthoritativeServerIdSuccess() throws Exception {
mockHttpResponse(200, "[{\"id\":\"localhost\", \"daemon_type\":\"authoritative\"}]");
String result = client.discoverAuthoritativeServerId("http://pdns:8081", null, "apikey");
assertEquals("localhost", result);
}
@Test
public void testDiscoverAuthoritativeServerIdFallback() throws Exception {
mockHttpResponse(200, "[{\"id\":\"server1\", \"daemon_type\":\"recursor\"}, {\"id\":\"server2\", \"daemon_type\":\"authoritative\"}]");
String result = client.discoverAuthoritativeServerId("http://pdns", 8081, "apikey");
assertEquals("server2", result);
}
@Test(expected = DnsOperationException.class)
public void testDiscoverAuthoritativeServerIdEmpty() throws Exception {
mockHttpResponse(200, "[]");
client.discoverAuthoritativeServerId("http://pdns", 8081, "apikey");
}
@Test(expected = DnsOperationException.class)
public void testDiscoverAuthoritativeServerIdNoAuthoritative() throws Exception {
mockHttpResponse(200, "[{\"id\":\"server1\", \"daemon_type\":\"recursor\"}]");
client.discoverAuthoritativeServerId("http://pdns", 8081, "apikey");
}
@Test
public void testValidateServerIdSuccess() throws Exception {
mockHttpResponse(200, "{\"id\":\"abc\", \"daemon_type\":\"authoritative\"}");
String result = client.validateServerId("http://pdns", 8081, "apikey", "abc");
assertEquals("abc", result);
}
@Test(expected = DnsOperationException.class)
public void testValidateServerIdNotAuthoritative() throws Exception {
mockHttpResponse(200, "{\"id\":\"abc\", \"daemon_type\":\"recursor\"}");
client.validateServerId("http://pdns", 8081, "apikey", "abc");
}
@Test
public void testResolveServerIdWithExternalId() throws Exception {
mockHttpResponse(200, "{\"id\":\"abc\", \"daemon_type\":\"authoritative\"}");
String result = client.resolveServerId("http://pdns", 8081, "apikey", "abc");
assertEquals("abc", result);
}
@Test
public void testResolveServerIdWithoutExternalId() throws Exception {
mockHttpResponse(200, "[{\"id\":\"localhost\", \"daemon_type\":\"authoritative\"}]");
String result = client.resolveServerId("http://pdns", 8081, "apikey", null);
assertEquals("localhost", result);
}
@Test
public void testCreateZone() throws Exception {
when(httpClientMock.execute(any(HttpUriRequest.class))).thenAnswer(new Answer<CloseableHttpResponse>() {
@Override
public CloseableHttpResponse answer(InvocationOnMock invocation) {
HttpUriRequest request = invocation.getArgument(0);
if (request.getMethod().equals("GET")) {
return createResponse(200, "{\"id\":\"abc\", \"daemon_type\":\"authoritative\"}");
}
if (request.getMethod().equals("POST")) {
return createResponse(201, "{\"id\":\"example.com.\"}");
}
return createResponse(500, null);
}
});
String result = client.createZone("http://pdns", 8081, "apikey", "abc", "example.com", "Native", false, Arrays.asList("ns1.com"));
assertEquals("example.com.", result);
}
@Test
public void testUpdateZone() throws Exception {
when(httpClientMock.execute(any(HttpUriRequest.class))).thenAnswer(new Answer<CloseableHttpResponse>() {
@Override
public CloseableHttpResponse answer(InvocationOnMock invocation) {
HttpUriRequest request = invocation.getArgument(0);
if (request.getMethod().equals("GET")) {
return createResponse(200, "{\"id\":\"abc\", \"daemon_type\":\"authoritative\"}");
}
if (request.getMethod().equals("PUT")) {
return createResponse(204, null);
}
return createResponse(500, null);
}
});
client.updateZone("http://pdns", 8081, "apikey", "abc", "example.com", "Native", true, Arrays.asList("ns1.com"));
// No exception means success
}
@Test
public void testDeleteZone() throws Exception {
when(httpClientMock.execute(any(HttpUriRequest.class))).thenAnswer(new Answer<CloseableHttpResponse>() {
@Override
public CloseableHttpResponse answer(InvocationOnMock invocation) {
HttpUriRequest request = invocation.getArgument(0);
if (request.getMethod().equals("GET")) {
return createResponse(200, "{\"id\":\"abc\", \"daemon_type\":\"authoritative\"}");
}
if (request.getMethod().equals("DELETE")) {
return createResponse(204, null);
}
return createResponse(500, null);
}
});
client.deleteZone("http://pdns", 8081, "apikey", "abc", "example.com");
}
@Test
public void testModifyRecord() throws Exception {
when(httpClientMock.execute(any(HttpUriRequest.class))).thenAnswer(new Answer<CloseableHttpResponse>() {
@Override
public CloseableHttpResponse answer(InvocationOnMock invocation) {
HttpUriRequest request = invocation.getArgument(0);
if (request.getMethod().equals("GET")) {
return createResponse(200, "{\"id\":\"abc\", \"daemon_type\":\"authoritative\"}");
}
if (request.getMethod().equals("PATCH")) {
return createResponse(204, null);
}
return createResponse(500, null);
}
});
String result = client.modifyRecord("http://pdns", 8081, "apikey", "abc", "example.com", "www", "A", 300, Arrays.asList("1.2.3.4"), "REPLACE");
assertEquals("www.example.com", result);
}
@Test
public void testModifyRecordApex() throws Exception {
when(httpClientMock.execute(any(HttpUriRequest.class))).thenAnswer(new Answer<CloseableHttpResponse>() {
@Override
public CloseableHttpResponse answer(InvocationOnMock invocation) {
HttpUriRequest request = invocation.getArgument(0);
if (request.getMethod().equals("GET")) {
return createResponse(200, "{\"id\":\"abc\", \"daemon_type\":\"authoritative\"}");
}
if (request.getMethod().equals("PATCH")) {
return createResponse(204, null);
}
return createResponse(500, null);
}
});
String result = client.modifyRecord("http://pdns", 8081, "apikey", "abc", "example.com", "@", "A", 300, Arrays.asList("1.2.3.4"), "REPLACE");
assertEquals("example.com", result);
}
@Test
public void testListRecords() throws Exception {
when(httpClientMock.execute(any(HttpUriRequest.class))).thenAnswer(new Answer<CloseableHttpResponse>() {
@Override
public CloseableHttpResponse answer(InvocationOnMock invocation) {
HttpUriRequest request = invocation.getArgument(0);
// validateServerId uses /servers/abc
// listRecords uses /servers/abc/zones/example.com.
if (request.getURI().getPath().endsWith("abc")) {
return createResponse(200, "{\"id\":\"abc\", \"daemon_type\":\"authoritative\"}");
}
if (request.getURI().getPath().endsWith("example.com.")) {
return createResponse(200, "{\"rrsets\":[{\"name\":\"www.example.com.\",\"type\":\"A\"}]}");
}
return createResponse(500, null);
}
});
Iterable<JsonNode> records = client.listRecords("http://pdns", 8081, "apikey", "abc", "example.com");
assertNotNull(records);
assertTrue(records.iterator().hasNext());
assertEquals("www.example.com.", records.iterator().next().path("name").asText());
}
@Test
public void testListRecordsEmpty() throws Exception {
when(httpClientMock.execute(any(HttpUriRequest.class))).thenAnswer(new Answer<CloseableHttpResponse>() {
@Override
public CloseableHttpResponse answer(InvocationOnMock invocation) {
HttpUriRequest request = invocation.getArgument(0);
if (request.getURI().getPath().endsWith("abc")) {
return createResponse(200, "{\"id\":\"abc\", \"daemon_type\":\"authoritative\"}");
}
if (request.getURI().getPath().endsWith("example.com.")) {
return createResponse(200, "{}");
}
return createResponse(500, null);
}
});
Iterable<JsonNode> records = client.listRecords("http://pdns", 8081, "apikey", "abc", "example.com");
assertNotNull(records);
assertTrue(!records.iterator().hasNext());
}
@Test(expected = DnsNotFoundException.class)
public void testExecuteThrowsNotFound() throws Exception {
mockHttpResponse(404, "Not Found");
client.validateServerId("http://pdns", 8081, "apikey", "abc");
}
@Test(expected = DnsAuthenticationException.class)
public void testExecuteThrowsAuthError() throws Exception {
mockHttpResponse(401, "Unauthorized");
client.validateServerId("http://pdns", 8081, "apikey", "abc");
}
@Test(expected = DnsConflictException.class)
public void testExecuteThrowsConflictError() throws Exception {
mockHttpResponse(409, "Conflict");
client.validateServerId("http://pdns", 8081, "apikey", "abc");
}
@Test(expected = DnsOperationException.class)
public void testExecuteThrowsUnexpectedStatus() throws Exception {
mockHttpResponse(500, "Server Error");
client.validateServerId("http://pdns", 8081, "apikey", "abc");
}
// Route helper: GET /servers/abc validate; GET /zones/... zone response
private void mockRecordExists(String zoneJson) throws IOException {
when(httpClientMock.execute(any(HttpUriRequest.class))).thenAnswer(new Answer<CloseableHttpResponse>() {
@Override
public CloseableHttpResponse answer(InvocationOnMock invocation) {
HttpUriRequest request = invocation.getArgument(0);
String path = request.getURI().getPath();
if (path.endsWith("/abc")) {
return createResponse(200, "{\"id\":\"abc\", \"daemon_type\":\"authoritative\"}");
}
// zone query (contains /zones/)
if (zoneJson == null) {
return createResponse(200, null); // empty body execute() returns null
}
return createResponse(200, zoneJson);
}
});
}
@Test
public void testRecordExistsZoneNodeNull() throws Exception {
// execute() returns null zoneNode == null false
mockRecordExists(null);
boolean result = client.recordExists("http://pdns", 8081, "apikey", "abc", "example.com", "www", "A");
assertEquals(false, result);
}
@Test
public void testRecordExistsMissingRrSetsField() throws Exception {
// response has no "rrsets" key !zoneNode.has(RR_SETS) false
mockRecordExists("{}");
boolean result = client.recordExists("http://pdns", 8081, "apikey", "abc", "example.com", "www", "A");
assertEquals(false, result);
}
@Test
public void testRecordExistsRrSetsNotArray() throws Exception {
// rrsets is a scalar string, not an ArrayNode isArray() == false false
mockRecordExists("{\"rrsets\":\"not-an-array\"}");
boolean result = client.recordExists("http://pdns", 8081, "apikey", "abc", "example.com", "www", "A");
assertEquals(false, result);
}
@Test
public void testRecordExistsEmptyRrSetsArray() throws Exception {
// rrsets is an empty array isArray() == true && isEmpty() == true false
mockRecordExists("{\"rrsets\":[]}");
boolean result = client.recordExists("http://pdns", 8081, "apikey", "abc", "example.com", "www", "A");
assertEquals(false, result);
}
@Test
public void testRecordExistsNonEmptyRrSetsArray() throws Exception {
// rrsets is a non-empty array isArray() == true && !isEmpty() true
mockRecordExists("{\"rrsets\":[{\"name\":\"www.example.com.\",\"type\":\"A\"}]}");
boolean result = client.recordExists("http://pdns", 8081, "apikey", "abc", "example.com", "www", "A");
assertEquals(true, result);
}
@Test
public void testCloseSucceeds() throws Exception {
// httpClient.close() completes normally no exception propagated
CloseableHttpClient mockClient = mock(CloseableHttpClient.class);
ReflectionTestUtils.setField(client, "httpClient", mockClient);
client.close();
org.mockito.Mockito.verify(mockClient).close();
}
@Test
public void testCloseSwallowsIOException() throws Exception {
// httpClient.close() throws IOException caught and logged (warn), no rethrow
CloseableHttpClient mockClient = mock(CloseableHttpClient.class);
org.mockito.Mockito.doThrow(new IOException("connection reset")).when(mockClient).close();
ReflectionTestUtils.setField(client, "httpClient", mockClient);
client.close(); // must NOT throw
}
}

View File

@ -0,0 +1,391 @@
// 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.powerdns;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import org.apache.cloudstack.dns.DnsRecord;
import org.apache.cloudstack.dns.DnsRecord.RecordType;
import org.apache.cloudstack.dns.DnsServer;
import org.apache.cloudstack.dns.DnsZone;
import org.apache.cloudstack.dns.DnsProviderType;
import org.apache.cloudstack.dns.exception.DnsProviderException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
@RunWith(MockitoJUnitRunner.class)
public class PowerDnsProviderTest {
private static final ObjectMapper MAPPER = new ObjectMapper();
private PowerDnsProvider provider;
private PowerDnsClient clientMock;
private DnsServer serverMock;
private DnsZone zoneMock;
@Before
public void setUp() {
provider = new PowerDnsProvider();
clientMock = mock(PowerDnsClient.class);
serverMock = mock(DnsServer.class);
zoneMock = mock(DnsZone.class);
ReflectionTestUtils.setField(provider, "client", clientMock);
when(serverMock.getUrl()).thenReturn("http://pdns:8081");
when(serverMock.getDnsApiKey()).thenReturn("secret");
when(serverMock.getPort()).thenReturn(8081);
when(serverMock.getDetail(PowerDnsProvider.PDNS_SERVER_ID)).thenReturn("localhost");
when(serverMock.getNameServers()).thenReturn(Arrays.asList("ns1.example.com"));
when(zoneMock.getName()).thenReturn("example.com");
}
@Test
public void testGetProviderType() {
assertEquals(DnsProviderType.PowerDNS, provider.getProviderType());
}
@Test
public void testConfigureCreatesClientWhenNull() {
PowerDnsProvider freshProvider = new PowerDnsProvider();
boolean result = freshProvider.configure("test", new HashMap<>());
assertTrue(result);
assertNotNull(ReflectionTestUtils.getField(freshProvider, "client"));
}
@Test
public void testConfigureDoesNotReplaceExistingClient() {
PowerDnsClient existingClient = mock(PowerDnsClient.class);
ReflectionTestUtils.setField(provider, "client", existingClient);
boolean result = provider.configure("test", new HashMap<>());
assertTrue(result);
assertEquals(existingClient, ReflectionTestUtils.getField(provider, "client"));
}
@Test
public void testStopClosesClient() {
boolean result = provider.stop();
assertTrue(result);
verify(clientMock, times(1)).close();
}
@Test
public void testStopWithNullClientSucceeds() {
ReflectionTestUtils.setField(provider, "client", null);
boolean result = provider.stop();
assertTrue(result);
}
@Test(expected = IllegalArgumentException.class)
public void testValidateServerFieldsNullUrl() {
when(serverMock.getUrl()).thenReturn(null);
provider.validateRequiredServerFields(serverMock);
}
@Test(expected = IllegalArgumentException.class)
public void testValidateServerFieldsBlankUrl() {
when(serverMock.getUrl()).thenReturn(" ");
provider.validateRequiredServerFields(serverMock);
}
@Test(expected = IllegalArgumentException.class)
public void testValidateServerFieldsNullApiKey() {
when(serverMock.getDnsApiKey()).thenReturn(null);
provider.validateRequiredServerFields(serverMock);
}
@Test(expected = IllegalArgumentException.class)
public void testValidateServerFieldsBlankApiKey() {
when(serverMock.getDnsApiKey()).thenReturn("");
provider.validateRequiredServerFields(serverMock);
}
@Test(expected = IllegalArgumentException.class)
public void testValidateServerAndZoneFieldsBlankZoneName() {
when(zoneMock.getName()).thenReturn(" ");
provider.validateRequiredServerAndZoneFields(serverMock, zoneMock);
}
@Test(expected = IllegalArgumentException.class)
public void testValidateServerAndZoneFieldsNullZoneName() {
when(zoneMock.getName()).thenReturn(null);
provider.validateRequiredServerAndZoneFields(serverMock, zoneMock);
}
@Test
public void testValidateDelegatesToClient() throws DnsProviderException {
when(clientMock.validateServerId(anyString(), anyInt(), anyString(), anyString())).thenReturn("localhost");
provider.validate(serverMock);
verify(clientMock).validateServerId("http://pdns:8081", 8081, "secret", "localhost");
}
@Test(expected = IllegalArgumentException.class)
public void testValidateThrowsWhenServerUrlBlank() throws DnsProviderException {
when(serverMock.getUrl()).thenReturn("");
provider.validate(serverMock);
}
@Test
public void testValidateAndResolveServer() throws Exception {
when(clientMock.resolveServerId(anyString(), anyInt(), anyString(), anyString())).thenReturn("localhost");
String result = provider.validateAndResolveServer(serverMock);
assertEquals("localhost", result);
verify(clientMock).resolveServerId("http://pdns:8081", 8081, "secret", "localhost");
}
@Test(expected = IllegalArgumentException.class)
public void testValidateAndResolveServerThrowsWhenUrlBlank() throws Exception {
when(serverMock.getUrl()).thenReturn(null);
provider.validateAndResolveServer(serverMock);
}
@Test
public void testProvisionZoneDelegatesToClient() throws DnsProviderException {
when(clientMock.createZone(anyString(), anyInt(), anyString(), anyString(), anyString(), anyString(), eq(false), anyList())).thenReturn("example.com.");
String zoneId = provider.provisionZone(serverMock, zoneMock);
assertEquals("example.com.", zoneId);
verify(clientMock).createZone("http://pdns:8081", 8081, "secret", "localhost", "example.com",
"Native", false, Arrays.asList("ns1.example.com"));
}
@Test(expected = IllegalArgumentException.class)
public void testProvisionZoneThrowsWhenZoneNameBlank() throws DnsProviderException {
when(zoneMock.getName()).thenReturn(null);
provider.provisionZone(serverMock, zoneMock);
}
@Test
public void testDeleteZoneDelegatesToClient() throws DnsProviderException {
provider.deleteZone(serverMock, zoneMock);
verify(clientMock).deleteZone("http://pdns:8081", 8081, "secret", "localhost", "example.com");
}
@Test(expected = IllegalArgumentException.class)
public void testDeleteZoneThrowsWhenZoneNameBlank() throws DnsProviderException {
when(zoneMock.getName()).thenReturn("");
provider.deleteZone(serverMock, zoneMock);
}
@Test
public void testUpdateZoneDelegatesToClient() throws DnsProviderException {
provider.updateZone(serverMock, zoneMock);
verify(clientMock).updateZone("http://pdns:8081", 8081, "secret", "localhost", "example.com",
"Native", false, Arrays.asList("ns1.example.com"));
}
@Test
public void testAddRecordDelegatesToClient() throws DnsProviderException {
DnsRecord record = new DnsRecord("www", RecordType.A, Arrays.asList("1.2.3.4"), 300);
when(clientMock.modifyRecord(anyString(), anyInt(), anyString(), anyString(), anyString(),
anyString(), anyString(), anyLong(), anyList(), anyString())).thenReturn("www.example.com");
String result = provider.addRecord(serverMock, zoneMock, record);
assertEquals("www.example.com", result);
verify(clientMock).modifyRecord("http://pdns:8081", 8081, "secret", "localhost", "example.com",
"www", "A", 300L, Arrays.asList("1.2.3.4"), "REPLACE");
}
@Test(expected = IllegalArgumentException.class)
public void testAddRecordThrowsWhenServerUrlBlank() throws DnsProviderException {
when(serverMock.getUrl()).thenReturn("");
DnsRecord record = new DnsRecord("www", RecordType.A, Arrays.asList("1.2.3.4"), 300);
provider.addRecord(serverMock, zoneMock, record);
}
@Test
public void testUpdateRecordDelegatesToAddRecord() throws DnsProviderException {
DnsRecord record = new DnsRecord("mail", RecordType.MX, Arrays.asList("10 mail.example.com"), 300);
when(clientMock.modifyRecord(anyString(), anyInt(), anyString(), anyString(), anyString(),
anyString(), anyString(), anyLong(), anyList(), anyString())).thenReturn("mail.example.com");
String result = provider.updateRecord(serverMock, zoneMock, record);
assertEquals("mail.example.com", result);
verify(clientMock).modifyRecord(anyString(), anyInt(), anyString(), anyString(), anyString(),
eq("mail"), eq("MX"), eq(300L), eq(Arrays.asList("10 mail.example.com")), eq("REPLACE"));
}
@Test
public void testDeleteRecordDelegatesToClientWithDeleteChangeType() throws DnsProviderException {
DnsRecord record = new DnsRecord("old", RecordType.CNAME, Arrays.asList("target.com"), 600);
when(clientMock.modifyRecord(anyString(), anyInt(), anyString(), anyString(), anyString(),
anyString(), anyString(), anyLong(), anyList(), anyString())).thenReturn("old.example.com");
String result = provider.deleteRecord(serverMock, zoneMock, record);
assertEquals("old.example.com", result);
verify(clientMock).modifyRecord(anyString(), anyInt(), anyString(), anyString(), anyString(),
eq("old"), eq("CNAME"), eq(600L), eq(Arrays.asList("target.com")), eq("DELETE"));
}
@Test
public void testApplyRecordPassesChangeTypeToClient() throws DnsProviderException {
DnsRecord record = new DnsRecord("txt", RecordType.TXT, Arrays.asList("v=spf1 include:example.com ~all"), 3600);
when(clientMock.modifyRecord(anyString(), anyInt(), anyString(), anyString(), anyString(),
anyString(), anyString(), anyLong(), anyList(), anyString())).thenReturn("txt.example.com");
provider.applyRecord("http://pdns:8081", 8081, "secret", "localhost", "example.com",
record, PowerDnsProvider.ChangeType.REPLACE);
verify(clientMock).modifyRecord("http://pdns:8081", 8081, "secret", "localhost", "example.com",
"txt", "TXT", 3600L, Arrays.asList("v=spf1 include:example.com ~all"), "REPLACE");
}
@Test
public void testListRecordsParsesRrsets() throws DnsProviderException {
ObjectNode aRecord = MAPPER.createObjectNode();
aRecord.put("name", "www.example.com.");
aRecord.put("type", "A");
aRecord.put("ttl", 300);
ArrayNode records = aRecord.putArray("records");
records.addObject().put("content", "1.2.3.4");
ObjectNode mxRecord = MAPPER.createObjectNode();
mxRecord.put("name", "example.com.");
mxRecord.put("type", "MX");
mxRecord.put("ttl", 600);
ArrayNode mxRecords = mxRecord.putArray("records");
mxRecords.addObject().put("content", "10 mail.example.com");
when(clientMock.listRecords(anyString(), anyInt(), anyString(), anyString(), anyString()))
.thenReturn(Arrays.asList(aRecord, mxRecord));
List<DnsRecord> result = provider.listRecords(serverMock, zoneMock);
assertEquals(2, result.size());
DnsRecord first = result.get(0);
assertEquals("www.example.com.", first.getName());
assertEquals(RecordType.A, first.getType());
assertEquals(300, first.getTtl());
assertEquals(Arrays.asList("1.2.3.4"), first.getContents());
DnsRecord second = result.get(1);
assertEquals("example.com.", second.getName());
assertEquals(RecordType.MX, second.getType());
assertEquals(600, second.getTtl());
assertEquals(Arrays.asList("10 mail.example.com"), second.getContents());
}
@Test
public void testListRecordsSkipsSoaRecords() throws DnsProviderException {
ObjectNode soaRecord = MAPPER.createObjectNode();
soaRecord.put("name", "example.com.");
soaRecord.put("type", "SOA");
soaRecord.put("ttl", 3600);
soaRecord.putArray("records").addObject().put("content", "ns1.example.com. admin.example.com. ...");
when(clientMock.listRecords(anyString(), anyInt(), anyString(), anyString(), anyString()))
.thenReturn(Collections.singletonList(soaRecord));
List<DnsRecord> result = provider.listRecords(serverMock, zoneMock);
assertTrue(result.isEmpty());
}
@Test
public void testListRecordsSkipsUnknownRecordTypes() throws DnsProviderException {
ObjectNode unknownRecord = MAPPER.createObjectNode();
unknownRecord.put("name", "test.example.com.");
unknownRecord.put("type", "UNKNOWNTYPE");
unknownRecord.put("ttl", 300);
unknownRecord.putArray("records").addObject().put("content", "some-data");
when(clientMock.listRecords(anyString(), anyInt(), anyString(), anyString(), anyString()))
.thenReturn(Collections.singletonList(unknownRecord));
List<DnsRecord> result = provider.listRecords(serverMock, zoneMock);
assertTrue(result.isEmpty());
}
@Test
public void testListRecordsIgnoresEmptyContentEntries() throws DnsProviderException {
ObjectNode aRecord = MAPPER.createObjectNode();
aRecord.put("name", "host.example.com.");
aRecord.put("type", "A");
aRecord.put("ttl", 300);
ArrayNode records = aRecord.putArray("records");
records.addObject().put("content", "");
records.addObject().put("content", "5.6.7.8");
when(clientMock.listRecords(anyString(), anyInt(), anyString(), anyString(), anyString()))
.thenReturn(Collections.singletonList(aRecord));
List<DnsRecord> result = provider.listRecords(serverMock, zoneMock);
assertEquals(1, result.size());
assertEquals(Collections.singletonList("5.6.7.8"), result.get(0).getContents());
}
@Test
public void testListRecordsReturnsEmptyListWhenClientReturnsEmpty() throws DnsProviderException {
when(clientMock.listRecords(anyString(), anyInt(), anyString(), anyString(), anyString()))
.thenReturn(Collections.emptyList());
List<DnsRecord> result = provider.listRecords(serverMock, zoneMock);
assertNotNull(result);
assertTrue(result.isEmpty());
}
@Test(expected = DnsProviderException.class)
public void testListRecordsPropagatesClientException() throws DnsProviderException {
when(clientMock.listRecords(anyString(), anyInt(), anyString(), anyString(), anyString()))
.thenThrow(mock(DnsProviderException.class));
provider.listRecords(serverMock, zoneMock);
}
@Test(expected = IllegalArgumentException.class)
public void testListRecordsThrowsWhenZoneNameBlank() throws DnsProviderException {
when(zoneMock.getName()).thenReturn("");
provider.listRecords(serverMock, zoneMock);
}
@Test
public void testChangeTypeValues() {
assertEquals("REPLACE", PowerDnsProvider.ChangeType.REPLACE.name());
assertEquals("DELETE", PowerDnsProvider.ChangeType.DELETE.name());
}
}

View File

@ -153,6 +153,7 @@
<module>user-two-factor-authenticators/totp</module>
<module>user-two-factor-authenticators/static-pin</module>
<module>dns/powerdns</module>
</modules>
<dependencies>

View File

@ -28,6 +28,9 @@ import org.apache.cloudstack.acl.RolePermissionEntity;
import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.dns.DnsProviderManager;
import org.apache.cloudstack.dns.DnsServer;
import org.apache.cloudstack.dns.DnsZone;
import org.apache.cloudstack.query.QueryService;
import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao;
import org.springframework.stereotype.Component;
@ -101,6 +104,8 @@ public class DomainChecker extends AdapterBase implements SecurityChecker {
private ProjectDao projectDao;
@Inject
private AccountService accountService;
@Inject
private DnsProviderManager dnsProviderManager;
protected DomainChecker() {
super();
@ -216,7 +221,11 @@ public class DomainChecker extends AdapterBase implements SecurityChecker {
_networkMgr.checkRouterPermissions(caller, (VirtualRouter)entity);
} else if (entity instanceof AffinityGroup) {
return false;
} else {
} else if (entity instanceof DnsServer) {
dnsProviderManager.checkDnsServerPermission(caller, (DnsServer) entity);
} else if (entity instanceof DnsZone) {
dnsProviderManager.checkDnsZonePermission(caller, (DnsZone) entity);
} else {
validateCallerHasAccessToEntityOwner(caller, entity, accessType);
}
return true;

View File

@ -80,6 +80,10 @@ import org.apache.cloudstack.backup.dao.BackupOfferingDao;
import org.apache.cloudstack.backup.dao.BackupRepositoryDao;
import org.apache.cloudstack.backup.dao.BackupScheduleDao;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.dns.dao.DnsZoneDao;
import org.apache.cloudstack.dns.dao.DnsZoneNetworkMapDao;
import org.apache.cloudstack.dns.vo.DnsZoneNetworkMapVO;
import org.apache.cloudstack.dns.vo.DnsZoneVO;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
@ -363,6 +367,7 @@ import org.apache.cloudstack.acl.dao.ApiKeyPairDao;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.util.Strings;
public class ApiDBUtils {
private static final Logger log = LogManager.getLogger(ApiDBUtils.class);
@ -504,6 +509,8 @@ public class ApiDBUtils {
static SharedFSJoinDao s_sharedFSJoinDao;
static BucketDao s_bucketDao;
static DnsZoneDao s_dnsZoneDao;
static DnsZoneNetworkMapDao s_dnsZoneNetworkMapDao;
static VirtualMachineManager s_virtualMachineManager;
@Inject
@ -776,6 +783,10 @@ public class ApiDBUtils {
private VirtualMachineManager virtualMachineManager;
@Inject
private SharedFSJoinDao sharedFSJoinDao;
@Inject
private DnsZoneDao dnsZoneDao;
@Inject
private DnsZoneNetworkMapDao dnsZoneNetworkMapDao;
@PostConstruct
void init() {
@ -914,6 +925,8 @@ public class ApiDBUtils {
s_bucketDao = bucketDao;
s_virtualMachineManager = virtualMachineManager;
s_sharedFSJoinDao = sharedFSJoinDao;
s_dnsZoneDao = dnsZoneDao;
s_dnsZoneNetworkMapDao = dnsZoneNetworkMapDao;
}
// ///////////////////////////////////////////////////////////
@ -2296,6 +2309,17 @@ public class ApiDBUtils {
return details.isEmpty() ? null : details;
}
public static Pair<String, String> findDnsZoneByNetworkId(long networkId) {
DnsZoneNetworkMapVO dnsNetworkMapVO = s_dnsZoneNetworkMapDao.findByNetworkId(networkId);
if (dnsNetworkMapVO != null) {
DnsZoneVO dnsZoneVO = s_dnsZoneDao.findById(dnsNetworkMapVO.getDnsZoneId());
if (Strings.isNotBlank(dnsZoneVO.getName())) {
return new Pair<> (dnsZoneVO.getName(), dnsNetworkMapVO.getSubDomain());
}
}
return new Pair<>(null, null);
}
public static boolean isAdmin(Account account) {
return s_accountService.isAdmin(account.getId());
}

View File

@ -549,6 +549,7 @@ public class ApiResponseHelper implements ResponseGenerator, ResourceIdSupport {
@Inject
AsyncJobDao asyncJobDao;
public static String getPrettyDomainPath(String path) {
if (path == null) {
return null;
@ -2657,6 +2658,14 @@ public class ApiResponseHelper implements ResponseGenerator, ResourceIdSupport {
response.setDetails(details);
}
Pair<String, String> dnsZoneAndSubDomain = ApiDBUtils.findDnsZoneByNetworkId(network.getId());
if (StringUtils.isNotBlank(dnsZoneAndSubDomain.first())) {
response.setDnsZone(dnsZoneAndSubDomain.first());
}
if (StringUtils.isNotBlank(dnsZoneAndSubDomain.second())) {
response.setDnsSubdomain(dnsZoneAndSubDomain.second());
}
DataCenter zone = ApiDBUtils.findZoneById(network.getDataCenterId());
if (zone != null) {
response.setZoneId(zone.getUuid());

View File

@ -360,6 +360,7 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
nicResponse.setIp6Gateway(userVm.getIp6Gateway());
nicResponse.setIp6Cidr(userVm.getIp6Cidr());
nicResponse.setEnabled(userVm.isNicEnabled());
nicResponse.setNicDnsName(userVm.getNicDnsName());
if (userVm.getBroadcastUri() != null) {
nicResponse.setBroadcastUri(userVm.getBroadcastUri().toString());
}
@ -621,6 +622,9 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
nicResponse.setIp6Gateway(uvo.getIp6Gateway());
/*13: IPv6Cidr*/
nicResponse.setIp6Cidr(uvo.getIp6Cidr());
/* dnsRecordUrl */
nicResponse.setNicDnsName(uvo.getNicDnsName());
/*14: deviceId*/
// where do we find nicResponse.setDeviceId(
// this is probably not String.valueOf(uvo.getNicId())); as this is a db-id

View File

@ -32,21 +32,22 @@ import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import org.apache.cloudstack.util.HypervisorTypeConverter;
import com.cloud.host.Status;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.network.Network.GuestType;
import com.cloud.network.Networks.TrafficType;
import com.cloud.resource.ResourceState;
import com.cloud.storage.Storage;
import com.cloud.storage.Storage.TemplateType;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.Storage.TemplateType;
import com.cloud.storage.Volume;
import com.cloud.user.Account;
import com.cloud.util.StoragePoolTypeConverter;
import com.cloud.utils.db.GenericDao;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachine.State;
import org.apache.cloudstack.util.HypervisorTypeConverter;
@Entity
@Table(name = "user_vm_view")
@ -404,6 +405,9 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro
@Column(name = "public_ip_address")
private String publicIpAddress;
@Column(name = "nic_dns_name")
private String nicDnsName;
@Column(name = "user_data", updatable = true, nullable = true, length = 2048)
private String userData;
@ -1103,4 +1107,8 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro
public boolean isNicEnabled() {
return isNicEnabled;
}
public String getNicDnsName() {
return nicDnsName;
}
}

View File

@ -315,7 +315,7 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService,
sb.and("id", sb.entity().getId(), Op.EQ);
sb.and("trafficType", sb.entity().getTrafficType(), Op.EQ);
sb.and("networkId", sb.entity().getNetworkId(), Op.EQ);
sb.and("networkId", sb.entity().getNetworkId(), Op.EQ);
sb.and("ip", sb.entity().getSourceIpAddressId(), Op.EQ);
sb.and("purpose", sb.entity().getPurpose(), Op.EQ);
sb.and("display", sb.entity().isDisplay(), Op.EQ);

View File

@ -96,6 +96,8 @@ import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
import org.apache.cloudstack.backup.BackupOffering;
import org.apache.cloudstack.config.ApiServiceConfiguration;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.dns.DnsServer;
import org.apache.cloudstack.dns.DnsZone;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
@ -775,7 +777,8 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
}
if (entity.getAccountId() != -1 && domainId != -1 && !(entity instanceof VirtualMachineTemplate)
&& !(entity instanceof Network && (accessType == AccessType.UseEntry || accessType == AccessType.OperateEntry))
&& !(entity instanceof AffinityGroup) && !(entity instanceof VirtualRouter)) {
&& !(entity instanceof AffinityGroup) && !(entity instanceof VirtualRouter)
&& !(entity instanceof DnsServer) && !(entity instanceof DnsZone)) {
List<ControlledEntity> toBeChecked = domains.get(entity.getDomainId());
// for templates, we don't have to do cross domains check
if (toBeChecked == null) {

View File

@ -16,9 +16,15 @@
// under the License.
package com.cloud.vm;
import static com.cloud.event.EventTypes.EVENT_NIC_CREATE;
import static com.cloud.event.EventTypes.EVENT_NIC_DELETE;
import static com.cloud.event.EventTypes.EVENT_NIC_UPDATE;
import static com.cloud.event.EventTypes.EVENT_VM_UPDATE;
import static com.cloud.hypervisor.Hypervisor.HypervisorType.Functionality;
import static com.cloud.storage.Volume.IOPS_LIMIT;
import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
import static com.cloud.vm.VirtualMachineManager.Topics.VM_ACTION;
import static com.cloud.vm.VirtualMachineManager.Topics.VM_LIFECYCLE_STATE;
import static org.apache.cloudstack.api.ApiConstants.MAX_IOPS;
import static org.apache.cloudstack.api.ApiConstants.MIN_IOPS;
@ -159,6 +165,7 @@ import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.logging.log4j.util.Strings;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
@ -1447,7 +1454,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_NIC_CREATE, eventDescription = "Creating NIC", async = true)
@ActionEvent(eventType = EVENT_NIC_CREATE, eventDescription = "Creating NIC", async = true)
public UserVm addNicToVirtualMachine(AddNicToVMCmd cmd) throws InvalidParameterValueException, PermissionDeniedException, CloudRuntimeException {
Long vmId = cmd.getVmId();
Long networkId = cmd.getNetworkId();
@ -1549,9 +1556,59 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
CallContext.current().putContextParameter(Nic.class, guestNic.getUuid());
logger.debug(String.format("Successful addition of %s from %s through %s", network, vmInstance, guestNic));
publishNicEventMessageBus(vmInstance.getId(), vmInstance.getAccountId(), guestNic.getId(), EVENT_NIC_CREATE);
return _vmDao.findById(vmInstance.getId());
}
private void publishVmLifecycleMessageBus(UserVm instance, @Nullable VirtualMachine.State oldState, VirtualMachine.State newState) {
try {
Map<String, Object> event = new HashMap<>();
event.put(ApiConstants.EVENT_ID, UUID.randomUUID().toString());
event.put(ApiConstants.INSTANCE_ID, instance.getId());
event.put(ApiConstants.ACCOUNT_ID, instance.getAccountId());
event.put(ApiConstants.OLD_STATE, oldState != null ? oldState : State.Unknown);
event.put(ApiConstants.NEW_STATE, newState);
event.put(ApiConstants.TIME_STAMP, System.currentTimeMillis());
messageBus.publish(_name, VM_LIFECYCLE_STATE, PublishScope.GLOBAL, event);
} catch (Exception ex) {
logger.warn("Failed to publish lifecycle event for Instance: {}", instance.getUuid(), ex);
}
}
private void publishNicEventMessageBus(Long instanceId, Long accountId, Long nicId, String eventType) {
try {
Map<String, Object> event = new HashMap<>();
event.put(ApiConstants.EVENT_ID, UUID.randomUUID().toString());
event.put(ApiConstants.INSTANCE_ID, instanceId);
event.put(ApiConstants.ACCOUNT_ID, accountId);
event.put(ApiConstants.NIC_ID, nicId);
event.put(ApiConstants.EVENT_TYPE, eventType); // NIC.CREATE, NIC.DELETE or NIC.UPDATE
event.put(ApiConstants.TIME_STAMP, System.currentTimeMillis());
messageBus.publish(_name, Nic.Topics.NIC_LIFECYCLE, PublishScope.GLOBAL, event);
} catch (Exception ex) {
logger.error("Failed to publish lifecycle event for NIC with ID: {}", nicId, ex);
}
}
private void publishVmHostNameUpdateMessageBus(long instanceId, String oldHostName, String hostName) {
if (Strings.isBlank(hostName) || oldHostName.equalsIgnoreCase(hostName)) {
return;
}
try {
Map<String, Object> event = new HashMap<>();
event.put(ApiConstants.EVENT_ID, UUID.randomUUID().toString());
event.put(ApiConstants.INSTANCE_ID, instanceId);
event.put(ApiConstants.OLD_HOST_NAME, oldHostName);
event.put(ApiConstants.HOST_NAME, hostName);
event.put(ApiConstants.EVENT_TYPE, EVENT_VM_UPDATE);
event.put(ApiConstants.TIME_STAMP, System.currentTimeMillis());
messageBus.publish(_name, VM_ACTION, PublishScope.GLOBAL, event);
} catch (Exception ex) {
logger.error("Failed to publish Instance action event for ID: {}", instanceId, ex);
}
}
/**
* Set NIC as default if VM has no default NIC
* @param vmInstance VM instance to be checked
@ -1666,6 +1723,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
logger.debug("Successful removal of " + network + " from " + vmInstance);
publishNicEventMessageBus(vmInstance.getId(), vmInstance.getAccountId(), nic.getId(), EVENT_NIC_DELETE);
return _vmDao.findById(vmInstance.getId());
}
@ -1907,7 +1965,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
logger.debug("Updating IPv4 address of NIC " + nicVO + " to " + ipaddr + "/" + nicVO.getIPv4Netmask() + " with gateway " + nicVO.getIPv4Gateway());
nicVO.setIPv4Address(ipaddr);
_nicDao.persist(nicVO);
publishNicEventMessageBus(vm.getId(), vm.getAccountId(), nicVO.getId(), EVENT_NIC_UPDATE);
return vm;
}
@ -2929,7 +2987,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_UPDATE, eventDescription = "updating Vm")
@ActionEvent(eventType = EVENT_VM_UPDATE, eventDescription = "updating Vm")
public UserVm updateVirtualMachine(UpdateVMCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException {
validateInputsAndPermissionForUpdateVirtualMachineCommand(cmd);
@ -3310,6 +3368,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
if (State.Running == vm.getState()) {
publishVmHostNameUpdateMessageBus(vm.getId(), vm.getHostName(), hostName);
updateDns(vm, hostName);
}
@ -3650,7 +3709,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
logger.warn("Tried to destroy ROOT volume for VM [{}], but couldn't retrieve it.", vm);
}
}
publishVmLifecycleMessageBus(destroyedVm, vm.getState(), VirtualMachine.State.Destroyed);
return destroyedVm;
}
@ -5341,7 +5400,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
additionalParams.put(VirtualMachineProfile.Param.VmPassword, cmd.getPassword());
}
return startVirtualMachine(vmId, podId, clusterId, hostId, diskOfferingMap, additionalParams, cmd.getDeploymentPlanner());
UserVm userVm = startVirtualMachine(vmId, podId, clusterId, hostId, diskOfferingMap, additionalParams, cmd.getDeploymentPlanner());
publishVmLifecycleMessageBus(userVm, null, VirtualMachine.State.Running);
return userVm;
}
private UserVm startVirtualMachine(long vmId, Long podId, Long clusterId, Long hostId, Map<Long, DiskOffering> diskOfferingMap

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,249 @@
// 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.net.Inet4Address;
import java.net.Inet6Address;
import org.apache.commons.validator.routines.DomainValidator;
import com.cloud.utils.StringUtils;
import com.google.common.net.InetAddresses;
public class DnsProviderUtil {
static DomainValidator validator = DomainValidator.getInstance(true);
public static String appendPublicSuffixToZone(String zoneName, String suffixDomain) {
if (StringUtils.isBlank(suffixDomain)) {
return zoneName;
}
suffixDomain = DnsProviderUtil.normalizeDomainForDb(suffixDomain);
// Already suffixed return as-is
if (zoneName.toLowerCase().endsWith("." + suffixDomain)) {
return zoneName;
}
if (zoneName.equals(suffixDomain)) {
throw new IllegalArgumentException("Cannot create DNS zone at root-level: " + suffixDomain);
}
// Check TLD matches
String tldUser = getTld(zoneName);
String tldSuffix = getTld(suffixDomain);
if (!tldUser.equalsIgnoreCase(tldSuffix)) {
throw new IllegalArgumentException("TLD mismatch between user zone and domain suffix");
}
// Remove TLD from userZone
int lastDot = zoneName.lastIndexOf('.');
String zonePrefix = zoneName.substring(0, lastDot);
return zonePrefix + "." + suffixDomain;
}
private static String getTld(String domain) {
String[] labels = domain.split("\\.");
return labels[labels.length - 1];
}
// lowercase, no trailing dot (used for DB storage, comparisons)
public static String normalizeDomainForDb(String domain) {
if (StringUtils.isBlank(domain)) {
throw new IllegalArgumentException("Domain cannot be empty");
}
String normalized = domain.trim().toLowerCase();
if (normalized.endsWith(".")) {
normalized = normalized.substring(0, normalized.length() - 1);
}
// Validate domain, allow local/private TLDs
boolean valid = isValidInternalDnsZoneName(normalized) || validator.isValid(normalized);
if (!valid) {
throw new IllegalArgumentException("Invalid domain name: " + domain);
}
return normalized;
}
static boolean isValidInternalDnsZoneName(String domain) {
// Total length limit (DNS standard)
if (domain.length() > 253) {
return false;
}
String[] labels = domain.split("\\.");
// Must have at least 2 labels (zone + suffix like internal/test/etc.)
if (labels.length < 2) {
return false;
}
for (String label : labels) {
if (label.isEmpty() || label.length() > 63) {
return false;
}
// Must start and end with alphanumeric (even if underscores exist inside)
if (!label.matches("^[a-z0-9](?:[a-z0-9_-]{0,61}[a-z0-9])?$")) {
return false;
}
// Prevent obviously invalid cases like "__" only labels
if (label.equals("_")) {
return false;
}
}
return true;
}
// DNS wire form: lowercase, validated, WITH trailing dot (used in record values)
public static String normalizeDnsRecordValue(String value, DnsRecord.RecordType recordType) {
if (StringUtils.isBlank(value)) {
throw new IllegalArgumentException("DNS record value cannot be empty");
}
String trimmedValue = value.trim();
switch (recordType) {
case A:
if (!(InetAddresses.forString(trimmedValue) instanceof Inet4Address)) {
throw new IllegalArgumentException(
String.format("Invalid IP address for %s record: %s", recordType, value));
}
return trimmedValue;
case AAAA:
if (!(InetAddresses.forString(trimmedValue) instanceof Inet6Address)) {
throw new IllegalArgumentException(
String.format("Invalid IP address for %s record: %s", recordType, value));
}
return trimmedValue;
case CNAME:
case NS:
case PTR:
return normalizeDomainForDnsRecord(trimmedValue);
case SRV:
return normalizeSrvRecord(trimmedValue);
case MX:
return normalizeMxRecord(trimmedValue);
case TXT:
// Free text: preserve exactly
return trimmedValue;
default:
throw new IllegalArgumentException("Unsupported DNS record type: " + recordType);
}
}
static String normalizeDomainForDnsRecord(String domain) {
if (StringUtils.isBlank(domain)) {
throw new IllegalArgumentException("Domain name cannot be empty");
}
String normalized = domain.trim().toLowerCase();
// Strip trailing dot first (normalize input)
if (normalized.endsWith(".")) {
normalized = normalized.substring(0, normalized.length() - 1);
}
// Reject IP addresses
if (InetAddresses.isInetAddress(normalized)) {
throw new IllegalArgumentException("Domain cannot be an IP address: " + normalized);
}
// Validate total length (max 253 chars, excluding trailing dot)
if (normalized.length() > 253) {
throw new IllegalArgumentException(
"Domain name exceeds maximum length: " + normalized);
}
// Validate labels
String[] labels = normalized.split("\\.", -1);
for (String label : labels) {
if (label.isEmpty()) {
throw new IllegalArgumentException(
"Domain contains empty label: " + normalized);
}
if (label.length() > 63) {
throw new IllegalArgumentException(
"Domain label too long: " + label);
}
if (!label.matches("[a-z0-9]([a-z0-9-]*[a-z0-9])?")) {
throw new IllegalArgumentException(
"Invalid domain label: " + label);
}
}
return normalized + ".";
}
private static String normalizeSrvRecord(String value) {
String trimmed = value.trim();
String[] parts = trimmed.split("\\s+", 4);
if (parts.length != 4) {
throw new IllegalArgumentException(
"Invalid SRV record value (expected '<priority> <weight> <port> <target>'): " + trimmed);
}
int priority;
int weight;
int port;
try {
priority = Integer.parseInt(parts[0]);
weight = Integer.parseInt(parts[1]);
port = Integer.parseInt(parts[2]);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(
"SRV priority/weight/port must be numeric: " + trimmed);
}
if (priority < 0 || priority > 65535) {
throw new IllegalArgumentException("SRV priority out of range (065535): " + parts[0]);
}
if (weight < 0 || weight > 65535) {
throw new IllegalArgumentException("SRV weight out of range (065535): " + parts[1]);
}
if (port < 1 || port > 65535) {
throw new IllegalArgumentException("SRV port out of range (165535): " + parts[2]);
}
String target = normalizeDomainForDnsRecord(parts[3]);
return priority + " " + weight + " " + port + " " + target;
}
private static String normalizeMxRecord(String value) {
String trimmed = value.trim();
String[] parts = trimmed.split("\\s+", 2);
if (parts.length != 2) {
throw new IllegalArgumentException(
"Invalid MX record value (expected '<priority> <mail-exchanger>'): " + trimmed);
}
int priority;
try {
priority = Integer.parseInt(parts[0]);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(
"MX priority must be numeric: " + parts[0]);
}
if (priority < 0 || priority > 65535) {
throw new IllegalArgumentException(
"MX priority out of range (065535): " + parts[0]);
}
String mailExchanger = normalizeDomainForDnsRecord(parts[1]);
return priority + " " + mailExchanger;
}
}

View File

@ -0,0 +1,44 @@
// 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 java.util.Set;
import org.apache.cloudstack.dns.DnsProviderType;
import org.apache.cloudstack.dns.DnsServer;
import org.apache.cloudstack.dns.vo.DnsServerVO;
import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDao;
public interface DnsServerDao extends GenericDao<DnsServerVO, Long> {
DnsServerVO findById(Long dnsServerId);
DnsServer findByUrlAndAccount(String url, long accountId);
List<Long> listDnsServerIdsByAccountId(Long accountId);
Pair<List<DnsServerVO>, Integer> searchDnsServer(Long dnsServerId, Long accountId, Set<Long> domainIds, DnsProviderType providerType, String keyword, Filter filter);
void loadDetails(DnsServer dnsServer);
void saveDetails(DnsServer dnsServer);
}

View File

@ -0,0 +1,196 @@
// 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.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.dns.DnsProviderType;
import org.apache.cloudstack.dns.DnsServer;
import org.apache.cloudstack.dns.vo.DnsServerDetailVO;
import org.apache.cloudstack.dns.vo.DnsServerVO;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.GenericSearchBuilder;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallback;
@Component
public class DnsServerDaoImpl extends GenericDaoBase<DnsServerVO, Long> implements DnsServerDao {
@Inject
DnsServerDetailsDao dnsServerDetailsDao;
SearchBuilder<DnsServerVO> AllFieldsSearch;
SearchBuilder<DnsServerVO> AccountUrlSearch;
GenericSearchBuilder<DnsServerVO, Long> DnsServerIdsByAccountSearch;
public DnsServerDaoImpl() {
super();
AccountUrlSearch = createSearchBuilder();
AccountUrlSearch.and(ApiConstants.URL, AccountUrlSearch.entity().getUrl(), SearchCriteria.Op.EQ);
AccountUrlSearch.and(ApiConstants.ACCOUNT_ID, AccountUrlSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
AccountUrlSearch.done();
AllFieldsSearch = createSearchBuilder();
AllFieldsSearch.and(ApiConstants.ID, AllFieldsSearch.entity().getId(), SearchCriteria.Op.EQ);
AllFieldsSearch.and(ApiConstants.NAME, AllFieldsSearch.entity().getName(), SearchCriteria.Op.LIKE);
AllFieldsSearch.and(ApiConstants.PROVIDER_TYPE, AllFieldsSearch.entity().getProviderType(), SearchCriteria.Op.EQ);
AllFieldsSearch.and(ApiConstants.ACCOUNT_ID, AllFieldsSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
AllFieldsSearch.done();
DnsServerIdsByAccountSearch = createSearchBuilder(Long.class);
DnsServerIdsByAccountSearch.selectFields(DnsServerIdsByAccountSearch.entity().getId());
DnsServerIdsByAccountSearch.and(ApiConstants.ACCOUNT_ID, DnsServerIdsByAccountSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
DnsServerIdsByAccountSearch.and(ApiConstants.STATE, DnsServerIdsByAccountSearch.entity().getState(), SearchCriteria.Op.EQ);
DnsServerIdsByAccountSearch.done();
}
@Override
public DnsServerVO findById(Long dnsServerId) {
DnsServerVO dnsServer = super.findById(dnsServerId);
loadDetails(dnsServer);
return dnsServer;
}
@Override
public DnsServer findByUrlAndAccount(String url, long accountId) {
SearchCriteria<DnsServerVO> sc = AccountUrlSearch.create();
sc.setParameters(ApiConstants.URL, url);
sc.setParameters(ApiConstants.ACCOUNT_ID, accountId);
return findOneBy(sc);
}
@Override
public List<Long> listDnsServerIdsByAccountId(Long accountId) {
SearchCriteria<Long> sc = DnsServerIdsByAccountSearch.create();
if (accountId != null) {
sc.setParameters(ApiConstants.ACCOUNT_ID, accountId);
}
sc.setParameters(ApiConstants.STATE, DnsServer.State.Enabled);
return customSearch(sc, null);
}
@Override
public Pair<List<DnsServerVO>, Integer> searchDnsServer(Long dnsServerId, Long accountId, Set<Long> domainIds, DnsProviderType providerType,
String keyword, Filter filter) {
SearchBuilder<DnsServerVO> sb = createSearchBuilder();
sb.and(ApiConstants.ID, sb.entity().getId(), SearchCriteria.Op.EQ);
sb.and(ApiConstants.NAME, sb.entity().getName(), SearchCriteria.Op.LIKE);
sb.and().op(ApiConstants.ACCOUNT_ID, sb.entity().getAccountId(), SearchCriteria.Op.EQ);
if (!CollectionUtils.isEmpty(domainIds)) {
sb.or().op(ApiConstants.IS_PUBLIC, sb.entity().getPublicServer(), SearchCriteria.Op.EQ);
sb.and(ApiConstants.DOMAIN_IDS, sb.entity().getDomainId(), SearchCriteria.Op.IN);
sb.cp();
}
sb.cp();
sb.and(ApiConstants.PROVIDER_TYPE, sb.entity().getProviderType(), SearchCriteria.Op.EQ);
sb.and(ApiConstants.STATE, sb.entity().getState(), SearchCriteria.Op.EQ);
sb.done();
SearchCriteria<DnsServerVO> sc = sb.create();
if (dnsServerId != null) {
sc.setParameters(ApiConstants.ID, dnsServerId);
}
if (accountId != null) {
sc.setParameters(ApiConstants.ACCOUNT_ID, accountId);
}
if (!CollectionUtils.isEmpty(domainIds)) {
sc.setParameters(ApiConstants.IS_PUBLIC, true);
sc.setParameters(ApiConstants.DOMAIN_IDS, domainIds.toArray());
}
if (providerType != null) {
sc.setParameters(ApiConstants.PROVIDER_TYPE, providerType);
}
if (keyword != null) {
sc.setParameters(ApiConstants.NAME, "%" + keyword + "%");
}
sc.setParameters(ApiConstants.STATE, DnsServer.State.Enabled);
return searchAndCount(sc, filter);
}
@Override
public DnsServerVO persist(DnsServerVO dnsServer) {
return Transaction.execute((TransactionCallback<DnsServerVO>) status -> {
DnsServerVO dnsServerDb = super.persist(dnsServer);
saveDetails(dnsServer);
loadDetails(dnsServerDb);
return dnsServerDb;
});
}
@Override
public boolean update(Long id, DnsServerVO dnsServer) {
return Transaction.execute((TransactionCallback<Boolean>) status -> {
boolean result = super.update(id, dnsServer);
if (result) {
saveDetails(dnsServer);
}
return result;
});
}
@Override
public boolean remove(Long dnsServerId) {
return Transaction.execute((TransactionCallback<Boolean>) status -> {
boolean result = super.remove(dnsServerId);
if (result) {
dnsServerDetailsDao.removeDetails(dnsServerId);
}
return result;
});
}
@Override
public void loadDetails(DnsServer dnsServer) {
if (dnsServer == null) {
return;
}
Map<String, String> details = dnsServerDetailsDao.listDetailsKeyPairs(dnsServer.getId());
dnsServer.setDetails(details);
}
@Override
public void saveDetails(DnsServer dnsServer) {
Map<String, String> detailsStr = dnsServer.getDetails();
if (detailsStr == null) {
return;
}
List<DnsServerDetailVO> details = new ArrayList<>();
for (String key : detailsStr.keySet()) {
DnsServerDetailVO detail = new DnsServerDetailVO(dnsServer.getId(), key, detailsStr.get(key), true);
details.add(detail);
}
dnsServerDetailsDao.saveDetails(details);
}
}

View File

@ -0,0 +1,26 @@
// 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 org.apache.cloudstack.dns.vo.DnsServerDetailVO;
import org.apache.cloudstack.resourcedetail.ResourceDetailsDao;
import com.cloud.utils.db.GenericDao;
public interface DnsServerDetailsDao extends GenericDao<DnsServerDetailVO, Long>, ResourceDetailsDao<DnsServerDetailVO> {
}

View File

@ -0,0 +1,29 @@
// 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 org.apache.cloudstack.dns.vo.DnsServerDetailVO;
import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase;
public class DnsServerDetailsDaoImpl extends ResourceDetailsDaoBase<DnsServerDetailVO> implements DnsServerDetailsDao {
@Override
public void addDetail(long resourceId, String key, String value, boolean display) {
super.addDetail(new DnsServerDetailVO(resourceId, key, value, display));
}
}

View File

@ -0,0 +1,26 @@
// 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 org.apache.cloudstack.dns.vo.DnsServerJoinVO;
import com.cloud.utils.db.GenericDao;
public interface DnsServerJoinDao extends GenericDao<DnsServerJoinVO, Long> {
}

View File

@ -0,0 +1,25 @@
// 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 org.apache.cloudstack.dns.vo.DnsServerJoinVO;
import com.cloud.utils.db.GenericDaoBase;
public class DnsServerJoinDaoImpl extends GenericDaoBase<DnsServerJoinVO, Long> implements DnsServerJoinDao {
}

View File

@ -0,0 +1,37 @@
// 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.DnsZone;
import org.apache.cloudstack.dns.vo.DnsZoneVO;
import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDao;
public interface DnsZoneDao extends GenericDao<DnsZoneVO, Long> {
List<DnsZoneVO> listByAccount(long accountId);
DnsZoneVO findByNameServerAndType(String name, long dnsServerId, DnsZone.ZoneType type);
Pair<List<DnsZoneVO>, Integer> searchZones(Long id, Long accountId, List<Long> ownDnsServerIds, Long targetDnsServerId,
String keyword, Filter filter);
List<DnsZoneVO> findDnsZonesByServerId(long dnsServerId);
}

View File

@ -0,0 +1,122 @@
// 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.DnsZone;
import org.apache.cloudstack.dns.vo.DnsZoneVO;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
@Component
public class DnsZoneDaoImpl extends GenericDaoBase<DnsZoneVO, Long> implements DnsZoneDao {
SearchBuilder<DnsZoneVO> DnsServerSearch;
SearchBuilder<DnsZoneVO> AccountSearch;
SearchBuilder<DnsZoneVO> NameServerTypeSearch;
public DnsZoneDaoImpl() {
super();
DnsServerSearch = createSearchBuilder();
DnsServerSearch.selectFields(DnsServerSearch.entity().getId());
DnsServerSearch.and(ApiConstants.DNS_SERVER_ID, DnsServerSearch.entity().getDnsServerId(), SearchCriteria.Op.EQ);
DnsServerSearch.done();
AccountSearch = createSearchBuilder();
AccountSearch.and(ApiConstants.ACCOUNT_ID, AccountSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
AccountSearch.and(ApiConstants.STATE, AccountSearch.entity().getState(), SearchCriteria.Op.EQ);
AccountSearch.done();
NameServerTypeSearch = createSearchBuilder();
NameServerTypeSearch.and(ApiConstants.NAME, NameServerTypeSearch.entity().getName(), SearchCriteria.Op.EQ);
NameServerTypeSearch.and(ApiConstants.DNS_SERVER_ID, NameServerTypeSearch.entity().getDnsServerId(), SearchCriteria.Op.EQ);
NameServerTypeSearch.and(ApiConstants.TYPE, NameServerTypeSearch.entity().getType(), SearchCriteria.Op.EQ);
NameServerTypeSearch.and(ApiConstants.STATE, NameServerTypeSearch.entity().getState(), SearchCriteria.Op.EQ);
NameServerTypeSearch.done();
}
@Override
public List<DnsZoneVO> listByAccount(long accountId) {
SearchCriteria<DnsZoneVO> sc = AccountSearch.create();
sc.setParameters(ApiConstants.ACCOUNT_ID, accountId);
sc.setParameters(ApiConstants.STATE, DnsZone.State.Active);
return listBy(sc);
}
@Override
public DnsZoneVO findByNameServerAndType(String name, long dnsServerId, DnsZone.ZoneType type) {
SearchCriteria<DnsZoneVO> sc = NameServerTypeSearch.create();
sc.setParameters(ApiConstants.NAME, name);
sc.setParameters(ApiConstants.DNS_SERVER_ID, dnsServerId);
sc.setParameters(ApiConstants.TYPE, type);
sc.setParameters(ApiConstants.STATE, DnsZone.State.Active);
return findOneBy(sc);
}
@Override
public Pair<List<DnsZoneVO>, Integer> searchZones(Long id, Long accountId, List<Long> ownDnsServerIds, Long targetDnsServerId,
String keyword, Filter filter) {
SearchBuilder<DnsZoneVO> sb = createSearchBuilder();
sb.and(ApiConstants.STATE, sb.entity().getState(), SearchCriteria.Op.EQ);
sb.and(ApiConstants.ID, sb.entity().getId(), SearchCriteria.Op.EQ);
sb.and(ApiConstants.NAME, sb.entity().getName(), SearchCriteria.Op.LIKE);
sb.and(ApiConstants.TARGET_ID, sb.entity().getDnsServerId(), SearchCriteria.Op.EQ);
if (!CollectionUtils.isEmpty(ownDnsServerIds)) {
sb.and().op(ApiConstants.DNS_SERVER_ID, sb.entity().getDnsServerId(), SearchCriteria.Op.IN);
sb.or(ApiConstants.ACCOUNT_ID, sb.entity().getAccountId(), SearchCriteria.Op.EQ);
sb.cp();
} else {
sb.and(ApiConstants.ACCOUNT_ID, sb.entity().getAccountId(), SearchCriteria.Op.EQ);
}
sb.done();
SearchCriteria<DnsZoneVO> sc = sb.create();
if (id != null) {
sc.setParameters(ApiConstants.ID, id);
}
if (!CollectionUtils.isEmpty(ownDnsServerIds)) {
sc.setParameters(ApiConstants.DNS_SERVER_ID, ownDnsServerIds.toArray());
}
if (keyword != null) {
sc.setParameters(ApiConstants.NAME, "%" + keyword + "%");
}
if (accountId != null) {
sc.setParameters(ApiConstants.ACCOUNT_ID, accountId);
}
if (targetDnsServerId != null) {
sc.setParameters(ApiConstants.TARGET_ID, targetDnsServerId);
}
sc.setParameters(ApiConstants.STATE, DnsZone.State.Active);
return searchAndCount(sc, filter);
}
public List<DnsZoneVO> findDnsZonesByServerId(long dnsServerId) {
SearchCriteria<DnsZoneVO> sc = DnsServerSearch.create();
sc.setParameters(ApiConstants.DNS_SERVER_ID, dnsServerId);
return listBy(sc);
}
}

View File

@ -0,0 +1,26 @@
// 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 org.apache.cloudstack.dns.vo.DnsZoneJoinVO;
import com.cloud.utils.db.GenericDao;
public interface DnsZoneJoinDao extends GenericDao<DnsZoneJoinVO, Long> {
}

View File

@ -0,0 +1,25 @@
// 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 org.apache.cloudstack.dns.vo.DnsZoneJoinVO;
import com.cloud.utils.db.GenericDaoBase;
public class DnsZoneJoinDaoImpl extends GenericDaoBase<DnsZoneJoinVO, Long> implements DnsZoneJoinDao {
}

View File

@ -0,0 +1,29 @@
// 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 org.apache.cloudstack.dns.vo.DnsZoneNetworkMapVO;
import com.cloud.utils.db.GenericDao;
public interface DnsZoneNetworkMapDao extends GenericDao<DnsZoneNetworkMapVO, Long> {
void removeNetworkMappingByZoneId(long dnsZoneId);
DnsZoneNetworkMapVO findByNetworkId(long networkId);
DnsZoneNetworkMapVO findByZoneId(long networkId);
}

View File

@ -0,0 +1,64 @@
// 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 org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.dns.vo.DnsZoneNetworkMapVO;
import org.springframework.stereotype.Component;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
@Component
public class DnsZoneNetworkMapDaoImpl extends GenericDaoBase<DnsZoneNetworkMapVO, Long> implements DnsZoneNetworkMapDao {
private final SearchBuilder<DnsZoneNetworkMapVO> ZoneNetworkSearch;
private final SearchBuilder<DnsZoneNetworkMapVO> NetworkSearch;
public DnsZoneNetworkMapDaoImpl() {
super();
ZoneNetworkSearch = createSearchBuilder();
ZoneNetworkSearch.and(ApiConstants.DNS_ZONE_ID, ZoneNetworkSearch.entity().getDnsZoneId(), SearchCriteria.Op.EQ);
ZoneNetworkSearch.done();
NetworkSearch = createSearchBuilder();
NetworkSearch.and(ApiConstants.NETWORK_ID, NetworkSearch.entity().getNetworkId(), SearchCriteria.Op.EQ);
NetworkSearch.done();
}
@Override
public void removeNetworkMappingByZoneId(long dnsZoneId) {
SearchCriteria<DnsZoneNetworkMapVO> sc = ZoneNetworkSearch.create();
sc.setParameters(ApiConstants.DNS_ZONE_ID, dnsZoneId);
remove(sc);
}
@Override
public DnsZoneNetworkMapVO findByNetworkId(long networkId) {
SearchCriteria<DnsZoneNetworkMapVO> sc = NetworkSearch.create();
sc.setParameters(ApiConstants.NETWORK_ID, networkId);
return findOneBy(sc);
}
@Override
public DnsZoneNetworkMapVO findByZoneId(long dnsZoneId) {
SearchCriteria<DnsZoneNetworkMapVO> sc = ZoneNetworkSearch.create();
sc.setParameters(ApiConstants.DNS_ZONE_ID, dnsZoneId);
return findOneBy(sc);
}
}

View File

@ -0,0 +1,67 @@
// 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.NicDnsJoinVO;
import com.cloud.utils.db.GenericDao;
public interface NicDnsJoinDao extends GenericDao<NicDnsJoinVO, Long> {
/**
* Used for Collision Checks.
*
* @param dnsZoneId
* @param nicDnsName
* @return active records to see who currently owns the dnsRecordUrl.
*/
NicDnsJoinVO findActiveByDnsRecordAndZone(long dnsZoneId, String nicDnsName);
/**
* Used to sync DNS record url based on available ips for vmId in the dnsZone
*
* @param vmId
* @param dnsZoneId
* @param nicDnsName
* @return list of active nics using the dnsRecordUrl, supports null vmId for dnsZone wide query
*/
List<NicDnsJoinVO> listActiveByVmIdZoneAndDnsRecord(Long vmId, long dnsZoneId, String nicDnsName);
/**
* Used for VM Start/Running
* @param vmId
* @return records associated to vmId
*/
List<NicDnsJoinVO> listActiveByVmId(long vmId);
/**
* Used by Instance Destroy/Stop or NIC delete
* @param vmId
* @return records with soft-delete
*/
List<NicDnsJoinVO> listIncludingRemovedByVmId(long vmId);
/**
* Find all records for dnsZoneId with valid nicDnsName
* @param dnsZoneId
* @return
*/
List<NicDnsJoinVO> listByZoneId(long dnsZoneId);
}

View File

@ -0,0 +1,107 @@
// 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.NicDnsJoinVO;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
public class NicDnsJoinDaoImpl extends GenericDaoBase<NicDnsJoinVO, Long> implements NicDnsJoinDao {
private final SearchBuilder<NicDnsJoinVO> activeDnsRecordZoneSearch;
private final SearchBuilder<NicDnsJoinVO> activeVmZoneDnsRecordSearch; // Route for null vmId
private final SearchBuilder<NicDnsJoinVO> activeVmSearch;
private final SearchBuilder<NicDnsJoinVO> activeDnsRecordsByZoneIdSearch;
public NicDnsJoinDaoImpl() {
activeDnsRecordZoneSearch = createSearchBuilder();
activeDnsRecordZoneSearch.and(ApiConstants.NIC_DNS_NAME, activeDnsRecordZoneSearch.entity().getNicDnsName(), 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_NAME, activeVmZoneDnsRecordSearch.entity().getNicDnsName(), 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();
activeDnsRecordsByZoneIdSearch = createSearchBuilder();
activeDnsRecordsByZoneIdSearch.selectFields(activeDnsRecordsByZoneIdSearch.entity().getId());
activeDnsRecordsByZoneIdSearch.and(ApiConstants.DNS_ZONE_ID, activeDnsRecordsByZoneIdSearch.entity().getDnsZoneId(), SearchCriteria.Op.EQ);
activeDnsRecordsByZoneIdSearch.and(ApiConstants.NIC_DNS_NAME, activeDnsRecordsByZoneIdSearch.entity().getNicDnsName(), SearchCriteria.Op.NNULL);
activeDnsRecordsByZoneIdSearch.and(ApiConstants.REMOVED, activeDnsRecordsByZoneIdSearch.entity().getRemoved(), SearchCriteria.Op.NULL);
activeDnsRecordsByZoneIdSearch.done();
}
@Override
public NicDnsJoinVO findActiveByDnsRecordAndZone(long dnsZoneId, String nicDnsName) {
SearchCriteria<NicDnsJoinVO> sc = activeDnsRecordZoneSearch.create();
sc.setParameters(ApiConstants.NIC_DNS_NAME, nicDnsName);
sc.setParameters(ApiConstants.DNS_ZONE_ID, dnsZoneId);
return findOneBy(sc);
}
@Override
public List<NicDnsJoinVO> listActiveByVmIdZoneAndDnsRecord(Long vmId, long dnsZoneId, String nicDnsName) {
if (vmId != null) {
SearchCriteria<NicDnsJoinVO> sc = activeVmZoneDnsRecordSearch.create();
sc.setParameters(ApiConstants.INSTANCE_ID, vmId);
sc.setParameters(ApiConstants.DNS_ZONE_ID, dnsZoneId);
sc.setParameters(ApiConstants.NIC_DNS_NAME, nicDnsName);
return listBy(sc);
} else {
SearchCriteria<NicDnsJoinVO> sc = activeDnsRecordZoneSearch.create();
sc.setParameters(ApiConstants.DNS_ZONE_ID, dnsZoneId);
sc.setParameters(ApiConstants.NIC_DNS_NAME, nicDnsName);
return listBy(sc);
}
}
@Override
public List<NicDnsJoinVO> listActiveByVmId(long vmId) {
SearchCriteria<NicDnsJoinVO> sc = activeVmSearch.create();
sc.setParameters(ApiConstants.INSTANCE_ID, vmId);
return listBy(sc);
}
@Override
public List<NicDnsJoinVO> listIncludingRemovedByVmId(long vmId) {
SearchCriteria<NicDnsJoinVO> sc = activeVmSearch.create();
sc.setParameters(ApiConstants.INSTANCE_ID, vmId);
return listIncludingRemovedBy(sc);
}
@Override
public List<NicDnsJoinVO> listByZoneId(long dnsZoneId) {
SearchCriteria<NicDnsJoinVO> sc = activeDnsRecordsByZoneIdSearch.create();
sc.setParameters(ApiConstants.DNS_ZONE_ID, dnsZoneId);
return listBy(sc);
}
}

Some files were not shown because too many files have changed in this diff Show More