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