Merge "[OVN] Add hash ring methods"
This commit is contained in:
commit
15f8c0e399
69
neutron/db/ovn_hash_ring_db.py
Normal file
69
neutron/db/ovn_hash_ring_db.py
Normal file
@ -0,0 +1,69 @@
|
||||
# Copyright 2019 Red Hat, 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 datetime
|
||||
|
||||
from neutron_lib.db import api as db_api
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import timeutils
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from neutron.db.models import ovn as ovn_models
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
# NOTE(ralonsoh): this was migrated from networking-ovn to neutron and should
|
||||
# be refactored to be integrated in a OVO.
|
||||
def add_node(context, group_name, node_uuid=None):
|
||||
if node_uuid is None:
|
||||
node_uuid = uuidutils.generate_uuid()
|
||||
|
||||
with db_api.CONTEXT_WRITER.using(context):
|
||||
context.session.add(ovn_models.OVNHashRing(
|
||||
node_uuid=node_uuid, hostname=CONF.host, group_name=group_name))
|
||||
return node_uuid
|
||||
|
||||
|
||||
def remove_nodes_from_host(context, group_name):
|
||||
with db_api.CONTEXT_WRITER.using(context):
|
||||
context.session.query(ovn_models.OVNHashRing).filter(
|
||||
ovn_models.OVNHashRing.hostname == CONF.host,
|
||||
ovn_models.OVNHashRing.group_name == group_name).delete()
|
||||
|
||||
|
||||
def _touch(context, **filter_args):
|
||||
with db_api.CONTEXT_WRITER.using(context):
|
||||
context.session.query(ovn_models.OVNHashRing).filter_by(
|
||||
**filter_args).update({'updated_at': timeutils.utcnow()})
|
||||
|
||||
|
||||
def touch_nodes_from_host(context, group_name):
|
||||
_touch(context, hostname=CONF.host, group_name=group_name)
|
||||
|
||||
|
||||
def touch_node(context, node_uuid):
|
||||
_touch(context, node_uuid=node_uuid)
|
||||
|
||||
|
||||
def get_active_nodes(context, interval, group_name, from_host=False):
|
||||
limit = timeutils.utcnow() - datetime.timedelta(seconds=interval)
|
||||
with db_api.CONTEXT_READER.using(context):
|
||||
query = context.session.query(ovn_models.OVNHashRing).filter(
|
||||
ovn_models.OVNHashRing.updated_at >= limit,
|
||||
ovn_models.OVNHashRing.group_name == group_name)
|
||||
if from_host:
|
||||
query = query.filter_by(hostname=CONF.host)
|
||||
return query.all()
|
231
neutron/tests/unit/db/test_ovn_hash_ring_db.py
Normal file
231
neutron/tests/unit/db/test_ovn_hash_ring_db.py
Normal file
@ -0,0 +1,231 @@
|
||||
# Copyright 2019 Red Hat, 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 datetime
|
||||
|
||||
import mock
|
||||
from neutron_lib import context
|
||||
from neutron_lib.db import api as db_api
|
||||
from oslo_utils import timeutils
|
||||
from oslo_utils import uuidutils
|
||||
from sqlalchemy.orm import exc
|
||||
|
||||
from neutron.db.models import ovn as ovn_models
|
||||
from neutron.db import ovn_hash_ring_db
|
||||
from neutron.tests.unit import testlib_api
|
||||
|
||||
HASH_RING_TEST_GROUP = 'test_group'
|
||||
|
||||
|
||||
class TestHashRing(testlib_api.SqlTestCaseLight):
|
||||
|
||||
def setUp(self):
|
||||
super(TestHashRing, self).setUp()
|
||||
self.admin_ctx = context.get_admin_context()
|
||||
self.addCleanup(self._delete_objs)
|
||||
|
||||
def _delete_objs(self):
|
||||
with db_api.CONTEXT_WRITER.using(self.admin_ctx):
|
||||
self.admin_ctx.session.query(
|
||||
ovn_models.OVNRevisionNumbers).delete()
|
||||
|
||||
def _get_node_row(self, node_uuid):
|
||||
try:
|
||||
with db_api.CONTEXT_WRITER.using(self.admin_ctx):
|
||||
return self.admin_ctx.session.query(
|
||||
ovn_models.OVNHashRing).filter_by(
|
||||
node_uuid=node_uuid).one()
|
||||
except exc.NoResultFound:
|
||||
return
|
||||
|
||||
def _add_nodes_and_assert_exists(self, count=1,
|
||||
group_name=HASH_RING_TEST_GROUP):
|
||||
nodes = []
|
||||
for i in range(count):
|
||||
node_uuid = ovn_hash_ring_db.add_node(self.admin_ctx, group_name)
|
||||
self.assertIsNotNone(self._get_node_row(node_uuid))
|
||||
nodes.append(node_uuid)
|
||||
return nodes
|
||||
|
||||
def test_add_node(self):
|
||||
self._add_nodes_and_assert_exists()
|
||||
|
||||
def test_remove_nodes_from_host(self):
|
||||
nodes = self._add_nodes_and_assert_exists(count=3)
|
||||
|
||||
# Add another node from a different host
|
||||
with mock.patch.object(ovn_hash_ring_db, 'CONF') as mock_conf:
|
||||
mock_conf.host = 'another-host-' + uuidutils.generate_uuid()
|
||||
another_host_node = self._add_nodes_and_assert_exists()[0]
|
||||
|
||||
ovn_hash_ring_db.remove_nodes_from_host(self.admin_ctx,
|
||||
HASH_RING_TEST_GROUP)
|
||||
# Assert that all nodes from that host have been removed
|
||||
for n in nodes:
|
||||
self.assertIsNone(self._get_node_row(n))
|
||||
|
||||
# Assert that the node from another host wasn't removed
|
||||
self.assertIsNotNone(self._get_node_row(another_host_node))
|
||||
|
||||
def test_touch_nodes_from_host(self):
|
||||
nodes = self._add_nodes_and_assert_exists(count=3)
|
||||
|
||||
# Add another node from a different host
|
||||
with mock.patch.object(ovn_hash_ring_db, 'CONF') as mock_conf:
|
||||
mock_conf.host = 'another-host-' + uuidutils.generate_uuid()
|
||||
another_host_node = self._add_nodes_and_assert_exists()[0]
|
||||
|
||||
# Assert that updated_at isn't updated yet
|
||||
for node in nodes:
|
||||
node_db = self._get_node_row(node)
|
||||
self.assertEqual(node_db.created_at, node_db.updated_at)
|
||||
|
||||
# Assert the same for the node from another host
|
||||
node_db = self._get_node_row(another_host_node)
|
||||
self.assertEqual(node_db.created_at, node_db.updated_at)
|
||||
|
||||
# Touch the nodes from our host
|
||||
ovn_hash_ring_db.touch_nodes_from_host(self.admin_ctx,
|
||||
HASH_RING_TEST_GROUP)
|
||||
|
||||
# Assert that updated_at is now updated
|
||||
for node in nodes:
|
||||
node_db = self._get_node_row(node)
|
||||
self.assertGreater(node_db.updated_at, node_db.created_at)
|
||||
|
||||
# Assert that the node from another host hasn't been touched
|
||||
# (updated_at is not updated)
|
||||
node_db = self._get_node_row(another_host_node)
|
||||
self.assertEqual(node_db.created_at, node_db.updated_at)
|
||||
|
||||
def test_active_nodes(self):
|
||||
self._add_nodes_and_assert_exists(count=3)
|
||||
|
||||
# Add another node from a different host
|
||||
with mock.patch.object(ovn_hash_ring_db, 'CONF') as mock_conf:
|
||||
mock_conf.host = 'another-host-' + uuidutils.generate_uuid()
|
||||
another_host_node = self._add_nodes_and_assert_exists()[0]
|
||||
|
||||
# Assert all nodes are active (within 60 seconds)
|
||||
self.assertEqual(4, len(ovn_hash_ring_db.get_active_nodes(
|
||||
self.admin_ctx, interval=60, group_name=HASH_RING_TEST_GROUP)))
|
||||
|
||||
# Substract 60 seconds from utcnow() and touch the nodes from our host
|
||||
fake_utcnow = timeutils.utcnow() - datetime.timedelta(seconds=60)
|
||||
with mock.patch.object(timeutils, 'utcnow') as mock_utcnow:
|
||||
mock_utcnow.return_value = fake_utcnow
|
||||
ovn_hash_ring_db.touch_nodes_from_host(self.admin_ctx,
|
||||
HASH_RING_TEST_GROUP)
|
||||
|
||||
# Now assert that all nodes from our host are seeing as offline.
|
||||
# Only the node from another host should be active
|
||||
active_nodes = ovn_hash_ring_db.get_active_nodes(
|
||||
self.admin_ctx, interval=60, group_name=HASH_RING_TEST_GROUP)
|
||||
self.assertEqual(1, len(active_nodes))
|
||||
self.assertEqual(another_host_node, active_nodes[0].node_uuid)
|
||||
|
||||
def test_active_nodes_from_host(self):
|
||||
self._add_nodes_and_assert_exists(count=3)
|
||||
|
||||
# Add another node from a different host
|
||||
another_host_id = 'another-host-52359446-c366'
|
||||
with mock.patch.object(ovn_hash_ring_db, 'CONF') as mock_conf:
|
||||
mock_conf.host = another_host_id
|
||||
self._add_nodes_and_assert_exists()
|
||||
|
||||
# Assert only the 3 nodes from this host is returned
|
||||
active_nodes = ovn_hash_ring_db.get_active_nodes(
|
||||
self.admin_ctx, interval=60, group_name=HASH_RING_TEST_GROUP,
|
||||
from_host=True)
|
||||
self.assertEqual(3, len(active_nodes))
|
||||
self.assertNotIn(another_host_id, active_nodes)
|
||||
|
||||
def test_touch_node(self):
|
||||
nodes = self._add_nodes_and_assert_exists(count=3)
|
||||
|
||||
# Assert no nodes were updated yet
|
||||
for node in nodes:
|
||||
node_db = self._get_node_row(node)
|
||||
self.assertEqual(node_db.created_at, node_db.updated_at)
|
||||
|
||||
# Touch one of the nodes
|
||||
ovn_hash_ring_db.touch_node(self.admin_ctx, nodes[0])
|
||||
|
||||
# Assert it has been updated
|
||||
node_db = self._get_node_row(nodes[0])
|
||||
self.assertGreater(node_db.updated_at, node_db.created_at)
|
||||
|
||||
# Assert the other two nodes hasn't been updated
|
||||
for node in nodes[1:]:
|
||||
node_db = self._get_node_row(node)
|
||||
self.assertEqual(node_db.created_at, node_db.updated_at)
|
||||
|
||||
def test_active_nodes_different_groups(self):
|
||||
another_group = 'another_test_group'
|
||||
self._add_nodes_and_assert_exists(count=3)
|
||||
self._add_nodes_and_assert_exists(count=2, group_name=another_group)
|
||||
|
||||
active_nodes = ovn_hash_ring_db.get_active_nodes(
|
||||
self.admin_ctx, interval=60, group_name=HASH_RING_TEST_GROUP)
|
||||
self.assertEqual(3, len(active_nodes))
|
||||
for node in active_nodes:
|
||||
self.assertEqual(HASH_RING_TEST_GROUP, node.group_name)
|
||||
|
||||
active_nodes = ovn_hash_ring_db.get_active_nodes(
|
||||
self.admin_ctx, interval=60, group_name=another_group)
|
||||
self.assertEqual(2, len(active_nodes))
|
||||
for node in active_nodes:
|
||||
self.assertEqual(another_group, node.group_name)
|
||||
|
||||
def test_remove_nodes_from_host_different_groups(self):
|
||||
another_group = 'another_test_group'
|
||||
group1 = self._add_nodes_and_assert_exists(count=3)
|
||||
group2 = self._add_nodes_and_assert_exists(
|
||||
count=2, group_name=another_group)
|
||||
|
||||
ovn_hash_ring_db.remove_nodes_from_host(self.admin_ctx,
|
||||
HASH_RING_TEST_GROUP)
|
||||
# Assert that all nodes from that group have been removed
|
||||
for node in group1:
|
||||
self.assertIsNone(self._get_node_row(node))
|
||||
|
||||
# Assert that all nodes from a different group are intact
|
||||
for node in group2:
|
||||
self.assertIsNotNone(self._get_node_row(node))
|
||||
|
||||
def test_touch_nodes_from_host_different_groups(self):
|
||||
another_group = 'another_test_group'
|
||||
group1 = self._add_nodes_and_assert_exists(count=3)
|
||||
group2 = self._add_nodes_and_assert_exists(
|
||||
count=2, group_name=another_group)
|
||||
|
||||
# Assert that updated_at isn't updated yet
|
||||
for node in group1 + group2:
|
||||
node_db = self._get_node_row(node)
|
||||
self.assertEqual(node_db.created_at, node_db.updated_at)
|
||||
|
||||
# Touch the nodes from group1
|
||||
ovn_hash_ring_db.touch_nodes_from_host(self.admin_ctx,
|
||||
HASH_RING_TEST_GROUP)
|
||||
|
||||
# Assert that updated_at was updated for group1
|
||||
for node in group1:
|
||||
node_db = self._get_node_row(node)
|
||||
self.assertGreater(node_db.updated_at, node_db.created_at)
|
||||
|
||||
# Assert that updated_at wasn't updated for group2
|
||||
for node in group2:
|
||||
node_db = self._get_node_row(node)
|
||||
self.assertEqual(node_db.created_at, node_db.updated_at)
|
Loading…
Reference in New Issue
Block a user