diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/CreateDnsZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/CreateDnsZoneCmd.java index b69752e67b2..e3db71dde61 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/CreateDnsZoneCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/CreateDnsZoneCmd.java @@ -63,7 +63,7 @@ public class CreateDnsZoneCmd extends BaseAsyncCreateCmd { description = "The type of zone (Public, Private). Defaults to Public.") private String type; - @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "Display text for the zone") + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "The description of the DNS zone") private String description; ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsServerCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsServerCmd.java index 2fb8a9903e4..f5674c42427 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsServerCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsServerCmd.java @@ -27,6 +27,7 @@ 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; @@ -51,6 +52,10 @@ public class DeleteDnsServerCmd extends BaseAsyncCmd { required = true, description = "the ID of the DNS server") private Long id; + @Parameter(name = ApiConstants.CLEANUP, type = CommandType.BOOLEAN, + entityType = DnsZoneResponse.class, description = "True if all associated DNS zones have to be cleaned up with this server") + private Boolean cleanup; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -93,4 +98,8 @@ public class DeleteDnsServerCmd extends BaseAsyncCmd { @Override public String getEventDescription() { return "Deleting DNS server ID: " + getId(); } + + public Boolean getCleanup() { + return Boolean.TRUE.equals(cleanup); + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/UpdateDnsZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/UpdateDnsZoneCmd.java index d4ebe71396c..5e3af83a5da 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/UpdateDnsZoneCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/UpdateDnsZoneCmd.java @@ -45,7 +45,7 @@ public class UpdateDnsZoneCmd extends BaseCmd { required = true, description = "The ID of the DNS zone") private Long id; - @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "Display text for the zone") + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "The description of the DNS zone to be updated") private String description; ///////////////////////////////////////////////////// diff --git a/plugins/dns/powerdns/src/main/java/org/apache/cloudstack/dns/powerdns/PowerDnsClient.java b/plugins/dns/powerdns/src/main/java/org/apache/cloudstack/dns/powerdns/PowerDnsClient.java index 4abe2b0b02d..8e756d224fc 100644 --- a/plugins/dns/powerdns/src/main/java/org/apache/cloudstack/dns/powerdns/PowerDnsClient.java +++ b/plugins/dns/powerdns/src/main/java/org/apache/cloudstack/dns/powerdns/PowerDnsClient.java @@ -40,6 +40,7 @@ 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; @@ -185,8 +186,8 @@ public class PowerDnsClient implements AutoCloseable { nsArray.add(ns.endsWith(".") ? ns : ns + "."); } } - HttpPatch request = new HttpPatch(url); - request.setEntity(new org.apache.http.entity.StringEntity(json.toString(), StandardCharsets.UTF_8)); + HttpPut request = new HttpPut(url); + request.setEntity(new StringEntity(json.toString(), StandardCharsets.UTF_8)); execute(request, apiKey, 204); } @@ -221,7 +222,7 @@ public class PowerDnsClient implements AutoCloseable { } String encodedZone = URLEncoder.encode(normalizedZone, StandardCharsets.UTF_8); HttpPatch request = new HttpPatch(buildUrl(baseUrl, port, "/servers/" + externalServerId + "/zones/" + encodedZone)); - request.setEntity(new org.apache.http.entity.StringEntity(root.toString(), StandardCharsets.UTF_8)); + request.setEntity(new StringEntity(root.toString(), StandardCharsets.UTF_8)); execute(request, apiKey, 204); return normalizedRecord.endsWith(".") ? normalizedRecord.substring(0, normalizedRecord.length() - 1) : normalizedRecord; } diff --git a/server/src/main/java/org/apache/cloudstack/dns/DnsProviderManagerImpl.java b/server/src/main/java/org/apache/cloudstack/dns/DnsProviderManagerImpl.java index 811a8499b18..626f71ba2e7 100644 --- a/server/src/main/java/org/apache/cloudstack/dns/DnsProviderManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/dns/DnsProviderManagerImpl.java @@ -51,7 +51,9 @@ import org.apache.cloudstack.dns.dao.DnsServerJoinDao; import org.apache.cloudstack.dns.dao.DnsZoneDao; import org.apache.cloudstack.dns.dao.DnsZoneJoinDao; import org.apache.cloudstack.dns.dao.DnsZoneNetworkMapDao; +import org.apache.cloudstack.dns.exception.DnsConflictException; import org.apache.cloudstack.dns.exception.DnsNotFoundException; +import org.apache.cloudstack.dns.exception.DnsTransportException; import org.apache.cloudstack.dns.vo.DnsServerJoinVO; import org.apache.cloudstack.dns.vo.DnsServerVO; import org.apache.cloudstack.dns.vo.DnsZoneJoinVO; @@ -176,18 +178,10 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa } private Pair, Integer> searchForDnsServerInternal(ListDnsServersCmd cmd) { - Long dnsServerId = cmd.getId(); Account caller = CallContext.current().getCallingAccount(); - if (dnsServerId != null) { - DnsServerVO dnsServerVO = dnsServerDao.findById(dnsServerId); - if (dnsServerVO == null) { - return null; - } - return new Pair<>(Collections.singletonList(dnsServerVO), 1); - } Set parentDomainIds = domainDao.getDomainParentIds(caller.getDomainId()); Filter searchFilter = new Filter(DnsServerVO.class, ApiConstants.ID, true, cmd.getStartIndex(), cmd.getPageSizeVal()); - return dnsServerDao.searchDnsServer(dnsServerId, caller.getAccountId(), parentDomainIds, cmd.getProviderType(), cmd.getKeyword(), searchFilter); + return dnsServerDao.searchDnsServer(cmd.getId(), caller.getAccountId(), parentDomainIds, cmd.getProviderType(), cmd.getKeyword(), searchFilter); } @Override @@ -272,6 +266,9 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa } Account caller = CallContext.current().getCallingAccount(); accountMgr.checkAccess(caller, dnsServer); + if (cmd.getCleanup()) { + // ToDo cleanup associated dnsZones + } return dnsServerDao.remove(dnsServerId); } @@ -322,6 +319,7 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa try { DnsProvider provider = getProviderByType(server.getProviderType()); provider.updateZone(server, dnsZone); + dnsZoneDao.update(dnsZone.getId(), dnsZone); } catch (Exception ex) { logger.error("Failed to update DNS zone: {} on DNS server: {}", dnsZone.getName(), server.getName(), ex); throw new CloudRuntimeException("Failed to update DNS zone: " + dnsZone.getName()); @@ -375,20 +373,23 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa if (StringUtils.isBlank(recordName)) { throw new InvalidParameterValueException("Empty DNS record name is not allowed"); } - DnsZoneVO zone = dnsZoneDao.findById(cmd.getDnsZoneId()); - if (zone == null) { + DnsZoneVO dnsZone = dnsZoneDao.findById(cmd.getDnsZoneId()); + if (dnsZone == null) { throw new InvalidParameterValueException("DNS zone not found."); } Account caller = CallContext.current().getCallingAccount(); - accountMgr.checkAccess(caller, null, true, zone); - DnsServerVO server = dnsServerDao.findById(zone.getDnsServerId()); + accountMgr.checkAccess(caller, null, true, dnsZone); + DnsServerVO server = dnsServerDao.findById(dnsZone.getDnsServerId()); + if (server == null) { + throw new CloudRuntimeException("The underlying DNS server for this DNS zone is missing."); + } try { DnsRecord.RecordType type = cmd.getType(); List normalizedContents = cmd.getContents().stream() .map(value -> DnsProviderUtil.normalizeDnsRecordValue(value, type)).collect(Collectors.toList()); DnsRecord record = new DnsRecord(recordName, type, normalizedContents, cmd.getTtl()); DnsProvider provider = getProviderByType(server.getProviderType()); - String normalizedRecordName = provider.addRecord(server, zone, record); + String normalizedRecordName = provider.addRecord(server, dnsZone, record); record.setName(normalizedRecordName); return createDnsRecordResponse(record); } catch (Exception ex) { @@ -507,7 +508,13 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa } catch (Exception ex) { dnsZoneDao.remove(dnsZoneId); logger.error("Failed to provision DNS zone: {} on DNS server: {}", dnsZone.getName(), server.getName(), ex); - throw new CloudRuntimeException("Failed to provision DNS zone: " + dnsZone.getName()); + String errorMsg = ""; + if ( ex instanceof DnsConflictException) { + errorMsg = String.format("DNS zone: %s already exists", dnsZone.getName()); + } else if (ex instanceof DnsTransportException){ + errorMsg = String.format("DNS server: %s not reachable", server.getName()); + } + throw new CloudRuntimeException(errorMsg); } return dnsZone; } @@ -562,6 +569,7 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa res.setName(record.getName()); res.setType(record.getType()); res.setContent(record.getContents()); + res.setTtl(record.getTtl()); return res; } diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index dd8cd3b6ade..019647443d3 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -716,6 +716,7 @@ "label.create.webhook": "Create Webhook", "label.created": "Created", "label.creating": "Creating", +"label.creating.dns.zone": "Creating DNS zone", "label.creating.iprange": "Creating IP ranges", "label.nameservers": "DNS Nameservers", "label.credit": "Credit", @@ -941,12 +942,14 @@ "label.dns": "DNS", "label.dns1": "DNS 1", "label.dns2": "DNS 2", -"label.dns.add.record": "Add DNS Record", +"label.dns.create.record": "Create DNS Record", "label.dns.add.server": "Add DNS Server", -"label.dns.add.zone": "Add DNS Zone", +"label.dns.create.zone": "Create DNS Zone", "label.dns.credentials": "DNS API key", "label.dns.delete.server": "Delete DNS Server", "label.dns.delete.zone": "Delete DNS Zone", +"label.dns.externalserverid": "DNS server ID", +"label.dns.publicdomainsuffix": "Public domain suffix", "label.dns.records": "DNS Records", "label.dns.record.name.tooltip": "The hostname or subdomain for this record (e.g. www)", "label.dns.record.type.tooltip": "The DNS record type", @@ -954,6 +957,7 @@ "label.dns.record.ttl.tooltip": "Time to live in seconds", "label.dns.record.url": "Instance URL", "label.dns.server": "DNS Server", +"label.dnsserverid": "DNS Server ID", "label.dnsservername": "DNS Server name", "label.dns.servers": "DNS Servers", "label.dns.zone": "DNS Zone", @@ -2252,6 +2256,7 @@ "label.scope": "Scope", "label.scope.tooltip": "Primary Storage Pool Scope", "label.search": "Search", +"label.seconds": "Seconds", "label.secondary.isolated.vlan.type.isolated": "Isolated", "label.secondary.isolated.vlan.type.promiscuous": "Promiscuous", "label.secondary.storage": "Secondary Storage", @@ -3540,6 +3545,7 @@ "message.error.remove.tungsten.routing.policy": "Removing Tungsten-Fabric Routing Policy from Network failed", "message.error.remove.vm.schedule": "Removing Instance Schedule failed", "message.error.required.input": "Please enter input", +"message.error.required.publicdomainsuffix": "Please enter the public domain suffix if the public server is enabled", "message.error.reset.config": "Unable to reset config to default value", "message.error.retrieve.kubeconfig": "Unable to retrieve Kubernetes Cluster config", "message.error.routing.policy.term": "Community need to have the following format number:number", @@ -3884,10 +3890,10 @@ "message.success.add.egress.rule": "Successfully added new egress rule", "message.success.add.firewall.rule": "Successfully added new firewall rule", "message.success.add.dns.server": "Successfully added DNS server", -"message.success.add.dns.zone": "Successfully added DNS zone", +"message.success.create.dns.zone": "Successfully created DNS zone", +"message.success.create.dns.record": "Successfully created DNS record", "message.success.update.dns.server": "Successfully updated DNS server", "message.success.update.dns.zone": "Successfully updated DNS zone", -"message.success.add.dns.record": "Successfully added DNS record", "message.success.delete.dns.record": "Successfully deleted DNS record", "message.success.add.guest.network": "Successfully created guest Network", "message.success.add.gpu.device": "Successfully added GPU device", diff --git a/ui/src/config/section/network.js b/ui/src/config/section/network.js index 44af0df13b8..cbcf6a9aaa0 100644 --- a/ui/src/config/section/network.js +++ b/ui/src/config/section/network.js @@ -1522,8 +1522,7 @@ export default { label: 'label.dns.update.server', dataView: true, popup: true, - component: shallowRef(defineAsyncComponent(() => import('@/views/network/dns/UpdateDnsServer.vue'))), - show: (record) => { return true } + component: shallowRef(defineAsyncComponent(() => import('@/views/network/dns/UpdateDnsServer.vue'))) }, { api: 'deleteDnsServer', @@ -1531,8 +1530,8 @@ export default { label: 'label.dns.delete.server', message: 'message.action.delete.dns.server', dataView: true, - groupAction: true, - show: (record) => { return true }, + popup: true, + groupAction: false, groupMap: (selection) => { return selection.map(x => { return { id: x } }) } } ] @@ -1542,8 +1541,8 @@ export default { title: 'label.dns.zones', icon: 'global-outlined', permission: ['listDnsZones'], - columns: ['name', 'state', 'dnsservername', 'account'], - details: ['name', 'id', 'state', 'dnsservername', 'dnsserverid', 'account', 'domainpath'], + columns: ['name', 'state', 'dnsservername', 'account', 'description'], + details: ['name', 'id', 'state', 'dnsservername', 'dnsserverid', 'account', 'domainpath', 'description'], tabs: [{ name: 'details', component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue'))) @@ -1557,11 +1556,10 @@ export default { { api: 'createDnsZone', icon: 'plus-outlined', - label: 'label.dns.add.zone', + label: 'label.dns.create.zone', listView: true, popup: true, - disabled: (record) => false, - component: shallowRef(defineAsyncComponent(() => import('@/views/network/dns/AddDnsZone.vue'))), + component: shallowRef(defineAsyncComponent(() => import('@/views/network/dns/CreateDnsZone.vue'))), show: () => { return true } @@ -1572,7 +1570,6 @@ export default { label: 'label.dns.update.zone', dataView: true, popup: true, - disabled: (record) => false, component: shallowRef(defineAsyncComponent(() => import('@/views/network/dns/UpdateDnsZone.vue'))), show: (record) => { return true } }, @@ -1582,7 +1579,8 @@ export default { label: 'label.dns.delete.zone', message: 'message.action.delete.dns.zone', dataView: true, - groupAction: true, + popup: true, + groupAction: false, disabled: (record) => false, show: (record) => { return true }, groupMap: (selection) => { return selection.map(x => { return { id: x } }) } diff --git a/ui/src/views/network/dns/AddDnsServer.vue b/ui/src/views/network/dns/AddDnsServer.vue index 8b5024bd7bd..47dd18eab5d 100644 --- a/ui/src/views/network/dns/AddDnsServer.vue +++ b/ui/src/views/network/dns/AddDnsServer.vue @@ -91,6 +91,28 @@ :placeholder="apiParams.credentials?.description || 'Enter API Key'" /> + + + + + + + + + +