func (d *DaemonConfig) handleForceRemoveLock(req *config.VolumeRequest, vc *config.Volume, locks []config.UseLocker) error { exists, err := control.ExistsVolume(vc, d.Global.Timeout) if err != nil && err != errors.NoActionTaken { return errors.RemoveVolume.Combine(errored.New(vc.String())).Combine(err) } if err == errors.NoActionTaken { if err := d.completeRemove(req, vc); err != nil { return err } d.removeVolumeUse(locks[0], vc) } if err != nil { return errors.RemoveVolume.Combine(errored.New(vc.String())).Combine(err) } if !exists { d.removeVolume(req, vc) return errors.RemoveVolume.Combine(errored.New(vc.String())).Combine(errors.NotExists) } err = d.completeRemove(req, vc) if err != nil { return errors.RemoveVolume.Combine(errored.New(vc.String())).Combine(errors.NotExists) } d.removeVolumeUse(locks[0], vc) return nil }
// SplitName splits a docker volume name from policy/name safely. func SplitName(name string) (string, string, error) { if strings.Count(name, "/") > 1 { return "", "", errors.InvalidVolume.Combine(errored.New(name)) } parts := strings.SplitN(name, "/", 2) if len(parts) != 2 || parts[0] == "" || parts[1] == "" { return "", "", errors.InvalidVolume.Combine(errored.New(name)) } return parts[0], parts[1], nil }
// Combines array of errors into a single error func combineErrors(resultErrors []gojson.ResultError) error { var errors []string for _, err := range resultErrors { errors = append(errors, fmt.Sprintf("%s\n", err)) } return errored.New(strings.Join(errors, "\n")) }
// CopySnapshot copies a snapshot into a new volume. Takes a DriverOptions, // snap and volume name (string). Returns error on failure. func (c *Driver) CopySnapshot(do storage.DriverOptions, snapName, newName string) error { intOrigName, err := c.internalName(do.Volume.Name) if err != nil { return err } intNewName, err := c.internalName(newName) if err != nil { return err } poolName := do.Volume.Params["pool"] list, err := c.List(storage.ListOptions{Params: storage.Params{"pool": poolName}}) for _, vol := range list { if intNewName == vol.Name { return errored.Errorf("Volume %q already exists", vol.Name) } } errChan := make(chan error, 1) cmd := exec.Command("rbd", "snap", "protect", mkpool(poolName, intOrigName), "--snap", snapName) er, err := runWithTimeout(cmd, do.Timeout) // EBUSY indicates that the snapshot is already protected. if err != nil && er.ExitStatus != 0 && er.ExitStatus != int(unix.EBUSY) { if er.ExitStatus == int(unix.EEXIST) { err = errored.Errorf("Volume %q or snapshot name %q already exists. Snapshots cannot share the same name as the target volume.", do.Volume.Name, snapName).Combine(errors.Exists).Combine(errors.SnapshotProtect) } errChan <- err return err } defer c.cleanupCopy(snapName, newName, do, errChan) cmd = exec.Command("rbd", "clone", mkpool(poolName, intOrigName), mkpool(poolName, intNewName), "--snap", snapName) er, err = runWithTimeout(cmd, do.Timeout) if err != nil && er.ExitStatus == 0 { var err2 *errored.Error var ok bool err2, ok = err.(*errored.Error) if !ok { err2 = errored.New(err.Error()) } errChan <- err2.Combine(errors.SnapshotCopy) return err2 } if er.ExitStatus != 0 { newerr := errored.Errorf("Cloning snapshot to volume (volume %q, snapshot %q): %v", intOrigName, snapName, err).Combine(errors.SnapshotCopy).Combine(errors.SnapshotProtect) if er.ExitStatus != int(unix.EEXIST) { errChan <- newerr } return err } return nil }
func (d *DaemonConfig) completeRemove(req *config.VolumeRequest, vc *config.Volume) error { if err := control.RemoveVolume(vc, d.Global.Timeout); err != nil && err != errors.NoActionTaken { logrus.Warn(errors.RemoveImage.Combine(errored.New(vc.String())).Combine(err)) } return d.removeVolume(req, vc) }
func (d *DaemonConfig) removeVolume(req *config.VolumeRequest, vc *config.Volume) error { if err := d.Config.RemoveVolume(req.Policy, req.Name); err != nil { return errors.ClearVolume.Combine(errored.New(vc.String())).Combine(err) } return nil }
func (d *DaemonConfig) handleListAll(w http.ResponseWriter, r *http.Request) { vols, err := d.Config.ListAllVolumes() if err != nil { api.RESTHTTPError(w, errors.ListVolume.Combine(err)) return } response := []*config.Volume{} for _, vol := range vols { parts := strings.SplitN(vol, "/", 2) if len(parts) != 2 { api.RESTHTTPError(w, errors.InvalidVolume.Combine(errored.New(vol))) return } // FIXME make this take a single string and not a split one volConfig, err := d.Config.GetVolume(parts[0], parts[1]) if err != nil { api.RESTHTTPError(w, errors.ListVolume.Combine(err)) return } response = append(response, volConfig) } content, err := json.Marshal(response) if err != nil { api.RESTHTTPError(w, errors.ListVolume.Combine(err)) return } w.Write(content) }
// CombineError is a simplication of errored.Combine func CombineError(err error, format string, args ...interface{}) error { if erd, ok := err.(*errored.Error); ok { erd.Combine(errored.Errorf(format, args...)) } return errored.New(err.Error()).Combine(errored.Errorf(format, args...)) }
func (t *testEntity) Validate() error { if t.FailsValidation { return errored.New("failed validation") } return nil }
// Path provides the path to this volumes data store. func (v *Volume) Path() (string, error) { if v.PolicyName == "" || v.VolumeName == "" { return "", errors.InvalidVolume.Combine(errored.New("Volume or policy name is missing")) } return strings.Join([]string{v.Prefix(), v.PolicyName, v.VolumeName}, "/"), nil }
// Validate does nothing on use locks. func (m *Use) Validate() error { parts := strings.Split(m.Volume, "/") if len(parts) != 2 || parts[0] == "" || parts[1] == "" { return errors.InvalidVolume.Combine(errored.New(m.Volume)) } return nil }
func (d *DaemonConfig) handleCreate(w http.ResponseWriter, r *http.Request) { content, err := ioutil.ReadAll(r.Body) if err != nil { api.RESTHTTPError(w, errors.ReadBody.Combine(err)) return } req := &config.VolumeRequest{} if err := json.Unmarshal(content, req); err != nil { api.RESTHTTPError(w, errors.UnmarshalRequest.Combine(err)) return } if req.Policy == "" { api.RESTHTTPError(w, errors.GetPolicy.Combine(errored.Errorf("policy was blank"))) return } if req.Name == "" { api.RESTHTTPError(w, errors.GetVolume.Combine(errored.Errorf("volume was blank"))) return } hostname, err := os.Hostname() if err != nil { api.RESTHTTPError(w, errors.GetHostname.Combine(err)) return } policy, err := d.Config.GetPolicy(req.Policy) if err != nil { api.RESTHTTPError(w, errors.GetPolicy.Combine(errored.New(req.Policy).Combine(err))) return } uc := &config.UseMount{ Volume: strings.Join([]string{req.Policy, req.Name}, "/"), Reason: lock.ReasonCreate, Hostname: hostname, } snapUC := &config.UseSnapshot{ Volume: strings.Join([]string{req.Policy, req.Name}, "/"), Reason: lock.ReasonCreate, } err = lock.NewDriver(d.Config).ExecuteWithMultiUseLock( []config.UseLocker{uc, snapUC}, d.Global.Timeout, d.createVolume(w, req, policy), ) if err != nil && err != errors.Exists { api.RESTHTTPError(w, errors.CreateVolume.Combine(err)) return } }
func unmarshalRequest(r *http.Request) (*config.VolumeRequest, error) { cfg := &config.VolumeRequest{} content, err := ioutil.ReadAll(r.Body) if err != nil { return cfg, err } if err := json.Unmarshal(content, cfg); err != nil { return cfg, err } if cfg.Policy == "" { return cfg, errored.New("Policy was blank") } if cfg.Name == "" { return cfg, errored.New("volume was blank") } return cfg, nil }
// SetKey implements the SetKey entity interface. func (p *Policy) SetKey(key string) error { suffix := strings.Trim(strings.TrimPrefix(key, rootPolicy), "/") if strings.Contains(suffix, "/") { return errors.InvalidDBPath.Combine(errored.Errorf("Policy name %q contains invalid characters", suffix)) } if suffix == "" { return errors.InvalidDBPath.Combine(errored.New("Policy name is empty")) } p.Name = suffix return nil }
// WatchStopPath stops a watch given a path to stop the watch on. func (c *Client) watchStopPath(path string) error { c.watcherMutex.Lock() defer c.watcherMutex.Unlock() stopChan, ok := c.watchers[path] if !ok { return errors.InvalidDBPath.Combine(errored.New("missing key during watch")) } close(stopChan) delete(c.watchers, path) return nil }
// Create fully creates a volume func (a *API) Create(w http.ResponseWriter, r *http.Request) { volume, err := a.ReadCreate(r) if err != nil { a.HTTPError(w, err) return } if vol, err := a.Client.GetVolume(volume.Policy, volume.Name); err == nil && vol != nil { a.HTTPError(w, errors.Exists) return } logrus.Infof("Creating volume %s", volume) hostname, err := os.Hostname() if err != nil { a.HTTPError(w, errors.GetHostname.Combine(err)) return } policyObj, err := a.Client.GetPolicy(volume.Policy) if err != nil { a.HTTPError(w, errors.GetPolicy.Combine(errored.New(volume.Policy)).Combine(err)) return } uc := &config.UseMount{ Volume: volume.String(), Reason: lock.ReasonCreate, Hostname: hostname, } snapUC := &config.UseSnapshot{ Volume: volume.String(), Reason: lock.ReasonCreate, } global := *a.Global err = lock.NewDriver(a.Client).ExecuteWithMultiUseLock( []config.UseLocker{uc, snapUC}, global.Timeout, a.createVolume(w, volume, policyObj), ) if err != nil && err != errors.Exists { a.HTTPError(w, errors.CreateVolume.Combine(err)) return } }
// Free a lock. Passing true as the second parameter will force the removal. func (c *Client) Free(obj db.Lock, force bool) error { path, err := obj.Path() if err != nil { return errors.LockFailed.Combine(err) } logrus.Debugf("Attempting to free %q by %v", path, obj) mylock, ok := c.getLock(path) if !ok { return errors.LockFailed.Combine(errored.New("Could not locate lock")) } if !reflect.DeepEqual(obj, mylock.obj) { if force { goto free } return errors.LockFailed.Combine(errored.New("invalid lock requested to be freed (wrong host?)")) } free: select { case <-mylock.monitorChan: default: mylock.lock.Unlock() } c.lockMutex.Lock() if _, ok := c.locks[path]; ok { delete(c.locks, path) } c.Delete(mylock.obj) c.lockMutex.Unlock() return nil }
// Get retrieves an object from consul, returns error on any problems. func (c *Client) Get(obj db.Entity) error { return helpers.WrapGet(c, obj, func(path string) (string, []byte, error) { pair, _, err := c.client.KV().Get(c.qualified(path), nil) if err != nil { return "", nil, err } if pair == nil { return "", nil, errors.NotExists.Combine(errored.New(c.qualified(path))) } return pair.Key, pair.Value, nil }) }
func validateJSON(schema string, obj Entity) error { schemaObj := gojson.NewStringLoader(schema) doc := gojson.NewGoLoader(obj) if result, err := gojson.Validate(schemaObj, doc); err != nil { return err } else if !result.Valid() { var errors []string for _, err := range result.Errors() { errors = append(errors, fmt.Sprintf("%s\n", err)) } return errored.New(strings.Join(errors, "\n")) } return nil }
// triggered on any failure during call into mount. func (a *API) clearMount(ms mountState) { logrus.Errorf("MOUNT FAILURE: %v", ms.err) if err := ms.driver.Unmount(ms.driverOpts); err != nil { // literally can't do anything about this situation. Log. logrus.Errorf("Failure during unmount after failed mount: %v %v", err, ms.err) } if err := a.Lock.ClearLock(ms.ut, (*a.Global).Timeout); err != nil { a.HTTPError(ms.w, errors.RefreshMount.Combine(errored.New(ms.volConfig.String())).Combine(err).Combine(ms.err)) return } a.HTTPError(ms.w, errors.MountFailed.Combine(ms.err)) return }
// NewClient creates a new Client with connections to etcd already established. func NewClient(hosts []string, prefix string) (*Client, error) { ec, err := client.New(client.Config{Endpoints: hosts}) if err != nil { return nil, err } c := &Client{ client: client.NewKeysAPI(ec), prefix: prefix, watchers: map[string]chan struct{}{}, } if _, err := c.client.Set(context.Background(), c.prefix, "", &client.SetOptions{Dir: true, PrevExist: client.PrevNoExist}); err != nil { if err != nil { er, ok := errors.EtcdToErrored(err).(*errored.Error) if !ok || !er.Contains(errors.Exists) { return nil, errored.New("Initial setup").Combine(err) } } } return c, nil }
func (d *DaemonConfig) handleCopy(w http.ResponseWriter, r *http.Request) { req, err := unmarshalRequest(r) if err != nil { api.RESTHTTPError(w, errors.UnmarshalRequest.Combine(err)) return } if _, ok := req.Options["snapshot"]; !ok { api.RESTHTTPError(w, errors.MissingSnapshotOption) return } if _, ok := req.Options["target"]; !ok { api.RESTHTTPError(w, errors.MissingTargetOption) return } if strings.Contains(req.Options["target"], "/") { api.RESTHTTPError(w, errors.InvalidVolume.Combine(errored.New("/"))) return } volConfig, err := d.Config.GetVolume(req.Policy, req.Name) if err != nil { api.RESTHTTPError(w, errors.GetVolume.Combine(err)) return } if volConfig.Backends.Snapshot == "" { api.RESTHTTPError(w, errors.SnapshotsUnsupported.Combine(errored.New(volConfig.Backends.Snapshot))) return } driver, err := backend.NewSnapshotDriver(volConfig.Backends.Snapshot) if err != nil { api.RESTHTTPError(w, errors.GetDriver.Combine(err)) return } newVolConfig, err := d.Config.GetVolume(req.Policy, req.Name) if err != nil { api.RESTHTTPError(w, errors.GetVolume.Combine(err)) return } newVolConfig.VolumeName = req.Options["target"] do := storage.DriverOptions{ Volume: storage.Volume{ Name: volConfig.String(), Params: volConfig.DriverOptions, }, Timeout: d.Global.Timeout, } host, err := os.Hostname() if err != nil { api.RESTHTTPError(w, errors.GetHostname.Combine(err)) return } if volConfig.VolumeName == newVolConfig.VolumeName { api.RESTHTTPError(w, errors.CannotCopyVolume.Combine(errored.Errorf("You cannot copy volume %q onto itself.", volConfig.VolumeName))) return } snapUC := &config.UseSnapshot{ Volume: volConfig.String(), Reason: lock.ReasonCopy, } newUC := &config.UseMount{ Volume: newVolConfig.String(), Reason: lock.ReasonCopy, Hostname: host, } newSnapUC := &config.UseSnapshot{ Volume: newVolConfig.String(), Reason: lock.ReasonCopy, } err = lock.NewDriver(d.Config).ExecuteWithMultiUseLock([]config.UseLocker{newUC, newSnapUC, snapUC}, d.Global.Timeout, func(ld *lock.Driver, ucs []config.UseLocker) error { if err := d.Config.PublishVolume(newVolConfig); err != nil { return err } if err := driver.CopySnapshot(do, req.Options["snapshot"], newVolConfig.String()); err != nil { return err } return nil }) if err != nil { api.RESTHTTPError(w, errors.PublishVolume.Combine(errored.Errorf( "Creating new volume %q from volume %q, snapshot %q", req.Options["target"], volConfig.String(), req.Options["snapshot"], )).Combine(err)) return } content, err := json.Marshal(newVolConfig) if err != nil { api.RESTHTTPError(w, errors.PublishVolume.Combine(errored.Errorf( "Creating new volume %q from volume %q, snapshot %q", req.Options["target"], volConfig.String(), req.Options["snapshot"], )).Combine(err)) } w.Write(content) }
// this cleans up uses when forcing the removal func (d *DaemonConfig) removeVolumeUse(lock config.UseLocker, vc *config.Volume) { // locks[0] is the usemount lock if err := d.Config.RemoveUse(lock, true); err != nil { logrus.Warn(errors.RemoveImage.Combine(errored.New(vc.String())).Combine(err)) } }
func (d *DaemonConfig) handleRemove(w http.ResponseWriter, r *http.Request) { // set a default timeout if none is specified timeout := d.Global.Timeout req, err := unmarshalRequest(r) if err != nil { api.RESTHTTPError(w, errors.UnmarshalRequest.Combine(err)) return } if req.Options["timeout"] != "" { var t time.Duration if t, err = time.ParseDuration(req.Options["timeout"]); err != nil { api.RESTHTTPError(w, errors.RemoveVolume.Combine(err)) return } timeout = t } vc, err := d.Config.GetVolume(req.Policy, req.Name) if err != nil { api.RESTHTTPError(w, errors.GetVolume.Combine(err)) return } locks, err := d.createRemoveLocks(vc) if err != nil { api.RESTHTTPError(w, err) return } if req.Options["force"] == "true" { if err := d.handleForceRemoveLock(req, vc, locks); err != nil { api.RESTHTTPError(w, err) return } } err = lock.NewDriver(d.Config).ExecuteWithMultiUseLock(locks, timeout, func(ld *lock.Driver, ucs []config.UseLocker) error { exists, err := control.ExistsVolume(vc, timeout) if err != nil && err != errors.NoActionTaken { return err } if err == errors.NoActionTaken { return d.completeRemove(req, vc) } if !exists { d.removeVolume(req, vc) return errors.NotExists } return d.completeRemove(req, vc) }) if err == errors.NotExists { w.WriteHeader(404) return } if err != nil { api.RESTHTTPError(w, errors.RemoveVolume.Combine(errored.New(vc.String())).Combine(err)) return } }
package errors import "github.com/contiv/errored" // service-level errors var ( // Unknown is for those times when we just. don't. know. Unknown = errored.New("Unknown error") // Exists is used to exit in situations where duplicated data would be written. Exists = errored.New("Already exists") // NotExists is used to exit in situations where no data would be read. NotExists = errored.New("Does not exist") // LockFailed is for when locks fails to acquire. LockFailed = errored.New("Locking Operation Failed") // LockMismatch is when our compare/swap operations fail. LockMismatch = errored.New("Compare/swap lock operation failed. Perhaps it's mounted on a different host?") // NoActionTaken signifies that the requested operation was ignored. NoActionTaken = errored.New("No action taken") // ErrPublish is an error for when use locks cannot be published ErrLockPublish = errored.New("Could not publish use lock") // ErrRemove is an error for when use locks cannot be removed ErrLockRemove = errored.New("Could not remove use lock") // VolmasterDown signifies that the apiserver could not be reached. VolmasterDown = errored.New("apiserver could not be contacted")