volume_routes.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. package volume // import "github.com/docker/docker/api/server/router/volume"
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "strconv"
  7. "github.com/containerd/log"
  8. "github.com/docker/docker/api/server/httputils"
  9. "github.com/docker/docker/api/types/filters"
  10. "github.com/docker/docker/api/types/versions"
  11. "github.com/docker/docker/api/types/volume"
  12. "github.com/docker/docker/errdefs"
  13. "github.com/docker/docker/volume/service/opts"
  14. "github.com/pkg/errors"
  15. )
  16. const (
  17. // clusterVolumesVersion defines the API version that swarm cluster volume
  18. // functionality was introduced. avoids the use of magic numbers.
  19. clusterVolumesVersion = "1.42"
  20. )
  21. func (v *volumeRouter) getVolumesList(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  22. if err := httputils.ParseForm(r); err != nil {
  23. return err
  24. }
  25. filters, err := filters.FromJSON(r.Form.Get("filters"))
  26. if err != nil {
  27. return errors.Wrap(err, "error reading volume filters")
  28. }
  29. volumes, warnings, err := v.backend.List(ctx, filters)
  30. if err != nil {
  31. return err
  32. }
  33. version := httputils.VersionFromContext(ctx)
  34. if versions.GreaterThanOrEqualTo(version, clusterVolumesVersion) && v.cluster.IsManager() {
  35. clusterVolumes, swarmErr := v.cluster.GetVolumes(volume.ListOptions{Filters: filters})
  36. if swarmErr != nil {
  37. // if there is a swarm error, we may not want to error out right
  38. // away. the local list probably worked. instead, let's do what we
  39. // do if there's a bad driver while trying to list: add the error
  40. // to the warnings. don't do this if swarm is not initialized.
  41. warnings = append(warnings, swarmErr.Error())
  42. }
  43. // add the cluster volumes to the return
  44. volumes = append(volumes, clusterVolumes...)
  45. }
  46. return httputils.WriteJSON(w, http.StatusOK, &volume.ListResponse{Volumes: volumes, Warnings: warnings})
  47. }
  48. func (v *volumeRouter) getVolumeByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  49. if err := httputils.ParseForm(r); err != nil {
  50. return err
  51. }
  52. version := httputils.VersionFromContext(ctx)
  53. // re: volume name duplication
  54. //
  55. // we prefer to get volumes locally before attempting to get them from the
  56. // cluster. Local volumes can only be looked up by name, but cluster
  57. // volumes can also be looked up by ID.
  58. vol, err := v.backend.Get(ctx, vars["name"], opts.WithGetResolveStatus)
  59. // if the volume is not found in the regular volume backend, and the client
  60. // is using an API version greater than 1.42 (when cluster volumes were
  61. // introduced), then check if Swarm has the volume.
  62. if errdefs.IsNotFound(err) && versions.GreaterThanOrEqualTo(version, clusterVolumesVersion) && v.cluster.IsManager() {
  63. swarmVol, err := v.cluster.GetVolume(vars["name"])
  64. // if swarm returns an error and that error indicates that swarm is not
  65. // initialized, return original NotFound error. Otherwise, we'd return
  66. // a weird swarm unavailable error on non-swarm engines.
  67. if err != nil {
  68. return err
  69. }
  70. vol = &swarmVol
  71. } else if err != nil {
  72. // otherwise, if this isn't NotFound, or this isn't a high enough version,
  73. // just return the error by itself.
  74. return err
  75. }
  76. return httputils.WriteJSON(w, http.StatusOK, vol)
  77. }
  78. func (v *volumeRouter) postVolumesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  79. if err := httputils.ParseForm(r); err != nil {
  80. return err
  81. }
  82. var req volume.CreateOptions
  83. if err := httputils.ReadJSON(r, &req); err != nil {
  84. return err
  85. }
  86. var (
  87. vol *volume.Volume
  88. err error
  89. version = httputils.VersionFromContext(ctx)
  90. )
  91. // if the ClusterVolumeSpec is filled in, then this is a cluster volume
  92. // and is created through the swarm cluster volume backend.
  93. //
  94. // re: volume name duplication
  95. //
  96. // As it happens, there is no good way to prevent duplication of a volume
  97. // name between local and cluster volumes. This is because Swarm volumes
  98. // can be created from any manager node, bypassing most of the protections
  99. // we could put into the engine side.
  100. //
  101. // Instead, we will allow creating a volume with a duplicate name, which
  102. // should not break anything.
  103. if req.ClusterVolumeSpec != nil && versions.GreaterThanOrEqualTo(version, clusterVolumesVersion) {
  104. log.G(ctx).Debug("using cluster volume")
  105. vol, err = v.cluster.CreateVolume(req)
  106. } else {
  107. log.G(ctx).Debug("using regular volume")
  108. vol, err = v.backend.Create(ctx, req.Name, req.Driver, opts.WithCreateOptions(req.DriverOpts), opts.WithCreateLabels(req.Labels))
  109. }
  110. if err != nil {
  111. return err
  112. }
  113. return httputils.WriteJSON(w, http.StatusCreated, vol)
  114. }
  115. func (v *volumeRouter) putVolumesUpdate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  116. if !v.cluster.IsManager() {
  117. return errdefs.Unavailable(errors.New("volume update only valid for cluster volumes, but swarm is unavailable"))
  118. }
  119. if err := httputils.ParseForm(r); err != nil {
  120. return err
  121. }
  122. rawVersion := r.URL.Query().Get("version")
  123. version, err := strconv.ParseUint(rawVersion, 10, 64)
  124. if err != nil {
  125. err = fmt.Errorf("invalid swarm object version '%s': %v", rawVersion, err)
  126. return errdefs.InvalidParameter(err)
  127. }
  128. var req volume.UpdateOptions
  129. if err := httputils.ReadJSON(r, &req); err != nil {
  130. return err
  131. }
  132. return v.cluster.UpdateVolume(vars["name"], version, req)
  133. }
  134. func (v *volumeRouter) deleteVolumes(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  135. if err := httputils.ParseForm(r); err != nil {
  136. return err
  137. }
  138. force := httputils.BoolValue(r, "force")
  139. // First we try deleting local volume. The volume may not be found as a
  140. // local volume, but could be a cluster volume, so we ignore "not found"
  141. // errors at this stage. Note that no "not found" error is produced if
  142. // "force" is enabled.
  143. err := v.backend.Remove(ctx, vars["name"], opts.WithPurgeOnError(force))
  144. if err != nil && !errdefs.IsNotFound(err) {
  145. return err
  146. }
  147. // If no volume was found, the volume may be a cluster volume. If force
  148. // is enabled, the volume backend won't return an error for non-existing
  149. // volumes, so we don't know if removal succeeded (or not volume existed).
  150. // In that case we always try to delete cluster volumes as well.
  151. if errdefs.IsNotFound(err) || force {
  152. version := httputils.VersionFromContext(ctx)
  153. if versions.GreaterThanOrEqualTo(version, clusterVolumesVersion) && v.cluster.IsManager() {
  154. err = v.cluster.RemoveVolume(vars["name"], force)
  155. }
  156. }
  157. if err != nil {
  158. return err
  159. }
  160. w.WriteHeader(http.StatusNoContent)
  161. return nil
  162. }
  163. func (v *volumeRouter) postVolumesPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  164. if err := httputils.ParseForm(r); err != nil {
  165. return err
  166. }
  167. pruneFilters, err := filters.FromJSON(r.Form.Get("filters"))
  168. if err != nil {
  169. return err
  170. }
  171. // API version 1.42 changes behavior where prune should only prune anonymous volumes.
  172. // To keep older API behavior working, we need to add this filter option to consider all (local) volumes for pruning, not just anonymous ones.
  173. if versions.LessThan(httputils.VersionFromContext(ctx), "1.42") {
  174. pruneFilters.Add("all", "true")
  175. }
  176. pruneReport, err := v.backend.Prune(ctx, pruneFilters)
  177. if err != nil {
  178. return err
  179. }
  180. return httputils.WriteJSON(w, http.StatusOK, pruneReport)
  181. }