diff --git a/.zuul.yaml b/.zuul.yaml index 97ceaec..3282dec 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -7,13 +7,33 @@ - x/ospurge - openstack/designate - openstack/python-designateclient + - openstack/octavia + - openstack/octavia-lib + - openstack/python-octaviaclient roles: - zuul: openstack-infra/devstack vars: devstack_plugins: designate: https://opendev.org/openstack/designate + octavia: https://opendev.org/openstack/octavia devstack_localrc: DESIGNATE_SERVICE_PORT_DNS: 5322 + DISABLE_AMP_IMAGE_BUILD: True + devstack_local_conf: + post-config: + $OCTAVIA_CONF: + controller_worker: + amphora_driver: amphora_noop_driver + compute_driver: compute_noop_driver + network_driver: network_noop_driver + certificates: + cert_manager: local_cert_manager + devstack_services: + octavia: true + o-api: true + o-cw: true + o-hm: false + o-hk: false tox_envlist: functional irrelevant-files: &dsvm-irrelevant-files - ^(test-|)requirements.txt$ diff --git a/ospurge/resources/octavia.py b/ospurge/resources/octavia.py new file mode 100644 index 0000000..6642820 --- /dev/null +++ b/ospurge/resources/octavia.py @@ -0,0 +1,31 @@ +# 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. +from ospurge.resources import base + + +class LoadBalancers(base.ServiceResource): + ORDER = 10 + + def list(self): + if not self.cloud.has_service('load-balancer'): + return [] + return self.cloud.load_balancer.load_balancers( + project_id=self.cloud.current_project_id) + + def delete(self, resource): + self.cloud.load_balancer.delete_load_balancer( + resource['id'], cascade=True) + + @staticmethod + def to_str(resource): + return "Octavia LoadBalancer (id='{}', name='{}')".format( + resource['id'], resource['name']) diff --git a/ospurge/resources/octavia.pyi b/ospurge/resources/octavia.pyi new file mode 100644 index 0000000..bcb51d3 --- /dev/null +++ b/ospurge/resources/octavia.pyi @@ -0,0 +1,28 @@ +# 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. +from typing import Any +from typing import Dict +from typing import Iterable + +from ospurge.resources import base + + +class LoadBalancers(base.ServiceResource): + def list(self) -> Iterable: + ... + + def delete(self, resource: Dict[str, Any]) -> None: + ... + + @staticmethod + def to_str(resource: Dict[str, Any]) -> str: + ... diff --git a/ospurge/tests/resources/test_octavia.py b/ospurge/tests/resources/test_octavia.py new file mode 100644 index 0000000..ac1d27a --- /dev/null +++ b/ospurge/tests/resources/test_octavia.py @@ -0,0 +1,50 @@ +# 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 unittest +import uuid + +import openstack.connection + +from ospurge.resources import octavia +from ospurge.tests import mock + + +class TestLoadBalancers(unittest.TestCase): + def setUp(self): + self.cloud = mock.Mock(spec_set=openstack.connection.Connection) + self.creds_manager = mock.Mock(cloud=self.cloud) + + def test_list_without_service(self): + self.cloud.has_service.return_value = False + self.assertEqual(octavia.LoadBalancers(self.creds_manager).list(), []) + self.cloud.load_balancer.load_balancers.assert_not_called() + + def test_list_with_service(self): + self.cloud.has_service.return_value = True + my_project = str(uuid.uuid4()) + self.cloud.current_project_id = my_project + self.assertIs( + self.cloud.load_balancer.load_balancers.return_value, + octavia.LoadBalancers(self.creds_manager).list()) + self.cloud.load_balancer.load_balancers.assert_called_once_with( + project_id=my_project) + + def test_delete(self): + lb = mock.MagicMock() + self.assertIsNone(octavia.LoadBalancers(self.creds_manager).delete(lb)) + (self.cloud.load_balancer.delete_load_balancer + .assert_called_once_with(lb['id'], cascade=True)) + + def test_to_string(self): + stack = mock.MagicMock() + self.assertIn("Octavia LoadBalancer", + octavia.LoadBalancers(self.creds_manager).to_str(stack)) diff --git a/test-requirements.txt b/test-requirements.txt index 77b09b1..8a7cfb7 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -7,6 +7,7 @@ sphinx>=1.6.2 # BSD testrepository>=0.0.18 # Apache-2.0/BSD python-openstackclient>=3.16.1 python-designateclient +python-octaviaclient # Python 2.7 dependencies mock; python_version < '3.0' diff --git a/tools/func-tests.sh b/tools/func-tests.sh index de69aab..1dca760 100755 --- a/tools/func-tests.sh +++ b/tools/func-tests.sh @@ -82,7 +82,8 @@ if [[ ! "$(openstack flavor list)" =~ 'm1.nano' ]]; then openstack flavor create --id 42 --ram 64 --disk 1 --vcpus 1 m1.nano fi - +# Allow demo/invisible_to_admin to access the load-balancer service +openstack role add --user demo --project invisible_to_admin load-balancer_member ######################## ### Populate @@ -175,3 +176,8 @@ if [[ $(openstack zone list --all-projects | wc -l) -ne 1 ]]; then # This also echo "Not all zones were cleaned up" exit 1 fi + +if [[ $(openstack loadbalancer list | wc -l) -ne 1 ]]; then + echo "Not all loadbalancers were cleaned up" + exit 1 +fi diff --git a/tools/populate.sh b/tools/populate.sh index f72f5e3..5950834 100755 --- a/tools/populate.sh +++ b/tools/populate.sh @@ -54,6 +54,21 @@ function wait_for_volume_to_be_available { done } +function wait_for_lb_active { + LB_ID=$1 + LB_STATUS=$(openstack loadbalancer show ${LB_ID} -c provisioning_status -f value) + while [ $LB_STATUS != "ACTIVE" ]; do + if [ $LB_STATUS == "ERROR" ]; then + echo "Status of LB ${LB_NAME} is $LB_STATUS. Failing." && false + exit_on_failure "Octavia LoadBalancer ${LB_NAME} entered $LB_STATUS status." + fi + + echo "Status of LB ${LB_NAME} is $LB_STATUS. Waiting 3 sec" + sleep 3 + LB_STATUS=$(openstack loadbalancer show ${LB_ID} -c provisioning_status -f value) + done +} + # Check if needed environment variable OS_PROJECT_NAME is set and non-empty. : "${OS_PROJECT_NAME:?Need to set OS_PROJECT_NAME non-empty}" @@ -70,6 +85,11 @@ FLAVOR=${FLAVOR:-m1.nano} VMIMG_NAME=${VMIMG_NAME:-cirros-0.4.0-x86_64-disk} # Zone name used for the Designate Zone ZONE_NAME="${UUID//-/}.com." +# LoadBalancer name used for the Octavia LoadBalancer +LB_NAME="lb-${UUID//-/}" +LB_LISTENER_NAME="listener-${UUID//-/}" +# Subnet used for the Octavia LoadBalancer VIP +LB_VIP_SUBNET_ID=${LB_VIP_SUBNET_ID:-$UUID} @@ -191,6 +211,24 @@ openstack zone create --email hostmaster@example.com ${ZONE_NAME} exit_on_failure "Unable to create Designate Zone ${ZONE_NAME}" +############################### +### Octavia +############################### +# Create Octavia LoadBalancer +LB_ID=$(openstack loadbalancer create --name ${LB_NAME} --vip-subnet-id ${LB_VIP_SUBNET_ID} -f value -c id) +exit_on_failure "Unable to create Octavia LoadBalancer ${LB_NAME} (${LB_ID}) as $OS_USERNAME/$OS_PROJECT_NAME" +# Wait for LB to be active +wait_for_lb_active $LB_ID + +# Create Octavia Listener +openstack loadbalancer listener create \ + --protocol HTTP --protocol-port 80 --name ${LB_LISTENER_NAME} \ + ${LB_NAME} +exit_on_failure "Unable to create Octavia Listener ${LB_LISTENER_NAME}" +# Wait for LB to be active +wait_for_lb_active $LB_ID + + ############################### ### Link resources ###############################