// startMachine creates a new data value for tracking details of the // machine and starts watching the machine for units added or removed. func (fw *Firewaller) startMachine(tag names.MachineTag) error { machined := &machineData{ fw: fw, tag: tag, unitds: make(map[names.UnitTag]*unitData), openedPorts: make([]network.PortRange, 0), definedPorts: make(map[network.PortRange]names.UnitTag), } m, err := machined.machine() if params.IsCodeNotFound(err) { return nil } else if err != nil { return errors.Annotate(err, "cannot watch machine units") } unitw, err := m.WatchUnits() if err != nil { return err } select { case <-fw.tomb.Dying(): return tomb.ErrDying case change, ok := <-unitw.Changes(): if !ok { return watcher.EnsureErr(unitw) } fw.machineds[tag] = machined err = fw.unitsChanged(&unitsChange{machined, change}) if err != nil { delete(fw.machineds, tag) return errors.Annotatef(err, "cannot respond to units changes for %q", tag) } } go machined.watchLoop(unitw) return nil }
func opClientAddRelation(c *gc.C, st api.Connection, mst *state.State) (func(), error) { _, err := st.Client().AddRelation("nosuch1", "nosuch2") if params.IsCodeNotFound(err) { err = nil } return func() {}, err }
func opClientDestroyRelation(c *gc.C, st *api.State, mst *state.State) (func(), error) { err := st.Client().DestroyRelation("nosuch1", "nosuch2") if params.IsCodeNotFound(err) { err = nil } return func() {}, err }
func opClientServiceSetCharm(c *gc.C, st api.Connection, mst *state.State) (func(), error) { err := service.NewClient(st).ServiceSetCharm("nosuch", "local:quantal/wordpress", false, false) if params.IsCodeNotFound(err) { err = nil } return func() {}, err }
func (p *updater) startMachines(tags []names.MachineTag) error { for _, tag := range tags { if c := p.machines[tag]; c == nil { // We don't know about the machine - start // a goroutine to deal with it. m, err := p.context.getMachine(tag) if params.IsCodeNotFound(err) { logger.Warningf("watcher gave notification of non-existent machine %q", tag.Id()) continue } if err != nil { return err } // We don't poll manual machines. isManual, err := m.IsManual() if err != nil { return err } if isManual { continue } c = make(chan struct{}) p.machines[tag] = c go runMachine(p.context.newMachineContext(), m, c, p.machineDead) } else { select { case <-p.context.dying(): return p.context.errDying() case c <- struct{}{}: } } } return nil }
// Life returns the entity's life value; or ErrNotFound; or some // other error. func (facade *Facade) Life(entity names.Tag) (life.Value, error) { args := params.Entities{ Entities: []params.Entity{{Tag: entity.String()}}, } var results params.LifeResults err := facade.caller.FacadeCall("Life", args, &results) if err != nil { return "", errors.Trace(err) } if count := len(results.Results); count != 1 { return "", errors.Errorf("expected 1 Life result, got %d", count) } result := results.Results[0] if err := result.Error; err != nil { if params.IsCodeNotFound(err) { return "", ErrNotFound } return "", errors.Trace(result.Error) } life := life.Value(result.Life) if err := life.Validate(); err != nil { return "", errors.Trace(err) } return life, nil }
// UpdateStorage responds to changes in the lifecycle states of the // storage attachments corresponding to the supplied storage tags, // sending storage hooks on the channel returned by Hooks(). func (a *Attachments) UpdateStorage(tags []names.StorageTag) error { ids := make([]params.StorageAttachmentId, len(tags)) for i, storageTag := range tags { ids[i] = params.StorageAttachmentId{ StorageTag: storageTag.String(), UnitTag: a.unitTag.String(), } } results, err := a.st.StorageAttachmentLife(ids) if err != nil { return errors.Trace(err) } for i, result := range results { if result.Error == nil { continue } else if params.IsCodeNotFound(result.Error) { a.pending.Remove(tags[i]) continue } return errors.Annotatef( result.Error, "getting life of storage %s attachment", tags[i].Id(), ) } for i, result := range results { if result.Error != nil { continue } if err := a.updateOneStorage(tags[i], result.Life); err != nil { return errors.Trace(err) } } return nil }
// storageChanged responds to unit storage changes. func (w *RemoteStateWatcher) storageChanged(keys []string) error { tags := make([]names.StorageTag, len(keys)) for i, key := range keys { tags[i] = names.NewStorageTag(key) } ids := make([]params.StorageAttachmentId, len(keys)) for i, tag := range tags { ids[i] = params.StorageAttachmentId{ StorageTag: tag.String(), UnitTag: w.unit.Tag().String(), } } results, err := w.st.StorageAttachmentLife(ids) if err != nil { return errors.Trace(err) } w.mu.Lock() defer w.mu.Unlock() for i, result := range results { tag := tags[i] if result.Error == nil { if storageSnapshot, ok := w.current.Storage[tag]; ok { // We've previously started a watcher for this storage // attachment, so all we needed to do was update the // lifecycle state. storageSnapshot.Life = result.Life w.current.Storage[tag] = storageSnapshot continue } // We haven't seen this storage attachment before, so start // a watcher now; add it to our catacomb in case of mishap; // and wait for the initial event. saw, err := w.st.WatchStorageAttachment(tag, w.unit.Tag()) if err != nil { return errors.Annotate(err, "watching storage attachment") } if err := w.catacomb.Add(saw); err != nil { return errors.Trace(err) } if err := w.watchStorageAttachment(tag, result.Life, saw); err != nil { return errors.Trace(err) } } else if params.IsCodeNotFound(result.Error) { if watcher, ok := w.storageAttachmentWatchers[tag]; ok { // already under catacomb management, any error tracked already worker.Stop(watcher) delete(w.storageAttachmentWatchers, tag) } delete(w.current.Storage, tag) } else { return errors.Annotatef( result.Error, "getting life of %s attachment", names.ReadableString(tag), ) } } return nil }
func opClientDestroyRelation(c *gc.C, st api.Connection, mst *state.State) (func(), error) { err := application.NewClient(st).DestroyRelation("nosuch1", "nosuch2") if params.IsCodeNotFound(err) { err = nil } return func() {}, err }
func opClientAddServiceUnits(c *gc.C, st api.Connection, mst *state.State) (func(), error) { _, err := application.NewClient(st).AddUnits("nosuch", 1, nil) if params.IsCodeNotFound(err) { err = nil } return func() {}, err }
// refreshMachine refreshes the specified machine's instance ID. If it is set, // then the machine watcher is stopped and pending entities' parameters are // updated. If the machine is not provisioned yet, this method is a no-op. func refreshMachine(ctx *context, tag names.MachineTag) error { w, ok := ctx.machines[tag] if !ok { return errors.Errorf("machine %s is not being watched", tag.Id()) } stopAndRemove := func() error { worker.Stop(w) delete(ctx.machines, tag) return nil } results, err := ctx.config.Machines.InstanceIds([]names.MachineTag{tag}) if err != nil { return errors.Annotate(err, "getting machine instance ID") } if err := results[0].Error; err != nil { if params.IsCodeNotProvisioned(err) { return nil } else if params.IsCodeNotFound(err) { // Machine is gone, so stop watching. return stopAndRemove() } return errors.Annotate(err, "getting machine instance ID") } machineProvisioned(ctx, tag, instance.Id(results[0].Result)) // machine provisioning is the only thing we care about; // stop the watcher. return stopAndRemove() }
// RestoreError makes a best effort at converting the given error // back into an error originally converted by ServerError(). If the // error could not be converted then false is returned. func RestoreError(err error) (error, bool) { err = errors.Cause(err) if apiErr, ok := err.(*params.Error); !ok { return err, false } else if apiErr == nil { return nil, true } if params.ErrCode(err) == "" { return err, false } msg := err.Error() if singleton, ok := singletonError(err); ok { return singleton, true } // TODO(ericsnow) Support the other error types handled by ServerError(). switch { case params.IsCodeUnauthorized(err): return errors.NewUnauthorized(nil, msg), true case params.IsCodeNotFound(err): // TODO(ericsnow) UnknownModelError should be handled here too. // ...by parsing msg? return errors.NewNotFound(nil, msg), true case params.IsCodeAlreadyExists(err): return errors.NewAlreadyExists(nil, msg), true case params.IsCodeNotAssigned(err): return errors.NewNotAssigned(nil, msg), true case params.IsCodeHasAssignedUnits(err): // TODO(ericsnow) Handle state.HasAssignedUnitsError here. // ...by parsing msg? return err, false case params.IsCodeNoAddressSet(err): // TODO(ericsnow) Handle isNoAddressSetError here. // ...by parsing msg? return err, false case params.IsCodeNotProvisioned(err): return errors.NewNotProvisioned(nil, msg), true case params.IsCodeUpgradeInProgress(err): // TODO(ericsnow) Handle state.UpgradeInProgressError here. // ...by parsing msg? return err, false case params.IsCodeMachineHasAttachedStorage(err): // TODO(ericsnow) Handle state.HasAttachmentsError here. // ...by parsing msg? return err, false case params.IsCodeNotSupported(err): return errors.NewNotSupported(nil, msg), true case params.IsBadRequest(err): return errors.NewBadRequest(nil, msg), true case params.IsMethodNotAllowed(err): return errors.NewMethodNotAllowed(nil, msg), true case params.ErrCode(err) == params.CodeDischargeRequired: // TODO(ericsnow) Handle DischargeRequiredError here. return err, false default: return err, false } }
func opClientServiceDestroy(c *gc.C, st *api.State, mst *state.State) (func(), error) { err := st.Client().ServiceDestroy("non-existent") if params.IsCodeNotFound(err) { err = nil } return func() {}, err }
func opClientServiceDestroy(c *gc.C, st api.Connection, mst *state.State) (func(), error) { err := application.NewClient(st).Destroy("non-existent") if params.IsCodeNotFound(err) { err = nil } return func() {}, err }
func opClientAddServiceUnits(c *gc.C, st api.Connection, mst *state.State) (func(), error) { _, err := st.Client().AddServiceUnits("nosuch", 1, "") if params.IsCodeNotFound(err) { err = nil } return func() {}, err }
func (s *storageAttachmentWatcher) loop() error { for { select { case <-s.catacomb.Dying(): return s.catacomb.ErrDying() case _, ok := <-s.changes: if !ok { return errors.New("storage attachment watcher closed") } snapshot, err := getStorageSnapshot( s.st, s.storageTag, s.unitTag, ) if params.IsCodeNotFound(err) { // The storage attachment was removed // from state, so we can stop watching. return nil } else if params.IsCodeNotProvisioned(err) { // We do not care about unattached // storage here. continue } else if err != nil { return err } change := storageAttachmentChange{ s.storageTag, snapshot, } select { case <-s.catacomb.Dying(): return s.catacomb.ErrDying() case s.out <- change: } } } }
// startMachine creates a new data value for tracking details of the // machine and starts watching the machine for units added or removed. func (fw *Firewaller) startMachine(tag names.MachineTag) error { machined := &machineData{ fw: fw, tag: tag, unitds: make(map[names.UnitTag]*unitData), openedPorts: make([]network.PortRange, 0), definedPorts: make(map[network.PortRange]names.UnitTag), } m, err := machined.machine() if params.IsCodeNotFound(err) { return nil } else if err != nil { return errors.Annotate(err, "cannot watch machine units") } unitw, err := m.WatchUnits() if err != nil { return errors.Trace(err) } // XXX(fwereade): this is the best of a bunch of bad options. We've started // the watch, so we're responsible for it; but we (probably?) need to do this // little dance below to update the machined data on the fw loop goroutine, // whence it's usually accessed, before we start the machined watchLoop // below. That catacomb *should* be the only one responsible -- and it *is* // responsible -- but having it in the main fw catacomb as well does no harm, // and greatly simplifies the code below (which would otherwise have to // manage unitw lifetime and errors manually). if err := fw.catacomb.Add(unitw); err != nil { return errors.Trace(err) } select { case <-fw.catacomb.Dying(): return fw.catacomb.ErrDying() case change, ok := <-unitw.Changes(): if !ok { return errors.New("machine units watcher closed") } fw.machineds[tag] = machined err = fw.unitsChanged(&unitsChange{machined, change}) if err != nil { delete(fw.machineds, tag) return errors.Annotatef(err, "cannot respond to units changes for %q", tag) } } err = catacomb.Invoke(catacomb.Plan{ Site: &machined.catacomb, Work: func() error { return machined.watchLoop(unitw) }, }) if err != nil { delete(fw.machineds, tag) return errors.Trace(err) } // register the machined with the firewaller's catacomb. return fw.catacomb.Add(machined) }
// reconcileInstances compares the initially started watcher for machines, // units and services with the opened and closed ports of the instances and // opens and closes the appropriate ports for each instance. func (fw *Firewaller) reconcileInstances() error { for _, machined := range fw.machineds { m, err := machined.machine() if params.IsCodeNotFound(err) { if err := fw.forgetMachine(machined); err != nil { return err } continue } if err != nil { return err } instanceId, err := m.InstanceId() if errors.IsNotProvisioned(err) { logger.Errorf("Machine not yet provisioned: %v", err) continue } if err != nil { return err } instances, err := fw.environ.Instances([]instance.Id{instanceId}) if err == environs.ErrNoInstances { return nil } if err != nil { return err } machineId := machined.tag.Id() initialPortRanges, err := instances[0].Ports(machineId) if err != nil { return err } // Check which ports to open or to close. toOpen := diffRanges(machined.openedPorts, initialPortRanges) toClose := diffRanges(initialPortRanges, machined.openedPorts) if len(toOpen) > 0 { logger.Infof("opening instance port ranges %v for %q", toOpen, machined.tag) if err := instances[0].OpenPorts(machineId, toOpen); err != nil { // TODO(mue) Add local retry logic. return err } network.SortPortRanges(toOpen) } if len(toClose) > 0 { logger.Infof("closing instance port ranges %v for %q", toClose, machined.tag) if err := instances[0].ClosePorts(machineId, toClose); err != nil { // TODO(mue) Add local retry logic. return err } network.SortPortRanges(toClose) } } return nil }
// commonLoop implements the loop structure common to the client // watchers. It should be started in a separate goroutine by any // watcher that embeds commonWatcher. It kills the commonWatcher's // tomb when an error occurs. func (w *commonWatcher) commonLoop() { defer close(w.in) var wg sync.WaitGroup wg.Add(1) go func() { // When the watcher has been stopped, we send a Stop request // to the server, which will remove the watcher and return a // CodeStopped error to any currently outstanding call to // Next. If a call to Next happens just after the watcher has // been stopped, we'll get a CodeNotFound error; Either way // we'll return, wait for the stop request to complete, and // the watcher will die with all resources cleaned up. defer wg.Done() <-w.tomb.Dying() if err := w.call("Stop", nil); err != nil { logger.Errorf("error trying to stop watcher: %v", err) } }() wg.Add(1) go func() { // Because Next blocks until there are changes, we need to // call it in a separate goroutine, so the watcher can be // stopped normally. defer wg.Done() for { result := w.newResult() err := w.call("Next", &result) if err != nil { if params.IsCodeStopped(err) || params.IsCodeNotFound(err) { if w.tomb.Err() != tomb.ErrStillAlive { // The watcher has been stopped at the client end, so we're // expecting one of the above two kinds of error. // We might see the same errors if the server itself // has been shut down, in which case we leave them // untouched. err = tomb.ErrDying } } // Something went wrong, just report the error and bail out. w.tomb.Kill(err) return } select { case <-w.tomb.Dying(): return case w.in <- result: // Report back the result we just got. } } }() wg.Wait() }
func (s syncToolsAPIAdapter) FindTools(majorVersion int, stream string) (coretools.List, error) { result, err := s.syncToolsAPI.FindTools(majorVersion, -1, "", "") if err != nil { return nil, err } if result.Error != nil { if params.IsCodeNotFound(result.Error) { return nil, coretools.ErrNoMatches } return nil, result.Error } return result.List, nil }
// unitsChanged responds to changes to the assigned units. func (fw *Firewaller) unitsChanged(change *unitsChange) error { changed := []*unitData{} for _, name := range change.units { unitTag := names.NewUnitTag(name) unit, err := fw.st.Unit(unitTag) if err != nil && !params.IsCodeNotFound(err) { return err } var machineTag names.MachineTag if unit != nil { machineTag, err = unit.AssignedMachine() if params.IsCodeNotFound(err) { continue } else if err != nil && !params.IsCodeNotAssigned(err) { return err } } if unitd, known := fw.unitds[unitTag]; known { knownMachineTag := fw.unitds[unitTag].machined.tag if unit == nil || unit.Life() == params.Dead || machineTag != knownMachineTag { fw.forgetUnit(unitd) changed = append(changed, unitd) logger.Debugf("stopped watching unit %s", name) } // TODO(dfc) fw.machineds should be map[names.Tag] } else if unit != nil && unit.Life() != params.Dead && fw.machineds[machineTag] != nil { err = fw.startUnit(unit, machineTag) if err != nil { return err } changed = append(changed, fw.unitds[unitTag]) logger.Debugf("started watching %q", unitTag) } } if err := fw.flushUnits(changed); err != nil { return errors.Annotate(err, "cannot change firewall ports") } return nil }
func opClientServiceSetCharm(c *gc.C, st api.Connection, mst *state.State) (func(), error) { cfg := application.SetCharmConfig{ ApplicationName: "nosuch", CharmID: charmstore.CharmID{ URL: charm.MustParseURL("local:quantal/wordpress"), }, } err := application.NewClient(st).SetCharm(cfg) if params.IsCodeNotFound(err) { err = nil } return func() {}, err }
func opClientServiceUpdate(c *gc.C, st *api.State, mst *state.State) (func(), error) { args := params.ServiceUpdate{ ServiceName: "no-such-charm", CharmUrl: "cs:quantal/wordpress-42", ForceCharmUrl: true, SettingsStrings: map[string]string{"blog-title": "foo"}, SettingsYAML: `"wordpress": {"blog-title": "foo"}`, } err := st.Client().ServiceUpdate(args) if params.IsCodeNotFound(err) { err = nil } return func() {}, err }
func opClientServiceUpdate(c *gc.C, st api.Connection, mst *state.State) (func(), error) { args := params.ApplicationUpdate{ ApplicationName: "no-such-charm", CharmURL: "cs:quantal/wordpress-42", ForceCharmURL: true, SettingsStrings: map[string]string{"blog-title": "foo"}, SettingsYAML: `"wordpress": {"blog-title": "foo"}`, } err := application.NewClient(st).Update(args) if params.IsCodeNotFound(err) { err = nil } return func() {}, err }
// refreshVolumeBlockDevices refreshes the block devices for the specified // volumes. func refreshVolumeBlockDevices(ctx *context, volumeTags []names.VolumeTag) error { machineTag, ok := ctx.config.Scope.(names.MachineTag) if !ok { // This function should only be called by machine-scoped // storage provisioners. panic(errors.New("expected machine tag")) } ids := make([]params.MachineStorageId, len(volumeTags)) for i, volumeTag := range volumeTags { ids[i] = params.MachineStorageId{ MachineTag: machineTag.String(), AttachmentTag: volumeTag.String(), } } results, err := ctx.config.Volumes.VolumeBlockDevices(ids) if err != nil { return errors.Annotate(err, "refreshing volume block devices") } for i, result := range results { if result.Error == nil { ctx.volumeBlockDevices[volumeTags[i]] = result.Result for _, params := range ctx.incompleteFilesystemParams { if params.Volume == volumeTags[i] { updatePendingFilesystem(ctx, params) } } for id, params := range ctx.incompleteFilesystemAttachmentParams { filesystem, ok := ctx.filesystems[params.Filesystem] if !ok { continue } if filesystem.Volume == volumeTags[i] { updatePendingFilesystemAttachment(ctx, id, params) } } } else if params.IsCodeNotProvisioned(result.Error) || params.IsCodeNotFound(result.Error) { // Either the volume (attachment) isn't provisioned, // or the corresponding block device is not yet known. // // Neither of these errors is fatal; we just wait for // the block device watcher to notify us again. } else { return errors.Annotatef( err, "getting block device info for volume attachment %v", ids[i], ) } } return nil }
// update is called when hook.SourceChanges are applied. func (s *storageSource) update() error { attachment, err := s.st.StorageAttachment(s.storageTag, s.unitTag) if params.IsCodeNotFound(err) { // The storage attachment was removed from state, which // implies that the storage has been detached already. logger.Debugf("storage attachment %q not found", s.storageTag.Id()) return nil } else if params.IsCodeNotProvisioned(err) { logger.Debugf("storage attachment %q not provisioned yet", s.storageTag.Id()) return nil } else if err != nil { logger.Debugf("error refreshing storage details: %v", err) return errors.Annotate(err, "refreshing storage details") } return s.storageHookQueue.Update(attachment) }
func defaultCloud(cloudClient CloudAPI) (names.CloudTag, jujucloud.Cloud, error) { cloudTag, err := cloudClient.DefaultCloud() if err != nil { if params.IsCodeNotFound(err) { return names.CloudTag{}, jujucloud.Cloud{}, errors.NewNotFound(nil, ` there is no default cloud defined, please specify one using: juju add-model [flags] <model-name> cloud[/region]`[1:]) } return names.CloudTag{}, jujucloud.Cloud{}, errors.Trace(err) } cloud, err := cloudClient.Cloud(cloudTag) if err != nil { return names.CloudTag{}, jujucloud.Cloud{}, errors.Trace(err) } return cloudTag, cloud, nil }
// flushInstancePorts opens and closes ports global on the machine. func (fw *Firewaller) flushInstancePorts(machined *machineData, toOpen, toClose []network.PortRange) error { // If there's nothing to do, do nothing. // This is important because when a machine is first created, // it will have no instance id but also no open ports - // InstanceId will fail but we don't care. if len(toOpen) == 0 && len(toClose) == 0 { return nil } m, err := machined.machine() if params.IsCodeNotFound(err) { return nil } if err != nil { return err } machineId := machined.tag.Id() instanceId, err := m.InstanceId() if err != nil { return err } instances, err := fw.environ.Instances([]instance.Id{instanceId}) if err != nil { return err } // Open and close the ports. if len(toOpen) > 0 { if err := instances[0].OpenPorts(machineId, toOpen); err != nil { // TODO(mue) Add local retry logic. return err } network.SortPortRanges(toOpen) logger.Infof("opened port ranges %v on %q", toOpen, machined.tag) } if len(toClose) > 0 { if err := instances[0].ClosePorts(machineId, toClose); err != nil { // TODO(mue) Add local retry logic. return err } network.SortPortRanges(toClose) logger.Infof("closed port ranges %v on %q", toClose, machined.tag) } return nil }
// machineLifeChanged starts watching new machines when the firewaller // is starting, or when new machines come to life, and stops watching // machines that are dying. func (fw *Firewaller) machineLifeChanged(tag names.MachineTag) error { m, err := fw.st.Machine(tag) found := !params.IsCodeNotFound(err) if found && err != nil { return err } dead := !found || m.Life() == params.Dead machined, known := fw.machineds[tag] if known && dead { return fw.forgetMachine(machined) } if !known && !dead { err = fw.startMachine(tag) if err != nil { return err } logger.Debugf("started watching %q", tag) } return nil }
// initVersions collects state relevant to an upgrade decision. The returned // agent and client versions, and the list of currently available tools, will // always be accurate; the chosen version, and the flag indicating development // mode, may remain blank until uploadTools or validate is called. func (c *upgradeJujuCommand) initVersions(client upgradeJujuAPI, cfg *config.Config, agentVersion version.Number, filterOnPrior bool) (*upgradeContext, error) { if c.Version == agentVersion { return nil, errUpToDate } filterVersion := jujuversion.Current if c.Version != version.Zero { filterVersion = c.Version } else if filterOnPrior { // Trying to find the latest of the prior major version. // TODO (cherylj) if no tools found, suggest upgrade to // the current client version. filterVersion.Major-- } logger.Debugf("searching for tools with major: %d", filterVersion.Major) findResult, err := client.FindTools(filterVersion.Major, -1, "", "") if err != nil { return nil, err } err = findResult.Error if findResult.Error != nil { if !params.IsCodeNotFound(err) { return nil, err } if !c.UploadTools { // No tools found and we shouldn't upload any, so if we are not asking for a // major upgrade, pretend there is no more recent version available. if c.Version == version.Zero && agentVersion.Major == filterVersion.Major { return nil, errUpToDate } return nil, err } } return &upgradeContext{ agent: agentVersion, client: jujuversion.Current, chosen: c.Version, tools: findResult.List, apiClient: client, config: cfg, }, nil }