Code review comment for lp:~hduran-8/juju-core/add_state_network_check_on_specified_machine

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

firehose of suggestions :)

https://codereview.appspot.com/86430043/diff/20001/state/unit.go
File state/unit.go (right):

https://codereview.appspot.com/86430043/diff/20001/state/unit.go#newcode539
state/unit.go:539: return readRequestedNetworks(u.st, u.globalKey())
don't think we need this method now

https://codereview.appspot.com/86430043/diff/20001/state/unit.go#newcode833
state/unit.go:833: machineIncludedNetworks, machineExcludedNetworks, _
:= m.RequestedNetworks()
Basically never ignore errors. Certainly not here ;p.

https://codereview.appspot.com/86430043/diff/20001/state/unit.go#newcode839
state/unit.go:839: unitIncludedNetworks, unitExcludedNetworks, _ :=
networked_service.Networks()
readRequestedNetworks(u.st, serviceGlobalKey(u.doc.Service))

and don't ignore errors :).

https://codereview.appspot.com/86430043/diff/20001/state/unit.go#newcode852
state/unit.go:852: return fmt.Errorf("The requested network %q is
explicitly excluded in the requested machine %q ", includedNetwork,
m.Id())
We tend not to capitalise error messages

https://codereview.appspot.com/86430043/diff/20001/state/unit.go#newcode859
state/unit.go:859: }
This all might be easier with the methods on utils/set.Strings

https://codereview.appspot.com/86430043/diff/20001/state/unit.go#newcode864
state/unit.go:864: Assert: bson.D{{"txn-revno",
networked_service.doc.TxnRevno}},
We definitely don't want to assert on the service doc's txn-revno -- we
care about changes to the networks, which are on a different doc.

Ofc, you don't have access to them at the moment. I'd add a TxnRevno
field to the requestedNetworks doc, and have readRequestedNetworks
return that too; the RequestedNetworks methods can ignore it, and this
method can use it in txns.

Make sure you return -1 if it's missing, though, and assert DocMissing
:).

https://codereview.appspot.com/86430043/diff/20001/state/unit.go#newcode866
state/unit.go:866: err = u.st.runTransaction(ops)
When building this sort of construction:

if err := u.st.runTransaction(ops); err != txn.ErrAborted {
     return err
}

...replaces this line and the subsequent two blocks.

*but* asserting at this point does us no good -- we have to build the
correct asserts and include them in the transaction, below, that
actually does the assignment.

https://codereview.appspot.com/86430043/diff/20001/state/unit.go#newcode887
state/unit.go:887: assert := append(isAliveDoc, bson.D{
(rename this unitAssert for clarity's sake)

https://codereview.appspot.com/86430043/diff/20001/state/unit.go#newcode893
state/unit.go:893: massert := isAliveDoc
(and this to machineAssert)

https://codereview.appspot.com/86430043/diff/20001/state/unit.go#newcode932
state/unit.go:932: }
I would recommend starting off by refactoring the method to have the
shape we want, so it can handle retries, and making sure all original
tests still pass; and then adding in the extra bits. So, something like:

func (u *Unit) assignToMachine(m *Machine, unused bool) (err error) {
     // On the expectation that we'll have more than one possible nil
return,
     // defer the updates to local state.
     defer func() {
         if err != nil {
             u.doc.MachineId = m.doc.Id
             m.doc.Clean = false
         }
     }()

     // Clone u and m for working with and possible subsequent refreshes.
     // If we don't already have clone methods we probably should, but if
     // you're going to write them please fix the places we do it dumbly.
     // (And write the clone method smartly: copy *all* fields, make sure
     // maps/slices in doc fields are copied, etc)
     unit := &Unit{st: u.st, doc: doc: u.doc}
     machine := &Machine{st: u.st, doc: m.doc}

     // It's ok to validate immutable stuff outside as early as you like:
     if unit.doc.Series != machine.doc.Series ...
     if unit.doc.Principal != "" ...

     // But everything else, including Jobs -- that may be immutable now
but will
     // not be forever -- we should always check against fresh state.
     for i := 0; i < 3; i++ {
         if unit.doc.Life != Alive ...
         if machine.doc.Life != Alive ...
         if unit.doc.MachineId != "" ...
         // etc...

         // Build transaction as before (subsequently inserting
network-related
         // lookups/ops). Note: I would recommend factoring out a method
called
         // assignToMachineOps at this point, this is getting pretty
unwieldy.
         ops := ...

         // Run transaction, and return on success, or surprising
failure.
         if err := u.st.runTransaction(ops); err != txn.ErrAborted {
             return err
         }

         // Now we're here, all we know is that some assert failed. But
if we
         // reload the unit and the machine, we'll go back to the top of
the
         // loop and go all the way through all the state checking again,
and
         // be able to skip the nasty duplication we have here.
         if err := unit.Refresh(); err != nil {
             return err
         }
         if err := machine.Refresh(); err != nil {
             return err
         }
     }

     // This is the usual error when we fail out of a retry loop. It
*says*
     // "changing too fast" but what it usually *means* is "a programmer
     // screwed up"
     return ErrExcessiveContention
}

https://codereview.appspot.com/86430043/diff/20001/state/unit_test.go
File state/unit_test.go (right):

https://codereview.appspot.com/86430043/diff/20001/state/unit_test.go#newcode29
state/unit_test.go:29: type NetworkedUnitSuite UnitSuite
This is interesting. It *works* but it's a bit surprising. I think I'd
usually rather see the common stuff embedded in a separate type, as is
ConnSuite.

I'm heartily +1 on small separate test suites, though, good call there.

https://codereview.appspot.com/86430043/diff/20001/state/unit_test.go#newcode211
state/unit_test.go:211: excludeNetworks := []string{"127.0.0.2",
"127.0.0.4", "127.0.0.6"}
these are addresses, not network names

https://codereview.appspot.com/86430043/diff/20001/state/unit_test.go#newcode242
state/unit_test.go:242: }
I'd recommend putting these tests in assign_test.go, instead.

https://codereview.appspot.com/86430043/

« Back to merge proposal