devmapper: Use transaction mechanism during device or snap device creation
Finally this patch uses the notion of transaction for device or snapshot device creation. Following is sequence of event. - Open a trasaction and save details in a file. - Create a new device/snapshot device - If a new device id is used, refresh transaction with new device id details. - Create device metadata file - Close transaction. If docker crashes anywhere in between without closing transaction, then upon next start, docker will figure out that there was a pending transaction and it will roll back transaction. That is it will do following. - Delete Device from pool - Delete device metadata file - Remove transaction file to mark no transaction is pending. Signed-off-by: Vivek Goyal <vgoyal@redhat.com>
This commit is contained in:
parent
e28a419e11
commit
c115c4aa45
1 changed files with 142 additions and 7 deletions
|
@ -36,9 +36,12 @@ var (
|
|||
)
|
||||
|
||||
const deviceSetMetaFile string = "deviceset-metadata"
|
||||
const transactionMetaFile string = "transaction-metadata"
|
||||
|
||||
type Transaction struct {
|
||||
OpenTransactionId uint64 `json:"-"`
|
||||
OpenTransactionId uint64 `json:"open_transaction_id"`
|
||||
DeviceIdHash string `json:"device_hash"`
|
||||
DeviceId int `json:"device_id"`
|
||||
}
|
||||
|
||||
type DevInfo struct {
|
||||
|
@ -149,6 +152,10 @@ func (devices *DeviceSet) metadataFile(info *DevInfo) string {
|
|||
return path.Join(devices.metadataDir(), file)
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) transactionMetaFile() string {
|
||||
return path.Join(devices.metadataDir(), transactionMetaFile)
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) deviceSetMetaFile() string {
|
||||
return path.Join(devices.metadataDir(), deviceSetMetaFile)
|
||||
}
|
||||
|
@ -492,6 +499,10 @@ func (devices *DeviceSet) initMetaData() error {
|
|||
if err := devices.constructDeviceIdMap(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := devices.processPendingTransaction(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -519,6 +530,12 @@ func (devices *DeviceSet) createRegisterDevice(hash string) (*DevInfo, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if err := devices.openTransaction(hash, deviceId); err != nil {
|
||||
log.Debugf("Error opening transaction hash = %s deviceId = %d", hash, deviceId)
|
||||
devices.markDeviceIdFree(deviceId)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for {
|
||||
if err := devicemapper.CreateDevice(devices.getPoolDevName(), deviceId); err != nil {
|
||||
if devicemapper.DeviceIdExists(err) {
|
||||
|
@ -531,6 +548,8 @@ func (devices *DeviceSet) createRegisterDevice(hash string) (*DevInfo, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Save new device id into transaction
|
||||
devices.refreshTransaction(deviceId)
|
||||
continue
|
||||
}
|
||||
log.Debugf("Error creating device: %s", err)
|
||||
|
@ -540,16 +559,15 @@ func (devices *DeviceSet) createRegisterDevice(hash string) (*DevInfo, error) {
|
|||
break
|
||||
}
|
||||
|
||||
transactionId := devices.allocateTransactionId()
|
||||
log.Debugf("Registering device (id %v) with FS size %v", deviceId, devices.baseFsSize)
|
||||
info, err := devices.registerDevice(deviceId, hash, devices.baseFsSize, transactionId)
|
||||
info, err := devices.registerDevice(deviceId, hash, devices.baseFsSize, devices.OpenTransactionId)
|
||||
if err != nil {
|
||||
_ = devicemapper.DeleteDevice(devices.getPoolDevName(), deviceId)
|
||||
devices.markDeviceIdFree(deviceId)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := devices.updatePoolTransactionId(); err != nil {
|
||||
if err := devices.closeTransaction(); err != nil {
|
||||
devices.unregisterDevice(deviceId, hash)
|
||||
devicemapper.DeleteDevice(devices.getPoolDevName(), deviceId)
|
||||
devices.markDeviceIdFree(deviceId)
|
||||
|
@ -564,6 +582,12 @@ func (devices *DeviceSet) createRegisterSnapDevice(hash string, baseInfo *DevInf
|
|||
return err
|
||||
}
|
||||
|
||||
if err := devices.openTransaction(hash, deviceId); err != nil {
|
||||
log.Debugf("Error opening transaction hash = %s deviceId = %d", hash, deviceId)
|
||||
devices.markDeviceIdFree(deviceId)
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
if err := devicemapper.CreateSnapDevice(devices.getPoolDevName(), deviceId, baseInfo.Name(), baseInfo.DeviceId); err != nil {
|
||||
if devicemapper.DeviceIdExists(err) {
|
||||
|
@ -576,6 +600,8 @@ func (devices *DeviceSet) createRegisterSnapDevice(hash string, baseInfo *DevInf
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Save new device id into transaction
|
||||
devices.refreshTransaction(deviceId)
|
||||
continue
|
||||
}
|
||||
log.Debugf("Error creating snap device: %s", err)
|
||||
|
@ -585,15 +611,14 @@ func (devices *DeviceSet) createRegisterSnapDevice(hash string, baseInfo *DevInf
|
|||
break
|
||||
}
|
||||
|
||||
transactionId := devices.allocateTransactionId()
|
||||
if _, err := devices.registerDevice(deviceId, hash, baseInfo.Size, transactionId); err != nil {
|
||||
if _, err := devices.registerDevice(deviceId, hash, baseInfo.Size, devices.OpenTransactionId); err != nil {
|
||||
devicemapper.DeleteDevice(devices.getPoolDevName(), deviceId)
|
||||
devices.markDeviceIdFree(deviceId)
|
||||
log.Debugf("Error registering device: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := devices.updatePoolTransactionId(); err != nil {
|
||||
if err := devices.closeTransaction(); err != nil {
|
||||
devices.unregisterDevice(deviceId, hash)
|
||||
devicemapper.DeleteDevice(devices.getPoolDevName(), deviceId)
|
||||
devices.markDeviceIdFree(deviceId)
|
||||
|
@ -775,6 +800,90 @@ func (devices *DeviceSet) ResizePool(size int64) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) loadTransactionMetaData() error {
|
||||
jsonData, err := ioutil.ReadFile(devices.transactionMetaFile())
|
||||
if err != nil {
|
||||
// There is no active transaction. This will be the case
|
||||
// during upgrade.
|
||||
if os.IsNotExist(err) {
|
||||
devices.OpenTransactionId = devices.TransactionId
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
json.Unmarshal(jsonData, &devices.Transaction)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) saveTransactionMetaData() error {
|
||||
jsonData, err := json.Marshal(&devices.Transaction)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error encoding metadata to json: %s", err)
|
||||
}
|
||||
|
||||
return devices.writeMetaFile(jsonData, devices.transactionMetaFile())
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) removeTransactionMetaData() error {
|
||||
if err := os.RemoveAll(devices.transactionMetaFile()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) rollbackTransaction() error {
|
||||
log.Debugf("Rolling back open transaction: TransactionId=%d hash=%s device_id=%d", devices.OpenTransactionId, devices.DeviceIdHash, devices.DeviceId)
|
||||
|
||||
// A device id might have already been deleted before transaction
|
||||
// closed. In that case this call will fail. Just leave a message
|
||||
// in case of failure.
|
||||
if err := devicemapper.DeleteDevice(devices.getPoolDevName(), devices.DeviceId); err != nil {
|
||||
log.Errorf("Warning: Unable to delete device: %s", err)
|
||||
}
|
||||
|
||||
dinfo := &DevInfo{Hash: devices.DeviceIdHash}
|
||||
if err := devices.removeMetadata(dinfo); err != nil {
|
||||
log.Errorf("Warning: Unable to remove meta data: %s", err)
|
||||
} else {
|
||||
devices.markDeviceIdFree(devices.DeviceId)
|
||||
}
|
||||
|
||||
if err := devices.removeTransactionMetaData(); err != nil {
|
||||
log.Errorf("Warning: Unable to remove transaction meta file %s: %s", devices.transactionMetaFile(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) processPendingTransaction() error {
|
||||
if err := devices.loadTransactionMetaData(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If there was open transaction but pool transaction Id is same
|
||||
// as open transaction Id, nothing to roll back.
|
||||
if devices.TransactionId == devices.OpenTransactionId {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If open transaction Id is less than pool transaction Id, something
|
||||
// is wrong. Bail out.
|
||||
if devices.OpenTransactionId < devices.TransactionId {
|
||||
log.Errorf("Warning: Open Transaction id %d is less than pool transaction id %d", devices.OpenTransactionId, devices.TransactionId)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pool transaction Id is not same as open transaction. There is
|
||||
// a transaction which was not completed.
|
||||
if err := devices.rollbackTransaction(); err != nil {
|
||||
return fmt.Errorf("Rolling back open transaction failed: %s", err)
|
||||
}
|
||||
|
||||
devices.OpenTransactionId = devices.TransactionId
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) loadDeviceSetMetaData() error {
|
||||
jsonData, err := ioutil.ReadFile(devices.deviceSetMetaFile())
|
||||
if err != nil {
|
||||
|
@ -798,6 +907,32 @@ func (devices *DeviceSet) saveDeviceSetMetaData() error {
|
|||
return devices.writeMetaFile(jsonData, devices.deviceSetMetaFile())
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) openTransaction(hash string, DeviceId int) error {
|
||||
devices.allocateTransactionId()
|
||||
devices.DeviceIdHash = hash
|
||||
devices.DeviceId = DeviceId
|
||||
if err := devices.saveTransactionMetaData(); err != nil {
|
||||
return fmt.Errorf("Error saving transaction meta data: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) refreshTransaction(DeviceId int) error {
|
||||
devices.DeviceId = DeviceId
|
||||
if err := devices.saveTransactionMetaData(); err != nil {
|
||||
return fmt.Errorf("Error saving transaction meta data: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) closeTransaction() error {
|
||||
if err := devices.updatePoolTransactionId(); err != nil {
|
||||
log.Debugf("Failed to close Transaction")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) initDevmapper(doInit bool) error {
|
||||
// give ourselves to libdm as a log handler
|
||||
devicemapper.LogInit(devices)
|
||||
|
|
Loading…
Reference in a new issue