[openstack-dev] [Heat] RPC API versioning

Zane Bitter zbitter at redhat.com
Thu Aug 6 16:49:00 UTC 2015


On 06/08/15 10:08, Dan Smith wrote:
>> This is, I believe, sufficient to solve our entire problem.
>> Specifically, we have no need for an indirection API that rebroadcasts
>> messages that are too new (since that can't happen with pinning) and no
>> need for Versioned Objects in the RPC layer. (Versioned objects for the
>> DB are still critical, and we are very much better off for all the hard
>> work that Michal and others have put into them. Thanks!)
>
> So all your calls have simple types for all the arguments? Meaning,
> everything looks like this:
>
>    do_thing(uuid, 'foo', 'bar', 123)

Mostly.

> and not:
>
>    do_thing(uuid, params, data, dict_of_stuff)
>
> ?

We do have that, but dict_of_stuff is always just data that was provided 
to us by the user, verbatim. e.g. it's the contents of a template or an 
environment file. We can't control what the user sends us, so pretending 
to 'version' it is meaningless. We just pass it on without modification, 
and the engine either handles it or raises an exception so we can 
provide a 40x error to the user.

This isn't actually the interesting part though, because you're still 
thinking of it backwards - in Heat (unlike Nova) the API has no access 
to the DB, so it's not like dict_of_stuff could contain any internal 
data structures because there _are_ no internal data structures.

The interesting part is the *response* containing complex types. 
However, the same argument applies: the response just contains data that 
we are going to pass straight back to the user verbatim (at least in the 
native API), and comprises a mix of simple types and echoing data we 
originally received from the user.

> If you have the latter, then just doing RPC versioning is a mirage. Nova
> has had basic RPC versioning forever, but we didn't get actual upgrade
> ability until we tightened the screws on what we're actually sending
> over the wire. Just versioning the signatures of the calls doesn't help
> you if you're sending complex data structures (such as our Instance)
> over the wire.
>
> If you think that the object facade is necessary for insulating you from
> DB changes, I feel pretty confident that you need it for the RPC side
> for the same reason.

This assumes Nova's architecture.

> Unless you're going to unpack everything from the
> object into primitive call arguments and ensure that nobody ever changes
> one.

This is effectively what we do, although as noted above it's actually 
the response and not the arguments that we're talking about.

> If you pull things out of the DB and send them over the wire, then
> the DB schema affects your RPC API.

As I've been trying to explain, apparently unsuccessfully, we never ever 
ever pull things out of the DB and send them over the wire. Ever. Never 
have. Never will.

Here's an example of what we actually do:

http://git.openstack.org/cgit/openstack/heat/tree/heat/engine/api.py?h=stable%2Fkilo#n158

This is how we show a resource. The function takes a Resource object, 
which in turn contains a VO with the DB representation of the resource. 
We extract various attributes and perform various calculations with the 
methods of the Resource object (all of which rely to some extent on data 
obtained from the DB). Each bit of data becomes an entry in a dict - 
this is actually the return value, but you could think of it as 
equivalent to each item in the dict as being an argument to call if the 
RPC were initiated from the opposite direction. The values are, for the 
most part, simple types, and the few exceptions are either very basic, 
well-defined and unchanging or they're just echoing data provided 
originally by the user.

The keys to the dict (~argument names) are well-defined in the 
heat.rpc.api module. We never remove a key, because that would break 
userspace. We never change the format of an item, because that would 
break userspace. Sometimes we add a key, but we always implement 
heat-api in such a way that it doesn't care whether the new key is 
present or not (i.e. it passes the data directly to the user without 
looking, or uses response.get(rpc_api.NEW_KEY, default) if it really 
needs to introspect it).

>> The nature of Heat's RPC API is that it is effectively user-facing - the
>> heat-api process is essentially a thin proxy between ReST and RPC. We
>> already have a translation layer between the internal representation(s)
>> of objects and the user-facing representation, in the form of
>> heat.engine.api, and the RPC API is firmly on the user-facing side. The
>> requirements for the content of these messages are actually much
>> stricter than anything we need for RPC API stability, since they need to
>> remain compatible not just with heat-api but with heatclient - and we
>> have *zero* control over when that gets upgraded. Despite that, we've
>> managed quite nicely for ~3 years without breaking changes afaik.
>
> I'm not sure how you evolve the internals without affecting the REST
> side if you don't have a translation layer. If you do, then the RPC API
> changes independently from the REST API.

We do have a translation layer (it's that whole file I linked above), 
but it's inside the engine (i.e. stuff gets translated *before* being 
sent over RPC, not after).

> Anyway, I don't really know anything about the internals of heat, and am
> completely willing to believe that it's fundamentally different in some
> way that makes it immune to the problems something like Nova has trying
> to make this work. I'm not sure I'm convinced of that so far, but that's
> fine :)

Here's the Heat architecture:

           ReST                RPC
   User --------> heat-api ---------> heat-engine ---------> DB

   [-----------------------------------][-------*-------------]
             User-facing data                Internal data

* = boundary where VO are needed

versus Nova (warning: handwaving - I don't really know anything about 
the internals of Nova):

                       DB
                       ^
           ReST        |       RPC
   User --------> nova-api ----------> nova-conductor, &c.
   [---------------][--*---*---------*-------------------]
      User-facing         Internal data structures

The difference is that Nova has internal data structures (specifically 
ones based on DB tables, although that's not necessarily relevant) that 
span multiple processes with an RPC bridge between them. And Heat does 
not. That isn't what we use RPC for.

It isn't that backwards/upwards compat over RPC isn't important, it's 
that we already have a much, much, much stronger contract between 
heat-engine and the *user* than we would ever need to enforce between 
heat-engine and heat-api, and therefore the issue just never comes up.

cheers,
Zane.



More information about the OpenStack-dev mailing list