Coverage for manila/data/utils.py: 98%
120 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright 2015, Hitachi Data Systems.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
15import os
17from oslo_log import log
19from manila import exception
20from manila.i18n import _
21from manila import utils
23LOG = log.getLogger(__name__)
26class Copy(object):
28 def __init__(self, src, dest, ignore_list, check_hash=False):
29 self.src = src
30 self.dest = dest
31 self.total_size = 0
32 self.current_size = 0
33 self.files = []
34 self.dirs = []
35 self.current_copy = None
36 self.ignore_list = ignore_list
37 self.cancelled = False
38 self.initialized = False
39 self.completed = False
40 self.check_hash = check_hash
42 def get_progress(self):
44 # Empty share or empty contents
45 if self.completed and self.total_size == 0:
46 return {'total_progress': 100}
48 if not self.initialized or self.current_copy is None:
49 return {'total_progress': 0}
51 try:
52 size, err = utils.execute("stat", "-c", "%s",
53 self.current_copy['file_path'],
54 run_as_root=True)
55 size = int(size)
56 except utils.processutils.ProcessExecutionError:
57 size = 0
59 current_file_progress = 0
60 if self.current_copy['size'] > 0: 60 ↛ 62line 60 didn't jump to line 62 because the condition on line 60 was always true
61 current_file_progress = size * 100 / self.current_copy['size']
62 current_file_path = self.current_copy['file_path']
64 total_progress = 0
65 if self.total_size > 0: 65 ↛ 71line 65 didn't jump to line 71 because the condition on line 65 was always true
66 if current_file_progress == 100:
67 size = 0
68 total_progress = int((self.current_size + size) *
69 100 / self.total_size)
71 progress = {
72 'total_progress': total_progress,
73 'current_file_path': current_file_path,
74 'current_file_progress': current_file_progress
75 }
77 return progress
79 def cancel(self):
81 self.cancelled = True
83 def run(self):
85 self.get_total_size(self.src)
86 self.initialized = True
87 self.copy_data(self.src)
88 self.copy_stats(self.src)
89 self.completed = True
91 LOG.info(self.get_progress())
93 def get_total_size(self, path):
94 if self.cancelled:
95 return
96 out, err = utils.execute(
97 "ls", "-pA1", "--group-directories-first", path,
98 run_as_root=True)
99 for line in out.split('\n'):
100 if self.cancelled:
101 return
102 if len(line) == 0:
103 continue
104 src_item = os.path.join(path, line)
105 if line[-1] == '/':
106 if line[0:-1] in self.ignore_list:
107 continue
108 self.get_total_size(src_item)
109 else:
110 if line in self.ignore_list:
111 continue
112 size, err = utils.execute("stat", "-c", "%s", src_item,
113 run_as_root=True)
114 self.total_size += int(size)
116 def copy_data(self, path):
117 if self.cancelled:
118 return
119 out, err = utils.execute(
120 "ls", "-pA1", "--group-directories-first", path,
121 run_as_root=True)
122 for line in out.split('\n'):
123 if self.cancelled:
124 return
125 if len(line) == 0:
126 continue
127 src_item = os.path.join(path, line)
128 dest_item = src_item.replace(self.src, self.dest)
129 if line[-1] == '/':
130 if line[0:-1] in self.ignore_list:
131 continue
132 utils.execute("mkdir", "-p", dest_item, run_as_root=True)
133 self.copy_data(src_item)
134 else:
135 if line in self.ignore_list:
136 continue
137 size, err = utils.execute("stat", "-c", "%s", src_item,
138 run_as_root=True)
140 self.current_copy = {'file_path': dest_item,
141 'size': int(size)}
143 self._copy_and_validate(src_item, dest_item)
145 self.current_size += int(size)
146 LOG.info(self.get_progress())
148 @utils.retry(retry_param=exception.ShareDataCopyFailed, retries=2)
149 def _copy_and_validate(self, src_item, dest_item):
150 utils.execute("cp", "-P", "--preserve=all", src_item,
151 dest_item, run_as_root=True)
153 if self.check_hash: 153 ↛ exitline 153 didn't return from function '_copy_and_validate' because the condition on line 153 was always true
154 _validate_item(src_item, dest_item)
156 def copy_stats(self, path):
157 if self.cancelled:
158 return
159 out, err = utils.execute(
160 "ls", "-pA1", "--group-directories-first", path,
161 run_as_root=True)
162 for line in out.split('\n'):
163 if self.cancelled:
164 return
165 if len(line) == 0:
166 continue
167 src_item = os.path.join(path, line)
168 dest_item = src_item.replace(self.src, self.dest)
169 # NOTE(ganso): Should re-apply attributes for folders.
170 if line[-1] == '/':
171 if line[0:-1] in self.ignore_list:
172 continue
173 self.copy_stats(src_item)
174 utils.execute("chmod", "--reference=%s" % src_item, dest_item,
175 run_as_root=True)
176 utils.execute("touch", "--reference=%s" % src_item, dest_item,
177 run_as_root=True)
178 utils.execute("chown", "--reference=%s" % src_item, dest_item,
179 run_as_root=True)
182def _validate_item(src_item, dest_item):
183 src_sum, err = utils.execute(
184 "sha256sum", "%s" % src_item, run_as_root=True)
185 dest_sum, err = utils.execute(
186 "sha256sum", "%s" % dest_item, run_as_root=True)
187 if src_sum.split()[0] != dest_sum.split()[0]: 187 ↛ exitline 187 didn't return from function '_validate_item' because the condition on line 187 was always true
188 msg = _("Data corrupted while copying. Aborting data copy.")
189 raise exception.ShareDataCopyFailed(reason=msg)