Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proto: Add example.cpp to get working JSON serialization #3

Open
wants to merge 7 commits into
base: protobuf3
Choose a base branch
from

Conversation

wking
Copy link

@wking wking commented Sep 27, 2015

Lots more details in the commit messages, but basically this gets us
working JSON (de)serialization for testing until we get protobuf-JSON
bindings working in Go (see #2).

Regardless of which language the eventual example lives in, I think
the Linux(Runtime)Spec drops from 9371318 and 83aff07 make sense
(motivation in the 9371318 commit message), although I'd love to have
a clean way to avoid my “linuxx” hack.

I don't care who shifts to close the under_score vs. camelCase gap (I
shifted runtime_config.proto in 5f422ee to avoid changing the Markdown
spec, but if we want to transition the Markdown spec to under_scores
I'm fine with that (we're still pre-1.0 ;).

Define syntax to fix [1]:

  protoc --go_out=./go config.proto
  [libprotobuf WARNING google/protobuf/compiler/parser.cc:492] No
    syntax specified for the proto file. Please use 'syntax =
    "proto2";' or 'syntax = "proto3";' to specify a syntax
    version. (Defaulted to proto2 syntax.)

Drop 'optional' (with 'sed -i 's/\toptional /\t/' *.proto') to fix:

  protoc --go_out=./go config.proto
  config.proto:9:18: Explicit 'optional' labels are disallowed in the
    Proto3 syntax. To define 'optional' fields in Proto3, simply
    remove the 'optional' label, as fields are 'optional' by default.

Replace the User extensions with 'Any' [2,3] to fix:

  protoc --go_out=./go config.proto
  config.proto: Extensions in proto3 are only allowed for defining
    options.

Drop required (with 'sed -i 's/\trequired /\t/' *.proto') to fix:

  protoc --go_out=./go runtime_config.proto
  runtime_config.proto: Required fields are not allowed in proto3.

Drop DefaultState to fix:

  protoc --go_out=./go runtime_config.proto
  runtime_config.proto: Explicit default values are not allowed in
    proto3.

There's still some trouble with the resulting Go:

  go run ./example.go
  go/config.pb.go:26:8: cannot find package "google/protobuf" in any of:
          /usr/lib/go/src/google/protobuf (from $GOROOT)
          /home/wking/.local/lib/go/src/google/protobuf (from $GOPATH)
  Makefile:31: recipe for target 'example' failed

But I haven't been able to figure that out yet.

[1]: https://developers.google.com/protocol-buffers/docs/proto3#simple
[2]: https://developers.google.com/protocol-buffers/docs/proto3#any
[3]: protocolbuffers/protobuf#828

Signed-off-by: W. Trevor King <[email protected]>
In a separate branch where I tried to handle Any in Go, I got [1]:

  go run ./example.go
  go/config.pb.go:26:8: cannot find package "google/protobuf" in any of:
          /usr/lib/go/src/google/protobuf (from $GOROOT)
          /home/wking/.local/lib/go/src/google/protobuf (from $GOPATH)
  Makefile:31: recipe for target 'example' failed

This commit switches the main example to C++, because it's protobuf's
implementation language, so new features like Any support tend to land
there first.

The example tries to round-trip source JSON into a protobuf message
and back.  It does that by reading from config.json (I've skipped
runtime.json for now) into a message, and then writing JSON serialized
from that message to stdout.  Attributes in the input JSON file that
aren't represented in the protobuf message structure seem to be
silently dropped (which makes forward compatibility somewhat easier,
but explicit validation somewhat harder ;).

The docs for this sort of thing seem sparse (although there is a stale
C++ Any example here [2]).  The source skeleton [3] and Makefile rule
[4] are based on the protobuf examples.  kTypeUrlPrefix [5],
GetTypeUrl [6], the resolver handling [7,8], and the basic to/from
JSON conversion [9].

The commented-out LinuxUser section was how I figured out what to put
in the process.user block of config.json.  The bits of that that
weren't in [10], I figured out just by reading proto/cpp/config.pb.h.

The Makefile rule should likely be using:

   $(filter %.cc, $(CPP_FILES))

but my more restrictive filter removes runtime_config.pb.cc to avoid:

  $ make example_cpp
  c++ example.cc ./cpp/config.pb.cc ./cpp/runtime_config.pb.cc -o example_cpp $(pkg-config --cflags --libs protobuf) -I ./cpp
  ./cpp/runtime_config.pb.h:647:30: error: expected unqualified-id before numeric constant
     const ::oci::LinuxRuntime& linux() const;
                                ^
  ...

[1]: vbatts#2
[2]: https://developers.google.com/protocol-buffers/docs/proto3#any
     Although it recommends:
       status.add_details()->PackFrom(details);
     And the actual usage would be:
       status.mutable_details()->PackFrom(details);
     See [10].
[3]: https://github.com/google/protobuf/blob/v3.0.0-beta-1/examples/add_person.cc
[4]: https://github.com/google/protobuf/blob/v3.0.0-beta-1/examples/Makefile#L24-L26
[5]: https://github.com/google/protobuf/blob/v3.0.0-beta-1/src/google/protobuf/util/json_util_test.cc#L52
[6]: https://github.com/google/protobuf/blob/v3.0.0-beta-1/src/google/protobuf/util/json_util_test.cc#L54-L56
[7]: https://github.com/google/protobuf/blob/v3.0.0-beta-1/src/google/protobuf/util/json_util_test.cc#L66-L67
[8]: https://github.com/google/protobuf/blob/v3.0.0-beta-1/src/google/protobuf/util/json_util_test.cc#L85
[9]: https://github.com/google/protobuf/blob/v3.0.0-beta-1/src/google/protobuf/util/json_util_test.cc#L70-L83
[10]: https://github.com/google/protobuf/blob/v3.0.0-beta-1/src/google/protobuf/any_test.cc#L42

Signed-off-by: W. Trevor King <[email protected]>
Since the Go example is broken in this branch.  See the previous
commit for details.

Signed-off-by: W. Trevor King <[email protected]>
With LinuxSpec, a parser will have to know ahead of time what sort of
config it's reading, any you'll end up with config JSON like:

  {
    "spec": { ...platform agnostic stuff ... },
    "linux_config": { ...Linux-specific stuff... }
  }

By dropping it and adding a LinuxConfig entry to Spec itself, we get
back to our Markdown-specified:

  {
    ...platform agnostic stuff... ,
    "linux": { ...Linux-specific stuff... }
  }

As a minor (I hope) wrinkle, I've changed the field name from "linux"
to "linuxx" to avoid:

  $ make
  protoc --cpp_out=./cpp/ config.proto
  pkg-config --cflags protobuf  # fails if protobuf is not installed
  -pthread
  c++ example.cc ./cpp/config.pb.cc -o example_cpp $(pkg-config --cflags --libs protobuf) -I ./cpp
  ./cpp/config.pb.h:159:25: error: expected unqualified-id before numeric constant
     const ::oci::Process& linux() const;
                           ^
  ...

that seems tied to attributes named "linux".  Hopefully someone will
figure out how to avoid that, in which case we can drop the silly
"linuxx" name.

Signed-off-by: W. Trevor King <[email protected]>
This mirror's the previous commit's change for config.proto, so see
that commit message for an explaination.

The "linuxx" hack works around the "expected unqualified-id before
numeric constant" breakage, so we can restore cpp/runtime_config.pb.h
in the Makefile rule.

Signed-off-by: W. Trevor King <[email protected]>
So it matches (modulo key order in the process object and the @type
addition required by Any) our examples from config.md.  This is all
successfully passed through example_cpp:

  $ diff -u config.json <(./example_cpp | jq .)
  $ echo $?
  0

Signed-off-by: W. Trevor King <[email protected]>
So it matches (modulo key order) our examples from the
runtime-config.md series.  To get as much matching up as possible,
I've converted externally-visible runtime_config.proto entries from
under_scores to camelCase.  We're still not matching some cases well,
and I've interspersed my notes on why in the diff:

  $ diff -u <(cat config.json runtime.json) <(./example_cpp | jq .)
  --- /dev/fd/63	2015-09-27 00:04:18.464247761 -0700
  +++ /dev/fd/62	2015-09-27 00:04:18.463247761 -0700
  @@ -34,43 +34,9 @@
     }
   }
   {
  -  "mounts": {
  -    "proc": {
  -      "type": "proc",
  -      "source": "proc",
  -      "options": []
  -    },
  -    ...
  -  },
  +  "mounts": [
  +    {}
  +  ],

Protobuf doesn't like objects with arbitrary keys (or at least we
haven't found a way to set that up).  So we probably need a
pre-processer to convert mount entries into:

  "mounts": [
    {
      "key": "proc",
      "value": {
        "type": "proc",
        "source": "proc",
        "options": []
      },
    }
  ]

You can do that with jq [1], and that makes the mount difference much
smaller (sysctl need the same change):

  $ mv runtime.json runtime.json-orig
  $ jq '.mounts |= to_entries | .linuxx.sysctl |= to_entries' <runtime.json-orig >runtime.json
  $ diff -u <(cat config.json runtime.json) <(./example_cpp | jq .)
  --- /dev/fd/63  2015-09-27 00:15:56.656282533 -0700
  +++ /dev/fd/62  2015-09-27 00:15:56.656282533 -0700
  @@ -78,8 +78,7 @@
         "key": "proc",
         "value": {
           "type": "proc",
  -        "source": "proc",
  -        "options": []
  +        "source": "proc"
         }
       }
     ],
  ...

The difference here is that protobuf isn't serializing default values,
so it drops the empty array [2] (which I'd suggested we do anyway [3] ;)

  ...
       "rlimits": [
         {
           "type": "RLIMIT_NPROC",
  -        "soft": 1024,
  -        "hard": 102400
  +        "hard": "102400",
  +        "soft": "1024"
         }
       ],
  ...

Protobuf uses strings when writing 64-bit-wide numbers [2], but it
reads both numbers and strings, so this isn't a big deal.

  @@ -198,63 +171,44 @@
       "devices": [
         {
           "path": "/dev/random",
  -        "type": "c",
  -        "major": 1,
  -        "minor": 8,
  -        "permissions": "rwm",
  -        "fileMode": 666,
  -        "uid": 0,
  -        "gid": 0
  +        "major": "1",
  +        "minor": "8",
  +        "permissions": "rwm",
  +        "fileMode": 666
         },
  ...

This has some of the default-dropping and number-string issues
mentioned earlier, as well as the effect of runtime_config.proto's:

  // TODO(vbatts) ensure int32 is fine here, instead of golang's rune
  optional int32 type = 2;

[1]: https://stedolan.github.io/jq/
[2]: https://developers.google.com/protocol-buffers/docs/proto3#json
[3]: opencontainers#123

Signed-off-by: W. Trevor King <[email protected]>
"process": {
"terminal": true,
"user": {
"@type": "type.googleapis.com/oci.LinuxUser",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is type added by the json generation? Or is this something we have in the spec now? (I don't see it)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Wed, Sep 30, 2015 at 10:29:33AM -0700, Timothy Chen wrote:

  •  "@type": "type.googleapis.com/oci.LinuxUser",
    

Is type added by the json generation?

It's added by protobuf so it can figure out which message type to use
for deserializing Any fields. See 1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants