components/openstack/neutron/files/agent/evs_l3_agent.py
changeset 1944 56ac2df1785b
parent 1760 353323c7bdc1
child 1959 ba86b21a837b
child 3196 4c06db2d9388
equal deleted inserted replaced
1943:1a27f000029f 1944:56ac2df1785b
    19 # @author: Dan Wendlandt, Nicira, Inc
    19 # @author: Dan Wendlandt, Nicira, Inc
    20 # @author: Girish Moodalbail, Oracle, Inc.
    20 # @author: Girish Moodalbail, Oracle, Inc.
    21 #
    21 #
    22 
    22 
    23 """
    23 """
    24 Based off generic l3_agent (quantum/agent/l3_agent) code
    24 Based off generic l3_agent (neutron/agent/l3_agent) code
    25 """
    25 """
    26 
    26 
       
    27 import netaddr
       
    28 
    27 from oslo.config import cfg
    29 from oslo.config import cfg
    28 
    30 
    29 from quantum.agent import l3_agent
    31 from neutron.agent import l3_agent
    30 from quantum.agent.solaris import interface
    32 from neutron.agent.linux import utils
    31 from quantum.agent.solaris import ipfilters_manager
    33 from neutron.agent.solaris import interface
    32 from quantum.agent.solaris import net_lib
    34 from neutron.agent.solaris import ipfilters_manager
    33 from quantum.common import constants as l3_constants
    35 from neutron.agent.solaris import net_lib
    34 from quantum.openstack.common import log as logging
    36 from neutron.common import constants as l3_constants
       
    37 from neutron.openstack.common import log as logging
    35 
    38 
    36 
    39 
    37 LOG = logging.getLogger(__name__)
    40 LOG = logging.getLogger(__name__)
    38 INTERNAL_DEV_PREFIX = 'l3i'
    41 INTERNAL_DEV_PREFIX = 'l3i'
    39 EXTERNAL_DEV_PREFIX = 'l3e'
    42 EXTERNAL_DEV_PREFIX = 'l3e'
       
    43 FLOATING_IP_CIDR_SUFFIX = '/32'
    40 
    44 
    41 
    45 
    42 class RouterInfo(object):
    46 class RouterInfo(object):
    43 
    47 
    44     def __init__(self, router_id, root_helper, use_namespaces, router):
    48     def __init__(self, router_id, root_helper, use_namespaces, router):
    45         self.router_id = router_id
    49         self.router_id = router_id
    46         self.ex_gw_port = None
    50         self.ex_gw_port = None
       
    51         self._snat_enabled = None
       
    52         self._snat_action = None
    47         self.internal_ports = []
    53         self.internal_ports = []
    48         self.floating_ips = []
    54         # We do not need either root_helper or namespace, so set them to None
       
    55         self.root_helper = None
       
    56         self.use_namespaces = None
       
    57         # Invoke the setter for establishing initial SNAT action
    49         self.router = router
    58         self.router = router
    50         self.ipfilters_manager = ipfilters_manager.IpfiltersManager()
    59         self.ipfilters_manager = ipfilters_manager.IPfiltersManager()
    51         self.routes = []
    60         self.routes = []
    52 
    61 
       
    62     @property
       
    63     def router(self):
       
    64         return self._router
       
    65 
       
    66     @router.setter
       
    67     def router(self, value):
       
    68         self._router = value
       
    69         if not self._router:
       
    70             return
       
    71         # enable_snat by default if it wasn't specified by plugin
       
    72         self._snat_enabled = self._router.get('enable_snat', True)
       
    73         # Set a SNAT action for the router
       
    74         if self._router.get('gw_port'):
       
    75             self._snat_action = ('add_rules' if self._snat_enabled
       
    76                                  else 'remove_rules')
       
    77         elif self.ex_gw_port:
       
    78             # Gateway port was removed, remove rules
       
    79             self._snat_action = 'remove_rules'
       
    80 
       
    81     def ns_name(self):
       
    82         pass
       
    83 
       
    84     def perform_snat_action(self, snat_callback, *args):
       
    85         # Process SNAT rules for attached subnets
       
    86         if self._snat_action:
       
    87             snat_callback(self, self._router.get('gw_port'),
       
    88                           *args, action=self._snat_action)
       
    89         self._snat_action = None
       
    90 
    53 
    91 
    54 class EVSL3NATAgent(l3_agent.L3NATAgent):
    92 class EVSL3NATAgent(l3_agent.L3NATAgent):
    55 
    93 
    56     RouterInfo = RouterInfo
    94     RouterInfo = RouterInfo
    57 
    95 
    58     OPTS = [
    96     OPTS = [
    59         cfg.StrOpt('external_network_bridge', default='',
       
    60                    help=_("Name of bridge used for external network "
       
    61                           "traffic.")),
       
    62         cfg.StrOpt('interface_driver',
       
    63                    help=_("The driver used to manage the virtual "
       
    64                           "interface.")),
       
    65         cfg.BoolOpt('use_namespaces', default=False,
       
    66                     help=_("Allow overlapping IP.")),
       
    67         cfg.StrOpt('router_id',
       
    68                    help=_("If namespaces is disabled, the l3 agent can only"
       
    69                           " configure a router that has the matching router "
       
    70                           "ID.")),
       
    71         cfg.BoolOpt('handle_internal_only_routers', default=True,
       
    72                     help=_("Agent should implement routers with no gateway")),
       
    73         cfg.StrOpt('gateway_external_network_id', default='',
       
    74                    help=_("UUID of external network for routers implemented "
       
    75                           "by the agents.")),
       
    76         cfg.StrOpt('external_network_datalink', default='net0',
    97         cfg.StrOpt('external_network_datalink', default='net0',
    77                    help=_("Name of the datalink that connects to "
    98                    help=_("Name of the datalink that connects to "
    78                           "an external network.")),
    99                           "an external network.")),
    79         cfg.BoolOpt('allow_forwarding_between_networks', default=False,
   100         cfg.BoolOpt('allow_forwarding_between_networks', default=False,
    80                     help=_("Allow forwarding of packets between tenant's "
   101                     help=_("Allow forwarding of packets between tenant's "
    82     ]
   103     ]
    83 
   104 
    84     def __init__(self, host, conf=None):
   105     def __init__(self, host, conf=None):
    85         cfg.CONF.register_opts(self.OPTS)
   106         cfg.CONF.register_opts(self.OPTS)
    86         cfg.CONF.register_opts(interface.OPTS)
   107         cfg.CONF.register_opts(interface.OPTS)
    87         if not cfg.CONF.router_id:
       
    88             raise SystemExit(_("router_id option needs to be set"))
       
    89 
       
    90         super(EVSL3NATAgent, self).__init__(host=host, conf=conf)
   108         super(EVSL3NATAgent, self).__init__(host=host, conf=conf)
    91 
   109 
    92     def _router_added(self, router_id, router):
   110     def _router_added(self, router_id, router):
    93         ri = RouterInfo(router_id, self.root_helper,
   111         ri = RouterInfo(router_id, self.root_helper,
    94                         self.conf.use_namespaces, router)
   112                         self.conf.use_namespaces, router)
    99         ri.router['gw_port'] = None
   117         ri.router['gw_port'] = None
   100         ri.router[l3_constants.INTERFACE_KEY] = []
   118         ri.router[l3_constants.INTERFACE_KEY] = []
   101         ri.router[l3_constants.FLOATINGIP_KEY] = []
   119         ri.router[l3_constants.FLOATINGIP_KEY] = []
   102         self.process_router(ri)
   120         self.process_router(ri)
   103         del self.router_info[router_id]
   121         del self.router_info[router_id]
       
   122 
       
   123     def process_router(self, ri):
       
   124         ex_gw_port = self._get_ex_gw_port(ri)
       
   125         internal_ports = ri.router.get(l3_constants.INTERFACE_KEY, [])
       
   126         existing_port_ids = set([p['id'] for p in ri.internal_ports])
       
   127         current_port_ids = set([p['id'] for p in internal_ports
       
   128                                 if p['admin_state_up']])
       
   129         new_ports = [p for p in internal_ports if
       
   130                      p['id'] in current_port_ids and
       
   131                      p['id'] not in existing_port_ids]
       
   132         old_ports = [p for p in ri.internal_ports if
       
   133                      p['id'] not in current_port_ids]
       
   134         for p in new_ports:
       
   135             self._set_subnet_info(p)
       
   136             ri.internal_ports.append(p)
       
   137             self.internal_network_added(ri, p)
       
   138 
       
   139         for p in old_ports:
       
   140             ri.internal_ports.remove(p)
       
   141             self.internal_network_removed(ri, p)
       
   142 
       
   143         internal_cidrs = [p['ip_cidr'] for p in ri.internal_ports]
       
   144         # TODO(salv-orlando): RouterInfo would be a better place for
       
   145         # this logic too
       
   146         ex_gw_port_id = (ex_gw_port and ex_gw_port['id'] or
       
   147                          ri.ex_gw_port and ri.ex_gw_port['id'])
       
   148 
       
   149         interface_name = None
       
   150         if ex_gw_port_id:
       
   151             interface_name = self.get_external_device_name(ex_gw_port_id)
       
   152         if ex_gw_port and not ri.ex_gw_port:
       
   153             self._set_subnet_info(ex_gw_port)
       
   154             self.external_gateway_added(ri, ex_gw_port,
       
   155                                         interface_name, internal_cidrs)
       
   156         elif not ex_gw_port and ri.ex_gw_port:
       
   157             self.external_gateway_removed(ri, ri.ex_gw_port,
       
   158                                           interface_name, internal_cidrs)
       
   159 
       
   160         # We don't need this since our IPnat rules are bi-directional
       
   161         # Process SNAT rules for external gateway
       
   162         # ri.perform_snat_action(self._handle_router_snat_rules,
       
   163         #                       internal_cidrs, interface_name)
       
   164 
       
   165         # Process DNAT rules for floating IPs
       
   166         if ex_gw_port:
       
   167             self.process_router_floating_ips(ri, ex_gw_port)
       
   168 
       
   169         ri.ex_gw_port = ex_gw_port
       
   170         ri.enable_snat = ri.router.get('enable_snat')
       
   171         self.routes_updated(ri)
       
   172 
       
   173     def process_router_floating_ips(self, ri, ex_gw_port):
       
   174         """Configure the router's floating IPs
       
   175         Configures floating ips using ipnat(1m) on the router's gateway device.
       
   176 
       
   177         Cleans up floating ips that should not longer be configured.
       
   178         """
       
   179         ifname = self.get_external_device_name(ex_gw_port['id'])
       
   180         ipintf = net_lib.IPInterface(ifname)
       
   181         ipaddr_list = ipintf.ipaddr_list()['static']
       
   182 
       
   183         # Clear out all ipnat rules for floating ips
       
   184         ri.ipfilters_manager.remove_nat_rules(ri.ipfilters_manager.ipv4['nat'])
       
   185 
       
   186         existing_cidrs = set([addr for addr in ipaddr_list])
       
   187         new_cidrs = set()
       
   188 
       
   189         # Loop once to ensure that floating ips are configured.
       
   190         for fip in ri.router.get(l3_constants.FLOATINGIP_KEY, []):
       
   191             fip_ip = fip['floating_ip_address']
       
   192             fip_cidr = str(fip_ip) + FLOATING_IP_CIDR_SUFFIX
       
   193 
       
   194             new_cidrs.add(fip_cidr)
       
   195 
       
   196             if fip_cidr not in existing_cidrs:
       
   197                 ipintf.create_address(fip_cidr)
       
   198 
       
   199             # Rebuild iptables rules for the floating ip.
       
   200             fixed_cidr = str(fip['fixed_ip_address']) + '/32'
       
   201             nat_rules = ['bimap %s %s -> %s' % (ifname, fixed_cidr, fip_cidr)]
       
   202             ri.ipfilters_manager.add_nat_rules(nat_rules)
       
   203 
       
   204         # Clean up addresses that no longer belong on the gateway interface.
       
   205         for ip_cidr in existing_cidrs - new_cidrs:
       
   206             if ip_cidr.endswith(FLOATING_IP_CIDR_SUFFIX):
       
   207                 ipintf.delete_address(ip_cidr)
   104 
   208 
   105     def get_internal_device_name(self, port_id):
   209     def get_internal_device_name(self, port_id):
   106         # Because of the way how dnsmasq works on Solaris, the length
   210         # Because of the way how dnsmasq works on Solaris, the length
   107         # of datalink name cannot exceed 16 (includes terminating nul
   211         # of datalink name cannot exceed 16 (includes terminating nul
   108         # character). So, the linkname can only have 15 characters and
   212         # character). So, the linkname can only have 15 characters and
   111         dname = (INTERNAL_DEV_PREFIX + port_id)[:13]
   215         dname = (INTERNAL_DEV_PREFIX + port_id)[:13]
   112         dname += '_0'
   216         dname += '_0'
   113         return dname.replace('-', '_')
   217         return dname.replace('-', '_')
   114 
   218 
   115     def get_external_device_name(self, port_id):
   219     def get_external_device_name(self, port_id):
       
   220         # please see the comment above
   116         dname = (EXTERNAL_DEV_PREFIX + port_id)[:13]
   221         dname = (EXTERNAL_DEV_PREFIX + port_id)[:13]
   117         dname += '_0'
   222         dname += '_0'
   118         return dname.replace('-', '_')
   223         return dname.replace('-', '_')
   119 
   224 
   120     def external_gateway_added(self, ri, ex_gw_port, internal_cidrs):
   225     def external_gateway_added(self, ri, ex_gw_port,
   121         pass
   226                                external_dlname, internal_cidrs):
   122 
   227 
   123     def external_gateway_removed(self, ri, ex_gw_port, internal_cidrs):
   228         if not net_lib.Datalink.datalink_exists(external_dlname):
   124         pass
   229             dl = net_lib.Datalink(external_dlname)
       
   230             # need to determine the VLAN ID for the VNIC
       
   231             evsname = ex_gw_port['network_id']
       
   232             tenantname = ex_gw_port['tenant_id']
       
   233             cmd = ['/usr/sbin/evsadm', 'show-evs', '-co', 'vid',
       
   234                    '-f', 'tenant=%s' % tenantname, evsname]
       
   235             try:
       
   236                 stdout = utils.execute(cmd)
       
   237             except Exception as err:
       
   238                 LOG.error(_("Failed to retrieve the VLAN ID associated "
       
   239                             "with the external network, and it is required "
       
   240                             "to create external gateway port: %s") % err)
       
   241                 return
       
   242             vid = stdout.splitlines()[0].strip()
       
   243             if vid == "":
       
   244                 LOG.error(_("External Network does not has a VLAN ID "
       
   245                             "associated with it, and it is required to "
       
   246                             "create external gateway port"))
       
   247                 return
       
   248             mac_address = ex_gw_port['mac_address']
       
   249             dl.create_vnic(self.conf.external_network_datalink,
       
   250                            mac_address=mac_address, vid=vid)
       
   251         self.driver.init_l3(external_dlname, [ex_gw_port['ip_cidr']])
       
   252 
       
   253         # TODO(gmoodalb): wrap route(1m) command within a class in net_lib.py
       
   254         gw_ip = ex_gw_port['subnet']['gateway_ip']
       
   255         if gw_ip:
       
   256             cmd = ['/usr/bin/pfexec', '/usr/sbin/route', 'add', 'default',
       
   257                    gw_ip]
       
   258             utils.execute(cmd, check_exit_code=False)
       
   259 
       
   260     def external_gateway_removed(self, ri, ex_gw_port,
       
   261                                  external_dlname, internal_cidrs):
       
   262 
       
   263         if net_lib.Datalink.datalink_exists(external_dlname):
       
   264             self.driver.fini_l3(external_dlname)
       
   265             self.driver.unplug(external_dlname)
       
   266         gw_ip = ex_gw_port['subnet']['gateway_ip']
       
   267         if gw_ip:
       
   268             cmd = ['/usr/bin/pfexec', '/usr/sbin/route', 'delete', 'default',
       
   269                    gw_ip]
       
   270             utils.execute(cmd, check_exit_code=False)
   125 
   271 
   126     def _get_ippool_name(self, mac_address):
   272     def _get_ippool_name(self, mac_address):
   127         # generate a unique-name for ippool(1m) from that last 3
   273         # generate a unique-name for ippool(1m) from that last 3
   128         # bytes of mac-address
   274         # bytes of mac-address
   129         mac_suffix = mac_address.split(':')[3:]
   275         mac_suffix = mac_address.split(':')[3:]
   130         return int("".join(mac_suffix), 16)
   276         return int("".join(mac_suffix), 16)
   131 
   277 
   132     def internal_network_added(self, ri, ex_gw_port, port):
   278     def internal_network_added(self, ri, port):
   133 
   279 
   134         internal_dlname = self.get_internal_device_name(port['id'])
   280         internal_dlname = self.get_internal_device_name(port['id'])
   135         if not net_lib.Datalink.datalink_exists(internal_dlname):
   281         if not net_lib.Datalink.datalink_exists(internal_dlname):
   136             self.driver.plug(port['tenant_id'], port['network_id'], port['id'],
   282             self.driver.plug(port['tenant_id'], port['network_id'], port['id'],
   137                              internal_dlname)
   283                              internal_dlname)
   147         subnet_cidr = port['subnet']['cidr']
   293         subnet_cidr = port['subnet']['cidr']
   148         other_subnet_cidrs = []
   294         other_subnet_cidrs = []
   149         for oip in ri.internal_ports:
   295         for oip in ri.internal_ports:
   150             if oip['mac_address'] != port['mac_address']:
   296             if oip['mac_address'] != port['mac_address']:
   151                 if (self.conf.allow_forwarding_between_networks and
   297                 if (self.conf.allow_forwarding_between_networks and
   152                     oip['tenant_id'] == port['tenant_id']):
   298                         oip['tenant_id'] == port['tenant_id']):
   153                     continue
   299                     continue
   154                 other_subnet_cidrs.append(oip['subnet']['cidr'])
   300                 other_subnet_cidrs.append(oip['subnet']['cidr'])
   155                 ippool_name = self._get_ippool_name(oip['mac_address'])
   301                 ippool_name = self._get_ippool_name(oip['mac_address'])
   156                 ri.ipfilters_manager.add_ippool(ippool_name, [subnet_cidr])
   302                 ri.ipfilters_manager.add_ippool(ippool_name, [subnet_cidr])
   157         # update the new port's pool with other port's cidrs
   303         # update the new port's pool with other port's cidrs
   160         # now setup the IPF rule
   306         # now setup the IPF rule
   161         rules = ['block in quick on %s from %s to pool/%d' %
   307         rules = ['block in quick on %s from %s to pool/%d' %
   162                  (internal_dlname, subnet_cidr, new_ippool_name)]
   308                  (internal_dlname, subnet_cidr, new_ippool_name)]
   163         ri.ipfilters_manager.add_ipf_rules(rules)
   309         ri.ipfilters_manager.add_ipf_rules(rules)
   164 
   310 
   165     def internal_network_removed(self, ri, ex_gw_port, port):
   311     def internal_network_removed(self, ri, port):
   166         internal_dlname = self.get_internal_device_name(port['id'])
   312         internal_dlname = self.get_internal_device_name(port['id'])
   167         if net_lib.Datalink.datalink_exists(internal_dlname):
   313         if net_lib.Datalink.datalink_exists(internal_dlname):
   168             self.driver.fini_l3(internal_dlname)
   314             self.driver.fini_l3(internal_dlname)
   169             self.driver.unplug(internal_dlname)
   315             self.driver.unplug(internal_dlname)
   170 
   316 
   175         ri.ipfilters_manager.remove_ipf_rules(rules)
   321         ri.ipfilters_manager.remove_ipf_rules(rules)
   176         # remove the ippool
   322         # remove the ippool
   177         ri.ipfilters_manager.remove_ippool(ippool_name, None)
   323         ri.ipfilters_manager.remove_ippool(ippool_name, None)
   178         for internal_port in ri.internal_ports:
   324         for internal_port in ri.internal_ports:
   179             if (self.conf.allow_forwarding_between_networks and
   325             if (self.conf.allow_forwarding_between_networks and
   180                 internal_port['tenant_id'] == port['tenant_id']):
   326                     internal_port['tenant_id'] == port['tenant_id']):
   181                 continue
   327                 continue
   182             ippool_name = \
   328             ippool_name = \
   183                 self._get_ippool_name(internal_port['mac_address'])
   329                 self._get_ippool_name(internal_port['mac_address'])
   184             ri.ipfilters_manager.remove_ippool(ippool_name,
   330             subnet_cidr = internal_port['subnet']['cidr']
   185                                                internal_port['subnet']['cidr'])
   331             ri.ipfilters_manager.remove_ippool(ippool_name, [subnet_cidr])
   186 
   332 
   187     def floating_ip_added(self, ri, ex_gw_port, floating_ip, fixed_ip):
   333     def routers_updated(self, context, routers):
   188         floating_ipcidr = str(floating_ip) + '/32'
   334         super(EVSL3NATAgent, self).routers_updated(context, routers)
   189         fixed_ipcidr = str(fixed_ip) + '/32'
   335         if routers:
   190         #ifname = self.get_external_device_name(ex_gw_port['id'])
   336             # If router's interface was removed, then the VNIC associated
   191         ifname = self.conf.external_network_datalink
   337             # with that interface must be deleted immediately. The EVS
   192         ipif = net_lib.IPInterface(ifname)
   338             # plugin can delete the virtual port iff the VNIC associated
   193         ipif.create_address(floating_ipcidr)
   339             # with that virtual port is deleted first.
   194 
   340             self._rpc_loop()
   195         nat_rules = ['bimap %s %s -> %s' %
       
   196                      (ifname, fixed_ipcidr, floating_ipcidr)]
       
   197         ri.ipfilters_manager.add_nat_rules(nat_rules)
       
   198 
       
   199     def floating_ip_removed(self, ri, ex_gw_port, floating_ip, fixed_ip):
       
   200         floating_ipcidr = str(floating_ip) + '/32'
       
   201         fixed_ipcidr = str(fixed_ip) + '/32'
       
   202         ifname = self.conf.external_network_datalink
       
   203         ipif = net_lib.IPInterface(ifname)
       
   204         ipif.delete_address(floating_ipcidr)
       
   205 
       
   206         nat_rules = ['bimap %s %s -> %s' %
       
   207                      (ifname, fixed_ipcidr, floating_ipcidr)]
       
   208         ri.ipfilters_manager.remove_nat_rules(nat_rules)
       
   209 
   341 
   210     def routes_updated(self, ri):
   342     def routes_updated(self, ri):
   211         pass
   343         pass