Merge "Support Host header inject for healthmonitor HTTP 1.1 health check"

This commit is contained in:
Zuul 2019-03-06 18:49:32 +00:00 committed by Gerrit Code Review
commit 564659a3d0
27 changed files with 394 additions and 48 deletions

View File

@ -507,6 +507,22 @@ healthmonitor-delay-optional:
in: body
required: false
type: integer
healthmonitor-domain_name:
description: |
The domain name, which be injected into the HTTP Host Header to the backend
server for HTTP health check.
in: body
min_version: 2.10
required: true
type: string
healthmonitor-domain_name-optional:
description: |
The domain name, which be injected into the HTTP Host Header to the backend
server for HTTP health check.
in: body
min_version: 2.10
required: false
type: string
healthmonitor-expected_codes:
description: |
The list of HTTP status codes expected in response from the member to
@ -547,6 +563,20 @@ healthmonitor-http_method-optional:
in: body
required: false
type: string
healthmonitor-http_version:
description: |
The HTTP version. One of ``1.0`` or ``1.1``. The default is ``1.0``.
in: body
min_version: 2.10
required: true
type: float
healthmonitor-http_version-optional:
description: |
The HTTP version. One of ``1.0`` or ``1.1``. The default is ``1.0``.
in: body
min_version: 2.10
required: false
type: float
healthmonitor-id:
description: |
The associated health monitor ID.

View File

@ -1 +1 @@
curl -X POST -H "Content-Type: application/json" -H "X-Auth-Token: <token>" -d '{"healthmonitor":{"name":"super-pool-health-monitor","admin_state_up":true,"pool_id":"4029d267-3983-4224-a3d0-afb3fe16a2cd","delay":"10","expected_codes":"200","max_retries":"1","http_method":"GET","timeout":"5","url_path":"/","type":"HTTP","max_retries_down":3,"tags":["test_tag"]}}' http://198.51.100.10:9876/v2/lbaas/healthmonitors
curl -X POST -H "Content-Type: application/json" -H "X-Auth-Token: <token>" -d '{"healthmonitor":{"name":"super-pool-health-monitor","admin_state_up":true,"pool_id":"4029d267-3983-4224-a3d0-afb3fe16a2cd","delay":"10","expected_codes":"200","max_retries":"1","http_method":"GET","timeout":"5","url_path":"/","type":"HTTP","max_retries_down":3,"tags":["test_tag"],"http_version":1.1,"domain_name":"testlab.com"}}' http://198.51.100.10:9876/v2/lbaas/healthmonitors

View File

@ -11,6 +11,8 @@
"url_path": "/",
"type": "HTTP",
"max_retries_down": 3,
"tags": ["test_tag"]
"tags": ["test_tag"],
"http_version": 1.1,
"domain_name": "testlab.com"
}
}

View File

@ -21,6 +21,8 @@
"type": "HTTP",
"id": "8ed3c5ac-6efa-420c-bedb-99ba14e58db5",
"operating_status": "ONLINE",
"tags": ["test_tag"]
"tags": ["test_tag"],
"http_version": 1.1,
"domain_name": "testlab.com"
}
}

View File

@ -21,6 +21,8 @@
"type": "HTTP",
"id": "8ed3c5ac-6efa-420c-bedb-99ba14e58db5",
"operating_status": "ONLINE",
"tags": ["test_tag"]
"tags": ["test_tag"],
"http_version": 1.0,
"domain_name": null
}
}

View File

@ -1 +1 @@
curl -X PUT -H "Content-Type: application/json" -H "X-Auth-Token: <token>" -d '{"healthmonitor":{"name":"super-pool-health-monitor-updated","admin_state_up":true,"delay":5,"expected_codes":"200","http_method":"HEAD","timeout":2,"url_path":"/index.html","max_retries":2,"max_retries_down":2,"tags":["updated_tag"]}}' http://198.51.100.10:9876/v2/lbaas/healthmonitors/8ed3c5ac-6efa-420c-bedb-99ba14e58db5
curl -X PUT -H "Content-Type: application/json" -H "X-Auth-Token: <token>" -d '{"healthmonitor":{"name":"super-pool-health-monitor-updated","admin_state_up":true,"delay":5,"expected_codes":"200","http_method":"HEAD","timeout":2,"url_path":"/index.html","max_retries":2,"max_retries_down":2,"tags":["updated_tag"],"http_version":1.1}}' http://198.51.100.10:9876/v2/lbaas/healthmonitors/8ed3c5ac-6efa-420c-bedb-99ba14e58db5

View File

@ -9,6 +9,7 @@
"url_path": "/index.html",
"max_retries": 2,
"max_retries_down": 2,
"tags": ["updated_tag"]
"tags": ["updated_tag"],
"http_version": 1.1
}
}

View File

@ -21,6 +21,8 @@
"type": "HTTP",
"id": "8ed3c5ac-6efa-420c-bedb-99ba14e58db5",
"operating_status": "ONLINE",
"tags": ["updated_tag"]
"tags": ["updated_tag"],
"http_version": 1.1,
"domain_name": null
}
}

View File

@ -22,7 +22,9 @@
"type": "HTTP",
"id": "8ed3c5ac-6efa-420c-bedb-99ba14e58db5",
"operating_status": "ONLINE",
"tags": ["test_tag"]
"tags": ["test_tag"],
"http_vesion": 1.0,
"domain_name": null
}
]
}

View File

@ -48,8 +48,10 @@ Response Parameters
- admin_state_up: admin_state_up
- created_at: created_at
- delay: healthmonitor-delay
- domain_name: healthmonitor-domain_name
- expected_codes: healthmonitor-expected_codes
- http_method: healthmonitor-http_method
- http_version: healthmonitor-http_version
- id: healthmonitor-id
- max_retries: healthmonitor-max-retries
- max_retries_down: healthmonitor-max-retries-down
@ -127,6 +129,8 @@ Some attributes receive default values if you omit them from the request:
- ``http_method`` The default is ``GET``.
- ``http_version`` The default is ``1.0``.
- ``max_retries_down`` The default is ``3``.
- ``url_path`` The default is ``/``.
@ -155,8 +159,10 @@ Request
- admin_state_up: admin_state_up-default-optional
- delay: healthmonitor-delay
- domain_name: healthmonitor-domain_name-optional
- expected_codes: healthmonitor-expected_codes-optional
- http_method: healthmonitor-http_method-optional
- http_version: healthmonitor-http_version-optional
- name: name-optional
- max_retries: healthmonitor-max-retries
- max_retries_down: healthmonitor-max-retries-down-optional
@ -187,8 +193,10 @@ Response Parameters
- admin_state_up: admin_state_up
- created_at: created_at
- delay: healthmonitor-delay
- domain_name: healthmonitor-domain_name
- expected_codes: healthmonitor-expected_codes
- http_method: healthmonitor-http_method
- http_version: healthmonitor-http_version
- id: healthmonitor-id
- max_retries: healthmonitor-max-retries
- max_retries_down: healthmonitor-max-retries-down
@ -255,8 +263,10 @@ Response Parameters
- admin_state_up: admin_state_up
- created_at: created_at
- delay: healthmonitor-delay
- domain_name: healthmonitor-domain_name
- expected_codes: healthmonitor-expected_codes
- http_method: healthmonitor-http_method
- http_version: healthmonitor-http_version
- id: healthmonitor-id
- max_retries: healthmonitor-max-retries
- max_retries_down: healthmonitor-max-retries-down
@ -312,9 +322,11 @@ Request
- admin_state_up: admin_state_up-default-optional
- delay: healthmonitor-delay-optional
- domain_name: healthmonitor-domain_name-optional
- expected_codes: healthmonitor-expected_codes-optional
- healthmonitor_id: path-healthmonitor-id
- http_method: healthmonitor-http_method-optional
- http_version: healthmonitor-http_version-optional
- max_retries: healthmonitor-max-retries-optional
- max_retries_down: healthmonitor-max-retries-down-optional
- name: name-optional
@ -342,8 +354,10 @@ Response Parameters
- admin_state_up: admin_state_up
- created_at: created_at
- delay: healthmonitor-delay
- domain_name: healthmonitor-domain_name
- expected_codes: healthmonitor-expected_codes
- http_method: healthmonitor-http_method
- http_version: healthmonitor-http_version
- id: healthmonitor-id
- max_retries: healthmonitor-max-retries
- max_retries_down: healthmonitor-max-retries-down

View File

@ -218,7 +218,8 @@ class HealthMonitor(BaseDataModel):
def __init__(self, admin_state_up=Unset, delay=Unset, expected_codes=Unset,
healthmonitor_id=Unset, http_method=Unset, max_retries=Unset,
max_retries_down=Unset, name=Unset, pool_id=Unset,
timeout=Unset, type=Unset, url_path=Unset):
timeout=Unset, type=Unset, url_path=Unset, http_version=Unset,
domain_name=Unset):
self.admin_state_up = admin_state_up
self.delay = delay
@ -232,6 +233,8 @@ class HealthMonitor(BaseDataModel):
self.timeout = timeout
self.type = type
self.url_path = url_path
self.http_version = http_version
self.domain_name = domain_name
class L7Policy(BaseDataModel):

View File

@ -95,6 +95,9 @@ class RootController(rest.RestController):
self._add_a_version(versions, 'v2.8', 'v2', 'SUPPORTED',
'2019-02-12T00:00:00Z', host_url)
# HTTP Redirect code
self._add_a_version(versions, 'v2.9', 'v2', 'CURRENT',
self._add_a_version(versions, 'v2.9', 'v2', 'SUPPORTED',
'2019-03-04T00:00:00Z', host_url)
# Healthmonitor host header
self._add_a_version(versions, 'v2.10', 'v2', 'CURRENT',
'2019-03-05T00:00:00Z', host_url)
return {'versions': versions}

View File

@ -139,6 +139,17 @@ class HealthMonitorController(base.BaseController):
hm_dict[consts.EXPECTED_CODES] = (
consts.HEALTH_MONITOR_DEFAULT_EXPECTED_CODES)
if hm_dict.get('domain_name') and not hm_dict.get('http_version'):
raise exceptions.ValidationException(
detail=_("'http_version' must be specified when 'domain_name' "
"is provided."))
if hm_dict.get('http_version') and hm_dict.get('domain_name'):
if hm_dict['http_version'] < 1.1:
raise exceptions.InvalidOption(
value='http_version %s' % hm_dict['http_version'],
option='health monitors HTTP 1.1 domain name health check')
try:
return self.repositories.health_monitor.create(
lock_session, **hm_dict)
@ -280,6 +291,20 @@ class HealthMonitorController(base.BaseController):
if health_monitor.expected_codes is None:
health_monitor.expected_codes = wtypes.Unset
if health_monitor.domain_name and not (
db_hm.http_version or health_monitor.http_version):
raise exceptions.ValidationException(
detail=_("'http_version' must be specified when 'domain_name' "
"is provided."))
if ((db_hm.http_version or health_monitor.http_version) and
(db_hm.domain_name or health_monitor.domain_name)):
http_version = health_monitor.http_version or db_hm.http_version
if http_version < 1.1:
raise exceptions.InvalidOption(
value='http_version %s' % http_version,
option='health monitors HTTP 1.1 domain name health check')
@wsme_pecan.wsexpose(hm_types.HealthMonitorRootResponse, wtypes.text,
body=hm_types.HealthMonitorRootPUT, status_code=200)
def put(self, id, health_monitor_):

View File

@ -45,6 +45,8 @@ class HealthMonitorResponse(BaseHealthMonitorType):
created_at = wtypes.wsattr(wtypes.datetime.datetime)
updated_at = wtypes.wsattr(wtypes.datetime.datetime)
tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType()))
http_version = wtypes.wsattr(float)
domain_name = wtypes.wsattr(wtypes.StringType())
@classmethod
def from_data_model(cls, data_model, children=False):
@ -100,6 +102,11 @@ class HealthMonitorPOST(BaseHealthMonitorType):
project_id = wtypes.wsattr(wtypes.StringType(max_length=36))
pool_id = wtypes.wsattr(wtypes.UuidType(), mandatory=True)
tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(max_length=255)))
http_version = wtypes.wsattr(
wtypes.Enum(float, *constants.SUPPORTED_HTTP_VERSIONS))
domain_name = wtypes.wsattr(
wtypes.StringType(min_length=1, max_length=255,
pattern=constants.DOMAIN_NAME_REGEX))
class HealthMonitorRootPOST(types.BaseType):
@ -124,6 +131,11 @@ class HealthMonitorPUT(BaseHealthMonitorType):
wtypes.StringType(pattern=r'^(\d{3}(\s*,\s*\d{3})*)$|^(\d{3}-\d{3})$'))
admin_state_up = wtypes.wsattr(bool)
tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(max_length=255)))
http_version = wtypes.wsattr(
wtypes.Enum(float, *constants.SUPPORTED_HTTP_VERSIONS))
domain_name = wtypes.wsattr(
wtypes.StringType(min_length=1, max_length=255,
pattern=constants.DOMAIN_NAME_REGEX))
class HealthMonitorRootPUT(types.BaseType):
@ -152,6 +164,11 @@ class HealthMonitorSingleCreate(BaseHealthMonitorType):
wtypes.StringType(pattern=r'^(\d{3}(\s*,\s*\d{3})*)$|^(\d{3}-\d{3})$'))
admin_state_up = wtypes.wsattr(bool, default=True)
tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(max_length=255)))
http_version = wtypes.wsattr(
wtypes.Enum(float, *constants.SUPPORTED_HTTP_VERSIONS))
domain_name = wtypes.wsattr(
wtypes.StringType(min_length=1, max_length=255,
pattern=constants.DOMAIN_NAME_REGEX))
class HealthMonitorStatusResponse(BaseHealthMonitorType):

View File

@ -52,6 +52,7 @@ SUPPORTED_HEALTH_MONITOR_HTTP_METHODS = (
HEALTH_MONITOR_HTTP_METHOD_DELETE, HEALTH_MONITOR_HTTP_METHOD_TRACE,
HEALTH_MONITOR_HTTP_METHOD_OPTIONS, HEALTH_MONITOR_HTTP_METHOD_CONNECT,
HEALTH_MONITOR_HTTP_METHOD_PATCH)
SUPPORTED_HTTP_VERSIONS = [1.0, 1.1]
HEALTH_MONITOR_DEFAULT_EXPECTED_CODES = '200'
HEALTH_MONITOR_DEFAULT_URL_PATH = '/'
TYPE = 'type'
@ -203,6 +204,9 @@ HTTP_HEADER_VALUE_REGEX = (r'\A[a-zA-Z0-9'
HTTP_QUOTED_HEADER_VALUE_REGEX = (r'\A"[a-zA-Z0-9 \t'
r'!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~\\]*"\Z')
DOMAIN_NAME_REGEX = (
r'^(?=.{1,253}\.?$)(?:(?!-|[^.]+_)[A-Za-z0-9-_]{1,63}(?<!-)(?:\.|$))+$')
# Task/Flow constants
AMPHORA = 'amphora'
FAILED_AMPHORA = 'failed_amphora'

View File

@ -231,7 +231,8 @@ class HealthMonitor(BaseDataModel):
rise_threshold=None, http_method=None, url_path=None,
expected_codes=None, enabled=None, pool=None, name=None,
provisioning_status=None, operating_status=None,
created_at=None, updated_at=None, tags=None):
created_at=None, updated_at=None, tags=None,
http_version=None, domain_name=None):
self.id = id
self.project_id = project_id
self.pool_id = pool_id
@ -251,6 +252,8 @@ class HealthMonitor(BaseDataModel):
self.created_at = created_at
self.updated_at = updated_at
self.tags = tags
self.http_version = http_version
self.domain_name = domain_name
def delete(self):
self.pool.health_monitor = None

View File

@ -372,6 +372,8 @@ class JinjaTemplater(object):
'url_path': monitor.url_path,
'expected_codes': codes,
'enabled': monitor.enabled,
'http_version': monitor.http_version,
'domain_name': monitor.domain_name,
}
def _transform_l7policy(self, l7policy, feature_compatibility,

View File

@ -293,8 +293,18 @@ backend {{ pool.id }}
{% if (pool.health_monitor.type ==
constants.HEALTH_MONITOR_HTTP or pool.health_monitor.type ==
constants.HEALTH_MONITOR_HTTPS) %}
option httpchk {{ pool.health_monitor.http_method }} {{
pool.health_monitor.url_path }}
{% if (pool.health_monitor.http_version and
pool.health_monitor.http_version == 1.1 and
pool.health_monitor.domain_name) %}
option httpchk {{ pool.health_monitor.http_method }} {{ pool.health_monitor.url_path }} HTTP/
{{- pool.health_monitor.http_version -}}{{- "\\r\\n" | safe -}}
Host:\ {{ pool.health_monitor.domain_name }}
{% elif pool.health_monitor.http_version %}
option httpchk {{ pool.health_monitor.http_method }} {{ pool.health_monitor.url_path }} HTTP/
{{- pool.health_monitor.http_version -}}{{- "\\r\\n" | safe }}
{% else %}
option httpchk {{ pool.health_monitor.http_method }} {{ pool.health_monitor.url_path }}
{% endif %}
http-check expect rstatus {{ pool.health_monitor.expected_codes }}
{% endif %}
{% if pool.health_monitor.type == constants.HEALTH_MONITOR_TLS_HELLO %}

View File

@ -0,0 +1,39 @@
#
# 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 l7policy action redirect prefix
Revision ID: 7432f1d4ea83
Revises: 6742ca1b27c2
Create Date: 2018-09-09 20:35:38.780054
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '7432f1d4ea83'
down_revision = '6742ca1b27c2'
def upgrade():
op.add_column(
u'health_monitor',
sa.Column(u'http_version', sa.Float(), nullable=True)
)
op.add_column(
u'health_monitor',
sa.Column(u'domain_name', sa.String(255), nullable=True)
)

View File

@ -273,6 +273,8 @@ class HealthMonitor(base_models.BASE, base_models.IdMixin,
cascade='all,delete-orphan',
primaryjoin='and_(foreign(Tags.resource_id)==HealthMonitor.id)'
)
http_version = sa.Column(sa.Float, nullable=True)
domain_name = sa.Column(sa.String(255), nullable=True)
class Pool(base_models.BASE, base_models.IdMixin, base_models.ProjectMixin,

View File

@ -46,7 +46,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
versions = self._get_versions_with_config(
api_v1_enabled=True, api_v2_enabled=True)
version_ids = tuple(v.get('id') for v in versions)
self.assertEqual(11, len(version_ids))
self.assertEqual(12, len(version_ids))
self.assertIn('v1', version_ids)
self.assertIn('v2.0', version_ids)
self.assertIn('v2.1', version_ids)
@ -58,6 +58,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
self.assertIn('v2.7', version_ids)
self.assertIn('v2.8', version_ids)
self.assertIn('v2.9', version_ids)
self.assertIn('v2.10', version_ids)
# Each version should have a 'self' 'href' to the API version URL
# [{u'rel': u'self', u'href': u'http://localhost/v2'}]
@ -77,7 +78,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
def test_api_v1_disabled(self):
versions = self._get_versions_with_config(
api_v1_enabled=False, api_v2_enabled=True)
self.assertEqual(10, len(versions))
self.assertEqual(11, len(versions))
self.assertEqual('v2.0', versions[0].get('id'))
self.assertEqual('v2.1', versions[1].get('id'))
self.assertEqual('v2.2', versions[2].get('id'))
@ -88,6 +89,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
self.assertEqual('v2.7', versions[7].get('id'))
self.assertEqual('v2.8', versions[8].get('id'))
self.assertEqual('v2.9', versions[9].get('id'))
self.assertEqual('v2.10', versions[10].get('id'))
def test_api_v2_disabled(self):
versions = self._get_versions_with_config(

View File

@ -1039,6 +1039,42 @@ class TestHealthMonitor(base.BaseAPITest):
project_id=pid).get(self.root_tag)
self.assertEqual(self.project_id, api_hm.get('project_id'))
def test_create_with_default_http_version(self):
# Use the default HTTP/1.0
api_hm = self.create_health_monitor(
self.pool_id, constants.HEALTH_MONITOR_HTTP,
1, 1, 1, 1, admin_state_up=False, expected_codes='200',
http_method='GET', name='Test HM', url_path='/',
http_version='1.0').get(self.root_tag)
self.assertEqual(1.0, api_hm.get('http_version'))
def test_create_without_http_version(self):
# Check the default http_version is 1.0
api_hm = self.create_health_monitor(
self.pool_id, constants.HEALTH_MONITOR_HTTP,
1, 1, 1, 1, admin_state_up=False, expected_codes='200',
http_method='GET', name='Test HM', url_path='/').get(self.root_tag)
self.assertIsNone(api_hm.get('http_version'))
def test_create_with_http_version_11_and_domain_name(self):
# Create with http_version 1.1 and domain_name
api_hm = self.create_health_monitor(
self.pool_id, constants.HEALTH_MONITOR_HTTPS,
1, 1, 1, 1, admin_state_up=False, expected_codes='200',
http_method='GET', name='Test HM', url_path='/',
http_version=1.1, domain_name='testlab.com').get(self.root_tag)
self.assertEqual(1.1, api_hm.get('http_version'))
self.assertEqual('testlab.com', api_hm.get('domain_name'))
def test_create_with_http_version_11(self):
# Create with http_version 1.1
api_hm = self.create_health_monitor(
self.pool_id, constants.HEALTH_MONITOR_HTTPS,
1, 1, 1, 1, admin_state_up=False, expected_codes='200',
http_method='GET', name='Test HM', url_path='/',
http_version=1.1).get(self.root_tag)
self.assertEqual(1.1, api_hm.get('http_version'))
def test_bad_create(self):
hm_json = {'name': 'test1', 'pool_id': self.pool_id}
self.post(self.HMS_PATH, self._build_body(hm_json), status=400)
@ -1212,6 +1248,40 @@ class TestHealthMonitor(base.BaseAPITest):
'max_retries': 1}
self.post(self.HMS_PATH, self._build_body(hm), status=403)
def test_bad_create_with_http_version_and_domain_name_cases(self):
hm_json = {'pool_id': self.pool_id,
'type': constants.HEALTH_MONITOR_HTTP,
'delay': 1,
'timeout': 1,
'max_retries_down': 1,
'max_retries': 1,
'expected_codes': '200',
'http_version': 1.00, 'domain_name': 'testlab.com'}
api_hm = self.post(
self.HMS_PATH, self._build_body(hm_json), status=400).json
expect_error_msg = ("http_version 1.0 is not a valid option for "
"health monitors HTTP 1.1 domain name health "
"check")
self.assertEqual(expect_error_msg, api_hm['faultstring'])
for bad_case in [{'http_version': 1.0, 'domain_name': '^testla&b.com'},
{'http_version': 1.1,
'domain_name': 'testla\nb.com'}]:
hm_json = {'pool_id': self.pool_id,
'type': constants.HEALTH_MONITOR_HTTP,
'delay': 1,
'timeout': 1,
'max_retries_down': 1,
'max_retries': 1,
'expected_codes': '200'}
hm_json.update(bad_case)
api_hm = self.post(
self.HMS_PATH, self._build_body(hm_json), status=400).json
expect_error_msg = (
"Invalid input for field/attribute domain_name. Value: '%s'. "
"Value should match the pattern %s") % (bad_case[
'domain_name'], constants.DOMAIN_NAME_REGEX)
self.assertEqual(expect_error_msg, api_hm['faultstring'])
def test_update(self):
api_hm = self.create_health_monitor(
self.pool_with_listener_id,
@ -1257,6 +1327,28 @@ class TestHealthMonitor(base.BaseAPITest):
healthmonitor_id=api_hm.get('id'))).json.get(self.root_tag)
self.assertEqual('/health', response[constants.URL_PATH])
def test_update_http_version_and_domain_name(self):
api_hm = self.create_health_monitor(
self.pool_with_listener_id, constants.HEALTH_MONITOR_HTTP,
1, 1, 1, 1, admin_state_up=False, expected_codes='200',
http_method='GET', name='Test HM', url_path='/').get(self.root_tag)
self.set_lb_status(self.lb_id)
new_hm = {'http_version': 1.1, 'domain_name': 'testlab.com'}
self.put(
self.HM_PATH.format(healthmonitor_id=api_hm.get('id')),
self._build_body(new_hm))
self.assert_correct_status(
lb_id=self.lb_id, listener_id=self.listener_id,
pool_id=self.pool_with_listener_id, hm_id=api_hm.get('id'),
lb_prov_status=constants.PENDING_UPDATE,
listener_prov_status=constants.PENDING_UPDATE,
pool_prov_status=constants.PENDING_UPDATE,
hm_prov_status=constants.PENDING_UPDATE)
response = self.get(self.HM_PATH.format(
healthmonitor_id=api_hm.get('id'))).json.get(self.root_tag)
self.assertEqual(1.1, response['http_version'])
self.assertEqual('testlab.com', response['domain_name'])
def test_update_TCP(self):
api_hm = self.create_health_monitor(
self.pool_with_listener_id,
@ -1494,6 +1586,31 @@ class TestHealthMonitor(base.BaseAPITest):
self.assertEqual(constants.HEALTH_MONITOR_DEFAULT_EXPECTED_CODES,
response['expected_codes'])
def test_bad_update_http_version_and_domain_name(self):
api_hm = self.create_health_monitor(
self.pool_id, constants.HEALTH_MONITOR_HTTP,
1, 1, 1, 1, admin_state_up=False, expected_codes='200',
http_method='GET', name='Test HM', url_path='/').get(self.root_tag)
self.set_lb_status(self.lb_id)
new_hm = {'http_version': 1.0, 'domain_name': 'testlab.com'}
response = self.put(
self.HM_PATH.format(healthmonitor_id=api_hm.get('id')),
self._build_body(new_hm), status=400)
expect_error_msg = ("http_version 1.0 is not a valid option for "
"health monitors HTTP 1.1 domain name health "
"check")
self.assertEqual(expect_error_msg, response.json['faultstring'])
new_hm = {'http_version': 1.0, 'domain_name': '^testla&b.com'}
response = self.put(
self.HM_PATH.format(healthmonitor_id=api_hm.get('id')),
self._build_body(new_hm), status=400)
expect_error_msg = (
"Invalid input for field/attribute domain_name. Value: '%s'. "
"Value should match the pattern %s") % (new_hm[
'domain_name'], constants.DOMAIN_NAME_REGEX)
self.assertEqual(expect_error_msg, response.json['faultstring'])
def test_delete(self):
api_hm = self.create_health_monitor(
self.pool_with_listener_id,

View File

@ -73,13 +73,16 @@ class SampleDriverDataModels(object):
'delay': 1, 'timeout': 3, 'fall_threshold': 1,
'rise_threshold': 2, 'http_method': 'GET',
'url_path': '/', 'expected_codes': '200',
'name': 'hm1', 'pool_id': self.pool1_id}
'name': 'hm1', 'pool_id': self.pool1_id,
'http_version': 1.0, 'domain_name': None}
self.test_hm1_dict.update(self._common_test_dict)
self.test_hm2_dict = copy.deepcopy(self.test_hm1_dict)
self.test_hm2_dict['id'] = self.hm2_id
self.test_hm2_dict['name'] = 'hm2'
self.test_hm2_dict.update({'http_version': 1.1,
'domain_name': 'testdomainname.com'})
self.db_hm1 = data_models.HealthMonitor(**self.test_hm1_dict)
self.db_hm2 = data_models.HealthMonitor(**self.test_hm2_dict)
@ -94,11 +97,15 @@ class SampleDriverDataModels(object):
'pool_id': self.pool1_id,
'timeout': 3,
'type': constants.HEALTH_MONITOR_PING,
'url_path': '/'}
'url_path': '/',
'http_version': 1.0,
'domain_name': None}
self.provider_hm2_dict = copy.deepcopy(self.provider_hm1_dict)
self.provider_hm2_dict['healthmonitor_id'] = self.hm2_id
self.provider_hm2_dict['name'] = 'hm2'
self.provider_hm2_dict.update({'http_version': 1.1,
'domain_name': 'testdomainname.com'})
self.provider_hm1 = driver_dm.HealthMonitor(**self.provider_hm1_dict)
self.provider_hm2 = driver_dm.HealthMonitor(**self.provider_hm2_dict)

View File

@ -292,6 +292,14 @@ class TestUtils(base.TestCase):
self.sample_data.test_hm1_dict)
self.assertEqual(self.sample_data.provider_hm1_dict, provider_hm_dict)
def test_HM_to_provider_HM_with_http_version_and_domain_name(self):
provider_hm = utils.db_HM_to_provider_HM(self.sample_data.db_hm2)
self.assertEqual(self.sample_data.provider_hm2, provider_hm)
provider_hm_dict = utils.hm_dict_to_provider_dict(
self.sample_data.test_hm2_dict)
self.assertEqual(self.sample_data.provider_hm2_dict, provider_hm_dict)
def test_hm_dict_to_provider_dict_partial(self):
provider_hm_dict = utils.hm_dict_to_provider_dict({'id': 1})
self.assertEqual({'healthmonitor_id': 1}, provider_hm_dict)

View File

@ -54,7 +54,7 @@ class TestHaproxyCfg(base.TestCase):
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31s\n"
" option httpchk GET /index.html\n"
" option httpchk GET /index.html HTTP/1.0\\r\\n\n"
" http-check expect rstatus 418\n"
" fullconn {maxconn}\n"
" option allbackups\n"
@ -101,7 +101,7 @@ class TestHaproxyCfg(base.TestCase):
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31s\n"
" option httpchk GET /index.html\n"
" option httpchk GET /index.html HTTP/1.0\\r\\n\n"
" http-check expect rstatus 418\n"
" fullconn {maxconn}\n"
" option allbackups\n"
@ -134,7 +134,7 @@ class TestHaproxyCfg(base.TestCase):
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31s\n"
" option httpchk GET /index.html\n"
" option httpchk GET /index.html HTTP/1.0\\r\\n\n"
" http-check expect rstatus 418\n"
" fullconn {maxconn}\n"
" option allbackups\n"
@ -160,7 +160,7 @@ class TestHaproxyCfg(base.TestCase):
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31s\n"
" option httpchk GET /index.html\n"
" option httpchk GET /index.html HTTP/1.0\\r\\n\n"
" http-check expect rstatus 418\n"
" fullconn {maxconn}\n"
" option allbackups\n"
@ -197,7 +197,7 @@ class TestHaproxyCfg(base.TestCase):
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31s\n"
" option httpchk GET /index.html\n"
" option httpchk GET /index.html HTTP/1.0\\r\\n\n"
" http-check expect rstatus 418\n"
" fullconn {maxconn}\n"
" option allbackups\n"
@ -233,7 +233,7 @@ class TestHaproxyCfg(base.TestCase):
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31s\n"
" option httpchk GET /index.html\n"
" option httpchk GET /index.html HTTP/1.0\\r\\n\n"
" http-check expect rstatus 418\n"
" fullconn {maxconn}\n"
" option allbackups\n"
@ -261,7 +261,7 @@ class TestHaproxyCfg(base.TestCase):
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31s\n"
" option httpchk GET /index.html\n"
" option httpchk GET /index.html HTTP/1.0\\r\\n\n"
" http-check expect rstatus 418\n"
" fullconn {maxconn}\n"
" option allbackups\n"
@ -297,7 +297,7 @@ class TestHaproxyCfg(base.TestCase):
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31s\n"
" option httpchk GET /index.html\n"
" option httpchk GET /index.html HTTP/1.0\\r\\n\n"
" http-check expect rstatus 418\n"
" fullconn {maxconn}\n"
" option allbackups\n"
@ -446,6 +446,34 @@ class TestHaproxyCfg(base.TestCase):
self.assertEqual(sample_configs.sample_base_expected_config(
frontend=fe, backend=be), rendered_obj)
def test_render_template_health_monitor_http_check(self):
be = ("backend sample_pool_id_1\n"
" mode http\n"
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31s\n"
" option httpchk GET /index.html HTTP/1.1\\r\\nHost:\\ "
"testlab.com\n"
" http-check expect rstatus 418\n"
" fullconn {maxconn}\n"
" option allbackups\n"
" timeout connect 5000\n"
" timeout server 50000\n"
" server sample_member_id_1 10.0.0.99:82 "
"weight 13 check inter 30s fall 3 rise 2 "
"cookie sample_member_id_1\n"
" server sample_member_id_2 10.0.0.98:82 "
"weight 13 check inter 30s fall 3 rise 2 "
"cookie sample_member_id_2\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(proto='HTTP',
monitor_proto='HTTP',
hm_host_http_check=True))
self.assertEqual(sample_configs.sample_base_expected_config(
backend=be), rendered_obj)
def test_render_template_no_persistence_https(self):
fe = ("frontend sample_listener_id_1\n"
" option tcplog\n"
@ -497,7 +525,7 @@ class TestHaproxyCfg(base.TestCase):
" stick-table type ip size 10k\n"
" stick on src\n"
" timeout check 31s\n"
" option httpchk GET /index.html\n"
" option httpchk GET /index.html HTTP/1.0\\r\\n\n"
" http-check expect rstatus 418\n"
" fullconn {maxconn}\n"
" option allbackups\n"
@ -524,7 +552,7 @@ class TestHaproxyCfg(base.TestCase):
" stick store-response res.cook(JSESSIONID)\n"
" stick match req.cook(JSESSIONID)\n"
" timeout check 31s\n"
" option httpchk GET /index.html\n"
" option httpchk GET /index.html HTTP/1.0\\r\\n\n"
" http-check expect rstatus 418\n"
" fullconn {maxconn}\n"
" option allbackups\n"
@ -633,7 +661,7 @@ class TestHaproxyCfg(base.TestCase):
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31s\n"
" option httpchk GET /index.html\n"
" option httpchk GET /index.html HTTP/1.0\\r\\n\n"
" http-check expect rstatus 418\n"
" fullconn {maxconn}\n"
" option allbackups\n"
@ -649,7 +677,7 @@ class TestHaproxyCfg(base.TestCase):
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31s\n"
" option httpchk GET /healthmon.html\n"
" option httpchk GET /healthmon.html HTTP/1.0\\r\\n\n"
" http-check expect rstatus 418\n"
" fullconn {maxconn}\n"
" option allbackups\n"
@ -670,7 +698,7 @@ class TestHaproxyCfg(base.TestCase):
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31s\n"
" option httpchk GET /index.html\n"
" option httpchk GET /index.html HTTP/1.0\\r\\n\n"
" http-check expect rstatus 418\n"
" option forwardfor\n"
" fullconn {maxconn}\n"
@ -698,7 +726,7 @@ class TestHaproxyCfg(base.TestCase):
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31s\n"
" option httpchk GET /index.html\n"
" option httpchk GET /index.html HTTP/1.0\\r\\n\n"
" http-check expect rstatus 418\n"
" option forwardfor\n"
" http-request set-header X-Forwarded-Port %[dst_port]\n"
@ -755,7 +783,7 @@ class TestHaproxyCfg(base.TestCase):
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31s\n"
" option httpchk GET /index.html\n"
" option httpchk GET /index.html HTTP/1.0\\r\\n\n"
" http-check expect rstatus 418\n"
" fullconn {maxconn}\n"
" option allbackups\n"
@ -790,7 +818,7 @@ class TestHaproxyCfg(base.TestCase):
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31s\n"
" option httpchk GET /index.html\n"
" option httpchk GET /index.html HTTP/1.0\\r\\n\n"
" http-check expect rstatus 418\n"
" fullconn {maxconn}\n"
" option allbackups\n"
@ -1083,7 +1111,7 @@ class TestHaproxyCfg(base.TestCase):
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31s\n"
" option httpchk GET /index.html\n"
" option httpchk GET /index.html HTTP/1.0\\r\\n\n"
" http-check expect rstatus 418\n"
" fullconn 1000000\n"
" option allbackups\n"
@ -1098,7 +1126,7 @@ class TestHaproxyCfg(base.TestCase):
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31s\n"
" option httpchk GET /healthmon.html\n"
" option httpchk GET /healthmon.html HTTP/1.0\\r\\n\n"
" http-check expect rstatus 418\n"
" fullconn 1000000\n"
" option allbackups\n"

View File

@ -59,7 +59,9 @@ RET_MONITOR_1 = {
'http_method': 'GET',
'url_path': '/index.html',
'expected_codes': '418',
'enabled': True}
'enabled': True,
'http_version': 1.0,
'domain_name': None}
RET_MONITOR_2 = {
'id': 'sample_monitor_id_2',
@ -71,7 +73,9 @@ RET_MONITOR_2 = {
'http_method': 'GET',
'url_path': '/healthmon.html',
'expected_codes': '418',
'enabled': True}
'enabled': True,
'http_version': 1.0,
'domain_name': None}
RET_MEMBER_1 = {
'id': 'sample_member_id_1',
@ -554,7 +558,7 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
client_ca_cert=False, client_crl_cert=False,
ssl_type_l7=False, pool_cert=False,
pool_ca_cert=False, pool_crl=False,
tls_enabled=False):
tls_enabled=False, hm_host_http_check=False):
proto = 'HTTP' if proto is None else proto
if be_proto is None:
be_proto = 'HTTP' if proto is 'TERMINATED_HTTPS' else proto
@ -580,14 +584,16 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
persistence_cookie=persistence_cookie,
monitor_ip_port=monitor_ip_port, monitor_proto=monitor_proto,
pool_cert=pool_cert, pool_ca_cert=pool_ca_cert,
pool_crl=pool_crl, tls_enabled=tls_enabled),
pool_crl=pool_crl, tls_enabled=tls_enabled,
hm_host_http_check=hm_host_http_check),
sample_pool_tuple(
proto=be_proto, monitor=monitor, persistence=persistence,
persistence_type=persistence_type,
persistence_cookie=persistence_cookie, sample_pool=2,
monitor_ip_port=monitor_ip_port, monitor_proto=monitor_proto,
pool_cert=pool_cert, pool_ca_cert=pool_ca_cert,
pool_crl=pool_crl, tls_enabled=tls_enabled)]
pool_crl=pool_crl, tls_enabled=tls_enabled,
hm_host_http_check=hm_host_http_check)]
l7policies = [
sample_l7policy_tuple('sample_l7policy_id_1', sample_policy=1),
sample_l7policy_tuple('sample_l7policy_id_2', sample_policy=2),
@ -608,7 +614,8 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
monitor_ip_port=monitor_ip_port, monitor_proto=monitor_proto,
backup_member=backup_member, disabled_member=disabled_member,
pool_cert=pool_cert, pool_ca_cert=pool_ca_cert,
pool_crl=pool_crl, tls_enabled=tls_enabled)]
pool_crl=pool_crl, tls_enabled=tls_enabled,
hm_host_http_check=hm_host_http_check)]
l7policies = []
return in_listener(
id='sample_listener_id_1',
@ -629,7 +636,8 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
pool_cert=pool_cert,
pool_ca_cert=pool_ca_cert,
pool_crl=pool_crl,
tls_enabled=tls_enabled
tls_enabled=tls_enabled,
hm_host_http_check=hm_host_http_check
) if alloc_default_pool else '',
connection_limit=connection_limit,
tls_certificate_id='cont_id_1' if tls else '',
@ -705,7 +713,7 @@ def sample_pool_tuple(proto=None, monitor=True, persistence=True,
monitor_proto=None, backup_member=False,
disabled_member=False, has_http_reuse=True,
pool_cert=False, pool_ca_cert=False, pool_crl=False,
tls_enabled=False):
tls_enabled=False, hm_host_http_check=False):
proto = 'HTTP' if proto is None else proto
monitor_proto = proto if monitor_proto is None else monitor_proto
in_pool = collections.namedtuple(
@ -732,13 +740,16 @@ def sample_pool_tuple(proto=None, monitor=True, persistence=True,
backup=backup_member,
enabled=not disabled_member)]
if monitor is True:
mon = sample_health_monitor_tuple(proto=monitor_proto)
mon = sample_health_monitor_tuple(
proto=monitor_proto, host_http_check=hm_host_http_check)
elif sample_pool == 2:
id = 'sample_pool_id_2'
members = [sample_member_tuple('sample_member_id_3', '10.0.0.97',
monitor_ip_port=monitor_ip_port)]
if monitor is True:
mon = sample_health_monitor_tuple(proto=monitor_proto, sample_hm=2)
mon = sample_health_monitor_tuple(
proto=monitor_proto, sample_hm=2,
host_http_check=hm_host_http_check)
return in_pool(
id=id,
@ -793,12 +804,13 @@ def sample_session_persistence_tuple(persistence_type=None,
persistence_granularity=persistence_granularity)
def sample_health_monitor_tuple(proto='HTTP', sample_hm=1):
def sample_health_monitor_tuple(proto='HTTP', sample_hm=1,
host_http_check=False):
proto = 'HTTP' if proto is 'TERMINATED_HTTPS' else proto
monitor = collections.namedtuple(
'monitor', 'id, type, delay, timeout, fall_threshold, rise_threshold,'
'http_method, url_path, expected_codes, enabled, '
'check_script_path')
'check_script_path, http_version, domain_name')
if sample_hm == 1:
id = 'sample_monitor_id_1'
@ -818,6 +830,10 @@ def sample_health_monitor_tuple(proto='HTTP', sample_hm=1):
'expected_codes': '418',
'enabled': True
}
if host_http_check:
kwargs.update({'http_version': 1.1, 'domain_name': 'testlab.com'})
else:
kwargs.update({'http_version': 1.0, 'domain_name': None})
if proto == constants.HEALTH_MONITOR_UDP_CONNECT:
kwargs['check_script_path'] = (CONF.haproxy_amphora.base_path +
'lvs/check/' + 'udp_check.sh')
@ -988,7 +1004,7 @@ def sample_base_expected_config(frontend=None, backend=None,
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
" timeout check 31s\n"
" option httpchk GET /index.html\n"
" option httpchk GET /index.html HTTP/1.0\\r\\n\n"
" http-check expect rstatus 418\n"
" fullconn {maxconn}\n"
" option allbackups\n"

View File

@ -0,0 +1,5 @@
---
features:
- Extend the Octavia Health Monitor API with two new fields ``http_version``
and ``domain_name`` for support HTTP health check, which will inject the
domain name into HTTP host header.