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
1=== modified file 'state/open.go'
2--- state/open.go 2014-02-07 18:38:02 +0000
3+++ state/open.go 2014-02-17 16:43:18 +0000
4@@ -255,6 +255,7 @@
5 annotations: db.C("annotations"),
6 statuses: db.C("statuses"),
7 stateServers: db.C("stateServers"),
8+ sequences: db.C("sequence"),
9 }
10 log := db.C("txns.log")
11 logInfo := mgo.CollectionInfo{Capped: true, MaxBytes: logSize}
12
13=== modified file 'state/sequence.go'
14--- state/sequence.go 2013-07-09 10:32:23 +0000
15+++ state/sequence.go 2014-02-17 16:43:18 +0000
16@@ -16,7 +16,7 @@
17 }
18
19 func (s *State) sequence(name string) (int, error) {
20- query := s.db.C("sequence").Find(D{{"_id", name}})
21+ query := s.sequences.FindId(name)
22 inc := mgo.Change{
23 Update: bson.M{"$inc": bson.M{"counter": 1}},
24 Upsert: true,
25
26=== modified file 'state/service.go'
27--- state/service.go 2014-01-15 06:52:56 +0000
28+++ state/service.go 2014-02-17 16:43:18 +0000
29@@ -37,7 +37,6 @@
30 CharmURL *charm.URL
31 ForceCharm bool
32 Life Life
33- UnitSeq int
34 UnitCount int
35 RelationCount int
36 Exposed bool
37@@ -529,15 +528,11 @@
38
39 // newUnitName returns the next unit name.
40 func (s *Service) newUnitName() (string, error) {
41- change := mgo.Change{Update: D{{"$inc", D{{"unitseq", 1}}}}}
42- result := serviceDoc{}
43- if _, err := s.st.services.Find(D{{"_id", s.doc.Name}}).Apply(change, &result); err == mgo.ErrNotFound {
44- return "", errors.NotFoundf("service %q", s)
45- } else if err != nil {
46- return "", fmt.Errorf("cannot increment unit sequence: %v", err)
47+ seq, err := s.st.sequence(s.globalKey() + "#unit")
48+ if err != nil {
49+ return "", err
50 }
51- name := s.doc.Name + "/" + strconv.Itoa(result.UnitSeq)
52- return name, nil
53+ return s.doc.Name + "/" + strconv.Itoa(seq), nil
54 }
55
56 // addUnitOps returns a unique name for a new unit, and a list of txn operations
57@@ -591,7 +586,7 @@
58 })
59 } else {
60 scons, err := s.Constraints()
61- if err != nil {
62+ if err != nil && !errors.IsNotFoundError(err) {
63 return "", nil, err
64 }
65 econs, err := s.st.EnvironConstraints()
66@@ -631,7 +626,7 @@
67 if alive, err := isAlive(s.st.services, s.doc.Name); err != nil {
68 return nil, err
69 } else if !alive {
70- return nil, fmt.Errorf("service is not alive")
71+ return nil, fmt.Errorf("service is no longer alive")
72 }
73 return nil, fmt.Errorf("inconsistent state")
74 } else if err != nil {
75
76=== modified file 'state/service_test.go'
77--- state/service_test.go 2014-01-15 06:52:56 +0000
78+++ state/service_test.go 2014-02-17 16:43:18 +0000
79@@ -6,6 +6,7 @@
80 import (
81 "fmt"
82 "sort"
83+ "strconv"
84
85 "labix.org/v2/mgo"
86 gc "launchpad.net/gocheck"
87@@ -815,13 +816,13 @@
88 err = s.mysql.Destroy()
89 c.Assert(err, gc.IsNil)
90 _, err = s.mysql.AddUnit()
91- c.Assert(err, gc.ErrorMatches, `cannot add unit to service "mysql": service is not alive`)
92+ c.Assert(err, gc.ErrorMatches, `cannot add unit to service "mysql": service is no longer alive`)
93 err = u.EnsureDead()
94 c.Assert(err, gc.IsNil)
95 err = u.Remove()
96 c.Assert(err, gc.IsNil)
97 _, err = s.mysql.AddUnit()
98- c.Assert(err, gc.ErrorMatches, `cannot add unit to service "mysql": service "mysql" not found`)
99+ c.Assert(err, gc.ErrorMatches, `cannot add unit to service "mysql": service is no longer alive`)
100 }
101
102 func (s *ServiceSuite) TestReadUnit(c *gc.C) {
103@@ -1429,3 +1430,24 @@
104 c.Assert(state.GetServiceOwnerTag(service), gc.Equals, "")
105 c.Assert(service.GetOwnerTag(), gc.Equals, "user-admin")
106 }
107+
108+func (s *ServiceSuite) TestUnitIdsAreUnique(c *gc.C) {
109+ for i, charmName := range []string{"wordpress", "riak", "dummy"} {
110+ // Add a new charm and service with the same name.
111+ charm := s.AddTestingCharm(c, charmName)
112+ service := s.AddTestingService(c, "myservice", charm)
113+
114+ // Add a unit and check the id increases each time.
115+ unit, err := service.AddUnit()
116+ c.Check(err, gc.IsNil)
117+ c.Check(unit.Name(), gc.Equals, "myservice/"+strconv.Itoa(i))
118+
119+ // Destroy the unit and service and retry.
120+ err = unit.Destroy()
121+ c.Check(err, gc.IsNil)
122+ c.Check(unit.Refresh(), jc.Satisfies, errors.IsNotFoundError)
123+ err = service.Destroy()
124+ c.Check(err, gc.IsNil)
125+ c.Check(service.Refresh(), jc.Satisfies, errors.IsNotFoundError)
126+ }
127+}
128
129=== modified file 'state/state.go'
130--- state/state.go 2014-02-05 17:46:02 +0000
131+++ state/state.go 2014-02-17 16:43:18 +0000
132@@ -64,6 +64,7 @@
133 annotations *mgo.Collection
134 statuses *mgo.Collection
135 stateServers *mgo.Collection
136+ sequences *mgo.Collection
137 runner *txn.Runner
138 transactionHooks chan ([]transactionHook)
139 watcher *watcher.Watcher

Subscribers

People subscribed via source and target branches

to status/vote changes: