github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/machineremovals.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "sort" 8 "strings" 9 10 "github.com/juju/errors" 11 "github.com/juju/utils/set" 12 "gopkg.in/juju/names.v2" 13 "gopkg.in/mgo.v2/bson" 14 "gopkg.in/mgo.v2/txn" 15 ) 16 17 // machineRemovalDoc indicates that this machine needs to be removed 18 // and any necessary provider-level cleanup should now be done. 19 type machineRemovalDoc struct { 20 DocID string `bson:"_id"` 21 MachineID string `bson:"machine-id"` 22 } 23 24 func (m *Machine) markForRemovalOps() ([]txn.Op, error) { 25 if m.doc.Life != Dead { 26 return nil, errors.Errorf("machine is not dead") 27 } 28 ops := []txn.Op{{ 29 C: machinesC, 30 Id: m.doc.DocID, 31 // Check that the machine is still dead (and implicitly that 32 // it still exists). 33 Assert: isDeadDoc, 34 }, { 35 C: machineRemovalsC, 36 Id: m.globalKey(), 37 Insert: &machineRemovalDoc{MachineID: m.Id()}, 38 // No assert here - it's ok if the machine has already been 39 // marked. The id will prevent duplicates. 40 }} 41 return ops, nil 42 } 43 44 // MarkForRemoval requests that this machine be removed after any 45 // needed provider-level cleanup is done. 46 func (m *Machine) MarkForRemoval() (err error) { 47 defer errors.DeferredAnnotatef(&err, "cannot remove machine %s", m.doc.Id) 48 // Local variable so we can refresh the machine if needed. 49 machine := m 50 buildTxn := func(attempt int) ([]txn.Op, error) { 51 if attempt != 0 { 52 if machine, err = machine.st.Machine(machine.Id()); err != nil { 53 return nil, errors.Trace(err) 54 } 55 } 56 ops, err := machine.markForRemovalOps() 57 if err != nil { 58 return nil, errors.Trace(err) 59 } 60 return ops, nil 61 } 62 return m.st.run(buildTxn) 63 } 64 65 // AllMachineRemovals returns (the ids of) all of the machines that 66 // need to be removed but need provider-level cleanup. 67 func (st *State) AllMachineRemovals() ([]string, error) { 68 removals, close := st.getCollection(machineRemovalsC) 69 defer close() 70 71 var docs []machineRemovalDoc 72 err := removals.Find(nil).All(&docs) 73 if err != nil { 74 return nil, errors.Trace(err) 75 } 76 results := make([]string, len(docs)) 77 for i := range docs { 78 results[i] = docs[i].MachineID 79 } 80 return results, nil 81 } 82 83 func (st *State) allMachinesMatching(query bson.D) ([]*Machine, error) { 84 machines, close := st.getCollection(machinesC) 85 defer close() 86 87 var docs []machineDoc 88 err := machines.Find(query).All(&docs) 89 if err != nil { 90 return nil, errors.Trace(err) 91 } 92 results := make([]*Machine, len(docs)) 93 for i, doc := range docs { 94 results[i] = newMachine(st, &doc) 95 } 96 return results, nil 97 } 98 99 func plural(count int) string { 100 if count == 1 { 101 return "" 102 } 103 return "s" 104 } 105 106 func collectMissingMachineIds(expectedIds []string, machines []*Machine) []string { 107 expectedSet := set.NewStrings(expectedIds...) 108 actualSet := set.NewStrings() 109 for _, machine := range machines { 110 actualSet.Add(machine.Id()) 111 } 112 return expectedSet.Difference(actualSet).SortedValues() 113 } 114 115 func checkValidMachineIds(machineIds []string) error { 116 var invalidIds []string 117 for _, id := range machineIds { 118 if !names.IsValidMachine(id) { 119 invalidIds = append(invalidIds, id) 120 } 121 } 122 if len(invalidIds) == 0 { 123 return nil 124 } 125 return errors.Errorf("Invalid machine id%s: %s", 126 plural(len(invalidIds)), 127 strings.Join(invalidIds, ", "), 128 ) 129 } 130 131 func (st *State) completeMachineRemovalsOps(ids []string) ([]txn.Op, error) { 132 removals, err := st.AllMachineRemovals() 133 if err != nil { 134 return nil, errors.Trace(err) 135 } 136 removalSet := set.NewStrings(removals...) 137 query := bson.D{{"machineid", bson.D{{"$in", ids}}}} 138 machinesToRemove, err := st.allMachinesMatching(query) 139 if err != nil { 140 return nil, errors.Trace(err) 141 } 142 143 var ops []txn.Op 144 var missingRemovals []string 145 for _, machine := range machinesToRemove { 146 if !removalSet.Contains(machine.Id()) { 147 missingRemovals = append(missingRemovals, machine.Id()) 148 continue 149 } 150 151 ops = append(ops, txn.Op{ 152 C: machineRemovalsC, 153 Id: machine.globalKey(), 154 Assert: txn.DocExists, 155 Remove: true, 156 }) 157 removeMachineOps, err := machine.removeOps() 158 if err != nil { 159 return nil, errors.Trace(err) 160 } 161 ops = append(ops, removeMachineOps...) 162 } 163 // We should complain about machines that still exist but haven't 164 // been marked for removal. 165 if len(missingRemovals) > 0 { 166 sort.Strings(missingRemovals) 167 return nil, errors.Errorf( 168 "cannot remove machine%s %s: not marked for removal", 169 plural(len(missingRemovals)), 170 strings.Join(missingRemovals, ", "), 171 ) 172 } 173 174 // Log last to reduce the likelihood of repeating the message on 175 // retries. 176 if len(machinesToRemove) < len(ids) { 177 missingMachines := collectMissingMachineIds(ids, machinesToRemove) 178 logger.Debugf("skipping nonexistent machine%s: %s", 179 plural(len(missingMachines)), 180 strings.Join(missingMachines, ", "), 181 ) 182 } 183 184 return ops, nil 185 } 186 187 // CompleteMachineRemovals finishes the removal of the specified 188 // machines. The machines must have been marked for removal 189 // previously. Valid-looking-but-unknown machine ids are ignored so 190 // that this is idempotent. 191 func (st *State) CompleteMachineRemovals(ids ...string) error { 192 if err := checkValidMachineIds(ids); err != nil { 193 return errors.Trace(err) 194 } 195 196 buildTxn := func(int) ([]txn.Op, error) { 197 // We don't need to reget state for subsequent attempts since 198 // completeMachineRemovalsOps gets the removals and the 199 // machines each time anyway. 200 ops, err := st.completeMachineRemovalsOps(ids) 201 if err != nil { 202 return nil, errors.Trace(err) 203 } 204 return ops, nil 205 } 206 return st.run(buildTxn) 207 }