blob: 0ec4426d87c3309fdbbdd4837c40f25a3e579860 [file] [log] [blame]
Max Rasskazov86089432015-06-04 22:07:20 +03001#-*- coding: utf-8 -*-
2
3import os
4import re
5
6import utils
7
8from tempfiles import TempFiles
9from shell import Shell
10from rsync_url import RsyncUrl
11
12
13class RsyncRemote(object):
14 def __init__(self,
15 rsync_url,
Max Rasskazov4fe9bb42015-09-01 09:04:06 +030016 rsync_extra_params='',
Max Rasskazov86089432015-06-04 22:07:20 +030017 ):
18 # TODO: retry parameters for rsync
19 self.logger = utils.logger.getChild('RsyncRemote.' + rsync_url)
20 self.tmp = TempFiles()
21 self.shell = Shell(self.logger)
Max Rasskazovd3f57d82015-06-05 15:56:53 +030022 self.url = RsyncUrl(rsync_url)
Max Rasskazov4fe9bb42015-09-01 09:04:06 +030023 self.rsync_extra_params = ' '.join(['-v --no-owner --no-group',
24 rsync_extra_params])
Max Rasskazov86089432015-06-04 22:07:20 +030025
Max Rasskazov32798e32015-07-13 21:02:04 +030026 def _rsync_push(self, source='', dest=None, opts='', extra=None):
Max Rasskazov86089432015-06-04 22:07:20 +030027 # TODO: retry for rsync
28 # TODO: locking:
29 # https://review.openstack.org/#/c/147120/4/utils/simple_http_daemon.py
30 # create lock-files on remotes during operations
Max Rasskazov6d7b5c82015-07-13 13:31:58 +030031 # symlink dir-timestamp.lock -> dir-timestamp
Max Rasskazov86089432015-06-04 22:07:20 +030032 # for reading and writing
33 # special option for ignore lock-files (for manual fixing)
34 # all high-level functions (like ls) specify type of lock(read or
Max Rasskazov32798e32015-07-13 21:02:04 +030035 # write), and _rsync_push creates special lock file on remote.
36 # also _rsync_push uses retry for waiting wnen resource will be
37 # unlocked
Max Rasskazov86089432015-06-04 22:07:20 +030038 # TODO: check for url compatibility (local->remote, remote->local,
39 # local->local)
Max Rasskazovd3f57d82015-06-05 15:56:53 +030040 dest = self.url.urljoin(dest)
Max Rasskazov86089432015-06-04 22:07:20 +030041 allextra = self.rsync_extra_params
42 if extra is not None:
43 allextra = ' '.join((allextra, extra))
44 cmd = 'rsync {opts} {allextra} {source} {dest}'.format(**(locals()))
45 return self.shell.shell(cmd)[1]
46
Max Rasskazov9d82e202015-07-13 21:03:32 +030047 def _rsync_pull(self, source='', dest=None, opts='', extra=None):
48 source = self.url.urljoin(self.symlink_target(source))
49 #opts = '--archive --force --ignore-errors --delete --copy-dirlinks'
50 opts = '--archive --force --ignore-errors --delete'
51 # TODO: if dest is dir - detect dest on this alhorithm
52 # or don't touch it if it file
53 if dest is None:
54 raise RuntimeError('There are no "dest" specified for pull {}'
55 ''.format(source))
56 dest = self.url.a_file(dest, os.path.split(self.url.path)[-1])
57 allextra = self.rsync_extra_params
58 if extra is not None:
59 allextra = ' '.join((allextra, extra))
60 cmd = 'rsync {opts} {allextra} {source} {dest}'.format(**(locals()))
61 return self.shell.shell(cmd)[1]
62
Max Rasskazov86089432015-06-04 22:07:20 +030063 def _rsync_ls(self, dirname=None, pattern=r'.*', opts=''):
64 extra = '--no-v'
Max Rasskazov32798e32015-07-13 21:02:04 +030065 out = self._rsync_push(dest=dirname, opts=opts, extra=extra)
Max Rasskazov86089432015-06-04 22:07:20 +030066 regexp = re.compile(pattern)
67 out = [_ for _ in out.splitlines()
68 if (_.split()[-1] != '.') and
69 (regexp.match(_.split()[-1]) is not None)]
70 return out
71
72 def ls(self, dirname=None, pattern=r'.*'):
73 self.logger.debug('ls on "{}", pattern="{}"'.format(dirname, pattern))
74 out = self._rsync_ls(dirname, pattern=pattern)
75 out = [_.split()[-1] for _ in out]
76 return out
77
78 def ls_dirs(self, dirname=None, pattern=r'.*'):
79 self.logger.debug('ls dirs on "{}", pattern="{}"'
80 ''.format(dirname, pattern))
81 out = self._rsync_ls(dirname, pattern=pattern)
82 out = [_.split()[-1] for _ in out if _.startswith('d')]
83 return out
84
85 def ls_symlinks(self, dirname=None, pattern=r'.*'):
86 self.logger.debug('ls symlinks on "{}", pattern="{}"'
87 ''.format(dirname, pattern))
88 out = self._rsync_ls(dirname, pattern=pattern, opts='-l')
89 out = [_.split()[-3:] for _ in out if _.startswith('l')]
90 out = [[_[0], _[-1]] for _ in out]
91 return out
92
Max Rasskazov20643b42015-06-22 20:36:48 +030093 def symlink_target(self, symlink):
94 target = symlink
95 while True:
96 try:
97 target_path = os.path.split(target)[0]
98 target = self.ls_symlinks(target)[0][1]
99 target = os.path.join(target_path, target)
100 except:
101 return target
102
Max Rasskazov86089432015-06-04 22:07:20 +0300103 def rmfile(self, filename):
104 '''Removes file on rsync_url.'''
105 report_name = filename
106 dirname, filename = os.path.split(filename)
Max Rasskazov46cc0732015-06-05 19:23:24 +0300107 dirname = self.url.a_dir(dirname)
108 source = self.url.a_dir(self.tmp.empty_dir)
Max Rasskazov86089432015-06-04 22:07:20 +0300109 opts = "-r --delete --include={} '--exclude=*'".format(filename)
110 self.logger.info('Removing file "{}"'.format(report_name))
Max Rasskazov32798e32015-07-13 21:02:04 +0300111 return self._rsync_push(source=source, dest=dirname, opts=opts)
Max Rasskazov86089432015-06-04 22:07:20 +0300112
113 def cleandir(self, dirname):
114 '''Removes directories (recursive) on rsync_url'''
Max Rasskazov46cc0732015-06-05 19:23:24 +0300115 dirname = self.url.a_dir(dirname)
116 source = self.url.a_dir(self.tmp.empty_dir)
Max Rasskazov86089432015-06-04 22:07:20 +0300117 opts = "-a --delete"
118 self.logger.info('Cleaning directory "{}"'.format(dirname))
Max Rasskazov32798e32015-07-13 21:02:04 +0300119 return self._rsync_push(source=source, dest=dirname, opts=opts)
Max Rasskazov86089432015-06-04 22:07:20 +0300120
121 def rmdir(self, dirname):
122 '''Removes directories (recursive) on rsync_url'''
123 self.logger.info('Removing directory "{}"'.format(dirname))
124 self.cleandir(dirname)
Max Rasskazov46cc0732015-06-05 19:23:24 +0300125 return self.rmfile(self.url.a_file(dirname))
Max Rasskazov86089432015-06-04 22:07:20 +0300126
127 def mkdir(self, dirname):
128 '''Creates directories (recirsive, like mkdir -p) on rsync_url'''
Max Rasskazov46cc0732015-06-05 19:23:24 +0300129 source = self.url.a_dir(self.tmp.get_temp_dir(dirname))
Max Rasskazov86089432015-06-04 22:07:20 +0300130 opts = "-a"
131 self.logger.info('Creating directory "{}"'.format(dirname))
Max Rasskazov32798e32015-07-13 21:02:04 +0300132 return self._rsync_push(source=source, opts=opts)
Max Rasskazov86089432015-06-04 22:07:20 +0300133
Max Rasskazoveb72c932015-07-13 21:25:34 +0300134 def symlink(self, symlink, target,
135 create_target_file=True, store_history=True):
Max Rasskazov86089432015-06-04 22:07:20 +0300136 '''Creates symlink targeted to target'''
Max Rasskazov96bb0292015-06-23 15:22:05 +0300137
Max Rasskazov46cc0732015-06-05 19:23:24 +0300138 symlink = self.url.a_file(symlink)
Max Rasskazov96bb0292015-06-23 15:22:05 +0300139 if create_target_file is True:
Max Rasskazoveb72c932015-07-13 21:25:34 +0300140 infofile = '{}.target.txt'.format(symlink)
141 if store_history is True:
142 temp_dir = self.tmp.get_temp_dir()
143 source = '{}/{}'.format(temp_dir, os.path.split(infofile)[-1])
144 try:
145 self._rsync_pull(source=infofile, dest=source)
146 with open(source, 'r') as inf:
147 content = '{}\n{}'.format(target, inf.read())
148 except RuntimeError:
149 content = target
150 with open(source, 'w') as outf:
151 outf.write(content)
152 else:
153 source = self.tmp.get_file(content='{}'.format(target))
154 temp_dir = self.tmp.last_temp_dir
Max Rasskazov96bb0292015-06-23 15:22:05 +0300155 self.rmfile(infofile)
156 self.logger.info('Creating informaion file "{}"'.format(infofile))
Max Rasskazov32798e32015-07-13 21:02:04 +0300157 self._rsync_push(source=source, dest=infofile)
Max Rasskazov96bb0292015-06-23 15:22:05 +0300158 else:
159 temp_dir = self.tmp.get_temp_dir()
160
Max Rasskazov86089432015-06-04 22:07:20 +0300161 opts = "-l"
Max Rasskazov96bb0292015-06-23 15:22:05 +0300162 source = self.tmp.get_symlink_to(target, temp_dir=temp_dir)
Max Rasskazov86089432015-06-04 22:07:20 +0300163 self.rmfile(symlink)
164 self.logger.info('Creating symlink "{}" -> "{}"'
165 ''.format(symlink, target))
Max Rasskazov32798e32015-07-13 21:02:04 +0300166 return self._rsync_push(source=source, dest=symlink, opts=opts)
Max Rasskazov86089432015-06-04 22:07:20 +0300167
Max Rasskazova9f89732015-06-17 01:25:23 +0300168 def push(self, source, repo_name=None, extra=None):
Max Rasskazov86089432015-06-04 22:07:20 +0300169 '''Push source to destination'''
170 opts = '--archive --force --ignore-errors --delete'
Max Rasskazova9f89732015-06-17 01:25:23 +0300171 self.logger.info('Push "{}" to "{}"'.format(source, repo_name))
Max Rasskazov32798e32015-07-13 21:02:04 +0300172 return self._rsync_push(source=source,
173 dest=repo_name,
174 opts=opts,
175 extra=extra)