mirror of https://github.com/apache/cloudstack.git
add marvin test for powerdns
This commit is contained in:
parent
6466362552
commit
9d0884a0a6
|
|
@ -1351,7 +1351,6 @@ public class ApiConstants {
|
|||
// DNS provider related
|
||||
public static final String NAME_SERVERS = "nameservers";
|
||||
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_RECORD = "dnsrecord";
|
||||
|
|
|
|||
|
|
@ -62,8 +62,8 @@ public class AddDnsServerCmd extends BaseCmd {
|
|||
description = "Username or email associated with the external DNS provider account (used for authentication)")
|
||||
private String dnsUserName;
|
||||
|
||||
@Parameter(name = ApiConstants.CREDENTIALS, required = true, type = CommandType.STRING, description = "API key or credentials for the external provider")
|
||||
private String credentials;
|
||||
@Parameter(name = ApiConstants.API_KEY, required = true, type = CommandType.STRING, description = "API key or token for the external provider")
|
||||
private String apiKey;
|
||||
|
||||
@Parameter(name = ApiConstants.PORT, type = CommandType.INTEGER, description = "Port number of the external DNS server")
|
||||
private Integer port;
|
||||
|
|
@ -89,8 +89,8 @@ public class AddDnsServerCmd extends BaseCmd {
|
|||
|
||||
public String getUrl() { return url; }
|
||||
|
||||
public String getCredentials() {
|
||||
return credentials;
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public Integer getPort() {
|
||||
|
|
|
|||
|
|
@ -58,8 +58,8 @@ public class UpdateDnsServerCmd extends BaseCmd {
|
|||
@Parameter(name = ApiConstants.URL, type = CommandType.STRING, description = "API URL of the provider")
|
||||
private String url;
|
||||
|
||||
@Parameter(name = ApiConstants.CREDENTIALS, type = CommandType.STRING, required = false, description = "API Key or Credentials for the external provider")
|
||||
private String credentials;
|
||||
@Parameter(name = ApiConstants.API_KEY, type = CommandType.STRING, required = false, description = "API Key or Credentials for the external provider")
|
||||
private String apiKey;
|
||||
|
||||
@Parameter(name = ApiConstants.PORT, type = CommandType.INTEGER, description = "Port number of the external DNS server")
|
||||
private Integer port;
|
||||
|
|
@ -83,8 +83,8 @@ public class UpdateDnsServerCmd extends BaseCmd {
|
|||
public Long getId() { return id; }
|
||||
public String getName() { return name; }
|
||||
public String getUrl() { return url; }
|
||||
public String getCredentials() {
|
||||
return credentials;
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
public Integer getPort() {
|
||||
return port;
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ public class AddDnsServerCmdTest extends BaseDnsCmdTest {
|
|||
setField(cmd, "name", "test-dns");
|
||||
setField(cmd, "url", "http://dns.example.com");
|
||||
setField(cmd, "provider", "PowerDNS");
|
||||
setField(cmd, "credentials", "api-key-123");
|
||||
setField(cmd, "apiKey", "api-key-123");
|
||||
setField(cmd, "port", 8081);
|
||||
setField(cmd, "isPublic", true);
|
||||
setField(cmd, "publicDomainSuffix", "public.example.com");
|
||||
|
|
@ -56,7 +56,7 @@ public class AddDnsServerCmdTest extends BaseDnsCmdTest {
|
|||
|
||||
assertEquals("test-dns", cmd.getName());
|
||||
assertEquals("http://dns.example.com", cmd.getUrl());
|
||||
assertEquals("api-key-123", cmd.getCredentials());
|
||||
assertEquals("api-key-123", cmd.getApiKey());
|
||||
assertEquals(Integer.valueOf(8081), cmd.getPort());
|
||||
assertTrue(cmd.isPublic());
|
||||
assertEquals("public.example.com", cmd.getPublicDomainSuffix());
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ public class UpdateDnsServerCmdTest extends BaseDnsCmdTest {
|
|||
setField(cmd, "id", ENTITY_ID);
|
||||
setField(cmd, "name", "updated-dns");
|
||||
setField(cmd, "url", "http://updated.dns.com");
|
||||
setField(cmd, "credentials", "new-api-key");
|
||||
setField(cmd, "apiKey", "new-api-key");
|
||||
setField(cmd, "port", 9090);
|
||||
setField(cmd, "isPublic", true);
|
||||
setField(cmd, "publicDomainSuffix", "updated.example.com");
|
||||
|
|
@ -55,7 +55,7 @@ public class UpdateDnsServerCmdTest extends BaseDnsCmdTest {
|
|||
assertEquals(Long.valueOf(ENTITY_ID), cmd.getId());
|
||||
assertEquals("updated-dns", cmd.getName());
|
||||
assertEquals("http://updated.dns.com", cmd.getUrl());
|
||||
assertEquals("new-api-key", cmd.getCredentials());
|
||||
assertEquals("new-api-key", cmd.getApiKey());
|
||||
assertEquals(Integer.valueOf(9090), cmd.getPort());
|
||||
assertTrue(cmd.isPublic());
|
||||
assertEquals("updated.example.com", cmd.getPublicDomainSuffix());
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ CREATE TABLE IF NOT EXISTS `cloud`.`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',
|
||||
`api_key` varchar(255) NOT NULL COMMENT 'dns server api_key',
|
||||
`api_key` varchar(255) NOT NULL COMMENT 'api key or token for the dns server ',
|
||||
`external_server_id` varchar(255) COMMENT 'dns server id e.g. localhost for powerdns',
|
||||
`port` int(11) DEFAULT NULL COMMENT 'optional dns server port',
|
||||
`name_servers` varchar(1024) DEFAULT NULL COMMENT 'Comma separated list of name servers',
|
||||
|
|
@ -186,7 +186,3 @@ CREATE TABLE IF NOT EXISTS `cloud`.`dns_zone_network_map` (
|
|||
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;
|
||||
|
||||
-- Set default limit to 10 DNS zones for standard Accounts
|
||||
INSERT INTO `cloud`.`configuration` (`category`, `instance`, `component`, `name`, `value`, `description`, `default_value`)
|
||||
VALUES ('Advanced', 'DEFAULT', 'ResourceLimitManager', 'max.account.dns_zones', '10', 'The default maximum number of DNS zones that can be created by an Account', '10');
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
|
|||
|
||||
DnsProviderType type = cmd.getProvider();
|
||||
DnsServerVO server = new DnsServerVO(cmd.getName(), cmd.getUrl(), cmd.getPort(), cmd.getExternalServerId(), type,
|
||||
cmd.getDnsUserName(), cmd.getCredentials(), isDnsPublic, publicDomainSuffix, cmd.getNameServers(),
|
||||
cmd.getDnsUserName(), cmd.getApiKey(), isDnsPublic, publicDomainSuffix, cmd.getNameServers(),
|
||||
caller.getAccountId(), caller.getDomainId());
|
||||
try {
|
||||
DnsProvider provider = getProviderByType(type);
|
||||
|
|
@ -250,8 +250,8 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
|
|||
}
|
||||
}
|
||||
|
||||
if (cmd.getCredentials() != null && !cmd.getCredentials().equals(originalKey)) {
|
||||
dnsServer.setApiKey(cmd.getCredentials());
|
||||
if (cmd.getApiKey() != null && !cmd.getApiKey().equals(originalKey)) {
|
||||
dnsServer.setApiKey(cmd.getApiKey());
|
||||
validationRequired = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -849,7 +849,7 @@ public class DnsProviderManagerImplTest {
|
|||
org.apache.cloudstack.api.command.user.dns.UpdateDnsServerCmd cmd = mock(
|
||||
org.apache.cloudstack.api.command.user.dns.UpdateDnsServerCmd.class);
|
||||
when(cmd.getId()).thenReturn(SERVER_ID);
|
||||
when(cmd.getCredentials()).thenReturn("new-api-key");
|
||||
when(cmd.getApiKey()).thenReturn("new-api-key");
|
||||
|
||||
when(dnsServerDao.findById(SERVER_ID)).thenReturn(serverVO);
|
||||
Mockito.doReturn("old-api-key").when(serverVO).getApiKey();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,318 @@
|
|||
# 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.
|
||||
|
||||
from marvin.cloudstackTestCase import cloudstackTestCase
|
||||
from marvin.cloudstackAPI import *
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import logging
|
||||
import socket
|
||||
|
||||
class TestCloudStackDNSFramework(cloudstackTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""
|
||||
Pre-requisite:
|
||||
Bring up PDNS via docker compose (external dependency for DNS provider).
|
||||
"""
|
||||
super(TestCloudStackDNSFramework, cls).setUpClass()
|
||||
cls.api_client = cls.testClient.getApiClient()
|
||||
|
||||
cls.logger = logging.getLogger("TestCloudStackDNSFramework")
|
||||
cls.stream_handler = logging.StreamHandler()
|
||||
cls.logger.setLevel(logging.DEBUG)
|
||||
cls.logger.addHandler(cls.stream_handler)
|
||||
# -------------------------
|
||||
# Detect Marvin VM IP (reachable by MS)
|
||||
# -------------------------
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
try:
|
||||
s.connect(("8.8.8.8", 80))
|
||||
cls.marvin_vm_ip = s.getsockname()[0]
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
cls.logger.info(f"Detected Marvin VM IP: {cls.marvin_vm_ip}")
|
||||
|
||||
# -------------------------
|
||||
# PDNS compose config
|
||||
# -------------------------
|
||||
|
||||
cls.compose_file = "/marvin/pdns/docker-compose.yml"
|
||||
cls.compose_dir = "/marvin/pdns"
|
||||
cls.logger.info("Bringing up PDNS via docker compose...")
|
||||
|
||||
up_cmd = [
|
||||
"docker", "compose",
|
||||
"-f", cls.compose_file,
|
||||
"up", "-d"
|
||||
]
|
||||
|
||||
result = subprocess.run(
|
||||
up_cmd,
|
||||
cwd=cls.compose_dir,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
cls.tearDownClass()
|
||||
raise Exception(f"Failed to start PDNS:\n{result.stderr}")
|
||||
|
||||
# Allow PDNS to initialize
|
||||
time.sleep(15)
|
||||
|
||||
cls.logger.info("PDNS is up and running")
|
||||
|
||||
# Construct PDNS URL once
|
||||
cls.pdns_url = f"http://{cls.marvin_vm_ip}"
|
||||
cls.logger.info(f"PDNS endpoint: {cls.pdns_url}")
|
||||
|
||||
|
||||
def test_01_list_dns_providers(self):
|
||||
"""
|
||||
List DNS providers, expect PowerDNS provider to be present
|
||||
"""
|
||||
list_providers_cmd = listDnsProviders.listDnsProvidersCmd()
|
||||
self.logger.info("Listing DNS providers to verify PowerDNS presence")
|
||||
response = self.api_client.listDnsProviders(list_providers_cmd)
|
||||
self.assertIsNotNone(response, "Failed to list DNS providers")
|
||||
self.logger.info(f"DNS Providers found: {[provider.name for provider in response]}")
|
||||
|
||||
def test_02_add_dns_server(self):
|
||||
"""
|
||||
Register PDNS as DNS provider in CloudStack
|
||||
"""
|
||||
self.logger.info("Adding PDNS DNS server")
|
||||
|
||||
response = self._add_dns_server()
|
||||
self.assertIsNotNone(response, "Failed to add DNS provider")
|
||||
self.__class__.dns_server_id = response.id
|
||||
self.logger.info(f"DNS Provider added: {response.id}")
|
||||
self.assertIsNotNone(response.id, "DNS server ID should not be None")
|
||||
|
||||
|
||||
def test_03_list_dns_servers(self):
|
||||
"""
|
||||
List DNS servers and verify the newly added PDNS provider is present
|
||||
"""
|
||||
self.logger.info("Listing DNS servers to verify addition")
|
||||
list_cmd = listDnsServers.listDnsServersCmd()
|
||||
list_cmd.id = self.dns_server_id
|
||||
response = self.api_client.listDnsServers(list_cmd)
|
||||
self.assertIsNotNone(response, "Failed to list DNS servers")
|
||||
self.assertEqual(len(response), 1, "Expected exactly one DNS server")
|
||||
self.assertEqual(response[0].id, self.dns_server_id, "DNS server ID mismatch")
|
||||
|
||||
|
||||
def test_04_create_dns_zone(self):
|
||||
"""
|
||||
Create a DNS zone in the added PDNS provider
|
||||
"""
|
||||
self.logger.info("Creating a DNS zone")
|
||||
response = self._create_zone(self.dns_server_id)
|
||||
self.assertIsNotNone(response, "Failed to create DNS zone")
|
||||
self.assertIsNotNone(response.id, "DNS zone ID should not be None")
|
||||
self.__class__.dns_zone_id = response.id
|
||||
self.logger.info(f"DNS Zone created: {response.id}")
|
||||
|
||||
|
||||
def test_05_list_dns_zones(self):
|
||||
"""
|
||||
List DNS zones and verify the newly created zone is present
|
||||
"""
|
||||
self.logger.info("Listing DNS zones to verify creation")
|
||||
list_zones_cmd = listDnsZones.listDnsZonesCmd()
|
||||
list_zones_cmd.id = self.dns_zone_id
|
||||
response = self.api_client.listDnsZones(list_zones_cmd)
|
||||
self.assertIsNotNone(response, "Failed to list DNS zones")
|
||||
self.assertEqual(len(response), 1, "Expected exactly one DNS zone")
|
||||
self.assertEqual(response[0].id, self.dns_zone_id, "DNS zone ID mismatch")
|
||||
self.assertEqual(response[0].name, "example.com", "DNS zone name mismatch")
|
||||
|
||||
def test_06_create_a_dns_record(self):
|
||||
"""
|
||||
Create a DNS record in the previously created zone
|
||||
"""
|
||||
self.logger.info("Creating A DNS record")
|
||||
response = self._create_record(
|
||||
self.dns_zone_id,
|
||||
"www.example.com",
|
||||
"A",
|
||||
"10.1.1.10"
|
||||
)
|
||||
self.assertIsNotNone(response, "Failed to create DNS record")
|
||||
self.assertEqual(response.name, "www.example.com", "DNS record name mismatch")
|
||||
|
||||
def test_07_create_aaaa_dns_records(self):
|
||||
"""
|
||||
Create AAAA DNS records in the previously created zone
|
||||
"""
|
||||
self.logger.info("Creating AAAA DNS records")
|
||||
response = self._create_record(
|
||||
self.dns_zone_id,
|
||||
"www.example.com",
|
||||
"AAAA",
|
||||
"2001:db8::10"
|
||||
)
|
||||
self.assertIsNotNone(response, "Failed to create AAAA DNS record")
|
||||
self.assertTrue(response.name is not None, "DNS record name should not be None")
|
||||
|
||||
|
||||
def test_08_create_mx_dns_record(self):
|
||||
"""
|
||||
Create an MX DNS record in the previously created zone
|
||||
"""
|
||||
self.logger.info("Creating an MX DNS record")
|
||||
response = self._create_record(
|
||||
self.dns_zone_id,
|
||||
"example.com",
|
||||
"MX",
|
||||
"10 mail.example.com"
|
||||
)
|
||||
self.assertIsNotNone(response, "Failed to create MX DNS record")
|
||||
self.assertTrue(response.name is not None, "DNS record name should not be None")
|
||||
|
||||
|
||||
def test_09_list_dns_records(self):
|
||||
"""
|
||||
List DNS records in the zone and verify the created records are present
|
||||
"""
|
||||
self.logger.info("Listing DNS records to verify creation")
|
||||
list_records_cmd = listDnsRecords.listDnsRecordsCmd()
|
||||
list_records_cmd.dnszoneid = self.dns_zone_id
|
||||
response = self.api_client.listDnsRecords(list_records_cmd)
|
||||
self.assertIsNotNone(response, "Failed to list DNS records")
|
||||
self.assertEqual(len(response), 4, "Expected four DNS records, including NS record")
|
||||
record_types = set(record.type for record in response)
|
||||
self.assertSetEqual(record_types, {"NS", "A", "AAAA", "MX"}, "DNS record types mismatch")
|
||||
|
||||
|
||||
def test_10_delete_dns_record(self):
|
||||
"""
|
||||
Delete one of the DNS records and verify it's removed
|
||||
"""
|
||||
self.logger.info("Deleting a DNS record")
|
||||
delete_record_cmd = deleteDnsRecord.deleteDnsRecordCmd()
|
||||
delete_record_cmd.name = "www.example.com"
|
||||
delete_record_cmd.type = "A"
|
||||
delete_record_cmd.dnszoneid = self.dns_zone_id
|
||||
delete_response = self.api_client.deleteDnsRecord(delete_record_cmd)
|
||||
self.assertIsNotNone(delete_response, "Failed to delete DNS record")
|
||||
self.logger.info(f"DNS Record deleted: {delete_record_cmd.name}")
|
||||
|
||||
# Verify deletion
|
||||
list_record_cmd = listDnsRecords.listDnsRecordsCmd()
|
||||
list_record_cmd.dnszoneid = self.dns_zone_id
|
||||
response_after_deletion = self.api_client.listDnsRecords(list_record_cmd)
|
||||
self.assertEqual(len(response_after_deletion), 3, "Expected three DNS records after deletion")
|
||||
remaining_record_names = set(record.name for record in response_after_deletion)
|
||||
self.assertNotIn(delete_record_cmd.name, remaining_record_names, "Deleted DNS record still present")
|
||||
|
||||
def test_11_delete_dns_zone(self):
|
||||
"""
|
||||
Delete the DNS zone and verify it's removed
|
||||
"""
|
||||
self.logger.info("Deleting the DNS zone")
|
||||
delete_zone_cmd = deleteDnsZone.deleteDnsZoneCmd()
|
||||
delete_zone_cmd.id = self.dns_zone_id
|
||||
response = self.api_client.deleteDnsZone(delete_zone_cmd)
|
||||
self.assertIsNotNone(response, "Failed to delete DNS zone")
|
||||
self.logger.info(f"DNS Zone deleted: {self.dns_zone_id}")
|
||||
|
||||
# Verify deletion
|
||||
list_zones_cmd = listDnsZones.listDnsZonesCmd()
|
||||
list_zones_cmd.id = self.dns_zone_id
|
||||
try:
|
||||
self.api_client.listDnsZones(list_zones_cmd)
|
||||
self.fail("DNS zone still exists after deletion")
|
||||
except Exception as e:
|
||||
self.logger.info(f"Expected exception after delete: {str(e)}")
|
||||
|
||||
def test_12_delete_dns_server(self):
|
||||
"""
|
||||
Delete the PDNS DNS server and verify it's removed
|
||||
"""
|
||||
self.logger.info("Deleting the PDNS DNS server")
|
||||
delete_cmd = deleteDnsServer.deleteDnsServerCmd()
|
||||
delete_cmd.id = self.dns_server_id
|
||||
response = self.api_client.deleteDnsServer(delete_cmd)
|
||||
self.assertIsNotNone(response, "Failed to delete DNS server")
|
||||
self.logger.info(f"DNS Server deleted: {self.dns_server_id}")
|
||||
|
||||
# Verify deletion
|
||||
list_cmd = listDnsServers.listDnsServersCmd()
|
||||
list_cmd.id = self.dns_server_id
|
||||
response = self.api_client.listDnsServers(list_cmd)
|
||||
dns_servers = response or []
|
||||
self.assertEqual(len(dns_servers), 0, "Expected no DNS servers after deletion")
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
"""
|
||||
Stop PDNS after tests
|
||||
"""
|
||||
|
||||
try:
|
||||
cls.logger.info("Stopping PDNS stack...")
|
||||
|
||||
cmd = [
|
||||
"docker", "compose",
|
||||
"-f", cls.compose_file,
|
||||
"down"
|
||||
]
|
||||
|
||||
subprocess.run(cmd, cwd=cls.compose_dir)
|
||||
|
||||
finally:
|
||||
super(TestCloudStackDNSFramework, cls).tearDownClass()
|
||||
|
||||
|
||||
def _create_record(self, zone_id, name, rtype, contents):
|
||||
cmd = createDnsRecord.createDnsRecordCmd()
|
||||
cmd.dnszoneid = zone_id
|
||||
cmd.name = name
|
||||
cmd.type = rtype
|
||||
cmd.contents = contents
|
||||
|
||||
return self.api_client.createDnsRecord(cmd)
|
||||
|
||||
|
||||
def _add_dns_server(self):
|
||||
cmd = addDnsServer.addDnsServerCmd()
|
||||
cmd.name = "pdns-server"
|
||||
cmd.url = self.pdns_url
|
||||
cmd.credentials = "supersecretapikey"
|
||||
cmd.provider = "PowerDNS"
|
||||
cmd.nameservers = ["ns1.example.com", "ns2.example.com"]
|
||||
cmd.externalserverid = "localhost"
|
||||
cmd.ispublic = True
|
||||
cmd.port = 8081
|
||||
cmd.publicdomainsuffix = "pdns-public.example.com"
|
||||
|
||||
return self.api_client.addDnsServer(cmd)
|
||||
|
||||
def _create_zone(self, server_id):
|
||||
cmd = createDnsZone.createDnsZoneCmd()
|
||||
cmd.dnsserverid = server_id
|
||||
cmd.name = "example.com"
|
||||
cmd.description = "Test DNS Zone for PDNS"
|
||||
|
||||
return self.api_client.createDnsZone(cmd)
|
||||
Loading…
Reference in New Issue