Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(92)

Side by Side Diff: client/common_lib/software_manager.py

Issue 6246035: Merge remote branch 'cros/upstream' into master (Closed) Base URL: ssh://git@gitrw.chromium.org:9222/autotest.git@master
Patch Set: patch Created 9 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 #!/usr/bin/python
2 """
3 Software package management library.
4
5 This is an abstraction layer on top of the existing distributions high level
6 package managers. It supports package operations useful for testing purposes,
7 and multiple high level package managers (here called backends). If you want
8 to make this lib to support your particular package manager/distro, please
9 implement the given backend class.
10
11 @author: Higor Vieira Alves (halves@br.ibm.com)
12 @author: Lucas Meneghel Rodrigues (lmr@redhat.com)
13 @author: Ramon de Carvalho Valle (rcvalle@br.ibm.com)
14
15 @copyright: IBM 2008-2009
16 @copyright: Red Hat 2009-2010
17 """
18 import os, re, logging, ConfigParser, optparse, random, string
19 try:
20 import yum
21 except:
22 pass
23 import common
24 from autotest_lib.client.bin import os_dep, utils
25 from autotest_lib.client.common_lib import error
26 from autotest_lib.client.common_lib import logging_config, logging_manager
27
28
29 def generate_random_string(length):
30 """
31 Return a random string using alphanumeric characters.
32
33 @length: Length of the string that will be generated.
34 """
35 r = random.SystemRandom()
36 str = ""
37 chars = string.letters + string.digits
38 while length > 0:
39 str += r.choice(chars)
40 length -= 1
41 return str
42
43
44 class SoftwareManagerLoggingConfig(logging_config.LoggingConfig):
45 """
46 Used with the sole purpose of providing convenient logging setup
47 for the KVM test auxiliary programs.
48 """
49 def configure_logging(self, results_dir=None, verbose=False):
50 super(SoftwareManagerLoggingConfig, self).configure_logging(
51 use_console=True,
52 verbose=verbose)
53
54
55 class SystemInspector(object):
56 """
57 System inspector class.
58
59 This may grow up to include more complete reports of operating system and
60 machine properties.
61 """
62 def __init__(self):
63 """
64 Probe system, and save information for future reference.
65 """
66 self.distro = utils.get_os_vendor()
67 self.high_level_pms = ['apt-get', 'yum', 'zypper']
68
69
70 def get_package_management(self):
71 """
72 Determine the supported package management systems present on the
73 system. If more than one package management system installed, try
74 to find the best supported system.
75 """
76 list_supported = []
77 for high_level_pm in self.high_level_pms:
78 try:
79 os_dep.command(high_level_pm)
80 list_supported.append(high_level_pm)
81 except:
82 pass
83
84 pm_supported = None
85 if len(list_supported) == 0:
86 pm_supported = None
87 if len(list_supported) == 1:
88 pm_supported = list_supported[0]
89 elif len(list_supported) > 1:
90 if 'apt-get' in list_supported and self.distro in ['Debian', 'Ubuntu ']:
91 pm_supported = 'apt-get'
92 elif 'yum' in list_supported and self.distro == 'Fedora':
93 pm_supported = 'yum'
94 else:
95 pm_supported = list_supported[0]
96
97 logging.debug('Package Manager backend: %s' % pm_supported)
98 return pm_supported
99
100
101 class SoftwareManager(object):
102 """
103 Package management abstraction layer.
104
105 It supports a set of common package operations for testing purposes, and it
106 uses the concept of a backend, a helper class that implements the set of
107 operations of a given package management tool.
108 """
109 def __init__(self):
110 """
111 Class constructor.
112
113 Determines the best supported package management system for the given
114 operating system running and initializes the appropriate backend.
115 """
116 inspector = SystemInspector()
117 backend_type = inspector.get_package_management()
118 if backend_type == 'yum':
119 self.backend = YumBackend()
120 elif backend_type == 'zypper':
121 self.backend = ZypperBackend()
122 elif backend_type == 'apt-get':
123 self.backend = AptBackend()
124 else:
125 raise NotImplementedError('Unimplemented package management '
126 'system: %s.' % backend_type)
127
128
129 def check_installed(self, name, version=None, arch=None):
130 """
131 Check whether a package is installed on this system.
132
133 @param name: Package name.
134 @param version: Package version.
135 @param arch: Package architecture.
136 """
137 return self.backend.check_installed(name, version, arch)
138
139
140 def list_all(self):
141 """
142 List all installed packages.
143 """
144 return self.backend.list_all()
145
146
147 def list_files(self, name):
148 """
149 Get a list of all files installed by package [name].
150
151 @param name: Package name.
152 """
153 return self.backend.list_files(name)
154
155
156 def install(self, name):
157 """
158 Install package [name].
159
160 @param name: Package name.
161 """
162 return self.backend.install(name)
163
164
165 def remove(self, name):
166 """
167 Remove package [name].
168
169 @param name: Package name.
170 """
171 return self.backend.remove(name)
172
173
174 def add_repo(self, url):
175 """
176 Add package repo described by [url].
177
178 @param name: URL of the package repo.
179 """
180 return self.backend.add_repo(url)
181
182
183 def remove_repo(self, url):
184 """
185 Remove package repo described by [url].
186
187 @param url: URL of the package repo.
188 """
189 return self.backend.remove_repo(url)
190
191
192 def upgrade(self):
193 """
194 Upgrade all packages available.
195 """
196 return self.backend.upgrade()
197
198
199 def provides(self, file):
200 """
201 Returns a list of packages that provides a given capability to the
202 system (be it a binary, a library).
203
204 @param file: Path to the file.
205 """
206 return self.backend.provides(file)
207
208
209 def install_what_provides(self, file):
210 """
211 Installs package that provides [file].
212
213 @param file: Path to file.
214 """
215 provides = self.provides(file)
216 if provides is not None:
217 self.install(provides)
218 else:
219 logging.warning('No package seems to provide %s', file)
220
221
222 class RpmBackend(object):
223 """
224 This class implements operations executed with the rpm package manager.
225
226 rpm is a lower level package manager, used by higher level managers such
227 as yum and zypper.
228 """
229 def __init__(self):
230 self.lowlevel_base_cmd = os_dep.command('rpm')
231
232
233 def _check_installed_version(self, name, version):
234 """
235 Helper for the check_installed public method.
236
237 @param name: Package name.
238 @param version: Package version.
239 """
240 cmd = (self.lowlevel_base_cmd + ' -q --qf %{VERSION} ' + name +
241 ' 2> /dev/null')
242 inst_version = utils.system_output(cmd)
243
244 if inst_version >= version:
245 return True
246 else:
247 return False
248
249
250 def check_installed(self, name, version=None, arch=None):
251 """
252 Check if package [name] is installed.
253
254 @param name: Package name.
255 @param version: Package version.
256 @param arch: Package architecture.
257 """
258 if arch:
259 cmd = (self.lowlevel_base_cmd + ' -q --qf %{ARCH} ' + name +
260 ' 2> /dev/null')
261 inst_archs = utils.system_output(cmd)
262 inst_archs = inst_archs.split('\n')
263
264 for inst_arch in inst_archs:
265 if inst_arch == arch:
266 return self._check_installed_version(name, version)
267 return False
268
269 elif version:
270 return self._check_installed_version(name, version)
271 else:
272 cmd = 'rpm -q ' + name + ' 2> /dev/null'
273 return (os.system(cmd) == 0)
274
275
276 def list_all(self):
277 """
278 List all installed packages.
279 """
280 installed_packages = utils.system_output('rpm -qa').splitlines()
281 return installed_packages
282
283
284 def list_files(self, name):
285 """
286 List files installed on the system by package [name].
287
288 @param name: Package name.
289 """
290 path = os.path.abspath(name)
291 if os.path.isfile(path):
292 option = '-qlp'
293 name = path
294 else:
295 option = '-ql'
296
297 l_cmd = 'rpm' + ' ' + option + ' ' + name + ' 2> /dev/null'
298
299 try:
300 result = utils.system_output(l_cmd)
301 list_files = result.split('\n')
302 return list_files
303 except error.CmdError:
304 return []
305
306
307 class DpkgBackend(object):
308 """
309 This class implements operations executed with the dpkg package manager.
310
311 dpkg is a lower level package manager, used by higher level managers such
312 as apt and aptitude.
313 """
314 def __init__(self):
315 self.lowlevel_base_cmd = os_dep.command('dpkg')
316
317
318 def check_installed(self, name):
319 if os.path.isfile(name):
320 n_cmd = (self.lowlevel_base_cmd + ' -f ' + name +
321 ' Package 2>/dev/null')
322 name = utils.system_output(n_cmd)
323 i_cmd = self.lowlevel_base_cmd + ' -s ' + name + ' 2>/dev/null'
324 # Checking if package is installed
325 package_status = utils.system_output(i_cmd, ignore_status=True)
326 not_inst_pattern = re.compile('not-installed', re.IGNORECASE)
327 dpkg_not_installed = re.search(not_inst_pattern, package_status)
328 if dpkg_not_installed:
329 return False
330 return True
331
332
333 def list_all(self):
334 """
335 List all packages available in the system.
336 """
337 installed_packages = []
338 raw_list = utils.system_output('dpkg -l').splitlines()[5:]
339 for line in raw_list:
340 parts = line.split()
341 if parts[0] == "ii": # only grab "installed" packages
342 installed_packages.append("%s-%s" % (parts[1], parts[2]))
343
344
345 def list_files(self, package):
346 """
347 List files installed by package [package].
348
349 @param package: Package name.
350 @return: List of paths installed by package.
351 """
352 if os.path.isfile(package):
353 l_cmd = self.lowlevel_base_cmd + ' -c ' + package
354 else:
355 l_cmd = self.lowlevel_base_cmd + ' -l ' + package
356 return utils.system_output(l_cmd).split('\n')
357
358
359 class YumBackend(RpmBackend):
360 """
361 Implements the yum backend for software manager.
362
363 Set of operations for the yum package manager, commonly found on Yellow Dog
364 Linux and Red Hat based distributions, such as Fedora and Red Hat
365 Enterprise Linux.
366 """
367 def __init__(self):
368 """
369 Initializes the base command and the yum package repository.
370 """
371 super(YumBackend, self).__init__()
372 executable = os_dep.command('yum')
373 base_arguments = '-y'
374 self.base_command = executable + ' ' + base_arguments
375 self.repo_file_path = '/etc/yum.repos.d/autotest.repo'
376 self.cfgparser = ConfigParser.ConfigParser()
377 self.cfgparser.read(self.repo_file_path)
378 y_cmd = executable + ' --version | head -1'
379 self.yum_version = utils.system_output(y_cmd, ignore_status=True)
380 logging.debug('Yum backend initialized')
381 logging.debug('Yum version: %s' % self.yum_version)
382 self.yum_base = yum.YumBase()
383
384
385 def _cleanup(self):
386 """
387 Clean up the yum cache so new package information can be downloaded.
388 """
389 utils.system("yum clean all")
390
391
392 def install(self, name):
393 """
394 Installs package [name]. Handles local installs.
395 """
396 if os.path.isfile(name):
397 name = os.path.abspath(name)
398 command = 'localinstall'
399 else:
400 command = 'install'
401
402 i_cmd = self.base_command + ' ' + command + ' ' + name
403
404 try:
405 utils.system(i_cmd)
406 return True
407 except:
408 return False
409
410
411 def remove(self, name):
412 """
413 Removes package [name].
414
415 @param name: Package name (eg. 'ipython').
416 """
417 r_cmd = self.base_command + ' ' + 'erase' + ' ' + name
418 try:
419 utils.system(r_cmd)
420 return True
421 except:
422 return False
423
424
425 def add_repo(self, url):
426 """
427 Adds package repository located on [url].
428
429 @param url: Universal Resource Locator of the repository.
430 """
431 # Check if we URL is already set
432 for section in self.cfgparser.sections():
433 for option, value in self.cfgparser.items(section):
434 if option == 'url' and value == url:
435 return True
436
437 # Didn't find it, let's set it up
438 while True:
439 section_name = 'software_manager' + '_' + generate_random_string(4)
440 if not self.cfgparser.has_section(section_name):
441 break
442 self.cfgparser.add_section(section_name)
443 self.cfgparser.set(section_name, 'name',
444 'Repository added by the autotest software manager.')
445 self.cfgparser.set(section_name, 'url', url)
446 self.cfgparser.set(section_name, 'enabled', 1)
447 self.cfgparser.set(section_name, 'gpgcheck', 0)
448 self.cfgparser.write(self.repo_file_path)
449
450
451 def remove_repo(self, url):
452 """
453 Removes package repository located on [url].
454
455 @param url: Universal Resource Locator of the repository.
456 """
457 for section in self.cfgparser.sections():
458 for option, value in self.cfgparser.items(section):
459 if option == 'url' and value == url:
460 self.cfgparser.remove_section(section)
461 self.cfgparser.write(self.repo_file_path)
462
463
464 def upgrade(self):
465 """
466 Upgrade all available packages.
467 """
468 r_cmd = self.base_command + ' ' + 'update'
469 try:
470 utils.system(r_cmd)
471 return True
472 except:
473 return False
474
475
476 def provides(self, name):
477 """
478 Returns a list of packages that provides a given capability.
479
480 @param name: Capability name (eg, 'foo').
481 """
482 d_provides = self.yum_base.searchPackageProvides(args=[name])
483 provides_list = [key for key in d_provides]
484 if provides_list:
485 logging.info("Package %s provides %s", provides_list[0], name)
486 return str(provides_list[0])
487 else:
488 return None
489
490
491 class ZypperBackend(RpmBackend):
492 """
493 Implements the zypper backend for software manager.
494
495 Set of operations for the zypper package manager, found on SUSE Linux.
496 """
497 def __init__(self):
498 """
499 Initializes the base command and the yum package repository.
500 """
501 super(ZypperBackend, self).__init__()
502 self.base_command = os_dep.command('zypper') + ' -n'
503 z_cmd = self.base_command + ' --version'
504 self.zypper_version = utils.system_output(z_cmd, ignore_status=True)
505 logging.debug('Zypper backend initialized')
506 logging.debug('Zypper version: %s' % self.zypper_version)
507
508
509 def install(self, name):
510 """
511 Installs package [name]. Handles local installs.
512
513 @param name: Package Name.
514 """
515 path = os.path.abspath(name)
516 i_cmd = self.base_command + ' install -l ' + name
517 try:
518 utils.system(i_cmd)
519 return True
520 except:
521 return False
522
523
524 def add_repo(self, url):
525 """
526 Adds repository [url].
527
528 @param url: URL for the package repository.
529 """
530 ar_cmd = self.base_command + ' addrepo ' + url
531 try:
532 utils.system(ar_cmd)
533 return True
534 except:
535 return False
536
537
538 def remove_repo(self, url):
539 """
540 Removes repository [url].
541
542 @param url: URL for the package repository.
543 """
544 rr_cmd = self.base_command + ' removerepo ' + url
545 try:
546 utils.system(rr_cmd)
547 return True
548 except:
549 return False
550
551
552 def remove(self, name):
553 """
554 Removes package [name].
555 """
556 r_cmd = self.base_command + ' ' + 'erase' + ' ' + name
557
558 try:
559 utils.system(r_cmd)
560 return True
561 except:
562 return False
563
564
565 def upgrade(self):
566 """
567 Upgrades all packages of the system.
568 """
569 u_cmd = self.base_command + ' update -l'
570
571 try:
572 utils.system(u_cmd)
573 return True
574 except:
575 return False
576
577
578 def provides(self, name):
579 """
580 Searches for what provides a given file.
581
582 @param name: File path.
583 """
584 p_cmd = self.base_command + ' what-provides ' + name
585 list_provides = []
586 try:
587 p_output = utils.system_output(p_cmd).split('\n')[4:]
588 for line in p_output:
589 line = [a.strip() for a in line.split('|')]
590 try:
591 state, pname, type, version, arch, repository = line
592 if pname not in list_provides:
593 list_provides.append(pname)
594 except IndexError:
595 pass
596 if len(list_provides) > 1:
597 logging.warning('More than one package found, '
598 'opting by the first queue result')
599 if list_provides:
600 logging.info("Package %s provides %s", list_provides[0], name)
601 return list_provides[0]
602 return None
603 except:
604 return None
605
606
607 class AptBackend(DpkgBackend):
608 """
609 Implements the apt backend for software manager.
610
611 Set of operations for the apt package manager, commonly found on Debian and
612 Debian based distributions, such as Ubuntu Linux.
613 """
614 def __init__(self):
615 """
616 Initializes the base command and the debian package repository.
617 """
618 super(AptBackend, self).__init__()
619 executable = os_dep.command('apt-get')
620 self.base_command = executable + ' -y'
621 self.repo_file_path = '/etc/apt/sources.list.d/autotest'
622 self.apt_version = utils.system_output('apt-get -v | head -1',
623 ignore_status=True)
624 logging.debug('Apt backend initialized')
625 logging.debug('apt version: %s' % self.apt_version)
626
627
628 def install(self, name):
629 """
630 Installs package [name].
631
632 @param name: Package name.
633 """
634 command = 'install'
635 i_cmd = self.base_command + ' ' + command + ' ' + name
636
637 try:
638 utils.system(i_cmd)
639 return True
640 except:
641 return False
642
643
644 def remove(self, name):
645 """
646 Remove package [name].
647
648 @param name: Package name.
649 """
650 command = 'remove'
651 flag = '--purge'
652 r_cmd = self.base_command + ' ' + command + ' ' + flag + ' ' + name
653
654 try:
655 utils.system(r_cmd)
656 return True
657 except:
658 return False
659
660
661 def add_repo(self, repo):
662 """
663 Add an apt repository.
664
665 @param repo: Repository string. Example:
666 'deb http://archive.ubuntu.com/ubuntu/ maverick universe'
667 """
668 repo_file = open(self.repo_file_path, 'a')
669 repo_file_contents = repo_file.read()
670 if repo not in repo_file_contents:
671 repo_file.write(repo)
672
673
674 def remove_repo(self, repo):
675 """
676 Remove an apt repository.
677
678 @param repo: Repository string. Example:
679 'deb http://archive.ubuntu.com/ubuntu/ maverick universe'
680 """
681 repo_file = open(self.repo_file_path, 'r')
682 new_file_contents = []
683 for line in repo_file.readlines:
684 if not line == repo:
685 new_file_contents.append(line)
686 repo_file.close()
687 new_file_contents = "\n".join(new_file_contents)
688 repo_file.open(self.repo_file_path, 'w')
689 repo_file.write(new_file_contents)
690 repo_file.close()
691
692
693 def upgrade(self):
694 """
695 Upgrade all packages of the system with eventual new versions.
696 """
697 ud_command = 'update'
698 ud_cmd = self.base_command + ' ' + ud_command
699 try:
700 utils.system(ud_cmd)
701 except:
702 logging.error("Apt package update failed")
703 up_command = 'upgrade'
704 up_cmd = self.base_command + ' ' + up_command
705 try:
706 utils.system(up_cmd)
707 return True
708 except:
709 return False
710
711
712 def provides(self, file):
713 """
714 Return a list of packages that provide [file].
715
716 @param file: File path.
717 """
718 if not self.check_installed('apt-file'):
719 self.install('apt-file')
720 command = os_dep.command('apt-file')
721 cache_update_cmd = command + ' update'
722 try:
723 utils.system(cache_update_cmd, ignore_status=True)
724 except:
725 logging.error("Apt file cache update failed")
726 fu_cmd = command + ' search ' + file
727 try:
728 provides = utils.system_output(fu_cmd).split('\n')
729 list_provides = []
730 for line in provides:
731 if line:
732 try:
733 line = line.split(':')
734 package = line[0].strip()
735 path = line[1].strip()
736 if path == file and package not in list_provides:
737 list_provides.append(package)
738 except IndexError:
739 pass
740 if len(list_provides) > 1:
741 logging.warning('More than one package found, '
742 'opting by the first queue result')
743 if list_provides:
744 logging.info("Package %s provides %s", list_provides[0], file)
745 return list_provides[0]
746 return None
747 except:
748 return None
749
750
751 if __name__ == '__main__':
752 parser = optparse.OptionParser(
753 "usage: %prog [install|remove|list-all|list-files|add-repo|remove-repo|"
754 "upgrade|what-provides|install-what-provides] arguments")
755 parser.add_option('--verbose', dest="debug", action='store_true',
756 help='include debug messages in console output')
757
758 options, args = parser.parse_args()
759 debug = options.debug
760 logging_manager.configure_logging(SoftwareManagerLoggingConfig(),
761 verbose=debug)
762 software_manager = SoftwareManager()
763 if args:
764 action = args[0]
765 args = " ".join(args[1:])
766 else:
767 action = 'show-help'
768
769 if action == 'install':
770 software_manager.install(args)
771 elif action == 'remove':
772 software_manager.remove(args)
773 if action == 'list-all':
774 software_manager.list_all()
775 elif action == 'list-files':
776 software_manager.list_files(args)
777 elif action == 'add-repo':
778 software_manager.add_repo(args)
779 elif action == 'remove-repo':
780 software_manager.remove_repo(args)
781 elif action == 'upgrade':
782 software_manager.upgrade()
783 elif action == 'what-provides':
784 software_manager.provides(args)
785 elif action == 'install-what-provides':
786 software_manager.install_what_provides(args)
787 elif action == 'show-help':
788 parser.print_help()
OLDNEW
« cli/job.py ('K') | « client/common_lib/magic.py ('k') | client/common_lib/test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698