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 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
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
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
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.retrieveDepends(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
374 subprocess.call("ldconfig -r %s &> /dev/null" % (myroot,), shell = True)
375
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 for broken_lib in broken_libs_mask:
400 reg_lib = re.compile(broken_lib)
401 broken_libs_mask_regexp.append(reg_lib)
402
403 ldpaths = set(self.entropyTools.collect_linker_paths())
404 ldpaths |= self.entropyTools.collect_paths()
405
406
407 ldpaths.add("/usr/share")
408
409 ldpaths.add("/usr/libexec")
410
411
412 for real_dir in reverse_symlink_map.keys():
413 syms = reverse_symlink_map[real_dir]
414 for sym in syms:
415 if sym in ldpaths:
416 ldpaths.discard(real_dir)
417 self.Output.updateProgress(
418 "%s: %s, %s: %s" % (
419 brown(_("discarding directory")),
420 purple(real_dir),
421 brown(_("because it's symlinked on")),
422 purple(sym),
423 ),
424 importance = 0,
425 type = "info",
426 header = darkgreen(" @@ ")
427 )
428 break
429
430 executables = set()
431 total = len(ldpaths)
432 count = 0
433 sys_root_len = len(etpConst['systemroot'])
434 for ldpath in ldpaths:
435
436 if callable(task_bombing_func):
437 task_bombing_func()
438 count += 1
439 self.Output.updateProgress(
440 blue("Tree: ")+red(etpConst['systemroot'] + ldpath),
441 importance = 0,
442 type = "info",
443 count = (count,total),
444 back = True,
445 percent = True,
446 header = " "
447 )
448 ldpath = ldpath.encode(sys.getfilesystemencoding())
449 mywalk_iter = os.walk(etpConst['systemroot'] + ldpath)
450
451 def mywimf(dt):
452
453 currentdir, subdirs, files = dt
454
455 def mymf(item):
456 filepath = os.path.join(currentdir,item)
457 if not os.access(filepath, os.R_OK):
458 return 0
459 if not os.path.isfile(filepath):
460 return 0
461 if not self.entropyTools.is_elf_file(filepath):
462 return 0
463 return filepath[sys_root_len:]
464
465 return set([x for x in map(mymf, files) if type(x) != int])
466
467 for x in map(mywimf,mywalk_iter):
468 executables |= x
469
470 self.Output.updateProgress(
471 blue(_("Collecting broken executables")),
472 importance = 2,
473 type = "info",
474 header = red(" @@ ")
475 )
476 t = red(_("Attention")) + ": " + \
477 blue(_("don't worry about libraries that are shown here but not later."))
478 self.Output.updateProgress(
479 t,
480 importance = 1,
481 type = "info",
482 header = red(" @@ ")
483 )
484
485 plain_brokenexecs = set()
486 total = len(executables)
487 count = 0
488 scan_txt = blue("%s ..." % (_("Scanning libraries"),))
489 for executable in executables:
490
491
492 if callable(task_bombing_func):
493 task_bombing_func()
494
495 count += 1
496 if (count % 10 == 0) or (count == total) or (count == 1):
497 self.Output.updateProgress(
498 scan_txt,
499 importance = 0,
500 type = "info",
501 count = (count,total),
502 back = True,
503 percent = True,
504 header = " "
505 )
506
507 real_exec_path = etpConst['systemroot'] + executable
508
509 myelfs = self.entropyTools.read_elf_dynamic_libraries(
510 real_exec_path)
511
512 def mymf2(mylib):
513 return not self.entropyTools.resolve_dynamic_library(mylib,
514 executable)
515
516 mylibs = set(filter(mymf2, myelfs))
517
518
519 if mylibs:
520
521 mylib_filter = set()
522 for mylib in mylibs:
523 mylib_matched = False
524 for reg_lib in broken_libs_mask_regexp:
525 if reg_lib.match(mylib):
526 mylib_matched = True
527 break
528
529 if mylib_matched:
530 mylib_filter.add(mylib)
531
532 elif self_dir_check:
533
534
535 my_real_exec_dir = os.path.dirname(real_exec_path)
536 mylib_guess = os.path.join(my_real_exec_dir, mylib)
537 if os.access(mylib_guess, os.R_OK | os.F_OK):
538 if self.entropyTools.is_elf_file(mylib_guess):
539
540
541
542 mylib_filter.add(mylib)
543
544 mylibs -= mylib_filter
545
546
547 broken_sym_found = set()
548 if broken_symbols and not mylibs:
549
550 read_broken_syms = self.entropyTools.read_elf_broken_symbols(
551 etpConst['systemroot'] + executable)
552 my_broken_syms = set()
553 for read_broken_sym in read_broken_syms:
554 for reg_sym in broken_syms_list_regexp:
555 if reg_sym.match(read_broken_sym):
556 my_broken_syms.add(read_broken_sym)
557 break
558 broken_sym_found.update(my_broken_syms)
559
560 if not (mylibs or broken_sym_found):
561 continue
562
563 if mylibs:
564 alllibs = blue(' :: ').join(sorted(mylibs))
565 self.Output.updateProgress(
566 red(etpConst['systemroot']+executable)+" [ "+alllibs+" ]",
567 importance = 1,
568 type = "info",
569 percent = True,
570 count = (count,total),
571 header = " "
572 )
573 elif broken_sym_found:
574
575 allsyms = darkred(' :: ').join(
576 [brown(x) for x in list(broken_sym_found)])
577 if len(allsyms) > 50:
578 allsyms = brown(_('various broken symbols'))
579
580 self.Output.updateProgress(
581 red(etpConst['systemroot']+executable)+" { "+allsyms+" }",
582 importance = 1,
583 type = "info",
584 percent = True,
585 count = (count,total),
586 header = " "
587 )
588
589 plain_brokenexecs.add(executable)
590
591 del executables
592 packagesMatched = {}
593
594 if not etpSys['serverside']:
595
596
597
598
599
600
601 from entropy.client.interfaces import Client
602 client = Client()
603
604 self.Output.updateProgress(
605 blue(_("Matching broken libraries/executables")),
606 importance = 1,
607 type = "info",
608 header = red(" @@ ")
609 )
610 matched = set()
611 for brokenlib in plain_brokenexecs:
612 idpackages = dbconn.searchBelongs(brokenlib)
613
614 for idpackage in idpackages:
615
616 key, slot = dbconn.retrieveKeySlot(idpackage)
617 mymatch = client.atom_match(key, matchSlot = slot)
618 if mymatch[0] == -1:
619 matched.add(brokenlib)
620 continue
621
622 cmpstat = client.get_package_action(mymatch)
623 if cmpstat == 0:
624 continue
625 if not packagesMatched.has_key(brokenlib):
626 packagesMatched[brokenlib] = set()
627
628 packagesMatched[brokenlib].add(mymatch)
629 matched.add(brokenlib)
630
631 plain_brokenexecs -= matched
632
633 return packagesMatched, plain_brokenexecs, 0
634
635 - def _content_test(self, mycontent):
636 """
637 Test whether the given list of files contain files
638 with broken shared object links.
639
640 @param mycontent: list of file paths
641 @type mycontent: list or set
642 @return: dict containing a map between file path
643 and list (set) of broken libraries (just the library name,
644 the same that is contained inside ELF metadata)
645 @rtype: dict
646 """
647 def is_contained(needed, content):
648 for item in content:
649 if os.path.basename(item) == needed:
650 return True
651 return False
652
653 mylibs = {}
654 for myfile in mycontent:
655 myfile = myfile.encode('raw_unicode_escape')
656 if not os.access(myfile, os.R_OK):
657 continue
658 if not os.path.isfile(myfile):
659 continue
660 if not self.entropyTools.is_elf_file(myfile):
661 continue
662 mylibs[myfile] = self.entropyTools.read_elf_dynamic_libraries(
663 myfile)
664
665 broken_libs = {}
666 for mylib in mylibs:
667 for myneeded in mylibs[mylib]:
668
669 if is_contained(myneeded, mycontent):
670 continue
671 found = self.entropyTools.resolve_dynamic_library(myneeded,
672 mylib)
673 if found:
674 continue
675 if not broken_libs.has_key(mylib):
676 broken_libs[mylib] = set()
677 broken_libs[mylib].add(myneeded)
678
679 return broken_libs
680
682 """
683 Service method able to determine whether dependencies are missing
684 on the given idpackage (belonging to the given
685 entropy.db.EntropyRepository "dbconn" argument) using shared objects
686 linking information between packages.
687
688 @todo: swap the first two arguments?
689 @param dbconn: entropy.db.EntropyRepository instance from which idpackage
690 argument belongs
691 @type dbconn: entropy.db.EntropyRepository instance
692 @param idpackage: entropy.db.EntropyRepository package identifier
693 @type idpackage: int
694 @keyword self_check: also check inside the given package
695 (idpackage) itself
696 @type self_check: bool
697 @return: tuple of length 2, composed by a dictionary with the
698 following structure:
699 {('KEY', 'SLOT': set([list of missing deps for the given key])}
700 and a "plain" list (set) of missing dependencies
701 set([list of missing dependencies])
702 @rtype: tuple
703 """
704 rdepends = {}
705 rdepends_plain = set()
706 neededs = dbconn.retrieveNeeded(idpackage, extended = True)
707 ldpaths = set(self.entropyTools.collect_linker_paths())
708 deps_content = set()
709 dependencies = self.get_deep_dependency_list(dbconn, idpackage,
710 atoms = True)
711 scope_cache = set()
712
713 def update_depscontent(mycontent, dbconn, ldpaths):
714 return set( \
715 [x for x in mycontent if os.path.dirname(x) in ldpaths \
716 and (dbconn.isNeededAvailable(os.path.basename(x)) > 0) ])
717
718 def is_in_content(myneeded, content):
719 for item in content:
720 item = os.path.basename(item)
721 if myneeded == item:
722 return True
723 return False
724
725 for dependency in dependencies:
726 match = dbconn.atomMatch(dependency)
727 if match[0] != -1:
728 mycontent = dbconn.retrieveContent(match[0])
729 deps_content |= update_depscontent(mycontent, dbconn, ldpaths)
730 key, slot = dbconn.retrieveKeySlot(match[0])
731 scope_cache.add((key, slot))
732
733 key, slot = dbconn.retrieveKeySlot(idpackage)
734 mycontent = dbconn.retrieveContent(idpackage)
735 deps_content |= update_depscontent(mycontent, dbconn, ldpaths)
736 scope_cache.add((key, slot))
737
738 idpackages_cache = set()
739 idpackage_map = {}
740 idpackage_map_reverse = {}
741 for needed, elfclass in neededs:
742 data_solved = dbconn.resolveNeeded(needed, elfclass = elfclass,
743 extended = True)
744 data_size = len(data_solved)
745 data_solved = set([x for x in data_solved if x[0] \
746 not in idpackages_cache])
747 if not data_solved or (data_size != len(data_solved)):
748 continue
749
750 if self_check:
751 if is_in_content(needed, mycontent):
752 continue
753
754 found = False
755 for data in data_solved:
756 if data[1] in deps_content:
757 found = True
758 break
759 if not found:
760 for data in data_solved:
761 r_idpackage = data[0]
762 key, slot = dbconn.retrieveKeySlot(r_idpackage)
763 if (key, slot) not in scope_cache:
764 if not dbconn.isSystemPackage(r_idpackage):
765 if not rdepends.has_key((needed, elfclass)):
766 rdepends[(needed, elfclass)] = set()
767 if not idpackage_map.has_key((needed, elfclass)):
768 idpackage_map[(needed, elfclass)] = set()
769 keyslot = "%s:%s" % (key, slot,)
770 obj = idpackage_map_reverse.setdefault(
771 keyslot, set())
772 obj.add((needed, elfclass,))
773 rdepends[(needed, elfclass)].add(keyslot)
774 idpackage_map[(needed, elfclass)].add(r_idpackage)
775 rdepends_plain.add(keyslot)
776 idpackages_cache.add(r_idpackage)
777
778
779
780 r_deplist = set()
781 for key in idpackage_map:
782 r_idpackages = idpackage_map.get(key)
783 for r_idpackage in r_idpackages:
784 r_deplist |= dbconn.retrieveDependencies(r_idpackage)
785
786 r_keyslots = set()
787 for r_dep in r_deplist:
788 m_idpackage, m_rc = dbconn.atomMatch(r_dep)
789 if m_rc != 0:
790 continue
791 keyslot = dbconn.retrieveKeySlotAggregated(m_idpackage)
792 if keyslot in rdepends_plain:
793 r_keyslots.add(keyslot)
794
795 rdepends_plain -= r_keyslots
796 for r_keyslot in r_keyslots:
797 keys = [x for x in idpackage_map_reverse.get(keyslot, set()) if \
798 x in rdepends]
799 for key in keys:
800 rdepends[key].discard(r_keyslot)
801 if not rdepends[key]:
802 del rdepends[key]
803
804 return rdepends, rdepends_plain
805
807 """
808 Service method which returns a complete, expanded list of dependencies
809 for the given idpackage on the given entropy.db.EntropyRepository
810 "dbconn" instance.
811
812 @param dbconn: entropy.db.EntropyRepository instance which contains
813 the given idpackage item.
814 @type dbconn: entropy.db.EntropyRepository instance
815 @param idpackage: Entropy database package key
816 @type idpackage: int
817 @keyword atoms: !! return type modifier !! , make method returning
818 a list of atom strings instead of list of db match tuples.
819 @type atoms: bool
820 @return: list of dependencies in form of matching tuple list
821 ( [(idpackage, repoid,) ... ] ) or plain dependency list (if
822 atom == True -- set([atom_string1, atom_string2, atom_string3])
823 @rtype: list or set
824 """
825 mybuffer = self.Lifo()
826 matchcache = set()
827 depcache = set()
828 mydeps = dbconn.retrieveDependencies(idpackage)
829 for mydep in mydeps:
830 mybuffer.push(mydep)
831 try:
832 mydep = mybuffer.pop()
833 except ValueError:
834 mydep = None
835
836 while mydep:
837
838 if mydep in depcache:
839 try:
840 mydep = mybuffer.pop()
841 except ValueError:
842 break
843 continue
844
845 my_idpackage, my_rc = dbconn.atomMatch(mydep)
846 if atoms:
847 matchcache.add(mydep)
848 else:
849 matchcache.add(my_idpackage)
850
851 if my_idpackage != -1:
852 owndeps = dbconn.retrieveDependencies(my_idpackage)
853 for owndep in owndeps:
854 mybuffer.push(owndep)
855
856 depcache.add(mydep)
857 try:
858 mydep = mybuffer.pop()
859 except ValueError:
860 break
861
862
863 matchcache.discard(-1)
864 return matchcache
865
867 """
868 Check if the physical Entropy package file contains
869 a valid Entropy embedded database.
870
871 @param pkg_path: path to physical entropy package file
872 @type pkg_path: string
873 @return: package validity
874 @rtype: bool
875 """
876 from entropy.db import EntropyRepository, dbapi2
877 fd, tmp_path = tempfile.mkstemp()
878 extract_path = self.entropyTools.extract_edb(pkg_path, tmp_path)
879 if extract_path is None:
880 os.remove(tmp_path)
881 os.close(fd)
882 return False
883 try:
884 dbc = EntropyRepository(
885 readOnly = False,
886 dbFile = tmp_path,
887 clientDatabase = True,
888 dbname = 'qa_testing',
889 xcache = False,
890 indexing = False,
891 OutputInterface = self.Output,
892 skipChecks = False
893 )
894 except dbapi2.Error:
895 os.remove(tmp_path)
896 os.close(fd)
897 return False
898
899 valid = True
900 try:
901 dbc.validateDatabase()
902 except SystemDatabaseError:
903 valid = False
904
905 if valid:
906 try:
907 for idpackage in dbc.listAllIdpackages():
908 dbc.retrieveContent(idpackage, extended = True,
909 formatted = True, insert_formatted = True)
910 except dbapi2.Error:
911 valid = False
912
913 dbc.closeDB()
914 os.remove(tmp_path)
915 os.close(fd)
916
917 return valid
918
920 """
921 Main method for the execution of QA tests on physical Entropy
922 package files.
923
924 @param package_path: path to physical Entropy package file path
925 @type package_path: string
926 @return: True, if all checks passed
927 @rtype: bool
928 """
929 qa_methods = [self.__analyze_package_edb]
930 for method in qa_methods:
931 qa_rc = method(package_path)
932 if not qa_rc:
933 return False
934 return True
935
936
938
939 """
940
941 Interface used by Entropy Client to remotely send errors via HTTP POST.
942 Some anonymous info about the running system are collected and sent over,
943 once the user gives the acknowledgement for this operation.
944 User should be asked for valid credentials, such as name, surname and email.
945 This has two advantages: block stupid and lazy people and make possible
946 for Entropy developers to contact him/her back.
947 Moreover, the same applies for a simple description. To improve the
948 ability to debug an issue, it is also asked the user to describe his/her
949 action prior to the error.
950
951 Sample code:
952
953 >>> from entropy.qa import ErrorReportInterface
954 >>> error = ErrorReportInterface('http://url_for_http_post')
955 >>> error.prepare('traceback_text', 'John Foo', 'john@foo.com',
956 report_data = 'extra traceback info',
957 description = 'I was installing foo!')
958 >>> error.submit()
959
960 """
961
962 import entropy.tools as entropyTools
964 """
965 ErrorReportInterface constructor.
966
967 @param post_url: HTTP post url where to submit data
968 @type post_url: string
969 """
970 from entropy.misc import MultipartPostHandler
971 import urllib2
972 self.url = post_url
973 self.opener = urllib2.build_opener(MultipartPostHandler)
974 self.generated = False
975 self.params = {}
976
977 sys_settings = SystemSettings()
978 proxy_settings = sys_settings['system']['proxy']
979 mydict = {}
980 if proxy_settings['ftp']:
981 mydict['ftp'] = proxy_settings['ftp']
982 if proxy_settings['http']:
983 mydict['http'] = proxy_settings['http']
984 if mydict:
985 mydict['username'] = proxy_settings['username']
986 mydict['password'] = proxy_settings['password']
987 self.entropyTools.add_proxy_opener(urllib2, mydict)
988 else:
989
990 urllib2._opener = None
991
992 - def prepare(self, tb_text, name, email, report_data = "", description = ""):
993
994 """
995 This method must be called prior to submit(). It is used to prepare
996 and collect system information before the submission.
997 It is intentionally split from submit() to allow easy reimplementation.
998
999 @param tb_text: Python traceback text to send
1000 @type tb_text: string
1001 @param name: submitter name
1002 @type name: string
1003 @param email: submitter email address
1004 @type email: string
1005 @keyword report_data: extra information
1006 @type report_data: string
1007 @keyword description: submitter action description
1008 @type description: string
1009 @return: None
1010 @rtype: None
1011 """
1012
1013 import sys
1014 from entropy.tools import getstatusoutput
1015 self.params['arch'] = etpConst['currentarch']
1016 self.params['stacktrace'] = tb_text
1017 self.params['name'] = name
1018 self.params['email'] = email
1019 self.params['version'] = etpConst['entropyversion']
1020 self.params['errordata'] = report_data
1021 self.params['description'] = description
1022 self.params['arguments'] = ' '.join(sys.argv)
1023 self.params['uid'] = etpConst['uid']
1024 self.params['system_version'] = "N/A"
1025 if os.access(etpConst['systemreleasefile'], os.R_OK):
1026 f_rel = open(etpConst['systemreleasefile'], "r")
1027 self.params['system_version'] = f_rel.readline().strip()
1028 f_rel.close()
1029
1030 self.params['processes'] = getstatusoutput('ps auxf')[1]
1031 self.params['lspci'] = getstatusoutput('/usr/sbin/lspci')[1]
1032 self.params['dmesg'] = getstatusoutput('dmesg')[1]
1033 self.params['locale'] = getstatusoutput('locale -v')[1]
1034
1035 self.generated = True
1036
1037
1039 """
1040 Submit collected data remotely via HTTP POST.
1041
1042 @raise PermissionDenied: when prepare() hasn't been called.
1043 @return: None
1044 @rtype: None
1045 """
1046 if self.generated:
1047 result = self.opener.open(self.url, self.params).read()
1048 if result.strip() == "1":
1049 return True
1050 return False
1051 else:
1052 mytxt = _("Not prepared yet")
1053 raise PermissionDenied("PermissionDenied: %s" % (mytxt,))
1054