Merge "Add housekeeper GET/PUT run options"

This commit is contained in:
Zuul 2018-07-05 12:06:10 +00:00 committed by Gerrit Code Review
commit b09de3485d
13 changed files with 280 additions and 77 deletions

View File

@ -39,6 +39,8 @@ A naive devstack example could be::
source devstack/openrc admin demo
export AUTH_TOKEN=`openstack token issue | awk '/ id /{print $4}'`
curl -X GET -s -H "X-Auth-Token: $AUTH_TOKEN" -H 'Content-Type: application/json' -d '{"housekeeper": {}}' http://<IP address>:9696/v2.0/housekeepers/all
curl -X PUT -s -H "X-Auth-Token: $AUTH_TOKEN" -H 'Content-Type: application/json' -d '{"housekeeper": {}}' http://<IP address>:9696/v2.0/housekeepers/all
Where <IP address> would be the Neutron controller's IP or the virtual IP of
@ -47,6 +49,9 @@ It is important to use the virtual IP in case of a load balanced active-backup
Neutron servers, as otherwise the housekeeping request may be handled by the
wrong controller.
The GET curl call will run all jobs in readonly mode
the PUT curl call will run all jobs in readwrite mode (for that the housekeeping_readonly should be set to False)
To operate the housekeeper periodically as it should, it should be scheduled
via a timing mechanism such as Linux cron.

View File

@ -16,7 +16,6 @@
import abc
from neutron_lib.plugins import directory
from oslo_config import cfg
from oslo_log import log
import six
@ -28,11 +27,10 @@ class BaseJob(object):
_core_plugin = None
def __init__(self, readonly):
self.readonly = readonly or (self.get_name() in
cfg.CONF.nsxv.housekeeping_readonly_jobs)
def __init__(self, global_readonly, readonly_jobs):
job_readonly = global_readonly or (self.get_name() in readonly_jobs)
LOG.info('Housekeeping: %s job initialized in %s mode',
self.get_name(), 'RO' if self.readonly else 'RW')
self.get_name(), 'RO' if job_readonly else 'RW')
@property
def plugin(self):

View File

@ -26,8 +26,9 @@ from neutron_lib import exceptions as n_exc
from vmware_nsx.common import locking
LOG = log.getLogger(__name__)
ALL_DUMMY_JOB_NAME = 'all'
ALL_DUMMY_JOB = {
'name': 'all',
'name': ALL_DUMMY_JOB_NAME,
'description': 'Execute all housekeepers',
'enabled': True,
'error_count': 0,
@ -35,33 +36,35 @@ ALL_DUMMY_JOB = {
'error_info': None}
class NsxvHousekeeper(stevedore.named.NamedExtensionManager):
def __init__(self, hk_ns, hk_jobs):
class NsxHousekeeper(stevedore.named.NamedExtensionManager):
def __init__(self, hk_ns, hk_jobs, hk_readonly, hk_readonly_jobs):
self.global_readonly = hk_readonly
self.readonly_jobs = hk_readonly_jobs
self.email_notifier = None
if (cfg.CONF.smtp_gateway and
cfg.CONF.smtp_from_addr and
cfg.CONF.snmp_to_list):
self.email_notifier = HousekeeperEmailNotifier()
self.readonly = cfg.CONF.nsxv.housekeeping_readonly
self.results = {}
if self.readonly:
if self.global_readonly:
LOG.info('Housekeeper initialized in readonly mode')
else:
LOG.info('Housekeeper initialized')
self.jobs = {}
super(NsxvHousekeeper, self).__init__(
hk_ns, hk_jobs, invoke_on_load=True, invoke_args=(self.readonly,))
super(NsxHousekeeper, self).__init__(
hk_ns, hk_jobs, invoke_on_load=True,
invoke_args=(self.global_readonly, self.readonly_jobs))
LOG.info("Loaded housekeeping job names: %s", self.names())
for job in self:
if job.obj.get_name() in cfg.CONF.nsxv.housekeeping_jobs:
if job.obj.get_name() in hk_jobs:
self.jobs[job.obj.get_name()] = job.obj
def get(self, job_name):
if job_name == ALL_DUMMY_JOB['name']:
if job_name == ALL_DUMMY_JOB_NAME:
return {'name': job_name,
'description': ALL_DUMMY_JOB['description'],
'enabled': job_name in self.jobs,
@ -88,15 +91,15 @@ class NsxvHousekeeper(stevedore.named.NamedExtensionManager):
raise n_exc.ObjectNotFound(id=job_name)
def list(self):
results = [{'name': ALL_DUMMY_JOB['name'],
results = [{'name': ALL_DUMMY_JOB_NAME,
'description': ALL_DUMMY_JOB['description'],
'enabled': ALL_DUMMY_JOB['name'] in self.jobs,
'enabled': ALL_DUMMY_JOB_NAME in self.jobs,
'error_count': self.results.get(
ALL_DUMMY_JOB['name'], {}).get('error_count', 0),
ALL_DUMMY_JOB_NAME, {}).get('error_count', 0),
'fixed_count': self.results.get(
ALL_DUMMY_JOB['name'], {}).get('fixed_count', 0),
ALL_DUMMY_JOB_NAME, {}).get('fixed_count', 0),
'error_info': self.results.get(
ALL_DUMMY_JOB['name'], {}).get('error_info', '')}]
ALL_DUMMY_JOB_NAME, {}).get('error_info', '')}]
for job in self:
job_name = job.obj.get_name()
@ -112,7 +115,24 @@ class NsxvHousekeeper(stevedore.named.NamedExtensionManager):
return results
def run(self, context, job_name):
def readwrite_allowed(self, job_name):
# Check if a job can run in readwrite mode
if self.global_readonly:
return False
non_readonly_jobs = set(self.jobs.keys()) - set(self.readonly_jobs)
if job_name == ALL_DUMMY_JOB_NAME:
# 'all' readwrite is allowed if it has non readonly jobs
if non_readonly_jobs:
return True
return False
else:
# specific job is allowed if it is not in the readonly list
if job_name in self.readonly_jobs:
return False
return True
def run(self, context, job_name, readonly=False):
self.results = {}
if context.is_admin:
if self.email_notifier:
@ -122,9 +142,16 @@ class NsxvHousekeeper(stevedore.named.NamedExtensionManager):
error_count = 0
fixed_count = 0
error_info = ''
if job_name == ALL_DUMMY_JOB.get('name'):
if job_name == ALL_DUMMY_JOB_NAME:
if (not readonly and
not self.readwrite_allowed(ALL_DUMMY_JOB_NAME)):
raise n_exc.ObjectNotFound(id=ALL_DUMMY_JOB_NAME)
for job in self.jobs.values():
result = job.run(context)
if (not readonly and
not self.readwrite_allowed(job.get_name())):
# skip this job as it is readonly
continue
result = job.run(context, readonly=readonly)
if result:
if self.email_notifier and result['error_count']:
self._add_job_text_to_notifier(job, result)
@ -140,7 +167,10 @@ class NsxvHousekeeper(stevedore.named.NamedExtensionManager):
else:
job = self.jobs.get(job_name)
if job:
result = job.run(context)
if (not readonly and
not self.readwrite_allowed(job_name)):
raise n_exc.ObjectNotFound(id=job_name)
result = job.run(context, readonly=readonly)
if result:
error_count = result['error_count']
if self.email_notifier:

View File

@ -359,6 +359,27 @@ class NsxPluginBase(db_base_plugin_v2.NeutronDbPluginV2,
err_msg = _("Can not enable DHCP on external network")
raise n_exc.InvalidInput(error_message=err_msg)
def get_housekeeper(self, context, name, fields=None):
# run the job in readonly mode and get the results
self.housekeeper.run(context, name, readonly=True)
return self.housekeeper.get(name)
def get_housekeepers(self, context, filters=None, fields=None, sorts=None,
limit=None, marker=None, page_reverse=False):
return self.housekeeper.list()
def update_housekeeper(self, context, name, housekeeper):
# run the job in non-readonly mode and get the results
if not self.housekeeper.readwrite_allowed(name):
err_msg = (_("Can not run housekeeper job %s in readwrite "
"mode") % name)
raise n_exc.InvalidInput(error_message=err_msg)
self.housekeeper.run(context, name, readonly=False)
return self.housekeeper.get(name)
def get_housekeeper_count(self, context, filters=None):
return len(self.housekeeper.list())
# Register the callback
def _validate_network_has_subnet(resource, event, trigger, **kwargs):

View File

@ -29,8 +29,9 @@ LOG = log.getLogger(__name__)
class ErrorBackupEdgeJob(base_job.BaseJob):
def __init__(self, readonly):
super(ErrorBackupEdgeJob, self).__init__(readonly)
def __init__(self, global_readonly, readonly_jobs):
super(ErrorBackupEdgeJob, self).__init__(
global_readonly, readonly_jobs)
self.azs = nsx_az.NsxVAvailabilityZones()
def get_project_plugin(self, plugin):
@ -42,7 +43,7 @@ class ErrorBackupEdgeJob(base_job.BaseJob):
def get_description(self):
return 'revalidate backup Edge appliances in ERROR state'
def run(self, context):
def run(self, context, readonly=False):
super(ErrorBackupEdgeJob, self).run(context)
error_count = 0
fixed_count = 0
@ -69,7 +70,7 @@ class ErrorBackupEdgeJob(base_job.BaseJob):
error_info, 'Backup Edge appliance %s is in ERROR state',
binding['edge_id'])
if not self.readonly:
if not readonly:
with locking.LockManager.get_lock(binding['edge_id']):
if self._handle_backup_edge(context, binding):
fixed_count += 1

View File

@ -27,8 +27,8 @@ LOG = log.getLogger(__name__)
class ErrorDhcpEdgeJob(base_job.BaseJob):
def __init__(self, readonly):
super(ErrorDhcpEdgeJob, self).__init__(readonly)
def __init__(self, global_readonly, readonly_jobs):
super(ErrorDhcpEdgeJob, self).__init__(global_readonly, readonly_jobs)
self.error_count = 0
self.fixed_count = 0
self.fixed_sub_if_count = 0
@ -43,7 +43,7 @@ class ErrorDhcpEdgeJob(base_job.BaseJob):
def get_description(self):
return 'revalidate DHCP Edge appliances in ERROR state'
def run(self, context):
def run(self, context, readonly=False):
super(ErrorDhcpEdgeJob, self).run(context)
self.error_count = 0
self.fixed_count = 0
@ -80,7 +80,7 @@ class ErrorDhcpEdgeJob(base_job.BaseJob):
for edge_id in edge_dict.keys():
try:
self._validate_dhcp_edge(
context, edge_dict, pfx_dict, networks, edge_id)
context, edge_dict, pfx_dict, networks, edge_id, readonly)
except Exception as e:
self.error_count += 1
self.error_info = base_job.housekeeper_warning(
@ -92,7 +92,7 @@ class ErrorDhcpEdgeJob(base_job.BaseJob):
'error_info': self.error_info}
def _validate_dhcp_edge(
self, context, edge_dict, pfx_dict, networks, edge_id):
self, context, edge_dict, pfx_dict, networks, edge_id, readonly):
# Also metadata network should be a valid network for the edge
az_name = self.plugin.get_availability_zone_name_by_edge(context,
edge_id)
@ -119,7 +119,7 @@ class ErrorDhcpEdgeJob(base_job.BaseJob):
'router binding %s for edge %s has no matching '
'neutron network', router_id, edge_id)
if not self.readonly:
if not readonly:
nsxv_db.delete_nsxv_router_binding(
context.session, binding['router_id'])
self.fixed_count += 1
@ -132,7 +132,7 @@ class ErrorDhcpEdgeJob(base_job.BaseJob):
'edge %s vnic binding missing for network %s',
edge_id, net_id)
if not self.readonly:
if not readonly:
nsxv_db.allocate_edge_vnic_with_tunnel_index(
context.session, edge_id, net_id, az_name)
self.fixed_count += 1
@ -154,7 +154,7 @@ class ErrorDhcpEdgeJob(base_job.BaseJob):
'edge vnic binding for edge %s is for invalid '
'network id %s', edge_id, bind['network_id'])
if not self.readonly:
if not readonly:
nsxv_db.free_edge_vnic_by_network(
context.session, edge_id, bind['network_id'])
self.fixed_count += 1
@ -178,9 +178,10 @@ class ErrorDhcpEdgeJob(base_job.BaseJob):
self._validate_edge_subinterfaces(
context, edge_id, backend_vnics, vnic_dict, if_changed)
self._add_missing_subinterfaces(
context, edge_id, vnic_binds, backend_vnics, if_changed)
context, edge_id, vnic_binds, backend_vnics, if_changed,
readonly)
if not self.readonly:
if not readonly:
for vnic in backend_vnics:
if if_changed[vnic['index']]:
self.plugin.nsx_v.vcns.update_interface(edge_id,
@ -218,7 +219,7 @@ class ErrorDhcpEdgeJob(base_job.BaseJob):
vnic['subInterfaces']['subInterfaces'].remove(sub_if)
def _add_missing_subinterfaces(self, context, edge_id, vnic_binds,
backend_vnics, if_changed):
backend_vnics, if_changed, readonly):
# Verify that all the entries in
# nsxv_edge_vnic_bindings are attached on the Edge
@ -252,7 +253,7 @@ class ErrorDhcpEdgeJob(base_job.BaseJob):
tunnel_index, vnic['index'], edge_id,
network_id)
if_changed[vnic['index']] = True
if not self.readonly:
if not readonly:
self._recreate_vnic_subinterface(
context, network_id, edge_id, vnic,
tunnel_index)
@ -267,7 +268,7 @@ class ErrorDhcpEdgeJob(base_job.BaseJob):
tunnel_index, vnic['index'], edge_id, network_id)
if_changed[vnic['index']] = True
if not self.readonly:
if not readonly:
self._recreate_vnic_subinterface(
context, network_id, edge_id, vnic,
tunnel_index)

View File

@ -46,7 +46,7 @@ class LbaasPendingJob(base_job.BaseJob):
def get_description(self):
return 'Monitor LBaaS objects in pending states'
def run(self, context):
def run(self, context, readonly=False):
super(LbaasPendingJob, self).run(context)
curr_time = time.time()
error_count = 0
@ -74,7 +74,7 @@ class LbaasPendingJob(base_job.BaseJob):
'LBaaS %s %s is stuck in pending state',
model.NAME, element['id'])
if not self.readonly:
if not readonly:
element['provisioning_status'] = constants.ERROR
fixed_count += 1
del self.lbaas_objects[element['id']]

View File

@ -358,9 +358,11 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
nsx_v_md_proxy.NsxVMetadataProxyHandler(
self, az))
self.housekeeper = housekeeper.NsxvHousekeeper(
self.housekeeper = housekeeper.NsxHousekeeper(
hk_ns='vmware_nsx.neutron.nsxv.housekeeper.jobs',
hk_jobs=cfg.CONF.nsxv.housekeeping_jobs)
hk_jobs=cfg.CONF.nsxv.housekeeping_jobs,
hk_readonly=cfg.CONF.nsxv.housekeeping_readonly,
hk_readonly_jobs=cfg.CONF.nsxv.housekeeping_readonly_jobs)
self.init_is_complete = True
@ -4798,19 +4800,5 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
results.append(self._nsx_policy_to_dict(policy))
return results
def get_housekeeper(self, context, name, fields=None):
return self.housekeeper.get(name)
def get_housekeepers(self, context, filters=None, fields=None, sorts=None,
limit=None, marker=None, page_reverse=False):
return self.housekeeper.list()
def update_housekeeper(self, context, name, housekeeper):
self.housekeeper.run(context, name)
return self.housekeeper.get(name)
def get_housekeeper_count(self, context, filters=None):
return len(self.housekeeper.list())
def _get_appservice_id(self, name):
return self.nsx_v.vcns.get_application_id(name)

View File

@ -64,7 +64,7 @@ class NsxVPluginWrapper(plugin.NsxVPlugin):
# finish the plugin initialization
# (with md-proxy config, but without housekeeping)
with mock.patch("vmware_nsx.plugins.common.housekeeper."
"housekeeper.NsxvHousekeeper"):
"housekeeper.NsxHousekeeper"):
self.init_complete(0, 0, 0)
def _start_rpc_listeners(self):

View File

@ -0,0 +1,156 @@
# Copyright 2018 VMware, Inc.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from neutron.tests import base
from neutron_lib import exceptions as n_exc
from vmware_nsx.plugins.common.housekeeper import base_job
from vmware_nsx.plugins.common.housekeeper import housekeeper
class TestJob1(base_job.BaseJob):
def __init__(self, global_readonly, readonly_jobs):
super(TestJob1, self).__init__(global_readonly, readonly_jobs)
def get_name(self):
return 'test_job1'
def get_project_plugin(self, plugin):
return 'Dummy'
def get_description(self):
return 'test'
def run(self, context, readonly=False):
pass
class TestJob2(TestJob1):
def get_name(self):
return 'test_job2'
class TestHousekeeper(base.BaseTestCase):
def setUp(self):
self.jobs = ['test_job1', 'test_job2']
self.readonly_jobs = ['test_job1']
self.readonly = False
self.housekeeper = housekeeper.NsxHousekeeper(
hk_ns='stevedore.test.extension',
hk_jobs=self.jobs,
hk_readonly=self.readonly,
hk_readonly_jobs=self.readonly_jobs)
self.job1 = TestJob1(self.readonly, self.readonly_jobs)
self.job2 = TestJob2(self.readonly, self.readonly_jobs)
self.housekeeper.jobs = {'test_job1': self.job1,
'test_job2': self.job2}
self.context = mock.Mock()
self.context.session = mock.Mock()
super(TestHousekeeper, self).setUp()
def test_run_job_readonly(self):
with mock.patch.object(self.job1, 'run') as run1,\
mock.patch.object(self.job2, 'run') as run2:
self.housekeeper.run(self.context, 'test_job1', readonly=True)
run1.assert_called_with(mock.ANY, readonly=True)
self.housekeeper.run(self.context, 'test_job2', readonly=True)
run2.assert_called_with(mock.ANY, readonly=True)
def test_run_job_readwrite(self):
with mock.patch.object(self.job1, 'run') as run1,\
mock.patch.object(self.job2, 'run') as run2:
# job1 is configured as a readonly job so this should fail
self.assertRaises(
n_exc.ObjectNotFound,
self.housekeeper.run, self.context, 'test_job1',
readonly=False)
self.assertFalse(run1.called)
# job2 should run
self.housekeeper.run(self.context, 'test_job2', readonly=False)
run2.assert_called_with(mock.ANY, readonly=False)
def test_run_all_readonly(self):
with mock.patch.object(self.job1, 'run') as run1,\
mock.patch.object(self.job2, 'run') as run2:
self.housekeeper.run(self.context, 'all', readonly=True)
run1.assert_called_with(mock.ANY, readonly=True)
run2.assert_called_with(mock.ANY, readonly=True)
def test_run_all_readwrite(self):
with mock.patch.object(self.job1, 'run') as run1,\
mock.patch.object(self.job2, 'run') as run2:
self.housekeeper.run(self.context, 'all', readonly=False)
# job1 is configured as a readonly job so it was not called
self.assertFalse(run1.called)
# job2 should run
run2.assert_called_with(mock.ANY, readonly=False)
class TestHousekeeperReadOnly(TestHousekeeper):
def setUp(self):
super(TestHousekeeperReadOnly, self).setUp()
self.housekeeper.global_readonly = True
def test_run_job_readonly(self):
with mock.patch.object(self.job1, 'run') as run1,\
mock.patch.object(self.job2, 'run') as run2:
self.housekeeper.run(self.context, 'test_job1', readonly=True)
run1.assert_called_with(mock.ANY, readonly=True)
self.housekeeper.run(self.context, 'test_job2', readonly=True)
run2.assert_called_with(mock.ANY, readonly=True)
def test_run_job_readwrite(self):
with mock.patch.object(self.job1, 'run') as run1,\
mock.patch.object(self.job2, 'run') as run2:
# job1 is configured as a readonly job so this should fail
self.assertRaises(
n_exc.ObjectNotFound,
self.housekeeper.run, self.context, 'test_job1',
readonly=False)
self.assertFalse(run1.called)
# global readonly flag so job2 should also fail
self.assertRaises(
n_exc.ObjectNotFound,
self.housekeeper.run, self.context, 'test_job2',
readonly=False)
self.assertFalse(run2.called)
def test_run_all_readonly(self):
with mock.patch.object(self.job1, 'run') as run1,\
mock.patch.object(self.job2, 'run') as run2:
self.housekeeper.run(self.context, 'all', readonly=True)
run1.assert_called_with(mock.ANY, readonly=True)
run2.assert_called_with(mock.ANY, readonly=True)
def test_run_all_readwrite(self):
with mock.patch.object(self.job1, 'run') as run1,\
mock.patch.object(self.job2, 'run') as run2:
# global readonly flag so 'all' should fail
self.assertRaises(
n_exc.ObjectNotFound,
self.housekeeper.run, self.context, 'all',
readonly=False)
self.assertFalse(run1.called)
self.assertFalse(run2.called)

View File

@ -28,8 +28,6 @@ FAKE_ROUTER_BINDINGS = [
class ErrorBackupEdgeTestCaseReadOnly(base.BaseTestCase):
def _is_readonly(self):
return True
def setUp(self):
def get_plugin_mock(alias=constants.CORE):
@ -44,25 +42,28 @@ class ErrorBackupEdgeTestCaseReadOnly(base.BaseTestCase):
side_effect=get_plugin_mock).start()
self.log = mock.Mock()
base_job.LOG = self.log
self.job = error_backup_edge.ErrorBackupEdgeJob(self._is_readonly())
self.job = error_backup_edge.ErrorBackupEdgeJob(True, [])
def run_job(self):
self.job.run(self.context, readonly=True)
def test_clean_run(self):
mock.patch('vmware_nsx.db.nsxv_db.get_nsxv_router_bindings',
return_value=[]).start()
self.job.run(self.context)
self.run_job()
self.log.warning.assert_not_called()
def test_broken_backup_edge(self):
mock.patch('vmware_nsx.db.nsxv_db.get_nsxv_router_bindings',
return_value=FAKE_ROUTER_BINDINGS).start()
self.job.run(self.context)
self.run_job()
self.log.warning.assert_called_once()
class ErrorBackupEdgeTestCaseReadWrite(ErrorBackupEdgeTestCaseReadOnly):
def _is_readonly(self):
return False
def run_job(self):
self.job.run(self.context, readonly=False)
def test_broken_backup_edge(self):
upd_binding = mock.patch(

View File

@ -270,8 +270,6 @@ BAD_INTERFACE = {
class ErrorDhcpEdgeTestCaseReadOnly(base.BaseTestCase):
def _is_readonly(self):
return True
def setUp(self):
def get_plugin_mock(alias=constants.CORE):
@ -291,12 +289,15 @@ class ErrorDhcpEdgeTestCaseReadOnly(base.BaseTestCase):
return_value='default').start()
self.log = mock.Mock()
base_job.LOG = self.log
self.job = error_dhcp_edge.ErrorDhcpEdgeJob(self._is_readonly())
self.job = error_dhcp_edge.ErrorDhcpEdgeJob(True, [])
def run_job(self):
self.job.run(self.context, readonly=True)
def test_clean_run(self):
mock.patch('vmware_nsx.db.nsxv_db.get_nsxv_router_bindings',
return_value=[]).start()
self.job.run(self.context)
self.run_job()
self.log.warning.assert_not_called()
def test_invalid_router_binding(self):
@ -312,7 +313,7 @@ class ErrorDhcpEdgeTestCaseReadOnly(base.BaseTestCase):
return_value=(None, BACKEND_EDGE_VNICS)).start()
mock.patch('vmware_nsx.db.nsxv_db.get_nsxv_internal_networks',
return_value=FAKE_INTERNAL_NETWORKS).start()
self.job.run(self.context)
self.run_job()
self.log.warning.assert_called_once()
def test_invalid_edge_vnic_bindings(self):
@ -340,7 +341,7 @@ class ErrorDhcpEdgeTestCaseReadOnly(base.BaseTestCase):
return_value=(None, BACKEND_EDGE_VNICS)).start()
mock.patch('vmware_nsx.db.nsxv_db.get_nsxv_internal_networks',
return_value=FAKE_INTERNAL_NETWORKS).start()
self.job.run(self.context)
self.run_job()
self.log.warning.assert_called_once()
def test_invalid_edge_sub_if(self):
@ -357,7 +358,7 @@ class ErrorDhcpEdgeTestCaseReadOnly(base.BaseTestCase):
return_value=(None, backend_vnics)).start()
mock.patch('vmware_nsx.db.nsxv_db.get_nsxv_internal_networks',
return_value=FAKE_INTERNAL_NETWORKS).start()
self.job.run(self.context)
self.run_job()
self.log.warning.assert_called_once()
def test_missing_edge_sub_if(self):
@ -373,7 +374,7 @@ class ErrorDhcpEdgeTestCaseReadOnly(base.BaseTestCase):
return_value=(None, backend_vnics)).start()
mock.patch('vmware_nsx.db.nsxv_db.get_nsxv_internal_networks',
return_value=FAKE_INTERNAL_NETWORKS).start()
self.job.run(self.context)
self.run_job()
self.log.warning.assert_called_once()
def test_missing_edge_interface(self):
@ -389,13 +390,14 @@ class ErrorDhcpEdgeTestCaseReadOnly(base.BaseTestCase):
return_value=(None, backend_vnics)).start()
mock.patch('vmware_nsx.db.nsxv_db.get_nsxv_internal_networks',
return_value=FAKE_INTERNAL_NETWORKS).start()
self.job.run(self.context)
self.run_job()
self.assertEqual(2, self.log.warning.call_count)
class ErrorDhcpEdgeTestCaseReadWrite(ErrorDhcpEdgeTestCaseReadOnly):
def _is_readonly(self):
return False
def run_job(self):
self.job.run(self.context, readonly=False)
def test_invalid_router_binding(self):
del_binding = mock.patch(