Skip to content

Commit 3a75882

Browse files
committed
devmapper: add pool device manager
Signed-off-by: Maksym Pavlenko <[email protected]>
1 parent 6e0ae68 commit 3a75882

2 files changed

Lines changed: 552 additions & 0 deletions

File tree

snapshots/devmapper/pool_device.go

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package devmapper
18+
19+
import (
20+
"context"
21+
"os"
22+
"path/filepath"
23+
24+
"github.com/containerd/containerd/log"
25+
"github.com/containerd/containerd/snapshots/devmapper/dmsetup"
26+
"github.com/hashicorp/go-multierror"
27+
"github.com/pkg/errors"
28+
)
29+
30+
// PoolDevice ties together data and metadata volumes, represents thin-pool and manages volumes, snapshots and device ids.
31+
type PoolDevice struct {
32+
poolName string
33+
metadata *PoolMetadata
34+
}
35+
36+
// NewPoolDevice creates new thin-pool from existing data and metadata volumes.
37+
// If pool 'poolName' already exists, it'll be reloaded with new parameters.
38+
func NewPoolDevice(ctx context.Context, config *Config) (*PoolDevice, error) {
39+
log.G(ctx).Infof("initializing pool device %q", config.PoolName)
40+
41+
version, err := dmsetup.Version()
42+
if err != nil {
43+
log.G(ctx).Errorf("dmsetup not available")
44+
return nil, err
45+
}
46+
47+
log.G(ctx).Infof("using dmsetup:\n%s", version)
48+
49+
dbpath := filepath.Join(config.RootPath, config.PoolName+".db")
50+
poolMetaStore, err := NewPoolMetadata(dbpath)
51+
if err != nil {
52+
return nil, err
53+
}
54+
55+
if err := openPool(ctx, config); err != nil {
56+
return nil, err
57+
}
58+
59+
return &PoolDevice{
60+
poolName: config.PoolName,
61+
metadata: poolMetaStore,
62+
}, nil
63+
}
64+
65+
func openPool(ctx context.Context, config *Config) error {
66+
if err := config.Validate(); err != nil {
67+
return err
68+
}
69+
70+
var (
71+
poolPath = dmsetup.GetFullDevicePath(config.PoolName)
72+
poolExists = false
73+
)
74+
75+
if _, err := os.Stat(poolPath); err != nil && !os.IsNotExist(err) {
76+
return errors.Wrapf(err, "failed to stat for %q", poolPath)
77+
} else if err == nil {
78+
poolExists = true
79+
}
80+
81+
// Create new pool if not exists
82+
if !poolExists {
83+
log.G(ctx).Debug("creating new pool device")
84+
if err := dmsetup.CreatePool(config.PoolName, config.DataDevice, config.MetadataDevice, config.DataBlockSizeSectors); err != nil {
85+
return errors.Wrapf(err, "failed to create thin-pool with name %q", config.PoolName)
86+
}
87+
88+
return nil
89+
}
90+
91+
// Pool exists, check if it needs to be reloaded
92+
if config.DataDevice != "" && config.MetadataDevice != "" {
93+
log.G(ctx).Debugf("reloading existing pool %q", poolPath)
94+
if err := dmsetup.ReloadPool(config.PoolName, config.DataDevice, config.MetadataDevice, config.DataBlockSizeSectors); err != nil {
95+
return errors.Wrapf(err, "failed to reload pool %q", config.PoolName)
96+
}
97+
98+
return nil
99+
}
100+
101+
// If data and meta devices are not provided, use existing pool. Query info to make sure it's OK.
102+
if _, err := dmsetup.Info(poolPath); err != nil {
103+
return errors.Wrapf(err, "failed to query info for existing pool %q", poolPath)
104+
}
105+
106+
return nil
107+
}
108+
109+
// transition invokes 'updateStateFn' callback to perform devmapper operation and reflects device state changes/errors in meta store.
110+
// 'tryingState' will be set before invoking callback. If callback succeeded 'successState' will be set, otherwise
111+
// error details will be recorded in meta store.
112+
func (p *PoolDevice) transition(ctx context.Context, deviceName string, tryingState DeviceState, successState DeviceState, updateStateFn func() error) error {
113+
// Set device to trying state
114+
uerr := p.metadata.UpdateDevice(ctx, deviceName, func(deviceInfo *DeviceInfo) error {
115+
deviceInfo.State = tryingState
116+
return nil
117+
})
118+
119+
if uerr != nil {
120+
return errors.Wrapf(uerr, "failed to set device %q state to %q", deviceName, tryingState)
121+
}
122+
123+
var result *multierror.Error
124+
125+
// Invoke devmapper operation
126+
err := updateStateFn()
127+
128+
if err != nil {
129+
result = multierror.Append(result, err)
130+
}
131+
132+
// If operation succeeded transition to success state, otherwise save error details
133+
uerr = p.metadata.UpdateDevice(ctx, deviceName, func(deviceInfo *DeviceInfo) error {
134+
if err == nil {
135+
deviceInfo.State = successState
136+
deviceInfo.Error = ""
137+
} else {
138+
deviceInfo.Error = err.Error()
139+
}
140+
return nil
141+
})
142+
143+
if uerr != nil {
144+
result = multierror.Append(result, uerr)
145+
}
146+
147+
return result.ErrorOrNil()
148+
}
149+
150+
// CreateThinDevice creates new devmapper thin-device with given name and size.
151+
// Device ID for thin-device will be allocated from metadata store.
152+
// If allocation successful, device will be activated with /dev/mapper/<deviceName>
153+
func (p *PoolDevice) CreateThinDevice(ctx context.Context, deviceName string, virtualSizeBytes uint64) error {
154+
info := &DeviceInfo{
155+
Name: deviceName,
156+
Size: virtualSizeBytes,
157+
State: Unknown,
158+
}
159+
160+
// Save initial device metadata and allocate new device ID from store
161+
if err := p.metadata.AddDevice(ctx, info); err != nil {
162+
return errors.Wrapf(err, "failed to save initial metadata for new thin device %q", deviceName)
163+
}
164+
165+
// Create thin device
166+
if err := p.transition(ctx, deviceName, Creating, Created, func() error {
167+
return dmsetup.CreateDevice(p.poolName, info.DeviceID)
168+
}); err != nil {
169+
return errors.Wrapf(err, "failed to create new thin device %q (dev: %d)", info.Name, info.DeviceID)
170+
}
171+
172+
// Activate thin device
173+
if err := p.transition(ctx, deviceName, Activating, Activated, func() error {
174+
return dmsetup.ActivateDevice(p.poolName, info.Name, info.DeviceID, info.Size, "")
175+
}); err != nil {
176+
return errors.Wrapf(err, "failed to activate new thin device %q (dev: %d)", info.Name, info.DeviceID)
177+
}
178+
179+
return nil
180+
}
181+
182+
// CreateSnapshotDevice creates and activates new thin-device from parent thin-device (makes snapshot)
183+
func (p *PoolDevice) CreateSnapshotDevice(ctx context.Context, deviceName string, snapshotName string, virtualSizeBytes uint64) error {
184+
baseInfo, err := p.metadata.GetDevice(ctx, deviceName)
185+
if err != nil {
186+
return errors.Wrapf(err, "failed to query device metadata for %q", deviceName)
187+
}
188+
189+
isActivated := baseInfo.State == Activated
190+
191+
// Suspend thin device if it was activated previously
192+
if isActivated {
193+
if err := p.transition(ctx, baseInfo.Name, Suspending, Suspended, func() error {
194+
return dmsetup.SuspendDevice(baseInfo.Name)
195+
}); err != nil {
196+
return errors.Wrapf(err, "failed to suspend device %q", baseInfo.Name)
197+
}
198+
}
199+
200+
snapInfo := &DeviceInfo{
201+
Name: snapshotName,
202+
Size: virtualSizeBytes,
203+
ParentName: deviceName,
204+
State: Unknown,
205+
}
206+
207+
// Save snapshot metadata and allocate new device ID
208+
if err := p.metadata.AddDevice(ctx, snapInfo); err != nil {
209+
return errors.Wrapf(err, "failed to save initial metadata for snapshot %q", snapshotName)
210+
}
211+
212+
// Create thin device snapshot
213+
if err := p.transition(ctx, snapInfo.Name, Creating, Created, func() error {
214+
return dmsetup.CreateSnapshot(p.poolName, snapInfo.DeviceID, baseInfo.DeviceID)
215+
}); err != nil {
216+
return errors.Wrapf(err,
217+
"failed to create snapshot %q (dev: %d) from %q (dev: %d, activated: %t)",
218+
snapInfo.Name,
219+
snapInfo.DeviceID,
220+
baseInfo.Name,
221+
baseInfo.DeviceID,
222+
isActivated)
223+
}
224+
225+
if isActivated {
226+
// Resume base thin-device
227+
if err := p.transition(ctx, baseInfo.Name, Resuming, Resumed, func() error {
228+
return dmsetup.ResumeDevice(baseInfo.Name)
229+
}); err != nil {
230+
return errors.Wrapf(err, "failed to resume device %q", deviceName)
231+
}
232+
}
233+
234+
// Activate snapshot
235+
if err := p.transition(ctx, snapInfo.Name, Activating, Activated, func() error {
236+
return dmsetup.ActivateDevice(p.poolName, snapInfo.Name, snapInfo.DeviceID, snapInfo.Size, "")
237+
}); err != nil {
238+
return errors.Wrapf(err, "failed to activate snapshot device %q (dev: %d)", snapInfo.Name, snapInfo.DeviceID)
239+
}
240+
241+
return nil
242+
}
243+
244+
// DeactivateDevice deactivates thin device
245+
func (p *PoolDevice) DeactivateDevice(ctx context.Context, deviceName string, deferred bool) error {
246+
devicePath := dmsetup.GetFullDevicePath(deviceName)
247+
if _, err := os.Stat(devicePath); err != nil {
248+
if os.IsNotExist(err) {
249+
return ErrNotFound
250+
}
251+
252+
return err
253+
}
254+
255+
opts := []dmsetup.RemoveDeviceOpt{dmsetup.RemoveWithForce, dmsetup.RemoveWithRetries}
256+
if deferred {
257+
opts = append(opts, dmsetup.RemoveDeferred)
258+
}
259+
260+
if err := p.transition(ctx, deviceName, Deactivating, Deactivated, func() error {
261+
return dmsetup.RemoveDevice(deviceName, opts...)
262+
}); err != nil {
263+
return errors.Wrapf(err, "failed to deactivate device %q", deviceName)
264+
}
265+
266+
return nil
267+
}
268+
269+
// RemoveDevice completely wipes out thin device from thin-pool and frees it's device ID
270+
func (p *PoolDevice) RemoveDevice(ctx context.Context, deviceName string) error {
271+
info, err := p.metadata.GetDevice(ctx, deviceName)
272+
if err != nil {
273+
return errors.Wrapf(err, "can't query metadata for device %q", deviceName)
274+
}
275+
276+
if err := p.DeactivateDevice(ctx, deviceName, true); err != nil && err != ErrNotFound {
277+
return err
278+
}
279+
280+
if err := p.transition(ctx, deviceName, Removing, Removed, func() error {
281+
// Send 'delete' message to thin-pool
282+
return dmsetup.DeleteDevice(p.poolName, info.DeviceID)
283+
}); err != nil {
284+
return errors.Wrapf(err, "failed to delete device %q (dev id: %d)", info.Name, info.DeviceID)
285+
}
286+
287+
// Remove record from meta store and free device ID
288+
if err := p.metadata.RemoveDevice(ctx, deviceName); err != nil {
289+
return errors.Wrapf(err, "can't remove device %q metadata from store after removal", deviceName)
290+
}
291+
292+
return nil
293+
}
294+
295+
// RemovePool deactivates all child thin-devices and removes thin-pool device
296+
func (p *PoolDevice) RemovePool(ctx context.Context) error {
297+
deviceNames, err := p.metadata.GetDeviceNames(ctx)
298+
if err != nil {
299+
return errors.Wrap(err, "can't query device names")
300+
}
301+
302+
var result *multierror.Error
303+
304+
// Deactivate devices if any
305+
for _, name := range deviceNames {
306+
if err := p.DeactivateDevice(ctx, name, true); err != nil && err != ErrNotFound {
307+
result = multierror.Append(result, errors.Wrapf(err, "failed to remove %q", name))
308+
}
309+
}
310+
311+
if err := dmsetup.RemoveDevice(p.poolName, dmsetup.RemoveWithForce, dmsetup.RemoveWithRetries, dmsetup.RemoveDeferred); err != nil {
312+
result = multierror.Append(result, errors.Wrapf(err, "failed to remove pool %q", p.poolName))
313+
}
314+
315+
return result.ErrorOrNil()
316+
}
317+
318+
// Close closes pool device (thin-pool will not be removed)
319+
func (p *PoolDevice) Close() error {
320+
return p.metadata.Close()
321+
}

0 commit comments

Comments
 (0)