LxcProvider network refactoring

Tunnels are used for ip connectivity between nodes. This makes
possible to use LxcProvider in any environment, and deploy
clouds with huge number of nodes.

Class LxcContainer has been replaced by more convenient LxcHost.

Added new exception ConfigValidationError.

blueprint multihost-deploy

Change-Id: Iff287468535afc0840d4d233bf393c7ddf02b911
This commit is contained in:
Sergey Skripnick 2013-12-19 21:39:14 +02:00
parent a4e906dcfc
commit 33ea28f0cd
7 changed files with 593 additions and 300 deletions

View File

@ -5,10 +5,11 @@
},
"provider": {
"name": "LxcProvider",
"containers_per_host": 1,
"containers_per_host": 4,
"container_name_prefix": "rally-providertest-02-",
"start_lxc_network": "10.100.1.0/28",
"tunnel_to": ["10.5.0.1"],
"distribution": "ubuntu",
"ipv4_start_address": "10.2.232.1",
"ipv4_prefixlen": 16,
"host_provider": {
"name": "DummyProvider",
"credentials": [{"user": "root", "host": "10.2.250.103"}]

View File

@ -15,8 +15,11 @@
import netaddr
import os
import uuid
import re
import tempfile
import time
from rally import exceptions
from rally.openstack.common.gettextutils import _
from rally.openstack.common import log as logging
from rally.serverprovider import provider
@ -24,54 +27,165 @@ from rally import utils
LOG = logging.getLogger(__name__)
INET_ADDR_RE = re.compile(r' *inet ((\d+\.){3}\d+)\/\d+ .*')
class LxcContainer(object):
def _get_script_path(filename):
return os.path.abspath(os.path.join(os.path.dirname(__file__),
'lxc', filename))
def _write_script_from_template(template_filename, **kwargs):
template = open(_get_script_path(template_filename)).read()
new_file = tempfile.NamedTemporaryFile(delete=False)
new_file.write(template.format(**kwargs))
new_file.close()
return new_file.name
class LxcHost(object):
"""Represent lxc enabled host."""
def __init__(self, server, config):
self.path = '/var/lib/lxc/%s/rootfs/'
self.host = server
self.config = {'network_bridge': 'br0'}
self.config.update(config)
self.server = provider.Server('', self.config['ip'].split('/')[0],
'root', '')
self.config = config
if 'network' in config:
self.network = netaddr.IPNetwork(config['network'])
else:
self.network = None
self.server = server
self.containers = []
self.path = '/var/lib/lxc/'
def prepare_host(self):
script = os.path.abspath(os.path.join(os.path.dirname(__file__),
'lxc', 'lxc-install.sh'))
self.host.ssh.execute_script(script)
def _get_server_with_ip(self, ip):
credentials = self.server.get_credentials()
credentials['host'] = ip
return provider.Server.from_credentials(credentials)
def configure(self):
path = self.path % self.config['name']
configure_script = os.path.join(os.path.dirname(__file__),
'lxc',
'configure_container.sh')
self.host.ssh.upload(configure_script, '/tmp/.rally_cont_conf.sh')
ip = netaddr.IPNetwork(self.config['ip'])
netmask = str(ip.netmask)
ip = str(ip.ip)
self.host.ssh.execute('/bin/sh', '/tmp/.rally_cont_conf.sh', path,
ip, netmask, self.config['gateway'],
self.config['nameserver'])
@property
def backingstore(self):
if not hasattr(self, '_backingstore'):
try:
self.server.ssh.execute('df -t btrfs %s' % self.path)
self._backingstore = 'btrfs'
except exceptions.SSHError:
self._backingstore = 'dir'
return self._backingstore
def create(self, distribution):
self.host.ssh.execute('lxc-create', '-B', 'btrfs',
'-n', self.config['name'],
'-t', distribution)
self.configure()
def prepare(self):
if self.network:
dhcp_start = str(self.network.network + 2)
dhcp_end = str(self.network.network + self.network.size - 2)
dhcp_range = ','.join([dhcp_start, dhcp_end])
values = {
'USE_LXC_BRIDGE': "true",
'LXC_BRIDGE': self.config.get('lxc_bridge', 'lxcbr0'),
'LXC_ADDR': self.network.network + 1,
'LXC_NETMASK': self.network.netmask,
'LXC_NETWORK': self.network,
'LXC_DHCP_RANGE': dhcp_range,
'LXC_DHCP_MAX': self.network.size - 3,
}
config = tempfile.NamedTemporaryFile(delete=False)
for name, value in values.iteritems():
config.write('%(name)s="%(value)s"\n' % {'name': name,
'value': value})
config.close()
self.server.ssh.upload(config.name, '/tmp/.lxc_default')
os.unlink(config.name)
def clone(self, source):
self.host.ssh.execute('lxc-clone', '--snapshot', '-o', source, '-n',
self.config['name'])
self.configure()
script = _get_script_path('lxc-install.sh')
self.server.ssh.execute_script(script)
self.create_local_tunnels()
self.create_remote_tunnels()
def start(self):
self.host.ssh.execute('lxc-start', '-d', '-n', self.config['name'])
def create_local_tunnels(self):
"""Create tunel on lxc host side."""
for tunnel_to in self.config['tunnel_to']:
script = _write_script_from_template('tunnel-local.sh',
net=self.network,
local=self.server.host,
remote=tunnel_to)
self.server.ssh.execute_script(script)
os.unlink(script)
def stop(self):
self.host.ssh.execute('lxc-stop', '-n', self.config['name'])
def create_remote_tunnels(self):
"""Create tunel on remote side."""
for tunnel_to in self.config['tunnel_to']:
script = _write_script_from_template('tunnel-remote.sh',
net=self.network,
local=tunnel_to,
remote=self.server.host)
server = self._get_server_with_ip(tunnel_to)
server.ssh.execute_script(script)
os.unlink(script)
def destroy(self):
self.host.ssh.execute('lxc-destroy', '-n', self.config['name'])
def delete_tunnels(self):
for tunnel_to in self.config['tunnel_to']:
remote_server = self._get_server_with_ip(tunnel_to)
remote_server.ssh.execute('ip tun del t%s' % self.network.ip)
self.server.ssh.execute('ip tun del t%s' % tunnel_to)
def get_ip(self, name):
"""Get container's ip by name."""
cmd = 'lxc-attach -n %s ip addr list dev eth0' % name
for attempt in range(1, 16):
stdout = self.server.ssh.execute(cmd, get_stdout=True)[0]
for line in stdout.splitlines():
m = INET_ADDR_RE.match(line)
if m:
return m.group(1)
time.sleep(attempt)
msg = _('Timeout waiting for ip address of container "%s"') % name
raise exceptions.TimeoutException(msg)
def create_container(self, name, distribution):
self.server.ssh.execute('lxc-create', '-B', self.backingstore,
'-n', name,
'-t', distribution)
self.configure_container(name)
self.containers.append(name)
def create_clone(self, name, source):
cmd = ['lxc-clone']
if self.backingstore == 'btrfs':
cmd.append('--snapshot')
cmd.extend(['-o', source, '-n', name])
self.server.ssh.execute(*cmd)
self.configure_container(name)
self.containers.append(name)
def configure_container(self, name):
path = os.path.join(self.path, name, 'rootfs')
configure_script = _get_script_path('configure_container.sh')
self.server.ssh.upload(configure_script, '/tmp/.rally_cont_conf.sh')
self.server.ssh.execute('/bin/sh', '/tmp/.rally_cont_conf.sh', path)
def start_containers(self):
for name in self.containers:
self.server.ssh.execute('lxc-start -d -n %s' % name)
def stop_containers(self):
for name in self.containers:
self.server.ssh.execute('lxc-stop -n %s' % name)
def destroy_containers(self):
for name in self.containers:
self.server.ssh.execute('lxc-stop -n %s' % name)
self.server.ssh.execute('lxc-destroy -n %s' % name)
def get_server_object(self, name, wait=True):
"""Create Server object for container."""
server = self._get_server_with_ip(self.get_ip(name))
if wait:
server.ssh.wait(timeout=300)
return server
def get_server_objects(self, wait=True):
"""Generate Server objects from all containers."""
for name in self.containers:
yield self.get_server_object(name, wait)
class LxcProvider(provider.ProviderFactory):
@ -79,75 +193,99 @@ class LxcProvider(provider.ProviderFactory):
Sample configuration:
{
'name': 'LxcProvider',
'distribution': 'ubuntu',
'start_ip_address': '10.0.0.10/24',
'containers_per_host': 32,
'container_config': {
'network_bridge': 'br0',
'nameserver': '10.0.0.1',
'gateway': '10.0.0.1',
},
'host_provider': {
'name': 'DummyProvider',
'credentials': [{'user': 'root', 'host': 'host.net'}]
"name": "LxcProvider",
"distribution": "ubuntu",
"start_lxc_network": "10.1.1.0/24",
"containers_per_host": 32,
"tunnel_to": ["10.10.10.10"],
"container_name_prefix": "rally-multinode-02",
"host_provider": {
"name": "DummyProvider",
"credentials": [{"user": "root", "host": "host.net"}]
}
}
"""
def _next_ip(self):
self.ip += 1
return '%s/%d' % (self.ip, self.network.prefixlen)
CONFIG_SCHEMA = {
'type': 'object',
'properties': {
'name': {'type': 'string'},
'distribution': {'type': 'string'},
'start_lxc_network': {'type': 'string',
'pattern': '^(\d+\.){3}\d+\/\d+$'},
'containers_per_host': {'type': 'integer'},
'tunnel_to': {'type': 'array',
'elements': {'type': 'string',
'pattern': '^(\d+\.){3}\d+$'}},
'container_name_prefix': {'type': 'string'},
'host_provider': {'type': 'object',
'properties': {'name': {'type': 'string'}}},
},
'required': ['name', 'containers_per_host',
'container_name_prefix', 'host_provider'],
}
def validate(self):
super(LxcProvider, self).validate()
if 'start_lxc_network' not in self.config:
return
lxc_net = netaddr.IPNetwork(self.config['start_lxc_network'])
num_containers = self.config['containers_per_host']
if lxc_net.size - 3 < num_containers:
message = _("Network size is not enough for %d hosts.")
raise exceptions.InvalidConfigException(message % num_containers)
@utils.log_deploy_wrapper(LOG.info, _("Create containers on host"))
def create_servers(self):
host_provider = provider.ProviderFactory.get_provider(
self.config['host_provider'], self.deployment)
self.network = netaddr.IPNetwork(self.config['start_ip_address'])
self.ip = self.network.ip - 1
first = str(uuid.uuid4())
containers = []
name_prefix = self.config['container_name_prefix']
hosts = []
if 'start_lxc_network' in self.config:
network = netaddr.IPNetwork(self.config['start_lxc_network'])
else:
network = None
distribution = self.config.get('distribution', 'ubuntu')
for server in host_provider.create_servers():
config = self.config['container_config'].copy()
config['ip'] = self._next_ip()
config['name'] = first
first_container = LxcContainer(server, config)
first_container.prepare_host()
first_container.create(self.config['distribution'])
containers.append(first_container)
self.resources.create({
'server': first_container.server.get_credentials(),
'config': config,
})
for i in range(1, self.config['containers_per_host']):
config = self.config['container_config'].copy()
config['ip'] = self._next_ip()
config['name'] = '%s-%d' % (first, i)
container = LxcContainer(server, config)
container.clone(first)
container.start()
containers.append(container)
self.resources.create({
'server': container.server.get_credentials(),
'config': config,
})
first_container.start()
for container in containers:
container.server.ssh.wait()
return [c.server for c in containers]
config = {'tunnel_to': self.config.get('tunnel_to', [])}
if network:
config['network'] = str(network)
host = LxcHost(server, config)
host.prepare()
ip = str(network.ip).replace('.', '-') if network else '0'
first_name = '%s-000-%s' % (name_prefix, ip)
host.create_container(first_name, distribution)
for i in range(1, self.config.get('containers_per_host', 1)):
name = '%s-%03d-%s' % (name_prefix, i, ip)
host.create_clone(name, first_name)
host.start_containers()
hosts.append(host)
if network:
network += 1
servers = []
for host in hosts:
containers = []
for server in host.get_server_objects():
containers.append(server.get_credentials())
servers.append(server)
info = {'host': host.server.get_credentials(),
'config': host.config,
'container_names': host.containers}
self.resources.create(info)
return servers
@utils.log_deploy_wrapper(LOG.info, _("Destroy host(s)"))
def destroy_servers(self):
for resource in self.resources.get_all():
config = resource['info']['config']
server = provider.Server.from_credentials(
resource['info']['server'])
container = LxcContainer(server, config)
container.stop()
container.destroy()
self.resources.delete(resource)
host_provider = provider.ProviderFactory.get_provider(
self.config['host_provider'], self.deployment)
host_provider.destroy_servers()
server = provider.Server.from_credentials(resource['info']['host'])
lxc_host = LxcHost(server, resource['info']['config'])
lxc_host.containers = resource['info']['container_names']
lxc_host.destroy_containers()
lxc_host.delete_tunnels()
self.resources.delete(resource['id'])

View File

@ -1,20 +1,6 @@
#!/bin/sh
CONTAINER=$1
IP=$2
NETMASK=$3
GATEWAY=$4
NAMESERVER=$5
mkdir -p $CONTAINER/root/.ssh
cp ~/.ssh/authorized_keys $CONTAINER/root/.ssh/
echo "nameserver $NAMESERVER" > $CONTAINER/etc/resolv.conf
cat > $CONTAINER/etc/network/interfaces <<EOF
auto lo
auto eth0
iface lo inet loopback
iface eth0 inet static
address $IP
netmask $NETMASK
gateway $GATEWAY
EOF

View File

@ -3,14 +3,21 @@
apt-get update
apt-get install -yq btrfs-tools
#configure networking
[ grep lxctun /etc/iproute2/rt_tables ] || echo "16 lxctun" >> /etc/iproute2/rt_tables
sysctl net.ipv4.conf.all.rp_filter=0
sysctl net.ipv4.conf.default.rp_filter=0
for iface in `ls /sys/class/net/ | grep -v "lo"` ; do
sysctl net.ipv4.conf."$iface".rp_filter=0 > /dev/null 2> /dev/null || true
done
# configure btrfs storage
if [ -d "/var/lib/lxc" ]; then
echo "Directory exists. Assume btrfs is available."
else
if [ ! -d "/var/lib/lxc" ]; then
mkdir /var/lib/lxc
if df -t btrfs /var/lib/lxc > /dev/null 2>&1; then
echo "Btrfs is already available."
else
if ! df -t btrfs /var/lib/lxc > /dev/null 2>&1; then
echo "Creating btrfs volume."
SIZE=`df -h /var | awk '/[0-9]%/{print $(NF-2)}'`
truncate -s $SIZE /var/rally-btrfs-volume
@ -21,36 +28,11 @@ else
fi
# install lxc
DEBIAN_FRONTEND='noninteractive' apt-get install -yq lxc
#configure lxc
if [ -f "/etc/lxc/lxc.conf" ]; then
CONFIG="/etc/lxc/lxc.conf"
if [ dpkg -s lxc > /dev/null 2>&1 ]; then
echo "Lxc already installed"
else
CONFIG="/etc/lxc/default.conf"
DEBIAN_FRONTEND='noninteractive' apt-get install -yq lxc
service lxc stop
cat /tmp/.lxc_default >> /etc/default/lxc || true
service lxc start
fi
cat > $CONFIG <<EOF
lxc.network.type=veth
lxc.network.link=br0
lxc.network.flags=up
EOF
# configure virtual network
if /sbin/ip link show br0 2> /dev/null
then
echo "br0 already exists"
else
modprobe bridge
ip link add br0 type bridge
ip link set br0 up
ip=`ip addr list dev eth0 | grep "inet "| awk '{ print $2}'`
gw=`ip route | grep default | awk '{print $3}'`
(
ip addr del $ip dev eth0
ip addr add $ip dev br0
ip route add default via $gw
ip link set eth0 master br0
)
fi

View File

@ -0,0 +1,9 @@
rule="from {net} to {remote} lookup lxctun"
ip rule del $rule 2> /dev/null || true
ip rule add $rule
if ! ip tun list | egrep "^t{net.ip}" ; then
iptables -t nat -I POSTROUTING -s {net} -d {remote} -j ACCEPT
ip tun add t{remote} mode ipip local {local} remote {remote}
ip link set t{remote} up
ip route add {remote}/32 dev t{remote} table lxctun
fi

View File

@ -0,0 +1,3 @@
ip tun add t{net.ip} mode ipip local {local} remote {remote} || true
ip link set t{net.ip} up
ip route add {net} dev t{net.ip} src {local} || true

View File

@ -13,9 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
import jsonschema
import mock
import netaddr
from rally.openstack.common.fixture import mockpatch
from rally import exceptions
from rally.openstack.common import test
from rally.serverprovider.providers import lxc
@ -23,77 +25,241 @@ from rally.serverprovider.providers import lxc
MOD_NAME = 'rally.serverprovider.providers.lxc.'
class LxcContainerTestCase(test.BaseTestCase):
class HelperFunctionsTestCase(test.BaseTestCase):
def test__get_script_path(self):
full_path = lxc._get_script_path('script.sh')
self.assertTrue(full_path.endswith('rally/serverprovider/'
'providers/lxc/script.sh'))
@mock.patch(MOD_NAME + '_get_script_path', return_value='fake_path')
@mock.patch(MOD_NAME + 'tempfile')
@mock.patch(MOD_NAME + 'open', create=True)
def test__write_script_from_template(self, m_open, m_tempfile, m_gsp):
fake_tempfile = mock.Mock()
m_tempfile.NamedTemporaryFile.return_value = fake_tempfile
fake_file = mock.Mock()
fake_data = mock.Mock()
fake_data.format.return_value = 'fake_formatted_data'
fake_file.read.return_value = fake_data
m_open.return_value = fake_file
retval = lxc._write_script_from_template('script', key='value')
m_gsp.assert_called_once_with('script')
m_open.assert_called_once_with('fake_path')
m_tempfile.NamedTemporaryFile.assert_called_once_with(delete=False)
fake_data.format.assert_called_once_with(key='value')
fake_tempfile.write.assert_called_once_with('fake_formatted_data')
self.assertEqual(fake_tempfile.name, retval)
class LxcHostTestCase(test.BaseTestCase):
def setUp(self):
super(LxcContainerTestCase, self).setUp()
self.server = mock.MagicMock()
config = {'ip': '1.2.3.4/24',
'gateway': '1.2.3.1',
'nameserver': '1.2.3.1',
'name': 'name'}
self.container = lxc.LxcContainer(self.server, config)
super(LxcHostTestCase, self).setUp()
def test_container_construct(self):
expected = {'ip': '1.2.3.4/24',
'gateway': '1.2.3.1',
'name': 'name',
'nameserver': '1.2.3.1',
'network_bridge': 'br0'}
self.assertEqual(expected, self.container.config)
self.assertIsInstance(self.container.server, lxc.provider.Server)
sample_config = {'network': '10.1.1.0/24',
'tunnel_to': ['1.1.1.1', '2.2.2.2']}
self.server = mock.Mock()
self.server.host = 'fake_server_ip'
self.server.get_credentials.return_value = {'ip': '3.3.3.3'}
self.host = lxc.LxcHost(self.server, sample_config)
def test_container_create(self):
with mock.patch.object(lxc.LxcContainer, 'configure') as configure:
self.container.create('ubuntu')
expected = [mock.call.ssh.execute('lxc-create',
'-B', 'btrfs',
'-n', 'name',
'-t', 'ubuntu')]
self.assertEqual(expected, self.server.mock_calls)
configure.assert_called_once()
@mock.patch(MOD_NAME + 'provider.Server')
def test__get_server_with_ip(self, m_Server):
server = self.host._get_server_with_ip('4.4.4.4')
new_server = m_Server.from_credentials({'ip': '4.4.4.4'})
self.assertEqual(new_server, server)
def test_container_clone(self):
with mock.patch.object(lxc.LxcContainer, 'configure') as configure:
self.container.clone('src')
expected = [mock.call.ssh.execute('lxc-clone',
'--snapshot',
'-o', 'src',
'-n', 'name')]
self.assertEqual(expected, self.server.mock_calls)
configure.assert_called_once()
def test_backingstore_btrfs(self):
self.assertEqual('btrfs', self.host.backingstore)
self.assertEqual('btrfs', self.host.backingstore)
# second call will return cached value
self.assertEqual([mock.call.ssh.execute('df -t btrfs /var/lib/lxc/')],
self.server.mock_calls)
def test_container_configure(self):
self.container.configure()
s_filename = self.server.mock_calls[0][1][0]
expected = [
mock.call.ssh.upload(s_filename, '/tmp/.rally_cont_conf.sh'),
mock.call.ssh.execute('/bin/sh', '/tmp/.rally_cont_conf.sh',
'/var/lib/lxc/name/rootfs/', '1.2.3.4',
'255.255.255.0', '1.2.3.1', '1.2.3.1')
def test_backingstore_none(self):
self.server.ssh.execute.side_effect = exceptions.SSHError()
self.assertEqual('dir', self.host.backingstore)
@mock.patch(MOD_NAME + '_get_script_path', return_value='fake_sp')
@mock.patch(MOD_NAME + 'os.unlink')
@mock.patch(MOD_NAME + 'tempfile')
def test_prepare(self, m_tempfile, m_unlink, m_gsp):
self.host.create_local_tunnels = mock.Mock()
self.host.create_remote_tunnels = mock.Mock()
fake_tempfile = mock.Mock()
m_tempfile.NamedTemporaryFile.return_value = fake_tempfile
self.host.prepare()
write_calls = [
mock.call('LXC_DHCP_MAX="253"\n'),
mock.call('LXC_NETMASK="255.255.255.0"\n'),
mock.call('LXC_ADDR="10.1.1.1"\n'),
mock.call('LXC_DHCP_RANGE="10.1.1.2,10.1.1.254"\n'),
mock.call('LXC_NETWORK="10.1.1.0/24"\n'),
mock.call('LXC_BRIDGE="lxcbr0"\n'),
mock.call('USE_LXC_BRIDGE="true"\n')
]
self.assertEqual(expected, self.server.mock_calls)
for call in write_calls:
fake_tempfile.write.assert_has_calls(call)
self.server.ssh.upload.assert_called_once_with(fake_tempfile.name,
'/tmp/.lxc_default')
self.server.ssh.execute_script.assert_called_once_with('fake_sp')
m_unlink.assert_called_once_with(fake_tempfile.name)
self.host.create_local_tunnels.assert_called_once()
self.host.create_remote_tunnels.assert_called_once()
def test_container_start(self):
self.container.start()
expected = [
mock.call.ssh.execute('lxc-start', '-d', '-n', 'name')
@mock.patch(MOD_NAME + 'os.unlink')
@mock.patch(MOD_NAME + '_write_script_from_template')
def test_create_local_tunnels(self, m_ws, m_unlink):
m_ws.side_effect = ['1', '2']
self.host.create_local_tunnels()
ws_calls = [
mock.call('tunnel-local.sh', local='fake_server_ip',
net=netaddr.IPNetwork('10.1.1.0/24'), remote='1.1.1.1'),
mock.call('tunnel-local.sh', local='fake_server_ip',
net=netaddr.IPNetwork('10.1.1.0/24'), remote='2.2.2.2'),
]
self.assertEqual(expected, self.server.mock_calls)
self.assertEqual(ws_calls, m_ws.mock_calls)
self.assertEqual([mock.call('1'), mock.call('2')],
self.server.ssh.execute_script.mock_calls)
def test_container_stop(self):
self.container.stop()
expected = [
mock.call.ssh.execute('lxc-stop', '-n', 'name')
]
self.assertEqual(expected, self.server.mock_calls)
@mock.patch(MOD_NAME + 'os.unlink')
@mock.patch(MOD_NAME + '_write_script_from_template')
def test_create_remote_tunnels(self, m_ws, m_unlink):
m_ws.side_effect = ['1', '2']
fake_server = mock.Mock()
self.host._get_server_with_ip = mock.Mock(return_value=fake_server)
def test_container_destroy(self):
self.container.destroy()
expected = [
mock.call.ssh.execute('lxc-destroy', '-n', 'name')
self.host.create_remote_tunnels()
ws_calls = [
mock.call('tunnel-remote.sh', local='1.1.1.1',
net=netaddr.IPNetwork('10.1.1.0/24'),
remote='fake_server_ip'),
mock.call('tunnel-remote.sh', local='2.2.2.2',
net=netaddr.IPNetwork('10.1.1.0/24'),
remote='fake_server_ip'),
]
self.assertEqual(expected, self.server.mock_calls)
self.assertEqual(ws_calls, m_ws.mock_calls)
self.assertEqual([mock.call('1'), mock.call('2')],
fake_server.ssh.execute_script.mock_calls)
def test_delete_tunnels(self):
s1 = mock.Mock()
s2 = mock.Mock()
self.host._get_server_with_ip = mock.Mock(side_effect=[s1, s2])
self.host.delete_tunnels()
s1.ssh.execute.assert_called_once_with('ip tun del t10.1.1.0')
s2.ssh.execute.assert_called_once_with('ip tun del t10.1.1.0')
self.assertEqual([mock.call('ip tun del t1.1.1.1'),
mock.call('ip tun del t2.2.2.2')],
self.server.ssh.execute.mock_calls)
@mock.patch(MOD_NAME + 'time.sleep')
def test_get_ip(self, m_sleep):
s1 = 'link/ether fe:54:00:d3:f5:98 brd ff:ff:ff:ff:ff:ff'
s2 = s1 + '\n inet 10.20.0.1/24 scope global br1'
self.host.server.ssh.execute.side_effect = [(s1, ''), (s2, '')]
ip = self.host.get_ip('name')
self.assertEqual('10.20.0.1', ip)
self.assertEqual([mock.call('lxc-attach -n name ip addr list dev eth0',
get_stdout=True)] * 2,
self.host.server.ssh.execute.mock_calls)
def test_create_container(self):
self.host.configure_container = mock.Mock()
self.host._backingstore = 'btrfs'
self.host.create_container('name', 'dist')
self.server.ssh.execute.assert_called_once_with(
'lxc-create', '-B', 'btrfs', '-n', 'name', '-t', 'dist')
self.assertEqual(['name'], self.host.containers)
self.host.configure_container.assert_called_once_with('name')
#check with no btrfs
self.host._backingstore = 'dir'
self.host.create_container('name', 'dist')
self.assertEqual(mock.call('lxc-create', '-B', 'dir', '-n',
'name', '-t', 'dist'),
self.server.ssh.execute.mock_calls[1])
def test_create_clone(self):
self.host._backingstore = 'btrfs'
self.host.configure_container = mock.Mock()
self.host.create_clone('name', 'src')
self.server.ssh.execute.assert_called_once_with('lxc-clone',
'--snapshot',
'-o', 'src',
'-n', 'name')
self.assertEqual(['name'], self.host.containers)
#check with no btrfs
self.host._backingstore = 'dir'
self.host.create_clone('name', 'src')
self.assertEqual(mock.call('lxc-clone', '-o', 'src', '-n', 'name'),
self.server.ssh.execute.mock_calls[1])
@mock.patch(MOD_NAME + 'os.path.join')
@mock.patch(MOD_NAME + '_get_script_path')
def test_configure_container(self, m_gsp, m_join):
m_gsp.return_value = 'fake_script'
m_join.return_value = 'fake_path'
self.host.configure_container('name')
calls = [
mock.call.upload('fake_script', '/tmp/.rally_cont_conf.sh'),
mock.call.execute('/bin/sh', '/tmp/.rally_cont_conf.sh',
'fake_path'),
]
self.assertEqual(calls, self.server.ssh.mock_calls)
def test_start_containers(self):
self.host.containers = ['c1', 'c2']
self.host.start_containers()
calls = [mock.call('lxc-start -d -n c1'),
mock.call('lxc-start -d -n c2')]
self.assertEqual(calls, self.server.ssh.execute.mock_calls)
def test_stop_containers(self):
self.host.containers = ['c1', 'c2']
self.host.stop_containers()
calls = [
mock.call('lxc-stop -n c1'),
mock.call('lxc-stop -n c2'),
]
self.assertEqual(calls, self.server.ssh.execute.mock_calls)
def test_destroy_containers(self):
self.host.containers = ['c1', 'c2']
self.host.destroy_containers()
calls = [
mock.call('lxc-stop -n c1'), mock.call('lxc-destroy -n c1'),
mock.call('lxc-stop -n c2'), mock.call('lxc-destroy -n c2'),
]
self.assertEqual(calls, self.server.ssh.execute.mock_calls)
@mock.patch(MOD_NAME + 'provider.Server.from_credentials')
def test_get_server_object(self, m_fc):
fake_server = mock.Mock()
m_fc.return_value = fake_server
self.server.get_credentials = mock.Mock(return_value={})
self.host.get_ip = mock.Mock(return_value='ip')
so = self.host.get_server_object('c1', wait=False)
self.assertEqual(fake_server, so)
m_fc.assert_called_once_with({'host': 'ip'})
self.assertFalse(fake_server.ssh.wait.mock_calls)
so = self.host.get_server_object('c1', wait=True)
fake_server.ssh.wait.assert_called_once()
@mock.patch(MOD_NAME + 'LxcHost.get_server_object')
def test_get_server_objects(self, m_gso):
m_gso.side_effect = ['s1', 's2']
self.host.containers = ['c1', 'c2']
retval = list(self.host.get_server_objects(wait='wait'))
self.assertEqual(['s1', 's2'], retval)
self.assertEqual([mock.call('c1', 'wait'), mock.call('c2', 'wait')],
m_gso.mock_calls)
class LxcProviderTestCase(test.BaseTestCase):
@ -102,109 +268,117 @@ class LxcProviderTestCase(test.BaseTestCase):
super(LxcProviderTestCase, self).setUp()
self.config = {
'name': 'LxcProvider',
'containers_per_host': 3,
'distribution': 'ubuntu',
'start_ip_address': '192.168.0.10/24',
'container_config': {
'nameserver': '192.168.0.1',
'gateway': '192.168.0.1',
},
'start_lxc_network': '10.1.1.0/29',
'containers_per_host': 2,
'tunnel_to': ['10.10.10.10', '20.20.20.20'],
'container_name_prefix': 'rally-lxc',
'host_provider': {
'name': 'DummyProvider',
'credentials': [{'user': 'root', 'host': 'host1.net'},
{'user': 'root', 'host': 'host2.net'}]}
}
self.mock_deployment = mock.MagicMock()
self.provider = lxc.provider.ProviderFactory.get_provider(
self.config, self.mock_deployment)
self.useFixture(mockpatch.PatchObject(self.provider, 'resources'))
self.deployment = {'uuid': 'fake_uuid'}
self.provider = lxc.LxcProvider(self.deployment, self.config)
@mock.patch(MOD_NAME + 'uuid')
@mock.patch('rally.serverprovider.provider.Server')
@mock.patch(MOD_NAME + 'LxcContainer')
def test_validate(self):
self.provider.validate()
def test_validate_invalid_tunnel(self):
config = self.config.copy()
config['tunnel_to'] = 'ok'
self.assertRaises(jsonschema.ValidationError,
lxc.LxcProvider, self.deployment, config)
def test_validate_required_field(self):
config = self.config.copy()
del(config['host_provider'])
self.assertRaises(jsonschema.ValidationError,
lxc.LxcProvider, self.deployment, config)
def test_validate_too_small_network(self):
config = self.config.copy()
config['containers_per_host'] = 42
self.assertRaises(exceptions.InvalidConfigException,
lxc.LxcProvider, self.deployment, config)
@mock.patch(MOD_NAME + 'LxcHost')
@mock.patch(MOD_NAME + 'provider.ProviderFactory.get_provider')
def test_create_servers(self, get_provider, mock_lxc_container,
mock_server, mock_uuid):
def create_config(ip, name):
conf = self.config['container_config'].copy()
conf['ip'] = ip
conf['name'] = name
return conf
def test_create_servers(self, m_get_provider, m_lxchost):
fake_provider = mock.Mock()
fake_provider.create_servers.return_value = ['server1', 'server2']
fake_hosts = []
fake_sos = []
for i in (1, 2):
fake_host_sos = [mock.Mock(), mock.Mock()]
fake_sos.extend(fake_host_sos)
fake_host = mock.Mock()
fake_host.containers = ['c-%d-1' % i, 'c-%d-2' % i]
fake_host.config = {'netwrork': 'fake-%d' % i}
fake_host.server.get_credentials.return_value = {'ip': 'f%d' % i}
fake_host.get_server_objects.return_value = fake_host_sos
fake_hosts.append(fake_host)
m_lxchost.side_effect = fake_hosts
m_get_provider.return_value = fake_provider
mock_uuid.uuid4.return_value = 'fakeuuid'
mock_first_conts = [mock.Mock(), mock.Mock()]
mock_conts = [mock.Mock() for i in range(4)]
mock_lxc_container.side_effect = containers = \
[mock_first_conts[0]] + mock_conts[:2] + \
[mock_first_conts[1]] + mock_conts[2:]
for (i, mock_cont_i) in enumerate(containers):
mock_cont_i.server.get_credentials.return_value = i
s1 = mock.Mock()
s2 = mock.Mock()
provider = mock.Mock()
provider.create_servers = mock.Mock(return_value=[s1, s2])
get_provider.return_value = provider
with mock.patch.object(self.provider, 'resources') as m_resources:
servers = self.provider.create_servers()
self.provider.create_servers()
self.assertEqual(fake_sos, servers)
configs = [
create_config('192.168.0.10/24', 'fakeuuid'),
create_config('192.168.0.11/24', 'fakeuuid-1'),
create_config('192.168.0.12/24', 'fakeuuid-2'),
create_config('192.168.0.13/24', 'fakeuuid'),
create_config('192.168.0.14/24', 'fakeuuid-1'),
create_config('192.168.0.15/24', 'fakeuuid-2'),
info1 = {'host': {'ip': 'f1'},
'config': {'netwrork': 'fake-1'},
'container_names': ['c-1-1', 'c-1-2']}
info2 = {'host': {'ip': 'f2'},
'config': {'netwrork': 'fake-2'},
'container_names': ['c-2-1', 'c-2-2']}
resource_calls = [
mock.call.create(info1),
mock.call.create(info2),
]
self.assertEqual(resource_calls, m_resources.mock_calls)
call = mock.call
host1_calls = [
call.prepare(),
call.create_container('rally-lxc-000-10-1-1-0', 'ubuntu'),
call.create_clone('rally-lxc-001-10-1-1-0',
'rally-lxc-000-10-1-1-0'),
call.start_containers(),
call.get_server_objects(),
call.server.get_credentials(),
]
host2_calls = [
call.prepare(),
call.create_container('rally-lxc-000-10-1-1-8', 'ubuntu'),
call.create_clone('rally-lxc-001-10-1-1-8',
'rally-lxc-000-10-1-1-8'),
call.start_containers(),
call.get_server_objects(),
call.server.get_credentials(),
]
mock_lxc_container.assert_has_calls(
[mock.call(*a) for a in zip(3 * [s1] + 3 * [s2], configs)])
self.assertEqual(host1_calls, fake_hosts[0].mock_calls)
self.assertEqual(host2_calls, fake_hosts[1].mock_calls)
for mock_cont_i in mock_first_conts:
mock_cont_i.assert_has_calls([
mock.call.prepare_host(),
mock.call.create('ubuntu'),
mock.call.server.get_credentials(),
mock.call.start(),
mock.call.server.ssh.wait(),
])
for mock_cont_i in mock_conts:
mock_cont_i.assert_has_calls([
mock.call.clone('fakeuuid'),
mock.call.start(),
mock.call.server.get_credentials(),
mock.call.server.ssh.wait(),
])
get_provider.assert_called_once_with(self.config['host_provider'],
self.mock_deployment)
self.provider.resources.create.assert_has_calls(
[mock.call({'config': a[0], 'server': a[1]})
for a in zip(configs, range(6))])
@mock.patch(MOD_NAME + 'LxcHost')
@mock.patch(MOD_NAME + 'provider.Server.from_credentials')
def test_destroy_servers(self, m_fc, m_lxchost):
fake_resource = {'info': {'config': 'fake_config',
'host': 'fake_credentials',
'container_names': ['n1', 'n2']}}
fake_resource['id'] = 'fake_res_id'
fake_host = mock.Mock()
m_fc.return_value = 'fake_server'
m_lxchost.return_value = fake_host
self.provider.resources = mock.Mock()
self.provider.resources.get_all.return_value = [fake_resource]
@mock.patch(MOD_NAME + 'provider.ProviderFactory.get_provider')
@mock.patch(MOD_NAME + 'provider.Server')
@mock.patch(MOD_NAME + 'LxcContainer')
def test_destroy_servers(self, mock_lxc_container, mock_server,
mock_get_provider):
mock_lxc_container.return_value = mock_container = mock.Mock()
mock_get_provider.return_value = mock_provider = mock.Mock()
resource = {
'info': {
'config': 'fakeconfig',
'server': 'fakeserver0',
},
}
self.provider.resources.get_all.return_value = [resource]
mock_server.from_credentials.return_value = 'fakeserver1'
self.provider.destroy_servers()
mock_server.from_credentials.assert_called_once_with('fakeserver0')
mock_lxc_container.assert_called_once_with('fakeserver1', 'fakeconfig')
mock_container.assert_has_calls([
mock.call.stop(),
mock.call.destroy(),
])
self.provider.resources.delete.asert_called_once_with(resource)
mock_get_provider.assert_called_once_with(self.config['host_provider'],
self.mock_deployment)
mock_provider.assert_has_calls([
mock.call.destroy_servers(),
])
m_lxchost.assert_called_once_with('fake_server', 'fake_config')
host_calls = [mock.call.destroy_containers(),
mock.call.delete_tunnels()]
self.assertEqual(host_calls, fake_host.mock_calls)
self.provider.resources.delete.assert_called_once_with('fake_res_id')