/pkg/operator/ceph/version/version.go

https://github.com/rook/rook · Go · 300 lines · 209 code · 41 blank · 50 comment · 72 complexity · b55346de6049ad24f9e702c7d0cd13ee MD5 · raw file

  1. /*
  2. Copyright 2019 The Rook Authors. All rights reserved.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package version
  14. import (
  15. "fmt"
  16. "regexp"
  17. "strconv"
  18. "github.com/coreos/pkg/capnslog"
  19. "github.com/pkg/errors"
  20. )
  21. // CephVersion represents the Ceph version format
  22. type CephVersion struct {
  23. Major int
  24. Minor int
  25. Extra int
  26. Build int
  27. }
  28. const (
  29. unknownVersionString = "<unknown version>"
  30. )
  31. var (
  32. // Minimum supported version is 14.2.5
  33. Minimum = CephVersion{14, 2, 5, 0}
  34. // Nautilus Ceph version
  35. Nautilus = CephVersion{14, 0, 0, 0}
  36. // Octopus Ceph version
  37. Octopus = CephVersion{15, 0, 0, 0}
  38. // Pacific Ceph version
  39. Pacific = CephVersion{16, 0, 0, 0}
  40. // supportedVersions are production-ready versions that rook supports
  41. supportedVersions = []CephVersion{Nautilus, Octopus}
  42. unsupportedVersions = []CephVersion{Pacific}
  43. // for parsing the output of `ceph --version`
  44. versionPattern = regexp.MustCompile(`ceph version (\d+)\.(\d+)\.(\d+)`)
  45. // For a build release the output is "ceph version 14.2.4-64.el8cp"
  46. // So we need to detect the build version change
  47. buildVersionPattern = regexp.MustCompile(`ceph version (\d+)\.(\d+)\.(\d+)\-(\d+)`)
  48. logger = capnslog.NewPackageLogger("github.com/rook/rook", "cephver")
  49. )
  50. func (v *CephVersion) String() string {
  51. return fmt.Sprintf("%d.%d.%d-%d %s",
  52. v.Major, v.Minor, v.Extra, v.Build, v.ReleaseName())
  53. }
  54. // CephVersionFormatted returns the Ceph version in a human readable format
  55. func (v *CephVersion) CephVersionFormatted() string {
  56. return fmt.Sprintf("ceph version %d.%d.%d-%d %s",
  57. v.Major, v.Minor, v.Extra, v.Build, v.ReleaseName())
  58. }
  59. // ReleaseName is the name of the Ceph release
  60. func (v *CephVersion) ReleaseName() string {
  61. switch v.Major {
  62. case Octopus.Major:
  63. return "octopus"
  64. case Nautilus.Major:
  65. return "nautilus"
  66. default:
  67. return unknownVersionString
  68. }
  69. }
  70. // ExtractCephVersion extracts the major, minor and extra digit of a Ceph release
  71. func ExtractCephVersion(src string) (*CephVersion, error) {
  72. var build int
  73. m := versionPattern.FindStringSubmatch(src)
  74. if m == nil {
  75. return nil, errors.Errorf("failed to parse version from: %q", src)
  76. }
  77. major, err := strconv.Atoi(m[1])
  78. if err != nil {
  79. return nil, errors.Errorf("failed to parse version major part: %q", m[1])
  80. }
  81. minor, err := strconv.Atoi(m[2])
  82. if err != nil {
  83. return nil, errors.Errorf("failed to parse version minor part: %q", m[2])
  84. }
  85. extra, err := strconv.Atoi(m[3])
  86. if err != nil {
  87. return nil, errors.Errorf("failed to parse version extra part: %q", m[3])
  88. }
  89. // See if we are running on a build release
  90. mm := buildVersionPattern.FindStringSubmatch(src)
  91. // We don't need to handle any error here, so let's jump in only when "mm" has content
  92. if mm != nil {
  93. build, err = strconv.Atoi(mm[4])
  94. if err != nil {
  95. logger.Warningf("failed to convert version build number part %q to an integer, ignoring", mm[4])
  96. }
  97. }
  98. return &CephVersion{major, minor, extra, build}, nil
  99. }
  100. // Supported checks if a given release is supported
  101. func (v *CephVersion) Supported() bool {
  102. for _, sv := range supportedVersions {
  103. if v.isRelease(sv) {
  104. return true
  105. }
  106. }
  107. return false
  108. }
  109. func (v *CephVersion) isRelease(other CephVersion) bool {
  110. return v.Major == other.Major
  111. }
  112. // IsNautilus checks if the Ceph version is Nautilus
  113. func (v *CephVersion) IsNautilus() bool {
  114. return v.isRelease(Nautilus)
  115. }
  116. // IsOctopus checks if the Ceph version is Octopus
  117. func (v *CephVersion) IsOctopus() bool {
  118. return v.isRelease(Octopus)
  119. }
  120. // IsPacific checks if the Ceph version is Pacific
  121. func (v *CephVersion) IsPacific() bool {
  122. return v.isRelease(Pacific)
  123. }
  124. // IsAtLeast checks a given Ceph version is at least a given one
  125. func (v *CephVersion) IsAtLeast(other CephVersion) bool {
  126. if v.Major > other.Major {
  127. return true
  128. } else if v.Major < other.Major {
  129. return false
  130. }
  131. // If we arrive here then v.Major == other.Major
  132. if v.Minor > other.Minor {
  133. return true
  134. } else if v.Minor < other.Minor {
  135. return false
  136. }
  137. // If we arrive here then v.Minor == other.Minor
  138. if v.Extra > other.Extra {
  139. return true
  140. } else if v.Extra < other.Extra {
  141. return false
  142. }
  143. // If we arrive here then both versions are identical
  144. return true
  145. }
  146. // IsAtLeastPacific check that the Ceph version is at least Pacific
  147. func (v *CephVersion) IsAtLeastPacific() bool {
  148. return v.IsAtLeast(Pacific)
  149. }
  150. // IsAtLeastOctopus check that the Ceph version is at least Octopus
  151. func (v *CephVersion) IsAtLeastOctopus() bool {
  152. return v.IsAtLeast(Octopus)
  153. }
  154. // IsAtLeastNautilus check that the Ceph version is at least Nautilus
  155. func (v *CephVersion) IsAtLeastNautilus() bool {
  156. return v.IsAtLeast(Nautilus)
  157. }
  158. // IsIdentical checks if Ceph versions are identical
  159. func IsIdentical(a, b CephVersion) bool {
  160. if a.Major == b.Major {
  161. if a.Minor == b.Minor {
  162. if a.Extra == b.Extra {
  163. if a.Build == b.Build {
  164. return true
  165. }
  166. }
  167. }
  168. }
  169. return false
  170. }
  171. // IsSuperior checks if a given version if superior to another one
  172. func IsSuperior(a, b CephVersion) bool {
  173. if a.Major > b.Major {
  174. return true
  175. }
  176. if a.Major == b.Major {
  177. if a.Minor > b.Minor {
  178. return true
  179. }
  180. }
  181. if a.Major == b.Major {
  182. if a.Minor == b.Minor {
  183. if a.Extra > b.Extra {
  184. return true
  185. }
  186. }
  187. }
  188. if a.Major == b.Major {
  189. if a.Minor == b.Minor {
  190. if a.Extra == b.Extra {
  191. if a.Build > b.Build {
  192. return true
  193. }
  194. }
  195. }
  196. }
  197. return false
  198. }
  199. // IsInferior checks if a given version if inferior to another one
  200. func IsInferior(a, b CephVersion) bool {
  201. if a.Major < b.Major {
  202. return true
  203. }
  204. if a.Major == b.Major {
  205. if a.Minor < b.Minor {
  206. return true
  207. }
  208. }
  209. if a.Major == b.Major {
  210. if a.Minor == b.Minor {
  211. if a.Extra < b.Extra {
  212. return true
  213. }
  214. }
  215. }
  216. if a.Major == b.Major {
  217. if a.Minor == b.Minor {
  218. if a.Extra == b.Extra {
  219. if a.Build < b.Build {
  220. return true
  221. }
  222. }
  223. }
  224. }
  225. return false
  226. }
  227. // ValidateCephVersionsBetweenLocalAndExternalClusters makes sure an external cluster can be connected
  228. // by checking the external ceph versions available and comparing it with the local image provided
  229. func ValidateCephVersionsBetweenLocalAndExternalClusters(localVersion, externalVersion CephVersion) error {
  230. logger.Debugf("local version is %q, external version is %q", localVersion.String(), externalVersion.String())
  231. // We only support Nautilus or newer
  232. if !externalVersion.IsAtLeastNautilus() {
  233. return errors.Errorf("unsupported ceph version %q, need at least nautilus, delete your cluster CR and create a new one with a correct ceph version", externalVersion.String())
  234. }
  235. // Identical version, regardless if other CRs are running, it's ok!
  236. if IsIdentical(localVersion, externalVersion) {
  237. return nil
  238. }
  239. // Local version must never be higher than the external one
  240. if IsSuperior(localVersion, externalVersion) {
  241. return errors.Errorf("local cluster ceph version is higher %q than the external cluster %q, this must never happen", externalVersion.String(), localVersion.String())
  242. }
  243. // External cluster was updated to a minor version higher, consider updating too!
  244. if localVersion.Major == externalVersion.Major {
  245. if IsSuperior(externalVersion, localVersion) {
  246. logger.Warningf("external cluster ceph version is a minor version higher %q than the local cluster %q, consider upgrading", externalVersion.String(), localVersion.String())
  247. return nil
  248. }
  249. }
  250. // The external cluster was upgraded, consider upgrading too!
  251. if localVersion.Major < externalVersion.Major {
  252. logger.Errorf("external cluster ceph version is a major version higher %q than the local cluster %q, consider upgrading", externalVersion.String(), localVersion.String())
  253. return nil
  254. }
  255. return nil
  256. }