Pārlūkot izejas kodu

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>
Vivek Goyal 10 gadi atpakaļ
vecāks
revīzija
c115c4aa45
1 mainītis faili ar 142 papildinājumiem un 7 dzēšanām
  1. 142 7
      daemon/graphdriver/devmapper/deviceset.go

+ 142 - 7
daemon/graphdriver/devmapper/deviceset.go

@@ -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)