tripleo-ansible/tripleo_ansible/ansible_plugins/modules/tripleo_ovs_upgrade.py
Sofer Athlan-Guyot 31ed062a9c Add support for rdo openvswitch layered upgrade special treatment.
The special treatment for openvswitch works on Red Hat. This patch add
RDO support.

We also adjust for the all naming convention of the openvswitch
package.

This was discovered during the related-bug investigation. Note that it
in itself doesn't solve it.

Co-Authored-By: yatinkarel <ykarel@redhat.com>
Depends-On: https://review.opendev.org/#/c/761463/
Change-Id: I1986ef8109061dab8213f8ec29160b6d830cdc49
Related-Bug: #1897863
2020-11-05 04:37:53 +00:00

321 lines
10 KiB
Python

#!/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 os
import re
from ansible.module_utils._text import to_native
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']
def get_current_ovs_pkg_name(module):
""" Get currently installed ovs pkg name, layered or not."""
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(?!-[a-z]+)) # 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 set_openflow_version_on_bridges(module, bridges=None):
if bridges is None:
bridges = ['br-int']
for bridge in bridges:
cmd = ['ovs-vsctl', 'set', 'bridge', bridge,
'protocols=OpenFlow10,OpenFlow13,OpenFlow15']
rc, out, err = module.run_command(cmd)
if rc != 0:
module.warn('Cannot set new OpenFlow protocols on a bridge: '
'%s: %s.' %
(bridge, to_native(err)))
def layer_product_upgrade(module, result, ovs_pkg, lp_ovs_current_version):
"""Actually do the layered ovs upgrade with workaround.
So we have a layered package (rhosp|rdo)-openvswitch. To prevent
any cut in networking during update/upgrade, we update ovs without
triggering the scripts in the package that stop the service.
So first it determines if the package has a upgrade coming and
then erases it making sure no package script is triggered and
finally it re-install the new package.
It also prevents incomptible issues between ovs database schema
during a rolling update.
No cut in service at the cost of a needed reboot to get the new
binaries in place.
"""
layered_product_name = get_layered_product_name()
lp_ovs_coming_versions = get_version(module, layered_product_name)
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)
if ovs_pkg == 'openvswitch':
pkg_base_name = 'openvswitch*'
else:
pkg_base_name = 'openvswitch{}*'.format(pkg_suffix)
if flatten_version(lp_ovs_coming_versions) \
!= flatten_version(ovs_current_version):
# NOTE(mjozefcz): Workaround for bz1863024.
if '2.11' == flatten_version(ovs_current_version, join_str='.'):
set_openflow_version_on_bridges(module)
ovs_pkgs = get_current_ovs_pkg_names(module, pkg_base_name)
remove_package_noaction(module, ovs_pkgs,
excludes=['selinux'])
upgraded = upgrade_pkg(module, layered_product_name)
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_disruption(module):
"""Check if the current ovs pkg include a disruptive action."""
awk_cmds = ["awk '/postuninstall/,/*/' | grep -q 'systemctl.*try-restart'",
"awk '/preun/,/*/' | grep -q 'systemctl.*disable'"]
rc = 1
for awk in awk_cmds:
cmd = "rpm -q --scripts openvswitch | {}".format(awk)
rc, _, _ = module.run_command(cmd, check_rc=False,
use_unsafe_shell=True)
if rc == 0:
break
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_disruption(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 get_distro():
"""Get the distro as defined in /etc/os-release[ID]."""
distro = None
os_release_file = '/etc/os-release'
if os.path.isfile(os_release_file):
with open(os_release_file) as release_file:
for line in release_file.readlines():
match = re.match('^ *ID *="?([^"]+)"?$', line)
if match:
distro = match.group(1)
break
return distro
def get_layered_product_name():
"""Get the layer product name version depending on os.
It's rhosp-openvswitch on redhat, and rdo-openvswitch on centos.
"""
distro = get_distro()
if distro is not None and distro == 'centos':
return 'rdo-openvswitch'
return 'rhosp-openvswitch'
def main():
module = AnsibleModule(argument_spec={}, supports_check_mode=False)
result = dict(
changed=False,
msg=''
)
layered_product_name = get_layered_product_name()
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,
layered_product_name,
new=False)
if ovs_current_version:
result['msg'] += "Found a layered product ovs - {} - " \
.format(layered_product_name)
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()