3 # Copyright 2016 RIFT.IO Inc
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
26 logger
= logging
.getLogger(__name__
)
30 collections
.namedtuple(
45 collections
.namedtuple(
60 class LoopbackVolumeGroup(object):
61 def __init__(self
, name
):
67 "filepath": self
.filepath
,
68 "loopback": self
.loopback
,
69 "exists": self
.exists
,
70 "volume_group": self
.volume_group
,
75 return any(v
.vg
== self
.name
for v
in volume_groups())
83 return find_backing_file(self
.name
)
87 return find_loop_device(self
.name
)
90 def volume_group(self
):
91 for vgroup
in volume_groups():
92 if vgroup
.vg
== self
.name
:
96 def physical_volume(self
):
97 for pvolume
in physical_volumes():
98 if pvolume
.vg
== self
.name
:
103 return os
.path
.getsize(self
.filepath
)
105 def extend_mbytes(self
, num_mbytes
):
106 """ Extend the size of the Loopback volume group
109 num_bytes - Number of megabytes to extend by
112 # Extend the size of the backing store
113 shell
.command('truncate -c -s +{}M {}'.format(
114 num_mbytes
, self
.filepath
)
117 # Notify loopback driver of the resized backing store
118 shell
.command('losetup -c {}'.format(self
.loopback
))
120 # Expand the physical volume to match new size
121 shell
.command('pvresize {}'.format(self
.physical_volume
.pv
))
124 def find_loop_device(volume
):
125 pvolumes
= physical_volumes()
126 for pvolume
in pvolumes
:
127 if pvolume
.vg
== volume
:
133 def find_backing_file(volume
):
135 /dev/loop0: [64513]:414503 (/lvm/rift.img)
138 loop
= find_loop_device(volume
)
142 output
= shell
.command("losetup {}".format(loop
))[0]
143 return re
.search('.*\(([^)]*)\).*', output
).group(1)
146 def create(volume
="rift", filepath
="/lvm/rift.img"):
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
152 pvolumes
= physical_volumes()
153 for pvolume
in pvolumes
:
154 if pvolume
.vg
== volume
:
155 raise ValueError("VolumeGroup %s already exists" % volume
)
157 # Delete the existing backing file if it exists
158 if os
.path
.exists(filepath
):
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
))
165 # Create a minimal file to hold any LVM physical volume metadata
166 shell
.command('truncate -s 50M {}'.format(filepath
))
168 # Acquire the next available loopback device
169 loopback
= shell
.command('losetup -f --show {}'.format(filepath
))[0]
171 # Create a physical volume
172 shell
.command('pvcreate {}'.format(loopback
))
174 # Create a volume group
175 shell
.command('vgcreate {} {}'.format(volume
, loopback
))
177 return LoopbackVolumeGroup(volume
)
180 def get(volume
="rift"):
181 pvolumes
= physical_volumes()
182 for pvolume
in pvolumes
:
183 if pvolume
.vg
== volume
:
184 return LoopbackVolumeGroup(pvolume
.vg
)
187 def destroy(volume
="rift"):
188 pvolumes
= physical_volumes()
189 for pvolume
in pvolumes
:
190 if pvolume
.vg
== volume
:
195 # Cache the backing file path
196 filepath
= find_backing_file(volume
)
198 # Remove the volume group
199 shell
.command('vgremove -f {}'.format(pvolume
.vg
))
201 # Remove the physical volume
202 shell
.command('pvremove -y {}'.format(pvolume
.pv
))
204 # Release the loopback device
205 shell
.command('losetup -d {}'.format(pvolume
.pv
))
207 # Remove the backing file
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
)]
227 # Transpose the data so that the first element of the list is a list of
229 transpose
= list(map(list, zip(*lines
)))
234 # Iterate over the remaining data and create the physical volume objects
236 for values
in transpose
[1:]:
238 for k
, v
in zip(keys
, values
):
239 volume
[mapping
[k
]] = v
241 volumes
.append(PhysicalVolume(**volume
))
247 """Returns a list of volume groups"""
248 cmd
= 'vgs --separator "," --rows'
249 lines
= [line
.strip().split(',') for line
in shell
.command(cmd
)]
263 # Transpose the data so that the first element of the list is a list of
265 transpose
= list(map(list, zip(*lines
)))
270 # Iterate over the remaining data and create the volume groups
272 for values
in transpose
[1:]:
274 for k
, v
in zip(keys
, values
):
275 group
[mapping
[k
]] = v
277 groups
.append(VolumeGroup(**group
))