/* * 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; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode; import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState; import org.apache.brooklyn.api.mgmt.ha.ManagementPlaneSyncRecord; import org.apache.brooklyn.api.mgmt.ha.MementoCopyMode; import org.apache.brooklyn.api.mgmt.rebind.PersistenceExceptionHandler; import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoRawData; import org.apache.brooklyn.api.mgmt.rebind.mementos.Memento; import org.apache.brooklyn.api.objs.BrooklynObject; import org.apache.brooklyn.api.objs.BrooklynObjectType; import org.apache.brooklyn.api.policy.Policy; import org.apache.brooklyn.api.sensor.Enricher; import org.apache.brooklyn.api.sensor.Feed; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.entity.EntityInternal; import org.apache.brooklyn.core.mgmt.ha.ManagementPlaneSyncRecordPersisterToObjectStore; import org.apache.brooklyn.core.mgmt.internal.LocalLocationManager; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.mgmt.rebind.PersistenceExceptionHandlerImpl; import org.apache.brooklyn.core.mgmt.rebind.transformer.CompoundTransformer; import org.apache.brooklyn.core.mgmt.rebind.transformer.CompoundTransformerLoader; import org.apache.brooklyn.core.objs.BrooklynObjectInternal; import org.apache.brooklyn.core.server.BrooklynServerConfig; import org.apache.brooklyn.core.server.BrooklynServerPaths; import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation; import org.apache.brooklyn.util.core.ResourceUtils; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.time.Duration; import org.apache.brooklyn.util.time.Time; import com.google.common.annotations.Beta; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; public class BrooklynPersistenceUtils { private static final Logger log = LoggerFactory.getLogger(BrooklynPersistenceUtils.class); @Beta public static final List<BrooklynObjectType> STANDARD_BROOKLYN_OBJECT_TYPE_PERSISTENCE_ORDER = ImmutableList.of( BrooklynObjectType.ENTITY, BrooklynObjectType.LOCATION, BrooklynObjectType.POLICY, BrooklynObjectType.ENRICHER, BrooklynObjectType.FEED, BrooklynObjectType.CATALOG_ITEM); /** Creates a {@link PersistenceObjectStore} for general-purpose use. */ public static PersistenceObjectStore newPersistenceObjectStore(ManagementContext managementContext, String locationSpec, String locationContainer) { return newPersistenceObjectStore(managementContext, locationSpec, locationContainer, PersistMode.AUTO, HighAvailabilityMode.STANDBY); } /** Creates a {@link PersistenceObjectStore} for use with a specified set of modes. */ public static PersistenceObjectStore newPersistenceObjectStore(ManagementContext managementContext, String locationSpec, String locationContainer, PersistMode persistMode, HighAvailabilityMode highAvailabilityMode) { PersistenceObjectStore destinationObjectStore; locationContainer = BrooklynServerPaths.newMainPersistencePathResolver(managementContext).location(locationSpec).dir(locationContainer).resolve(); Location location = null; if (Strings.isBlank(locationSpec)) { location = managementContext.getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class) .configure(LocalLocationManager.CREATE_UNMANAGED, true)); } else { location = managementContext.getLocationRegistry().resolve(locationSpec, false, null).get(); if (!(location instanceof LocationWithObjectStore)) { throw new IllegalArgumentException("Destination location "+location+" does not offer a persistent store"); } } destinationObjectStore = ((LocationWithObjectStore)location).newPersistenceObjectStore(locationContainer); destinationObjectStore.injectManagementContext(managementContext); destinationObjectStore.prepareForSharedUse(persistMode, highAvailabilityMode); return destinationObjectStore; } public static void writeMemento(ManagementContext managementContext, BrooklynMementoRawData memento, PersistenceObjectStore destinationObjectStore) { BrooklynMementoPersisterToObjectStore persister = new BrooklynMementoPersisterToObjectStore( destinationObjectStore, ((ManagementContextInternal)managementContext).getBrooklynProperties(), managementContext.getCatalogClassLoader()); PersistenceExceptionHandler exceptionHandler = PersistenceExceptionHandlerImpl.builder().build(); persister.enableWriteAccess(); persister.checkpoint(memento, exceptionHandler); } public static void writeManagerMemento(ManagementContext managementContext, ManagementPlaneSyncRecord optionalPlaneRecord, PersistenceObjectStore destinationObjectStore) { if (optionalPlaneRecord != null) { ManagementPlaneSyncRecordPersisterToObjectStore managementPersister = new ManagementPlaneSyncRecordPersisterToObjectStore( managementContext, destinationObjectStore, managementContext.getCatalogClassLoader()); managementPersister.checkpoint(optionalPlaneRecord); } } public static CompoundTransformer loadTransformer(ResourceUtils resources, String transformationsFileUrl) { if (Strings.isBlank(transformationsFileUrl)) { return CompoundTransformer.NOOP; } else { String contents = resources.getResourceAsString(transformationsFileUrl); return CompoundTransformerLoader.load(contents); } } public static Memento newObjectMemento(BrooklynObject instance) { return ((BrooklynObjectInternal)instance).getRebindSupport().getMemento(); } public static BrooklynMementoRawData newStateMemento(ManagementContext mgmt, MementoCopyMode source) { switch (source) { case LOCAL: return newStateMementoFromLocal(mgmt); case REMOTE: return mgmt.getRebindManager().retrieveMementoRawData(); case AUTO: throw new IllegalStateException("Copy mode AUTO not supported here"); } throw new IllegalStateException("Should not come here, unknown mode "+source); } public static ManagementPlaneSyncRecord newManagerMemento(ManagementContext mgmt, MementoCopyMode source) { switch (source) { case LOCAL: return mgmt.getHighAvailabilityManager().getLastManagementPlaneSyncRecord(); case REMOTE: return mgmt.getHighAvailabilityManager().loadManagementPlaneSyncRecord(true); case AUTO: throw new IllegalStateException("Copy mode AUTO not supported here"); } throw new IllegalStateException("Should not come here, unknown mode "+source); } private static BrooklynMementoRawData newStateMementoFromLocal(ManagementContext mgmt) { BrooklynMementoRawData.Builder result = BrooklynMementoRawData.builder(); MementoSerializer<Object> rawSerializer = new XmlMementoSerializer<Object>(mgmt.getClass().getClassLoader()); RetryingMementoSerializer<Object> serializer = new RetryingMementoSerializer<Object>(rawSerializer, 1); for (Location instance: mgmt.getLocationManager().getLocations()) result.location(instance.getId(), serializer.toString(newObjectMemento(instance))); for (Entity instance: mgmt.getEntityManager().getEntities()) { instance = Entities.deproxy(instance); result.entity(instance.getId(), serializer.toString(newObjectMemento(instance))); for (Feed instanceAdjunct: ((EntityInternal)instance).feeds().getFeeds()) result.feed(instanceAdjunct.getId(), serializer.toString(newObjectMemento(instanceAdjunct))); for (Enricher instanceAdjunct: instance.enrichers()) result.enricher(instanceAdjunct.getId(), serializer.toString(newObjectMemento(instanceAdjunct))); for (Policy instanceAdjunct: instance.policies()) result.policy(instanceAdjunct.getId(), serializer.toString(newObjectMemento(instanceAdjunct))); } for (CatalogItem<?,?> instance: mgmt.getCatalog().getCatalogItems()) result.catalogItem(instance.getId(), serializer.toString(newObjectMemento(instance))); return result.build(); } /** generates and writes mementos for the given mgmt context to the given targetStore; * this may be taken from {@link MementoCopyMode#LOCAL} current state * or {@link MementoCopyMode#REMOTE} persisted state, or the default {@link MementoCopyMode#AUTO} detected */ public static void writeMemento(ManagementContext mgmt, PersistenceObjectStore targetStore, MementoCopyMode source) { if (source==null || source==MementoCopyMode.AUTO) source = (mgmt.getHighAvailabilityManager().getNodeState()==ManagementNodeState.MASTER ? MementoCopyMode.LOCAL : MementoCopyMode.REMOTE); Stopwatch timer = Stopwatch.createStarted(); BrooklynMementoRawData dataRecord = newStateMemento(mgmt, source); ManagementPlaneSyncRecord mgmtRecord = newManagerMemento(mgmt, source); writeMemento(mgmt, dataRecord, targetStore); writeManagerMemento(mgmt, mgmtRecord, targetStore); log.debug("Wrote full memento to "+targetStore+" in "+Time.makeTimeStringRounded(Duration.of(timer))); } public static enum CreateBackupMode { PROMOTION, DEMOTION, CUSTOM; @Override public String toString() { return super.toString().toLowerCase(); } } public static void createBackup(ManagementContext managementContext, CreateBackupMode mode, MementoCopyMode source) { if (source==null || source==MementoCopyMode.AUTO) { switch (mode) { case PROMOTION: source = MementoCopyMode.REMOTE; break; case DEMOTION: source = MementoCopyMode.LOCAL; break; default: throw new IllegalArgumentException("Cannot detect copy mode for "+mode+"/"+source); } } BrooklynMementoRawData memento = null; ManagementPlaneSyncRecord planeState = null; try { log.debug("Loading persisted state on "+mode+" for backup purposes"); memento = newStateMemento(managementContext, source); try { planeState = newManagerMemento(managementContext, source); } catch (Exception e) { Exceptions.propagateIfFatal(e); log.warn("Unable to access management plane sync state on "+mode+" (ignoring): "+e, e); } PersistenceObjectStore destinationObjectStore = null; String backupSpec = managementContext.getConfig().getConfig(BrooklynServerConfig.PERSISTENCE_BACKUPS_LOCATION_SPEC); String nonBackupSpec = managementContext.getConfig().getConfig(BrooklynServerConfig.PERSISTENCE_LOCATION_SPEC); try { String backupContainer = BrooklynServerPaths.newBackupPersistencePathResolver(managementContext) .location(backupSpec).nonBackupLocation(nonBackupSpec).resolveWithSubpathFor(managementContext, mode.toString()); destinationObjectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(managementContext, backupSpec, backupContainer); log.debug("Backing up persisted state on "+mode+", to "+destinationObjectStore.getSummaryName()); BrooklynPersistenceUtils.writeMemento(managementContext, memento, destinationObjectStore); BrooklynPersistenceUtils.writeManagerMemento(managementContext, planeState, destinationObjectStore); if (!memento.isEmpty()) { log.info("Back-up of persisted state created on "+mode+", in "+destinationObjectStore.getSummaryName()); } else { log.debug("Back-up of (empty) persisted state created on "+mode+", in "+destinationObjectStore.getSummaryName()); } } catch (Exception e) { Exceptions.propagateIfFatal(e); PersistenceObjectStore failedStore = destinationObjectStore; if (!Strings.isBlank(backupSpec) && !"localhost".equals(backupSpec)) { String failedSpec = backupSpec; backupSpec = "localhost"; String backupContainer = BrooklynServerPaths.newBackupPersistencePathResolver(managementContext) .location(backupSpec).nonBackupLocation(nonBackupSpec).resolveWithSubpathFor(managementContext, mode.toString()); destinationObjectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(managementContext, backupSpec, backupContainer); log.warn("Persisted state back-up to "+(failedStore!=null ? failedStore.getSummaryName() : failedSpec) +" failed with "+e, e); log.debug("Backing up persisted state on "+mode+", locally because remote failed, to "+destinationObjectStore.getSummaryName()); BrooklynPersistenceUtils.writeMemento(managementContext, memento, destinationObjectStore); BrooklynPersistenceUtils.writeManagerMemento(managementContext, planeState, destinationObjectStore); log.info("Back-up of persisted state created on "+mode+", locally because remote failed, in "+destinationObjectStore.getSummaryName()); } } } catch (Exception e) { Exceptions.propagateIfFatal(e); log.warn("Unable to backup management plane sync state on "+mode+" (ignoring): "+e, e); } } }