blob: 3e1a464efa31698ef36576989cdd07775ab4ef17 [file] [log] [blame]
Peter Pentchevd00519f2021-11-08 01:17:13 +02001"""Clean up: remove volumes, snapshots, and the file itself."""
2
3from __future__ import annotations
4
5import argparse
6import errno
7import pathlib
8
9from typing import NamedTuple, Tuple # noqa: H301
10
11import confget
12
13from storpool import spapi # type: ignore
14from storpool import sptypes
15
16from . import defs
17
18
19class Config(NamedTuple):
20 """Runtime configuration for the sp_rand_cleanup tool."""
21
22 confdir: pathlib.Path
23 noop: bool
24 skip_api: bool
25
26 @staticmethod
27 def default() -> Config:
28 """Build a configuration object with default values."""
29 return Config(confdir=defs.DEFAULT_CONFDIR, noop=False, skip_api=False)
30
31
32def parse_file(cfg: Config) -> Tuple[pathlib.Path, str]:
33 """Parse the StorPool configuration file."""
34 conffile = cfg.confdir / defs.FILENAME
35 print(f"Parsing {conffile}")
36 data = confget.read_ini_file(confget.Config([], filename=str(conffile)))
37
38 assert isinstance(data, dict)
39 assert sorted(data) == [""]
40 assert sorted(data[""]) == [defs.PREFIX_VAR]
41 prefix = data[""][defs.PREFIX_VAR]
42 print(f"- got prefix {prefix!r}")
43
44 return conffile, prefix
45
46
47def remove_volumes(cfg: Config, api: spapi.Api, prefix: str) -> None:
48 """Detach and remove any pertinent StorPool volumes and snapshots."""
49 print("Querying the StorPool API for attached volumes and snapshots")
50 to_detach = []
51 for desc in api.attachmentsList(): # pylint: disable=not-callable
52 if not desc.volume.startswith(prefix):
53 continue
54 if desc.snapshot:
55 print(f"Will detach snapshot {desc.volume} from client {desc.client}")
56 to_detach.append(
57 sptypes.SnapshotReassignDesc(
58 detach=[desc.client], force=True, snapshot=desc.volume
59 )
60 )
61 else:
62 print(f"Will detach volume {desc.volume} from client {desc.client}")
63 to_detach.append(
64 sptypes.VolumeReassignDesc(
65 detach=[desc.client], force=True, volume=desc.volume
66 )
67 )
68
69 if to_detach:
70 print(f"Detaching {len(to_detach)} volumes/snapshots")
71 if not cfg.noop:
72 api.volumesReassignWait( # pylint: disable=not-callable
73 sptypes.VolumesReassignWaitDesc(reassign=to_detach)
74 )
75
76 print("Querying the StorPool API for existing volumes")
77 for name in [vol.name for vol in api.volumesList()]:
78 if not name.startswith(prefix):
79 continue
80 print(f"Removing volume {name}")
81 if not cfg.noop:
82 api.volumeDelete(name)
83
84 print("Querying the StorPool API for existing snapshots")
85 for name in [snap.name for snap in api.snapshotsList()]:
86 if not name.startswith(prefix):
87 continue
88 print(f"Removing snapshot {name}")
89 if not cfg.noop:
90 api.snapshotDelete(name)
91
92
93def remove_file(cfg: Config, conffile: pathlib.Path) -> None:
94 """Remove the StorPool configuration file."""
95 print(f"Removing {conffile}")
96 if not cfg.noop:
97 try:
98 conffile.unlink()
99 except OSError as err:
100 if err.errno != errno.ENOENT:
101 raise
102
103
104def parse_args() -> Config:
105 """Parse the command-line arguments."""
106 parser = argparse.ArgumentParser(prog="sp_rand_cleanup")
107 parser.add_argument(
108 "-d",
109 "--confdir",
110 type=pathlib.Path,
111 default=defs.DEFAULT_CONFDIR,
112 help="The directory to look for the configuration file in",
113 )
114 parser.add_argument(
115 "-N",
116 "--noop",
117 action="store_true",
118 help="No-operation mode; display what would have been done",
119 )
120 parser.add_argument(
121 "--skip-api",
122 type=str,
123 help=(
124 "If explicitly given the 'yes' value, do not "
125 "delete any volumes or snapshots"
126 ),
127 )
128
129 args = parser.parse_args()
130
131 return Config(confdir=args.confdir, noop=args.noop, skip_api=args.skip_api == "yes")
132
133
134def main() -> None:
135 """Main program: parse command-line options, handle them."""
136 cfg = parse_args()
137 conffile, prefix = parse_file(cfg)
138
139 if not cfg.skip_api:
140 api = spapi.Api.fromConfig()
141 remove_volumes(cfg, api, prefix)
142
143 remove_file(cfg, conffile)
144
145
146if __name__ == "__main__":
147 main()