Implements: blueprint anti-affinity server group

https://blueprints.launchpad.net/octavia/+spec/anti-affinity
Added a new column in lb table for server group id;
Added a new task in compute tasks for creating server group;
Added a new task in dtabase tasks to update server
group id info for lb;
Add server group id in create method in nova driver to support
anti-affinity when creating compute instance

Change-Id: If0d3a9ba1012651937a2bda9bc95ab4f4c8852d5
This commit is contained in:
minwang 2016-02-18 10:33:25 -08:00
parent dd542b1080
commit 07a608f681
25 changed files with 518 additions and 47 deletions

View File

@ -231,6 +231,10 @@
# the OpenStack services.
# endpoint_type = publicURL
# Flag to enable nova anti-affinity capabilities to place amphorae on
# different hosts
# enable_anti_affinity = False
[neutron]
# The name of the neutron service in the keystone catalog
# service_name =

View File

@ -359,6 +359,9 @@ nova_opts = [
'communication with the OpenStack services.')),
cfg.StrOpt('endpoint_type', default='publicURL',
help=_('Endpoint interface in identity service to use')),
cfg.BoolOpt('enable_anti_affinity', default=False,
help=_('Flag to indicate if nova anti-affinity feature is '
'turned on.'))
]
neutron_opts = [

View File

@ -139,6 +139,8 @@ LISTENER = 'listener'
LISTENERS = 'listeners'
LOADBALANCER = 'loadbalancer'
LOADBALANCER_ID = 'loadbalancer_id'
SERVER_GROUP_ID = 'server_group_id'
ANTI_AFFINITY = 'anti-affinity'
MEMBER = 'member'
MEMBER_ID = 'member_id'
COMPUTE_ID = 'compute_id'
@ -166,6 +168,9 @@ CREATE_AMPHORA_FLOW = 'octavia-create-amphora-flow'
CREATE_AMPHORA_FOR_LB_FLOW = 'octavia-create-amp-for-lb-flow'
CREATE_HEALTH_MONITOR_FLOW = 'octavia-create-health-monitor-flow'
CREATE_LISTENER_FLOW = 'octavia-create-listener_flow'
PRE_CREATE_LOADBALANCER_FLOW = 'octavia-pre-create-loadbalancer-flow'
CREATE_SERVER_GROUP_FLOW = 'octavia-create-server-group-flow'
UPDATE_LB_SERVERGROUPID_FLOW = 'octavia-update-lb-server-group-id-flow'
CREATE_LOADBALANCER_FLOW = 'octavia-create-loadbalancer-flow'
CREATE_MEMBER_FLOW = 'octavia-create-member-flow'
CREATE_POOL_FLOW = 'octavia-create-pool-flow'
@ -227,7 +232,6 @@ CREATE_VRRP_SECURITY_RULES = 'octavia-create-vrrp-security-rules'
GENERATE_SERVER_PEM_TASK = 'GenerateServerPEMTask'
# Task Names
RELOAD_LB_AFTER_AMP_ASSOC = 'reload-lb-after-amp-assoc'
RELOAD_LB_AFTER_PLUG_VIP = 'reload-lb-after-plug-vip'

View File

@ -310,7 +310,8 @@ class LoadBalancer(BaseDataModel):
def __init__(self, id=None, project_id=None, name=None, description=None,
provisioning_status=None, operating_status=None, enabled=None,
topology=None, vip=None, listeners=None, amphorae=None,
pools=None, vrrp_group=None):
pools=None, vrrp_group=None, server_group_id=None):
self.id = id
self.project_id = project_id
self.name = name
@ -324,6 +325,7 @@ class LoadBalancer(BaseDataModel):
self.listeners = listeners or []
self.amphorae = amphorae or []
self.pools = pools or []
self.server_group_id = server_group_id
class VRRPGroup(BaseDataModel):

View File

@ -217,3 +217,11 @@ class InvalidRegex(OctaviaException):
class InvalidL7Rule(OctaviaException):
message = _LE('Invalid L7 Rule: $(msg)s')
class ServerGroupObjectCreateException(OctaviaException):
message = _LE('Failed to create server group object.')
class ServerGroupObjectDeleteException(OctaviaException):
message = _LE('Failed to delete server group object.')

View File

@ -23,7 +23,7 @@ class ComputeBase(object):
@abc.abstractmethod
def build(self, name="amphora_name", amphora_flavor=None, image_id=None,
key_name=None, sec_groups=None, network_ids=None,
config_drive_files=None, user_data=None):
config_drive_files=None, user_data=None, server_group_id=None):
"""Build a new amphora.
:param name: Optional name for Amphora
@ -40,6 +40,8 @@ class ComputeBase(object):
:param user_data: Optional user data to pass to be exposed by the
metadata server this can be a file type object as well or
a string
:param server_group_id: Optional server group id(uuid) which is used
for anti_affinity feature
:raises ComputeBuildException: if compute failed to build amphora
:returns: UUID of amphora
@ -71,3 +73,21 @@ class ComputeBase(object):
:returns: the amphora object
"""
pass
@abc.abstractmethod
def create_server_group(self, name, policy):
"""Create a server group object
:param name: the name of the server group
:param policy: the policy of the server group
:returns: the server group object
"""
pass
@abc.abstractmethod
def delete_server_group(self, server_group_id):
"""Delete a server group object
:param server_group_id: the uuid of a server group
"""
pass

View File

@ -29,13 +29,15 @@ class NoopManager(object):
def build(self, name="amphora_name", amphora_flavor=None, image_id=None,
key_name=None, sec_groups=None, network_ids=None,
config_drive_files=None, user_data=None, port_ids=None):
config_drive_files=None, user_data=None, port_ids=None,
server_group_id=None):
LOG.debug("Compute %s no-op, build name %s, amphora_flavor %s, "
"image_id %s, key_name %s, sec_groups %s, network_ids %s,"
"config_drive_files %s, user_data %s, port_ids %s",
"config_drive_files %s, user_data %s, port_ids %s,"
"server_group_id %s",
self.__class__.__name__, name, amphora_flavor, image_id,
key_name, sec_groups, network_ids, config_drive_files,
user_data, port_ids)
user_data, port_ids, server_group_id)
self.computeconfig[(name, amphora_flavor, image_id, key_name,
user_data)] = (
name, amphora_flavor,
@ -66,6 +68,16 @@ class NoopManager(object):
lb_network_ip='192.0.2.1'
)
def create_server_group(self, name, policy):
LOG.debug("Create Server Group %s no-op, name %s, policy %s ",
self.__class__.__name__, name, policy)
self.computeconfig[(name, policy)] = (name, policy, 'create')
def delete_server_group(self, server_group_id):
LOG.debug("Delete Server Group %s no-op, id %s ",
self.__class__.__name__, server_group_id)
self.computeconfig[server_group_id] = (server_group_id, 'delete')
class NoopComputeDriver(driver_base.ComputeBase):
def __init__(self):
@ -74,11 +86,13 @@ class NoopComputeDriver(driver_base.ComputeBase):
def build(self, name="amphora_name", amphora_flavor=None, image_id=None,
key_name=None, sec_groups=None, network_ids=None,
config_drive_files=None, user_data=None, port_ids=None):
config_drive_files=None, user_data=None, port_ids=None,
server_group_id=None):
compute_id = self.driver.build(name, amphora_flavor, image_id,
key_name, sec_groups, network_ids,
config_drive_files, user_data, port_ids)
config_drive_files, user_data, port_ids,
server_group_id)
return compute_id
def delete(self, compute_id):
@ -89,3 +103,9 @@ class NoopComputeDriver(driver_base.ComputeBase):
def get_amphora(self, compute_id):
return self.driver.get_amphora(compute_id)
def create_server_group(self, name, policy):
return self.driver.create_server_group(name, policy)
def delete_server_group(self, server_group_id):
self.driver.delete_server_group(server_group_id)

View File

@ -42,10 +42,12 @@ class VirtualMachineManager(compute_base.ComputeBase):
region=CONF.nova.region_name,
endpoint_type=CONF.nova.endpoint_type)
self.manager = self._nova_client.servers
self.server_groups = self._nova_client.server_groups
def build(self, name="amphora_name", amphora_flavor=None, image_id=None,
key_name=None, sec_groups=None, network_ids=None,
port_ids=None, config_drive_files=None, user_data=None):
port_ids=None, config_drive_files=None, user_data=None,
server_group_id=None):
'''Create a new virtual machine.
:param name: optional name for amphora
@ -63,6 +65,8 @@ class VirtualMachineManager(compute_base.ComputeBase):
:param user_data: Optional user data to pass to be exposed by the
metadata server this can be a file type object as well or
a string
:param server_group_id: Optional server group id(uuid) which is used
for anti_affinity feature
:raises ComputeBuildException: if nova failed to build virtual machine
:returns: UUID of amphora
@ -77,13 +81,17 @@ class VirtualMachineManager(compute_base.ComputeBase):
if port_ids:
nics.extend([{"port-id": port_id} for port_id in port_ids])
server_group = None if server_group_id is None else {
"group": server_group_id}
amphora = self.manager.create(
name=name, image=image_id, flavor=amphora_flavor,
key_name=key_name, security_groups=sec_groups,
nics=nics,
files=config_drive_files,
userdata=user_data,
config_drive=True
config_drive=True,
scheduler_hints=server_group
)
return amphora.id
@ -175,3 +183,36 @@ class VirtualMachineManager(compute_base.ComputeBase):
lb_network_ip=lb_network_ip
)
return response
def create_server_group(self, name, policy):
"""Create a server group object
:param name: the name of the server group
:param policy: the policy of the server group
:raises: Generic exception if the server group is not created
:returns: the server group object
"""
kwargs = {'name': name,
'policies': [policy]}
try:
server_group_obj = self.server_groups.create(**kwargs)
return server_group_obj
except Exception:
LOG.exception(_LE("Error create server group instance."))
raise exceptions.ServerGroupObjectCreateException()
def delete_server_group(self, server_group_id):
"""Delete a server group object
:raises: Generic exception if the server group is not deleted
:param server_group_id: the uuid of a server group
"""
try:
self.server_groups.delete(server_group_id)
except nova_exceptions.NotFound:
LOG.warn(_LW("Server group instance with id: %s not found. "
"Assuming already deleted."), server_group_id)
except Exception:
LOG.exception(_LE("Error delete server group instance."))
raise exceptions.ServerGroupObjectDeleteException()

View File

@ -310,9 +310,12 @@ class ControllerWorker(base_taskflow.BaseTaskFlowEngine):
lb = self._lb_repo.get(db_apis.get_session(),
id=load_balancer_id)
store = {constants.LOADBALANCER: lb,
constants.SERVER_GROUP_ID: lb.server_group_id}
delete_lb_tf = self._taskflow_load(self._lb_flows.
get_delete_load_balancer_flow(),
store={constants.LOADBALANCER: lb})
store=store)
with tf_logging.DynamicLoggingListener(delete_lb_tf,
log=LOG):

View File

@ -26,6 +26,7 @@ from octavia.controller.worker.tasks import network_tasks
CONF = cfg.CONF
CONF.import_group('controller_worker', 'octavia.common.config')
CONF.import_group('nova', 'octavia.common.config')
class AmphoraFlows(object):
@ -111,11 +112,12 @@ class AmphoraFlows(object):
sf_name = prefix + '-' + constants.CREATE_AMP_FOR_LB_SUBFLOW
create_amp_for_lb_subflow = linear_flow.Flow(sf_name)
create_amp_for_lb_subflow.add(database_tasks.CreateAmphoraInDB(
name=sf_name + '-' + constants.CREATE_AMPHORA_INDB,
provides=constants.AMPHORA_ID))
anti_affinity = CONF.nova.enable_anti_affinity
if self.REST_AMPHORA_DRIVER:
create_amp_for_lb_subflow.add(cert_task.GenerateServerPEMTask(
name=sf_name + '-' + constants.GENERATE_SERVER_PEM,
@ -126,11 +128,27 @@ class AmphoraFlows(object):
name=sf_name + '-' + constants.UPDATE_CERT_EXPIRATION,
requires=(constants.AMPHORA_ID, constants.SERVER_PEM)))
if role in (constants.ROLE_BACKUP, constants.ROLE_MASTER
) and anti_affinity:
create_amp_for_lb_subflow.add(compute_tasks.CertComputeCreate(
name=sf_name + '-' + constants.CERT_COMPUTE_CREATE,
requires=(constants.AMPHORA_ID, constants.SERVER_PEM,
constants.SERVER_GROUP_ID),
provides=constants.COMPUTE_ID))
else:
create_amp_for_lb_subflow.add(compute_tasks.CertComputeCreate(
name=sf_name + '-' + constants.CERT_COMPUTE_CREATE,
requires=(constants.AMPHORA_ID, constants.SERVER_PEM),
provides=constants.COMPUTE_ID))
else:
if role in (constants.ROLE_BACKUP, constants.ROLE_MASTER
) and anti_affinity:
create_amp_for_lb_subflow.add(compute_tasks.ComputeCreate(
name=sf_name + '-' + constants.COMPUTE_CREATE,
requires=(constants.AMPHORA_ID, constants.SERVER_GROUP_ID),
provides=constants.COMPUTE_ID))
else:
create_amp_for_lb_subflow.add(compute_tasks.ComputeCreate(
name=sf_name + '-' + constants.COMPUTE_CREATE,
requires=constants.AMPHORA_ID,

View File

@ -31,6 +31,7 @@ from octavia.i18n import _LE
CONF = cfg.CONF
CONF.import_group('controller_worker', 'octavia.common.config')
CONF.import_group('nova', 'octavia.common.config')
LOG = logging.getLogger(__name__)
@ -44,18 +45,46 @@ class LoadBalancerFlows(object):
two spare amphorae.
:raises InvalidTopology: Invalid topology specified
:return: The graph flow for creating an active_standby loadbalancer.
:return: The graph flow for creating a loadbalancer.
"""
# create a linear flow as a wrapper
lf_name = constants.PRE_CREATE_LOADBALANCER_FLOW
create_lb_flow_wrapper = linear_flow.Flow(lf_name)
f_name = constants.CREATE_LOADBALANCER_FLOW
lb_create_flow = unordered_flow.Flow(f_name)
if topology == constants.TOPOLOGY_ACTIVE_STANDBY:
# When we boot up amphora for an active/standby topology,
# we should leverage the Nova anti-affinity capabilities
# to place the amphora on different hosts, also we need to check
# if anti-affinity-flag is enabled or not:
anti_affinity = CONF.nova.enable_anti_affinity
if anti_affinity:
# we need to create a server group first
create_lb_flow_wrapper.add(
compute_tasks.NovaServerGroupCreate(
name=lf_name + '-' +
constants.CREATE_SERVER_GROUP_FLOW,
requires=(constants.LOADBALANCER_ID),
provides=constants.SERVER_GROUP_ID))
# update server group id in lb table
create_lb_flow_wrapper.add(
database_tasks.UpdateLBServerGroupInDB(
name=lf_name + '-' +
constants.UPDATE_LB_SERVERGROUPID_FLOW,
requires=(constants.LOADBALANCER_ID,
constants.SERVER_GROUP_ID)))
master_amp_sf = self.amp_flows.get_amphora_for_lb_subflow(
prefix=constants.ROLE_MASTER, role=constants.ROLE_MASTER)
backup_amp_sf = self.amp_flows.get_amphora_for_lb_subflow(
prefix=constants.ROLE_BACKUP, role=constants.ROLE_BACKUP)
lb_create_flow.add(master_amp_sf, backup_amp_sf)
elif topology == constants.TOPOLOGY_SINGLE:
amphora_sf = self.amp_flows.get_amphora_for_lb_subflow(
prefix=constants.ROLE_STANDALONE,
@ -66,7 +95,8 @@ class LoadBalancerFlows(object):
"balancer."), topology)
raise exceptions.InvalidTopology(topology=topology)
return lb_create_flow
create_lb_flow_wrapper.add(lb_create_flow)
return create_lb_flow_wrapper
def get_post_lb_amp_association_flow(self, prefix, topology):
"""Reload the loadbalancer and create networking subflows for
@ -109,6 +139,8 @@ class LoadBalancerFlows(object):
:returns: The flow for deleting a load balancer
"""
delete_LB_flow = linear_flow.Flow(constants.DELETE_LOADBALANCER_FLOW)
delete_LB_flow.add(compute_tasks.NovaServerGroupDelete(
requires=constants.SERVER_GROUP_ID))
delete_LB_flow.add(database_tasks.MarkLBAmphoraeHealthBusy(
requires=constants.LOADBALANCER))
delete_LB_flow.add(controller_tasks.DeleteListenersOnLB(

View File

@ -48,7 +48,8 @@ class BaseComputeTask(task.Task):
class ComputeCreate(BaseComputeTask):
"""Create the compute instance for a new amphora."""
def execute(self, amphora_id, ports=None, config_drive_files=None):
def execute(self, amphora_id, ports=None, config_drive_files=None,
server_group_id=None):
"""Create an amphora
:returns: an amphora
@ -82,7 +83,8 @@ class ComputeCreate(BaseComputeTask):
network_ids=[CONF.controller_worker.amp_network],
port_ids=[port.id for port in ports],
config_drive_files=config_drive_files,
user_data=user_data)
user_data=user_data,
server_group_id=server_group_id)
LOG.debug("Server created with id: %s for amphora id: %s",
compute_id, amphora_id)
@ -111,7 +113,8 @@ class ComputeCreate(BaseComputeTask):
class CertComputeCreate(ComputeCreate):
def execute(self, amphora_id, server_pem, ports=None):
def execute(self, amphora_id, server_pem, ports=None,
server_group_id=None):
"""Create an amphora
:returns: an amphora
@ -125,7 +128,8 @@ class CertComputeCreate(ComputeCreate):
'/etc/octavia/certs/server.pem': server_pem,
'/etc/octavia/certs/client_ca.pem': ca}
return super(CertComputeCreate, self).execute(
amphora_id, ports=ports, config_drive_files=config_drive_files)
amphora_id, ports=ports, config_drive_files=config_drive_files,
server_group_id=server_group_id)
class DeleteAmphoraeOnLoadBalancer(BaseComputeTask):
@ -177,3 +181,39 @@ class ComputeWait(BaseComputeTask):
time.sleep(CONF.controller_worker.amp_active_wait_sec)
raise exceptions.ComputeWaitTimeoutException()
class NovaServerGroupCreate(BaseComputeTask):
def execute(self, loadbalancer_id):
"""Create a server group by nova client api
:param loadbalancer_id: will be used for server group's name
:param policy: will used for server group's policy
:raises: Generic exception if the server group is not created
:returns: server group's id
"""
name = 'octavia-lb-' + loadbalancer_id
server_group = self.compute.create_server_group(
name, constants.ANTI_AFFINITY)
LOG.debug("Server Group created with id: %s for load balancer id: "
"%s", server_group.id, loadbalancer_id)
return server_group.id
def revert(self, result, *args, **kwargs):
"""This method will revert the creation of the
:param result: here it refers to server group id
"""
server_group_id = result
LOG.warn(_LW("Reverting server group create with id:%s"),
server_group_id)
self.compute.delete_server_group(server_group_id)
class NovaServerGroupDelete(BaseComputeTask):
def execute(self, server_group_id):
if server_group_id is not None:
self.compute.delete_server_group(server_group_id)
else:
return

View File

@ -690,6 +690,25 @@ class MarkLBActiveInDB(BaseDatabaseTask):
provisioning_status=constants.ERROR)
class UpdateLBServerGroupInDB(BaseDatabaseTask):
"""Update the server group id info for load balancer in DB."""
def execute(self, loadbalancer_id, server_group_id):
LOG.debug("Server Group updated with id: %s for load balancer id: %s:",
server_group_id, loadbalancer_id)
self.loadbalancer_repo.update(db_apis.get_session(),
id=loadbalancer_id,
server_group_id=server_group_id)
def revert(self, loadbalancer_id, server_group_id, *args, **kwargs):
LOG.warn(_LW('Reverting Server Group updated with id: %(s1)s for '
'load balancer id: %(s2)s '),
{'s1': server_group_id, 's2': loadbalancer_id})
self.loadbalancer_repo.update(db_apis.get_session(),
id=loadbalancer_id,
server_group_id=None)
class MarkLBDeletedInDB(BaseDatabaseTask):
"""Mark the load balancer deleted in the DB.

View File

@ -0,0 +1,38 @@
# 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.
#
"""add_server_group_id_to_loadbalancer
Revision ID: 186509101b9b
Revises: 29ff921a6eb
Create Date: 2016-01-25 15:12:52.489652
"""
# revision identifiers, used by Alembic.
revision = '186509101b9b'
down_revision = '458c9ee2a011'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column(u'load_balancer', sa.Column(u'server_group_id',
sa.String(36), nullable=True))
def downgrade():
pass

View File

@ -277,6 +277,7 @@ class LoadBalancer(base_models.BASE, base_models.IdMixin,
amphorae = orm.relationship("Amphora", uselist=True,
backref=orm.backref("load_balancer",
uselist=False))
server_group_id = sa.Column(sa.String(36), nullable=True)
class VRRPGroup(base_models.BASE):

View File

@ -103,7 +103,8 @@ class ModelTestMixin(object):
'id': self.FAKE_UUID_1,
'provisioning_status': constants.ACTIVE,
'operating_status': constants.ONLINE,
'enabled': True}
'enabled': True,
'server_group_id': self.FAKE_UUID_1}
kwargs.update(overrides)
return self._insert(session, models.LoadBalancer, kwargs)

View File

@ -120,6 +120,7 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
'operating_status': constants.OFFLINE,
'topology': constants.TOPOLOGY_ACTIVE_STANDBY,
'vrrp_group': None,
'server_group_id': uuidutils.generate_uuid(),
'project_id': uuidutils.generate_uuid()}
vip = {'ip_address': '10.0.0.1',
'port_id': uuidutils.generate_uuid(),
@ -547,7 +548,8 @@ class ListenerRepositoryTest(BaseRepositoryTest):
self.session, id=self.FAKE_UUID_1, project_id=self.FAKE_UUID_2,
name="lb_name", description="lb_description",
provisioning_status=constants.ACTIVE,
operating_status=constants.ONLINE, enabled=True)
operating_status=constants.ONLINE, enabled=True,
server_group_id=self.FAKE_UUID_1)
def create_listener(self, listener_id, port, default_pool_id=None):
listener = self.listener_repo.create(

View File

@ -36,15 +36,20 @@ class TestNoopComputeDriver(base.TestCase):
self.confdrivefiles = "config_driver_files"
self.user_data = "user_data"
self.amphora_id = self.FAKE_UUID_1
self.loadbalancer_id = self.FAKE_UUID_1
self.server_group_policy = 'anti-affinity'
self.server_group_id = self.FAKE_UUID_1
def build(self):
self.driver.build(self.name, self.amphora_flavor, self.image_id,
self.key_name, self.sec_groups, self.network_ids,
self.confdrivefiles, self.user_data)
self.confdrivefiles, self.user_data,
self.server_group_id)
self.assertEqual((self.name, self.amphora_flavor, self.image_id,
self.key_name, self.sec_groups, self.network_ids,
self.config_drive_files, self.user_data, 'build'),
self.config_drive_files, self.user_data,
self.server_group_id, 'build'),
self.driver.driver.computeconfig[(self.name,
self.amphora_flavor,
self.image_id,
@ -52,7 +57,9 @@ class TestNoopComputeDriver(base.TestCase):
self.sec_groups,
self.network_ids,
self.confdrivefiles,
self.user_data)])
self.user_data,
self.server_group_id
)])
def test_delete(self):
self.driver.delete(self.amphora_id)
@ -65,3 +72,10 @@ class TestNoopComputeDriver(base.TestCase):
def get_amphora(self):
self.driver.get_amphora(self.amphora_id)
def test_create_server_group(self):
self.driver.create_server_group(self.loadbalancer_id,
self.server_group_policy)
def test_delete_server_group(self):
self.driver.delete_server_group(self.server_group_id)

View File

@ -13,6 +13,7 @@
# under the License.
import mock
from novaclient import exceptions as nova_exceptions
from oslo_config import cfg
from oslo_utils import uuidutils
@ -50,19 +51,34 @@ class TestNovaClient(base.TestCase):
self.interface_list.fixed_ips = [mock.MagicMock()]
self.interface_list.fixed_ips[0] = {'ip_address': '10.0.0.1'}
self.loadbalancer_id = uuidutils.generate_uuid()
self.server_group_policy = constants.ANTI_AFFINITY
self.server_group_id = uuidutils.generate_uuid()
self.manager = nova_common.VirtualMachineManager()
self.manager.manager = mock.MagicMock()
self.manager.server_groups = mock.MagicMock()
self.manager._nova_client = mock.MagicMock()
self.nova_response.interface_list.side_effect = [[self.interface_list]]
self.manager.manager.get.return_value = self.nova_response
self.manager.manager.create.return_value = self.nova_response
self.manager.server_groups.create.return_value = mock.Mock()
self.nova_response.addresses = {self.net_name: [{'addr': '10.0.0.1'}]}
self.nova_network = mock.Mock()
self.nova_network.label = self.net_name
self.server_group_name = 'octavia-lb-' + self.loadbalancer_id
self.server_group_kwargs = {'name': self.server_group_name,
'policies': [self.server_group_policy]}
self.server_group_mock = mock.Mock()
self.server_group_mock.name = self.server_group_name
self.server_group_mock.policy = self.server_group_policy
self.server_group_mock.id = self.server_group_id
super(TestNovaClient, self).setUp()
def test_build(self):
@ -85,7 +101,8 @@ class TestNovaClient(base.TestCase):
security_groups=1,
files='Files Blah',
userdata='Blah',
config_drive=True)
config_drive=True,
scheduler_hints=None)
def test_bad_build(self):
self.manager.manager.create.side_effect = Exception
@ -144,3 +161,43 @@ class TestNovaClient(base.TestCase):
self.assertEqual(self.amphora, amphora)
self.assertTrue(self.nova_response.interface_list.called)
self.manager._nova_client.networks.get.called_with(self.net_name)
def test_create_server_group(self):
self.manager.server_groups.create.return_value = self.server_group_mock
sg = self.manager.create_server_group(self.server_group_name,
self.server_group_policy)
self.assertEqual(sg.id, self.server_group_id)
self.assertEqual(sg.name, self.server_group_name)
self.assertEqual(sg.policy, self.server_group_policy)
self.manager.server_groups.create.called_with(
**self.server_group_kwargs)
def test_bad_create_server_group(self):
self.manager.server_groups.create.side_effect = Exception
self.assertRaises(exceptions.ServerGroupObjectCreateException,
self.manager.create_server_group,
self.server_group_name, self.server_group_policy)
self.manager.server_groups.create.called_with(
**self.server_group_kwargs)
def test_delete_server_group(self):
self.manager.delete_server_group(self.server_group_id)
self.manager.server_groups.delete.called_with(self.server_group_id)
def test_bad_delete_server_group(self):
self.manager.server_groups.delete.side_effect = [
nova_exceptions.NotFound('test_exception'), Exception]
# NotFound should not raise an exception
self.manager.delete_server_group(self.server_group_id)
self.manager.server_groups.delete.called_with(self.server_group_id)
# Catch the exception for server group object delete exception
self.assertRaises(exceptions.ServerGroupObjectDeleteException,
self.manager.delete_server_group,
self.server_group_id)
self.manager.server_groups.delete.called_with(self.server_group_id)

View File

@ -37,6 +37,7 @@ class TestEndpoint(base.TestCase):
self.context = {}
self.resource_updates = {}
self.resource_id = 1234
self.server_group_id = 3456
def test_create_load_balancer(self):
self.ep.create_load_balancer(self.context, self.resource_id)

View File

@ -30,6 +30,8 @@ class TestAmphoraFlows(base.TestCase):
def setUp(self):
cfg.CONF.set_override('amphora_driver', 'amphora_haproxy_rest_driver',
group='controller_worker')
cfg.CONF.set_override('enable_anti_affinity', False,
group='nova')
self.AmpFlow = amphora_flows.AmphoraFlows()
conf = oslo_fixture.Config(cfg.CONF)
conf.config(group="keystone_authtoken", auth_version=AUTH_VERSION)
@ -89,6 +91,7 @@ class TestAmphoraFlows(base.TestCase):
def test_get_cert_create_amphora_for_lb_flow(self):
cfg.CONF.set_override('amphora_driver', 'amphora_haproxy_rest_driver',
group='controller_worker')
self.AmpFlow = amphora_flows.AmphoraFlows()
amp_flow = self.AmpFlow._get_create_amp_for_lb_subflow(
@ -110,6 +113,7 @@ class TestAmphoraFlows(base.TestCase):
def test_get_cert_master_create_amphora_for_lb_flow(self):
cfg.CONF.set_override('amphora_driver', 'amphora_haproxy_rest_driver',
group='controller_worker')
self.AmpFlow = amphora_flows.AmphoraFlows()
amp_flow = self.AmpFlow._get_create_amp_for_lb_subflow(
@ -128,6 +132,28 @@ class TestAmphoraFlows(base.TestCase):
self.assertEqual(5, len(amp_flow.provides))
self.assertEqual(1, len(amp_flow.requires))
def test_get_cert_master_rest_anti_affinity_create_amphora_for_lb_flow(
self):
cfg.CONF.set_override('amphora_driver', 'amphora_haproxy_rest_driver',
group='controller_worker')
cfg.CONF.set_override('enable_anti_affinity', True,
group='nova')
self.AmpFlow = amphora_flows.AmphoraFlows()
amp_flow = self.AmpFlow._get_create_amp_for_lb_subflow(
'SOMEPREFIX', constants.ROLE_MASTER)
self.assertIsInstance(amp_flow, flow.Flow)
self.assertIn(constants.AMPHORA_ID, amp_flow.provides)
self.assertIn(constants.SERVER_GROUP_ID, amp_flow.requires)
self.assertIn(constants.COMPUTE_ID, amp_flow.provides)
self.assertIn(constants.COMPUTE_OBJ, amp_flow.provides)
self.assertIn(constants.SERVER_PEM, amp_flow.provides)
self.assertEqual(5, len(amp_flow.provides))
self.assertEqual(2, len(amp_flow.requires))
def test_get_cert_backup_create_amphora_for_lb_flow(self):
cfg.CONF.set_override('amphora_driver', 'amphora_haproxy_rest_driver',
group='controller_worker')
@ -170,6 +196,27 @@ class TestAmphoraFlows(base.TestCase):
self.assertEqual(5, len(amp_flow.provides))
self.assertEqual(1, len(amp_flow.requires))
def test_get_cert_backup_rest_anti_affinity_create_amphora_for_lb_flow(
self):
cfg.CONF.set_override('amphora_driver', 'amphora_haproxy_rest_driver',
group='controller_worker')
cfg.CONF.set_override('enable_anti_affinity', True,
group='nova')
self.AmpFlow = amphora_flows.AmphoraFlows()
amp_flow = self.AmpFlow._get_create_amp_for_lb_subflow(
'SOMEPREFIX', constants.ROLE_BACKUP)
self.assertIsInstance(amp_flow, flow.Flow)
self.assertIn(constants.AMPHORA_ID, amp_flow.provides)
self.assertIn(constants.SERVER_GROUP_ID, amp_flow.requires)
self.assertIn(constants.COMPUTE_ID, amp_flow.provides)
self.assertIn(constants.COMPUTE_OBJ, amp_flow.provides)
self.assertIn(constants.SERVER_PEM, amp_flow.provides)
self.assertEqual(5, len(amp_flow.provides))
self.assertEqual(2, len(amp_flow.requires))
def test_get_delete_amphora_flow(self):
amp_flow = self.AmpFlow.get_delete_amphora_flow()

View File

@ -12,9 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
#
from oslo_config import cfg
from oslo_config import fixture as oslo_fixture
from taskflow.patterns import linear_flow as flow
from taskflow.patterns import unordered_flow
from octavia.common import constants
from octavia.common import exceptions
@ -26,13 +26,14 @@ class TestLoadBalancerFlows(base.TestCase):
def setUp(self):
self.LBFlow = load_balancer_flows.LoadBalancerFlows()
conf = oslo_fixture.Config(cfg.CONF)
conf.config(group="nova", enable_anti_affinity=False)
super(TestLoadBalancerFlows, self).setUp()
def test_get_create_load_balancer_flow(self):
amp_flow = self.LBFlow.get_create_load_balancer_flow(
constants.TOPOLOGY_SINGLE)
self.assertIsInstance(amp_flow, unordered_flow.Flow)
self.assertIsInstance(amp_flow, flow.Flow)
self.assertIn(constants.LOADBALANCER_ID, amp_flow.requires)
self.assertIn(constants.AMPHORA, amp_flow.provides)
self.assertIn(constants.AMPHORA_ID, amp_flow.provides)
@ -42,13 +43,27 @@ class TestLoadBalancerFlows(base.TestCase):
def test_get_create_active_standby_load_balancer_flow(self):
amp_flow = self.LBFlow.get_create_load_balancer_flow(
constants.TOPOLOGY_ACTIVE_STANDBY)
self.assertIsInstance(amp_flow, unordered_flow.Flow)
self.assertIsInstance(amp_flow, flow.Flow)
self.assertIn(constants.LOADBALANCER_ID, amp_flow.requires)
self.assertIn(constants.AMPHORA, amp_flow.provides)
self.assertIn(constants.AMPHORA_ID, amp_flow.provides)
self.assertIn(constants.COMPUTE_ID, amp_flow.provides)
self.assertIn(constants.COMPUTE_OBJ, amp_flow.provides)
def test_get_create_anti_affinity_active_standby_load_balancer_flow(self):
cfg.CONF.set_override('enable_anti_affinity', True,
group='nova')
self._LBFlow = load_balancer_flows.LoadBalancerFlows()
amp_flow = self._LBFlow.get_create_load_balancer_flow(
constants.TOPOLOGY_ACTIVE_STANDBY)
self.assertIsInstance(amp_flow, flow.Flow)
self.assertIn(constants.LOADBALANCER_ID, amp_flow.requires)
self.assertIn(constants.SERVER_GROUP_ID, amp_flow.provides)
self.assertIn(constants.AMPHORA, amp_flow.provides)
self.assertIn(constants.AMPHORA_ID, amp_flow.provides)
self.assertIn(constants.COMPUTE_ID, amp_flow.provides)
self.assertIn(constants.COMPUTE_OBJ, amp_flow.provides)
def test_get_create_bogus_topology_load_balancer_flow(self):
self.assertRaises(exceptions.InvalidTopology,
self.LBFlow.get_create_load_balancer_flow,
@ -61,9 +76,10 @@ class TestLoadBalancerFlows(base.TestCase):
self.assertIsInstance(lb_flow, flow.Flow)
self.assertIn(constants.LOADBALANCER, lb_flow.requires)
self.assertIn(constants.SERVER_GROUP_ID, lb_flow.requires)
self.assertEqual(0, len(lb_flow.provides))
self.assertEqual(1, len(lb_flow.requires))
self.assertEqual(2, len(lb_flow.requires))
def test_get_new_LB_networking_subflow(self):

View File

@ -19,6 +19,7 @@ from oslo_config import fixture as oslo_fixture
from oslo_utils import uuidutils
import six.moves.builtins as builtins
from octavia.common import constants
from octavia.common import exceptions
from octavia.controller.worker.tasks import compute_tasks
@ -35,6 +36,7 @@ COMPUTE_ID = uuidutils.generate_uuid()
LB_NET_IP = '192.0.2.1'
PORT_ID = uuidutils.generate_uuid()
AUTH_VERSION = '2'
SERVER_GRPOUP_ID = uuidutils.generate_uuid()
class TestException(Exception):
@ -85,7 +87,8 @@ class TestComputeTasks(base.TestCase):
mock_driver.build.return_value = COMPUTE_ID
# Test execute()
compute_id = createcompute.execute(_amphora_mock.id, ports=[_port])
compute_id = createcompute.execute(_amphora_mock.id, ports=[_port],
server_group_id=SERVER_GRPOUP_ID)
# Validate that the build method was called properly
mock_driver.build.assert_called_once_with(
@ -98,7 +101,8 @@ class TestComputeTasks(base.TestCase):
port_ids=[PORT_ID],
config_drive_files={'/etc/octavia/'
'amphora-agent.conf': 'test_conf'},
user_data=None)
user_data=None,
server_group_id=SERVER_GRPOUP_ID)
# Make sure it returns the expected compute_id
assert(compute_id == COMPUTE_ID)
@ -155,7 +159,8 @@ class TestComputeTasks(base.TestCase):
network_ids=[AMP_NET],
port_ids=[PORT_ID],
config_drive_files=None,
user_data='test_ud_conf')
user_data='test_ud_conf',
server_group_id=None)
# Make sure it returns the expected compute_id
assert(compute_id == COMPUTE_ID)
@ -199,7 +204,8 @@ class TestComputeTasks(base.TestCase):
conf.config(group="controller_worker", amp_ssh_access_allowed=False)
# Test execute()
compute_id = createcompute.execute(_amphora_mock.id, ports=[_port])
compute_id = createcompute.execute(_amphora_mock.id, ports=[_port],
server_group_id=SERVER_GRPOUP_ID)
# Validate that the build method was called properly
mock_driver.build.assert_called_once_with(
@ -212,7 +218,8 @@ class TestComputeTasks(base.TestCase):
port_ids=[PORT_ID],
config_drive_files={'/etc/octavia/'
'amphora-agent.conf': 'test_conf'},
user_data=None)
user_data=None,
server_group_id=SERVER_GRPOUP_ID)
# Make sure it returns the expected compute_id
self.assertEqual(COMPUTE_ID, compute_id)
@ -252,8 +259,9 @@ class TestComputeTasks(base.TestCase):
m = mock.mock_open(read_data='test')
with mock.patch.object(builtins, 'open', m, create=True):
# Test execute()
compute_id = createcompute.execute(_amphora_mock.id,
'test_cert')
compute_id = createcompute.execute(_amphora_mock.id, 'test_cert',
server_group_id=SERVER_GRPOUP_ID
)
# Validate that the build method was called properly
mock_driver.build.assert_called_once_with(
@ -268,7 +276,8 @@ class TestComputeTasks(base.TestCase):
config_drive_files={
'/etc/octavia/certs/server.pem': 'test_cert',
'/etc/octavia/certs/client_ca.pem': 'test',
'/etc/octavia/amphora-agent.conf': 'test_conf'})
'/etc/octavia/amphora-agent.conf': 'test_conf'},
server_group_id=SERVER_GRPOUP_ID)
# Make sure it returns the expected compute_id
assert(compute_id == COMPUTE_ID)
@ -355,3 +364,44 @@ class TestComputeTasks(base.TestCase):
delete_compute.execute(_amphora_mock)
mock_driver.delete.assert_called_once_with(COMPUTE_ID)
@mock.patch('stevedore.driver.DriverManager.driver')
def test_nova_server_group_create(self, mock_driver):
nova_sever_group_obj = compute_tasks.NovaServerGroupCreate()
server_group_test_id = '6789'
fake_server_group = mock.MagicMock()
fake_server_group.id = server_group_test_id
fake_server_group.policy = 'anti-affinity'
mock_driver.create_server_group.return_value = fake_server_group
# Test execute()
sg_id = nova_sever_group_obj.execute('123')
# Validate that the build method was called properly
mock_driver.create_server_group.assert_called_once_with(
'octavia-lb-123', 'anti-affinity')
# Make sure it returns the expected server group_id
assert(sg_id == server_group_test_id)
# Test revert()
nova_sever_group_obj.revert(sg_id)
# Validate that the delete_server_group method was called properly
mock_driver.delete_server_group.assert_called_once_with(sg_id)
nova_sever_group_obj.revert(sg_id)
@mock.patch('stevedore.driver.DriverManager.driver')
def test_nova_server_group_delete_with_sever_group_id(self, mock_driver):
nova_sever_group_obj = compute_tasks.NovaServerGroupDelete()
sg_id = '6789'
nova_sever_group_obj.execute(sg_id)
mock_driver.delete_server_group.assert_called_once_with(sg_id)
@mock.patch('stevedore.driver.DriverManager.driver')
def test_nova_server_group_delete_with_None(self, mock_driver):
nova_sever_group_obj = compute_tasks.NovaServerGroupDelete()
sg_id = None
nova_sever_group_obj.execute(sg_id)
self.assertFalse(mock_driver.delete_server_group.called, sg_id)

View File

@ -29,6 +29,7 @@ import octavia.tests.unit.base as base
AMP_ID = uuidutils.generate_uuid()
COMPUTE_ID = uuidutils.generate_uuid()
LB_ID = uuidutils.generate_uuid()
SERVER_GROUP_ID = uuidutils.generate_uuid()
LB_NET_IP = '192.0.2.2'
LISTENER_ID = uuidutils.generate_uuid()
POOL_ID = uuidutils.generate_uuid()
@ -1348,3 +1349,27 @@ class TestDatabaseTasks(base.TestCase):
mark_busy.execute(_loadbalancer_mock)
mock_amp_health_repo_update.assert_called_once_with(
'TEST', amphora_id=AMP_ID, busy=True)
@mock.patch('octavia.db.repositories.LoadBalancerRepository.update')
def test_update_lb_server_group_in_db(self,
mock_listner_repo_update,
mock_generate_uuid,
mock_LOG,
mock_get_session,
mock_loadbalancer_repo_update,
mock_listener_repo_update,
mock_amphora_repo_update,
mock_amphora_repo_delete):
update_server_group_info = database_tasks.UpdateLBServerGroupInDB()
update_server_group_info.execute(LB_ID, SERVER_GROUP_ID)
repo.LoadBalancerRepository.update.assert_called_once_with(
'TEST',
id=LB_ID,
server_group_id=SERVER_GROUP_ID)
# Test the revert
mock_listener_repo_update.reset_mock()
update_server_group_info.revert(LB_ID, SERVER_GROUP_ID)

View File

@ -469,7 +469,12 @@ class TestControllerWorker(base.TestCase):
(base_taskflow.BaseTaskFlowEngine._taskflow_load.
assert_called_once_with(_flow_mock,
store={constants.LOADBALANCER:
_load_balancer_mock}))
_load_balancer_mock,
constants.SERVER_GROUP_ID:
_load_balancer_mock.server_group_id
}
)
)
_flow_mock.run.assert_called_once_with()