Add tripleo_ovs_upgrade module.

This module replace the code in [1] as we need to replicate this
sequence of actions in several places in the templates.

It first checks if it's a layered product ovs update which uses
rhosp-openvswitch.  If so, it makes sure that openvswitch is removed
without triggering any actions, like stopping the service.  Then we
install the rhos-openvswitch package which will take care of updating
everything without disturbance.

If the os is not using layer product, then we download the openvswitch
and update it without triggering the systemctl restart if present in
the postun scripts.

[1] https://opendev.org/openstack/tripleo-heat-templates/src/branch/master/deployment/tripleo-packages/tripleo-packages-baremetal-puppet.yaml#L234-L370

Change-Id: I793bdb8db1d34011cf64cf9942a811ab5efc00ed
This commit is contained in:
Sofer Athlan-Guyot 2020-07-24 20:45:25 +02:00
parent 83bb2e3f03
commit 6344bbb43d
1 changed files with 250 additions and 0 deletions

View File

@ -0,0 +1,250 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright 2020 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
__metaclass__ = type
import glob
import re
from ansible.module_utils.basic import AnsibleModule
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = """
---
module: tripleo_ovs_update
author:
- Sofer Athlan-Guyot <sathlang@redhat.com>
version_added: '2.8'
short_description: Handle special ovs update.
notes: []
description:
- This module check if ovs need a special treatment during update of the
package.
options:
debug:
description:
- Whether or not debug is enabled.
default: False
required: False
type: bool
"""
EXAMPLES = """
- name: Special treatment for ovs upgrade.
tripleo_ovs_upgrade:
"""
RETURN = """
msg:
description: Descrption of the action taken.
returned: always
type: str
changed:
description: Was the ovs package update or not.
returned: always
type: bool
"""
def pkg_manager(module, downloader=False):
dnf = module.get_bin_path('dnf')
if dnf:
module.debug("Using dnf as package manager")
if not downloader:
return dnf
else:
return ['dnf', 'download']
if not downloader:
return module.get_bin_path('yum')
else:
return ['yumdownloader']
# Get current OpenvSwitch package name
# return stuff like openvswitch2.11 in original yaml.
def get_current_ovs_pkg_name(module):
cmd = ['rpm', '-qa']
_, output, _ = module.run_command(cmd, check_rc=True)
ovs_re = re.compile(r"""
^(openvswitch[0-9]+\.[0-9]+-[0-9]+\.[0-9]+\.[0-9]+ # layered
| # or
openvswitch-2) # non-layered
""", re.X)
for pkg in output.split("\n"):
ovs = re.search(ovs_re, pkg)
if ovs:
return ovs.group(0)
return None
# Process rhosp-openvswitch layered package for new version number
# return stuff like ["2.11"] in original module
def get_version(module, pkg, new=True):
if new:
cmd = [pkg_manager(module), 'info', '-q', pkg]
else:
cmd = ['rpm', '-qi', pkg]
# This may fail if the package is not around for non-lp product.
_, output, _ = module.run_command(cmd, check_rc=False)
versions = re.findall(r'Version[^:]*:[^0-9]*([0-9.]+)', output)
found = []
for version in versions:
if version:
# we are only interested in major/minor number here.
if new:
# We can have several version here
found.append(version.split('.')[:2])
else:
found = version.split('.')[:2]
return found
def flatten_version(versions, join_str=''):
flatten_str = ""
if not isinstance(versions, list):
versions = [versions]
if isinstance(versions[0], list):
for version in versions:
flatten_str += join_str.join(version)
else:
flatten_str += join_str.join(versions)
return flatten_str
def get_current_ovs_pkg_names(module, pkg):
cmd = ['rpm', '-qa', pkg]
_, output, _ = module.run_command(cmd, check_rc=True)
# Make sure we remove empty element.
return [pkg for pkg in output.split("\n") if pkg]
def remove_package_noaction(module, pkgs, excludes=[]):
cmd = ['rpm', '-e', '--noscripts', '--nopreun',
'--nopostun', '--notriggers', '--nodeps']
pkgs_to_remove = []
for pkg in pkgs:
for exclude in excludes:
if not re.match(r'{}'.format(exclude), pkg):
pkgs_to_remove.append(pkg)
_, output, _ = module.run_command(cmd + pkgs_to_remove, check_rc=True)
return output
def upgrade_pkg(module, pkg):
cmd = [pkg_manager(module), 'upgrade', '-y', pkg]
_, output, _ = module.run_command(cmd, check_rc=True)
return output
def layer_product_upgrade(module, result, ovs_pkg, lp_ovs_current_version):
lp_ovs_coming_versions = get_version(module, 'rhosp-openvswitch')
ovs_current_version = get_version(module, ovs_pkg, new=False)
pkg_suffix = ''
if int(ovs_current_version[0]) >= 3 or int(ovs_current_version[1]) >= 10:
pkg_suffix = '.'.join(ovs_current_version)
pkg_base_name = 'openvswitch{}*'.format(pkg_suffix)
if flatten_version(lp_ovs_coming_versions) \
!= flatten_version(ovs_current_version):
ovs_pkgs = get_current_ovs_pkg_names(module, pkg_base_name)
remove_package_noaction(module, ovs_pkgs,
excludes=['selinux'])
upgraded = upgrade_pkg(module, 'rhosp-openvswitch')
result['msg'] += \
""" Layer product update workaround applied for {} \
Upgraded:'{}'""".format(ovs_pkgs, upgraded)
result['changed'] = True
else:
result['msg'] += " No need to upgrade ovs."
def pkg_has_restart(module):
cmd = """rpm -q --scripts openvswitch | \\
awk '/postuninstall/,/*/' | \\
grep -q 'systemctl.*try-restart'"""
rc, _, _ = module.run_command(cmd, check_rc=False,
use_unsafe_shell=True)
return rc == 0
def upgrade_non_layered_ovs(module, result):
tmp_dir = '/root/OVS_UPGRADE'
cmds = [
['rm', '-rf', tmp_dir],
['install', '-d', '-o', 'root', '-g', 'root', '-m', '0750', tmp_dir],
[pkg_manager(module), 'makecache'],
pkg_manager(module, downloader=True)
+ ['--destdir', tmp_dir, '--resolve', 'openvswitch']]
for cmd in cmds:
module.run_command(cmd, check_rc=True)
for pkg in glob.glob(tmp_dir + '/*.rpm'):
cmd = ['rpm', '-U',
'--replacepkgs',
'--notriggerun',
'--nopostun',
pkg]
module.run_command(cmd, check_rc=True)
result['msg'] += " {} handled".format(pkg)
result['changed'] = True
def non_layered_ovs_upgrade(module, result):
if not pkg_has_restart(module):
result['msg'] += 'Nothing to be done for non layered ovs upgrade, ' \
"post-script doesn't have restart."
else:
result['msg'] += "OVS upgrade special handling."
upgrade_non_layered_ovs(module, result)
def main():
module = AnsibleModule(argument_spec={}, supports_check_mode=False)
result = dict(
changed=False,
msg=''
)
ovs_current_pkg = get_current_ovs_pkg_name(module)
if ovs_current_pkg:
# We found a ovs package, let's dive in.
ovs_current_version = get_version(module,
'rhosp-openvswitch',
new=False)
if ovs_current_version:
result['msg'] += "Found a layered product ovs. "
layer_product_upgrade(module, result,
ovs_current_pkg, ovs_current_version)
else:
result['msg'] += "Found ovs. "
non_layered_ovs_upgrade(module, result)
else:
result['msg'] += "No ovs installed, nothing to do."
module.exit_json(**result)
if __name__ == '__main__':
main()