1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: Apache-2.0
5 from __future__ import division
6 from builtins import object
9 _logger = logging.getLogger('arvados.ranges')
11 # Log level below 'debug' !
15 __slots__ = ("locator", "range_start", "range_size", "segment_offset")
17 def __init__(self, locator, range_start, range_size, segment_offset=0):
18 self.locator = locator
19 self.range_start = range_start
20 self.range_size = range_size
21 self.segment_offset = segment_offset
24 return "Range(%r, %r, %r, %r)" % (self.locator, self.range_start, self.range_size, self.segment_offset)
26 def __eq__(self, other):
27 return (self.locator == other.locator and
28 self.range_start == other.range_start and
29 self.range_size == other.range_size and
30 self.segment_offset == other.segment_offset)
32 def first_block(data_locators, range_start):
35 # range_start/block_start is the inclusive lower bound
36 # range_end/block_end is the exclusive upper bound
38 hi = len(data_locators)
41 block_size = data_locators[i].range_size
42 block_start = data_locators[i].range_start
43 block_end = block_start + block_size
45 # perform a binary search for the first block
46 # assumes that all of the blocks are contiguous, so range_start is guaranteed
47 # to either fall into the range of a block or be outside the block range entirely
48 while not (range_start >= block_start and range_start < block_end):
50 # must be out of range, fail
52 if range_start > block_start:
57 block_size = data_locators[i].range_size
58 block_start = data_locators[i].range_start
59 block_end = block_start + block_size
63 class LocatorAndRange(object):
64 __slots__ = ("locator", "block_size", "segment_offset", "segment_size")
66 def __init__(self, locator, block_size, segment_offset, segment_size):
67 self.locator = locator
68 self.block_size = block_size
69 self.segment_offset = segment_offset
70 self.segment_size = segment_size
72 def __eq__(self, other):
73 return (self.locator == other.locator and
74 self.block_size == other.block_size and
75 self.segment_offset == other.segment_offset and
76 self.segment_size == other.segment_size)
79 return "LocatorAndRange(%r, %r, %r, %r)" % (self.locator, self.block_size, self.segment_offset, self.segment_size)
81 def locators_and_ranges(data_locators, range_start, range_size, limit=None):
82 """Get blocks that are covered by a range.
84 Returns a list of LocatorAndRange objects.
87 list of Range objects, assumes that blocks are in order and contiguous
96 Maximum segments to return, default None (unlimited). Will truncate the
97 result if there are more segments needed to cover the range than the
104 range_end = range_start + range_size
106 i = first_block(data_locators, range_start)
110 # We should always start at the first segment due to the binary
112 while i < len(data_locators) and len(resp) != limit:
113 dl = data_locators[i]
114 block_start = dl.range_start
115 block_size = dl.range_size
116 block_end = block_start + block_size
117 _logger.log(RANGES_SPAM,
118 "L&R %s range_start %s block_start %s range_end %s block_end %s",
119 dl.locator, range_start, block_start, range_end, block_end)
120 if range_end <= block_start:
121 # range ends before this block starts, so don't look at any more locators
124 if range_start >= block_start and range_end <= block_end:
125 # range starts and ends in this block
126 resp.append(LocatorAndRange(dl.locator, block_size, dl.segment_offset + (range_start - block_start), range_size))
127 elif range_start >= block_start and range_end > block_end:
128 # range starts in this block
129 resp.append(LocatorAndRange(dl.locator, block_size, dl.segment_offset + (range_start - block_start), block_end - range_start))
130 elif range_start < block_start and range_end > block_end:
131 # range starts in a previous block and extends to further blocks
132 resp.append(LocatorAndRange(dl.locator, block_size, dl.segment_offset, block_size))
133 elif range_start < block_start and range_end <= block_end:
134 # range starts in a previous block and ends in this block
135 resp.append(LocatorAndRange(dl.locator, block_size, dl.segment_offset, range_end - block_start))
136 block_start = block_end
140 def replace_range(data_locators, new_range_start, new_range_size, new_locator, new_segment_offset):
142 Replace a file segment range with a new segment.
145 data_locators will be updated in place
148 list of Range objects, assumes that segments are in order and contiguous
151 start of range to replace in data_locators
154 size of range to replace in data_locators
157 locator for new segment to be inserted
160 segment offset within the locator
163 if new_range_size == 0:
166 new_range_end = new_range_start + new_range_size
168 if len(data_locators) == 0:
169 data_locators.append(Range(new_locator, new_range_start, new_range_size, new_segment_offset))
172 last = data_locators[-1]
173 if (last.range_start+last.range_size) == new_range_start:
174 if last.locator == new_locator and (last.segment_offset+last.range_size) == new_segment_offset:
175 # extend last segment
176 last.range_size += new_range_size
178 data_locators.append(Range(new_locator, new_range_start, new_range_size, new_segment_offset))
181 i = first_block(data_locators, new_range_start)
185 # We should always start at the first segment due to the binary
187 while i < len(data_locators):
188 dl = data_locators[i]
189 old_segment_start = dl.range_start
190 old_segment_end = old_segment_start + dl.range_size
191 _logger.log(RANGES_SPAM,
192 "RR %s range_start %s segment_start %s range_end %s segment_end %s",
193 dl, new_range_start, old_segment_start, new_range_end,
195 if new_range_end <= old_segment_start:
196 # range ends before this segment starts, so don't look at any more locators
199 if old_segment_start <= new_range_start and new_range_end <= old_segment_end:
200 # new range starts and ends in old segment
201 # split segment into up to 3 pieces
202 if (new_range_start-old_segment_start) > 0:
203 data_locators[i] = Range(dl.locator, old_segment_start, (new_range_start-old_segment_start), dl.segment_offset)
204 data_locators.insert(i+1, Range(new_locator, new_range_start, new_range_size, new_segment_offset))
206 data_locators[i] = Range(new_locator, new_range_start, new_range_size, new_segment_offset)
208 if (old_segment_end-new_range_end) > 0:
209 data_locators.insert(i+2, Range(dl.locator, new_range_end, (old_segment_end-new_range_end), dl.segment_offset + (new_range_start-old_segment_start) + new_range_size))
211 elif old_segment_start <= new_range_start and new_range_end > old_segment_end:
212 # range starts in this segment
213 # split segment into 2 pieces
214 data_locators[i] = Range(dl.locator, old_segment_start, (new_range_start-old_segment_start), dl.segment_offset)
215 data_locators.insert(i+1, Range(new_locator, new_range_start, new_range_size, new_segment_offset))
217 elif new_range_start < old_segment_start and new_range_end >= old_segment_end:
218 # range starts in a previous segment and extends to further segments
219 # delete this segment
222 elif new_range_start < old_segment_start and new_range_end < old_segment_end:
223 # range starts in a previous segment and ends in this segment
224 # move the starting point of this segment up, and shrink it.
225 data_locators[i] = Range(dl.locator, new_range_end, (old_segment_end-new_range_end), dl.segment_offset + (new_range_end-old_segment_start))