RIFT OSM R1 Initial Submission
[osm/SO.git] / rwcal / plugins / vala / rwcal_cloudsim / rift / rwcal / cloudsim / lvm.py
1
2 #
3 # Copyright 2016 RIFT.IO Inc
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 #
17
18 import collections
19 import logging
20 import os
21 import re
22
23 from . import shell
24
25
26 logger = logging.getLogger(__name__)
27
28
29 class PhysicalVolume(
30 collections.namedtuple(
31 "PhysicalVolume", [
32 "pv",
33 "vg",
34 "fmt",
35 "attr",
36 "psize",
37 "pfree",
38 ]
39 )
40 ):
41 pass
42
43
44 class VolumeGroup(
45 collections.namedtuple(
46 "VolumeGroup", [
47 "vg",
48 "num_pv",
49 "num_lv",
50 "num_sn",
51 "attr",
52 "vsize",
53 "vfree",
54 ]
55 )
56 ):
57 pass
58
59
60 class LoopbackVolumeGroup(object):
61 def __init__(self, name):
62 self._name = name
63
64 def __repr__(self):
65 return repr({
66 "name": self.name,
67 "filepath": self.filepath,
68 "loopback": self.loopback,
69 "exists": self.exists,
70 "volume_group": self.volume_group,
71 })
72
73 @property
74 def exists(self):
75 return any(v.vg == self.name for v in volume_groups())
76
77 @property
78 def name(self):
79 return self._name
80
81 @property
82 def filepath(self):
83 return find_backing_file(self.name)
84
85 @property
86 def loopback(self):
87 return find_loop_device(self.name)
88
89 @property
90 def volume_group(self):
91 for vgroup in volume_groups():
92 if vgroup.vg == self.name:
93 return vgroup
94
95 @property
96 def physical_volume(self):
97 for pvolume in physical_volumes():
98 if pvolume.vg == self.name:
99 return pvolume
100
101 @property
102 def size(self):
103 return os.path.getsize(self.filepath)
104
105 def extend_mbytes(self, num_mbytes):
106 """ Extend the size of the Loopback volume group
107
108 Arguments:
109 num_bytes - Number of megabytes to extend by
110 """
111
112 # Extend the size of the backing store
113 shell.command('truncate -c -s +{}M {}'.format(
114 num_mbytes, self.filepath)
115 )
116
117 # Notify loopback driver of the resized backing store
118 shell.command('losetup -c {}'.format(self.loopback))
119
120 # Expand the physical volume to match new size
121 shell.command('pvresize {}'.format(self.physical_volume.pv))
122
123
124 def find_loop_device(volume):
125 pvolumes = physical_volumes()
126 for pvolume in pvolumes:
127 if pvolume.vg == volume:
128 return pvolume.pv
129
130 return None
131
132
133 def find_backing_file(volume):
134 """
135 /dev/loop0: [64513]:414503 (/lvm/rift.img)
136
137 """
138 loop = find_loop_device(volume)
139 if loop is None:
140 return None
141
142 output = shell.command("losetup {}".format(loop))[0]
143 return re.search('.*\(([^)]*)\).*', output).group(1)
144
145
146 def create(volume="rift", filepath="/lvm/rift.img"):
147 """
148 First, we create a loopback device using a file that we put in the file
149 system where running this from. Second, we create an LVM volume group onto
150 the loop device that was just created
151 """
152 pvolumes = physical_volumes()
153 for pvolume in pvolumes:
154 if pvolume.vg == volume:
155 raise ValueError("VolumeGroup %s already exists" % volume)
156
157 # Delete the existing backing file if it exists
158 if os.path.exists(filepath):
159 os.remove(filepath)
160
161 # Create the file that will be used as the backing store
162 if not os.path.exists(os.path.dirname(filepath)):
163 os.makedirs(os.path.dirname(filepath))
164
165 # Create a minimal file to hold any LVM physical volume metadata
166 shell.command('truncate -s 50M {}'.format(filepath))
167
168 # Acquire the next available loopback device
169 loopback = shell.command('losetup -f --show {}'.format(filepath))[0]
170
171 # Create a physical volume
172 shell.command('pvcreate {}'.format(loopback))
173
174 # Create a volume group
175 shell.command('vgcreate {} {}'.format(volume, loopback))
176
177 return LoopbackVolumeGroup(volume)
178
179
180 def get(volume="rift"):
181 pvolumes = physical_volumes()
182 for pvolume in pvolumes:
183 if pvolume.vg == volume:
184 return LoopbackVolumeGroup(pvolume.vg)
185
186
187 def destroy(volume="rift"):
188 pvolumes = physical_volumes()
189 for pvolume in pvolumes:
190 if pvolume.vg == volume:
191 break
192 else:
193 return
194
195 # Cache the backing file path
196 filepath = find_backing_file(volume)
197
198 # Remove the volume group
199 shell.command('vgremove -f {}'.format(pvolume.vg))
200
201 # Remove the physical volume
202 shell.command('pvremove -y {}'.format(pvolume.pv))
203
204 # Release the loopback device
205 shell.command('losetup -d {}'.format(pvolume.pv))
206
207 # Remove the backing file
208 os.remove(filepath)
209
210
211 def physical_volumes():
212 """Returns a list of physical volumes"""
213 cmd = 'pvs --separator "," --rows'
214 lines = [line.strip().split(',') for line in shell.command(cmd)]
215 if not lines:
216 return []
217
218 mapping = {
219 "PV": "pv",
220 "VG": "vg",
221 "Fmt": "fmt",
222 "Attr": "attr",
223 "PSize": "psize",
224 "PFree": "pfree",
225 }
226
227 # Transpose the data so that the first element of the list is a list of
228 # keys.
229 transpose = list(map(list, zip(*lines)))
230
231 # Extract keys
232 keys = transpose[0]
233
234 # Iterate over the remaining data and create the physical volume objects
235 volumes = []
236 for values in transpose[1:]:
237 volume = {}
238 for k, v in zip(keys, values):
239 volume[mapping[k]] = v
240
241 volumes.append(PhysicalVolume(**volume))
242
243 return volumes
244
245
246 def volume_groups():
247 """Returns a list of volume groups"""
248 cmd = 'vgs --separator "," --rows'
249 lines = [line.strip().split(',') for line in shell.command(cmd)]
250 if not lines:
251 return []
252
253 mapping = {
254 "VG": "vg",
255 "#PV": "num_pv",
256 "#LV": "num_lv",
257 "#SN": "num_sn",
258 "Attr": "attr",
259 "VSize": "vsize",
260 "VFree": "vfree",
261 }
262
263 # Transpose the data so that the first element of the list is a list of
264 # keys.
265 transpose = list(map(list, zip(*lines)))
266
267 # Extract keys
268 keys = transpose[0]
269
270 # Iterate over the remaining data and create the volume groups
271 groups = []
272 for values in transpose[1:]:
273 group = {}
274 for k, v in zip(keys, values):
275 group[mapping[k]] = v
276
277 groups.append(VolumeGroup(**group))
278
279 return groups
280