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.

__________________________________________________________________________
OpenStack Development Mailing List (not for usage questions)
Unsubscribe: openstack-dev-requ...@lists.openstack.org?subject:unsubscribe
http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev

Reply via email to