Adds ConntrackdManager

Used for setting up conntrackd between two clustered peers.

Partially-implements: blueprint appliance-ha

Change-Id: Ice3f4dbed02b877bc64ae73879a74acc26cca47e
This commit is contained in:
Adam Gandelman 2016-02-12 12:50:25 -08:00
parent 02383adf64
commit 8633d1a5bc
10 changed files with 244 additions and 2 deletions

View File

@ -21,6 +21,7 @@
- include: tasks/base.yml
- include: tasks/astara.yml
- include: tasks/bird.yml
- include: tasks/conntrackd.yml
- include: tasks/dnsmasq.yml
- include: tasks/extras.yml
when: install_extras

View File

@ -0,0 +1,7 @@
---
- name: install conntrackd
apt: name=conntrackd state=installed install_recommends=no
- name: install conntrackd notify script to /etc/conntrackd
copy: src=/usr/share/doc/conntrackd/examples/sync/primary-backup.sh dest=/etc/conntrackd/primary-backup.sh mode=0755

View File

@ -0,0 +1,38 @@
General {
HashSize 8192
HashLimit 65535
Syslog on
LockFile /var/lock/conntrackd.lock
UNIX {
Path /var/run/conntrackd.sock
Backlog 20
}
SocketBufferSize 262142
SocketBufferSizeMaxGrown 655355
Filter {
Protocol Accept {
TCP
}
Address Ignore {
IPv4_address 127.0.0.1
}
}
}
Sync {
Mode FTFW {
}
UDP Default {
{%- if management_ip_version == 4 %}
IPv4_address {{ source_address }}
IPv4_Destination_Address {{ destination_address }}
{%- else %}
IPv6_address {{ source_address }}
IPv6_Destination_Address {{ destination_address }}
{%- endif %}
Port 3780
Interface {{ interface }}
SndSocketBuffer 24985600
RcvSocketBuffer 24985600
Checksum on
}
}

View File

@ -0,0 +1,93 @@
# Copyright (c) 2016 Akanda, Inc. All Rights Reserved.
#
# Licensed 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.
import os
from astara_router.drivers import base
from astara_router import utils
class ConntrackdManager(base.Manager):
"""
A class to provide facilities to interact with the conntrackd daemon.
"""
EXECUTABLE = 'service'
CONFIG_FILE_TEMPLATE = os.path.join(
os.path.dirname(__file__), 'conntrackd.conf.template')
# Debian defaults
CONFIG_FILE = '/etc/conntrackd/conntrackd.conf'
# Debian installs this to /usr/share/doc/examples/sync but our
# DIB recipe will install it here.
NOTIFY_SCRIPT = '/etc/conntrackd/primary-backup.sh'
def __init__(self, root_helper='sudo astara-rootwrap /etc/rootwrap.conf'):
"""
Initializes ConntrackdManager class.
:type root_helper: str
:param root_helper: System utility used to gain escalate privileges.
"""
super(ConntrackdManager, self).__init__(root_helper)
self._config_templ = utils.load_template(self.CONFIG_FILE_TEMPLATE)
self._should_restart = False
def save_config(self, config, generic_to_host):
"""
Renders template and writes to the conntrackd file
:type config: astara_router.models.Configuration
:param config: An astara_router.models.Configuration object containing
the ha_config configuration.
:param generic_to_host: A callable used to resolve generic interface
name to system interface name.
"""
mgt_interface = None
for interface in config.interfaces:
if interface.management:
mgt_interface = interface
break
mgt_addr = mgt_interface.first_v6 or mgt_interface.first_v4
ctxt = {
'source_address': str(mgt_addr),
'management_ip_version': mgt_addr.version,
'destination_address': config.ha_config['peers'][0],
'interface': generic_to_host(interface.ifname),
}
try:
old_config_hash = utils.hash_file(self.CONFIG_FILE)
except IOError:
old_config_hash = None
utils.replace_file(
'/tmp/conntrackd.conf',
self._config_templ.render(ctxt))
utils.execute(
['mv', '/tmp/conntrackd.conf', self.CONFIG_FILE],
self.root_helper)
if old_config_hash != utils.hash_file(self.CONFIG_FILE):
self._should_restart = True
def restart(self):
"""
Restarts the conntrackd daemon if config has been changed
"""
if not self._should_restart:
return
self.sudo('conntrackd', 'restart')

View File

@ -1,3 +1,14 @@
vrrp_sync_group astara_vrrp_group {
group {
{%- for instance in vrrp_instances %}
{{ instance.name }}
{%- endfor %}
}
notify_master "{{ notify_script }} primary"
notify_backup "{{ notify_script }} backup"
notify_fault "{{ notify_script }} fault"
}
{%- for instance in vrrp_instances %}
vrrp_instance {{ instance.name }} {
native_ipv6

View File

@ -14,7 +14,7 @@
import os
from astara_router.drivers import base
from astara_router.drivers import base, conntrackd
from astara_router import utils
@ -84,6 +84,7 @@ class KeepalivedManager(base.Manager):
self.config_tmpl = utils.load_template(self.CONFIG_FILE_TEMPLATE)
self.peers = []
self.priority = 0
self.notify_script = conntrackd.ConntrackdManager.NOTIFY_SCRIPT
self._last_config_hash = None
def set_management_address(self, address):
@ -123,6 +124,7 @@ class KeepalivedManager(base.Manager):
return self.config_tmpl.render(
priority=self.priority,
peers=self.peers,
notify_script=self.notify_script,
vrrp_instances=self.instances.values())
def reload(self):

View File

@ -20,7 +20,7 @@ import re
from astara_router import models
from astara_router import settings
from astara_router.drivers import (bird, dnsmasq, ip, metadata,
from astara_router.drivers import (bird, conntrackd, dnsmasq, ip, metadata,
iptables, arp, hostname, loadbalancer)
@ -113,8 +113,16 @@ class RouterManager(ServiceManagerBase):
self.update_firewall()
self.update_routes(cache)
self.update_arp()
self.update_conntrackd()
self.reload_config()
def update_conntrackd(self):
if not self._config.ha:
return
mgr = conntrackd.ConntrackdManager()
mgr.save_config(self._config, self.ip_mgr.generic_to_host)
mgr.restart()
def update_dhcp(self):
mgr = dnsmasq.DHCPManager()
mgr.delete_all_config()

View File

@ -9,6 +9,9 @@ mv_bird: RegExpFilter, mv, root, mv, /tmp/bird6\.conf, /etc/bird/bird6\.conf
arp: CommandFilter, /usr/sbin/arp, root
astara_gratuitous_arp: CommandFilter, astara-gratuitous-arp, root
# astara_router/drivers/conntrackd.py:
mv_conntrackd: RegExpFilter, mv, root, mv, /tmp/conntrackd\.conf, /etc/conntrackd/conntrackd\.conf
# astara_router/drivers/dnsmasq.py:
mv_dnsmasq: RegExpFilter, mv, root, mv, /tmp/dnsmasq\.conf, /etc/dnsmasq\.d/.*\.conf
rm: CommandFilter, rm, root

View File

@ -0,0 +1,4 @@
---
features:
- The appliance is now built with conntrackd installed and supports configuring
the connection tracking service among pairs of clustered HA router appliances.

View File

@ -0,0 +1,75 @@
# Copyright 2016 Akanda, Inc.
#
# Author: Akanda, Inc.
#
# Licensed 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.
import __builtin__
from unittest2 import TestCase
import mock
from test.unit import fakes
from astara_router.drivers import conntrackd
class ConntrackddManagerTestCase(TestCase):
def setUp(self):
super(ConntrackddManagerTestCase, self).setUp()
self.mgr = conntrackd.ConntrackdManager()
self.mgr._config_templ = mock.Mock(
render=mock.Mock()
)
@mock.patch('astara_router.utils.execute')
@mock.patch('astara_router.utils.replace_file')
@mock.patch('astara_router.utils.hash_file')
def test_save_config(self, fake_hash, fake_replace, fake_execute):
fake_generic_to_host = mock.Mock(return_value='eth0')
fake_interface = fakes.fake_interface()
fake_mgt_interface = fakes.fake_mgt_interface()
ha_config = {
'peers': ['10.0.0.2'],
}
fake_config = mock.Mock(
interfaces=[fake_interface, fake_mgt_interface],
ha_config=ha_config,
)
fake_hash.side_effect = ['hash1', 'hash2']
self.mgr._config_templ.render.return_value = 'new_config'
self.mgr.save_config(fake_config, fake_generic_to_host)
self.mgr._config_templ.render.assert_called_with(dict(
source_address=str(fake_mgt_interface.addresses[0].ip),
management_ip_version=4,
destination_address='10.0.0.2',
interface='eth0',
))
self.assertTrue(self.mgr._should_restart)
fake_replace.assert_called_with('/tmp/conntrackd.conf', 'new_config')
fake_execute.assert_called_with(
['mv', '/tmp/conntrackd.conf', '/etc/conntrackd/conntrackd.conf'],
self.mgr.root_helper)
@mock.patch.object(conntrackd.ConntrackdManager, 'sudo')
def test_restart(self, fake_sudo):
self.mgr._should_restart = True
self.mgr.restart()
fake_sudo.assert_called_with('conntrackd', 'restart')
@mock.patch.object(conntrackd.ConntrackdManager, 'sudo')
def test_restart_skip(self, fake_sudo):
self.mgr._should_restart = False
self.mgr.restart()
self.assertFalse(fake_sudo.called)