~juju-qa/ubuntu/xenial/juju/xenial-2.0-beta3

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/resource/api/server/server.go

  • Committer: Martin Packman
  • Date: 2016-03-30 19:31:08 UTC
  • mfrom: (1.1.41)
  • Revision ID: martin.packman@canonical.com-20160330193108-h9iz3ak334uk0z5r
Merge new upstream source 2.0~beta3

Show diffs side-by-side

added added

removed removed

Lines of Context:
9
9
        "github.com/juju/errors"
10
10
        "github.com/juju/loggo"
11
11
        "github.com/juju/names"
 
12
        "gopkg.in/juju/charm.v6-unstable"
12
13
        charmresource "gopkg.in/juju/charm.v6-unstable/resource"
 
14
        "gopkg.in/macaroon.v1"
13
15
 
14
16
        "github.com/juju/juju/apiserver/common"
15
17
        "github.com/juju/juju/apiserver/params"
30
32
        UploadDataStore
31
33
}
32
34
 
 
35
// CharmStore exposes the functionality of the charm store as needed here.
 
36
type CharmStore interface {
 
37
        // ListResources composes, for each of the identified charms, the
 
38
        // list of details for each of the charm's resources. Those details
 
39
        // are those associated with the specific charm revision. They
 
40
        // include the resource's metadata and revision.
 
41
        ListResources(charmURLs []*charm.URL) ([][]charmresource.Resource, error)
 
42
 
 
43
        // GetResource returns a reader for the resource's data. That data
 
44
        // is streamed from the charm store. The charm's revision, if any,
 
45
        // is ignored. If the identified resource is not in the charm store
 
46
        // then errors.NotFound is returned.
 
47
        GetResource(cURL *charm.URL, resourceName string, revision int) (charmresource.Resource, io.ReadCloser, error)
 
48
}
 
49
 
33
50
// Facade is the public API facade for resources.
34
51
type Facade struct {
35
52
        // store is the data source for the facade.
36
53
        store resourceInfoStore
 
54
 
 
55
        newCharmstoreClient func(*charm.URL, *macaroon.Macaroon) (CharmStore, error)
37
56
}
38
57
 
39
58
// NewFacade returns a new resoures facade for the given Juju state.
40
 
func NewFacade(store DataStore) *Facade {
41
 
        return &Facade{
42
 
                store: store,
43
 
        }
 
59
func NewFacade(store DataStore, newClient func(*charm.URL, *macaroon.Macaroon) (CharmStore, error)) (*Facade, error) {
 
60
        if store == nil {
 
61
                return nil, errors.Errorf("missing data store")
 
62
        }
 
63
        if newClient == nil {
 
64
                // Technically this only matters for one code path through
 
65
                // AddPendingResources(). However, that functionality should be
 
66
                // provided. So we indicate the problem here instead of later
 
67
                // in the specific place where it actually matters.
 
68
                return nil, errors.Errorf("missing factory for new charm store clients")
 
69
        }
 
70
 
 
71
        f := &Facade{
 
72
                store:               store,
 
73
                newCharmstoreClient: newClient,
 
74
        }
 
75
        return f, nil
44
76
}
45
77
 
46
78
// resourceInfoStore is the portion of Juju's "state" needed
54
86
        // it is resolved. The returned ID is used to identify the pending
55
87
        // resources when resolving it.
56
88
        AddPendingResource(serviceID, userID string, chRes charmresource.Resource, r io.Reader) (string, error)
57
 
 
58
 
        // Units returns the tags for all units in the given service.
59
 
        Units(serviceID string) (units []names.UnitTag, err error)
60
89
}
61
90
 
62
91
// ListResources returns the list of resources for the given service.
82
111
                        continue
83
112
                }
84
113
 
85
 
                units, err := f.store.Units(tag.Id())
86
 
                if err != nil {
87
 
                        r.Results[i] = errorResult(err)
88
 
                        continue
89
 
                }
90
 
 
91
 
                r.Results[i] = api.ServiceResources2APIResult(svcRes, units)
 
114
                r.Results[i] = api.ServiceResources2APIResult(svcRes)
92
115
        }
93
116
        return r, nil
94
117
}
106
129
        }
107
130
        serviceID := tag.Id()
108
131
 
 
132
        ids, err := f.addPendingResources(serviceID, args.URL, args.CharmStoreMacaroon, args.Resources)
 
133
        if err != nil {
 
134
                result.Error = common.ServerError(err)
 
135
                return result, nil
 
136
        }
 
137
        result.PendingIDs = ids
 
138
        return result, nil
 
139
}
 
140
 
 
141
func (f Facade) addPendingResources(serviceID, chRef string, csMac *macaroon.Macaroon, apiResources []api.CharmResource) ([]string, error) {
 
142
        var resources []charmresource.Resource
 
143
        for _, apiRes := range apiResources {
 
144
                res, err := api.API2CharmResource(apiRes)
 
145
                if err != nil {
 
146
                        return nil, errors.Annotatef(err, "bad resource info for %q", apiRes.Name)
 
147
                }
 
148
                resources = append(resources, res)
 
149
        }
 
150
 
 
151
        if chRef != "" {
 
152
                cURL, err := charm.ParseURL(chRef)
 
153
                if err != nil {
 
154
                        return nil, err
 
155
                }
 
156
                // TODO(ericsnow) Do something else for local charms.
 
157
                client, err := f.newCharmstoreClient(cURL, csMac)
 
158
                if err != nil {
 
159
                        return nil, errors.Trace(err)
 
160
                }
 
161
                storeResources, err := f.resourcesFromCharmstore(cURL, client)
 
162
                if err != nil {
 
163
                        return nil, err
 
164
                }
 
165
                resolved, err := resolveResources(resources, storeResources, cURL, client)
 
166
                if err != nil {
 
167
                        return nil, err
 
168
                }
 
169
                // TODO(ericsnow) Ensure that the non-upload resource revisions
 
170
                // match a previously published revision set?
 
171
                resources = resolved
 
172
        }
 
173
 
109
174
        var ids []string
110
 
        for _, apiRes := range args.Resources {
111
 
                pendingID, err := f.addPendingResource(serviceID, apiRes)
 
175
        for _, res := range resources {
 
176
                pendingID, err := f.addPendingResource(serviceID, res)
112
177
                if err != nil {
113
 
                        result.Error = common.ServerError(err)
114
178
                        // We don't bother aggregating errors since a partial
115
179
                        // completion is disruptive and a retry of this endpoint
116
180
                        // is not expensive.
117
 
                        return result, nil
 
181
                        return nil, err
118
182
                }
119
183
                ids = append(ids, pendingID)
120
184
        }
121
 
        result.PendingIDs = ids
122
 
        return result, nil
 
185
        return ids, nil
123
186
}
124
187
 
125
 
func (f Facade) addPendingResource(serviceID string, apiRes api.CharmResource) (pendingID string, err error) {
126
 
        chRes, err := api.API2CharmResource(apiRes)
 
188
// resourcesFromCharmstore gets the info for the charm's resources in
 
189
// the charm store. If the charm URL has a revision then that revision's
 
190
// resources are returned. Otherwise the latest info for each of the
 
191
// resources is returned.
 
192
func (f Facade) resourcesFromCharmstore(cURL *charm.URL, client CharmStore) (map[string]charmresource.Resource, error) {
 
193
        results, err := client.ListResources([]*charm.URL{cURL})
127
194
        if err != nil {
128
 
                return "", errors.Annotatef(err, "bad resource info for %q", chRes.Name)
129
 
        }
130
 
 
 
195
                return nil, errors.Trace(err)
 
196
        }
 
197
        storeResources := make(map[string]charmresource.Resource)
 
198
        if len(results) != 0 {
 
199
                for _, res := range results[0] {
 
200
                        storeResources[res.Name] = res
 
201
                }
 
202
        }
 
203
        return storeResources, nil
 
204
}
 
205
 
 
206
// resolveResources determines the resource info that should actually
 
207
// be stored on the controller. That decision is based on the provided
 
208
// resources along with those in the charm store (if any).
 
209
func resolveResources(resources []charmresource.Resource, storeResources map[string]charmresource.Resource, cURL *charm.URL, client CharmStore) ([]charmresource.Resource, error) {
 
210
        allResolved := make([]charmresource.Resource, len(resources))
 
211
        copy(allResolved, resources)
 
212
        for i, res := range resources {
 
213
                // Note that incoming "upload" resources take precedence over
 
214
                // ones already known to the controller, regardless of their
 
215
                // origin.
 
216
                if res.Origin != charmresource.OriginStore {
 
217
                        continue
 
218
                }
 
219
 
 
220
                resolved, err := resolveStoreResource(res, storeResources, cURL, client)
 
221
                if err != nil {
 
222
                        return nil, errors.Trace(err)
 
223
                }
 
224
                allResolved[i] = resolved
 
225
        }
 
226
        return allResolved, nil
 
227
}
 
228
 
 
229
// resolveStoreResource selects the resource info to use. It decides
 
230
// between the provided and latest info based on the revision.
 
231
func resolveStoreResource(res charmresource.Resource, storeResources map[string]charmresource.Resource, cURL *charm.URL, client CharmStore) (charmresource.Resource, error) {
 
232
        storeRes, ok := storeResources[res.Name]
 
233
        if !ok {
 
234
                // This indicates that AddPendingResources() was called for
 
235
                // a resource the charm store doesn't know about (for the
 
236
                // relevant charm revision).
 
237
                // TODO(ericsnow) Do the following once the charm store supports
 
238
                // the necessary endpoints:
 
239
                // return res, errors.NotFoundf("charm store resource %q", res.Name)
 
240
                return res, nil
 
241
        }
 
242
 
 
243
        if res.Revision < 0 {
 
244
                // The caller wants to use the charm store info.
 
245
                return storeRes, nil
 
246
        }
 
247
        if res.Revision == storeRes.Revision {
 
248
                // We don't worry about if they otherwise match. Only the
 
249
                // revision is significant here. So we use the info from the
 
250
                // charm store since it is authoritative.
 
251
                return storeRes, nil
 
252
        }
 
253
        if res.Fingerprint.IsZero() {
 
254
                // The caller wants resource info from the charm store, but with
 
255
                // a different resource revision than the one associated with
 
256
                // the charm in the store.
 
257
                storeRes, r, err := client.GetResource(cURL, res.Name, res.Revision)
 
258
                if err != nil {
 
259
                        return storeRes, errors.Trace(err)
 
260
                }
 
261
                r.Close() // We don't care about the file.
 
262
                return storeRes, nil
 
263
        }
 
264
        // The caller fully-specified a resource with a different resource
 
265
        // revision than the one associated with the charm in the store. So
 
266
        // we use the provided info as-is.
 
267
        return res, nil
 
268
}
 
269
 
 
270
func (f Facade) addPendingResource(serviceID string, chRes charmresource.Resource) (pendingID string, err error) {
131
271
        userID := ""
132
272
        var reader io.Reader
133
273
        pendingID, err = f.store.AddPendingResource(serviceID, userID, chRes, reader)