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

« back to all changes in this revision

Viewing changes to src/gopkg.in/juju/charmstore.v5-unstable/internal/charmstore/migrations.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:
5
5
 
6
6
import (
7
7
        "gopkg.in/errgo.v1"
 
8
        "gopkg.in/juju/charm.v6-unstable"
 
9
        "gopkg.in/juju/charmrepo.v2-unstable/csclient/params"
8
10
        "gopkg.in/mgo.v2"
9
11
        "gopkg.in/mgo.v2/bson"
10
12
 
 
13
        "gopkg.in/juju/charmstore.v5-unstable/internal/blobstore"
11
14
        "gopkg.in/juju/charmstore.v5-unstable/internal/mongodoc"
12
15
)
13
16
 
14
17
const (
15
 
        migrationAddSupportedSeries mongodoc.MigrationName = "add supported series"
16
 
        migrationAddDevelopment     mongodoc.MigrationName = "add development"
17
 
        migrationAddDevelopmentACLs mongodoc.MigrationName = "add development acls"
 
18
        migrationAddSupportedSeries      mongodoc.MigrationName = "add supported series"
 
19
        migrationAddDevelopment          mongodoc.MigrationName = "add development"
 
20
        migrationAddDevelopmentACLs      mongodoc.MigrationName = "add development acls"
 
21
        migrationFixBogusPromulgatedURL  mongodoc.MigrationName = "fix promulgate url"
 
22
        migrationAddPreV5CompatBlobBogus mongodoc.MigrationName = "add pre-v5 compatibility blobs"
 
23
        migrationAddPreV5CompatBlob      mongodoc.MigrationName = "add pre-v5 compatibility blobs; second try"
 
24
        migrationNewChannelsModel        mongodoc.MigrationName = "new channels model"
18
25
)
19
26
 
20
27
// migrations holds all the migration functions that are executed in the order
39
46
}, {
40
47
        name: "write acl creation",
41
48
}, {
42
 
        name:    migrationAddSupportedSeries,
43
 
        migrate: addSupportedSeries,
44
 
}, {
45
 
        name:    migrationAddDevelopment,
46
 
        migrate: addDevelopment,
47
 
}, {
48
 
        name:    migrationAddDevelopmentACLs,
49
 
        migrate: addDevelopmentACLs,
 
49
        name: migrationAddSupportedSeries,
 
50
}, {
 
51
        name: migrationAddDevelopment,
 
52
}, {
 
53
        name: migrationAddDevelopmentACLs,
 
54
}, {
 
55
        name: migrationFixBogusPromulgatedURL,
 
56
}, {
 
57
        // The original migration that attempted to do this actually did
 
58
        // nothing, so leave it here but use a new name for the
 
59
        // fixed version.
 
60
        name: migrationAddPreV5CompatBlobBogus,
 
61
}, {
 
62
        name:    migrationAddPreV5CompatBlob,
 
63
        migrate: addPreV5CompatBlob,
 
64
}, {
 
65
        name:    migrationNewChannelsModel,
 
66
        migrate: migrateToNewChannelsModel,
50
67
}}
51
68
 
52
69
// migration holds a migration function with its corresponding name.
115
132
        return executed, nil
116
133
}
117
134
 
118
 
// addSupportedSeries adds the supported-series field
119
 
// to entities that don't have it. Note that it does not
120
 
// need to work for multi-series charms because support
121
 
// for those has not been implemented before this migration.
122
 
func addSupportedSeries(db StoreDatabase) error {
 
135
func addPreV5CompatBlob(db StoreDatabase) error {
 
136
        blobStore := blobstore.New(db.Database, "entitystore")
123
137
        entities := db.Entities()
 
138
        iter := entities.Find(nil).Select(map[string]int{
 
139
                "size":             1,
 
140
                "blobhash":         1,
 
141
                "blobname":         1,
 
142
                "blobhash256":      1,
 
143
                "charmmeta.series": 1,
 
144
        }).Iter()
124
145
        var entity mongodoc.Entity
 
146
        for iter.Next(&entity) {
 
147
                var info *preV5CompatibilityHackBlobInfo
 
148
 
 
149
                if entity.CharmMeta == nil || len(entity.CharmMeta.Series) == 0 {
 
150
                        info = &preV5CompatibilityHackBlobInfo{
 
151
                                hash:    entity.BlobHash,
 
152
                                hash256: entity.BlobHash256,
 
153
                                size:    entity.Size,
 
154
                        }
 
155
                } else {
 
156
                        r, _, err := blobStore.Open(entity.BlobName)
 
157
                        if err != nil {
 
158
                                return errgo.Notef(err, "cannot open original blob")
 
159
                        }
 
160
                        info, err = addPreV5CompatibilityHackBlob(blobStore, r, entity.BlobName, entity.Size)
 
161
                        r.Close()
 
162
                        if err != nil {
 
163
                                return errgo.Mask(err)
 
164
                        }
 
165
                }
 
166
                err := entities.UpdateId(entity.URL, bson.D{{
 
167
                        "$set", bson.D{{
 
168
                                "prev5blobhash", info.hash,
 
169
                        }, {
 
170
                                "prev5blobhash256", info.hash256,
 
171
                        }, {
 
172
                                "prev5blobsize", info.size,
 
173
                        }},
 
174
                }})
 
175
                if err != nil {
 
176
                        return errgo.Notef(err, "cannot update pre-v5 info")
 
177
                }
 
178
        }
 
179
        if err := iter.Err(); err != nil {
 
180
                return errgo.Notef(err, "cannot iterate through entities")
 
181
        }
 
182
        return nil
 
183
}
 
184
 
 
185
func migrateToNewChannelsModel(db StoreDatabase) error {
 
186
        if err := ncmUpdateDevelopmentAndStable(db); err != nil {
 
187
                return errgo.Mask(err)
 
188
        }
 
189
        if err := ncmUpdateBaseEntities(db); err != nil {
 
190
                return errgo.Mask(err)
 
191
        }
 
192
        return nil
 
193
}
 
194
 
 
195
// ncmUpdateDevelopmentAndStable updates the Development and Stable
 
196
// entity fields to conform to the new channels model.
 
197
// All entities are treated as if they're in development; entities
 
198
// without the development field set are treated as stable.
 
199
func ncmUpdateDevelopmentAndStable(db StoreDatabase) error {
 
200
        entities := db.Entities()
125
201
        iter := entities.Find(bson.D{{
126
 
                // Use the supportedseries field to collect not migrated entities.
127
 
                "supportedseries", bson.D{{"$exists", false}},
128
 
        }, {
129
 
                "series", bson.D{{"$ne", "bundle"}},
130
 
        }}).Select(bson.D{{"_id", 1}}).Iter()
131
 
        defer iter.Close()
 
202
                "stable", bson.D{{"$exists", false}},
 
203
        }}).Select(map[string]int{
 
204
                "_id":         1,
 
205
                "development": 1,
 
206
        }).Iter()
132
207
 
 
208
        // For every entity without a stable field, update
 
209
        // its development and stable fields appropriately.
 
210
        var entity mongodoc.Entity
133
211
        for iter.Next(&entity) {
134
 
                logger.Infof("updating %s", entity.URL)
135
 
                if err := entities.UpdateId(entity.URL, bson.D{{
 
212
                err := entities.UpdateId(entity.URL, bson.D{{
136
213
                        "$set", bson.D{
137
 
                                {"supportedseries", []string{entity.URL.Series}},
 
214
                                {"development", true},
 
215
                                {"stable", !entity.Development},
138
216
                        },
139
 
                }}); err != nil {
140
 
                        return errgo.Notef(err, "cannot denormalize entity id %s", entity.URL)
 
217
                }})
 
218
                if err != nil {
 
219
                        return errgo.Notef(err, "cannot update entity")
141
220
                }
142
221
        }
143
 
        if err := iter.Close(); err != nil {
144
 
                return errgo.Notef(err, "cannot iterate entities")
145
 
        }
146
 
        return nil
147
 
}
148
 
 
149
 
// addDevelopment adds the Development field to all entities on which that
150
 
// field is not present.
151
 
func addDevelopment(db StoreDatabase) error {
152
 
        logger.Infof("adding development field to all entities")
153
 
        if _, err := db.Entities().UpdateAll(bson.D{{
154
 
                "development", bson.D{{"$exists", false}},
155
 
        }}, bson.D{{
156
 
                "$set", bson.D{{"development", false}},
157
 
        }}); err != nil {
158
 
                return errgo.Notef(err, "cannot add development field to all entities")
159
 
        }
160
 
        return nil
161
 
}
162
 
 
163
 
// addDevelopmentACLs sets up ACLs on base entities for development revisions.
164
 
func addDevelopmentACLs(db StoreDatabase) error {
165
 
        logger.Infof("adding development ACLs to all base entities")
 
222
        if err := iter.Err(); err != nil {
 
223
                return errgo.Notef(err, "cannot iterate through entities")
 
224
        }
 
225
        return nil
 
226
}
 
227
 
 
228
// preNCMBaseEntity holds the type of a base entity just before
 
229
// the new channels model migration.
 
230
type preNCMBaseEntity struct {
 
231
        // URL holds the reference URL of of charm on bundle
 
232
        // regardless of its revision, series or promulgation status
 
233
        // (this omits the revision and series from URL).
 
234
        // e.g., cs:~user/collection/foo
 
235
        URL *charm.URL `bson:"_id"`
 
236
 
 
237
        // User holds the user part of the entity URL (for instance, "joe").
 
238
        User string
 
239
 
 
240
        // Name holds the name of the entity (for instance "wordpress").
 
241
        Name string
 
242
 
 
243
        // Public specifies whether the charm or bundle
 
244
        // is available to all users. If this is true, the ACLs will
 
245
        // be ignored when reading a charm.
 
246
        Public bool
 
247
 
 
248
        // ACLs holds permission information relevant to the base entity.
 
249
        // The permissions apply to all revisions.
 
250
        ACLs mongodoc.ACL
 
251
 
 
252
        // DevelopmentACLs is similar to ACLs but applies to all development
 
253
        // revisions.
 
254
        DevelopmentACLs mongodoc.ACL
 
255
 
 
256
        // Promulgated specifies whether the charm or bundle should be
 
257
        // promulgated.
 
258
        Promulgated mongodoc.IntBool
 
259
 
 
260
        // CommonInfo holds arbitrary common extra metadata associated with
 
261
        // the base entity. Thhose data apply to all revisions.
 
262
        // The byte slices hold JSON-encoded data.
 
263
        CommonInfo map[string][]byte `bson:",omitempty" json:",omitempty"`
 
264
}
 
265
 
 
266
// ncmUpdateBaseEntities updates all the base entities to conform to
 
267
// the new channels model. It assumes that ncmUpdateDevelopmentAndStable
 
268
// has been run already.
 
269
func ncmUpdateBaseEntities(db StoreDatabase) error {
166
270
        baseEntities := db.BaseEntities()
167
 
        var baseEntity mongodoc.BaseEntity
168
271
        iter := baseEntities.Find(bson.D{{
169
 
                "developmentacls", bson.D{{"$exists", false}},
170
 
        }}).Select(bson.D{{"_id", 1}, {"acls", 1}}).Iter()
171
 
        defer iter.Close()
 
272
                "channelentities", bson.D{{"$exists", false}},
 
273
        }}).Iter()
 
274
        // For every base entity without a ChannelEntities field, update
 
275
        // its ChannelEntities and and ChannelACLs field appropriately.
 
276
        var baseEntity preNCMBaseEntity
172
277
        for iter.Next(&baseEntity) {
173
 
                if err := baseEntities.UpdateId(baseEntity.URL, bson.D{{
174
 
                        "$set", bson.D{{"developmentacls", baseEntity.ACLs}},
175
 
                }}); err != nil {
176
 
                        return errgo.Notef(err, "cannot add development ACLs to base entity id %s", baseEntity.URL)
177
 
                }
178
 
        }
179
 
        if err := iter.Close(); err != nil {
180
 
                return errgo.Notef(err, "cannot iterate base entities")
 
278
                if err := ncmUpdateBaseEntity(db, &baseEntity); err != nil {
 
279
                        return errgo.Mask(err)
 
280
                }
 
281
        }
 
282
        if err := iter.Err(); err != nil {
 
283
                return errgo.Notef(err, "cannot iterate through base entities")
 
284
        }
 
285
        return nil
 
286
}
 
287
 
 
288
// ncmUpdateBaseEntity updates a single base entity to conform to
 
289
// the new channels model.
 
290
func ncmUpdateBaseEntity(db StoreDatabase, baseEntity *preNCMBaseEntity) error {
 
291
        channelEntities := make(map[params.Channel]map[string]*charm.URL)
 
292
 
 
293
        updateChannelURL := func(url *charm.URL, ch params.Channel, series string) {
 
294
                if channelEntities[ch] == nil {
 
295
                        channelEntities[ch] = make(map[string]*charm.URL)
 
296
                }
 
297
                if oldURL := channelEntities[ch][series]; oldURL == nil || oldURL.Revision < url.Revision {
 
298
                        channelEntities[ch][series] = url
 
299
                }
 
300
        }
 
301
        // updateChannelEntity updates the series entries in channelEntities
 
302
        // for the given entity, setting the entity URL entry if the revision
 
303
        // is greater than any already found.
 
304
        updateChannelEntity := func(entity *mongodoc.Entity, ch params.Channel) {
 
305
                if entity.URL.Series == "" {
 
306
                        for _, series := range entity.SupportedSeries {
 
307
                                updateChannelURL(entity.URL, ch, series)
 
308
                        }
 
309
                } else {
 
310
                        updateChannelURL(entity.URL, ch, entity.URL.Series)
 
311
                }
 
312
        }
 
313
 
 
314
        // Iterate through all the entities associated with the base entity
 
315
        // to find the most recent "published" entities so that we can
 
316
        // populate the ChannelEntities field.
 
317
        var entity mongodoc.Entity
 
318
        iter := db.Entities().Find(bson.D{{"baseurl", baseEntity.URL}}).Iter()
 
319
        for iter.Next(&entity) {
 
320
                if entity.Development {
 
321
                        updateChannelEntity(&entity, params.DevelopmentChannel)
 
322
                }
 
323
                if entity.Stable {
 
324
                        updateChannelEntity(&entity, params.StableChannel)
 
325
                }
 
326
        }
 
327
        if err := iter.Err(); err != nil {
 
328
                return errgo.Notef(err, "cannot iterate through entities")
 
329
        }
 
330
        err := db.BaseEntities().UpdateId(baseEntity.URL, bson.D{{
 
331
                "$set", bson.D{{
 
332
                        "channelentities", channelEntities,
 
333
                }, {
 
334
                        "channelacls", map[params.Channel]mongodoc.ACL{
 
335
                                params.UnpublishedChannel: baseEntity.DevelopmentACLs,
 
336
                                params.DevelopmentChannel: baseEntity.DevelopmentACLs,
 
337
                                params.StableChannel:      baseEntity.ACLs,
 
338
                        },
 
339
                }},
 
340
        }, {
 
341
                "$unset", bson.D{{
 
342
                        "developmentacls", nil,
 
343
                }, {
 
344
                        "acls", nil,
 
345
                }},
 
346
        }})
 
347
        if err != nil {
 
348
                return errgo.Notef(err, "cannot update base entity")
181
349
        }
182
350
        return nil
183
351
}