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