[openstack-dev] [nova] Timestamp formats in the REST API

Mark McLoughlin markmc at redhat.com
Wed Apr 30 21:54:10 UTC 2014


On Tue, 2014-04-29 at 10:39 -0400, Doug Hellmann wrote:
> On Tue, Apr 29, 2014 at 9:48 AM, Mark McLoughlin <markmc at redhat.com> wrote:
> > Hey
> >
> > In this patch:
> >
> >   https://review.openstack.org/83681
> >
> > by Ghanshyam Mann, we encountered an unusual situation where a timestamp
> > in the returned XML looked like this:
> >
> >   2014-04-08 09:00:14.399708+00:00
> >
> > What appeared to be unusual was that the timestamp had both sub-second
> > time resolution and timezone information. It was felt that this wasn't a
> > valid timestamp format and then some debate about how to 'fix' it:
> >
> >   https://review.openstack.org/87563
> >
> > Anyway, this lead me down a bit of a rabbit hole, so I'm going to
> > attempt to document some findings.
> >
> > Firstly, some definitions:
> >
> >   - Python's datetime module talk about datetime objects being 'naive'
> >     or 'aware'
> >
> >         https://docs.python.org/2.7/library/datetime.html
> >
> >        "A datetime object d is aware if d.tzinfo is not None and
> >         d.tzinfo.utcoffset(d) does not return None. If d.tzinfo is None,
> >         or if d.tzinfo is not None but d.tzinfo.utcoffset(d) returns
> >         None, d is naive."
> >
> >     (Most people will have encountered this already, but I'm including
> >     it for completeness)
> >
> >   - The ISO8601 time and date format specifies timestamps like this:
> >
> >         2014-04-29T11:37:00Z
> >
> >     with many variations. One distinguishing aspect of the ISO8601
> >     format is the 'T' separating date and time. RFC3339 is very closely
> >     related and serves as easily accessible documentation of the format:
> >
> >        http://www.ietf.org/rfc/rfc3339.txt
> >
> >   - The Python iso8601 library allows parsing this time format, but
> >     also allows subtle variations that don't conform to the standard
> >     like omitting the 'T' separator:
> >
> >       >>> import iso8601
> >       >>> iso8601.parse_date('2014-04-29 11:37:00Z')
> >       datetime.datetime(2014, 4, 29, 11, 37, tzinfo=<iso8601.iso8601.Utc object at 0x214b050>)
> >
> >     Presumably this is for the pragmatic reason that when you stringify
> >     a datetime object, the resulting string uses ' ' as a separator:
> >
> >       >>> import datetime
> >       >>> str(datetime.datetime(2014, 4, 29, 11, 37))
> >       '2014-04-29 11:37:00'
> >
> > And now some observations on what's going on in Nova:
> >
> >   - We don't store timezone information in the database, but all our
> >     timestamps are relative to UTC nonetheless.
> >
> >   - The objects code automatically adds the UTC to naive datetime
> >     objects:
> >
> >         if value.utcoffset() is None:
> >             value = value.replace(tzinfo=iso8601.iso8601.Utc())
> >
> >     so code that is ported to objects may now be using aware datetime
> >     objects where they were previously using naive objects.
> >
> >   - Whether we store sub-second resolution timestamps in the database
> >     appears to be database specific. In my quick tests, we store that
> >     information in sqlite but not MySQL.
> >
> >   - However, timestamps added by SQLAlchemy when you do e.g. save() do
> >     include sub-second information, so some DB API calls may return
> >     sub-second timestamps even when that information isn't stored in
> >     the database.
> >
> > In our REST APIs, you'll essentially see one of three time formats. I'm
> > calling them 'isotime', 'strtime' and 'xmltime':
> >
> >   - 'isotime' - this is the result from timeutils.isotime(). It
> >     includes timezone information (i.e. a 'Z' prefix) but not
> >     microseconds. You'll see this in places where we stringify the
> >     datetime objects in the API layer using isotime() before passing
> >     them to the JSON/XML serializers.
> >
> >   - 'strtime' - this is the result from timeutils.strtime(). It doesn't
> >     include timezone information but does include decimal seconds. This
> >     is what jsonutils.dumps() uses when we're serializing API responses
> >
> >   - 'xmltime' or 'str(datetime)' format - this is just what you get
> >     when you stringify a datetime using str(). If the datetime is tz
> >     aware or includes non-zero microseconds, then that information will
> >     be included in the result. This is a significant different versus
> >     the other two formats where it is clear whether tz and microsecond
> >     information is included in the string.
> >
> > but there are some caveats:
> >
> >   - I don't know how significant it is these days, but timestamps will
> >     be serialized to strtime format when going over RPC, but won't be
> >     de-serialized on the remote end. This could lead to a situation
> >     where the API layer tries and stringify a strtime formatted string
> >     using timeutils.isotime(). (see below for a description of those
> >     formats)
> >
> >   - In at least one place - e.g. the 'updated' timestamp for v2
> >     extensions - we hardcode the timestamp as strings in the code and
> >     don't currently use one of the formats above.
> >
> >
> > My conclusions from all that:
> >
> >   1) This sucks
> >
> >   2) At the very least, we should be clear in our API samples tests
> >      which of the three formats we expect - we should only change the
> >      format used in a given part of the API after considering any
> >      compatibility considerations
> >
> >   3) We should unify on a single format in the v3 API - IMHO, we should
> >      be explicit about use of the UTC timezone and we should avoid
> >      including microseconds unless there's a clear use case. In other
> >      words, we should use the 'isotime' format.
> 
> This seems like a situation where we should try take the needs of all
> projects into account, so we converge on a consistent timestamp format
> across APIs. For example, ceilometer's database schema was updated to
> support sub-second timestamps to differentiate between events coming
> in very close together
> (https://bugs.launchpad.net/ceilometer/+bug/1215676). I don't know if
> Marconi uses timestamps in their API, but if so that's another case
> where I would expect the caller to want sub-second precision, at least
> sometimes.

Yeah, it's probably easiest for everyone if we just favor
isotime(subsecond=True) as the preferred format in all public APIs
rather than just doing it for those cases we see a real need for it.

That said, there's a bunch of stuff involved to do that even just for
Nova:

  - whether it would be reasonable to change the format used in the v2 
    API - i.e. how likely would such a change break existing clients. 
    IMO, probably too much risk for too little reward to justify it.

  - for the v3 API, either converting jsonutils.dumps() to use 
    isotime(subsecond=True) rather than strtime() or ensuring that all 
    timestamps are serialized with isotime(subsecond=True) before 
    calling dumps()

  - for the v3 API, adding subsecond=True to existing uses of isotime()

  - avoiding having any of this cause incompatible changes in the RPC 
    APIs which also use dumps()

  - ensuring sub-second time information is actually saved in the DB

Thanks,
Mark.




More information about the OpenStack-dev mailing list