[openstack-dev] [Neutron][Heat] The Neutron API and orchestration
Zane Bitter
zbitter at redhat.com
Tue Apr 8 00:28:19 UTC 2014
The Neutron API is a constant cause of pain for us as Heat developers,
but afaik we've never attempted to bring up the issues we have found in
a cross-project forum. I've recently been doing some more investigation
and I want to document the exact ways in which the current Neutron API
breaks orchestration, both in the hope that a future version of it might
be better and as a guide for other API authors.
BTW it's my contention that an API that is bad for orchestration is also
hard to use for the ordinary user as well. When you're trying to figure
out the order of operations you need to do, there are two times at which
you could find out you've got it wrong:
1) Before you run the command, when you realise you don't have all of
the required data yet; or
2) After you run the command, when you get a cryptic error message.
Not only is (1) *mandatory* for a data-driven orchestration system like
Heat, it offers orders-of-magnitude better user experience for everyone.
I should say at the outset that I know next to nothing about Neutron,
and one of the goals of this message is to find out which parts I am
completely wrong about. I did know a little bit about traditional
networking at one time, and even remember some of it ;)
Neutron has a little documentation on workflow, so let's begin there:
http://docs.openstack.org/api/openstack-network/2.0/content/Overview-d1e71.html#Theory
(1) Create a network
Instinctively, I want a Network to be something like a virtual VRF
(VVRF?): a separate namespace with it's own route table, within which
subnet prefixes are not overlapping, but which is completely independent
of other Networks that may contain overlapping subnets. As far as I can
tell, this basically seems to be the case. The difference, of course, is
that instead of having to configure a VRF on every switch/router and
make sure they're all in sync and connected up in the right ways, I just
define it in one place globally and Neutron does the rest. I call this
#winning. Nice work, Neutron.
(2) Associate a subnet with the network
Slightly odd choice of words, because you're actually creating a new
Subnet (there's no such thing as a Subnet not associated with a
Network), but this is probably just a minor documentation nit.
Instinctively, I want a Subnet to be something like a virtual VLAN
(VVLAN?): at its most basic level, just a group of ports that share a
broadcast domain, but also having other properties (e.g. if L3 is in
use, all IP addresses in the subnet should be in the same CIDR). This
doesn't seem to be the case, though, it's just a CIDR prefix, which
leaves me wondering how L2 traffic will be treated, as well as how I
would do things like use both IPv4 and IPv6 on a single port (by
assigning a port to multiple Subnets?). Looking at the docs, there is a
much bigger emphasis on DHCP client settings than I expected - surely I
might want to want to give two sets of ports in the same Subnet
different DHCP configs? Still, this is not bad - the DHCP configuration
is done by the time the Subnet is created, so there's no problem in
connecting stuff to it immediately after.
(3) Boot a VM and attach it to the network
Here's where you completely lost me. I just created a Subnet - maybe a
bunch of Subnets. I don't want to attach my VM just anywhere in the
*Network*, I want to attach it to a *particular* Subnet. It's not at all
obvious where my instance will get attached (at random?), because this
API just plain takes the Wrong data type. As a user, I'm irritated and
confused.
The situation for orchestration, though, is much, much worse. Because
the server takes a reference to a network, the dependency graph
generated from my template will look like this:
Network <---------- Subnet
^
\
------------ Server
And yet if the Server is created before the Subnet (as will happen ~50%
of the time), it will fail. And vice-versa on delete, where the server
must be removed before the subnet. The dependency graph we needed to
create was this:
Network <---------- Subnet <---------- Server
The solution used here was to jury-rig the resource types in Heat with a
hidden dependency. We can't know which Subnet the server will end up
attached to, so we create hidden dependencies on all of the ones defined
in the same template. There's nothing we can do about Subnets defined in
different templates (Heat allows a tree of templates to be instantiated
with a single command) - I'm not sure, but it may be possible even now
to create a tree of stacks that in practice could never be successfully
deleted.
The Neutron models in Heat are so riddled with these kinds of invisible
special-case hacks that all of our public documentation about how Heat
can be expected to respond to a particular template is rendered
effectively meaningless with respect to Neutron.
I should add that we can't blame Nova here, because explicitly creating
a Port doesn't help - it too takes only a network argument, despite
_requiring_ a Subnet that it will be attached to, presumably at random.
In fact using a Port makes things even worse, because although there is
an API for it Nova and Neutron seem to assume that nobody would ever use
it, and therefore even if you create a port explicitly and pass it to
Nova to connect a Server, when you disconnect the Server again the Port
will be deleted at the same time as if you had let Nova create it
implicitly for you. This issue is currently breaking stack updates
because we tend to assume that once we've explicitly created something,
it stays created.
Evidently there is a mechanism for associating a Port with a Subnet, and
that's by assigning a fixed IP - which is hardly ever what I want.
There's no middle ground that I can find between specifying the exact,
fixed IP for a port and just letting it end up somewhere - anywhere - on
the network, entirely at random.
Let's move on to the L3 extension, starting with Routers. There's kind
of an inconsistency here, because Routers are virtual devices that I
need to manage. Hitherto, the point of Neutron was to free me from
managing individual devices and let me manage the network as a whole. Is
there a reason I wouldn't want all of the Subnets in the Network to just
do the Right Thing and make sure everywhere is reachable efficiently
from everywhere else? If I want something separate, wouldn't I use a
different Network? (It's not like I have any control over where in a
Network ports get attached anyway.)
Nonetheless, Routers exist and it appears I have to create one to route
packets between Subnets. From an orchestration perspective, I'd like
Router to take a list of Ports to attach to (and of course I'd like each
Port to be explicitly associated with a Subnet!). I'd be out of luck
though, because even though the Port list is a property of a Router, you
can't set it at creation time, only through an update. This is by
definition possible to do at creation time (if I can do a create call
immediately followed by an update call then the Neutron API can
certainly do this internally), so it's very strange to see it
disallowed. Following this API led us to implement it wrong in Heat as
well, leading to headaches with floating IPs, about which more later. We
also mistakenly used a similar design for the Router's external gateway,
but later corrected it by making it a property of the Router, as it is
in the API (though we still have to live with a lengthy deprecation
period). We'll probably end up doing the same with the interfaces.
Of course it goes without saying that the router gateway is just a
reference to another network and, once again, requires a hidden
dependency on all of the Subnets in the hopes of picking up the right
one. BTW I'm just assuming that the definition of the gateway is
"interface to another Network over which I will do NAT"? I assume that
because of the generic way in which Floating IPs are handled, with a
reference to an external network (I guess the operator provides the user
with the Network UUID for the Internet?) It's not exactly clear why the
external gateway is special enough that you can have only one interface
of this type on a Router, but not so special that it would be considered
a separate thing. There is also a separate Network Gateway, and I have
no idea what that is...
The big problem with Floating IPs is that you can't create them until
all the necessary hops in the internetwork have been set up. And, once
again, there's nothing in the creation parameters that would naturally
order them - you just pass a reference to the external network. We still
have a bug open on this, but what we will have to do is create a hidden
dependency on any RouterInterfaces that connect any Routers whose
external gateway is the same network where the floating IP is allocated.
That's about as horrible as it sounds. A Floating IP needs to take as an
argument a reference to the Router/Gateway which does the NAT:
External External
Network <---- Subnet <---- (gateway)
\
Router <---- Floating IP
Internal / /
Network <---- Subnet <------<---- Port <----
The bane of my existence during Icehouse development has been the
ExtraRoutes table. First off, this is broken in a way completely
unrelated to orchestration: you can't add, remove or change an entry in
the table without rewriting the whole table, so the whole API is a giant
race condition waiting to happen. (This can, and IMHO should, be fixed -
at least for those using the official client - with an ETags header and
the 409 return code.) Everything about this API, though, is strange.
It's another one of those only-on-update properties of a Router, though
in this case that's forced by the fact that you can't attach the Router
to its Subnets during its creation. An extra route doesn't behave at all
like a static RIB entry (with a weight and an administrative distance),
but much like a FIB entry (i.e. it's for routes that have already been
selected to be active). That part makes sense, but the next hop for a
FIB entry is a layer 2 address and this takes an IP address. That makes
no sense to me, since the IP address(es) assigned to the nexthop play no
part in how packets are forwarded. And, of course, it creates massive
dependency issues, because we don't know which ports are going to end up
with the IP addresses required. This API should take a reference to a
Port as the nexthop. I've been told we can't even simulate this in Heat
at the moment because a VPN connection doesn't have a port associated
with it. (If the API accepted _either_ a Port or a VPN connection, that
would be fine by me though.) So far we've been unable to merge
ExtraRoutes into Heat, except for a plugin in /contrib, for want of a
way to make this reliably work in the correct dependency order without
resorting to progressively worse hacks.
I'm sure fresh horrors await in corners I have not yet dug into. I must
say that the VPN Service, happily, is one that seems to have done things
right. Firewall looks pretty good in itself, although the fact that it
is completely disjoint from any other configuration - i.e. you can't
even specify which network it applies to, let alone which gateway - is
incomprehensible.
Over the past couple of development cycles, we've seen a number of
proposals to push orchestration-like features into Neutron itself. It is
now clear to me why: because the Neutron API is illegible to external
orchestration tools, this leads to people wanting to do an end run
around it.
I don't expect that the current API can be fixed without breaking
backwards compatibility, but I hope that folks will take these concepts
into account the next time the Neutron API gets revised. (I also hope we
won't see any more proposals to effectively reimplement Heat behind the
Neutron API ;) Please fell free to include [Heat] in any discussion
along those lines, we'd be happy to give feedback on any given API
designs. In exchange, if any Neutron folks are able to explain the exact
ways in which my ideas about how the current Neutron API does and/or
should work are wrong and/or crazy, I would be most appreciative :)
cheers,
Zane.
More information about the OpenStack-dev
mailing list