L3-ext-gw-modes-spec

= Enabling configurable external gateway modes =

High level description
WIP

Semantics of the various gateway modes

 * 1) SNAT enabled (enable_snat=True, default):  Outbound traffic is Source NATted using the gateway port's IP address
 * 2) SNAT disabled (enable_snat=False). The external gateway will be the default route in the Quantum router, but traffic will not be NATted.

API Changes
Currently the external gateway is configured for a router using the following attribute:

'external_gateway_info': {'allow_post': True, 'allow_put': True, 'is_visible': True, 'default': None}

This attribute is a free-form value at the moment. Leveraging the dict validator introduced in Grizzly, it will be possible to give a structure to this attribute, in the following way:


 * network_id: the identifier of the network to be used an external gateway
 * enable_snat: {True | False} (default: True)

Please note that preserving the network_id attribute, together with the default values for the other attributes, guarantees backward compatibility. if enable_snat is set to True, then the API will throw a 400 if the network specified for the gateway is not external, consistently with the Quantum v2 API.

The policy framework needs to be augmented to set policies on sub attributes; for instance a provider might want to restrict enable_snat to admin only users.

Data Model Changes
The router data model entity should not store just a reference to the external gateway port, but it should also store the 'gateway mode', as this information needs to be persisted in the database.

As the allowed modes are SNAT / no SNAT, these can be achieved with a single boolean flag, enable_snat.

As this is going to be implemented as a distinct API extension, altering the 'Router' model defined in db.l3_db might not be ideal, as these two extra attributes would be available also to plugins which do not support such extension.

For this reason, a new model class, linked to the 'Router' model, might be need. We will call this model class 'RouterNATInfo'. An approach similar to the ExternalNetwork model could be used; this would require installing a model hook for ensuring the two tables are joined whenever the Router table is queried.

Alternatively, SqlAlchemy Joined Table inheritance might be leveraged. This provides a simple mechanism for creating the joined table by using OO constructs. On the other hand it comes with the disadvantage that a joined query is not automatically performed unless the with_polymorphic construct is employed. In this case polymorphic behaviour is definitely an overkill. For this reason we will use joined table inheritance but guaranteeing the join is always performed when querying by ensuring the l3 db class always queries the 'child' model class.

For more information please refer to: http://docs.sqlalchemy.org/en/latest/orm/inheritance.html

L3 agent support
The L3 agent enforces SNAT rules in the process_router routine. This routine should be modified in order to ensure SNAT rules are removed if enable_snat=False, whereas they should be added if enabe_snat=True. If the gateway is removed, the rules should always be removed.

For a correct processing of enable_snat it is also necessary to augment the routines which collect router synchronization data on the server side.

Plugin support
This blueprint will add support for every plugin which leverages the l3 agent. To the best of the drafter's knowledge, these plugins are:


 * hyper-V
 * linuxbridge
 * meta
 * openvswitch
 * nec
 * ryu

Support for other plugins might be added with separate blueprints/bug fixes. As the feature is being implemented with a separate API extension, this won't cause any backward incompatibility or broken use case for the other plugins.

Why only SNAT?
The initial revision of this design spec allowed for disabling floating IPs too by setting enable_dnat to False on the external gateway info. After reviewing the specification and the implementation however we found out this design was a bit flawed. While the use case for disabling floating IPs on a specific router surely makes sense, specifying this property as a part of the external_gateway_info is not the right way of doing it.

From a conceptual point of view:
 * enable_dnat=False does not mean "disable floating IPs". Indeed a floating IPs is usually implemented with a pair of DNAT/SNAT rules.
 * external_gateway_info is a data structure for specifying how traffic from internal router interface should be forwarded to the router's uplink. Floating IPs are instead a configuration for allowing traffic from the outside world. They should not be globally enabled/disabled by setting a property on external_gateway_info.

A way for implementing this use case is to have an attribute, perhaps called enable_floating_ips directly on the router data strucure.
 * enable_floating_ips = True (default value): Operations will work as they do currently
 * enable_floating_ips = False: Floating IP association will fail if it has to go through this router

Code
Code is currently ready under review and divided into three patches