/* * 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.brooklyn.core.mgmt.persist.jclouds; import static com.google.common.base.Preconditions.checkNotNull; import java.util.List; import javax.annotation.Nullable; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode; import org.apache.brooklyn.core.location.LocationConfigKeys; import org.apache.brooklyn.core.location.cloud.CloudLocationConfig; import org.apache.brooklyn.core.mgmt.persist.PersistMode; import org.apache.brooklyn.core.mgmt.persist.PersistenceObjectStore; import org.apache.brooklyn.core.server.BrooklynServerConfig; import org.jclouds.blobstore.BlobStoreContext; import org.jclouds.blobstore.domain.StorageMetadata; import org.jclouds.blobstore.options.ListContainerOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.brooklyn.location.jclouds.JcloudsLocation; import org.apache.brooklyn.location.jclouds.JcloudsUtil; import org.apache.brooklyn.util.exceptions.FatalConfigurationRuntimeException; import org.apache.brooklyn.util.text.Strings; import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.collect.FluentIterable; /** * @author Andrea Turli */ public class JcloudsBlobStoreBasedObjectStore implements PersistenceObjectStore { private static final Logger log = LoggerFactory.getLogger(JcloudsBlobStoreBasedObjectStore.class); private final String containerNameFirstPart; private final String containerSubPath; private String locationSpec; private JcloudsLocation location; private BlobStoreContext context; private ManagementContext mgmt; public JcloudsBlobStoreBasedObjectStore(String locationSpec, String containerName) { this.locationSpec = locationSpec; String[] segments = splitOnce(containerName); this.containerNameFirstPart = segments[0]; this.containerSubPath = segments[1]; } private String[] splitOnce(String path) { String separator = subPathSeparator(); int index = path.indexOf(separator); if (index<0) return new String[] { path, "" }; return new String[] { path.substring(0, index), path.substring(index+separator.length()) }; } public JcloudsBlobStoreBasedObjectStore(JcloudsLocation location, String containerName) { this.location = location; String[] segments = splitOnce(containerName); this.containerNameFirstPart = segments[0]; this.containerSubPath = segments[1]; getBlobStoreContext(); } public String getSummaryName() { return (locationSpec!=null ? locationSpec : location)+":"+getContainerNameFull(); } public synchronized BlobStoreContext getBlobStoreContext() { if (context==null) { if (location==null) { Preconditions.checkNotNull(locationSpec, "locationSpec required for remote object store when location is null"); Preconditions.checkNotNull(mgmt, "mgmt not injected / object store not prepared"); location = (JcloudsLocation) mgmt.getLocationRegistry().resolve(locationSpec); } String identity = checkNotNull(location.getConfig(LocationConfigKeys.ACCESS_IDENTITY), "identity must not be null"); String credential = checkNotNull(location.getConfig(LocationConfigKeys.ACCESS_CREDENTIAL), "credential must not be null"); String provider = checkNotNull(location.getConfig(LocationConfigKeys.CLOUD_PROVIDER), "provider must not be null"); String endpoint = location.getConfig(CloudLocationConfig.CLOUD_ENDPOINT); context = JcloudsUtil.newBlobstoreContext(provider, endpoint, identity, credential); // TODO do we need to get location from region? can't see the jclouds API. // doesn't matter in some places because it's already in the endpoint // String region = location.getConfig(CloudLocationConfig.CLOUD_REGION_ID); context.getBlobStore().createContainerInLocation(null, getContainerNameFirstPart()); } return context; } @Override public void prepareForMasterUse() { // backups not supported here, that is all which is needed for master use // that's now normally done *prior* to calling in to here for writes // (and we have already thrown in prepareForSharedUse if legacy backups have been specified as required) } public String getContainerName() { return getContainerNameFull(); } protected String getContainerNameFull() { return mergePaths(containerNameFirstPart, containerSubPath); } protected String getContainerNameFirstPart() { return containerNameFirstPart; } protected String getItemInContainerSubPath(String path) { if (Strings.isBlank(containerSubPath)) return path; return mergePaths(containerSubPath, path); } @Override public void createSubPath(String subPath) { // not needed - subpaths are created on demant // (and buggy on softlayer w swift w jclouds 1.7.2: // throws a "not found" if we're creating an empty directory from scratch) // context.getBlobStore().createDirectory(getContainerName(), subPath); } protected void checkPrepared() { if (context==null) throw new IllegalStateException("object store not prepared"); } @Override public StoreObjectAccessor newAccessor(String path) { checkPrepared(); return new JcloudsStoreObjectAccessor(context.getBlobStore(), getContainerNameFirstPart(), getItemInContainerSubPath(path)); } protected String mergePaths(String basePath, String ...subPaths) { StringBuilder result = new StringBuilder(basePath); for (String subPath: subPaths) { if (result.length()>0 && subPath.length()>0) { result.append(subPathSeparator()); result.append(subPath); } } return result.toString(); } protected String subPathSeparator() { // in case some object stores don't allow / for paths return "/"; } @Override public List<String> listContentsWithSubPath(final String parentSubPath) { checkPrepared(); return FluentIterable.from(context.getBlobStore().list(getContainerNameFirstPart(), ListContainerOptions.Builder.inDirectory(getItemInContainerSubPath(parentSubPath)))) .transform(new Function<StorageMetadata, String>() { @Override public String apply(@javax.annotation.Nullable StorageMetadata input) { String result = input.getName(); result = Strings.removeFromStart(result, containerSubPath); result = Strings.removeFromStart(result, "/"); return result; } }).toList(); } @Override public void close() { if (context!=null) context.close(); } @Override public String toString() { return Objects.toStringHelper(this) .add("blobStoreContext", context) .add("basedir", containerNameFirstPart) .toString(); } @Override public void injectManagementContext(ManagementContext mgmt) { if (this.mgmt!=null && !this.mgmt.equals(mgmt)) throw new IllegalStateException("Cannot change mgmt context of "+this); this.mgmt = mgmt; } @SuppressWarnings("deprecation") @Override public void prepareForSharedUse(@Nullable PersistMode persistMode, HighAvailabilityMode haMode) { if (mgmt==null) throw new NullPointerException("Must inject ManagementContext before preparing "+this); getBlobStoreContext(); if (persistMode==null || persistMode==PersistMode.DISABLED) { log.warn("Should not be using "+this+" when persistMode is "+persistMode); return; } Boolean backups = mgmt.getConfig().getConfig(BrooklynServerConfig.PERSISTENCE_BACKUPS_REQUIRED); if (Boolean.TRUE.equals(backups)) { log.warn("Using legacy backup for "+this+"; functionality will be removed in future versions, in favor of promotion/demotion-specific backups to a configurable backup location."); throw new FatalConfigurationRuntimeException("Backups not supported for object store ("+this+")"); } } @Override public void deleteCompletely() { if (Strings.isBlank(containerSubPath)) getBlobStoreContext().getBlobStore().deleteContainer(containerNameFirstPart); else newAccessor(containerSubPath).delete(); } }