components/openstack/neutron/files/agent/evs_l3_agent.py
branchs11-update
changeset 4072 db0cec748ec0
parent 3435 25a421888935
child 4430 5858809d8d01
equal deleted inserted replaced
4067:4be1f488dda8 4072:db0cec748ec0
     1 # vim: tabstop=4 shiftwidth=4 softtabstop=4
     1 # vim: tabstop=4 shiftwidth=4 softtabstop=4
     2 
     2 
     3 # Copyright 2012 Nicira Networks, Inc.  All rights reserved.
     3 # Copyright 2012 VMware, Inc.  All rights reserved.
     4 #
     4 #
     5 # Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
     5 # Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
     6 #
     6 #
     7 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
     7 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
     8 #    not use this file except in compliance with the License. You may obtain
     8 #    not use this file except in compliance with the License. You may obtain
     9 #    a copy of the License at
     9 #    a copy of the License at
    10 #
    10 #
    14 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    14 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    15 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    15 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    16 #    License for the specific language governing permissions and limitations
    16 #    License for the specific language governing permissions and limitations
    17 #    under the License.
    17 #    under the License.
    18 #
    18 #
    19 # @author: Dan Wendlandt, Nicira, Inc
       
    20 # @author: Girish Moodalbail, Oracle, Inc.
       
    21 #
       
    22 
    19 
    23 """
    20 """
    24 Based off generic l3_agent (neutron/agent/l3_agent) code
    21 Based off generic l3_agent (neutron/agent/l3_agent) code
    25 """
    22 """
    26 
    23 
       
    24 import errno
    27 import netaddr
    25 import netaddr
    28 
    26 
    29 from oslo.config import cfg
    27 from oslo.config import cfg
    30 
    28 
       
    29 from neutron.agent.common import config
    31 from neutron.agent import l3_agent
    30 from neutron.agent import l3_agent
    32 from neutron.agent.linux import utils
    31 from neutron.agent.linux import utils
    33 from neutron.agent.solaris import interface
    32 from neutron.agent.solaris import interface
    34 from neutron.agent.solaris import ipfilters_manager
       
    35 from neutron.agent.solaris import net_lib
    33 from neutron.agent.solaris import net_lib
       
    34 from neutron.agent.solaris import ra
    36 from neutron.common import constants as l3_constants
    35 from neutron.common import constants as l3_constants
       
    36 from neutron.common import utils as common_utils
    37 from neutron.openstack.common import log as logging
    37 from neutron.openstack.common import log as logging
    38 
    38 
    39 
    39 
    40 LOG = logging.getLogger(__name__)
    40 LOG = logging.getLogger(__name__)
    41 INTERNAL_DEV_PREFIX = 'l3i'
    41 INTERNAL_DEV_PREFIX = 'l3i'
    42 EXTERNAL_DEV_PREFIX = 'l3e'
    42 EXTERNAL_DEV_PREFIX = 'l3e'
    43 FLOATING_IP_CIDR_SUFFIX = '/32'
    43 FLOATING_IP_CIDR_SUFFIX = '/32'
    44 
    44 
    45 
    45 
    46 class RouterInfo(object):
    46 class EVSL3NATAgent(l3_agent.L3NATAgentWithStateReport):
    47 
       
    48     def __init__(self, router_id, root_helper, use_namespaces, router):
       
    49         self.router_id = router_id
       
    50         self.ex_gw_port = None
       
    51         self._snat_enabled = None
       
    52         self._snat_action = None
       
    53         self.internal_ports = []
       
    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
       
    58         self.router = router
       
    59         self.ipfilters_manager = ipfilters_manager.IPfiltersManager()
       
    60         self.routes = []
       
    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 
       
    91 
       
    92 class EVSL3NATAgent(l3_agent.L3NATAgent):
       
    93 
       
    94     RouterInfo = RouterInfo
       
    95 
       
    96     OPTS = [
    47     OPTS = [
    97         cfg.StrOpt('external_network_datalink', default='net0',
    48         cfg.StrOpt('external_network_datalink', default='net0',
    98                    help=_("Name of the datalink that connects to "
    49                    help=_("Name of the datalink that connects to "
    99                           "an external network.")),
    50                           "an external network.")),
   100         cfg.BoolOpt('allow_forwarding_between_networks', default=False,
    51         cfg.BoolOpt('allow_forwarding_between_networks', default=False,
   106         cfg.CONF.register_opts(self.OPTS)
    57         cfg.CONF.register_opts(self.OPTS)
   107         cfg.CONF.register_opts(interface.OPTS)
    58         cfg.CONF.register_opts(interface.OPTS)
   108         super(EVSL3NATAgent, self).__init__(host=host, conf=conf)
    59         super(EVSL3NATAgent, self).__init__(host=host, conf=conf)
   109 
    60 
   110     def _router_added(self, router_id, router):
    61     def _router_added(self, router_id, router):
   111         ri = RouterInfo(router_id, self.root_helper,
    62         ri = l3_agent.RouterInfo(router_id, None,
   112                         self.conf.use_namespaces, router)
    63                                  self.conf.use_namespaces, router)
   113         self.router_info[router_id] = ri
    64         self.router_info[router_id] = ri
   114 
    65 
       
    66         if self.conf.enable_metadata_proxy:
       
    67             self._spawn_metadata_proxy(ri.router_id, ri.ns_name)
       
    68 
   115     def _router_removed(self, router_id):
    69     def _router_removed(self, router_id):
   116         ri = self.router_info[router_id]
    70         ri = self.router_info.get(router_id)
       
    71         if ri is None:
       
    72             LOG.warn(_("Info for router %s were not found. "
       
    73                        "Skipping router removal"), router_id)
       
    74             return
   117         ri.router['gw_port'] = None
    75         ri.router['gw_port'] = None
   118         ri.router[l3_constants.INTERFACE_KEY] = []
    76         ri.router[l3_constants.INTERFACE_KEY] = []
   119         ri.router[l3_constants.FLOATINGIP_KEY] = []
    77         ri.router[l3_constants.FLOATINGIP_KEY] = []
   120         self.process_router(ri)
    78         self.process_router(ri)
       
    79         if self.conf.enable_metadata_proxy:
       
    80             self._destroy_metadata_proxy(ri.router_id, ri.ns_name)
       
    81 
   121         del self.router_info[router_id]
    82         del self.router_info[router_id]
   122 
    83 
       
    84     def _get_metadata_proxy_callback(self, router_id):
       
    85         """Need to override this since we need to pass the absolute
       
    86         path to neutron-ns-metadata-proxy binary.
       
    87         """
       
    88         def callback(pid_file):
       
    89             metadata_proxy_socket = cfg.CONF.metadata_proxy_socket
       
    90             proxy_cmd = ['/usr/lib/neutron/neutron-ns-metadata-proxy',
       
    91                          '--pid_file=%s' % pid_file,
       
    92                          '--metadata_proxy_socket=%s' % metadata_proxy_socket,
       
    93                          '--router_id=%s' % router_id,
       
    94                          '--state_path=%s' % self.conf.state_path,
       
    95                          '--metadata_port=%s' % self.conf.metadata_port]
       
    96             proxy_cmd.extend(config.get_log_args(
       
    97                 cfg.CONF, 'neutron-ns-metadata-proxy-%s.log' %
       
    98                 router_id))
       
    99             return proxy_cmd
       
   100 
       
   101         return callback
       
   102 
       
   103     def external_gateway_snat_rules(self, ex_gw_ip, internal_cidrs,
       
   104                                     interface_name):
       
   105         rules = []
       
   106         for cidr in internal_cidrs:
       
   107             rules.append('map %s %s -> %s/32' %
       
   108                          (interface_name, cidr, ex_gw_ip))
       
   109         return rules
       
   110 
       
   111     def _handle_router_snat_rules(self, ri, ex_gw_port, internal_cidrs,
       
   112                                   interface_name, action):
       
   113         assert not ri.router['distributed']
       
   114 
       
   115         # Remove all the old SNAT rules
       
   116         # This is safe because if use_namespaces is set as False
       
   117         # then the agent can only configure one router, otherwise
       
   118         # each router's SNAT rules will be in their own namespace
       
   119 
       
   120         # get only the SNAT rules
       
   121         old_snat_rules = [rule for rule in ri.ipfilters_manager.ipv4['nat']
       
   122                           if rule.startswith('map')]
       
   123         ri.ipfilters_manager.remove_nat_rules(old_snat_rules)
       
   124 
       
   125         # And add them back if the action is add_rules
       
   126         if action == 'add_rules' and ex_gw_port:
       
   127             # NAT rules are added only if ex_gw_port has an IPv4 address
       
   128             for ip_addr in ex_gw_port['fixed_ips']:
       
   129                 ex_gw_ip = ip_addr['ip_address']
       
   130                 if netaddr.IPAddress(ex_gw_ip).version == 4:
       
   131                     rules = self.external_gateway_snat_rules(ex_gw_ip,
       
   132                                                              internal_cidrs,
       
   133                                                              interface_name)
       
   134                     ri.ipfilters_manager.add_nat_rules(rules)
       
   135                     break
       
   136 
       
   137     @common_utils.exception_logger()
   123     def process_router(self, ri):
   138     def process_router(self, ri):
       
   139         # TODO(mrsmith) - we shouldn't need to check here
       
   140         if 'distributed' not in ri.router:
       
   141             ri.router['distributed'] = False
   124         ex_gw_port = self._get_ex_gw_port(ri)
   142         ex_gw_port = self._get_ex_gw_port(ri)
   125         internal_ports = ri.router.get(l3_constants.INTERFACE_KEY, [])
   143         internal_ports = ri.router.get(l3_constants.INTERFACE_KEY, [])
   126         existing_port_ids = set([p['id'] for p in ri.internal_ports])
   144         existing_port_ids = set([p['id'] for p in ri.internal_ports])
   127         current_port_ids = set([p['id'] for p in internal_ports
   145         current_port_ids = set([p['id'] for p in internal_ports
   128                                 if p['admin_state_up']])
   146                                 if p['admin_state_up']])
   129         new_ports = [p for p in internal_ports if
   147         new_ports = [p for p in internal_ports if
   130                      p['id'] in current_port_ids and
   148                      p['id'] in current_port_ids and
   131                      p['id'] not in existing_port_ids]
   149                      p['id'] not in existing_port_ids]
   132         old_ports = [p for p in ri.internal_ports if
   150         old_ports = [p for p in ri.internal_ports if
   133                      p['id'] not in current_port_ids]
   151                      p['id'] not in current_port_ids]
       
   152         new_ipv6_port = False
       
   153         old_ipv6_port = False
   134         for p in new_ports:
   154         for p in new_ports:
   135             self._set_subnet_info(p)
   155             self._set_subnet_info(p)
       
   156             self.internal_network_added(ri, p)
   136             ri.internal_ports.append(p)
   157             ri.internal_ports.append(p)
   137             self.internal_network_added(ri, p)
   158             if (not new_ipv6_port and
       
   159                     netaddr.IPNetwork(p['subnet']['cidr']).version == 6):
       
   160                 new_ipv6_port = True
   138 
   161 
   139         for p in old_ports:
   162         for p in old_ports:
       
   163             self.internal_network_removed(ri, p)
   140             ri.internal_ports.remove(p)
   164             ri.internal_ports.remove(p)
   141             self.internal_network_removed(ri, p)
   165             if (not old_ipv6_port and
   142 
   166                     netaddr.IPNetwork(p['subnet']['cidr']).version == 6):
   143         internal_cidrs = [p['ip_cidr'] for p in ri.internal_ports]
   167                 old_ipv6_port = True
       
   168 
       
   169         if new_ipv6_port or old_ipv6_port:
       
   170             # refresh ndpd daemon after filling in ndpd.conf
       
   171             # with the right entries
       
   172             ra.enable_ipv6_ra(ri.router_id,
       
   173                               internal_ports,
       
   174                               self.get_internal_device_name)
       
   175 
       
   176         # remove any internal stale router interfaces (i.e., l3i.. VNICs)
       
   177         existing_devices = net_lib.Datalink.show_vnic()
       
   178         current_internal_devs = set([n for n in existing_devices
       
   179                                      if n.startswith(INTERNAL_DEV_PREFIX)])
       
   180         current_port_devs = set([self.get_internal_device_name(id) for
       
   181                                  id in current_port_ids])
       
   182         stale_devs = current_internal_devs - current_port_devs
       
   183         for stale_dev in stale_devs:
       
   184             LOG.debug(_('Deleting stale internal router device: %s'),
       
   185                       stale_dev)
       
   186             self.driver.fini_l3(stale_dev)
       
   187             self.driver.unplug(stale_dev)
       
   188 
   144         # TODO(salv-orlando): RouterInfo would be a better place for
   189         # TODO(salv-orlando): RouterInfo would be a better place for
   145         # this logic too
   190         # this logic too
   146         ex_gw_port_id = (ex_gw_port and ex_gw_port['id'] or
   191         ex_gw_port_id = (ex_gw_port and ex_gw_port['id'] or
   147                          ri.ex_gw_port and ri.ex_gw_port['id'])
   192                          ri.ex_gw_port and ri.ex_gw_port['id'])
   148 
   193 
   149         interface_name = None
   194         interface_name = None
   150         if ex_gw_port_id:
   195         if ex_gw_port_id:
   151             interface_name = self.get_external_device_name(ex_gw_port_id)
   196             interface_name = self.get_external_device_name(ex_gw_port_id)
   152         if ex_gw_port and not ri.ex_gw_port:
   197         if ex_gw_port:
       
   198             def _gateway_ports_equal(port1, port2):
       
   199                 def _get_filtered_dict(d, ignore):
       
   200                     return dict((k, v) for k, v in d.iteritems()
       
   201                                 if k not in ignore)
       
   202 
       
   203                 keys_to_ignore = set(['binding:host_id'])
       
   204                 port1_filtered = _get_filtered_dict(port1, keys_to_ignore)
       
   205                 port2_filtered = _get_filtered_dict(port2, keys_to_ignore)
       
   206                 return port1_filtered == port2_filtered
       
   207 
   153             self._set_subnet_info(ex_gw_port)
   208             self._set_subnet_info(ex_gw_port)
   154             self.external_gateway_added(ri, ex_gw_port,
   209             if not ri.ex_gw_port:
   155                                         interface_name, internal_cidrs)
   210                 self.external_gateway_added(ri, ex_gw_port, interface_name)
       
   211             elif not _gateway_ports_equal(ex_gw_port, ri.ex_gw_port):
       
   212                 self.external_gateway_updated(ri, ex_gw_port, interface_name)
   156         elif not ex_gw_port and ri.ex_gw_port:
   213         elif not ex_gw_port and ri.ex_gw_port:
   157             self.external_gateway_removed(ri, ri.ex_gw_port,
   214             self.external_gateway_removed(ri, ri.ex_gw_port, interface_name)
   158                                           interface_name, internal_cidrs)
   215 
   159 
   216         # Remove any external stale router interfaces (i.e., l3e.. VNICs)
   160         # We don't need this since our IPnat rules are bi-directional
   217         stale_devs = [dev for dev in existing_devices
       
   218                       if dev.startswith(EXTERNAL_DEV_PREFIX)
       
   219                       and dev != interface_name]
       
   220         for stale_dev in stale_devs:
       
   221             LOG.debug(_('Deleting stale external router device: %s'),
       
   222                       stale_dev)
       
   223             self.driver.fini_l3(stale_dev)
       
   224             self.driver.unplug(stale_dev)
       
   225 
       
   226         # Process static routes for router
       
   227         self.routes_updated(ri)
       
   228 
   161         # Process SNAT rules for external gateway
   229         # Process SNAT rules for external gateway
   162         # ri.perform_snat_action(self._handle_router_snat_rules,
   230         if (not ri.router['distributed'] or
   163         #                       internal_cidrs, interface_name)
   231                 ex_gw_port and self.get_gw_port_host(ri.router) == self.host):
   164 
   232             # Get IPv4 only internal CIDRs
   165         # Process DNAT rules for floating IPs
   233             internal_cidrs = [p['ip_cidr'] for p in ri.internal_ports
       
   234                               if netaddr.IPNetwork(p['ip_cidr']).version == 4]
       
   235             ri.perform_snat_action(self._handle_router_snat_rules,
       
   236                                    internal_cidrs, interface_name)
       
   237 
       
   238         # Process SNAT/DNAT rules for floating IPs
       
   239         fip_statuses = {}
   166         if ex_gw_port:
   240         if ex_gw_port:
   167             self.process_router_floating_ips(ri, ex_gw_port)
   241             existing_floating_ips = ri.floating_ips
   168 
   242             fip_statuses = self.process_router_floating_ips(ri, ex_gw_port)
       
   243             # Identify floating IPs which were disabled
       
   244             ri.floating_ips = set(fip_statuses.keys())
       
   245             for fip_id in existing_floating_ips - ri.floating_ips:
       
   246                 fip_statuses[fip_id] = l3_constants.FLOATINGIP_STATUS_DOWN
       
   247             # Update floating IP status on the neutron server
       
   248             self.plugin_rpc.update_floatingip_statuses(
       
   249                 self.context, ri.router_id, fip_statuses)
       
   250 
       
   251         # Update ex_gw_port and enable_snat on the router info cache
   169         ri.ex_gw_port = ex_gw_port
   252         ri.ex_gw_port = ex_gw_port
   170         ri.enable_snat = ri.router.get('enable_snat')
   253         ri.enable_snat = ri.router.get('enable_snat')
   171         self.routes_updated(ri)
       
   172 
   254 
   173     def process_router_floating_ips(self, ri, ex_gw_port):
   255     def process_router_floating_ips(self, ri, ex_gw_port):
   174         """Configure the router's floating IPs
   256         """Configure the router's floating IPs
   175         Configures floating ips using ipnat(1m) on the router's gateway device.
   257         Configures floating ips using ipnat(1m) on the router's gateway device.
   176 
   258 
   178         """
   260         """
   179         ifname = self.get_external_device_name(ex_gw_port['id'])
   261         ifname = self.get_external_device_name(ex_gw_port['id'])
   180         ipintf = net_lib.IPInterface(ifname)
   262         ipintf = net_lib.IPInterface(ifname)
   181         ipaddr_list = ipintf.ipaddr_list()['static']
   263         ipaddr_list = ipintf.ipaddr_list()['static']
   182 
   264 
   183         existing_cidrs = set([addr for addr in ipaddr_list])
   265         existing_cidrs = set(ipaddr_list)
   184         new_cidrs = set()
   266         new_cidrs = set()
   185 
   267 
   186         existing_nat_rules = [nat_rule for nat_rule in
   268         existing_nat_rules = [nat_rule for nat_rule in
   187                               ri.ipfilters_manager.ipv4['nat']]
   269                               ri.ipfilters_manager.ipv4['nat']]
   188         new_nat_rules = []
   270         new_nat_rules = []
   189 
   271 
   190         # Loop once to ensure that floating ips are configured.
   272         # Loop once to ensure that floating ips are configured.
       
   273         fip_statuses = {}
   191         for fip in ri.router.get(l3_constants.FLOATINGIP_KEY, []):
   274         for fip in ri.router.get(l3_constants.FLOATINGIP_KEY, []):
   192             fip_ip = fip['floating_ip_address']
   275             fip_ip = fip['floating_ip_address']
   193             fip_cidr = str(fip_ip) + FLOATING_IP_CIDR_SUFFIX
   276             fip_cidr = str(fip_ip) + FLOATING_IP_CIDR_SUFFIX
   194             new_cidrs.add(fip_cidr)
   277             new_cidrs.add(fip_cidr)
   195             fixed_cidr = str(fip['fixed_ip_address']) + '/32'
   278             fixed_cidr = str(fip['fixed_ip_address']) + '/32'
   196             nat_rule = 'bimap %s %s -> %s' % (ifname, fixed_cidr, fip_cidr)
   279             nat_rule = 'bimap %s %s -> %s' % (ifname, fixed_cidr, fip_cidr)
   197 
   280 
   198             if fip_cidr not in existing_cidrs:
   281             if fip_cidr not in existing_cidrs:
   199                 ipintf.create_address(fip_cidr)
   282                 try:
   200                 ri.ipfilters_manager.add_nat_rules([nat_rule])
   283                     ipintf.create_address(fip_cidr)
       
   284                     ri.ipfilters_manager.add_nat_rules([nat_rule])
       
   285                 except Exception as err:
       
   286                     # TODO(gmoodalb): If we fail in add_nat_rules(), then
       
   287                     # we need to remove the fip_cidr address
       
   288 
       
   289                     # any exception occurred here should cause the floating IP
       
   290                     # to be set in error state
       
   291                     fip_statuses[fip['id']] = (
       
   292                         l3_constants.FLOATINGIP_STATUS_ERROR)
       
   293                     LOG.warn(_("Unable to configure IP address for "
       
   294                                "floating IP: %s: %s") % (fip['id'], err))
       
   295                     continue
       
   296             fip_statuses[fip['id']] = (
       
   297                 l3_constants.FLOATINGIP_STATUS_ACTIVE)
   201             new_nat_rules.append(nat_rule)
   298             new_nat_rules.append(nat_rule)
   202 
   299 
   203         # remove all the old NAT rules
   300         # remove all the old NAT rules
   204         ri.ipfilters_manager.remove_nat_rules(list(set(existing_nat_rules) -
   301         old_nat_rules = list(set(existing_nat_rules) - set(new_nat_rules))
   205                                               set(new_nat_rules)))
   302         # Filter out 'bimap' NAT rules as we don't want to remove NAT rules
       
   303         # that were added for Metadata server
       
   304         old_nat_rules = [rule for rule in old_nat_rules if "bimap" in rule]
       
   305         ri.ipfilters_manager.remove_nat_rules(old_nat_rules)
   206 
   306 
   207         # Clean up addresses that no longer belong on the gateway interface.
   307         # Clean up addresses that no longer belong on the gateway interface.
   208         for ip_cidr in existing_cidrs - new_cidrs:
   308         for ip_cidr in existing_cidrs - new_cidrs:
   209             if ip_cidr.endswith(FLOATING_IP_CIDR_SUFFIX):
   309             if ip_cidr.endswith(FLOATING_IP_CIDR_SUFFIX):
   210                 ipintf.delete_address(ip_cidr)
   310                 ipintf.delete_address(ip_cidr)
       
   311         return fip_statuses
   211 
   312 
   212     def get_internal_device_name(self, port_id):
   313     def get_internal_device_name(self, port_id):
   213         # Because of the way how dnsmasq works on Solaris, the length
   314         # Because of the way how dnsmasq works on Solaris, the length
   214         # of datalink name cannot exceed 16 (includes terminating nul
   315         # of datalink name cannot exceed 16 (includes terminating nul
   215         # character). So, the linkname can only have 15 characters and
   316         # character). So, the linkname can only have 15 characters and
   223         # please see the comment above
   324         # please see the comment above
   224         dname = (EXTERNAL_DEV_PREFIX + port_id)[:13]
   325         dname = (EXTERNAL_DEV_PREFIX + port_id)[:13]
   225         dname += '_0'
   326         dname += '_0'
   226         return dname.replace('-', '_')
   327         return dname.replace('-', '_')
   227 
   328 
   228     def external_gateway_added(self, ri, ex_gw_port,
   329     def external_gateway_added(self, ri, ex_gw_port, external_dlname):
   229                                external_dlname, internal_cidrs):
       
   230 
   330 
   231         if not net_lib.Datalink.datalink_exists(external_dlname):
   331         if not net_lib.Datalink.datalink_exists(external_dlname):
   232             dl = net_lib.Datalink(external_dlname)
   332             dl = net_lib.Datalink(external_dlname)
   233             # need to determine the VLAN ID for the VNIC
   333             # determine the network type of the external network
   234             evsname = ex_gw_port['network_id']
   334             evsname = ex_gw_port['network_id']
   235             tenantname = ex_gw_port['tenant_id']
   335             cmd = ['/usr/sbin/evsadm', 'show-evs', '-co', 'l2type,vid',
   236             cmd = ['/usr/sbin/evsadm', 'show-evs', '-co', 'vid',
   336                    '-f', 'evs=%s' % evsname]
   237                    '-f', 'tenant=%s' % tenantname, evsname]
       
   238             try:
   337             try:
   239                 stdout = utils.execute(cmd)
   338                 stdout = utils.execute(cmd)
   240             except Exception as err:
   339             except Exception as err:
   241                 LOG.error(_("Failed to retrieve the VLAN ID associated "
   340                 LOG.error(_("Failed to retrieve the network type for "
   242                             "with the external network, and it is required "
   341                             "the external network, and it is required "
   243                             "to create external gateway port: %s") % err)
   342                             "to create an external gateway port: %s") % err)
   244                 return
   343                 return
   245             vid = stdout.splitlines()[0].strip()
   344             output = stdout.splitlines()[0].strip()
   246             if vid == "":
   345             l2type, vid = output.split(':')
   247                 LOG.error(_("External Network does not have a VLAN ID "
   346             if l2type != 'flat' and l2type != 'vlan':
   248                             "associated with it, and it is required to "
   347                 LOG.error(_("External network should be either Flat or "
   249                             "create external gateway port"))
   348                             "VLAN based, and it is required to "
       
   349                             "create an external gateway port"))
   250                 return
   350                 return
   251             mac_address = ex_gw_port['mac_address']
   351             elif (l2type == 'vlan' and
   252             dl.create_vnic(self.conf.external_network_datalink,
   352                   self.conf.get("external_network_datalink", None)):
   253                            mac_address=mac_address, vid=vid)
   353                 LOG.warning(_("external_network_datalink is deprecated in "
       
   354                              "Juno and will be removed in the next release of "
       
   355                              "Solaris OpenStack. Please use the evsadm "
       
   356                              "set-controlprop subcommand to setup the "
       
   357                              "uplink-port for an external network"))
       
   358                 # proceed with the old-style of doing things
       
   359                 mac_address = ex_gw_port['mac_address']
       
   360                 dl.create_vnic(self.conf.external_network_datalink,
       
   361                                mac_address=mac_address, vid=vid)
       
   362             else:
       
   363                 # This is to handle HA by Solaris Cluster and is similar to
       
   364                 # the code we already have for the DHCP Agent. So, when
       
   365                 # the 1st L3 agent is down and the second L3 agent tries to
       
   366                 # connect its VNIC to EVS, we will end up in "vport in use"
       
   367                 # error. So, we need to reset the vport before we connect
       
   368                 # the VNIC to EVS.
       
   369                 cmd = ['/usr/sbin/evsadm', 'show-vport', '-f',
       
   370                        'vport=%s' % ex_gw_port['id'], '-co',
       
   371                        'evs,vport,status']
       
   372                 stdout = utils.execute(cmd)
       
   373                 evsname, vportname, status = stdout.strip().split(':')
       
   374                 tenant_id = ex_gw_port['tenant_id']
       
   375                 if status == 'used':
       
   376                     cmd = ['/usr/sbin/evsadm', 'reset-vport', '-T', tenant_id,
       
   377                            '%s/%s' % (evsname, vportname)]
       
   378                     utils.execute(cmd)
       
   379 
       
   380                 # next remove protection setting on the VPort to allow
       
   381                 # multiple floating IPs to be configured on the l3e*
       
   382                 # interface
       
   383                 evsvport = "%s/%s" % (ex_gw_port['network_id'],
       
   384                                       ex_gw_port['id'])
       
   385                 cmd = ['/usr/sbin/evsadm', 'set-vportprop', '-T',
       
   386                        tenant_id, '-p', 'protection=none', evsvport]
       
   387                 utils.execute(cmd)
       
   388                 dl.connect_vnic(evsvport, tenant_id)
       
   389 
   254         self.driver.init_l3(external_dlname, [ex_gw_port['ip_cidr']])
   390         self.driver.init_l3(external_dlname, [ex_gw_port['ip_cidr']])
   255 
   391 
   256         # TODO(gmoodalb): wrap route(1m) command within a class in net_lib.py
   392         # TODO(gmoodalb): wrap route(1m) command within a class in net_lib.py
   257         gw_ip = ex_gw_port['subnet']['gateway_ip']
   393         gw_ip = ex_gw_port['subnet']['gateway_ip']
   258         if gw_ip:
   394         if gw_ip:
   259             cmd = ['/usr/bin/pfexec', '/usr/sbin/route', 'add', 'default',
   395             cmd = ['/usr/bin/pfexec', '/usr/sbin/route', 'add', 'default',
   260                    gw_ip]
   396                    gw_ip]
   261             utils.execute(cmd, check_exit_code=False)
   397             stdout = utils.execute(cmd, extra_ok_codes=[errno.EEXIST])
       
   398             ri.remove_route = True
       
   399             if 'entry exists' in stdout:
       
   400                 ri.remove_route = False
   262 
   401 
   263             # for each of the internal ports, add Policy Based
   402             # for each of the internal ports, add Policy Based
   264             # Routing (PBR) rule
   403             # Routing (PBR) rule
   265             for port in ri.internal_ports:
   404             for port in ri.internal_ports:
   266                 internal_dlname = self.get_internal_device_name(port['id'])
   405                 internal_dlname = self.get_internal_device_name(port['id'])
   268                          (internal_dlname, external_dlname, gw_ip,
   407                          (internal_dlname, external_dlname, gw_ip,
   269                           port['subnet']['cidr'])]
   408                           port['subnet']['cidr'])]
   270                 ipversion = netaddr.IPNetwork(port['subnet']['cidr']).version
   409                 ipversion = netaddr.IPNetwork(port['subnet']['cidr']).version
   271                 ri.ipfilters_manager.add_ipf_rules(rules, ipversion)
   410                 ri.ipfilters_manager.add_ipf_rules(rules, ipversion)
   272 
   411 
   273     def external_gateway_removed(self, ri, ex_gw_port,
   412     def external_gateway_updated(self, ri, ex_gw_port, external_dlname):
   274                                  external_dlname, internal_cidrs):
   413         # There is nothing to do on Solaris
   275 
   414         pass
       
   415 
       
   416     def external_gateway_removed(self, ri, ex_gw_port, external_dlname):
   276         gw_ip = ex_gw_port['subnet']['gateway_ip']
   417         gw_ip = ex_gw_port['subnet']['gateway_ip']
   277         if gw_ip:
   418         if gw_ip:
   278             # remove PBR rules
   419             # remove PBR rules
   279             for port in ri.internal_ports:
   420             for port in ri.internal_ports:
   280                 internal_dlname = self.get_internal_device_name(port['id'])
   421                 internal_dlname = self.get_internal_device_name(port['id'])
   282                          (internal_dlname, external_dlname, gw_ip,
   423                          (internal_dlname, external_dlname, gw_ip,
   283                           port['subnet']['cidr'])]
   424                           port['subnet']['cidr'])]
   284                 ipversion = netaddr.IPNetwork(port['subnet']['cidr']).version
   425                 ipversion = netaddr.IPNetwork(port['subnet']['cidr']).version
   285                 ri.ipfilters_manager.remove_ipf_rules(rules, ipversion)
   426                 ri.ipfilters_manager.remove_ipf_rules(rules, ipversion)
   286 
   427 
   287             cmd = ['/usr/bin/pfexec', '/usr/sbin/route', 'delete', 'default',
   428             if ri.remove_route:
   288                    gw_ip]
   429                 cmd = ['/usr/bin/pfexec', '/usr/sbin/route', 'delete',
   289             utils.execute(cmd, check_exit_code=False)
   430                        'default', gw_ip]
       
   431                 utils.execute(cmd, check_exit_code=False)
   290 
   432 
   291         if net_lib.Datalink.datalink_exists(external_dlname):
   433         if net_lib.Datalink.datalink_exists(external_dlname):
   292             self.driver.fini_l3(external_dlname)
   434             self.driver.fini_l3(external_dlname)
   293             self.driver.unplug(external_dlname)
   435             self.driver.unplug(external_dlname)
       
   436 
       
   437         # remove the EVS VPort associated with external network
       
   438         cmd = ['/usr/sbin/evsadm', 'remove-vport',
       
   439                '-T', ex_gw_port['tenant_id'],
       
   440                '%s/%s' % (ex_gw_port['network_id'], ex_gw_port['id'])]
       
   441         try:
       
   442             utils.execute(cmd)
       
   443         except Exception as err:
       
   444             LOG.error(_("Failed to delete the EVS VPort associated with "
       
   445                         "external network: %s") % err)
   294 
   446 
   295     def _get_ippool_name(self, mac_address, suffix=None):
   447     def _get_ippool_name(self, mac_address, suffix=None):
   296         # Generate a unique-name for ippool(1m) from that last 3
   448         # Generate a unique-name for ippool(1m) from that last 3
   297         # bytes of mac-address. It is called pool name, but it is
   449         # bytes of mac-address. It is called pool name, but it is
   298         # actually a 32 bit integer
   450         # actually a 32 bit integer
   366                           port_subnet))
   518                           port_subnet))
   367 
   519 
   368         ipversion = netaddr.IPNetwork(port_subnet).version
   520         ipversion = netaddr.IPNetwork(port_subnet).version
   369         ri.ipfilters_manager.add_ipf_rules(rules, ipversion)
   521         ri.ipfilters_manager.add_ipf_rules(rules, ipversion)
   370 
   522 
       
   523         # if metadata proxy is enabled, then add the necessary
       
   524         # IP NAT rules to forward the metadata requests to the
       
   525         # metadata proxy server
       
   526         if self.conf.enable_metadata_proxy and ipversion == 4:
       
   527             # TODO(gmoodalb): when IP Filter allows redirection of packets
       
   528             # to loopback IP address, then we need to add an IPF rule allowing
       
   529             # only packets destined to 127.0.0.1:9697 to
       
   530             # neutron-ns-metadata-proxy server
       
   531             rules = ['rdr %s 169.254.169.254/32 port 80 -> %s port %d tcp' %
       
   532                      (internal_dlname, port['fixed_ips'][0]['ip_address'],
       
   533                       self.conf.metadata_port)]
       
   534             ri.ipfilters_manager.add_nat_rules(rules)
       
   535 
   371     def internal_network_removed(self, ri, port):
   536     def internal_network_removed(self, ri, port):
   372         internal_dlname = self.get_internal_device_name(port['id'])
   537         internal_dlname = self.get_internal_device_name(port['id'])
   373         port_subnet = port['subnet']['cidr']
   538         port_subnet = port['subnet']['cidr']
   374         # remove all the IP filter rules that we added during
   539         # remove all the IP filter rules that we added during
   375         # internal network addition
   540         # internal network addition
   409                 iport_block_pname = \
   574                 iport_block_pname = \
   410                     self._get_ippool_name(internal_port['mac_address'])
   575                     self._get_ippool_name(internal_port['mac_address'])
   411                 ri.ipfilters_manager.remove_ippool(iport_block_pname,
   576                 ri.ipfilters_manager.remove_ippool(iport_block_pname,
   412                                                    [port_subnet])
   577                                                    [port_subnet])
   413 
   578 
       
   579         # if metadata proxy is enabled, then remove the IP NAT rules that
       
   580         # were added while adding the internal network
       
   581         if self.conf.enable_metadata_proxy and ipversion == 4:
       
   582             rules = ['rdr %s 169.254.169.254/32 port 80 -> %s port %d tcp' %
       
   583                      (internal_dlname, port['fixed_ips'][0]['ip_address'],
       
   584                       self.conf.metadata_port)]
       
   585             ri.ipfilters_manager.remove_nat_rules(rules)
       
   586 
   414         if net_lib.Datalink.datalink_exists(internal_dlname):
   587         if net_lib.Datalink.datalink_exists(internal_dlname):
   415             self.driver.fini_l3(internal_dlname)
   588             self.driver.fini_l3(internal_dlname)
   416             self.driver.unplug(internal_dlname)
   589             self.driver.unplug(internal_dlname)
   417 
   590 
   418     def routers_updated(self, context, routers):
   591         # remove the EVS VPort associated with internal network
   419         super(EVSL3NATAgent, self).routers_updated(context, routers)
   592         cmd = ['/usr/sbin/evsadm', 'remove-vport', '-T', port['tenant_id'],
   420         if routers:
   593                '%s/%s' % (port['network_id'], port['id'])]
   421             # If router's interface was removed, then the VNIC associated
   594         try:
   422             # with that interface must be deleted immediately. The EVS
   595             utils.execute(cmd)
   423             # plugin can delete the virtual port iff the VNIC associated
   596         except Exception as err:
   424             # with that virtual port is deleted first.
   597             LOG.error(_("Failed to delete the EVS VPort associated with "
   425             self._rpc_loop()
   598                         "internal network: %s") % err)
   426 
   599 
   427     def routes_updated(self, ri):
   600     def routes_updated(self, ri):
   428         pass
   601         pass