Example #1
0
 def __init__(self, conf, driver):  # XXX maybe we need a better name: conf
     """Create a BigipTenantManager."""
     self.conf = conf
     self.driver = driver
     self.system_helper = SystemHelper()
     self.network_helper = NetworkHelper()
     self.service_adapter = self.driver.service_adapter
Example #2
0
    def __init__(self, driver, f5_global_routed_mode):
        self.conf = driver.conf
        self.driver = driver
        self.f5_global_routed_mode = f5_global_routed_mode
        self.vlan_binding = None
        self.fdb_connector = None
        self.interface_mapping = {}
        self.tagging_mapping = {}
        self.system_helper = SystemHelper()
        self.network_helper = NetworkHelper()
        self.service_adapter = ServiceModelAdapter(self.conf)

        if not f5_global_routed_mode:
            self.fdb_connector = FDBConnectorML2(self.conf)

        if self.conf.vlan_binding_driver:
            try:
                self.vlan_binding = importutils.import_object(
                    self.conf.vlan_binding_driver, self.conf, self)
            except ImportError:
                LOG.error('Failed to import VLAN binding driver: %s'
                          % self.conf.vlan_binding_driver)
                raise

        # map format is  phynet:interface:tagged
        for maps in self.conf.f5_external_physical_mappings:
            intmap = maps.split(':')
            net_key = str(intmap[0]).strip()
            if len(intmap) > 3:
                net_key = net_key + ':' + str(intmap[3]).strip()
            self.interface_mapping[net_key] = str(intmap[1]).strip()
            self.tagging_mapping[net_key] = str(intmap[2]).strip()
            LOG.debug('physical_network %s = interface %s, tagged %s'
                      % (net_key, intmap[1], intmap[2]))
    def test_network_helper_delete_fdb_entries(
            self, l2_service, service, bigips):
        # delete member fdb entry
        loadbalancer = None
        members = list()
        member = service['members'][1]
        network_id = member['network_id']
        member['network'] = service['networks'][network_id]
        members.append(member)

        tunnel_records = l2_service.create_fdb_records(loadbalancer, members)

        network_helper = NetworkHelper()
        tunnel = mock.MagicMock()
        tunnel.exists = mock.MagicMock(return_value=True)

        tunnel_record = mock.MagicMock()
        tunnel.records_s.records.load = mock.MagicMock(
            return_value=tunnel_record)
        tunnel.records_s.records.exist = mock.MagicMock(return_value=True)

        bigip = bigips[0]
        bigip.tm.net.fdb.tunnels.tunnel.load = mock.MagicMock(
            return_value=tunnel)
        network_helper.delete_fdb_entries(bigip, fdb_entries=tunnel_records)

        # expect to delete
        tunnel_record.delete.assert_called()
Example #4
0
 def __init__(self, conf, driver):  # XXX maybe we need a better name: conf
     """Create a BigipTenantManager."""
     self.conf = conf
     self.driver = driver
     self.system_helper = SystemHelper()
     self.network_helper = NetworkHelper()
     self.service_adapter = self.driver.service_adapter
    def __init__(self, conf, f5_global_routed_mode):
        self.conf = conf
        self.f5_global_routed_mode = f5_global_routed_mode
        self.vlan_binding = None
        self.fdb_connector = None
        self.vcmp_manager = None
        self.interface_mapping = {}
        self.tagging_mapping = {}
        self.system_helper = SystemHelper()
        self.network_helper = NetworkHelper()
        self.service_adapter = ServiceModelAdapter(conf)

        if not f5_global_routed_mode:
            self.fdb_connector = FDBConnectorML2(conf)

        if self.conf.vlan_binding_driver:
            try:
                self.vlan_binding = importutils.import_object(
                    self.conf.vlan_binding_driver, self.conf, self)
            except ImportError:
                LOG.error('Failed to import VLAN binding driver: %s'
                          % self.conf.vlan_binding_driver)

        # map format is   phynet:interface:tagged
        for maps in self.conf.f5_external_physical_mappings:
            intmap = maps.split(':')
            net_key = str(intmap[0]).strip()
            if len(intmap) > 3:
                net_key = net_key + ':' + str(intmap[3]).strip()
            self.interface_mapping[net_key] = str(intmap[1]).strip()
            self.tagging_mapping[net_key] = str(intmap[2]).strip()
            LOG.debug('physical_network %s = interface %s, tagged %s'
                      % (net_key, intmap[1], intmap[2]))
class DisconnectedService(object):
    network_name = "disconnected_network"

    def __init__(self):
        self.network_name = DisconnectedService.network_name
        self.network_helper = NetworkHelper()

    # The following method presumes that the plugin driver is aware that we're
    # running in hierarchical mode or not and sets segmentation_id correctly.
    def is_service_connected(self, service):
        networks = service["networks"]
        network_id = service["loadbalancer"]["network_id"]
        network = networks.get(network_id, {})

        if network.get("provider:network_type", "") == "flat":
            return True

        segmentation_id = network.get("provider:segmentation_id", 0)

        return segmentation_id

    def is_virtual_connected(self, virtual, bigips):
        # check if virtual_server is connected on any of our bigips
        connected = True
        for bigip in bigips:
            vsf = bigip.tm.ltm.virtuals.virtual
            if vsf.exists(name=virtual["name"], partition=virtual["partition"]):
                vs = vsf.load(name=virtual["name"], partition=virtual["partition"])
                if getattr(vs, "vlansDisabled", False) or not getattr(vs, "vlansEnabled", True):
                    # accommodate quirk of how big-ip returns virtual server
                    # if vlans are disabled OR vlans are not enabled, then
                    # we're connected
                    continue
                network_path = "/%s/%s" % (virtual["partition"], self.network_name)
                if network_path in getattr(vs, "vlans", []):
                    connected = False
                    break
        return connected

    def network_exists(self, bigip, partition):
        t = bigip.tm.net.tunnels.tunnels.tunnel
        return t.exists(name=self.network_name, partition=partition)

    @log_helpers.log_method_call
    def create_network(self, bigip, partition):
        model = {
            "name": self.network_name,
            "partition": partition,
            "profile": "ppp",
            "description": "Tenant disconnected network",
        }
        t = self.network_helper.create_tunnel(bigip, model)
        return t

    @log_helpers.log_method_call
    def delete_network(self, bigip, partition):
        tf = bigip.tm.net.tunnels.tunnels.tunnel
        if tf.exists(name=self.network_name, partition=partition):
            tf.load(name=self.network_name, partition=partition).delete()
Example #7
0
 def __init__(self, driver, l2_service, l3_binding):
     self.driver = driver
     self.l2_service = l2_service
     self.l3_binding = l3_binding
     self.snatpool_manager = BigIPResourceHelper(ResourceType.snatpool)
     self.snat_translation_manager = BigIPResourceHelper(
         ResourceType.snat_translation)
     self.network_helper = NetworkHelper()
Example #8
0
 def __init__(self, driver, l2_service, l3_binding):
     self.driver = driver
     self.l2_service = l2_service
     self.l3_binding = l3_binding
     self.snatpool_manager = BigIPResourceHelper(ResourceType.snatpool)
     self.snat_translation_manager = BigIPResourceHelper(
         ResourceType.snat_translation)
     self.network_helper = NetworkHelper()
    def purge_folder_contents(self, bigip, folder):
        network_helper = NetworkHelper()

        if folder not in self.exempt_folders:

            # First remove all LTM resources.
            ltm_types = [
                ResourceType.virtual,
                ResourceType.virtual_address,
                ResourceType.pool,
                ResourceType.http_monitor,
                ResourceType.https_monitor,
                ResourceType.tcp_monitor,
                ResourceType.ping_monitor,
                ResourceType.node,
                ResourceType.snat,
                ResourceType.snatpool,
                ResourceType.snat_translation,
                ResourceType.universal_persistence,
                ResourceType.rule,
                ResourceType.l7policy
            ]
            for ltm_type in ltm_types:
                resource = BigIPResourceHelper(ltm_type)
                [r.delete() for r in resource.get_resources(bigip, folder)]

            # Remove all net resources
            net_types = [
                ResourceType.arp,
                ResourceType.selfip,
                ResourceType.vlan,
                ResourceType.route_domain
            ]
            for net_type in net_types:
                resource = BigIPResourceHelper(net_type)
                [r.delete() for r in resource.get_resources(bigip, folder)]

            # Tunnels and fdb's require some special attention.
            resource = BigIPResourceHelper(ResourceType.tunnel)
            tunnels = resource.get_resources(bigip, folder)
            for tunnel in tunnels:
                network_helper.delete_all_fdb_entries(
                    bigip, tunnel.name, folder)
                network_helper.delete_tunnel(
                    bigip, tunnel.name, folder)
Example #10
0
    def __init__(self, f5_global_routed_mode, conf, driver, l3_binding=None):
        self.f5_global_routed_mode = f5_global_routed_mode
        self.conf = conf
        self.driver = driver
        self.l3_binding = l3_binding
        self.l2_service = L2ServiceBuilder(driver, f5_global_routed_mode)

        self.bigip_selfip_manager = BigipSelfIpManager(self.driver,
                                                       self.l2_service,
                                                       self.driver.l3_binding)
        self.bigip_snat_manager = BigipSnatManager(self.driver,
                                                   self.l2_service,
                                                   self.driver.l3_binding)

        self.rds_cache = {}
        self.interface_mapping = self.l2_service.interface_mapping
        self.network_helper = NetworkHelper()
        self.service_adapter = self.driver.service_adapter
Example #11
0
class DisconnectedService(object):
    network_name = 'disconnected_network'

    def __init__(self):
        self.network_name = DisconnectedService.network_name
        self.network_helper = NetworkHelper()

    # The following method presumes that the plugin driver is aware that we're
    # running in hierarchical mode or not and sets segmentation_id correctly.
    def is_service_connected(self, service):
        networks = service['networks']
        network_id = service['loadbalancer']['network_id']
        segmentation_id = networks[network_id]['provider:segmentation_id']
        return (segmentation_id)

    def is_virtual_connected(self, virtual, bigips):
        # check if virtual_server is connected on any of our bigips
        connected = True
        for bigip in bigips:
            vsf = bigip.tm.ltm.virtuals.virtual
            if vsf.exists(name=virtual['name'],
                          partition=virtual['partition']):
                vs = vsf.load(name=virtual['name'],
                              partition=virtual['partition'])
                if (getattr(vs, 'vlansDisabled', False)
                        or not getattr(vs, 'vlansEnabled', True)):
                    # accommodate quirk of how big-ip returns virtual server
                    # if vlans are disabled OR vlans are not enabled, then
                    # we're connected
                    continue
                network_path = "/%s/%s" % (virtual['partition'],
                                           self.network_name)
                if network_path in getattr(vs, 'vlans', []):
                    connected = False
                    break
        return connected

    def network_exists(self, bigip, partition):
        t = bigip.tm.net.tunnels.tunnels.tunnel
        return t.exists(name=self.network_name, partition=partition)

    @log_helpers.log_method_call
    def create_network(self, bigip, partition):
        model = {
            'name': self.network_name,
            'partition': partition,
            'profile': 'ppp',
            'description': 'Tenant disconnected network'
        }
        t = self.network_helper.create_tunnel(bigip, model)
        return t

    @log_helpers.log_method_call
    def delete_network(self, bigip, partition):
        tf = bigip.tm.net.tunnels.tunnels.tunnel
        if tf.exists(name=self.network_name, partition=partition):
            tf.load(name=self.network_name, partition=partition).delete()
Example #12
0
def test_add_remove_fdbs(bigip, icontrol_driver):
    """ Test simulating L2 pop events to add/remove fdb entries."""

    net_helper = NetworkHelper()
    tunnels = list()
    fdb_entries = list()

    seg_id_start = 167
    seg_id_end = 176
    n_records = 9

    # create tunnels on BIG-IP, and fake fdb entries
    for seg_id in range(seg_id_start, seg_id_end):
        tunnel_name = 'tunnel-vxlan-{}'.format(seg_id)
        model = {
            'name': tunnel_name,
            'key': seg_id,
            'profile': 'vxlan_ovs',
            'localAddress': '201.0.155.10'
        }
        net_helper.create_multipoint_tunnel(bigip.bigip, model)
        tunnels.append(tunnel_name)

        # create a set of fdb entries that reference network seg ID
        for _ in range(n_records):
            entry = create_fdb_entry(seg_id)
            fdb_entries.append(entry)

    # add fdb entries
    for fdb_entry in fdb_entries:
        # mimic neutron L2 pop add_fdb_entries
        icontrol_driver.fdb_add(fdb_entry)

    for fdb_entry in fdb_entries:
        # mimic neutron L2 pop add_fdb_entries
        icontrol_driver.fdb_add(fdb_entry)

    # check created
    for tunnel_name in tunnels:
        records = net_helper.get_fdb_entry(bigip.bigip,
                                           tunnel_name=tunnel_name)
        assert records

    # remove fdb entries
    for fdb_entry in fdb_entries:
        # mimic neutron L2 pop remove_fdb_entries
        icontrol_driver.fdb_remove(fdb_entry)

    # check removed
    for tunnel_name in tunnels:
        records = net_helper.get_fdb_entry(bigip.bigip,
                                           tunnel_name=tunnel_name)
        assert not records
        net_helper.delete_tunnel(bigip.bigip, tunnel_name)
class DisconnectedService(object):
    network_name = 'disconnected_network'

    def __init__(self):
        self.network_name = DisconnectedService.network_name
        self.network_helper = NetworkHelper()

    # The following method presumes that the plugin driver is aware that we're
    # running in hierarchical mode or not and sets segmentation_id correctly.
    def is_service_connected(self, service):
        networks = service['networks']
        network_id = service['loadbalancer']['network_id']
        segmentation_id = networks[network_id]['provider:segmentation_id']
        return (segmentation_id)

    def is_virtual_connected(self, virtual, bigips):
        # check if virtual_server is connected on any of our bigips
        connected = True
        for bigip in bigips:
            vsf = bigip.tm.ltm.virtuals.virtual
            if vsf.exists(name=virtual['name'],
                          partition=virtual['partition']):
                vs = vsf.load(
                    name=virtual['name'], partition=virtual['partition'])
                if (getattr(vs, 'vlansDisabled', False) or
                        not getattr(vs, 'vlansEnabled', True)):
                    # accommodate quirk of how big-ip returns virtual server
                    # if vlans are disabled OR vlans are not enabled, then
                    # we're connected
                    continue
                network_path = "/%s/%s" % (virtual['partition'],
                                           self.network_name)
                if network_path in getattr(vs, 'vlans', []):
                    connected = False
                    break
        return connected

    def network_exists(self, bigip, partition):
        t = bigip.tm.net.tunnels.tunnels.tunnel
        return t.exists(name=self.network_name, partition=partition)

    @log_helpers.log_method_call
    def create_network(self, bigip, partition):
        model = {'name': self.network_name,
                 'partition': partition,
                 'profile': 'ppp',
                 'description': 'Tenant disconnected network'}
        t = self.network_helper.create_tunnel(bigip, model)
        return t

    @log_helpers.log_method_call
    def delete_network(self, bigip, partition):
        tf = bigip.tm.net.tunnels.tunnels.tunnel
        if tf.exists(name=self.network_name, partition=partition):
            tf.load(name=self.network_name, partition=partition).delete()
    def purge_folder_contents(self, bigip, folder):
        network_helper = NetworkHelper()

        if folder not in self.exempt_folders:

            # First remove all LTM resources.
            ltm_types = [
                ResourceType.virtual, ResourceType.pool,
                ResourceType.http_monitor, ResourceType.https_monitor,
                ResourceType.tcp_monitor, ResourceType.ping_monitor,
                ResourceType.node, ResourceType.snat, ResourceType.snatpool,
                ResourceType.snat_translation, ResourceType.rule
            ]
            for ltm_type in ltm_types:
                resource = BigIPResourceHelper(ltm_type)
                [r.delete() for r in resource.get_resources(bigip, folder)]

            # Remove all net resources
            net_types = [
                ResourceType.arp, ResourceType.selfip, ResourceType.vlan,
                ResourceType.route_domain
            ]
            for net_type in net_types:
                resource = BigIPResourceHelper(net_type)
                [r.delete() for r in resource.get_resources(bigip, folder)]

            # Tunnels and fdb's require some special attention.
            resource = BigIPResourceHelper(ResourceType.tunnel)
            tunnels = resource.get_resources(bigip, folder)
            for tunnel in tunnels:
                network_helper.delete_all_fdb_entries(bigip, tunnel.name,
                                                      folder)
                network_helper.delete_tunnel(bigip, tunnel.name, folder)
    def test_network_helper_delete_fdb_entries(
            self, l2_service, service, bigips):
        # delete member fdb entry
        loadbalancer = None
        members = list()
        member = service['members'][1]
        network_id = member['network_id']
        member['network'] = service['networks'][network_id]
        members.append(member)

        tunnel_records = l2_service.create_fdb_records(loadbalancer, members)

        network_helper = NetworkHelper()
        fdb_entry = mock.MagicMock()
        fdb_entry.modify = mock.MagicMock()
        bigip = bigips[0]
        bigip.tm.net.fdb.tunnels.tunnel.load = mock.MagicMock(
            return_value=fdb_entry)
        network_helper.delete_fdb_entries(bigip, fdb_entries=tunnel_records)

        # expect to modify with no records (i.e, removing entry)
        fdb_entry.modify.assert_called_with(records=None)
    def test_network_helper_delete_fdb_entries(self, l2_service, service,
                                               bigips):
        # delete member fdb entry
        loadbalancer = None
        members = list()
        member = service['members'][1]
        network_id = member['network_id']
        member['network'] = service['networks'][network_id]
        members.append(member)

        tunnel_records = l2_service.create_fdb_records(loadbalancer, members)

        network_helper = NetworkHelper()
        fdb_entry = mock.MagicMock()
        fdb_entry.modify = mock.MagicMock()
        bigip = bigips[0]
        bigip.tm.net.fdb.tunnels.tunnel.load = mock.MagicMock(
            return_value=fdb_entry)
        network_helper.delete_fdb_entries(bigip, fdb_entries=tunnel_records)

        # expect to modify with no records (i.e, removing entry)
        fdb_entry.modify.assert_called_with(records=None)
    def test_network_helper_add_fdb_entries(self, l2_service, service, bigips):
        # add first member fdb entry
        loadbalancer = None
        members = list()
        member = service['members'][0]
        network_id = member['network_id']
        member['network'] = service['networks'][network_id]
        members.append(member)

        tunnel_records = l2_service.create_fdb_records(loadbalancer, members)

        network_helper = NetworkHelper()
        fdb_entry = mock.MagicMock()
        fdb_entry.modify = mock.MagicMock()
        bigip = bigips[0]
        bigip.tm.net.fdb.tunnels.tunnel.load = mock.MagicMock(
            return_value=fdb_entry)
        network_helper.add_fdb_entries(bigip, fdb_entries=tunnel_records)

        # expect to modify with first member's VTEP and MAC addr
        fdb_entry.modify.assert_called_with(
            records=[{'endpoint': '192.168.130.59',
                      'name': 'fa:16:3e:0d:fa:c8'}])

        # add second member fdb entry
        members = list()
        member = service['members'][1]
        network_id = member['network_id']
        member['network'] = service['networks'][network_id]
        members.append(member)

        tunnel_records = l2_service.create_fdb_records(loadbalancer, members)
        network_helper.add_fdb_entries(bigip, fdb_entries=tunnel_records)

        # expect to modify with second member's VTEP and MAC addr
        fdb_entry.modify.assert_called_with(
            records=[{'endpoint': '192.168.130.60',
                      'name': 'fa:16:3e:0d:fa:c6'}])
def test_add_remove_fdbs(bigip, icontrol_driver):
    """ Test simulating L2 pop events to add/remove fdb entries."""

    net_helper = NetworkHelper()
    tunnels = list()
    fdb_entries = list()

    seg_id_start = 167
    seg_id_end = 176
    n_records = 9 

    # create tunnels on BIG-IP, and fake fdb entries
    for seg_id in range(seg_id_start, seg_id_end):
        tunnel_name = 'tunnel-vxlan-{}'.format(seg_id)
        model = {
            'name': tunnel_name,
            'key': seg_id,
            'profile': 'vxlan_ovs',
            'localAddress': '201.0.155.10'}
        net_helper.create_multipoint_tunnel(bigip.bigip, model)
        tunnels.append(tunnel_name)

        # create a set of fdb entries that reference network seg ID
        for _ in range(n_records):
            entry = create_fdb_entry(seg_id)
            fdb_entries.append(entry)

    # add fdb entries
    for fdb_entry in fdb_entries:
        # mimic neutron L2 pop add_fdb_entries
        icontrol_driver.fdb_add(fdb_entry)

    for fdb_entry in fdb_entries:
        # mimic neutron L2 pop add_fdb_entries
        icontrol_driver.fdb_add(fdb_entry)

    # check created
    for tunnel_name in tunnels:
        records = net_helper.get_fdb_entry(bigip.bigip, tunnel_name=tunnel_name)
        assert records

    # remove fdb entries
    for fdb_entry in fdb_entries:
        # mimic neutron L2 pop remove_fdb_entries
        icontrol_driver.fdb_remove(fdb_entry)

    # check removed
    for tunnel_name in tunnels:
        records = net_helper.get_fdb_entry(bigip.bigip, tunnel_name=tunnel_name)
        assert not records
        net_helper.delete_tunnel(bigip.bigip, tunnel_name)
    def __init__(self, f5_global_routed_mode, conf, driver, l3_binding=None):
        self.f5_global_routed_mode = f5_global_routed_mode
        self.conf = conf
        self.driver = driver
        self.l3_binding = l3_binding
        self.l2_service = L2ServiceBuilder(driver, f5_global_routed_mode)

        self.bigip_selfip_manager = BigipSelfIpManager(
            self.driver, self.l2_service, self.driver.l3_binding)
        self.bigip_snat_manager = BigipSnatManager(
            self.driver, self.l2_service, self.driver.l3_binding)

        self.rds_cache = {}
        self.interface_mapping = self.l2_service.interface_mapping
        self.network_helper = NetworkHelper()
        self.service_adapter = self.driver.service_adapter
    def test_network_helper_add_fdb_entries(self, l2_service, service, bigips):
        # add first member fdb entry
        loadbalancer = None
        members = list()
        member = service['members'][0]
        network_id = member['network_id']
        member['network'] = service['networks'][network_id]
        members.append(member)

        tunnel_records = l2_service.create_fdb_records(loadbalancer, members)

        network_helper = NetworkHelper()
        fdb_entry = mock.MagicMock()
        fdb_entry.modify = mock.MagicMock()
        bigip = bigips[0]
        bigip.tm.net.fdb.tunnels.tunnel.load = mock.MagicMock(
            return_value=fdb_entry)
        network_helper.add_fdb_entries(bigip, fdb_entries=tunnel_records)

        # expect to modify with first member's VTEP and MAC addr
        fdb_entry.modify.assert_called_with(
            records=[{
                'endpoint': '192.168.130.59',
                'name': 'fa:16:3e:0d:fa:c8'
            }])

        # add second member fdb entry
        members = list()
        member = service['members'][1]
        network_id = member['network_id']
        member['network'] = service['networks'][network_id]
        members.append(member)

        tunnel_records = l2_service.create_fdb_records(loadbalancer, members)
        network_helper.add_fdb_entries(bigip, fdb_entries=tunnel_records)

        # expect to modify with second member's VTEP and MAC addr
        fdb_entry.modify.assert_called_with(
            records=[{
                'endpoint': '192.168.130.60',
                'name': 'fa:16:3e:0d:fa:c6'
            }])
Example #21
0
    def network_helper(self, bigip):
        nh = NetworkHelper()
        nh.get_route_domain_by_id = mock.MagicMock(
            return_value=bigip.tm.net.route_domains.route_domain)

        return nh
Example #22
0
class NetworkServiceBuilder(object):
    def __init__(self, f5_global_routed_mode, conf, driver, l3_binding=None):
        self.f5_global_routed_mode = f5_global_routed_mode
        self.conf = conf
        self.driver = driver
        self.l3_binding = l3_binding
        self.l2_service = L2ServiceBuilder(driver, f5_global_routed_mode)

        self.bigip_selfip_manager = BigipSelfIpManager(self.driver,
                                                       self.l2_service,
                                                       self.driver.l3_binding)
        self.bigip_snat_manager = BigipSnatManager(self.driver,
                                                   self.l2_service,
                                                   self.driver.l3_binding)

        self.rds_cache = {}
        self.interface_mapping = self.l2_service.interface_mapping
        self.network_helper = NetworkHelper()
        self.service_adapter = self.driver.service_adapter

    def post_init(self):
        # Run and Post Initialization Tasks """
        # run any post initialized tasks, now that the agent
        # is fully connected
        self.l2_service.post_init()

    def tunnel_sync(self, tunnel_ips):
        self.l2_service.tunnel_sync(tunnel_ips)

    def set_tunnel_rpc(self, tunnel_rpc):
        # Provide FDB Connector with ML2 RPC access """
        self.l2_service.set_tunnel_rpc(tunnel_rpc)

    def set_l2pop_rpc(self, l2pop_rpc):
        # Provide FDB Connector with ML2 RPC access """
        self.l2_service.set_l2pop_rpc(l2pop_rpc)

    def initialize_vcmp(self):
        self.l2_service.initialize_vcmp_manager()

    def initialize_tunneling(self):
        # setup tunneling
        vtep_folder = self.conf.f5_vtep_folder
        vtep_selfip_name = self.conf.f5_vtep_selfip_name
        local_ips = []

        for bigip in self.driver.get_all_bigips():

            if not vtep_folder or vtep_folder.lower() == 'none':
                vtep_folder = 'Common'

            if vtep_selfip_name and \
               not vtep_selfip_name.lower() == 'none':

                # profiles may already exist
                # create vxlan_multipoint_profile`
                self.network_helper.create_vxlan_multipoint_profile(
                    bigip, 'vxlan_ovs', partition='Common')
                # create l2gre_multipoint_profile
                self.network_helper.create_l2gre_multipoint_profile(
                    bigip, 'gre_ovs', partition='Common')

                # find the IP address for the selfip for each box
                local_ip = self.bigip_selfip_manager.get_selfip_addr(
                    bigip, vtep_selfip_name, partition=vtep_folder)

                if local_ip:
                    bigip.local_ip = local_ip
                    local_ips.append(local_ip)
                else:
                    raise f5_ex.MissingVTEPAddress(
                        'device %s missing vtep selfip %s' %
                        (bigip.device_name,
                         '/' + vtep_folder + '/' + vtep_selfip_name))
        return local_ips

    def prep_service_networking(self, service, traffic_group):
        # Assure network connectivity is established on all bigips
        if self.conf.f5_global_routed_mode or not service['loadbalancer']:
            return

        if self.conf.use_namespaces:
            try:
                LOG.debug("Annotating the service definition networks "
                          "with route domain ID.")
                self._annotate_service_route_domains(service)
            except Exception as err:
                LOG.exception(err)
                raise f5_ex.RouteDomainCreationException(
                    "Route domain annotation error")

        # Per Device Network Connectivity (VLANs or Tunnels)
        subnetsinfo = self._get_subnets_to_assure(service)
        for (assure_bigip,
             subnetinfo) in (itertools.product(self.driver.get_all_bigips(),
                                               subnetsinfo)):
            LOG.debug("Assuring per device network connectivity "
                      "for %s on subnet %s." %
                      (assure_bigip.hostname, subnetinfo['subnet']))

            # Make sure the L2 network is established
            self.l2_service.assure_bigip_network(assure_bigip,
                                                 subnetinfo['network'])

            # Connect the BigIP device to network, by getting
            # a self-ip address on the subnet.
            self.bigip_selfip_manager.assure_bigip_selfip(
                assure_bigip, service, subnetinfo)

        # L3 Shared Config
        assure_bigips = self.driver.get_config_bigips()
        LOG.debug("Getting subnetinfo for ...")
        LOG.debug(assure_bigips)
        for subnetinfo in subnetsinfo:
            if self.conf.f5_snat_addresses_per_subnet > 0:
                self._assure_subnet_snats(assure_bigips, service, subnetinfo)

            if subnetinfo['is_for_member'] and not self.conf.f5_snat_mode:
                try:
                    self._allocate_gw_addr(subnetinfo)
                except KeyError as err:
                    raise f5_ex.VirtualServerCreationException(err.message)

                for assure_bigip in assure_bigips:
                    # If we are not using SNATS, attempt to become
                    # the subnet's default gateway.
                    self.bigip_selfip_manager.assure_gateway_on_subnet(
                        assure_bigip, subnetinfo, traffic_group)

    def _annotate_service_route_domains(self, service):
        # Add route domain notation to pool member and vip addresses.
        LOG.debug("Service before route domains: %s" % service)
        tenant_id = service['loadbalancer']['tenant_id']
        self.update_rds_cache(tenant_id)

        if 'members' in service:
            for member in service['members']:
                if 'address' in member:
                    LOG.debug("processing member %s" % member['address'])
                    if 'network_id' in member and member['network_id']:
                        member_network = (
                            self.service_adapter.get_network_from_service(
                                service, member['network_id']))
                        member_subnet = (
                            self.service_adapter.get_subnet_from_service(
                                service, member['subnet_id']))
                        if member_network:
                            self.assign_route_domain(tenant_id, member_network,
                                                     member_subnet)
                            rd_id = ('%' +
                                     str(member_network['route_domain_id']))
                            member['address'] += rd_id
                    else:
                        member['address'] += '%0'

        if 'vip_address' in service['loadbalancer']:
            loadbalancer = service['loadbalancer']
            if 'network_id' in loadbalancer:
                lb_network = self.service_adapter.get_network_from_service(
                    service, loadbalancer['network_id'])
                vip_subnet = self.service_adapter.get_subnet_from_service(
                    service, loadbalancer['vip_subnet_id'])
                self.assign_route_domain(tenant_id, lb_network, vip_subnet)
                rd_id = '%' + str(lb_network['route_domain_id'])
                service['loadbalancer']['vip_address'] += rd_id
            else:
                service['loadbalancer']['vip_address'] += '%0'
        LOG.debug("Service after route domains: %s" % service)

    def assign_route_domain(self, tenant_id, network, subnet):
        # Assign route domain for a network
        if self.l2_service.is_common_network(network):
            network['route_domain_id'] = 0
            return

        LOG.debug("assign route domain get from cache %s" % network)
        route_domain_id = self.get_route_domain_from_cache(network)
        if route_domain_id is not None:
            network['route_domain_id'] = route_domain_id
            return

        LOG.debug("max namespaces: %s" % self.conf.max_namespaces_per_tenant)
        LOG.debug("max namespaces == 1: %s" %
                  (self.conf.max_namespaces_per_tenant == 1))

        if self.conf.max_namespaces_per_tenant == 1:
            bigip = self.driver.get_bigip()
            LOG.debug("bigip before get_domain: %s" % bigip)
            partition_id = self.service_adapter.get_folder_name(tenant_id)
            tenant_rd = self.network_helper.get_route_domain(
                bigip, partition=partition_id)
            network['route_domain_id'] = tenant_rd.id
            return

        LOG.debug("assign route domain checking for available route domain")
        # need new route domain ?
        check_cidr = netaddr.IPNetwork(subnet['cidr'])
        placed_route_domain_id = None
        for route_domain_id in self.rds_cache[tenant_id]:
            LOG.debug("checking rd %s" % route_domain_id)
            rd_entry = self.rds_cache[tenant_id][route_domain_id]
            overlapping_subnet = None
            for net_shortname in rd_entry:
                LOG.debug("checking net %s" % net_shortname)
                net_entry = rd_entry[net_shortname]
                for exist_subnet_id in net_entry['subnets']:
                    if exist_subnet_id == subnet['id']:
                        continue
                    exist_subnet = net_entry['subnets'][exist_subnet_id]
                    exist_cidr = exist_subnet['cidr']
                    if check_cidr in exist_cidr or exist_cidr in check_cidr:
                        overlapping_subnet = exist_subnet
                        LOG.debug(
                            'rd %s: overlaps with subnet %s id: %s' %
                            ((route_domain_id, exist_subnet, exist_subnet_id)))
                        break
                if overlapping_subnet:
                    # no need to keep looking
                    break
            if not overlapping_subnet:
                placed_route_domain_id = route_domain_id
                break

        if placed_route_domain_id is None:
            if (len(self.rds_cache[tenant_id]) <
                    self.conf.max_namespaces_per_tenant):
                placed_route_domain_id = self._create_aux_rd(tenant_id)
                self.rds_cache[tenant_id][placed_route_domain_id] = {}
                LOG.debug("Tenant %s now has %d route domains" %
                          (tenant_id, len(self.rds_cache[tenant_id])))
            else:
                raise Exception("Cannot allocate route domain")

        LOG.debug("Placed in route domain %s" % placed_route_domain_id)
        rd_entry = self.rds_cache[tenant_id][placed_route_domain_id]

        net_short_name = self.get_neutron_net_short_name(network)
        if net_short_name not in rd_entry:
            rd_entry[net_short_name] = {'subnets': {}}
        net_subnets = rd_entry[net_short_name]['subnets']
        net_subnets[subnet['id']] = {'cidr': check_cidr}
        network['route_domain_id'] = placed_route_domain_id

    def _create_aux_rd(self, tenant_id):
        # Create a new route domain
        route_domain_id = None
        for bigip in self.driver.get_all_bigips():
            partition_id = self.service_adapter.get_folder_name(tenant_id)
            bigip_route_domain_id = self.network_helper.create_route_domain(
                bigip,
                partition=partition_id,
                strictness=self.conf.f5_route_domain_strictness,
                is_aux=True)
            if route_domain_id is None:
                route_domain_id = bigip_route_domain_id.id
            elif bigip_route_domain_id.id != route_domain_id:
                # FixME error
                LOG.debug(
                    "Bigips allocated two different route domains!: %s %s" %
                    (bigip_route_domain_id, route_domain_id))
        LOG.debug("Allocated route domain %s for tenant %s" %
                  (route_domain_id, tenant_id))
        return route_domain_id

    # The purpose of the route domain subnet cache is to
    # determine whether there is an existing bigip
    # subnet that conflicts with a new one being
    # assigned to the route domain.
    """
    # route domain subnet cache
    rds_cache =
        {'<tenant_id>': {
            {'0': {
                '<network type>-<segmentation id>': [
                    'subnets': [
                        '<subnet id>': {
                            'cidr': '<cidr>'
                        }
                ],
            '1': {}}}}
    """

    def update_rds_cache(self, tenant_id):
        # Update the route domain cache from bigips
        if tenant_id not in self.rds_cache:
            LOG.debug("rds_cache: adding tenant %s" % tenant_id)
            self.rds_cache[tenant_id] = {}
            for bigip in self.driver.get_all_bigips():
                self.update_rds_cache_bigip(tenant_id, bigip)
            LOG.debug("rds_cache updated: " + str(self.rds_cache))

    def update_rds_cache_bigip(self, tenant_id, bigip):
        # Update the route domain cache for this tenant
        # with information from bigip's vlan and tunnels
        LOG.debug("rds_cache: processing bigip %s" % bigip.device_name)

        route_domain_ids = self.network_helper.get_route_domain_ids(
            bigip, partition=self.service_adapter.get_folder_name(tenant_id))
        # LOG.debug("rds_cache: got bigip route domains: %s" % route_domains)
        for route_domain_id in route_domain_ids:
            self.update_rds_cache_bigip_rd_vlans(tenant_id, bigip,
                                                 route_domain_id)

    def update_rds_cache_bigip_rd_vlans(self, tenant_id, bigip,
                                        route_domain_id):
        # Update the route domain cache with information
        # from the bigip vlans and tunnels from
        # this route domain
        LOG.debug("rds_cache: processing bigip %s rd %s" %
                  (bigip.device_name, route_domain_id))
        # this gets tunnels too
        partition_id = self.service_adapter.get_folder_name(tenant_id)
        rd_vlans = self.network_helper.get_vlans_in_route_domain_by_id(
            bigip, partition=partition_id, id=route_domain_id)
        LOG.debug("rds_cache: bigip %s rd %s vlans: %s" %
                  (bigip.device_name, route_domain_id, rd_vlans))
        if len(rd_vlans) == 0:
            LOG.debug("No vlans found for route domain: %d" %
                      (route_domain_id))
            return

        # make sure this rd has a cache entry
        tenant_entry = self.rds_cache[tenant_id]
        if route_domain_id not in tenant_entry:
            tenant_entry[route_domain_id] = {}

        # for every VLAN or TUNNEL on this bigip...
        for rd_vlan in rd_vlans:
            self.update_rds_cache_bigip_vlan(tenant_id, bigip, route_domain_id,
                                             rd_vlan)

    def update_rds_cache_bigip_vlan(self, tenant_id, bigip, route_domain_id,
                                    rd_vlan):
        # Update the route domain cache with information
        #    from the bigip vlan or tunnel
        LOG.debug("rds_cache: processing bigip %s rd %d vlan %s" %
                  (bigip.device_name, route_domain_id, rd_vlan))
        net_short_name = self.get_bigip_net_short_name(bigip, tenant_id,
                                                       rd_vlan)

        # make sure this net has a cache entry
        tenant_entry = self.rds_cache[tenant_id]
        rd_entry = tenant_entry[route_domain_id]
        if net_short_name not in rd_entry:
            rd_entry[net_short_name] = {'subnets': {}}
        net_subnets = rd_entry[net_short_name]['subnets']

        partition_id = self.service_adapter.get_folder_name(tenant_id)
        LOG.debug("Calling get_selfips with: partition %s and vlan_name %s",
                  partition_id, rd_vlan)
        selfips = self.bigip_selfip_manager.get_selfips(bigip,
                                                        partition=partition_id,
                                                        vlan_name=rd_vlan)

        LOG.debug("rds_cache: got selfips")
        for selfip in selfips:
            LOG.debug(
                "rds_cache: processing bigip %s rd %s vlan %s self %s" %
                (bigip.device_name, route_domain_id, rd_vlan, selfip.name))
            if bigip.device_name not in selfip.name:
                LOG.error(
                    "rds_cache: Found unexpected selfip %s for tenant %s" %
                    (selfip.name, tenant_id))
                continue
            subnet_id = selfip.name.split(bigip.device_name + '-')[1]

            # convert 10.1.1.1%1/24 to 10.1.1.1/24
            (addr, netbits) = selfip.address.split('/')
            addr = addr.split('%')[0]
            selfip.address = addr + '/' + netbits

            # selfip addresses will have slash notation: 10.1.1.1/24
            netip = netaddr.IPNetwork(selfip.address)
            LOG.debug("rds_cache: updating subnet %s with %s" %
                      (subnet_id, str(netip.cidr)))
            net_subnets[subnet_id] = {'cidr': netip.cidr}
            LOG.debug("rds_cache: now %s" % self.rds_cache)

    def get_route_domain_from_cache(self, network):
        # Get route domain from cache by network
        net_short_name = self.get_neutron_net_short_name(network)
        for tenant_id in self.rds_cache:
            tenant_cache = self.rds_cache[tenant_id]
            for route_domain_id in tenant_cache:
                if net_short_name in tenant_cache[route_domain_id]:
                    return route_domain_id

    def remove_from_rds_cache(self, network, subnet):
        # Get route domain from cache by network
        LOG.debug("remove_from_rds_cache")
        net_short_name = self.get_neutron_net_short_name(network)
        for tenant_id in self.rds_cache:
            LOG.debug("rds_cache: processing remove for %s" % tenant_id)
            deleted_rds = []
            tenant_cache = self.rds_cache[tenant_id]
            for route_domain_id in tenant_cache:
                if net_short_name in tenant_cache[route_domain_id]:
                    net_entry = tenant_cache[route_domain_id][net_short_name]
                    if subnet['id'] in net_entry['subnets']:
                        del net_entry['subnets'][subnet['id']]
                        if len(net_entry['subnets']) == 0:
                            del net_entry['subnets']
                    if len(tenant_cache[route_domain_id][net_short_name]) == 0:
                        del tenant_cache[route_domain_id][net_short_name]
                if len(self.rds_cache[tenant_id][route_domain_id]) == 0:
                    deleted_rds.append(route_domain_id)
            for rd in deleted_rds:
                LOG.debug("removing route domain %d from tenant %s" %
                          (rd, tenant_id))
                del self.rds_cache[tenant_id][rd]

    def get_bigip_net_short_name(self, bigip, tenant_id, network_name):
        # Return <network_type>-<seg_id> for bigip network
        LOG.debug("get_bigip_net_short_name: %s:%s" %
                  (tenant_id, network_name))
        partition_id = self.service_adapter.get_folder_name(tenant_id)
        LOG.debug("network_name %s", network_name.split('/'))
        network_name = network_name.split("/")[-1]
        if 'tunnel-gre-' in network_name:
            tunnel_key = self.network_helper.get_tunnel_key(
                bigip, network_name, partition=partition_id)
            return 'gre-%s' % tunnel_key
        elif 'tunnel-vxlan-' in network_name:
            LOG.debug("Getting tunnel key for VXLAN: %s", network_name)
            tunnel_key = self.network_helper.get_tunnel_key(
                bigip, network_name, partition=partition_id)
            return 'vxlan-%s' % tunnel_key
        else:
            LOG.debug("Getting tunnel key for VLAN: %s", network_name)
            vlan_id = self.network_helper.get_vlan_id(bigip,
                                                      name=network_name,
                                                      partition=partition_id)
            return 'vlan-%s' % vlan_id

    @staticmethod
    def get_neutron_net_short_name(network):
        # Return <network_type>-<seg_id> for neutron network
        net_type = network['provider:network_type']
        net_seg_key = network['provider:segmentation_id']
        return net_type + '-' + str(net_seg_key)

    def _assure_subnet_snats(self, assure_bigips, service, subnetinfo):
        # Ensure snat for subnet exists on bigips
        tenant_id = service['loadbalancer']['tenant_id']
        subnet = subnetinfo['subnet']
        snats_per_subnet = self.conf.f5_snat_addresses_per_subnet

        assure_bigips = \
            [bigip for bigip in assure_bigips
                if tenant_id not in bigip.assured_tenant_snat_subnets or
                subnet['id'] not in
                bigip.assured_tenant_snat_subnets[tenant_id]]

        LOG.debug("_assure_subnet_snats: getting snat addrs for: %s" %
                  subnet['id'])
        if len(assure_bigips):
            snat_addrs = self.bigip_snat_manager.get_snat_addrs(
                subnetinfo, tenant_id, snats_per_subnet)

            if len(snat_addrs) != snats_per_subnet:
                raise f5_ex.SNAT_CreationException(
                    "Unable to satisfy request to allocate %d "
                    "snats.  Actual SNAT count: %d SNATs" %
                    (snats_per_subnet, len(snat_addrs)))
            for assure_bigip in assure_bigips:
                self.bigip_snat_manager.assure_bigip_snats(
                    assure_bigip, subnetinfo, snat_addrs, tenant_id)

    def _allocate_gw_addr(self, subnetinfo):
        # Create a name for the port and for the IP Forwarding
        # Virtual Server as well as the floating Self IP which
        # will answer ARP for the members
        need_port_for_gateway = False
        network = subnetinfo['network']
        subnet = subnetinfo['subnet']
        if not network or not subnet:
            LOG.error('Attempted to create default gateway'
                      ' for network with no id...skipping.')
            return

        if not subnet['gateway_ip']:
            raise KeyError("attempting to create gateway on subnet without "
                           "gateway ip address specified.")

        gw_name = "gw-" + subnet['id']
        ports = self.driver.plugin_rpc.get_port_by_name(port_name=gw_name)
        if len(ports) < 1:
            need_port_for_gateway = True

        # There was no port on this agent's host, so get one from Neutron
        if need_port_for_gateway:
            try:
                rpc = self.driver.plugin_rpc
                new_port = rpc.create_port_on_subnet_with_specific_ip(
                    subnet_id=subnet['id'],
                    mac_address=None,
                    name=gw_name,
                    ip_address=subnet['gateway_ip'])
                LOG.info('gateway IP for subnet %s will be port %s' %
                         (subnet['id'], new_port['id']))
            except Exception as exc:
                ermsg = 'Invalid default gateway for subnet %s:%s - %s.' \
                    % (subnet['id'],
                       subnet['gateway_ip'],
                       exc.message)
                ermsg += " SNAT will not function and load balancing"
                ermsg += " support will likely fail. Enable f5_snat_mode."
                LOG.exception(ermsg)
        return True

    def post_service_networking(self, service, all_subnet_hints):
        # Assure networks are deleted from big-ips
        if self.conf.f5_global_routed_mode:
            return

        # L2toL3 networking layer
        # Non Shared Config -  Local Per BIG-IP
        self.update_bigip_l2(service)

        # Delete shared config objects
        deleted_names = set()
        for bigip in self.driver.get_config_bigips():
            LOG.debug('post_service_networking: calling '
                      '_assure_delete_networks del nets sh for bigip %s %s' %
                      (bigip.device_name, all_subnet_hints))
            subnet_hints = all_subnet_hints[bigip.device_name]
            deleted_names = deleted_names.union(
                self._assure_delete_nets_shared(bigip, service, subnet_hints))

        # Delete non shared config objects
        for bigip in self.driver.get_all_bigips():
            LOG.debug('    post_service_networking: calling '
                      '    _assure_delete_networks del nets ns for bigip %s' %
                      bigip.device_name)

            subnet_hints = all_subnet_hints[bigip.device_name]

            deleted_names = deleted_names.union(
                self._assure_delete_nets_nonshared(bigip, service,
                                                   subnet_hints))

        for port_name in deleted_names:
            LOG.debug('    post_service_networking: calling '
                      '    del port %s' % port_name)
            self.driver.plugin_rpc.delete_port_by_name(port_name=port_name)

    def update_bigip_l2(self, service):
        # Update fdb entries on bigip
        loadbalancer = service['loadbalancer']
        service_adapter = self.service_adapter

        for bigip in self.driver.get_all_bigips():
            for member in service['members']:
                LOG.debug("update_bigip_l2 update service members")
                member['network'] = service_adapter.get_network_from_service(
                    service, member['network_id'])
                member_status = member['provisioning_status']
                if member_status == plugin_const.PENDING_DELETE:
                    self.delete_bigip_member_l2(bigip, loadbalancer, member)
                else:
                    self.update_bigip_member_l2(bigip, loadbalancer, member)

            if "network_id" not in loadbalancer:
                LOG.error("update_bigip_l2, expected network ID")
                return

            LOG.debug("update_bigip_l2 get network for ID %s" %
                      loadbalancer["network_id"])
            loadbalancer['network'] = service_adapter.get_network_from_service(
                service, loadbalancer['network_id'])
            lb_status = loadbalancer['provisioning_status']
            if lb_status == plugin_const.PENDING_DELETE:
                self.delete_bigip_vip_l2(bigip, loadbalancer)
            else:
                LOG.debug("update_bigip_l2 calling update_bigip_vip_l2")
                self.update_bigip_vip_l2(bigip, loadbalancer)
            LOG.debug("update_bigip_l2 complete")

    def update_bigip_member_l2(self, bigip, loadbalancer, member):
        # update pool member l2 records
        network = member['network']
        if network:
            if self.l2_service.is_common_network(network):
                net_folder = 'Common'
            else:
                net_folder = self.service_adapter.get_folder_name(
                    loadbalancer['tenant_id'])
            fdb_info = {
                'network': network,
                'ip_address': member['address'],
                'mac_address': member['port']['mac_address']
            }
            self.l2_service.add_bigip_fdbs(bigip, net_folder, fdb_info, member)

    def delete_bigip_member_l2(self, bigip, loadbalancer, member):
        # Delete pool member l2 records
        network = member['network']
        if network:
            if 'port' in member:
                if self.l2_service.is_common_network(network):
                    net_folder = 'Common'
                else:
                    net_folder = self.service_adapter.get_folder_name(
                        loadbalancer['tenant_id'])
                fdb_info = {
                    'network': network,
                    'ip_address': member['address'],
                    'mac_address': member['port']['mac_address']
                }
                self.l2_service.delete_bigip_fdbs(bigip, net_folder, fdb_info,
                                                  member)
            else:
                LOG.error('Member on SDN has no port. Manual '
                          'removal on the BIG-IP will be '
                          'required. Was the vm instance '
                          'deleted before the pool member '
                          'was deleted?')

    def update_bigip_vip_l2(self, bigip, loadbalancer):
        # Update vip l2 records
        network = loadbalancer['network']
        if network:
            if self.l2_service.is_common_network(network):
                net_folder = 'Common'
            else:
                net_folder = self.service_adapter.get_folder_name(
                    loadbalancer['tenant_id'])
            fdb_info = {
                'network': network,
                'ip_address': None,
                'mac_address': None
            }
            self.l2_service.add_bigip_fdbs(bigip, net_folder, fdb_info,
                                           loadbalancer)

    def delete_bigip_vip_l2(self, bigip, loadbalancer):
        # Delete loadbalancer l2 records
        network = loadbalancer['network']
        if network:
            if self.l2_service.is_common_network(network):
                net_folder = 'Common'
            else:
                net_folder = self.service_adapter.get_folder_name(
                    loadbalancer['tenant_id'])
            fdb_info = {
                'network': network,
                'ip_address': None,
                'mac_address': None
            }
            self.l2_service.delete_bigip_fdbs(bigip, net_folder, fdb_info,
                                              loadbalancer)

    def _assure_delete_nets_shared(self, bigip, service, subnet_hints):
        # Assure shared configuration (which syncs) is deleted
        deleted_names = set()
        tenant_id = service['loadbalancer']['tenant_id']

        delete_gateway = self.bigip_selfip_manager.delete_gateway_on_subnet
        for subnetinfo in self._get_subnets_to_delete(bigip, service,
                                                      subnet_hints):
            try:
                if not self.conf.f5_snat_mode:
                    gw_name = delete_gateway(bigip, subnetinfo)
                    deleted_names.add(gw_name)
                my_deleted_names, my_in_use_subnets = \
                    self.bigip_snat_manager.delete_bigip_snats(
                        bigip, subnetinfo, tenant_id)
                deleted_names = deleted_names.union(my_deleted_names)
                for in_use_subnetid in my_in_use_subnets:
                    subnet_hints['check_for_delete_subnets'].pop(
                        in_use_subnetid, None)
            except NeutronException as exc:
                LOG.error("assure_delete_nets_shared: exception: %s" %
                          str(exc.msg))
            except Exception as exc:
                LOG.error("assure_delete_nets_shared: exception: %s" %
                          str(exc.message))

        return deleted_names

    def _assure_delete_nets_nonshared(self, bigip, service, subnet_hints):
        # Delete non shared base objects for networks
        deleted_names = set()
        for subnetinfo in self._get_subnets_to_delete(bigip, service,
                                                      subnet_hints):
            try:
                network = subnetinfo['network']
                if self.l2_service.is_common_network(network):
                    network_folder = 'Common'
                else:
                    network_folder = self.service_adapter.get_folder_name(
                        service['loadbalancer']['tenant_id'])

                subnet = subnetinfo['subnet']
                if self.conf.f5_populate_static_arp:
                    self.network_helper.arp_delete_by_subnet(
                        bigip,
                        subnet=subnet['cidr'],
                        mask=None,
                        partition=network_folder)

                local_selfip_name = "local-" + bigip.device_name + \
                                    "-" + subnet['id']

                selfip_address = self.bigip_selfip_manager.get_selfip_addr(
                    bigip, local_selfip_name, partition=network_folder)

                if not selfip_address:
                    LOG.error("Failed to get self IP address %s in cleanup.",
                              local_selfip_name)

                self.bigip_selfip_manager.delete_selfip(
                    bigip, local_selfip_name, partition=network_folder)

                if self.l3_binding and selfip_address:
                    self.l3_binding.unbind_address(subnet_id=subnet['id'],
                                                   ip_address=selfip_address)

                deleted_names.add(local_selfip_name)

                self.l2_service.delete_bigip_network(bigip, network)

                if subnet['id'] not in subnet_hints['do_not_delete_subnets']:
                    subnet_hints['do_not_delete_subnets'].append(subnet['id'])

                self.remove_from_rds_cache(network, subnet)
                tenant_id = service['loadbalancer']['tenant_id']
                if tenant_id in bigip.assured_tenant_snat_subnets:
                    tenant_snat_subnets = \
                        bigip.assured_tenant_snat_subnets[tenant_id]
                    if subnet['id'] in tenant_snat_subnets:
                        tenant_snat_subnets.remove(subnet['id'])
            except NeutronException as exc:
                LOG.error("assure_delete_nets_nonshared: exception: %s" %
                          str(exc.msg))
            except Exception as exc:
                LOG.error("assure_delete_nets_nonshared: exception: %s" %
                          str(exc.message))

        return deleted_names

    def _get_subnets_to_delete(self, bigip, service, subnet_hints):
        # Clean up any Self IP, SNATs, networks, and folder for
        # services items that we deleted.
        subnets_to_delete = []
        for subnetinfo in subnet_hints['check_for_delete_subnets'].values():
            subnet = self.service_adapter.get_subnet_from_service(
                service, subnetinfo['subnet_id'])
            subnetinfo['subnet'] = subnet
            network = self.service_adapter.get_network_from_service(
                service, subnetinfo['network_id'])
            subnetinfo['network'] = network
            route_domain = network['route_domain_id']
            if not subnet:
                continue
            if not self._ips_exist_on_subnet(bigip, service, subnet,
                                             route_domain):
                subnets_to_delete.append(subnetinfo)

        return subnets_to_delete

    def _ips_exist_on_subnet(self, bigip, service, subnet, route_domain):
        # Does the big-ip have any IP addresses on this subnet?
        LOG.debug("_ips_exist_on_subnet entry %s rd %s" %
                  (str(subnet['cidr']), route_domain))
        route_domain = str(route_domain)
        ipsubnet = netaddr.IPNetwork(subnet['cidr'])

        # Are there any virtual addresses on this subnet?
        folder = self.service_adapter.get_folder_name(
            service['loadbalancer']['tenant_id'])
        virtual_services = self.network_helper.get_virtual_service_insertion(
            bigip, partition=folder)
        for virt_serv in virtual_services:
            (_, dest) = virt_serv.items()[0]
            LOG.debug("            _ips_exist_on_subnet: checking vip %s" %
                      str(dest['address']))
            if len(dest['address'].split('%')) > 1:
                vip_route_domain = dest['address'].split('%')[1]
            else:
                vip_route_domain = '0'
            if vip_route_domain != route_domain:
                continue
            vip_addr = strip_domain_address(dest['address'])
            if netaddr.IPAddress(vip_addr) in ipsubnet:
                LOG.debug("            _ips_exist_on_subnet: found")
                return True

        # If there aren't any virtual addresses, are there
        # node addresses on this subnet?
        nodes = self.network_helper.get_node_addresses(bigip, partition=folder)
        for node in nodes:
            LOG.debug("            _ips_exist_on_subnet: checking node %s" %
                      str(node))
            if len(node.split('%')) > 1:
                node_route_domain = node.split('%')[1]
            else:
                node_route_domain = '0'
            if node_route_domain != route_domain:
                continue
            node_addr = strip_domain_address(node)
            if netaddr.IPAddress(node_addr) in ipsubnet:
                LOG.debug("        _ips_exist_on_subnet: found")
                return True

        LOG.debug("            _ips_exist_on_subnet exit %s" %
                  str(subnet['cidr']))
        # nothing found
        return False

    def add_bigip_fdb(self, bigip, fdb):
        self.l2_service.add_bigip_fdb(bigip, fdb)

    def remove_bigip_fdb(self, bigip, fdb):
        self.l2_service.remove_bigip_fdb(bigip, fdb)

    def update_bigip_fdb(self, bigip, fdb):
        self.l2_service.update_bigip_fdb(bigip, fdb)

    def set_context(self, context):
        self.l2_service.set_context(context)

    def vlan_exists(self, network, folder='Common'):
        return False

    def _get_subnets_to_assure(self, service):
        # Examine service and return active networks
        networks = dict()
        loadbalancer = service['loadbalancer']
        service_adapter = self.service_adapter

        lb_status = loadbalancer['provisioning_status']
        if lb_status != plugin_const.PENDING_DELETE:
            if 'network_id' in loadbalancer:
                network = service_adapter.get_network_from_service(
                    service, loadbalancer['network_id'])
                subnet = service_adapter.get_subnet_from_service(
                    service, loadbalancer['vip_subnet_id'])
                networks[network['id']] = {
                    'network': network,
                    'subnet': subnet,
                    'is_for_member': False
                }

        for member in service['members']:
            if member['provisioning_status'] != plugin_const.PENDING_DELETE:
                if 'network_id' in member:
                    network = service_adapter.get_network_from_service(
                        service, member['network_id'])
                    subnet = service_adapter.get_subnet_from_service(
                        service, member['subnet_id'])
                    networks[network['id']] = {
                        'network': network,
                        'subnet': subnet,
                        'is_for_member': True
                    }
        return networks.values()
Example #23
0
class BigipSnatManager(object):
    def __init__(self, driver, l2_service, l3_binding):
        self.driver = driver
        self.l2_service = l2_service
        self.l3_binding = l3_binding
        self.snatpool_manager = BigIPResourceHelper(ResourceType.snatpool)
        self.snat_translation_manager = BigIPResourceHelper(
            ResourceType.snat_translation)
        self.network_helper = NetworkHelper()

    def _get_snat_name(self, subnet, tenant_id):
        # Get the snat name based on HA type
        if self.driver.conf.f5_ha_type == 'standalone':
            return 'snat-traffic-group-local-only-' + subnet['id']
        elif self.driver.conf.f5_ha_type == 'pair':
            # REVISIT(RJB): should this name have a hyphen before subnetid
            return 'snat-traffic-group-1' + subnet['id']
        elif self.driver.conf.f5_ha_type == 'scalen':
            traffic_group = self.driver.tenant_to_traffic_group(tenant_id)
            base_traffic_group = os.path.basename(traffic_group)
            return 'snat-' + base_traffic_group + '-' + subnet['id']
        LOG.error('Invalid f5_ha_type:%s' % self.driver.conf.f5_ha_type)
        return ''

    def _get_snat_traffic_group(self, tenant_id):
        # Get the snat name based on HA type """
        if self.driver.conf.f5_ha_type == 'standalone':
            return 'traffic-group-local-only'
        elif self.driver.conf.f5_ha_type == 'pair':
            return 'traffic-group-1'
        elif self.driver.conf.f5_ha_type == 'scalen':
            traffic_group = self.driver.tenant_to_traffic_group(tenant_id)
            return os.path.basename(traffic_group)
        # If this is an error shouldn't we raise?
        LOG.error('Invalid f5_ha_type:%s' % self.driver.conf.f5_ha_type)
        return ''

    def get_snat_addrs(self, subnetinfo, tenant_id):
        # Get the ip addresses for snat """
        subnet = subnetinfo['subnet']
        snat_addrs = []

        snat_name = self._get_snat_name(subnet, tenant_id)
        for i in range(self.driver.conf.f5_snat_addresses_per_subnet):
            index_snat_name = snat_name + "_" + str(i)
            ports = self.driver.plugin_rpc.get_port_by_name(
                port_name=index_snat_name)
            if len(ports) > 0:
                first_port = ports[0]
                first_fixed_ip = first_port['fixed_ips'][0]
                ip_address = first_fixed_ip['ip_address']
            else:
                new_port = self.driver.plugin_rpc.create_port_on_subnet(
                    subnet_id=subnet['id'],
                    mac_address=None,
                    name=index_snat_name,
                    fixed_address_count=1)
                ip_address = new_port['fixed_ips'][0]['ip_address']
            snat_addrs.append(ip_address)
        return snat_addrs

    def assure_bigip_snats(self, bigip, subnetinfo, snat_addrs, tenant_id):
        # Ensure Snat Addresses are configured on a bigip.
        # Called for every bigip only in replication mode.
        # otherwise called once and synced.
        network = subnetinfo['network']

        snat_info = {}
        if self.l2_service.is_common_network(network):
            snat_info['network_folder'] = 'Common'
        else:
            snat_info['network_folder'] = (
                self.driver.service_adapter.get_folder_name(tenant_id)
            )

        snat_info['pool_name'] = self.driver.service_adapter.get_folder_name(
            tenant_id
        )
        snat_info['pool_folder'] = self.driver.service_adapter.get_folder_name(
            tenant_id
        )
        snat_info['addrs'] = snat_addrs

        self._assure_bigip_snats(bigip, subnetinfo, snat_info, tenant_id)

    def _assure_bigip_snats(self, bigip, subnetinfo, snat_info, tenant_id):
        # Configure the ip addresses for snat
        network = subnetinfo['network']
        subnet = subnetinfo['subnet']
        LOG.debug("_assure_bigip_snats")
        if tenant_id not in bigip.assured_tenant_snat_subnets:
            bigip.assured_tenant_snat_subnets[tenant_id] = []
        if subnet['id'] in bigip.assured_tenant_snat_subnets[tenant_id]:
            return

        snat_name = self._get_snat_name(subnet, tenant_id)
        for i in range(self.driver.conf.f5_snat_addresses_per_subnet):
            ip_address = snat_info['addrs'][i] + \
                '%' + str(network['route_domain_id'])
            index_snat_name = snat_name + "_" + str(i)
            if self.l2_service.is_common_network(network):
                index_snat_name = '/Common/' + index_snat_name

            snat_traffic_group = self._get_snat_traffic_group(tenant_id)
            # snat.create() did  the following in LBaaSv1
            # Creates the SNAT
            #   * if the traffic_group is empty it uses a const
            #     but this seems like it should be an error see message
            #     in this file about this
            # Create a SNAT Pool if a name was passed in
            #   * Add the snat to the list of members
            model = {
                "name": index_snat_name,
                "partition": snat_info['network_folder'],
                "address": ip_address,
                "trafficGroup": snat_traffic_group
            }
            if not self.snat_translation_manager.exists(
                    bigip,
                    name=index_snat_name,
                    partition=snat_info['network_folder']):
                LOG.debug("Calling SNAT translation manager CREATE.")
                self.snat_translation_manager.create(bigip, model)
            else:
                LOG.debug("SNAT translation_manager LOAD")
                self.snat_translation_manager.load(
                    bigip,
                    name=index_snat_name,
                    partition=snat_info['network_folder'])

            model = {
                "name": index_snat_name,
                "partition": snat_info['pool_folder'],
            }
            model["members"] = ['/' + model["partition"] + '/' +
                                index_snat_name]
            try:
                LOG.debug("Calling SNAT pool create: %s" % model)
                self.snatpool_manager.create(bigip, model)
                LOG.debug("SNAT pool created: %s" % model)

            except HTTPError as err:
                LOG.error("Create SNAT pool failed %s" % err.message)
                if err.response.status_code == 409:
                    try:
                        snatpool = self.snatpool_manager.load(
                            bigip,
                            name=model["name"],
                            partition=model["partition"]
                        )
                        snatpool.members.append(model["members"])
                        snatpool.update()
                    except HTTPError as err:
                        LOG.error("Update SNAT pool failed %s" % err.message)

            if self.l3_binding:
                self.l3_binding.bind_address(subnet_id=subnet['id'],
                                             ip_address=ip_address)

        bigip.assured_tenant_snat_subnets[tenant_id].append(subnet['id'])

    def delete_bigip_snats(self, bigip, subnetinfo, tenant_id):
        # Assure shared snat configuration (which syncs) is deleted.
        #
        if not subnetinfo['network']:
            LOG.error('Attempted to delete selfip and snats '
                      'for missing network ... skipping.')
            return set()

        return self._delete_bigip_snats(bigip, subnetinfo, tenant_id)

    def _remove_assured_tenant_snat_subnet(self, bigip, tenant_id, subnet):
        # Remove ref for the subnet for this tenant"""
        if tenant_id in bigip.assured_tenant_snat_subnets:
            tenant_snat_subnets = \
                bigip.assured_tenant_snat_subnets[tenant_id]
            if tenant_snat_subnets and subnet['id'] in tenant_snat_subnets:
                LOG.debug(
                    'Remove subnet id %s from '
                    'bigip.assured_tenant_snat_subnets for tenant %s' %
                    (subnet['id'], tenant_id))
                tenant_snat_subnets.remove(subnet['id'])
            else:
                LOG.debug(
                    'Subnet id %s does not exist in '
                    'bigip.assured_tenant_snat_subnets for tenant %s' %
                    (subnet['id'], tenant_id))
        else:
            LOG.debug(
                'Tenant id %s does not exist in '
                'bigip.assured_tenant_snat_subnets' % tenant_id)

    def _delete_bigip_snats(self, bigip, subnetinfo, tenant_id):
        # Assure snats deleted in standalone mode """
        network = subnetinfo['network']
        subnet = subnetinfo['subnet']
        partition = self.driver.service_adapter.get_folder_name(tenant_id)
        deleted_names = set()
        in_use_subnets = set()
        # Delete SNATs on traffic-group-local-only
        snat_name = self._get_snat_name(subnet, tenant_id)
        for i in range(self.driver.conf.f5_snat_addresses_per_subnet):
            index_snat_name = snat_name + "_" + str(i)
            if self.l2_service.is_common_network(network):
                tmos_snat_name = '/Common/' + index_snat_name
            else:
                tmos_snat_name = index_snat_name

            if self.l3_binding:
                try:
                    snat_xlate = self.snat_translation_manager.load(
                        bigip, name=index_snat_name, partition=partition)
                    self.l3_binding.unbind_address(
                        subnet_id=subnet['id'], ip_address=snat_xlate.address)
                except HTTPError as err:
                    LOG.error("Load SNAT xlate failed %s" % err.message)

            # Remove translation address from tenant snat pool
            # This seems strange that name and partition are tenant_id
            # but that is what the v1 code was doing.
            # The v1 code was also comparing basename in some cases
            # which seems dangerous because the folder may be in play?
            #
            # Revised (jl): It appears that v2 SNATs are created with a
            # name, not tenant_id, so we need to load SNAT by name.
            LOG.debug('Remove translation address from tenant SNAT pool')
            try:
                snatpool = self.snatpool_manager.load(bigip,
                                                      index_snat_name,
                                                      partition)

                snatpool.members = [
                    member for member in snatpool.members
                    if os.path.basename(member) != tmos_snat_name
                ]

                # Delete snat pool if empty (no members)
                # In LBaaSv1 the snat.remove_from_pool() method did this if
                # there was only one member and it matched the one we were
                # deleting making this call basically useless, but the
                # code above makes this still necessary and probably what the
                # original authors intended anyway since there is logging here
                # but not in the snat.py module from LBaaSv1
                LOG.debug('Check if snat pool is empty')
                if not snatpool.members:
                    LOG.debug('Snat pool is empty - delete snatpool')
                    try:
                        snatpool.delete()
                    except HTTPError as err:
                        LOG.error("Delete SNAT pool failed %s" % err.message)
                else:
                    LOG.debug('Snat pool is not empty - update snatpool')
                    try:
                        snatpool.update()
                    except HTTPError as err:
                        LOG.error("Update SNAT pool failed %s" % err.message)
            except HTTPError as err:
                LOG.error("Failed to load SNAT pool %s" % err.message)

            # Check if subnet in use by any tenants/snatpools. If in use,
            # add subnet to hints list of subnets in use.
            self._remove_assured_tenant_snat_subnet(bigip, tenant_id, subnet)
            LOG.debug(
                'Check cache for subnet %s in use by other tenant' %
                subnet['id'])
            in_use_count = 0
            for loop_tenant_id in bigip.assured_tenant_snat_subnets:
                tenant_snat_subnets = \
                    bigip.assured_tenant_snat_subnets[loop_tenant_id]
                if subnet['id'] in tenant_snat_subnets:
                    LOG.debug(
                        'Subnet %s in use (tenant %s)' %
                        (subnet['id'], loop_tenant_id))
                    in_use_count += 1

            if in_use_count:
                in_use_subnets.add(subnet['id'])
            else:
                LOG.debug('Check subnet in use by any tenant')
                member_use_count = \
                    self.network_helper.get_snatpool_member_use_count(
                        bigip, subnet['id'])
                if member_use_count:
                    LOG.debug('Subnet in use - do not delete')
                    in_use_subnets.add(subnet['id'])
                else:
                    LOG.debug('Subnet not in use - delete')

            # Check if trans addr in use by any snatpool.  If not in use,
            # okay to delete associated neutron port.
            LOG.debug('Check trans addr %s in use.' % tmos_snat_name)
            in_use_count = \
                self.network_helper.get_snatpool_member_use_count(
                    bigip, tmos_snat_name)
            if not in_use_count:
                LOG.debug('Trans addr not in use - delete')
                deleted_names.add(index_snat_name)
            else:
                LOG.debug('Trans addr in use - do not delete')

        return deleted_names, in_use_subnets
Example #24
0
class L2ServiceBuilder(object):

    def __init__(self, driver, f5_global_routed_mode):
        self.conf = driver.conf
        self.driver = driver
        self.f5_global_routed_mode = f5_global_routed_mode
        self.vlan_binding = None
        self.fdb_connector = None
        self.interface_mapping = {}
        self.tagging_mapping = {}
        self.system_helper = SystemHelper()
        self.network_helper = NetworkHelper()
        self.service_adapter = ServiceModelAdapter(self.conf)

        if not f5_global_routed_mode:
            self.fdb_connector = FDBConnectorML2(self.conf)

        if self.conf.vlan_binding_driver:
            try:
                self.vlan_binding = importutils.import_object(
                    self.conf.vlan_binding_driver, self.conf, self)
            except ImportError:
                LOG.error('Failed to import VLAN binding driver: %s'
                          % self.conf.vlan_binding_driver)
                raise

        # map format is  phynet:interface:tagged
        for maps in self.conf.f5_external_physical_mappings:
            intmap = maps.split(':')
            net_key = str(intmap[0]).strip()
            if len(intmap) > 3:
                net_key = net_key + ':' + str(intmap[3]).strip()
            self.interface_mapping[net_key] = str(intmap[1]).strip()
            self.tagging_mapping[net_key] = str(intmap[2]).strip()
            LOG.debug('physical_network %s = interface %s, tagged %s'
                      % (net_key, intmap[1], intmap[2]))

    def initialize_vcmp_manager(self):
        '''Intialize the vCMP manager when the driver is ready.'''

        self.vcmp_manager = VcmpManager(self.driver)

    def post_init(self):
        if self.vlan_binding:
            LOG.debug(
                'Getting BIG-IP device interface for VLAN Binding')
            self.vlan_binding.register_bigip_interfaces()

    def tunnel_sync(self, tunnel_ips):
        if self.fdb_connector:
            self.fdb_connector.advertise_tunnel_ips(tunnel_ips)

    def set_tunnel_rpc(self, tunnel_rpc):
        # Provide FDB Connector with ML2 RPC access
        if self.fdb_connector:
            self.fdb_connector.set_tunnel_rpc(tunnel_rpc)

    def set_l2pop_rpc(self, l2pop_rpc):
        # Provide FDB Connector with ML2 RPC access
        if self.fdb_connector:
            self.fdb_connector.set_l2pop_rpc(l2pop_rpc)

    def set_context(self, context):
        if self.fdb_connector:
            self.fdb_connector.set_context(context)

    def is_common_network(self, network):
        # Does this network belong in the /Common folder?
        return network['shared'] or \
            (network['id'] in self.conf.common_network_ids) or \
            ('router:external' in network and
             network['router:external'] and
             self.conf.f5_common_external_networks)

    def get_vlan_name(self, network, hostname):
        # Construct a consistent vlan name
        net_key = network['provider:physical_network']
        net_type = network['provider:network_type']

        # look for host specific interface mapping
        if net_key + ':' + hostname in self.interface_mapping:
            interface = self.interface_mapping[net_key + ':' + hostname]
            tagged = self.tagging_mapping[net_key + ':' + hostname]
        elif net_key in self.interface_mapping:
            interface = self.interface_mapping[net_key]
            tagged = self.tagging_mapping[net_key]
        else:
            interface = self.interface_mapping['default']
            tagged = self.tagging_mapping['default']

        if tagged:
            vlanid = network['provider:segmentation_id']
        else:
            vlanid = 0

        if net_type == "flat":
            interface_name = str(interface).replace(".", "-")
            if (len(interface_name) > 15):
                LOG.warn(
                    "Interface name is greater than 15 chars in length")
            vlan_name = "flat-%s" % (interface_name)
        else:
            vlan_name = "vlan-%d" % (vlanid)

        return vlan_name

    def assure_bigip_network(self, bigip, network):
        # Ensure bigip has configured network object
        if not network:
            LOG.error('assure_bigip_network: '
                      'Attempted to assure a network with no id..skipping.')
            return

        if network['id'] in bigip.assured_networks:
            return

        if network['id'] in self.conf.common_network_ids:
            LOG.debug('assure_bigip_network: '
                      'Network is a common global network... skipping.')
            return

        LOG.debug("assure_bigip_network network: %s" % str(network))
        start_time = time()
        if self.is_common_network(network):
            network_folder = 'Common'
        else:
            network_folder = self.service_adapter.get_folder_name(
                network['tenant_id']
            )

        # setup all needed L2 network segments
        if network['provider:network_type'] == 'flat':
            network_name = self._assure_device_network_flat(
                network, bigip, network_folder)
        elif network['provider:network_type'] == 'vlan':
            network_name = self._assure_device_network_vlan(
                network, bigip, network_folder)
        elif network['provider:network_type'] == 'vxlan':
            network_name = self._assure_device_network_vxlan(
                network, bigip, network_folder)
        elif network['provider:network_type'] == 'gre':
            network_name = self._assure_device_network_gre(
                network, bigip, network_folder)
        elif network['provider:network_type'] == 'opflex':
            raise f5_ex.NetworkNotReady(
                "Opflex network segment definition required")
        else:
            error_message = 'Unsupported network type %s.' \
                            % network['provider:network_type'] + \
                            ' Cannot setup network.'
            LOG.error(error_message)
            raise f5_ex.InvalidNetworkType(error_message)
        bigip.assured_networks[network['id']] = network_name

        if time() - start_time > .001:
            LOG.debug("        assure bigip network took %.5f secs" %
                      (time() - start_time))

    def _assure_device_network_flat(self, network, bigip, network_folder):
        # Ensure bigip has configured flat vlan (untagged)
        vlan_name = ""
        interface = self.interface_mapping['default']
        vlanid = 0

        # Do we have host specific mappings?
        net_key = network['provider:physical_network']
        if net_key + ':' + bigip.hostname in \
                self.interface_mapping:
            interface = self.interface_mapping[
                net_key + ':' + bigip.hostname]
        # Do we have a mapping for this network
        elif net_key in self.interface_mapping:
            interface = self.interface_mapping[net_key]

        vlan_name = self.get_vlan_name(network,
                                       bigip.hostname)

        self._assure_vcmp_device_network(bigip,
                                         vlan={'name': vlan_name,
                                               'folder': network_folder,
                                               'id': vlanid,
                                               'interface': interface,
                                               'network': network})

        if self.vcmp_manager and self.vcmp_manager.get_vcmp_host(bigip):
            interface = None
        try:
            model = {'name': vlan_name,
                     'interface': interface,
                     'partition': network_folder,
                     'description': network['id'],
                     'route_domain_id': network['route_domain_id']}
            self.network_helper.create_vlan(bigip, model)
        except Exception as err:
            LOG.exception("%s", err.message)
            raise f5_ex.VLANCreationException("Failed to create flat network")

        return vlan_name

    def _assure_device_network_vlan(self, network, bigip, network_folder):
        # Ensure bigip has configured tagged vlan
        # VLAN names are limited to 64 characters including
        # the folder name, so we name them foolish things.
        vlan_name = ""
        interface = self.interface_mapping['default']
        tagged = self.tagging_mapping['default']

        # Do we have host specific mappings?
        net_key = network['provider:physical_network']
        if net_key + ':' + bigip.hostname in \
                self.interface_mapping:
            interface = self.interface_mapping[
                net_key + ':' + bigip.hostname]
            tagged = self.tagging_mapping[
                net_key + ':' + bigip.hostname]
        # Do we have a mapping for this network
        elif net_key in self.interface_mapping:
            interface = self.interface_mapping[net_key]
            tagged = self.tagging_mapping[net_key]

        if tagged:
            vlanid = network['provider:segmentation_id']
        else:
            vlanid = 0

        vlan_name = self.get_vlan_name(network,
                                       bigip.hostname)

        self._assure_vcmp_device_network(bigip,
                                         vlan={'name': vlan_name,
                                               'folder': network_folder,
                                               'id': vlanid,
                                               'interface': interface,
                                               'network': network})

        if self.vcmp_manager and self.vcmp_manager.get_vcmp_host(bigip):
            interface = None
        try:
            model = {'name': vlan_name,
                     'interface': interface,
                     'tag': vlanid,
                     'partition': network_folder,
                     'description': network['id'],
                     'route_domain_id': network['route_domain_id']}
            self.network_helper.create_vlan(bigip, model)
        except Exception as err:
            LOG.exception("%s", err.message)
            raise f5_ex.VLANCreationException(
                "Failed to create vlan: %s" % vlan_name
            )

        if self.vlan_binding:
            self.vlan_binding.allow_vlan(
                device_name=bigip.device_name,
                interface=interface,
                vlanid=vlanid
            )

        return vlan_name

    def _assure_device_network_vxlan(self, network, bigip, partition):
        # Ensure bigip has configured vxlan
        tunnel_name = ""

        if not bigip.local_ip:
            error_message = 'Cannot create tunnel %s on %s' \
                % (network['id'], bigip.hostname)
            error_message += ' no VTEP SelfIP defined.'
            LOG.error('VXLAN:' + error_message)
            raise f5_ex.MissingVTEPAddress('VXLAN:' + error_message)

        tunnel_name = _get_tunnel_name(network)
        # create the main tunnel entry for the fdb records
        payload = {'name': tunnel_name,
                   'partition': partition,
                   'profile': 'vxlan_ovs',
                   'key': network['provider:segmentation_id'],
                   'localAddress': bigip.local_ip,
                   'description': network['id'],
                   'route_domain_id': network['route_domain_id']}
        try:
            self.network_helper.create_multipoint_tunnel(bigip, payload)
        except Exception as err:
            LOG.exception("%s", err.message)
            raise f5_ex.VXLANCreationException(
                "Failed to create vxlan tunnel: %s" % tunnel_name
            )

        if self.fdb_connector:
            self.fdb_connector.notify_vtep_added(network, bigip.local_ip)

        return tunnel_name

    def _assure_device_network_gre(self, network, bigip, partition):
        tunnel_name = ""

        # Ensure bigip has configured gre tunnel
        if not bigip.local_ip:
            error_message = 'Cannot create tunnel %s on %s' \
                % (network['id'], bigip.hostname)
            error_message += ' no VTEP SelfIP defined.'
            LOG.error('L2GRE:' + error_message)
            raise f5_ex.MissingVTEPAddress('L2GRE:' + error_message)

        tunnel_name = _get_tunnel_name(network)
        payload = {'name': tunnel_name,
                   'partition': partition,
                   'profile': 'gre_ovs',
                   'key': network['provider:segmentation_id'],
                   'localAddress': bigip.local_ip,
                   'description': network['id'],
                   'route_domain_id': network['route_domain_id']}
        try:
            self.network_helper.create_multipoint_tunnel(bigip, payload)
        except Exception as err:
            LOG.exception("%s", err.message)
            raise f5_ex.VXLANCreationException(
                "Failed to create gre tunnel: %s" % tunnel_name
            )

        if self.fdb_connector:
            self.fdb_connector.notify_vtep_added(network, bigip.local_ip)

        return tunnel_name

    def _assure_vcmp_device_network(self, bigip, vlan):
        if not self.vcmp_manager:
            return

        vcmp_host = self.vcmp_manager.get_vcmp_host(bigip)
        if not vcmp_host:
            return

        # Create the VLAN on the vCMP Host
        model = {'name': vlan['name'],
                 'partition': 'Common',
                 'tag': vlan['id'],
                 'interface': vlan['interface'],
                 'description': vlan['network']['id'],
                 'route_domain_id': vlan['network']['route_domain_id']}
        try:
            self.network_helper.create_vlan(vcmp_host['bigip'], model)
            LOG.debug(('Created VLAN %s on vCMP Host %s' %
                       (vlan['name'], vcmp_host['bigip'].hostname)))
        except Exception as exc:
            LOG.error(
                ('Exception creating VLAN %s on vCMP Host %s:%s' %
                 (vlan['name'], vcmp_host['bigip'].hostname, exc)))

        # Associate the VLAN with the vCMP Guest, if necessary
        self.vcmp_manager.assoc_vlan_with_vcmp_guest(bigip, vlan)

    def delete_bigip_network(self, bigip, network):
        # Delete network on bigip
        if network['id'] in self.conf.common_network_ids:
            LOG.debug('skipping delete of common network %s'
                      % network['id'])
            return
        if self.is_common_network(network):
            network_folder = 'Common'
        else:
            network_folder = self.service_adapter.get_folder_name(
                network['tenant_id'])
        if network['provider:network_type'] == 'vlan':
            self._delete_device_vlan(bigip, network, network_folder)
        elif network['provider:network_type'] == 'flat':
            self._delete_device_flat(bigip, network, network_folder)
        elif network['provider:network_type'] == 'vxlan':
            self._delete_device_vxlan(bigip, network, network_folder)
        elif network['provider:network_type'] == 'gre':
            self._delete_device_gre(bigip, network, network_folder)
        elif network['provider:network_type'] == 'opflex':
            raise f5_ex.NetworkNotReady(
                "Opflex network segment definition required")
        else:
            LOG.error('Unsupported network type %s. Can not delete.'
                      % network['provider:network_type'])
        if network['id'] in bigip.assured_networks:
            del bigip.assured_networks[network['id']]

    def _delete_device_vlan(self, bigip, network, network_folder):
        # Delete tagged vlan on specific bigip
        vlan_name = self.get_vlan_name(network,
                                       bigip.hostname)
        try:
            self.network_helper.delete_vlan(
                bigip,
                vlan_name,
                partition=network_folder
            )
        except Exception as err:
            LOG.exception(err)
            LOG.error(
                "Failed to delete vlan: %s" % vlan_name)

        if self.vlan_binding:
            interface = self.interface_mapping['default']
            tagged = self.tagging_mapping['default']
            vlanid = 0
            # Do we have host specific mappings?
            net_key = network['provider:physical_network']
            if net_key + ':' + bigip.hostname in \
                    self.interface_mapping:
                interface = self.interface_mapping[
                    net_key + ':' + bigip.hostname]
                tagged = self.tagging_mapping[
                    net_key + ':' + bigip.hostname]
            # Do we have a mapping for this network
            elif net_key in self.interface_mapping:
                interface = self.interface_mapping[net_key]
                tagged = self.tagging_mapping[net_key]
            if tagged:
                vlanid = network['provider:segmentation_id']
            else:
                vlanid = 0

            self.vlan_binding.prune_vlan(
                device_name=bigip.device_name,
                interface=interface,
                vlanid=vlanid
            )

        self._delete_vcmp_device_network(bigip, vlan_name)

    def _delete_device_flat(self, bigip, network, network_folder):
        # Delete untagged vlan on specific bigip
        vlan_name = self.get_vlan_name(network,
                                       bigip.hostname)
        try:
            self.network_helper.delete_vlan(
                bigip,
                vlan_name,
                partition=network_folder
            )
        except Exception as err:
            LOG.exception(err)
            LOG.error(
                "Failed to delete vlan: %s" % vlan_name)

        self._delete_vcmp_device_network(bigip, vlan_name)

    def _delete_device_vxlan(self, bigip, network, network_folder):
        # Delete vxlan tunnel on specific bigip
        tunnel_name = _get_tunnel_name(network)

        try:
            self.network_helper.delete_all_fdb_entries(
                bigip,
                tunnel_name,
                partition=network_folder)

            self.network_helper.delete_tunnel(
                bigip,
                tunnel_name,
                partition=network_folder)
        except Exception as err:
            # Just log the exception, we want to continue cleanup
            LOG.exception(err)
            LOG.error(
                "Failed to delete vxlan tunnel: %s" % tunnel_name)

        if self.fdb_connector:
            self.fdb_connector.notify_vtep_removed(network, bigip.local_ip)

    def _delete_device_gre(self, bigip, network, network_folder):
        # Delete gre tunnel on specific bigip
        tunnel_name = _get_tunnel_name(network)

        try:
            self.network_helper.delete_all_fdb_entries(
                bigip,
                tunnel_name,
                partition=network_folder)

            self.network_helper.delete_tunnel(
                bigip,
                tunnel_name,
                partition=network_folder)

        except Exception as err:
            # Just log the exception, we want to continue cleanup
            LOG.exception(err)
            LOG.error(
                "Failed to delete gre tunnel: %s" % tunnel_name)

        if self.fdb_connector:
            self.fdb_connector.notify_vtep_removed(network, bigip.local_ip)

    def _delete_vcmp_device_network(self, bigip, vlan_name):
        '''Disassociated VLAN with vCMP Guest, then delete it from vCMP Host

        :param bigip: ManagementRoot object -- vCMP guest
        :param vlan_name: str -- name of vlan
        '''

        if not self.vcmp_manager:
            return
        vcmp_host = self.vcmp_manager.get_vcmp_host(bigip)
        if not vcmp_host:
            return
        self.vcmp_manager.disassoc_vlan_with_vcmp_guest(bigip, vlan_name)

    def add_bigip_fdbs(self, bigip, net_folder, fdb_info, vteps_by_type):
        # Add fdb records for a mac/ip with specified vteps
        network = fdb_info['network']
        net_type = network['provider:network_type']
        vteps_key = net_type + '_vteps'
        if vteps_key in vteps_by_type:
            vteps = vteps_by_type[vteps_key]
            if net_type == 'gre':
                self.add_gre_fdbs(bigip, net_folder, fdb_info, vteps)
            elif net_type == 'vxlan':
                self.add_vxlan_fdbs(bigip, net_folder, fdb_info, vteps)

    def add_gre_fdbs(self, bigip, net_folder, fdb_info, vteps):
        # Add gre fdb records
        network = fdb_info['network']
        ip_address = fdb_info['ip_address']
        mac_address = fdb_info['mac_address']
        tunnel_name = _get_tunnel_name(network)
        for vtep in vteps:
            if mac_address:
                mac_addr = mac_address
            else:
                mac_addr = _get_tunnel_fake_mac(network, vtep)
            self.network_helper.add_fdb_entry(
                bigip,
                tunnel_name=tunnel_name,
                partition=net_folder,
                mac_address=mac_addr,
                vtep_ip_address=vtep,
                arp_ip_address=ip_address)

    def add_vxlan_fdbs(self, bigip, net_folder, fdb_info, vteps):
        # Add vxlan fdb records
        network = fdb_info['network']
        ip_address = fdb_info['ip_address']
        mac_address = fdb_info['mac_address']
        tunnel_name = _get_tunnel_name(network)
        for vtep in vteps:
            if mac_address:
                mac_addr = mac_address
            else:
                mac_addr = _get_tunnel_fake_mac(network, vtep)

            self.network_helper.add_fdb_entry(
                bigip,
                tunnel_name=tunnel_name,
                partition=net_folder,
                mac_address=mac_addr,
                vtep_ip_address=vtep,
                arp_ip_address=ip_address)

    def delete_bigip_fdbs(self, bigip, net_folder, fdb_info, vteps_by_type):
        # Delete fdb records for a mac/ip with specified vteps
        network = fdb_info['network']
        net_type = network['provider:network_type']
        vteps_key = net_type + '_vteps'
        if vteps_key in vteps_by_type:
            vteps = vteps_by_type[vteps_key]
            if net_type == 'gre':
                self.delete_gre_fdbs(bigip, net_folder, fdb_info, vteps)
            elif net_type == 'vxlan':
                self.delete_vxlan_fdbs(bigip, net_folder, fdb_info, vteps)

    def delete_gre_fdbs(self, bigip, net_folder, fdb_info, vteps):
        # delete gre fdb records
        network = fdb_info['network']
        ip_address = fdb_info['ip_address']
        mac_address = fdb_info['mac_address']
        tunnel_name = _get_tunnel_name(network)
        for vtep in vteps:
            if mac_address:
                mac_addr = mac_address
            else:
                mac_addr = _get_tunnel_fake_mac(network, vtep)

            self.network_helper.delete_fdb_entry(
                bigip,
                tunnel_name=tunnel_name,
                mac_address=mac_addr,
                arp_ip_address=ip_address,
                partition=net_folder)

    def delete_vxlan_fdbs(self, bigip, net_folder, fdb_info, vteps):
        # delete vxlan fdb records
        network = fdb_info['network']
        ip_address = fdb_info['ip_address']
        mac_address = fdb_info['mac_address']
        tunnel_name = _get_tunnel_name(network)
        for vtep in vteps:
            if mac_address:
                mac_addr = mac_address
            else:
                mac_addr = _get_tunnel_fake_mac(network, vtep)
            self.network_helper.delete_fdb_entry(
                bigip,
                tunnel_name=tunnel_name,
                mac_address=mac_addr,
                arp_ip_address=ip_address,
                partition=net_folder)

    def add_bigip_fdb(self, bigip, fdb):
        # Add entries from the fdb relevant to the bigip
        for fdb_operation in \
            [{'network_type': 'vxlan',
              'get_tunnel_folder': self.network_helper.get_tunnel_folder,
              'fdb_method': self.network_helper.add_fdb_entries},

             {'network_type': 'gre',
              'get_tunnel_folder': self.network_helper.get_tunnel_folder,
              'fdb_method': self.network_helper.add_fdb_entries}]:
            self._operate_bigip_fdb(bigip, fdb, fdb_operation)

    def _operate_bigip_fdb(self, bigip, fdb, fdb_operation):
        """Add L2 records for MAC addresses behind tunnel endpoints.

            Description of fdb structure:
            {'<network_id>':
                'segment_id': <int>
                'ports': [ '<vtep>': ['<mac_address>': '<ip_address>'] ]
             '<network_id>':
                'segment_id':
                'ports': [ '<vtep>': ['<mac_address>': '<ip_address>'] ] }

            Sample real fdb structure:
            {u'45bbbce1-191b-4f7b-84c5-54c6c8243bd2':
                {u'segment_id': 1008,
                 u'ports':
                     {u'10.30.30.2': [[u'00:00:00:00:00:00', u'0.0.0.0'],
                                      [u'fa:16:3e:3d:7b:7f', u'10.10.1.4']]},
                 u'network_type': u'vxlan'}}
        """
        network_type = fdb_operation['network_type']
        get_tunnel_folder = fdb_operation['get_tunnel_folder']
        fdb_method = fdb_operation['fdb_method']

        for network in fdb:
            net_fdb = fdb[network]
            if net_fdb['network_type'] == network_type:
                net = {'name': network,
                       'provider:network_type': net_fdb['network_type'],
                       'provider:segmentation_id': net_fdb['segment_id']}
                tunnel_name = _get_tunnel_name(net)
                folder = get_tunnel_folder(bigip, tunnel_name=tunnel_name)
                net_info = {'network': network,
                            'folder': folder,
                            'tunnel_name': tunnel_name,
                            'net_fdb': net_fdb}
                fdbs = self._get_bigip_network_fdbs(bigip, net_info)
                if len(fdbs) > 0:
                    fdb_method(fdb_entries=fdbs)

    def _get_bigip_network_fdbs(self, bigip, net_info):
        # Get network fdb entries to add to a bigip
        if not net_info['folder']:
            return {}
        net_fdb = net_info['net_fdb']
        fdbs = {}
        for vtep in net_fdb['ports']:
            # bigip does not need to set fdb entries for local addresses
            if vtep == bigip.local_ip:
                continue

            # most net_info applies to the vtep
            vtep_info = dict(net_info)
            # but the network fdb is too broad so delete it
            del vtep_info['net_fdb']
            # use a slice of the fdb for the vtep instead
            vtep_info['vtep'] = vtep
            vtep_info['fdb_entries'] = net_fdb['ports'][vtep]

            self._merge_vtep_fdbs(vtep_info, fdbs)
        return fdbs

    def _merge_vtep_fdbs(self, vtep_info, fdbs):
        # Add L2 records for a specific network+vtep
        folder = vtep_info['folder']
        tunnel_name = vtep_info['tunnel_name']
        for entry in vtep_info['fdb_entries']:
            mac_address = entry[0]
            if mac_address == '00:00:00:00:00:00':
                continue
            ip_address = entry[1]

            # create/get tunnel data
            if tunnel_name not in fdbs:
                fdbs[tunnel_name] = {}
            tunnel_fdbs = fdbs[tunnel_name]
            # update tunnel folder
            tunnel_fdbs['folder'] = folder

            # maybe create records for tunnel
            if 'records' not in tunnel_fdbs:
                tunnel_fdbs['records'] = {}

            # add entry to records map keyed by mac address
            tunnel_fdbs['records'][mac_address] = \
                {'endpoint': vtep_info['vtep'], 'ip_address': ip_address}

    def update_bigip_fdb(self, bigip, fdb):
        # Update l2 records
        self.add_bigip_fdb(bigip, fdb)

    def remove_bigip_fdb(self, bigip, fdb):
        # Add L2 records for MAC addresses behind tunnel endpoints
        for fdb_operation in \
            [{'network_type': 'vxlan',
              'get_tunnel_folder': self.network_helper.get_tunnel_folder,
              'fdb_method': self.network_helper.delete_fdb_entries},
             {'network_type': 'gre',
              'get_tunnel_folder': self.network_helper.get_tunnel_folder,
              'fdb_method': self.network_helper.delete_fdb_entries}]:
            self._operate_bigip_fdb(bigip, fdb, fdb_operation)

    # Utilities
    def get_network_name(self, bigip, network):
        # This constructs a name for a tunnel or vlan interface
        preserve_network_name = False
        if network['id'] in self.conf.common_network_ids:
            network_name = self.conf.common_network_ids[network['id']]
            preserve_network_name = True
        elif network['provider:network_type'] == 'vlan':
            network_name = self.get_vlan_name(network,
                                              bigip.hostname)
        elif network['provider:network_type'] == 'flat':
            network_name = self.get_vlan_name(network,
                                              bigip.hostname)
        elif network['provider:network_type'] == 'vxlan':
            network_name = _get_tunnel_name(network)
        elif network['provider:network_type'] == 'gre':
            network_name = _get_tunnel_name(network)
        else:
            error_message = 'Unsupported network type %s.' \
                            % network['provider:network_type'] + \
                            ' Cannot setup selfip or snat.'
            LOG.error(error_message)
            raise f5_ex.InvalidNetworkType(error_message)
        return network_name, preserve_network_name
class NetworkServiceBuilder(object):

    def __init__(self, f5_global_routed_mode, conf, driver, l3_binding=None):
        self.f5_global_routed_mode = f5_global_routed_mode
        self.conf = conf
        self.driver = driver
        self.l3_binding = l3_binding
        self.l2_service = L2ServiceBuilder(driver, f5_global_routed_mode)

        self.bigip_selfip_manager = BigipSelfIpManager(
            self.driver, self.l2_service, self.driver.l3_binding)
        self.bigip_snat_manager = BigipSnatManager(
            self.driver, self.l2_service, self.driver.l3_binding)

        self.vlan_manager = resource_helper.BigIPResourceHelper(
            resource_helper.ResourceType.vlan)
        self.rds_cache = {}
        self.interface_mapping = self.l2_service.interface_mapping
        self.network_helper = NetworkHelper()
        self.service_adapter = self.driver.service_adapter

    def post_init(self):
        # Run and Post Initialization Tasks """
        # run any post initialized tasks, now that the agent
        # is fully connected
        self.l2_service.post_init()

    def tunnel_sync(self, tunnel_ips):
        self.l2_service.tunnel_sync(tunnel_ips)

    def set_tunnel_rpc(self, tunnel_rpc):
        # Provide FDB Connector with ML2 RPC access """
        self.l2_service.set_tunnel_rpc(tunnel_rpc)

    def set_l2pop_rpc(self, l2pop_rpc):
        # Provide FDB Connector with ML2 RPC access """
        self.l2_service.set_l2pop_rpc(l2pop_rpc)

    def initialize_vcmp(self):
        self.l2_service.initialize_vcmp_manager()

    def initialize_tunneling(self):
        # setup tunneling
        vtep_folder = self.conf.f5_vtep_folder
        vtep_selfip_name = self.conf.f5_vtep_selfip_name
        local_ips = []

        for bigip in self.driver.get_all_bigips():

            bigip.local_ip = None

            if not vtep_folder or vtep_folder.lower() == 'none':
                vtep_folder = 'Common'

            if vtep_selfip_name and \
               not vtep_selfip_name.lower() == 'none':

                # profiles may already exist
                # create vxlan_multipoint_profile`
                self.network_helper.create_vxlan_multipoint_profile(
                    bigip,
                    'vxlan_ovs',
                    partition='Common')
                # create l2gre_multipoint_profile
                self.network_helper.create_l2gre_multipoint_profile(
                    bigip,
                    'gre_ovs',
                    partition='Common')

                # find the IP address for the selfip for each box
                local_ip = self.bigip_selfip_manager.get_selfip_addr(
                    bigip,
                    vtep_selfip_name,
                    partition=vtep_folder
                )

                if local_ip:
                    bigip.local_ip = local_ip
                    local_ips.append(local_ip)
                else:
                    raise f5_ex.MissingVTEPAddress(
                        'device %s missing vtep selfip %s'
                        % (bigip.device_name,
                           '/' + vtep_folder + '/' +
                           vtep_selfip_name))
        return local_ips

    def prep_service_networking(self, service, traffic_group):
        # Assure network connectivity is established on all bigips
        if self.conf.f5_global_routed_mode or not service['loadbalancer']:
            return

        if self.conf.use_namespaces:
            try:
                LOG.debug("Annotating the service definition networks "
                          "with route domain ID.")
                self._annotate_service_route_domains(service)
            except Exception as err:
                LOG.exception(err)
                raise f5_ex.RouteDomainCreationException(
                    "Route domain annotation error")

        # Per Device Network Connectivity (VLANs or Tunnels)
        subnetsinfo = self._get_subnets_to_assure(service)
        for (assure_bigip, subnetinfo) in (
                itertools.product(self.driver.get_all_bigips(), subnetsinfo)):
            LOG.debug("Assuring per device network connectivity "
                      "for %s on subnet %s." % (assure_bigip.hostname,
                                                subnetinfo['subnet']))

            # Make sure the L2 network is established
            self.l2_service.assure_bigip_network(
                assure_bigip, subnetinfo['network'])

            # Connect the BigIP device to network, by getting
            # a self-ip address on the subnet.
            self.bigip_selfip_manager.assure_bigip_selfip(
                assure_bigip, service, subnetinfo)

        # L3 Shared Config
        assure_bigips = self.driver.get_config_bigips()
        LOG.debug("Getting subnetinfo for ...")
        LOG.debug(assure_bigips)
        for subnetinfo in subnetsinfo:
            if self.conf.f5_snat_addresses_per_subnet > 0:
                self._assure_subnet_snats(assure_bigips, service, subnetinfo)

            if subnetinfo['is_for_member'] and not self.conf.f5_snat_mode:
                try:
                    self._allocate_gw_addr(subnetinfo)
                except KeyError as err:
                    raise f5_ex.VirtualServerCreationException(err.message)

                for assure_bigip in assure_bigips:
                    # If we are not using SNATS, attempt to become
                    # the subnet's default gateway.
                    self.bigip_selfip_manager.assure_gateway_on_subnet(
                        assure_bigip, subnetinfo, traffic_group)

    def _annotate_service_route_domains(self, service):
        # Add route domain notation to pool member and vip addresses.
        LOG.debug("Service before route domains: %s" % service)
        tenant_id = service['loadbalancer']['tenant_id']
        self.update_rds_cache(tenant_id)

        if 'members' in service:
            for member in service['members']:
                if 'address' in member:
                    LOG.debug("processing member %s" % member['address'])
                    if 'network_id' in member and member['network_id']:
                        member_network = (
                            self.service_adapter.get_network_from_service(
                                service,
                                member['network_id']
                            ))
                        member_subnet = (
                            self.service_adapter.get_subnet_from_service(
                                service,
                                member['subnet_id']
                            ))
                        if member_network:
                            self.assign_route_domain(
                                tenant_id, member_network, member_subnet)
                            rd_id = (
                                '%' + str(member_network['route_domain_id'])
                            )
                            member['address'] += rd_id
                    else:
                        member['address'] += '%0'

        if 'vip_address' in service['loadbalancer']:
            loadbalancer = service['loadbalancer']
            if 'network_id' in loadbalancer:
                lb_network = self.service_adapter.get_network_from_service(
                    service, loadbalancer['network_id'])
                vip_subnet = self.service_adapter.get_subnet_from_service(
                    service, loadbalancer['vip_subnet_id'])
                self.assign_route_domain(
                    tenant_id, lb_network, vip_subnet)
                rd_id = '%' + str(lb_network['route_domain_id'])
                service['loadbalancer']['vip_address'] += rd_id
            else:
                service['loadbalancer']['vip_address'] += '%0'
        LOG.debug("Service after route domains: %s" % service)

    def assign_route_domain(self, tenant_id, network, subnet):
        # Assign route domain for a network
        if self.l2_service.is_common_network(network):
            network['route_domain_id'] = 0
            return

        LOG.debug("assign route domain get from cache %s" % network)
        route_domain_id = self.get_route_domain_from_cache(network)
        if route_domain_id is not None:
            network['route_domain_id'] = route_domain_id
            return

        LOG.debug("max namespaces: %s" % self.conf.max_namespaces_per_tenant)
        LOG.debug("max namespaces == 1: %s" %
                  (self.conf.max_namespaces_per_tenant == 1))

        if self.conf.max_namespaces_per_tenant == 1:
            bigip = self.driver.get_bigip()
            LOG.debug("bigip before get_domain: %s" % bigip)
            partition_id = self.service_adapter.get_folder_name(
                tenant_id)
            tenant_rd = self.network_helper.get_route_domain(
                bigip, partition=partition_id)
            network['route_domain_id'] = tenant_rd.id
            return

        LOG.debug("assign route domain checking for available route domain")
        # need new route domain ?
        check_cidr = netaddr.IPNetwork(subnet['cidr'])
        placed_route_domain_id = None
        for route_domain_id in self.rds_cache[tenant_id]:
            LOG.debug("checking rd %s" % route_domain_id)
            rd_entry = self.rds_cache[tenant_id][route_domain_id]
            overlapping_subnet = None
            for net_shortname in rd_entry:
                LOG.debug("checking net %s" % net_shortname)
                net_entry = rd_entry[net_shortname]
                for exist_subnet_id in net_entry['subnets']:
                    if exist_subnet_id == subnet['id']:
                        continue
                    exist_subnet = net_entry['subnets'][exist_subnet_id]
                    exist_cidr = exist_subnet['cidr']
                    if check_cidr in exist_cidr or exist_cidr in check_cidr:
                        overlapping_subnet = exist_subnet
                        LOG.debug('rd %s: overlaps with subnet %s id: %s' % (
                            (route_domain_id, exist_subnet, exist_subnet_id)))
                        break
                if overlapping_subnet:
                    # no need to keep looking
                    break
            if not overlapping_subnet:
                placed_route_domain_id = route_domain_id
                break

        if placed_route_domain_id is None:
            if (len(self.rds_cache[tenant_id]) <
                    self.conf.max_namespaces_per_tenant):
                placed_route_domain_id = self._create_aux_rd(tenant_id)
                self.rds_cache[tenant_id][placed_route_domain_id] = {}
                LOG.debug("Tenant %s now has %d route domains" %
                          (tenant_id, len(self.rds_cache[tenant_id])))
            else:
                raise Exception("Cannot allocate route domain")

        LOG.debug("Placed in route domain %s" % placed_route_domain_id)
        rd_entry = self.rds_cache[tenant_id][placed_route_domain_id]

        net_short_name = self.get_neutron_net_short_name(network)
        if net_short_name not in rd_entry:
            rd_entry[net_short_name] = {'subnets': {}}
        net_subnets = rd_entry[net_short_name]['subnets']
        net_subnets[subnet['id']] = {'cidr': check_cidr}
        network['route_domain_id'] = placed_route_domain_id

    def _create_aux_rd(self, tenant_id):
        # Create a new route domain
        route_domain_id = None
        for bigip in self.driver.get_all_bigips():
            partition_id = self.service_adapter.get_folder_name(tenant_id)
            bigip_route_domain_id = self.network_helper.create_route_domain(
                bigip,
                partition=partition_id,
                strictness=self.conf.f5_route_domain_strictness,
                is_aux=True)
            if route_domain_id is None:
                route_domain_id = bigip_route_domain_id.id
            elif bigip_route_domain_id.id != route_domain_id:
                # FixME error
                LOG.debug(
                    "Bigips allocated two different route domains!: %s %s"
                    % (bigip_route_domain_id, route_domain_id))
        LOG.debug("Allocated route domain %s for tenant %s"
                  % (route_domain_id, tenant_id))
        return route_domain_id

    # The purpose of the route domain subnet cache is to
    # determine whether there is an existing bigip
    # subnet that conflicts with a new one being
    # assigned to the route domain.
    """
    # route domain subnet cache
    rds_cache =
        {'<tenant_id>': {
            {'0': {
                '<network type>-<segmentation id>': [
                    'subnets': [
                        '<subnet id>': {
                            'cidr': '<cidr>'
                        }
                ],
            '1': {}}}}
    """
    def update_rds_cache(self, tenant_id):
        # Update the route domain cache from bigips
        if tenant_id not in self.rds_cache:
            LOG.debug("rds_cache: adding tenant %s" % tenant_id)
            self.rds_cache[tenant_id] = {}
            for bigip in self.driver.get_all_bigips():
                self.update_rds_cache_bigip(tenant_id, bigip)
            LOG.debug("rds_cache updated: " + str(self.rds_cache))

    def update_rds_cache_bigip(self, tenant_id, bigip):
        # Update the route domain cache for this tenant
        # with information from bigip's vlan and tunnels
        LOG.debug("rds_cache: processing bigip %s" % bigip.device_name)

        route_domain_ids = self.network_helper.get_route_domain_ids(
            bigip,
            partition=self.service_adapter.get_folder_name(tenant_id))
        # LOG.debug("rds_cache: got bigip route domains: %s" % route_domains)
        for route_domain_id in route_domain_ids:
            self.update_rds_cache_bigip_rd_vlans(
                tenant_id, bigip, route_domain_id)

    def update_rds_cache_bigip_rd_vlans(
            self, tenant_id, bigip, route_domain_id):
        # Update the route domain cache with information
        # from the bigip vlans and tunnels from
        # this route domain
        LOG.debug("rds_cache: processing bigip %s rd %s"
                  % (bigip.device_name, route_domain_id))
        # this gets tunnels too
        partition_id = self.service_adapter.get_folder_name(tenant_id)
        rd_vlans = self.network_helper.get_vlans_in_route_domain_by_id(
            bigip,
            partition=partition_id,
            id=route_domain_id
        )
        LOG.debug("rds_cache: bigip %s rd %s vlans: %s"
                  % (bigip.device_name, route_domain_id, rd_vlans))
        if len(rd_vlans) == 0:
            LOG.debug("No vlans found for route domain: %d" %
                      (route_domain_id))
            return

        # make sure this rd has a cache entry
        tenant_entry = self.rds_cache[tenant_id]
        if route_domain_id not in tenant_entry:
            tenant_entry[route_domain_id] = {}

        # for every VLAN or TUNNEL on this bigip...
        for rd_vlan in rd_vlans:
            self.update_rds_cache_bigip_vlan(
                tenant_id, bigip, route_domain_id, rd_vlan)

    def update_rds_cache_bigip_vlan(
            self, tenant_id, bigip, route_domain_id, rd_vlan):
        # Update the route domain cache with information
        #    from the bigip vlan or tunnel
        LOG.debug("rds_cache: processing bigip %s rd %d vlan %s"
                  % (bigip.device_name, route_domain_id, rd_vlan))
        net_short_name = self.get_bigip_net_short_name(
            bigip, tenant_id, rd_vlan)

        # make sure this net has a cache entry
        tenant_entry = self.rds_cache[tenant_id]
        rd_entry = tenant_entry[route_domain_id]
        if net_short_name not in rd_entry:
            rd_entry[net_short_name] = {'subnets': {}}
        net_subnets = rd_entry[net_short_name]['subnets']

        partition_id = self.service_adapter.get_folder_name(tenant_id)
        LOG.debug("Calling get_selfips with: partition %s and vlan_name %s",
                  partition_id, rd_vlan)
        selfips = self.bigip_selfip_manager.get_selfips(
            bigip,
            partition=partition_id,
            vlan_name=rd_vlan
        )

        LOG.debug("rds_cache: got selfips")
        for selfip in selfips:
            LOG.debug("rds_cache: processing bigip %s rd %s vlan %s self %s" %
                      (bigip.device_name, route_domain_id, rd_vlan,
                       selfip.name))
            if bigip.device_name not in selfip.name:
                LOG.error("rds_cache: Found unexpected selfip %s for tenant %s"
                          % (selfip.name, tenant_id))
                continue
            subnet_id = selfip.name.split(bigip.device_name + '-')[1]

            # convert 10.1.1.1%1/24 to 10.1.1.1/24
            (addr, netbits) = selfip.address.split('/')
            addr = addr.split('%')[0]
            selfip.address = addr + '/' + netbits

            # selfip addresses will have slash notation: 10.1.1.1/24
            netip = netaddr.IPNetwork(selfip.address)
            LOG.debug("rds_cache: updating subnet %s with %s"
                      % (subnet_id, str(netip.cidr)))
            net_subnets[subnet_id] = {'cidr': netip.cidr}
            LOG.debug("rds_cache: now %s" % self.rds_cache)

    def get_route_domain_from_cache(self, network):
        # Get route domain from cache by network
        net_short_name = self.get_neutron_net_short_name(network)
        for tenant_id in self.rds_cache:
            tenant_cache = self.rds_cache[tenant_id]
            for route_domain_id in tenant_cache:
                if net_short_name in tenant_cache[route_domain_id]:
                    return route_domain_id

    def remove_from_rds_cache(self, network, subnet):
        # Get route domain from cache by network
        LOG.debug("remove_from_rds_cache")
        net_short_name = self.get_neutron_net_short_name(network)
        for tenant_id in self.rds_cache:
            LOG.debug("rds_cache: processing remove for %s" % tenant_id)
            deleted_rds = []
            tenant_cache = self.rds_cache[tenant_id]
            for route_domain_id in tenant_cache:
                if net_short_name in tenant_cache[route_domain_id]:
                    net_entry = tenant_cache[route_domain_id][net_short_name]
                    if subnet['id'] in net_entry['subnets']:
                        del net_entry['subnets'][subnet['id']]
                        if len(net_entry['subnets']) == 0:
                            del net_entry['subnets']
                    if len(tenant_cache[route_domain_id][net_short_name]) == 0:
                        del tenant_cache[route_domain_id][net_short_name]
                if len(self.rds_cache[tenant_id][route_domain_id]) == 0:
                    deleted_rds.append(route_domain_id)
            for rd in deleted_rds:
                LOG.debug("removing route domain %d from tenant %s" %
                          (rd, tenant_id))
                del self.rds_cache[tenant_id][rd]

    def get_bigip_net_short_name(self, bigip, tenant_id, network_name):
        # Return <network_type>-<seg_id> for bigip network
        LOG.debug("get_bigip_net_short_name: %s:%s" % (
            tenant_id, network_name))
        partition_id = self.service_adapter.get_folder_name(tenant_id)
        LOG.debug("network_name %s", network_name.split('/'))
        network_name = network_name.split("/")[-1]
        if 'tunnel-gre-' in network_name:
            tunnel_key = self.network_helper.get_tunnel_key(
                bigip,
                network_name,
                partition=partition_id
            )
            return 'gre-%s' % tunnel_key
        elif 'tunnel-vxlan-' in network_name:
            LOG.debug("Getting tunnel key for VXLAN: %s", network_name)
            tunnel_key = self.network_helper.get_tunnel_key(
                bigip,
                network_name,
                partition=partition_id
            )
            return 'vxlan-%s' % tunnel_key
        else:
            LOG.debug("Getting tunnel key for VLAN: %s", network_name)
            vlan_id = self.network_helper.get_vlan_id(bigip,
                                                      name=network_name,
                                                      partition=partition_id)
            return 'vlan-%s' % vlan_id

    @staticmethod
    def get_neutron_net_short_name(network):
        # Return <network_type>-<seg_id> for neutron network
        net_type = network['provider:network_type']
        net_seg_key = network['provider:segmentation_id']
        return net_type + '-' + str(net_seg_key)

    def _assure_subnet_snats(self, assure_bigips, service, subnetinfo):
        # Ensure snat for subnet exists on bigips
        tenant_id = service['loadbalancer']['tenant_id']
        subnet = subnetinfo['subnet']
        snats_per_subnet = self.conf.f5_snat_addresses_per_subnet

        assure_bigips = \
            [bigip for bigip in assure_bigips
                if tenant_id not in bigip.assured_tenant_snat_subnets or
                subnet['id'] not in
                bigip.assured_tenant_snat_subnets[tenant_id]]

        LOG.debug("_assure_subnet_snats: getting snat addrs for: %s" %
                  subnet['id'])
        if len(assure_bigips):
            snat_addrs = self.bigip_snat_manager.get_snat_addrs(
                subnetinfo, tenant_id, snats_per_subnet)

            if len(snat_addrs) != snats_per_subnet:
                raise f5_ex.SNAT_CreationException(
                    "Unable to satisfy request to allocate %d "
                    "snats.  Actual SNAT count: %d SNATs" %
                    (snats_per_subnet, len(snat_addrs)))
            for assure_bigip in assure_bigips:
                self.bigip_snat_manager.assure_bigip_snats(
                    assure_bigip, subnetinfo, snat_addrs, tenant_id)

    def _allocate_gw_addr(self, subnetinfo):
        # Create a name for the port and for the IP Forwarding
        # Virtual Server as well as the floating Self IP which
        # will answer ARP for the members
        need_port_for_gateway = False
        network = subnetinfo['network']
        subnet = subnetinfo['subnet']
        if not network or not subnet:
            LOG.error('Attempted to create default gateway'
                      ' for network with no id...skipping.')
            return

        if not subnet['gateway_ip']:
            raise KeyError("attempting to create gateway on subnet without "
                           "gateway ip address specified.")

        gw_name = "gw-" + subnet['id']
        ports = self.driver.plugin_rpc.get_port_by_name(port_name=gw_name)
        if len(ports) < 1:
            need_port_for_gateway = True

        # There was no port on this agent's host, so get one from Neutron
        if need_port_for_gateway:
            try:
                rpc = self.driver.plugin_rpc
                new_port = rpc.create_port_on_subnet_with_specific_ip(
                    subnet_id=subnet['id'], mac_address=None,
                    name=gw_name, ip_address=subnet['gateway_ip'])
                LOG.info('gateway IP for subnet %s will be port %s'
                         % (subnet['id'], new_port['id']))
            except Exception as exc:
                ermsg = 'Invalid default gateway for subnet %s:%s - %s.' \
                    % (subnet['id'],
                       subnet['gateway_ip'],
                       exc.message)
                ermsg += " SNAT will not function and load balancing"
                ermsg += " support will likely fail. Enable f5_snat_mode."
                LOG.exception(ermsg)
        return True

    def post_service_networking(self, service, all_subnet_hints):
        # Assure networks are deleted from big-ips
        if self.conf.f5_global_routed_mode:
            return

        # L2toL3 networking layer
        # Non Shared Config -  Local Per BIG-IP
        self.update_bigip_l2(service)

        # Delete shared config objects
        deleted_names = set()
        for bigip in self.driver.get_config_bigips():
            LOG.debug('post_service_networking: calling '
                      '_assure_delete_networks del nets sh for bigip %s %s'
                      % (bigip.device_name, all_subnet_hints))
            subnet_hints = all_subnet_hints[bigip.device_name]
            deleted_names = deleted_names.union(
                self._assure_delete_nets_shared(bigip, service,
                                                subnet_hints))

        # Delete non shared config objects
        for bigip in self.driver.get_all_bigips():
            LOG.debug('    post_service_networking: calling '
                      '    _assure_delete_networks del nets ns for bigip %s'
                      % bigip.device_name)

            subnet_hints = all_subnet_hints[bigip.device_name]

            deleted_names = deleted_names.union(
                self._assure_delete_nets_nonshared(
                    bigip, service, subnet_hints)
            )

        for port_name in deleted_names:
            LOG.debug('    post_service_networking: calling '
                      '    del port %s'
                      % port_name)
            self.driver.plugin_rpc.delete_port_by_name(
                port_name=port_name)

    def update_bigip_l2(self, service):
        # Update fdb entries on bigip
        loadbalancer = service['loadbalancer']
        service_adapter = self.service_adapter

        for bigip in self.driver.get_all_bigips():
            for member in service['members']:
                LOG.debug("update_bigip_l2 update service members")
                member['network'] = service_adapter.get_network_from_service(
                    service,
                    member['network_id']
                )
                member_status = member['provisioning_status']
                if member_status == plugin_const.PENDING_DELETE:
                    self.delete_bigip_member_l2(bigip, loadbalancer, member)
                else:
                    self.update_bigip_member_l2(bigip, loadbalancer, member)

            if "network_id" not in loadbalancer:
                LOG.error("update_bigip_l2, expected network ID")
                return

            LOG.debug("update_bigip_l2 get network for ID %s" %
                      loadbalancer["network_id"])
            loadbalancer['network'] = service_adapter.get_network_from_service(
                service,
                loadbalancer['network_id']
            )
            lb_status = loadbalancer['provisioning_status']
            if lb_status == plugin_const.PENDING_DELETE:
                self.delete_bigip_vip_l2(bigip, loadbalancer)
            else:
                LOG.debug("update_bigip_l2 calling update_bigip_vip_l2")
                self.update_bigip_vip_l2(bigip, loadbalancer)
            LOG.debug("update_bigip_l2 complete")

    def update_bigip_member_l2(self, bigip, loadbalancer, member):
        # update pool member l2 records
        network = member['network']
        if network:
            if self.l2_service.is_common_network(network):
                net_folder = 'Common'
            else:
                net_folder = self.service_adapter.get_folder_name(
                    loadbalancer['tenant_id']
                )
            fdb_info = {'network': network,
                        'ip_address': member['address'],
                        'mac_address': member['port']['mac_address']}
            self.l2_service.add_bigip_fdbs(
                bigip, net_folder, fdb_info, member)

    def delete_bigip_member_l2(self, bigip, loadbalancer, member):
        # Delete pool member l2 records
        network = member['network']
        if network:
            if 'port' in member:
                if self.l2_service.is_common_network(network):
                    net_folder = 'Common'
                else:
                    net_folder = self.service_adapter.get_folder_name(
                        loadbalancer['tenant_id']
                    )
                fdb_info = {'network': network,
                            'ip_address': member['address'],
                            'mac_address': member['port']['mac_address']}
                self.l2_service.delete_bigip_fdbs(
                    bigip, net_folder, fdb_info, member)
            else:
                LOG.error('Member on SDN has no port. Manual '
                          'removal on the BIG-IP will be '
                          'required. Was the vm instance '
                          'deleted before the pool member '
                          'was deleted?')

    def update_bigip_vip_l2(self, bigip, loadbalancer):
        # Update vip l2 records
        network = loadbalancer['network']
        if network:
            if self.l2_service.is_common_network(network):
                net_folder = 'Common'
            else:
                net_folder = self.service_adapter.get_folder_name(
                    loadbalancer['tenant_id']
                )
            fdb_info = {'network': network,
                        'ip_address': None,
                        'mac_address': None}
            self.l2_service.add_bigip_fdbs(
                bigip, net_folder, fdb_info, loadbalancer)

    def delete_bigip_vip_l2(self, bigip, loadbalancer):
        # Delete loadbalancer l2 records
        network = loadbalancer['network']
        if network:
            if self.l2_service.is_common_network(network):
                net_folder = 'Common'
            else:
                net_folder = self.service_adapter.get_folder_name(
                    loadbalancer['tenant_id']
                )
            fdb_info = {'network': network,
                        'ip_address': None,
                        'mac_address': None}
            self.l2_service.delete_bigip_fdbs(
                bigip, net_folder, fdb_info, loadbalancer)

    def _assure_delete_nets_shared(self, bigip, service, subnet_hints):
        # Assure shared configuration (which syncs) is deleted
        deleted_names = set()
        tenant_id = service['loadbalancer']['tenant_id']

        delete_gateway = self.bigip_selfip_manager.delete_gateway_on_subnet
        for subnetinfo in self._get_subnets_to_delete(bigip,
                                                      service,
                                                      subnet_hints):
            try:
                if not self.conf.f5_snat_mode:
                    gw_name = delete_gateway(bigip, subnetinfo)
                    deleted_names.add(gw_name)
                my_deleted_names, my_in_use_subnets = \
                    self.bigip_snat_manager.delete_bigip_snats(
                        bigip, subnetinfo, tenant_id)
                deleted_names = deleted_names.union(my_deleted_names)
                for in_use_subnetid in my_in_use_subnets:
                    subnet_hints['check_for_delete_subnets'].pop(
                        in_use_subnetid, None)
            except NeutronException as exc:
                LOG.error("assure_delete_nets_shared: exception: %s"
                          % str(exc.msg))
            except Exception as exc:
                LOG.error("assure_delete_nets_shared: exception: %s"
                          % str(exc.message))

        return deleted_names

    def _assure_delete_nets_nonshared(self, bigip, service, subnet_hints):
        # Delete non shared base objects for networks
        deleted_names = set()
        for subnetinfo in self._get_subnets_to_delete(bigip,
                                                      service,
                                                      subnet_hints):
            try:
                network = subnetinfo['network']
                if self.l2_service.is_common_network(network):
                    network_folder = 'Common'
                else:
                    network_folder = self.service_adapter.get_folder_name(
                        service['loadbalancer']['tenant_id'])

                subnet = subnetinfo['subnet']
                if self.conf.f5_populate_static_arp:
                    self.network_helper.arp_delete_by_subnet(
                        bigip,
                        subnet=subnet['cidr'],
                        mask=None,
                        partition=network_folder
                    )

                local_selfip_name = "local-" + bigip.device_name + \
                                    "-" + subnet['id']

                selfip_address = self.bigip_selfip_manager.get_selfip_addr(
                    bigip,
                    local_selfip_name,
                    partition=network_folder
                )

                if not selfip_address:
                    LOG.error("Failed to get self IP address %s in cleanup.",
                              local_selfip_name)

                self.bigip_selfip_manager.delete_selfip(
                    bigip,
                    local_selfip_name,
                    partition=network_folder
                )

                if self.l3_binding and selfip_address:
                    self.l3_binding.unbind_address(subnet_id=subnet['id'],
                                                   ip_address=selfip_address)

                deleted_names.add(local_selfip_name)

                self.l2_service.delete_bigip_network(bigip, network)

                if subnet['id'] not in subnet_hints['do_not_delete_subnets']:
                    subnet_hints['do_not_delete_subnets'].append(subnet['id'])

                self.remove_from_rds_cache(network, subnet)
                tenant_id = service['loadbalancer']['tenant_id']
                if tenant_id in bigip.assured_tenant_snat_subnets:
                    tenant_snat_subnets = \
                        bigip.assured_tenant_snat_subnets[tenant_id]
                    if subnet['id'] in tenant_snat_subnets:
                        tenant_snat_subnets.remove(subnet['id'])
            except NeutronException as exc:
                LOG.error("assure_delete_nets_nonshared: exception: %s"
                          % str(exc.msg))
            except Exception as exc:
                LOG.error("assure_delete_nets_nonshared: exception: %s"
                          % str(exc.message))

        return deleted_names

    def _get_subnets_to_delete(self, bigip, service, subnet_hints):
        # Clean up any Self IP, SNATs, networks, and folder for
        # services items that we deleted.
        subnets_to_delete = []
        for subnetinfo in subnet_hints['check_for_delete_subnets'].values():
            subnet = self.service_adapter.get_subnet_from_service(
                service, subnetinfo['subnet_id'])
            subnetinfo['subnet'] = subnet
            network = self.service_adapter.get_network_from_service(
                service, subnetinfo['network_id'])
            subnetinfo['network'] = network
            route_domain = network['route_domain_id']
            if not subnet:
                continue
            if not self._ips_exist_on_subnet(
                    bigip,
                    service,
                    subnet,
                    route_domain):
                subnets_to_delete.append(subnetinfo)

        return subnets_to_delete

    def _ips_exist_on_subnet(self, bigip, service, subnet, route_domain):
        # Does the big-ip have any IP addresses on this subnet?
        LOG.debug("_ips_exist_on_subnet entry %s rd %s"
                  % (str(subnet['cidr']), route_domain))
        route_domain = str(route_domain)
        ipsubnet = netaddr.IPNetwork(subnet['cidr'])

        # Are there any virtual addresses on this subnet?
        folder = self.service_adapter.get_folder_name(
            service['loadbalancer']['tenant_id']
        )
        virtual_services = self.network_helper.get_virtual_service_insertion(
            bigip,
            partition=folder
        )
        for virt_serv in virtual_services:
            (_, dest) = virt_serv.items()[0]
            LOG.debug("            _ips_exist_on_subnet: checking vip %s"
                      % str(dest['address']))
            if len(dest['address'].split('%')) > 1:
                vip_route_domain = dest['address'].split('%')[1]
            else:
                vip_route_domain = '0'
            if vip_route_domain != route_domain:
                continue
            vip_addr = strip_domain_address(dest['address'])
            if netaddr.IPAddress(vip_addr) in ipsubnet:
                LOG.debug("            _ips_exist_on_subnet: found")
                return True

        # If there aren't any virtual addresses, are there
        # node addresses on this subnet?
        nodes = self.network_helper.get_node_addresses(
            bigip,
            partition=folder
        )
        for node in nodes:
            LOG.debug("            _ips_exist_on_subnet: checking node %s"
                      % str(node))
            if len(node.split('%')) > 1:
                node_route_domain = node.split('%')[1]
            else:
                node_route_domain = '0'
            if node_route_domain != route_domain:
                continue
            node_addr = strip_domain_address(node)
            if netaddr.IPAddress(node_addr) in ipsubnet:
                LOG.debug("        _ips_exist_on_subnet: found")
                return True

        LOG.debug("            _ips_exist_on_subnet exit %s"
                  % str(subnet['cidr']))
        # nothing found
        return False

    def add_bigip_fdb(self, bigip, fdb):
        self.l2_service.add_bigip_fdb(bigip, fdb)

    def remove_bigip_fdb(self, bigip, fdb):
        self.l2_service.remove_bigip_fdb(bigip, fdb)

    def update_bigip_fdb(self, bigip, fdb):
        self.l2_service.update_bigip_fdb(bigip, fdb)

    def set_context(self, context):
        self.l2_service.set_context(context)

    def vlan_exists(self, bigip, network, folder='Common'):
        return self.vlan_manager.exists(bigip, name=network, partition=folder)

    def _get_subnets_to_assure(self, service):
        # Examine service and return active networks
        networks = dict()
        loadbalancer = service['loadbalancer']
        service_adapter = self.service_adapter

        lb_status = loadbalancer['provisioning_status']
        if lb_status != plugin_const.PENDING_DELETE:
            if 'network_id' in loadbalancer:
                network = service_adapter.get_network_from_service(
                    service,
                    loadbalancer['network_id']
                )
                subnet = service_adapter.get_subnet_from_service(
                    service,
                    loadbalancer['vip_subnet_id']
                )
                networks[network['id']] = {'network': network,
                                           'subnet': subnet,
                                           'is_for_member': False}

        for member in service['members']:
            if member['provisioning_status'] != plugin_const.PENDING_DELETE:
                if 'network_id' in member:
                    network = service_adapter.get_network_from_service(
                        service,
                        member['network_id']
                    )
                    subnet = service_adapter.get_subnet_from_service(
                        service,
                        member['subnet_id']
                    )
                    networks[network['id']] = {'network': network,
                                               'subnet': subnet,
                                               'is_for_member': True}
        return networks.values()
Example #26
0
class BigipTenantManager(object):
    """Create network connectivity for a bigip."""

    def __init__(self, conf, driver):
        """Create a BigipTenantManager."""
        self.conf = conf
        self.driver = driver
        self.system_helper = SystemHelper()
        self.network_helper = NetworkHelper()
        self.service_adapter = self.driver.service_adapter

    def assure_tenant_created(self, service):
        """Create tenant partition."""
        tenant_id = service["loadbalancer"]["tenant_id"]
        traffic_group = self.driver.service_to_traffic_group(service)
        traffic_group = "/Common/" + traffic_group
        service["traffic_group"] = traffic_group

        # create tenant folder
        folder_name = self.service_adapter.get_folder_name(tenant_id)
        LOG.debug("have folder name %s" % folder_name)
        for bigip in self.driver.get_config_bigips():
            if not self.system_helper.folder_exists(bigip, folder_name):
                folder = self.service_adapter.get_folder(service)
                self.system_helper.create_folder(bigip, folder)

        # folder must sync before route domains are created.
        self.driver.sync_if_clustered()

        # create tenant route domain
        if self.conf.use_namespaces:
            for bigip in self.driver.get_all_bigips():
                if not self.network_helper.route_domain_exists(bigip, folder_name):
                    self.network_helper.create_route_domain(bigip, folder_name, self.conf.f5_route_domain_strictness)

    def assure_tenant_cleanup(self, service, all_subnet_hints):
        """Delete tenant partition."""
        # Called for every bigip only in replication mode,
        # otherwise called once.
        for bigip in self.driver.get_config_bigips():
            subnet_hints = all_subnet_hints[bigip.device_name]
            self._assure_bigip_tenant_cleanup(bigip, service, subnet_hints)

    # called for every bigip only in replication mode.
    # otherwise called once
    def _assure_bigip_tenant_cleanup(self, bigip, service, subnet_hints):
        tenant_id = service["loadbalancer"]["tenant_id"]
        if self.conf.f5_sync_mode == "replication":
            self._remove_tenant_replication_mode(bigip, tenant_id)
        else:
            self._remove_tenant_autosync_mode(bigip, tenant_id)

    def _remove_tenant_replication_mode(self, bigip, tenant_id):
        # Remove tenant in replication sync-mode
        partition = self.service_adapter.get_folder_name(tenant_id)
        domain_names = self.network_helper.get_route_domain_names(bigip, partition)
        if domain_names:
            for domain_name in domain_names:
                self.network_helper.delete_route_domain(bigip, partition, domain_name)
        # sudslog = std_logging.getLogger('suds.client')
        # sudslog.setLevel(std_logging.FATAL)
        self.system_helper.force_root_folder(bigip)
        # sudslog.setLevel(std_logging.ERROR)

        try:
            self.system_helper.delete_folder(bigip, partition)
        except f5ex.SystemDeleteException:
            self.system_helper.purge_folder_contents(bigip, partition)
            self.system_helper.delete_folder(bigip, partition)

    def _remove_tenant_autosync_mode(self, bigip, tenant_id):
        partition = self.service_adapter.get_folder_name(tenant_id)

        # Remove tenant in autosync sync-mode
        # all domains must be gone before we attempt to delete
        # the folder or it won't delete due to not being empty
        for set_bigip in self.driver.get_all_bigips():
            self.network_helper.delete_route_domain(set_bigip, partition, None)
            sudslog = std_logging.getLogger("suds.client")
            sudslog.setLevel(std_logging.FATAL)
            self.system_helper.force_root_folder(set_bigip)
            sudslog.setLevel(std_logging.ERROR)

        # we need to ensure that the following folder deletion
        # is clearly the last change that needs to be synced.
        self.driver.sync_if_clustered()
        greenthread.sleep(5)
        try:
            self.system_helper.delete_folder(bigip, partition)
        except f5ex.SystemDeleteException:
            self.system_helper.purge_folder_contents(bigip, partition)
            self.system_helper.delete_folder(bigip, partition)

        # Need to make sure this folder delete syncs before
        # something else runs and changes the current folder to
        # the folder being deleted which will cause big problems.
        self.driver.sync_if_clustered()
class L2ServiceBuilder(object):

    def __init__(self, conf, f5_global_routed_mode):
        self.conf = conf
        self.f5_global_routed_mode = f5_global_routed_mode
        self.vlan_binding = None
        self.fdb_connector = None
        self.vcmp_manager = None
        self.interface_mapping = {}
        self.tagging_mapping = {}
        self.system_helper = SystemHelper()
        self.network_helper = NetworkHelper()
        self.service_adapter = ServiceModelAdapter(conf)

        if not f5_global_routed_mode:
            self.fdb_connector = FDBConnectorML2(conf)

        if self.conf.vlan_binding_driver:
            try:
                self.vlan_binding = importutils.import_object(
                    self.conf.vlan_binding_driver, self.conf, self)
            except ImportError:
                LOG.error('Failed to import VLAN binding driver: %s'
                          % self.conf.vlan_binding_driver)

        # map format is   phynet:interface:tagged
        for maps in self.conf.f5_external_physical_mappings:
            intmap = maps.split(':')
            net_key = str(intmap[0]).strip()
            if len(intmap) > 3:
                net_key = net_key + ':' + str(intmap[3]).strip()
            self.interface_mapping[net_key] = str(intmap[1]).strip()
            self.tagging_mapping[net_key] = str(intmap[2]).strip()
            LOG.debug('physical_network %s = interface %s, tagged %s'
                      % (net_key, intmap[1], intmap[2]))

    def post_init(self):
        if self.vlan_binding:
            LOG.debug(
                'Getting BIG-IP device interface for VLAN Binding')
            self.vlan_binding.register_bigip_interfaces()

    def tunnel_sync(self, tunnel_ips):
        if self.fdb_connector:
            self.fdb_connector.advertise_tunnel_ips(tunnel_ips)

    def set_tunnel_rpc(self, tunnel_rpc):
        # Provide FDB Connector with ML2 RPC access
        if self.fdb_connector:
            self.fdb_connector.set_tunnel_rpc(tunnel_rpc)

    def set_l2pop_rpc(self, l2pop_rpc):
        # Provide FDB Connector with ML2 RPC access
        if self.fdb_connector:
            self.fdb_connector.set_l2pop_rpc(l2pop_rpc)

    def set_context(self, context):
        if self.fdb_connector:
            self.fdb_connector.set_context(context)

    def is_common_network(self, network):
        # Does this network belong in the /Common folder?
        return network['shared'] or \
            (network['id'] in self.conf.common_network_ids) or \
            ('router:external' in network and
             network['router:external'] and
             self.conf.f5_common_external_networks)

    def get_vlan_name(self, network, hostname):
        # Construct a consistent vlan name
        net_key = network['provider:physical_network']
        # look for host specific interface mapping
        if net_key + ':' + hostname in self.interface_mapping:
            interface = self.interface_mapping[net_key + ':' + hostname]
            tagged = self.tagging_mapping[net_key + ':' + hostname]
        # look for specific interface mapping
        elif net_key in self.interface_mapping:
            interface = self.interface_mapping[net_key]
            tagged = self.tagging_mapping[net_key]
        # use default mapping
        else:
            interface = self.interface_mapping['default']
            tagged = self.tagging_mapping['default']

        if tagged:
            vlanid = network['provider:segmentation_id']
        else:
            vlanid = 0

        vlan_name = "vlan-" + \
                    str(interface).replace(".", "-") + \
                    "-" + str(vlanid)
        if len(vlan_name) > 15:
            vlan_name = 'vlan-tr-' + str(vlanid)
        return vlan_name

    def assure_bigip_network(self, bigip, network):
        # Ensure bigip has configured network object
        if not network:
            LOG.error('    assure_bigip_network: '
                      'Attempted to assure a network with no id..skipping.')
            return

        if network['id'] in bigip.assured_networks:
            return

        if network['id'] in self.conf.common_network_ids:
            LOG.debug('    assure_bigip_network: '
                      'Network is a common global network... skipping.')
            return

        LOG.debug("        assure_bigip_network network: %s" % str(network))
        start_time = time()
        if self.is_common_network(network):
            network_folder = 'Common'
        else:
            network_folder = self.service_adapter.get_folder_name(
                network['tenant_id']
            )

        # setup all needed L2 network segments
        if network['provider:network_type'] == 'flat':
            self._assure_device_network_flat(network, bigip, network_folder)
        elif network['provider:network_type'] == 'vlan':
            self._assure_device_network_vlan(network, bigip, network_folder)
        elif network['provider:network_type'] == 'vxlan':
            self._assure_device_network_vxlan(network, bigip, network_folder)
        elif network['provider:network_type'] == 'gre':
            self._assure_device_network_gre(network, bigip, network_folder)
        else:
            error_message = 'Unsupported network type %s.' \
                            % network['provider:network_type'] + \
                            ' Cannot setup network.'
            LOG.error(error_message)
            raise f5ex.InvalidNetworkType(error_message)
        bigip.assured_networks.append(network['id'])
        if time() - start_time > .001:
            LOG.debug("        assure bigip network took %.5f secs" %
                      (time() - start_time))

    def _assure_device_network_flat(self, network, bigip, network_folder):
        # Ensure bigip has configured flat vlan (untagged)
        interface = self.interface_mapping['default']
        vlanid = 0

        # Do we have host specific mappings?
        net_key = network['provider:physical_network']
        if net_key + ':' + bigip.hostname in \
                self.interface_mapping:
            interface = self.interface_mapping[
                net_key + ':' + bigip.hostname]
        # Do we have a mapping for this network
        elif net_key in self.interface_mapping:
            interface = self.interface_mapping[net_key]

        vlan_name = self.get_vlan_name(network,
                                       bigip.hostname)

        # TODO(Rich Browne): Implementation with VCMP
        self._assure_vcmp_device_network(bigip,
                                         vlan={'name': vlan_name,
                                               'folder': network_folder,
                                               'id': vlanid,
                                               'interface': interface,
                                               'network': network})

        if self.vcmp_manager and self.vcmp_manager.get_vcmp_host(bigip):
            interface = None

        model = {'name': vlan_name,
                 'interface': interface,
                 'partition': network_folder,
                 'description': network['id'],
                 'route_domain_id': network['route_domain_id']}
        self.network_helper.create_vlan(bigip, model)

    def _assure_device_network_vlan(self, network, bigip, network_folder):
        # Ensure bigip has configured tagged vlan
        # VLAN names are limited to 64 characters including
        # the folder name, so we name them foolish things.
        interface = self.interface_mapping['default']
        tagged = self.tagging_mapping['default']

        # Do we have host specific mappings?
        net_key = network['provider:physical_network']
        if net_key + ':' + bigip.hostname in \
                self.interface_mapping:
            interface = self.interface_mapping[
                net_key + ':' + bigip.hostname]
            tagged = self.tagging_mapping[
                net_key + ':' + bigip.hostname]
        # Do we have a mapping for this network
        elif net_key in self.interface_mapping:
            interface = self.interface_mapping[net_key]
            tagged = self.tagging_mapping[net_key]

        if tagged:
            vlanid = network['provider:segmentation_id']
        else:
            vlanid = 0

        vlan_name = self.get_vlan_name(network,
                                       bigip.hostname)

        self._assure_vcmp_device_network(bigip,
                                         vlan={'name': vlan_name,
                                               'folder': network_folder,
                                               'id': vlanid,
                                               'interface': interface,
                                               'network': network})

        if self.vcmp_manager and self.vcmp_manager.get_vcmp_host(bigip):
            interface = None

        model = {'name': vlan_name,
                 'interface': interface,
                 'tag': vlanid,
                 'partition': network_folder,
                 'description': network['id'],
                 'route_domain_id': network['route_domain_id']}
        self.network_helper.create_vlan(bigip, model)

        if self.vlan_binding:
            self.vlan_binding.allow_vlan(
                device_name=bigip.device_name,
                interface=interface,
                vlanid=vlanid
            )

    def _assure_device_network_vxlan(self, network, bigip, partition):
        # Ensure bigip has configured vxlan
        if not bigip.local_ip:
            error_message = 'Cannot create tunnel %s on %s' \
                % (network['id'], bigip.hostname)
            error_message += ' no VTEP SelfIP defined.'
            LOG.error('VXLAN:' + error_message)
            raise f5ex.MissingVTEPAddress('VXLAN:' + error_message)

        tunnel_name = _get_tunnel_name(network)
        # create the main tunnel entry for the fdb records
        payload = {'name': tunnel_name,
                   'partition': partition,
                   'profile': 'vxlan_ovs',
                   'key': network['provider:segmentation_id'],
                   'localAddress': bigip.local_ip,
                   'description': network['id'],
                   'route_domain_id': network['route_domain_id']}
        LOG.debug("---Creating VXLAN network---")
        LOG.debug(payload)
        self.network_helper.create_multipoint_tunnel(bigip, payload)
        LOG.debug("---VXLAN network Created---")
        if self.fdb_connector:
            self.fdb_connector.notify_vtep_added(network, bigip.local_ip)

    def _assure_device_network_gre(self, network, bigip, partition):
        # Ensure bigip has configured gre tunnel
        if not bigip.local_ip:
            error_message = 'Cannot create tunnel %s on %s' \
                % (network['id'], bigip.hostname)
            error_message += ' no VTEP SelfIP defined.'
            LOG.error('L2GRE:' + error_message)
            raise f5ex.MissingVTEPAddress('L2GRE:' + error_message)

        tunnel_name = _get_tunnel_name(network)
        payload = {'name': tunnel_name,
                   'partition': partition,
                   'profile': 'gre_ovs',
                   'key': network['provider:segmentation_id'],
                   'localAddress': bigip.local_ip,
                   'description': network['id'],
                   'route_domain_id': network['route_domain_id']}
        self.network_helper.create_multipoint_tunnel(bigip, payload)
        if self.fdb_connector:
            self.fdb_connector.notify_vtep_added(network, bigip.local_ip)

    def _is_vlan_assoc_with_vcmp_guest(self, bigip, vlan):
        if not self.vcmp_manager:
            return False

        # Is a vlan associated with a vcmp_guest?
        try:
            vcmp_host = self.vcmp_manager.get_vcmp_host(bigip)
            vcmp_guest = self.vcmp_manager.get_vcmp_guest(vcmp_host, bigip)
            vlan_list = vcmp_host['bigip'].system.sys_vcmp.get_vlan(
                [vcmp_guest['name']])
            full_path_vlan_name = '/Common/' + prefixed(vlan['name'])
            if full_path_vlan_name in vlan_list[0]:
                LOG.debug(('VLAN %s is associated with guest %s' %
                           (full_path_vlan_name, vcmp_guest['mgmt_addr'])))
                return True
        except Exception as exc:
            LOG.error(('Exception checking association of VLAN %s '
                       'to vCMP Guest %s: %s ' %
                       (vlan['name'], vcmp_guest['mgmt_addr'], exc)))
            return False
        LOG.debug(('VLAN %s is not associated with guest %s' %
                  (full_path_vlan_name, vcmp_guest['mgmt_addr'])))
        return False

    def _assure_vcmp_device_network(self, bigip, vlan):
        # REVISIT FOR VCMP SUPPORT
        # For vCMP Guests, add VLAN to vCMP Host, associate VLAN with
        # vCMP Guest, and remove VLAN from /Common on vCMP Guest.
        if not self.vcmp_manager:
            return

        vcmp_host = self.vcmp_manager.get_vcmp_host(bigip)
        if not vcmp_host:
            return

        # Create the VLAN on the vCMP Host
        try:
            model = {'name': vlan['name'],
                     'partition': '/Common',
                     'tag': vlan['id'],
                     'interface': vlan['interface'],
                     'description': vlan['network']['id'],
                     'route_domain_id': vlan['network']['route_domain_id']}
            self.network_helper.create_vlan(bigip, model)
            LOG.debug(('Created VLAN %s on vCMP Host %s' %
                       (vlan['name'], vcmp_host['bigip'].hostname)))
        except Exception as exc:
            LOG.error(
                ('Exception creating VLAN %s on vCMP Host %s:%s' %
                 (vlan['name'], vcmp_host['bigip'].hostname, exc)))

        # Determine if the VLAN is already associated with the vCMP Guest
        if self._is_vlan_assoc_with_vcmp_guest(bigip, vlan):
            return

        # Associate the VLAN with the vCMP Guest
        # MSG: bigip.system does not exist
        vcmp_guest = self.vcmp_manager.get_vcmp_guest(vcmp_host, bigip)
        try:
            vlan_seq = vcmp_host['bigip'].system.sys_vcmp.typefactory.\
                create('Common.StringSequence')
            vlan_seq.values = prefixed(vlan['name'])
            vlan_seq_seq = vcmp_host['bigip'].system.sys_vcmp.typefactory.\
                create('Common.StringSequenceSequence')
            vlan_seq_seq.values = [vlan_seq]
            vcmp_host['bigip'].system.sys_vcmp.add_vlan([vcmp_guest['name']],
                                                        vlan_seq_seq)
            LOG.debug(('Associated VLAN %s with vCMP Guest %s' %
                       (vlan['name'], vcmp_guest['mgmt_addr'])))
        except Exception as exc:
            LOG.error(('Exception associating VLAN %s to vCMP Guest %s: %s '
                      % (vlan['name'], vcmp_guest['mgmt_addr'], exc)))

        # Wait for the VLAN to propagate to /Common on vCMP Guest
        full_path_vlan_name = '/Common/' + prefixed(vlan['name'])
        vlan_created = False
        v = bigip.net.vlans.vlan
        try:
            for _ in range(0, 30):
                if v.exists(name=vlan['name'], partition='/Common'):
                    v.load(name=vlan['name'], partition='/Common')
                    vlan_created = True
                    break
                LOG.debug(('Wait for VLAN %s to be created on vCMP Guest %s.'
                          % (full_path_vlan_name, vcmp_guest['mgmt_addr'])))
                # sleep(1)

            if vlan_created:
                LOG.debug(('VLAN %s exists on vCMP Guest %s.' %
                          (full_path_vlan_name, vcmp_guest['mgmt_addr'])))
            else:
                LOG.error(('VLAN %s does not exist on vCMP Guest %s.' %
                          (full_path_vlan_name, vcmp_guest['mgmt_addr'])))
        except Exception as exc:
            LOG.error(('Exception waiting for vCMP Host VLAN %s to '
                       'be created on vCMP Guest %s: %s' %
                      (vlan['name'], vcmp_guest['mgmt_addr'], exc)))

        # Delete the VLAN from the /Common folder on the vCMP Guest
        if vlan_created:
            try:
                v.delete()
                LOG.debug(('Deleted VLAN %s from vCMP Guest %s' %
                          (full_path_vlan_name, vcmp_guest['mgmt_addr'])))
            except Exception as exc:
                LOG.error(
                    ('Exception deleting VLAN %s from vCMP Guest %s: %s' %
                     (full_path_vlan_name, vcmp_guest['mgmt_addr'], exc)))

    def delete_bigip_network(self, bigip, network):
        # Delete network on bigip
        if network['id'] in self.conf.common_network_ids:
            LOG.debug('skipping delete of common network %s'
                      % network['id'])
            return
        if self.is_common_network(network):
            network_folder = 'Common'
        else:
            network_folder = self.service_adapter.get_folder_name(
                network['tenant_id'])
        if network['provider:network_type'] == 'vlan':
            self._delete_device_vlan(bigip, network, network_folder)
        elif network['provider:network_type'] == 'flat':
            self._delete_device_flat(bigip, network, network_folder)
        elif network['provider:network_type'] == 'vxlan':
            self._delete_device_vxlan(bigip, network, network_folder)
        elif network['provider:network_type'] == 'gre':
            self._delete_device_gre(bigip, network, network_folder)
        else:
            LOG.error('Unsupported network type %s. Can not delete.'
                      % network['provider:network_type'])
        if network['id'] in bigip.assured_networks:
            bigip.assured_networks.remove(network['id'])

    def _delete_device_vlan(self, bigip, network, network_folder):
        # Delete tagged vlan on specific bigip
        vlan_name = self.get_vlan_name(network,
                                       bigip.hostname)
        self.network_helper.delete_vlan(
            bigip,
            vlan_name,
            partition=network_folder
        )
        if self.vlan_binding:
            interface = self.interface_mapping['default']
            tagged = self.tagging_mapping['default']
            vlanid = 0
            # Do we have host specific mappings?
            net_key = network['provider:physical_network']
            if net_key + ':' + bigip.hostname in \
                    self.interface_mapping:
                interface = self.interface_mapping[
                    net_key + ':' + bigip.hostname]
                tagged = self.tagging_mapping[
                    net_key + ':' + bigip.hostname]
            # Do we have a mapping for this network
            elif net_key in self.interface_mapping:
                interface = self.interface_mapping[net_key]
                tagged = self.tagging_mapping[net_key]
            if tagged:
                vlanid = network['provider:segmentation_id']
            else:
                vlanid = 0

            self.vlan_binding.prune_vlan(
                device_name=bigip.device_name,
                interface=interface,
                vlanid=vlanid
            )

        self._delete_vcmp_device_network(bigip, vlan_name)

    def _delete_device_flat(self, bigip, network, network_folder):
        # Delete untagged vlan on specific bigip
        vlan_name = self.get_vlan_name(network,
                                       bigip.hostname)

        self.network_helper.delete_vlan(
            bigip,
            vlan_name,
            partition=network_folder
        )

        self._delete_vcmp_device_network(bigip, vlan_name)

    def _delete_device_vxlan(self, bigip, network, network_folder):
        # Delete vxlan tunnel on specific bigip
        tunnel_name = _get_tunnel_name(network)

        self.network_helper.delete_all_fdb_entries(
            bigip,
            tunnel_name,
            partition=network_folder)

        self.network_helper.delete_tunnel(
            bigip,
            tunnel_name,
            partition=network_folder)

        if self.fdb_connector:
            self.fdb_connector.notify_vtep_removed(network, bigip.local_ip)

    def _delete_device_gre(self, bigip, network, network_folder):
        # Delete gre tunnel on specific bigip
        tunnel_name = _get_tunnel_name(network)

        self.network_helper.delete_all_fdb_entries(
            bigip,
            tunnel_name,
            partition=network_folder)

        self.network_helper.delete_tunnel(
            bigip,
            tunnel_name,
            partition=network_folder)

        if self.fdb_connector:
            self.fdb_connector.notify_vtep_removed(network, bigip.local_ip)

    def _delete_vcmp_device_network(self, bigip, vlan_name):
        # For vCMP Guests, disassociate VLAN from vCMP Guest and
        # delete VLAN from vCMP Host.

        if not self.vcmp_manager:
            return

        vcmp_host = self.vcmp_manager.get_vcmp_host(bigip)
        if not vcmp_host:
            return

        # Remove VLAN association from the vCMP Guest
        vcmp_guest = self.vcmp_manager.get_vcmp_guest(vcmp_host, bigip)
        try:
            # REVISIT: the extra attributes in vcmp bigips need to be
            # worked on.
            vlan_seq = vcmp_host['bigip'].system.sys_vcmp.typefactory.\
                create('Common.StringSequence')
            vlan_seq.values = prefixed(vlan_name)
            vlan_seq_seq = vcmp_host['bigip'].system.sys_vcmp.typefactory.\
                create('Common.StringSequenceSequence')
            vlan_seq_seq.values = [vlan_seq]
            vcmp_host['bigip'].system.sys_vcmp.remove_vlan(
                [vcmp_guest['name']], vlan_seq_seq)
            LOG.debug(('Removed VLAN %s association from vCMP Guest %s' %
                      (vlan_name, vcmp_guest['mgmt_addr'])))
        except Exception as exc:
            LOG.error(('Exception removing VLAN %s association from vCMP '
                       'Guest %s:%s' %
                       (vlan_name, vcmp_guest['mgmt_addr'], exc)))

        # Only delete VLAN if it is not in use by other vCMP Guests
        if self.vcmp_manager.get_vlan_use_count(vcmp_host, vlan_name):
            LOG.debug(('VLAN %s in use by other vCMP Guests on vCMP Host %s' %
                      (vlan_name, vcmp_host['bigip'].hostname)))
            return

        # Delete VLAN from vCMP Host.  This will fail if any other vCMP Guest
        # is using this VLAN
        try:
            self.network_helper.delete_vlan(
                vcmp_host['bigip'],
                vlan_name)
            LOG.debug(('Deleted VLAN %s from vCMP Host %s' %
                      (vlan_name, vcmp_host['bigip'].hostname)))
        except Exception as exc:
            LOG.error(('Exception deleting VLAN %s from vCMP Host %s:%s' %
                      (vlan_name, vcmp_host['bigip'].icontrol.hostname, exc)))

    def add_bigip_fdbs(self, bigip, net_folder, fdb_info, vteps_by_type):
        # Add fdb records for a mac/ip with specified vteps
        network = fdb_info['network']
        net_type = network['provider:network_type']
        vteps_key = net_type + '_vteps'
        if vteps_key in vteps_by_type:
            vteps = vteps_by_type[vteps_key]
            if net_type == 'gre':
                self.add_gre_fdbs(bigip, net_folder, fdb_info, vteps)
            elif net_type == 'vxlan':
                self.add_vxlan_fdbs(bigip, net_folder, fdb_info, vteps)

    def add_gre_fdbs(self, bigip, net_folder, fdb_info, vteps):
        # Add gre fdb records
        network = fdb_info['network']
        ip_address = fdb_info['ip_address']
        mac_address = fdb_info['mac_address']
        tunnel_name = _get_tunnel_name(network)
        for vtep in vteps:
            if mac_address:
                mac_addr = mac_address
            else:
                mac_addr = _get_tunnel_fake_mac(network, vtep)
            # REVISIT(Rich Browne) caller must provide folder and not tenant_id
            self.network_helper.add_fdb_entry(
                bigip,
                tunnel_name=tunnel_name,
                partition=net_folder,
                mac_address=mac_addr,
                vtep_ip_address=vtep,
                arp_ip_address=ip_address)

    def add_vxlan_fdbs(self, bigip, net_folder, fdb_info, vteps):
        # Add vxlan fdb records
        network = fdb_info['network']
        ip_address = fdb_info['ip_address']
        mac_address = fdb_info['mac_address']
        tunnel_name = _get_tunnel_name(network)
        for vtep in vteps:
            if mac_address:
                mac_addr = mac_address
            else:
                mac_addr = _get_tunnel_fake_mac(network, vtep)
            # REVISIT caller must provide folder and not tenant_id
            self.network_helper.add_fdb_entry(
                bigip,
                tunnel_name=tunnel_name,
                partition=net_folder,
                mac_address=mac_addr,
                vtep_ip_address=vtep,
                arp_ip_address=ip_address)

    def delete_bigip_fdbs(self, bigip, net_folder, fdb_info, vteps_by_type):
        # Delete fdb records for a mac/ip with specified vteps
        network = fdb_info['network']
        net_type = network['provider:network_type']
        vteps_key = net_type + '_vteps'
        if vteps_key in vteps_by_type:
            vteps = vteps_by_type[vteps_key]
            if net_type == 'gre':
                self.delete_gre_fdbs(bigip, net_folder, fdb_info, vteps)
            elif net_type == 'vxlan':
                self.delete_vxlan_fdbs(bigip, net_folder, fdb_info, vteps)

    def delete_gre_fdbs(self, bigip, net_folder, fdb_info, vteps):
        # delete gre fdb records
        network = fdb_info['network']
        ip_address = fdb_info['ip_address']
        mac_address = fdb_info['mac_address']
        tunnel_name = _get_tunnel_name(network)
        for vtep in vteps:
            if mac_address:
                mac_addr = mac_address
            else:
                mac_addr = _get_tunnel_fake_mac(network, vtep)

            self.network_helper.delete_fdb_entry(
                bigip,
                tunnel_name=tunnel_name,
                mac_address=mac_addr,
                arp_ip_address=ip_address,
                partition=net_folder)

    def delete_vxlan_fdbs(self, bigip, net_folder, fdb_info, vteps):
        # delete vxlan fdb records
        network = fdb_info['network']
        ip_address = fdb_info['ip_address']
        mac_address = fdb_info['mac_address']
        tunnel_name = _get_tunnel_name(network)
        for vtep in vteps:
            if mac_address:
                mac_addr = mac_address
            else:
                mac_addr = _get_tunnel_fake_mac(network, vtep)
            self.network_helper.delete_fdb_entry(
                bigip,
                tunnel_name=tunnel_name,
                mac_address=mac_addr,
                arp_ip_address=ip_address,
                partition=net_folder)

    def add_bigip_fdb(self, bigip, fdb):
        # Add entries from the fdb relevant to the bigip
        for fdb_operation in \
            [{'network_type': 'vxlan',
              'get_tunnel_folder': self.network_helper.get_tunnel_folder,
              'fdb_method': self.network_helper.add_fdb_entries},

             {'network_type': 'gre',
              'get_tunnel_folder': self.network_helper.get_tunnel_folder,
              'fdb_method': self.network_helper.add_fdb_entries}]:
            self._operate_bigip_fdb(bigip, fdb, fdb_operation)

    def _operate_bigip_fdb(self, bigip, fdb, fdb_operation):
        """Add L2 records for MAC addresses behind tunnel endpoints.

            Description of fdb structure:
            {'<network_id>':
                'segment_id': <int>
                'ports': [ '<vtep>': ['<mac_address>': '<ip_address>'] ]
             '<network_id>':
                'segment_id':
                'ports': [ '<vtep>': ['<mac_address>': '<ip_address>'] ] }

            Sample real fdb structure:
            {u'45bbbce1-191b-4f7b-84c5-54c6c8243bd2':
                {u'segment_id': 1008,
                 u'ports':
                     {u'10.30.30.2': [[u'00:00:00:00:00:00', u'0.0.0.0'],
                                      [u'fa:16:3e:3d:7b:7f', u'10.10.1.4']]},
                 u'network_type': u'vxlan'}}
        """
        network_type = fdb_operation['network_type']
        get_tunnel_folder = fdb_operation['get_tunnel_folder']
        fdb_method = fdb_operation['fdb_method']

        for network in fdb:
            net_fdb = fdb[network]
            if net_fdb['network_type'] == network_type:
                net = {'name': network,
                       'provider:network_type': net_fdb['network_type'],
                       'provider:segmentation_id': net_fdb['segment_id']}
                tunnel_name = _get_tunnel_name(net)
                folder = get_tunnel_folder(bigip, tunnel_name=tunnel_name)
                net_info = {'network': network,
                            'folder': folder,
                            'tunnel_name': tunnel_name,
                            'net_fdb': net_fdb}
                fdbs = self._get_bigip_network_fdbs(bigip, net_info)
                if len(fdbs) > 0:
                    fdb_method(fdb_entries=fdbs)

    def _get_bigip_network_fdbs(self, bigip, net_info):
        # Get network fdb entries to add to a bigip
        if not net_info['folder']:
            return {}
        net_fdb = net_info['net_fdb']
        fdbs = {}
        for vtep in net_fdb['ports']:
            # bigip does not need to set fdb entries for local addresses
            if vtep == bigip.local_ip:
                continue

            # most net_info applies to the vtep
            vtep_info = dict(net_info)
            # but the network fdb is too broad so delete it
            del vtep_info['net_fdb']
            # use a slice of the fdb for the vtep instead
            vtep_info['vtep'] = vtep
            vtep_info['fdb_entries'] = net_fdb['ports'][vtep]

            self._merge_vtep_fdbs(vtep_info, fdbs)
        return fdbs

    def _merge_vtep_fdbs(self, vtep_info, fdbs):
        # Add L2 records for a specific network+vtep
        folder = vtep_info['folder']
        tunnel_name = vtep_info['tunnel_name']
        for entry in vtep_info['fdb_entries']:
            mac_address = entry[0]
            if mac_address == '00:00:00:00:00:00':
                continue
            ip_address = entry[1]

            # create/get tunnel data
            if tunnel_name not in fdbs:
                fdbs[tunnel_name] = {}
            tunnel_fdbs = fdbs[tunnel_name]
            # update tunnel folder
            tunnel_fdbs['folder'] = folder

            # maybe create records for tunnel
            if 'records' not in tunnel_fdbs:
                tunnel_fdbs['records'] = {}

            # add entry to records map keyed by mac address
            tunnel_fdbs['records'][mac_address] = \
                {'endpoint': vtep_info['vtep'], 'ip_address': ip_address}

    def update_bigip_fdb(self, bigip, fdb):
        # Update l2 records
        self.add_bigip_fdb(bigip, fdb)

    def remove_bigip_fdb(self, bigip, fdb):
        # Add L2 records for MAC addresses behind tunnel endpoints
        for fdb_operation in \
            [{'network_type': 'vxlan',
              'get_tunnel_folder': self.network_helper.get_tunnel_folder,
              'fdb_method': self.network_helper.delete_fdb_entries},
             {'network_type': 'gre',
              'get_tunnel_folder': self.network_helper.get_tunnel_folder,
              'fdb_method': self.network_helper.delete_fdb_entries}]:
            self._operate_bigip_fdb(bigip, fdb, fdb_operation)

    # Utilities
    def get_network_name(self, bigip, network):
        # This constructs a name for a tunnel or vlan interface
        preserve_network_name = False
        if network['id'] in self.conf.common_network_ids:
            network_name = self.conf.common_network_ids[network['id']]
            preserve_network_name = True
        elif network['provider:network_type'] == 'vlan':
            network_name = self.get_vlan_name(network,
                                              bigip.hostname)
        elif network['provider:network_type'] == 'flat':
            network_name = self.get_vlan_name(network,
                                              bigip.hostname)
        elif network['provider:network_type'] == 'vxlan':
            network_name = _get_tunnel_name(network)
        elif network['provider:network_type'] == 'gre':
            network_name = _get_tunnel_name(network)
        else:
            error_message = 'Unsupported network type %s.' \
                            % network['provider:network_type'] + \
                            ' Cannot setup selfip or snat.'
            LOG.error(error_message)
            raise f5ex.InvalidNetworkType(error_message)
        return network_name, preserve_network_name
Example #28
0
class BigipSelfIpManager(object):
    def __init__(self, driver, l2_service, l3_binding):
        self.driver = driver
        self.l2_service = l2_service
        self.l3_binding = l3_binding
        self.selfip_manager = BigIPResourceHelper(ResourceType.selfip)
        self.network_helper = NetworkHelper()

    def _create_bigip_selfip(self, bigip, model):
        created = False
        if self.selfip_manager.exists(bigip,
                                      name=model['name'],
                                      partition=model['partition']):
            created = True
        else:
            try:
                self.selfip_manager.create(bigip, model)
                created = True
            except HTTPError as err:

                if (err.response.status_code == 400 and err.response.text.find(
                        "must be one of the vlans "
                        "in the associated route domain") > 0):
                    try:
                        self.network_helper.add_vlan_to_domain(
                            bigip,
                            name=model['vlan'],
                            partition=model['partition'])
                        self.selfip_manager.create(bigip, model)
                        created = True
                    except HTTPError as err:
                        LOG.exception("Error creating selfip %s. "
                                      "Repsponse status code: %s. "
                                      "Response message: %s." %
                                      (model["name"], err.response.status_code,
                                       err.message))
                        raise f5_ex.SelfIPCreationException("selfip")
                else:
                    LOG.exception("selfip creation error: %s(%s)" %
                                  (err.message, err.response.status_code))
                    raise
            except Exception as err:
                LOG.error("Failed to create selfip")
                LOG.exception(err.message)
                raise f5_ex.SelfIPCreationException("selfip creation")

        return created

    def assure_bigip_selfip(self, bigip, service, subnetinfo):
        u"""Ensure the BigIP has a selfip address on the tenant subnet."""

        network = None
        subnet = None

        if 'network' in subnetinfo:
            network = subnetinfo['network']
        if 'subnet' in subnetinfo:
            subnet = subnetinfo['subnet']

        if not network or not subnet:
            LOG.error('Attempted to create selfip and snats '
                      'for network with not id...')
            raise KeyError("network and subnet need to be specified")

        tenant_id = service['loadbalancer']['tenant_id']
        lb_id = service['loadbalancer']['id']

        # If we have already assured this subnet.. return.
        # Note this cache is periodically cleared in order to
        # force assurance that the configuration is present.
        if tenant_id in bigip.assured_tenant_snat_subnets and \
                subnet['id'] in bigip.assured_tenant_snat_subnets[tenant_id]:
            return True

        selfip_address = self._get_bigip_selfip_address(bigip, subnet, lb_id)
        if 'route_domain_id' not in network:
            LOG.error("network route domain is not set")
            raise KeyError()

        selfip_address += '%' + str(network['route_domain_id'])

        if self.l2_service.is_common_network(network):
            network_folder = 'Common'
        else:
            network_folder = self.driver.service_adapter.\
                get_folder_name(service['loadbalancer']['tenant_id'])

        # Get the name of the vlan.
        (network_name, preserve_network_name) = \
            self.l2_service.get_network_name(bigip, network)

        netmask = netaddr.IPNetwork(subnet['cidr']).prefixlen
        address = selfip_address + ("/%d" % netmask)
        model = {
            "name": "local-" + bigip.device_name + "-" + subnet['id'],
            "address": address,
            "vlan": network_name,
            "floating": "disabled",
            "partition": network_folder
        }
        self._create_bigip_selfip(bigip, model)

        if self.l3_binding:
            self.l3_binding.bind_address(subnet_id=subnet['id'],
                                         ip_address=selfip_address)

    def _get_bigip_selfip_address(self, bigip, subnet, device_id):
        u"""Ensure a selfip address is allocated on Neutron network."""
        # Get ip address for selfip to use on BIG-IP.
        if self.driver.conf.unlegacy_setting_placeholder:
            LOG.debug('setting vnic_type to normal instead of baremetal')
            vnic_type = "normal"
        else:
            vnic_type = "baremetal"

        selfip_address = ""
        selfip_name = "local-" + bigip.device_name + "-" + subnet['id']
        ports = self.driver.plugin_rpc.get_port_by_name(port_name=selfip_name)
        if len(ports) > 0:
            port = ports[0]
        else:
            port = self.driver.plugin_rpc.create_port_on_subnet(
                subnet_id=subnet['id'],
                mac_address=None,
                name=selfip_name,
                fixed_address_count=1,
                device_id=device_id,
                vnic_type=vnic_type)

        if port and 'fixed_ips' in port:
            fixed_ip = port['fixed_ips'][0]
            selfip_address = fixed_ip['ip_address']

        return selfip_address

    def assure_gateway_on_subnet(self, bigip, subnetinfo, traffic_group):
        """Ensure """
        network = None
        subnet = None

        if 'network' in subnetinfo:
            network = subnetinfo['network']
        if 'subnet' in subnetinfo:
            subnet = subnetinfo['subnet']

        if not network or not subnet:
            raise KeyError("network and subnet must be specified to create "
                           "gateway on subnet.")

        if not subnet['gateway_ip']:
            raise KeyError("attempting to create gateway on subnet without "
                           "gateway ip address specified.")

        if subnet['id'] in bigip.assured_gateway_subnets:
            return True

        (network_name, preserve_network_name) = \
            self.l2_service.get_network_name(bigip, network)

        if self.l2_service.is_common_network(network):
            network_folder = 'Common'
        else:
            network_folder = self.driver.service_adapter.\
                get_folder_name(subnet['tenant_id'])

        # Create a floating SelfIP for the given traffic-group.
        floating_selfip_name = "gw-" + subnet['id']
        netmask = netaddr.IPNetwork(subnet['cidr']).prefixlen
        address = subnet['gateway_ip'] + "%" + str(network['route_domain_id'])
        address += ("/%d" % (netmask))
        model = {
            'name': floating_selfip_name,
            'address': address,
            'vlan': network_name,
            'floating': True,
            'traffic-group': traffic_group,
            'partition': network_folder
        }

        if not self._create_bigip_selfip(bigip, model):
            LOG.error("failed to create gateway selfip")

        if self.l3_binding:
            self.l3_binding.bind_address(subnet_id=subnet['id'],
                                         ip_address=subnet['gateway_ip'])

        # Setup a wild card ip forwarding virtual service for this subnet
        gw_name = "gw-" + subnet['id']
        vs = bigip.tm.ltm.virtuals.virtual
        if not vs.exists(name=gw_name, partition=network_folder):
            try:
                vs.create(name=gw_name,
                          partition=network_folder,
                          destination='0.0.0.0:0',
                          mask='0.0.0.0',
                          vlansEnabled=True,
                          vlans=[network_name],
                          sourceAddressTranslation={'type': 'automap'},
                          ipForward=True)
            except Exception as err:
                LOG.exception(err)
                raise f5_ex.VirtualServerCreationException(
                    "Failed to create gateway virtual service on subnet %s",
                    subnet['id'])

        # Put the virtual server address in the specified traffic group
        virtual_address = bigip.tm.ltm.virtual_address_s.virtual_address
        try:
            obj = virtual_address.load(name='0.0.0.0',
                                       partition=network_folder)
            obj.modify(trafficGroup=traffic_group)
        except Exception as err:
            LOG.exception(err)
            raise f5_ex.VirtualServerCreationException(
                "Failed to add virtual address to traffic group %s",
                traffic_group)

        bigip.assured_gateway_subnets.append(subnet['id'])

    def delete_gateway_on_subnet(self, bigip, subnetinfo):
        network = None
        subnet = None

        if 'network' in subnetinfo:
            network = subnetinfo['network']
        if 'subnet' in subnetinfo:
            subnet = subnetinfo['subnet']

        if not network or not subnet:
            LOG.error('Attempted to create selfip and snats '
                      'for network with no id...')
            raise KeyError("network and subnet must be specified")

        if not subnet['gateway_ip']:
            raise KeyError("attempting to create gateway on subnet without "
                           "gateway ip address specified.")

        if self.l2_service.is_common_network(network):
            network_folder = 'Common'
        else:
            network_folder = self.driver.service_adapter.\
                get_folder_name(subnet['tenant_id'])

        if self.driver.conf.f5_populate_static_arp:
            self.network_helper.arp_delete_by_subnet(
                bigip,
                partition=network_folder,
                subnet=subnetinfo['subnet']['cidr'],
                mask=None)

        floating_selfip_name = "gw-" + subnet['id']
        self.delete_selfip(bigip, floating_selfip_name, network_folder)

        if self.l3_binding:
            self.l3_binding.unbind_address(subnet_id=subnet['id'],
                                           ip_address=subnet['gateway_ip'])

        gw_name = "gw-" + subnet['id']
        vs = bigip.tm.ltm.virtuals.virtual
        try:
            if vs.exists(name=gw_name, partition=network_folder):
                obj = vs.load(name=gw_name, partition=network_folder)
                obj.delete()
        except Exception as err:
            LOG.exception(err)
            raise f5_ex.VirtualServerDeleteException(
                "Failed to delete gateway service on subnet %s", subnet['id'])

        if subnet['id'] in bigip.assured_gateway_subnets:
            bigip.assured_gateway_subnets.remove(subnet['id'])

        return gw_name

    def get_selfip_addr(self, bigip, name, partition=const.DEFAULT_PARTITION):
        selfip_addr = ""
        try:
            s = bigip.tm.net.selfips.selfip
            if s.exists(name=name, partition=partition):
                obj = s.load(name=name, partition=partition)

                # The selfip address on BigIP is actually a network,
                # parse out the address portion.
                if obj.address:
                    (selfip_addr, netbits) = obj.address.split("/")

        except HTTPError as err:
            LOG.exception("Error getting selfip address for %s. "
                          "Repsponse status code: %s. Response "
                          "message: %s." %
                          (name, err.response.status_code, err.message))
        except Exception as err:
            LOG.exception("Error getting selfip address for %s.", name)

        return selfip_addr

    def get_selfips(self,
                    bigip,
                    partition=const.DEFAULT_PARTITION,
                    vlan_name=None):
        selfips_list = []

        if vlan_name:
            if not vlan_name.startswith('/'):
                vlan_name = "/%s/%s" % (partition, vlan_name)

        params = {'params': get_filter(bigip, 'partition', 'eq', partition)}
        try:
            selfips_list = [
                selfip for selfip in bigip.tm.net.selfips.get_collection(
                    requests_params=params)
                if vlan_name == selfip.vlan or not vlan_name
            ]
        except HTTPError as err:
            LOG.exception("Error getting selfips for vlan(%s). "
                          "Response status code: %s. "
                          "Response message: %s." %
                          (vlan_name, err.response.status_code, err.message))
            raise f5_ex.SelfIPQueryException(
                "Failed to get selfips assigned to vlan")

        return selfips_list

    def delete_selfip(self, bigip, name, partition=const.DEFAULT_PARTITION):
        """Delete the selfip if it exists."""
        try:
            s = bigip.tm.net.selfips.selfip
            if s.exists(name=name, partition=partition):
                obj = s.load(name=name, partition=partition)
                obj.delete()
        except HTTPError as err:
            LOG.exception("Error deleting selfip %s. "
                          "Response status code: %s. Response "
                          "message: %s." %
                          (name, err.response.status_code, err.message))
            raise f5_ex.SelfIPDeleteException("Failed to delete selfip %s." %
                                              name)

        except Exception as err:
            raise f5_ex.SelfIPDeleteException("Failed to delete selfip %s." %
                                              name)
Example #29
0
class BigipTenantManager(object):
    """Create network connectivity for a bigip."""

    def __init__(self, conf, driver):  # XXX maybe we need a better name: conf
        """Create a BigipTenantManager."""
        self.conf = conf
        self.driver = driver
        self.system_helper = SystemHelper()
        self.network_helper = NetworkHelper()
        self.service_adapter = self.driver.service_adapter

    def assure_tenant_created(self, service):
        """Create tenant partition.

        This method modifies its argument 'service' in place.
        This method adds a 'traffic_group" key to the service
        dict with a value of traffic_group_string_id.  But aren't
        traffic_groups a bigIP device concept?  And wasn't (until
        this method was called) the service object a configuration
        entirely defined by neutron?  Also for neutron->device
        adaptations shouldn't we be using ServiceModelAdapter...  though I
        suppose this is the other way.
        """
        tenant_id = service['loadbalancer']['tenant_id']
        traffic_group = self.driver.service_to_traffic_group(service)
        traffic_group = '/Common/' + traffic_group
        service["traffic_group"] = traffic_group  # modify the passed dict

        # create tenant folder
        folder_name = self.service_adapter.get_folder_name(tenant_id)
        LOG.debug("Creating tenant folder %s" % folder_name)
        for bigip in self.driver.get_config_bigips():
            if not self.system_helper.folder_exists(bigip, folder_name):
                folder = self.service_adapter.get_folder(service)
                # This folder is a dict config obj, that can be passed to
                # folder.create in the SDK
                try:
                    self.system_helper.create_folder(bigip, folder)
                except Exception as err:
                    # XXX Maybe we can make this more specific?
                    LOG.exception("Error creating folder %s" %
                                  (folder))
                    raise f5ex.SystemCreationException(
                        "Folder creation error for tenant %s" %
                        (tenant_id))

            if not self.driver.disconnected_service.network_exists(
                    bigip, folder_name):
                try:
                    self.driver.disconnected_service.create_network(
                        bigip, folder_name)
                except Exception as err:
                    LOG.exception("Error creating disconnected network %s." %
                                  (folder_name))
                    raise f5ex.SystemCreationException(
                        "Disconnected network create error for tenant %s" %
                        (tenant_id))

        # create tenant route domain
        if self.conf.use_namespaces:
            for bigip in self.driver.get_all_bigips():
                if not self.network_helper.route_domain_exists(bigip,
                                                               folder_name):
                    try:
                        self.network_helper.create_route_domain(
                            bigip,
                            folder_name,
                            self.conf.f5_route_domain_strictness)
                    except Exception as err:
                        LOG.exception(err.message)
                        raise f5ex.RouteDomainCreationException(
                            "Failed to create route domain for "
                            "tenant in %s" % (folder_name))

    def assure_tenant_cleanup(self, service, all_subnet_hints):
        """Delete tenant partition."""
        # Called for every bigip only in replication mode,
        # otherwise called once.
        for bigip in self.driver.get_config_bigips():
            subnet_hints = all_subnet_hints[bigip.device_name]
            self._assure_bigip_tenant_cleanup(bigip, service, subnet_hints)

    # called for every bigip only in replication mode.
    # otherwise called once
    def _assure_bigip_tenant_cleanup(self, bigip, service, subnet_hints):
        tenant_id = service['loadbalancer']['tenant_id']

        self._remove_tenant_replication_mode(bigip, tenant_id)

    def _remove_tenant_replication_mode(self, bigip, tenant_id):
        # Remove tenant in replication sync-mode
        partition = self.service_adapter.get_folder_name(tenant_id)
        domain_names = self.network_helper.get_route_domain_names(bigip,
                                                                  partition)
        for domain_name in domain_names:
            try:
                self.network_helper.delete_route_domain(bigip,
                                                        partition,
                                                        domain_name)
            except Exception as err:
                LOG.error("Failed to delete route domain %s. "
                          "%s. Manual intervention might be required."
                          % (domain_name, err.message))
            if self.driver.disconnected_service.network_exists(
                    bigip, partition):
                try:
                    self.driver.disconnected_service.delete_network(bigip,
                                                                    partition)
                except Exception as err:
                    LOG.error("Failed to delete disconnected network %s. "
                              "%s. Manual intervention might be required."
                              % (partition, err.message))

        try:
            self.system_helper.delete_folder(bigip, partition)
        except Exception as err:
            LOG.error(
                "Folder deletion exception for tenant partition %s occurred."
                % tenant_id)
            LOG.exception("%s" % err.message)
            raise f5ex.SystemDeleteException(
                "Failed to destroy folder %s manual cleanup might be "
                "required." % partition)
Example #30
0
class L2ServiceBuilder(object):

    def __init__(self, driver, f5_global_routed_mode):
        self.conf = driver.conf
        self.driver = driver
        self.f5_global_routed_mode = f5_global_routed_mode
        self.vlan_binding = None
        self.fdb_connector = None
        self.interface_mapping = {}
        self.tagging_mapping = {}
        self.system_helper = SystemHelper()
        self.network_helper = NetworkHelper()
        self.service_adapter = ServiceModelAdapter(self.conf)

        if not f5_global_routed_mode:
            self.fdb_connector = FDBConnectorML2(self.conf)

        if self.conf.vlan_binding_driver:
            try:
                self.vlan_binding = importutils.import_object(
                    self.conf.vlan_binding_driver, self.conf, self)
            except ImportError:
                LOG.error('Failed to import VLAN binding driver: %s'
                          % self.conf.vlan_binding_driver)
                raise

        # map format is  phynet:interface:tagged
        for maps in self.conf.f5_external_physical_mappings:
            intmap = maps.split(':')
            net_key = str(intmap[0]).strip()
            if len(intmap) > 3:
                net_key = net_key + ':' + str(intmap[3]).strip()
            self.interface_mapping[net_key] = str(intmap[1]).strip()
            self.tagging_mapping[net_key] = str(intmap[2]).strip()
            LOG.debug('physical_network %s = interface %s, tagged %s'
                      % (net_key, intmap[1], intmap[2]))

    def initialize_vcmp_manager(self):
        '''Intialize the vCMP manager when the driver is ready.'''

        self.vcmp_manager = VcmpManager(self.driver)

    def post_init(self):
        if self.vlan_binding:
            LOG.debug(
                'Getting BIG-IP device interface for VLAN Binding')
            self.vlan_binding.register_bigip_interfaces()

    def tunnel_sync(self, tunnel_ips):
        if self.fdb_connector:
            self.fdb_connector.advertise_tunnel_ips(tunnel_ips)

    def set_tunnel_rpc(self, tunnel_rpc):
        # Provide FDB Connector with ML2 RPC access
        if self.fdb_connector:
            self.fdb_connector.set_tunnel_rpc(tunnel_rpc)

    def set_l2pop_rpc(self, l2pop_rpc):
        # Provide FDB Connector with ML2 RPC access
        if self.fdb_connector:
            self.fdb_connector.set_l2pop_rpc(l2pop_rpc)

    def set_context(self, context):
        if self.fdb_connector:
            self.fdb_connector.set_context(context)

    def is_common_network(self, network):
        """Returns True if this belongs in the /Common folder

        This object method will return positive if the L2ServiceBuilder object
        should be stored under the Common partition on the BIG-IP.
        """
        return network['shared'] or \
            self.conf.f5_common_networks or \
            (network['id'] in self.conf.common_network_ids) or \
            ('router:external' in network and
             network['router:external'] and
             self.conf.f5_common_external_networks)

    def get_vlan_name(self, network, hostname):
        # Construct a consistent vlan name
        net_key = network['provider:physical_network']
        net_type = network['provider:network_type']

        # look for host specific interface mapping
        if net_key and net_key + ':' + hostname in self.interface_mapping:
            interface = self.interface_mapping[net_key + ':' + hostname]
            tagged = self.tagging_mapping[net_key + ':' + hostname]
        elif net_key and net_key in self.interface_mapping:
            interface = self.interface_mapping[net_key]
            tagged = self.tagging_mapping[net_key]
        else:
            interface = self.interface_mapping['default']
            tagged = self.tagging_mapping['default']

        if tagged:
            vlanid = network['provider:segmentation_id']
        else:
            vlanid = 0

        if net_type == "flat":
            interface_name = str(interface).replace(".", "-")
            if (len(interface_name) > 15):
                LOG.warn(
                    "Interface name is greater than 15 chars in length")
            vlan_name = "flat-%s" % (interface_name)
        else:
            vlan_name = "vlan-%d" % (vlanid)

        return vlan_name

    def assure_bigip_network(self, bigip, network):
        # Ensure bigip has configured network object
        if not network:
            LOG.error('assure_bigip_network: '
                      'Attempted to assure a network with no id..skipping.')
            return

        if network['id'] in bigip.assured_networks:
            return

        if network['id'] in self.conf.common_network_ids:
            LOG.debug('assure_bigip_network: '
                      'Network is a common global network... skipping.')
            return

        LOG.debug("assure_bigip_network network: %s" % str(network))
        start_time = time()
        if self.is_common_network(network):
            network_folder = 'Common'
        else:
            network_folder = self.service_adapter.get_folder_name(
                network['tenant_id']
            )

        # setup all needed L2 network segments
        if network['provider:network_type'] == 'flat':
            network_name = self._assure_device_network_flat(
                network, bigip, network_folder)
        elif network['provider:network_type'] == 'vlan':
            network_name = self._assure_device_network_vlan(
                network, bigip, network_folder)
        elif network['provider:network_type'] == 'vxlan':
            network_name = self._assure_device_network_vxlan(
                network, bigip, network_folder)
        elif network['provider:network_type'] == 'gre':
            network_name = self._assure_device_network_gre(
                network, bigip, network_folder)
        elif network['provider:network_type'] == 'opflex':
            raise f5_ex.NetworkNotReady(
                "Opflex network segment definition required")
        else:
            error_message = 'Unsupported network type %s.' \
                            % network['provider:network_type'] + \
                            ' Cannot setup network.'
            LOG.error(error_message)
            raise f5_ex.InvalidNetworkType(error_message)
        bigip.assured_networks[network['id']] = network_name

        if time() - start_time > .001:
            LOG.debug("        assure bigip network took %.5f secs" %
                      (time() - start_time))

    def _assure_device_network_flat(self, network, bigip, network_folder):
        # Ensure bigip has configured flat vlan (untagged)
        vlan_name = ""
        interface = self.interface_mapping['default']
        vlanid = 0

        # Do we have host specific mappings?
        net_key = network['provider:physical_network']
        if net_key and net_key + ':' + bigip.hostname in \
                self.interface_mapping:
            interface = self.interface_mapping[
                net_key + ':' + bigip.hostname]
        # Do we have a mapping for this network
        elif net_key and net_key in self.interface_mapping:
            interface = self.interface_mapping[net_key]

        vlan_name = self.get_vlan_name(network,
                                       bigip.hostname)

        self._assure_vcmp_device_network(bigip,
                                         vlan={'name': vlan_name,
                                               'folder': network_folder,
                                               'id': vlanid,
                                               'interface': interface,
                                               'network': network})

        if self.vcmp_manager and self.vcmp_manager.get_vcmp_host(bigip):
            interface = None
        try:
            model = {'name': vlan_name,
                     'interface': interface,
                     'partition': network_folder,
                     'description': network['id'],
                     'route_domain_id': network['route_domain_id']}
            self.network_helper.create_vlan(bigip, model)
        except Exception as err:
            LOG.exception("%s", err.message)
            raise f5_ex.VLANCreationException("Failed to create flat network")

        return vlan_name

    def _assure_device_network_vlan(self, network, bigip, network_folder):
        # Ensure bigip has configured tagged vlan
        # VLAN names are limited to 64 characters including
        # the folder name, so we name them foolish things.
        vlan_name = ""
        interface = self.interface_mapping['default']
        tagged = self.tagging_mapping['default']

        # Do we have host specific mappings?
        net_key = network['provider:physical_network']
        if net_key and net_key + ':' + bigip.hostname in \
                self.interface_mapping:
            interface = self.interface_mapping[
                net_key + ':' + bigip.hostname]
            tagged = self.tagging_mapping[
                net_key + ':' + bigip.hostname]
        # Do we have a mapping for this network
        elif net_key and net_key in self.interface_mapping:
            interface = self.interface_mapping[net_key]
            tagged = self.tagging_mapping[net_key]

        if tagged:
            vlanid = network['provider:segmentation_id']
        else:
            vlanid = 0

        vlan_name = self.get_vlan_name(network,
                                       bigip.hostname)

        self._assure_vcmp_device_network(bigip,
                                         vlan={'name': vlan_name,
                                               'folder': network_folder,
                                               'id': vlanid,
                                               'interface': interface,
                                               'network': network})

        if self.vcmp_manager and self.vcmp_manager.get_vcmp_host(bigip):
            interface = None
        try:
            model = {'name': vlan_name,
                     'interface': interface,
                     'tag': vlanid,
                     'partition': network_folder,
                     'description': network['id'],
                     'route_domain_id': network['route_domain_id']}
            self.network_helper.create_vlan(bigip, model)
        except Exception as err:
            LOG.exception("%s", err.message)
            raise f5_ex.VLANCreationException(
                "Failed to create vlan: %s" % vlan_name
            )

        if self.vlan_binding:
            self.vlan_binding.allow_vlan(
                device_name=bigip.device_name,
                interface=interface,
                vlanid=vlanid
            )

        return vlan_name

    def _assure_device_network_vxlan(self, network, bigip, partition):
        # Ensure bigip has configured vxlan
        tunnel_name = ""

        if not bigip.local_ip:
            error_message = 'Cannot create tunnel %s on %s' \
                % (network['id'], bigip.hostname)
            error_message += ' no VTEP SelfIP defined.'
            LOG.error('VXLAN:' + error_message)
            raise f5_ex.MissingVTEPAddress('VXLAN:' + error_message)

        tunnel_name = _get_tunnel_name(network)
        # create the main tunnel entry for the fdb records
        payload = {'name': tunnel_name,
                   'partition': partition,
                   'profile': 'vxlan_ovs',
                   'key': network['provider:segmentation_id'],
                   'localAddress': bigip.local_ip,
                   'description': network['id'],
                   'route_domain_id': network['route_domain_id']}
        try:
            self.network_helper.create_multipoint_tunnel(bigip, payload)
        except Exception as err:
            LOG.exception("%s", err.message)
            raise f5_ex.VXLANCreationException(
                "Failed to create vxlan tunnel: %s" % tunnel_name
            )

        if self.fdb_connector:
            self.fdb_connector.notify_vtep_added(network, bigip.local_ip)

        return tunnel_name

    def _assure_device_network_gre(self, network, bigip, partition):
        tunnel_name = ""

        # Ensure bigip has configured gre tunnel
        if not bigip.local_ip:
            error_message = 'Cannot create tunnel %s on %s' \
                % (network['id'], bigip.hostname)
            error_message += ' no VTEP SelfIP defined.'
            LOG.error('L2GRE:' + error_message)
            raise f5_ex.MissingVTEPAddress('L2GRE:' + error_message)

        tunnel_name = _get_tunnel_name(network)
        payload = {'name': tunnel_name,
                   'partition': partition,
                   'profile': 'gre_ovs',
                   'key': network['provider:segmentation_id'],
                   'localAddress': bigip.local_ip,
                   'description': network['id'],
                   'route_domain_id': network['route_domain_id']}
        try:
            self.network_helper.create_multipoint_tunnel(bigip, payload)
        except Exception as err:
            LOG.exception("%s", err.message)
            raise f5_ex.VXLANCreationException(
                "Failed to create gre tunnel: %s" % tunnel_name
            )

        if self.fdb_connector:
            self.fdb_connector.notify_vtep_added(network, bigip.local_ip)

        return tunnel_name

    def _assure_vcmp_device_network(self, bigip, vlan):
        if not self.vcmp_manager:
            return

        vcmp_host = self.vcmp_manager.get_vcmp_host(bigip)
        if not vcmp_host:
            return

        # Create the VLAN on the vCMP Host
        model = {'name': vlan['name'],
                 'partition': 'Common',
                 'tag': vlan['id'],
                 'interface': vlan['interface'],
                 'description': vlan['network']['id'],
                 'route_domain_id': vlan['network']['route_domain_id']}
        try:
            self.network_helper.create_vlan(vcmp_host['bigip'], model)
            LOG.debug(('Created VLAN %s on vCMP Host %s' %
                       (vlan['name'], vcmp_host['bigip'].hostname)))
        except Exception as exc:
            LOG.error(
                ('Exception creating VLAN %s on vCMP Host %s:%s' %
                 (vlan['name'], vcmp_host['bigip'].hostname, exc)))

        # Associate the VLAN with the vCMP Guest, if necessary
        self.vcmp_manager.assoc_vlan_with_vcmp_guest(bigip, vlan)

    def delete_bigip_network(self, bigip, network):
        # Delete network on bigip
        if network['id'] in self.conf.common_network_ids:
            LOG.debug('skipping delete of common network %s'
                      % network['id'])
            return
        if self.is_common_network(network):
            network_folder = 'Common'
        else:
            network_folder = self.service_adapter.get_folder_name(
                network['tenant_id'])
        if network['provider:network_type'] == 'vlan':
            self._delete_device_vlan(bigip, network, network_folder)
        elif network['provider:network_type'] == 'flat':
            self._delete_device_flat(bigip, network, network_folder)
        elif network['provider:network_type'] == 'vxlan':
            self._delete_device_vxlan(bigip, network, network_folder)
        elif network['provider:network_type'] == 'gre':
            self._delete_device_gre(bigip, network, network_folder)
        elif network['provider:network_type'] == 'opflex':
            raise f5_ex.NetworkNotReady(
                "Opflex network segment definition required")
        else:
            LOG.error('Unsupported network type %s. Can not delete.'
                      % network['provider:network_type'])
        if network['id'] in bigip.assured_networks:
            del bigip.assured_networks[network['id']]

    def _delete_device_vlan(self, bigip, network, network_folder):
        # Delete tagged vlan on specific bigip
        vlan_name = self.get_vlan_name(network,
                                       bigip.hostname)
        try:
            self.network_helper.delete_vlan(
                bigip,
                vlan_name,
                partition=network_folder
            )
        except Exception as err:
            LOG.exception(err)
            LOG.error(
                "Failed to delete vlan: %s" % vlan_name)

        if self.vlan_binding:
            interface = self.interface_mapping['default']
            tagged = self.tagging_mapping['default']
            vlanid = 0
            # Do we have host specific mappings?
            net_key = network['provider:physical_network']
            if net_key and net_key + ':' + bigip.hostname in \
                    self.interface_mapping:
                interface = self.interface_mapping[
                    net_key + ':' + bigip.hostname]
                tagged = self.tagging_mapping[
                    net_key + ':' + bigip.hostname]
            # Do we have a mapping for this network
            elif net_key and net_key in self.interface_mapping:
                interface = self.interface_mapping[net_key]
                tagged = self.tagging_mapping[net_key]
            if tagged:
                vlanid = network['provider:segmentation_id']
            else:
                vlanid = 0

            self.vlan_binding.prune_vlan(
                device_name=bigip.device_name,
                interface=interface,
                vlanid=vlanid
            )

        self._delete_vcmp_device_network(bigip, vlan_name)

    def _delete_device_flat(self, bigip, network, network_folder):
        # Delete untagged vlan on specific bigip
        vlan_name = self.get_vlan_name(network,
                                       bigip.hostname)
        try:
            self.network_helper.delete_vlan(
                bigip,
                vlan_name,
                partition=network_folder
            )
        except Exception as err:
            LOG.exception(err)
            LOG.error(
                "Failed to delete vlan: %s" % vlan_name)

        self._delete_vcmp_device_network(bigip, vlan_name)

    def _delete_device_vxlan(self, bigip, network, network_folder):
        # Delete vxlan tunnel on specific bigip
        tunnel_name = _get_tunnel_name(network)

        try:
            self.network_helper.delete_all_fdb_entries(
                bigip,
                tunnel_name,
                partition=network_folder)

            self.network_helper.delete_tunnel(
                bigip,
                tunnel_name,
                partition=network_folder)
        except Exception as err:
            # Just log the exception, we want to continue cleanup
            LOG.exception(err)
            LOG.error(
                "Failed to delete vxlan tunnel: %s" % tunnel_name)

        if self.fdb_connector:
            self.fdb_connector.notify_vtep_removed(network, bigip.local_ip)

    def _delete_device_gre(self, bigip, network, network_folder):
        # Delete gre tunnel on specific bigip
        tunnel_name = _get_tunnel_name(network)

        try:
            self.network_helper.delete_all_fdb_entries(
                bigip,
                tunnel_name,
                partition=network_folder)

            self.network_helper.delete_tunnel(
                bigip,
                tunnel_name,
                partition=network_folder)

        except Exception as err:
            # Just log the exception, we want to continue cleanup
            LOG.exception(err)
            LOG.error(
                "Failed to delete gre tunnel: %s" % tunnel_name)

        if self.fdb_connector:
            self.fdb_connector.notify_vtep_removed(network, bigip.local_ip)

    def _delete_vcmp_device_network(self, bigip, vlan_name):
        '''Disassociated VLAN with vCMP Guest, then delete it from vCMP Host

        :param bigip: ManagementRoot object -- vCMP guest
        :param vlan_name: str -- name of vlan
        '''

        if not self.vcmp_manager:
            return
        vcmp_host = self.vcmp_manager.get_vcmp_host(bigip)
        if not vcmp_host:
            return
        self.vcmp_manager.disassoc_vlan_with_vcmp_guest(bigip, vlan_name)

    def add_bigip_fdb(self, bigip, fdb):
        # Add entries from the fdb relevant to the bigip
        for fdb_operation in \
            [{'network_type': 'vxlan',
              'get_tunnel_folder': self.network_helper.get_tunnel_folder,
              'fdb_method': self.network_helper.add_fdb_entries},

             {'network_type': 'gre',
              'get_tunnel_folder': self.network_helper.get_tunnel_folder,
              'fdb_method': self.network_helper.add_fdb_entries}]:
            self._operate_bigip_fdb(bigip, fdb, fdb_operation)

    def _operate_bigip_fdb(self, bigip, fdb, fdb_operation):
        """Add L2 records for MAC addresses behind tunnel endpoints.

            Description of fdb structure:
            {'<network_id>':
                'segment_id': <int>
                'ports': [ '<vtep>': ['<mac_address>': '<ip_address>'] ]
             '<network_id>':
                'segment_id':
                'ports': [ '<vtep>': ['<mac_address>': '<ip_address>'] ] }

            Sample real fdb structure:
            {u'45bbbce1-191b-4f7b-84c5-54c6c8243bd2':
                {u'segment_id': 1008,
                 u'ports':
                     {u'10.30.30.2': [[u'00:00:00:00:00:00', u'0.0.0.0'],
                                      [u'fa:16:3e:3d:7b:7f', u'10.10.1.4']]},
                 u'network_type': u'vxlan'}}
        """
        network_type = fdb_operation['network_type']
        get_tunnel_folder = fdb_operation['get_tunnel_folder']
        fdb_method = fdb_operation['fdb_method']

        for network in fdb:
            net_fdb = fdb[network]
            if net_fdb['network_type'] == network_type:
                net = {'name': network,
                       'provider:network_type': net_fdb['network_type'],
                       'provider:segmentation_id': net_fdb['segment_id']}
                tunnel_name = _get_tunnel_name(net)
                folder = get_tunnel_folder(bigip, tunnel_name=tunnel_name)
                net_info = {'network': network,
                            'folder': folder,
                            'tunnel_name': tunnel_name,
                            'net_fdb': net_fdb}
                fdbs = self._get_bigip_network_fdbs(bigip, net_info)
                if len(fdbs) > 0:
                    fdb_method(bigip, fdb_entries=fdbs)

    def _get_bigip_network_fdbs(self, bigip, net_info):
        # Get network fdb entries to add to a bigip
        if not net_info['folder']:
            return {}
        net_fdb = net_info['net_fdb']
        fdbs = {}
        for vtep in net_fdb['ports']:
            # bigip does not need to set fdb entries for local addresses
            if vtep == bigip.local_ip:
                continue

            # most net_info applies to the vtep
            vtep_info = dict(net_info)
            # but the network fdb is too broad so delete it
            del vtep_info['net_fdb']
            # use a slice of the fdb for the vtep instead
            vtep_info['vtep'] = vtep
            vtep_info['fdb_entries'] = net_fdb['ports'][vtep]

            self._merge_vtep_fdbs(vtep_info, fdbs)
        return fdbs

    def _merge_vtep_fdbs(self, vtep_info, fdbs):
        # Add L2 records for a specific network+vtep
        folder = vtep_info['folder']
        tunnel_name = vtep_info['tunnel_name']
        for entry in vtep_info['fdb_entries']:
            mac_address = entry[0]
            if mac_address == '00:00:00:00:00:00':
                continue
            ip_address = entry[1]

            # create/get tunnel data
            if tunnel_name not in fdbs:
                fdbs[tunnel_name] = {}
            tunnel_fdbs = fdbs[tunnel_name]
            # update tunnel folder
            tunnel_fdbs['folder'] = folder

            # maybe create records for tunnel
            if 'records' not in tunnel_fdbs:
                tunnel_fdbs['records'] = {}

            # add entry to records map keyed by mac address
            tunnel_fdbs['records'][mac_address] = \
                {'endpoint': vtep_info['vtep'], 'ip_address': ip_address}

    def update_bigip_fdb(self, bigip, fdb):
        # Update l2 records
        self.add_bigip_fdb(bigip, fdb)

    def remove_bigip_fdb(self, bigip, fdb):
        # Add L2 records for MAC addresses behind tunnel endpoints
        for fdb_operation in \
            [{'network_type': 'vxlan',
              'get_tunnel_folder': self.network_helper.get_tunnel_folder,
              'fdb_method': self.network_helper.delete_fdb_entries},
             {'network_type': 'gre',
              'get_tunnel_folder': self.network_helper.get_tunnel_folder,
              'fdb_method': self.network_helper.delete_fdb_entries}]:
            self._operate_bigip_fdb(bigip, fdb, fdb_operation)

    # Utilities
    def get_network_name(self, bigip, network):
        # This constructs a name for a tunnel or vlan interface
        preserve_network_name = False
        if network['id'] in self.conf.common_network_ids:
            network_name = self.conf.common_network_ids[network['id']]
            preserve_network_name = True
        elif network['provider:network_type'] == 'vlan':
            network_name = self.get_vlan_name(network,
                                              bigip.hostname)
        elif network['provider:network_type'] == 'flat':
            network_name = self.get_vlan_name(network,
                                              bigip.hostname)
        elif network['provider:network_type'] == 'vxlan':
            network_name = _get_tunnel_name(network)
        elif network['provider:network_type'] == 'gre':
            network_name = _get_tunnel_name(network)
        else:
            error_message = 'Unsupported network type %s.' \
                            % network['provider:network_type'] + \
                            ' Cannot setup selfip or snat.'
            LOG.error(error_message)
            raise f5_ex.InvalidNetworkType(error_message)
        return network_name, preserve_network_name

    def _get_network_folder(self, network):
        if self.is_common_network(network):
            return 'Common'
        else:
            return self.service_adapter.get_folder_name(network['tenant_id'])

    def add_fdb_entries(self, bigips, loadbalancer, members):
        """Update fdb entries for loadbalancer and member VTEPs.

        :param bigips: one or more BIG-IPs to update.
        :param loadbalancer: Loadbalancer with VTEPs to update. Can be None.
        :param members: List of members. Can be emtpy ([]).
        """
        tunnel_records = self.create_fdb_records(loadbalancer, members)
        if tunnel_records:
            for bigip in bigips:
                self.network_helper.add_fdb_entries(bigip,
                                                    fdb_entries=tunnel_records)

    def delete_fdb_entries(self, bigips, loadbalancer, members):
        """Remove fdb entries for loadbalancer and member VTEPs.

        :param bigips: one or more BIG-IPs to update.
        :param loadbalancer: Loadbalancer with VTEPs to remove. Can be None.
        :param members: List of members. Can be emtpy ([]).
        """
        tunnel_records = self.create_fdb_records(loadbalancer, members)
        if tunnel_records:
            for bigip in bigips:
                self.network_helper.delete_fdb_entries(
                    bigip,
                    fdb_entries=tunnel_records)

    def create_fdb_records(self, loadbalancer, members):
        fdbs = dict()

        if loadbalancer:
            network = loadbalancer['network']
            tunnel_name = _get_tunnel_name(network)
            fdbs[tunnel_name] = dict()
            fdbs[tunnel_name]['folder'] = self._get_network_folder(network)
            records = dict()
            fdbs[tunnel_name]['records'] = records
            self.append_loadbalancer_fdb_records(
                network, loadbalancer, records)

        for member in members:
            network = member['network']
            tunnel_name = _get_tunnel_name(network)
            if tunnel_name not in fdbs:
                fdbs[tunnel_name] = dict()
                fdbs[tunnel_name]['folder'] = self._get_network_folder(network)
                fdbs[tunnel_name]['records'] = dict()

            records = fdbs[tunnel_name]['records']
            if 'port' in member and 'mac_address' in member['port']:
                mac_addr = member['port']['mac_address']
                self.append_member_fdb_records(network,
                                               member,
                                               records,
                                               mac_addr,
                                               ip_address=member['address'])

        return fdbs

    def append_loadbalancer_fdb_records(self, network, loadbalancer, records):
        vteps = _get_vteps(network, loadbalancer)
        for vtep in vteps:
            # create an arbitrary MAC address for VTEP
            mac_addr = _get_tunnel_fake_mac(network, vtep)
            records[mac_addr] = {'endpoint': vtep, 'ip_address': ''}

    def append_member_fdb_records(self, network, member, records,
                                  mac_addr, ip_address=''):
        vteps = _get_vteps(network, member)
        for vtep in vteps:
            records[mac_addr] = {'endpoint': vtep,
                                 'ip_address': ip_address}
Example #31
0
 def __init__(self, driver, l2_service, l3_binding):
     self.driver = driver
     self.l2_service = l2_service
     self.l3_binding = l3_binding
     self.selfip_manager = BigIPResourceHelper(ResourceType.selfip)
     self.network_helper = NetworkHelper()
Example #32
0
class BigipSelfIpManager(object):

    def __init__(self, driver, l2_service, l3_binding):
        self.driver = driver
        self.l2_service = l2_service
        self.l3_binding = l3_binding
        self.selfip_manager = BigIPResourceHelper(ResourceType.selfip)
        self.network_helper = NetworkHelper()

    def _create_bigip_selfip(self, bigip, model):
        created = False
        if self.selfip_manager.exists(bigip, name=model['name'],
                                      partition=model['partition']):
            created = True
        else:
            try:
                self.selfip_manager.create(bigip, model)
                created = True
            except HTTPError as err:

                if (err.response.status_code == 400 and
                    err.response.text.find(
                        "must be one of the vlans "
                        "in the associated route domain") > 0):
                    try:
                        self.network_helper.add_vlan_to_domain(
                            bigip,
                            name=model['vlan'],
                            partition=model['partition'])
                        self.selfip_manager.create(bigip, model)
                        created = True
                    except HTTPError as err:
                        LOG.exception("Error creating selfip %s. "
                                      "Repsponse status code: %s. "
                                      "Response message: %s." % (
                                          model["name"],
                                          err.response.status_code,
                                          err.message))
                        raise f5_ex.SelfIPCreationException("selfip")
                else:
                    LOG.exception("selfip creation error: %s(%s)" %
                                  (err.message, err.response.status_code))
                    raise
            except Exception as err:
                LOG.error("Failed to create selfip")
                LOG.exception(err.message)
                raise f5_ex.SelfIPCreationException("selfip creation")

        return created

    def assure_bigip_selfip(self, bigip, service, subnetinfo):
        u"""Ensure the BigIP has a selfip address on the tenant subnet."""

        network = None
        subnet = None

        if 'network' in subnetinfo:
            network = subnetinfo['network']
        if 'subnet' in subnetinfo:
            subnet = subnetinfo['subnet']

        if not network or not subnet:
            LOG.error('Attempted to create selfip and snats '
                      'for network with not id...')
            raise KeyError("network and subnet need to be specified")

        tenant_id = service['loadbalancer']['tenant_id']
        lb_id = service['loadbalancer']['id']

        # If we have already assured this subnet.. return.
        # Note this cache is periodically cleared in order to
        # force assurance that the configuration is present.
        if tenant_id in bigip.assured_tenant_snat_subnets and \
                subnet['id'] in bigip.assured_tenant_snat_subnets[tenant_id]:
            return True

        selfip_address = self._get_bigip_selfip_address(bigip, subnet, lb_id)
        if 'route_domain_id' not in network:
            LOG.error("network route domain is not set")
            raise KeyError()

        selfip_address += '%' + str(network['route_domain_id'])

        if self.l2_service.is_common_network(network):
            network_folder = 'Common'
        else:
            network_folder = self.driver.service_adapter.\
                get_folder_name(service['loadbalancer']['tenant_id'])

        # Get the name of the vlan.
        (network_name, preserve_network_name) = \
            self.l2_service.get_network_name(bigip, network)

        netmask = netaddr.IPNetwork(subnet['cidr']).prefixlen
        address = selfip_address + ("/%d" % netmask)
        model = {
            "name": "local-" + bigip.device_name + "-" + subnet['id'],
            "address": address,
            "vlan": network_name,
            "floating": "disabled",
            "partition": network_folder
        }
        self._create_bigip_selfip(bigip, model)

        if self.l3_binding:
            self.l3_binding.bind_address(subnet_id=subnet['id'],
                                         ip_address=selfip_address)

    def _get_bigip_selfip_address(self, bigip, subnet, device_id):
        u"""Ensure a selfip address is allocated on Neutron network."""
        # Get ip address for selfip to use on BIG-IP.
        selfip_address = ""
        selfip_name = "local-" + bigip.device_name + "-" + subnet['id']
        ports = self.driver.plugin_rpc.get_port_by_name(port_name=selfip_name)
        if len(ports) > 0:
            port = ports[0]
        else:
            port = self.driver.plugin_rpc.create_port_on_subnet(
                subnet_id=subnet['id'],
                mac_address=None,
                name=selfip_name,
                fixed_address_count=1,
                device_id=device_id
            )

        if port and 'fixed_ips' in port:
            fixed_ip = port['fixed_ips'][0]
            selfip_address = fixed_ip['ip_address']

        return selfip_address

    def assure_gateway_on_subnet(self, bigip, subnetinfo, traffic_group):
        """Ensure """
        network = None
        subnet = None

        if 'network' in subnetinfo:
            network = subnetinfo['network']
        if 'subnet' in subnetinfo:
            subnet = subnetinfo['subnet']

        if not network or not subnet:
            raise KeyError("network and subnet must be specified to create "
                           "gateway on subnet.")

        if not subnet['gateway_ip']:
            raise KeyError("attempting to create gateway on subnet without "
                           "gateway ip address specified.")

        if subnet['id'] in bigip.assured_gateway_subnets:
            return True

        (network_name, preserve_network_name) = \
            self.l2_service.get_network_name(bigip, network)

        if self.l2_service.is_common_network(network):
            network_folder = 'Common'
        else:
            network_folder = self.driver.service_adapter.\
                get_folder_name(subnet['tenant_id'])

        # Create a floating SelfIP for the given traffic-group.
        floating_selfip_name = "gw-" + subnet['id']
        netmask = netaddr.IPNetwork(subnet['cidr']).prefixlen
        address = subnet['gateway_ip'] + "%" + str(network['route_domain_id'])
        address += ("/%d" % (netmask))
        model = {
            'name': floating_selfip_name,
            'address': address,
            'vlan': network_name,
            'floating': True,
            'traffic-group': traffic_group,
            'partition': network_folder
        }

        if not self._create_bigip_selfip(bigip, model):
            LOG.error("failed to create gateway selfip")

        if self.l3_binding:
            self.l3_binding.bind_address(subnet_id=subnet['id'],
                                         ip_address=subnet['gateway_ip'])

        # Setup a wild card ip forwarding virtual service for this subnet
        gw_name = "gw-" + subnet['id']
        vs = bigip.tm.ltm.virtuals.virtual
        if not vs.exists(name=gw_name, partition=network_folder):
            try:
                vs.create(
                    name=gw_name,
                    partition=network_folder,
                    destination='0.0.0.0:0',
                    mask='0.0.0.0',
                    vlansEnabled=True,
                    vlans=[network_name],
                    sourceAddressTranslation={'type': 'automap'},
                    ipForward=True
                )
            except Exception as err:
                LOG.exception(err)
                raise f5_ex.VirtualServerCreationException(
                    "Failed to create gateway virtual service on subnet %s",
                    subnet['id']
                )

        # Put the virtual server address in the specified traffic group
        virtual_address = bigip.tm.ltm.virtual_address_s.virtual_address
        try:
            obj = virtual_address.load(
                name='0.0.0.0', partition=network_folder)
            obj.modify(trafficGroup=traffic_group)
        except Exception as err:
            LOG.exception(err)
            raise f5_ex.VirtualServerCreationException(
                "Failed to add virtual address to traffic group %s",
                traffic_group)

        bigip.assured_gateway_subnets.append(subnet['id'])

    def delete_gateway_on_subnet(self, bigip, subnetinfo):
        network = None
        subnet = None

        if 'network' in subnetinfo:
            network = subnetinfo['network']
        if 'subnet' in subnetinfo:
            subnet = subnetinfo['subnet']

        if not network or not subnet:
            LOG.error('Attempted to create selfip and snats '
                      'for network with no id...')
            raise KeyError("network and subnet must be specified")

        if not subnet['gateway_ip']:
            raise KeyError("attempting to create gateway on subnet without "
                           "gateway ip address specified.")

        if self.l2_service.is_common_network(network):
            network_folder = 'Common'
        else:
            network_folder = self.driver.service_adapter.\
                get_folder_name(subnet['tenant_id'])

        if self.driver.conf.f5_populate_static_arp:
            self.network_helper.arp_delete_by_subnet(
                bigip,
                partition=network_folder,
                subnet=subnetinfo['subnet']['cidr'],
                mask=None
            )

        floating_selfip_name = "gw-" + subnet['id']
        self.delete_selfip(
            bigip, floating_selfip_name, network_folder)

        if self.l3_binding:
            self.l3_binding.unbind_address(subnet_id=subnet['id'],
                                           ip_address=subnet['gateway_ip'])

        gw_name = "gw-" + subnet['id']
        vs = bigip.tm.ltm.virtuals.virtual
        try:
            if vs.exists(name=gw_name, partition=network_folder):
                obj = vs.load(name=gw_name, partition=network_folder)
                obj.delete()
        except Exception as err:
            LOG.exception(err)
            raise f5_ex.VirtualServerDeleteException(
                "Failed to delete gateway service on subnet %s", subnet['id'])

        if subnet['id'] in bigip.assured_gateway_subnets:
            bigip.assured_gateway_subnets.remove(subnet['id'])

        return gw_name

    def get_selfip_addr(self, bigip, name, partition=const.DEFAULT_PARTITION):
        selfip_addr = ""
        try:
            s = bigip.tm.net.selfips.selfip
            if s.exists(name=name, partition=partition):
                obj = s.load(name=name, partition=partition)

                # The selfip address on BigIP is actually a network,
                # parse out the address portion.
                if obj.address:
                    (selfip_addr, netbits) = obj.address.split("/")

        except HTTPError as err:
            LOG.exception("Error getting selfip address for %s. "
                          "Repsponse status code: %s. Response "
                          "message: %s." % (name,
                                            err.response.status_code,
                                            err.message))
        except Exception as err:
            LOG.exception("Error getting selfip address for %s.",
                          name)

        return selfip_addr

    def get_selfips(self, bigip, partition=const.DEFAULT_PARTITION,
                    vlan_name=None):
        selfips_list = []

        if vlan_name:
            if not vlan_name.startswith('/'):
                vlan_name = "/%s/%s" % (partition, vlan_name)

        params = {'params': get_filter(bigip, 'partition', 'eq', partition)}
        try:
            selfips_list = [selfip for selfip in
                            bigip.tm.net.selfips.get_collection(
                                requests_params=params
                            )
                            if vlan_name == selfip.vlan or not vlan_name]
        except HTTPError as err:
            LOG.exception("Error getting selfips for vlan(%s). "
                          "Response status code: %s. "
                          "Response message: %s." % (
                              vlan_name,
                              err.response.status_code,
                              err.message))
            raise f5_ex.SelfIPQueryException(
                "Failed to get selfips assigned to vlan")

        return selfips_list

    def delete_selfip(self, bigip, name, partition=const.DEFAULT_PARTITION):
        """Delete the selfip if it exists."""
        try:
            s = bigip.tm.net.selfips.selfip
            if s.exists(name=name, partition=partition):
                obj = s.load(name=name, partition=partition)
                obj.delete()
        except HTTPError as err:
            LOG.exception("Error deleting selfip %s. "
                          "Response status code: %s. Response "
                          "message: %s." % (name,
                                            err.response.status_code,
                                            err.message))
            raise f5_ex.SelfIPDeleteException(
                "Failed to delete selfip %s." % name)

        except Exception as err:
            raise f5_ex.SelfIPDeleteException(
                "Failed to delete selfip %s." % name)
Example #33
0
    def network_helper(self, bigip):
        nh = NetworkHelper()
        nh.get_route_domain_by_id = mock.MagicMock(
            return_value=bigip.tm.net.route_domains.route_domain)

        return nh
 def __init__(self):
     self.network_name = DisconnectedService.network_name
     self.network_helper = NetworkHelper()
import pytest
import requests

from requests.packages.urllib3.exceptions import InsecureRequestWarning

requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

from f5_openstack_agent.lbaasv2.drivers.bigip.network_helper import \
    NetworkHelper
from f5_openstack_agent.lbaasv2.drivers.bigip.system_helper import \
    SystemHelper
from pprint import pprint
from requests.exceptions import HTTPError


network_helper = NetworkHelper()
system_helper = SystemHelper()
default_partition = 'test'


def log_test_call(func):
    def wrapper(func, *args, **kwargs):
        print("\nRunning %s" % func.func_name)
        return func(*args, **kwargs)
    return decorator.decorator(wrapper, func)


def delete_resource(resource):
    try:
        resource.delete()
    except HTTPError as err:
Example #36
0
class BigipTenantManager(object):
    """Create network connectivity for a bigip."""
    def __init__(self, conf, driver):  # XXX maybe we need a better name: conf
        """Create a BigipTenantManager."""
        self.conf = conf
        self.driver = driver
        self.system_helper = SystemHelper()
        self.network_helper = NetworkHelper()
        self.service_adapter = self.driver.service_adapter

    def assure_tenant_created(self, service):
        """Create tenant partition.

        This method modifies its argument 'service' in place.
        This method adds a 'traffic_group" key to the service
        dict with a value of traffic_group_string_id.  But aren't
        traffic_groups a bigIP device concept?  And wasn't (until
        this method was called) the service object a configuration
        entirely defined by neutron?  Also for neutron->device
        adaptations shouldn't we be using ServiceModelAdapter...  though I
        suppose this is the other way.
        """
        tenant_id = service['loadbalancer']['tenant_id']
        traffic_group = self.driver.service_to_traffic_group(service)
        traffic_group = '/Common/' + traffic_group
        service["traffic_group"] = traffic_group  # modify the passed dict

        # create tenant folder
        folder_name = self.service_adapter.get_folder_name(tenant_id)
        LOG.debug("Creating tenant folder %s" % folder_name)
        for bigip in self.driver.get_config_bigips():
            if not self.system_helper.folder_exists(bigip, folder_name):
                folder = self.service_adapter.get_folder(service)
                # This folder is a dict config obj, that can be passed to
                # folder.create in the SDK
                try:
                    self.system_helper.create_folder(bigip, folder)
                except Exception as err:
                    # XXX Maybe we can make this more specific?
                    LOG.exception("Error creating folder %s" % (folder))
                    raise f5ex.SystemCreationException(
                        "Folder creation error for tenant %s" % (tenant_id))

        # create tenant route domain
        if self.conf.use_namespaces:
            for bigip in self.driver.get_all_bigips():
                if not self.network_helper.route_domain_exists(
                        bigip, folder_name):
                    try:
                        self.network_helper.create_route_domain(
                            bigip, folder_name,
                            self.conf.f5_route_domain_strictness)
                    except Exception as err:
                        LOG.exception(err.message)
                        raise f5ex.RouteDomainCreationException(
                            "Failed to create route domain for "
                            "tenant in %s" % (folder_name))

    def assure_tenant_cleanup(self, service, all_subnet_hints):
        """Delete tenant partition."""
        # Called for every bigip only in replication mode,
        # otherwise called once.
        for bigip in self.driver.get_config_bigips():
            subnet_hints = all_subnet_hints[bigip.device_name]
            self._assure_bigip_tenant_cleanup(bigip, service, subnet_hints)

    # called for every bigip only in replication mode.
    # otherwise called once
    def _assure_bigip_tenant_cleanup(self, bigip, service, subnet_hints):
        tenant_id = service['loadbalancer']['tenant_id']

        self._remove_tenant_replication_mode(bigip, tenant_id)

    def _remove_tenant_replication_mode(self, bigip, tenant_id):
        # Remove tenant in replication sync-mode
        partition = self.service_adapter.get_folder_name(tenant_id)
        domain_names = self.network_helper.get_route_domain_names(
            bigip, partition)
        for domain_name in domain_names:
            try:
                self.network_helper.delete_route_domain(
                    bigip, partition, domain_name)
            except Exception as err:
                LOG.debug("Failed to delete route domain %s. "
                          "%s. Manual intervention might be required." %
                          (domain_name, err.message))

        try:
            self.system_helper.delete_folder(bigip, partition)
        except Exception as err:
            LOG.debug(
                "Folder deletion exception for tenant partition %s occurred. "
                "Manual cleanup might be required." % (tenant_id))
Example #37
0
 def __init__(self, driver, l2_service, l3_binding):
     self.driver = driver
     self.l2_service = l2_service
     self.l3_binding = l3_binding
     self.selfip_manager = BigIPResourceHelper(ResourceType.selfip)
     self.network_helper = NetworkHelper()
Example #38
0
class BigipSelfIpManager(object):

    def __init__(self, driver, l2_service, l3_binding):
        self.driver = driver
        self.l2_service = l2_service
        self.l3_binding = l3_binding
        self.selfip_manager = BigIPResourceHelper(ResourceType.selfip)
        self.network_helper = NetworkHelper()

    def create_bigip_selfip(self, bigip, model):
        if not model['name']:
            return False
        LOG.debug("Getting selfip....")
        s = bigip.net.selfips.selfip

        if s.exists(name=model['name'], partition=model['partition']):
            LOG.debug("It exists!!!!")
            return True
        try:
            LOG.debug("Doesn't exist!!!!")
            self.selfip_manager.create(bigip, model)
            LOG.debug("CREATED!!!!")
        except HTTPError as err:
            if err.response.status_code is not 400:
                raise
            if err.response.text.find("must be one of the vlans "
                                      "in the associated route domain") > 0:
                self.network_helper.add_vlan_to_domain(
                    bigip,
                    name=model['vlan'],
                    partition=model['partition'])
                try:
                    self.selfip_manager.create(bigip, model)
                except HTTPError as err:
                    LOG.error("Error creating selfip %s. "
                              "Repsponse status code: %s. Response "
                              "message: %s." % (model["name"],
                                                err.response.status_code,
                                                err.message))

    def assure_bigip_selfip(self, bigip, service, subnetinfo):

        network = subnetinfo['network']
        if not network:
            LOG.error('Attempted to create selfip and snats '
                      'for network with no id... skipping.')
            return
        subnet = subnetinfo['subnet']

        tenant_id = service['loadbalancer']['tenant_id']
        # If we have already assured this subnet.. return.
        # Note this cache is periodically cleared in order to
        # force assurance that the configuration is present.
        if tenant_id in bigip.assured_tenant_snat_subnets and \
                subnet['id'] in bigip.assured_tenant_snat_subnets[tenant_id]:
            return

        selfip_address = self._get_bigip_selfip_address(bigip, subnet)
        # FIXME(Rich Browne): it is possible this is not set unless
        # use namespaces is true.  I think this method is only called
        # in the global_routed_mode == False case though.  Need to check
        # that network['route_domain_id'] exists.
        if 'route_domain_id' not in network:
            LOG.debug("NETWORK ROUTE DOMAIN NOT SET")
            network['route_domain_id'] = "0"

        LOG.debug("route domain id: %s" % network['route_domain_id'])

        selfip_address += '%' + str(network['route_domain_id'])
        LOG.debug("have selfip address: %s" % selfip_address)

        if self.l2_service.is_common_network(network):
            network_folder = 'Common'
        else:
            network_folder = self.driver.service_adapter.\
                get_folder_name(service['loadbalancer']['tenant_id'])

        LOG.debug("getting network name")
        (network_name, preserve_network_name) = \
            self.l2_service.get_network_name(bigip, network)

        LOG.debug("CREATING THE SELFIP--------------------")
        netmask = netaddr.IPNetwork(subnet['cidr']).prefixlen
        address = selfip_address + ("/%d" % netmask)
        model = {
            "name": "local-" + bigip.device_name + "-" + subnet['id'],
            "address": address,
            "vlan": network_name,
            "floating": "disabled",
            "partition": network_folder
        }
        LOG.debug("Model: %s" % model)
        self.create_bigip_selfip(bigip, model)
        # TO DO: we need to only bind the local SelfIP to the
        # local device... not treat it as if it was floating
        LOG.debug("self ip CREATED!!!!!!")
        if self.l3_binding:
            self.l3_binding.bind_address(subnet_id=subnet['id'],
                                         ip_address=selfip_address)

    def _get_bigip_selfip_address(self, bigip, subnet):
        # Get ip address for selfip to use on BIG-IP®.
        selfip_name = "local-" + bigip.device_name + "-" + subnet['id']
        ports = self.driver.plugin_rpc.get_port_by_name(port_name=selfip_name)
        if len(ports) > 0:
            port = ports[0]
        else:
            port = self.driver.plugin_rpc.create_port_on_subnet(
                subnet_id=subnet['id'],
                mac_address=None,
                name=selfip_name,
                fixed_address_count=1)
        return port['fixed_ips'][0]['ip_address']

    def assure_gateway_on_subnet(self, bigip, subnetinfo, traffic_group):
        # Called for every bigip only in replication mode.
        # Otherwise called once.
        subnet = subnetinfo['subnet']
        if subnet['id'] in bigip.assured_gateway_subnets:
            return

        network = subnetinfo['network']
        (network_name, preserve_network_name) = \
            self.l2_service.get_network_name(bigip, network)

        if self.l2_service.is_common_network(network):
            network_folder = 'Common'
            network_name = '/Common/' + network_name
        else:
            network_folder = self.driver.service_adapter.\
                get_folder_name(subnet['tenant_id'])

        # Select a traffic group for the floating SelfIP
        floating_selfip_name = "gw-" + subnet['id']
        netmask = netaddr.IPNetwork(subnet['cidr']).netmask

        model = {
            'name': floating_selfip_name,
            'ip_address': subnet['gateway_ip'],
            'netmask': netmask,
            'vlan_name': network_name,
            'floating': True,
            'traffic_group': traffic_group,
            'partition': network_folder,
            'preserve_vlan_name': preserve_network_name
        }
        self.create_bigip_selfip(bigip, model)

        if self.l3_binding:
            self.l3_binding.bind_address(subnet_id=subnet['id'],
                                         ip_address=subnet['gateway_ip'])

        # Setup a wild card ip forwarding virtual service for this subnet
        gw_name = "gw-" + subnet['id']
        vs = bigip.ltm.virtuals.virtual
        if not vs.exists(name=gw_name, partition=network_folder):
            vs.create(
                name=gw_name,
                partition=network_folder,
                destination='0.0.0.0:0',
                mask='0.0.0.0',
                vlansEnabled=True,
                vlans=[network_name],
                sourceAddressTranslation={'type': 'automap'},
                ipForward=True
            )
        else:
            vs.load(name=gw_name, partition=network_folder)
        virtual_address = bigip.ltm.virtual_address_s.virtual_address
        virtual_address.load(name='0.0.0.0:0', partition=network_folder)
        virtual_address.update(trafficGroup=traffic_group)
        bigip.assured_gateway_subnets.append(subnet['id'])

    def delete_gateway_on_subnet(self, bigip, subnetinfo):
        # Called for every bigip only in replication mode.
        # Otherwise called once.
        network = subnetinfo['network']
        if not network:
            LOG.error('Attempted to delete default gateway '
                      'for network with no id... skipping.')
            return
        subnet = subnetinfo['subnet']
        if self.l2_service.is_common_network(network):
            network_folder = 'Common'
        else:
            network_folder = self.driver.service_adapter.\
                get_folder_name(subnet['tenant_id'])

        floating_selfip_name = "gw-" + subnet['id']
        if self.driver.conf.f5_populate_static_arp:
            self.network_helper.arp_delete_by_subnet(
                bigip,
                partition=network_folder,
                subnet=subnetinfo['subnet']['cidr'],
                mask=None
            )

        self.network_helper.delete_selfip(
            bigip, floating_selfip_name, network_folder)

        if self.l3_binding:
            self.l3_binding.unbind_address(subnet_id=subnet['id'],
                                           ip_address=subnet['gateway_ip'])

        gw_name = "gw-" + subnet['id']

        vs = bigip.ltm.virtuals.virtual
        if vs.exists(name=gw_name, partition=network_folder):
            vs.load(name=gw_name, partition=network_folder)
            vs.delete()

        if subnet['id'] in bigip.assured_gateway_subnets:
            bigip.assured_gateway_subnets.remove(subnet['id'])
        return gw_name
Example #39
0
class L2ServiceBuilder(object):
    def __init__(self, conf, f5_global_routed_mode):
        self.conf = conf
        self.f5_global_routed_mode = f5_global_routed_mode
        self.vlan_binding = None
        self.fdb_connector = None
        self.vcmp_manager = None
        self.interface_mapping = {}
        self.tagging_mapping = {}
        self.system_helper = SystemHelper()
        self.network_helper = NetworkHelper()
        self.service_adapter = ServiceModelAdapter(conf)

        if not f5_global_routed_mode:
            self.fdb_connector = FDBConnectorML2(conf)

        if self.conf.vlan_binding_driver:
            try:
                self.vlan_binding = importutils.import_object(
                    self.conf.vlan_binding_driver, self.conf, self)
            except ImportError:
                LOG.error('Failed to import VLAN binding driver: %s' %
                          self.conf.vlan_binding_driver)

        # map format is   phynet:interface:tagged
        for maps in self.conf.f5_external_physical_mappings:
            intmap = maps.split(':')
            net_key = str(intmap[0]).strip()
            if len(intmap) > 3:
                net_key = net_key + ':' + str(intmap[3]).strip()
            self.interface_mapping[net_key] = str(intmap[1]).strip()
            self.tagging_mapping[net_key] = str(intmap[2]).strip()
            LOG.debug('physical_network %s = interface %s, tagged %s' %
                      (net_key, intmap[1], intmap[2]))

    def post_init(self):
        if self.vlan_binding:
            LOG.debug('Getting BIG-IP device interface for VLAN Binding')
            self.vlan_binding.register_bigip_interfaces()

    def tunnel_sync(self, tunnel_ips):
        if self.fdb_connector:
            self.fdb_connector.advertise_tunnel_ips(tunnel_ips)

    def set_tunnel_rpc(self, tunnel_rpc):
        # Provide FDB Connector with ML2 RPC access
        if self.fdb_connector:
            self.fdb_connector.set_tunnel_rpc(tunnel_rpc)

    def set_l2pop_rpc(self, l2pop_rpc):
        # Provide FDB Connector with ML2 RPC access
        if self.fdb_connector:
            self.fdb_connector.set_l2pop_rpc(l2pop_rpc)

    def set_context(self, context):
        if self.fdb_connector:
            self.fdb_connector.set_context(context)

    def is_common_network(self, network):
        # Does this network belong in the /Common folder?
        return network['shared'] or \
            (network['id'] in self.conf.common_network_ids) or \
            ('router:external' in network and
             network['router:external'] and
             self.conf.f5_common_external_networks)

    def get_vlan_name(self, network, hostname):
        segment = self._get_segment(network, 'vlan')

        # Construct a consistent vlan name
        net_key = segment['provider:physical_network']
        # look for host specific interface mapping
        if net_key + ':' + hostname in self.interface_mapping:
            interface = self.interface_mapping[net_key + ':' + hostname]
            tagged = self.tagging_mapping[net_key + ':' + hostname]
        # look for specific interface mapping
        elif net_key in self.interface_mapping:
            interface = self.interface_mapping[net_key]
            tagged = self.tagging_mapping[net_key]
        # use default mapping
        else:
            interface = self.interface_mapping['default']
            tagged = self.tagging_mapping['default']

        if tagged:
            vlanid = segment['provider:segmentation_id']
        else:
            vlanid = 0

        # vlan_name = "vlan-" + \
        #              str(interface).replace(".", "-") + \
        #             "-" + str(vlanid)
        # if len(vlan_name) > 15:
        #     vlan_name = 'vlan-tr-' + str(vlanid)

        vlan_name = 'vlan-' + network['id'][0:10]

        return vlan_name

    def assure_bigip_network(self, bigip, network):
        segment = self._get_segment(network, 'vlan')

        # Ensure bigip has configured network object
        if not network:
            LOG.error('    assure_bigip_network: '
                      'Attempted to assure a network with no id..skipping.')
            return

        if network['id'] in bigip.assured_networks:
            return

        if network['id'] in self.conf.common_network_ids:
            LOG.debug('    assure_bigip_network: '
                      'Network is a common global network... skipping.')
            return

        LOG.debug("        assure_bigip_network network: %s" % str(network))
        start_time = time()
        if self.is_common_network(network):
            network_folder = 'Common'
        else:
            network_folder = self.service_adapter.get_folder_name(
                network['tenant_id'])

        # setup all needed L2 network segments
        if segment['provider:network_type'] == 'flat':
            self._assure_device_network_flat(network, bigip, network_folder)
        elif segment['provider:network_type'] == 'vlan':
            self._assure_device_network_vlan(network, bigip, network_folder)
        elif segment['provider:network_type'] == 'vxlan':
            self._assure_device_network_vxlan(network, bigip, network_folder)
        elif segment['provider:network_type'] == 'gre':
            self._assure_device_network_gre(network, bigip, network_folder)
        else:
            error_message = 'Unsupported network type %s.' \
                            % segment['provider:network_type'] + \
                            ' Cannot setup network.'
            LOG.error(error_message)
            raise f5ex.InvalidNetworkType(error_message)
        bigip.assured_networks.append(network['id'])
        if time() - start_time > .001:
            LOG.debug("        assure bigip network took %.5f secs" %
                      (time() - start_time))

    def _assure_device_network_flat(self, network, bigip, network_folder):
        segment = self._get_segment(network, 'vlan')

        # Ensure bigip has configured flat vlan (untagged)
        interface = self.interface_mapping['default']
        vlanid = 0

        # Do we have host specific mappings?
        net_key = segment['provider:physical_network']
        if net_key + ':' + bigip.hostname in \
                self.interface_mapping:
            interface = self.interface_mapping[net_key + ':' + bigip.hostname]
        # Do we have a mapping for this network
        elif net_key in self.interface_mapping:
            interface = self.interface_mapping[net_key]

        vlan_name = self.get_vlan_name(network, bigip.hostname)

        # TODO(Rich Browne): Implementation with VCMP
        self._assure_vcmp_device_network(bigip,
                                         vlan={
                                             'name': vlan_name,
                                             'folder': network_folder,
                                             'id': vlanid,
                                             'interface': interface,
                                             'network': network
                                         })

        if self.vcmp_manager and self.vcmp_manager.get_vcmp_host(bigip):
            interface = None

        model = {
            'name': vlan_name,
            'interface': interface,
            'partition': network_folder,
            'description': network['id'],
            'route_domain_id': network['route_domain_id']
        }
        self.network_helper.create_vlan(bigip, model)

    def _assure_device_network_vlan(self, network, bigip, network_folder):
        segment = self._get_segment(network, 'vlan')

        # Ensure bigip has configured tagged vlan
        # VLAN names are limited to 64 characters including
        # the folder name, so we name them foolish things.
        interface = self.interface_mapping['default']
        tagged = self.tagging_mapping['default']

        # Do we have host specific mappings?
        net_key = segment['provider:physical_network']
        if net_key + ':' + bigip.hostname in \
                self.interface_mapping:
            interface = self.interface_mapping[net_key + ':' + bigip.hostname]
            tagged = self.tagging_mapping[net_key + ':' + bigip.hostname]
        # Do we have a mapping for this network
        elif net_key in self.interface_mapping:
            LOG.error('*****************')

            interface = self.interface_mapping[net_key]
            tagged = self.tagging_mapping[net_key]
            LOG.error(interface)
            LOG.error(tagged)

        if tagged:
            vlanid = segment['provider:segmentation_id']
        else:
            vlanid = 0

        vlan_name = self.get_vlan_name(network, bigip.hostname)

        self._assure_vcmp_device_network(bigip,
                                         vlan={
                                             'name': vlan_name,
                                             'folder': network_folder,
                                             'id': vlanid,
                                             'interface': interface,
                                             'network': network
                                         })

        if self.vcmp_manager and self.vcmp_manager.get_vcmp_host(bigip):
            interface = None

        model = {
            'name': vlan_name,
            'interface': interface,
            'tag': vlanid,
            'partition': network_folder,
            'description': network['id'],
            'route_domain_id': network['route_domain_id']
        }

        LOG.error('*****************')
        LOG.error(network)
        LOG.error(model)

        self.network_helper.create_vlan(bigip, model)

        if self.vlan_binding:
            self.vlan_binding.allow_vlan(device_name=bigip.device_name,
                                         interface=interface,
                                         vlanid=vlanid)

    def _assure_device_network_vxlan(self, network, bigip, partition):
        # Ensure bigip has configured vxlan
        if not bigip.local_ip:
            error_message = 'Cannot create tunnel %s on %s' \
                % (network['id'], bigip.hostname)
            error_message += ' no VTEP SelfIP defined.'
            LOG.error('VXLAN:' + error_message)
            raise f5ex.MissingVTEPAddress('VXLAN:' + error_message)

        tunnel_name = _get_tunnel_name(network)
        # create the main tunnel entry for the fdb records
        payload = {
            'name': tunnel_name,
            'partition': partition,
            'profile': 'vxlan_ovs',
            'key': network['provider:segmentation_id'],
            'localAddress': bigip.local_ip,
            'description': network['id'],
            'route_domain_id': network['route_domain_id']
        }
        LOG.debug("---Creating VXLAN network---")
        LOG.debug(payload)
        self.network_helper.create_multipoint_tunnel(bigip, payload)
        LOG.debug("---VXLAN network Created---")
        if self.fdb_connector:
            self.fdb_connector.notify_vtep_added(network, bigip.local_ip)

    def _assure_device_network_gre(self, network, bigip, partition):
        # Ensure bigip has configured gre tunnel
        if not bigip.local_ip:
            error_message = 'Cannot create tunnel %s on %s' \
                % (network['id'], bigip.hostname)
            error_message += ' no VTEP SelfIP defined.'
            LOG.error('L2GRE:' + error_message)
            raise f5ex.MissingVTEPAddress('L2GRE:' + error_message)

        tunnel_name = _get_tunnel_name(network)
        payload = {
            'name': tunnel_name,
            'partition': partition,
            'profile': 'gre_ovs',
            'key': network['provider:segmentation_id'],
            'localAddress': bigip.local_ip,
            'description': network['id'],
            'route_domain_id': network['route_domain_id']
        }
        self.network_helper.create_multipoint_tunnel(bigip, payload)
        if self.fdb_connector:
            self.fdb_connector.notify_vtep_added(network, bigip.local_ip)

    def _is_vlan_assoc_with_vcmp_guest(self, bigip, vlan):
        if not self.vcmp_manager:
            return False

        # Is a vlan associated with a vcmp_guest?
        try:
            vcmp_host = self.vcmp_manager.get_vcmp_host(bigip)
            vcmp_guest = self.vcmp_manager.get_vcmp_guest(vcmp_host, bigip)
            vlan_list = vcmp_host['bigip'].system.sys_vcmp.get_vlan(
                [vcmp_guest['name']])
            full_path_vlan_name = '/Common/' + prefixed(vlan['name'])
            if full_path_vlan_name in vlan_list[0]:
                LOG.debug(('VLAN %s is associated with guest %s' %
                           (full_path_vlan_name, vcmp_guest['mgmt_addr'])))
                return True
        except Exception as exc:
            LOG.error(('Exception checking association of VLAN %s '
                       'to vCMP Guest %s: %s ' %
                       (vlan['name'], vcmp_guest['mgmt_addr'], exc)))
            return False
        LOG.debug(('VLAN %s is not associated with guest %s' %
                   (full_path_vlan_name, vcmp_guest['mgmt_addr'])))
        return False

    def _assure_vcmp_device_network(self, bigip, vlan):
        # REVISIT FOR VCMP SUPPORT
        # For vCMP Guests, add VLAN to vCMP Host, associate VLAN with
        # vCMP Guest, and remove VLAN from /Common on vCMP Guest.
        if not self.vcmp_manager:
            return

        vcmp_host = self.vcmp_manager.get_vcmp_host(bigip)
        if not vcmp_host:
            return

        # Create the VLAN on the vCMP Host
        try:
            model = {
                'name': vlan['name'],
                'partition': '/Common',
                'tag': vlan['id'],
                'interface': vlan['interface'],
                'description': vlan['network']['id'],
                'route_domain_id': vlan['network']['route_domain_id']
            }
            self.network_helper.create_vlan(bigip, model)
            LOG.debug(('Created VLAN %s on vCMP Host %s' %
                       (vlan['name'], vcmp_host['bigip'].hostname)))
        except Exception as exc:
            LOG.error(('Exception creating VLAN %s on vCMP Host %s:%s' %
                       (vlan['name'], vcmp_host['bigip'].hostname, exc)))

        # Determine if the VLAN is already associated with the vCMP Guest
        if self._is_vlan_assoc_with_vcmp_guest(bigip, vlan):
            return

        # Associate the VLAN with the vCMP Guest
        # MSG: bigip.system does not exist
        vcmp_guest = self.vcmp_manager.get_vcmp_guest(vcmp_host, bigip)
        try:
            vlan_seq = vcmp_host['bigip'].system.sys_vcmp.typefactory.\
                create('Common.StringSequence')
            vlan_seq.values = prefixed(vlan['name'])
            vlan_seq_seq = vcmp_host['bigip'].system.sys_vcmp.typefactory.\
                create('Common.StringSequenceSequence')
            vlan_seq_seq.values = [vlan_seq]
            vcmp_host['bigip'].system.sys_vcmp.add_vlan([vcmp_guest['name']],
                                                        vlan_seq_seq)
            LOG.debug(('Associated VLAN %s with vCMP Guest %s' %
                       (vlan['name'], vcmp_guest['mgmt_addr'])))
        except Exception as exc:
            LOG.error(('Exception associating VLAN %s to vCMP Guest %s: %s ' %
                       (vlan['name'], vcmp_guest['mgmt_addr'], exc)))

        # Wait for the VLAN to propagate to /Common on vCMP Guest
        full_path_vlan_name = '/Common/' + prefixed(vlan['name'])
        vlan_created = False
        v = bigip.net.vlans.vlan
        try:
            for _ in range(0, 30):
                if v.exists(name=vlan['name'], partition='/Common'):
                    v.load(name=vlan['name'], partition='/Common')
                    vlan_created = True
                    break
                LOG.debug(('Wait for VLAN %s to be created on vCMP Guest %s.' %
                           (full_path_vlan_name, vcmp_guest['mgmt_addr'])))
                # sleep(1)

            if vlan_created:
                LOG.debug(('VLAN %s exists on vCMP Guest %s.' %
                           (full_path_vlan_name, vcmp_guest['mgmt_addr'])))
            else:
                LOG.error(('VLAN %s does not exist on vCMP Guest %s.' %
                           (full_path_vlan_name, vcmp_guest['mgmt_addr'])))
        except Exception as exc:
            LOG.error(('Exception waiting for vCMP Host VLAN %s to '
                       'be created on vCMP Guest %s: %s' %
                       (vlan['name'], vcmp_guest['mgmt_addr'], exc)))

        # Delete the VLAN from the /Common folder on the vCMP Guest
        if vlan_created:
            try:
                v.delete()
                LOG.debug(('Deleted VLAN %s from vCMP Guest %s' %
                           (full_path_vlan_name, vcmp_guest['mgmt_addr'])))
            except Exception as exc:
                LOG.error(
                    ('Exception deleting VLAN %s from vCMP Guest %s: %s' %
                     (full_path_vlan_name, vcmp_guest['mgmt_addr'], exc)))

    def delete_bigip_network(self, bigip, network):

        segment = self._get_segment(network, 'vlan')

        # Delete network on bigip
        if network['id'] in self.conf.common_network_ids:
            LOG.debug('skipping delete of common network %s' % network['id'])
            return
        if self.is_common_network(network):
            network_folder = 'Common'
        else:
            network_folder = self.service_adapter.get_folder_name(
                network['tenant_id'])
        if segment['provider:network_type'] == 'vlan':
            self._delete_device_vlan(bigip, network, network_folder)
        elif segment['provider:network_type'] == 'flat':
            self._delete_device_flat(bigip, network, network_folder)
        elif segment['provider:network_type'] == 'vxlan':
            self._delete_device_vxlan(bigip, network, network_folder)
        elif segment['provider:network_type'] == 'gre':
            self._delete_device_gre(bigip, network, network_folder)
        else:
            LOG.error('Unsupported network type %s. Can not delete.' %
                      network['provider:network_type'])
        if network['id'] in bigip.assured_networks:
            bigip.assured_networks.remove(network['id'])

    def _delete_device_vlan(self, bigip, network, network_folder):
        # Delete tagged vlan on specific bigip
        vlan_name = self.get_vlan_name(network, bigip.hostname)
        self.network_helper.delete_vlan(bigip,
                                        vlan_name,
                                        partition=network_folder)
        if self.vlan_binding:
            interface = self.interface_mapping['default']
            tagged = self.tagging_mapping['default']
            vlanid = 0
            # Do we have host specific mappings?
            net_key = network['provider:physical_network']
            if net_key + ':' + bigip.hostname in \
                    self.interface_mapping:
                interface = self.interface_mapping[net_key + ':' +
                                                   bigip.hostname]
                tagged = self.tagging_mapping[net_key + ':' + bigip.hostname]
            # Do we have a mapping for this network
            elif net_key in self.interface_mapping:
                interface = self.interface_mapping[net_key]
                tagged = self.tagging_mapping[net_key]
            if tagged:
                vlanid = network['provider:segmentation_id']
            else:
                vlanid = 0

            self.vlan_binding.prune_vlan(device_name=bigip.device_name,
                                         interface=interface,
                                         vlanid=vlanid)

        self._delete_vcmp_device_network(bigip, vlan_name)

    def _delete_device_flat(self, bigip, network, network_folder):
        # Delete untagged vlan on specific bigip
        vlan_name = self.get_vlan_name(network, bigip.hostname)

        self.network_helper.delete_vlan(bigip,
                                        vlan_name,
                                        partition=network_folder)

        self._delete_vcmp_device_network(bigip, vlan_name)

    def _delete_device_vxlan(self, bigip, network, network_folder):
        # Delete vxlan tunnel on specific bigip
        tunnel_name = _get_tunnel_name(network)

        self.network_helper.delete_all_fdb_entries(bigip,
                                                   tunnel_name,
                                                   partition=network_folder)

        self.network_helper.delete_tunnel(bigip,
                                          tunnel_name,
                                          partition=network_folder)

        if self.fdb_connector:
            self.fdb_connector.notify_vtep_removed(network, bigip.local_ip)

    def _delete_device_gre(self, bigip, network, network_folder):
        # Delete gre tunnel on specific bigip
        tunnel_name = _get_tunnel_name(network)

        self.network_helper.delete_all_fdb_entries(bigip,
                                                   tunnel_name,
                                                   partition=network_folder)

        self.network_helper.delete_tunnel(bigip,
                                          tunnel_name,
                                          partition=network_folder)

        if self.fdb_connector:
            self.fdb_connector.notify_vtep_removed(network, bigip.local_ip)

    def _delete_vcmp_device_network(self, bigip, vlan_name):
        # For vCMP Guests, disassociate VLAN from vCMP Guest and
        # delete VLAN from vCMP Host.

        if not self.vcmp_manager:
            return

        vcmp_host = self.vcmp_manager.get_vcmp_host(bigip)
        if not vcmp_host:
            return

        # Remove VLAN association from the vCMP Guest
        vcmp_guest = self.vcmp_manager.get_vcmp_guest(vcmp_host, bigip)
        try:
            # REVISIT: the extra attributes in vcmp bigips need to be
            # worked on.
            vlan_seq = vcmp_host['bigip'].system.sys_vcmp.typefactory.\
                create('Common.StringSequence')
            vlan_seq.values = prefixed(vlan_name)
            vlan_seq_seq = vcmp_host['bigip'].system.sys_vcmp.typefactory.\
                create('Common.StringSequenceSequence')
            vlan_seq_seq.values = [vlan_seq]
            vcmp_host['bigip'].system.sys_vcmp.remove_vlan(
                [vcmp_guest['name']], vlan_seq_seq)
            LOG.debug(('Removed VLAN %s association from vCMP Guest %s' %
                       (vlan_name, vcmp_guest['mgmt_addr'])))
        except Exception as exc:
            LOG.error(
                ('Exception removing VLAN %s association from vCMP '
                 'Guest %s:%s' % (vlan_name, vcmp_guest['mgmt_addr'], exc)))

        # Only delete VLAN if it is not in use by other vCMP Guests
        if self.vcmp_manager.get_vlan_use_count(vcmp_host, vlan_name):
            LOG.debug(('VLAN %s in use by other vCMP Guests on vCMP Host %s' %
                       (vlan_name, vcmp_host['bigip'].hostname)))
            return

        # Delete VLAN from vCMP Host.  This will fail if any other vCMP Guest
        # is using this VLAN
        try:
            self.network_helper.delete_vlan(vcmp_host['bigip'], vlan_name)
            LOG.debug(('Deleted VLAN %s from vCMP Host %s' %
                       (vlan_name, vcmp_host['bigip'].hostname)))
        except Exception as exc:
            LOG.error(('Exception deleting VLAN %s from vCMP Host %s:%s' %
                       (vlan_name, vcmp_host['bigip'].icontrol.hostname, exc)))

    def add_bigip_fdbs(self, bigip, net_folder, fdb_info, vteps_by_type):
        # Add fdb records for a mac/ip with specified vteps
        network = fdb_info['network']
        net_type = network['provider:network_type']
        vteps_key = net_type + '_vteps'
        if vteps_key in vteps_by_type:
            vteps = vteps_by_type[vteps_key]
            if net_type == 'gre':
                self.add_gre_fdbs(bigip, net_folder, fdb_info, vteps)
            elif net_type == 'vxlan':
                self.add_vxlan_fdbs(bigip, net_folder, fdb_info, vteps)

    def add_gre_fdbs(self, bigip, net_folder, fdb_info, vteps):
        # Add gre fdb records
        network = fdb_info['network']
        ip_address = fdb_info['ip_address']
        mac_address = fdb_info['mac_address']
        tunnel_name = _get_tunnel_name(network)
        for vtep in vteps:
            if mac_address:
                mac_addr = mac_address
            else:
                mac_addr = _get_tunnel_fake_mac(network, vtep)
            # REVISIT(Rich Browne) caller must provide folder and not tenant_id
            self.network_helper.add_fdb_entry(bigip,
                                              tunnel_name=tunnel_name,
                                              partition=net_folder,
                                              mac_address=mac_addr,
                                              vtep_ip_address=vtep,
                                              arp_ip_address=ip_address)

    def add_vxlan_fdbs(self, bigip, net_folder, fdb_info, vteps):
        # Add vxlan fdb records
        network = fdb_info['network']
        ip_address = fdb_info['ip_address']
        mac_address = fdb_info['mac_address']
        tunnel_name = _get_tunnel_name(network)
        for vtep in vteps:
            if mac_address:
                mac_addr = mac_address
            else:
                mac_addr = _get_tunnel_fake_mac(network, vtep)
            # REVISIT caller must provide folder and not tenant_id
            self.network_helper.add_fdb_entry(bigip,
                                              tunnel_name=tunnel_name,
                                              partition=net_folder,
                                              mac_address=mac_addr,
                                              vtep_ip_address=vtep,
                                              arp_ip_address=ip_address)

    def delete_bigip_fdbs(self, bigip, net_folder, fdb_info, vteps_by_type):
        # Delete fdb records for a mac/ip with specified vteps
        network = fdb_info['network']
        net_type = network['provider:network_type']
        vteps_key = net_type + '_vteps'
        if vteps_key in vteps_by_type:
            vteps = vteps_by_type[vteps_key]
            if net_type == 'gre':
                self.delete_gre_fdbs(bigip, net_folder, fdb_info, vteps)
            elif net_type == 'vxlan':
                self.delete_vxlan_fdbs(bigip, net_folder, fdb_info, vteps)

    def delete_gre_fdbs(self, bigip, net_folder, fdb_info, vteps):
        # delete gre fdb records
        network = fdb_info['network']
        ip_address = fdb_info['ip_address']
        mac_address = fdb_info['mac_address']
        tunnel_name = _get_tunnel_name(network)
        for vtep in vteps:
            if mac_address:
                mac_addr = mac_address
            else:
                mac_addr = _get_tunnel_fake_mac(network, vtep)

            self.network_helper.delete_fdb_entry(bigip,
                                                 tunnel_name=tunnel_name,
                                                 mac_address=mac_addr,
                                                 arp_ip_address=ip_address,
                                                 partition=net_folder)

    def delete_vxlan_fdbs(self, bigip, net_folder, fdb_info, vteps):
        # delete vxlan fdb records
        network = fdb_info['network']
        ip_address = fdb_info['ip_address']
        mac_address = fdb_info['mac_address']
        tunnel_name = _get_tunnel_name(network)
        for vtep in vteps:
            if mac_address:
                mac_addr = mac_address
            else:
                mac_addr = _get_tunnel_fake_mac(network, vtep)
            self.network_helper.delete_fdb_entry(bigip,
                                                 tunnel_name=tunnel_name,
                                                 mac_address=mac_addr,
                                                 arp_ip_address=ip_address,
                                                 partition=net_folder)

    def add_bigip_fdb(self, bigip, fdb):
        # Add entries from the fdb relevant to the bigip
        for fdb_operation in \
            [{'network_type': 'vxlan',
              'get_tunnel_folder': self.network_helper.get_tunnel_folder,
              'fdb_method': self.network_helper.add_fdb_entries},

             {'network_type': 'gre',
              'get_tunnel_folder': self.network_helper.get_tunnel_folder,
              'fdb_method': self.network_helper.add_fdb_entries}]:
            self._operate_bigip_fdb(bigip, fdb, fdb_operation)

    def _operate_bigip_fdb(self, bigip, fdb, fdb_operation):
        """Add L2 records for MAC addresses behind tunnel endpoints.

            Description of fdb structure:
            {'<network_id>':
                'segment_id': <int>
                'ports': [ '<vtep>': ['<mac_address>': '<ip_address>'] ]
             '<network_id>':
                'segment_id':
                'ports': [ '<vtep>': ['<mac_address>': '<ip_address>'] ] }

            Sample real fdb structure:
            {u'45bbbce1-191b-4f7b-84c5-54c6c8243bd2':
                {u'segment_id': 1008,
                 u'ports':
                     {u'10.30.30.2': [[u'00:00:00:00:00:00', u'0.0.0.0'],
                                      [u'fa:16:3e:3d:7b:7f', u'10.10.1.4']]},
                 u'network_type': u'vxlan'}}
        """
        network_type = fdb_operation['network_type']
        get_tunnel_folder = fdb_operation['get_tunnel_folder']
        fdb_method = fdb_operation['fdb_method']

        for network in fdb:
            net_fdb = fdb[network]
            if net_fdb['network_type'] == network_type:
                net = {
                    'name': network,
                    'provider:network_type': net_fdb['network_type'],
                    'provider:segmentation_id': net_fdb['segment_id']
                }
                tunnel_name = _get_tunnel_name(net)
                folder = get_tunnel_folder(bigip, tunnel_name=tunnel_name)
                net_info = {
                    'network': network,
                    'folder': folder,
                    'tunnel_name': tunnel_name,
                    'net_fdb': net_fdb
                }
                fdbs = self._get_bigip_network_fdbs(bigip, net_info)
                if len(fdbs) > 0:
                    fdb_method(fdb_entries=fdbs)

    def _get_bigip_network_fdbs(self, bigip, net_info):
        # Get network fdb entries to add to a bigip
        if not net_info['folder']:
            return {}
        net_fdb = net_info['net_fdb']
        fdbs = {}
        for vtep in net_fdb['ports']:
            # bigip does not need to set fdb entries for local addresses
            if vtep == bigip.local_ip:
                continue

            # most net_info applies to the vtep
            vtep_info = dict(net_info)
            # but the network fdb is too broad so delete it
            del vtep_info['net_fdb']
            # use a slice of the fdb for the vtep instead
            vtep_info['vtep'] = vtep
            vtep_info['fdb_entries'] = net_fdb['ports'][vtep]

            self._merge_vtep_fdbs(vtep_info, fdbs)
        return fdbs

    def _merge_vtep_fdbs(self, vtep_info, fdbs):
        # Add L2 records for a specific network+vtep
        folder = vtep_info['folder']
        tunnel_name = vtep_info['tunnel_name']
        for entry in vtep_info['fdb_entries']:
            mac_address = entry[0]
            if mac_address == '00:00:00:00:00:00':
                continue
            ip_address = entry[1]

            # create/get tunnel data
            if tunnel_name not in fdbs:
                fdbs[tunnel_name] = {}
            tunnel_fdbs = fdbs[tunnel_name]
            # update tunnel folder
            tunnel_fdbs['folder'] = folder

            # maybe create records for tunnel
            if 'records' not in tunnel_fdbs:
                tunnel_fdbs['records'] = {}

            # add entry to records map keyed by mac address
            tunnel_fdbs['records'][mac_address] = \
                {'endpoint': vtep_info['vtep'], 'ip_address': ip_address}

    def update_bigip_fdb(self, bigip, fdb):
        # Update l2 records
        self.add_bigip_fdb(bigip, fdb)

    def remove_bigip_fdb(self, bigip, fdb):
        # Add L2 records for MAC addresses behind tunnel endpoints
        for fdb_operation in \
            [{'network_type': 'vxlan',
              'get_tunnel_folder': self.network_helper.get_tunnel_folder,
              'fdb_method': self.network_helper.delete_fdb_entries},
             {'network_type': 'gre',
              'get_tunnel_folder': self.network_helper.get_tunnel_folder,
              'fdb_method': self.network_helper.delete_fdb_entries}]:
            self._operate_bigip_fdb(bigip, fdb, fdb_operation)

    # Utilities
    def get_network_name(self, bigip, network):

        segment = self._get_segment(network, 'vlan')

        # This constructs a name for a tunnel or vlan interface
        preserve_network_name = False
        if network['id'] in self.conf.common_network_ids:
            network_name = self.conf.common_network_ids[network['id']]
            preserve_network_name = True
        elif segment['provider:network_type'] == 'vlan':
            network_name = self.get_vlan_name(network, bigip.hostname)
        elif segment['provider:network_type'] == 'flat':
            network_name = self.get_vlan_name(network, bigip.hostname)
        elif segment['provider:network_type'] == 'vxlan':
            network_name = _get_tunnel_name(network)
        elif segment['provider:network_type'] == 'gre':
            network_name = _get_tunnel_name(network)
        else:
            error_message = 'Unsupported network type %s.' \
                            % network['provider:network_type'] + \
                            ' Cannot setup selfip or snat.'
            LOG.error(error_message)
            raise f5ex.InvalidNetworkType(error_message)
        return network_name, preserve_network_name

    def _get_segment(self, network, segment_type):
        segments = network['segments']
        for segment in segments:
            LOG.error(_(segment))

            if segment['provider:network_type'] == segment_type:
                LOG.error("Returning segment")
                LOG.error(_(segment))
                return segment
 def __init__(self):
     self.network_name = DisconnectedService.network_name
     self.network_helper = NetworkHelper()