17 changed files with 539 additions and 68 deletions
@ -0,0 +1,96 @@
|
||||
# Copyright 2016 Hewlett-Packard Development Company, L.P. |
||||
# |
||||
# 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 time |
||||
|
||||
from oslo_config import cfg |
||||
from oslo_log import log as logging |
||||
|
||||
from octavia.common import exceptions |
||||
from octavia.db import api as db_apis |
||||
from octavia.db import repositories as repo |
||||
|
||||
LOG = logging.getLogger(__name__) |
||||
|
||||
CONF = cfg.CONF |
||||
CONF.import_group('haproxy_amphora', 'octavia.common.config') |
||||
|
||||
|
||||
class AmphoraBuildRateLimit(object): |
||||
|
||||
def __init__(self): |
||||
self.amp_build_slots_repo = repo.AmphoraBuildSlotsRepository() |
||||
self.amp_build_req_repo = repo.AmphoraBuildReqRepository() |
||||
|
||||
def add_to_build_request_queue(self, amphora_id, build_priority): |
||||
self.amp_build_req_repo.add_to_build_queue( |
||||
db_apis.get_session(), |
||||
amphora_id=amphora_id, |
||||
priority=build_priority) |
||||
LOG.debug("Added build request for %s to the queue", amphora_id) |
||||
self.wait_for_build_slot(amphora_id) |
||||
|
||||
def has_build_slot(self): |
||||
build_rate_limit = CONF.haproxy_amphora.build_rate_limit |
||||
session = db_apis.get_session() |
||||
with session.begin(subtransactions=True): |
||||
used_build_slots = (self.amp_build_slots_repo |
||||
.get_used_build_slots_count(session)) |
||||
available_build_slots = build_rate_limit - used_build_slots |
||||
LOG.debug("Available build slots %d", available_build_slots) |
||||
return available_build_slots > 0 |
||||
|
||||
def has_highest_priority(self, amphora_id): |
||||
session = db_apis.get_session() |
||||
with session.begin(subtransactions=True): |
||||
highest_priority_build_req = ( |
||||
self.amp_build_req_repo.get_highest_priority_build_req( |
||||
session)) |
||||
LOG.debug("Highest priority req: %s, Current req: %s", |
||||
highest_priority_build_req, amphora_id) |
||||
return amphora_id == highest_priority_build_req |
||||
|
||||
def update_build_status_and_available_build_slots(self, amphora_id): |
||||
session = db_apis.get_session() |
||||
with session.begin(subtransactions=True): |
||||
self.amp_build_slots_repo.update_count(session, action='increment') |
||||
self.amp_build_req_repo.update_req_status(session, amphora_id) |
||||
|
||||
def remove_from_build_req_queue(self, amphora_id): |
||||
session = db_apis.get_session() |
||||
with session.begin(subtransactions=True): |
||||
self.amp_build_req_repo.delete(session, amphora_id=amphora_id) |
||||
self.amp_build_slots_repo.update_count(session, action='decrement') |
||||
LOG.debug("Removed request for %s from queue" |
||||
" and released the build slot", amphora_id) |
||||
|
||||
def remove_all_from_build_req_queue(self): |
||||
session = db_apis.get_session() |
||||
with session.begin(subtransactions=True): |
||||
self.amp_build_req_repo.delete_all(session) |
||||
self.amp_build_slots_repo.update_count(session, action='reset') |
||||
LOG.debug("Removed all the build requests and " |
||||
"released the build slots") |
||||
|
||||
def wait_for_build_slot(self, amphora_id): |
||||
LOG.debug("Waiting for a build slot") |
||||
for i in range(CONF.haproxy_amphora.build_active_retries): |
||||
if (self.has_build_slot() and |
||||
self.has_highest_priority(amphora_id)): |
||||
self.update_build_status_and_available_build_slots(amphora_id) |
||||
return |
||||
time.sleep(CONF.haproxy_amphora.build_retry_interval) |
||||
self.remove_all_from_build_req_queue() |
||||
raise exceptions.ComputeBuildQueueTimeoutException() |
@ -0,0 +1,64 @@
|
||||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP |
||||
# |
||||
# 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. |
||||
|
||||
"""create_amphora_build_rate_limit_tables |
||||
|
||||
Revision ID: fc5582da7d8a |
||||
Revises: 443fe6676637 |
||||
Create Date: 2016-04-07 19:42:28.171902 |
||||
|
||||
""" |
||||
|
||||
from alembic import op |
||||
import sqlalchemy as sa |
||||
from sqlalchemy import sql |
||||
|
||||
# revision identifiers, used by Alembic. |
||||
revision = 'fc5582da7d8a' |
||||
down_revision = '443fe6676637' |
||||
|
||||
|
||||
def upgrade(): |
||||
op.create_table( |
||||
u'amphora_build_slots', |
||||
sa.Column(u'id', sa.Integer(), primary_key=True), |
||||
sa.Column(u'slots_used', sa.Integer(), default=0) |
||||
) |
||||
|
||||
# Create temporary table for table data seeding |
||||
insert_table = sql.table( |
||||
u'amphora_build_slots', |
||||
sql.column(u'id', sa.Integer), |
||||
sql.column(u'slots_used', sa.Integer) |
||||
) |
||||
|
||||
op.bulk_insert( |
||||
insert_table, |
||||
[ |
||||
{'id': 1, 'slots_used': 0} |
||||
] |
||||
) |
||||
|
||||
op.create_table( |
||||
u'amphora_build_request', |
||||
sa.Column(u'amphora_id', sa.String(36), nullable=True, |
||||
primary_key=True), |
||||
sa.Column(u'priority', sa.Integer()), |
||||
sa.Column(u'created_time', sa.DateTime(timezone=True), nullable=False), |
||||
sa.Column(u'status', sa.String(16), default='WAITING', nullable=False) |
||||
) |
||||
|
||||
|
||||
def downgrade(): |
||||
pass |
@ -0,0 +1,129 @@
|
||||
# Copyright 2016 Hewlett-Packard Development Company, L.P. |
||||
# |
||||
# 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 oslo_config import cfg |
||||
from oslo_config import fixture as oslo_fixture |
||||
from oslo_utils import uuidutils |
||||
|
||||
from octavia.controller.worker import amphora_rate_limit |
||||
import octavia.tests.unit.base as base |
||||
|
||||
AMP_ID = uuidutils.generate_uuid() |
||||
BUILD_PRIORITY = 40 |
||||
USED_BUILD_SLOTS = 0 |
||||
|
||||
|
||||
class TestAmphoraBuildRateLimit(base.TestCase): |
||||
|
||||
def setUp(self): |
||||
super(TestAmphoraBuildRateLimit, self).setUp() |
||||
self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF)) |
||||
|
||||
self.rate_limit = amphora_rate_limit.AmphoraBuildRateLimit() |
||||
self.amp_build_slots_repo = mock.MagicMock() |
||||
self.amp_build_req_repo = mock.MagicMock() |
||||
self.conf.config(group='haproxy_amphora', build_rate_limit=1) |
||||
|
||||
@mock.patch('octavia.db.api.get_session', mock.MagicMock()) |
||||
@mock.patch('octavia.controller.worker.amphora_rate_limit' |
||||
'.AmphoraBuildRateLimit.wait_for_build_slot') |
||||
@mock.patch('octavia.db.repositories.AmphoraBuildReqRepository' |
||||
'.add_to_build_queue') |
||||
def test_add_to_build_request_queue(self, |
||||
mock_add_to_build_queue, |
||||
mock_wait_for_build_slot): |
||||
self.rate_limit.add_to_build_request_queue(AMP_ID, BUILD_PRIORITY) |
||||
|
||||
mock_add_to_build_queue.assert_called_once() |
||||
mock_wait_for_build_slot.assert_called_once() |
||||
|
||||
@mock.patch('octavia.db.api.get_session', mock.MagicMock()) |
||||
@mock.patch('octavia.db.repositories.AmphoraBuildSlotsRepository' |
||||
'.get_used_build_slots_count', |
||||
return_value=USED_BUILD_SLOTS) |
||||
def test_has_build_slot(self, mock_get_used_build_slots_count): |
||||
result = self.rate_limit.has_build_slot() |
||||
|
||||
mock_get_used_build_slots_count.assert_called_once() |
||||
self.assertTrue(result) |
||||
|
||||
@mock.patch('octavia.db.api.get_session', mock.MagicMock()) |
||||
@mock.patch('octavia.db.repositories.AmphoraBuildReqRepository' |
||||
'.get_highest_priority_build_req', return_value=AMP_ID) |
||||
def test_has_highest_priority(self, mock_get_highest_priority_build_req): |
||||
result = self.rate_limit.has_highest_priority(AMP_ID) |
||||
|
||||
mock_get_highest_priority_build_req.assert_called_once() |
||||
self.assertTrue(result) |
||||
|
||||
@mock.patch('octavia.db.api.get_session', mock.MagicMock()) |
||||
@mock.patch('octavia.db.repositories.AmphoraBuildReqRepository' |
||||
'.update_req_status') |
||||
@mock.patch('octavia.db.repositories.AmphoraBuildSlotsRepository' |
||||
'.update_count') |
||||
def test_update_build_status_and_available_build_slots(self, |
||||
mock_update_count, |
||||
mock_update_status): |
||||
self.rate_limit.update_build_status_and_available_build_slots(AMP_ID) |
||||
|
||||
mock_update_count.assert_called_once() |
||||
mock_update_status.assert_called_once() |
||||
|
||||
@mock.patch('octavia.db.api.get_session', mock.MagicMock()) |
||||
@mock.patch('octavia.db.repositories.AmphoraBuildReqRepository.delete') |
||||
@mock.patch('octavia.db.repositories.AmphoraBuildSlotsRepository' |
||||
'.update_count') |
||||
def test_remove_from_build_req_queue(self, |
||||
mock_update_count, |
||||
mock_delete): |
||||
self.rate_limit.remove_from_build_req_queue(AMP_ID) |
||||
|
||||
mock_update_count.assert_called_once() |
||||
mock_delete.assert_called_once() |
||||
|
||||
@mock.patch('octavia.db.api.get_session', mock.MagicMock()) |
||||
@mock.patch('octavia.db.repositories.AmphoraBuildReqRepository' |
||||
'.delete_all') |
||||
@mock.patch('octavia.db.repositories.AmphoraBuildSlotsRepository' |
||||
'.update_count') |
||||
def test_remove_all_from_build_req_queue(self, |
||||
mock_update_count, |
||||
mock_delete_all): |
||||
self.rate_limit.remove_all_from_build_req_queue() |
||||
|
||||
mock_update_count.assert_called_once() |
||||
mock_delete_all.assert_called_once() |
||||
|
||||
@mock.patch('octavia.controller.worker.amphora_rate_limit' |
||||
'.AmphoraBuildRateLimit.has_build_slot', return_value=True) |
||||
@mock.patch('octavia.controller.worker.amphora_rate_limit' |
||||
'.AmphoraBuildRateLimit.has_highest_priority', |
||||
return_value=True) |
||||
@mock.patch('octavia.controller.worker.amphora_rate_limit' |
||||
'.AmphoraBuildRateLimit.' |
||||
'update_build_status_and_available_build_slots') |
||||
@mock.patch('octavia.controller.worker.amphora_rate_limit' |
||||
'.AmphoraBuildRateLimit.remove_all_from_build_req_queue') |
||||
@mock.patch('time.sleep') |
||||
def test_wait_for_build_slot(self, |
||||
mock_time_sleep, |
||||
mock_remove_all, |
||||
mock_update_status_and_slots_count, |
||||
mock_has_high_priority, |
||||
mock_has_build_slot): |
||||
self.rate_limit.wait_for_build_slot(AMP_ID) |
||||
|
||||
self.assertTrue(mock_has_build_slot.called) |
||||
self.assertTrue(mock_has_high_priority.called) |
Loading…
Reference in new issue