Skip to content
Snippets Groups Projects
Commit d3ef8a12 authored by Robin Sonnabend's avatar Robin Sonnabend
Browse files

List existing snapshots

parent d013fe7f
Branches
No related tags found
No related merge requests found
[[volume]]
volume_group = "exports"
name = "pub"
[[volume.keep]]
number = 12
interval = "1H"
[[volume.keep]]
number = 30
interval = "1d"
[[volume.keep]]
number = 6
interval = "1m"
...@@ -47,59 +47,72 @@ def freeze_xfs(mountpoint, freeze): ...@@ -47,59 +47,72 @@ def freeze_xfs(mountpoint, freeze):
] ]
sp.run(command, check=True) sp.run(command, check=True)
class Snapshot: class Volume:
def __init__(self, volume_group, parent_volume, timestamp): def __init__(self, volume_group, name):
self.volume_group = volume_group self.volume_group = volume_group
self.name = name
def get_full_name(self):
return "{}/{}".format(self.volume_group, self.name)
def get_mapper_device(self):
return "/dev/mapper/{}-{}".format(self.volume_group, self.name)
def get_mountpoint(self):
mapper_device = self.get_mapper_device()
command = [
"/bin/findmnt",
"--source", mapper_device,
"--json"
]
result = sp.check_output(command)
data = json.loads(result)
filesystems = data["filesystems"]
if len(filesystems) < 1:
raise Exception("No parent device {} found!".format(mapper_device))
return filesystems[0]["target"]
class Snapshot:
def __init__(self, parent_volume, timestamp, active=False):
self.parent_volume = parent_volume self.parent_volume = parent_volume
self.timestamp = timestamp self.timestamp = timestamp
self.active = active
def get_full_parent_volume(self):
return "{}/{}".format(self.volume_group, self.volume)
def get_timestamp_str(self): def get_timestamp_str(self):
return self.timestamp.strftime(TIMESTAMP_FORMAT) return self.timestamp.strftime(TIMESTAMP_FORMAT)
def get_name(self): def get_name(self):
return "{}-snapshot-{}".format( return "{}-snapshot-{}".format(
self.parent_volume, self.parent_volume.name,
self.get_timestamp_str()) self.get_timestamp_str())
@staticmethod
def parse_name(name):
parent_name, _, timestamp_str = name.split("-", 2)
timestamp = datetime.strptime(timestamp_str, TIMESTAMP_FORMAT)
return parent_name, timestamp
def get_full_volume(self): def get_full_volume(self):
return "{}/{}".format(self.volume_group, self.get_name()) return "{}/{}".format(self.volume_group, self.get_name())
def get_mountpoint(self): def get_mountpoint(self):
return os.path.join([ return os.path.join([
SNAPSHOT_BASE_DIR, self.parent_volume, self.get_timestamp_str() SNAPSHOT_BASE_DIR, self.parent_volume.name, self.get_timestamp_str()
]) ])
def get_parent_mapper_device(self):
return "/dev/mapper/{}-{}".format(self.volume_group, self.parent_volume)
def get_mapper_device(self): def get_mapper_device(self):
return "/dev/mapper/{}-{}".format(self.volume_group, self.get_name()) return "/dev/mapper/{}-{}".format(self.volume_group, self.get_name())
def get_parent_mountpoint(self):
command = [
"/bin/findmnt",
"--source", self.get_parent_mapper_device(),
"--json"
]
result = sp.check_output(command)
data = json.loads(result)
filesystems = data["filesystems"]
if len(filesystems) < 1:
raise Exception("No parent device {} found!".format(self.get_parent_mapper_device()))
return filesystems[0]["target"]
def create(self): def create(self):
parent_mountpoint = self.get_parent_mountpoint() parent_mountpoint = self.parent_volume.get_mountpoint()
with xfs_freeze(mountpoint): with xfs_freeze(mountpoint):
create_command = [ create_command = [
"/sbin/lvcreate", "/sbin/lvcreate",
"--snapshot", "--snapshot",
"--name", self.get_name(), "--name", self.get_name(),
"--permission", "r", "--permission", "r",
self.get_full_parent_volume() self.parent_volume.get_full_name()
] ]
sp.run(create_command, check=True) sp.run(create_command, check=True)
self.mount() self.mount()
...@@ -112,6 +125,7 @@ class Snapshot: ...@@ -112,6 +125,7 @@ class Snapshot:
self.get_full_volume() self.get_full_volume()
] ]
sp.run(activate_command, check=True) sp.run(activate_command, check=True)
self.active = True
mount_command = [ mount_command = [
"/bin/mount", "/bin/mount",
self.get_mapper_device(), self.get_mapper_device(),
...@@ -141,4 +155,60 @@ class Snapshot: ...@@ -141,4 +155,60 @@ class Snapshot:
self.get_full_volume() self.get_full_volume()
] ]
sp.run(deactivate_command, check=True) sp.run(deactivate_command, check=True)
self.active = False
@staticmethod
def list_snapshots():
list_command = [
"/sbin/lvs",
"-o", "vg_name,lv_name,lv_active,origin,lv_role",
"--reportformat", "json"
]
result = sp.check_output(list_command)
data = json.loads(result)
raw_volumes = data["report"][0]["lv"]
parent_name_map = {}
snapshots = {}
raw_snapshots = []
for raw_volume in raw_volumes:
volume_group = raw_volume["vg_name"]
volume_name = raw_volume["lv_name"]
active = raw_volume["lv_active"] == "active"
origin = raw_volume["origin"] or None
roles = raw_volume["lv_role"].split(",")
if "snapshot" in roles:
raw_snapshots.append((volume_group, volume_name, active, origin, roles))
elif "public" in roles:
volume = Volume(volume_group, volume_name)
snapshots[volume] = []
parent_name_map[volume.name] = volume
else:
print("Ignoring volume {}/{}".format(volume_group, volume_name)) # todo: remove this output
for (volume_group, volume_name, active, origin, roles) in raw_snapshots:
parent_name, timestamp = Snapshot.parse_name(volume_name)
if parent_name != origin:
raise Exception("Parent volume name not matching: '{}' != '{}'".format(parent_name, origin))
parent = parent_name_map[parent_name]
snapshot = Snapshot(parent, timestamp, active)
snapshots[parent].append(snapshot)
return snapshots
def load_config():
import sys
import toml
config_path = "config.toml"
ENV_VAR = "LVM_SNAPSHOT_CONFIG"
if ENV_VAR in os.environ:
config_path = os.environ[ENV_VAR]
if len(sys.argv) > 1:
config_path = sys.argv[1]
with open(config_path, "r") as config_file:
return toml.load(config_file)
def main():
config = load_config()
print(config)
snapshots = Snapshot.list_snapshots()
if __name__ == "__main__":
main()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment