diff --git a/ovn_octavia_provider/common/constants.py b/ovn_octavia_provider/common/constants.py index 72b609fb..e88c3de5 100644 --- a/ovn_octavia_provider/common/constants.py +++ b/ovn_octavia_provider/common/constants.py @@ -20,3 +20,16 @@ OVN_PORT_FIP_EXT_ID_KEY = 'neutron:port_fip' OVN_SUBNET_EXT_ID_KEY = 'neutron:subnet_id' OVN_SUBNET_EXT_IDS_KEY = 'neutron:subnet_ids' OVN_NETWORK_NAME_EXT_ID_KEY = 'neutron:network_name' +OVN_SG_IDS_EXT_ID_KEY = 'neutron:security_group_ids' +OVN_DEVICE_OWNER_EXT_ID_KEY = 'neutron:device_owner' +OVN_FIP_EXT_ID_KEY = 'neutron:fip_id' +OVN_FIP_PORT_EXT_ID_KEY = 'neutron:fip_port_id' + +LB_EXT_IDS_LS_REFS_KEY = 'ls_refs' +LB_EXT_IDS_LR_REF_KEY = 'lr_ref' +LB_EXT_IDS_POOL_PREFIX = 'pool_' +LB_EXT_IDS_LISTENER_PREFIX = 'listener_' +LB_EXT_IDS_MEMBER_PREFIX = 'member_' +LB_EXT_IDS_VIP_KEY = 'neutron:vip' +LB_EXT_IDS_VIP_FIP_KEY = 'neutron:vip_fip' +LB_EXT_IDS_VIP_PORT_ID_KEY = 'neutron:vip_port_id' diff --git a/ovn_octavia_provider/common/utils.py b/ovn_octavia_provider/common/utils.py index e2f32091..49bd0217 100644 --- a/ovn_octavia_provider/common/utils.py +++ b/ovn_octavia_provider/common/utils.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo_utils import netutils + from ovn_octavia_provider.common import constants @@ -31,3 +33,19 @@ def ovn_lrouter_port_name(id): # - patch--to-lrp- # lrp stands for Logical Router Port return constants.LRP_PREFIX + '%s' % id + + +def remove_macs_from_lsp_addresses(addresses): + """Remove the mac addreses from the Logical_Switch_Port addresses column. + + :param addresses: The list of addresses from the Logical_Switch_Port. + Example: ["80:fa:5b:06:72:b7 158.36.44.22", + "ff:ff:ff:ff:ff:ff 10.0.0.2"] + :returns: A list of IP addesses (v4 and v6) + """ + ip_list = [] + for addr in addresses: + ip_list.extend([x for x in addr.split() if + (netutils.is_valid_ipv4(x) or + netutils.is_valid_ipv6(x))]) + return ip_list diff --git a/ovn_octavia_provider/driver.py b/ovn_octavia_provider/driver.py index d4e3a74f..66db4981 100644 --- a/ovn_octavia_provider/driver.py +++ b/ovn_octavia_provider/driver.py @@ -64,23 +64,17 @@ REQ_TYPE_MEMBER_UPDATE = 'member_update' REQ_TYPE_LB_CREATE_LRP_ASSOC = 'lb_create_lrp_assoc' REQ_TYPE_LB_DELETE_LRP_ASSOC = 'lb_delete_lrp_assoc' REQ_TYPE_HANDLE_VIP_FIP = 'handle_vip_fip' +REQ_TYPE_HANDLE_MEMBER_DVR = 'handle_member_dvr' REQ_TYPE_EXIT = 'exit' REQ_INFO_ACTION_ASSOCIATE = 'associate' REQ_INFO_ACTION_DISASSOCIATE = 'disassociate' +REQ_INFO_MEMBER_ADDED = 'member_added' +REQ_INFO_MEMBER_DELETED = 'member_deleted' DISABLED_RESOURCE_SUFFIX = 'D' -LB_EXT_IDS_LS_REFS_KEY = 'ls_refs' -LB_EXT_IDS_LR_REF_KEY = 'lr_ref' -LB_EXT_IDS_POOL_PREFIX = 'pool_' -LB_EXT_IDS_LISTENER_PREFIX = 'listener_' -LB_EXT_IDS_MEMBER_PREFIX = 'member_' -LB_EXT_IDS_VIP_KEY = 'neutron:vip' -LB_EXT_IDS_VIP_FIP_KEY = 'neutron:vip_fip' -LB_EXT_IDS_VIP_PORT_ID_KEY = 'neutron:vip_port_id' - OVN_NATIVE_LB_PROTOCOLS = [constants.PROTOCOL_TCP, constants.PROTOCOL_UDP, ] OVN_NATIVE_LB_ALGORITHMS = [constants.LB_ALGORITHM_SOURCE_IP_PORT, ] @@ -162,7 +156,7 @@ class OvnNbIdlForLb(ovsdb_monitor.OvnIdl): SCHEMA = "OVN_Northbound" TABLES = ('Logical_Switch', 'Load_Balancer', 'Logical_Router', 'Logical_Switch_Port', 'Logical_Router_Port', - 'Gateway_Chassis') + 'Gateway_Chassis', 'NAT') def __init__(self, event_lock_name=None): self.conn_string = ovn_conf.get_ovn_nb_connection() @@ -236,6 +230,7 @@ class OvnProviderHelper(object): REQ_TYPE_LB_CREATE_LRP_ASSOC: self.lb_create_lrp_assoc, REQ_TYPE_LB_DELETE_LRP_ASSOC: self.lb_delete_lrp_assoc, REQ_TYPE_HANDLE_VIP_FIP: self.handle_vip_fip, + REQ_TYPE_HANDLE_MEMBER_DVR: self.handle_member_dvr, } @staticmethod @@ -425,7 +420,7 @@ class OvnProviderHelper(object): # than there is more than one (for more than 1 L4 protocol). for lb in ovn_lbs: fip = vip_lp.external_ids.get(ovn_const.OVN_PORT_FIP_EXT_ID_KEY) - lb_vip_fip = lb.external_ids.get(LB_EXT_IDS_VIP_FIP_KEY) + lb_vip_fip = lb.external_ids.get(ovn_const.LB_EXT_IDS_VIP_FIP_KEY) request_info = {'ovn_lb': lb, 'vip_fip': fip} if fip and fip != lb_vip_fip: @@ -451,8 +446,9 @@ class OvnProviderHelper(object): Output: set of rows of type Load_Balancer or empty set """ return {lb for lb in network.load_balancer - if network.name in lb.external_ids.get(LB_EXT_IDS_LS_REFS_KEY, - [])} + if network.name in lb.external_ids.get( + ovn_const.LB_EXT_IDS_LS_REFS_KEY, + [])} def _find_lb_in_table(self, lb, table): return [item for item in self.ovn_nbdb_api.tables[table].rows.values() @@ -558,21 +554,22 @@ class OvnProviderHelper(object): 'id': lb_id, 'protocol': protocol, 'vip_address': ovn_lbs[0].external_ids.get( - LB_EXT_IDS_VIP_KEY), + ovn_const.LB_EXT_IDS_VIP_KEY), 'vip_port_id': ovn_lbs[0].external_ids.get( - LB_EXT_IDS_VIP_PORT_ID_KEY), - LB_EXT_IDS_LR_REF_KEY: + ovn_const.LB_EXT_IDS_VIP_PORT_ID_KEY), + ovn_const.LB_EXT_IDS_LR_REF_KEY: ovn_lbs[0].external_ids.get( - LB_EXT_IDS_LR_REF_KEY), - LB_EXT_IDS_LS_REFS_KEY: + ovn_const.LB_EXT_IDS_LR_REF_KEY), + ovn_const.LB_EXT_IDS_LS_REFS_KEY: ovn_lbs[0].external_ids.get( - LB_EXT_IDS_LS_REFS_KEY), + ovn_const.LB_EXT_IDS_LS_REFS_KEY), 'admin_state_up': admin_state_up} # NOTE(mjozefcz): Handle vip_fip info if exists. - vip_fip = ovn_lbs[0].external_ids.get(LB_EXT_IDS_VIP_FIP_KEY) + vip_fip = ovn_lbs[0].external_ids.get( + ovn_const.LB_EXT_IDS_VIP_FIP_KEY) if vip_fip: - lb_info.update({LB_EXT_IDS_VIP_FIP_KEY: vip_fip}) + lb_info.update({ovn_const.LB_EXT_IDS_VIP_FIP_KEY: vip_fip}) self.lb_create(lb_info, protocol=protocol) # Looks like we've just added new LB # or updated exising, empty one. @@ -639,7 +636,7 @@ class OvnProviderHelper(object): return commands ovn_ls = None - ls_refs = ovn_lb.external_ids.get(LB_EXT_IDS_LS_REFS_KEY) + ls_refs = ovn_lb.external_ids.get(ovn_const.LB_EXT_IDS_LS_REFS_KEY) if ls_refs: try: ls_refs = jsonutils.loads(ls_refs) @@ -671,7 +668,7 @@ class OvnProviderHelper(object): else: ls_refs[ls_name] = ref_ct - 1 - ls_refs = {LB_EXT_IDS_LS_REFS_KEY: jsonutils.dumps(ls_refs)} + ls_refs = {ovn_const.LB_EXT_IDS_LS_REFS_KEY: jsonutils.dumps(ls_refs)} commands.append(self.ovn_nbdb_api.db_set( 'Load_Balancer', ovn_lb.uuid, ('external_ids', ls_refs))) @@ -696,12 +693,12 @@ class OvnProviderHelper(object): self.ovn_nbdb_api.db_set( 'Load_Balancer', ovn_lb.uuid, ('external_ids', - {LB_EXT_IDS_LR_REF_KEY: ','.join(lr_ref)}))) + {ovn_const.LB_EXT_IDS_LR_REF_KEY: ','.join(lr_ref)}))) else: commands.append( self.ovn_nbdb_api.db_remove( 'Load_Balancer', ovn_lb.uuid, 'external_ids', - (LB_EXT_IDS_LR_REF_KEY)) + (ovn_const.LB_EXT_IDS_LR_REF_KEY)) ) commands.append( self.ovn_nbdb_api.lr_lb_del(ovn_lr.uuid, ovn_lb.uuid, @@ -724,15 +721,18 @@ class OvnProviderHelper(object): if ovn_lr.name not in str(lr_rf): # Multiple routers in lr_rf are separated with ',' - lr_rf = {LB_EXT_IDS_LR_REF_KEY: ovn_lr.name} if not lr_rf else { - LB_EXT_IDS_LR_REF_KEY: "%s,%s" % (lr_rf, ovn_lr.name)} + if lr_rf: + lr_rf = {ovn_const.LB_EXT_IDS_LR_REF_KEY: + "%s,%s" % (lr_rf, ovn_lr.name)} + else: + lr_rf = {ovn_const.LB_EXT_IDS_LR_REF_KEY: ovn_lr.name} commands.append( self.ovn_nbdb_api.db_set('Load_Balancer', ovn_lb.uuid, ('external_ids', lr_rf))) return commands def _update_lb_to_lr_association(self, ovn_lb, ovn_lr, delete=False): - lr_ref = ovn_lb.external_ids.get(LB_EXT_IDS_LR_REF_KEY) + lr_ref = ovn_lb.external_ids.get(ovn_const.LB_EXT_IDS_LR_REF_KEY) if delete: return self._del_lb_to_lr_association(ovn_lb, ovn_lr, lr_ref) else: @@ -778,13 +778,13 @@ class OvnProviderHelper(object): return lr def _get_listener_key(self, listener_id, is_enabled=True): - listener_key = LB_EXT_IDS_LISTENER_PREFIX + str(listener_id) + listener_key = ovn_const.LB_EXT_IDS_LISTENER_PREFIX + str(listener_id) if not is_enabled: listener_key += ':' + DISABLED_RESOURCE_SUFFIX return listener_key def _get_pool_key(self, pool_id, is_enabled=True): - pool_key = LB_EXT_IDS_POOL_PREFIX + str(pool_id) + pool_key = ovn_const.LB_EXT_IDS_POOL_PREFIX + str(pool_id) if not is_enabled: pool_key += ':' + DISABLED_RESOURCE_SUFFIX return pool_key @@ -796,9 +796,24 @@ class OvnProviderHelper(object): mem_info += str(mem.split('_')[2]) + ',' return mem_info[:-1] # Remove the last ',' - def _get_member_key(self, member): - member_info = LB_EXT_IDS_MEMBER_PREFIX + member['id'] + "_" - member_info += member['address'] + ":" + str(member['protocol_port']) + def _get_member_key(self, member, old_convention=False): + member_info = '' + if isinstance(member, dict): + member_info = '%s%s_%s:%s' % ( + ovn_const.LB_EXT_IDS_MEMBER_PREFIX, + member['id'], + member['address'], + member['protocol_port']) + if not old_convention and member.get('subnet_id'): + member_info += "_" + member['subnet_id'] + elif isinstance(member, o_datamodels.Member): + member_info = '%s%s_%s:%s' % ( + ovn_const.LB_EXT_IDS_MEMBER_PREFIX, + member.member_id, + member.address, + member.protocol_port) + if not old_convention and member.subnet_id: + member_info += "_" + member.subnet_id return member_info def _make_listener_key_value(self, listener_port, pool_id): @@ -821,12 +836,12 @@ class OvnProviderHelper(object): def _get_pool_listeners(self, ovn_lb, pool_key): pool_listeners = [] for k, v in ovn_lb.external_ids.items(): - if LB_EXT_IDS_LISTENER_PREFIX not in k: + if ovn_const.LB_EXT_IDS_LISTENER_PREFIX not in k: continue vip_port, p_key = self._extract_listener_key_value(v) if pool_key == p_key: - pool_listeners.append(k[len(LB_EXT_IDS_LISTENER_PREFIX):]) - + pool_listeners.append( + k[len(ovn_const.LB_EXT_IDS_LISTENER_PREFIX):]) return pool_listeners def _frame_vip_ips(self, lb_external_ids): @@ -835,11 +850,11 @@ class OvnProviderHelper(object): if lb_external_ids.get('enabled') == 'False': return vip_ips - lb_vip = lb_external_ids[LB_EXT_IDS_VIP_KEY] - vip_fip = lb_external_ids.get(LB_EXT_IDS_VIP_FIP_KEY) + lb_vip = lb_external_ids[ovn_const.LB_EXT_IDS_VIP_KEY] + vip_fip = lb_external_ids.get(ovn_const.LB_EXT_IDS_VIP_FIP_KEY) for k, v in lb_external_ids.items(): - if (LB_EXT_IDS_LISTENER_PREFIX not in k or + if (ovn_const.LB_EXT_IDS_LISTENER_PREFIX not in k or self._is_listener_disabled(k)): continue @@ -867,7 +882,7 @@ class OvnProviderHelper(object): def _is_listener_in_lb(self, lb): for key in list(lb.external_ids): - if key.startswith(LB_EXT_IDS_LISTENER_PREFIX): + if key.startswith(ovn_const.LB_EXT_IDS_LISTENER_PREFIX): return True return False @@ -902,18 +917,18 @@ class OvnProviderHelper(object): # In case port is not found for the vip_address we will see an # exception when port['id'] is accessed. external_ids = { - LB_EXT_IDS_VIP_KEY: loadbalancer['vip_address'], - LB_EXT_IDS_VIP_PORT_ID_KEY: + ovn_const.LB_EXT_IDS_VIP_KEY: loadbalancer['vip_address'], + ovn_const.LB_EXT_IDS_VIP_PORT_ID_KEY: loadbalancer.get('vip_port_id') or port['id'], 'enabled': str(loadbalancer['admin_state_up'])} # In case vip_fip was passed - use it. - vip_fip = loadbalancer.get(LB_EXT_IDS_VIP_FIP_KEY) + vip_fip = loadbalancer.get(ovn_const.LB_EXT_IDS_VIP_FIP_KEY) if vip_fip: - external_ids[LB_EXT_IDS_VIP_FIP_KEY] = vip_fip + external_ids[ovn_const.LB_EXT_IDS_VIP_FIP_KEY] = vip_fip # In case of lr_ref passed - use it. - lr_ref = loadbalancer.get(LB_EXT_IDS_LR_REF_KEY) + lr_ref = loadbalancer.get(ovn_const.LB_EXT_IDS_LR_REF_KEY) if lr_ref: - external_ids[LB_EXT_IDS_LR_REF_KEY] = lr_ref + external_ids[ovn_const.LB_EXT_IDS_LR_REF_KEY] = lr_ref try: self.ovn_nbdb_api.db_create( @@ -938,7 +953,7 @@ class OvnProviderHelper(object): # NOTE(mjozefcz): In case of LS references where passed - # apply LS to the new LB. That could happend in case we # need another loadbalancer for other L4 protocol. - ls_refs = loadbalancer.get(LB_EXT_IDS_LS_REFS_KEY) + ls_refs = loadbalancer.get(ovn_const.LB_EXT_IDS_LS_REFS_KEY) if ls_refs: try: ls_refs = jsonutils.loads(ls_refs) @@ -1001,7 +1016,8 @@ class OvnProviderHelper(object): return status try: - port_id = ovn_lbs[0].external_ids[LB_EXT_IDS_VIP_PORT_ID_KEY] + port_id = ovn_lbs[0].external_ids[ + ovn_const.LB_EXT_IDS_VIP_PORT_ID_KEY] for ovn_lb in ovn_lbs: status = self._lb_delete(loadbalancer, ovn_lb, status) # Clear the status dict of any key having [] value @@ -1024,7 +1040,7 @@ class OvnProviderHelper(object): if loadbalancer['cascade']: # Delete all pools for key, value in ovn_lb.external_ids.items(): - if key.startswith(LB_EXT_IDS_POOL_PREFIX): + if key.startswith(ovn_const.LB_EXT_IDS_POOL_PREFIX): pool_id = key.split('_')[1] # Delete all members in the pool if value and len(value.split(',')) > 0: @@ -1036,12 +1052,12 @@ class OvnProviderHelper(object): {"id": pool_id, "provisioning_status": constants.DELETED}) - if key.startswith(LB_EXT_IDS_LISTENER_PREFIX): + if key.startswith(ovn_const.LB_EXT_IDS_LISTENER_PREFIX): status['listeners'].append({ 'id': key.split('_')[1], 'provisioning_status': constants.DELETED, 'operating_status': constants.OFFLINE}) - ls_refs = ovn_lb.external_ids.get(LB_EXT_IDS_LS_REFS_KEY, {}) + ls_refs = ovn_lb.external_ids.get(ovn_const.LB_EXT_IDS_LS_REFS_KEY, {}) if ls_refs: try: ls_refs = jsonutils.loads(ls_refs) @@ -1062,7 +1078,7 @@ class OvnProviderHelper(object): commands.append( self.ovn_nbdb_api.ls_lb_del(ls.uuid, ovn_lb.uuid, if_exists=True)) - lr_ref = ovn_lb.external_ids.get(LB_EXT_IDS_LR_REF_KEY, {}) + lr_ref = ovn_lb.external_ids.get(ovn_const.LB_EXT_IDS_LR_REF_KEY, {}) if lr_ref: for lr in self.ovn_nbdb_api.tables[ 'Logical_Router'].rows.values(): @@ -1396,7 +1412,7 @@ class OvnProviderHelper(object): # Remove Pool from Listener if it is associated listener_id = None for key, value in ovn_lb.external_ids.items(): - if (key.startswith(LB_EXT_IDS_LISTENER_PREFIX) and + if (key.startswith(ovn_const.LB_EXT_IDS_LISTENER_PREFIX) and pool_key in value): external_ids[key] = value.split(':')[0] + ':' commands.append( @@ -1517,12 +1533,19 @@ class OvnProviderHelper(object): def _add_member(self, member, ovn_lb, pool_key): external_ids = copy.deepcopy(ovn_lb.external_ids) existing_members = external_ids[pool_key] + if existing_members: + existing_members = existing_members.split(",") member_info = self._get_member_key(member) - if member_info in existing_members: + # TODO(mjozefcz): Remove this workaround in W release. + member_info_old = self._get_member_key(member, old_convention=True) + member_found = [x for x in existing_members + if re.match(member_info_old, x)] + if member_found: # Member already present return if existing_members: - pool_data = {pool_key: existing_members + "," + member_info} + existing_members.append(member_info) + pool_data = {pool_key: ",".join(existing_members)} else: pool_data = {pool_key: member_info} @@ -1578,10 +1601,18 @@ class OvnProviderHelper(object): def _remove_member(self, member, ovn_lb, pool_key): external_ids = copy.deepcopy(ovn_lb.external_ids) existing_members = external_ids[pool_key].split(",") - member_info = self._get_member_key(member) - if member_info in existing_members: + # TODO(mjozefcz): Delete this workaround in W release. + # To support backward compatibility member + # could be defined as `member`_`id`_`ip`:`port`_`subnet_id` + # or defined as `member`_`id`_`ip`:`port + member_info_old = self._get_member_key(member, old_convention=True) + + member_found = [x for x in existing_members + if re.match(member_info_old, x)] + if member_found: commands = [] - existing_members.remove(member_info) + existing_members.remove(member_found[0]) + if not existing_members: pool_status = constants.OFFLINE else: @@ -1589,17 +1620,14 @@ class OvnProviderHelper(object): pool_data = {pool_key: ",".join(existing_members)} commands.append( self.ovn_nbdb_api.db_set('Load_Balancer', ovn_lb.uuid, - ('external_ids', pool_data)) - ) + ('external_ids', pool_data))) external_ids[pool_key] = ",".join(existing_members) commands.extend( - self._refresh_lb_vips(ovn_lb.uuid, external_ids) - ) - + self._refresh_lb_vips(ovn_lb.uuid, external_ids)) commands.extend( self._update_lb_to_ls_association( - ovn_lb, subnet_id=member['subnet_id'], associate=False) - ) + ovn_lb, subnet_id=member.get('subnet_id'), + associate=False)) self._execute_commands(commands) return pool_status else: @@ -1725,22 +1753,6 @@ class OvnProviderHelper(object): if mem_addr_port == meminf.split('_')[2]: return meminf.split('_')[1] - def get_member_info(self, pool_id): - '''Gets Member information - - :param pool_id: ID of the Pool whose member information is reqd. - :param mem_addr_port: Combination of Member Address+Port. Default=None - :returns: List -- List of Member Address+Pool of all members in pool. - :returns:[None] -- if no member exists in the pool. - :raises: Exception if Loadbalancer is not found for a Pool ID - ''' - existing_members = self._get_existing_pool_members(pool_id) - # Members are saved in OVN in the form of - # member1_UUID_IP:Port, member2_UUID_IP:Port - # Return the list of (UUID,IP:Port) for all members. - return [(meminf.split('_')[1], meminf.split( - '_')[2]) for meminf in existing_members.split(',')] - def create_vip_port(self, project_id, lb_id, vip_d): port = {'port': {'name': ovn_const.LB_VIP_PORT_PREFIX + str(lb_id), 'network_id': vip_d['vip_network_id'], @@ -1789,18 +1801,20 @@ class OvnProviderHelper(object): commands = [] if fip_info['action'] == REQ_INFO_ACTION_ASSOCIATE: - external_ids[LB_EXT_IDS_VIP_FIP_KEY] = fip_info['vip_fip'] - vip_fip_info = {LB_EXT_IDS_VIP_FIP_KEY: fip_info['vip_fip']} + external_ids[ovn_const.LB_EXT_IDS_VIP_FIP_KEY] = ( + fip_info['vip_fip']) + vip_fip_info = { + ovn_const.LB_EXT_IDS_VIP_FIP_KEY: fip_info['vip_fip']} commands.append( self.ovn_nbdb_api.db_set('Load_Balancer', ovn_lb.uuid, ('external_ids', vip_fip_info)) ) else: - external_ids.pop(LB_EXT_IDS_VIP_FIP_KEY) + external_ids.pop(ovn_const.LB_EXT_IDS_VIP_FIP_KEY) commands.append( self.ovn_nbdb_api.db_remove( 'Load_Balancer', ovn_lb.uuid, 'external_ids', - (LB_EXT_IDS_VIP_FIP_KEY)) + (ovn_const.LB_EXT_IDS_VIP_FIP_KEY)) ) commands.extend( @@ -1808,6 +1822,89 @@ class OvnProviderHelper(object): ) self._execute_commands(commands) + def handle_member_dvr(self, info): + pool_key, ovn_lb = self._find_ovn_lb_by_pool_id(info['pool_id']) + if not ovn_lb.external_ids.get(ovn_const.LB_EXT_IDS_VIP_FIP_KEY): + LOG.debug("LB %(lb)s has no FIP on VIP configured. " + "There is no need to centralize member %(member)s " + "traffic.", + {'lb': ovn_lb.uuid, 'member': info['id']}) + return + + # Find out if member has FIP assigned. + network_driver = get_network_driver() + try: + subnet = network_driver.get_subnet(info['subnet_id']) + ls_name = utils.ovn_name(subnet.network_id) + except n_exc.NotFound: + LOG.exception('Subnet %s not found while trying to ' + 'fetch its data.', info['subnet_id']) + return + + try: + ls = self.ovn_nbdb_api.lookup('Logical_Switch', ls_name) + except idlutils.RowNotFound: + LOG.warning("Logical Switch %s not found. " + "Cannot verify member FIP configuration.", + ls_name) + return + + fip = None + f = utils.remove_macs_from_lsp_addresses + for port in ls.ports: + if info['address'] in f(port.addresses): + # We found particular port + fip = self.ovn_nbdb_api.db_find_rows( + 'NAT', ('external_ids', '=', { + ovn_const.OVN_FIP_PORT_EXT_ID_KEY: port.name}) + ).execute(check_error=True) + fip = fip[0] if fip else fip + break + + if not fip: + LOG.debug('Member %s has no FIP assigned. ' + 'There is no need to modify its NAT.', + info['id']) + return + + if info['action'] == REQ_INFO_MEMBER_ADDED: + LOG.info('Member %(member)s is added to Load Balancer %(lb)s ' + 'and both have FIP assigned. Member FIP %(fip)s ' + 'needs to be centralized in those conditions. ' + 'Deleting external_mac/logical_port from it.', + {'member': info['id'], + 'lb': ovn_lb.uuid, + 'fip': fip.external_ip}) + self.ovn_nbdb_api.db_clear( + 'NAT', fip.uuid, 'external_mac').execute(check_error=True) + self.ovn_nbdb_api.db_clear( + 'NAT', fip.uuid, 'logical_port').execute(check_error=True) + else: + LOG.info('Member %(member)s is deleted from Load Balancer ' + '%(lb)s and both have FIP assigned. Member FIP %(fip)s ' + 'can be decentralized now if environment has DVR ' + 'enabled. Updating FIP object for recomputation.', + {'member': info['id'], + 'lb': ovn_lb.uuid, + 'fip': fip.external_ip}) + # NOTE(mjozefcz): We don't know if this env is DVR or not. + # We should call neutron API to do 'empty' update of the FIP. + # It will bump revision number and do recomputation of the FIP. + try: + fip_info = network_driver.neutron_client.show_floatingip( + fip.external_ids[ovn_const.OVN_FIP_EXT_ID_KEY]) + empty_update = { + "floatingip": { + 'description': fip_info['floatingip']['description']}} + network_driver.neutron_client.update_floatingip( + fip.external_ids[ovn_const.OVN_FIP_EXT_ID_KEY], + empty_update) + except n_exc.NotFound: + LOG.warning('Members %(member)s FIP %(fip)s not found in ' + 'Neutron. Cannot update it.', + {'member': info['id'], + 'fip': fip.external_ip}) + class OvnProviderDriver(driver_base.ProviderDriver): _ovn_helper = None @@ -1960,7 +2057,7 @@ class OvnProviderDriver(driver_base.ProviderDriver): def _ip_version_differs(self, member): _, ovn_lb = self._ovn_helper._find_ovn_lb_by_pool_id(member.pool_id) - lb_vip = ovn_lb.external_ids[LB_EXT_IDS_VIP_KEY] + lb_vip = ovn_lb.external_ids[ovn_const.LB_EXT_IDS_VIP_KEY] return netaddr.IPNetwork(lb_vip).version != ( netaddr.IPNetwork(member.address).version) @@ -1973,7 +2070,8 @@ class OvnProviderDriver(driver_base.ProviderDriver): if self._ip_version_differs(member): raise IPVersionsMixingNotSupportedError() admin_state_up = member.admin_state_up - if isinstance(member.subnet_id, o_datamodels.UnsetType): + if (isinstance(member.subnet_id, o_datamodels.UnsetType) or + not member.subnet_id): msg = _('Subnet is required for Member creation' ' with OVN Provider Driver') raise driver_exceptions.UnsupportedOptionError( @@ -1992,6 +2090,18 @@ class OvnProviderDriver(driver_base.ProviderDriver): 'info': request_info} self._ovn_helper.add_request(request) + # NOTE(mjozefcz): If LB has FIP on VIP + # and member has FIP we need to centralize + # traffic for member. + request_info = {'id': member.member_id, + 'address': member.address, + 'pool_id': member.pool_id, + 'subnet_id': member.subnet_id, + 'action': REQ_INFO_MEMBER_ADDED} + request = {'type': REQ_TYPE_HANDLE_MEMBER_DVR, + 'info': request_info} + self._ovn_helper.add_request(request) + def member_delete(self, member): request_info = {'id': member.member_id, 'address': member.address, @@ -2001,6 +2111,17 @@ class OvnProviderDriver(driver_base.ProviderDriver): request = {'type': REQ_TYPE_MEMBER_DELETE, 'info': request_info} self._ovn_helper.add_request(request) + # NOTE(mjozefcz): If LB has FIP on VIP + # and member had FIP we can decentralize + # the traffic now. + request_info = {'id': member.member_id, + 'address': member.address, + 'pool_id': member.pool_id, + 'subnet_id': member.subnet_id, + 'action': REQ_INFO_MEMBER_DELETED} + request = {'type': REQ_TYPE_HANDLE_MEMBER_DVR, + 'info': request_info} + self._ovn_helper.add_request(request) def member_update(self, old_member, new_member): if self._check_monitor_options(new_member): @@ -2038,25 +2159,45 @@ class OvnProviderDriver(driver_base.ProviderDriver): raise driver_exceptions.UnsupportedOptionError( user_fault_string=msg, operator_fault_string=msg) - current_members = self._ovn_helper.get_member_info(pool_id) - # current_members gets a list of tuples (ID, IP:Port) for pool members + pool_key, ovn_lb = self._ovn_helper._find_ovn_lb_by_pool_id(pool_id) + external_ids = copy.deepcopy(ovn_lb.external_ids) + existing_members = external_ids[pool_key].split(',') + members_to_delete = copy.copy(existing_members) for member in members: if (self._check_monitor_options(member) or member.address and self._ip_version_differs(member)): skipped_members.append(member.member_id) continue + # NOTE(mjozefcz): We need to have subnet_id information. + if (isinstance(member.subnet_id, o_datamodels.UnsetType) or + not member.subnet_id): + msg = _('Subnet is required for Member creation' + ' with OVN Provider Driver') + raise driver_exceptions.UnsupportedOptionError( + user_fault_string=msg, + operator_fault_string=msg) admin_state_up = member.admin_state_up if isinstance(admin_state_up, o_datamodels.UnsetType): admin_state_up = True - mem_addr_port = str(member.address) + ':' + str( - member.protocol_port) - if (member.member_id, mem_addr_port) not in current_members: + + member_info = self._ovn_helper._get_member_key(member) + # TODO(mjozefcz): Remove this workaround in W release. + member_info_old = self._ovn_helper._get_member_key( + member, old_convention=True) + member_found = [x for x in existing_members + if re.match(member_info_old, x)] + if not member_found: req_type = REQ_TYPE_MEMBER_CREATE else: # If member exists in pool, then Update req_type = REQ_TYPE_MEMBER_UPDATE - current_members.remove((member.member_id, mem_addr_port)) # Remove all updating members so only deleted ones are left + # TODO(mjozefcz): Remove this workaround in W release. + try: + members_to_delete.remove(member_info_old) + except ValueError: + members_to_delete.remove(member_info) + request_info = {'id': member.member_id, 'address': member.address, 'protocol_port': member.protocol_port, @@ -2066,14 +2207,19 @@ class OvnProviderDriver(driver_base.ProviderDriver): request = {'type': req_type, 'info': request_info} request_list.append(request) - for cmember in current_members: - request_info = {'id': cmember[0], - 'address': cmember[1].split(':')[0], - 'protocol_port': cmember[1].split(':')[1], + + for member in members_to_delete: + member_info = member.split('_') + request_info = {'id': member_info[1], + 'address': member_info[2].split(':')[0], + 'protocol_port': member_info[2].split(':')[1], 'pool_id': pool_id} + if len(member_info) == 4: + request_info['subnet_id'] = member_info[3] request = {'type': REQ_TYPE_MEMBER_DELETE, 'info': request_info} request_list.append(request) + for request in request_list: self._ovn_helper.add_request(request) if skipped_members: diff --git a/ovn_octavia_provider/tests/functional/test_driver.py b/ovn_octavia_provider/tests/functional/test_driver.py index e569f762..060f577f 100644 --- a/ovn_octavia_provider/tests/functional/test_driver.py +++ b/ovn_octavia_provider/tests/functional/test_driver.py @@ -180,10 +180,10 @@ class TestOctaviaOvnProviderDriver( lbs = [] for lb in self.nb_api.tables['Load_Balancer'].rows.values(): external_ids = dict(lb.external_ids) - ls_refs = external_ids.get(ovn_driver.LB_EXT_IDS_LS_REFS_KEY) + ls_refs = external_ids.get(ovn_const.LB_EXT_IDS_LS_REFS_KEY) if ls_refs: external_ids[ - ovn_driver.LB_EXT_IDS_LS_REFS_KEY] = jsonutils.loads( + ovn_const.LB_EXT_IDS_LS_REFS_KEY] = jsonutils.loads( ls_refs) lbs.append({'name': lb.name, 'protocol': lb.protocol, 'vips': lb.vips, 'external_ids': external_ids}) @@ -265,12 +265,12 @@ class TestOctaviaOvnProviderDriver( net_id = LR_REF_KEY_HEADER + '%s' % net_id if add_ref: - if net_id not in lb_data[ovn_driver.LB_EXT_IDS_LS_REFS_KEY]: - lb_data[ovn_driver.LB_EXT_IDS_LS_REFS_KEY][net_id] = 1 + if net_id not in lb_data[ovn_const.LB_EXT_IDS_LS_REFS_KEY]: + lb_data[ovn_const.LB_EXT_IDS_LS_REFS_KEY][net_id] = 1 else: - ref_ct = lb_data[ovn_driver.LB_EXT_IDS_LS_REFS_KEY][net_id] + ref_ct = lb_data[ovn_const.LB_EXT_IDS_LS_REFS_KEY][net_id] if ref_ct <= 0: - del lb_data[ovn_driver.LB_EXT_IDS_LS_REFS_KEY][net_id] + del lb_data[ovn_const.LB_EXT_IDS_LS_REFS_KEY][net_id] def _wait_for_status(self, expected_statuses, check_call=True): call_count = len(expected_statuses) @@ -331,7 +331,7 @@ class TestOctaviaOvnProviderDriver( r_id = self._create_router("r1") if create_router else None if r_id: lb_data[ - ovn_driver.LB_EXT_IDS_LR_REF_KEY] = LR_REF_KEY_HEADER + r_id + ovn_const.LB_EXT_IDS_LR_REF_KEY] = LR_REF_KEY_HEADER + r_id net_info = self._create_net(lb_info['vip_network'], lb_info['cidr'], router_id=r_id) lb_data['vip_net_info'] = net_info @@ -339,7 +339,7 @@ class TestOctaviaOvnProviderDriver( vip_network_id=net_info[0], vip_port_id=net_info[3], admin_state_up=admin_state_up) - lb_data[ovn_driver.LB_EXT_IDS_LS_REFS_KEY] = {} + lb_data[ovn_const.LB_EXT_IDS_LS_REFS_KEY] = {} lb_data['listeners'] = [] lb_data['pools'] = [] self._update_ls_refs(lb_data, net_info[0]) @@ -447,7 +447,7 @@ class TestOctaviaOvnProviderDriver( return [] vip_net_info = lb_data['vip_net_info'] - external_ids = {ovn_driver.LB_EXT_IDS_LS_REFS_KEY: {}, + external_ids = {ovn_const.LB_EXT_IDS_LS_REFS_KEY: {}, 'neutron:vip': lb_data['model'].vip_address, 'neutron:vip_port_id': vip_net_info[3], 'enabled': str(lb_data['model'].admin_state_up)} @@ -480,20 +480,20 @@ class TestOctaviaOvnProviderDriver( # For every connected subnet to the LB set the ref # counter. for net_id, ref_ct in lb_data[ - ovn_driver.LB_EXT_IDS_LS_REFS_KEY].items(): + ovn_const.LB_EXT_IDS_LS_REFS_KEY].items(): for lb in expected_lbs: # If given LB hasn't VIP configured from # this network we shouldn't touch it here. if net_id == 'neutron-%s' % lb_data['model'].vip_network_id: lb.get('external_ids')[ - ovn_driver.LB_EXT_IDS_LS_REFS_KEY][net_id] = 1 + ovn_const.LB_EXT_IDS_LS_REFS_KEY][net_id] = 1 # For every connected router set it here. - if lb_data.get(ovn_driver.LB_EXT_IDS_LR_REF_KEY): + if lb_data.get(ovn_const.LB_EXT_IDS_LR_REF_KEY): for lb in expected_lbs: lb.get('external_ids')[ - ovn_driver.LB_EXT_IDS_LR_REF_KEY] = lb_data[ - ovn_driver.LB_EXT_IDS_LR_REF_KEY] + ovn_const.LB_EXT_IDS_LR_REF_KEY] = lb_data[ + ovn_const.LB_EXT_IDS_LR_REF_KEY] pool_info = {} for p in lb_data.get('pools', []): @@ -506,6 +506,7 @@ class TestOctaviaOvnProviderDriver( continue m_info = 'member_' + m.member_id + '_' + m.address m_info += ":" + str(m.protocol_port) + m_info += "_" + str(m.subnet_id) if p_members: p_members += "," + m_info else: @@ -517,7 +518,7 @@ class TestOctaviaOvnProviderDriver( for fixed_ip in port['fixed_ips']: if fixed_ip['subnet_id'] == m.subnet_id: ex = external_ids[ - ovn_driver.LB_EXT_IDS_LS_REFS_KEY] + ovn_const.LB_EXT_IDS_LS_REFS_KEY] act = ex.get( 'neutron-%s' % port['network_id'], 0) ex['neutron-%s' % port['network_id']] = act + 1 @@ -771,6 +772,8 @@ class TestOctaviaOvnProviderDriver( 'loadbalancers': [{'id': pool.loadbalancer_id, 'provisioning_status': 'ACTIVE'}], 'listeners': []}) + # Delete member from lb_data + pool.members.remove(m) self._wait_for_status_and_validate(lb_data, expected_status, check_call=False) @@ -1133,7 +1136,7 @@ class TestOctaviaOvnProviderDriver( lba_data = self._create_load_balancer_and_validate( {'vip_network': 'N1', 'cidr': '10.0.0.0/24'}) - router_id = lba_data[ovn_driver.LB_EXT_IDS_LR_REF_KEY][ + router_id = lba_data[ovn_const.LB_EXT_IDS_LR_REF_KEY][ len(LR_REF_KEY_HEADER):] # Create Network N2, connect it to R1 nw_info = self._create_net("N2", "10.0.1.0/24", router_id) @@ -1152,14 +1155,14 @@ class TestOctaviaOvnProviderDriver( # Add N3 to R1 self.l3_plugin.add_router_interface( self.context, lba_data[ - ovn_driver.LB_EXT_IDS_LR_REF_KEY][len(LR_REF_KEY_HEADER):], + ovn_const.LB_EXT_IDS_LR_REF_KEY][len(LR_REF_KEY_HEADER):], {'subnet_id': lbb_data['vip_net_info'][1]}) # Check LBB exists on R1 n_utils.wait_until_true( lambda: self._is_lb_associated_to_lr( lbb_data['model'].loadbalancer_id, - lba_data[ovn_driver.LB_EXT_IDS_LR_REF_KEY]), + lba_data[ovn_const.LB_EXT_IDS_LR_REF_KEY]), timeout=10) # Check LBA connected to N3 n_utils.wait_until_true( @@ -1185,7 +1188,7 @@ class TestOctaviaOvnProviderDriver( # N3 removed from R1 self.l3_plugin.remove_router_interface( self.context, lba_data[ - ovn_driver.LB_EXT_IDS_LR_REF_KEY][len(LR_REF_KEY_HEADER):], + ovn_const.LB_EXT_IDS_LR_REF_KEY][len(LR_REF_KEY_HEADER):], {'subnet_id': lbb_data['vip_net_info'][1]}) else: # Delete LBB Cascade @@ -1195,7 +1198,7 @@ class TestOctaviaOvnProviderDriver( # Check LBB doesn't exists on R1 n_utils.wait_until_true( lambda: not self._is_lb_associated_to_lr( - lbb_id, lba_data[ovn_driver.LB_EXT_IDS_LR_REF_KEY]), + lbb_id, lba_data[ovn_const.LB_EXT_IDS_LR_REF_KEY]), timeout=10) # Check LBB not connected to N1 n_utils.wait_until_true( @@ -1219,7 +1222,7 @@ class TestOctaviaOvnProviderDriver( lba_data = self._create_load_balancer_and_validate( {'vip_network': 'N1', 'cidr': '10.0.0.0/24'}) - router_id = lba_data[ovn_driver.LB_EXT_IDS_LR_REF_KEY][ + router_id = lba_data[ovn_const.LB_EXT_IDS_LR_REF_KEY][ len(LR_REF_KEY_HEADER):] # Create provider network N2, connect it to R1 diff --git a/ovn_octavia_provider/tests/unit/fakes.py b/ovn_octavia_provider/tests/unit/fakes.py index a7e210c0..5841aa1a 100644 --- a/ovn_octavia_provider/tests/unit/fakes.py +++ b/ovn_octavia_provider/tests/unit/fakes.py @@ -17,6 +17,9 @@ import copy import mock from oslo_utils import uuidutils +from ovn_octavia_provider.common import constants +from ovn_octavia_provider.common import utils + class FakeResource(dict): @@ -201,6 +204,25 @@ class FakeOVNPort(object): port_attrs.update(attrs) return type('Logical_Switch_Port', (object, ), port_attrs) + @staticmethod + def from_neutron_port(port): + """Create a fake ovn port based on a neutron port.""" + external_ids = { + constants.OVN_NETWORK_NAME_EXT_ID_KEY: + utils.ovn_name(port['network_id']), + constants.OVN_SG_IDS_EXT_ID_KEY: + ' '.join(port['security_groups']), + constants.OVN_DEVICE_OWNER_EXT_ID_KEY: + port.get('device_owner', '')} + addresses = [port['mac_address'], ] + addresses += [x['ip_address'] for x in port.get('fixed_ips', [])] + port_security = ( + addresses + [x['ip_address'] for x in + port.get('allowed_address_pairs', [])]) + return FakeOVNPort.create_one_port( + {'external_ids': external_ids, 'addresses': addresses, + 'port_security': port_security}) + class FakeOVNRouter(object): @@ -220,3 +242,51 @@ class FakeOVNRouter(object): # Overwrite default attributes. router_attrs.update(attrs) return type('Logical_Router', (object, ), router_attrs) + + +class FakePort(object): + """Fake one or more ports.""" + + @staticmethod + def create_one_port(attrs=None): + """Create a fake port. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object faking the port + """ + attrs = attrs or {} + + # Set default attributes. + fake_uuid = uuidutils.generate_uuid() + port_attrs = { + 'admin_state_up': True, + 'allowed_address_pairs': [{}], + 'binding:host_id': 'binding-host-id-' + fake_uuid, + 'binding:profile': {}, + 'binding:vif_details': {}, + 'binding:vif_type': 'ovs', + 'binding:vnic_type': 'normal', + 'device_id': 'device-id-' + fake_uuid, + 'device_owner': 'compute:nova', + 'dns_assignment': [{}], + 'dns_name': 'dns-name-' + fake_uuid, + 'extra_dhcp_opts': [{}], + 'fixed_ips': [{'subnet_id': 'subnet-id-' + fake_uuid, + 'ip_address': '10.10.10.20'}], + 'id': 'port-id-' + fake_uuid, + 'mac_address': 'fa:16:3e:a9:4e:72', + 'name': 'port-name-' + fake_uuid, + 'network_id': 'network-id-' + fake_uuid, + 'port_security_enabled': True, + 'security_groups': [], + 'status': 'ACTIVE', + 'tenant_id': 'project-id-' + fake_uuid, + } + + # Overwrite default attributes. + port_attrs.update(attrs) + + return FakeResource(info=copy.deepcopy(port_attrs), + loaded=True) diff --git a/ovn_octavia_provider/tests/unit/test_driver.py b/ovn_octavia_provider/tests/unit/test_driver.py index 26f4ccef..731488dd 100644 --- a/ovn_octavia_provider/tests/unit/test_driver.py +++ b/ovn_octavia_provider/tests/unit/test_driver.py @@ -134,10 +134,16 @@ class TestOvnProviderDriver(TestOvnOctaviaBase): self.driver = ovn_driver.OvnProviderDriver() add_req_thread = mock.patch.object(ovn_driver.OvnProviderHelper, 'add_request') + self.member_line = ( + 'member_%s_%s:%s_%s' % + (self.member_id, self.member_address, + self.member_port, self.member_subnet_id)) self.ovn_lb = mock.MagicMock() self.ovn_lb.name = 'foo_ovn_lb' self.ovn_lb.external_ids = { - ovn_driver.LB_EXT_IDS_VIP_KEY: '10.22.33.4'} + ovn_const.LB_EXT_IDS_VIP_KEY: '10.22.33.4', + 'pool_%s' % self.pool_id: self.member_line, + 'listener_%s' % self.listener_id: '80:pool_%s' % self.pool_id} self.mock_add_request = add_req_thread.start() self.project_id = uuidutils.generate_uuid() @@ -268,11 +274,6 @@ class TestOvnProviderDriver(TestOvnOctaviaBase): ovn_driver.OvnProviderHelper, '_find_ovn_lbs', side_effect=lambda x, protocol=None: self.ovn_lb if protocol else [self.ovn_lb]).start() - mock.patch.object( - ovn_driver.OvnProviderHelper, 'get_member_info', - return_value=[ - (self.ref_member.member_id, "198.52.100.4:99"), - (self.fail_member.member_id, "198.51.100.4:99")]).start() self.mock_find_lb_pool_key = mock.patch.object( ovn_driver.OvnProviderHelper, '_find_ovn_lb_with_pool_key', @@ -299,8 +300,20 @@ class TestOvnProviderDriver(TestOvnOctaviaBase): 'admin_state_up': self.ref_member.admin_state_up} expected_dict = {'type': ovn_driver.REQ_TYPE_MEMBER_CREATE, 'info': info} + info_dvr = { + 'id': self.ref_member.member_id, + 'address': self.ref_member.address, + 'pool_id': self.ref_member.pool_id, + 'subnet_id': self.ref_member.subnet_id, + 'action': ovn_driver.REQ_INFO_MEMBER_ADDED} + expected_dict_dvr = { + 'type': ovn_driver.REQ_TYPE_HANDLE_MEMBER_DVR, + 'info': info_dvr} self.driver.member_create(self.ref_member) - self.mock_add_request.assert_called_once_with(expected_dict) + expected = [ + mock.call(expected_dict), + mock.call(expected_dict_dvr)] + self.mock_add_request.assert_has_calls(expected) def test_member_create_failure(self): self.assertRaises(exceptions.UnsupportedOptionError, @@ -325,6 +338,9 @@ class TestOvnProviderDriver(TestOvnOctaviaBase): self.ref_member.subnet_id = data_models.UnsetType() self.assertRaises(exceptions.UnsupportedOptionError, self.driver.member_create, self.ref_member) + self.ref_member.subnet_id = None + self.assertRaises(exceptions.UnsupportedOptionError, + self.driver.member_create, self.ref_member) def test_member_create_monitor_opts(self): self.ref_member.monitor_address = '172.20.20.1' @@ -344,8 +360,13 @@ class TestOvnProviderDriver(TestOvnOctaviaBase): 'admin_state_up': True} expected_dict = {'type': ovn_driver.REQ_TYPE_MEMBER_CREATE, 'info': info} + expected_dict_dvr = {'type': ovn_driver.REQ_TYPE_HANDLE_MEMBER_DVR, + 'info': mock.ANY} + expected = [ + mock.call(expected_dict), + mock.call(expected_dict_dvr)] self.driver.member_create(self.ref_member) - self.mock_add_request.assert_called_once_with(expected_dict) + self.mock_add_request.assert_has_calls(expected) def test_member_update(self): info = {'id': self.update_member.member_id, @@ -395,6 +416,11 @@ class TestOvnProviderDriver(TestOvnOctaviaBase): self.driver.member_batch_update([self.ref_member]) self.assertEqual(self.mock_add_request.call_count, 2) + def test_member_batch_update_missing_subnet_id(self): + self.ref_member.subnet_id = None + self.assertRaises(exceptions.UnsupportedOptionError, + self.driver.member_batch_update, [self.ref_member]) + def test_member_update_failure(self): self.assertRaises(exceptions.UnsupportedOptionError, self.driver.member_update, self.ref_member, @@ -414,8 +440,20 @@ class TestOvnProviderDriver(TestOvnOctaviaBase): 'subnet_id': self.ref_member.subnet_id} expected_dict = {'type': ovn_driver.REQ_TYPE_MEMBER_DELETE, 'info': info} + info_dvr = { + 'id': self.ref_member.member_id, + 'address': self.ref_member.address, + 'pool_id': self.ref_member.pool_id, + 'subnet_id': self.ref_member.subnet_id, + 'action': ovn_driver.REQ_INFO_MEMBER_DELETED} + expected_dict_dvr = { + 'type': ovn_driver.REQ_TYPE_HANDLE_MEMBER_DVR, + 'info': info_dvr} self.driver.member_delete(self.ref_member) - self.mock_add_request.assert_called_once_with(expected_dict) + expected = [ + mock.call(expected_dict), + mock.call(expected_dict_dvr)] + self.mock_add_request.assert_has_calls(expected) def test_listener_create(self): info = {'id': self.ref_listener.listener_id, @@ -589,7 +627,11 @@ class TestOvnProviderDriver(TestOvnOctaviaBase): expected_members = { 'type': ovn_driver.REQ_TYPE_MEMBER_DELETE, 'info': info_member} + expected_members_dvr = { + 'type': ovn_driver.REQ_TYPE_HANDLE_MEMBER_DVR, + 'info': mock.ANY} calls = [mock.call(expected_members), + mock.call(expected_members_dvr), mock.call(expected)] self.driver.pool_delete(self.ref_pool) self.mock_add_request.assert_has_calls(calls) @@ -671,12 +713,13 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): self.ovn_lb.protocol = ['tcp'] self.ovn_lb.uuid = uuidutils.generate_uuid() self.member_line = ( - 'member_%s_%s:%s' % - (self.member_id, self.member_address, self.member_port)) + 'member_%s_%s:%s_%s' % + (self.member_id, self.member_address, + self.member_port, self.member_subnet_id)) self.ovn_lb.external_ids = { - ovn_driver.LB_EXT_IDS_VIP_KEY: '10.22.33.4', - ovn_driver.LB_EXT_IDS_VIP_FIP_KEY: '123.123.123.123', - ovn_driver.LB_EXT_IDS_VIP_PORT_ID_KEY: 'foo_port', + ovn_const.LB_EXT_IDS_VIP_KEY: '10.22.33.4', + ovn_const.LB_EXT_IDS_VIP_FIP_KEY: '123.123.123.123', + ovn_const.LB_EXT_IDS_VIP_PORT_ID_KEY: 'foo_port', 'enabled': True, 'pool_%s' % self.pool_id: self.member_line, 'listener_%s' % self.listener_id: '80:pool_%s' % self.pool_id} @@ -684,9 +727,10 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): execute.return_value = [self.ovn_lb] self.helper.ovn_nbdb_api.db_list_rows.return_value.\ execute.return_value = [self.ovn_lb] - mock.patch.object(self.helper, - '_find_ovn_lb_with_pool_key', - return_value=self.ovn_lb).start() + self.mock_find_lb_pool_key = mock.patch.object( + self.helper, + '_find_ovn_lb_with_pool_key', + return_value=self.ovn_lb).start() self.mock_find_ovn_lbs = mock.patch.object( ovn_driver.OvnProviderHelper, '_find_ovn_lbs', @@ -721,8 +765,8 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): vip_address=self.vip_address, vip_network_id=self.vip_network_id, ext_ids={ - ovn_driver.LB_EXT_IDS_LR_REF_KEY: "neutron-%s" % net_id, - ovn_driver.LB_EXT_IDS_LS_REFS_KEY: + ovn_const.LB_EXT_IDS_LR_REF_KEY: "neutron-%s" % net_id, + ovn_const.LB_EXT_IDS_LS_REFS_KEY: '{\"neutron-%s\": 1}' % net_id}) self.ref_lb2 = MockedLB( uuid=uuidutils.generate_uuid(), @@ -734,8 +778,8 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): vip_address=self.vip_address, vip_network_id=self.vip_network_id, ext_ids={ - ovn_driver.LB_EXT_IDS_LR_REF_KEY: "neutron-%s" % net_id, - ovn_driver.LB_EXT_IDS_LS_REFS_KEY: + ovn_const.LB_EXT_IDS_LR_REF_KEY: "neutron-%s" % net_id, + ovn_const.LB_EXT_IDS_LS_REFS_KEY: '{\"neutron-%s\": 1}' % net_id}) # TODO(mjozefcz): Consider using FakeOVNRouter. self.router = fakes.FakeOvsdbRow.create_one_ovsdb_row( @@ -845,20 +889,20 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): 'id': self.ovn_lb.name, 'protocol': 'tcp', 'vip_address': udp_lb.external_ids.get( - ovn_driver.LB_EXT_IDS_VIP_KEY), + ovn_const.LB_EXT_IDS_VIP_KEY), 'vip_port_id': udp_lb.external_ids.get( - ovn_driver.LB_EXT_IDS_VIP_PORT_ID_KEY), - ovn_driver.LB_EXT_IDS_LR_REF_KEY: + ovn_const.LB_EXT_IDS_VIP_PORT_ID_KEY), + ovn_const.LB_EXT_IDS_LR_REF_KEY: udp_lb.external_ids.get( - ovn_driver.LB_EXT_IDS_LR_REF_KEY), - ovn_driver.LB_EXT_IDS_LS_REFS_KEY: + ovn_const.LB_EXT_IDS_LR_REF_KEY), + ovn_const.LB_EXT_IDS_LS_REFS_KEY: udp_lb.external_ids.get( - ovn_driver.LB_EXT_IDS_LS_REFS_KEY), + ovn_const.LB_EXT_IDS_LS_REFS_KEY), 'admin_state_up': 'True', - ovn_driver.LB_EXT_IDS_VIP_FIP_KEY: + ovn_const.LB_EXT_IDS_VIP_FIP_KEY: udp_lb.external_ids.get( - ovn_driver.LB_EXT_IDS_VIP_FIP_KEY)} + ovn_const.LB_EXT_IDS_VIP_FIP_KEY)} lbc.assert_called_once_with(expected_lb_info, protocol='tcp') def test__get_or_create_ovn_lb_found(self): @@ -896,8 +940,8 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): constants.OFFLINE) self.helper.ovn_nbdb_api.db_create.assert_called_once_with( 'Load_Balancer', external_ids={ - ovn_driver.LB_EXT_IDS_VIP_KEY: mock.ANY, - ovn_driver.LB_EXT_IDS_VIP_PORT_ID_KEY: mock.ANY, + ovn_const.LB_EXT_IDS_VIP_KEY: mock.ANY, + ovn_const.LB_EXT_IDS_VIP_PORT_ID_KEY: mock.ANY, 'enabled': 'False'}, name=mock.ANY, protocol=None) @@ -914,8 +958,8 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): constants.ONLINE) self.helper.ovn_nbdb_api.db_create.assert_called_once_with( 'Load_Balancer', external_ids={ - ovn_driver.LB_EXT_IDS_VIP_KEY: mock.ANY, - ovn_driver.LB_EXT_IDS_VIP_PORT_ID_KEY: mock.ANY, + ovn_const.LB_EXT_IDS_VIP_KEY: mock.ANY, + ovn_const.LB_EXT_IDS_VIP_PORT_ID_KEY: mock.ANY, 'enabled': 'True'}, name=mock.ANY, protocol=None) @@ -929,8 +973,8 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): """ self.lb['admin_state_up'] = True self.lb['protocol'] = 'UDP' - self.lb[ovn_driver.LB_EXT_IDS_LR_REF_KEY] = 'foo' - self.lb[ovn_driver.LB_EXT_IDS_LS_REFS_KEY] = "{\"neutron-foo\": 1}" + self.lb[ovn_const.LB_EXT_IDS_LR_REF_KEY] = 'foo' + self.lb[ovn_const.LB_EXT_IDS_LS_REFS_KEY] = "{\"neutron-foo\": 1}" net_dr.return_value.neutron_client.list_ports.return_value = ( self.ports) status = self.helper.lb_create(self.lb, protocol='UDP') @@ -940,9 +984,9 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): constants.ONLINE) self.helper.ovn_nbdb_api.db_create.assert_called_once_with( 'Load_Balancer', external_ids={ - ovn_driver.LB_EXT_IDS_VIP_KEY: mock.ANY, - ovn_driver.LB_EXT_IDS_VIP_PORT_ID_KEY: mock.ANY, - ovn_driver.LB_EXT_IDS_LR_REF_KEY: 'foo', + ovn_const.LB_EXT_IDS_VIP_KEY: mock.ANY, + ovn_const.LB_EXT_IDS_VIP_PORT_ID_KEY: mock.ANY, + ovn_const.LB_EXT_IDS_LR_REF_KEY: 'foo', 'enabled': 'True'}, name=mock.ANY, protocol='udp') @@ -1036,8 +1080,8 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): @mock.patch('ovn_octavia_provider.driver.get_network_driver') def test_lb_delete_ls_lr(self, net_dr): self.ovn_lb.external_ids.update({ - ovn_driver.LB_EXT_IDS_LR_REF_KEY: self.router.name, - ovn_driver.LB_EXT_IDS_LS_REFS_KEY: + ovn_const.LB_EXT_IDS_LR_REF_KEY: self.router.name, + ovn_const.LB_EXT_IDS_LS_REFS_KEY: '{\"neutron-%s\": 1}' % self.network.uuid}) net_dr.return_value.neutron_client.delete_port.return_value = None (self.helper.ovn_nbdb_api.ls_get.return_value.execute. @@ -1501,9 +1545,9 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): mock.call( 'Load_Balancer', self.ovn_lb.uuid, ('external_ids', { - ovn_driver.LB_EXT_IDS_VIP_KEY: '10.22.33.4', - ovn_driver.LB_EXT_IDS_VIP_FIP_KEY: '123.123.123.123', - ovn_driver.LB_EXT_IDS_VIP_PORT_ID_KEY: 'foo_port', + ovn_const.LB_EXT_IDS_VIP_KEY: '10.22.33.4', + ovn_const.LB_EXT_IDS_VIP_FIP_KEY: '123.123.123.123', + ovn_const.LB_EXT_IDS_VIP_PORT_ID_KEY: 'foo_port', 'enabled': True, 'listener_%s' % self.listener_id: '80:'}))] self.assertEqual(self.helper.ovn_nbdb_api.db_set.call_count, @@ -1539,9 +1583,9 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): ('external_ids', { 'enabled': True, 'listener_%s' % self.listener_id: '80:', - ovn_driver.LB_EXT_IDS_VIP_KEY: '10.22.33.4', - ovn_driver.LB_EXT_IDS_VIP_FIP_KEY: '123.123.123.123', - ovn_driver.LB_EXT_IDS_VIP_PORT_ID_KEY: 'foo_port'})) + ovn_const.LB_EXT_IDS_VIP_KEY: '10.22.33.4', + ovn_const.LB_EXT_IDS_VIP_FIP_KEY: '123.123.123.123', + ovn_const.LB_EXT_IDS_VIP_PORT_ID_KEY: 'foo_port'})) def test_pool_delete_pool_disabled(self): disabled_p_key = self.helper._get_pool_key(self.pool_id, @@ -1640,6 +1684,15 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): self.helper.member_create(self.member) self.helper.ovn_nbdb_api.db_set.assert_not_called() + def test_member_create_already_exists_backward_compat(self): + old_member_line = ('member_%s_%s:%s' % + (self.member_id, self.member_address, + self.member_port)) + self.ovn_lb.external_ids.update( + {'pool_%s' % self.pool_id: old_member_line}) + self.helper.member_create(self.member) + self.helper.ovn_nbdb_api.db_set.assert_not_called() + def test_member_create_first_member_in_pool(self): self.ovn_lb.external_ids.update({ 'pool_' + self.pool_id: ''}) @@ -1657,17 +1710,20 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): def test_member_create_second_member_in_pool(self): member2_id = uuidutils.generate_uuid() + member2_subnet_id = uuidutils.generate_uuid() member2_port = "1010" member2_address = "192.168.2.150" - member2_line = ('member_%s_%s:%s' % - (member2_id, member2_address, member2_port)) + member2_line = ('member_%s_%s:%s_%s' % + (member2_id, member2_address, + member2_port, member2_subnet_id)) self.ovn_lb.external_ids.update( {'pool_%s' % self.pool_id: member2_line}) self.helper.member_create(self.member) all_member_line = ( - '%s,member_%s_%s:%s' % + '%s,member_%s_%s:%s_%s' % (member2_line, self.member_id, - self.member_address, self.member_port)) + self.member_address, self.member_port, + self.member_subnet_id)) # We have two members now. expected_calls = [ mock.call('Load_Balancer', self.ovn_lb.uuid, @@ -1729,16 +1785,37 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): self.assertEqual(status['pools'][0]['provisioning_status'], constants.ACTIVE) + def test_member_update_new_member_line(self): + old_member_line = ( + 'member_%s_%s:%s' % + (self.member_id, self.member_address, + self.member_port)) + new_member_line = ( + 'member_%s_%s:%s_%s' % + (self.member_id, self.member_address, + self.member_port, self.member_subnet_id)) + self.ovn_lb.external_ids.update( + {'pool_%s' % self.pool_id: old_member_line}) + self.helper.member_update(self.member) + expected_calls = [ + mock.call('Load_Balancer', self.ovn_lb.uuid, + ('external_ids', { + 'pool_%s' % self.pool_id: new_member_line}))] + self.helper.ovn_nbdb_api.db_set.assert_has_calls( + expected_calls) + def test_member_update_new_port(self): new_port = 11 - member_line = ('member_%s_%s:%s' % - (self.member_id, self.member_address, new_port)) + member_line = ('member_%s_%s:%s_%s' % + (self.member_id, self.member_address, + new_port, self.member_subnet_id)) self.ovn_lb.external_ids.update( {'pool_%s' % self.pool_id: member_line}) self.helper.member_update(self.member) new_member_line = ( - 'member_%s_%s:%s' % - (self.member_id, self.member_address, self.member_port)) + 'member_%s_%s:%s_%s' % + (self.member_id, self.member_address, + self.member_port, self.member_subnet_id)) expected_calls = [ mock.call('Load_Balancer', self.ovn_lb.uuid, ('external_ids', { @@ -1776,6 +1853,20 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): self.assertEqual(status['pools'][0]['provisioning_status'], constants.ACTIVE) + def test_member_delete_backward_compat(self): + old_member_line = ('member_%s_%s:%s' % + (self.member_id, self.member_address, + self.member_port)) + self.ovn_lb.external_ids.update( + {'pool_%s' % self.pool_id: old_member_line}) + self.helper.member_delete(self.member) + expected_calls = [ + mock.call('Load_Balancer', self.ovn_lb.uuid, + ('external_ids', {'pool_%s' % self.pool_id: ''})), + mock.call('Load_Balancer', self.ovn_lb.uuid, + ('vips', {}))] + self.helper.ovn_nbdb_api.db_set.has_calls(expected_calls) + @mock.patch.object(ovn_driver.OvnProviderHelper, '_remove_member') def test_member_delete_exception(self, mock_remove_member): mock_remove_member.side_effect = [RuntimeError] @@ -2012,7 +2103,7 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): def test__find_lb_in_ls_wrong_ref(self): # lets break external_ids refs self.network.load_balancer[0].external_ids.update({ - ovn_driver.LB_EXT_IDS_LS_REFS_KEY: 'foo'}) + ovn_const.LB_EXT_IDS_LS_REFS_KEY: 'foo'}) net_lb = self.helper._find_lb_in_ls(self.network) for lb in self.network.load_balancer: self.assertNotIn(lb, net_lb) @@ -2072,7 +2163,7 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): self._update_lb_to_lr_association.stop() self.helper._update_lb_to_lr_association(self.ref_lb1, self.router) lr_ref = self.ref_lb1.external_ids.get( - ovn_driver.LB_EXT_IDS_LR_REF_KEY) + ovn_const.LB_EXT_IDS_LR_REF_KEY) add.assert_called_once_with(self.ref_lb1, self.router, lr_ref) delete.assert_not_called() @@ -2085,14 +2176,14 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): self.helper._update_lb_to_lr_association( self.ref_lb1, self.router, delete=True) lr_ref = self.ref_lb1.external_ids.get( - ovn_driver.LB_EXT_IDS_LR_REF_KEY) + ovn_const.LB_EXT_IDS_LR_REF_KEY) add.assert_not_called() delete.assert_called_once_with(self.ref_lb1, self.router, lr_ref) @mock.patch('ovn_octavia_provider.driver.get_network_driver') def test__del_lb_to_lr_association(self, net_dr): lr_ref = self.ref_lb1.external_ids.get( - ovn_driver.LB_EXT_IDS_LR_REF_KEY) + ovn_const.LB_EXT_IDS_LR_REF_KEY) upd_lr_ref = '%s,%s' % (lr_ref, self.router.name) self.helper._del_lb_to_lr_association( self.ref_lb1, self.router, upd_lr_ref) @@ -2100,7 +2191,7 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): mock.call.db_set( 'Load_Balancer', self.ref_lb1.uuid, (('external_ids', - {ovn_driver.LB_EXT_IDS_LR_REF_KEY: lr_ref}))), + {ovn_const.LB_EXT_IDS_LR_REF_KEY: lr_ref}))), mock.call.lr_lb_del( self.router.uuid, self.ref_lb1.uuid, if_exists=True)] @@ -2124,7 +2215,7 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): self.ref_lb1, self.router, lr_ref) self.helper.ovn_nbdb_api.db_remove.assert_called_once_with( 'Load_Balancer', self.ref_lb1.uuid, 'external_ids', - ovn_driver.LB_EXT_IDS_LR_REF_KEY) + ovn_const.LB_EXT_IDS_LR_REF_KEY) self.helper.ovn_nbdb_api.lr_lb_del.assert_called_once_with( self.router.uuid, self.ref_lb1.uuid, if_exists=True) self.helper.ovn_nbdb_api.db_set.assert_not_called() @@ -2464,6 +2555,110 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): '172.26.21.20:80': '192.168.2.149:1010'}))] self.helper.ovn_nbdb_api.assert_has_calls(calls) + @mock.patch('ovn_octavia_provider.driver.get_network_driver') + def test_handle_member_dvr_lb_has_no_fip(self, net_dr): + lb = mock.MagicMock() + info = { + 'id': self.member_id, + 'pool_id': self.pool_id, + 'action': ovn_driver.REQ_INFO_MEMBER_ADDED} + external_ids = { + 'neutron:vip_fip': ''} + lb.external_ids = external_ids + self.mock_find_lb_pool_key.return_value = lb + self.helper.handle_member_dvr(info) + net_dr.assert_not_called() + self.helper.ovn_nbdb_api.db_clear.assert_not_called() + + @mock.patch('ovn_octavia_provider.driver.get_network_driver') + def test_handle_member_dvr_lb_fip_no_ls_ports(self, net_dr): + lb = mock.MagicMock() + info = { + 'id': self.member_id, + 'subnet_id': self.member_subnet_id, + 'pool_id': self.pool_id, + 'action': ovn_driver.REQ_INFO_MEMBER_ADDED} + external_ids = { + 'neutron:vip_fip': '11.11.11.11'} + lb.external_ids = external_ids + self.mock_find_lb_pool_key.return_value = lb + fake_ls = fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={ + 'external_ids': {}, + 'ports': {}}) + self.helper.ovn_nbdb_api.lookup.return_value = fake_ls + self.helper.handle_member_dvr(info) + self.helper.ovn_nbdb_api.db_clear.assert_not_called() + + def _test_handle_member_dvr_lb_fip( + self, net_dr, action=ovn_driver.REQ_INFO_MEMBER_ADDED): + lb = mock.MagicMock() + fake_port = fakes.FakePort.create_one_port( + attrs={'allowed_address_pairs': ''}) + info = { + 'id': self.member_id, + 'address': fake_port['fixed_ips'][0]['ip_address'], + 'pool_id': self.pool_id, + 'subnet_id': fake_port['fixed_ips'][0]['subnet_id'], + 'action': action} + member_subnet = fakes.FakeSubnet.create_one_subnet() + member_subnet.id = self.member_subnet_id + member_subnet.network_id = 'foo' + net_dr.return_value.get_subnet.return_value = member_subnet + fake_lsp = fakes.FakeOVNPort.from_neutron_port( + fake_port) + fake_ls = fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={ + 'external_ids': {}, + 'name': 'foo', + 'ports': [fake_lsp]}) + self.helper.ovn_nbdb_api.lookup.return_value = fake_ls + fake_nat = fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={ + 'external_ip': '22.22.22.22', + 'external_ids': { + ovn_const.OVN_FIP_EXT_ID_KEY: 'fip_id'}}) + fip_info = { + 'floatingip': { + 'description': 'bar'}} + net_dr.return_value.neutron_client.show_floatingip.return_value = ( + fip_info) + self.helper.ovn_nbdb_api.db_find_rows.return_value.\ + execute.return_value = [fake_nat] + external_ids = { + ovn_const.LB_EXT_IDS_VIP_FIP_KEY: '11.11.11.11'} + lb.external_ids = external_ids + self.mock_find_lb_pool_key.return_value = lb + self.helper.handle_member_dvr(info) + + if action == ovn_driver.REQ_INFO_MEMBER_ADDED: + calls = [ + mock.call.lookup('Logical_Switch', 'neutron-foo'), + mock.call.db_find_rows('NAT', ('external_ids', '=', { + ovn_const.OVN_FIP_PORT_EXT_ID_KEY: fake_lsp.name})), + mock.ANY, + mock.call.db_clear('NAT', fake_nat.uuid, 'external_mac'), + mock.ANY, + mock.call.db_clear('NAT', fake_nat.uuid, 'logical_port'), + mock.ANY] + self.helper.ovn_nbdb_api.assert_has_calls(calls) + else: + (net_dr.return_value.neutron_client.show_floatingip. + assert_called_once_with('fip_id')) + (net_dr.return_value.neutron_client.update_floatingip. + assert_called_once_with('fip_id', { + 'floatingip': {'description': 'bar'}})) + self.helper.ovn_nbdb_api.db_clear.assert_not_called() + + @mock.patch('ovn_octavia_provider.driver.get_network_driver') + def test_handle_member_dvr_lb_fip_member_added(self, net_dr): + self._test_handle_member_dvr_lb_fip(net_dr) + + @mock.patch('ovn_octavia_provider.driver.get_network_driver') + def test_handle_member_dvr_lb_fip_member_deleted(self, net_dr): + self._test_handle_member_dvr_lb_fip( + net_dr, action=ovn_driver.REQ_INFO_MEMBER_DELETED) + @mock.patch.object(ovn_driver, 'atexit') def test_ovsdb_connections(self, mock_atexit): ovn_driver.OvnProviderHelper.ovn_nbdb_api = None @@ -2569,11 +2764,6 @@ class TestOvnProviderHelper(TestOvnOctaviaBase): 'provisioning_status': 'ERROR', 'operating_status': 'ERROR'}]}) - def test_get_member_info(self): - ret = self.helper.get_member_info(self.pool_id) - self.assertEqual([(self.member_id, '%s:%s' % (self.member_address, - self.member_port))], ret) - def test_get_pool_member_id(self): ret = self.helper.get_pool_member_id( self.pool_id, mem_addr_port='192.168.2.149:1010')