Merge lp:~dimitern/juju-core/300-lp-1174610-unit-ids-unique into lp:~go-bot/juju-core/trunk

Proposed by Dimiter Naydenov
Status: Rejected
Rejected by: William Reade
Proposed branch: lp:~dimitern/juju-core/300-lp-1174610-unit-ids-unique
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 139 lines (+33/-14)
5 files modified
state/open.go (+1/-0)
state/sequence.go (+1/-1)
state/service.go (+6/-11)
state/service_test.go (+24/-2)
state/state.go (+1/-0)
To merge this branch: bzr merge lp:~dimitern/juju-core/300-lp-1174610-unit-ids-unique
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+206765@code.launchpad.net

Description of the change

state: Make unit ids unique. Fix #1174610

This changes the way unit names are generated
when adding a new unit to a service. Before,
we used serviceDoc.UnitSeq to generate the
last part of a unit name. Now, we use the
sequence collection with a key "s#<svcname>#unit"
as the unit ids sequence (as we do for machine,
containers, etc.).

This is a schema change (removing of UnitSeq from
the service document). So it should be handled the
same way as any schema change during an upgrade,
but this CL does not do that.

I realized the sequence collection is not created
as all the other collections in state.Initialize,
so I added it there.

Also added a test to guarantee unit ids increase
each time for the same service name (like the bug
suggests and how it was before).

Per fwereade's suggestion, I changed the error
message AddUnit() returns when a service is not
found: "service is no longer alive" rather than
"service "blah" not found", and also fixed a
test that relied on that.

https://codereview.appspot.com/64890044/

To post a comment you must log in.
Revision history for this message
Dimiter Naydenov (dimitern) wrote :
Download full text (6.4 KiB)

Reviewers: mp+206765_code.launchpad.net,

Message:
Please take a look.

Description:
state: Make unit ids unique. Fix #1174610

This changes the way unit names are generated
when adding a new unit to a service. Before,
we used serviceDoc.UnitSeq to generate the
last part of a unit name. Now, we use the
sequence collection with a key "s#<svcname>#unit"
as the unit ids sequence (as we do for machine,
containers, etc.).

This is a schema change (removing of UnitSeq from
the service document). So it should be handled the
same way as any schema change during an upgrade,
but this CL does not do that.

I realized the sequence collection is not created
as all the other collections in state.Initialize,
so I added it there.

Also added a test to guarantee unit ids increase
each time for the same service name (like the bug
suggests and how it was before).

Per fwereade's suggestion, I changed the error
message AddUnit() returns when a service is not
found: "service is no longer alive" rather than
"service "blah" not found", and also fixed a
test that relied on that.

https://code.launchpad.net/~dimitern/juju-core/300-lp-1174610-unit-ids-unique/+merge/206765

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/64890044/

Affected files (+35, -14 lines):
   A [revision details]
   M state/open.go
   M state/sequence.go
   M state/service.go
   M state/service_test.go
   M state/state.go

Index: [revision details]
=== added file '[revision details]'
--- [revision details] 2012-01-01 00:00:00 +0000
+++ [revision details] 2012-01-01 00:00:00 +0000
@@ -0,0 +1,2 @@
+Old revision: tarmac-20140217133244-ukuzvegy1wqly6nm
+New revision:
<email address hidden>

Index: state/open.go
=== modified file 'state/open.go'
--- state/open.go 2014-02-07 18:38:02 +0000
+++ state/open.go 2014-02-17 16:32:56 +0000
@@ -255,6 +255,7 @@
    annotations: db.C("annotations"),
    statuses: db.C("statuses"),
    stateServers: db.C("stateServers"),
+ sequences: db.C("sequence"),
   }
   log := db.C("txns.log")
   logInfo := mgo.CollectionInfo{Capped: true, MaxBytes: logSize}

Index: state/sequence.go
=== modified file 'state/sequence.go'
--- state/sequence.go 2013-07-09 10:32:23 +0000
+++ state/sequence.go 2014-02-17 16:32:56 +0000
@@ -16,7 +16,7 @@
  }

  func (s *State) sequence(name string) (int, error) {
- query := s.db.C("sequence").Find(D{{"_id", name}})
+ query := s.sequences.FindId(name)
   inc := mgo.Change{
    Update: bson.M{"$inc": bson.M{"counter": 1}},
    Upsert: true,

Index: state/service.go
=== modified file 'state/service.go'
--- state/service.go 2014-01-15 06:52:56 +0000
+++ state/service.go 2014-02-17 16:32:56 +0000
@@ -37,7 +37,6 @@
   CharmURL *charm.URL
   ForceCharm bool
   Life Life
- UnitSeq int
   UnitCount int
   RelationCount int
   Exposed bool
@@ -529,15 +528,11 @@

  // newUnitName returns the next unit name.
  func (s *Service) newUnitName() (string, error) {
- change := mgo.Change{Update: D{{"$inc", D{{"unitseq", 1}}}}}
- result := serviceDoc{}
- if _, err := s.st.services.Find(D{{"_id", s.doc.Name}}).Apply...

Read more...

Revision history for this message
Roger Peppe (rogpeppe) wrote :

On 2014/02/17 16:44:18, dimitern wrote:
> Please take a look.

Looks reasonable, but I'm concerned that it can't be applied
until we have mongo-schema-upgrade capability, which might be
a while off yet.

How about making the schema change backwardly compatible?

For example, when a service is created, we mark it with a "uses global
sequence numbering" flag
(this will be false in already existing services).
When a service is destroyed that doesn't have that flag set, we could
update the sequences collection
with the most recent unit number for that service.
When a unit is created, if allocates it from the service sequence number
unless the "uses global sequence" flag is set
in which case it allocates it using the sequences collection.

In that way, we can remain compatible with the old schema but update to
the new schema over time.

Eventually we can delete the flag and its associated logic when we don't
need to support 1.16
environments (assuming this fix goes into 1.18)

https://codereview.appspot.com/64890044/

Revision history for this message
William Reade (fwereade) wrote :

On 2014/02/17 17:04:34, rog wrote:
> On 2014/02/17 16:44:18, dimitern wrote:
> > Please take a look.

> Looks reasonable, but I'm concerned that it can't be applied
> until we have mongo-schema-upgrade capability, which might be
> a while off yet.

> How about making the schema change backwardly compatible?

> For example, when a service is created, we mark it with a "uses global
sequence
> numbering" flag
> (this will be false in already existing services).
> When a service is destroyed that doesn't have that flag set, we could
update the
> sequences collection
> with the most recent unit number for that service.
> When a unit is created, if allocates it from the service sequence
number unless
> the "uses global sequence" flag is set
> in which case it allocates it using the sequences collection.

> In that way, we can remain compatible with the old schema but update
to the new
> schema over time.

> Eventually we can delete the flag and its associated logic when we
don't need to
> support 1.16
> environments (assuming this fix goes into 1.18)

This isn't quite enough. While clients still have direct DB access (ie
until past 1.18) we need to *always* read from, and write to, the old
location, as well as the new one -- and this actually renders any change
pointless, because we could *always* have an old client removing then
creating a new service. We actually *can't* do this yet. Sorry Dimiter,
my enthusiasm for fixing something that annoyed hazmat got me
over-excited and I failed to think it through.

FWIW this *is* exactly what we *should* do once we have mongo fully
locked down...

https://codereview.appspot.com/64890044/

Revision history for this message
William Reade (fwereade) wrote :

On 2014/02/18 10:25:23, fwereade wrote:
> On 2014/02/17 17:04:34, rog wrote:
> > On 2014/02/17 16:44:18, dimitern wrote:
> > > Please take a look.
> >
> > Looks reasonable, but I'm concerned that it can't be applied
> > until we have mongo-schema-upgrade capability, which might be
> > a while off yet.
> >
> > How about making the schema change backwardly compatible?
> >
> > For example, when a service is created, we mark it with a "uses
global
> sequence
> > numbering" flag
> > (this will be false in already existing services).
> > When a service is destroyed that doesn't have that flag set, we
could update
> the
> > sequences collection
> > with the most recent unit number for that service.
> > When a unit is created, if allocates it from the service sequence
number
> unless
> > the "uses global sequence" flag is set
> > in which case it allocates it using the sequences collection.
> >
> > In that way, we can remain compatible with the old schema but update
to the
> new
> > schema over time.
> >
> > Eventually we can delete the flag and its associated logic when we
don't need
> to
> > support 1.16
> > environments (assuming this fix goes into 1.18)

> This isn't quite enough. While clients still have direct DB access (ie
until
> past 1.18) we need to *always* read from, and write to, the old
location, as
> well as the new one -- and this actually renders any change pointless,
because
> we could *always* have an old client removing then creating a new
service. We
> actually *can't* do this yet. Sorry Dimiter, my enthusiasm for fixing
something
> that annoyed hazmat got me over-excited and I failed to think it
through.

> FWIW this *is* exactly what we *should* do once we have mongo fully
locked
> down...

so, to be explicit, this sadly does not lgtm

https://codereview.appspot.com/64890044/

Revision history for this message
William Reade (fwereade) wrote :

On 2014/02/18 10:25:48, fwereade wrote:
> On 2014/02/18 10:25:23, fwereade wrote:
> > On 2014/02/17 17:04:34, rog wrote:
> > > On 2014/02/17 16:44:18, dimitern wrote:
> > > > Please take a look.
> > >
> > > Looks reasonable, but I'm concerned that it can't be applied
> > > until we have mongo-schema-upgrade capability, which might be
> > > a while off yet.
> > >
> > > How about making the schema change backwardly compatible?
> > >
> > > For example, when a service is created, we mark it with a "uses
global
> > sequence
> > > numbering" flag
> > > (this will be false in already existing services).
> > > When a service is destroyed that doesn't have that flag set, we
could update
> > the
> > > sequences collection
> > > with the most recent unit number for that service.
> > > When a unit is created, if allocates it from the service sequence
number
> > unless
> > > the "uses global sequence" flag is set
> > > in which case it allocates it using the sequences collection.
> > >
> > > In that way, we can remain compatible with the old schema but
update to the
> > new
> > > schema over time.
> > >
> > > Eventually we can delete the flag and its associated logic when we
don't
> need
> > to
> > > support 1.16
> > > environments (assuming this fix goes into 1.18)
> >
> > This isn't quite enough. While clients still have direct DB access
(ie until
> > past 1.18) we need to *always* read from, and write to, the old
location, as
> > well as the new one -- and this actually renders any change
pointless, because
> > we could *always* have an old client removing then creating a new
service. We
> > actually *can't* do this yet. Sorry Dimiter, my enthusiasm for
fixing
> something
> > that annoyed hazmat got me over-excited and I failed to think it
through.
> >
> > FWIW this *is* exactly what we *should* do once we have mongo fully
locked
> > down...

> so, to be explicit, this sadly does not lgtm

(it's a shame to lose the incidental fixes though -- can they be easily
extracted?)

https://codereview.appspot.com/64890044/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'state/open.go'
--- state/open.go 2014-02-07 18:38:02 +0000
+++ state/open.go 2014-02-17 16:43:18 +0000
@@ -255,6 +255,7 @@
255 annotations: db.C("annotations"),255 annotations: db.C("annotations"),
256 statuses: db.C("statuses"),256 statuses: db.C("statuses"),
257 stateServers: db.C("stateServers"),257 stateServers: db.C("stateServers"),
258 sequences: db.C("sequence"),
258 }259 }
259 log := db.C("txns.log")260 log := db.C("txns.log")
260 logInfo := mgo.CollectionInfo{Capped: true, MaxBytes: logSize}261 logInfo := mgo.CollectionInfo{Capped: true, MaxBytes: logSize}
261262
=== modified file 'state/sequence.go'
--- state/sequence.go 2013-07-09 10:32:23 +0000
+++ state/sequence.go 2014-02-17 16:43:18 +0000
@@ -16,7 +16,7 @@
16}16}
1717
18func (s *State) sequence(name string) (int, error) {18func (s *State) sequence(name string) (int, error) {
19 query := s.db.C("sequence").Find(D{{"_id", name}})19 query := s.sequences.FindId(name)
20 inc := mgo.Change{20 inc := mgo.Change{
21 Update: bson.M{"$inc": bson.M{"counter": 1}},21 Update: bson.M{"$inc": bson.M{"counter": 1}},
22 Upsert: true,22 Upsert: true,
2323
=== modified file 'state/service.go'
--- state/service.go 2014-01-15 06:52:56 +0000
+++ state/service.go 2014-02-17 16:43:18 +0000
@@ -37,7 +37,6 @@
37 CharmURL *charm.URL37 CharmURL *charm.URL
38 ForceCharm bool38 ForceCharm bool
39 Life Life39 Life Life
40 UnitSeq int
41 UnitCount int40 UnitCount int
42 RelationCount int41 RelationCount int
43 Exposed bool42 Exposed bool
@@ -529,15 +528,11 @@
529528
530// newUnitName returns the next unit name.529// newUnitName returns the next unit name.
531func (s *Service) newUnitName() (string, error) {530func (s *Service) newUnitName() (string, error) {
532 change := mgo.Change{Update: D{{"$inc", D{{"unitseq", 1}}}}}531 seq, err := s.st.sequence(s.globalKey() + "#unit")
533 result := serviceDoc{}532 if err != nil {
534 if _, err := s.st.services.Find(D{{"_id", s.doc.Name}}).Apply(change, &result); err == mgo.ErrNotFound {533 return "", err
535 return "", errors.NotFoundf("service %q", s)
536 } else if err != nil {
537 return "", fmt.Errorf("cannot increment unit sequence: %v", err)
538 }534 }
539 name := s.doc.Name + "/" + strconv.Itoa(result.UnitSeq)535 return s.doc.Name + "/" + strconv.Itoa(seq), nil
540 return name, nil
541}536}
542537
543// addUnitOps returns a unique name for a new unit, and a list of txn operations538// addUnitOps returns a unique name for a new unit, and a list of txn operations
@@ -591,7 +586,7 @@
591 })586 })
592 } else {587 } else {
593 scons, err := s.Constraints()588 scons, err := s.Constraints()
594 if err != nil {589 if err != nil && !errors.IsNotFoundError(err) {
595 return "", nil, err590 return "", nil, err
596 }591 }
597 econs, err := s.st.EnvironConstraints()592 econs, err := s.st.EnvironConstraints()
@@ -631,7 +626,7 @@
631 if alive, err := isAlive(s.st.services, s.doc.Name); err != nil {626 if alive, err := isAlive(s.st.services, s.doc.Name); err != nil {
632 return nil, err627 return nil, err
633 } else if !alive {628 } else if !alive {
634 return nil, fmt.Errorf("service is not alive")629 return nil, fmt.Errorf("service is no longer alive")
635 }630 }
636 return nil, fmt.Errorf("inconsistent state")631 return nil, fmt.Errorf("inconsistent state")
637 } else if err != nil {632 } else if err != nil {
638633
=== modified file 'state/service_test.go'
--- state/service_test.go 2014-01-15 06:52:56 +0000
+++ state/service_test.go 2014-02-17 16:43:18 +0000
@@ -6,6 +6,7 @@
6import (6import (
7 "fmt"7 "fmt"
8 "sort"8 "sort"
9 "strconv"
910
10 "labix.org/v2/mgo"11 "labix.org/v2/mgo"
11 gc "launchpad.net/gocheck"12 gc "launchpad.net/gocheck"
@@ -815,13 +816,13 @@
815 err = s.mysql.Destroy()816 err = s.mysql.Destroy()
816 c.Assert(err, gc.IsNil)817 c.Assert(err, gc.IsNil)
817 _, err = s.mysql.AddUnit()818 _, err = s.mysql.AddUnit()
818 c.Assert(err, gc.ErrorMatches, `cannot add unit to service "mysql": service is not alive`)819 c.Assert(err, gc.ErrorMatches, `cannot add unit to service "mysql": service is no longer alive`)
819 err = u.EnsureDead()820 err = u.EnsureDead()
820 c.Assert(err, gc.IsNil)821 c.Assert(err, gc.IsNil)
821 err = u.Remove()822 err = u.Remove()
822 c.Assert(err, gc.IsNil)823 c.Assert(err, gc.IsNil)
823 _, err = s.mysql.AddUnit()824 _, err = s.mysql.AddUnit()
824 c.Assert(err, gc.ErrorMatches, `cannot add unit to service "mysql": service "mysql" not found`)825 c.Assert(err, gc.ErrorMatches, `cannot add unit to service "mysql": service is no longer alive`)
825}826}
826827
827func (s *ServiceSuite) TestReadUnit(c *gc.C) {828func (s *ServiceSuite) TestReadUnit(c *gc.C) {
@@ -1429,3 +1430,24 @@
1429 c.Assert(state.GetServiceOwnerTag(service), gc.Equals, "")1430 c.Assert(state.GetServiceOwnerTag(service), gc.Equals, "")
1430 c.Assert(service.GetOwnerTag(), gc.Equals, "user-admin")1431 c.Assert(service.GetOwnerTag(), gc.Equals, "user-admin")
1431}1432}
1433
1434func (s *ServiceSuite) TestUnitIdsAreUnique(c *gc.C) {
1435 for i, charmName := range []string{"wordpress", "riak", "dummy"} {
1436 // Add a new charm and service with the same name.
1437 charm := s.AddTestingCharm(c, charmName)
1438 service := s.AddTestingService(c, "myservice", charm)
1439
1440 // Add a unit and check the id increases each time.
1441 unit, err := service.AddUnit()
1442 c.Check(err, gc.IsNil)
1443 c.Check(unit.Name(), gc.Equals, "myservice/"+strconv.Itoa(i))
1444
1445 // Destroy the unit and service and retry.
1446 err = unit.Destroy()
1447 c.Check(err, gc.IsNil)
1448 c.Check(unit.Refresh(), jc.Satisfies, errors.IsNotFoundError)
1449 err = service.Destroy()
1450 c.Check(err, gc.IsNil)
1451 c.Check(service.Refresh(), jc.Satisfies, errors.IsNotFoundError)
1452 }
1453}
14321454
=== modified file 'state/state.go'
--- state/state.go 2014-02-05 17:46:02 +0000
+++ state/state.go 2014-02-17 16:43:18 +0000
@@ -64,6 +64,7 @@
64 annotations *mgo.Collection64 annotations *mgo.Collection
65 statuses *mgo.Collection65 statuses *mgo.Collection
66 stateServers *mgo.Collection66 stateServers *mgo.Collection
67 sequences *mgo.Collection
67 runner *txn.Runner68 runner *txn.Runner
68 transactionHooks chan ([]transactionHook)69 transactionHooks chan ([]transactionHook)
69 watcher *watcher.Watcher70 watcher *watcher.Watcher

Subscribers

People subscribed via source and target branches

to status/vote changes: