github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/cmd/juju/system/kill.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package system 5 6 import ( 7 "time" 8 9 "github.com/juju/cmd" 10 "github.com/juju/errors" 11 "launchpad.net/gnuflag" 12 13 "github.com/juju/juju/api" 14 "github.com/juju/juju/api/systemmanager" 15 "github.com/juju/juju/apiserver/common" 16 "github.com/juju/juju/apiserver/params" 17 "github.com/juju/juju/environs" 18 "github.com/juju/juju/environs/configstore" 19 ) 20 21 var ( 22 apiTimeout = 10 * time.Second 23 ErrConnTimedOut = errors.New("connection to state server timed out") 24 ) 25 26 const killDoc = ` 27 Forcibly destroy the specified system. If the API server is accessible, 28 this command will attempt to destroy the state server environment and all 29 hosted environments and their resources. 30 31 If the API server is unreachable, the machines of the state server environment 32 will be destroyed through the cloud provisioner. If there are additional 33 machines, including machines within hosted environments, these machines 34 will not be destroyed and will never be reconnected to the Juju system being 35 destroyed. 36 ` 37 38 // KillCommand kills the specified system. 39 type KillCommand struct { 40 DestroyCommandBase 41 // TODO (cherylj) If timeouts for dialing the API are added to new or 42 // existing commands later, the dialer should be pulled into a common 43 // base and made to be an interface rather than a function. 44 apiDialerFunc func(string) (api.Connection, error) 45 } 46 47 // Info implements Command.Info. 48 func (c *KillCommand) Info() *cmd.Info { 49 return &cmd.Info{ 50 Name: "kill", 51 Args: "<system name>", 52 Purpose: "forcibly terminate all machines and other associated resources for a system environment", 53 Doc: killDoc, 54 } 55 } 56 57 // SetFlags implements Command.SetFlags. 58 func (c *KillCommand) SetFlags(f *gnuflag.FlagSet) { 59 f.BoolVar(&c.assumeYes, "y", false, "Do not ask for confirmation") 60 f.BoolVar(&c.assumeYes, "yes", false, "") 61 } 62 63 // Init implements Command.Init. 64 func (c *KillCommand) Init(args []string) error { 65 if c.apiDialerFunc == nil { 66 // This should never happen, but check here instead of panicking later. 67 return errors.New("no api dialer specified") 68 } 69 70 return c.DestroyCommandBase.Init(args) 71 } 72 73 func (c *KillCommand) getSystemAPI(info configstore.EnvironInfo) (destroySystemAPI, error) { 74 if c.api != nil { 75 return c.api, c.apierr 76 } 77 78 // Attempt to connect to the API with a short timeout 79 apic := make(chan api.Connection) 80 errc := make(chan error) 81 go func() { 82 api, dialErr := c.apiDialerFunc(c.systemName) 83 if dialErr != nil { 84 errc <- dialErr 85 return 86 } 87 apic <- api 88 }() 89 90 var apiRoot api.Connection 91 select { 92 case err := <-errc: 93 return nil, err 94 case apiRoot = <-apic: 95 case <-time.After(apiTimeout): 96 return nil, ErrConnTimedOut 97 } 98 99 return systemmanager.NewClient(apiRoot), nil 100 } 101 102 // Run implements Command.Run 103 func (c *KillCommand) Run(ctx *cmd.Context) error { 104 store, err := configstore.Default() 105 if err != nil { 106 return errors.Annotate(err, "cannot open system info storage") 107 } 108 109 cfgInfo, err := store.ReadInfo(c.systemName) 110 if err != nil { 111 return errors.Annotate(err, "cannot read system info") 112 } 113 114 // Verify that we're destroying a system 115 apiEndpoint := cfgInfo.APIEndpoint() 116 if apiEndpoint.ServerUUID != "" && apiEndpoint.EnvironUUID != apiEndpoint.ServerUUID { 117 return errors.Errorf("%q is not a system; use juju environment destroy to destroy it", c.systemName) 118 } 119 120 if !c.assumeYes { 121 if err = confirmDestruction(ctx, c.systemName); err != nil { 122 return err 123 } 124 } 125 126 // Attempt to connect to the API. 127 api, err := c.getSystemAPI(cfgInfo) 128 switch { 129 case err == nil: 130 defer api.Close() 131 case errors.Cause(err) == common.ErrPerm: 132 return errors.Annotate(err, "cannot destroy system") 133 default: 134 if err != ErrConnTimedOut { 135 logger.Debugf("unable to open api: %s", err) 136 } 137 ctx.Infof("Unable to open API: %s\n", err) 138 api = nil 139 } 140 141 // Obtain bootstrap / system environ information 142 systemEnviron, err := c.getSystemEnviron(cfgInfo, api) 143 if err != nil { 144 return errors.Annotate(err, "cannot obtain bootstrap information") 145 } 146 147 // If we were unable to connect to the API, just destroy the system through 148 // the environs interface. 149 if api == nil { 150 return environs.Destroy(systemEnviron, store) 151 } 152 153 // Attempt to destroy the system with destroyEnvs and ignoreBlocks = true 154 err = api.DestroySystem(true, true) 155 if params.IsCodeNotImplemented(err) { 156 // Fall back to using the client endpoint to destroy the system, 157 // sending the info we were already able to collect. 158 return c.killSystemViaClient(ctx, cfgInfo, systemEnviron, store) 159 } 160 161 if err != nil { 162 ctx.Infof("Unable to destroy system through the API: %s. Destroying through provider.", err) 163 } 164 165 return environs.Destroy(systemEnviron, store) 166 } 167 168 // killSystemViaClient attempts to kill the system using the client 169 // endpoint for older juju systems which do not implement systemmanager.DestroySystem 170 func (c *KillCommand) killSystemViaClient(ctx *cmd.Context, info configstore.EnvironInfo, systemEnviron environs.Environ, store configstore.Storage) error { 171 api, err := c.getClientAPI() 172 if err != nil { 173 defer api.Close() 174 } 175 176 if api != nil { 177 err = api.DestroyEnvironment() 178 if err != nil { 179 ctx.Infof("Unable to destroy system through the API: %s. Destroying through provider.", err) 180 } 181 } 182 183 return environs.Destroy(systemEnviron, store) 184 }