Skip to content

Commit 9abf393

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Amphora vertical scaling optimization in Octavia"
2 parents 250b54a + 0b1b6c5 commit 9abf393

File tree

8 files changed

+158
-47
lines changed

8 files changed

+158
-47
lines changed

octavia/amphorae/backends/agent/api_server/amphora_info.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import socket
1818
import subprocess
1919

20+
from oslo_log import log as logging
2021
import pyroute2
2122
import webob
2223

@@ -26,6 +27,8 @@
2627
from octavia.common import constants as consts
2728
from octavia.common import exceptions
2829

30+
LOG = logging.getLogger(__name__)
31+
2932

3033
class AmphoraInfo(object):
3134
def __init__(self, osutils):
@@ -60,6 +63,9 @@ def compile_amphora_details(self, extend_lvs_driver=None):
6063
meminfo = self._get_meminfo()
6164
cpu = self._cpu()
6265
st = os.statvfs('/')
66+
listeners = (
67+
sorted(set(haproxy_listener_list + lvs_listener_list))
68+
if lvs_listener_list else haproxy_listener_list)
6369
body = {'hostname': socket.gethostname(),
6470
'haproxy_version':
6571
self._get_version_of_installed_package('haproxy'),
@@ -68,6 +74,7 @@ def compile_amphora_details(self, extend_lvs_driver=None):
6874
'active': True,
6975
'haproxy_count':
7076
self._count_haproxy_processes(haproxy_listener_list),
77+
'cpu_count': os.cpu_count(),
7178
'cpu': {
7279
'total': cpu['total'],
7380
'user': cpu['user'],
@@ -85,11 +92,10 @@ def compile_amphora_details(self, extend_lvs_driver=None):
8592
'used': (st.f_blocks - st.f_bfree) * st.f_frsize,
8693
'available': st.f_bavail * st.f_frsize},
8794
'load': self._load(),
95+
'active_tuned_profiles': self._get_active_tuned_profiles(),
8896
'topology': consts.TOPOLOGY_SINGLE,
8997
'topology_status': consts.TOPOLOGY_STATUS_OK,
90-
'listeners': sorted(list(
91-
set(haproxy_listener_list + lvs_listener_list)))
92-
if lvs_listener_list else haproxy_listener_list,
98+
'listeners': listeners,
9399
'packages': {}}
94100
if extend_body:
95101
body.update(extend_body)
@@ -188,3 +194,12 @@ def get_interface(self, ip_addr):
188194
status=404)
189195
return webob.Response(json=dict(message='OK', interface=interface),
190196
status=200)
197+
198+
def _get_active_tuned_profiles(self) -> str:
199+
"""Returns the active TuneD profile(s)"""
200+
try:
201+
with open("/etc/tuned/active_profile", "r", encoding="utf-8") as f:
202+
return f.read(1024).strip()
203+
except OSError as ex:
204+
LOG.debug("Reading active TuneD profiles failed: %r", ex)
205+
return ""

octavia/amphorae/drivers/haproxy/rest_api_driver.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,13 @@ def update_amphora_listeners(self, loadbalancer, amphora,
219219
if has_tcp and not split_config:
220220
if listeners_to_update:
221221
# Generate HaProxy configuration from listener object
222+
amp_details = self.clients[amphora.api_version].get_details(
223+
amphora)
222224
config = self.jinja_combo.build_config(
223225
host_amphora=amphora, listeners=listeners_to_update,
224226
tls_certs=certs,
225-
haproxy_versions=haproxy_versions)
227+
haproxy_versions=haproxy_versions,
228+
amp_details=amp_details)
226229
self.clients[amphora.api_version].upload_config(
227230
amphora, loadbalancer.id, config,
228231
timeout_dict=timeout_dict)

octavia/common/jinja/haproxy/combined_listeners/jinja_cfg.py

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import os
1616
import re
17+
from typing import Optional
1718

1819
import jinja2
1920
from octavia_lib.common import constants as lib_consts
@@ -85,11 +86,12 @@ def __init__(self,
8586
self.connection_logging = connection_logging
8687

8788
def build_config(self, host_amphora, listeners, tls_certs,
88-
haproxy_versions, socket_path=None):
89+
haproxy_versions, amp_details, socket_path=None):
8990
"""Convert a logical configuration to the HAProxy version
9091
9192
:param host_amphora: The Amphora this configuration is hosted on
9293
:param listener: The listener configuration
94+
:param amp_details: Detail information from the amphora
9395
:param socket_path: The socket path for Haproxy process
9496
:return: Rendered configuration
9597
"""
@@ -115,7 +117,8 @@ def build_config(self, host_amphora, listeners, tls_certs,
115117
return self.render_loadbalancer_obj(
116118
host_amphora, listeners, tls_certs=tls_certs,
117119
socket_path=socket_path,
118-
feature_compatibility=feature_compatibility)
120+
feature_compatibility=feature_compatibility,
121+
amp_details=amp_details)
119122

120123
def _get_template(self):
121124
"""Returns the specified Jinja configuration template."""
@@ -152,13 +155,15 @@ def _format_log_string(self, load_balancer, protocol):
152155

153156
def render_loadbalancer_obj(self, host_amphora, listeners,
154157
tls_certs=None, socket_path=None,
155-
feature_compatibility=None):
158+
feature_compatibility=None,
159+
amp_details: Optional[dict] = None):
156160
"""Renders a templated configuration from a load balancer object
157161
158162
:param host_amphora: The Amphora this configuration is hosted on
159163
:param listener: The listener configuration
160164
:param tls_certs: Dict of the TLS certificates for the listener
161165
:param socket_path: The socket path for Haproxy process
166+
:param amp_details: Detail information from the amphora
162167
:return: Rendered configuration
163168
"""
164169
feature_compatibility = feature_compatibility or {}
@@ -167,36 +172,45 @@ def render_loadbalancer_obj(self, host_amphora, listeners,
167172
listeners[0].load_balancer,
168173
listeners,
169174
tls_certs,
170-
feature_compatibility,)
175+
feature_compatibility)
171176
if not socket_path:
172177
socket_path = '%s/%s.sock' % (self.base_amp_path,
173178
listeners[0].load_balancer.id)
174179
state_file_path = '%s/%s/servers-state' % (
175180
self.base_amp_path,
176181
listeners[0].load_balancer.id) if feature_compatibility.get(
177182
constants.SERVER_STATE_FILE) else ''
178-
prometheus_listener = False
179-
for listener in listeners:
180-
if listener.protocol == lib_consts.PROTOCOL_PROMETHEUS:
181-
prometheus_listener = True
182-
break
183+
prometheus_listener = any(
184+
lsnr.protocol == lib_consts.PROTOCOL_PROMETHEUS for lsnr in
185+
listeners)
183186
require_insecure_fork = feature_compatibility.get(
184187
constants.INSECURE_FORK)
185188
enable_prometheus = prometheus_listener and feature_compatibility.get(
186189
lib_consts.PROTOCOL_PROMETHEUS, False)
190+
191+
jinja_dict = {
192+
'loadbalancer': loadbalancer,
193+
'stats_sock': socket_path,
194+
'log_http': self.log_http,
195+
'log_server': self.log_server,
196+
'state_file': state_file_path,
197+
'administrative_log_facility':
198+
CONF.amphora_agent.administrative_log_facility,
199+
'user_log_facility':
200+
CONF.amphora_agent.user_log_facility,
201+
'connection_logging': self.connection_logging,
202+
'enable_prometheus': enable_prometheus,
203+
'require_insecure_fork': require_insecure_fork,
204+
}
205+
try:
206+
# Enable cpu-pinning only if the amphora TuneD profile is active
207+
if "amphora" in amp_details["active_tuned_profiles"].split():
208+
jinja_dict["cpu_count"] = int(amp_details["cpu_count"])
209+
except (KeyError, TypeError):
210+
pass
211+
187212
return self._get_template().render(
188-
{'loadbalancer': loadbalancer,
189-
'stats_sock': socket_path,
190-
'log_http': self.log_http,
191-
'log_server': self.log_server,
192-
'state_file': state_file_path,
193-
'administrative_log_facility':
194-
CONF.amphora_agent.administrative_log_facility,
195-
'user_log_facility': CONF.amphora_agent.user_log_facility,
196-
'connection_logging': self.connection_logging,
197-
'enable_prometheus': enable_prometheus,
198-
'require_insecure_fork': require_insecure_fork},
199-
constants=constants, lib_consts=lib_consts)
213+
jinja_dict, constants=constants, lib_consts=lib_consts)
200214

201215
def _transform_loadbalancer(self, host_amphora, loadbalancer, listeners,
202216
tls_certs, feature_compatibility):

octavia/common/jinja/haproxy/combined_listeners/templates/base.j2

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ global
2626
{% if loadbalancer.global_connection_limit is defined %}
2727
maxconn {{ loadbalancer.global_connection_limit }}
2828
{% endif %}
29+
{%- if cpu_count is defined and cpu_count > 1 %}
30+
nbthread {{ cpu_count - 1 }}
31+
cpu-map auto:1/1-{{ cpu_count - 1 }} 1-{{ cpu_count - 1 }}
32+
{%- endif %}
2933
{% set found_ns = namespace(found=false) %}
3034
{% for listener in loadbalancer.listeners if listener.enabled %}
3135
{% for pool in listener.pools if pool.enabled %}

octavia/tests/functional/amphorae/backend/agent/api_server/test_server.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2964,10 +2964,14 @@ def _test_details(self, distro, mock_subbprocess, mock_hostname,
29642964

29652965
haproxy_count = random.randrange(0, 100)
29662966
mock_count_haproxy.return_value = haproxy_count
2967+
tuned_profiles = "virtual-guest optimize-serial-console amphora"
29672968

2968-
expected_dict = {'active': True, 'api_version': '1.0',
2969+
expected_dict = {'active': True,
2970+
'active_tuned_profiles': tuned_profiles,
2971+
'api_version': '1.0',
29692972
'cpu': {'soft_irq': cpu_softirq, 'system': cpu_system,
29702973
'total': cpu_total, 'user': cpu_user},
2974+
'cpu_count': os.cpu_count(),
29712975
'disk': {'available': disk_available,
29722976
'used': disk_used},
29732977
'haproxy_count': haproxy_count,
@@ -2995,10 +2999,13 @@ def _test_details(self, distro, mock_subbprocess, mock_hostname,
29952999
'topology_status': consts.TOPOLOGY_STATUS_OK,
29963000
'lvs_listener_process_count': 0}
29973001

2998-
if distro == consts.UBUNTU:
2999-
rv = self.ubuntu_app.get('/' + api_server.VERSION + '/details')
3000-
elif distro == consts.CENTOS:
3001-
rv = self.centos_app.get('/' + api_server.VERSION + '/details')
3002+
with mock.patch("octavia.amphorae.backends.agent.api_server"
3003+
".amphora_info.open",
3004+
mock.mock_open(read_data=tuned_profiles)):
3005+
if distro == consts.UBUNTU:
3006+
rv = self.ubuntu_app.get('/' + api_server.VERSION + '/details')
3007+
elif distro == consts.CENTOS:
3008+
rv = self.centos_app.get('/' + api_server.VERSION + '/details')
30023009

30033010
self.assertEqual(200, rv.status_code)
30043011
self.assertEqual(expected_dict,

octavia/tests/unit/amphorae/backends/agent/api_server/test_amphora_info.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
14+
import os
1415
import random
1516
from unittest import mock
1617

@@ -151,11 +152,13 @@ def test_compile_amphora_details(self, mhostname, m_count, m_pkg_version,
151152
original_version = api_server.VERSION
152153
api_server.VERSION = self.API_VERSION
153154
expected_dict = {u'active': True,
155+
'active_tuned_profiles': '',
154156
u'api_version': self.API_VERSION,
155157
u'cpu': {u'soft_irq': u'8336',
156158
u'system': u'52554',
157159
u'total': 7503411,
158160
u'user': u'252551'},
161+
'cpu_count': os.cpu_count(),
159162
u'disk': {u'available': 109079126016,
160163
u'used': 25718685696},
161164
u'haproxy_count': 5,
@@ -235,11 +238,13 @@ def test_compile_amphora_details_for_ipvs(self, mhostname, m_count,
235238
original_version = api_server.VERSION
236239
api_server.VERSION = self.API_VERSION
237240
expected_dict = {u'active': True,
241+
'active_tuned_profiles': '',
238242
u'api_version': self.API_VERSION,
239243
u'cpu': {u'soft_irq': u'8336',
240244
u'system': u'52554',
241245
u'total': 7503411,
242246
u'user': u'252551'},
247+
'cpu_count': os.cpu_count(),
243248
u'disk': {u'available': 109079126016,
244249
u'used': 25718685696},
245250
u'haproxy_count': 5,

0 commit comments

Comments
 (0)