// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package org.apache.cloudstack.storage.datastore; import javax.inject.Inject; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; import org.apache.cloudstack.engine.subsystem.api.storage.DataObjectInStore; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.State; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao; import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO; import org.apache.cloudstack.storage.db.ObjectInDataStoreDao; import com.cloud.agent.api.to.DataObjectType; import com.cloud.agent.api.to.S3TO; import com.cloud.exception.ConcurrentOperationException; import com.cloud.storage.DataStoreRole; import com.cloud.storage.SnapshotVO; import com.cloud.storage.VMTemplateStoragePoolVO; import com.cloud.storage.dao.SnapshotDao; import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VMTemplatePoolDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.storage.template.TemplateConstants; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.fsm.NoTransitionException; import com.cloud.utils.fsm.StateMachine2; @Component public class ObjectInDataStoreManagerImpl implements ObjectInDataStoreManager { private static final Logger s_logger = Logger.getLogger(ObjectInDataStoreManagerImpl.class); @Inject TemplateDataFactory imageFactory; @Inject DataStoreManager storeMgr; @Inject VolumeDataFactory volumeFactory; @Inject TemplateDataStoreDao templateDataStoreDao; @Inject SnapshotDataStoreDao snapshotDataStoreDao; @Inject VolumeDataStoreDao volumeDataStoreDao; @Inject VMTemplatePoolDao templatePoolDao; @Inject SnapshotDataFactory snapshotFactory; @Inject ObjectInDataStoreDao objInStoreDao; @Inject VMTemplateDao templateDao; @Inject SnapshotDao snapshotDao; @Inject VolumeDao volumeDao; protected StateMachine2<State, Event, DataObjectInStore> stateMachines; public ObjectInDataStoreManagerImpl() { stateMachines = new StateMachine2<State, Event, DataObjectInStore>(); stateMachines.addTransition(State.Allocated, Event.CreateOnlyRequested, State.Creating); stateMachines.addTransition(State.Allocated, Event.DestroyRequested, State.Destroying); stateMachines.addTransition(State.Allocated, Event.OperationFailed, State.Failed); stateMachines.addTransition(State.Creating, Event.OperationFailed, State.Allocated); stateMachines.addTransition(State.Creating, Event.OperationSuccessed, State.Ready); stateMachines.addTransition(State.Ready, Event.CopyingRequested, State.Copying); stateMachines.addTransition(State.Copying, Event.OperationSuccessed, State.Ready); stateMachines.addTransition(State.Copying, Event.OperationFailed, State.Ready); stateMachines.addTransition(State.Ready, Event.DestroyRequested, State.Destroying); stateMachines.addTransition(State.Destroying, Event.DestroyRequested, State.Destroying); stateMachines.addTransition(State.Destroying, Event.OperationSuccessed, State.Destroyed); stateMachines.addTransition(State.Destroying, Event.OperationFailed, State.Destroying); stateMachines.addTransition(State.Failed, Event.DestroyRequested, State.Destroying); // TODO: further investigate why an extra event is sent when it is // alreay Ready for DownloadListener stateMachines.addTransition(State.Ready, Event.OperationSuccessed, State.Ready); } @Override public DataObject create(DataObject obj, DataStore dataStore) { if (dataStore.getRole() == DataStoreRole.Primary) { if (obj.getType() == DataObjectType.TEMPLATE) { VMTemplateStoragePoolVO vo = new VMTemplateStoragePoolVO(dataStore.getId(), obj.getId()); vo = templatePoolDao.persist(vo); } else if (obj.getType() == DataObjectType.SNAPSHOT) { SnapshotInfo snapshotInfo = (SnapshotInfo)obj; SnapshotDataStoreVO ss = new SnapshotDataStoreVO(); ss.setSnapshotId(obj.getId()); ss.setDataStoreId(dataStore.getId()); ss.setRole(dataStore.getRole()); ss.setVolumeId(snapshotInfo.getVolumeId()); ss.setSize(snapshotInfo.getSize()); // this is the virtual size of snapshot in primary storage. ss.setPhysicalSize(snapshotInfo.getSize()); // this physical size will get updated with actual size once the snapshot backup is done. SnapshotDataStoreVO snapshotDataStoreVO = snapshotDataStoreDao.findParent(dataStore.getRole(), dataStore.getId(), snapshotInfo.getVolumeId()); if (snapshotDataStoreVO != null) { //Double check the snapshot is removed or not SnapshotVO parentSnap = snapshotDao.findById(snapshotDataStoreVO.getSnapshotId()); if (parentSnap != null) { ss.setParentSnapshotId(snapshotDataStoreVO.getSnapshotId()); } else { s_logger.debug("find inconsistent db for snapshot " + snapshotDataStoreVO.getSnapshotId()); } } ss.setState(ObjectInDataStoreStateMachine.State.Allocated); ss = snapshotDataStoreDao.persist(ss); } } else { // Image store switch (obj.getType()) { case TEMPLATE: TemplateDataStoreVO ts = new TemplateDataStoreVO(); ts.setTemplateId(obj.getId()); ts.setDataStoreId(dataStore.getId()); ts.setDataStoreRole(dataStore.getRole()); String installPath = TemplateConstants.DEFAULT_TMPLT_ROOT_DIR + "/" + TemplateConstants.DEFAULT_TMPLT_FIRST_LEVEL_DIR + templateDao.findById(obj.getId()).getAccountId() + "/" + obj.getId(); if (dataStore.getTO() instanceof S3TO) { TemplateInfo tmpl = (TemplateInfo)obj; installPath += "/" + tmpl.getUniqueName(); // for S3, we // append // template name // in the path // for template // sync since we // don't have // template.properties // there } ts.setInstallPath(installPath); ts.setState(ObjectInDataStoreStateMachine.State.Allocated); ts = templateDataStoreDao.persist(ts); break; case SNAPSHOT: SnapshotInfo snapshot = (SnapshotInfo)obj; SnapshotDataStoreVO ss = new SnapshotDataStoreVO(); ss.setSnapshotId(obj.getId()); ss.setDataStoreId(dataStore.getId()); ss.setRole(dataStore.getRole()); ss.setSize(snapshot.getSize()); ss.setVolumeId(snapshot.getVolumeId()); SnapshotDataStoreVO snapshotDataStoreVO = snapshotDataStoreDao.findParent(dataStore.getRole(), dataStore.getId(), snapshot.getVolumeId()); if (snapshotDataStoreVO != null) { ss.setParentSnapshotId(snapshotDataStoreVO.getSnapshotId()); } ss.setInstallPath(TemplateConstants.DEFAULT_SNAPSHOT_ROOT_DIR + "/" + snapshotDao.findById(obj.getId()).getAccountId() + "/" + snapshot.getVolumeId()); ss.setState(ObjectInDataStoreStateMachine.State.Allocated); ss = snapshotDataStoreDao.persist(ss); break; case VOLUME: VolumeDataStoreVO vs = new VolumeDataStoreVO(); vs.setVolumeId(obj.getId()); vs.setDataStoreId(dataStore.getId()); vs.setInstallPath(TemplateConstants.DEFAULT_VOLUME_ROOT_DIR + "/" + volumeDao.findById(obj.getId()).getAccountId() + "/" + obj.getId()); vs.setState(ObjectInDataStoreStateMachine.State.Allocated); vs = volumeDataStoreDao.persist(vs); break; } } return this.get(obj, dataStore); } @Override public boolean delete(DataObject dataObj) { long objId = dataObj.getId(); DataStore dataStore = dataObj.getDataStore(); if (dataStore.getRole() == DataStoreRole.Primary) { if (dataObj.getType() == DataObjectType.TEMPLATE) { VMTemplateStoragePoolVO destTmpltPool = templatePoolDao.findByPoolTemplate(dataStore.getId(), objId); if (destTmpltPool != null) { return templatePoolDao.remove(destTmpltPool.getId()); } else { s_logger.warn("Template " + objId + " is not found on storage pool " + dataStore.getId() + ", so no need to delete"); return true; } } } else { // Image store switch (dataObj.getType()) { case TEMPLATE: TemplateDataStoreVO destTmpltStore = templateDataStoreDao.findByStoreTemplate(dataStore.getId(), objId); if (destTmpltStore != null) { return templateDataStoreDao.remove(destTmpltStore.getId()); } else { s_logger.warn("Template " + objId + " is not found on image store " + dataStore.getId() + ", so no need to delete"); return true; } case SNAPSHOT: SnapshotDataStoreVO destSnapshotStore = snapshotDataStoreDao.findByStoreSnapshot(dataStore.getRole(), dataStore.getId(), objId); if (destSnapshotStore != null) { return snapshotDataStoreDao.remove(destSnapshotStore.getId()); } else { s_logger.warn("Snapshot " + objId + " is not found on image store " + dataStore.getId() + ", so no need to delete"); return true; } case VOLUME: VolumeDataStoreVO destVolumeStore = volumeDataStoreDao.findByStoreVolume(dataStore.getId(), objId); if (destVolumeStore != null) { return volumeDataStoreDao.remove(destVolumeStore.getId()); } else { s_logger.warn("Volume " + objId + " is not found on image store " + dataStore.getId() + ", so no need to delete"); return true; } } } s_logger.warn("Unsupported data object (" + dataObj.getType() + ", " + dataObj.getDataStore() + ")"); return false; } @Override public boolean deleteIfNotReady(DataObject dataObj) { long objId = dataObj.getId(); DataStore dataStore = dataObj.getDataStore(); if (dataStore.getRole() == DataStoreRole.Primary) { if (dataObj.getType() == DataObjectType.TEMPLATE) { VMTemplateStoragePoolVO destTmpltPool = templatePoolDao.findByPoolTemplate(dataStore.getId(), objId); if (destTmpltPool != null && destTmpltPool.getState() != ObjectInDataStoreStateMachine.State.Ready) { return templatePoolDao.remove(destTmpltPool.getId()); } else { s_logger.warn("Template " + objId + " is not found on storage pool " + dataStore.getId() + ", so no need to delete"); return true; } } else if (dataObj.getType() == DataObjectType.SNAPSHOT) { SnapshotDataStoreVO destSnapshotStore = snapshotDataStoreDao.findByStoreSnapshot(dataStore.getRole(), dataStore.getId(), objId); if (destSnapshotStore != null && destSnapshotStore.getState() != ObjectInDataStoreStateMachine.State.Ready) { snapshotDataStoreDao.remove(destSnapshotStore.getId()); } return true; } } else { // Image store switch (dataObj.getType()) { case TEMPLATE: return true; case SNAPSHOT: SnapshotDataStoreVO destSnapshotStore = snapshotDataStoreDao.findByStoreSnapshot(dataStore.getRole(), dataStore.getId(), objId); if (destSnapshotStore != null && destSnapshotStore.getState() != ObjectInDataStoreStateMachine.State.Ready) { return snapshotDataStoreDao.remove(destSnapshotStore.getId()); } else { s_logger.warn("Snapshot " + objId + " is not found on image store " + dataStore.getId() + ", so no need to delete"); return true; } case VOLUME: VolumeDataStoreVO destVolumeStore = volumeDataStoreDao.findByStoreVolume(dataStore.getId(), objId); if (destVolumeStore != null && destVolumeStore.getState() != ObjectInDataStoreStateMachine.State.Ready) { return volumeDataStoreDao.remove(destVolumeStore.getId()); } else { s_logger.warn("Volume " + objId + " is not found on image store " + dataStore.getId() + ", so no need to delete"); return true; } } } s_logger.warn("Unsupported data object (" + dataObj.getType() + ", " + dataObj.getDataStore() + "), no need to delete from object in store ref table"); return false; } @Override public boolean update(DataObject data, Event event) throws NoTransitionException, ConcurrentOperationException { DataObjectInStore obj = this.findObject(data, data.getDataStore()); if (obj == null) { throw new CloudRuntimeException("can't find mapping in ObjectInDataStore table for: " + data); } boolean result = true; if (data.getDataStore().getRole() == DataStoreRole.Image || data.getDataStore().getRole() == DataStoreRole.ImageCache) { switch (data.getType()) { case TEMPLATE: result = this.stateMachines.transitTo(obj, event, null, templateDataStoreDao); break; case SNAPSHOT: result = this.stateMachines.transitTo(obj, event, null, snapshotDataStoreDao); break; case VOLUME: result = this.stateMachines.transitTo(obj, event, null, volumeDataStoreDao); break; } } else if (data.getType() == DataObjectType.TEMPLATE && data.getDataStore().getRole() == DataStoreRole.Primary) { result = this.stateMachines.transitTo(obj, event, null, templatePoolDao); } else if (data.getType() == DataObjectType.SNAPSHOT && data.getDataStore().getRole() == DataStoreRole.Primary) { result = this.stateMachines.transitTo(obj, event, null, snapshotDataStoreDao); } else { throw new CloudRuntimeException("Invalid data or store type: " + data.getType() + " " + data.getDataStore().getRole()); } if (!result) { throw new ConcurrentOperationException("Multiple threads are trying to update data object state, racing condition"); } return true; } @Override public DataObject get(DataObject dataObj, DataStore store) { if (dataObj.getType() == DataObjectType.TEMPLATE) { return imageFactory.getTemplate(dataObj, store); } else if (dataObj.getType() == DataObjectType.VOLUME) { return volumeFactory.getVolume(dataObj, store); } else if (dataObj.getType() == DataObjectType.SNAPSHOT) { return snapshotFactory.getSnapshot(dataObj, store); } throw new CloudRuntimeException("unknown type"); } @Override public DataObjectInStore findObject(DataObject obj, DataStore store) { return findObject(obj.getId(), obj.getType(), store.getId(), store.getRole()); } @Override public DataObjectInStore findObject(long objId, DataObjectType type, long dataStoreId, DataStoreRole role) { DataObjectInStore vo = null; if (role == DataStoreRole.Image || role == DataStoreRole.ImageCache) { switch (type) { case TEMPLATE: vo = templateDataStoreDao.findByStoreTemplate(dataStoreId, objId); break; case SNAPSHOT: vo = snapshotDataStoreDao.findByStoreSnapshot(role, dataStoreId, objId); break; case VOLUME: vo = volumeDataStoreDao.findByStoreVolume(dataStoreId, objId); break; } } else if (type == DataObjectType.TEMPLATE && role == DataStoreRole.Primary) { vo = templatePoolDao.findByPoolTemplate(dataStoreId, objId); } else if (type == DataObjectType.SNAPSHOT && role == DataStoreRole.Primary) { vo = snapshotDataStoreDao.findByStoreSnapshot(role, dataStoreId, objId); } else { s_logger.debug("Invalid data or store type: " + type + " " + role); throw new CloudRuntimeException("Invalid data or store type: " + type + " " + role); } return vo; } @Override public DataStore findStore(long objId, DataObjectType type, DataStoreRole role) { DataStore store = null; if (role == DataStoreRole.Image) { DataObjectInStore vo = null; switch (type) { case TEMPLATE: vo = templateDataStoreDao.findByTemplate(objId, role); break; case SNAPSHOT: vo = snapshotDataStoreDao.findBySnapshot(objId, role); break; case VOLUME: vo = volumeDataStoreDao.findByVolume(objId); break; } if (vo != null) { store = this.storeMgr.getDataStore(vo.getDataStoreId(), role); } } return store; } }