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

Source Code for Module backend.server.rhnDependency

  1  # 
  2  # Copyright (c) 2008--2016 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   
 17  from spacewalk.common.rhnLog import log_debug, log_error 
 18  from spacewalk.common.rhnException import rhnFault 
 19  import rhnSQL 
 20  import rhnLib 
 21  import rpm 
 22   
 23  # QUERY PACKAGES 
 24  # sql query for solving a dep as a package 
 25  __packages_with_arch_and_id_sql = """ 
 26  select distinct 
 27      p.id id, 
 28      pn.name, 
 29      (pe.evr).version as version, 
 30      (pe.evr).release as release, 
 31      (pe.evr).epoch as epoch, 
 32      pa.label as arch, 
 33      1 as preference 
 34  from 
 35      rhnPackageEvr pe, 
 36      rhnChannelPackage cp, 
 37      rhnPackage p, 
 38      rhnServerChannel sc, 
 39      rhnPackageName pn, 
 40      rhnPackageArch pa 
 41  where 1=1 
 42  and pn.name = :dep 
 43  and sc.server_id = :server_id 
 44  and p.name_id = pn.id 
 45  and cp.channel_id = sc.channel_id 
 46  and p.id = cp.package_id 
 47  and p.evr_id = pe.id 
 48  and p.package_arch_id = pa.id 
 49  and pe.evr = ( 
 50      select MAX(pe1.evr) 
 51      from 
 52          rhnPackageEVR pe1, 
 53          rhnChannelPackage cp1, 
 54          rhnPackage p1, 
 55          rhnServerChannel sc1 
 56      where 
 57          sc1.server_id = :server_id 
 58      and p1.name_id = pn.id 
 59      and sc1.channel_id = cp1.channel_id 
 60      and cp1.package_id = p1.id 
 61      and p1.evr_id = pe1.id 
 62      ) 
 63  """ 
 64  __packages_sql = """ 
 65  select distinct 
 66      pn.name, 
 67      (pe.evr).version as version, 
 68      (pe.evr).release as release, 
 69      (pe.evr).epoch as epoch, 
 70      pa.label as arch, 
 71      1 as preference 
 72  from 
 73      rhnPackageEvr pe, 
 74      rhnChannelPackage cp, 
 75      rhnPackage p, 
 76      rhnServerChannel sc, 
 77      rhnPackageName pn, 
 78      rhnPackageArch pa 
 79  where 1=1 
 80  and pn.name = :dep 
 81  and sc.server_id = :server_id 
 82  and p.name_id = pn.id 
 83  and cp.channel_id = sc.channel_id 
 84  and p.id = cp.package_id 
 85  and p.evr_id = pe.id 
 86  and p.package_arch_id = pa.id 
 87  and pe.evr = ( 
 88      select MAX(pe1.evr) 
 89      from 
 90          rhnPackageEVR pe1, 
 91          rhnChannelPackage cp1, 
 92          rhnPackage p1, 
 93          rhnServerChannel sc1 
 94      where 
 95          sc1.server_id = :server_id 
 96      and p1.name_id = pn.id 
 97      and sc1.channel_id = cp1.channel_id 
 98      and cp1.package_id = p1.id 
 99      and p1.evr_id = pe1.id 
100      ) 
101  """ 
102   
103  __packages_all_sql = """ 
104  select distinct 
105      pn.name, 
106      (pe.evr).version as version, 
107      (pe.evr).release as release, 
108      (pe.evr).epoch as epoch, 
109      pa.label as arch, 
110      1 as preference 
111  from 
112      rhnPackageEvr pe, 
113      rhnChannelPackage cp, 
114      rhnPackage p, 
115      rhnServerChannel sc, 
116      rhnPackageName pn, 
117      rhnPackageArch pa 
118  where 1=1 
119  and pn.name = :dep 
120  and sc.server_id = :server_id 
121  and p.name_id = pn.id 
122  and cp.channel_id = sc.channel_id 
123  and p.id = cp.package_id 
124  and p.evr_id = pe.id 
125  and p.package_arch_id = pa.id 
126  """ 
127  # QUERY PROVIDES 
128  # sql query for solving a dep as a provide 
129  __provides_sql = """ 
130  select  distinct 
131      pn.name, 
132      (pe.evr).version as version, 
133      (pe.evr).release as release, 
134      (pe.evr).epoch as epoch, 
135      pa.label as arch, 
136      2 as preference 
137  from 
138      rhnServerChannel sc, 
139      rhnChannelPackage cp, 
140      rhnPackageProvides pr, 
141      rhnPackage p, 
142      rhnPackageCapability cap, 
143      rhnPackageName pn, 
144      rhnPackageEVR pe, 
145      rhnPackageArch pa 
146  where 
147      sc.server_id = :server_id 
148  and sc.channel_id = cp.channel_id 
149  and cp.package_id = p.id 
150  and cp.package_id = pr.package_id 
151  and pr.package_id = p.id 
152  and pr.capability_id = cap.id 
153  and cap.name = :dep 
154  and p.name_id = pn.id 
155  and p.evr_id = pe.id 
156  and p.package_arch_id = pa.id 
157  -- and this package is the latest one from all the channels 
158  -- this server is subscribed to. 
159  and pe.evr = ( 
160      select MAX(pe1.evr) 
161      from 
162          rhnPackage p1, 
163          rhnPackageEVR pe1, 
164          rhnServerChannel sc1, 
165          rhnChannelPackage cp1 
166      where 
167          sc1.server_id = :server_id 
168      and sc1.channel_id = cp1.channel_id 
169      and cp1.package_id = p1.id 
170      and p1.name_id = pn.id 
171      and p1.evr_id = pe1.id 
172      ) 
173  """ 
174   
175  __provides_all_sql = """ 
176  select  distinct 
177      pn.name, 
178      (pe.evr).version as version, 
179      (pe.evr).release as release, 
180      (pe.evr).epoch as epoch, 
181      pa.label as arch, 
182      2 as preference 
183  from 
184      rhnServerChannel sc, 
185      rhnChannelPackage cp, 
186      rhnPackageProvides pr, 
187      rhnPackage p, 
188      rhnPackageCapability cap, 
189      rhnPackageName pn, 
190      rhnPackageEVR pe, 
191      rhnPackageArch pa 
192  where 
193      sc.server_id = :server_id 
194  and sc.channel_id = cp.channel_id 
195  and cp.package_id = p.id 
196  and cp.package_id = pr.package_id 
197  and pr.package_id = p.id 
198  and pr.capability_id = cap.id 
199  and cap.name = :dep 
200  and p.name_id = pn.id 
201  and p.evr_id = pe.id 
202  and p.package_arch_id = pa.id 
203  """ 
204  # QUERY FILES 
205  # sql query for solving a dependency as a file provide 
206  __files_sql = """ 
207  select distinct 
208      pn.name, 
209      (pe.evr).version as version, 
210      (pe.evr).release as release, 
211      (pe.evr).epoch as epoch, 
212      pa.label as arch, 
213      3 as preference 
214  from 
215      rhnServerChannel sc, 
216      rhnChannelPackage cp, 
217      rhnPackageFile f, 
218      rhnPackage p, 
219      rhnPackageCapability cap, 
220      rhnPackageName pn, 
221      rhnPackageEVR pe, 
222      rhnPackageArch pa 
223  where 
224      sc.server_id = :server_id 
225  and sc.channel_id = cp.channel_id 
226  and cp.package_id = p.id 
227  and cp.package_id = f.package_id 
228  and f.capability_id = cap.id 
229  and cap.name = :dep 
230  and p.name_id = pn.id 
231  and p.evr_id = pe.id 
232  and p.package_arch_id = pa.id 
233  -- and this package is the latest one from all the channels 
234  -- this server is subscribed to. 
235  and pe.evr = ( 
236      select MAX(pe1.evr) 
237      from 
238          rhnPackage p1, 
239          rhnPackageEVR pe1, 
240          rhnServerChannel sc1, 
241          rhnChannelPackage cp1 
242      where 
243          sc1.server_id = :server_id 
244      and sc1.channel_id = cp1.channel_id 
245      and cp1.package_id = p1.id 
246      and p1.name_id = pn.id 
247      and p1.evr_id = pe1.id 
248      ) 
249  """ 
250   
251   
252  __files_all_sql = """ 
253  select distinct 
254      pn.name, 
255      (pe.evr).version as version, 
256      (pe.evr).release as release, 
257      (pe.evr).epoch as epoch, 
258      pa.label as arch, 
259      3 as preference 
260  from 
261      rhnServerChannel sc, 
262      rhnChannelPackage cp, 
263      rhnPackageFile f, 
264      rhnPackage p, 
265      rhnPackageCapability cap, 
266      rhnPackageName pn, 
267      rhnPackageEVR pe, 
268      rhnPackageArch pa 
269  where 
270      sc.server_id = :server_id 
271  and sc.channel_id = cp.channel_id 
272  and cp.package_id = p.id 
273  and cp.package_id = f.package_id 
274  and f.capability_id = cap.id 
275  and cap.name = :dep 
276  and p.name_id = pn.id 
277  and p.evr_id = pe.id 
278  and p.package_arch_id = pa.id 
279  """ 
280   
281   
282 -class SolveDependenciesError(Exception):
283
284 - def __init__(self, deps=None, packages=None, *args, **kwargs):
285 Exception.__init__(self, *args, **kwargs) 286 self.deps = deps 287 self.packages = packages
288 289
290 -def __single_query_with_arch_and_id(server_id, deps, query):
291 """ Run one of the queries and return the results along with the arch. """ 292 ret = {} 293 h = rhnSQL.prepare(query) 294 for dep in deps: 295 h.execute(server_id=server_id, dep=dep) 296 data = h.fetchall() or [] 297 ret[dep] = [a[:6] for a in data] 298 return ret
299 300 # 301 # Interfaces 302 # 303 304 # simple one type queries 305 306
307 -def find_package_with_arch(server_id, deps):
308 log_debug(4, server_id, deps) 309 return __single_query_with_arch_and_id(server_id, deps, __packages_with_arch_and_id_sql)
310 311
312 -def solve_dependencies_with_limits(server_id, deps, version, all=0, limit_operator=None, limit=None):
313 """ This version of solve_dependencies allows the caller to get all of the packages that solve a dependency and limit 314 the packages that are returned to those that match the criteria defined by limit_operator and limit. This version 315 of the function also returns the architecture label of the package[s] that get returned. 316 317 limit_operator can be any of: '<', '<=', '==', '>=', or '>'. 318 limit is a a string of the format [epoch:]name-version-release 319 deps is a list of filenames that the packages that are returned must provide. 320 version is the version of the client that is calling the function. 321 322 Indexes for the tuple 323 entry_index = 0 324 preference_index = 1 325 326 Indexes for the list of package fields. 327 name_index = 0 328 version_index = 1 329 release_index = 2 330 epoch_index = 3 331 """ 332 # Containers used while the packages get categorized, sorted, and filtered. 333 packages_all = {} 334 package_list = [] 335 336 # List of fields in a package. Corresponds to the keys for the dictionary that holds the package information. 337 nvre = ['name', 'version', 'release', 'epoch', 'arch'] 338 339 # Make sure there are no duplicate dependencies. 340 deplist = set(deps) 341 342 statement = "%s UNION ALL %s UNION ALL %s" % (__packages_all_sql, __provides_all_sql, __files_all_sql) 343 h = rhnSQL.prepare(statement) 344 345 # prepare return value 346 packages = {} 347 348 for dep in deplist: 349 dict = {} 350 351 # Retrieve the package information from the database. 352 h.execute(server_id=server_id, dep=dep) 353 354 # Get a list of dictionaries containing row data. 355 rs = h.fetchall_dict() or [] # rs = [{},{},... ] 356 357 # Each package gets a list that may contain multiple versions of a package 358 for record in rs: 359 if record['name'] in packages_all: 360 packages_all[record['name']].append(record) 361 else: 362 packages_all[record['name']] = [record] 363 364 # sort all the package lists so the most recent version is first 365 for pl in packages_all.keys(): 366 367 packages_all[pl].sort(cmp_evr) 368 package_list = package_list + packages_all[pl] 369 370 package_list.reverse() 371 # Use the limit* parameters to filter out packages you don't want. 372 if limit_operator is not None and limit is not None: 373 keep_list = [] 374 375 try: 376 limit = rhnLib.make_evr(limit) 377 except: 378 raise 379 380 for package in package_list: 381 try: 382 keep = test_evr(package, limit_operator, limit) 383 except: 384 raise 385 386 if keep: 387 keep_list.append(package) 388 389 package_list = keep_list 390 391 list_of_tuples = [] 392 for p in package_list: 393 if p['epoch'] is None: 394 p['epoch'] = "" 395 396 entry = [] 397 398 list(map(lambda f, e=entry, p=p: e.append(p[f]), nvre)) 399 400 # Added for readability 401 name_key = entry[0] 402 403 if all == 0: 404 # NOTE: Remember that the values in dict are tuples that look like (entry, preference). 405 # NOTE, Part Deux: the '<=' was a '<' originally. I changed it because if two packages 406 # with the same preference but different versions came through, the second package was being used. 407 # The changes I made above make it so that at this point the packages are sorted from highest nvre 408 # to lowest nvre. Selecting the second package was causing the earlier package to be 409 # returned, which is bad. 410 if name_key in dict and dict[name_key][1] <= p['preference']: 411 # Already have it with a lower preference 412 continue 413 # The first time we see this package. 414 dict[name_key] = (entry, p['preference']) 415 else: 416 name_key = entry[0] 417 newtuple = (entry, p['preference']) 418 list_of_tuples.append(newtuple) 419 420 if all == 0: 421 packages[dep] = _avoid_compat_packages(dict) 422 else: 423 # filter out compats 424 if len(list_of_tuples) > 1: 425 filterstring = "compat-" 426 len_filter = len(filterstring) 427 tup_keep = [] 428 for tup in list_of_tuples: 429 if tup[0][0][:len_filter] != filterstring: 430 tup_keep.append(tup) 431 list_of_tuples = tup_keep 432 433 list_of_tuples.sort(lambda a, b: cmp(a[1], b[1])) 434 packages[dep] = [x[0] for x in list_of_tuples] 435 436 # v2 clients are done 437 if version > 1: 438 return packages 439 else: 440 return _v2packages_to_v1list(packages, deplist, all)
441 442
443 -def _v2packages_to_v1list(packages, deplist, all=0):
444 # v1 clients expect a list as a result 445 result = [] 446 # Return the results in order (not that anyone would care) 447 for dep in deplist: 448 if not packages[dep]: 449 # Unresolved dependency; skip it 450 continue 451 # consider only the first one for each dep 452 r = packages[dep][0] 453 # Avoid sending the same result back multiple times 454 if all == 0: 455 if r not in result: 456 result.append(r) 457 else: 458 result.append(r) 459 return result
460 461
462 -def solve_dependencies_arch(server_id, deps, version):
463 """ Does the same thing as solve_dependencies, but also returns the architecture label with the package info. 464 E.g. 465 OUT: 466 Dictionary with key values being the filnames in deps and the values being a list of lists of package info. 467 Example := {'filename1' : [['name', 'version', 'release', 'epoch', 'architecture'], 468 ['name2', 'version2', 'release2', 'epoch2', 'architecture2']]} 469 """ 470 # list of the keys to the values in each row of the recordset. 471 nvre = ['name', 'version', 'release', 'epoch', 'arch'] 472 return solve_dependencies(server_id, deps, version, nvre)
473 474
475 -def solve_dependencies(server_id, deps, version, nvre=None):
476 """ The unchanged version of solve_dependencies. 477 IN: 478 server_id := id info of the server 479 deps := list of filenames that are needed by the caller 480 version := version of the client 481 482 OUT: 483 Dictionary with key values being the filnames in deps and the values being a list of lists of package info. 484 Example := {'filename1' : [['name', 'version', 'release', 'epoch'], 485 ['name2', 'version2', 'release2', 'epoch2']]} 486 """ 487 if not nvre: 488 # list of the keys to the values in each row of the recordset. 489 nvre = ['name', 'version', 'release', 'epoch'] 490 491 # first, uniquify deps 492 deplist = set(deps) 493 494 # SQL statement. It is a union of 3 statements: 495 # - Lookup by package name 496 # - Lookup by provides 497 # - Lookup by file name 498 499 statement = "%s UNION ALL %s UNION ALL %s" % ( 500 __packages_sql, __provides_sql, __files_sql) 501 h = rhnSQL.prepare(statement) 502 503 # prepare return value 504 packages = {} 505 # Iterate through the dependency problems 506 for dep in deplist: 507 dict = {} 508 h.execute(server_id=server_id, dep=dep) 509 rs = h.fetchall_dict() or [] 510 if not rs: # test shortcut 511 log_error("Unable to solve dependency", server_id, dep) 512 packages[dep] = [] 513 continue 514 515 for p in rs: 516 if p['epoch'] is None: 517 p['epoch'] = "" 518 entry = [] 519 list(map(lambda f, e=entry, p=p: e.append(p[f]), nvre)) 520 521 name_key = entry[0] 522 if name_key in dict and dict[name_key][1] < p['preference']: 523 # Already have it with a lower preference 524 continue 525 # The first time we see this package. 526 dict[name_key] = (entry, p['preference']) 527 528 packages[dep] = _avoid_compat_packages(dict) 529 530 # v2 clients are done 531 if version > 1: 532 return packages 533 else: 534 return _v2packages_to_v1list(packages, deplist)
535 536
537 -def _avoid_compat_packages(dict):
538 """ attempt to avoid giving out the compat-* packages 539 if there are other candidates 540 """ 541 if len(dict) > 1: 542 matches = list(dict.keys()) 543 # check we have at least one non- "compat-*" package name 544 compats = [a for a in matches if a[:7] == "compat-"] 545 if len(compats) > 0 and len(compats) < len(matches): # compats and other things 546 for p in compats: # delete all references to a compat package for this dependency 547 del dict[p] 548 # otherwise there's nothing much we can do (no compats or only compats) 549 # and now return these final results ordered by preferece 550 l = list(dict.values()) 551 l.sort(lambda a, b: cmp(a[1], b[1])) 552 return [x[0] for x in l]
553 554
555 -def cmp_evr(pkg1, pkg2):
556 """ Intended to be passed to a list object's sort(). 557 In: {'epoch': 'value', 'version':'value', 'release':'value'} 558 """ 559 pkg1_epoch = pkg1['epoch'] 560 pkg1_version = pkg1['version'] 561 pkg1_release = pkg1['release'] 562 563 pkg2_epoch = pkg2['epoch'] 564 pkg2_version = pkg2['version'] 565 pkg2_release = pkg2['release'] 566 567 if pkg1_epoch is not None: 568 pkg1_epoch = str(pkg1_epoch) 569 elif pkg1_epoch == '': 570 pkg1_epoch = None 571 572 if pkg2_epoch is not None: 573 pkg2_epoch = str(pkg2_epoch) 574 elif pkg1_epoch == '': 575 pkg1_epoch = None 576 577 return rpm.labelCompare((pkg1_epoch, pkg1_version, pkg1_release), 578 (pkg2_epoch, pkg2_version, pkg2_release))
579 580
581 -def test_evr(evr, operator, limit):
582 """ Check to see if evr is within the limit. 583 IN: evr = { 'epoch' : value, 'version':value, 'release':value } 584 operator can be any of: '<', '<=', '==', '>=', '>' 585 limit = { 'epoch' : value, 'version':value, 'release':value } 586 OUT: 587 1 or 0 588 """ 589 good_operators = ['<', '<=', '==', '>=', '>'] 590 591 if not operator in good_operators: 592 raise rhnFault(err_code=21, 593 err_text="Bad operator passed into test_evr.") 594 595 evr_epoch = evr['epoch'] 596 evr_version = evr['version'] 597 evr_release = evr['release'] 598 599 limit_epoch = limit['epoch'] 600 limit_version = limit['version'] 601 limit_release = limit['release'] 602 603 if evr_epoch is not None: 604 evr_epoch = str(evr_epoch) 605 elif evr_epoch == '': 606 evr_epoch = None 607 608 if limit_epoch is not None: 609 limit_epoch = str(limit_epoch) 610 elif limit_epoch == '': 611 limit_epoch = None 612 613 ret = rpm.labelCompare((evr_epoch, evr_version, evr_release), 614 (limit_epoch, limit_version, limit_release)) 615 616 return check_against_operator(ret, operator)
617 618
619 -def check_against_operator(ret, operator):
620 if ret == -1: 621 if operator in (">", ">=", "=="): 622 return 0 623 if operator in ("<", "<="): 624 return 1 625 if ret == 0: 626 if operator in (">", "<"): 627 return 0 628 if operator in (">=", "<=", "=="): 629 return 1 630 if ret == 1: 631 if operator in ("<", "<=", "=="): 632 return 0 633 if operator in (">", ">="): 634 return 1 635 return 0
636 637 # DEVEL NOTES 638 # This faster query for solvind dependencies that refer to package 639 # names causes Oracle 9i (all versions) to segfault badly. We 640 # therefore use a slower query that has the drawback of effectively 641 # double selecting the same data and then filtering it to obtain the 642 # correct response. 643 # __packages_sql = """ 644 # select 645 # q.name, 646 # q.evr.version version, 647 # q.evr.release release, 648 # q.evr.epoch epoch, 649 # 1 preference 650 # from 651 # ( select 652 # pn.name name, 653 # max(pe.evr) evr 654 # from 655 # rhnServerChannel sc, 656 # rhnChannelPackage cp, 657 # rhnPackage p, 658 # rhnPackageName pn, 659 # rhnPackageEVR pe 660 # where 661 # sc.server_id = :server_id 662 # and sc.channel_id = cp.channel_id 663 # and cp.package_id = p.id 664 # and p.name_id = pn.id 665 # and pn.name = :dep 666 # and p.evr_id = pe.id 667 # group by pn.name 668 # ) q 669 # """ 670