Package backend :: Package server :: Package rhnServer :: Module server_packages
[hide private]
[frames] | no frames]

Source Code for Module backend.server.rhnServer.server_packages

  1  # 
  2  # Copyright (c) 2008--2017 Red Hat, Inc. 
  3  # 
  4  # This software is licensed to you under the GNU General Public License, 
  5  # version 2 (GPLv2). There is NO WARRANTY for this software, express or 
  6  # implied, including the implied warranties of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 
  8  # along with this software; if not, see 
  9  # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. 
 10  # 
 11  # Red Hat trademarks are not licensed under GPLv2. No permission is 
 12  # granted to use or replicate Red Hat trademarks that are incorporated 
 13  # in this software or its documentation. 
 14  # 
 15  # 
 16  # This file contains classes and functions that save and retrieve package 
 17  # profiles. 
 18  # 
 19   
 20  import string 
 21  import sys 
 22  import time 
 23  from spacewalk.common.usix import DictType 
 24   
 25  from spacewalk.common.usix import raise_with_tb 
 26  from spacewalk.common import rhn_rpm 
 27  from spacewalk.common.rhnLog import log_debug 
 28  from spacewalk.common.rhnException import rhnFault 
 29  from spacewalk.server import rhnSQL 
 30  from server_lib import snapshot_server, check_entitlement 
 31   
 32  UNCHANGED = 0 
 33  ADDED = 1 
 34  DELETED = 2 
 35  UPDATED = 3 
 36   
 37   
38 -class dbPackage:
39 40 """ A small class that helps us represent things about a 41 database package. In this structure "real" means that we have an 42 entry in the database for it. 43 """ 44
45 - def __init__(self, pdict, real=0, name_id=None, evr_id=None, 46 package_arch_id=None):
47 if type(pdict) != DictType: 48 return None 49 if ('arch' not in pdict) or (pdict['arch'] is None): 50 pdict['arch'] = "" 51 if string.lower(str(pdict['epoch'])) == "(none)" or pdict['epoch'] == "" or pdict['epoch'] is None: 52 pdict['epoch'] = None 53 else: 54 pdict['epoch'] = str(pdict['epoch']) 55 for k in ('name', 'version', 'release', 'arch'): 56 if pdict[k] is None: 57 return None 58 self.n = str(pdict['name']) 59 self.v = str(pdict['version']) 60 self.r = str(pdict['release']) 61 self.e = pdict['epoch'] 62 self.a = str(pdict['arch']) 63 if 'installtime' in pdict: 64 self.installtime = pdict['installtime'] 65 else: 66 self.installtime = None 67 # nvrea is a tuple; we can use tuple as dictionary keys since they are 68 # immutable 69 self.nvrea = (self.n, self.v, self.r, self.e, self.a) 70 self.real = real 71 self.name_id = name_id 72 self.evr_id = evr_id 73 self.package_arch_id = package_arch_id 74 if real: 75 self.status = UNCHANGED 76 else: 77 self.status = ADDED
78
79 - def setval(self, value):
80 self.status = value
81
82 - def add(self):
83 if self.status == DELETED: 84 if self.real: 85 self.status = UNCHANGED # real entries remain unchanged 86 else: 87 self.status = ADDED # others are added 88 return
89
90 - def delete(self):
91 if self.real: 92 self.status = DELETED 93 else: 94 self.status = UNCHANGED # we prefer unchanged for the non-real packages 95 return
96
97 - def __str__(self):
98 return "server.rhnServer.dbPackage instance %s" % { 99 'n': self.n, 100 'v': self.v, 101 'r': self.r, 102 'e': self.e, 103 'a': self.a, 104 'installtime': self.installtime, 105 'real': self.real, 106 'name_id': self.name_id, 107 'evr_id': self.evr_id, 108 'package_arch_id': self.package_arch_id, 109 'status': self.status, 110 }
111 __repr__ = __str__
112 113
114 -class Packages:
115
116 - def __init__(self):
117 self.__p = {} 118 # Have we loaded the packages or not? 119 self.__loaded = 0 120 self.__changed = 0
121
122 - def add_package(self, sysid, entry):
123 log_debug(4, sysid, entry) 124 p = dbPackage(entry) 125 if p is None: 126 # Not a valid package spec 127 return -1 128 if not self.__loaded: 129 self.reload_packages_byid(sysid) 130 if p.nvrea in self.__p: 131 if self.__p[p.nvrea].installtime != p.installtime: 132 self.__p[p.nvrea].installtime = p.installtime 133 self.__p[p.nvrea].status = UPDATED 134 else: 135 self.__p[p.nvrea].add() 136 self.__changed = 1 137 return 0 138 self.__p[p.nvrea] = p 139 self.__changed = 1 140 return 0
141
142 - def delete_package(self, sysid, entry):
143 """ delete a package from the list """ 144 log_debug(4, sysid, entry) 145 p = dbPackage(entry) 146 if p is None: 147 # Not a valid package spec 148 return -1 149 if not self.__loaded: 150 self.reload_packages_byid(sysid) 151 if p.nvrea in self.__p: 152 log_debug(4, " Package deleted") 153 self.__p[p.nvrea].delete() 154 self.__changed = 1 155 # deletion is always successfull 156 return 0
157
158 - def dispose_packages(self, sysid):
159 """ delete all packages and get an empty package list """ 160 log_debug(4, sysid) 161 if not self.__loaded: 162 self.reload_packages_byid(sysid) 163 for k in list(self.__p.keys()): 164 self.__p[k].delete() 165 self.__changed = 1 166 return 0
167
168 - def get_packages(self):
169 """ produce a list of packages """ 170 return [a.nvrea for a in [a for a in list(self.__p.values()) if a.status != DELETED]]
171
172 - def __expand_installtime(self, installtime):
173 """ Simulating the ternary operator, one liner is ugly """ 174 if installtime: 175 return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(installtime)) 176 else: 177 return None
178
179 - def save_packages_byid(self, sysid, schedule=1):
180 """ save the package list """ 181 log_debug(3, sysid, "Errata cache to run:", schedule, 182 "Changed:", self.__changed, "%d total packages" % len(self.__p)) 183 184 if not self.__changed: 185 return 0 186 187 commits = 0 188 189 # get rid of the deleted packages 190 dlist = [a for a in list(self.__p.values()) if a.real and a.status in (DELETED, UPDATED)] 191 if dlist: 192 log_debug(4, sysid, len(dlist), "deleted packages") 193 h = rhnSQL.prepare(""" 194 delete from rhnServerPackage 195 where server_id = :sysid 196 and name_id = :name_id 197 and evr_id = :evr_id 198 and ((:package_arch_id is null and package_arch_id is null) 199 or package_arch_id = :package_arch_id) 200 """) 201 h.execute_bulk({ 202 'sysid': [sysid] * len(dlist), 203 'name_id': [a.name_id for a in dlist], 204 'evr_id': [a.evr_id for a in dlist], 205 'package_arch_id': [a.package_arch_id for a in dlist], 206 }) 207 commits = commits + len(dlist) 208 del dlist 209 210 # And now add packages 211 alist = [a for a in list(self.__p.values()) if a.status in (ADDED, UPDATED)] 212 if alist: 213 log_debug(4, sysid, len(alist), "added packages") 214 h = rhnSQL.prepare(""" 215 insert into rhnServerPackage 216 (server_id, name_id, evr_id, package_arch_id, installtime) 217 values (:sysid, LOOKUP_PACKAGE_NAME(:n), LOOKUP_EVR(:e, :v, :r), 218 LOOKUP_PACKAGE_ARCH(:a), TO_TIMESTAMP(:instime, 'YYYY-MM-DD HH24:MI:SS') 219 ) 220 """) 221 # some fields are not allowed to contain empty string (varchar) 222 223 def lambdaae(a): 224 if a.e == '': 225 return None 226 else: 227 return a.e
228 package_data = { 229 'sysid': [sysid] * len(alist), 230 'n': [a.n for a in alist], 231 'v': [a.v for a in alist], 232 'r': [a.r for a in alist], 233 'e': list(map(lambdaae, alist)), 234 'a': [a.a for a in alist], 235 'instime': [self.__expand_installtime(a.installtime) for a in alist], 236 } 237 try: 238 h.execute_bulk(package_data) 239 rhnSQL.commit() 240 except rhnSQL.SQLSchemaError: 241 e = sys.exc_info()[1] 242 # LOOKUP_PACKAGE_ARCH failed 243 if e.errno == 20243: 244 log_debug(2, "Unknown package arch found", e) 245 raise_with_tb(rhnFault(45, "Unknown package arch found"), sys.exc_info()[2]) 246 247 commits = commits + len(alist) 248 del alist 249 250 if schedule: 251 # queue this server for an errata update 252 update_errata_cache(sysid) 253 254 # if provisioning box, and there was an actual delta, snapshot 255 ents = check_entitlement(sysid) 256 if commits and "enterprise_entitled" in ents: 257 snapshot_server(sysid, "Package profile changed") 258 259 # Our new state does not reflect what's on the database anymore 260 self.__loaded = 0 261 self.__changed = 0 262 return 0
263 264 _query_get_package_arches = rhnSQL.Statement(""" 265 select id, label 266 from rhnPackageArch 267 """) 268
269 - def get_package_arches(self):
270 # None gets automatically converted to empty string 271 package_arches_hash = {None: ''} 272 h = rhnSQL.prepare(self._query_get_package_arches) 273 h.execute() 274 while 1: 275 row = h.fetchone_dict() 276 if not row: 277 break 278 package_arches_hash[row['id']] = row['label'] 279 return package_arches_hash
280
281 - def reload_packages_byid(self, sysid):
282 """ reload the packages list from the database """ 283 log_debug(3, sysid) 284 # First, get the package arches 285 package_arches_hash = self.get_package_arches() 286 # XXX we could achieve the same thing with an outer join but that's 287 # more expensive 288 # Now load packages 289 h = rhnSQL.prepare(""" 290 select 291 rpn.name, 292 rpe.version, 293 rpe.release, 294 rpe.epoch, 295 sp.name_id, 296 sp.evr_id, 297 sp.package_arch_id, 298 TO_CHAR(sp.installtime, 'YYYY-MM-DD HH24:MI:SS') installtime 299 from 300 rhnServerPackage sp, 301 rhnPackageName rpn, 302 rhnPackageEVR rpe 303 where sp.server_id = :sysid 304 and sp.name_id = rpn.id 305 and sp.evr_id = rpe.id 306 """) 307 h.execute(sysid=sysid) 308 self.__p = {} 309 while 1: 310 t = h.fetchone_dict() 311 if not t: 312 break 313 t['arch'] = package_arches_hash[t['package_arch_id']] 314 if 'installtime' in t and t['installtime'] is not None: 315 t['installtime'] = time.mktime(time.strptime(t['installtime'], 316 "%Y-%m-%d %H:%M:%S")) 317 p = dbPackage(t, real=1, name_id=t['name_id'], evr_id=t['evr_id'], 318 package_arch_id=t['package_arch_id']) 319 self.__p[p.nvrea] = p 320 log_debug(4, "Loaded %d packages for server %s" % (len(self.__p), sysid)) 321 self.__loaded = 1 322 self.__changed = 0 323 return 0
324 325
326 -def update_errata_cache(server_id):
327 """ Queue an update the the server's errata cache. This queues for 328 Taskomatic instead of doing it in-line because updating many servers 329 at once was problematic and lead to unresponsive Satellite and 330 incorrectly reporting failed actions when they did not fail (see 331 bz 1119460). 332 """ 333 log_debug(2, "Queueing the errata cache update", server_id) 334 update_needed_cache = rhnSQL.Procedure("queue_server") 335 update_needed_cache(server_id, 0)
336 337
338 -def processPackageKeyAssociations(header, checksum_type, checksum):
339 provider_sql = rhnSQL.prepare(""" 340 insert into rhnPackageKeyAssociation 341 (package_id, key_id) values 342 (:package_id, :key_id) 343 """) 344 345 insert_keyid_sql = rhnSQL.prepare(""" 346 insert into rhnPackagekey 347 (id, key_id, key_type_id) values 348 (sequence_nextval('rhn_pkey_id_seq'), :key_id, :key_type_id) 349 """) 350 351 lookup_keyid_sql = rhnSQL.prepare(""" 352 select pk.id 353 from rhnPackagekey pk 354 where pk.key_id = :key_id 355 """) 356 357 lookup_keytype_id = rhnSQL.prepare(""" 358 select id 359 from rhnPackageKeyType 360 where LABEL in ('gpg', 'pgp') 361 """) 362 363 lookup_pkgid_sql = rhnSQL.prepare(""" 364 select p.id 365 from rhnPackage p, 366 rhnChecksumView c 367 where c.checksum = :csum 368 and c.checksum_type = :ctype 369 and p.checksum_id = c.id 370 """) 371 372 lookup_pkgkey_sql = rhnSQL.prepare(""" 373 select 1 374 from rhnPackageKeyAssociation 375 where package_id = :package_id 376 and key_id = :key_id 377 """) 378 379 lookup_pkgid_sql.execute(ctype=checksum_type, csum=checksum) 380 pkg_ids = lookup_pkgid_sql.fetchall_dict() 381 382 if not pkg_ids: 383 # No package to associate, continue with next 384 return 385 386 sigkeys = rhn_rpm.RPM_Header(header).signatures 387 key_id = None # _key_ids(sigkeys)[0] 388 for sig in sigkeys: 389 if sig['signature_type'] in ['gpg', 'pgp']: 390 key_id = sig['key_id'] 391 392 if not key_id: 393 # package is not signed, skip gpg key insertion 394 return 395 396 lookup_keyid_sql.execute(key_id=key_id) 397 keyid = lookup_keyid_sql.fetchall_dict() 398 399 if not keyid: 400 lookup_keytype_id.execute() 401 key_type_id = lookup_keytype_id.fetchone_dict() 402 insert_keyid_sql.execute(key_id=key_id, key_type_id=key_type_id['id']) 403 lookup_keyid_sql.execute(key_id=key_id) 404 keyid = lookup_keyid_sql.fetchall_dict() 405 406 for pkg_id in pkg_ids: 407 lookup_pkgkey_sql.execute(key_id=keyid[0]['id'], 408 package_id=pkg_id['id']) 409 exists_check = lookup_pkgkey_sql.fetchall_dict() 410 411 if not exists_check: 412 provider_sql.execute(key_id=keyid[0]['id'], package_id=pkg_id['id'])
413 414
415 -def package_delta(list1, list2):
416 """ Compares list1 and list2 (each list is a tuple (n, v, r, e) 417 returns two lists 418 (install, remove) 419 XXX upgrades and downgrades are simulated by a removal and an install 420 """ 421 # Package registry - canonical versions for all packages 422 package_registry = {} 423 hash1 = _package_list_to_hash(list1, package_registry) 424 hash2 = _package_list_to_hash(list2, package_registry) 425 del package_registry 426 427 installs = [] 428 removes = [] 429 for pn, ph1 in list(hash1.items()): 430 if pn not in hash2: 431 removes.extend(list(ph1.keys())) 432 continue 433 434 ph2 = hash2[pn] 435 del hash2[pn] 436 437 # Now, compute the differences between ph1 and ph2 438 for p in list(ph1.keys()): 439 if p not in ph2: 440 # We have to remove it 441 removes.append(p) 442 else: 443 del ph2[p] 444 # Everything else left in ph2 has to be installed 445 installs.extend(list(ph2.keys())) 446 447 # Whatever else is left in hash2 should be installed 448 for ph2 in list(hash2.values()): 449 installs.extend(list(ph2.keys())) 450 451 installs.sort() 452 removes.sort() 453 return installs, removes
454 455
456 -def _package_list_to_hash(package_list, package_registry):
457 """ Converts package_list into a hash keyed by name 458 package_registry contains the canonical version of the package 459 for instance, version 51 and 0051 are indentical, but that would break the 460 list comparison in Python. package_registry is storing representatives for 461 each equivalence class (where the equivalence relationship is rpm's version 462 comparison algorigthm 463 Side effect: Modifies second argument! 464 """ 465 hash = {} 466 for e in package_list: 467 e = tuple(e) 468 pn = e[0] 469 if pn not in package_registry: 470 # Definitely new equivalence class 471 _add_to_hash(package_registry, pn, e) 472 _add_to_hash(hash, pn, e) 473 continue 474 475 # Look for a match for this package name in the registry 476 plist = list(package_registry[pn].keys()) 477 for p in plist: 478 if rhn_rpm.nvre_compare(p, e) == 0: 479 # Packages are identical 480 e = p 481 break 482 else: 483 # Package not found in the global registry - add it 484 _add_to_hash(package_registry, pn, e) 485 486 # Add it to the hash too 487 _add_to_hash(hash, pn, e) 488 489 return hash
490 491
492 -def _add_to_hash(hash, key, value):
493 if key not in hash: 494 hash[key] = {value: None} 495 else: 496 hash[key][value] = None
497