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

api: Centralize defaults and add InsertDefaults flag #1744

Merged
merged 1 commit into from
Mar 30, 2017

Conversation

aaronlehmann
Copy link
Collaborator

@aaronlehmann aaronlehmann commented Nov 11, 2016

Currently, there is a problem with visibility of defaults. The best way
to create a service is to only specify fields that you care about,
because then the manager can apply appropriate defaults for the other
fields. However, there's no way to know which values are actually being
used for those fields. The spec is under the user's control, so if they
don't specify a value (for example, Spec.Task.Restart.Delay), this
field will show up as blank when the service is retrieved.

We don't want to break the spec ownership model, but we do want a way to
communicate actual values in use back to the client. For this, we add an
InsertDefaults flag to the ControlAPI which allows fetching a version
of the service with defaults inserted into empty fields.

This also has the benefit of centralizing defaults. Default values for
these fields that were defined in various places around the code are now
centralized in github.com/docker/swarmkit/api/defaults.

Right now this flag is only is only available for Service, but as we
encounter situations where other objects have default values which need
to be exposed, we should add this to other objects as well.

cc @aluzzardi @stevvooe

@codecov-io
Copy link

codecov-io commented Nov 11, 2016

Codecov Report

Merging #1744 into master will increase coverage by 0.01%.
The diff coverage is 66.66%.

@@            Coverage Diff             @@
##           master    #1744      +/-   ##
==========================================
+ Coverage   54.33%   54.35%   +0.01%     
==========================================
  Files         111      111              
  Lines       19323    19327       +4     
==========================================
+ Hits        10499    10505       +6     
+ Misses       7559     7557       -2     
  Partials     1265     1265

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 970b45a...27d3eb8. Read the comment docs.

@@ -463,6 +464,8 @@ func (s *Server) GetService(ctx context.Context, request *api.GetServiceRequest)
return nil, grpc.Errorf(codes.NotFound, "service %s not found", request.ServiceID)
}

service.InterpolatedSpec = defaults.InterpolateService(&service.Spec)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

add to ListServices as well

@dongluochen
Copy link
Contributor

Right now InterpolatedSpec is only added to Service, but if we move
forward with this, it would make sense to add it to other objects as
well.

This is a decision to make. Inconsistency in different objects would cause confusion. Can we list the alternatives and what Docker has been using in other places?

@aaronlehmann
Copy link
Collaborator Author

This is a decision to make. Inconsistency in different objects would cause confusion. Can we list the alternatives and what Docker has been using in other places?

I only meant that I haven't gone through the effort of adding this to other objects because the design hasn't been finalized yet. I think it should be part of every object.

@dongluochen
Copy link
Contributor

Since swarmkit is part of Docker, if we are going to implement this, should objects in other Docker kit follow?

@aaronlehmann
Copy link
Collaborator Author

To the extent that other objects have Specs and follow the declarative model, sure. I think swarmkit objects are essentially the only ones that allow updates like this, so it's up to us to define the model.

@aaronlehmann
Copy link
Collaborator Author

Rebased (fairly involved one, since so many things changed since this was opened - new deepcopy, native protobuf types, orchestrator changes, updater changes, etc.)

Would love to get this going again

},
Update: &api.UpdateConfig{
FailureAction: api.UpdateConfig_PAUSE,
Monitor: gogotypes.DurationProto(5 * time.Second),
Copy link
Contributor

Choose a reason for hiding this comment

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

There are more parameters like parallelism, update-delay, etc.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The default value of UpdateDelay is 0, so it's not necessary to include it here. You're right that Parallelism should be included. It used to default to 0, but now defaults to 1. Fixing.

// and it is useful for that to return detailed information about
// what values are actually being used to run the service. In the
// future, we should add add InterpolatedSpec to other objects as well.
ServiceSpec interpolated_spec = 7;
Copy link
Contributor

Choose a reason for hiding this comment

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

I have concern to add another ServiceSpec in service object. It might confuse developer which one to update.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'd welcome any other suggestions.

I actually wanted to support updating based on InterpolatedSpec as a feature. It would let the end user lock in the current default values.

Copy link
Contributor

Choose a reason for hiding this comment

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

If it's only generated on the fly, it's ok.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Expanded the comment to clarify.

Copy link
Contributor

@nishanttotla nishanttotla Feb 24, 2017

Choose a reason for hiding this comment

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

I agree with this design.

Do you think the user should be able to view both the service spec they provided, and the full spec on inspect? As a user, maybe I don't want to always see the defaults.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This will be the behavior with docker inspect <servicename>, which just dumps the JSON. I think docker inspect --pretty <servicename> should show values from the interpolated spec.

Copy link
Contributor

Choose a reason for hiding this comment

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

That sounds right.

@dongluochen
Copy link
Contributor

LGTM

@nishanttotla
Copy link
Contributor

The main question about this approach is whether we want to solve the
defaults visibility problem by using this approach where defaults are
substituted in, or by encouraging the client to set all fields instead
of leaving some empty. There are advantages and disadvantages to each
approach. The benefit of doing this client-side is that changes to
default values won't affect existing services retroactively. However,
expecting the client to fill in all fields with good defaults is
expecting a lot, and would be a barrier to centralizing defaults. I
think the best approach is to apply defaults on the manager side, but be
conservative about changing defaults, knowing that it may have
unexpected side effects.

I don't think the client should have to set all fields, from a usability perspective. It makes it harder to change defaults later for the user of the API, and for us to update protos.

@aluzzardi
Copy link
Member

What bothers me is I don't recall ever seeing one API doing this.

How is the world dealing wit h this right now? I'm sure we're not the first ones struggling with API defaults :)

@aaronlehmann
Copy link
Collaborator Author

@aluzzardi: I'm not sure. This seems to be a problem that's specific to declarative APIs - otherwise the server can just fill in default values when it returns objects.

k8s manages defaults in the apiserver - see kubernetes/enhancements#55 and the issue linked from there. I couldn't find any information about how apiserver defaults are exposed to end users.

I don't think this is particularly complex. We basically have two choices: offer a version of the spec that has missing fields filled in with defaults (InterpolatedSpec in this PR), or expose the default values and let the client merge them in. I proposed the former here because I think it's simpler, but I'm happy to consider the alternative approach if there's a good reason, or any other ideas. Also, if you can point out any similar systems with declarative APIs, I can research how they are (or aren't) handling this.

@aluzzardi
Copy link
Member

@aluzzardi: I'm not sure. This seems to be a problem that's specific to declarative APIs - otherwise the server can just fill in default values when it returns objects.

I think it's generic to REST APIs - PUTs your put the full resource back.

What I don't like is we're going to make the API response more complex with another thing to understand for API users. e.g. getting the interpolated spec and putting it back as the spec is bad.

Also, what happens to the CLI - if we display both it's just going be a pain to get meaningful data back.

@aaronlehmann
Copy link
Collaborator Author

I think it's generic to REST APIs - PUTs your put the full resource back.

The reason I think it's specific to declarative APIs is that in most other cases it's fine to fill in extra fields in the response. For example, if I PUT a very simple object and only define a few fields, there's usually no problem with the server sending me back a better-defined object that has defaults filled in. This isn't suitable for our declarative API because it's important for us not to mess with the Spec field.

What I don't like is we're going to make the API response more complex with another thing to understand for API users. e.g. getting the interpolated spec and putting it back as the spec is bad.

I understand your concerns but I'm wondering if you think there's a better way than what I'm proposing here. As I mentioned above, I see two basic approaches. We can either include something like InterpolatedSpec in API responses, or we can provide a way for the client to retrieve the defaults and merge them in client-side. A third option is to leave the problem unsolved, but I don't think that's a good outcome.

BTW, I don't think getting the InterpolatedSpec and sending it back as the spec is necessarily bad. Yes, it's bad to do it unintentionally, but if it's done intentionally it's an easy way of making the current defaults "sticky".

Also, what happens to the CLI - if we display both it's just going be a pain to get meaningful data back.

I would suggest that docker service inspect --pretty only format the interpolated spec. The user wants to see the values that actually in use. Currently this command skips anything that's not specified in the spec, which I think is really bad.

Currently docker service inspect without --pretty dumps a bunch of JSON to the terminal, which is a design I find very unfortunate. It's true that adding InterpolatedSpec would add more stuff to that JSON and might be confusing. But I think the real problem here is that we have a command that outputs unformatted JSON by default. I don't think we should let this drive our API design.

@aaronlehmann
Copy link
Collaborator Author

Discussed with @aluzzardi - let's change this so that instead of having InterpolatedSpec inside the Service object, there's a parameter in GetServiceRequest that returns a version of the service with defaults inserted.

The CLI can use the defaults package for its help output.

Consider clearing the Version field when the "include defaults" flag is specified, so that users can't inadvertently update based on this option.

@aaronlehmann aaronlehmann changed the title api: Centralize defaults and add InterpolatedSpec api: Centralize defaults and add InsertDefaults flag Mar 21, 2017
@aaronlehmann
Copy link
Collaborator Author

Updated according to the above comment. PTAL.

@aaronlehmann
Copy link
Collaborator Author

Rebased

service.Spec = *defaults.InterpolateService(&service.Spec)
// Clear version to prevent the returned service from being the
// basis for an update.
service.Meta.Version = api.Version{}
Copy link
Member

Choose a reason for hiding this comment

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

I'm having second thought about that.

It would make docker service inspect not include the version which is weird

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Okay, removed that part.

Currently, there is a problem with visibility of defaults. The best way
to create a service is to only specify fields that you care about,
because then the manager can apply appropriate defaults for the other
fields. However, there's no way to know which values are actually being
used for those fields. The spec is under the user's control, so if they
don't specify a value (for example, `Spec.Task.Restart.Delay`), this
field will show up as blank when the service is retrieved.

We don't want to break the spec ownership model, but we do want a way to
communicate actual values in use back to the client. For this, we add an
`InsertDefaults` flag to the ControlAPI which allows fetching a version
of the service with defaults inserted into empty fields.

This also has the benefit of centralizing defaults. Default values for
these fields that were defined in various places around the code are now
centralized in `github.com/docker/swarmkit/api/defaults`.

Right now this flag is only is only available for `Service`, but as we
encounter situations where other objects have default values which need
to be exposed, we should add this to other objects as well.

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
@aluzzardi
Copy link
Member

LGTM

@aaronlehmann aaronlehmann merged commit 50ee2bf into moby:master Mar 30, 2017
@aaronlehmann aaronlehmann deleted the interpolated-spec branch March 30, 2017 01:11
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.

5 participants