Merge pull request #48 from openstack-charmers/dragent-infer-asn-from-deployment

Infer ASn from deployment for BGP speaker test configuration
This commit is contained in:
Liam Young
2018-05-16 08:49:44 +01:00
committed by GitHub
6 changed files with 183 additions and 4 deletions
+21
View File
@@ -72,8 +72,18 @@ class TestModel(ut_utils.BaseTestCase):
self.unit1.is_leader_from_status.side_effect = _is_leader(False)
self.unit2.is_leader_from_status.side_effect = _is_leader(True)
self.units = [self.unit1, self.unit2]
self.relation1 = mock.MagicMock()
self.relation1.id = 42
self.relation1.matches.side_effect = \
lambda x: True if x == 'app' else False
self.relation2 = mock.MagicMock()
self.relation2.id = 51
self.relation2.matches.side_effect = \
lambda x: True if x == 'app:interface' else False
self.relations = [self.relation1, self.relation2]
_units = mock.MagicMock()
_units.units = self.units
_units.relations = self.relations
self.mymodel = mock.MagicMock()
self.mymodel.applications = {
'app': _units
@@ -179,6 +189,17 @@ class TestModel(ut_utils.BaseTestCase):
expected)
self.unit1.run.assert_called_once_with(cmd, timeout=None)
def test_get_relation_id(self):
self.patch_object(model, 'Model')
self.Model.return_value = self.Model_mock
self.assertEqual(model.get_relation_id('testmodel', 'app', 'app'), 42)
def test_get_relation_id_interface(self):
self.patch_object(model, 'Model')
self.Model.return_value = self.Model_mock
self.assertEqual(model.get_relation_id('testmodel', 'app', 'app',
'interface'), 51)
def test_run_action(self):
self.patch_object(model, 'Model')
self.patch_object(model, 'get_unit_from_name')
@@ -155,3 +155,46 @@ class TestJujuUtils(ut_utils.BaseTestCase):
# Fatal failure
with self.assertRaises(Exception):
juju_utils.remote_run(self.unit, _cmd, fatal=True)
def test_get_unit_names(self):
self.patch('zaza.model.get_first_unit_name', new_callable=mock.Mock(),
name='_get_first_unit_name')
juju_utils._get_unit_names(['aunit/0', 'otherunit/0'])
self.assertFalse(self._get_first_unit_name.called)
def test_get_unit_names_called_with_application_name(self):
self.patch_object(juju_utils, 'model')
juju_utils._get_unit_names(['aunit', 'otherunit/0'])
self.model.get_first_unit_name.assert_called()
def test_get_relation_from_unit(self):
self.patch_object(juju_utils, 'lifecycle_utils')
self.patch_object(juju_utils, '_get_unit_names')
self.patch_object(juju_utils, 'yaml')
self.patch_object(juju_utils, 'model')
self._get_unit_names.return_value = ['aunit/0', 'otherunit/0']
data = {'foo': 'bar'}
self.model.get_relation_id.return_value = 42
self.model.run_on_unit.return_value = {'Code': 0, 'Stdout': str(data)}
juju_utils.get_relation_from_unit('aunit/0', 'otherunit/0',
'arelation')
self.model.run_on_unit.assert_called_with(
self.lifecycle_utils.get_juju_model(), 'aunit/0',
'relation-get --format=yaml -r "42" - "otherunit/0"')
self.yaml.load.assert_called_with(str(data))
def test_get_relation_from_unit_fails(self):
self.patch_object(juju_utils, 'lifecycle_utils')
self.patch_object(juju_utils, '_get_unit_names')
self.patch_object(juju_utils, 'yaml')
self.patch_object(juju_utils, 'model')
self._get_unit_names.return_value = ['aunit/0', 'otherunit/0']
self.model.get_relation_id.return_value = 42
self.model.run_on_unit.return_value = {'Code': 1, 'Stderr': 'ERROR'}
with self.assertRaises(Exception):
juju_utils.get_relation_from_unit('aunit/0', 'otherunit/0',
'arelation')
self.model.run_on_unit.assert_called_with(
self.lifecycle_utils.get_juju_model(), 'aunit/0',
'relation-get --format=yaml -r "42" - "otherunit/0"')
self.assertFalse(self.yaml.load.called)
+2 -2
View File
@@ -2,7 +2,7 @@
import unittest
from zaza.utilities import generic as generic_utils
from zaza.utilities import cli as cli_utils
from zaza.charm_tests.dragent import test
@@ -12,7 +12,7 @@ class DRAgentTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
generic_utils.setup_logging()
cli_utils.setup_logging()
def test_bgp_routes(self):
test.test_bgp_routes(peer_application_name=self.BGP_PEER_APPLICATION)
+16 -2
View File
@@ -6,6 +6,7 @@ import sys
from zaza.utilities import (
cli as cli_utils,
openstack as openstack_utils,
juju as juju_utils,
)
@@ -24,6 +25,19 @@ def setup_bgp_speaker(peer_application_name, keystone_session=None):
:returns: None
:rtype: None
"""
# Get ASNs from deployment
dr_relation = juju_utils.get_relation_from_unit(
'neutron-dynamic-routing',
peer_application_name,
'bgpclient')
peer_asn = dr_relation.get('asn')
logging.debug('peer ASn: "{}"'.format(peer_asn))
peer_relation = juju_utils.get_relation_from_unit(
peer_application_name,
'neutron-dynamic-routing',
'bgp-speaker')
dr_asn = peer_relation.get('asn')
logging.debug('our ASn: "{}"'.format(dr_asn))
# If a session has not been provided, acquire one
if not keystone_session:
@@ -36,7 +50,7 @@ def setup_bgp_speaker(peer_application_name, keystone_session=None):
# Create BGP speaker
logging.info("Setting up BGP speaker")
bgp_speaker = openstack_utils.create_bgp_speaker(
neutron_client, local_as=12345)
neutron_client, local_as=dr_asn)
# Add networks to bgp speaker
logging.info("Advertising BGP routes")
@@ -53,7 +67,7 @@ def setup_bgp_speaker(peer_application_name, keystone_session=None):
logging.info("Setting up BGP peer")
bgp_peer = openstack_utils.create_bgp_peer(neutron_client,
peer_application_name,
remote_as=10000)
remote_as=peer_asn)
# Add peer to bgp speaker
logging.info("Adding BGP peer to BGP speaker")
openstack_utils.add_peer_to_bgp_speaker(
+29
View File
@@ -654,6 +654,35 @@ block_until_file_has_contents = sync_wrapper(
async_block_until_file_has_contents)
async def async_get_relation_id(model_name, application_name,
remote_application_name,
remote_interface_name=None):
"""
Get relation id of relation from model
:param model_name: Name of model to operate on
:type model_name: str
:param application_name: Name of application on this side of relation
:type application_name: str
:param remote_application_name: Name of application on other side of
relation
:type remote_application_name: str
:param remote_interface_name: Name of interface on remote end of relation
:type remote_interface_name: Optional(str)
:returns: Relation id of relation if found or None
:rtype: any
"""
async with run_in_model(model_name) as model:
for rel in model.applications[application_name].relations:
spec = '{}'.format(remote_application_name)
if remote_interface_name is not None:
spec += ':{}'.format(remote_interface_name)
if rel.matches(spec):
return(rel.id)
get_relation_id = sync_wrapper(async_get_relation_id)
def main():
# Run the deploy coroutine in an asyncio event loop, using a helper
# that abstracts loop creation and teardown.
+72
View File
@@ -1,7 +1,21 @@
#!/usr/bin/env python3
# Copyright 2018 Canonical Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
from pathlib import Path
import yaml
from zaza import (
model,
@@ -169,3 +183,61 @@ def remote_run(unit, remote_cmd, timeout=None, fatal=None):
raise Exception("Error running remote command: {}"
.format(result.get("Stderr")))
return result.get("Stderr")
def _get_unit_names(names):
"""
Helper function that resolves application names to first unit name of
said application. Any already resolved unit names are returned as-is.
:param names: List of units/applications to translate
:type names: list(str)
:returns: List of units
:rtype: list(str)
"""
result = []
for name in names:
if '/' in name:
result.append(name)
else:
result.append(
model.get_first_unit_name(lifecycle_utils.get_juju_model(),
name))
return result
def get_relation_from_unit(entity, remote_entity, remote_interface_name):
"""
Get relation data for relation with `remote_interface_name` between
`entity` and `remote_entity` from the perspective of `entity`.
`entity` and `remote_entity` may refer to either a application or a
specific unit. If application name is given first unit is found in model.
:param entity: Application or unit to get relation data from
:type entity: str
:param remote_entity: Application or Unit in the other end of the relation
we want to query
:type remote_entity: str
:param remote_interface_name: Name of interface to query on remote end of
relation
:type remote_interface_name: str
:returns: dict with relation data
:rtype: dict
"""
application = entity.split('/')[0]
remote_application = remote_entity.split('/')[0]
rid = model.get_relation_id(lifecycle_utils.get_juju_model(), application,
remote_application,
remote_interface_name=remote_interface_name)
(unit, remote_unit) = _get_unit_names([entity, remote_entity])
result = model.run_on_unit(
lifecycle_utils.get_juju_model(), unit,
'relation-get --format=yaml -r "{}" - "{}"'
.format(rid, remote_unit)
)
if result and int(result.get('Code')) == 0:
return yaml.load(result.get('Stdout'))
else:
raise Exception('Error running remote command: "{}"'
.format(result.get("Stderr")))