Package com.cloud.storage.s3

Source Code of com.cloud.storage.s3.S3ManagerImpl

/*
* 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 com.cloud.storage.s3;

import static com.cloud.storage.S3VO.ID_COLUMN_NAME;
import static com.cloud.utils.DateUtil.now;
import static com.cloud.utils.S3Utils.canConnect;
import static com.cloud.utils.S3Utils.canReadWriteBucket;
import static com.cloud.utils.S3Utils.checkBucketName;
import static com.cloud.utils.S3Utils.checkClientOptions;
import static com.cloud.utils.S3Utils.doesBucketExist;
import static com.cloud.utils.StringUtils.join;
import static com.cloud.utils.db.GlobalLock.executeWithNoWaitLock;
import static java.lang.Boolean.TRUE;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.shuffle;
import static java.util.Collections.singletonList;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;

import javax.annotation.PostConstruct;
import javax.ejb.Local;
import javax.inject.Inject;
import javax.naming.ConfigurationException;

import org.apache.cloudstack.api.command.admin.storage.AddS3Cmd;
import org.apache.cloudstack.api.command.admin.storage.ListS3sCmd;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;

import com.cloud.agent.AgentManager;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.DeleteTemplateFromS3Command;
import com.cloud.agent.api.DownloadTemplateFromS3ToSecondaryStorageCommand;
import com.cloud.agent.api.UploadTemplateToS3FromSecondaryStorageCommand;
import com.cloud.agent.api.to.S3TO;
import com.cloud.configuration.Config;
import com.cloud.configuration.dao.ConfigurationDao;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.exception.DiscoveryException;
import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao;
import com.cloud.storage.S3;
import com.cloud.storage.S3VO;
import com.cloud.storage.VMTemplateHostVO;
import com.cloud.storage.VMTemplateS3VO;
import com.cloud.storage.VMTemplateStorageResourceAssoc.Status;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.VMTemplateZoneVO;
import com.cloud.storage.dao.S3Dao;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.storage.dao.VMTemplateHostDao;
import com.cloud.storage.dao.VMTemplateS3Dao;
import com.cloud.storage.dao.VMTemplateZoneDao;
import com.cloud.storage.secondary.SecondaryStorageVmManager;
import com.cloud.utils.S3Utils.ClientOptions;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.exception.CloudRuntimeException;

@Component
@Local(value = { S3Manager.class })
public class S3ManagerImpl extends ManagerBase implements S3Manager {

    private static final Logger LOGGER = Logger.getLogger(S3ManagerImpl.class);

    @Inject
    private AgentManager agentManager;

    @Inject
    private S3Dao s3Dao;

    @Inject
    private VMTemplateZoneDao vmTemplateZoneDao;

    @Inject
    private VMTemplateS3Dao vmTemplateS3Dao;

    @Inject
    private VMTemplateHostDao vmTemplateHostDao;

    @Inject
    private VMTemplateDao vmTemplateDao;

    @Inject
    private ConfigurationDao configurationDao;

    @Inject
    private DataCenterDao dataCenterDao;

    @Inject
    private HostDao hostDao;

    @Inject
    private SecondaryStorageVmManager secondaryStorageVMManager;

    public S3ManagerImpl() {
    }
   
    private void verifyConnection(final S3TO s3) throws DiscoveryException {

        if (!canConnect(s3)) {
            throw new DiscoveryException(format("Unable to connect to S3 "
                    + "using access key %1$s, secret key %2$s, and endpoint, "
                    + "%3$S", s3.getAccessKey(), s3.getSecretKey(),
                    s3.getEndPoint() != null ? s3.getEndPoint() : "default"));
        }

    }

    private void verifyBuckets(S3TO s3) throws DiscoveryException {

        final List<String> errorMessages = new ArrayList<String>();

        errorMessages.addAll(verifyBucket(s3, s3.getBucketName()));

        throwDiscoveryExceptionFromErrorMessages(errorMessages);

    }

    private List<String> verifyBucket(final ClientOptions clientOptions,
            final String bucketName) {

        if (!doesBucketExist(clientOptions, bucketName)) {
            return singletonList(format("Bucket %1$s does not exist.",
                    bucketName));
        }

        if (!canReadWriteBucket(clientOptions, bucketName)) {
            return singletonList(format("Can read/write from bucket %1$s.",
                    bucketName));
        }

        return emptyList();
    }

    private void validateFields(final S3VO s3VO) {

        final List<String> errorMessages = new ArrayList<String>();

        errorMessages.addAll(checkClientOptions(s3VO.toS3TO()));

        errorMessages.addAll(checkBucketName("template", s3VO.getBucketName()));

        throwDiscoveryExceptionFromErrorMessages(errorMessages);

    }

    private void enforceS3PreConditions() throws DiscoveryException {

        if (!this.isS3Enabled()) {
            throw new DiscoveryException("S3 is not enabled.");
        }

        if (this.getS3TO() != null) {
            throw new DiscoveryException("Attempt to define multiple S3 "
                    + "instances.  Only one instance definition is supported.");
        }

    }

    private void throwDiscoveryExceptionFromErrorMessages(
            final List<String> errorMessages) {

        if (!errorMessages.isEmpty()) {
            throw new CloudRuntimeException(join(errorMessages, " "));
        }

    }

    @SuppressWarnings("unchecked")
    private String determineLockId(final long accountId, final long templateId) {

        // TBD The lock scope may be too coarse grained. Deletes need to lock
        // the template across all zones where upload and download could
        // probably safely scoped to the zone ...
        return join(asList("S3_TEMPLATE", accountId, templateId), "_");

    }

    @Override
    public S3TO getS3TO(final Long s3Id) {
        return this.s3Dao.getS3TO(s3Id);
    }

    @Override
    public S3TO getS3TO() {

        final List<S3VO> s3s = this.s3Dao.listAll();

        if (s3s == null || (s3s != null && s3s.isEmpty())) {
            return null;
        }

        if (s3s.size() == 1) {
            return s3s.get(0).toS3TO();
        }

        throw new CloudRuntimeException("Multiple S3 instances have been "
                + "defined.  Only one instance configuration is supported.");

    }

    @Override
    public S3 addS3(final AddS3Cmd addS3Cmd) throws DiscoveryException {

        this.enforceS3PreConditions();

        final S3VO s3VO = new S3VO(UUID.randomUUID().toString(),
                addS3Cmd.getAccessKey(), addS3Cmd.getSecretKey(),
                addS3Cmd.getEndPoint(), addS3Cmd.getBucketName(),
                addS3Cmd.getHttpsFlag(), addS3Cmd.getConnectionTimeout(),
                addS3Cmd.getMaxErrorRetry(), addS3Cmd.getSocketTimeout(), now());

        this.validateFields(s3VO);

        final S3TO s3 = s3VO.toS3TO();
        this.verifyConnection(s3);
        this.verifyBuckets(s3);

        return this.s3Dao.persist(s3VO);

    }

    @Override
    public boolean isS3Enabled() {
        return Boolean
                .valueOf(configurationDao.getValue(Config.S3Enable.key()));
    }

    @Override
    public boolean isTemplateInstalled(final Long templateId) {
        throw new UnsupportedOperationException(
                "S3Manager#isTemplateInstalled (DeleteIsoCmd) has not yet "
                        + "been implemented");
    }

    @Override
    public void deleteTemplate(final Long templateId, final Long accountId) {

        final S3TO s3 = getS3TO();

        if (s3 == null) {
            final String errorMessage = "Delete Template Failed: No S3 configuration defined.";
            LOGGER.error(errorMessage);
            throw new CloudRuntimeException(errorMessage);
        }

        final VMTemplateS3VO vmTemplateS3VO = vmTemplateS3Dao
                .findOneByS3Template(s3.getId(), templateId);
        if (vmTemplateS3VO == null) {
            final String errorMessage = format(
                    "Delete Template Failed: Unable to find Template %1$s in S3.",
                    templateId);
            LOGGER.error(errorMessage);
            throw new CloudRuntimeException(errorMessage);
        }

        try {

            executeWithNoWaitLock(determineLockId(accountId, templateId),
                    new Callable<Void>() {

                @Override
                public Void call() throws Exception {

                    final Answer answer = agentManager.sendToSSVM(null,
                            new DeleteTemplateFromS3Command(s3,
                                    accountId, templateId));
                    if (answer == null || !answer.getResult()) {
                        final String errorMessage = format(
                                "Delete Template Failed: Unable to delete template id %1$s from S3 due to following error: %2$s",
                                templateId,
                                ((answer == null) ? "answer is null"
                                        : answer.getDetails()));
                        LOGGER.error(errorMessage);
                        throw new CloudRuntimeException(errorMessage);
                    }

                    vmTemplateS3Dao.remove(vmTemplateS3VO.getId());
                    LOGGER.debug(format(
                            "Deleted template %1$s from S3.",
                            templateId));

                    return null;

                }

            });

        } catch (Exception e) {

            final String errorMessage = format(
                    "Delete Template Failed: Unable to delete template id %1$s from S3 due to the following error: %2$s.",
                    templateId, e.getMessage());
            LOGGER.error(errorMessage);
            throw new CloudRuntimeException(errorMessage, e);

        }

    }

    @SuppressWarnings("unchecked")
    @Override
    public String downloadTemplateFromS3ToSecondaryStorage(
            final long dataCenterId, final long templateId,
            final int primaryStorageDownloadWait) {

        if (!isS3Enabled()) {
            return null;
        }

        final VMTemplateVO template = vmTemplateDao.findById(templateId);
        if (template == null) {
            final String errorMessage = String
                    .format("Failed to download template id %1$s from S3 because the template definition was not found.",
                            templateId);
            LOGGER.error(errorMessage);
            return errorMessage;
        }

        final VMTemplateS3VO templateS3VO = findByTemplateId(templateId);
        if (templateS3VO == null) {
            final String errorMessage = format(
                    "Failed to download template id %1$s from S3 because it does not exist in S3.",
                    templateId);
            LOGGER.error(errorMessage);
            return errorMessage;
        }

        final S3TO s3 = getS3TO(templateS3VO.getS3Id());
        if (s3 == null) {
            final String errorMessage = format(
                    "Failed to download template id %1$s from S3 because S3 id %2$s does not exist.",
                    templateId, templateS3VO);
            LOGGER.error(errorMessage);
            return errorMessage;
        }

        final HostVO secondaryStorageHost = secondaryStorageVMManager
                .findSecondaryStorageHost(dataCenterId);
        if (secondaryStorageHost == null) {
            final String errorMessage = format(
                    "Unable to find secondary storage host for zone id %1$s.",
                    dataCenterId);
            LOGGER.error(errorMessage);
            throw new CloudRuntimeException(errorMessage);
        }

        final long accountId = template.getAccountId();
        final DownloadTemplateFromS3ToSecondaryStorageCommand cmd = new DownloadTemplateFromS3ToSecondaryStorageCommand(
                s3, accountId, templateId, secondaryStorageHost.getName(),
                primaryStorageDownloadWait);

        try {

            executeWithNoWaitLock(determineLockId(accountId, templateId),
                    new Callable<Void>() {

                @Override
                public Void call() throws Exception {

                    final Answer answer = agentManager.sendToSSVM(
                            dataCenterId, cmd);

                    if (answer == null || !answer.getResult()) {
                        final String errMsg = String
                                .format("Failed to download template from S3 to secondary storage due to %1$s",
                                        (answer == null ? "answer is null"
                                                : answer.getDetails()));
                        LOGGER.error(errMsg);
                        throw new CloudRuntimeException(errMsg);
                    }

                    final String installPath = join(
                            asList("template", "tmpl", accountId,
                                    templateId), File.separator);
                    final VMTemplateHostVO tmpltHost = new VMTemplateHostVO(
                            secondaryStorageHost.getId(), templateId,
                            now(), 100, Status.DOWNLOADED, null, null,
                            null, installPath, template.getUrl());
                    tmpltHost.setSize(templateS3VO.getSize());
                    tmpltHost.setPhysicalSize(templateS3VO
                            .getPhysicalSize());
                    vmTemplateHostDao.persist(tmpltHost);

                    return null;

                }

            });

        } catch (Exception e) {
            final String errMsg = "Failed to download template from S3 to secondary storage due to "
                    + e.toString();
            LOGGER.error(errMsg);
            throw new CloudRuntimeException(errMsg);
        }

        return null;

    }

    @Override
    public List<S3VO> listS3s(final ListS3sCmd cmd) {

        final Filter filter = new Filter(S3VO.class, ID_COLUMN_NAME, TRUE,
                cmd.getStartIndex(), cmd.getPageSizeVal());
        final SearchCriteria<S3VO> criteria = this.s3Dao.createSearchCriteria();

        return this.s3Dao.search(criteria, filter);

    }

    @Override
    public VMTemplateS3VO findByTemplateId(final Long templateId) {
        throw new UnsupportedOperationException(
                "S3Manager#findByTemplateId(Long) has not yet "
                        + "been implemented");
    }

    @Override
    public void propagateTemplatesToZone(final DataCenterVO zone) {

        if (!isS3Enabled()) {
            return;
        }

        final List<VMTemplateS3VO> s3VMTemplateRefs = this.vmTemplateS3Dao
                .listAll();
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info(format("Propagating %1$s templates to zone %2$s.",
                    s3VMTemplateRefs.size(), zone.getName()));
        }

        for (final VMTemplateS3VO templateS3VO : s3VMTemplateRefs) {
            this.vmTemplateZoneDao.persist(new VMTemplateZoneVO(zone.getId(),
                    templateS3VO.getTemplateId(), now()));
        }

    }

    @Override
    public boolean configure(final String name, final Map<String, Object> params)
            throws ConfigurationException {

        if (LOGGER.isInfoEnabled()) {
            LOGGER.info(format("Configuring S3 Manager %1$s", name));
        }

        return true;
    }

    @Override
    public boolean start() {
        LOGGER.info("Starting S3 Manager");
        return true;
    }

    @Override
    public boolean stop() {
        LOGGER.info("Stopping S3 Manager");
        return true;
    }

    @Override
    public void propagateTemplateToAllZones(final VMTemplateS3VO vmTemplateS3VO) {

        final long templateId = vmTemplateS3VO.getId();

        if (!isS3Enabled()) {
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace(format(
                        "Attempt to propogate template id %1$s across all zones.  However, S3 is not enabled.",
                        templateId));
            }
            return;

        }

        final S3TO s3 = getS3TO();

        if (s3 == null) {
            LOGGER.warn(format(
                    "Unable to propagate template id %1$s across all zones because S3 is enabled, but not configured.",
                    templateId));
            return;
        }

        if (vmTemplateS3VO != null) {
            final List<DataCenterVO> dataCenters = dataCenterDao.listAll();
            for (DataCenterVO dataCenter : dataCenters) {
                final VMTemplateZoneVO tmpltZoneVO = new VMTemplateZoneVO(
                        dataCenter.getId(), templateId, now());
                vmTemplateZoneDao.persist(tmpltZoneVO);
            }
        }

    }

    @Override
    public Long chooseZoneForTemplateExtract(VMTemplateVO template) {

        final S3TO s3 = getS3TO();

        if (s3 == null) {
            return null;
        }

        final List<VMTemplateHostVO> templateHosts = vmTemplateHostDao
                .listByOnlyTemplateId(template.getId());
        if (templateHosts != null) {
            shuffle(templateHosts);
            for (VMTemplateHostVO vmTemplateHostVO : templateHosts) {
                final HostVO host = hostDao.findById(vmTemplateHostVO
                        .getHostId());
                if (host != null) {
                    return host.getDataCenterId();
                }
                throw new CloudRuntimeException(
                        format("Unable to find secondary storage host for template id %1$s.",
                                template.getId()));
            }
        }

        final List<DataCenterVO> dataCenters = dataCenterDao.listAll();
        shuffle(dataCenters);
        return dataCenters.get(0).getId();

    }

    @Override
    public void uploadTemplateToS3FromSecondaryStorage(
            final VMTemplateVO template) {

        final Long templateId = template.getId();

        final List<VMTemplateHostVO> templateHostRefs = vmTemplateHostDao
                .listByTemplateId(templateId);

        if (templateHostRefs == null
                || (templateHostRefs != null && templateHostRefs.isEmpty())) {
            throw new CloudRuntimeException(
                    format("Attempt to sync template id %1$s that is not attached to a host.",
                            templateId));
        }

        final VMTemplateHostVO templateHostRef = templateHostRefs.get(0);

        if (!isS3Enabled()) {
            return;
        }

        final S3TO s3 = getS3TO();
        if (s3 == null) {
            LOGGER.warn("S3 Template Sync Failed: Attempt to sync templates with S3, but no S3 instance defined.");
            return;
        }

        final HostVO secondaryHost = this.hostDao.findById(templateHostRef
                .getHostId());
        if (secondaryHost == null) {
            throw new CloudRuntimeException(format(
                    "Unable to find secondary storage host id %1$s.",
                    templateHostRef.getHostId()));
        }

        final Long dataCenterId = secondaryHost.getDataCenterId();
        final Long accountId = template.getAccountId();

        try {

            executeWithNoWaitLock(determineLockId(accountId, templateId),
                    new Callable<Void>() {

                @Override
                public Void call() throws Exception {

                    final UploadTemplateToS3FromSecondaryStorageCommand cmd = new UploadTemplateToS3FromSecondaryStorageCommand(
                            s3, secondaryHost.getStorageUrl(),
                            dataCenterId, accountId, templateId);

                    final Answer answer = agentManager.sendToSSVM(
                            dataCenterId, cmd);
                    if (answer == null || !answer.getResult()) {

                        final String reason = answer != null ? answer
                                .getDetails()
                                : "S3 template sync failed due to an unspecified error.";
                                throw new CloudRuntimeException(
                                        format("Failed to upload template id %1$s to S3 from secondary storage due to %2$s.",
                                                templateId, reason));

                    }

                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug(format(
                                "Creating VMTemplateS3VO instance using template id %1s.",
                                templateId));
                    }

                    final VMTemplateS3VO vmTemplateS3VO = new VMTemplateS3VO(
                            s3.getId(), templateId, now(),
                            templateHostRef.getSize(), templateHostRef
                            .getPhysicalSize());

                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug(format("Persisting %1$s",
                                vmTemplateS3VO));
                    }

                    vmTemplateS3Dao.persist(vmTemplateS3VO);
                    propagateTemplateToAllZones(vmTemplateS3VO);

                    return null;

                }

            });

        } catch (Exception e) {

            final String errorMessage = format(
                    "Failed to upload template id %1$s for zone id %2$s to S3.",
                    templateId, dataCenterId);
            LOGGER.error(errorMessage, e);

        }

    }

}
TOP

Related Classes of com.cloud.storage.s3.S3ManagerImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.