diff --git a/src/config/svc-monitor/SConscript b/src/config/svc-monitor/SConscript index ca9af89cef5..e7ec12c5812 100644 --- a/src/config/svc-monitor/SConscript +++ b/src/config/svc-monitor/SConscript @@ -41,7 +41,6 @@ sources = [ 'svc_monitor/services/loadbalancer/drivers/ha_proxy/__init__.py', 'svc_monitor/services/loadbalancer/drivers/ha_proxy/driver.py', 'svc_monitor/services/loadbalancer/drivers/ha_proxy/haproxy_config.py', - 'svc_monitor/services/loadbalancer/drivers/ha_proxy/haproxy_template.txt', 'svc_monitor/snat_agent.py', 'svc_monitor/tests/__init__.py', 'svc_monitor/tests/test_common_utils.py', diff --git a/src/config/svc-monitor/svc_monitor/config_db.py b/src/config/svc-monitor/svc_monitor/config_db.py index b53d9478208..1078cfbd66a 100644 --- a/src/config/svc-monitor/svc_monitor/config_db.py +++ b/src/config/svc-monitor/svc_monitor/config_db.py @@ -490,6 +490,8 @@ def __init__(self, uuid, obj_dict=None): self.virtual_machines = set() self.logical_router = None self.params = None + self.bindings = None + self.kvps = None self.state = 'init' self.launch_count = 0 self.back_off = -1 @@ -520,6 +522,9 @@ def update(self, obj=None): self.proj_name = obj['fq_name'][-2] self.check_vn_changes(obj) self.params = obj.get('service_instance_properties', None) + self.bindings = obj.get('service_instance_bindings', None) + if self.bindings: + self.kvps = self.bindings.get('key_value_pair', None) self.update_single_ref('service_template', obj) self.update_single_ref('loadbalancer', obj) self.update_single_ref('loadbalancer_pool', obj) diff --git a/src/config/svc-monitor/svc_monitor/loadbalancer_agent.py b/src/config/svc-monitor/svc_monitor/loadbalancer_agent.py index 46fda9f768e..40e8dd3affb 100644 --- a/src/config/svc-monitor/svc_monitor/loadbalancer_agent.py +++ b/src/config/svc-monitor/svc_monitor/loadbalancer_agent.py @@ -208,6 +208,7 @@ def virtual_ip_add(self, vip): v = self.virtual_ip_get_reqdict(vip) driver = self._get_driver_for_pool(v['pool_id']) try: + driver.set_config_v1(vip.loadbalancer_pool) if not vip.last_sent: driver.create_vip(v) elif v != vip.last_sent: @@ -230,6 +231,7 @@ def loadbalancer_add(self, loadbalancer): lb = self.loadbalancer_get_reqdict(loadbalancer) driver = self._get_driver_for_loadbalancer(lb['id'], 'opencontrail') try: + driver.set_config_v2(loadbalancer.uuid) if not loadbalancer.last_sent: driver.create_loadbalancer(lb) elif lb != loadbalancer.last_sent: diff --git a/src/config/svc-monitor/svc_monitor/services/loadbalancer/drivers/f5/f5_driver.py b/src/config/svc-monitor/svc_monitor/services/loadbalancer/drivers/f5/f5_driver.py index b2e56928b4c..e01146203ea 100644 --- a/src/config/svc-monitor/svc_monitor/services/loadbalancer/drivers/f5/f5_driver.py +++ b/src/config/svc-monitor/svc_monitor/services/loadbalancer/drivers/f5/f5_driver.py @@ -1327,3 +1327,9 @@ def update_health_monitor(self, pool_id): pass # end update_health_monitor + + def set_config_v1(self, pool_id): + pass + + def set_config_v2(self, lb_id): + pass diff --git a/src/config/svc-monitor/svc_monitor/services/loadbalancer/drivers/ha_proxy/driver.py b/src/config/svc-monitor/svc_monitor/services/loadbalancer/drivers/ha_proxy/driver.py index ea204cd9514..0cf93a03c59 100644 --- a/src/config/svc-monitor/svc_monitor/services/loadbalancer/drivers/ha_proxy/driver.py +++ b/src/config/svc-monitor/svc_monitor/services/loadbalancer/drivers/ha_proxy/driver.py @@ -9,8 +9,10 @@ from vnc_api.vnc_api import ServiceTemplate, ServiceInstance, ServiceInstanceType from vnc_api.vnc_api import ServiceScaleOutType, ServiceInstanceInterfaceType from vnc_api.vnc_api import NoIdError, RefsExistError +from vnc_api.vnc_api import KeyValuePair, KeyValuePairs from svc_monitor.config_db import * +import haproxy_config LOADBALANCER_SERVICE_TEMPLATE = [ 'default-domain', @@ -383,3 +385,39 @@ def delete_pool_health_monitor(self, health_monitor, pool_id): def update_health_monitor(self, id, health_monitor): pass + + def set_config_v1(self, pool_id): + pool = LoadbalancerPoolSM.get(pool_id) + if not pool: + return + conf = haproxy_config.get_config_v1(pool) + self.set_haproxy_config(pool.service_instance, pool.uuid, conf) + + def set_config_v2(self, lb_id): + lb = LoadbalancerSM.get(lb_id) + if not lb: + return + conf = haproxy_config.get_config_v2(lb.uuid) + self.set_haproxy_config(pool.service_instance, lb.uuid, conf) + + def set_haproxy_config(self, si_id, lb_uuid, conf): + si = ServiceInstanceSM.get(si_id) + if not si: + return + + for kv in si.kvps: + if kv['key'] == 'haproxy_config': + if kv['value'] == conf: + return + + si_obj = ServiceInstance() + si_obj.uuid = si.uuid + si_obj.fq_name = si.fq_name + kvp = KeyValuePair('haproxy_config', conf) + si_obj.add_service_instance_bindings(kvp) + kvp = KeyValuePair('lb_uuid', lb_uuid) + si_obj.add_service_instance_bindings(kvp) + try: + self._api.service_instance_update(si_obj) + except NoIdError: + return diff --git a/src/config/svc-monitor/svc_monitor/services/loadbalancer/drivers/ha_proxy/haproxy_config.py b/src/config/svc-monitor/svc_monitor/services/loadbalancer/drivers/ha_proxy/haproxy_config.py index 26ca8907c67..b7c6107223f 100644 --- a/src/config/svc-monitor/svc_monitor/services/loadbalancer/drivers/ha_proxy/haproxy_config.py +++ b/src/config/svc-monitor/svc_monitor/services/loadbalancer/drivers/ha_proxy/haproxy_config.py @@ -1,13 +1,13 @@ -from mako.template import Template -from mako import exceptions -from mako.runtime import Context -from StringIO import StringIO from svc_monitor.config_db import * +PROTO_HTTP = 'HTTP' +PROTO_HTTPS = 'HTTPS' + PROTO_MAP = { 'TCP': 'tcp', 'HTTP': 'http', - 'HTTPS': 'http' + 'HTTPS': 'http', + 'TERMINATED_HTTPS': 'terminated_https' } LB_METHOD_MAP = { @@ -16,134 +16,218 @@ 'SOURCE_IP': 'source' } -class HaproxyConfig(object): - def __init__(self): - self.config = {} - self.template = Template(filename='haproxy_template.txt', format_exceptions=True) - self.conf_dir = '/var/lib/contrail/loadbalancer/' +HEALTH_MONITOR_PING = 'PING' +HEALTH_MONITOR_TCP = 'TCP' +HEALTH_MONITOR_HTTP = 'HTTP' +HEALTH_MONITOR_HTTPS = 'HTTPS' - def build_config_v1(self, pool_dict): - pool = LoadbalancerPoolSM.get(pool_dict['id']) - if not pool: - return - self.set_globals(pool.uuid) - self.set_defaults() - self.set_virtual_ip(pool) - buf = StringIO() - ctx = Context(buf, **self.config) - self.template.render_context(ctx) - return buf.getvalue() - - def build_config_v2(self, lb): - self.set_globals(lb.uuid) - self.set_defaults() - self.set_loadbalancer(lb) - buf = StringIO() - ctx = Context(buf, **self.config) - self.template.render_context(ctx) - return buf.getvalue() - - def set_globals(self, uuid): - self.config['sock_path'] = self.conf_dir + uuid + '/haproxy.sock' - self.config['max_conn'] = 65000 - self.config['ssl_ciphers'] = \ - 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:' \ - 'ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:' \ - 'RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS' - - def set_defaults(self): - self.config['client_timeout'] = "300000" - self.config['server_timeout'] = "300000" - self.config['connect_timeout'] = "5000" - - def set_loadbalancer(self, lb): - loadbalancer = {} - loadbalancer['vip'] = lb.virtual_ip - loadbalancer['listeners'] = [] - for listener_id in lb.listeners: - listener = LoadbalancerListener.get(listener_id) - if not listener: - continue - loadbalancer['listeners'].append(listener_id) - self.add_listener(listener) - self.config['loadbalancer'] = loadbalancer - - def add_listener(self, ll): - listener = {} - listener['port'] = ll.port - listener['protocol'] = PROTO_MAP.get(ll.protocol) - for pool_id in ll.pools: - pool = LoadbalancerPool.get(pool_id) - if not pool: - continue - listener['pool'] = pool_id - self.add_pool(pool) - self.config['listeners'][ll.uuid] = listener - - def set_virtual_ip(self, pool): +PERSISTENCE_SOURCE_IP = 'SOURCE_IP' +PERSISTENCE_HTTP_COOKIE = 'HTTP_COOKIE' +PERSISTENCE_APP_COOKIE = 'APP_COOKIE' + +def get_config_v2(lb): + sock_path = '/var/lib/contrail/loadbalancer/' + sock_path += lb.uuid + '/haproxy.sock' + conf = set_globals(sock_path) + '\n\n' + conf += set_defaults() + '\n\n' + conf += set_v2_frontend_backend(lb) + return conf + +def get_config_v1(pool): + sock_path = '/var/lib/contrail/loadbalancer/' + sock_path += pool.uuid + '/haproxy.sock' + conf = set_globals(sock_path) + '\n\n' + conf += set_defaults() + '\n\n' + conf += set_v1_frontend_backend(pool) + return conf + +def set_globals(sock_path): + maxconn = 65000 + ssl_ciphers = \ + 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:' \ + 'ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:' \ + 'RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS' + + conf = [ + 'global', + 'daemon', + 'user nobody', + 'group nogroup', + 'log /dev/log local0', + 'log /dev/log local1 notice', + 'tune.ssl.default-dh-param 2048', + 'ssl-default-bind-ciphers %s' % ssl_ciphers, + 'ulimit-n 200000', + 'maxconn %d' % maxconn + ] + conf.append('stats socket %s mode 0666 level user' % sock_path) + res = "\n\t".join(conf) + return res + +def set_defaults(): + client_timeout = 300000 + server_timeout = 300000 + connect_timeout = 5000 + + conf = [ + 'defaults', + 'log global', + 'retries 3', + 'option redispatch', + 'timeout connect %d' % connect_timeout, + 'timeout client %d' % client_timeout, + 'timeout server %d' % server_timeout, + ] + + res = "\n\t".join(conf) + return res + +def set_v1_frontend_backend(pool): + conf = [] + vip = VirtualIpSM.get(pool.virtual_ip) + if not vip and not vip.params['admin_state']: + return + + ssl = '' + if vip.params['protocol'] == PROTO_HTTPS: + ssl = 'ssl crt %s no-sslv3' % crt_file + + lconf = [ + 'frontend %s' % vip.uuid, + 'option tcplog', + 'bind %s:%s %s' % (vip.params['address'], + vip.params['protocol_port'], ssl), + 'mode %s' % PROTO_MAP[vip.params['protocol']] + ] + if vip.params['protocol'] == PROTO_HTTP or \ + vip.params['protocol'] == PROTO_HTTPS: + lconf.append('option forwardfor') + + if pool and pool.params['admin_state']: + lconf.append('default_backend %s' % pool.uuid) + res = "\n\t".join(lconf) + '\n\n' + res += set_backend(pool) + conf.append(res) + + return "\n".join(conf) + +def set_v2_frontend_backend(lb): + conf = [] + for ll_id in lb.loadbalancer_listeners: + ll = LoadbalancerListenerSM.get(ll_id) + if not ll: + continue + if not ll.params['admin_state']: + continue + + ssl = '' + if ll.params['protocol'] == PROTO_HTTPS: + ssl = 'ssl crt %s no-sslv3' % crt_file + + lconf = [ + 'frontend %s' % ll.uuid, + 'option tcplog', + 'bind %s:%s %s' % (lb.params['vip_address'], + ll.params['protocol_port'], ssl), + 'mode %s' % PROTO_MAP[ll.params['protocol']] + ] + if ll.params['protocol'] == PROTO_HTTP or \ + ll.params['protocol'] == PROTO_HTTPS: + lconf.append('option forwardfor') + + pool = LoadbalancerPoolSM.get(ll.loadbalancer_pool) + if pool and pool.params['admin_state']: + lconf.append('default_backend %s' % pool.uuid) + res = "\n\t".join(lconf) + '\n\n' + res += set_backend(pool) + conf.append(res) + + return "\n".join(conf) + +def set_backend(pool): + conf = [ + 'backend %s' % pool.uuid, + 'mode %s' % PROTO_MAP[pool.params['protocol']], + 'balance %s' % LB_METHOD_MAP[pool.params['loadbalancer_method']] + ] + if pool.params['protocol'] == PROTO_HTTP: + conf.append('option forwardfor') + + server_suffix = '' + for hm_id in pool.loadbalancer_healthmonitors: + hm = HealthMonitorSM.get(hm_id) + if not hm: + continue + server_suffix, monitor_conf = set_health_monitor(hm) + conf.extend(monitor_conf) + + session_conf = set_session_persistence(pool) + conf.extend(session_conf) + + for member_id in pool.members: + member = LoadbalancerMemberSM.get(member_id) + if not member or not member.params['admin_state']: + continue + server = (('server %s %s:%s weight %s') % (member.uuid, + member.params['address'], member.params['protocol_port'], + member.params['weight'])) + server_suffix + conf.append(server) + + return "\n\t".join(conf) + '\n' + +def set_health_monitor(hm): + if not hm.params['admin_state']: + return '', [] + + server_suffix = ' check inter %ss fall %s' % \ + (hm.params['delay'], hm.params['max_retries']) + conf = [ + 'timeout check %ss' % hm.params['timeout'] + ] + + if hm.params['monitor_type'] in (HEALTH_MONITOR_HTTP, HEALTH_MONITOR_HTTPS): + conf.append('option httpchk %s %s' % + (hm.params['http_method'], hm.params['url_path'])) + conf.append( + 'http-check expect rstatus %s' % + '|'.join(_get_codes(hm.params['expected_codes'])) + ) + + if hm.params['monitor_type'] == HEALTH_MONITOR_HTTPS: + conf.append('option ssl-hello-chk') + + return server_suffix, conf + +def set_session_persistence(pool): + conf = [] + if pool.virtual_ip: vip = VirtualIpSM.get(pool.virtual_ip) if not vip: return - loadbalancer = {} - loadbalancer['vip'] = vip.params['address'] - loadbalancer['listeners'] = [vip.uuid] - self.config['loadbalancer'] = loadbalancer - self.config['persistence'] = vip.params['persistence_type'] - self.config['cookie'] = vip.params['persistence_cookie_name'] - self.config['ssl_cert'] = '/tmp/ssl_cert' - self.config['listeners'] = {vip.uuid : - {'port': vip.params['protocol_port'], - 'protocol': PROTO_MAP.get(vip.params['protocol']), - 'pool': pool.uuid}} - self.config['pools'] = {} - self.add_pool(pool) - - def add_pool(self, lp): - pool = {} - pool['protocol'] = PROTO_MAP.get(lp.params['protocol']) - pool['method'] = LB_METHOD_MAP.get(lp.params['loadbalancer_method']) - pool['members'] = [] - self.set_healthmonitor(lp) - self.config['members'] = {} - for server_id in lp.members: - server = LoadbalancerMemberSM.get(server_id) - if not server: - continue - pool['members'].append(server_id) - self.add_server(server) - self.config['pools'][lp.uuid] = pool - - def add_server(self, server): - member = {} - member['address'] = server.params['address'] - member['port'] = server.params['protocol_port'] - member['weight'] = server.params['weight'] - self.config['members'][server.uuid] = member - - def set_healthmonitor(self, pool): - for hm_id in pool.loadbalancer_healthmonitors: - hm = HealthMonitorSM.get(hm_id) - if not hm: - continue - self.config['monitor_type'] = hm.params['monitor_type'] - self.config['timeout'] = hm.params['timeout'] - self.config['delay'] = hm.params['delay'] - self.config['max_retries'] = hm.params['max_retries'] - self.config['http_method'] = hm.params['http_method'] - self.config['url_path'] = hm.params['url_path'] - self.config['expected_codes'] = \ - '|'.join(self._get_codes(hm.params['expected_codes'])) - return + persistence = vip.params.get('persistence_type', None) + cookie = vip.params.get('persistence_cookie_name', None) + else: + persistence = pool.params.get('session_persistence', None) + cookie = pool.params.get('persistence_cookie_name', None) + + if persistence == PERSISTENCE_SOURCE_IP: + conf.append('stick-table type ip size 10k') + conf.append('stick on src') + elif persistence == PERSISTENCE_HTTP_COOKIE: + conf.append('cookie SRV insert indirect nocache') + elif (persistence == PERSISTENCE_APP_COOKIE and cookie): + conf.append('appsession %s len 56 timeout 3h' % cookie) + return conf - def _get_codes(self, codes): - response = set() - for code in codes.replace(',', ' ').split(' '): - code = code.strip() - if not code: - continue - elif '-' in code: - low, hi = code.split('-')[:2] - response.update(str(i) for i in xrange(int(low), int(hi) + 1)) - else: - response.add(code) - return response +def _get_codes(codes): + response = set() + for code in codes.replace(',', ' ').split(' '): + code = code.strip() + if not code: + continue + elif '-' in code: + low, hi = code.split('-')[:2] + response.update(str(i) for i in xrange(int(low), int(hi) + 1)) + else: + response.add(code) + return response diff --git a/src/config/svc-monitor/svc_monitor/services/loadbalancer/drivers/ha_proxy/haproxy_template.txt b/src/config/svc-monitor/svc_monitor/services/loadbalancer/drivers/ha_proxy/haproxy_template.txt deleted file mode 100644 index 78d94ba4556..00000000000 --- a/src/config/svc-monitor/svc_monitor/services/loadbalancer/drivers/ha_proxy/haproxy_template.txt +++ /dev/null @@ -1,63 +0,0 @@ -global - daemon - user nobody - group nogroup - log /dev/log local0 - log /dev/log local1 notice - tune.ssl.default-dh-param 2048 - ssl-default-bind-ciphers ${ssl_ciphers} - ulimit-n 200000 - maxconn ${max_conn} - stats socket ${sock_path} mode 0666 level user - -defaults - log global - retries 3 - option redispatch - timeout connect ${connect_timeout} - timeout client ${client_timeout} - timeout server ${server_timeout} - -% for listener_id in loadbalancer['listeners']: -<% listener = listeners[listener_id] %>\ -frontend ${listener_id} - option tcplog - bind ${loadbalancer['vip']}:${listener['port']} ${ssl_cert} - mode ${listener['protocol']} - default_backend ${listener['pool']} - option forwardfor - -<% pool = pools[listener['pool']] %>\ -backend ${listener['pool']} - mode ${pool['protocol']} - balance ${pool['method']} - % if pool['protocol'] == 'http': - option forwardfor - % endif - % if timeout: - timeout check ${timeout}s - % endif - % if monitor_type in ['HTTP', 'HTTPS']: - option httpchk ${http_method} ${url_path} - http-check expect rstatus ${expected_codes} - % endif - % if monitor_type in ['HTTPS']: - option ssl-hello-chk - % endif - % if persistence == 'PERSISTENCE_SOURCE_IP': - stick-table type ip size 10k - stick on src - % elif persistence == 'PERSISTENCE_HTTP_COOKIE': - cookie SRV insert indirect nocache - % elif (persistence == 'PERSISTENCE_APP_COOKIE' and cookie): - appsession ${cookie} len 56 timeout 3h - % endif - % for server_id in pool['members']: -<% server = members[server_id] %>\ - % if delay and max_retries: - server ${server_id} ${server['address']}:${server['port']} weight ${server['weight']} check inter ${delay}s fall ${max_retries} - % else: - server ${server_id} ${server['address']}:${server['port']} weight ${server['weight']} - % endif -% endfor -% endfor diff --git a/src/config/svc-monitor/svc_monitor/tests/fake_lb_driver.py b/src/config/svc-monitor/svc_monitor/tests/fake_lb_driver.py index 7df266a184f..3c8066b78c2 100644 --- a/src/config/svc-monitor/svc_monitor/tests/fake_lb_driver.py +++ b/src/config/svc-monitor/svc_monitor/tests/fake_lb_driver.py @@ -88,3 +88,9 @@ def update_health_monitor(self, id, health_monitor): def stats(self, pool_id): pass + + def set_config_v1(self, pool_id): + pass + + def set_config_v2(self, lb_id): + pass diff --git a/src/schema/loadbalancer.xsd b/src/schema/loadbalancer.xsd index 2f9a9c81dd6..e25795370f7 100644 --- a/src/schema/loadbalancer.xsd +++ b/src/schema/loadbalancer.xsd @@ -79,6 +79,7 @@ +