github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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  }