-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathnvidia-yum.py
277 lines (211 loc) · 9.27 KB
/
nvidia-yum.py
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
from yum.plugins import PluginYumExit, TYPE_CORE, TYPE_INTERACTIVE
from yum.packages import YumInstalledPackage
from yum.constants import *
from rpmUtils.miscutils import compareEVR
import sys
import os
import re
sys.path.insert(0,'/usr/share/yum-cli/')
import yum
from yum.Errors import *
from utils import YumUtilBase
from yum import _
import logging
import rpmUtils
requires_api_version = '2.3'
plugin_type = (TYPE_CORE)
KERNEL_PKG_NAME = 'kernel'
MODULE_PKG_BASENAME = 'kmod-nvidia'
MODULE_PKG_PATTERN = re.compile(MODULE_PKG_BASENAME + '-(branch-[0-9][0-9][0-9]|latest)$')
DRIVER_PKG_BASENAME = 'nvidia-driver'
DRIVER_PKG_PATTERN = re.compile(DRIVER_PKG_BASENAME + '-(branch-[0-9][0-9][0-9]|latest)$')
DEPEND_ON_KMOD_PATTERNS = [DRIVER_PKG_PATTERN]
def msg(conduit, message):
conduit.info(1, 'NVIDIA: ' + message)
def init_hook(conduit):
"""This is just here to make sure the plugin was loaded correctly.
Eventually this should just go away."""
# conduit.info(2, '#### NVIDIA ####')
def addErase(conduit, tsInfo, package):
"""additional sanity check that we only try to addErase() installed packages,
i.e. RPMInstalledPackage instances. If we add others here, e.g. just
YumAvailablePackages, the transaction fails later with a cryptic error message"""
if isinstance(package, YumInstalledPackage):
tsInfo.addErase(package)
else:
conduit.error(2, 'NVIDIA: tried erasing non-installed package ' + str(package) + '/' + str(type(package)))
raise AttributeError
def get_module_package(conduit, driverPackage, kernelPackage):
"""Return the corresponding kernel module package, given an installed driver package
and a kernel package."""
tsInfo = conduit.getTsInfo()
modName = get_module_pkg_name(driverPackage)
modRelease = get_module_pkg_release(kernelPackage, driverPackage)
# We search the DB first so we can be sure to get a YumInstalledPackage
# instance in case the module package is already installed and a
# YumAvailablePackage instance in case it ins't.
db = conduit.getRpmDB()
pkgs = db.searchNevra(modName, driverPackage.epoch, kernelPackage.version, \
modRelease, driverPackage.arch)
if pkgs:
# Assume len(pkgs) == 1, but don't assert
return pkgs[0]
try:
return conduit._base.getPackageObject((modName, driverPackage.arch,
driverPackage.epoch, kernelPackage.version,
modRelease))
except:
pass
tsInfo.deselect('kernel')
return None
def install_modules_for_kernels(conduit, driverPackage, kernelPackages):
"""Install kernel module packages for all given kernel packages"""
tsInfo = conduit.getTsInfo()
db = conduit.getRpmDB()
newestKernel = get_most_recent_kernel(conduit, kernelPackages)
modPo = get_module_package(conduit, driverPackage, newestKernel)
if modPo is None:
modName = get_module_pkg_name(driverPackage)
msg(conduit, 'No kernel module package ' + modName + ' for ' + \
str(newestKernel) + ' and ' + str(driverPackage) + ' found. ' + \
'Ignoring the new kernel')
return False
if db.contains(po = modPo):
return True
tsInfo.addTrueInstall(modPo)
return True
def installing_kernels(conduit, kernelPackages, driverPackage):
"""When installing new kernels, we need to also install the driver module packages
for each of them."""
tsInfo = conduit.getTsInfo()
db = conduit.getRpmDB()
# Remove the kernel module package for all other kernels
newestKernel = get_most_recent_kernel(conduit, kernelPackages)
allKernels = list(kernelPackages)
allKernels.extend(db.returnPackages(patterns=[KERNEL_PKG_NAME]))
# Will install the kernel module package for the newest one of the kernel packages
success = install_modules_for_kernels(conduit, driverPackage, kernelPackages)
if not success:
return
for k in allKernels:
if k != newestKernel:
modPo = get_module_package(conduit, driverPackage, k)
if db.contains(po = modPo):
addErase(conduit, tsInfo, modPo)
def erasing_kernels(conduit, kernelPackages, driverPackage):
"""When erasing kernel modules, we want to remove their driver kernel module
packages, provided they are installed at all."""
db = conduit.getRpmDB()
tsInfo = conduit.getTsInfo()
currentlyInstalledKernels = db.searchNames([KERNEL_PKG_NAME])
# This is the list of kernels we will have installed after the given ones were removed.
remainingKernels = list(set(currentlyInstalledKernels) - set(kernelPackages))
assert(len(remainingKernels) > 0)
newestRemainingKernel = sorted(remainingKernels, cmp = compare_po, reverse = True)[0]
newestModPo = get_module_package(conduit, driverPackage, newestRemainingKernel)
# Remove kernel module packages for all the kernels we remove
for k in kernelPackages:
modPo = get_module_package(conduit, driverPackage, k)
if newestModPo != modPo and db.contains(po = modPo):
addErase(conduit, tsInfo, modPo)
# Install the kernel module package for the now most recent kernel
if not db.contains(po = newestModPo):
tsInfo.addTrueInstall(newestModPo)
def erasing_driver(conduit, driverPackage):
"""When removing the driver package, we automatically remove all the installed
kernel module packages."""
db = conduit.getRpmDB()
tsInfo = conduit.getTsInfo()
modPackages = db.returnPackages(patterns=[MODULE_PKG_BASENAME + '*'])
for modPo in modPackages:
addErase(conduit, tsInfo, modPo)
def installing_driver(conduit, driverPackage, installingKernels):
"""We call this when installing the DRIVER_PKG_BASENAME package. If that happens,
we need to install kernel module packages for all the installed kernels,
as well as the kernels we additionally install in the current transaction"""
db = conduit.getRpmDB()
tsInfo = conduit.getTsInfo()
install_modules_for_kernels(conduit, driverPackage, [])
def postresolve_hook(conduit):
db = conduit.getRpmDB()
tsInfo = conduit.getTsInfo()
erasePkgs = tsInfo.getMembersWithState(output_states=[TS_ERASE, TS_UPDATED])
installPkgs = tsInfo.getMembersWithState(output_states=[TS_INSTALL, TS_TRUEINSTALL,
TS_UPDATE])
# Append a '*' to all the package names in our list
installedDriverPackage = db.returnPackages(patterns=[DRIVER_PKG_BASENAME + '*'])
# The above query for the rpm database returns all packages starting with
# the DRIVER_PKG_BASENAME, but all the subpackages of the nvidia-driver
# package start with 'nvidia-driver', so filter the list out for the correct
# package names.
for k in list(installedDriverPackage):
if not is_driver_po(k):
installedDriverPackage.remove(k)
installingDriverPackage = None
erasingDriverPackage = None
installingKernels = []
erasingKernels = []
for pkg in installPkgs:
if match_list(DEPEND_ON_KMOD_PATTERNS, pkg.name):
installingDriverPackage = pkg.po
break
for pkg in erasePkgs:
if match_list(DEPEND_ON_KMOD_PATTERNS, pkg.name):
erasingDriverPackage = pkg.po
break
for pkg in erasePkgs:
if pkg.po.name == KERNEL_PKG_NAME:
erasingKernels.append(pkg.po)
for pkg in installPkgs:
if pkg.po.name == KERNEL_PKG_NAME:
installingKernels.append(pkg.po)
# Since this is a postresolve hook, yum might've already added a kernel module
# package, to satisfy the dependency the nvidia-driver package has. However,
# we will handle that ourselves so remove all of them here.
for member in tsInfo.getMembers():
if MODULE_PKG_PATTERN.match(member.name):
tsInfo.deselect(member.name)
if installingDriverPackage:
installing_driver(conduit, installingDriverPackage, list(installingKernels))
if erasingDriverPackage:
erasing_driver(conduit, erasingDriverPackage)
if installedDriverPackage:
if installingKernels and not installingDriverPackage:
installing_kernels(conduit, installingKernels, installedDriverPackage[0])
if erasingKernels:
erasing_kernels(conduit, erasingKernels, installedDriverPackage[0])
def preresolve_hook(conduit):
tsInfo = conduit.getTsInfo()
moduleUpgrades = filter(lambda m: MODULE_PKG_PATTERN.match(m.name), tsInfo.getMembers())
# Not interesting for us
if not moduleUpgrades:
return
# Stop yum from automatically updating our packages, since we do it ourselves.
# This is technically not necessary, but we need to implement all the
# kmod package update handling ourselves anyway.
# This should really be the only one
po = moduleUpgrades[0]
tsInfo.deselect(po.name)
def match_list(patternList, pkg):
for p in patternList:
if p.match(pkg):
return True;
return False
def is_driver_po(po):
return DRIVER_PKG_PATTERN.match(po.name) and 'dkms' not in po.name
def get_module_pkg_name(driverPackage):
return driverPackage.name.replace(DRIVER_PKG_BASENAME, MODULE_PKG_BASENAME)
def get_module_pkg_release(kernelPackage, driverPackage):
"""In our scheme, we build up the kmod package release field from the
kernel release field as well as the driver version."""
start = kernelPackage.release[:kernelPackage.release.rfind('.')]
end = kernelPackage.release[kernelPackage.release.rfind('.'):]
return start + '.r' + driverPackage.version + end
def compare_po(po1, po2):
return compareEVR((po1.epoch, po1.version, po1.release),
(po2.epoch, po2.version, po2.release))
def get_most_recent_kernel(conduit, additional=[]):
db = conduit.getRpmDB()
kernels = list(additional)
kernels.extend(db.returnPackages(patterns=[KERNEL_PKG_NAME]))
return sorted(kernels, cmp = compare_po, reverse = True)[0]