cloudstack/scripts/vm/hypervisor/kvm/imageserver/util.py

126 lines
4.0 KiB
Python

# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import json
import time
from typing import Any, Dict, List, Set, Tuple
def coalesce_allocation_extents(
extents: List[Dict[str, Any]],
) -> List[Dict[str, Any]]:
"""Merge contiguous extents that share the same ``zero`` flag."""
if not extents:
return []
out: List[Dict[str, Any]] = [dict(extents[0])]
for e in extents[1:]:
prev = out[-1]
if (
prev["start"] + prev["length"] == e["start"]
and prev["zero"] == e["zero"]
):
prev["length"] += e["length"]
else:
out.append({"start": e["start"], "length": e["length"], "zero": e["zero"]})
return out
def coalesce_dirty_zero_extents(
extents: List[Dict[str, Any]],
) -> List[Dict[str, Any]]:
"""Merge contiguous extents that share the same ``dirty`` and ``zero`` flags."""
if not extents:
return []
out: List[Dict[str, Any]] = [dict(extents[0])]
for e in extents[1:]:
prev = out[-1]
if (
prev["start"] + prev["length"] == e["start"]
and prev["dirty"] == e["dirty"]
and prev["zero"] == e["zero"]
):
prev["length"] += e["length"]
else:
out.append(
{
"start": e["start"],
"length": e["length"],
"dirty": e["dirty"],
"zero": e["zero"],
}
)
return out
def json_bytes(obj: Any) -> bytes:
return json.dumps(obj, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
def merge_dirty_zero_extents(
allocation_extents: List[Tuple[int, int, bool]],
dirty_extents: List[Tuple[int, int, bool]],
size: int,
) -> List[Dict[str, Any]]:
"""
Merge allocation (start, length, zero) and dirty (start, length, dirty) extents
into a single list of {start, length, dirty, zero} with unified boundaries.
"""
boundaries: Set[int] = {0, size}
for start, length, _ in allocation_extents:
boundaries.add(start)
boundaries.add(start + length)
for start, length, _ in dirty_extents:
boundaries.add(start)
boundaries.add(start + length)
sorted_boundaries = sorted(boundaries)
def lookup(
extents: List[Tuple[int, int, bool]], offset: int, default: bool
) -> bool:
for start, length, flag in extents:
if start <= offset < start + length:
return flag
return default
result: List[Dict[str, Any]] = []
for i in range(len(sorted_boundaries) - 1):
a, b = sorted_boundaries[i], sorted_boundaries[i + 1]
if a >= b:
continue
result.append(
{
"start": a,
"length": b - a,
"dirty": lookup(dirty_extents, a, False),
"zero": lookup(allocation_extents, a, False),
}
)
return coalesce_dirty_zero_extents(result)
def is_fallback_dirty_response(extents: List[Dict[str, Any]]) -> bool:
"""True if extents is the single-extent fallback (dirty=false, zero=false)."""
return (
len(extents) == 1
and extents[0].get("dirty") is False
and extents[0].get("zero") is False
)
def now_s() -> float:
return time.monotonic()