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