github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/state/apiserver/client/destroy.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package client
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"launchpad.net/juju-core/environs"
    11  	"launchpad.net/juju-core/instance"
    12  	"launchpad.net/juju-core/state"
    13  )
    14  
    15  // DestroyEnvironment destroys all services and non-manager machine
    16  // instances in the environment.
    17  func (c *Client) DestroyEnvironment() error {
    18  	// TODO(axw) 2013-08-30 bug 1218688
    19  	//
    20  	// There's a race here: a client might add a manual machine
    21  	// after another client checks. Destroy-environment will
    22  	// still fail, but the environment will be in a state where
    23  	// entities can only be destroyed. In the future we should
    24  	// introduce a method of preventing Environment.Destroy()
    25  	// from succeeding if machines have been added.
    26  
    27  	// First, check for manual machines. We bail out if there are any,
    28  	// to stop the user from prematurely hobbling the environment.
    29  	machines, err := c.api.state.AllMachines()
    30  	if err != nil {
    31  		return err
    32  	}
    33  	if err := checkManualMachines(machines); err != nil {
    34  		return err
    35  	}
    36  
    37  	// Set the environment to Dying, to lock out new machines and services.
    38  	// Environment.Destroy() also schedules a cleanup for existing services.
    39  	// Afterwards, refresh the machines in case any were added between the
    40  	// first check and the Environment.Destroy().
    41  	env, err := c.api.state.Environment()
    42  	if err != nil {
    43  		return err
    44  	}
    45  	if err = env.Destroy(); err != nil {
    46  		return err
    47  	}
    48  	machines, err = c.api.state.AllMachines()
    49  	if err != nil {
    50  		return err
    51  	}
    52  
    53  	// We must destroy instances server-side to support hosted Juju,
    54  	// as there's no CLI to fall back on. In that case, we only ever
    55  	// destroy non-state machines; we leave destroying state servers
    56  	// in non-hosted environments to the CLI, as otherwise the API
    57  	// server may get cut off.
    58  	if err := destroyInstances(c.api.state, machines); err != nil {
    59  		return err
    60  	}
    61  
    62  	// Make sure once again that there are no manually provisioned
    63  	// non-manager machines. This caters for the race between the
    64  	// first check and the Environment.Destroy().
    65  	if err := checkManualMachines(machines); err != nil {
    66  		return err
    67  	}
    68  
    69  	// Return to the caller. If it's the CLI, it will finish up
    70  	// by calling the provider's Destroy method, which will
    71  	// destroy the state servers, any straggler instances, and
    72  	// other provider-specific resources.
    73  	return nil
    74  }
    75  
    76  // destroyInstances directly destroys all non-manager,
    77  // non-manual machine instances.
    78  func destroyInstances(st *state.State, machines []*state.Machine) error {
    79  	var ids []instance.Id
    80  	for _, m := range machines {
    81  		if m.IsManager() {
    82  			continue
    83  		}
    84  		manual, err := m.IsManual()
    85  		if manual {
    86  			continue
    87  		} else if err != nil {
    88  			return err
    89  		}
    90  		id, err := m.InstanceId()
    91  		if err != nil {
    92  			continue
    93  		}
    94  		ids = append(ids, id)
    95  	}
    96  	if len(ids) == 0 {
    97  		return nil
    98  	}
    99  	envcfg, err := st.EnvironConfig()
   100  	if err != nil {
   101  		return err
   102  	}
   103  	env, err := environs.New(envcfg)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	// TODO(axw) 2013-12-12 #1260171
   108  	// Modify InstanceBroker.StopInstances to take
   109  	// a slice of IDs rather than Instances.
   110  	instances, err := env.Instances(ids)
   111  	switch err {
   112  	case nil:
   113  	default:
   114  		return err
   115  	case environs.ErrNoInstances:
   116  		return nil
   117  	case environs.ErrPartialInstances:
   118  		var nonNilInstances []instance.Instance
   119  		for i, inst := range instances {
   120  			if inst == nil {
   121  				logger.Warningf("unknown instance ID: %v", ids[i])
   122  				continue
   123  			}
   124  			nonNilInstances = append(nonNilInstances, inst)
   125  		}
   126  		instances = nonNilInstances
   127  	}
   128  	return env.StopInstances(instances)
   129  }
   130  
   131  // checkManualMachines checks if any of the machines in the slice were
   132  // manually provisioned, and are non-manager machines. These machines
   133  // must (currently) be manually destroyed via destroy-machine before
   134  // destroy-environment can successfully complete.
   135  func checkManualMachines(machines []*state.Machine) error {
   136  	var ids []string
   137  	for _, m := range machines {
   138  		if m.IsManager() {
   139  			continue
   140  		}
   141  		manual, err := m.IsManual()
   142  		if err != nil {
   143  			return err
   144  		}
   145  		if manual {
   146  			ids = append(ids, m.Id())
   147  		}
   148  	}
   149  	if len(ids) > 0 {
   150  		return fmt.Errorf("manually provisioned machines must first be destroyed with `juju destroy-machine %s`", strings.Join(ids, " "))
   151  	}
   152  	return nil
   153  }