aos_version.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. #!/usr/bin/python
  2. # vim: expandtab:tabstop=4:shiftwidth=4
  3. '''
  4. Ansible module for yum-based systems determining if multiple releases
  5. of an OpenShift package are available, and if the release requested
  6. (if any) is available down to the given precision.
  7. For Enterprise, multiple releases available suggest that multiple repos
  8. are enabled for the different releases, which may cause installation
  9. problems. With Origin, however, this is a normal state of affairs as
  10. all the releases are provided in a single repo with the expectation that
  11. only the latest can be installed.
  12. Code in the openshift_version role contains a lot of logic to pin down
  13. the exact package and image version to use and so does some validation
  14. of release availability already. Without duplicating all that, we would
  15. like the user to have a helpful error message if we detect things will
  16. not work out right. Note that if openshift_release is not specified in
  17. the inventory, the version comparison checks just pass.
  18. TODO: fail gracefully on non-yum systems (dnf in Fedora)
  19. '''
  20. from ansible.module_utils.basic import AnsibleModule
  21. IMPORT_EXCEPTION = None
  22. try:
  23. import yum # pylint: disable=import-error
  24. except ImportError as err:
  25. IMPORT_EXCEPTION = err # in tox test env, yum import fails
  26. class AosVersionException(Exception):
  27. '''Base exception class for package version problems'''
  28. def __init__(self, message, problem_pkgs=None):
  29. Exception.__init__(self, message)
  30. self.problem_pkgs = problem_pkgs
  31. def main():
  32. '''Entrypoint for this Ansible module'''
  33. module = AnsibleModule(
  34. argument_spec=dict(
  35. requested_openshift_release=dict(type="str", default=''),
  36. openshift_deployment_type=dict(required=True),
  37. rpm_prefix=dict(required=True), # atomic-openshift, origin, ...?
  38. ),
  39. supports_check_mode=True
  40. )
  41. if IMPORT_EXCEPTION:
  42. module.fail_json(msg="aos_version module could not import yum: %s" % IMPORT_EXCEPTION)
  43. # determine the packages we will look for
  44. rpm_prefix = module.params['rpm_prefix']
  45. if not rpm_prefix:
  46. module.fail_json(msg="rpm_prefix must not be empty")
  47. expected_pkgs = set([
  48. rpm_prefix,
  49. rpm_prefix + '-master',
  50. rpm_prefix + '-node',
  51. ])
  52. # determine what level of precision the user specified for the openshift version.
  53. # should look like a version string with possibly many segments e.g. "3.4.1":
  54. requested_openshift_release = module.params['requested_openshift_release']
  55. # get the list of packages available and complain if anything is wrong
  56. try:
  57. pkgs = _retrieve_available_packages(expected_pkgs)
  58. if requested_openshift_release:
  59. _check_precise_version_found(pkgs, expected_pkgs, requested_openshift_release)
  60. _check_higher_version_found(pkgs, expected_pkgs, requested_openshift_release)
  61. if module.params['openshift_deployment_type'] in ['openshift-enterprise']:
  62. _check_multi_minor_release(pkgs, expected_pkgs)
  63. except AosVersionException as excinfo:
  64. module.fail_json(msg=str(excinfo))
  65. module.exit_json(changed=False)
  66. def _retrieve_available_packages(expected_pkgs):
  67. # search for package versions available for openshift pkgs
  68. yb = yum.YumBase() # pylint: disable=invalid-name
  69. # The openshift excluder prevents unintended updates to openshift
  70. # packages by setting yum excludes on those packages. See:
  71. # https://wiki.centos.org/SpecialInterestGroup/PaaS/OpenShift-Origin-Control-Updates
  72. # Excludes are then disabled during an install or upgrade, but
  73. # this check will most likely be running outside either. When we
  74. # attempt to determine what packages are available via yum they may
  75. # be excluded. So, for our purposes here, disable excludes to see
  76. # what will really be available during an install or upgrade.
  77. yb.conf.disable_excludes = ['all']
  78. try:
  79. pkgs = yb.pkgSack.returnPackages(patterns=expected_pkgs)
  80. except yum.Errors.PackageSackError as excinfo:
  81. # you only hit this if *none* of the packages are available
  82. raise AosVersionException('\n'.join([
  83. 'Unable to find any OpenShift packages.',
  84. 'Check your subscription and repo settings.',
  85. str(excinfo),
  86. ]))
  87. return pkgs
  88. class PreciseVersionNotFound(AosVersionException):
  89. '''Exception for reporting packages not available at given release'''
  90. def __init__(self, requested_release, not_found):
  91. msg = ['Not all of the required packages are available at requested version %s:' % requested_release]
  92. msg += [' ' + name for name in not_found]
  93. msg += ['Please check your subscriptions and enabled repositories.']
  94. AosVersionException.__init__(self, '\n'.join(msg), not_found)
  95. def _check_precise_version_found(pkgs, expected_pkgs, requested_openshift_release):
  96. # see if any packages couldn't be found at requested release version
  97. # we would like to verify that the latest available pkgs have however specific a version is given.
  98. # so e.g. if there is a package version 3.4.1.5 the check passes; if only 3.4.0, it fails.
  99. pkgs_precise_version_found = {}
  100. for pkg in pkgs:
  101. if pkg.name not in expected_pkgs:
  102. continue
  103. # does the version match, to the precision requested?
  104. # and, is it strictly greater, at the precision requested?
  105. match_version = '.'.join(pkg.version.split('.')[:requested_openshift_release.count('.') + 1])
  106. if match_version == requested_openshift_release:
  107. pkgs_precise_version_found[pkg.name] = True
  108. not_found = []
  109. for name in expected_pkgs:
  110. if name not in pkgs_precise_version_found:
  111. not_found.append(name)
  112. if not_found:
  113. raise PreciseVersionNotFound(requested_openshift_release, not_found)
  114. class FoundHigherVersion(AosVersionException):
  115. '''Exception for reporting that a higher version than requested is available'''
  116. def __init__(self, requested_release, higher_found):
  117. msg = ['Some required package(s) are available at a version',
  118. 'that is higher than requested %s:' % requested_release]
  119. msg += [' ' + name for name in higher_found]
  120. msg += ['This will prevent installing the version you requested.']
  121. msg += ['Please check your enabled repositories or adjust openshift_release.']
  122. AosVersionException.__init__(self, '\n'.join(msg), higher_found)
  123. def _check_higher_version_found(pkgs, expected_pkgs, requested_openshift_release):
  124. req_release_arr = [int(segment) for segment in requested_openshift_release.split(".")]
  125. # see if any packages are available in a version higher than requested
  126. higher_version_for_pkg = {}
  127. for pkg in pkgs:
  128. if pkg.name not in expected_pkgs:
  129. continue
  130. version = [int(segment) for segment in pkg.version.split(".")]
  131. too_high = version[:len(req_release_arr)] > req_release_arr
  132. higher_than_seen = version > higher_version_for_pkg.get(pkg.name, [])
  133. if too_high and higher_than_seen:
  134. higher_version_for_pkg[pkg.name] = version
  135. if higher_version_for_pkg:
  136. higher_found = []
  137. for name, version in higher_version_for_pkg.items():
  138. higher_found.append(name + '-' + '.'.join(str(segment) for segment in version))
  139. raise FoundHigherVersion(requested_openshift_release, higher_found)
  140. class FoundMultiRelease(AosVersionException):
  141. '''Exception for reporting multiple minor releases found for same package'''
  142. def __init__(self, multi_found):
  143. msg = ['Multiple minor versions of these packages are available']
  144. msg += [' ' + name for name in multi_found]
  145. msg += ["There should only be one OpenShift release repository enabled at a time."]
  146. AosVersionException.__init__(self, '\n'.join(msg), multi_found)
  147. def _check_multi_minor_release(pkgs, expected_pkgs):
  148. # see if any packages are available in more than one minor version
  149. pkgs_by_name_version = {}
  150. for pkg in pkgs:
  151. # keep track of x.y (minor release) versions seen
  152. minor_release = '.'.join(pkg.version.split('.')[:2])
  153. if pkg.name not in pkgs_by_name_version:
  154. pkgs_by_name_version[pkg.name] = {}
  155. pkgs_by_name_version[pkg.name][minor_release] = True
  156. multi_found = []
  157. for name in expected_pkgs:
  158. if name in pkgs_by_name_version and len(pkgs_by_name_version[name]) > 1:
  159. multi_found.append(name)
  160. if multi_found:
  161. raise FoundMultiRelease(multi_found)
  162. if __name__ == '__main__':
  163. main()