479 lines
17 KiB
Python
479 lines
17 KiB
Python
# Copyright 2014 DreamHost, LLC
|
|
#
|
|
# Author: DreamHost, LLC
|
|
#
|
|
# 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
|
|
import tempfile
|
|
|
|
import mock
|
|
|
|
import unittest2 as unittest
|
|
|
|
from akanda.rug import commands
|
|
from akanda.rug import event
|
|
from akanda.rug import notifications
|
|
from akanda.rug import vm_manager
|
|
from akanda.rug import worker
|
|
|
|
|
|
class TestCreatingRouter(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
super(TestCreatingRouter, self).setUp()
|
|
|
|
self.conf = mock.patch.object(vm_manager.cfg, 'CONF').start()
|
|
self.conf.boot_timeout = 1
|
|
self.conf.akanda_mgt_service_port = 5000
|
|
self.conf.max_retries = 3
|
|
self.conf.management_prefix = 'fdca:3ba5:a17a:acda::/64'
|
|
|
|
mock.patch('akanda.rug.worker.nova').start()
|
|
mock.patch('akanda.rug.worker.quantum').start()
|
|
|
|
self.addCleanup(mock.patch.stopall)
|
|
|
|
self.w = worker.Worker(0, mock.Mock())
|
|
self.tenant_id = '98dd9c41-d3ac-4fd6-8927-567afa0b8fc3'
|
|
self.router_id = 'ac194fc5-f317-412e-8611-fb290629f624'
|
|
self.msg = event.Event(
|
|
tenant_id=self.tenant_id,
|
|
router_id=self.router_id,
|
|
crud=event.CREATE,
|
|
body={'key': 'value'},
|
|
)
|
|
self.w.handle_message(self.tenant_id, self.msg)
|
|
|
|
def tearDown(self):
|
|
self.w._shutdown()
|
|
super(TestCreatingRouter, self).tearDown()
|
|
|
|
def test_in_tenant_managers(self):
|
|
self.assertIn(self.tenant_id, self.w.tenant_managers)
|
|
trm = self.w.tenant_managers[self.tenant_id]
|
|
self.assertEqual(self.tenant_id, trm.tenant_id)
|
|
|
|
def test_message_enqueued(self):
|
|
trm = self.w.tenant_managers[self.tenant_id]
|
|
sm = trm.get_state_machines(self.msg, worker.WorkerContext())[0]
|
|
self.assertEqual(1, len(sm._queue))
|
|
|
|
|
|
class TestWildcardMessages(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
super(TestWildcardMessages, self).setUp()
|
|
|
|
self.conf = mock.patch.object(vm_manager.cfg, 'CONF').start()
|
|
self.conf.boot_timeout = 1
|
|
self.conf.akanda_mgt_service_port = 5000
|
|
self.conf.max_retries = 3
|
|
self.conf.management_prefix = 'fdca:3ba5:a17a:acda::/64'
|
|
|
|
mock.patch('akanda.rug.worker.nova').start()
|
|
mock.patch('akanda.rug.worker.quantum').start()
|
|
|
|
self.addCleanup(mock.patch.stopall)
|
|
|
|
self.w = worker.Worker(0, mock.Mock())
|
|
# Create some tenants
|
|
for msg in [
|
|
event.Event(
|
|
tenant_id='98dd9c41-d3ac-4fd6-8927-567afa0b8fc3',
|
|
router_id='ABCD',
|
|
crud=event.CREATE,
|
|
body={'key': 'value'},
|
|
),
|
|
event.Event(
|
|
tenant_id='ac194fc5-f317-412e-8611-fb290629f624',
|
|
router_id='EFGH',
|
|
crud=event.CREATE,
|
|
body={'key': 'value'},
|
|
)]:
|
|
self.w.handle_message(msg.tenant_id, msg)
|
|
|
|
def tearDown(self):
|
|
self.w._shutdown()
|
|
super(TestWildcardMessages, self).tearDown()
|
|
|
|
def test_wildcard_to_all(self):
|
|
trms = self.w._get_trms('*')
|
|
ids = sorted(trm.tenant_id for trm in trms)
|
|
self.assertEqual(['98dd9c41-d3ac-4fd6-8927-567afa0b8fc3',
|
|
'ac194fc5-f317-412e-8611-fb290629f624'],
|
|
ids)
|
|
|
|
|
|
class TestShutdown(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
super(TestShutdown, self).setUp()
|
|
mock.patch('akanda.rug.worker.nova').start()
|
|
mock.patch('akanda.rug.worker.quantum').start()
|
|
self.addCleanup(mock.patch.stopall)
|
|
|
|
def test_shutdown_on_null_message(self):
|
|
self.w = worker.Worker(0, mock.Mock())
|
|
with mock.patch.object(self.w, '_shutdown') as meth:
|
|
self.w.handle_message(None, None)
|
|
meth.assert_called_once_with()
|
|
|
|
def test_stop_threads(self):
|
|
self.w = worker.Worker(1, mock.Mock())
|
|
original_queue = self.w.work_queue
|
|
self.assertTrue(self.w._keep_going)
|
|
self.w._shutdown()
|
|
self.assertFalse(self.w._keep_going)
|
|
new_queue = self.w.work_queue
|
|
self.assertIsNot(original_queue, new_queue)
|
|
|
|
@mock.patch('kombu.connection.BrokerConnection')
|
|
@mock.patch('kombu.entity.Exchange')
|
|
@mock.patch('kombu.Producer')
|
|
def test_stop_threads_notifier(self, producer, exchange, broker):
|
|
notifier = notifications.Publisher('url', 'quantum', 'topic')
|
|
self.w = worker.Worker(0, notifier)
|
|
self.assertTrue(self.w.notifier._t)
|
|
self.w._shutdown()
|
|
self.assertFalse(self.w.notifier._t)
|
|
|
|
|
|
class TestUpdateStateMachine(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
super(TestUpdateStateMachine, self).setUp()
|
|
|
|
self.conf = mock.patch.object(vm_manager.cfg, 'CONF').start()
|
|
self.conf.boot_timeout = 1
|
|
self.conf.akanda_mgt_service_port = 5000
|
|
self.conf.max_retries = 3
|
|
self.conf.management_prefix = 'fdca:3ba5:a17a:acda::/64'
|
|
|
|
mock.patch('akanda.rug.worker.nova').start()
|
|
mock.patch('akanda.rug.worker.quantum').start()
|
|
|
|
self.worker_context = worker.WorkerContext()
|
|
|
|
self.addCleanup(mock.patch.stopall)
|
|
|
|
def test(self):
|
|
w = worker.Worker(0, mock.Mock())
|
|
tenant_id = '98dd9c41-d3ac-4fd6-8927-567afa0b8fc3'
|
|
router_id = 'ac194fc5-f317-412e-8611-fb290629f624'
|
|
msg = event.Event(
|
|
tenant_id=tenant_id,
|
|
router_id=router_id,
|
|
crud=event.CREATE,
|
|
body={'key': 'value'},
|
|
)
|
|
# Create the router manager and state machine so we can
|
|
# replace the update() method with a mock.
|
|
trm = w._get_trms(tenant_id)[0]
|
|
sm = trm.get_state_machines(msg, self.worker_context)[0]
|
|
with mock.patch.object(sm, 'update') as meth:
|
|
w.handle_message(tenant_id, msg)
|
|
# Add a null message so the worker loop will exit. We have
|
|
# to do this directly, because if we do it through
|
|
# handle_message() that triggers shutdown logic that keeps
|
|
# the loop from working properly.
|
|
w.work_queue.put(None)
|
|
# We aren't using threads (and we trust that threads do
|
|
# work) so we just invoke the thread target ourselves to
|
|
# pretend.
|
|
used_context = w._thread_target()
|
|
meth.assert_called_once_with(used_context)
|
|
|
|
|
|
class TestReportStatus(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
super(TestReportStatus, self).setUp()
|
|
mock.patch('akanda.rug.worker.nova').start()
|
|
mock.patch('akanda.rug.worker.quantum').start()
|
|
self.addCleanup(mock.patch.stopall)
|
|
|
|
def test_report_status_dispatched(self):
|
|
self.w = worker.Worker(0, mock.Mock())
|
|
with mock.patch.object(self.w, 'report_status') as meth:
|
|
self.w.handle_message(
|
|
'debug',
|
|
event.Event('*', '', event.COMMAND,
|
|
{'payload': {'command': commands.WORKERS_DEBUG}})
|
|
)
|
|
meth.assert_called_once_with()
|
|
|
|
def test_handle_message_report_status(self):
|
|
self.w = worker.Worker(0, mock.Mock())
|
|
with mock.patch('akanda.rug.worker.cfg.CONF') as conf:
|
|
self.w.handle_message(
|
|
'debug',
|
|
event.Event('*', '', event.COMMAND,
|
|
{'payload': {'command': commands.WORKERS_DEBUG}})
|
|
)
|
|
self.assertTrue(conf.log_opt_values.called)
|
|
|
|
|
|
class TestDebugRouters(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
super(TestDebugRouters, self).setUp()
|
|
|
|
self.conf = mock.patch.object(vm_manager.cfg, 'CONF').start()
|
|
self.conf.boot_timeout = 1
|
|
self.conf.akanda_mgt_service_port = 5000
|
|
self.conf.max_retries = 3
|
|
self.conf.management_prefix = 'fdca:3ba5:a17a:acda::/64'
|
|
|
|
mock.patch('akanda.rug.worker.nova').start()
|
|
mock.patch('akanda.rug.worker.quantum').start()
|
|
|
|
self.w = worker.Worker(0, mock.Mock())
|
|
|
|
self.addCleanup(mock.patch.stopall)
|
|
|
|
def testNoDebugs(self):
|
|
self.assertEqual(set(), self.w._debug_routers)
|
|
|
|
def testWithDebugs(self):
|
|
self.w.handle_message(
|
|
'*',
|
|
event.Event('*', '', event.COMMAND,
|
|
{'payload': {'command': commands.ROUTER_DEBUG,
|
|
'router_id': 'this-router-id'}}),
|
|
)
|
|
self.assertEqual(set(['this-router-id']), self.w._debug_routers)
|
|
|
|
def testManage(self):
|
|
self.w._debug_routers = set(['this-router-id'])
|
|
self.w.handle_message(
|
|
'*',
|
|
event.Event('*', '', event.COMMAND,
|
|
{'payload': {'command': commands.ROUTER_MANAGE,
|
|
'router_id': 'this-router-id'}}),
|
|
)
|
|
self.assertEqual(set(), self.w._debug_routers)
|
|
|
|
def testDebugging(self):
|
|
self.w._debug_routers = set(['ac194fc5-f317-412e-8611-fb290629f624'])
|
|
|
|
tenant_id = '98dd9c41-d3ac-4fd6-8927-567afa0b8fc3'
|
|
router_id = 'ac194fc5-f317-412e-8611-fb290629f624'
|
|
msg = event.Event(
|
|
tenant_id=tenant_id,
|
|
router_id=router_id,
|
|
crud=event.CREATE,
|
|
body={'key': 'value'},
|
|
)
|
|
# Create the router manager and state machine so we can
|
|
# replace the send_message() method with a mock.
|
|
trm = self.w._get_trms(tenant_id)[0]
|
|
sm = trm.get_state_machines(msg, worker.WorkerContext())[0]
|
|
with mock.patch.object(sm, 'send_message') as meth:
|
|
# The router id is being ignored, so the send_message()
|
|
# method shouldn't ever be invoked.
|
|
meth.side_effect = AssertionError('send_message was called')
|
|
self.w.handle_message(tenant_id, msg)
|
|
|
|
|
|
class TestIgnoreRouters(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
super(TestIgnoreRouters, self).setUp()
|
|
|
|
self.conf = mock.patch.object(vm_manager.cfg, 'CONF').start()
|
|
self.conf.boot_timeout = 1
|
|
self.conf.akanda_mgt_service_port = 5000
|
|
self.conf.max_retries = 3
|
|
self.conf.management_prefix = 'fdca:3ba5:a17a:acda::/64'
|
|
|
|
mock.patch('akanda.rug.worker.nova').start()
|
|
mock.patch('akanda.rug.worker.quantum').start()
|
|
|
|
self.addCleanup(mock.patch.stopall)
|
|
|
|
def testNoIgnorePath(self):
|
|
w = worker.Worker(0, mock.Mock(), ignore_directory=None)
|
|
ignored = w._get_routers_to_ignore()
|
|
self.assertEqual(set(), ignored)
|
|
|
|
def testNoIgnores(self):
|
|
tmpdir = tempfile.mkdtemp()
|
|
self.addCleanup(lambda: os.rmdir(tmpdir))
|
|
w = worker.Worker(0, mock.Mock(), ignore_directory=tmpdir)
|
|
ignored = w._get_routers_to_ignore()
|
|
self.assertEqual(set(), ignored)
|
|
|
|
def testWithIgnores(self):
|
|
tmpdir = tempfile.mkdtemp()
|
|
fullname = os.path.join(tmpdir, 'this-router-id')
|
|
with open(fullname, 'a'):
|
|
os.utime(fullname, None)
|
|
self.addCleanup(lambda: os.unlink(fullname) and os.rmdir(tmpdir))
|
|
w = worker.Worker(0, mock.Mock(), ignore_directory=tmpdir)
|
|
ignored = w._get_routers_to_ignore()
|
|
self.assertEqual(set(['this-router-id']), ignored)
|
|
|
|
def testManage(self):
|
|
w = worker.Worker(0, mock.Mock())
|
|
w._debug_routers = set(['this-router-id'])
|
|
w.handle_message(
|
|
'*',
|
|
event.Event('*', '', event.COMMAND,
|
|
{'payload': {'command': commands.ROUTER_MANAGE,
|
|
'router_id': 'this-router-id'}}),
|
|
)
|
|
self.assertEqual(set(), w._debug_routers)
|
|
|
|
def testIgnoring(self):
|
|
tmpdir = tempfile.mkdtemp()
|
|
fullname = os.path.join(tmpdir, 'ac194fc5-f317-412e-8611-fb290629f624')
|
|
with open(fullname, 'a'):
|
|
os.utime(fullname, None)
|
|
self.addCleanup(lambda: os.unlink(fullname) and os.rmdir(tmpdir))
|
|
w = worker.Worker(0, mock.Mock(), ignore_directory=tmpdir)
|
|
|
|
tenant_id = '98dd9c41-d3ac-4fd6-8927-567afa0b8fc3'
|
|
router_id = 'ac194fc5-f317-412e-8611-fb290629f624'
|
|
msg = event.Event(
|
|
tenant_id=tenant_id,
|
|
router_id=router_id,
|
|
crud=event.CREATE,
|
|
body={'key': 'value'},
|
|
)
|
|
# Create the router manager and state machine so we can
|
|
# replace the send_message() method with a mock.
|
|
trm = w._get_trms(tenant_id)[0]
|
|
sm = trm.get_state_machines(msg, worker.WorkerContext())[0]
|
|
with mock.patch.object(sm, 'send_message') as meth:
|
|
# The router id is being ignored, so the send_message()
|
|
# method shouldn't ever be invoked.
|
|
meth.side_effect = AssertionError('send_message was called')
|
|
w.handle_message(tenant_id, msg)
|
|
|
|
|
|
class TestDebugTenants(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
super(TestDebugTenants, self).setUp()
|
|
|
|
self.conf = mock.patch.object(vm_manager.cfg, 'CONF').start()
|
|
self.conf.boot_timeout = 1
|
|
self.conf.akanda_mgt_service_port = 5000
|
|
self.conf.max_retries = 3
|
|
self.conf.management_prefix = 'fdca:3ba5:a17a:acda::/64'
|
|
|
|
mock.patch('akanda.rug.worker.nova').start()
|
|
mock.patch('akanda.rug.worker.quantum').start()
|
|
|
|
self.w = worker.Worker(0, mock.Mock())
|
|
|
|
self.addCleanup(mock.patch.stopall)
|
|
|
|
def testNoDebugs(self):
|
|
self.assertEqual(set(), self.w._debug_tenants)
|
|
|
|
def testWithDebugs(self):
|
|
self.w.handle_message(
|
|
'*',
|
|
event.Event('*', '', event.COMMAND,
|
|
{'payload': {'command': commands.TENANT_DEBUG,
|
|
'tenant_id': 'this-tenant-id'}}),
|
|
)
|
|
self.assertEqual(set(['this-tenant-id']), self.w._debug_tenants)
|
|
|
|
def testManage(self):
|
|
self.w._debug_tenants = set(['this-tenant-id'])
|
|
self.w.handle_message(
|
|
'*',
|
|
event.Event('*', '', event.COMMAND,
|
|
{'payload': {'command': commands.TENANT_MANAGE,
|
|
'tenant_id': 'this-tenant-id'}}),
|
|
)
|
|
self.assertEqual(set(), self.w._debug_tenants)
|
|
|
|
def testDebugging(self):
|
|
self.w._debug_tenants = set(['98dd9c41-d3ac-4fd6-8927-567afa0b8fc3'])
|
|
|
|
tenant_id = '98dd9c41-d3ac-4fd6-8927-567afa0b8fc3'
|
|
router_id = 'ac194fc5-f317-412e-8611-fb290629f624'
|
|
msg = event.Event(
|
|
tenant_id=tenant_id,
|
|
router_id=router_id,
|
|
crud=event.CREATE,
|
|
body={'key': 'value'},
|
|
)
|
|
# Create the router manager and state machine so we can
|
|
# replace the send_message() method with a mock.
|
|
trm = self.w._get_trms(tenant_id)[0]
|
|
sm = trm.get_state_machines(msg, worker.WorkerContext())[0]
|
|
with mock.patch.object(sm, 'send_message') as meth:
|
|
# The tenant id is being ignored, so the send_message()
|
|
# method shouldn't ever be invoked.
|
|
meth.side_effect = AssertionError('send_message was called')
|
|
self.w.handle_message(tenant_id, msg)
|
|
|
|
|
|
class TestConfigReload(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
super(TestConfigReload, self).setUp()
|
|
|
|
self.conf = mock.patch.object(worker.cfg, 'CONF').start()
|
|
self.conf.boot_timeout = 1
|
|
self.conf.akanda_mgt_service_port = 5000
|
|
self.conf.max_retries = 3
|
|
self.conf.management_prefix = 'fdca:3ba5:a17a:acda::/64'
|
|
|
|
mock.patch('akanda.rug.worker.nova').start()
|
|
mock.patch('akanda.rug.worker.quantum').start()
|
|
|
|
self.w = worker.Worker(0, mock.Mock())
|
|
|
|
self.addCleanup(mock.patch.stopall)
|
|
|
|
def test(self):
|
|
tenant_id = '*'
|
|
router_id = '*'
|
|
msg = event.Event(
|
|
tenant_id=tenant_id,
|
|
router_id=router_id,
|
|
crud=event.COMMAND,
|
|
body={
|
|
'payload': {'command': commands.CONFIG_RELOAD},
|
|
},
|
|
)
|
|
self.w.handle_message(tenant_id, msg)
|
|
self.assertTrue(self.conf.called)
|
|
self.assertTrue(self.conf.log_opt_values.called)
|
|
|
|
|
|
class TestNormalizeUUID(unittest.TestCase):
|
|
|
|
def test_upper(self):
|
|
self.assertEqual(
|
|
'ac194fc5-f317-412e-8611-fb290629f624',
|
|
worker._normalize_uuid(
|
|
'ac194fc5-f317-412e-8611-fb290629f624'.upper()
|
|
)
|
|
)
|
|
|
|
def test_no_dashes(self):
|
|
self.assertEqual(
|
|
'ac194fc5-f317-412e-8611-fb290629f624',
|
|
worker._normalize_uuid(
|
|
'ac194fc5f317412e8611fb290629f624'
|
|
)
|
|
)
|