Browse Source

openvpn: enable testing

keep-around/767ff486bc86b337f9b676e7846e94feb00d6960
Loïc Dachary 1 year ago
committed by Loic Dachary
parent
commit
06c7709ff1
Signed by: dachary GPG Key ID: 992D23B392F9E4F2
  1. 2
      ansible.cfg
  2. 44
      enough/common/__init__.py
  3. 2
      enough/common/data/base.dockerfile
  4. 43
      enough/common/openstack.py
  5. 39
      enough/common/service.py
  6. 1
      inventory/group_vars/all/network.yml
  7. 7
      inventory/services.yml
  8. 5
      playbooks/bind/roles/install_ssh_records/tasks/main.yml
  9. 47
      playbooks/conftest.py
  10. 5
      playbooks/icinga/roles/icinga2/tasks/main.yml
  11. 2
      playbooks/openvpn/conftest.py
  12. 3
      playbooks/openvpn/inventory/group_vars/openvpn-service-group.yml
  13. 2
      playbooks/openvpn/inventory/host_vars/internal-host.yml
  14. 4
      playbooks/openvpn/inventory/services.yml
  15. 2
      playbooks/openvpn/inventory/test-hosts.yml
  16. 22
      playbooks/openvpn/localhost-playbook.yml
  17. 2
      playbooks/openvpn/playbook.yml
  18. 26
      playbooks/openvpn/roles/openvpn/tasks/openvpn.yml
  19. 6
      playbooks/openvpn/roles/openvpn/templates/interface-eth1.cfg.j2
  20. 38
      playbooks/openvpn/test-openvpn-playbook.yml
  21. 2
      playbooks/openvpn/tests/test_localhost.py
  22. 6
      playbooks/openvpn/tests/test_openvpn.py
  23. 15
      tests/enough/common/test_common_service.py
  24. 2
      tests/enough/common/test_common_service/vpn_inventory/host_vars/icinga-host.yml
  25. 4
      tests/enough/common/test_common_service/vpn_inventory/services.yml
  26. 20
      tests/enough/common/test_init.py
  27. 0
      tests/enough/common/test_init/create-missings/.placeholder
  28. 5
      tests/enough/common/test_openstack.py

2
ansible.cfg

@ -1,2 +1,2 @@
[defaults]
roles_path = playbooks/infrastructure/roles:playbooks/authorized_keys/roles:playbooks/backup/roles:playbooks/bind/roles:playbooks/icinga/roles:playbooks/postfix/roles:playbooks/weblate/roles:playbooks/packages/roles:playbooks/jdauphant.nginx/roles:playbooks/enough-nginx/roles:playbooks/certificate/roles:playbooks/wazuh/roles:playbooks/firewall/roles:playbooks/api/roles
roles_path = playbooks/infrastructure/roles:playbooks/authorized_keys/roles:playbooks/backup/roles:playbooks/bind/roles:playbooks/icinga/roles:playbooks/postfix/roles:playbooks/weblate/roles:playbooks/packages/roles:playbooks/jdauphant.nginx/roles:playbooks/enough-nginx/roles:playbooks/certificate/roles:playbooks/wazuh/roles:playbooks/firewall/roles:playbooks/api/roles:playbooks/openvpn/roles

44
enough/common/__init__.py

@ -8,6 +8,7 @@ from enough.common.dotenough import DotEnough, Hosts
from enough.common.host import host_factory
from enough.common.service import service_factory
from enough.common.openstack import OpenStack, Heat
from enough.common import retry
from enough.common import ansible_utils
log = logging.getLogger(__name__)
@ -37,6 +38,49 @@ class Enough(object):
self.playbook = ansible_utils.Playbook(self.config_dir, self.share_dir)
self.ansible = ansible_utils.Ansible(self.config_dir, self.share_dir)
def create_missings(self, hosts):
public_key = f'{self.config_dir}/infrastructure_key.pub'
r = self.heat.create_missings(hosts, public_key)
self.update_internal_subnet_dns()
return r
def update_internal_subnet_dns(self):
dns = []
for host in self.service.service2hosts['bind']:
dns.append(self.openstack.server_ip_in_network(host, self.openstack.INTERNAL_NETWORK))
self.openstack.subnet_update_dns(self.openstack.INTERNAL_NETWORK, *dns)
def _vpn_credentials_path(self):
return f'{self.config_dir}/openvpn/localhost.tar.gz'
def vpn_has_credentials(self):
return os.path.exists(self._vpn_credentials_path())
def vpn_connect(self):
sh.sudo.tar('-xf', self._vpn_credentials_path(),
_cwd='/etc/openvpn/client')
network_prefix = self.ansible.get_variable('openvpn',
'openstack_internal_network_prefix',
'bind-host')
sh.sudo.openvpn(
'--config', 'localhost.conf',
'--writepid', '/var/run/openvpn-localhost.pid',
_cwd='/etc/openvpn/client', _bg=True, _truncate_exc=False)
@retry.retry(AssertionError, tries=5)
def check_route():
output = sh.ip.route()
assert network_prefix in output, f'{network_prefix} in {output}'
check_route()
def vpn_disconnect(self):
if os.path.exists('/var/run/openvpn-localhost.pid'):
pid = open('/var/run/openvpn-localhost.pid').read().strip()
sh.sudo.kill(pid)
sh.sudo.rm('/var/run/openvpn-localhost.pid')
def clone(self, target_domain, target_clouds):
config_base = os.path.dirname(self.config_dir)
config_dir = f'{config_base}/{target_domain}'

2
enough/common/data/base.dockerfile

@ -8,7 +8,7 @@ ENV USER_ID ${USER_ID:-0}
RUN apt-get update && \
apt-get install --quiet -y curl virtualenv python3 gcc libffi-dev libssl-dev python3-dev make git rsync \
systemd systemd-sysv sudo \
systemd systemd-sysv sudo openvpn \
openssh-server
RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
RUN curl -L "https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose

43
enough/common/openstack.py

@ -38,8 +38,8 @@ class OpenStackBase(object):
class Stack(OpenStackBase):
def __init__(self, config_dir, config_file, definition=None):
super().__init__(config_dir, config_file)
def __init__(self, config_dir, config_file, definition=None, **kwargs):
super().__init__(config_dir, config_file, **kwargs)
log.info(f'OS_CLIENT_CONFIG_FILE={self.config_file}')
self.definition = definition
@ -98,7 +98,8 @@ class Stack(OpenStackBase):
if return_on_create:
return None
info = self.update()
self.wait_for_ssh(info['ipv4'], info['port'])
if not self.only_internal() or self.args.get('route_to_internal', True):
self.wait_for_ssh(info['ipv4'], info['port'])
Hosts(self.config_dir).create_or_update(
self.definition['name'], info['ipv4'], info['port'])
return info
@ -123,6 +124,9 @@ class Stack(OpenStackBase):
self.connect_internal_network()
return r
def only_internal(self):
return self.definition.get('network', 'Ext-Net') != 'Ext-Net'
def create(self):
try:
log.info('create on network Ext-Net')
@ -131,7 +135,7 @@ class Stack(OpenStackBase):
log.info('retry create on network Ext-Net')
# retry once because there is no way to increase the timeout and create fails often
info = self._create_or_update('update', 'Ext-Net')
if self.definition.get('network', 'Ext-Net') != 'Ext-Net':
if self.only_internal():
log.info(f'remove network Ext-Net')
# wait for the interface to settle before removing it
self.wait_for_ssh(info['ipv4'], info['port'])
@ -221,7 +225,8 @@ class Heat(OpenStackBase):
# Launch the creation of all stacks in // and do not wait for them to complete
#
for name in names:
s = Stack(self.config_dir, self.config_file, self.get_stack_definition(name))
s = Stack(self.config_dir, self.config_file, self.get_stack_definition(name),
**self.args)
s.set_public_key(public_key)
info = s.create_or_update(return_on_create=True)
if info:
@ -232,7 +237,8 @@ class Heat(OpenStackBase):
# Verify all stacks previously launched complete as expected
#
for name in created:
s = Stack(self.config_dir, self.config_file, self.get_stack_definition(name))
s = Stack(self.config_dir, self.config_file, self.get_stack_definition(name),
**self.args)
s.set_public_key(public_key)
info = s.create_or_update(return_on_create=True)
r[name] = s.create_or_update()
@ -241,10 +247,11 @@ class Heat(OpenStackBase):
def create_test_subdomain(self, domain):
d = f"{self.config_dir}/inventory/group_vars/all"
assert os.path.exists(d)
if 'bind-host' not in Stack(self.config_dir, self.config_file).list():
if 'bind-host' not in Stack(self.config_dir, self.config_file, **self.args).list():
return None
h = Heat(self.config_dir, self.config_file)
s = Stack(self.config_dir, self.config_file, h.get_stack_definition('bind-host'))
s = Stack(self.config_dir, self.config_file, h.get_stack_definition('bind-host'),
**self.args)
s.set_public_key(f'{self.config_dir}/infrastructure_key.pub')
bind_host = s.create_or_update()
@ -375,14 +382,28 @@ class OpenStack(OpenStackBase):
if not self.subnet_exists(name):
self.o.subnet.create('--subnet-range', cidr, '--network', name, name)
def subnet_update_dns(self, name, *dns):
args = [f'--dns-nameserver={ip}' for ip in dns]
args.append(name)
self.o.subnet.set('--no-dns-nameserver', *args)
def network_and_subnet_create(self, name, cidr):
self.network_create(name)
self.subnet_create(name, cidr)
def server_connected_to_network(self, server, network):
port = self.o.port.list('--server', server, '--network', network,
'--format=value', '-c', 'ID')
return port.strip() != ''
return self.server_ip_in_network(server, network)
def server_ip_in_network(self, server, network):
info = self.o.port.list('--server', server, '--network', network,
'--format=value', '-c', 'Fixed IP Addresses')
info = info.strip()
if info == '':
return None
pattern = r"ip_address='(.*?)'"
m = re.search(pattern, info)
assert m, f're.search({pattern}, {info})'
return m.group(1)
def backup_date(self):
return datetime.datetime.today().strftime('%Y-%m-%d')

39
enough/common/service.py

@ -13,16 +13,49 @@ log = logging.getLogger(__name__)
class Service(object):
def __init__(self, config_dir, share_dir):
def __init__(self, config_dir, share_dir, **kwargs):
self.config_dir = config_dir
self.share_dir = share_dir
self.ansible = ansible_utils.Ansible(
self.config_dir, self.share_dir, kwargs.get('inventory', []))
self.set_service2hosts()
self.update_vpn_dependencies()
def set_service2hosts(self):
suffix = '-service-hosts'
groups = ansible_utils.Ansible(config_dir, share_dir).get_groups()
groups = self.ansible.get_groups()
self.service2hosts = {
name.replace(suffix, ''): hosts for name, hosts in groups.items()
if name.endswith(suffix)
}
def update_vpn_dependencies(self):
hosts = self.ansible.ansible_inventory()['_meta']['hostvars'].keys()
internal_hosts = set(self.hosts_with_internal_network(hosts))
if not internal_hosts:
return
assert 'openvpn' in self.service2hosts
openvpn_hosts = set(self.service2hosts['openvpn'])
for service in self.service2hosts.keys():
if service == 'openvpn':
continue
hosts = set(self.service2hosts[service])
if internal_hosts & hosts:
self.service2hosts[service] = list(internal_hosts | openvpn_hosts)
def add_vpn_hosts_if_needed(self, hosts):
internal_hosts = set(self.hosts_with_internal_network(hosts))
if not internal_hosts:
return hosts
assert 'openvpn' in self.service2hosts
openvpn_hosts = set(self.service2hosts['openvpn'])
hosts = set(hosts)
return list(hosts | openvpn_hosts)
def hosts_with_internal_network(self, hosts):
networks = self.ansible.get_variable_hosts('encrypted_device', 'openstack_network', *hosts)
return [host for (host, network) in networks.items() if network == 'internal']
def service_from_host(self, host):
found = None
hosts_count = 0
@ -47,7 +80,7 @@ class ServiceOpenStack(Service):
pass
def __init__(self, config_dir, share_dir, **kwargs):
super().__init__(config_dir, share_dir)
super().__init__(config_dir, share_dir, **kwargs)
self.args = kwargs
self.dotenough = dotenough.DotEnoughOpenStack(config_dir, self.args['domain'])
self.dotenough.ensure()

1
inventory/group_vars/all/network.yml

@ -2,4 +2,5 @@
openstack_network: Ext-Net
# openstack_network: internal
openstack_internal_network: internal
openstack_internal_network_prefix: "10.30.20"
openstack_internal_network_cidr: "10.30.20.0/24"

7
inventory/services.yml

@ -10,6 +10,10 @@ bind-service-group:
hosts:
bind-host:
bind-service-hosts:
children:
bind-service-group:
bind-client-group:
children:
all-hosts:
@ -135,8 +139,7 @@ pad-service-hosts:
essential-service-hosts:
openvpn-service-group:
hosts:
website-host:
hosts: {}
openvpn-service-hosts:
children:

5
playbooks/bind/roles/install_ssh_records/tasks/main.yml

@ -15,6 +15,11 @@
state: latest
delegate_to: bind-host
run_once: true
# The retries should not be necessary but proved useful May 2020
register: output
until: output is success
retries: 10
delay: 5
- name: generate SSHFP records
shell: |

47
playbooks/conftest.py

@ -54,6 +54,24 @@ def prepare_config_dir(domain, cache):
return config_dir
def get_hosts(session, enough):
hosts = session.config.getoption("--enough-hosts").split(',')
updated = enough.service.add_vpn_hosts_if_needed(hosts)
return (hosts != updated, updated)
def reboot_internal(enough, hosts):
for host in enough.service.hosts_with_internal_network(hosts):
#
# The reboot cannot be done immediately after removing the interface
# for some reason. Otherwise sshd does not bind to the interface.
#
# Hopefully doing it at this stage is late enough and does not cause this
# problem.
#
enough.openstack.o.server.reboot('--hard', host)
def pytest_sessionstart(session):
service_directory = session.config.getoption("--enough-service")
domain = f'{service_directory}.test'
@ -65,18 +83,33 @@ def pytest_sessionstart(session):
e = Enough(config_dir, '.',
domain=domain,
driver='openstack',
inventory=[f'playbooks/{service_directory}/inventory'])
names = session.config.getoption("--enough-hosts")
public_key = f'{e.config_dir}/infrastructure_key.pub'
r = e.heat.create_missings(names.split(','), public_key)
inventory=[f'playbooks/{service_directory}/inventory'],
route_to_internal=False)
(needs_vpn, hosts) = get_hosts(session, e)
r = e.create_missings(hosts)
if len(r) > 0:
e.heat.create_test_subdomain('enough.community')
if needs_vpn:
if not e.vpn_has_credentials():
vpn_hosts = ','.join(e.service.service2hosts['openvpn'])
reboot_internal(e, hosts)
e.playbook.run([
'--private-key', f'{e.config_dir}/infrastructure_key',
'-i', f'playbooks/openvpn/inventory',
f'--limit={vpn_hosts},localhost',
'playbooks/openvpn/playbook.yml',
])
e.vpn_connect()
hosts = ','.join(hosts)
e.playbook.run([
'--private-key', f'{e.config_dir}/infrastructure_key',
'-i', f'playbooks/{service_directory}/inventory',
f'--limit={names},localhost',
f'--limit={hosts},localhost',
f'playbooks/{service_directory}/playbook.yml',
])
if needs_vpn:
e.vpn_disconnect()
def pytest_runtest_setup(item):
@ -102,6 +135,10 @@ def pytest_sessionfinish(session, exitstatus):
inventory=[f'playbooks/{service_directory}/inventory'])
e.host.delete()
credentials = f'{config_dir}/openvpn/localhost.tar.gz'
if os.path.exists(credentials):
os.unlink(credentials)
def pytest_unconfigure(config):
pass

5
playbooks/icinga/roles/icinga2/tasks/main.yml

@ -15,6 +15,11 @@
- name: apt-key https://packages.icinga.com/icinga.key
apt_key:
url: https://packages.icinga.com/icinga.key
# The retries should not be necessary but proved useful May 2020
register: output
until: output is success
retries: 10
delay: 5
- name: deb http://packages.icinga.com/debian icinga-buster main
apt_repository:

2
playbooks/openvpn/conftest.py

@ -3,7 +3,7 @@ def pytest_addoption(parser):
"--enough-hosts",
action="store",
default="bind-host,icinga-host,website-host,openvpnclient-host",
default="bind-host,icinga-host,website-host,internal-host",
help="list of hosts"
)
parser.addoption(

3
playbooks/openvpn/inventory/group_vars/openvpn-service-group.yml

@ -1,6 +1,5 @@
---
openvpn_active_clients:
- openvpnclient-host
- localhost
openvpn_server_conf: |
push "route 10.11.12.0 255.255.255.0"
push "route {{ openstack_internal_network_prefix }}.0 255.255.255.0"

2
playbooks/openvpn/inventory/host_vars/internal-host.yml

@ -0,0 +1,2 @@
---
openstack_network: internal

4
playbooks/openvpn/inventory/services.yml

@ -0,0 +1,4 @@
---
openvpn-service-group:
hosts:
website-host:

2
playbooks/openvpn/inventory/test-hosts.yml

@ -1,4 +1,4 @@
---
all-hosts:
hosts:
openvpnclient-host:
internal-host:

22
playbooks/openvpn/localhost-playbook.yml

@ -0,0 +1,22 @@
---
- name: create openvpn client
hosts: localhost
become: true
tasks:
- name: mkdir -p /etc/openvpn
file:
state: directory
path: /etc/openvpn
- name: copy client credentials
copy:
src: "{{ openvpn_local_directory }}/localhost.tar.gz"
dest: "/etc/openvpn/localhost.tar.gz"
- name: cd /etc/openvpn ; tar zxvf localhost.tar.gz
shell: |
set -ex
tar zxvf localhost.tar.gz
args:
chdir: /etc/openvpn

2
playbooks/openvpn/playbook.yml

@ -7,4 +7,4 @@
- import_playbook: ../icinga/icinga-playbook.yml
- import_playbook: openvpn-server-playbook.yml
- import_playbook: openvpn-client-playbook.yml
- import_playbook: test-openvpn-playbook.yml
- import_playbook: localhost-playbook.yml

26
playbooks/openvpn/roles/openvpn/tasks/openvpn.yml

@ -56,6 +56,32 @@
enabled: True
changed_when: False
- name: ensure eth1 is not managed by /etc/network/interfaces
shell: |
set -ex
if grep -q eth1 /etc/network/interfaces ; then
ifdown eth1 || true
sed -i -e '/eth1/d' /etc/network/interfaces
echo Changed
else
echo Ok
fi
register: result
changed_when: '"Changed" in result.stdout'
- name: install /etc/network/interfaces.d/interface-eth1.cfg
template:
src: interface-eth1.cfg.j2
dest: /etc/network/interfaces.d/interface-eth1.cfg
register: interface
- name: bring eth1 up
shell: |
set -ex
ifdown eth1
ifup eth1
when: interface is changed
- name: apt-get install openvpn nftables
apt:
name: [openvpn, nftables]

6
playbooks/openvpn/roles/openvpn/templates/interface-eth1.cfg.j2

@ -0,0 +1,6 @@
allow-hotplug eth1
iface eth1 inet static
address {{ openstack_internal_network_prefix }}.1
netmask 255.255.255.0
broadcast {{ openstack_internal_network_prefix }}.255
network {{ openstack_internal_network_prefix }}.0

38
playbooks/openvpn/test-openvpn-playbook.yml

@ -1,38 +0,0 @@
---
- name: create openvpn client
hosts: openvpnclient-host,localhost
become: true
tasks:
- name: mkdir -p /etc/openvpn
file:
state: directory
path: /etc/openvpn
- name: copy client credentials
copy:
src: "{{ openvpn_local_directory }}/{{ inventory_hostname }}.tar.gz"
dest: "/etc/openvpn/{{ inventory_hostname }}.tar.gz"
- name: cd /etc/openvpn ; tar zxvf {{ inventory_hostname }}.tar.gz
shell: |
set -ex
tar zxvf {{ inventory_hostname }}.tar.gz
args:
chdir: /etc/openvpn
- name: run openvpn on openvpnclient-host
hosts: openvpnclient-host
become: true
tasks:
- name: apt-get install openvpn
apt:
name: [openvpn]
state: present
- name: systemctl enable openvpn@{{ inventory_hostname }} ; systemctl start openvpn@{{ inventory_hostname }}
systemd:
name: openvpn@{{ inventory_hostname }}
state: restarted
enabled: yes

2
playbooks/openvpn/tests/test_localhost.py

@ -10,6 +10,6 @@ def test_localhost_vpn_route():
@retry.retry(AssertionError, tries=5)
def check_route():
output = sh.ip.route()
assert '10.11.12' in output
assert '10.30.20' in output
check_route()

6
playbooks/openvpn/tests/test_openvpn.py

@ -1,9 +1,9 @@
testinfra_hosts = ['ansible://openvpnclient-host']
testinfra_hosts = ['ansible://internal-host']
def test_vpn_route(host):
cmd = host.run("ip route")
cmd = host.run("hostname")
print(cmd.stdout)
print(cmd.stderr)
assert cmd.rc == 0
assert '10.11.12' in cmd.stdout
assert 'internal-host' in cmd.stdout

15
tests/enough/common/test_common_service.py

@ -30,8 +30,21 @@ def test_openstack_create_or_update(tmpdir, openstack_name, requests_mock):
assert r['fqdn'] == f'essential.{domain}'
def test_service_from_host(tmpdir):
def test_service_from_host():
s = service.Service(settings.CONFIG_DIR, settings.SHARE_DIR)
assert s.service_from_host('bind-host') == 'essential'
assert s.service_from_host('cloud-host') == 'cloud'
assert s.service_from_host('unknown-host') is None
def test_update_vpn_dependencies():
s = service.Service(settings.CONFIG_DIR, settings.SHARE_DIR)
assert s.hosts_with_internal_network(['bind-host']) == []
assert 'website-host' not in s.service2hosts['openvpn']
assert 'website-host' not in s.service2hosts['weblate']
s.ansible.set_inventories('tests/enough/common/test_common_service/vpn_inventory')
s.set_service2hosts()
assert 'website-host' in s.service2hosts['openvpn']
assert s.hosts_with_internal_network(['icinga-host']) == ['icinga-host']
s.update_vpn_dependencies()
assert 'website-host' in s.service2hosts['weblate']

2
tests/enough/common/test_common_service/vpn_inventory/host_vars/icinga-host.yml

@ -0,0 +1,2 @@
---
openstack_network: internal

4
tests/enough/common/test_common_service/vpn_inventory/services.yml

@ -0,0 +1,4 @@
---
openvpn-service-group:
hosts:
website-host:

20
tests/enough/common/test_init.py

@ -187,6 +187,7 @@ def test_restore_remote(tmpdir):
original.set_args(name='sample', playbook='enough-playbook.yml')
original.service.create_or_update()
ip = original.hosts.load().get_ip('sample-host')
sh.ssh('-i', original.dotenough.private_key(), f'debian@{ip}', 'touch', '/srv/STONE')
original.openstack.backup_create(['sample-volume'])
snapshot = f'{original.openstack.backup_date()}-sample-volume'
@ -204,3 +205,22 @@ def test_restore_remote(tmpdir):
o = OpenStack(settings.CONFIG_DIR, clouds)
# comment out the following line to re-use the content of the regions and save time
o.destroy_everything(None)
@pytest.mark.skipif('SKIP_OPENSTACK_INTEGRATION_TESTS' in os.environ,
reason='skip integration test')
def test_create_missings(tmpdir):
test_clouds = 'inventory/group_vars/all/clouds.yml'
try:
enough = create_enough(tmpdir, test_clouds, 'create-missings')
r = enough.create_missings(['bind-host'])
assert 'bind-host' in r
internal_dns = enough.openstack.o.subnet.show(
'--format=value', '-c', 'dns_nameservers', 'internal').strip()
bind_internal_ip = enough.openstack.server_ip_in_network('bind-host', 'internal')
assert bind_internal_ip == internal_dns
finally:
o = OpenStack(settings.CONFIG_DIR, test_clouds)
# comment out the following line to re-use the content of the regions and save time
o.destroy_everything(None)

0
tests/enough/common/test_init/create-missings/.placeholder

5
tests/enough/common/test_openstack.py

@ -155,6 +155,11 @@ def test_network(openstack_name):
assert o.network_exists(openstack_name)
assert o.subnet_exists(openstack_name)
dns_ip = '1.2.3.4'
o.subnet_update_dns(openstack_name, dns_ip)
r = o.o.subnet.show('--format=value', '-c', 'dns_nameservers', openstack_name)
assert r.strip() == dns_ip
@pytest.mark.skipif('SKIP_OPENSTACK_INTEGRATION_TESTS' in os.environ,
reason='skip integration test')

Loading…
Cancel
Save