separate management port and used cloud init

This change cuts the ties between the ports and the service VM and
enable future VRRP support, but creating VM specific ports.

This updates unit tests to pass with recent Kilo refactoring and
enhancements.  A few irrelevant tests were removed, specifically
those that tested a specific state where routers have no management
port, which is no longer a possible state.

Implements blueprint: ci-updates
Co-Authored-By: Adam Gandelman<adamg@ubuntu.com>
Change-Id: I8a78b487df0d49fffd7a924f170d26147864994b
This commit is contained in:
Mark McClain 2015-02-27 03:40:46 -05:00
parent 34f781c228
commit dc37af313b
10 changed files with 699 additions and 698 deletions

View File

@ -43,10 +43,15 @@ SERVICE_DHCP = 'dhcp'
SERVICE_RA = 'ra' SERVICE_RA = 'ra'
def build_config(client, router, interfaces): def build_config(client, router, management_port, interfaces):
provider_rules = load_provider_rules(cfg.CONF.provider_rules_path) provider_rules = load_provider_rules(cfg.CONF.provider_rules_path)
networks = generate_network_config(client, router, interfaces) networks = generate_network_config(
client,
router,
management_port,
interfaces
)
gateway = get_default_v4_gateway(client, router, networks) gateway = get_default_v4_gateway(client, router, networks)
return { return {
@ -110,26 +115,26 @@ def load_provider_rules(path):
LOG.exception('unable to open provider rules: %s' % path) LOG.exception('unable to open provider rules: %s' % path)
def generate_network_config(client, router, interfaces): def generate_network_config(client, router, management_port, iface_map):
iface_map = dict((i['lladdr'], i['ifname']) for i in interfaces)
retval = [ retval = [
_network_config( _network_config(
client, client,
router.external_port, router.external_port,
iface_map[router.external_port.mac_address], iface_map[router.external_port.network_id],
EXTERNAL_NET), EXTERNAL_NET),
_management_network_config( _network_config(
router.management_port, client,
iface_map[router.management_port.mac_address], management_port,
interfaces, iface_map[management_port.network_id],
)] MANAGEMENT_NET
)
]
retval.extend( retval.extend(
_network_config( _network_config(
client, client,
p, p,
iface_map[p.mac_address], iface_map[p.network_id],
INTERNAL_NET, INTERNAL_NET,
client.get_network_ports(p.network_id)) client.get_network_ports(p.network_id))
for p in router.internal_ports) for p in router.internal_ports)
@ -193,6 +198,9 @@ def _allocation_config(ports, subnets_dict):
allocations = [] allocations = []
for port in ports: for port in ports:
if port.name.startswith('AKANDA:VRRP:'):
continue
addrs = { addrs = {
str(fixed.ip_address): subnets_dict[fixed.subnet_id].enable_dhcp str(fixed.ip_address): subnets_dict[fixed.subnet_id].enable_dhcp
for fixed in port.fixed_ips for fixed in port.fixed_ips

View File

@ -71,15 +71,13 @@ class MissingIPAllocation(Exception):
class Router(object): class Router(object):
def __init__(self, id_, tenant_id, name, admin_state_up, status, def __init__(self, id_, tenant_id, name, admin_state_up, status,
external_port=None, internal_ports=None, external_port=None, internal_ports=None, floating_ips=None):
management_port=None, floating_ips=None):
self.id = id_ self.id = id_
self.tenant_id = tenant_id self.tenant_id = tenant_id
self.name = name self.name = name
self.admin_state_up = admin_state_up self.admin_state_up = admin_state_up
self.status = status self.status = status
self.external_port = external_port self.external_port = external_port
self.management_port = management_port
self.internal_ports = internal_ports or [] self.internal_ports = internal_ports or []
self.floating_ips = floating_ips or [] self.floating_ips = floating_ips or []
@ -97,16 +95,14 @@ class Router(object):
@classmethod @classmethod
def from_dict(cls, d): def from_dict(cls, d):
external_port = None external_port = None
management_port = None
internal_ports = [] internal_ports = []
for port_dict in d.get('ports', []): if d.get('gw_port'):
external_port = Port.from_dict(d.get('gw_port'))
for port_dict in d.get('_interfaces', []):
port = Port.from_dict(port_dict) port = Port.from_dict(port_dict)
if port.device_owner == DEVICE_OWNER_ROUTER_GW: if port.device_owner == DEVICE_OWNER_ROUTER_INT:
external_port = port
elif port.device_owner == DEVICE_OWNER_ROUTER_MGT:
management_port = port
elif port.device_owner == DEVICE_OWNER_ROUTER_INT:
internal_ports.append(port) internal_ports.append(port)
fips = [FloatingIP.from_dict(fip) for fip in d.get('_floatingips', [])] fips = [FloatingIP.from_dict(fip) for fip in d.get('_floatingips', [])]
@ -119,14 +115,13 @@ class Router(object):
d['status'], d['status'],
external_port, external_port,
internal_ports, internal_ports,
management_port,
floating_ips=fips floating_ips=fips
) )
@property @property
def ports(self): def ports(self):
return itertools.chain( return itertools.chain(
[self.management_port, self.external_port], [self.external_port],
self.internal_ports self.internal_ports
) )
@ -177,13 +172,14 @@ class Subnet(object):
class Port(object): class Port(object):
def __init__(self, id_, device_id='', fixed_ips=None, mac_address='', def __init__(self, id_, device_id='', fixed_ips=None, mac_address='',
network_id='', device_owner=''): network_id='', device_owner='', name=''):
self.id = id_ self.id = id_
self.device_id = device_id self.device_id = device_id
self.fixed_ips = fixed_ips or [] self.fixed_ips = fixed_ips or []
self.mac_address = mac_address self.mac_address = mac_address
self.network_id = network_id self.network_id = network_id
self.device_owner = device_owner self.device_owner = device_owner
self.name = name
def __eq__(self, other): def __eq__(self, other):
return type(self) == type(other) and vars(self) == vars(other) return type(self) == type(other) and vars(self) == vars(other)
@ -204,7 +200,8 @@ class Port(object):
fixed_ips=[FixedIp.from_dict(fip) for fip in d['fixed_ips']], fixed_ips=[FixedIp.from_dict(fip) for fip in d['fixed_ips']],
mac_address=d['mac_address'], mac_address=d['mac_address'],
network_id=d['network_id'], network_id=d['network_id'],
device_owner=d['device_owner']) device_owner=d['device_owner'],
name=d['name'])
class FixedIp(object): class FixedIp(object):
@ -326,26 +323,48 @@ class Neutron(object):
network_id, e) network_id, e)
return response return response
def create_router_management_port(self, router_id): def get_ports_for_instance(self, instance_id):
port_dict = dict(admin_state_up=True, ports = self.api_client.list_ports(device_id=instance_id)['ports']
network_id=self.conf.management_network_id,
device_owner=DEVICE_OWNER_ROUTER_MGT mgt_port = None
) intf_ports = []
for port in (Port.from_dict(p) for p in ports):
if port.network_id == self.conf.management_network_id:
mgt_port = port
else:
intf_ports.append(port)
return mgt_port, intf_ports
def create_management_port(self, object_id):
return self.create_vrrp_port(
object_id,
self.conf.management_network_id,
'MGT'
)
def create_vrrp_port(self, object_id, network_id, label='VRRP'):
port_dict = dict(
admin_state_up=True,
network_id=network_id,
name='AKANDA:%s:%s' % (label, object_id),
security_groups=[]
)
if label == 'VRRP':
port_dict['fixed_ips'] = []
response = self.api_client.create_port(dict(port=port_dict)) response = self.api_client.create_port(dict(port=port_dict))
port_data = response.get('port') port_data = response.get('port')
if not port_data: if not port_data:
raise ValueError('No port data found for router %s network %s' % raise ValueError(
(router_id, self.conf.management_network_id)) 'Unable to create %s port for %s on network %s' %
(label, object_id, network_id)
)
port = Port.from_dict(port_data) port = Port.from_dict(port_data)
args = dict(port_id=port.id, owner=DEVICE_OWNER_ROUTER_MGT)
self.api_client.add_interface_router(router_id, args)
return port return port
def delete_router_management_port(self, router_id, port_id):
args = dict(port_id=port_id, owner=DEVICE_OWNER_ROUTER_MGT)
self.api_client.remove_interface_router(router_id, args)
def create_router_external_port(self, router): def create_router_external_port(self, router):
# FIXME: Need to make this smarter in case the switch is full. # FIXME: Need to make this smarter in case the switch is full.
network_args = {'network_id': self.conf.external_network_id} network_args = {'network_id': self.conf.external_network_id}
@ -389,12 +408,13 @@ class Neutron(object):
i, i,
cfg.CONF.max_retries, cfg.CONF.max_retries,
) )
ports = [ query_dict = {
p for p in self.api_client.show_router( 'device_owner': DEVICE_OWNER_ROUTER_GW,
router.id 'device_id': router.id,
)['router']['ports'] 'network_id': self.conf.external_network_id
if p['network_id'] == self.conf.external_network_id }
] ports = self.api_client.list_ports(**query_dict)['ports']
if len(ports): if len(ports):
port = Port.from_dict(ports[0]) port = Port.from_dict(ports[0])
LOG.debug('Found router external port: %s' % port.id) LOG.debug('Found router external port: %s' % port.id)
@ -409,8 +429,11 @@ class Neutron(object):
host_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, socket.gethostname())) host_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, socket.gethostname()))
name = 'AKANDA:RUG:%s' % network_type.upper()
query_dict = dict(device_owner=DEVICE_OWNER_RUG, query_dict = dict(device_owner=DEVICE_OWNER_RUG,
device_id=host_id, device_id=host_id,
name=name,
network_id=network_id) network_id=network_id)
ports = self.api_client.list_ports(**query_dict)['ports'] ports = self.api_client.list_ports(**query_dict)['ports']
@ -425,6 +448,7 @@ class Neutron(object):
'admin_state_up': True, 'admin_state_up': True,
'network_id': network_id, 'network_id': network_id,
'device_owner': DEVICE_OWNER_ROUTER_INT, # lying here for IP 'device_owner': DEVICE_OWNER_ROUTER_INT, # lying here for IP
'name': name,
'device_id': host_id, 'device_id': host_id,
'fixed_ips': [{ 'fixed_ips': [{
'ip_address': ip_address.split('/')[0], 'ip_address': ip_address.split('/')[0],
@ -480,7 +504,11 @@ class Neutron(object):
self.conf self.conf
) )
host_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, socket.gethostname())) host_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, socket.gethostname()))
query_dict = dict(device_owner=DEVICE_OWNER_RUG, device_id=host_id) query_dict = dict(
device_owner=DEVICE_OWNER_RUG,
name='AKANDA:RUG:MANAGEMENT',
device_id=host_id
)
ports = self.api_client.list_ports(**query_dict)['ports'] ports = self.api_client.list_ports(**query_dict)['ports']
if ports: if ports:

View File

@ -14,6 +14,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from datetime import datetime
import logging import logging
from novaclient.v1_1 import client from novaclient.v1_1 import client
@ -22,6 +23,46 @@ from novaclient.v1_1 import client
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class InstanceInfo(object):
def __init__(self, instance_id, name, management_port=None, ports=(),
image_uuid=None, booting=False, last_boot=None):
self.id_ = instance_id
self.name = name
self.image_uuid = image_uuid
self.booting = booting
self.last_boot = datetime.utcnow() if booting else last_boot
self.instance_up = True
self.boot_duration = None
self.nova_status = None
self.management_port = management_port
self._ports = ports
@property
def management_address(self):
return str(self.management_port.fixed_ips[0].ip_address)
@property
def time_since_boot(self):
if self.last_boot:
return datetime.utcnow() - self.last_boot
@property
def ports(self):
return self._ports
@ports.setter
def ports(self, port_list):
self._ports = [p for p in port_list if p != self.management_port]
def confirm_up(self):
if self.booting:
self.booting = False
if self.last_boot:
self.boot_duration = (datetime.utcnow() - self.last_boot)
class Nova(object): class Nova(object):
def __init__(self, conf): def __init__(self, conf):
self.conf = conf self.conf = conf
@ -33,55 +74,127 @@ class Nova(object):
auth_system=conf.auth_strategy, auth_system=conf.auth_strategy,
region_name=conf.auth_region) region_name=conf.auth_region)
def create_router_instance(self, router, router_image_uuid): def create_instance(self, router_id, image_uuid, make_ports_callback):
nics = [{'net-id': p.network_id, 'v4-fixed-ip': '', 'port-id': p.id} mgt_port, instance_ports = make_ports_callback()
for p in router.ports]
nics = [{'net-id': p.network_id, 'v4-fixed-ip': '', 'port-id': p.id}
for p in ([mgt_port] + instance_ports)]
# Sometimes a timing problem makes Nova try to create an akanda
# instance using some ports that haven't been cleaned up yet from
# Neutron. This problem makes the novaclient return an Internal Server
# Error to the rug.
# We can safely ignore this exception because the failed task is going
# to be requeued and executed again later when the ports should be
# finally cleaned up.
LOG.debug('creating vm for router %s with image %s', LOG.debug('creating vm for router %s with image %s',
router.id, router_image_uuid) router_id, image_uuid)
name = 'ak-' + router_id
server = self.client.servers.create( server = self.client.servers.create(
'ak-' + router.id, name,
image=router_image_uuid, image=image_uuid,
flavor=self.conf.router_instance_flavor, flavor=self.conf.router_instance_flavor,
nics=nics) nics=nics,
config_drive=True,
userdata=_format_userdata(mgt_port)
)
instance_info = InstanceInfo(
server.id,
name,
mgt_port,
instance_ports,
image_uuid,
True
)
assert server and server.created assert server and server.created
def get_instance(self, router): instance_info.nova_status = server.status
return instance_info
def get_instance_info_for_obj(self, router_id):
instance = self.get_instance_for_obj(router_id)
if instance:
return InstanceInfo(
instance.id,
instance.name,
image_uuid=instance.image['id']
)
def get_instance_for_obj(self, router_id):
instances = self.client.servers.list( instances = self.client.servers.list(
search_opts=dict(name='ak-' + router.id)) search_opts=dict(name='ak-' + router_id)
)
if instances: if instances:
return instances[0] return instances[0]
else: else:
return None return None
def get_router_instance_status(self, router): def get_instance_by_id(self, instance_id):
instance = self.get_instance(router) return self.client.servers.get(instance_id)
if instance:
return instance.status def destroy_instance(self, instance_info):
if instance_info:
LOG.debug('deleting vm for router %s', instance_info.name)
self.client.servers.delete(instance_info.id_)
def boot_instance(self, prev_instance_info, router_id, router_image_uuid,
make_ports_callback):
instance_info = None
if not prev_instance_info:
instance = self.get_instance_for_obj(router_id)
else: else:
return None instance = self.get_instance_by_id(prev_instance_info.id_)
def destroy_router_instance(self, router): # check to make sure this instance isn't pre-existing
instance = self.get_instance(router)
if instance:
LOG.debug('deleting vm for router %s', router.id)
self.client.servers.delete(instance.id)
def reboot_router_instance(self, router, router_image_uuid):
instance = self.get_instance(router)
if instance: if instance:
if 'BUILD' in instance.status: if 'BUILD' in instance.status:
return True # return the same instance with updated status
prev_instance_info.nova_status = instance.status
return prev_instance_info
self.client.servers.delete(instance.id) self.client.servers.delete(instance_info.id_)
return False return None
self.create_router_instance(router, router_image_uuid)
return True # it is now safe to attempt boot
instance_info = self.create_instance(
router_id,
router_image_uuid,
make_ports_callback
)
return instance_info
# TODO(mark): Convert this to dynamic yaml, proper network prefix and ssh-keys
TEMPLATE = """#cloud-config
cloud_config_modules:
- emit_upstart
- set_hostname
- locale
- set-passwords
- timezone
- disable-ec2-metadata
- runcmd
output: {all: '| tee -a /var/log/cloud-init-output.log'}
debug:
- verbose: true
bootcmd:
- /usr/local/bin/akanda-configure-management %s %s/64
users:
- name: akanda
gecos: Akanda
groups: users
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
passwd: $6$rounds=4096$zxaBh6omTayBSA$rI1.FNliuUl7R2SMdkj7zWv.FBhqGVd1lLYDatJd6MiE9WqEQx0M.o7bLyp5nA0CxV6ahoDb0m8Y5OQMDHx1V/
lock-passwd: false
final_message: "Akanda appliance is running"
""" # noqa
def _format_userdata(mgt_port):
return TEMPLATE % (mgt_port.mac_address, mgt_port.fixed_ips[0].ip_address)

View File

@ -40,12 +40,14 @@ fake_ext_port = FakeModel(
fake_mgt_port = FakeModel( fake_mgt_port = FakeModel(
'2', '2',
name='AKANDA:MGT:foo',
mac_address='aa:bb:cc:cc:bb:aa', mac_address='aa:bb:cc:cc:bb:aa',
network_id='mgt-net', network_id='mgt-net',
device_id='m-m-m-m') device_id='m-m-m-m')
fake_int_port = FakeModel( fake_int_port = FakeModel(
'3', '3',
name='AKANDA:RUG:foo',
mac_address='aa:aa:aa:aa:aa:aa', mac_address='aa:aa:aa:aa:aa:aa',
network_id='int-net', network_id='int-net',
fixed_ips=[FakeModel('', ip_address='192.168.1.1', subnet_id='s1')], fixed_ips=[FakeModel('', ip_address='192.168.1.1', subnet_id='s1')],
@ -53,6 +55,25 @@ fake_int_port = FakeModel(
fake_vm_port = FakeModel( fake_vm_port = FakeModel(
'4', '4',
name='foo',
mac_address='aa:aa:aa:aa:aa:bb',
network_id='int-net',
fixed_ips=[FakeModel('', ip_address='192.168.1.2', subnet_id='s1')],
first_v4='192.168.1.2',
device_id='v-v-v-v')
fake_vm_mgt_port = FakeModel(
'4',
name='AKANDA:MGT:foo',
mac_address='aa:aa:aa:aa:aa:bb',
network_id='int-net',
fixed_ips=[FakeModel('', ip_address='192.168.1.2', subnet_id='s1')],
first_v4='192.168.1.2',
device_id='v-v-v-v')
fake_vm_vrrp_port = FakeModel(
'4',
name='AKANDA:VRRP:foo',
mac_address='aa:aa:aa:aa:aa:bb', mac_address='aa:aa:aa:aa:aa:bb',
network_id='int-net', network_id='int-net',
fixed_ips=[FakeModel('', ip_address='192.168.1.2', subnet_id='s1')], fixed_ips=[FakeModel('', ip_address='192.168.1.2', subnet_id='s1')],
@ -137,7 +158,8 @@ class TestAkandaClient(unittest.TestCase):
mocks['generate_floating_config'].return_value = 'floating_config' mocks['generate_floating_config'].return_value = 'floating_config'
mocks['get_default_v4_gateway'].return_value = 'default_gw' mocks['get_default_v4_gateway'].return_value = 'default_gw'
config = conf_mod.build_config(mock_client, fake_router, ifaces) config = conf_mod.build_config(mock_client, fake_router,
fake_mgt_port, ifaces)
expected = { expected = {
'default_v4_gateway': 'default_gw', 'default_v4_gateway': 'default_gw',
@ -154,7 +176,7 @@ class TestAkandaClient(unittest.TestCase):
mocks['load_provider_rules'].assert_called_once_with('/the/path') mocks['load_provider_rules'].assert_called_once_with('/the/path')
mocks['generate_network_config'].assert_called_once_with( mocks['generate_network_config'].assert_called_once_with(
mock_client, fake_router, ifaces) mock_client, fake_router, fake_mgt_port, ifaces)
def test_load_provider_rules(self): def test_load_provider_rules(self):
rules_dict = {'labels': {}, 'preanchors': [], 'postanchors': []} rules_dict = {'labels': {}, 'preanchors': [], 'postanchors': []}
@ -175,42 +197,38 @@ class TestAkandaClient(unittest.TestCase):
mock_client = mock.Mock() mock_client = mock.Mock()
ifaces = [ iface_map = {
{'ifname': 'ge0', 'lladdr': fake_mgt_port.mac_address}, fake_mgt_port.network_id: 'ge0',
{'ifname': 'ge1', 'lladdr': fake_ext_port.mac_address}, fake_ext_port.network_id: 'ge1',
{'ifname': 'ge2', 'lladdr': fake_int_port.mac_address} fake_int_port.network_id: 'ge2'
] }
with mock.patch.multiple(conf_mod, **methods) as mocks: with mock.patch.multiple(conf_mod, **methods) as mocks:
mocks['_network_config'].return_value = 'configured_network' mocks['_network_config'].return_value = 'configured_network'
mocks['_management_network_config'].return_value = 'mgt_net' mocks['_management_network_config'].return_value = 'mgt_net'
result = conf_mod.generate_network_config( result = conf_mod.generate_network_config(
mock_client, fake_router, ifaces) mock_client, fake_router, fake_mgt_port, iface_map)
expected = [ expected = [
'configured_network', 'configured_network',
'mgt_net', 'configured_network',
'configured_network' 'configured_network'
] ]
self.assertEqual(result, expected) self.assertEqual(result, expected)
mocks['_network_config'].assert_has_calls([ expected_calls = [
mock.call( mock.call(
mock_client, mock_client, fake_router.external_port,
fake_router.external_port, 'ge1', 'external'),
'ge1',
'external'),
mock.call( mock.call(
mock_client, mock_client, fake_router.management_port,
fake_int_port, 'ge0', 'management'),
'ge2', mock.call(
'internal', mock_client, fake_int_port,
mock.ANY)]) 'ge2', 'internal', mock.ANY)]
mocks['_network_config'].assert_has_calls(expected_calls)
mocks['_management_network_config'].assert_called_once_with(
fake_router.management_port, 'ge0', ifaces)
def test_managment_network_config(self): def test_managment_network_config(self):
with mock.patch.object(conf_mod, '_make_network_config_dict') as nc: with mock.patch.object(conf_mod, '_make_network_config_dict') as nc:
@ -348,7 +366,14 @@ class TestAkandaClient(unittest.TestCase):
host_routes={}) host_routes={})
self.assertEqual(conf_mod._subnet_config(sn), expected) self.assertEqual(conf_mod._subnet_config(sn), expected)
def test_allocation_config(self): def test_allocation_config_vrrp(self):
subnets_dict = {fake_subnet.id: fake_subnet}
self.assertEqual(
conf_mod._allocation_config([fake_vm_vrrp_port], subnets_dict),
[]
)
def test_allocation_config_mgt(self):
subnets_dict = {fake_subnet.id: fake_subnet} subnets_dict = {fake_subnet.id: fake_subnet}
expected = [ expected = [
{'mac_address': 'aa:aa:aa:aa:aa:bb', {'mac_address': 'aa:aa:aa:aa:aa:bb',
@ -356,9 +381,8 @@ class TestAkandaClient(unittest.TestCase):
'hostname': '192-168-1-2.local', 'hostname': '192-168-1-2.local',
'device_id': 'v-v-v-v'} 'device_id': 'v-v-v-v'}
] ]
self.assertEqual( self.assertEqual(
conf_mod._allocation_config([fake_vm_port], subnets_dict), conf_mod._allocation_config([fake_vm_mgt_port], subnets_dict),
expected expected
) )

View File

@ -27,16 +27,16 @@ from akanda.rug.api import neutron
class TestuNeutronModels(unittest.TestCase): class TestuNeutronModels(unittest.TestCase):
def test_router(self): def test_router(self):
r = neutron.Router( r = neutron.Router(
'1', 'tenant_id', 'name', True, 'ACTIVE', 'ext', ['int'], 'mgt') '1', 'tenant_id', 'name', True, 'ACTIVE', 'ext', ['int'], ['fip'])
self.assertEqual(r.id, '1') self.assertEqual(r.id, '1')
self.assertEqual(r.tenant_id, 'tenant_id') self.assertEqual(r.tenant_id, 'tenant_id')
self.assertEqual(r.name, 'name') self.assertEqual(r.name, 'name')
self.assertTrue(r.admin_state_up) self.assertTrue(r.admin_state_up)
self.assertEqual(r.status, 'ACTIVE') self.assertEqual(r.status, 'ACTIVE')
self.assertEqual(r.external_port, 'ext') self.assertEqual(r.external_port, 'ext')
self.assertEqual(r.management_port, 'mgt') self.assertEqual(r.floating_ips, ['fip'])
self.assertEqual(r.internal_ports, ['int']) self.assertEqual(r.internal_ports, ['int'])
self.assertEqual(set(['ext', 'mgt', 'int']), set(r.ports)) self.assertEqual(set(['ext', 'int']), set(r.ports))
def test_router_from_dict(self): def test_router_from_dict(self):
p = { p = {
@ -193,6 +193,7 @@ class TestuNeutronModels(unittest.TestCase):
def test_port_model(self): def test_port_model(self):
d = { d = {
'id': '1', 'id': '1',
'name': 'name',
'device_id': 'device_id', 'device_id': 'device_id',
'fixed_ips': [{'ip_address': '192.168.1.1', 'subnet_id': 'sub1'}], 'fixed_ips': [{'ip_address': '192.168.1.1', 'subnet_id': 'sub1'}],
'mac_address': 'aa:bb:cc:dd:ee:ff', 'mac_address': 'aa:bb:cc:dd:ee:ff',
@ -371,6 +372,9 @@ class TestExternalPort(unittest.TestCase):
def test_create(self, client_wrapper): def test_create(self, client_wrapper):
mock_client = mock.Mock() mock_client = mock.Mock()
mock_client.show_router.return_value = {'router': self.ROUTER} mock_client.show_router.return_value = {'router': self.ROUTER}
mock_client.list_ports.return_value = {
'ports': [self.ROUTER['ports'][0]]
}
client_wrapper.return_value = mock_client client_wrapper.return_value = mock_client
neutron_wrapper = neutron.Neutron(self.conf) neutron_wrapper = neutron.Neutron(self.conf)
with mock.patch.object(neutron_wrapper, 'get_network_subnets') as gns: with mock.patch.object(neutron_wrapper, 'get_network_subnets') as gns:
@ -385,6 +389,8 @@ class TestExternalPort(unittest.TestCase):
router = copy.deepcopy(self.ROUTER) router = copy.deepcopy(self.ROUTER)
router['ports'] = [] router['ports'] = []
mock_client.show_router.return_value = {'router': router} mock_client.show_router.return_value = {'router': router}
mock_client.list_ports.return_value = {'ports': []}
client_wrapper.return_value = mock_client client_wrapper.return_value = mock_client
neutron_wrapper = neutron.Neutron(self.conf) neutron_wrapper = neutron.Neutron(self.conf)
with mock.patch.object(neutron_wrapper, 'get_network_subnets') as gns: with mock.patch.object(neutron_wrapper, 'get_network_subnets') as gns:
@ -402,6 +408,10 @@ class TestExternalPort(unittest.TestCase):
router = copy.deepcopy(self.ROUTER) router = copy.deepcopy(self.ROUTER)
del router['ports'][0]['fixed_ips'][0] del router['ports'][0]['fixed_ips'][0]
mock_client.list_ports.return_value = {
'ports': [router['ports'][0]]
}
mock_client.show_router.return_value = {'router': router} mock_client.show_router.return_value = {'router': router}
client_wrapper.return_value = mock_client client_wrapper.return_value = mock_client
neutron_wrapper = neutron.Neutron(self.conf) neutron_wrapper = neutron.Neutron(self.conf)
@ -421,6 +431,10 @@ class TestExternalPort(unittest.TestCase):
router = copy.deepcopy(self.ROUTER) router = copy.deepcopy(self.ROUTER)
del router['ports'][0]['fixed_ips'][1] del router['ports'][0]['fixed_ips'][1]
mock_client.list_ports.return_value = {
'ports': [router['ports'][0]]
}
mock_client.show_router.return_value = {'router': router} mock_client.show_router.return_value = {'router': router}
client_wrapper.return_value = mock_client client_wrapper.return_value = mock_client
neutron_wrapper = neutron.Neutron(self.conf) neutron_wrapper = neutron.Neutron(self.conf)
@ -441,7 +455,12 @@ class TestExternalPort(unittest.TestCase):
router['ports'][0]['fixed_ips'] = [] router['ports'][0]['fixed_ips'] = []
mock_client.show_router.return_value = {'router': router} mock_client.show_router.return_value = {'router': router}
mock_client.list_ports.return_value = {
'ports': [router['ports'][0]]
}
client_wrapper.return_value = mock_client client_wrapper.return_value = mock_client
neutron_wrapper = neutron.Neutron(self.conf) neutron_wrapper = neutron.Neutron(self.conf)
with mock.patch.object(neutron_wrapper, 'get_network_subnets') as gns: with mock.patch.object(neutron_wrapper, 'get_network_subnets') as gns:
gns.return_value = self.SUBNETS gns.return_value = self.SUBNETS

View File

@ -15,6 +15,7 @@
# under the License. # under the License.
import datetime
import mock import mock
import unittest2 as unittest import unittest2 as unittest
@ -64,6 +65,10 @@ class FakeConf:
router_instance_flavor = 1 router_instance_flavor = 1
def fake_make_ports_callback():
return (fake_mgt_port, [fake_ext_port, fake_int_port])
class TestNovaWrapper(unittest.TestCase): class TestNovaWrapper(unittest.TestCase):
def setUp(self): def setUp(self):
self.addCleanup(mock.patch.stopall) self.addCleanup(mock.patch.stopall)
@ -73,7 +78,19 @@ class TestNovaWrapper(unittest.TestCase):
self.client_cls.return_value = self.client self.client_cls.return_value = self.client
self.nova = nova.Nova(FakeConf) self.nova = nova.Nova(FakeConf)
def test_create_router_instance(self): self.INSTANCE_INFO = nova.InstanceInfo(
instance_id='fake_instance_id',
name='fake_name',
image_uuid='fake_image_id',
booting=False,
last_boot=datetime.datetime.utcnow(),
ports=(fake_ext_port, fake_int_port),
management_port=fake_mgt_port,
)
@mock.patch.object(nova, '_format_userdata')
def test_create_instance(self, mock_userdata):
mock_userdata.return_value = 'fake_userdata'
expected = [ expected = [
mock.call.servers.create( mock.call.servers.create(
'ak-router_id', 'ak-router_id',
@ -87,14 +104,17 @@ class TestNovaWrapper(unittest.TestCase):
'net-id': 'int-net', 'net-id': 'int-net',
'v4-fixed-ip': ''}], 'v4-fixed-ip': ''}],
flavor=1, flavor=1,
image='GLANCE-IMAGE-123' image='GLANCE-IMAGE-123',
config_drive=True,
userdata='fake_userdata',
) )
] ]
self.nova.create_router_instance(fake_router, 'GLANCE-IMAGE-123') self.nova.create_instance(
'router_id', 'GLANCE-IMAGE-123', fake_make_ports_callback)
self.client.assert_has_calls(expected) self.client.assert_has_calls(expected)
def test_get_instance(self): def test_get_instance_for_obj(self):
instance = mock.Mock() instance = mock.Mock()
self.client.servers.list.return_value = [instance] self.client.servers.list.return_value = [instance]
@ -102,87 +122,30 @@ class TestNovaWrapper(unittest.TestCase):
mock.call.servers.list(search_opts={'name': 'ak-router_id'}) mock.call.servers.list(search_opts={'name': 'ak-router_id'})
] ]
result = self.nova.get_instance(fake_router) result = self.nova.get_instance_for_obj('router_id')
self.client.assert_has_calls(expected) self.client.assert_has_calls(expected)
self.assertEqual(result, instance) self.assertEqual(result, instance)
def test_get_instance_not_found(self): def test_get_instance_for_obj_not_found(self):
self.client.servers.list.return_value = [] self.client.servers.list.return_value = []
expected = [ expected = [
mock.call.servers.list(search_opts={'name': 'ak-router_id'}) mock.call.servers.list(search_opts={'name': 'ak-router_id'})
] ]
result = self.nova.get_instance(fake_router) result = self.nova.get_instance_for_obj('router_id')
self.client.assert_has_calls(expected) self.client.assert_has_calls(expected)
self.assertIsNone(result) self.assertIsNone(result)
def test_get_router_instance_status(self): def test_get_instance_by_id(self):
instance = mock.Mock() self.client.servers.get.return_value = 'fake_instance'
instance.status = 'ACTIVE'
self.client.servers.list.return_value = [instance]
expected = [ expected = [
mock.call.servers.list(search_opts={'name': 'ak-router_id'}) mock.call.servers.get('instance_id')
] ]
result = self.nova.get_instance_by_id('instance_id')
result = self.nova.get_router_instance_status(fake_router) self.client.servers.get.assert_has_calls(expected)
self.client.assert_has_calls(expected) self.assertEquals(result, 'fake_instance')
self.assertEqual(result, 'ACTIVE')
def test_get_router_instance_status_not_found(self):
self.client.servers.list.return_value = []
expected = [
mock.call.servers.list(search_opts={'name': 'ak-router_id'})
]
result = self.nova.get_router_instance_status(fake_router)
self.client.assert_has_calls(expected)
self.assertIsNone(result)
def test_destroy_router_instance(self): def test_destroy_router_instance(self):
with mock.patch.object(self.nova, 'get_instance') as get_instance: self.nova.destroy_instance(self.INSTANCE_INFO)
get_instance.return_value.id = 'instance_id' self.client.servers.delete.assert_called_with(self.INSTANCE_INFO.id_)
expected = [
mock.call.servers.delete('instance_id')
]
self.nova.destroy_router_instance(fake_router)
self.client.assert_has_calls(expected)
def test_reboot_router_instance_exists(self):
with mock.patch.object(self.nova, 'get_instance') as get_instance:
get_instance.return_value.id = 'instance_id'
get_instance.return_value.status = 'ACTIVE'
expected = [
mock.call.servers.delete('instance_id'),
]
self.assertFalse(self.nova.reboot_router_instance(
fake_router,
'GLANCE-IMAGE-123'
))
self.client.assert_has_calls(expected)
def test_reboot_router_instance_rebooting(self):
with mock.patch.object(self.nova, 'get_instance') as get_instance:
get_instance.return_value.id = 'instance_id'
get_instance.return_value.status = 'BUILD'
self.nova.reboot_router_instance(fake_router, 'GLANCE-IMAGE-123')
self.assertEqual(self.client.mock_calls, [])
def test_reboot_router_instance_missing(self):
with mock.patch.object(self.nova, 'get_instance') as get_instance:
with mock.patch.object(self.nova, 'create_router_instance') as cr:
get_instance.return_value = None
self.nova.reboot_router_instance(
fake_router,
'GLANCE-IMAGE-123'
)
self.assertEqual(self.client.mock_calls, [])
cr.assert_called_once_with(fake_router, 'GLANCE-IMAGE-123')

View File

@ -21,7 +21,7 @@ import unittest2 as unittest
from datetime import datetime, timedelta from datetime import datetime, timedelta
from akanda.rug import vm_manager from akanda.rug import vm_manager
from akanda.rug.api import neutron from akanda.rug.api import neutron, nova
vm_manager.RETRY_DELAY = 0.4 vm_manager.RETRY_DELAY = 0.4
vm_manager.BOOT_WAIT = 1 vm_manager.BOOT_WAIT = 1
@ -29,6 +29,37 @@ vm_manager.BOOT_WAIT = 1
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class FakeModel(object):
def __init__(self, id_, **kwargs):
self.id = id_
self.__dict__.update(kwargs)
fake_mgt_port = FakeModel(
'1',
mac_address='aa:bb:cc:dd:ee:ff',
network_id='mgt-net',
fixed_ips=[FakeModel('', ip_address='9.9.9.9', subnet_id='s2')])
fake_int_port = FakeModel(
'2',
mac_address='bb:cc:cc:dd:ee:ff',
network_id='int-net',
fixed_ips=[FakeModel('', ip_address='10.10.10.10', subnet_id='s3')])
fake_ext_port = FakeModel(
'3',
mac_address='cc:cc:cc:dd:ee:ff',
network_id='ext-net',
fixed_ips=[FakeModel('', ip_address='192.168.1.1', subnet_id='s4')])
fake_add_port = FakeModel(
'4',
mac_address='aa:bb:cc:dd:ff:ff',
network_id='additional-net',
fixed_ips=[FakeModel('', ip_address='8.8.8.8', subnet_id='s3')])
class TestVmManager(unittest.TestCase): class TestVmManager(unittest.TestCase):
def setUp(self): def setUp(self):
@ -46,9 +77,20 @@ class TestVmManager(unittest.TestCase):
'update_state' 'update_state'
) )
self.INSTANCE_INFO = nova.InstanceInfo(
instance_id='fake_instance_id',
name='fake_name',
image_uuid='fake_image_id',
booting=False,
last_boot=(datetime.utcnow() - timedelta(minutes=15)),
ports=[fake_int_port, fake_ext_port, fake_mgt_port],
management_port=fake_mgt_port,
)
self.mock_update_state = self.update_state_p.start() self.mock_update_state = self.update_state_p.start()
self.vm_mgr = vm_manager.VmManager('the_id', 'tenant_id', self.vm_mgr = vm_manager.VmManager('the_id', 'tenant_id',
self.log, self.ctx) self.log, self.ctx)
self.vm_mgr.instance_info = self.INSTANCE_INFO
mock.patch.object(self.vm_mgr, '_ensure_cache', mock.Mock) mock.patch.object(self.vm_mgr, '_ensure_cache', mock.Mock)
self.next_state = None self.next_state = None
@ -60,20 +102,19 @@ class TestVmManager(unittest.TestCase):
self.mock_update_state.side_effect = next_state self.mock_update_state.side_effect = next_state
@mock.patch('akanda.rug.vm_manager.router_api') @mock.patch('akanda.rug.vm_manager.router_api')
@mock.patch('akanda.rug.vm_manager._get_management_address') def test_update_state_is_alive(self, router_api):
def test_update_state_is_alive(self, get_mgt_addr, router_api):
self.update_state_p.stop() self.update_state_p.stop()
get_mgt_addr.return_value = 'fe80::beef'
router_api.is_alive.return_value = True router_api.is_alive.return_value = True
self.assertEqual(self.vm_mgr.update_state(self.ctx), vm_manager.UP) self.assertEqual(self.vm_mgr.update_state(self.ctx), vm_manager.UP)
router_api.is_alive.assert_called_once_with('fe80::beef', 5000) router_api.is_alive.assert_called_once_with(
self.INSTANCE_INFO.management_address,
self.conf.akanda_mgt_service_port)
@mock.patch('time.sleep', lambda *a: None) @mock.patch('time.sleep', lambda *a: None)
@mock.patch('akanda.rug.vm_manager.router_api') @mock.patch('akanda.rug.vm_manager.router_api')
@mock.patch('akanda.rug.vm_manager._get_management_address')
@mock.patch('akanda.rug.api.configuration.build_config') @mock.patch('akanda.rug.api.configuration.build_config')
def test_router_status_sync(self, config, get_mgt_addr, router_api): def test_router_status_sync(self, config, router_api):
self.update_state_p.stop() self.update_state_p.stop()
router_api.is_alive.return_value = False router_api.is_alive.return_value = False
rtr = mock.sentinel.router rtr = mock.sentinel.router
@ -103,17 +144,10 @@ class TestVmManager(unittest.TestCase):
n.update_router_status.assert_called_once_with('R1', 'ACTIVE') n.update_router_status.assert_called_once_with('R1', 'ACTIVE')
n.update_router_status.reset_mock() n.update_router_status.reset_mock()
# Removing the management port will trigger a reboot
rtr.management_port = None
self.vm_mgr.update_state(self.ctx)
n.update_router_status.assert_called_once_with('R1', 'DOWN')
n.update_router_status.reset_mock()
@mock.patch('time.sleep', lambda *a: None) @mock.patch('time.sleep', lambda *a: None)
@mock.patch('akanda.rug.vm_manager.router_api') @mock.patch('akanda.rug.vm_manager.router_api')
@mock.patch('akanda.rug.vm_manager._get_management_address')
@mock.patch('akanda.rug.api.configuration.build_config') @mock.patch('akanda.rug.api.configuration.build_config')
def test_router_status_caching(self, config, get_mgt_addr, router_api): def test_router_status_caching(self, config, router_api):
self.update_state_p.stop() self.update_state_p.stop()
router_api.is_alive.return_value = False router_api.is_alive.return_value = False
rtr = mock.sentinel.router rtr = mock.sentinel.router
@ -134,11 +168,11 @@ class TestVmManager(unittest.TestCase):
@mock.patch('time.sleep') @mock.patch('time.sleep')
@mock.patch('akanda.rug.vm_manager.router_api') @mock.patch('akanda.rug.vm_manager.router_api')
@mock.patch('akanda.rug.vm_manager._get_management_address') def test_boot_timeout_still_booting(self, router_api, sleep):
def test_boot_timeout_still_booting(self, get_mgt_addr, router_api, sleep): now = datetime.utcnow()
self.vm_mgr.last_boot = datetime.utcnow() self.INSTANCE_INFO.last_boot = now
self.vm_mgr.last_boot = now
self.update_state_p.stop() self.update_state_p.stop()
get_mgt_addr.return_value = 'fe80::beef'
router_api.is_alive.return_value = False router_api.is_alive.return_value = False
self.assertEqual( self.assertEqual(
@ -146,19 +180,17 @@ class TestVmManager(unittest.TestCase):
vm_manager.BOOTING vm_manager.BOOTING
) )
router_api.is_alive.assert_has_calls([ router_api.is_alive.assert_has_calls([
mock.call('fe80::beef', 5000), mock.call(self.INSTANCE_INFO.management_address, 5000),
mock.call('fe80::beef', 5000), mock.call(self.INSTANCE_INFO.management_address, 5000),
mock.call('fe80::beef', 5000) mock.call(self.INSTANCE_INFO.management_address, 5000),
]) ])
@mock.patch('time.sleep') @mock.patch('time.sleep')
@mock.patch('akanda.rug.vm_manager.router_api') @mock.patch('akanda.rug.vm_manager.router_api')
@mock.patch('akanda.rug.vm_manager._get_management_address') def test_boot_timeout_error(self, router_api, sleep):
def test_boot_timeout_error(self, get_mgt_addr, router_api, sleep):
self.vm_mgr.state = vm_manager.ERROR self.vm_mgr.state = vm_manager.ERROR
self.vm_mgr.last_boot = datetime.utcnow() self.vm_mgr.last_boot = datetime.utcnow()
self.update_state_p.stop() self.update_state_p.stop()
get_mgt_addr.return_value = 'fe80::beef'
router_api.is_alive.return_value = False router_api.is_alive.return_value = False
self.assertEqual( self.assertEqual(
@ -166,20 +198,17 @@ class TestVmManager(unittest.TestCase):
vm_manager.ERROR, vm_manager.ERROR,
) )
router_api.is_alive.assert_has_calls([ router_api.is_alive.assert_has_calls([
mock.call('fe80::beef', 5000), mock.call(self.INSTANCE_INFO.management_address, 5000),
mock.call('fe80::beef', 5000), mock.call(self.INSTANCE_INFO.management_address, 5000),
mock.call('fe80::beef', 5000) mock.call(self.INSTANCE_INFO.management_address, 5000),
]) ])
@mock.patch('time.sleep') @mock.patch('time.sleep')
@mock.patch('akanda.rug.vm_manager.router_api') @mock.patch('akanda.rug.vm_manager.router_api')
@mock.patch('akanda.rug.vm_manager._get_management_address') def test_boot_timeout_error_no_last_boot(self, router_api, sleep):
def test_boot_timeout_error_no_last_boot(self, get_mgt_addr, router_api,
sleep):
self.vm_mgr.state = vm_manager.ERROR self.vm_mgr.state = vm_manager.ERROR
self.vm_mgr.last_boot = None self.vm_mgr.last_boot = None
self.update_state_p.stop() self.update_state_p.stop()
get_mgt_addr.return_value = 'fe80::beef'
router_api.is_alive.return_value = False router_api.is_alive.return_value = False
self.assertEqual( self.assertEqual(
@ -187,25 +216,23 @@ class TestVmManager(unittest.TestCase):
vm_manager.ERROR, vm_manager.ERROR,
) )
router_api.is_alive.assert_has_calls([ router_api.is_alive.assert_has_calls([
mock.call('fe80::beef', 5000), mock.call(self.INSTANCE_INFO.management_address, 5000),
mock.call('fe80::beef', 5000), mock.call(self.INSTANCE_INFO.management_address, 5000),
mock.call('fe80::beef', 5000) mock.call(self.INSTANCE_INFO.management_address, 5000),
]) ])
@mock.patch('time.sleep') @mock.patch('time.sleep')
@mock.patch('akanda.rug.vm_manager.router_api') @mock.patch('akanda.rug.vm_manager.router_api')
@mock.patch('akanda.rug.vm_manager._get_management_address') def test_boot_timeout(self, router_api, sleep):
def test_boot_timeout(self, get_mgt_addr, router_api, sleep):
self.vm_mgr.last_boot = datetime.utcnow() - timedelta(minutes=5) self.vm_mgr.last_boot = datetime.utcnow() - timedelta(minutes=5)
self.update_state_p.stop() self.update_state_p.stop()
get_mgt_addr.return_value = 'fe80::beef'
router_api.is_alive.return_value = False router_api.is_alive.return_value = False
self.assertEqual(self.vm_mgr.update_state(self.ctx), vm_manager.DOWN) self.assertEqual(self.vm_mgr.update_state(self.ctx), vm_manager.DOWN)
router_api.is_alive.assert_has_calls([ router_api.is_alive.assert_has_calls([
mock.call('fe80::beef', 5000), mock.call(self.INSTANCE_INFO.management_address, 5000),
mock.call('fe80::beef', 5000), mock.call(self.INSTANCE_INFO.management_address, 5000),
mock.call('fe80::beef', 5000) mock.call(self.INSTANCE_INFO.management_address, 5000),
]) ])
self.vm_mgr.log.info.assert_called_once_with( self.vm_mgr.log.info.assert_called_once_with(
mock.ANY, mock.ANY,
@ -214,25 +241,21 @@ class TestVmManager(unittest.TestCase):
@mock.patch('time.sleep') @mock.patch('time.sleep')
@mock.patch('akanda.rug.vm_manager.router_api') @mock.patch('akanda.rug.vm_manager.router_api')
@mock.patch('akanda.rug.vm_manager._get_management_address') def test_update_state_is_down(self, router_api, sleep):
def test_update_state_is_down(self, get_mgt_addr, router_api, sleep):
self.update_state_p.stop() self.update_state_p.stop()
get_mgt_addr.return_value = 'fe80::beef'
router_api.is_alive.return_value = False router_api.is_alive.return_value = False
self.assertEqual(self.vm_mgr.update_state(self.ctx), vm_manager.DOWN) self.assertEqual(self.vm_mgr.update_state(self.ctx), vm_manager.DOWN)
router_api.is_alive.assert_has_calls([ router_api.is_alive.assert_has_calls([
mock.call('fe80::beef', 5000), mock.call(self.INSTANCE_INFO.management_address, 5000),
mock.call('fe80::beef', 5000), mock.call(self.INSTANCE_INFO.management_address, 5000),
mock.call('fe80::beef', 5000) mock.call(self.INSTANCE_INFO.management_address, 5000),
]) ])
@mock.patch('time.sleep') @mock.patch('time.sleep')
@mock.patch('akanda.rug.vm_manager.router_api') @mock.patch('akanda.rug.vm_manager.router_api')
@mock.patch('akanda.rug.vm_manager._get_management_address') def test_update_state_retry_delay(self, router_api, sleep):
def test_update_state_retry_delay(self, get_mgt_addr, router_api, sleep):
self.update_state_p.stop() self.update_state_p.stop()
get_mgt_addr.return_value = 'fe80::beef'
router_api.is_alive.side_effect = [False, False, True] router_api.is_alive.side_effect = [False, False, True]
max_retries = 5 max_retries = 5
self.conf.max_retries = max_retries self.conf.max_retries = max_retries
@ -243,17 +266,6 @@ class TestVmManager(unittest.TestCase):
mock.call('Alive check failed. Attempt %d of %d', 1, max_retries) mock.call('Alive check failed. Attempt %d of %d', 1, max_retries)
]) ])
@mock.patch('akanda.rug.vm_manager._get_management_address')
def test_update_state_no_mgt_port(self, get_mgt_addr):
with mock.patch.object(self.ctx.neutron, 'get_router_detail') as grd:
r = mock.Mock()
r.management_port = None
grd.return_value = r
get_mgt_addr.side_effect = AssertionError('Should never be called')
self.update_state_p.stop()
self.assertEqual(self.vm_mgr.update_state(self.ctx),
vm_manager.DOWN)
@mock.patch('time.sleep') @mock.patch('time.sleep')
def test_boot_success(self, sleep): def test_boot_success(self, sleep):
self.next_state = vm_manager.UP self.next_state = vm_manager.UP
@ -266,10 +278,8 @@ class TestVmManager(unittest.TestCase):
rtr.ports.__iter__.return_value = [] rtr.ports.__iter__.return_value = []
self.vm_mgr.boot(self.ctx, 'GLANCE-IMAGE-123') self.vm_mgr.boot(self.ctx, 'GLANCE-IMAGE-123')
self.assertEqual(self.vm_mgr.state, vm_manager.BOOTING) # async self.assertEqual(self.vm_mgr.state, vm_manager.BOOTING) # async
self.ctx.nova_client.reboot_router_instance.assert_called_once_with( self.ctx.nova_client.boot_instance.assert_called_once_with(
self.vm_mgr.router_obj, self.INSTANCE_INFO, rtr.id, 'GLANCE-IMAGE-123', mock.ANY)
'GLANCE-IMAGE-123'
)
self.assertEqual(1, self.vm_mgr.attempts) self.assertEqual(1, self.vm_mgr.attempts)
@mock.patch('time.sleep') @mock.patch('time.sleep')
@ -284,10 +294,8 @@ class TestVmManager(unittest.TestCase):
rtr.ports.__iter__.return_value = [] rtr.ports.__iter__.return_value = []
self.vm_mgr.boot(self.ctx, 'GLANCE-IMAGE-123') self.vm_mgr.boot(self.ctx, 'GLANCE-IMAGE-123')
self.assertEqual(self.vm_mgr.state, vm_manager.BOOTING) self.assertEqual(self.vm_mgr.state, vm_manager.BOOTING)
self.ctx.nova_client.reboot_router_instance.assert_called_once_with( self.ctx.nova_client.boot_instance.assert_called_once_with(
self.vm_mgr.router_obj, self.INSTANCE_INFO, rtr.id, 'GLANCE-IMAGE-123', mock.ANY)
'GLANCE-IMAGE-123'
)
self.assertEqual(1, self.vm_mgr.attempts) self.assertEqual(1, self.vm_mgr.attempts)
@mock.patch('time.sleep') @mock.patch('time.sleep')
@ -300,13 +308,11 @@ class TestVmManager(unittest.TestCase):
rtr.ports = mock.MagicMock() rtr.ports = mock.MagicMock()
rtr.ports.__iter__.return_value = [] rtr.ports.__iter__.return_value = []
self.ctx.nova_client.reboot_router_instance.side_effect = RuntimeError self.ctx.nova_client.boot_instance.side_effect = RuntimeError
self.vm_mgr.boot(self.ctx, 'GLANCE-IMAGE-123') self.vm_mgr.boot(self.ctx, 'GLANCE-IMAGE-123')
self.assertEqual(self.vm_mgr.state, vm_manager.DOWN) self.assertEqual(self.vm_mgr.state, vm_manager.DOWN)
self.ctx.nova_client.reboot_router_instance.assert_called_once_with( self.ctx.nova_client.boot_instance.assert_called_once_with(
self.vm_mgr.router_obj, self.INSTANCE_INFO, rtr.id, 'GLANCE-IMAGE-123', mock.ANY)
'GLANCE-IMAGE-123'
)
self.assertEqual(1, self.vm_mgr.attempts) self.assertEqual(1, self.vm_mgr.attempts)
@mock.patch('time.sleep') @mock.patch('time.sleep')
@ -330,16 +336,12 @@ class TestVmManager(unittest.TestCase):
internal_port] internal_port]
self.vm_mgr.boot(self.ctx, 'GLANCE-IMAGE-123') self.vm_mgr.boot(self.ctx, 'GLANCE-IMAGE-123')
self.assertEqual(self.vm_mgr.state, vm_manager.BOOTING) # async self.assertEqual(self.vm_mgr.state, vm_manager.BOOTING) # async
self.ctx.nova_client.reboot_router_instance.assert_called_once_with( self.ctx.nova_client.boot_instance.assert_called_once_with(
self.vm_mgr.router_obj, self.INSTANCE_INFO,
'GLANCE-IMAGE-123' rtr.id,
'GLANCE-IMAGE-123',
mock.ANY, # TODO(adam_g): actually test make_vrrp_ports()
) )
assert self.ctx.neutron.clear_device_id.call_count == 3
self.ctx.neutron.clear_device_id.assert_has_calls([
mock.call(management_port),
mock.call(external_port),
mock.call(internal_port)
], any_order=True)
def test_boot_check_up(self): def test_boot_check_up(self):
with mock.patch.object( with mock.patch.object(
@ -422,10 +424,10 @@ class TestVmManager(unittest.TestCase):
@mock.patch('time.sleep') @mock.patch('time.sleep')
def test_stop_success(self, sleep): def test_stop_success(self, sleep):
self.vm_mgr.state = vm_manager.UP self.vm_mgr.state = vm_manager.UP
self.ctx.nova_client.get_router_instance_status.return_value = None self.ctx.nova_client.get_instance_by_id.return_value = None
self.vm_mgr.stop(self.ctx) self.vm_mgr.stop(self.ctx)
self.ctx.nova_client.destroy_router_instance.assert_called_once_with( self.ctx.nova_client.destroy_instance.assert_called_once_with(
self.vm_mgr.router_obj self.INSTANCE_INFO
) )
self.assertEqual(self.vm_mgr.state, vm_manager.DOWN) self.assertEqual(self.vm_mgr.state, vm_manager.DOWN)
@ -435,8 +437,8 @@ class TestVmManager(unittest.TestCase):
self.ctx.nova_client.get_router_instance_status.return_value = 'UP' self.ctx.nova_client.get_router_instance_status.return_value = 'UP'
self.vm_mgr.stop(self.ctx) self.vm_mgr.stop(self.ctx)
self.assertEqual(self.vm_mgr.state, vm_manager.UP) self.assertEqual(self.vm_mgr.state, vm_manager.UP)
self.ctx.nova_client.destroy_router_instance.assert_called_once_with( self.ctx.nova_client.destroy_instance.assert_called_once_with(
self.vm_mgr.router_obj self.INSTANCE_INFO
) )
self.log.error.assert_called_once_with(mock.ANY, 1) self.log.error.assert_called_once_with(mock.ANY, 1)
@ -444,43 +446,33 @@ class TestVmManager(unittest.TestCase):
def test_stop_router_already_deleted_from_neutron(self, sleep): def test_stop_router_already_deleted_from_neutron(self, sleep):
self.vm_mgr.state = vm_manager.GONE self.vm_mgr.state = vm_manager.GONE
self.vm_mgr.stop(self.ctx) self.vm_mgr.stop(self.ctx)
self.ctx.nova_client.destroy_instance.assert_called_once_with(
# Because the Router object is actually deleted from Neutron at this self.INSTANCE_INFO)
# point, an anonymous "fake" router (with an ID and tenant ID of the
# deleted router) is created. This allows us to pass an expected
# object to the Nova API code to cleans up the orphaned router VM.
args = self.ctx.nova_client.destroy_router_instance.call_args
assert args[0][0].name == 'unnamed'
self.assertEqual(self.vm_mgr.state, vm_manager.GONE) self.assertEqual(self.vm_mgr.state, vm_manager.GONE)
@mock.patch('akanda.rug.vm_manager.router_api') @mock.patch('akanda.rug.vm_manager.router_api')
@mock.patch('akanda.rug.vm_manager._get_management_address')
@mock.patch('akanda.rug.api.configuration.build_config') @mock.patch('akanda.rug.api.configuration.build_config')
def test_configure_success(self, config, get_mgt_addr, router_api): def test_configure_success(self, config, router_api):
get_mgt_addr.return_value = 'fe80::beef'
rtr = mock.sentinel.router rtr = mock.sentinel.router
self.ctx.neutron.get_router_detail.return_value = rtr self.ctx.neutron.get_router_detail.return_value = rtr
config.return_value = 'fake_config'
router_api.get_interfaces.return_value = []
with mock.patch.object(self.vm_mgr, '_verify_interfaces') as verify: with mock.patch.object(self.vm_mgr, '_verify_interfaces') as verify:
verify.return_value = True verify.return_value = True
self.vm_mgr.configure(self.ctx) self.vm_mgr.configure(self.ctx)
interfaces = router_api.get_interfaces.return_value verify.assert_called_once_with(rtr, [])
config.assert_called_once_with(
verify.assert_called_once_with(rtr, interfaces) self.ctx.neutron, rtr, fake_mgt_port, {})
config.assert_called_once_with(self.ctx.neutron, rtr, interfaces)
router_api.update_config.assert_called_once_with( router_api.update_config.assert_called_once_with(
'fe80::beef', self.INSTANCE_INFO.management_address, 5000, 'fake_config',
5000,
config.return_value
) )
self.assertEqual(self.vm_mgr.state, vm_manager.CONFIGURED) self.assertEqual(self.vm_mgr.state, vm_manager.CONFIGURED)
@mock.patch('akanda.rug.vm_manager.router_api') @mock.patch('akanda.rug.vm_manager.router_api')
@mock.patch('akanda.rug.vm_manager._get_management_address') def test_configure_mismatched_interfaces(self, router_api):
def test_configure_mismatched_interfaces(self, get_mgt_addr, router_api):
get_mgt_addr.return_value = 'fe80::beef'
rtr = mock.sentinel.router rtr = mock.sentinel.router
self.neutron.get_router_detail.return_value = rtr self.neutron.get_router_detail.return_value = rtr
@ -498,267 +490,180 @@ class TestVmManager(unittest.TestCase):
@mock.patch('time.sleep') @mock.patch('time.sleep')
@mock.patch('akanda.rug.vm_manager.router_api') @mock.patch('akanda.rug.vm_manager.router_api')
@mock.patch('akanda.rug.vm_manager._get_management_address')
@mock.patch('akanda.rug.api.configuration.build_config') @mock.patch('akanda.rug.api.configuration.build_config')
def test_configure_failure(self, config, get_mgt_addr, router_api, sleep): def test_configure_failure(self, config, router_api, sleep):
get_mgt_addr.return_value = 'fe80::beef'
rtr = {'id': 'the_id'} rtr = {'id': 'the_id'}
self.neutron.get_router_detail.return_value = rtr self.neutron.get_router_detail.return_value = rtr
router_api.update_config.side_effect = Exception router_api.update_config.side_effect = Exception
config.return_value = 'fake_config'
with mock.patch.object(self.vm_mgr, '_verify_interfaces') as verify: with mock.patch.object(self.vm_mgr, '_verify_interfaces') as verify:
verify.return_value = True verify.return_value = True
self.vm_mgr.configure(self.ctx) self.vm_mgr.configure(self.ctx)
interfaces = router_api.get_interfaces.return_value interfaces = router_api.get_interfaces.return_value
verify.assert_called_once_with(rtr, interfaces) verify.assert_called_once_with(rtr, interfaces)
config.assert_called_once_with(self.neutron, rtr, interfaces)
router_api.update_config.assert_has_calls([ config.assert_called_once_with(
mock.call('fe80::beef', 5000, config.return_value), self.neutron, rtr, fake_mgt_port, {})
mock.call('fe80::beef', 5000, config.return_value), expected_calls = [
mock.call('fe80::beef', 5000, config.return_value), mock.call(self.INSTANCE_INFO.management_address, 5000,
]) 'fake_config')
for i in range(0, 2)]
router_api.update_config.assert_has_calls(expected_calls)
self.assertEqual(self.vm_mgr.state, vm_manager.RESTART) self.assertEqual(self.vm_mgr.state, vm_manager.RESTART)
@mock.patch('time.sleep', lambda *a: None) @mock.patch('time.sleep', lambda *a: None)
@mock.patch('akanda.rug.vm_manager.router_api') @mock.patch('akanda.rug.vm_manager.router_api')
@mock.patch('akanda.rug.vm_manager._get_management_address') def test_replug_add_new_port_success(self, router_api):
def test_replug_add_new_port_success(self, get_mgt_addr, router_api):
self.vm_mgr.state = vm_manager.REPLUG self.vm_mgr.state = vm_manager.REPLUG
get_mgt_addr.return_value = 'fe80::beef'
rtr = mock.sentinel.router
rtr.management_port = mock.Mock()
rtr.external_port = mock.Mock()
rtr.management_port.mac_address = 'a:b:c:d'
rtr.external_port.mac_address = 'd:c:b:a'
p = mock.Mock()
p.id = 'ABC'
p.mac_address = 'a:a:a:a'
p2 = mock.Mock()
p2.id = 'DEF'
p2.mac_address = 'b:b:b:b'
rtr.internal_ports = [p, p2]
self.neutron.get_router_detail.return_value = rtr fake_router = mock.Mock()
self.vm_mgr.router_obj = rtr fake_router.id = 'fake_router_id'
fake_router.ports = [fake_ext_port, fake_int_port, fake_add_port]
self.neutron.get_router_detail.return_value = fake_router
self.vm_mgr.router_obj = fake_router
router_api.get_interfaces.return_value = [ router_api.get_interfaces.return_value = [
{'lladdr': rtr.management_port.mac_address}, {'lladdr': fake_mgt_port.mac_address},
{'lladdr': rtr.external_port.mac_address}, {'lladdr': fake_ext_port.mac_address},
{'lladdr': p.mac_address}, {'lladdr': fake_int_port.mac_address}
] ]
self.conf.hotplug_timeout = 5 self.conf.hotplug_timeout = 5
get_instance = self.ctx.nova_client.get_instance fake_instance = mock.MagicMock()
get_instance.return_value = mock.Mock() self.ctx.nova_client.get_instance_by_id = mock.Mock(
return_value=fake_instance)
fake_new_port = mock.Mock(id='fake_new_port_id')
self.ctx.neutron.create_vrrp_port.return_value = fake_new_port
with mock.patch.object(self.vm_mgr, '_verify_interfaces') as verify: with mock.patch.object(self.vm_mgr, '_verify_interfaces') as verify:
verify.return_value = True # the hotplug worked! verify.return_value = True # the hotplug worked!
self.vm_mgr.replug(self.ctx) self.vm_mgr.replug(self.ctx)
assert self.vm_mgr.state == vm_manager.REPLUG
get_instance.return_value.interface_attach.assert_called_once_with( self.ctx.neutron.create_vrrp_port.assert_called_with(
p2.id, None, None fake_router.id, 'additional-net'
) )
self.assertEqual(self.vm_mgr.state, vm_manager.REPLUG)
fake_instance.interface_attach.assert_called_once_with(
fake_new_port.id, None, None
)
self.assertIn(fake_new_port, self.INSTANCE_INFO.ports)
@mock.patch('time.sleep', lambda *a: None) @mock.patch('time.sleep', lambda *a: None)
@mock.patch('akanda.rug.vm_manager.router_api') @mock.patch('akanda.rug.vm_manager.router_api')
@mock.patch('akanda.rug.vm_manager._get_management_address') def test_replug_add_new_port_failure(self, router_api):
def test_replug_add_new_port_failure(self, get_mgt_addr, router_api):
self.vm_mgr.state = vm_manager.REPLUG self.vm_mgr.state = vm_manager.REPLUG
get_mgt_addr.return_value = 'fe80::beef'
rtr = mock.sentinel.router
rtr.management_port = mock.Mock()
rtr.external_port = mock.Mock()
rtr.management_port.mac_address = 'a:b:c:d'
rtr.external_port.mac_address = 'd:c:b:a'
p = mock.Mock()
p.id = 'ABC'
p.mac_address = 'a:a:a:a'
p2 = mock.Mock()
p2.id = 'DEF'
p2.mac_address = 'b:b:b:b'
rtr.internal_ports = [p, p2]
self.neutron.get_router_detail.return_value = rtr fake_router = mock.Mock()
self.vm_mgr.router_obj = rtr fake_router.id = 'fake_router_id'
fake_router.ports = [fake_ext_port, fake_int_port, fake_add_port]
self.neutron.get_router_detail.return_value = fake_router
self.vm_mgr.router_obj = fake_router
router_api.get_interfaces.return_value = [ router_api.get_interfaces.return_value = [
{'lladdr': rtr.management_port.mac_address}, {'lladdr': fake_mgt_port.mac_address},
{'lladdr': rtr.external_port.mac_address}, {'lladdr': fake_ext_port.mac_address},
{'lladdr': p.mac_address}, {'lladdr': fake_int_port.mac_address}
] ]
self.conf.hotplug_timeout = 5 self.conf.hotplug_timeout = 5
get_instance = self.ctx.nova_client.get_instance fake_instance = mock.MagicMock()
get_instance.return_value = mock.Mock() self.ctx.nova_client.get_instance_by_id = mock.Mock(
return_value=fake_instance)
fake_new_port = mock.Mock(id='fake_new_port_id')
self.ctx.neutron.create_vrrp_port.return_value = fake_new_port
with mock.patch.object(self.vm_mgr, '_verify_interfaces') as verify: with mock.patch.object(self.vm_mgr, '_verify_interfaces') as verify:
verify.return_value = False # The hotplug didn't work! verify.return_value = False # The hotplug didn't work!
self.vm_mgr.replug(self.ctx) self.vm_mgr.replug(self.ctx)
assert self.vm_mgr.state == vm_manager.RESTART self.assertEqual(self.vm_mgr.state, vm_manager.RESTART)
get_instance.return_value.interface_attach.assert_called_once_with( fake_instance.interface_attach.assert_called_once_with(
p2.id, None, None fake_new_port.id, None, None
) )
@mock.patch('time.sleep', lambda *a: None) @mock.patch('time.sleep', lambda *a: None)
@mock.patch('akanda.rug.vm_manager.router_api') @mock.patch('akanda.rug.vm_manager.router_api')
@mock.patch('akanda.rug.vm_manager._get_management_address') def test_replug_remove_port_success(self, router_api):
def test_replug_with_missing_external_port(self, get_mgt_addr, router_api):
"""
If the router doesn't have a management or external port, we should
attempt to create (and plug) them.
"""
self.vm_mgr.state = vm_manager.REPLUG self.vm_mgr.state = vm_manager.REPLUG
get_mgt_addr.return_value = 'fe80::beef'
rtr = mock.sentinel.router
rtr.id = 'SOME-ROUTER-ID'
rtr.management_port = None
rtr.external_port = None
self.ctx.neutron.create_router_management_port.return_value = \
mock.Mock(mac_address='a:b:c:d')
self.ctx.neutron.create_router_external_port.return_value = mock.Mock(
mac_address='d:c:b:a'
)
p = mock.Mock()
p.id = 'ABC'
p.mac_address = 'a:a:a:a'
p2 = mock.Mock()
p2.id = 'DEF'
p2.mac_address = 'b:b:b:b'
rtr.internal_ports = [p, p2]
self.neutron.get_router_detail.return_value = rtr fake_router = mock.Mock()
self.vm_mgr.router_obj = rtr fake_router.id = 'fake_router_id'
# Router lacks the fake_ext_port, it will be unplugged
fake_router.ports = [fake_mgt_port, fake_int_port]
self.neutron.get_router_detail.return_value = fake_router
self.vm_mgr.router_obj = fake_router
router_api.get_interfaces.return_value = [ router_api.get_interfaces.return_value = [
{'lladdr': 'd:c:b:a'}, {'lladdr': fake_mgt_port.mac_address},
{'lladdr': 'a:b:c:d'}, {'lladdr': fake_ext_port.mac_address},
{'lladdr': p.mac_address}, {'lladdr': fake_int_port.mac_address}
] ]
self.conf.hotplug_timeout = 5 self.conf.hotplug_timeout = 5
get_instance = self.ctx.nova_client.get_instance fake_instance = mock.MagicMock()
get_instance.return_value = mock.Mock() self.ctx.nova_client.get_instance_by_id = mock.Mock(
with mock.patch.object(self.vm_mgr, '_verify_interfaces') as verify: return_value=fake_instance)
verify.return_value = True # the hotplug worked!
self.vm_mgr.replug(self.ctx)
assert self.vm_mgr.state == vm_manager.REPLUG
self.ctx.neutron.create_router_management_port.assert_called_with(
'SOME-ROUTER-ID'
)
self.ctx.neutron.create_router_external_port.assert_called_with(
rtr
)
get_instance.return_value.interface_attach.assert_called_once_with(
p2.id, None, None
)
@mock.patch('time.sleep', lambda *a: None)
@mock.patch('akanda.rug.vm_manager.router_api')
@mock.patch('akanda.rug.vm_manager._get_management_address')
def test_replug_remove_port_success(self, get_mgt_addr, router_api):
self.vm_mgr.state = vm_manager.REPLUG
get_mgt_addr.return_value = 'fe80::beef'
rtr = mock.sentinel.router
rtr.management_port = mock.Mock()
rtr.external_port = mock.Mock()
rtr.management_port.mac_address = 'a:b:c:d'
rtr.external_port.mac_address = 'd:c:b:a'
p = mock.Mock()
p.id = 'ABC'
p.mac_address = 'a:a:a:a'
rtr.internal_ports = []
self.neutron.get_router_detail.return_value = rtr
self.vm_mgr.router_obj = rtr
router_api.get_interfaces.return_value = [
{'lladdr': rtr.management_port.mac_address},
{'lladdr': rtr.external_port.mac_address},
{'lladdr': p.mac_address}
]
self.conf.hotplug_timeout = 5
get_instance = self.ctx.nova_client.get_instance
get_instance.return_value = mock.Mock()
self.ctx.neutron.api_client.list_ports.return_value = {
'ports': [{
'id': p.id,
'device_id': 'INSTANCE123',
'fixed_ips': [],
'mac_address': p.mac_address,
'network_id': 'NETWORK123',
'device_owner': 'network:router_interface'
}]
}
with mock.patch.object(self.vm_mgr, '_verify_interfaces') as verify: with mock.patch.object(self.vm_mgr, '_verify_interfaces') as verify:
verify.return_value = True # the unplug worked! verify.return_value = True # the unplug worked!
self.vm_mgr.replug(self.ctx) self.vm_mgr.replug(self.ctx)
assert self.vm_mgr.state == vm_manager.REPLUG self.assertEqual(self.vm_mgr.state, vm_manager.REPLUG)
fake_instance.interface_detach.assert_called_once_with(
get_instance.return_value.interface_detach.assert_called_once_with( fake_ext_port.id
p.id
) )
self.assertNotIn(fake_ext_port, self.INSTANCE_INFO.ports)
@mock.patch('time.sleep', lambda *a: None) @mock.patch('time.sleep', lambda *a: None)
@mock.patch('akanda.rug.vm_manager.router_api') @mock.patch('akanda.rug.vm_manager.router_api')
@mock.patch('akanda.rug.vm_manager._get_management_address') def test_replug_remove_port_failure(self, router_api):
def test_replug_remove_port_failure(self, get_mgt_addr, router_api):
self.vm_mgr.state = vm_manager.REPLUG self.vm_mgr.state = vm_manager.REPLUG
get_mgt_addr.return_value = 'fe80::beef'
rtr = mock.sentinel.router
rtr.management_port = mock.Mock()
rtr.external_port = mock.Mock()
rtr.management_port.mac_address = 'a:b:c:d'
rtr.external_port.mac_address = 'd:c:b:a'
p = mock.Mock()
p.id = 'ABC'
p.mac_address = 'a:a:a:a'
rtr.internal_ports = []
self.neutron.get_router_detail.return_value = rtr fake_router = mock.Mock()
self.vm_mgr.router_obj = rtr fake_router.id = 'fake_router_id'
# Router lacks the fake_ext_port, it will be unplugged
fake_router.ports = [fake_mgt_port, fake_int_port]
self.neutron.get_router_detail.return_value = fake_router
self.vm_mgr.router_obj = fake_router
router_api.get_interfaces.return_value = [ router_api.get_interfaces.return_value = [
{'lladdr': rtr.management_port.mac_address}, {'lladdr': fake_mgt_port.mac_address},
{'lladdr': rtr.external_port.mac_address}, {'lladdr': fake_ext_port.mac_address},
{'lladdr': p.mac_address} {'lladdr': fake_int_port.mac_address}
] ]
self.conf.hotplug_timeout = 5 self.conf.hotplug_timeout = 5
get_instance = self.ctx.nova_client.get_instance fake_instance = mock.MagicMock()
get_instance.return_value = mock.Mock() self.ctx.nova_client.get_instance_by_id = mock.Mock(
self.ctx.neutron.api_client.list_ports.return_value = { return_value=fake_instance)
'ports': [{
'id': p.id,
'device_id': 'INSTANCE123',
'fixed_ips': [],
'mac_address': p.mac_address,
'network_id': 'NETWORK123',
'device_owner': 'network:router_interface'
}]
}
with mock.patch.object(self.vm_mgr, '_verify_interfaces') as verify: with mock.patch.object(self.vm_mgr, '_verify_interfaces') as verify:
verify.return_value = False # the unplug failed! verify.return_value = False # the unplug failed!
self.vm_mgr.replug(self.ctx) self.vm_mgr.replug(self.ctx)
assert self.vm_mgr.state == vm_manager.RESTART self.assertEquals(self.vm_mgr.state, vm_manager.RESTART)
fake_instance.interface_detach.assert_called_once_with(
get_instance.return_value.interface_detach.assert_called_once_with( fake_ext_port.id
p.id
) )
def test_verify_interfaces(self): def test_verify_interfaces(self):
rtr = mock.Mock() rtr = mock.Mock()
rtr.management_port.mac_address = 'a:b:c:d' rtr.management_port.mac_address = fake_mgt_port.mac_address
rtr.external_port.mac_address = 'd:c:b:a' rtr.external_port.mac_address = fake_ext_port.mac_address
p = mock.Mock() p = mock.Mock()
p.mac_address = 'a:a:a:a' p.mac_address = fake_int_port.mac_address
rtr.internal_ports = [p] rtr.internal_ports = [p]
rtr.ports = [p, rtr.management_port, rtr.external_port] rtr.ports = [p, rtr.management_port, rtr.external_port]
interfaces = [ interfaces = [
{'lladdr': 'a:b:c:d'}, {'lladdr': fake_mgt_port.mac_address},
{'lladdr': 'd:c:b:a'}, {'lladdr': fake_ext_port.mac_address},
{'lladdr': 'a:a:a:a'} {'lladdr': fake_int_port.mac_address}
] ]
self.assertTrue(self.vm_mgr._verify_interfaces(rtr, interfaces)) self.assertTrue(self.vm_mgr._verify_interfaces(rtr, interfaces))
@ -782,15 +687,7 @@ class TestVmManager(unittest.TestCase):
def test_ensure_provider_ports(self): def test_ensure_provider_ports(self):
rtr = mock.Mock() rtr = mock.Mock()
rtr.id = 'id'
rtr.management_port = None
rtr.external_port = None rtr.external_port = None
self.vm_mgr._ensure_provider_ports(rtr, self.ctx)
self.neutron.create_router_management_port.assert_called_once_with(
'id'
)
self.assertEqual(self.vm_mgr._ensure_provider_ports(rtr, self.ctx), self.assertEqual(self.vm_mgr._ensure_provider_ports(rtr, self.ctx),
rtr) rtr)
self.neutron.create_router_external_port.assert_called_once_with(rtr) self.neutron.create_router_external_port.assert_called_once_with(rtr)
@ -848,10 +745,8 @@ class TestVmManager(unittest.TestCase):
self.vm_mgr.set_error(self.ctx) self.vm_mgr.set_error(self.ctx)
self.vm_mgr.boot(self.ctx, 'GLANCE-IMAGE-123') self.vm_mgr.boot(self.ctx, 'GLANCE-IMAGE-123')
self.assertEqual(self.vm_mgr.state, vm_manager.BOOTING) # async self.assertEqual(self.vm_mgr.state, vm_manager.BOOTING) # async
self.ctx.nova_client.reboot_router_instance.assert_called_once_with( self.ctx.nova_client.boot_instance.assert_called_once_with(
self.vm_mgr.router_obj, self.INSTANCE_INFO, rtr.id, 'GLANCE-IMAGE-123', mock.ANY)
'GLANCE-IMAGE-123'
)
def test_error_cooldown(self): def test_error_cooldown(self):
self.conf.error_state_cooldown = 30 self.conf.error_state_cooldown = 30

View File

@ -29,12 +29,12 @@ from akanda.rug import notifications
from akanda.rug import vm_manager from akanda.rug import vm_manager
from akanda.rug import worker from akanda.rug import worker
from akanda.rug.api import neutron
class TestCreatingRouter(unittest.TestCase):
class WorkerTestBase(unittest.TestCase):
def setUp(self): def setUp(self):
super(TestCreatingRouter, self).setUp() super(WorkerTestBase, self).setUp()
self.conf = mock.patch.object(vm_manager.cfg, 'CONF').start() self.conf = mock.patch.object(vm_manager.cfg, 'CONF').start()
self.conf.boot_timeout = 1 self.conf.boot_timeout = 1
self.conf.akanda_mgt_service_port = 5000 self.conf.akanda_mgt_service_port = 5000
@ -42,10 +42,21 @@ class TestCreatingRouter(unittest.TestCase):
self.conf.management_prefix = 'fdca:3ba5:a17a:acda::/64' self.conf.management_prefix = 'fdca:3ba5:a17a:acda::/64'
mock.patch('akanda.rug.worker.nova').start() mock.patch('akanda.rug.worker.nova').start()
mock.patch('akanda.rug.worker.neutron').start() fake_neutron_obj = mock.patch.object(
neutron, 'Neutron', autospec=True).start()
fake_neutron_obj.get_ports_for_instance.return_value = (
'mgt_port', ['ext_port', 'int_port'])
mock.patch.object(neutron, 'Neutron',
return_value=fake_neutron_obj).start()
self.w = worker.Worker(0, mock.Mock())
self.addCleanup(mock.patch.stopall) self.addCleanup(mock.patch.stopall)
class TestCreatingRouter(WorkerTestBase):
def setUp(self):
super(TestCreatingRouter, self).setUp()
self.w = worker.Worker(0, mock.Mock()) self.w = worker.Worker(0, mock.Mock())
self.tenant_id = '98dd9c41-d3ac-4fd6-8927-567afa0b8fc3' self.tenant_id = '98dd9c41-d3ac-4fd6-8927-567afa0b8fc3'
self.router_id = 'ac194fc5-f317-412e-8611-fb290629f624' self.router_id = 'ac194fc5-f317-412e-8611-fb290629f624'
@ -73,22 +84,11 @@ class TestCreatingRouter(unittest.TestCase):
self.assertEqual(1, len(sm._queue)) self.assertEqual(1, len(sm._queue))
class TestWildcardMessages(unittest.TestCase): class TestWildcardMessages(WorkerTestBase):
def setUp(self): def setUp(self):
super(TestWildcardMessages, self).setUp() 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.neutron').start()
self.addCleanup(mock.patch.stopall)
self.w = worker.Worker(0, mock.Mock()) self.w = worker.Worker(0, mock.Mock())
# Create some tenants # Create some tenants
for msg in [ for msg in [
@ -125,14 +125,7 @@ class TestWildcardMessages(unittest.TestCase):
ids) ids)
class TestShutdown(unittest.TestCase): class TestShutdown(WorkerTestBase):
def setUp(self):
super(TestShutdown, self).setUp()
mock.patch('akanda.rug.worker.nova').start()
mock.patch('akanda.rug.worker.neutron').start()
self.addCleanup(mock.patch.stopall)
def test_shutdown_on_null_message(self): def test_shutdown_on_null_message(self):
self.w = worker.Worker(0, mock.Mock()) self.w = worker.Worker(0, mock.Mock())
with mock.patch.object(self.w, '_shutdown') as meth: with mock.patch.object(self.w, '_shutdown') as meth:
@ -159,24 +152,11 @@ class TestShutdown(unittest.TestCase):
self.assertFalse(self.w.notifier._t) self.assertFalse(self.w.notifier._t)
class TestUpdateStateMachine(unittest.TestCase): class TestUpdateStateMachine(WorkerTestBase):
def setUp(self): def setUp(self):
super(TestUpdateStateMachine, self).setUp() 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.neutron').start()
self.worker_context = worker.WorkerContext() self.worker_context = worker.WorkerContext()
self.addCleanup(mock.patch.stopall)
def test(self): def test(self):
w = worker.Worker(0, mock.Mock()) w = worker.Worker(0, mock.Mock())
tenant_id = '98dd9c41-d3ac-4fd6-8927-567afa0b8fc3' tenant_id = '98dd9c41-d3ac-4fd6-8927-567afa0b8fc3'
@ -205,14 +185,7 @@ class TestUpdateStateMachine(unittest.TestCase):
meth.assert_called_once_with(used_context) meth.assert_called_once_with(used_context)
class TestReportStatus(unittest.TestCase): class TestReportStatus(WorkerTestBase):
def setUp(self):
super(TestReportStatus, self).setUp()
mock.patch('akanda.rug.worker.nova').start()
mock.patch('akanda.rug.worker.neutron').start()
self.addCleanup(mock.patch.stopall)
def test_report_status_dispatched(self): def test_report_status_dispatched(self):
self.w = worker.Worker(0, mock.Mock()) self.w = worker.Worker(0, mock.Mock())
with mock.patch.object(self.w, 'report_status') as meth: with mock.patch.object(self.w, 'report_status') as meth:
@ -234,7 +207,7 @@ class TestReportStatus(unittest.TestCase):
self.assertTrue(conf.log_opt_values.called) self.assertTrue(conf.log_opt_values.called)
class TestDebugRouters(unittest.TestCase): class TestDebugRouters(WorkerTestBase):
def setUp(self): def setUp(self):
super(TestDebugRouters, self).setUp() super(TestDebugRouters, self).setUp()
@ -245,9 +218,6 @@ class TestDebugRouters(unittest.TestCase):
self.conf.max_retries = 3 self.conf.max_retries = 3
self.conf.management_prefix = 'fdca:3ba5:a17a:acda::/64' self.conf.management_prefix = 'fdca:3ba5:a17a:acda::/64'
mock.patch('akanda.rug.worker.nova').start()
mock.patch('akanda.rug.worker.neutron').start()
self.w = worker.Worker(0, mock.Mock()) self.w = worker.Worker(0, mock.Mock())
self.addCleanup(mock.patch.stopall) self.addCleanup(mock.patch.stopall)
@ -321,7 +291,7 @@ class TestDebugRouters(unittest.TestCase):
self.w.handle_message(tenant_id, msg) self.w.handle_message(tenant_id, msg)
class TestIgnoreRouters(unittest.TestCase): class TestIgnoreRouters(WorkerTestBase):
def setUp(self): def setUp(self):
super(TestIgnoreRouters, self).setUp() super(TestIgnoreRouters, self).setUp()
@ -332,11 +302,6 @@ class TestIgnoreRouters(unittest.TestCase):
self.conf.max_retries = 3 self.conf.max_retries = 3
self.conf.management_prefix = 'fdca:3ba5:a17a:acda::/64' self.conf.management_prefix = 'fdca:3ba5:a17a:acda::/64'
mock.patch('akanda.rug.worker.nova').start()
mock.patch('akanda.rug.worker.neutron').start()
self.addCleanup(mock.patch.stopall)
def testNoIgnorePath(self): def testNoIgnorePath(self):
w = worker.Worker(0, mock.Mock(), ignore_directory=None) w = worker.Worker(0, mock.Mock(), ignore_directory=None)
ignored = w._get_routers_to_ignore() ignored = w._get_routers_to_ignore()
@ -397,7 +362,7 @@ class TestIgnoreRouters(unittest.TestCase):
w.handle_message(tenant_id, msg) w.handle_message(tenant_id, msg)
class TestDebugTenants(unittest.TestCase): class TestDebugTenants(WorkerTestBase):
def setUp(self): def setUp(self):
super(TestDebugTenants, self).setUp() super(TestDebugTenants, self).setUp()
@ -408,13 +373,8 @@ class TestDebugTenants(unittest.TestCase):
self.conf.max_retries = 3 self.conf.max_retries = 3
self.conf.management_prefix = 'fdca:3ba5:a17a:acda::/64' self.conf.management_prefix = 'fdca:3ba5:a17a:acda::/64'
mock.patch('akanda.rug.worker.nova').start()
mock.patch('akanda.rug.worker.neutron').start()
self.w = worker.Worker(0, mock.Mock()) self.w = worker.Worker(0, mock.Mock())
self.addCleanup(mock.patch.stopall)
def testNoDebugs(self): def testNoDebugs(self):
self.assertEqual(set(), self.w._debug_tenants) self.assertEqual(set(), self.w._debug_tenants)
@ -459,24 +419,7 @@ class TestDebugTenants(unittest.TestCase):
self.w.handle_message(tenant_id, msg) self.w.handle_message(tenant_id, msg)
class TestConfigReload(unittest.TestCase): class TestConfigReload(WorkerTestBase):
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.neutron').start()
self.w = worker.Worker(0, mock.Mock())
self.addCleanup(mock.patch.stopall)
def test(self): def test(self):
tenant_id = '*' tenant_id = '*'
router_id = '*' router_id = '*'

View File

@ -17,7 +17,6 @@
from datetime import datetime from datetime import datetime
from functools import wraps from functools import wraps
import netaddr
import time import time
from oslo.config import cfg from oslo.config import cfg
@ -85,10 +84,9 @@ class VmManager(object):
self.log = log self.log = log
self.state = DOWN self.state = DOWN
self.router_obj = None self.router_obj = None
self.last_boot = None self.instance_info = None
self.last_error = None self.last_error = None
self._boot_counter = BootAttemptCounter() self._boot_counter = BootAttemptCounter()
self._currently_booting = False
self._last_synced_status = None self._last_synced_status = None
self.update_state(worker_context, silent=True) self.update_state(worker_context, silent=True)
@ -106,12 +104,12 @@ class VmManager(object):
self.log.debug('not updating state of deleted router') self.log.debug('not updating state of deleted router')
return self.state return self.state
if self.router_obj.management_port is None: if self.instance_info is None:
self.log.debug('no management port, marking router as down') self.log.debug('no backing instance, marking router as down')
self.state = DOWN self.state = DOWN
return self.state return self.state
addr = _get_management_address(self.router_obj) addr = self.instance_info.management_address
for i in xrange(cfg.CONF.max_retries): for i in xrange(cfg.CONF.max_retries):
if router_api.is_alive(addr, cfg.CONF.akanda_mgt_service_port): if router_api.is_alive(addr, cfg.CONF.akanda_mgt_service_port):
if self.state != CONFIGURED: if self.state != CONFIGURED:
@ -129,10 +127,13 @@ class VmManager(object):
self._check_boot_timeout() self._check_boot_timeout()
# If the router isn't responding, make sure Nova knows about it # If the router isn't responding, make sure Nova knows about it
instance = worker_context.nova_client.get_instance(self.router_obj) instance = worker_context.nova_client.get_instance_for_obj(
self.router_id
)
if instance is None and self.state != ERROR: if instance is None and self.state != ERROR:
self.log.info('No router VM was found; rebooting') self.log.info('No router VM was found; rebooting')
self.state = DOWN self.state = DOWN
self.instance_info = None
# update_state() is called from Alive() to check the # update_state() is called from Alive() to check the
# status of the router. If we can't talk to the API at # status of the router. If we can't talk to the API at
@ -147,22 +148,19 @@ class VmManager(object):
# After the router is all the way up, record how long it took # After the router is all the way up, record how long it took
# to boot and accept a configuration. # to boot and accept a configuration.
if self._currently_booting and self.state == CONFIGURED: if self.instance_info.booting and self.state == CONFIGURED:
# If we didn't boot the server (because we were restarted # If we didn't boot the server (because we were restarted
# while it remained running, for example), we won't have a # while it remained running, for example), we won't have a
# last_boot time to log. # duration to log.
if self.last_boot: self.instance_info.confirm_up()
boot_duration = (datetime.utcnow() - self.last_boot) if self.instance_info.boot_duration:
self.log.info('Router booted in %s seconds after %s attempts', self.log.info('Router booted in %s seconds after %s attempts',
boot_duration.total_seconds(), self.instance_info.boot_duration.total_seconds(),
self._boot_counter.count) self._boot_counter.count)
# Always reset the boot counter, even if we didn't boot # Always reset the boot counter, even if we didn't boot
# the server ourself, so we don't accidentally think we # the server ourself, so we don't accidentally think we
# have an erroring router. # have an erroring router.
self._boot_counter.reset() self._boot_counter.reset()
# We've reported how long it took to boot and reset the
# counter, so we are no longer "currently" booting.
self._currently_booting = False
return self.state return self.state
def boot(self, worker_context, router_image_uuid): def boot(self, worker_context, router_image_uuid):
@ -175,36 +173,43 @@ class VmManager(object):
self.state = DOWN self.state = DOWN
self._boot_counter.start() self._boot_counter.start()
def make_vrrp_ports():
mgt_port = worker_context.neutron.create_management_port(
self.router_obj.id
)
# FIXME(mark): ideally this should be ordered and de-duped
instance_ports = [
worker_context.neutron.create_vrrp_port(self.router_obj.id, n)
for n in (p.network_id for p in self.router_obj.ports)
]
return mgt_port, instance_ports
try: try:
# TODO(mark): make this pluggable
self._ensure_provider_ports(self.router_obj, worker_context) self._ensure_provider_ports(self.router_obj, worker_context)
# In the event that the current akanda instance isn't deleted # TODO(mark): make this handle errors more gracefully on cb fail
# cleanly (which we've seen in certain circumstances, like # TODO(mark): checkout from a pool - boot on demand for now
# hypervisor failures), or the vm has alredy been deleted but instance_info = worker_context.nova_client.boot_instance(
# device_id is still set incorrectly, be proactive and attempt to self.instance_info,
# clean up the router ports manually. This helps avoid a situation self.router_obj.id,
# where the rug repeatedly attempts to plug stale router ports into router_image_uuid,
# the newly created akanda instance (and fails). make_vrrp_ports
router = self.router_obj
for p in router.ports:
if p.device_id:
worker_context.neutron.clear_device_id(p)
created = worker_context.nova_client.reboot_router_instance(
router,
router_image_uuid
) )
if not created: if not instance_info:
self.log.info('Previous router is deleting') self.log.info('Previous router is deleting')
return return
except: except:
self.log.exception('Router failed to start boot') self.log.exception('Router failed to start boot')
# TODO(mark): attempt clean-up of failed ports
return return
else: else:
# We have successfully started a (re)boot attempt so # We have successfully started a (re)boot attempt so
# record the timestamp so we can report how long it takes. # record the timestamp so we can report how long it takes.
self.state = BOOTING self.state = BOOTING
self.last_boot = datetime.utcnow() self.instance_info = instance_info
self._currently_booting = True
def check_boot(self, worker_context): def check_boot(self, worker_context):
ready_states = (UP, CONFIGURED) ready_states = (UP, CONFIGURED)
@ -267,27 +272,19 @@ class VmManager(object):
def stop(self, worker_context): def stop(self, worker_context):
self._ensure_cache(worker_context) self._ensure_cache(worker_context)
if self.state == GONE: if self.state == GONE:
# We are being told to delete a router that neutron has
# already removed. Make a fake router object to use in
# this method.
router_obj = neutron.Router(
id_=self.router_id,
tenant_id=self.tenant_id,
name='unnamed',
admin_state_up=False,
status=neutron.STATUS_DOWN
)
self.log.info('Destroying router neutron has deleted') self.log.info('Destroying router neutron has deleted')
else: else:
router_obj = self.router_obj
self.log.info('Destroying router') self.log.info('Destroying router')
nova_client = worker_context.nova_client try:
nova_client.destroy_router_instance(router_obj) nova_client = worker_context.nova_client
nova_client.destroy_instance(self.instance_info)
except Exception:
self.log.exception('Error deleting router instance')
start = time.time() start = time.time()
while time.time() - start < cfg.CONF.boot_timeout: while time.time() - start < cfg.CONF.boot_timeout:
if not nova_client.get_router_instance_status(router_obj): if not nova_client.get_instance_by_id(self.instance_info.id_):
if self.state != GONE: if self.state != GONE:
self.state = DOWN self.state = DOWN
return return
@ -310,13 +307,12 @@ class VmManager(object):
if self.state == GONE: if self.state == GONE:
return return
addr = _get_management_address(self.router_obj)
# FIXME: This should raise an explicit exception so the caller # FIXME: This should raise an explicit exception so the caller
# knows that we could not talk to the router (versus the issue # knows that we could not talk to the router (versus the issue
# above). # above).
interfaces = router_api.get_interfaces( interfaces = router_api.get_interfaces(
addr, self.instance_info.management_address,
cfg.CONF.akanda_mgt_service_port cfg.CONF.akanda_mgt_service_port
) )
@ -327,18 +323,36 @@ class VmManager(object):
self.state = REPLUG self.state = REPLUG
return return
# TODO(mark): We're in the first phase of VRRP, so we need
# map the interface to the network ID.
# Eventually we'll send VRRP data and real interface data
port_mac_to_net = {
p.mac_address: p.network_id
for p in self.instance_info.ports
}
# Add in the management port
mgt_port = self.instance_info.management_port
port_mac_to_net[mgt_port.mac_address] = mgt_port.network_id
# this is a network to logical interface id
iface_map = {
port_mac_to_net[i['lladdr']]: i['ifname']
for i in interfaces if i['lladdr'] in port_mac_to_net
}
# FIXME: Need to catch errors talking to neutron here. # FIXME: Need to catch errors talking to neutron here.
config = configuration.build_config( config = configuration.build_config(
worker_context.neutron, worker_context.neutron,
self.router_obj, self.router_obj,
interfaces mgt_port,
iface_map
) )
self.log.debug('preparing to update config to %r', config) self.log.debug('preparing to update config to %r', config)
for i in xrange(attempts): for i in xrange(attempts):
try: try:
router_api.update_config( router_api.update_config(
addr, self.instance_info.management_address,
cfg.CONF.akanda_mgt_service_port, cfg.CONF.akanda_mgt_service_port,
config config
) )
@ -365,55 +379,58 @@ class VmManager(object):
self.log.debug('Attempting to replug...') self.log.debug('Attempting to replug...')
self._ensure_provider_ports(self.router_obj, worker_context) self._ensure_provider_ports(self.router_obj, worker_context)
addr = _get_management_address(self.router_obj)
interfaces = router_api.get_interfaces( interfaces = router_api.get_interfaces(
addr, self.instance_info.management_address,
cfg.CONF.akanda_mgt_service_port cfg.CONF.akanda_mgt_service_port
) )
actual_macs = set((iface['lladdr'] for iface in interfaces)) actual_macs = set((iface['lladdr'] for iface in interfaces))
instance_macs = set(p.mac_address for p in self.instance_info.ports)
instance_macs.add(self.instance_info.management_port.mac_address)
expected_ports = dict( if instance_macs != actual_macs:
(p.mac_address, p) for p in self.router_obj.internal_ports # our cached copy of the ports is wrong reboot and clean up
) self.log.warning(
expected_macs = set(expected_ports.keys()) ('Instance macs(%s) do not match actual macs (%s). Instance '
expected_macs.add(self.router_obj.management_port.mac_address) 'cache appears out-of-sync'),
expected_macs.add(self.router_obj.external_port.mac_address) instance_macs, actual_macs
)
self.state = RESTART
return
ports_to_delete = [] instance_ports = {p.network_id: p for p in self.instance_info.ports}
if expected_macs != actual_macs: instance_networks = set(instance_ports.keys())
instance = worker_context.nova_client.get_instance(self.router_obj)
logical_networks = set(p.network_id for p in self.router_obj.ports)
if logical_networks != instance_networks:
instance = worker_context.nova_client.get_instance_by_id(
self.instance_info.id_
)
# For each port that doesn't have a mac address on the VM... # For each port that doesn't have a mac address on the VM...
for mac in expected_macs - actual_macs: for network_id in logical_networks - instance_networks:
port = expected_ports.get(mac) port = worker_context.neutron.create_vrrp_port(
if port: self.router_obj.id,
self.log.debug( network_id
'New port %s, %s found, plugging...' % (port.id, mac)
)
instance.interface_attach(port.id, None, None)
# For each *extra* mac address on the VM...
for mac in actual_macs - expected_macs:
interface_ports = map(
neutron.Port.from_dict,
worker_context.neutron.api_client.list_ports(
device_id=instance.id,
device_owner=neutron.DEVICE_OWNER_ROUTER_INT
)['ports']
) )
for port in interface_ports: self.log.debug(
if port.mac_address == mac: 'Net %s is missing from the router, plugging: %s',
# If we find a router-interface port attached to the network_id, port.id
# device (meaning the interface has been removed )
# from the neutron router, but not the VM), detach the
# port from the Nova instance and mark the orphaned instance.interface_attach(port.id, None, None)
# port for deletion self.instance_info.ports.append(port)
self.log.debug(''.join([
'Port %s, %s is detached from ' % (port.id, mac), for network_id in instance_networks - logical_networks:
'the neutron router, unplugging...' port = instance_ports[network_id]
])) self.log.debug(
instance.interface_detach(port.id) 'Net %s is detached from the router, unplugging: %s',
ports_to_delete.append(port) network_id, port.id
)
instance.interface_detach(port.id)
self.instance_info.ports.remove(port)
# The action of attaching/detaching interfaces in Nova happens via the # The action of attaching/detaching interfaces in Nova happens via the
# message bus and is *not* blocking. We need to wait a few seconds to # message bus and is *not* blocking. We need to wait a few seconds to
@ -425,19 +442,12 @@ class VmManager(object):
"Waiting for interface attachments to take effect..." "Waiting for interface attachments to take effect..."
) )
interfaces = router_api.get_interfaces( interfaces = router_api.get_interfaces(
addr, self.instance_info.management_address,
cfg.CONF.akanda_mgt_service_port cfg.CONF.akanda_mgt_service_port
) )
if self._verify_interfaces(self.router_obj, interfaces): if self._verify_interfaces(self.router_obj, interfaces):
# If the interfaces now match (hotplugging was successful), go # replugging was successful
# ahead and clean up any orphaned neutron ports that may have # TODO(mark) update port states
# been detached
for port in ports_to_delete:
self.log.debug('Deleting orphaned port %s' % port.id)
worker_context.neutron.api_client.update_port(
port.id, {'port': {'device_owner': ''}}
)
worker_context.neutron.api_client.delete_port(port.id)
return return
time.sleep(1) time.sleep(1)
replug_seconds -= 1 replug_seconds -= 1
@ -456,12 +466,26 @@ class VmManager(object):
self.state = GONE self.state = GONE
self.router_obj = None self.router_obj = None
if not self.instance_info:
self.instance_info = (
worker_context.nova_client.get_instance_info_for_obj(
self.router_id
)
)
if self.instance_info:
(
self.instance_info.management_port,
self.instance_info.ports
) = worker_context.neutron.get_ports_for_instance(
self.instance_info.id_
)
def _check_boot_timeout(self): def _check_boot_timeout(self):
if self.last_boot: time_since_boot = self.instance_info.time_since_boot
seconds_since_boot = (
datetime.utcnow() - self.last_boot if time_since_boot:
).total_seconds() if time_since_boot.seconds < cfg.CONF.boot_timeout:
if seconds_since_boot < cfg.CONF.boot_timeout:
# Do not reset the state if we have an error # Do not reset the state if we have an error
# condition already. The state will be reset when # condition already. The state will be reset when
# the router starts responding again, or when the # the router starts responding again, or when the
@ -471,8 +495,6 @@ class VmManager(object):
else: else:
# If the VM was created more than `boot_timeout` seconds # If the VM was created more than `boot_timeout` seconds
# ago, log an error and set the state set to DOWN # ago, log an error and set the state set to DOWN
self.last_boot = None
self._currently_booting = False
self.log.info( self.log.info(
'Router is DOWN. Created over %d secs ago.', 'Router is DOWN. Created over %d secs ago.',
cfg.CONF.boot_timeout) cfg.CONF.boot_timeout)
@ -492,22 +514,19 @@ class VmManager(object):
): ):
return False return False
num_logical_ports = len(list(logical_config.ports))
num_instance_ports = len(list(self.instance_info.ports))
if num_logical_ports != num_instance_ports:
return False
expected_macs = set(p.mac_address expected_macs = set(p.mac_address
for p in logical_config.internal_ports) for p in self.instance_info.ports)
expected_macs.add(logical_config.management_port.mac_address) expected_macs.add(self.instance_info.management_port.mac_address)
expected_macs.add(logical_config.external_port.mac_address)
self.log.debug('MACs expected: %s', ', '.join(sorted(expected_macs))) self.log.debug('MACs expected: %s', ', '.join(sorted(expected_macs)))
return router_macs == expected_macs return router_macs == expected_macs
def _ensure_provider_ports(self, router, worker_context): def _ensure_provider_ports(self, router, worker_context):
if router.management_port is None:
self.log.debug('Adding management port to router')
mgt_port = worker_context.neutron.create_router_management_port(
router.id
)
router.management_port = mgt_port
if router.external_port is None: if router.external_port is None:
# FIXME: Need to do some work to pick the right external # FIXME: Need to do some work to pick the right external
# network for a tenant. # network for a tenant.
@ -517,14 +536,3 @@ class VmManager(object):
) )
router.external_port = ext_port router.external_port = ext_port
return router return router
def _get_management_address(router):
network = netaddr.IPNetwork(cfg.CONF.management_prefix)
tokens = ['%02x' % int(t, 16)
for t in router.management_port.mac_address.split(':')]
eui64 = int(''.join(tokens[0:3] + ['ff', 'fe'] + tokens[3:6]), 16)
# the bit inversion is required by the RFC
return str(netaddr.IPAddress(network.value + (eui64 ^ 0x0200000000000000)))

View File

@ -1,11 +1,11 @@
# -*- mode: shell-script -*- # -*- mode: shell-script -*-
# Set up default directories # Set up default directories
AKANDA_NEUTRON_DIR=$DEST/akanda-quantum AKANDA_NEUTRON_DIR=$DEST/akanda-neutron
AKANDA_NEUTRON_REPO=${AKANDA_NEUTRON_REPO:-git@github.com:neutron/akanda-neutron.git} AKANDA_NEUTRON_REPO=${AKANDA_NEUTRON_REPO:-git@github.com:markmcclain/akanda-neutron.git}
AKANDA_NEUTRON_BRANCH=${AKANDA_NEUTRON_BRANCH:-master} AKANDA_NEUTRON_BRANCH=${AKANDA_NEUTRON_BRANCH:-v2}
AKANDA_DEV_APPLIANCE=${AKANDA_DEV_APPLIANCE:-http://markmcclain.objects.dreamhost.com/akanda.qcow2} AKANDA_DEV_APPLIANCE=${AKANDA_DEV_APPLIANCE:-http://akandaio.objects.dreamhost.com/akanda_cloud.qcow2}
AKANDA_CONF_DIR=/etc/akanda-rug AKANDA_CONF_DIR=/etc/akanda-rug
AKANDA_RUG_CONF=$AKANDA_CONF_DIR/rug.ini AKANDA_RUG_CONF=$AKANDA_CONF_DIR/rug.ini
@ -116,7 +116,7 @@ function pre_start_akanda() {
upload_image $AKANDA_DEV_APPLIANCE $TOKEN upload_image $AKANDA_DEV_APPLIANCE $TOKEN
typeset image_id=$(glance $auth_args image-show akanda | grep ' id ' | awk '{print $4}') typeset image_id=$(glance $auth_args image-show akanda_cloud | grep ' id ' | awk '{print $4}')
die_if_not_set $LINENO image_id "Failed to find akanda image" die_if_not_set $LINENO image_id "Failed to find akanda image"
iniset $AKANDA_RUG_CONF DEFAULT router_image_uuid $image_id iniset $AKANDA_RUG_CONF DEFAULT router_image_uuid $image_id