summaryrefslogtreecommitdiff
path: root/azurelinuxagent/pa/rdma/centos.py
blob: c527e1be0f5d86fbf85cccae2b31bdedae6f244f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# Microsoft Azure Linux Agent
#
# Copyright 2014 Microsoft Corporation
#
# 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.
#
# Requires Python 2.4+ and Openssl 1.0+
#

import glob
import os
import re
import azurelinuxagent.common.logger as logger
import azurelinuxagent.common.utils.shellutil as shellutil
from azurelinuxagent.common.rdma import RDMAHandler


class CentOSRDMAHandler(RDMAHandler):
    rdma_user_mode_package_name = 'microsoft-hyper-v-rdma'
    rdma_kernel_mode_package_name = 'kmod-microsoft-hyper-v-rdma'
    rdma_wrapper_package_name = 'msft-rdma-drivers'

    hyper_v_package_name = "hypervkvpd"
    hyper_v_package_name_new = "microsoft-hyper-v"

    version_major = None
    version_minor = None

    def __init__(self, distro_version):
        v = distro_version.split('.')
        if len(v) < 2:
            raise Exception('Unexpected centos version: %s' % distro_version)
        self.version_major, self.version_minor = v[0], v[1]

    def install_driver(self):
        """
        Install the KVP daemon and the appropriate RDMA driver package for the
        RDMA firmware.
        """
        fw_version = RDMAHandler.get_rdma_version()
        if not fw_version:
            raise Exception('Cannot determine RDMA firmware version')
        logger.info("RDMA: found firmware version: {0}".format(fw_version))
        fw_version = self.get_int_rdma_version(fw_version)
        installed_pkg = self.get_rdma_package_info()
        if installed_pkg:
            logger.info(
                'RDMA: driver package present: {0}'.format(installed_pkg))
            if self.is_rdma_package_up_to_date(installed_pkg, fw_version):
                logger.info('RDMA: driver package is up-to-date')
                return
            else:
                logger.info('RDMA: driver package needs updating')
                self.update_rdma_package(fw_version)
        else:
            logger.info('RDMA: driver package is NOT installed')
            self.update_rdma_package(fw_version)

    def is_rdma_package_up_to_date(self, pkg, fw_version):
        # Example match (pkg name, -, followed by 3 segments, fw_version and -):
        # - pkg=microsoft-hyper-v-rdma-4.1.0.142-20160323.x86_64
        # - fw_version=142
        pattern = '{0}-\d\.\d\.\d\.({1})-'.format(
            self.rdma_user_mode_package_name, fw_version)
        return re.match(pattern, pkg)

    @staticmethod
    def get_int_rdma_version(version):
        s = version.split('.')
        if len(s) == 0:
            raise Exception('Unexpected RDMA firmware version: "%s"' % version)
        return s[0]

    def get_rdma_package_info(self):
        """
        Returns the installed rdma package name or None
        """
        ret, output = shellutil.run_get_output(
            'rpm -q %s' % self.rdma_user_mode_package_name, chk_err=False)
        if ret != 0:
            return None
        return output

    def update_rdma_package(self, fw_version):
        logger.info("RDMA: updating RDMA packages")
        self.refresh_repos()
        self.force_install_package(self.rdma_wrapper_package_name)
        self.install_rdma_drivers(fw_version)

    def force_install_package(self, pkg_name):
        """
        Attempts to remove existing package and installs the package
        """
        logger.info('RDMA: Force installing package: %s' % pkg_name)
        if self.uninstall_package(pkg_name) != 0:
            logger.info('RDMA: Erasing package failed but will continue')
        if self.install_package(pkg_name) != 0:
            raise Exception('Failed to install package "{0}"'.format(pkg_name))
        logger.info('RDMA: installation completed: %s' % pkg_name)

    @staticmethod
    def uninstall_package(pkg_name):
        return shellutil.run('yum erase -y -q {0}'.format(pkg_name))

    @staticmethod
    def install_package(pkg_name):
        return shellutil.run('yum install -y -q {0}'.format(pkg_name))

    def refresh_repos(self):
        logger.info("RDMA: refreshing yum repos")
        if shellutil.run('yum clean all') != 0:
            raise Exception('Cleaning yum repositories failed')
        if shellutil.run('yum updateinfo') != 0:
            raise Exception('Failed to act on yum repo update information')
        logger.info("RDMA: repositories refreshed")

    def install_rdma_drivers(self, fw_version):
        """
        Installs the drivers from /opt/rdma/rhel[Major][Minor] directory,
        particularly the microsoft-hyper-v-rdma-* kmod-* and (no debuginfo or
        src). Tries to uninstall them first.
        """
        pkg_dir = '/opt/microsoft/rdma/rhel{0}{1}'.format(
            self.version_major, self.version_minor)
        logger.info('RDMA: pkgs dir: {0}'.format(pkg_dir))
        if not os.path.isdir(pkg_dir):
            raise Exception('RDMA packages directory %s is missing' % pkg_dir)

        pkgs = os.listdir(pkg_dir)
        logger.info('RDMA: found %d files in package directory' % len(pkgs))

        # Uninstal KVP daemon first (if exists)
        self.uninstall_kvp_driver_package_if_exists()

        # Install kernel mode driver (kmod-microsoft-hyper-v-rdma-*)
        kmod_pkg = self.get_file_by_pattern(
            pkgs, "%s-\d\.\d\.\d\.+(%s)-\d{8}\.x86_64.rpm" % (self.rdma_kernel_mode_package_name, fw_version))
        if not kmod_pkg:
            raise Exception("RDMA kernel mode package not found")
        kmod_pkg_path = os.path.join(pkg_dir, kmod_pkg)
        self.uninstall_pkg_and_install_from(
            'kernel mode', self.rdma_kernel_mode_package_name, kmod_pkg_path)

        # Install user mode driver (microsoft-hyper-v-rdma-*)
        umod_pkg = self.get_file_by_pattern(
            pkgs, "%s-\d\.\d\.\d\.+(%s)-\d{8}\.x86_64.rpm" % (self.rdma_user_mode_package_name, fw_version))
        if not umod_pkg:
            raise Exception("RDMA user mode package not found")
        umod_pkg_path = os.path.join(pkg_dir, umod_pkg)
        self.uninstall_pkg_and_install_from(
            'user mode', self.rdma_user_mode_package_name, umod_pkg_path)

        logger.info("RDMA: driver packages installed")
        self.load_driver_module()
        if not self.is_driver_loaded():
            logger.info("RDMA: driver module is not loaded; reboot required")
            self.reboot_system()
        else:
            logger.info("RDMA: kernel module is loaded")

    @staticmethod
    def get_file_by_pattern(list, pattern):
        for l in list:
            if re.match(pattern, l):
                return l
        return None

    def uninstall_pkg_and_install_from(self, pkg_type, pkg_name, pkg_path):
        logger.info(
            "RDMA: Processing {0} driver: {1}".format(pkg_type, pkg_path))
        logger.info("RDMA: Try to uninstall existing version: %s" % pkg_name)
        if self.uninstall_package(pkg_name) == 0:
            logger.info("RDMA: Successfully uninstaled %s" % pkg_name)
        logger.info(
            "RDMA: Installing {0} package from {1}".format(pkg_type, pkg_path))
        if self.install_package(pkg_path) != 0:
            raise Exception(
                "Failed to install RDMA {0} package".format(pkg_type))

    def uninstall_kvp_driver_package_if_exists(self):
        kvp_pkgs = [self.hyper_v_package_name,
                    self.hyper_v_package_name_new]

        for kvp_pkg in kvp_pkgs:
            if shellutil.run("rpm -q %s" % kvp_pkg, chk_err=False) != 0:
                logger.info(
                    "RDMA: kvp package %s does not exist, skipping" % kvp_pkg)
            else:
                logger.info('RDMA: erasing kvp package "%s"' % kvp_pkg)
                if shellutil.run("yum erase -q -y %s" % kvp_pkg, chk_err=False) == 0:
                    logger.info("RDMA: successfully erased package")
                else:
                    logger.error("RDMA: failed to erase package")