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 (dc *DaemonConfig) pruneSnapshots(val *config.Volume) { logrus.Infof("starting snapshot prune for %q", val.VolumeName) if val.Backends.Snapshot == "" { logrus.Debugf("Snapshot driver for volume %v was empty, not snapshotting.", val) return } uc := &config.UseSnapshot{ Volume: val.String(), Reason: lock.ReasonSnapshotPrune, } stopChan, err := lock.NewDriver(dc.Config).AcquireWithTTLRefresh(uc, dc.Global.TTL, dc.Global.Timeout) if err != nil { logrus.Error(errors.LockFailed.Combine(err)) return } defer func() { stopChan <- struct{}{} }() driver, err := backend.NewSnapshotDriver(val.Backends.Snapshot) if err != nil { logrus.Errorf("failed to get driver: %v", err) return } driverOpts := storage.DriverOptions{ Volume: storage.Volume{ Name: val.String(), Params: storage.Params{ "pool": val.DriverOptions["pool"], }, }, Timeout: dc.Global.Timeout, } list, err := driver.ListSnapshots(driverOpts) if err != nil { logrus.Errorf("Could not list snapshots for volume %q: %v", val.VolumeName, err) return } logrus.Debugf("Volume %q: keeping %d snapshots", val, val.RuntimeOptions.Snapshot.Keep) toDeleteCount := len(list) - int(val.RuntimeOptions.Snapshot.Keep) if toDeleteCount < 0 { return } for i := 0; i < toDeleteCount; i++ { logrus.Infof("Removing snapshot %q for volume %q", list[i], val.VolumeName) if err := driver.RemoveSnapshot(list[i], driverOpts); err != nil { logrus.Errorf("Removing snapshot %q for volume %q failed: %v", list[i], val.VolumeName, err) } } }
// NewAPI returns an *API func NewAPI(volplugin Volplugin, hostname string, client *config.Client, global **config.Global) *API { return &API{ Volplugin: volplugin, Hostname: hostname, Client: client, Global: global, Lock: lock.NewDriver(client), MountCollection: mount.NewCollection(), MountCounter: mount.NewCounter(), lockStopChans: map[string]chan struct{}{}, } }
// Daemon is the top-level entrypoint for the volsupervisor from the CLI. func Daemon(ctx *cli.Context) { cfg, err := config.NewClient(ctx.String("prefix"), ctx.StringSlice("etcd")) if err != nil { logrus.Fatal(err) } retry: global, err := cfg.GetGlobal() if err != nil { logrus.Errorf("Could not retrieve global configuration: %v. Retrying in 1 second", err) time.Sleep(time.Second) goto retry } dc := &DaemonConfig{Config: cfg, Global: global, Hostname: ctx.String("host-label")} dc.setDebug() globalChan := make(chan *watch.Watch) dc.Config.WatchGlobal(globalChan) go dc.watchAndSetGlobal(globalChan) go info.HandleDebugSignal() stopChan, err := lock.NewDriver(dc.Config).AcquireWithTTLRefresh(&config.UseVolsupervisor{Hostname: dc.Hostname}, dc.Global.TTL, dc.Global.Timeout) if err != nil { logrus.Fatal("Could not start volsupervisor: already in use") } sigChan := make(chan os.Signal, 1) go func() { <-sigChan logrus.Infof("Removing volsupervisor global lock; waiting %v for lock to clear", dc.Global.TTL) stopChan <- struct{}{} time.Sleep(wait.Jitter(dc.Global.TTL+time.Second, 0)) // give us enough time to try to clear the lock os.Exit(0) }() signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT) dc.signalSnapshot() dc.updateVolumes() // doing it here ensures the goroutine is created when the first poll completes. go func() { for { time.Sleep(wait.Jitter(time.Second, 0)) dc.updateVolumes() } }() dc.loop() }
// 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 } }
func (dc *DaemonConfig) createSnapshot(val *config.Volume) { logrus.Infof("Snapshotting %q.", val) uc := &config.UseSnapshot{ Volume: val.String(), Reason: lock.ReasonSnapshot, } stopChan, err := lock.NewDriver(dc.Config).AcquireWithTTLRefresh(uc, dc.Global.TTL, dc.Global.Timeout) if err != nil { logrus.Error(err) return } defer func() { stopChan <- struct{}{} }() driver, err := backend.NewSnapshotDriver(val.Backends.Snapshot) if err != nil { logrus.Errorf("Error establishing driver backend %q; cannot snapshot", val.Backends.Snapshot) return } driverOpts := storage.DriverOptions{ Volume: storage.Volume{ Name: val.String(), Params: storage.Params{ "pool": val.DriverOptions["pool"], }, }, Timeout: dc.Global.Timeout, } if err := driver.CreateSnapshot(time.Now().String(), driverOpts); err != nil { logrus.Errorf("Error creating snapshot for volume %q: %v", val, 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 } }
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) }