func (s *sender) sendMetrics(reader spool.MetricReader) error { batches, err := reader.Read() if err != nil { return errors.Annotate(err, "failed to open the metric reader") } var sendBatches []params.MetricBatchParam for _, batch := range batches { sendBatches = append(sendBatches, spool.APIMetricBatch(batch)) } results, err := s.client.AddMetricBatches(sendBatches) if err != nil { return errors.Annotate(err, "could not send metrics") } for batchUUID, resultErr := range results { // if we fail to send any metric batch we log a warning with the assumption that // the unsent metric batches remain in the spool directory and will be sent to the // controller when the network partition is restored. if _, ok := resultErr.(*params.Error); ok || params.IsCodeAlreadyExists(resultErr) { err := reader.Remove(batchUUID) if err != nil { logger.Errorf("could not remove batch %q from spool: %v", batchUUID, err) } } else { logger.Errorf("failed to send batch %q: %v", batchUUID, resultErr) } } return nil }
// 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 } }
// Do sends metrics from the metric spool to the // state server via an api call. func (s *sender) Do(stop <-chan struct{}) error { reader, err := s.factory.Reader() if err != nil { return errors.Trace(err) } batches, err := reader.Read() if err != nil { logger.Warningf("failed to open the metric reader: %v", err) return errors.Trace(err) } defer reader.Close() var sendBatches []params.MetricBatchParam for _, batch := range batches { sendBatches = append(sendBatches, spool.APIMetricBatch(batch)) } results, err := s.client.AddMetricBatches(sendBatches) if err != nil { logger.Warningf("could not send metrics: %v", err) return errors.Trace(err) } for batchUUID, resultErr := range results { // if we fail to send any metric batch we log a warning with the assumption that // the unsent metric batches remain in the spool directory and will be sent to the // state server when the network partition is restored. if _, ok := resultErr.(*params.Error); ok || params.IsCodeAlreadyExists(resultErr) { err = reader.Remove(batchUUID) if err != nil { logger.Warningf("could not remove batch %q from spool: %v", batchUUID, err) } } else { logger.Warningf("failed to send batch %q: %v", batchUUID, resultErr) } } return nil }
// FlushContext implements the Context interface. func (ctx *HookContext) FlushContext(process string, ctxErr error) (err error) { // A non-existant metricsRecorder simply means that metrics were disabled // for this hook run. if ctx.metricsRecorder != nil { err := ctx.metricsRecorder.Close() if err != nil { return errors.Trace(err) } } writeChanges := ctxErr == nil // In the case of Actions, handle any errors using finalizeAction. if ctx.actionData != nil { // If we had an error in err at this point, it's part of the // normal behavior of an Action. Errors which happen during // the finalize should be handed back to the uniter. Close // over the existing err, clear it, and only return errors // which occur during the finalize, e.g. API call errors. defer func(ctxErr error) { err = ctx.finalizeAction(ctxErr, err) }(ctxErr) ctxErr = nil } else { // TODO(gsamfira): Just for now, reboot will not be supported in actions. defer ctx.handleReboot(&err) } for id, rctx := range ctx.relations { if writeChanges { if e := rctx.WriteSettings(); e != nil { e = errors.Errorf( "could not write settings from %q to relation %d: %v", process, id, e, ) logger.Errorf("%v", e) if ctxErr == nil { ctxErr = e } } } } for rangeKey, rangeInfo := range ctx.pendingPorts { if writeChanges { var e error var op string if rangeInfo.ShouldOpen { e = ctx.unit.OpenPorts( rangeKey.Ports.Protocol, rangeKey.Ports.FromPort, rangeKey.Ports.ToPort, ) op = "open" } else { e = ctx.unit.ClosePorts( rangeKey.Ports.Protocol, rangeKey.Ports.FromPort, rangeKey.Ports.ToPort, ) op = "close" } if e != nil { e = errors.Annotatef(e, "cannot %s %v", op, rangeKey.Ports) logger.Errorf("%v", e) if ctxErr == nil { ctxErr = e } } } } // add storage to unit dynamically if len(ctx.storageAddConstraints) > 0 && writeChanges { err := ctx.unit.AddStorage(ctx.storageAddConstraints) if err != nil { err = errors.Annotatef(err, "cannot add storage") logger.Errorf("%v", err) if ctxErr == nil { ctxErr = err } } } // TODO (tasdomas) 2014 09 03: context finalization needs to modified to apply all // changes in one api call to minimize the risk // of partial failures. if ctx.metricsReader == nil { return ctxErr } if !writeChanges { return ctxErr } batches, err := ctx.metricsReader.Open() if err != nil { return errors.Trace(err) } defer ctx.metricsReader.Close() var sendBatches []params.MetricBatch for _, batch := range batches { if len(batch.Metrics) == 0 { // empty batches not supported yet. logger.Infof("skipping and removing empty metrics batch with UUID %q", batch.UUID) err := ctx.metricsReader.Remove(batch.UUID) if err != nil { return errors.Trace(err) } continue } metrics := make([]params.Metric, len(batch.Metrics)) for i, metric := range batch.Metrics { metrics[i] = params.Metric{Key: metric.Key, Value: metric.Value, Time: metric.Time} } batchParam := params.MetricBatch{ UUID: batch.UUID, CharmURL: batch.CharmURL, Created: batch.Created, Metrics: metrics, } sendBatches = append(sendBatches, batchParam) } results, err := ctx.metricsSender.AddMetricBatches(sendBatches) if err != nil { // Do not return metric sending error. logger.Errorf("%v", err) } for batchUUID, resultErr := range results { if resultErr == nil || resultErr == (*params.Error)(nil) || params.IsCodeAlreadyExists(resultErr) { err = ctx.metricsReader.Remove(batchUUID) if err != nil { logger.Errorf("could not remove batch %q from spool: %v", batchUUID, err) } } else { logger.Errorf("failed to send batch %q: %v", batchUUID, resultErr) } } return ctxErr }