1
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
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
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
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
405 subprocess.call("ldconfig -r %s &> /dev/null" % (myroot,), shell = True)
406
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
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
443 ldpaths.add("/usr/share")
444
445 ldpaths.add("/usr/libexec")
446
447
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
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
552
553
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
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:
586 mylib_filter.add(mylib)
587
588 elif self_dir_check:
589
590
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
596
597
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
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
668
669
670
671
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
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
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
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
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
906
907 while mydep:
908
909 if mydep in depcache:
910 try:
911 mydep = mybuffer.pop()
912 except ValueError:
913 break
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
932
933
934 matchcache.discard(-1)
935 return matchcache
936
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
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
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
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
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
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
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