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