Package entropy :: Module qa

Source Code for Module entropy.qa

   1  # -*- coding: utf-8 -*- 
   2  """ 
   3   
   4      @author: Fabio Erculiani <lxnay@sabayonlinux.org> 
   5      @contact: lxnay@sabayonlinux.org 
   6      @copyright: Fabio Erculiani 
   7      @license: GPL-2 
   8   
   9      B{Entropy Framework QA module}. 
  10   
  11      This module contains various Quality Assurance routines used by Entropy. 
  12   
  13      B{QAInterface} is the main class for QA routines used by Entropy Server 
  14      and Entropy Client such as binary packages health check, dependency 
  15      test, broken or missing library tes. 
  16   
  17      B{ErrorReportInterface} is the HTTP POST based class for Entropy Client 
  18      exceptions (errors) submission. 
  19   
  20  """ 
  21  from __future__ import with_statement 
  22  import os 
  23  import sys 
  24  import subprocess 
  25  import tempfile 
  26   
  27  from entropy.const import etpConst, etpSys 
  28  from entropy.output import blue, darkgreen, red, darkred, bold, purple, brown 
  29  from entropy.exceptions import IncorrectParameter, PermissionDenied, \ 
  30      SystemDatabaseError 
  31  from entropy.i18n import _ 
  32  from entropy.core.settings.base import SystemSettings 
  33   
34 -class QAInterface:
35 36 """ 37 Entropy QA interface. This class contains all the Entropy 38 QA routines used by Entropy Server and Entropy Client. 39 40 An instance of QAInterface can be easily retrieved from 41 entropy.client.interfaces.Client or entropy.server.interfaces.Server 42 through an exposed QA() method. 43 This is anyway a stand-alone class. 44 45 @todo: remove non-QA methods 46 47 """ 48 49 import entropy.tools as entropyTools 50 from entropy.misc import Lifo
51 - def __init__(self, OutputInterface):
52 """ 53 QAInterface constructor. 54 55 @param OutputInterface: class instance used to print output. 56 Even if not enforced at the moment, it should be a subclass of 57 entropy.qa.TextInterface exposing the updateProgress() method 58 with proper signature. 59 @type OutputInterface: TextInterface class or subclass instance 60 """ 61 self.Output = OutputInterface 62 self.SystemSettings = SystemSettings() 63 64 if not hasattr(self.Output, 'updateProgress'): 65 mytxt = _("Output interface has no updateProgress method") 66 raise IncorrectParameter("IncorrectParameter: %s" % (mytxt,)) 67 elif not callable(self.Output.updateProgress): 68 mytxt = _("Output interface has no updateProgress method") 69 raise IncorrectParameter("IncorrectParameter: %s" % (mytxt,))
70
71 - def test_depends_linking(self, idpackages, dbconn, repo = None):
72 """ 73 Scan for broken shared objects linking for the given idpackages on 74 the given entropy.db.EntropyRepository based instance. 75 Note: this only works for packages actually installed on the running 76 system. 77 It is used by Entropy Server during packages injection into database 78 to warn about potentially broken packages. 79 80 @param idpackages: list of valid idpackages (int) on the given dbconn 81 argument passed 82 @type idpackages: list 83 @param dbconn: entropy.db.EntropyRepository instance containing the 84 given idpackages list 85 @type dbconn: entropy.db.EntropyRepository 86 @keyword repo: repository identifer from which dbconn and idpackages 87 arguments belong. Note: at the moment it's only used for output 88 purposes. 89 @type repo: string 90 @return: True if any breakage is found, otherwise False 91 @rtype: bool 92 """ 93 if repo is None: 94 repo = self.SystemSettings['repositories']['default_repository'] 95 96 scan_msg = blue(_("Now searching for broken depends")) 97 self.Output.updateProgress( 98 "[repo:%s] %s..." % ( 99 darkgreen(repo), 100 scan_msg, 101 ), 102 importance = 1, 103 type = "info", 104 header = red(" @@ ") 105 ) 106 107 broken = False 108 109 count = 0 110 maxcount = len(idpackages) 111 for idpackage in idpackages: 112 count += 1 113 atom = dbconn.retrieveAtom(idpackage) 114 scan_msg = "%s, %s:" % ( 115 blue(_("scanning for broken depends")), 116 darkgreen(atom), 117 ) 118 self.Output.updateProgress( 119 "[repo:%s] %s" % ( 120 darkgreen(repo), 121 scan_msg, 122 ), 123 importance = 1, 124 type = "info", 125 header = blue(" @@ "), 126 back = True, 127 count = (count, maxcount,) 128 ) 129 mydepends = dbconn.retrieveReverseDependencies(idpackage) 130 if not mydepends: 131 continue 132 for mydepend in mydepends: 133 myatom = dbconn.retrieveAtom(mydepend) 134 self.Output.updateProgress( 135 "[repo:%s] %s => %s" % ( 136 darkgreen(repo), 137 darkgreen(atom), 138 darkred(myatom), 139 ), 140 importance = 0, 141 type = "info", 142 header = blue(" @@ "), 143 back = True, 144 count = (count, maxcount,) 145 ) 146 mycontent = dbconn.retrieveContent(mydepend) 147 mybreakages = self._content_test(mycontent) 148 if not mybreakages: 149 continue 150 broken = True 151 self.Output.updateProgress( 152 "[repo:%s] %s %s => %s" % ( 153 darkgreen(repo), 154 darkgreen(atom), 155 darkred(myatom), 156 bold(_("broken libraries detected")), 157 ), 158 importance = 1, 159 type = "warning", 160 header = purple(" @@ "), 161 count = (count, maxcount,) 162 ) 163 for mylib in mybreakages: 164 self.Output.updateProgress( 165 "%s %s:" % ( 166 darkgreen(mylib), 167 red(_("needs")), 168 ), 169 importance = 1, 170 type = "warning", 171 header = brown(" ## ") 172 ) 173 for needed in mybreakages[mylib]: 174 self.Output.updateProgress( 175 "%s" % ( 176 red(needed), 177 ), 178 importance = 1, 179 type = "warning", 180 header = purple(" # ") 181 ) 182 return broken
183
184 - def test_missing_dependencies(self, idpackages, dbconn, ask = True, 185 self_check = False, repo = None, black_list = None, 186 black_list_adder = None):
187 """ 188 Scan missing dependencies for the given idpackages on the given 189 entropy.db.EntropyRepository "dbconn" instance. In addition, this method 190 will allow the user through OutputInterface to interactively add (if ask 191 == True) missing dependencies or blacklist them. 192 193 @param idpackages: list of valid idpackages (int) on the given dbconn 194 argument passed 195 @type idpackages: list 196 @param dbconn: entropy.db.EntropyRepository instance containing the 197 given idpackages list 198 @type dbconn: entropy.db.EntropyRepository 199 @keyword ask: request user interaction when finding missing dependencies 200 @type ask: bool 201 @keyword self_check: also introspect inside the complaining package 202 (to avoid reporting false positives when circular dependencies 203 occur) 204 @type self_check: bool 205 @keyword repo: repository identifier of the given 206 entropy.db.EntropyRepository dbconn instance. 207 It is used to correctly place blacklisted items. 208 @type repo: string 209 @keyword black_list: list of dependencies already blacklisted. 210 @type black_list: set 211 @keyword black_list_adder: callable function that accepts two arguments: 212 (1) list (set) of new dependencies to blacklist for the 213 given (2) repository identifier. 214 @type black_list_adder: callable 215 @return: tainting status, if any dependency has been added 216 @rtype: bool 217 """ 218 if repo is None: 219 repo = self.SystemSettings['repositories']['default_repository'] 220 221 if not isinstance(black_list, set): 222 black_list = set() 223 224 taint = False 225 scan_msg = blue(_("Now searching for missing RDEPENDs")) 226 self.Output.updateProgress( 227 "[repo:%s] %s..." % ( 228 darkgreen(repo), 229 scan_msg, 230 ), 231 importance = 1, 232 type = "info", 233 header = red(" @@ ") 234 ) 235 scan_msg = blue(_("scanning for missing RDEPENDs")) 236 count = 0 237 maxcount = len(idpackages) 238 for idpackage in idpackages: 239 count += 1 240 atom = dbconn.retrieveAtom(idpackage) 241 if not atom: 242 continue 243 self.Output.updateProgress( 244 "[repo:%s] %s: %s" % ( 245 darkgreen(repo), 246 scan_msg, 247 darkgreen(atom), 248 ), 249 importance = 1, 250 type = "info", 251 header = blue(" @@ "), 252 back = True, 253 count = (count, maxcount,) 254 ) 255 missing_extended, missing = self._get_missing_rdepends(dbconn, 256 idpackage, self_check = self_check) 257 missing -= black_list 258 for item in missing_extended.keys(): 259 missing_extended[item] -= black_list 260 if not missing_extended[item]: 261 del missing_extended[item] 262 if (not missing) or (not missing_extended): 263 continue 264 self.Output.updateProgress( 265 "[repo:%s] %s: %s %s:" % ( 266 darkgreen(repo), 267 blue("package"), 268 darkgreen(atom), 269 blue(_("is missing the following dependencies")), 270 ), 271 importance = 1, 272 type = "info", 273 header = red(" @@ "), 274 count = (count, maxcount,) 275 ) 276 for missing_data in missing_extended: 277 self.Output.updateProgress( 278 "%s:" % (brown(unicode(missing_data)),), 279 importance = 0, 280 type = "info", 281 header = purple(" ## ") 282 ) 283 for dependency in missing_extended[missing_data]: 284 self.Output.updateProgress( 285 "%s" % (darkred(dependency),), 286 importance = 0, 287 type = "info", 288 header = blue(" # ") 289 ) 290 if ask: 291 rc_ask = self.Output.askQuestion(_("Do you want to add them?")) 292 if rc_ask == "No": 293 continue 294 rc_ask = self.Output.askQuestion(_("Selectively?")) 295 if rc_ask == "Yes": 296 newmissing = set() 297 new_blacklist = set() 298 for dependency in missing: 299 self.Output.updateProgress( 300 "[repo:%s|%s] %s" % ( 301 darkgreen(repo), 302 brown(atom), 303 blue(dependency), 304 ), 305 importance = 0, 306 type = "info", 307 header = blue(" @@ ") 308 ) 309 rc_ask = self.Output.askQuestion(_("Want to add?")) 310 if rc_ask == "Yes": 311 newmissing.add(dependency) 312 else: 313 rc_ask = self.Output.askQuestion( 314 _("Want to blacklist?")) 315 if rc_ask == "Yes": 316 new_blacklist.add(dependency) 317 if new_blacklist and (black_list_adder != None): 318 black_list_adder(new_blacklist, repo = repo) 319 missing = newmissing 320 if missing: 321 taint = True 322 dbconn.insertDependencies(idpackage, missing) 323 dbconn.commitChanges() 324 self.Output.updateProgress( 325 "[repo:%s] %s: %s" % ( 326 darkgreen(repo), 327 darkgreen(atom), 328 blue(_("missing dependencies added")), 329 ), 330 importance = 1, 331 type = "info", 332 header = red(" @@ "), 333 count = (count, maxcount,) 334 ) 335 336 return taint
337
338 - def test_shared_objects(self, dbconn, broken_symbols = False, 339 task_bombing_func = None, self_dir_check = True, 340 dump_results_to_file = False):
341 342 """ 343 Scan system looking for broken shared object ELF library dependencies. 344 345 @param dbconn: entropy.db.EntropyRepository instance which contains 346 information on packages installed on the system (for example: 347 entropy.client.interfaces.Client.clientDbconn ). 348 @type dbconn: entropy.db.EntropyRepository instance 349 @keyword broken_symbols: enable or disable broken symbols extra check. 350 Symbols which are going to be checked have to be listed into: 351 /etc/entropy/brokensyms.conf (regexp supported). 352 @type broken_symbols: bool 353 @keyword task_bombing_func: callable that will be called on every 354 scan iteration to allow external routines to cleanly stop the 355 execution of this function. 356 @type task_bombing_func: callable 357 @keyword dump_results_to_file: dump test results to files (printed) 358 @type dump_results_to_file: bool 359 @return: tuple of length 3, composed by (1) a dict of matched packages, 360 (2) a list (set) of broken ELF objects and (3) the execution status 361 (int, 0 means success). 362 @rtype: tuple 363 """ 364 365 self.Output.updateProgress( 366 blue(_("Libraries test")), 367 importance = 2, 368 type = "info", 369 header = red(" @@ ") 370 ) 371 372 syms_list_path = None 373 files_list_path = None 374 if dump_results_to_file: 375 376 tmp_dir = os.path.dirname(self.entropyTools.get_random_temp_file()) 377 syms_list_path = os.path.join(tmp_dir, "libtest_syms.txt") 378 files_list_path = os.path.join(tmp_dir, "libtest_files.txt") 379 380 dmp_data = [ 381 (_("Broken symbols packages list"), syms_list_path,), 382 (_("Broken executables list"), files_list_path,), 383 ] 384 mytxt = "%s:" % (purple(_("Dumping results into these files")),) 385 self.Output.updateProgress( 386 mytxt, 387 importance = 1, 388 type = "info", 389 header = blue(" @@ ") 390 ) 391 for txt, path in dmp_data: 392 mytxt = "%s: %s" % (blue(txt), path,) 393 self.Output.updateProgress( 394 mytxt, 395 importance = 0, 396 type = "info", 397 header = darkgreen(" ## ") 398 ) 399 400 myroot = etpConst['systemroot'] + os.path.sep 401 if not etpConst['systemroot']: 402 myroot = os.path.sep 403 404 # run ldconfig first 405 subprocess.call("ldconfig -r %s &> /dev/null" % (myroot,), shell = True) 406 # open /etc/ld.so.conf 407 ld_conf = etpConst['systemroot'] + "/etc/ld.so.conf" 408 409 if not os.path.isfile(ld_conf): 410 self.Output.updateProgress( 411 blue(_("Cannot find "))+red(ld_conf), 412 importance = 1, 413 type = "error", 414 header = red(" @@ ") 415 ) 416 return {}, set(), -1 417 418 reverse_symlink_map = self.SystemSettings['system_rev_symlinks'] 419 broken_syms_list = self.SystemSettings['broken_syms'] 420 broken_libs_mask = self.SystemSettings['broken_libs_mask'] 421 422 import re 423 424 broken_syms_list_regexp = [] 425 for broken_sym in broken_syms_list: 426 reg_sym = re.compile(broken_sym) 427 broken_syms_list_regexp.append(reg_sym) 428 429 broken_libs_mask_regexp = [] 430 broken_libs_paths_mask_regexp = [] 431 for broken_lib in broken_libs_mask: 432 reg_lib = re.compile(broken_lib) 433 if os.path.sep in broken_lib: 434 # it's a path, not a lib name 435 broken_libs_paths_mask_regexp.append(reg_lib) 436 else: 437 broken_libs_mask_regexp.append(reg_lib) 438 439 ldpaths = set(self.entropyTools.collect_linker_paths()) 440 ldpaths |= self.entropyTools.collect_paths() 441 442 # some crappy packages put shit here too 443 ldpaths.add("/usr/share") 444 # always force /usr/libexec too 445 ldpaths.add("/usr/libexec") 446 447 # remove duplicated dirs (due to symlinks) to speed up scanning 448 for real_dir in reverse_symlink_map.keys(): 449 syms = reverse_symlink_map[real_dir] 450 for sym in syms: 451 if sym in ldpaths: 452 ldpaths.discard(real_dir) 453 self.Output.updateProgress( 454 "%s: %s, %s: %s" % ( 455 brown(_("discarding directory")), 456 purple(real_dir), 457 brown(_("because it's symlinked on")), 458 purple(sym), 459 ), 460 importance = 0, 461 type = "info", 462 header = darkgreen(" @@ ") 463 ) 464 break 465 466 executables = set() 467 total = len(ldpaths) 468 count = 0 469 sys_root_len = len(etpConst['systemroot']) 470 for ldpath in ldpaths: 471 472 if callable(task_bombing_func): 473 task_bombing_func() 474 count += 1 475 self.Output.updateProgress( 476 blue("Tree: ")+red(etpConst['systemroot'] + ldpath), 477 importance = 0, 478 type = "info", 479 count = (count,total), 480 back = True, 481 percent = True, 482 header = " " 483 ) 484 ldpath = ldpath.encode(sys.getfilesystemencoding()) 485 mywalk_iter = os.walk(etpConst['systemroot'] + ldpath) 486 487 def mywimf(dt): 488 489 currentdir, subdirs, files = dt 490 491 def mymf(item): 492 filepath = os.path.join(currentdir,item) 493 if not os.access(filepath, os.R_OK): 494 return 0 495 if not os.path.isfile(filepath): 496 return 0 497 if not self.entropyTools.is_elf_file(filepath): 498 return 0 499 return filepath[sys_root_len:]
500 501 return set([x for x in map(mymf, files) if type(x) != int])
502 503 for x in map(mywimf,mywalk_iter): 504 executables |= x 505 506 self.Output.updateProgress( 507 blue(_("Collecting broken executables")), 508 importance = 2, 509 type = "info", 510 header = red(" @@ ") 511 ) 512 t = red(_("Attention")) + ": " + \ 513 blue(_("don't worry about libraries that are shown here but not later.")) 514 self.Output.updateProgress( 515 t, 516 importance = 1, 517 type = "info", 518 header = red(" @@ ") 519 ) 520 521 syms_list_f = None 522 if syms_list_path: 523 syms_list_f = open(syms_list_path, "w") 524 525 files_list_f = None 526 if files_list_path: 527 files_list_f = open(files_list_path, "w") 528 529 plain_brokenexecs = set() 530 total = len(executables) 531 count = 0 532 scan_txt = blue("%s ..." % (_("Scanning libraries"),)) 533 for executable in executables: 534 535 # task bombing hook 536 if callable(task_bombing_func): 537 task_bombing_func() 538 539 count += 1 540 if (count % 10 == 0) or (count == total) or (count == 1): 541 self.Output.updateProgress( 542 scan_txt, 543 importance = 0, 544 type = "info", 545 count = (count,total), 546 back = True, 547 percent = True, 548 header = " " 549 ) 550 551 # filter broken paths 552 # there are paths known to be broken and must be 553 # excluded to avoid noisy false positives 554 exec_match = False 555 for reg_path in broken_libs_paths_mask_regexp: 556 if reg_path.match(executable): 557 exec_match = True 558 break 559 if exec_match: 560 continue 561 562 real_exec_path = etpConst['systemroot'] + executable 563 564 myelfs = self.entropyTools.read_elf_dynamic_libraries( 565 real_exec_path) 566 567 mylibs = set() 568 for mylib in myelfs: 569 lib_path = self.entropyTools.resolve_dynamic_library(mylib, 570 executable) 571 if not lib_path: 572 mylibs.add(mylib) 573 574 # filter broken libraries 575 if mylibs: 576 577 mylib_filter = set() 578 for mylib in mylibs: 579 mylib_matched = False 580 for reg_lib in broken_libs_mask_regexp: 581 if reg_lib.match(mylib): 582 mylib_matched = True 583 break 584 585 if mylib_matched: # filter out 586 mylib_filter.add(mylib) 587 588 elif self_dir_check: 589 # check inside the same directory of the failing ELF 590 # obviously, we're looking for another ELF object 591 my_real_exec_dir = os.path.dirname(real_exec_path) 592 mylib_guess = os.path.join(my_real_exec_dir, mylib) 593 if os.access(mylib_guess, os.R_OK | os.F_OK): 594 if self.entropyTools.is_elf_file(mylib_guess): 595 # we have found the missing library, 596 # which wasn't in LDPATH, booooo @ package 597 # developers !! boooo! 598 mylib_filter.add(mylib) 599 600 mylibs -= mylib_filter 601 602 broken_sym_found = set() 603 if broken_symbols and not mylibs: 604 605 read_broken_syms = self.entropyTools.read_elf_broken_symbols( 606 real_exec_path) 607 my_broken_syms = set() 608 for read_broken_sym in read_broken_syms: 609 for reg_sym in broken_syms_list_regexp: 610 if reg_sym.match(read_broken_sym): 611 my_broken_syms.add(read_broken_sym) 612 break 613 broken_sym_found.update(my_broken_syms) 614 615 if not (mylibs or broken_sym_found): 616 continue 617 618 if mylibs: 619 620 if files_list_f: 621 files_list_f.write(executable + "\n") 622 623 alllibs = blue(' :: ').join(sorted(mylibs)) 624 self.Output.updateProgress( 625 red(real_exec_path)+" [ "+alllibs+" ]", 626 importance = 1, 627 type = "info", 628 percent = True, 629 count = (count,total), 630 header = " " 631 ) 632 elif broken_sym_found: 633 634 allsyms = darkred(' :: ').join([brown(x) for x in \ 635 broken_sym_found]) 636 if len(allsyms) > 50: 637 allsyms = brown(_('various broken symbols')) 638 639 if syms_list_f and broken_sym_found: 640 syms_list_f.write("%s => %s\n" % (real_exec_path, 641 sorted(broken_sym_found),)) 642 643 self.Output.updateProgress( 644 red(real_exec_path)+" { "+allsyms+" }", 645 importance = 1, 646 type = "info", 647 percent = True, 648 count = (count,total), 649 header = " " 650 ) 651 652 plain_brokenexecs.add(executable) 653 654 # close open files 655 if syms_list_f: 656 syms_list_f.flush() 657 syms_list_f.close() 658 if files_list_f: 659 files_list_f.flush() 660 files_list_f.close() 661 662 del executables 663 packagesMatched = {} 664 665 if not etpSys['serverside']: 666 667 # we are client side 668 # this is hackish and must be fixed sooner or later 669 # but for now, it works 670 # Client class is singleton and is surely already 671 # loaded when we get here 672 from entropy.client.interfaces import Client 673 client = Client() 674 675 self.Output.updateProgress( 676 blue(_("Matching broken libraries/executables")), 677 importance = 1, 678 type = "info", 679 header = red(" @@ ") 680 ) 681 matched = set() 682 for brokenlib in plain_brokenexecs: 683 idpackages = dbconn.searchBelongs(brokenlib) 684 685 for idpackage in idpackages: 686 687 key, slot = dbconn.retrieveKeySlot(idpackage) 688 mymatch = client.atom_match(key, matchSlot = slot) 689 if mymatch[0] == -1: 690 matched.add(brokenlib) 691 continue 692 693 cmpstat = client.get_package_action(mymatch) 694 if cmpstat == 0: 695 continue 696 if not packagesMatched.has_key(brokenlib): 697 packagesMatched[brokenlib] = set() 698 699 packagesMatched[brokenlib].add(mymatch) 700 matched.add(brokenlib) 701 702 plain_brokenexecs -= matched 703 704 return packagesMatched, plain_brokenexecs, 0 705
706 - def _content_test(self, mycontent):
707 """ 708 Test whether the given list of files contain files 709 with broken shared object links. 710 711 @param mycontent: list of file paths 712 @type mycontent: list or set 713 @return: dict containing a map between file path 714 and list (set) of broken libraries (just the library name, 715 the same that is contained inside ELF metadata) 716 @rtype: dict 717 """ 718 def is_contained(needed, content): 719 for item in content: 720 if os.path.basename(item) == needed: 721 return True 722 return False
723 724 mylibs = {} 725 for myfile in mycontent: 726 myfile = myfile.encode('raw_unicode_escape') 727 if not os.access(myfile, os.R_OK): 728 continue 729 if not os.path.isfile(myfile): 730 continue 731 if not self.entropyTools.is_elf_file(myfile): 732 continue 733 mylibs[myfile] = self.entropyTools.read_elf_dynamic_libraries( 734 myfile) 735 736 broken_libs = {} 737 for mylib in mylibs: 738 for myneeded in mylibs[mylib]: 739 # is this inside myself ? 740 if is_contained(myneeded, mycontent): 741 continue 742 found = self.entropyTools.resolve_dynamic_library(myneeded, 743 mylib) 744 if found: 745 continue 746 if not broken_libs.has_key(mylib): 747 broken_libs[mylib] = set() 748 broken_libs[mylib].add(myneeded) 749 750 return broken_libs 751
752 - def _get_missing_rdepends(self, dbconn, idpackage, self_check = False):
753 """ 754 Service method able to determine whether dependencies are missing 755 on the given idpackage (belonging to the given 756 entropy.db.EntropyRepository "dbconn" argument) using shared objects 757 linking information between packages. 758 759 @todo: swap the first two arguments? 760 @param dbconn: entropy.db.EntropyRepository instance from which idpackage 761 argument belongs 762 @type dbconn: entropy.db.EntropyRepository instance 763 @param idpackage: entropy.db.EntropyRepository package identifier 764 @type idpackage: int 765 @keyword self_check: also check inside the given package 766 (idpackage) itself 767 @type self_check: bool 768 @return: tuple of length 2, composed by a dictionary with the 769 following structure: 770 {('KEY', 'SLOT': set([list of missing deps for the given key])} 771 and a "plain" list (set) of missing dependencies 772 set([list of missing dependencies]) 773 @rtype: tuple 774 """ 775 rdepends = {} 776 rdepends_plain = set() 777 neededs = dbconn.retrieveNeeded(idpackage, extended = True) 778 ldpaths = set(self.entropyTools.collect_linker_paths()) 779 deps_content = set() 780 dependencies = self.get_deep_dependency_list(dbconn, idpackage, 781 atoms = True) 782 scope_cache = set() 783 784 def update_depscontent(mycontent, dbconn, ldpaths): 785 return set( \ 786 [x for x in mycontent if os.path.dirname(x) in ldpaths \ 787 and (dbconn.isNeededAvailable(os.path.basename(x)) > 0) ])
788 789 def is_in_content(myneeded, content): 790 for item in content: 791 item = os.path.basename(item) 792 if myneeded == item: 793 return True 794 return False 795 796 for dependency in dependencies: 797 match = dbconn.atomMatch(dependency) 798 if match[0] != -1: 799 mycontent = dbconn.retrieveContent(match[0]) 800 deps_content |= update_depscontent(mycontent, dbconn, ldpaths) 801 key, slot = dbconn.retrieveKeySlot(match[0]) 802 scope_cache.add((key, slot)) 803 804 key, slot = dbconn.retrieveKeySlot(idpackage) 805 mycontent = dbconn.retrieveContent(idpackage) 806 deps_content |= update_depscontent(mycontent, dbconn, ldpaths) 807 scope_cache.add((key, slot)) 808 809 idpackages_cache = set() 810 idpackage_map = {} 811 idpackage_map_reverse = {} 812 for needed, elfclass in neededs: 813 data_solved = dbconn.resolveNeeded(needed, elfclass = elfclass, 814 extended = True) 815 data_size = len(data_solved) 816 data_solved = set([x for x in data_solved if x[0] \ 817 not in idpackages_cache]) 818 if not data_solved or (data_size != len(data_solved)): 819 continue 820 821 if self_check: 822 if is_in_content(needed, mycontent): 823 continue 824 825 found = False 826 for data in data_solved: 827 if data[1] in deps_content: 828 found = True 829 break 830 if not found: 831 for data in data_solved: 832 r_idpackage = data[0] 833 key, slot = dbconn.retrieveKeySlot(r_idpackage) 834 if (key, slot) not in scope_cache: 835 if not dbconn.isSystemPackage(r_idpackage): 836 if not rdepends.has_key((needed, elfclass)): 837 rdepends[(needed, elfclass)] = set() 838 if not idpackage_map.has_key((needed, elfclass)): 839 idpackage_map[(needed, elfclass)] = set() 840 keyslot = "%s:%s" % (key, slot,) 841 obj = idpackage_map_reverse.setdefault( 842 keyslot, set()) 843 obj.add((needed, elfclass,)) 844 rdepends[(needed, elfclass)].add(keyslot) 845 idpackage_map[(needed, elfclass)].add(r_idpackage) 846 rdepends_plain.add(keyslot) 847 idpackages_cache.add(r_idpackage) 848 849 # now reduce dependencies 850 851 r_deplist = set() 852 for key in idpackage_map: 853 r_idpackages = idpackage_map.get(key) 854 for r_idpackage in r_idpackages: 855 r_deplist |= dbconn.retrieveDependencies(r_idpackage) 856 857 r_keyslots = set() 858 for r_dep in r_deplist: 859 m_idpackage, m_rc = dbconn.atomMatch(r_dep) 860 if m_rc != 0: 861 continue 862 keyslot = dbconn.retrieveKeySlotAggregated(m_idpackage) 863 if keyslot in rdepends_plain: 864 r_keyslots.add(keyslot) 865 866 rdepends_plain -= r_keyslots 867 for r_keyslot in r_keyslots: 868 keys = [x for x in idpackage_map_reverse.get(keyslot, set()) if \ 869 x in rdepends] 870 for key in keys: 871 rdepends[key].discard(r_keyslot) 872 if not rdepends[key]: 873 del rdepends[key] 874 875 return rdepends, rdepends_plain 876
877 - def get_deep_dependency_list(self, dbconn, idpackage, atoms = False):
878 """ 879 Service method which returns a complete, expanded list of dependencies 880 for the given idpackage on the given entropy.db.EntropyRepository 881 "dbconn" instance. 882 883 @param dbconn: entropy.db.EntropyRepository instance which contains 884 the given idpackage item. 885 @type dbconn: entropy.db.EntropyRepository instance 886 @param idpackage: Entropy database package key 887 @type idpackage: int 888 @keyword atoms: !! return type modifier !! , make method returning 889 a list of atom strings instead of list of db match tuples. 890 @type atoms: bool 891 @return: list of dependencies in form of matching tuple list 892 ( [(idpackage, repoid,) ... ] ) or plain dependency list (if 893 atom == True -- set([atom_string1, atom_string2, atom_string3]) 894 @rtype: list or set 895 """ 896 mybuffer = self.Lifo() 897 matchcache = set() 898 depcache = set() 899 mydeps = dbconn.retrieveDependencies(idpackage) 900 for mydep in mydeps: 901 mybuffer.push(mydep) 902 try: 903 mydep = mybuffer.pop() 904 except ValueError: 905 mydep = None # stack empty 906 907 while mydep: 908 909 if mydep in depcache: 910 try: 911 mydep = mybuffer.pop() 912 except ValueError: 913 break # stack empty 914 continue 915 916 my_idpackage, my_rc = dbconn.atomMatch(mydep) 917 if atoms: 918 matchcache.add(mydep) 919 else: 920 matchcache.add(my_idpackage) 921 922 if my_idpackage != -1: 923 owndeps = dbconn.retrieveDependencies(my_idpackage) 924 for owndep in owndeps: 925 mybuffer.push(owndep) 926 927 depcache.add(mydep) 928 try: 929 mydep = mybuffer.pop() 930 except ValueError: 931 break # stack empty 932 933 # always discard -1 in set 934 matchcache.discard(-1) 935 return matchcache
936
937 - def __analyze_package_edb(self, pkg_path):
938 """ 939 Check if the physical Entropy package file contains 940 a valid Entropy embedded database. 941 942 @param pkg_path: path to physical entropy package file 943 @type pkg_path: string 944 @return: package validity 945 @rtype: bool 946 """ 947 from entropy.db import EntropyRepository, dbapi2 948 fd, tmp_path = tempfile.mkstemp() 949 extract_path = self.entropyTools.extract_edb(pkg_path, tmp_path) 950 if extract_path is None: 951 if os.access(tmp_path, os.F_OK): 952 os.remove(tmp_path) 953 os.close(fd) 954 return False # error! 955 try: 956 dbc = EntropyRepository( 957 readOnly = False, 958 dbFile = tmp_path, 959 clientDatabase = True, 960 dbname = 'qa_testing', 961 xcache = False, 962 indexing = False, 963 OutputInterface = self.Output, 964 skipChecks = False 965 ) 966 except dbapi2.Error: 967 os.remove(tmp_path) 968 os.close(fd) 969 return False 970 971 valid = True 972 try: 973 dbc.validateDatabase() 974 except SystemDatabaseError: 975 valid = False 976 977 if valid: 978 try: 979 for idpackage in dbc.listAllIdpackages(): 980 dbc.retrieveContent(idpackage, extended = True, 981 formatted = True, insert_formatted = True) 982 except dbapi2.Error: 983 valid = False 984 985 dbc.closeDB() 986 os.remove(tmp_path) 987 os.close(fd) 988 989 return valid
990
991 - def entropy_package_checks(self, package_path):
992 """ 993 Main method for the execution of QA tests on physical Entropy 994 package files. 995 996 @param package_path: path to physical Entropy package file path 997 @type package_path: string 998 @return: True, if all checks passed 999 @rtype: bool 1000 """ 1001 qa_methods = [self.__analyze_package_edb] 1002 for method in qa_methods: 1003 qa_rc = method(package_path) 1004 if not qa_rc: 1005 return False 1006 return True
1007 1008
1009 -class ErrorReportInterface:
1010 1011 """ 1012 1013 Interface used by Entropy Client to remotely send errors via HTTP POST. 1014 Some anonymous info about the running system are collected and sent over, 1015 once the user gives the acknowledgement for this operation. 1016 User should be asked for valid credentials, such as name, surname and email. 1017 This has two advantages: block stupid and lazy people and make possible 1018 for Entropy developers to contact him/her back. 1019 Moreover, the same applies for a simple description. To improve the 1020 ability to debug an issue, it is also asked the user to describe his/her 1021 action prior to the error. 1022 1023 Sample code: 1024 1025 >>> from entropy.qa import ErrorReportInterface 1026 >>> error = ErrorReportInterface('http://url_for_http_post') 1027 >>> error.prepare('traceback_text', 'John Foo', 'john@foo.com', 1028 report_data = 'extra traceback info', 1029 description = 'I was installing foo!') 1030 >>> error.submit() 1031 1032 """ 1033 1034 import entropy.tools as entropyTools
1035 - def __init__(self, post_url):
1036 """ 1037 ErrorReportInterface constructor. 1038 1039 @param post_url: HTTP post url where to submit data 1040 @type post_url: string 1041 """ 1042 from entropy.misc import MultipartPostHandler 1043 import urllib2 1044 self.url = post_url 1045 self.opener = urllib2.build_opener(MultipartPostHandler) 1046 self.generated = False 1047 self.params = {} 1048 1049 sys_settings = SystemSettings() 1050 proxy_settings = sys_settings['system']['proxy'] 1051 mydict = {} 1052 if proxy_settings['ftp']: 1053 mydict['ftp'] = proxy_settings['ftp'] 1054 if proxy_settings['http']: 1055 mydict['http'] = proxy_settings['http'] 1056 if mydict: 1057 mydict['username'] = proxy_settings['username'] 1058 mydict['password'] = proxy_settings['password'] 1059 self.entropyTools.add_proxy_opener(urllib2, mydict) 1060 else: 1061 # unset 1062 urllib2._opener = None
1063
1064 - def prepare(self, tb_text, name, email, report_data = "", description = ""):
1065 1066 """ 1067 This method must be called prior to submit(). It is used to prepare 1068 and collect system information before the submission. 1069 It is intentionally split from submit() to allow easy reimplementation. 1070 1071 @param tb_text: Python traceback text to send 1072 @type tb_text: string 1073 @param name: submitter name 1074 @type name: string 1075 @param email: submitter email address 1076 @type email: string 1077 @keyword report_data: extra information 1078 @type report_data: string 1079 @keyword description: submitter action description 1080 @type description: string 1081 @return: None 1082 @rtype: None 1083 """ 1084 1085 import sys 1086 from entropy.tools import getstatusoutput 1087 self.params['arch'] = etpConst['currentarch'] 1088 self.params['stacktrace'] = tb_text 1089 self.params['name'] = name 1090 self.params['email'] = email 1091 self.params['version'] = etpConst['entropyversion'] 1092 self.params['errordata'] = report_data 1093 self.params['description'] = description 1094 self.params['arguments'] = ' '.join(sys.argv) 1095 self.params['uid'] = etpConst['uid'] 1096 self.params['system_version'] = "N/A" 1097 if os.access(etpConst['systemreleasefile'], os.R_OK): 1098 f_rel = open(etpConst['systemreleasefile'], "r") 1099 self.params['system_version'] = f_rel.readline().strip() 1100 f_rel.close() 1101 1102 self.params['processes'] = getstatusoutput('ps auxf')[1] 1103 self.params['lspci'] = getstatusoutput('/usr/sbin/lspci')[1] 1104 self.params['dmesg'] = getstatusoutput('dmesg')[1] 1105 self.params['locale'] = getstatusoutput('locale -v')[1] 1106 1107 self.generated = True
1108 1109 # params is a dict, key(HTTP post item name): value
1110 - def submit(self):
1111 """ 1112 Submit collected data remotely via HTTP POST. 1113 1114 @raise PermissionDenied: when prepare() hasn't been called. 1115 @return: None 1116 @rtype: None 1117 """ 1118 if self.generated: 1119 result = self.opener.open(self.url, self.params).read() 1120 if result.strip() == "1": 1121 return True 1122 return False 1123 else: 1124 mytxt = _("Not prepared yet") 1125 raise PermissionDenied("PermissionDenied: %s" % (mytxt,))
1126