Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| // Copyright 2012, 2013 Canonical Ltd. | |
| // Licensed under the AGPLv3, see LICENCE file for details. | |
| package machiner | |
| import ( | |
| "net" | |
| "github.com/juju/errors" | |
| "github.com/juju/loggo" | |
| "gopkg.in/juju/names.v2" | |
| "gopkg.in/juju/worker.v1" | |
| "github.com/juju/juju/api/common" | |
| "github.com/juju/juju/apiserver/params" | |
| "github.com/juju/juju/network" | |
| "github.com/juju/juju/status" | |
| "github.com/juju/juju/watcher" | |
| jworker "github.com/juju/juju/worker" | |
| ) | |
| var logger = loggo.GetLogger("juju.worker.machiner") | |
| // Config defines the configuration for a machiner worker. | |
| type Config struct { | |
| // MachineAccessor provides a means of observing and updating the | |
| // machine's state. | |
| MachineAccessor MachineAccessor | |
| // Tag is the machine's tag. | |
| Tag names.MachineTag | |
| // ClearMachineAddressesOnStart indicates whether or not to clear | |
| // the machine's machine addresses when the worker starts. | |
| ClearMachineAddressesOnStart bool | |
| // NotifyMachineDead will, if non-nil, be called after the machine | |
| // is transitioned to the Dead lifecycle state. | |
| NotifyMachineDead func() error | |
| } | |
| // Validate reports whether or not the configuration is valid. | |
| func (cfg *Config) Validate() error { | |
| if cfg.MachineAccessor == nil { | |
| return errors.NotValidf("unspecified MachineAccessor") | |
| } | |
| if cfg.Tag == (names.MachineTag{}) { | |
| return errors.NotValidf("unspecified Tag") | |
| } | |
| return nil | |
| } | |
| // Machiner is responsible for a machine agent's lifecycle. | |
| type Machiner struct { | |
| config Config | |
| machine Machine | |
| } | |
| // NewMachiner returns a Worker that will wait for the identified machine | |
| // to become Dying and make it Dead; or until the machine becomes Dead by | |
| // other means. | |
| // | |
| // The machineDead function will be called immediately after the machine's | |
| // lifecycle is updated to Dead. | |
| var NewMachiner = func(cfg Config) (worker.Worker, error) { | |
| if err := cfg.Validate(); err != nil { | |
| return nil, errors.Annotate(err, "validating config") | |
| } | |
| handler := &Machiner{config: cfg} | |
| w, err := watcher.NewNotifyWorker(watcher.NotifyConfig{ | |
| Handler: handler, | |
| }) | |
| if err != nil { | |
| return nil, errors.Trace(err) | |
| } | |
| return w, nil | |
| } | |
| var getObservedNetworkConfig = common.GetObservedNetworkConfig | |
| func (mr *Machiner) SetUp() (watcher.NotifyWatcher, error) { | |
| // Find which machine we're responsible for. | |
| m, err := mr.config.MachineAccessor.Machine(mr.config.Tag) | |
| if params.IsCodeNotFoundOrCodeUnauthorized(err) { | |
| return nil, jworker.ErrTerminateAgent | |
| } else if err != nil { | |
| return nil, errors.Trace(err) | |
| } | |
| mr.machine = m | |
| if mr.config.ClearMachineAddressesOnStart { | |
| logger.Debugf("machiner configured to reset machine %q addresses to empty", mr.config.Tag) | |
| if err := m.SetMachineAddresses(nil); err != nil { | |
| return nil, errors.Annotate(err, "reseting machine addresses") | |
| } | |
| } else { | |
| // Set the addresses in state to the host's addresses. | |
| if err := setMachineAddresses(mr.config.Tag, m); err != nil { | |
| return nil, errors.Annotate(err, "setting machine addresses") | |
| } | |
| } | |
| // Mark the machine as started and log it. | |
| if err := m.SetStatus(status.Started, "", nil); err != nil { | |
| return nil, errors.Annotatef(err, "%s failed to set status started", mr.config.Tag) | |
| } | |
| logger.Infof("%q started", mr.config.Tag) | |
| return m.Watch() | |
| } | |
| var interfaceAddrs = net.InterfaceAddrs | |
| // setMachineAddresses sets the addresses for this machine to all of the | |
| // host's non-loopback interface IP addresses. | |
| func setMachineAddresses(tag names.MachineTag, m Machine) error { | |
| addrs, err := interfaceAddrs() | |
| if err != nil { | |
| return err | |
| } | |
| var hostAddresses []network.Address | |
| for _, addr := range addrs { | |
| var ip net.IP | |
| switch addr := addr.(type) { | |
| case *net.IPAddr: | |
| ip = addr.IP | |
| case *net.IPNet: | |
| ip = addr.IP | |
| default: | |
| continue | |
| } | |
| address := network.NewAddress(ip.String()) | |
| // Filter out link-local addresses as we cannot reliably use them. | |
| if address.Scope == network.ScopeLinkLocal { | |
| continue | |
| } | |
| hostAddresses = append(hostAddresses, address) | |
| } | |
| if len(hostAddresses) == 0 { | |
| return nil | |
| } | |
| // Filter out any LXC or LXD bridge addresses. | |
| hostAddresses = network.FilterBridgeAddresses(hostAddresses) | |
| logger.Infof("setting addresses for %q to %v", tag, hostAddresses) | |
| return m.SetMachineAddresses(hostAddresses) | |
| } | |
| func (mr *Machiner) Handle(_ <-chan struct{}) error { | |
| if err := mr.machine.Refresh(); params.IsCodeNotFoundOrCodeUnauthorized(err) { | |
| // NOTE(axw) we can distinguish between NotFound and CodeUnauthorized, | |
| // so we could call NotifyMachineDead here in case the agent failed to | |
| // call NotifyMachineDead directly after setting the machine Dead in | |
| // the first place. We're not doing that to be cautious: the machine | |
| // could be missing from state due to invalid global state. | |
| return jworker.ErrTerminateAgent | |
| } else if err != nil { | |
| return err | |
| } | |
| life := mr.machine.Life() | |
| if life == params.Alive { | |
| observedConfig, err := getObservedNetworkConfig(common.DefaultNetworkConfigSource()) | |
| if err != nil { | |
| return errors.Annotate(err, "cannot discover observed network config") | |
| } else if len(observedConfig) == 0 { | |
| logger.Warningf("not updating network config: no observed config found to update") | |
| } | |
| if len(observedConfig) > 0 { | |
| if err := mr.machine.SetObservedNetworkConfig(observedConfig); err != nil { | |
| return errors.Annotate(err, "cannot update observed network config") | |
| } | |
| } | |
| logger.Debugf("observed network config updated for %q to %v", mr.config.Tag, observedConfig) | |
| return nil | |
| } | |
| logger.Debugf("%q is now %s", mr.config.Tag, life) | |
| if err := mr.machine.SetStatus(status.Stopped, "", nil); err != nil { | |
| return errors.Annotatef(err, "%s failed to set status stopped", mr.config.Tag) | |
| } | |
| // Attempt to mark the machine Dead. If the machine still has units | |
| // assigned, or storage attached, this will fail with | |
| // CodeHasAssignedUnits or CodeMachineHasAttachedStorage respectively. | |
| // Once units or storage are removed, the watcher will trigger again | |
| // and we'll reattempt. | |
| if err := mr.machine.EnsureDead(); err != nil { | |
| if params.IsCodeHasAssignedUnits(err) { | |
| return nil | |
| } | |
| if params.IsCodeMachineHasAttachedStorage(err) { | |
| logger.Tracef("machine still has storage attached") | |
| return nil | |
| } | |
| return errors.Annotatef(err, "%s failed to set machine to dead", mr.config.Tag) | |
| } | |
| // Report on the machine's death. It is important that we do this after | |
| // the machine is Dead, because this is the mechanism we use to clean up | |
| // the machine (uninstall). If we were to report before marking the machine | |
| // as Dead, then we would risk uninstalling prematurely. | |
| if mr.config.NotifyMachineDead != nil { | |
| if err := mr.config.NotifyMachineDead(); err != nil { | |
| return errors.Annotate(err, "reporting machine death") | |
| } | |
| } | |
| return jworker.ErrTerminateAgent | |
| } | |
| func (mr *Machiner) TearDown() error { | |
| // Nothing to do here. | |
| return nil | |
| } |