diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index ca49ee188be..6df5e9dd477 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -1352,6 +1352,8 @@ public class ApiConstants { public static final String DNS_USER_NAME = "dnsusername"; public static final String CREDENTIALS = "credentials"; public static final String DNS_ZONE_ID = "dnszoneid"; + public static final String DNS_ZONE = "dnszone"; + 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"; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java index 7c4b733a80f..c8f25bb562f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java @@ -331,6 +331,14 @@ public class NetworkResponse extends BaseResponseWithAssociatedNetwork implement @Param(description = "The BGP peers for the network", since = "4.20.0") private Set bgpPeers; + @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() { @@ -702,4 +710,12 @@ public class NetworkResponse extends BaseResponseWithAssociatedNetwork implement public void setIpv6Dns2(String ipv6Dns2) { this.ipv6Dns2 = ipv6Dns2; } + + public void setDnsZone(String dnsZone) { + this.dnsZone = dnsZone; + } + + public void setDnsSubdomain(String dnsSubdomain) { + this.dnsSubdomain = dnsSubdomain; + } } diff --git a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java index c6f2ac27db8..7953a6a27cd 100644 --- a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java +++ b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java @@ -41,7 +41,6 @@ import org.apache.cloudstack.api.response.UserTwoFactorAuthenticationSetupRespon import org.apache.cloudstack.auth.UserTwoFactorAuthenticator; import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.framework.config.ConfigKey; -import org.apache.cloudstack.dns.DnsServer; import org.apache.cloudstack.acl.apikeypair.ApiKeyPair; import org.apache.cloudstack.acl.ControlledEntity; @@ -511,11 +510,6 @@ public class MockAccountManager extends ManagerBase implements AccountManager { // TODO Auto-generated method stub } - @Override - public void checkAccess(Account account, DnsServer dnsServer) throws PermissionDeniedException { - // NOOP - } - @Override public Pair> getKeys(GetUserKeysCmd cmd){ return null; diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java b/server/src/main/java/com/cloud/api/ApiDBUtils.java index 1d6d5d5c500..01c2bd3c1a2 100644 --- a/server/src/main/java/com/cloud/api/ApiDBUtils.java +++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java @@ -82,6 +82,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; @@ -365,6 +369,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); @@ -506,6 +511,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 @@ -778,6 +785,10 @@ public class ApiDBUtils { private VirtualMachineManager virtualMachineManager; @Inject private SharedFSJoinDao sharedFSJoinDao; + @Inject + private DnsZoneDao dnsZoneDao; + @Inject + private DnsZoneNetworkMapDao dnsZoneNetworkMapDao; @PostConstruct void init() { @@ -916,6 +927,8 @@ public class ApiDBUtils { s_bucketDao = bucketDao; s_virtualMachineManager = virtualMachineManager; s_sharedFSJoinDao = sharedFSJoinDao; + s_dnsZoneDao = dnsZoneDao; + s_dnsZoneNetworkMapDao = dnsZoneNetworkMapDao; } // /////////////////////////////////////////////////////////// @@ -2294,6 +2307,17 @@ public class ApiDBUtils { return details.isEmpty() ? null : details; } + public static Pair 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()); } diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index cf98df0da24..9b92e699fa4 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -543,6 +543,7 @@ public class ApiResponseHelper implements ResponseGenerator { @Inject ResourceIconManager resourceIconManager; + public static String getPrettyDomainPath(String path) { if (path == null) { return null; @@ -2635,6 +2636,14 @@ public class ApiResponseHelper implements ResponseGenerator { response.setDetails(details); } + Pair 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()); 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 8022d2fe1da..9b3e31d68d9 100644 --- a/server/src/main/java/org/apache/cloudstack/dns/DnsProviderManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/dns/DnsProviderManagerImpl.java @@ -661,10 +661,10 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa return null; } DnsServerVO server = dnsServerDao.findById(dnsZone.getDnsServerId()); - // Construct FQDN Prefix (e.g., "instance-id" or "instance-id.subdomain") + // Construct FQDN Prefix (e.g., "instance-id.dnsZoneName" or "instance-id.subdomain.dnsZoneName") String recordName = String.valueOf(instance.getInstanceName()); if (StringUtils.isNotBlank(dnsZoneNetworkMap.getSubDomain())) { - recordName = recordName + "." + dnsZoneNetworkMap.getSubDomain(); + recordName = String.join(".", recordName, dnsZoneNetworkMap.getSubDomain(), dnsZone.getName()); } try { diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index c22b585fb04..b14c2c35b6e 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -967,12 +967,15 @@ "label.dns.server": "DNS Server", "label.dnsserverid": "DNS Server ID", "label.dnsservername": "DNS Server name", +"label.dnssubdomain": "DNS Subdomain", "label.dns.servers": "DNS Servers", "label.dns.zone": "DNS Zone", "label.dns.zones": "DNS Zones", "label.dns.update.server": "Update DNS Server", "label.dns.update.zone": "Update DNS Zone", +"label.dns.zone.select": "Select a DNS zone", "label.dnsrecords": "DNS Records", +"label.dnszone": "DNS Zone", "label.domain": "Domain", "label.domain.id": "Domain ID", "label.domain.name": "Domain name", @@ -2498,6 +2501,7 @@ "label.storagetype": "Storage type", "label.storageip": "Storage IP address", "label.strict": "Strict", +"label.subdomain": "Subdomain", "label.subdomainaccess": "Subdomain access", "label.submit": "Submit", "label.subnet": "Subnet", @@ -2990,6 +2994,9 @@ "message.confirm.delete.dns.record": "Are you sure you want to delete this DNS record?", "message.action.delete.dns.server": "Please confirm you want to delete this DNS server.", "message.action.delete.dns.zone": "Please confirm you want to delete this DNS zone.", +"label.action.associate.dns.zone": "Associate DNS Zone", +"label.action.disassociate.dns.zone": "Disassociate DNS Zone", +"message.action.disassociate.dns.zone": "Please confirm you want to disassociate the DNS zone from this network.", "message.action.delete.domain": "Please confirm that you want to delete this domain.", "message.action.delete.extension": "Please confirm that you want to delete the extension", "message.action.delete.external.firewall": "Please confirm that you would like to remove this external firewall. Warning: If you are planning to add back the same external firewall, you must reset usage data on the device.", @@ -3911,6 +3918,8 @@ "message.success.update.dns.server": "Successfully updated DNS server", "message.success.update.dns.zone": "Successfully updated DNS zone", "message.success.delete.dns.record": "Successfully deleted DNS record", +"message.success.associate.dns.zone": "Successfully associated DNS zone with network", +"message.error.fetch.dns.zones": "Could not load DNS zones.", "message.success.add.guest.network": "Successfully created guest Network", "message.success.add.gpu.device": "Successfully added GPU device", "message.success.add.interface.static.route": "Successfully added interface Static Route", diff --git a/ui/src/config/section/network.js b/ui/src/config/section/network.js index eb82694e121..b919acd4ff8 100644 --- a/ui/src/config/section/network.js +++ b/ui/src/config/section/network.js @@ -18,7 +18,7 @@ import { shallowRef, defineAsyncComponent } from 'vue' import store from '@/store' import tungsten from '@/assets/icons/tungsten.svg?inline' -import { isAdmin } from '@/role' +import { isAdmin, isAdminOrDomainAdmin } from '@/role' import { isZoneCreated } from '@/utils/zone' import { vueProps } from '@/vue-app' @@ -49,7 +49,10 @@ export default { return fields }, details: () => { - var fields = ['name', 'id', 'description', 'type', 'traffictype', 'vpcid', 'vlan', 'broadcasturi', 'cidr', 'ip6cidr', 'netmask', 'gateway', 'asnumber', 'aclname', 'ispersistent', 'restartrequired', 'reservediprange', 'redundantrouter', 'networkdomain', 'egressdefaultpolicy', 'zonename', 'account', 'domainpath', 'associatednetwork', 'associatednetworkid', 'ip4routing', 'ip6firewall', 'ip6routing', 'ip6routes', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'publicmtu', 'privatemtu'] + var fields = ['name', 'id', 'description', 'type', 'traffictype', 'vpcid', 'vlan', 'broadcasturi', 'cidr', 'ip6cidr', 'netmask', 'gateway', 'asnumber', + 'aclname', 'ispersistent', 'restartrequired', 'reservediprange', 'redundantrouter', 'networkdomain', 'egressdefaultpolicy', 'zonename', 'account', + 'domainpath', 'associatednetwork', 'associatednetworkid', 'ip4routing', 'ip6firewall', 'ip6routing', 'ip6routes', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', + 'publicmtu', 'privatemtu', 'dnszone', 'dnssubdomain'] if (!isAdmin()) { fields = fields.filter(function (e) { return e !== 'broadcasturi' }) } @@ -202,6 +205,36 @@ export default { } } }, + { + api: 'associateDnsZoneToNetwork', + icon: 'link-outlined', + label: 'label.action.associate.dns.zone', + dataView: true, + show: (record, store) => { + return (record.type === 'Shared' && record.dnszone === undefined && + (record.account === store.userInfo.account || isAdminOrDomainAdmin(store.userInfo.roletype))) + }, + popup: true, + component: shallowRef(defineAsyncComponent(() => import('@/views/network/dns/AssociateDnsZone.vue'))) + }, + { + api: 'disassociateDnsZoneFromNetwork', + icon: 'disconnect-outlined', + label: 'label.action.disassociate.dns.zone', + message: 'message.action.disassociate.dns.zone', + dataView: true, + popup: true, + args: ['networkid'], + show: (record, store) => { + return record.dnszone !== undefined && record.type === 'Shared' && + (record.account === store.userInfo.account || isAdminOrDomainAdmin(store.userInfo.roletype)) + }, + mapping: { + networkid: { + value: (record) => { return record.id } + } + } + }, { api: 'deleteNetwork', icon: 'delete-outlined', diff --git a/ui/src/views/network/NicsTable.vue b/ui/src/views/network/NicsTable.vue index c209efb7849..6d3a22c4fbc 100644 --- a/ui/src/views/network/NicsTable.vue +++ b/ui/src/views/network/NicsTable.vue @@ -57,11 +57,10 @@ {{ record.isolationuri }} - - - {{ record.dnsrecordurl }} - + + {{ record.dnsrecordurl }} +