snapstate: auto install default-providers for content snaps #4103

Open
wants to merge 18 commits into
from

Conversation

Projects
None yet
6 participants
Collaborator

mvo5 commented Oct 30, 2017

This is the first iteration to install the default-providers for content snaps.

One missing feature is the check if the interface is already connected and to skip the default provider if that is the case. This requires some extra work to refactor the ifacestate repository information which is needed for another PR too so it will happen very soon.

mvo5 added some commits Sep 25, 2017

codecov-io commented Nov 7, 2017

Codecov Report

Merging #4103 into master will increase coverage by 0.48%.
The diff coverage is 55.22%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #4103      +/-   ##
==========================================
+ Coverage   75.39%   75.87%   +0.48%     
==========================================
  Files         436      442       +6     
  Lines       37735    38493     +758     
==========================================
+ Hits        28450    29208     +758     
+ Misses       7296     7265      -31     
- Partials     1989     2020      +31
Impacted Files Coverage Δ
store/store.go 80.44% <100%> (+0.44%) ⬆️
overlord/snapstate/snapmgr.go 83.37% <100%> (+1.3%) ⬆️
overlord/snapstate/snapstate.go 80.12% <22.72%> (-0.36%) ⬇️
overlord/snapstate/handlers.go 65.55% <51.85%> (-0.11%) ⬇️
interfaces/builtin/account_control.go 80.64% <0%> (-19.36%) ⬇️
errtracker/errtracker.go 65.94% <0%> (-5.49%) ⬇️
interfaces/builtin/pulseaudio.go 46.87% <0%> (-4.56%) ⬇️
interfaces/builtin/modem_manager.go 59.09% <0%> (-4.18%) ⬇️
wrappers/services.go 76.53% <0%> (-3.79%) ⬇️
interfaces/builtin/i2c.go 62.79% <0%> (-3.17%) ⬇️
... and 85 more

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 6afffa9...1fe027a. Read the comment docs.

@pedronis pedronis added the Blocked label Nov 9, 2017

mvo5 added some commits Nov 13, 2017

tests: rename test-snapd-content-plug-empty-content-attr to test-snap…
…d-content-plug-no-content-attr because the former is 42 chars long and the store does not accept uploads for it anymore
snapstate: only install a default-provider for a content plug if ther…
…e is no slot for this content interface already available on the system

@mvo5 mvo5 removed the Blocked label Nov 13, 2017

A few comments inline.

I know the test snap names are congested but I also found that part a little confusing. Perhaps we can name the content snaps a little bit differently (like test-snapd-content-provider-{a,b}). Just a suggestion though.

overlord/snapstate/handlers.go
@@ -166,8 +169,23 @@ func (m *SnapManager) doPrerequisites(t *state.Task, _ *tomb.Tomb) error {
prereqName = snapsup.Base
}
+ if err := m.installPrereq(t, prereqName, snapsup.UserID); err != nil {
@zyga

zyga Nov 16, 2017

Contributor

A comment here would be nice. Is this trying to get prerequisites and prerequisites of prerequisites?

overlord/snapstate/snapmgr.go
+ Channel string `json:"channel,omitempty"`
+ UserID int `json:"user-id,omitempty"`
+ Base string `json:"base,omitempty"`
+ Prereq []string `json:"prereq,omitempty"`
@zyga

zyga Nov 16, 2017

Contributor

Can you document what the content of Prereq is? Are those snap names / snap IDs?

+func contentAttr(attrs map[string]interface{}) string {
+ // we always have a "content" attr in plug/slots of interface
+ // type "content"
+ s, _ := attrs["content"].(string)
@zyga

zyga Nov 16, 2017

Contributor

Can you just say return attrs["content"].(string) here?

@mvo5

mvo5 Nov 20, 2017

Collaborator

I could, however it would panic if "content" is not defined in the code. This will just return an empty string. It should not be needed as we always create a content attr for type content but its slightly more defensive.

@niemeyer

niemeyer Nov 30, 2017

Contributor

To be pedantic, it would actually panic if it's not a string, whether it's defined or not. It's the type assertion that panics. The map will gladly return whatever it has or nil.

+ // fill in the plug/slot data
+ if rawYamlInfo, err := snap.InfoFromSnapYaml([]byte(d.SnapYAML)); err == nil {
+ if info.Plugs == nil {
+ info.Plugs = make(map[string]*snap.PlugInfo)
@zyga

zyga Nov 16, 2017

Contributor

Note that plugs refer to apps (and vice versa). Is this code correctly linking plugs/slots/apps/hooks from one info object together? Are we mixing two disconnected objects here?

EDIT: this is the reason for "changes requested" as everything else I remarked upon is cosmetic.

@mvo5

mvo5 Nov 20, 2017

Collaborator

Indeed, nice catch. I added a test and ensure now that the right *Snap is set (before it was wrong :(

Looks good overall, just one remark.

overlord/snapstate/snapstate.go
+ for _, plug := range info.Plugs {
+ if plug.Interface == "content" {
+ if !contentIfaceAvailable(st, contentAttr(plug.Attrs)) {
+ dprovider, ok := plug.Attrs["default-provider"].(string)
@stolowski

stolowski Nov 17, 2017

Contributor

I think we should make sure this attribute is sane in Sanitize method of the plug (after you've read the snap yaml the plugs/slots are already sanitized).

@mvo5

mvo5 Nov 20, 2017

Collaborator

I think this makes sense but unfortunately we have apparently already snaps in the wild that have strange default provider names. So we need to think how we sanitize (warn ideally) without breaking snaps that exist and used to work.

@niemeyer

niemeyer Nov 30, 2017

Contributor

I think this makes sense but unfortunately we have apparently already snaps in the wild that have strange default provider names. So we need to think how we sanitize (warn ideally) without breaking snaps that exist and used to work.

Agreed, we can phase in the implementation without breaking. That said, there's still a point to be sad about not taking user data blindly.

I've never reviewed our internal snapstate functions considering that completely corrupted data could be injected into it, which means we can have serious security issues if we do allow that to happen.

We really must sanitize, even if we don't error out. We can just drop invalid entries, and also fix foo:bar entries so they're just foo.

mvo5 added some commits Nov 20, 2017

Just one more question.

+ info.Slots[k] = v
+ info.Slots[k].Snap = info
+ }
+ }
@zyga

zyga Nov 22, 2017

Contributor

This looks better but I wonder if you need look into info.{Plugs,Slots}.{Apps,Hooks}?

@mvo5

mvo5 Nov 22, 2017

Collaborator

this is done via snap.InfoFromSnapYaml([]byte(d.SnapYAML) automatically AFAICT. I added an extra test for this now just to be sure.

@mvo5 mvo5 added this to the 2.30 milestone Nov 24, 2017

overlord/snapstate/handlers.go
for _, chg := range st.Changes() {
if chg.Status().Ready() {
continue
}
+ if myChgID == chg.ID() {
@pedronis

pedronis Nov 29, 2017

Contributor

I don't understand why we need this change? don't we end up adding the same thing again otherwise?

@niemeyer

niemeyer Nov 30, 2017

Contributor

I don't understand why we need this change? don't we end up adding the same thing again otherwise?

Yes, this looks error prone. If we ask whether a change is in flight, we should get a correct answer for it no matter what. If the call site doesn't care about some changes, then it shouldn't ask the question in the first place.

@niemeyer

niemeyer Nov 30, 2017

Contributor

This point makes me realize that there's a good chance we were talking across each other good part of the time in our call today.

@mvo5 mvo5 modified the milestones: 2.30, 2.31 Nov 30, 2017

A few points, and this one LGTM. We need to follow up on the terms conversation.

overlord/snapstate/handlers.go
for _, chg := range st.Changes() {
if chg.Status().Ready() {
continue
}
+ if myChgID == chg.ID() {
@pedronis

pedronis Nov 29, 2017

Contributor

I don't understand why we need this change? don't we end up adding the same thing again otherwise?

@niemeyer

niemeyer Nov 30, 2017

Contributor

I don't understand why we need this change? don't we end up adding the same thing again otherwise?

Yes, this looks error prone. If we ask whether a change is in flight, we should get a correct answer for it no matter what. If the call site doesn't care about some changes, then it shouldn't ask the question in the first place.

@niemeyer

niemeyer Nov 30, 2017

Contributor

This point makes me realize that there's a good chance we were talking across each other good part of the time in our call today.

+ // Prereq is a list of snap-names that need to get installed
+ // together with this snap. Typically used when installing
+ // content-snaps with default-providers.
+ Prereq []string `json:"prereq,omitempty"`
@niemeyer

niemeyer Nov 30, 2017

Contributor

This would be more like "Recommends", or perhaps "Wants". As discussed we also want to change the task name so it aligns with its purpose and with the usual conventions.

This is probably best for a separate PR, though. Let's please catch up so we decide on good terms we're happy with.

overlord/snapstate/snapstate.go
+func contentIfaceAvailable(st *state.State, contentTag string) bool {
+ repo := ifacerepo.Get(st)
+ for _, slot := range repo.AllSlots("content") {
+ if contentAttr(slot.Attrs) == contentTag {
@niemeyer

niemeyer Nov 30, 2017

Contributor

That implies two content interfaces with the "content" attribute missing ("") would match each other. Do we want that?

@mvo5

mvo5 Dec 11, 2017

Collaborator

The contentInterface.Sanitize{Plug,Slot}() will ensure it is not empty, it is set to {plug,slot}.Name when empty. However I agree the code should be defensive about it.

overlord/snapstate/snapstate.go
+ for _, plug := range info.Plugs {
+ if plug.Interface == "content" {
+ if !contentIfaceAvailable(st, contentAttr(plug.Attrs)) {
+ dprovider, ok := plug.Attrs["default-provider"].(string)
@stolowski

stolowski Nov 17, 2017

Contributor

I think we should make sure this attribute is sane in Sanitize method of the plug (after you've read the snap yaml the plugs/slots are already sanitized).

@mvo5

mvo5 Nov 20, 2017

Collaborator

I think this makes sense but unfortunately we have apparently already snaps in the wild that have strange default provider names. So we need to think how we sanitize (warn ideally) without breaking snaps that exist and used to work.

@niemeyer

niemeyer Nov 30, 2017

Contributor

I think this makes sense but unfortunately we have apparently already snaps in the wild that have strange default provider names. So we need to think how we sanitize (warn ideally) without breaking snaps that exist and used to work.

Agreed, we can phase in the implementation without breaking. That said, there's still a point to be sad about not taking user data blindly.

I've never reviewed our internal snapstate functions considering that completely corrupted data could be injected into it, which means we can have serious security issues if we do allow that to happen.

We really must sanitize, even if we don't error out. We can just drop invalid entries, and also fix foo:bar entries so they're just foo.

couple more comments now that I have stared at this more

overlord/snapstate/handlers.go
+ if err := m.installPrereq(t, prereqName, snapsup.UserID); err != nil {
+ return err
+ }
+ }
@pedronis

pedronis Nov 30, 2017

Contributor

I'm a bit worried that the Retry returned from installPrereq and this sort of looping is not compatible. If we keep the retry approach we should not add tasks to the change if we are not sure we are about to be able to be done and get out of the prerequisite task step. Otherwise we might get cross waits that will never succeed if we are not careful. These are similar problems to trying to get multiple locks at the same time, that is always delicate.

@pedronis

pedronis Nov 30, 2017

Contributor

I can make an example tomorrow if needed

overlord/snapstate/handlers.go
+ return nil
+ }
+ }
+ }
@pedronis

pedronis Nov 30, 2017

Contributor

I missed understanding this bit, so for link-snap we return nil, as we discussed then there's no waiting setup though which is wrong... also I'm not sure this bit is fully right if there's also a discard-snap in the change

pedronis requested changes Nov 30, 2017 edited

as I wrote I think we need to add all tasks or retry, not a mix of those two, with the code as it stands, also the special casing about the same change isn't waiting in any form (retry or task waits) at all atm afaict

mvo5 added some commits Dec 11, 2017

snapstate: calculate all prereqs before adding them
Then calculating the prereqs, do calculate them all first and then
add them to the change. It is important that we do not modify the
change until we can move on from prereqs in one go.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment