diff --git a/oak-it/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCachingDataStoreStatsTest.java b/oak-it/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCachingDataStoreStatsTest.java index 5f797712f6..38a892ccaa 100644 --- a/oak-it/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCachingDataStoreStatsTest.java +++ b/oak-it/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCachingDataStoreStatsTest.java @@ -18,6 +18,14 @@ */ package org.apache.jackrabbit.oak.segment; +import static com.google.common.collect.Maps.newHashMap; +import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.CUSTOM_BLOB_STORE; +import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.REPOSITORY_HOME_DIRECTORY; +import static org.apache.sling.testing.mock.osgi.MockOsgi.deactivate; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; + import java.io.File; import java.util.Map; @@ -30,21 +38,12 @@ import org.apache.jackrabbit.oak.stats.StatisticsProvider; import org.apache.sling.testing.mock.osgi.ReferenceViolationException; import org.apache.sling.testing.mock.osgi.junit.OsgiContext; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.osgi.framework.ServiceRegistration; -import static com.google.common.collect.Maps.newHashMap; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.CUSTOM_BLOB_STORE; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.REPOSITORY_HOME_DIRECTORY; -import static org.apache.sling.testing.mock.osgi.MockOsgi.deactivate; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.mockito.Mockito.mock; - /** * Tests the registration of the {@link ConsolidatedDataStoreCacheStatsMBean}. */ diff --git a/oak-segment-tar/pom.xml b/oak-segment-tar/pom.xml index c6d4265ed9..dd1d060c82 100644 --- a/oak-segment-tar/pom.xml +++ b/oak-segment-tar/pom.xml @@ -253,8 +253,13 @@ provided - org.apache.felix - org.apache.felix.scr.annotations + org.osgi + org.osgi.service.component.annotations + provided + + + org.osgi + org.osgi.service.metatype.annotations provided diff --git a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreFactory.java b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreFactory.java index 1809a8c0c6..7cb17dd959 100644 --- a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreFactory.java +++ b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreFactory.java @@ -16,40 +16,61 @@ */ package org.apache.jackrabbit.oak.segment; +import static com.google.common.base.Strings.isNullOrEmpty; +import static org.apache.jackrabbit.oak.commons.IOUtils.closeQuietly; import static org.apache.jackrabbit.oak.osgi.OsgiUtil.lookupConfigurationThenFramework; +import static org.apache.jackrabbit.oak.segment.CachingSegmentReader.DEFAULT_STRING_CACHE_MB; +import static org.apache.jackrabbit.oak.segment.CachingSegmentReader.DEFAULT_TEMPLATE_CACHE_MB; +import static org.apache.jackrabbit.oak.segment.SegmentCache.DEFAULT_SEGMENT_CACHE_MB; +import static org.apache.jackrabbit.oak.segment.WriterCacheManager.DEFAULT_NODE_CACHE_SIZE_OSGi; +import static org.apache.jackrabbit.oak.segment.WriterCacheManager.DEFAULT_STRING_CACHE_SIZE_OSGi; +import static org.apache.jackrabbit.oak.segment.WriterCacheManager.DEFAULT_TEMPLATE_CACHE_SIZE_OSGi; +import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.DISABLE_ESTIMATION_DEFAULT; +import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.FORCE_TIMEOUT_DEFAULT; +import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.GC_PROGRESS_LOG_DEFAULT; +import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.MEMORY_THRESHOLD_DEFAULT; +import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.PAUSE_DEFAULT; +import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.RETAINED_GENERATIONS_DEFAULT; +import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.RETRY_COUNT_DEFAULT; +import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.SIZE_DELTA_ESTIMATION_DEFAULT; +import static org.apache.jackrabbit.oak.segment.file.FileStoreBuilder.DEFAULT_MAX_FILE_SIZE; import static org.apache.jackrabbit.oak.spi.blob.osgi.SplitBlobStoreService.ONLY_STANDALONE_TARGET; import java.io.Closeable; +import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; import com.google.common.io.Closer; -import org.apache.felix.scr.annotations.Activate; -import org.apache.felix.scr.annotations.Component; -import org.apache.felix.scr.annotations.ConfigurationPolicy; -import org.apache.felix.scr.annotations.Deactivate; -import org.apache.felix.scr.annotations.Property; -import org.apache.felix.scr.annotations.Reference; -import org.apache.felix.scr.annotations.ReferenceCardinality; -import org.apache.felix.scr.annotations.ReferencePolicy; -import org.apache.felix.scr.annotations.ReferencePolicyOption; import org.apache.jackrabbit.api.stats.RepositoryStatistics; import org.apache.jackrabbit.api.stats.TimeSeries; -import org.apache.jackrabbit.oak.commons.IOUtils; +import org.apache.jackrabbit.oak.osgi.OsgiUtil; import org.apache.jackrabbit.oak.osgi.OsgiWhiteboard; import org.apache.jackrabbit.oak.segment.spi.persistence.SegmentNodeStorePersistence; import org.apache.jackrabbit.oak.spi.blob.BlobStore; -import org.apache.jackrabbit.oak.spi.state.NodeStore; import org.apache.jackrabbit.oak.spi.state.NodeStoreProvider; import org.apache.jackrabbit.oak.spi.whiteboard.Registration; +import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard; import org.apache.jackrabbit.oak.stats.CounterStats; import org.apache.jackrabbit.oak.stats.HistogramStats; import org.apache.jackrabbit.oak.stats.MeterStats; import org.apache.jackrabbit.oak.stats.StatisticsProvider; import org.apache.jackrabbit.oak.stats.StatsOptions; import org.apache.jackrabbit.oak.stats.TimerStats; +import org.osgi.framework.BundleContext; import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.Designate; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,111 +79,272 @@ import org.slf4j.LoggerFactory; *

* The different secondaries are distinguished by their role attribute. */ -@Component(policy = ConfigurationPolicy.REQUIRE, - name="org.apache.jackrabbit.oak.segment.SegmentNodeStoreFactory", - configurationFactory=true, - metatype = true, - label = "Apache Jackrabbit Oak Segment-Tar NodeStore Factory", - description = "Factory allowing configuration of adjacent instances of " + - "NodeStore implementation based on Segment model besides a default SegmentNodeStore in same setup." +@Component( + configurationPolicy = ConfigurationPolicy.REQUIRE, + factory = "org.apache.jackrabbit.oak.segment.SegmentNodeStoreFactory" ) +@Designate(factory = true, ocd = SegmentNodeStoreFactory.Configuration.class) public class SegmentNodeStoreFactory { - @Property( - label = "Role", - description="As multiple SegmentNodeStores can be configured, this parameter defines the role " + - "of 'this' SegmentNodeStore." - ) - public static final String ROLE = "role"; - - @Property(boolValue = false, - label = "Custom BlobStore", - description = "Boolean value indicating that a custom BlobStore is to be used. " + - "By default large binary content would be stored within segment tar files" - ) - public static final String CUSTOM_BLOB_STORE = "customBlobStore"; + private final Logger log = LoggerFactory.getLogger(getClass()); - @Property(boolValue = false, - label = "Custom segment store", - description = "Boolean value indicating that a custom (non-tar) segment store is used" - ) - public static final String CUSTOM_SEGMENT_STORE = "customSegmentStore"; + private static final long DEFAULT_BLOB_SNAPSHOT_INTERVAL = 12 * 60 * 60; - @Property(boolValue = false, - label = "Register JCR descriptors as OSGi services", - description="Should only be done for one factory instance") - public static final String REGISTER_DESCRIPTORS = "registerDescriptors"; + private static final long DEFAULT_BLOB_GC_MAX_AGE = 24 * 60 * 60; - private final Logger log = LoggerFactory.getLogger(getClass()); + @ObjectClassDefinition( + name = "Apache Jackrabbit Oak Segment-Tar NodeStore Factory", + description = "Factory allowing configuration of adjacent instances of " + + "NodeStore implementation based on Segment model besides a default SegmentNodeStore in same setup." + ) + @interface Configuration { + + @AttributeDefinition( + name = "Repository Home Directory", + description = "Path on the file system where repository data will be stored. " + + "Defaults to the value of the framework property 'repository.home' or to 'repository' " + + "if that is neither specified." + ) + String repository_home() default ""; + + @AttributeDefinition( + name = "Mode", + description = "TarMK mode (64 for memory mapped file access, 32 for normal file access). " + + "Default value is taken from the 'sun.arch.data.model' system property." + ) + String tarmk_mode() default ""; + + @AttributeDefinition( + name = "Maximum tar file size (MB)", + description = "The maximum size of the tar files in megabytes. " + + "Default value is '" + DEFAULT_MAX_FILE_SIZE + "'." + ) + int tarmk_size() default DEFAULT_MAX_FILE_SIZE; + + @AttributeDefinition( + name = "Segment cache size (MB)", + description = "Cache size for storing most recently used segments in megabytes. " + + "Default value is '" + DEFAULT_SEGMENT_CACHE_MB + "'." + ) + int segmentCache_size() default DEFAULT_SEGMENT_CACHE_MB; + + @AttributeDefinition( + name = "String cache size (MB)", + description = "Cache size for storing most recently used strings in megabytes. " + + "Default value is '" + DEFAULT_STRING_CACHE_MB + "'." + ) + int stringCache_size() default DEFAULT_STRING_CACHE_MB; + + @AttributeDefinition( + name = "Template cache size (MB)", + description = "Cache size for storing most recently used templates in megabytes. " + + "Default value is '" + DEFAULT_TEMPLATE_CACHE_MB + "'." + ) + int templateCache_size() default DEFAULT_TEMPLATE_CACHE_MB; + + @AttributeDefinition( + name = "String deduplication cache size (#items)", + description = "Maximum number of strings to keep in the deduplication cache. " + + "Default value is '" + DEFAULT_STRING_CACHE_SIZE_OSGi + "'." + ) + int stringDeduplicationCache_size() default DEFAULT_STRING_CACHE_SIZE_OSGi; + + @AttributeDefinition( + name = "Template deduplication cache size (#items)", + description = "Maximum number of templates to keep in the deduplication cache. " + + "Default value is '" + DEFAULT_TEMPLATE_CACHE_SIZE_OSGi + "'." + ) + int templateDeduplicationCache_size() default DEFAULT_TEMPLATE_CACHE_SIZE_OSGi; + + @AttributeDefinition( + name = "Node deduplication cache size (#items)", + description = "Maximum number of node to keep in the deduplication cache. If the supplied " + + "value is not a power of 2, it will be rounded up to the next power of 2. " + + "Default value is '" + DEFAULT_NODE_CACHE_SIZE_OSGi + "'." + ) + int nodeDeduplicationCache_size() default DEFAULT_NODE_CACHE_SIZE_OSGi; + + @AttributeDefinition( + name = "Pause compaction", + description = "When set to true the compaction phase is skipped during garbage collection. " + + "Default value is '" + PAUSE_DEFAULT + "'." + ) + boolean pauseCompaction() default PAUSE_DEFAULT; + + @AttributeDefinition( + name = "Compaction retries", + description = "Number of tries to compact concurrent commits on top of already " + + "compacted commits. " + + "Default value is '" + RETRY_COUNT_DEFAULT + "'." + ) + int compaction_retryCount() default RETRY_COUNT_DEFAULT; + + @AttributeDefinition( + name = "Force compaction timeout", + description = "Number of seconds to attempt to force compact concurrent commits on top " + + "of already compacted commits after the maximum number of retries has been " + + "reached. Forced compaction tries to acquire an exclusive write lock on the " + + "node store, blocking concurrent write access as long as the lock is held. " + + "Default value is '" + FORCE_TIMEOUT_DEFAULT + "'." + ) + int compaction_force_timeout() default FORCE_TIMEOUT_DEFAULT; + + @AttributeDefinition( + name = "Garbage collection repository size threshold", + description = "Garbage collection will be skipped unless the repository grew at least by " + + "the number of bytes specified. " + + "Default value is '" + SIZE_DELTA_ESTIMATION_DEFAULT + "'." + ) + long compaction_sizeDeltaEstimation() default SIZE_DELTA_ESTIMATION_DEFAULT; + + @AttributeDefinition( + name = "Disable estimation phase", + description = "Disables the estimation phase allowing garbage collection to run unconditionally. " + + "Default value is '" + DISABLE_ESTIMATION_DEFAULT + "'." + ) + boolean compaction_disableEstimation() default DISABLE_ESTIMATION_DEFAULT; + + @AttributeDefinition( + name = "Compaction retained generations", + description = "Number of segment generations to retain during garbage collection. " + + "The number of generations defaults to " + RETAINED_GENERATIONS_DEFAULT + " and " + + "can't be changed. This configuration option is considered deprecated " + + "and will be removed in the future." + ) + int compaction_retainedGenerations() default RETAINED_GENERATIONS_DEFAULT; + + @AttributeDefinition( + name = "Compaction memory threshold", + description = "Threshold of available heap memory in percent of total heap memory below " + + "which the compaction phase is canceled. 0 disables heap memory monitoring. " + + "Default value is '" + MEMORY_THRESHOLD_DEFAULT + "'." + ) + int compaction_memoryThreshold() default MEMORY_THRESHOLD_DEFAULT; + + @AttributeDefinition( + name = "Compaction progress log", + description = "The number of nodes compacted after which a status message is logged. " + + "-1 disables progress logging. " + + "Default value is '" + GC_PROGRESS_LOG_DEFAULT + "'." + ) + long compaction_progressLog() default GC_PROGRESS_LOG_DEFAULT; + + @AttributeDefinition( + name = "Standby mode", + description = "Flag indicating this component will not register as a NodeStore but as a " + + "NodeStoreProvider instead. " + + "Default value is 'false'." + ) + boolean standby() default false; + + @AttributeDefinition( + name = "Custom blob store", + description = "Boolean value indicating that a custom BlobStore is used for storing " + + "large binary values." + ) + boolean customBlobStore() default false; + + @AttributeDefinition( + name = "Custom segment store", + description = "Boolean value indicating that a custom (non-tar) segment store is used" + ) + boolean customSegmentStore() default false; + + @AttributeDefinition( + name = "Backup directory", + description = "Directory (relative to current working directory) for storing repository backups. " + + "Defaults to 'repository.home/segmentstore-backup'." + ) + String repository_backup_dir() default ""; + + @AttributeDefinition( + name = "Blob gc max age (in secs)", + description = "The blob garbage collection logic will only consider those blobs which " + + "are not accessed recently (currentTime - lastModifiedTime > blobGcMaxAgeInSecs). " + + "For example with the default setting only those blobs which have been created " + + "at least 24 hours ago will be considered for garbage collection. " + + "Default value is '" + DEFAULT_BLOB_GC_MAX_AGE + "'." + ) + long blobGcMaxAgeInSecs() default DEFAULT_BLOB_GC_MAX_AGE; + + @AttributeDefinition( + name = "Blob tracking snapshot interval", + description = "Interval in seconds in which snapshots of locally tracked blob ids are " + + "taken and synchronized with the blob store. This should be configured to be " + + "less than the frequency of blob garbage collection so that deletions during blob " + + "garbage collection can be accounted for in the next garbage collection execution. " + + "Default value is '" + DEFAULT_BLOB_SNAPSHOT_INTERVAL + "'." + ) + long blobTrackSnapshotIntervalInSecs() default DEFAULT_BLOB_SNAPSHOT_INTERVAL; + + @AttributeDefinition( + name = "Role", + description = "As multiple SegmentNodeStores can be configured, this parameter defines the role " + + "of 'this' SegmentNodeStore." + ) + String role() default ""; + + @AttributeDefinition( + name = "Register JCR descriptors as OSGi services", + description = "Should only be done for one factory instance" + ) + boolean registerDescriptors() default false; + } @Reference( - cardinality = ReferenceCardinality.OPTIONAL_UNARY, - policy = ReferencePolicy.STATIC, - policyOption = ReferencePolicyOption.GREEDY, - target = ONLY_STANDALONE_TARGET + cardinality = ReferenceCardinality.OPTIONAL, + policy = ReferencePolicy.STATIC, + policyOption = ReferencePolicyOption.GREEDY, + target = ONLY_STANDALONE_TARGET ) private volatile BlobStore blobStore; @Reference( - cardinality = ReferenceCardinality.OPTIONAL_UNARY, - policy = ReferencePolicy.STATIC, - policyOption = ReferencePolicyOption.GREEDY + cardinality = ReferenceCardinality.OPTIONAL, + policy = ReferencePolicy.STATIC, + policyOption = ReferencePolicyOption.GREEDY ) private volatile SegmentNodeStorePersistence segmentStore; @Reference private StatisticsProvider statisticsProvider = StatisticsProvider.NOOP; - private Closer registrations = Closer.create(); + private final Closer registrations = Closer.create(); @Activate - public void activate(ComponentContext context) throws IOException { - String role = property(ROLE, context); - // In secondaryNodeStore mode customBlobStore is always enabled - boolean isSecondaryStoreMode = "secondary".equals(role); - boolean customBlobStore = Boolean.parseBoolean(property(CUSTOM_BLOB_STORE, context)) || isSecondaryStoreMode; - boolean customSegmentStore = Boolean.parseBoolean(property(CUSTOM_SEGMENT_STORE, context)); - boolean registerRepositoryDescriptors = Boolean.parseBoolean(property(REGISTER_DESCRIPTORS, context)); - log.info("activate: SegmentNodeStore '" + role + "' starting."); - - if (blobStore == null && customBlobStore) { - log.info("BlobStore use enabled. SegmentNodeStore would be initialized when BlobStore would be available"); + public void activate(ComponentContext context, Configuration configuration) throws IOException { + log.info("activate: SegmentNodeStore '" + configuration.role() + "' starting."); + + if (configuration.role().isEmpty()) { return; } - if (segmentStore == null && customSegmentStore) { - log.info("customSegmentStore enabled. SegmentNodeStore will be initialized once the custom segment store becomes available"); + OsgiWhiteboard whiteboard = new OsgiWhiteboard(context.getBundleContext()); + SegmentNodeStore store = registerSegmentStore( + context, + configuration, + blobStore, + segmentStore, + getRoleStatisticsProvider(statisticsProvider, configuration.role()), + registrations, + whiteboard, + configuration.role(), + log + ); + + if (store == null) { return; } - if (role != null) { - registrations = Closer.create(); - OsgiWhiteboard whiteboard = new OsgiWhiteboard(context.getBundleContext()); - final SegmentNodeStore store = SegmentNodeStoreService.registerSegmentStore(context, blobStore, segmentStore, - getRoleStatisticsProvider(statisticsProvider, role), registrations, whiteboard, role, registerRepositoryDescriptors); - if (store != null) { - Map props = new HashMap(); - props.put(NodeStoreProvider.ROLE, role); - - registrations - .register(asCloseable(whiteboard.register(NodeStoreProvider.class, new NodeStoreProvider() { - - @Override - public NodeStore getNodeStore() { - return store; - } - }, props))); - log.info("Registered NodeStoreProvider backed by SegmentNodeStore of type '{}'", role); - } - } + Map props = new HashMap(); + props.put(NodeStoreProvider.ROLE, configuration.role()); + registrations.register(asCloseable(whiteboard.register(NodeStoreProvider.class, () -> store, props))); + log.info("Registered NodeStoreProvider backed by SegmentNodeStore of type '{}'", configuration.role()); } @Deactivate public void deactivate() { - if (registrations != null) { - IOUtils.closeQuietly(registrations); - registrations = null; - } + closeQuietly(registrations); } private static Closeable asCloseable(final Registration r) { @@ -180,20 +362,250 @@ public class SegmentNodeStoreFactory { return lookupConfigurationThenFramework(context, name); } - private static StatisticsProvider getRoleStatisticsProvider(StatisticsProvider delegate, String role) { - RepositoryStatistics repositoryStatistics = new RepositoryStatistics() { - @Override - public TimeSeries getTimeSeries(Type type) { - return getTimeSeries(type.name(), type.isResetValueEachSecond()); + private static SegmentNodeStore registerSegmentStore( + ComponentContext context, + Configuration configuration, + BlobStore blobStore, + SegmentNodeStorePersistence segmentStore, + StatisticsProvider statisticsProvider, + Closer closer, + Whiteboard whiteboard, + String role, + Logger logger + ) throws IOException { + return SegmentNodeStoreRegistrar.registerSegmentNodeStore(new SegmentNodeStoreRegistrar.Configuration() { + + String appendRole(String name) { + return name + "-" + role; + } + + int roundToNextPowerOfTwo(int size) { + return 1 << (32 - Integer.numberOfLeadingZeros(Math.max(0, size - 1))); + } + + String getMode() { + String mode = configuration.tarmk_mode(); + if (isNullOrEmpty(mode)) { + return System.getProperty("tarmk.mode", System.getProperty("sun.arch.data.model", "32")); + } + return mode; + } + + int getCacheSize(String name, int otherwise) { + Integer size = Integer.getInteger(name); + if (size != null) { + return size; + } + return otherwise; + } + + @Override + public boolean isPrimarySegmentStore() { + return false; + } + + @Override + public boolean isSecondarySegmentStore() { + return "secondary".equals(role); + } + + @Override + public boolean isStandbyInstance() { + return configuration.standby(); + } + + @Override + public String getRole() { + return role; + } + + @Override + public int getRetainedGenerations() { + return configuration.compaction_retainedGenerations(); + } + + @Override + public int getDefaultRetainedGenerations() { + return RETAINED_GENERATIONS_DEFAULT; + } + + @Override + public boolean getPauseCompaction() { + return configuration.pauseCompaction(); + } + + @Override + public int getRetryCount() { + return configuration.compaction_retryCount(); + } + + @Override + public int getForceCompactionTimeout() { + return configuration.compaction_force_timeout(); + } + + @Override + public long getSizeDeltaEstimation() { + return configuration.compaction_sizeDeltaEstimation(); + } + + @Override + public int getMemoryThreshold() { + return configuration.compaction_memoryThreshold(); + } + + @Override + public boolean getDisableEstimation() { + return configuration.compaction_disableEstimation(); + } + + @Override + public long getGCProcessLog() { + return configuration.compaction_progressLog(); + } + + @Override + public File getSegmentDirectory() { + return new File(getRepositoryHome(), appendRole("segmentstore")); + } + + @Override + public int getSegmentCacheSize() { + return getCacheSize("segmentCache.size", configuration.segmentCache_size()); + } + + @Override + public int getStringCacheSize() { + return getCacheSize("stringCache.size", configuration.stringCache_size()); + } + + @Override + public int getTemplateCacheSize() { + return getCacheSize("templateCache.size", configuration.templateCache_size()); + } + + @Override + public int getStringDeduplicationCacheSize() { + return getCacheSize("stringDeduplicationCache.size", configuration.stringDeduplicationCache_size()); + } + + @Override + public int getTemplateDeduplicationCacheSize() { + return getCacheSize("templateDeduplicationCache.size", configuration.templateDeduplicationCache_size()); + } + + @Override + public int getNodeDeduplicationCacheSize() { + return roundToNextPowerOfTwo(getCacheSize("nodeDeduplicationCache.size", configuration.nodeDeduplicationCache_size())); + } + + @Override + public int getMaxFileSize() { + return configuration.tarmk_size(); + } + + @Override + public boolean getMemoryMapping() { + return getMode().equals("64"); + } + + @Override + public boolean hasCustomBlobStore() { + return configuration.customBlobStore(); + } + + @Override + public boolean hasCustomSegmentStore() { + return configuration.customSegmentStore(); + } + + @Override + public boolean registerDescriptors() { + return configuration.registerDescriptors(); + } + + @Override + public String getRepositoryHome() { + String repositoryHome = OsgiUtil.lookupConfigurationThenFramework(context, "repository.home"); + if (isNullOrEmpty(repositoryHome)) { + return "repository"; } + return repositoryHome; + } + + @Override + public long getBlobSnapshotInterval() { + return configuration.blobTrackSnapshotIntervalInSecs(); + } + + @Override + public long getBlobGcMaxAge() { + return configuration.blobGcMaxAgeInSecs(); + } - @Override - public TimeSeries getTimeSeries(String type, boolean resetValueEachSecond) { - return delegate.getStats().getTimeSeries(addRoleToName(type, role), resetValueEachSecond); + @Override + public File getBackupDirectory() { + String backupDirectory = configuration.repository_backup_dir(); + if (isNullOrEmpty(backupDirectory)) { + return new File(getRepositoryHome(), appendRole("segmentstore-backup")); } + return new File(backupDirectory); + } + + @Override + public Whiteboard getWhiteboard() { + return whiteboard; + } + + @Override + public Closer getCloser() { + return closer; + } + + @Override + public Logger getLogger() { + return logger; + } + + @Override + public StatisticsProvider getStatisticsProvider() { + return statisticsProvider; + } + + @Override + public BlobStore getBlobStore() { + return blobStore; + } + + @Override + public SegmentNodeStorePersistence getSegmentNodeStorePersistence() { + return segmentStore; + } + + @Override + public BundleContext getBundleContext() { + return context.getBundleContext(); + } + + }); + } + + private static StatisticsProvider getRoleStatisticsProvider(StatisticsProvider delegate, String role) { + RepositoryStatistics repositoryStatistics = new RepositoryStatistics() { + + @Override + public TimeSeries getTimeSeries(Type type) { + return getTimeSeries(type.name(), type.isResetValueEachSecond()); + } + + @Override + public TimeSeries getTimeSeries(String type, boolean resetValueEachSecond) { + return delegate.getStats().getTimeSeries(addRoleToName(type, role), resetValueEachSecond); + } }; return new StatisticsProvider() { + @Override public RepositoryStatistics getStats() { return repositoryStatistics; @@ -218,12 +630,12 @@ public class SegmentNodeStoreFactory { public HistogramStats getHistogram(String name, StatsOptions options) { return delegate.getHistogram(addRoleToName(name, role), options); } + }; } private static String addRoleToName(String name, String role) { - return new StringBuilder(role).append('.').append(name).toString(); + return role + '.' + name; } - } diff --git a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreMonitorService.java b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreMonitorService.java index dcc2130df2..50cff1d93c 100644 --- a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreMonitorService.java +++ b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreMonitorService.java @@ -19,45 +19,44 @@ package org.apache.jackrabbit.oak.segment; -import java.io.IOException; -import java.util.Map; - -import org.apache.felix.scr.annotations.Activate; -import org.apache.felix.scr.annotations.Component; -import org.apache.felix.scr.annotations.ConfigurationPolicy; -import org.apache.felix.scr.annotations.Property; -import org.apache.felix.scr.annotations.PropertyUnbounded; -import org.apache.felix.scr.annotations.Reference; import org.apache.jackrabbit.oak.commons.PropertiesUtil; -import org.osgi.service.component.ComponentContext; +import org.apache.jackrabbit.oak.segment.SegmentNodeStoreMonitorService.Configuration; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.Designate; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; /** * An OSGi wrapper for segment node store monitoring configurations. */ -@Component(policy = ConfigurationPolicy.REQUIRE, - metatype = true, - label = "Oak Segment Tar Monitoring service", - description = "This service is responsible for different configurations related to " + - "Oak Segment Tar read/write monitoring." -) +@Component(configurationPolicy = ConfigurationPolicy.REQUIRE) +@Designate(ocd = Configuration.class) public class SegmentNodeStoreMonitorService { - - @Property(label = "Writer groups", - unbounded = PropertyUnbounded.ARRAY, - description = "Writer groups for which commits are tracked individually" + + @ObjectClassDefinition( + name = "Oak Segment Tar Monitoring service", + description = "This service is responsible for different configurations related to " + + "Oak Segment Tar read/write monitoring." ) - private static final String COMMITS_TRACKER_WRITER_GROUPS = "commitsTrackerWriterGroups"; + @interface Configuration { + + @AttributeDefinition( + name = "Writer groups", + description = "Writer groups for which commits are tracked individually" + ) + String[] commitsTrackerWriterGroups() default {}; + + } @Reference private SegmentNodeStoreStatsMBean snsStatsMBean; - + @Activate - public void activate(ComponentContext context, Map config) throws IOException { - augmentSegmentNodeStoreStatsMBean(config); + public void activate(Configuration config) { + snsStatsMBean.setWriterGroupsForLastMinuteCounts(PropertiesUtil.toStringArray(config.commitsTrackerWriterGroups(), null)); } - private void augmentSegmentNodeStoreStatsMBean(Map config) { - snsStatsMBean.setWriterGroupsForLastMinuteCounts( - PropertiesUtil.toStringArray(config.get(COMMITS_TRACKER_WRITER_GROUPS), null)); - } } diff --git a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreRegistrar.java b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreRegistrar.java new file mode 100644 index 0000000000..5901ec94f7 --- /dev/null +++ b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreRegistrar.java @@ -0,0 +1,525 @@ +/* + * 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.jackrabbit.oak.segment; + +import static org.apache.jackrabbit.oak.plugins.blob.datastore.SharedDataStoreUtils.isShared; +import static org.apache.jackrabbit.oak.segment.SegmentNotFoundExceptionListener.IGNORE_SNFE; +import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.RETAINED_GENERATIONS_DEFAULT; +import static org.apache.jackrabbit.oak.segment.file.FileStoreBuilder.fileStoreBuilder; +import static org.apache.jackrabbit.oak.spi.cluster.ClusterRepositoryInfo.getOrCreateId; + +import java.io.ByteArrayInputStream; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import com.google.common.io.Closer; +import org.apache.jackrabbit.commons.SimpleValueFactory; +import org.apache.jackrabbit.oak.api.Descriptors; +import org.apache.jackrabbit.oak.api.jmx.CacheStatsMBean; +import org.apache.jackrabbit.oak.api.jmx.CheckpointMBean; +import org.apache.jackrabbit.oak.api.jmx.FileStoreBackupRestoreMBean; +import org.apache.jackrabbit.oak.backup.impl.FileStoreBackupRestoreImpl; +import org.apache.jackrabbit.oak.cache.CacheStats; +import org.apache.jackrabbit.oak.plugins.blob.BlobGC; +import org.apache.jackrabbit.oak.plugins.blob.BlobGCMBean; +import org.apache.jackrabbit.oak.plugins.blob.BlobGarbageCollector; +import org.apache.jackrabbit.oak.plugins.blob.BlobTrackingStore; +import org.apache.jackrabbit.oak.plugins.blob.MarkSweepGarbageCollector; +import org.apache.jackrabbit.oak.plugins.blob.SharedDataStore; +import org.apache.jackrabbit.oak.plugins.blob.datastore.BlobIdTracker; +import org.apache.jackrabbit.oak.plugins.blob.datastore.SharedDataStoreUtils; +import org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions; +import org.apache.jackrabbit.oak.segment.compaction.SegmentRevisionGC; +import org.apache.jackrabbit.oak.segment.compaction.SegmentRevisionGCMBean; +import org.apache.jackrabbit.oak.segment.file.FileStore; +import org.apache.jackrabbit.oak.segment.file.FileStoreBuilder; +import org.apache.jackrabbit.oak.segment.file.FileStoreGCMonitor; +import org.apache.jackrabbit.oak.segment.file.FileStoreStatsMBean; +import org.apache.jackrabbit.oak.segment.file.InvalidFileStoreVersionException; +import org.apache.jackrabbit.oak.segment.file.MetricsIOMonitor; +import org.apache.jackrabbit.oak.segment.spi.persistence.SegmentNodeStorePersistence; +import org.apache.jackrabbit.oak.spi.blob.BlobStore; +import org.apache.jackrabbit.oak.spi.blob.GarbageCollectableBlobStore; +import org.apache.jackrabbit.oak.spi.cluster.ClusterRepositoryInfo; +import org.apache.jackrabbit.oak.spi.commit.ObserverTracker; +import org.apache.jackrabbit.oak.spi.descriptors.GenericDescriptors; +import org.apache.jackrabbit.oak.spi.gc.GCMonitor; +import org.apache.jackrabbit.oak.spi.gc.GCMonitorTracker; +import org.apache.jackrabbit.oak.spi.state.NodeStore; +import org.apache.jackrabbit.oak.spi.state.RevisionGC; +import org.apache.jackrabbit.oak.spi.state.RevisionGCMBean; +import org.apache.jackrabbit.oak.spi.whiteboard.AbstractServiceTracker; +import org.apache.jackrabbit.oak.spi.whiteboard.Registration; +import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard; +import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardExecutor; +import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils; +import org.apache.jackrabbit.oak.stats.Clock; +import org.apache.jackrabbit.oak.stats.StatisticsProvider; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.slf4j.Logger; + +class SegmentNodeStoreRegistrar { + + static SegmentNodeStore registerSegmentNodeStore(Configuration cfg) throws IOException { + return new SegmentNodeStoreRegistrar(cfg).register(); + } + + interface Configuration { + + boolean isPrimarySegmentStore(); + + boolean isSecondarySegmentStore(); + + boolean isStandbyInstance(); + + String getRole(); + + int getRetainedGenerations(); + + int getDefaultRetainedGenerations(); + + boolean getPauseCompaction(); + + int getRetryCount(); + + int getForceCompactionTimeout(); + + long getSizeDeltaEstimation(); + + int getMemoryThreshold(); + + boolean getDisableEstimation(); + + long getGCProcessLog(); + + File getSegmentDirectory(); + + int getSegmentCacheSize(); + + int getStringCacheSize(); + + int getTemplateCacheSize(); + + int getStringDeduplicationCacheSize(); + + int getTemplateDeduplicationCacheSize(); + + int getNodeDeduplicationCacheSize(); + + int getMaxFileSize(); + + boolean getMemoryMapping(); + + boolean hasCustomBlobStore(); + + boolean hasCustomSegmentStore(); + + boolean registerDescriptors(); + + String getRepositoryHome(); + + long getBlobSnapshotInterval(); + + long getBlobGcMaxAge(); + + File getBackupDirectory(); + + Whiteboard getWhiteboard(); + + Closer getCloser(); + + Logger getLogger(); + + StatisticsProvider getStatisticsProvider(); + + BlobStore getBlobStore(); + + SegmentNodeStorePersistence getSegmentNodeStorePersistence(); + + BundleContext getBundleContext(); + + } + + private final Configuration cfg; + + private SegmentNodeStoreRegistrar(Configuration cfg) { + this.cfg = cfg; + } + + private SegmentNodeStore register() throws IOException { + if (cfg.getBlobStore() == null && (cfg.hasCustomBlobStore() || cfg.isSecondarySegmentStore())) { + cfg.getLogger().info("BlobStore enabled. SegmentNodeStore will be initialized once the blob store becomes available"); + return null; + } + + if (cfg.getSegmentNodeStorePersistence() == null && cfg.hasCustomSegmentStore()) { + cfg.getLogger().info("customSegmentStore enabled. SegmentNodeStore will be initialized once the custom segment store becomes available"); + return null; + } + + // Listen for GCMonitor services + GCMonitor gcMonitor = GCMonitor.EMPTY; + + if (cfg.isPrimarySegmentStore()) { + GCMonitorTracker tracker = new GCMonitorTracker(); + tracker.start(cfg.getWhiteboard()); + registerCloseable(tracker); + gcMonitor = tracker; + } + + // Create the gc options + if (cfg.getRetainedGenerations() != cfg.getDefaultRetainedGenerations()) { + cfg.getLogger().warn( + "The number of retained generations defaults to {} and can't be " + + "changed. This configuration option is considered deprecated " + + "and will be removed in the future.", + RETAINED_GENERATIONS_DEFAULT + ); + } + SegmentGCOptions gcOptions = new SegmentGCOptions(cfg.getPauseCompaction(), cfg.getRetryCount(), cfg.getForceCompactionTimeout()) + .setGcSizeDeltaEstimation(cfg.getSizeDeltaEstimation()) + .setMemoryThreshold(cfg.getMemoryThreshold()) + .setEstimationDisabled(cfg.getDisableEstimation()) + .setGCLogInterval(cfg.getGCProcessLog()); + if (cfg.isStandbyInstance()) { + gcOptions.setRetainedGenerations(1); + } + + // Build the FileStore + FileStoreBuilder builder = fileStoreBuilder(cfg.getSegmentDirectory()) + .withSegmentCacheSize(cfg.getSegmentCacheSize()) + .withStringCacheSize(cfg.getStringCacheSize()) + .withTemplateCacheSize(cfg.getTemplateCacheSize()) + .withStringDeduplicationCacheSize(cfg.getStringDeduplicationCacheSize()) + .withTemplateDeduplicationCacheSize(cfg.getTemplateDeduplicationCacheSize()) + .withNodeDeduplicationCacheSize(cfg.getNodeDeduplicationCacheSize()) + .withMaxFileSize(cfg.getMaxFileSize()) + .withMemoryMapping(cfg.getMemoryMapping()) + .withGCMonitor(gcMonitor) + .withIOMonitor(new MetricsIOMonitor(cfg.getStatisticsProvider())) + .withStatisticsProvider(cfg.getStatisticsProvider()) + .withGCOptions(gcOptions); + + if (cfg.hasCustomBlobStore() && cfg.getBlobStore() != null) { + cfg.getLogger().info("Initializing SegmentNodeStore with BlobStore [{}]", cfg.getBlobStore()); + builder.withBlobStore(cfg.getBlobStore()); + } + + if (cfg.hasCustomSegmentStore() && cfg.getSegmentNodeStorePersistence() != null) { + cfg.getLogger().info("Initializing SegmentNodeStore with custom persistence [{}]", cfg.getSegmentNodeStorePersistence()); + builder.withCustomPersistence(cfg.getSegmentNodeStorePersistence()); + } + + if (cfg.isStandbyInstance()) { + builder.withSnfeListener(IGNORE_SNFE); + } + + FileStore store; + try { + store = builder.build(); + } catch (InvalidFileStoreVersionException e) { + cfg.getLogger().error("The storage format is not compatible with this version of Oak Segment Tar", e); + return null; + } + registerCloseable(store); + + // Listen for Executor services on the whiteboard + + WhiteboardExecutor executor = new WhiteboardExecutor(); + executor.start(cfg.getWhiteboard()); + registerCloseable(executor); + + // Expose stats about the segment cache + + CacheStatsMBean segmentCacheStats = store.getSegmentCacheStats(); + registerCloseable(registerMBean( + CacheStatsMBean.class, + segmentCacheStats, + CacheStats.TYPE, + segmentCacheStats.getName() + )); + + // Expose stats about the string and template caches + + CacheStatsMBean stringCacheStats = store.getStringCacheStats(); + registerCloseable(registerMBean( + CacheStatsMBean.class, + stringCacheStats, + CacheStats.TYPE, + stringCacheStats.getName() + )); + + CacheStatsMBean templateCacheStats = store.getTemplateCacheStats(); + registerCloseable(registerMBean( + CacheStatsMBean.class, + templateCacheStats, + CacheStats.TYPE, + templateCacheStats.getName() + )); + + WriterCacheManager cacheManager = builder.getCacheManager(); + CacheStatsMBean stringDeduplicationCacheStats = cacheManager.getStringCacheStats(); + if (stringDeduplicationCacheStats != null) { + registerCloseable(registerMBean( + CacheStatsMBean.class, + stringDeduplicationCacheStats, + CacheStats.TYPE, + stringDeduplicationCacheStats.getName() + )); + } + + CacheStatsMBean templateDeduplicationCacheStats = cacheManager.getTemplateCacheStats(); + if (templateDeduplicationCacheStats != null) { + registerCloseable(registerMBean( + CacheStatsMBean.class, + templateDeduplicationCacheStats, + CacheStats.TYPE, + templateDeduplicationCacheStats.getName() + )); + } + + CacheStatsMBean nodeDeduplicationCacheStats = cacheManager.getNodeCacheStats(); + if (nodeDeduplicationCacheStats != null) { + registerCloseable(registerMBean( + CacheStatsMBean.class, + nodeDeduplicationCacheStats, + CacheStats.TYPE, + nodeDeduplicationCacheStats.getName() + )); + } + + // Expose an MBean to managing and monitoring garbage collection + + FileStoreGCMonitor monitor = new FileStoreGCMonitor(Clock.SIMPLE); + registerCloseable(register( + GCMonitor.class, + monitor + )); + if (!cfg.isStandbyInstance()) { + registerCloseable(registerMBean( + SegmentRevisionGC.class, + new SegmentRevisionGCMBean(store, gcOptions, monitor), + SegmentRevisionGC.TYPE, + "Segment node store revision garbage collection" + )); + } + + registerCloseable(registerMBean( + RevisionGCMBean.class, + new RevisionGC(store.getGCRunner(), store::cancelGC, monitor::getStatus, executor), + RevisionGCMBean.TYPE, + "Revision garbage collection" + )); + + // Expose statistics about the FileStore + + registerCloseable(registerMBean( + FileStoreStatsMBean.class, + store.getStats(), + FileStoreStatsMBean.TYPE, + "FileStore statistics" + )); + + // register segment node store + + SegmentNodeStore.SegmentNodeStoreBuilder segmentNodeStoreBuilder = SegmentNodeStoreBuilders.builder(store).withStatisticsProvider(cfg.getStatisticsProvider()); + if (cfg.isStandbyInstance() || !cfg.isPrimarySegmentStore()) { + segmentNodeStoreBuilder.dispatchChanges(false); + } + SegmentNodeStore segmentNodeStore = segmentNodeStoreBuilder.build(); + + if (cfg.isPrimarySegmentStore()) { + ObserverTracker observerTracker = new ObserverTracker(segmentNodeStore); + observerTracker.start(cfg.getBundleContext()); + registerCloseable(observerTracker); + } + + if (cfg.isPrimarySegmentStore()) { + registerCloseable(registerMBean( + CheckpointMBean.class, + new SegmentCheckpointMBean(segmentNodeStore), + CheckpointMBean.TYPE, + "Segment node store checkpoint management" + )); + } + + if (cfg.registerDescriptors()) { + // ensure a clusterId is initialized + // and expose it as 'oak.clusterid' repository descriptor + GenericDescriptors clusterIdDesc = new GenericDescriptors(); + clusterIdDesc.put( + ClusterRepositoryInfo.OAK_CLUSTERID_REPOSITORY_DESCRIPTOR_KEY, + new SimpleValueFactory().createValue(getOrCreateId(segmentNodeStore)), + true, + false + ); + registerCloseable(register(Descriptors.class, clusterIdDesc)); + // Register "discovery lite" descriptors + registerCloseable(register(Descriptors.class, new SegmentDiscoveryLiteDescriptors(segmentNodeStore))); + } + + // If a shared data store register the repo id in the data store + if (cfg.isPrimarySegmentStore() && isShared(cfg.getBlobStore())) { + SharedDataStore sharedDataStore = (SharedDataStore) cfg.getBlobStore(); + try { + sharedDataStore.addMetadataRecord(new ByteArrayInputStream(new byte[0]), SharedDataStoreUtils.SharedStoreRecordType.REPOSITORY.getNameFromId(getOrCreateId(segmentNodeStore))); + } catch (Exception e) { + throw new IOException("Could not register a unique repositoryId", e); + } + if (cfg.getBlobStore() instanceof BlobTrackingStore) { + BlobTrackingStore trackingStore = (BlobTrackingStore) cfg.getBlobStore(); + if (trackingStore.getTracker() != null) { + trackingStore.getTracker().close(); + } + trackingStore.addTracker(new BlobIdTracker(cfg.getRepositoryHome(), getOrCreateId(segmentNodeStore), cfg.getBlobSnapshotInterval(), sharedDataStore)); + } + } + + if (cfg.isPrimarySegmentStore() && cfg.getBlobStore() instanceof GarbageCollectableBlobStore) { + BlobGarbageCollector gc = new MarkSweepGarbageCollector( + new SegmentBlobReferenceRetriever(store), + (GarbageCollectableBlobStore) cfg.getBlobStore(), + executor, + TimeUnit.SECONDS.toMillis(cfg.getBlobGcMaxAge()), + getOrCreateId(segmentNodeStore), + cfg.getWhiteboard(), + cfg.getStatisticsProvider() + ); + registerCloseable(registerMBean( + BlobGCMBean.class, + new BlobGC(gc, executor), + BlobGCMBean.TYPE, + "Segment node store blob garbage collection" + )); + } + + // Expose an MBean for backup/restore operations + + registerCloseable(registerMBean( + FileStoreBackupRestoreMBean.class, + new FileStoreBackupRestoreImpl( + segmentNodeStore, + store.getRevisions(), + store.getReader(), + cfg.getBackupDirectory(), + executor + ), + FileStoreBackupRestoreMBean.TYPE, + "Segment node store backup/restore" + )); + + // Expose statistics about the SegmentNodeStore + + registerCloseable(registerMBean( + SegmentNodeStoreStatsMBean.class, + segmentNodeStore.getStats(), + SegmentNodeStoreStatsMBean.TYPE, + "SegmentNodeStore statistics" + )); + + if (cfg.isPrimarySegmentStore()) { + cfg.getLogger().info("Primary SegmentNodeStore initialized"); + } else { + cfg.getLogger().info("Secondary SegmentNodeStore initialized, role={}", cfg.getRole()); + } + + // Register a factory service to expose the FileStore + registerCloseable(register( + SegmentStoreProvider.class, + new DefaultSegmentStoreProvider(store) + )); + + if (cfg.isStandbyInstance()) { + return segmentNodeStore; + } + + if (cfg.isPrimarySegmentStore()) { + Map props = new HashMap<>(); + props.put(Constants.SERVICE_PID, SegmentNodeStore.class.getName()); + props.put("oak.nodestore.description", new String[] {"nodeStoreType=segment"}); + registerCloseable(register(NodeStore.class, segmentNodeStore, props)); + } + + return segmentNodeStore; + } + + private Registration registerMBean(Class clazz, T bean, String type, String name) { + return registerMBean(clazz, bean, type, name, new HashMap<>()); + } + + private Registration registerMBean(Class clazz, T bean, String type, String name, Map attributes) { + return WhiteboardUtils.registerMBean(cfg.getWhiteboard(), clazz, bean, type, maybeAppendRole(name), maybePutRoleAttribute(attributes)); + } + + private Registration register(Class clazz, T service) { + return register(clazz, service, new HashMap<>()); + } + + private Registration register(Class clazz, T service, Map properties) { + return cfg.getWhiteboard().register(clazz, service, maybePutRoleProperty(properties)); + } + + private String maybeAppendRole(String name) { + if (cfg.getRole() != null) { + return name + " - " + cfg.getRole(); + } + return name; + } + + private String jmxRole() { + return cfg.getRole().replaceAll(":", "-"); + } + + private Map maybePutRoleAttribute(Map attributes) { + if (cfg.getRole() != null) { + attributes.put("role", jmxRole()); + } + return attributes; + } + + private Map maybePutRoleProperty(Map attributes) { + if (cfg.getRole() != null) { + attributes.put("role", cfg.getRole()); + } + return attributes; + } + + private void registerCloseable(Closeable c) { + cfg.getCloser().register(c); + } + + private void registerCloseable(final AbstractServiceTracker t) { + registerCloseable((Closeable) t::stop); + } + + private void registerCloseable(final Registration r) { + registerCloseable((Closeable) r::unregister); + } + + private void registerCloseable(final ObserverTracker t) { + registerCloseable((Closeable) t::stop); + } + +} diff --git a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreService.java b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreService.java index b37eac6c7c..e195de2869 100644 --- a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreService.java +++ b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreService.java @@ -21,40 +21,9 @@ package org.apache.jackrabbit.oak.segment; import static com.google.common.base.Strings.isNullOrEmpty; import static org.apache.jackrabbit.oak.commons.IOUtils.closeQuietly; -import static org.apache.jackrabbit.oak.commons.PropertiesUtil.toBoolean; -import static org.apache.jackrabbit.oak.commons.PropertiesUtil.toInteger; -import static org.apache.jackrabbit.oak.commons.PropertiesUtil.toLong; -import static org.apache.jackrabbit.oak.osgi.OsgiUtil.lookupConfigurationThenFramework; -import static org.apache.jackrabbit.oak.plugins.blob.datastore.SharedDataStoreUtils.isShared; import static org.apache.jackrabbit.oak.segment.CachingSegmentReader.DEFAULT_STRING_CACHE_MB; import static org.apache.jackrabbit.oak.segment.CachingSegmentReader.DEFAULT_TEMPLATE_CACHE_MB; import static org.apache.jackrabbit.oak.segment.SegmentCache.DEFAULT_SEGMENT_CACHE_MB; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.BACKUP_DIRECTORY; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.COMPACTION_DISABLE_ESTIMATION; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.COMPACTION_FORCE_TIMEOUT; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.COMPACTION_RETRY_COUNT; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.COMPACTION_SIZE_DELTA_ESTIMATION; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.CUSTOM_BLOB_STORE; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.CUSTOM_SEGMENT_STORE; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.DEFAULT_BLOB_GC_MAX_AGE; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.DEFAULT_BLOB_SNAPSHOT_INTERVAL; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.GC_PROGRESS_LOG; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.MEMORY_THRESHOLD; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.MODE; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.NODE_DEDUPLICATION_CACHE_SIZE; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.PAUSE_COMPACTION; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.PROP_BLOB_GC_MAX_AGE; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.PROP_BLOB_SNAPSHOT_INTERVAL; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.REPOSITORY_HOME_DIRECTORY; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.RETAINED_GENERATIONS; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.SEGMENT_CACHE_SIZE; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.SIZE; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.STANDBY; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.STRING_CACHE_SIZE; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.STRING_DEDUPLICATION_CACHE_SIZE; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.TEMPLATE_CACHE_SIZE; -import static org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.TEMPLATE_DEDUPLICATION_CACHE_SIZE; -import static org.apache.jackrabbit.oak.segment.SegmentNotFoundExceptionListener.IGNORE_SNFE; import static org.apache.jackrabbit.oak.segment.WriterCacheManager.DEFAULT_NODE_CACHE_SIZE_OSGi; import static org.apache.jackrabbit.oak.segment.WriterCacheManager.DEFAULT_STRING_CACHE_SIZE_OSGi; import static org.apache.jackrabbit.oak.segment.WriterCacheManager.DEFAULT_TEMPLATE_CACHE_SIZE_OSGi; @@ -67,995 +36,516 @@ import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.RETA import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.RETRY_COUNT_DEFAULT; import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.SIZE_DELTA_ESTIMATION_DEFAULT; import static org.apache.jackrabbit.oak.segment.file.FileStoreBuilder.DEFAULT_MAX_FILE_SIZE; -import static org.apache.jackrabbit.oak.segment.file.FileStoreBuilder.fileStoreBuilder; import static org.apache.jackrabbit.oak.spi.blob.osgi.SplitBlobStoreService.ONLY_STANDALONE_TARGET; -import static org.apache.jackrabbit.oak.spi.cluster.ClusterRepositoryInfo.getOrCreateId; -import java.io.ByteArrayInputStream; -import java.io.Closeable; import java.io.File; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import com.google.common.base.Supplier; import com.google.common.io.Closer; -import org.apache.felix.scr.annotations.Activate; -import org.apache.felix.scr.annotations.Component; -import org.apache.felix.scr.annotations.ConfigurationPolicy; -import org.apache.felix.scr.annotations.Deactivate; -import org.apache.felix.scr.annotations.Property; -import org.apache.felix.scr.annotations.Reference; -import org.apache.felix.scr.annotations.ReferenceCardinality; -import org.apache.felix.scr.annotations.ReferencePolicy; -import org.apache.felix.scr.annotations.ReferencePolicyOption; -import org.apache.jackrabbit.commons.SimpleValueFactory; -import org.apache.jackrabbit.oak.api.Descriptors; -import org.apache.jackrabbit.oak.api.jmx.CacheStatsMBean; -import org.apache.jackrabbit.oak.api.jmx.CheckpointMBean; -import org.apache.jackrabbit.oak.api.jmx.FileStoreBackupRestoreMBean; -import org.apache.jackrabbit.oak.backup.impl.FileStoreBackupRestoreImpl; -import org.apache.jackrabbit.oak.cache.CacheStats; -import org.apache.jackrabbit.oak.cache.CacheStatsMBeanWrapper; +import org.apache.jackrabbit.oak.osgi.OsgiUtil; import org.apache.jackrabbit.oak.osgi.OsgiWhiteboard; -import org.apache.jackrabbit.oak.plugins.blob.BlobGC; -import org.apache.jackrabbit.oak.plugins.blob.BlobGCMBean; -import org.apache.jackrabbit.oak.plugins.blob.BlobGarbageCollector; -import org.apache.jackrabbit.oak.plugins.blob.BlobTrackingStore; -import org.apache.jackrabbit.oak.plugins.blob.MarkSweepGarbageCollector; -import org.apache.jackrabbit.oak.plugins.blob.SharedDataStore; -import org.apache.jackrabbit.oak.plugins.blob.datastore.BlobIdTracker; -import org.apache.jackrabbit.oak.plugins.blob.datastore.SharedDataStoreUtils.SharedStoreRecordType; -import org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions; -import org.apache.jackrabbit.oak.segment.compaction.SegmentRevisionGC; -import org.apache.jackrabbit.oak.segment.compaction.SegmentRevisionGCMBean; -import org.apache.jackrabbit.oak.segment.file.FileStore; -import org.apache.jackrabbit.oak.segment.file.FileStoreBuilder; -import org.apache.jackrabbit.oak.segment.file.FileStoreGCMonitor; -import org.apache.jackrabbit.oak.segment.file.FileStoreStatsMBean; -import org.apache.jackrabbit.oak.segment.file.InvalidFileStoreVersionException; -import org.apache.jackrabbit.oak.segment.file.MetricsIOMonitor; -import org.apache.jackrabbit.oak.segment.file.tar.SegmentTarReader; import org.apache.jackrabbit.oak.segment.spi.persistence.SegmentNodeStorePersistence; -import org.apache.jackrabbit.oak.segment.util.RoleUtils; import org.apache.jackrabbit.oak.spi.blob.BlobStore; -import org.apache.jackrabbit.oak.spi.blob.GarbageCollectableBlobStore; -import org.apache.jackrabbit.oak.spi.cluster.ClusterRepositoryInfo; -import org.apache.jackrabbit.oak.spi.commit.ObserverTracker; -import org.apache.jackrabbit.oak.spi.descriptors.GenericDescriptors; -import org.apache.jackrabbit.oak.spi.gc.GCMonitor; -import org.apache.jackrabbit.oak.spi.gc.GCMonitorTracker; -import org.apache.jackrabbit.oak.spi.state.NodeStore; -import org.apache.jackrabbit.oak.spi.state.RevisionGC; -import org.apache.jackrabbit.oak.spi.state.RevisionGCMBean; -import org.apache.jackrabbit.oak.spi.whiteboard.AbstractServiceTracker; -import org.apache.jackrabbit.oak.spi.whiteboard.Registration; import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard; -import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardExecutor; -import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils; -import org.apache.jackrabbit.oak.stats.Clock; import org.apache.jackrabbit.oak.stats.StatisticsProvider; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.osgi.framework.Constants; +import org.osgi.framework.BundleContext; import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.Designate; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * An OSGi wrapper for the segment node store. */ -@Component(policy = ConfigurationPolicy.REQUIRE, - metatype = true, - label = "Oak Segment Tar NodeStore service", - description = "Apache Jackrabbit Oak NodeStore implementation based on the segment model. " + - "For configuration refer to http://jackrabbit.apache.org/oak/docs/osgi_config.html#SegmentNodeStore. " + - "Note that for system stability purpose it is advisable to not change these settings " + - "at runtime. Instead the config change should be done via file system based config " + - "file and this view should ONLY be used to determine which options are supported." -) +@Component(configurationPolicy = ConfigurationPolicy.REQUIRE) +@Designate(ocd = SegmentNodeStoreService.Configuration.class) public class SegmentNodeStoreService { private static final Logger log = LoggerFactory.getLogger(SegmentNodeStoreService.class); - @Property( - label = "Repository Home Directory", - description = "Path on the file system where repository data will be stored. " - + "Defaults to the value of the framework property 'repository.home' or to 'repository' " - + "if that is neither specified." - ) + // TODO(frm) This is only exposed to tests. Should it be removed? + public static final String CUSTOM_BLOB_STORE = "customBlobStore"; + + // TODO(frm) This is only exposed to tests. Should it be removed? public static final String REPOSITORY_HOME_DIRECTORY = "repository.home"; - @Property( - label = "Mode", - description = "TarMK mode (64 for memory mapped file access, 32 for normal file access). " + - "Default value is taken from the 'sun.arch.data.model' system property." + private static final long DEFAULT_BLOB_SNAPSHOT_INTERVAL = 12 * 60 * 60; + + private static final long DEFAULT_BLOB_GC_MAX_AGE = 24 * 60 * 60; + + @ObjectClassDefinition( + name = "Oak Segment Tar NodeStore service", + description = "Apache Jackrabbit Oak NodeStore implementation based on " + + "the segment model. For configuration refer to http://jackrabbit.apache.org/oak/docs/osgi_config.html#SegmentNodeStore. " + + "Note that for system stability purpose it is advisable to not " + + "change these settings at runtime. Instead the config change should " + + "be done via file system based config file and this view should ONLY " + + "be used to determine which options are supported." ) - public static final String MODE = "tarmk.mode"; + @interface Configuration { - @Property( - intValue = DEFAULT_MAX_FILE_SIZE, - label = "Maximum tar file size (MB)", + @AttributeDefinition( + name = "Repository Home Directory", + description = "Path on the file system where repository data will be stored. " + + "Defaults to the value of the framework property 'repository.home' or to 'repository' " + + "if that is neither specified." + ) + String repository_home() default ""; + + @AttributeDefinition( + name = "Mode", + description = "TarMK mode (64 for memory mapped file access, 32 for normal file access). " + + "Default value is taken from the 'sun.arch.data.model' system property." + ) + String tarmk_mode() default ""; + + @AttributeDefinition( + name = "Maximum tar file size (MB)", description = "The maximum size of the tar files in megabytes. " + - "Default value is '" + DEFAULT_MAX_FILE_SIZE + "'." - ) - public static final String SIZE = "tarmk.size"; + "Default value is '" + DEFAULT_MAX_FILE_SIZE + "'." + ) + int tarmk_size() default DEFAULT_MAX_FILE_SIZE; - @Property( - intValue = DEFAULT_SEGMENT_CACHE_MB, - label = "Segment cache size (MB)", + @AttributeDefinition( + name = "Segment cache size (MB)", description = "Cache size for storing most recently used segments in megabytes. " + - "Default value is '" + DEFAULT_SEGMENT_CACHE_MB + "'." - ) - public static final String SEGMENT_CACHE_SIZE = "segmentCache.size"; + "Default value is '" + DEFAULT_SEGMENT_CACHE_MB + "'." + ) + int segmentCache_size() default DEFAULT_SEGMENT_CACHE_MB; - @Property( - intValue = DEFAULT_STRING_CACHE_MB, - label = "String cache size (MB)", + @AttributeDefinition( + name = "String cache size (MB)", description = "Cache size for storing most recently used strings in megabytes. " + - "Default value is '" + DEFAULT_STRING_CACHE_MB + "'." - ) - public static final String STRING_CACHE_SIZE = "stringCache.size"; + "Default value is '" + DEFAULT_STRING_CACHE_MB + "'." + ) + int stringCache_size() default DEFAULT_STRING_CACHE_MB; - @Property( - intValue = DEFAULT_TEMPLATE_CACHE_MB, - label = "Template cache size (MB)", + @AttributeDefinition( + name = "Template cache size (MB)", description = "Cache size for storing most recently used templates in megabytes. " + - "Default value is '" + DEFAULT_TEMPLATE_CACHE_MB + "'." - ) - public static final String TEMPLATE_CACHE_SIZE = "templateCache.size"; + "Default value is '" + DEFAULT_TEMPLATE_CACHE_MB + "'." + ) + int templateCache_size() default DEFAULT_TEMPLATE_CACHE_MB; - @Property( - intValue = DEFAULT_STRING_CACHE_SIZE_OSGi, - label = "String deduplication cache size (#items)", + @AttributeDefinition( + name = "String deduplication cache size (#items)", description = "Maximum number of strings to keep in the deduplication cache. " + - "Default value is '" + DEFAULT_STRING_CACHE_SIZE_OSGi + "'." - ) - public static final String STRING_DEDUPLICATION_CACHE_SIZE = "stringDeduplicationCache.size"; + "Default value is '" + DEFAULT_STRING_CACHE_SIZE_OSGi + "'." + ) + int stringDeduplicationCache_size() default DEFAULT_STRING_CACHE_SIZE_OSGi; - @Property( - intValue = DEFAULT_TEMPLATE_CACHE_SIZE_OSGi, - label = "Template deduplication cache size (#items)", + @AttributeDefinition( + name = "Template deduplication cache size (#items)", description = "Maximum number of templates to keep in the deduplication cache. " + - "Default value is '" + DEFAULT_TEMPLATE_CACHE_SIZE_OSGi + "'." - ) - public static final String TEMPLATE_DEDUPLICATION_CACHE_SIZE = "templateDeduplicationCache.size"; + "Default value is '" + DEFAULT_TEMPLATE_CACHE_SIZE_OSGi + "'." + ) + int templateDeduplicationCache_size() default DEFAULT_TEMPLATE_CACHE_SIZE_OSGi; - @Property( - intValue = DEFAULT_NODE_CACHE_SIZE_OSGi, - label = "Node deduplication cache size (#items)", + @AttributeDefinition( + name = "Node deduplication cache size (#items)", description = "Maximum number of node to keep in the deduplication cache. If the supplied " + - "value is not a power of 2, it will be rounded up to the next power of 2. " + - "Default value is '" + DEFAULT_NODE_CACHE_SIZE_OSGi + "'." - ) - public static final String NODE_DEDUPLICATION_CACHE_SIZE = "nodeDeduplicationCache.size"; + "value is not a power of 2, it will be rounded up to the next power of 2. " + + "Default value is '" + DEFAULT_NODE_CACHE_SIZE_OSGi + "'." + ) + int nodeDeduplicationCache_size() default DEFAULT_NODE_CACHE_SIZE_OSGi; - @Property( - boolValue = PAUSE_DEFAULT, - label = "Pause compaction", + @AttributeDefinition( + name = "Pause compaction", description = "When set to true the compaction phase is skipped during garbage collection. " + - "Default value is '" + PAUSE_DEFAULT + "'." - ) - public static final String PAUSE_COMPACTION = "pauseCompaction"; + "Default value is '" + PAUSE_DEFAULT + "'." + ) + boolean pauseCompaction() default PAUSE_DEFAULT; - @Property( - intValue = RETRY_COUNT_DEFAULT, - label = "Compaction retries", + @AttributeDefinition( + name = "Compaction retries", description = "Number of tries to compact concurrent commits on top of already " + - "compacted commits. " + - "Default value is '" + RETRY_COUNT_DEFAULT + "'." - ) - public static final String COMPACTION_RETRY_COUNT = "compaction.retryCount"; + "compacted commits. " + + "Default value is '" + RETRY_COUNT_DEFAULT + "'." + ) + int compaction_retryCount() default RETRY_COUNT_DEFAULT; - @Property( - intValue = FORCE_TIMEOUT_DEFAULT, - label = "Force compaction timeout", + @AttributeDefinition( + name = "Force compaction timeout", description = "Number of seconds to attempt to force compact concurrent commits on top " + - "of already compacted commits after the maximum number of retries has been " + - "reached. Forced compaction tries to acquire an exclusive write lock on the " + - "node store, blocking concurrent write access as long as the lock is held. " + - "Default value is '" + FORCE_TIMEOUT_DEFAULT + "'." - ) - public static final String COMPACTION_FORCE_TIMEOUT = "compaction.force.timeout"; - - @Property( - longValue = SIZE_DELTA_ESTIMATION_DEFAULT, - label = "Garbage collection repository size threshold", + "of already compacted commits after the maximum number of retries has been " + + "reached. Forced compaction tries to acquire an exclusive write lock on the " + + "node store, blocking concurrent write access as long as the lock is held. " + + "Default value is '" + FORCE_TIMEOUT_DEFAULT + "'." + ) + int compaction_force_timeout() default FORCE_TIMEOUT_DEFAULT; + + @AttributeDefinition( + name = "Garbage collection repository size threshold", description = "Garbage collection will be skipped unless the repository grew at least by " + - "the number of bytes specified. " + - "Default value is '" + SIZE_DELTA_ESTIMATION_DEFAULT + "'." - ) - public static final String COMPACTION_SIZE_DELTA_ESTIMATION = "compaction.sizeDeltaEstimation"; + "the number of bytes specified. " + + "Default value is '" + SIZE_DELTA_ESTIMATION_DEFAULT + "'." + ) + long compaction_sizeDeltaEstimation() default SIZE_DELTA_ESTIMATION_DEFAULT; - @Property( - boolValue = DISABLE_ESTIMATION_DEFAULT, - label = "Disable estimation phase", + @AttributeDefinition( + name = "Disable estimation phase", description = "Disables the estimation phase allowing garbage collection to run unconditionally. " + - "Default value is '" + DISABLE_ESTIMATION_DEFAULT + "'." - ) - public static final String COMPACTION_DISABLE_ESTIMATION = "compaction.disableEstimation"; + "Default value is '" + DISABLE_ESTIMATION_DEFAULT + "'." + ) + boolean compaction_disableEstimation() default DISABLE_ESTIMATION_DEFAULT; - @Property( - intValue = RETAINED_GENERATIONS_DEFAULT, - label = "Compaction retained generations", + @AttributeDefinition( + name = "Compaction retained generations", description = "Number of segment generations to retain during garbage collection. " + "The number of generations defaults to " + RETAINED_GENERATIONS_DEFAULT + " and " + "can't be changed. This configuration option is considered deprecated " + "and will be removed in the future." - ) - public static final String RETAINED_GENERATIONS = "compaction.retainedGenerations"; + ) + int compaction_retainedGenerations() default RETAINED_GENERATIONS_DEFAULT; - @Property( - intValue = MEMORY_THRESHOLD_DEFAULT, - label = "Compaction memory threshold", + @AttributeDefinition( + name = "Compaction memory threshold", description = "Threshold of available heap memory in percent of total heap memory below " + - "which the compaction phase is canceled. 0 disables heap memory monitoring. " + - "Default value is '" + MEMORY_THRESHOLD_DEFAULT + "'." - ) - public static final String MEMORY_THRESHOLD = "compaction.memoryThreshold"; + "which the compaction phase is canceled. 0 disables heap memory monitoring. " + + "Default value is '" + MEMORY_THRESHOLD_DEFAULT + "'." + ) + int compaction_memoryThreshold() default MEMORY_THRESHOLD_DEFAULT; - @Property( - longValue = GC_PROGRESS_LOG_DEFAULT, - label = "Compaction progress log", + @AttributeDefinition( + name = "Compaction progress log", description = "The number of nodes compacted after which a status message is logged. " + - "-1 disables progress logging. " + - "Default value is '" + GC_PROGRESS_LOG_DEFAULT + "'." - ) - public static final String GC_PROGRESS_LOG = "compaction.progressLog"; + "-1 disables progress logging. " + + "Default value is '" + GC_PROGRESS_LOG_DEFAULT + "'." + ) + long compaction_progressLog() default GC_PROGRESS_LOG_DEFAULT; - @Property( - boolValue = false, - label = "Standby mode", + @AttributeDefinition( + name = "Standby mode", description = "Flag indicating this component will not register as a NodeStore but as a " + - "NodeStoreProvider instead. " + - "Default value is 'false'." - ) - public static final String STANDBY = "standby"; + "NodeStoreProvider instead. " + + "Default value is 'false'." + ) + boolean standby() default false; - @Property(boolValue = false, - label = "Custom blob store", + @AttributeDefinition( + name = "Custom blob store", description = "Boolean value indicating that a custom BlobStore is used for storing " + - "large binary values." - ) - public static final String CUSTOM_BLOB_STORE = "customBlobStore"; + "large binary values." + ) + boolean customBlobStore() default false; - @Property(boolValue = false, - label = "Custom segment store", + @AttributeDefinition( + name = "Custom segment store", description = "Boolean value indicating that a custom (non-tar) segment store is used" - ) - public static final String CUSTOM_SEGMENT_STORE = "customSegmentStore"; + ) + boolean customSegmentStore() default false; - @Property( - label = "Backup directory", + @AttributeDefinition( + name = "Backup directory", description = "Directory (relative to current working directory) for storing repository backups. " + - "Defaults to 'repository.home/segmentstore-backup'." - ) - public static final String BACKUP_DIRECTORY = "repository.backup.dir"; + "Defaults to 'repository.home/segmentstore-backup'." + ) + String repository_backup_dir() default ""; + + @AttributeDefinition( + name = "Blob gc max age (in secs)", + description = "The blob garbage collection logic will only consider those blobs which " + + "are not accessed recently (currentTime - lastModifiedTime > blobGcMaxAgeInSecs). " + + "For example with the default setting only those blobs which have been created " + + "at least 24 hours ago will be considered for garbage collection. " + + "Default value is '" + DEFAULT_BLOB_GC_MAX_AGE + "'." + ) + long blobGcMaxAgeInSecs() default DEFAULT_BLOB_GC_MAX_AGE; + + @AttributeDefinition( + name = "Blob tracking snapshot interval", + description = "Interval in seconds in which snapshots of locally tracked blob ids are " + + "taken and synchronized with the blob store. This should be configured to be " + + "less than the frequency of blob garbage collection so that deletions during blob " + + "garbage collection can be accounted for in the next garbage collection execution. " + + "Default value is '" + DEFAULT_BLOB_SNAPSHOT_INTERVAL + "'." + ) + long blobTrackSnapshotIntervalInSecs() default DEFAULT_BLOB_SNAPSHOT_INTERVAL; + + } @Reference( - cardinality = ReferenceCardinality.OPTIONAL_UNARY, - policy = ReferencePolicy.STATIC, - policyOption = ReferencePolicyOption.GREEDY, - target = ONLY_STANDALONE_TARGET + cardinality = ReferenceCardinality.OPTIONAL, + policy = ReferencePolicy.STATIC, + policyOption = ReferencePolicyOption.GREEDY, + target = ONLY_STANDALONE_TARGET ) private volatile BlobStore blobStore; @Reference( - cardinality = ReferenceCardinality.OPTIONAL_UNARY, - policy = ReferencePolicy.STATIC, - policyOption = ReferencePolicyOption.GREEDY + cardinality = ReferenceCardinality.OPTIONAL, + policy = ReferencePolicy.STATIC, + policyOption = ReferencePolicyOption.GREEDY ) private volatile SegmentNodeStorePersistence segmentStore; @Reference private StatisticsProvider statisticsProvider = StatisticsProvider.NOOP; - - private Closer closer; - - /** - * Blob modified before this time duration would be considered for Blob GC - */ - static final long DEFAULT_BLOB_GC_MAX_AGE = 24 * 60 * 60; - - @Property(longValue = DEFAULT_BLOB_GC_MAX_AGE, - label = "Blob gc max age (in secs)", - description = "The blob garbage collection logic will only consider those blobs which " + - "are not accessed recently (currentTime - lastModifiedTime > blobGcMaxAgeInSecs). " + - "For example with the default setting only those blobs which have been created " + - "at least 24 hours ago will be considered for garbage collection. " + - "Default value is '" + DEFAULT_BLOB_GC_MAX_AGE + "'." - ) - public static final String PROP_BLOB_GC_MAX_AGE = "blobGcMaxAgeInSecs"; - /** - * Default interval for taking snapshots of locally tracked blob ids. - */ - static final long DEFAULT_BLOB_SNAPSHOT_INTERVAL = 12 * 60 * 60; - - @Property(longValue = DEFAULT_BLOB_SNAPSHOT_INTERVAL, - label = "Blob tracking snapshot interval", - description = "Interval in seconds in which snapshots of locally tracked blob ids are " + - "taken and synchronized with the blob store. This should be configured to be " + - "less than the frequency of blob garbage collection so that deletions during blob " + - "garbage collection can be accounted for in the next garbage collection execution. " + - "Default value is '" + DEFAULT_BLOB_SNAPSHOT_INTERVAL + "'." - ) - public static final String PROP_BLOB_SNAPSHOT_INTERVAL = "blobTrackSnapshotIntervalInSecs"; + private final Closer closer = Closer.create(); @Activate - public void activate(ComponentContext context) throws IOException { - Configuration configuration = new Configuration(context); - if (blobStore == null && configuration.hasCustomBlobStore()) { - log.info("BlobStore enabled. SegmentNodeStore will be initialized once the blob " + - "store becomes available"); - return; - } - if (segmentStore == null && configuration.hasCustomSegmentStore()) { - log.info("customSegmentStore enabled. SegmentNodeStore will be initialized once the custom segment " + - "store becomes available"); - return; - } - closer = Closer.create(); + public void activate(ComponentContext context, Configuration configuration) throws IOException { OsgiWhiteboard whiteboard = new OsgiWhiteboard(context.getBundleContext()); - registerSegmentStore(context, blobStore, segmentStore, statisticsProvider, closer, whiteboard, null, true); - } - - /** - * Configures and registers a new SegmentNodeStore instance together will - * all required components. Anything that must be disposed of (like - * registered services or MBeans) will be registered via the - * {@code registration} parameter. - * - * @param context An instance of {@link ComponentContext}. - * @param blobStore An instance of {@link BlobStore}. It can be - * {@code null}. - * @param segmentStore An instance of {@link SegmentNodeStorePersistence}. It can be - * {@code null}. - * @param statisticsProvider An instance of {@link StatisticsProvider}. - * @param closer An instance of {@link Closer}. It will be used - * to track every registered service or - * component. - * @param whiteboard An instance of {@link Whiteboard}. It will be - * used to register services in the OSGi - * framework. - * @param role The role of this component. It can be {@code - * null}. - * @param descriptors Determines if repository descriptors related to - * discovery services should be registered. - * @return A configured {@link SegmentNodeStore}, or {@code null} if the - * setup failed. - * @throws IOException In case an unrecoverable error occurs. - */ - static SegmentNodeStore registerSegmentStore( - @NotNull ComponentContext context, - @Nullable BlobStore blobStore, - @Nullable SegmentNodeStorePersistence segmentStore, - @NotNull StatisticsProvider statisticsProvider, - @NotNull Closer closer, - @NotNull Whiteboard whiteboard, - @Nullable String role, - boolean descriptors + registerSegmentStore(context, configuration, blobStore, segmentStore, statisticsProvider, closer, whiteboard, log); + } + + private static SegmentNodeStore registerSegmentStore( + ComponentContext context, + Configuration configuration, + BlobStore blobStore, + SegmentNodeStorePersistence segmentStore, + StatisticsProvider statisticsProvider, + Closer closer, + Whiteboard whiteboard, + Logger logger ) throws IOException { - Configuration configuration = new Configuration(context, role); - Closeables closeables = new Closeables(closer); - Registrations registrations = new Registrations(whiteboard, role); - - // Listen for GCMonitor services - GCMonitor gcMonitor = GCMonitor.EMPTY; - - if (configuration.isPrimarySegmentStore()) { - GCMonitorTracker tracker = new GCMonitorTracker(); - tracker.start(whiteboard); - closeables.add(tracker); - gcMonitor = tracker; - } - - // Create the gc options - if (configuration.getCompactionGainThreshold() != null) { - log.warn("Detected deprecated flag 'compaction.gainThreshold'. " - + "Please use 'compaction.sizeDeltaEstimation' instead and " - + "'compaction.disableEstimation' to disable estimation."); - } - if (configuration.getRetainedGenerations() != RETAINED_GENERATIONS_DEFAULT) { - log.warn( - "The number of retained generations defaults to {} and can't be " + - "changed. This configuration option is considered deprecated " + - "and will be removed in the future.", - RETAINED_GENERATIONS_DEFAULT - ); - } - SegmentGCOptions gcOptions = new SegmentGCOptions(configuration.getPauseCompaction(), configuration.getRetryCount(), configuration.getForceCompactionTimeout()) - .setGcSizeDeltaEstimation(configuration.getSizeDeltaEstimation()) - .setMemoryThreshold(configuration.getMemoryThreshold()) - .setEstimationDisabled(configuration.getDisableEstimation()) - .setGCLogInterval(configuration.getGCProcessLog()); - if (configuration.isStandbyInstance()) { - gcOptions.setRetainedGenerations(1); - } - - // Build the FileStore - FileStoreBuilder builder = fileStoreBuilder(configuration.getSegmentDirectory()) - .withSegmentCacheSize(configuration.getSegmentCacheSize()) - .withStringCacheSize(configuration.getStringCacheSize()) - .withTemplateCacheSize(configuration.getTemplateCacheSize()) - .withStringDeduplicationCacheSize(configuration.getStringDeduplicationCacheSize()) - .withTemplateDeduplicationCacheSize(configuration.getTemplateDeduplicationCacheSize()) - .withNodeDeduplicationCacheSize(configuration.getNodeDeduplicationCacheSize()) - .withMaxFileSize(configuration.getMaxFileSize()) - .withMemoryMapping(configuration.getMemoryMapping()) - .withGCMonitor(gcMonitor) - .withIOMonitor(new MetricsIOMonitor(statisticsProvider)) - .withIOLogging(LoggerFactory.getLogger(SegmentTarReader.class)) - .withStatisticsProvider(statisticsProvider) - .withGCOptions(gcOptions); - - if (configuration.hasCustomBlobStore() && blobStore != null) { - log.info("Initializing SegmentNodeStore with BlobStore [{}]", blobStore); - builder.withBlobStore(blobStore); - } - - if (configuration.hasCustomSegmentStore() && segmentStore != null) { - log.info("Initializing SegmentNodeStore with custom persistence [{}]", segmentStore); - builder.withCustomPersistence(segmentStore); - } - - if (configuration.isStandbyInstance()) { - builder.withSnfeListener(IGNORE_SNFE); - } - - final FileStore store; - try { - store = builder.build(); - } catch (InvalidFileStoreVersionException e) { - log.error("The storage format is not compatible with this version of Oak Segment Tar", e); - return null; - } - // store should be closed last - closeables.add(store); - - // Listen for Executor services on the whiteboard - - WhiteboardExecutor executor = new WhiteboardExecutor(); - executor.start(whiteboard); - closeables.add(executor); - - // Expose stats about the segment cache - - CacheStatsMBean segmentCacheStats = addRoleToName(store.getSegmentCacheStats(), role); - closeables.add(registrations.registerMBean( - CacheStatsMBean.class, - segmentCacheStats, - CacheStats.TYPE, - segmentCacheStats.getName() - )); - - // Expose stats about the string and template caches - - CacheStatsMBean stringCacheStats = addRoleToName(store.getStringCacheStats(), role); - closeables.add(registrations.registerMBean( - CacheStatsMBean.class, - stringCacheStats, - CacheStats.TYPE, - stringCacheStats.getName() - )); - - CacheStatsMBean templateCacheStats = addRoleToName(store.getTemplateCacheStats(), role); - closeables.add(registrations.registerMBean( - CacheStatsMBean.class, - templateCacheStats, - CacheStats.TYPE, - templateCacheStats.getName() - )); - - WriterCacheManager cacheManager = builder.getCacheManager(); - CacheStatsMBean stringDeduplicationCacheStats = addRoleToName(cacheManager.getStringCacheStats(), role); - if (stringDeduplicationCacheStats != null) { - closeables.add(registrations.registerMBean( - CacheStatsMBean.class, - stringDeduplicationCacheStats, - CacheStats.TYPE, - stringDeduplicationCacheStats.getName() - )); - } - - CacheStatsMBean templateDeduplicationCacheStats = addRoleToName(cacheManager.getTemplateCacheStats(), role); - if (templateDeduplicationCacheStats != null) { - closeables.add(registrations.registerMBean( - CacheStatsMBean.class, - templateDeduplicationCacheStats, - CacheStats.TYPE, - templateDeduplicationCacheStats.getName() - )); - } - - CacheStatsMBean nodeDeduplicationCacheStats = addRoleToName(cacheManager.getNodeCacheStats(), role); - if (nodeDeduplicationCacheStats != null) { - closeables.add(registrations.registerMBean( - CacheStatsMBean.class, - nodeDeduplicationCacheStats, - CacheStats.TYPE, - nodeDeduplicationCacheStats.getName() - )); - } - - // Expose an MBean to managing and monitoring garbage collection - final FileStoreGCMonitor monitor = new FileStoreGCMonitor(Clock.SIMPLE); - closeables.add(registrations.register( - GCMonitor.class, - monitor - )); - if (!configuration.isStandbyInstance()) { - closeables.add(registrations.registerMBean( - SegmentRevisionGC.class, - new SegmentRevisionGCMBean(store, gcOptions, monitor), - SegmentRevisionGC.TYPE, - "Segment node store revision garbage collection" - )); - } - - Runnable cancelGC = new Runnable() { + return SegmentNodeStoreRegistrar.registerSegmentNodeStore(new SegmentNodeStoreRegistrar.Configuration() { - @Override - public void run() { - store.cancelGC(); + int roundToNextPowerOfTwo(int size) { + return 1 << (32 - Integer.numberOfLeadingZeros(Math.max(0, size - 1))); } - }; - Supplier statusMessage = new Supplier() { - - @Override - public String get() { - return monitor.getStatus(); + String getMode() { + String mode = configuration.tarmk_mode(); + if (isNullOrEmpty(mode)) { + return System.getProperty("tarmk.mode", System.getProperty("sun.arch.data.model", "32")); + } + return mode; } - }; - closeables.add(registrations.registerMBean( - RevisionGCMBean.class, - new RevisionGC(store.getGCRunner(), cancelGC, statusMessage, executor), - RevisionGCMBean.TYPE, - "Revision garbage collection" - )); - - // Expose statistics about the FileStore - - closeables.add(registrations.registerMBean( - FileStoreStatsMBean.class, - store.getStats(), - FileStoreStatsMBean.TYPE, - "FileStore statistics" - )); - - // register segment node store - - SegmentNodeStore.SegmentNodeStoreBuilder segmentNodeStoreBuilder = SegmentNodeStoreBuilders.builder(store).withStatisticsProvider(statisticsProvider); - if (configuration.isStandbyInstance() || !configuration.isPrimarySegmentStore()) { - segmentNodeStoreBuilder.dispatchChanges(false); - } - SegmentNodeStore segmentNodeStore = segmentNodeStoreBuilder.build(); - - if (configuration.isPrimarySegmentStore()) { - ObserverTracker observerTracker = new ObserverTracker(segmentNodeStore); - observerTracker.start(context.getBundleContext()); - closeables.add(observerTracker); - } - - if (configuration.isPrimarySegmentStore()) { - closeables.add(registrations.registerMBean( - CheckpointMBean.class, - new SegmentCheckpointMBean(segmentNodeStore), - CheckpointMBean.TYPE, - "Segment node store checkpoint management" - )); - } - - if (descriptors) { - // ensure a clusterId is initialized - // and expose it as 'oak.clusterid' repository descriptor - GenericDescriptors clusterIdDesc = new GenericDescriptors(); - clusterIdDesc.put( - ClusterRepositoryInfo.OAK_CLUSTERID_REPOSITORY_DESCRIPTOR_KEY, - new SimpleValueFactory().createValue(getOrCreateId(segmentNodeStore)), - true, - false - ); - closeables.add(registrations.register(Descriptors.class, clusterIdDesc)); - // Register "discovery lite" descriptors - closeables.add(registrations.register(Descriptors.class, new SegmentDiscoveryLiteDescriptors(segmentNodeStore))); - } - - // If a shared data store register the repo id in the data store - if (configuration.isPrimarySegmentStore() && isShared(blobStore)) { - SharedDataStore sharedDataStore = (SharedDataStore) blobStore; - try { - sharedDataStore.addMetadataRecord(new ByteArrayInputStream(new byte[0]), SharedStoreRecordType.REPOSITORY.getNameFromId(getOrCreateId(segmentNodeStore))); - } catch (Exception e) { - throw new IOException("Could not register a unique repositoryId", e); - } - if (blobStore instanceof BlobTrackingStore) { - BlobTrackingStore trackingStore = (BlobTrackingStore) blobStore; - if (trackingStore.getTracker() != null) { - trackingStore.getTracker().close(); + int getCacheSize(String name, int otherwise) { + Integer size = Integer.getInteger(name); + if (size != null) { + return size; } - trackingStore.addTracker(new BlobIdTracker(configuration.getRepositoryHome(), getOrCreateId(segmentNodeStore), configuration.getBlobSnapshotInterval(), sharedDataStore)); + return otherwise; } - } - - if (configuration.isPrimarySegmentStore() && blobStore instanceof GarbageCollectableBlobStore) { - BlobGarbageCollector gc = new MarkSweepGarbageCollector( - new SegmentBlobReferenceRetriever(store), - (GarbageCollectableBlobStore) blobStore, - executor, - TimeUnit.SECONDS.toMillis(configuration.getBlobGcMaxAge()), - getOrCreateId(segmentNodeStore), - whiteboard, - statisticsProvider - ); - closeables.add(registrations.registerMBean( - BlobGCMBean.class, - new BlobGC(gc, executor), - BlobGCMBean.TYPE, - "Segment node store blob garbage collection" - )); - } - - // Expose an MBean for backup/restore operations - - closeables.add(registrations.registerMBean( - FileStoreBackupRestoreMBean.class, - new FileStoreBackupRestoreImpl( - segmentNodeStore, - store.getRevisions(), - store.getReader(), - configuration.getBackupDirectory(), - executor - ), - FileStoreBackupRestoreMBean.TYPE, - "Segment node store backup/restore" - )); - - // Expose statistics about the SegmentNodeStore - - closeables.add(registrations.registerMBean( - SegmentNodeStoreStatsMBean.class, - segmentNodeStore.getStats(), - SegmentNodeStoreStatsMBean.TYPE, - "SegmentNodeStore statistics" - )); - - if (configuration.isPrimarySegmentStore()) { - log.info("Primary SegmentNodeStore initialized"); - } else { - log.info("Secondary SegmentNodeStore initialized, role={}", role); - } - - // Register a factory service to expose the FileStore - closeables.add(registrations.register( - SegmentStoreProvider.class, - new DefaultSegmentStoreProvider(store) - )); - - if (configuration.isStandbyInstance()) { - return segmentNodeStore; - } - - if (configuration.isPrimarySegmentStore()) { - Map props = new HashMap(); - props.put(Constants.SERVICE_PID, SegmentNodeStore.class.getName()); - props.put("oak.nodestore.description", new String[] {"nodeStoreType=segment"}); - closeables.add(registrations.register(NodeStore.class, segmentNodeStore, props)); - } - - return segmentNodeStore; - } - - @Deactivate - public void deactivate() { - closeQuietly(closer); - closer = null; - } - private static CacheStatsMBean addRoleToName(CacheStatsMBean cacheStatsMBean, String role) { - return new CacheStatsMBeanWrapper(cacheStatsMBean) { @Override - public String getName() { - return RoleUtils.maybeAppendRole(super.getName(), role); + public boolean isPrimarySegmentStore() { + return true; } - }; - } -} - -/** - * Encapsulates a {@link Closer} and makes it easier to track the lifecycle - * of entities that can be disposed. - */ -class Closeables implements Closeable { - - private final Closer closer; - - Closeables(Closer closer) { - this.closer = closer; - } - - void add(Closeable c) { - closer.register(c); - } - - void add(final AbstractServiceTracker t) { - add(new Closeable() { @Override - public void close() { - t.stop(); + public boolean isSecondarySegmentStore() { + return false; } - }); - } - - void add(final Registration r) { - add(new Closeable() { - @Override - public void close() { - r.unregister(); + public boolean isStandbyInstance() { + return configuration.standby(); } - }); - } - - void add(final ObserverTracker t) { - add(new Closeable() { - @Override - public void close() { - t.stop(); + public String getRole() { + return null; } - }); - } - - @Override - public void close() throws IOException { - closer.close(); - } - -} - -/** - * Allows simple access to the configuration of this component. Provides - * default values for unspecified properties and type conversion. - */ -class Configuration { - - private static int roundToNextPowerOfTwo(int size) { - return 1 << (32 - Integer.numberOfLeadingZeros(Math.max(0, size - 1))); - } - - private final ComponentContext context; - - private final String role; - - Configuration(ComponentContext context) { - this(context, null); - } - - Configuration(ComponentContext context, String role) { - this.context = context; - this.role = role; - } - - String property(String name) { - return lookupConfigurationThenFramework(context, name); - } - - /** - * Chooses repository home directory name based on repository.home - * property, defaulting to repository if property is not set. - * - * @return repository home directory name. - */ - String getRepositoryHome() { - String root = property(REPOSITORY_HOME_DIRECTORY); - if (isNullOrEmpty(root)) { - return "repository"; - } - return root; - } - - /** - * Creates a new sub-directory relative to {@link #getRepositoryHome()} for - * storing segments. - * - * @return directory for storing segments. - */ - File getSegmentDirectory() { - return new File(getRepositoryHome(), appendRole("segmentstore")); - } - - /** - * Creates a new sub-directory relative to {@link #getRepositoryHome()} for - * storing repository backups. - * - * @return directory for storing repository backups. - */ - File getBackupDirectory() { - String backupDirectory = property(BACKUP_DIRECTORY); - if (backupDirectory != null) { - return new File(backupDirectory); - } - return new File(getRepositoryHome(), appendRole("segmentstore-backup")); - } - - int getSegmentCacheSize() { - return toInteger(getCacheSize(SEGMENT_CACHE_SIZE), DEFAULT_SEGMENT_CACHE_MB); - } - - int getStringCacheSize() { - return toInteger(getCacheSize(STRING_CACHE_SIZE), DEFAULT_STRING_CACHE_MB); - } - - int getTemplateCacheSize() { - return toInteger(getCacheSize(TEMPLATE_CACHE_SIZE), DEFAULT_TEMPLATE_CACHE_MB); - } - - int getStringDeduplicationCacheSize() { - return toInteger(getCacheSize(STRING_DEDUPLICATION_CACHE_SIZE), DEFAULT_STRING_CACHE_SIZE_OSGi); - } - - int getTemplateDeduplicationCacheSize() { - return toInteger(getCacheSize(TEMPLATE_DEDUPLICATION_CACHE_SIZE), DEFAULT_TEMPLATE_CACHE_SIZE_OSGi); - } - - int getNodeDeduplicationCacheSize() { - return roundToNextPowerOfTwo(toInteger(getCacheSize(NODE_DEDUPLICATION_CACHE_SIZE), DEFAULT_NODE_CACHE_SIZE_OSGi)); - } + @Override + public int getRetainedGenerations() { + return configuration.compaction_retainedGenerations(); + } - boolean getPauseCompaction() { - return toBoolean(property(PAUSE_COMPACTION), PAUSE_DEFAULT); - } + @Override + public int getDefaultRetainedGenerations() { + return RETAINED_GENERATIONS_DEFAULT; + } - int getRetryCount() { - return toInteger(property(COMPACTION_RETRY_COUNT), RETRY_COUNT_DEFAULT); - } + @Override + public boolean getPauseCompaction() { + return configuration.pauseCompaction(); + } - int getForceCompactionTimeout() { - return toInteger(property(COMPACTION_FORCE_TIMEOUT), FORCE_TIMEOUT_DEFAULT); - } + @Override + public int getRetryCount() { + return configuration.compaction_retryCount(); + } - int getRetainedGenerations() { - return toInteger(property(RETAINED_GENERATIONS), RETAINED_GENERATIONS_DEFAULT); - } + @Override + public int getForceCompactionTimeout() { + return configuration.compaction_force_timeout(); + } - long getSizeDeltaEstimation() { - return toLong(property(COMPACTION_SIZE_DELTA_ESTIMATION), SIZE_DELTA_ESTIMATION_DEFAULT); - } + @Override + public long getSizeDeltaEstimation() { + return configuration.compaction_sizeDeltaEstimation(); + } - int getMemoryThreshold() { - return toInteger(property(MEMORY_THRESHOLD), MEMORY_THRESHOLD_DEFAULT); - } + @Override + public int getMemoryThreshold() { + return configuration.compaction_memoryThreshold(); + } - boolean getDisableEstimation() { - return toBoolean(property(COMPACTION_DISABLE_ESTIMATION), DISABLE_ESTIMATION_DEFAULT); - } + @Override + public boolean getDisableEstimation() { + return configuration.compaction_disableEstimation(); + } - String getCompactionGainThreshold() { - return property("compaction.gainThreshold"); - } + @Override + public long getGCProcessLog() { + return configuration.compaction_progressLog(); + } - long getGCProcessLog() { - return toLong(property(GC_PROGRESS_LOG), GC_PROGRESS_LOG_DEFAULT); - } + @Override + public File getSegmentDirectory() { + return new File(getRepositoryHome(), "segmentstore"); + } - int getMaxFileSize() { - return toInteger(property(SIZE), DEFAULT_MAX_FILE_SIZE); - } + @Override + public int getSegmentCacheSize() { + Integer size = Integer.getInteger("segmentCache.size"); + if (size != null) { + return size; + } + return configuration.segmentCache_size(); + } - String getMode() { - String mode = property(MODE); - if (mode != null) { - return mode; - } - return System.getProperty(MODE, System.getProperty("sun.arch.data.model", "32")); - } + @Override + public int getStringCacheSize() { + return getCacheSize("stringCache.size", configuration.stringCache_size()); + } - boolean getMemoryMapping() { - return getMode().equals("64"); - } + @Override + public int getTemplateCacheSize() { + Integer size = Integer.getInteger("templateCache.size"); + if (size != null) { + return size; + } + return configuration.templateCache_size(); + } - long getBlobSnapshotInterval() { - return toLong(property(PROP_BLOB_SNAPSHOT_INTERVAL), DEFAULT_BLOB_SNAPSHOT_INTERVAL); - } + @Override + public int getStringDeduplicationCacheSize() { + Integer size = Integer.getInteger("stringDeduplicationCache.size"); + if (size != null) { + return size; + } + return configuration.stringDeduplicationCache_size(); + } - boolean isStandbyInstance() { - return toBoolean(property(STANDBY), false); - } + @Override + public int getTemplateDeduplicationCacheSize() { + Integer size = Integer.getInteger("templateDeduplicationCache.size"); + if (size != null) { + return size; + } + return configuration.templateDeduplicationCache_size(); + } - boolean hasCustomBlobStore() { - return toBoolean(property(CUSTOM_BLOB_STORE), false); - } + @Override + public int getNodeDeduplicationCacheSize() { + Integer size = Integer.getInteger("nodeDeduplicationCache.size"); + if (size != null) { + return roundToNextPowerOfTwo(size); + } + return roundToNextPowerOfTwo(configuration.nodeDeduplicationCache_size()); + } - boolean hasCustomSegmentStore() { - return toBoolean(property(CUSTOM_SEGMENT_STORE), false); - } + @Override + public int getMaxFileSize() { + return configuration.tarmk_size(); + } - long getBlobGcMaxAge() { - return toLong(property(PROP_BLOB_GC_MAX_AGE), DEFAULT_BLOB_GC_MAX_AGE); - } + @Override + public boolean getMemoryMapping() { + return getMode().equals("64"); + } - boolean isPrimarySegmentStore() { - return role == null; - } + @Override + public boolean hasCustomBlobStore() { + return configuration.customBlobStore(); + } - private String appendRole(String name) { - if (role == null) { - return name; - } else { - return name + "-" + role; - } - } + @Override + public boolean hasCustomSegmentStore() { + return configuration.customSegmentStore(); + } - private String getCacheSize(String propertyName) { - String cacheSize = property(propertyName); - if (cacheSize != null) { - return cacheSize; - } - return System.getProperty(propertyName); - } + @Override + public boolean registerDescriptors() { + return true; + } -} + @Override + public String getRepositoryHome() { + String repositoryHome = OsgiUtil.lookupConfigurationThenFramework(context, "repository.home"); + if (isNullOrEmpty(repositoryHome)) { + return "repository"; + } + return repositoryHome; + } -/** - * Performs registrations of services and MBean in a uniform way. Augments - * the metadata of services and MBeans with an optionally provided role - * name. - */ -class Registrations { + @Override + public long getBlobSnapshotInterval() { + return configuration.blobTrackSnapshotIntervalInSecs(); + } - private final Whiteboard whiteboard; + @Override + public long getBlobGcMaxAge() { + return configuration.blobGcMaxAgeInSecs(); + } - private final String role; + @Override + public File getBackupDirectory() { + String backupDirectory = configuration.repository_backup_dir(); + if (isNullOrEmpty(backupDirectory)) { + return new File(getRepositoryHome(), "segmentstore-backup"); + } + return new File(backupDirectory); + } - Registrations(Whiteboard whiteboard, String role) { - this.whiteboard = whiteboard; - this.role = role; - } + @Override + public Whiteboard getWhiteboard() { + return whiteboard; + } - Registration registerMBean(Class clazz, T bean, String type, String name) { - return registerMBean(clazz, bean, type, name, new HashMap()); - } + @Override + public Closer getCloser() { + return closer; + } - Registration registerMBean(Class clazz, T bean, String type, String name, Map attributes) { - return WhiteboardUtils.registerMBean(whiteboard, clazz, bean, type, maybeAppendRole(name), maybePutRoleAttribute(attributes)); - } + @Override + public Logger getLogger() { + return logger; + } - Registration register(Class clazz, T service) { - return register(clazz, service, new HashMap()); - } + @Override + public StatisticsProvider getStatisticsProvider() { + return statisticsProvider; + } - Registration register(Class clazz, T service, Map properties) { - return whiteboard.register(clazz, service, maybePutRoleProperty(properties)); - } + @Override + public BlobStore getBlobStore() { + return blobStore; + } - private String maybeAppendRole(String name) { - return RoleUtils.maybeAppendRole(name, role); - } + @Override + public SegmentNodeStorePersistence getSegmentNodeStorePersistence() { + return segmentStore; + } - private String jmxRole() { - return role.replaceAll(":", "-"); - } + @Override + public BundleContext getBundleContext() { + return context.getBundleContext(); + } - private Map maybePutRoleAttribute(Map attributes) { - if (role != null) { - attributes.put("role", jmxRole()); - } - return attributes; + }); } - private Map maybePutRoleProperty(Map attributes) { - if (role != null) { - attributes.put("role", role); - } - return attributes; + @Deactivate + public void deactivate() { + closeQuietly(closer); } -} +} \ No newline at end of file diff --git a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/osgi/SegmentNodeStoreServiceDeprecationError.java b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/osgi/SegmentNodeStoreServiceDeprecationError.java index 8fb994456c..63da5af0eb 100644 --- a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/osgi/SegmentNodeStoreServiceDeprecationError.java +++ b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/osgi/SegmentNodeStoreServiceDeprecationError.java @@ -17,9 +17,9 @@ package org.apache.jackrabbit.oak.segment.osgi; -import org.apache.felix.scr.annotations.Activate; -import org.apache.felix.scr.annotations.Component; -import org.apache.felix.scr.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,8 +30,8 @@ import org.slf4j.LoggerFactory; * detected problem and hinting at a possible solution. */ @Component( - policy = ConfigurationPolicy.REQUIRE, - configurationPid = "org.apache.jackrabbit.oak.plugins.segment.SegmentNodeStoreService" + configurationPolicy = ConfigurationPolicy.REQUIRE, + configurationPid = "org.apache.jackrabbit.oak.plugins.segment.SegmentNodeStoreService" ) public class SegmentNodeStoreServiceDeprecationError { diff --git a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/osgi/StandbyStoreServiceDeprecationError.java b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/osgi/StandbyStoreServiceDeprecationError.java index 4da5b6004f..a456f026ff 100644 --- a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/osgi/StandbyStoreServiceDeprecationError.java +++ b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/osgi/StandbyStoreServiceDeprecationError.java @@ -16,9 +16,9 @@ */ package org.apache.jackrabbit.oak.segment.osgi; -import org.apache.felix.scr.annotations.Activate; -import org.apache.felix.scr.annotations.Component; -import org.apache.felix.scr.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,8 +29,8 @@ import org.slf4j.LoggerFactory; * detected problem and hinting at a possible solution. */ @Component( - policy = ConfigurationPolicy.REQUIRE, - configurationPid = "org.apache.jackrabbit.oak.plugins.segment.standby.store.StandbyStoreService" + configurationPolicy = ConfigurationPolicy.REQUIRE, + configurationPid = "org.apache.jackrabbit.oak.plugins.segment.standby.store.StandbyStoreService" ) public class StandbyStoreServiceDeprecationError { diff --git a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/store/StandbyStoreService.java b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/store/StandbyStoreService.java index 8b7da9c6ba..3847a8c11b 100644 --- a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/store/StandbyStoreService.java +++ b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/standby/store/StandbyStoreService.java @@ -17,25 +17,15 @@ package org.apache.jackrabbit.oak.segment.standby.store; -import static java.lang.String.valueOf; -import static org.apache.felix.scr.annotations.ReferencePolicy.STATIC; -import static org.apache.felix.scr.annotations.ReferencePolicyOption.GREEDY; +import static org.osgi.service.component.annotations.ReferencePolicy.STATIC; +import static org.osgi.service.component.annotations.ReferencePolicyOption.GREEDY; -import java.io.Closeable; import java.io.File; import java.util.Dictionary; import java.util.Hashtable; import com.google.common.base.StandardSystemProperty; import com.google.common.io.Closer; -import org.apache.felix.scr.annotations.Activate; -import org.apache.felix.scr.annotations.Component; -import org.apache.felix.scr.annotations.ConfigurationPolicy; -import org.apache.felix.scr.annotations.Deactivate; -import org.apache.felix.scr.annotations.Property; -import org.apache.felix.scr.annotations.PropertyOption; -import org.apache.felix.scr.annotations.Reference; -import org.apache.jackrabbit.oak.commons.PropertiesUtil; import org.apache.jackrabbit.oak.segment.SegmentStore; import org.apache.jackrabbit.oak.segment.SegmentStoreProvider; import org.apache.jackrabbit.oak.segment.file.FileStore; @@ -43,71 +33,99 @@ import org.apache.jackrabbit.oak.segment.standby.client.StandbyClientSync; import org.apache.jackrabbit.oak.segment.standby.server.StandbyServerSync; import org.osgi.framework.ServiceRegistration; import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.Designate; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import org.osgi.service.metatype.annotations.Option; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -@Property(name = "org.apache.sling.installer.configuration.persist", label = "Persist configuration", description = "Must be always disabled to avoid storing the configuration in the repository", boolValue = false) -@Component(metatype = true, policy = ConfigurationPolicy.REQUIRE) +@Component(configurationPolicy = ConfigurationPolicy.REQUIRE) +@Designate(ocd = StandbyStoreService.Configuration.class) public class StandbyStoreService { - private final Logger log = LoggerFactory.getLogger(getClass()); + private static final Logger log = LoggerFactory.getLogger(StandbyStoreService.class); - private static final String MODE_PRIMARY = "primary"; - - private static final String MODE_STANDBY = "standby"; - - public static final String MODE_DEFAULT = MODE_PRIMARY; - - @Property(options = { - @PropertyOption(name = MODE_PRIMARY, value = MODE_PRIMARY), - @PropertyOption(name = MODE_STANDBY, value = MODE_STANDBY)}, - value = MODE_DEFAULT) - public static final String MODE = "mode"; - - public static final int PORT_DEFAULT = 8023; - - @Property(intValue = PORT_DEFAULT) - public static final String PORT = "port"; - - public static final String PRIMARY_HOST_DEFAULT = "127.0.0.1"; - - @Property(value = PRIMARY_HOST_DEFAULT) - public static final String PRIMARY_HOST = "primary.host"; - - public static final int INTERVAL_DEFAULT = 5; - - @Property(intValue = INTERVAL_DEFAULT) - public static final String INTERVAL = "interval"; - - public static final String[] ALLOWED_CLIENT_IP_RANGES_DEFAULT = new String[] {}; - - @Property(cardinality = Integer.MAX_VALUE) - public static final String ALLOWED_CLIENT_IP_RANGES = "primary.allowed-client-ip-ranges"; - - public static final boolean SECURE_DEFAULT = false; - - @Property(boolValue = SECURE_DEFAULT) - public static final String SECURE = "secure"; - - public static final int READ_TIMEOUT_DEFAULT = 60000; - - @Property(intValue = READ_TIMEOUT_DEFAULT) - public static final String READ_TIMEOUT = "standby.readtimeout"; + private static final int BLOB_CHUNK_SIZE = Integer.getInteger("oak.standby.blob.chunkSize", 1024 * 1024); - public static final boolean AUTO_CLEAN_DEFAULT = true; + @ObjectClassDefinition( + name = "Apache Jackrabbit Oak Segment Tar Cold Standby Service", + description = "Provides continuous backups of repositories based on Segment Tar" + ) + @interface Configuration { + + @AttributeDefinition( + name = "Persist configuration", + description = "Must be always disabled to avoid storing the configuration in the repository" + ) + boolean org_apache_sling_installer_configuration_persist() default false; + + @AttributeDefinition( + name = "Mode", + description = "TarMK Cold Standby mode (primary or standby)", + options = { + @Option(label = "primary", value = "primary"), + @Option(label = "standby", value = "standby")} + ) + String mode() default "primary"; + + @AttributeDefinition( + name = "Port", + description = "TCP/IP port to use" + ) + int port() default 8023; + + @AttributeDefinition( + name = "Primary Host", + description = "Primary host (standby mode only)" + ) + String primary_host() default "127.0.0.1"; + + @AttributeDefinition( + name = "Sync interval (seconds)", + description = "Sync interval in seconds (standby mode only)" + ) + int interval() default 5; + + @AttributeDefinition( + name = "Allowed IP-Ranges", + description = "Accept incoming requests for these host names and IP-ranges only (primary mode only)", + cardinality = Integer.MAX_VALUE + ) + String[] primary_allowed$_$client$_$ip$_$ranges() default {}; + + @AttributeDefinition( + name = "Secure", + description = "Use secure connections" + ) + boolean secure() default false; + + @AttributeDefinition( + name = "Standby Read Timeout", + description = "Timeout for requests issued from the standby instance in milliseconds" + ) + int standby_readtimeout() default 60000; + + @AttributeDefinition( + name = "Standby Automatic Cleanup", + description = "Call the cleanup method when the root segment Garbage Collector (GC) generation number increases" + ) + boolean standby_autoclean() default true; - @Property(boolValue = AUTO_CLEAN_DEFAULT) - public static final String AUTO_CLEAN = "standby.autoclean"; + } @Reference(policy = STATIC, policyOption = GREEDY) private SegmentStoreProvider storeProvider = null; - - private static final int BLOB_CHUNK_SIZE = Integer.getInteger("oak.standby.blob.chunkSize", 1024 * 1024); private final Closer closer = Closer.create(); @Activate - private void activate(ComponentContext context) { + private void activate(ComponentContext context, Configuration config) { SegmentStore segmentStore = storeProvider.getSegmentStore(); if (!(segmentStore instanceof FileStore)) { @@ -116,15 +134,15 @@ public class StandbyStoreService { FileStore fileStore = (FileStore) segmentStore; - String mode = valueOf(context.getProperties().get(MODE)); + String mode = config.mode(); - if (MODE_PRIMARY.equals(mode)) { - bootstrapMaster(context, fileStore); + if (mode.equals("primary")) { + bootstrapMaster(config, fileStore); return; } - if (MODE_STANDBY.equals(mode)) { - bootstrapSlave(context, fileStore); + if (mode.equals("standby")) { + bootstrapSlave(context, config, fileStore); return; } @@ -136,11 +154,10 @@ public class StandbyStoreService { closer.close(); } - private void bootstrapMaster(ComponentContext context, FileStore fileStore) { - Dictionary props = context.getProperties(); - int port = PropertiesUtil.toInteger(props.get(PORT), PORT_DEFAULT); - String[] ranges = PropertiesUtil.toStringArray(props.get(ALLOWED_CLIENT_IP_RANGES), ALLOWED_CLIENT_IP_RANGES_DEFAULT); - boolean secure = PropertiesUtil.toBoolean(props.get(SECURE), SECURE_DEFAULT); + private void bootstrapMaster(Configuration config, FileStore fileStore) { + int port = config.port(); + String[] ranges = config.primary_allowed$_$client$_$ip$_$ranges(); + boolean secure = config.secure(); StandbyServerSync standbyServerSync = new StandbyServerSync(port, fileStore, BLOB_CHUNK_SIZE, ranges, secure); closer.register(standbyServerSync); @@ -149,14 +166,13 @@ public class StandbyStoreService { log.info("Started primary on port {} with allowed IP ranges {}", port, ranges); } - private void bootstrapSlave(ComponentContext context, FileStore fileStore) { - Dictionary props = context.getProperties(); - int port = PropertiesUtil.toInteger(props.get(PORT), PORT_DEFAULT); - long interval = PropertiesUtil.toInteger(props.get(INTERVAL), INTERVAL_DEFAULT); - String host = PropertiesUtil.toString(props.get(PRIMARY_HOST), PRIMARY_HOST_DEFAULT); - boolean secure = PropertiesUtil.toBoolean(props.get(SECURE), SECURE_DEFAULT); - int readTimeout = PropertiesUtil.toInteger(props.get(READ_TIMEOUT), READ_TIMEOUT_DEFAULT); - boolean clean = PropertiesUtil.toBoolean(props.get(AUTO_CLEAN), AUTO_CLEAN_DEFAULT); + private void bootstrapSlave(ComponentContext context, Configuration config, FileStore fileStore) { + int port = config.port(); + long interval = config.interval(); + String host = config.primary_host(); + boolean secure = config.secure(); + int readTimeout = config.standby_readtimeout(); + boolean clean = config.standby_autoclean(); StandbyClientSync standbyClientSync = new StandbyClientSync(host, port, fileStore, secure, readTimeout, clean, new File(StandardSystemProperty.JAVA_IO_TMPDIR.value())); closer.register(standbyClientSync); @@ -165,20 +181,9 @@ public class StandbyStoreService { dictionary.put("scheduler.period", interval); dictionary.put("scheduler.concurrent", false); ServiceRegistration registration = context.getBundleContext().registerService(Runnable.class.getName(), standbyClientSync, dictionary); - closer.register(asCloseable(registration)); + closer.register(registration::unregister); log.info("Started standby on port {} with {}s sync frequency", port, interval); } - private static Closeable asCloseable(final ServiceRegistration r) { - return new Closeable() { - - @Override - public void close() { - r.unregister(); - } - - }; - } - } diff --git a/oak-segment-tar/src/main/resources/OSGI-INF/l10n/metatype.properties b/oak-segment-tar/src/main/resources/OSGI-INF/l10n/metatype.properties deleted file mode 100644 index 9dc70b8bfa..0000000000 --- a/oak-segment-tar/src/main/resources/OSGI-INF/l10n/metatype.properties +++ /dev/null @@ -1,50 +0,0 @@ -# -# 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. -# - -# suppress inspection "UnusedProperty" for whole file - -org.apache.jackrabbit.oak.segment.standby.store.StandbyStoreService.name = Apache Jackrabbit Oak Segment Tar Cold Standby Service -org.apache.jackrabbit.oak.segment.standby.store.StandbyStoreService.description = Provides continuous backups of repositories based on Segment Tar - -mode.name = Mode -mode.description = TarMK Cold Standby mode (primary or standby) - -port.name = Port -port.description = TCP/IP port to use - -primary.host.name = Primary Host -primary.host.description = Primary host (standby mode only) - -interval.name = Sync interval (seconds) -interval.description = Sync interval in seconds (standby mode only) - -primary.allowed-client-ip-ranges.name = Allowed IP-Ranges -primary.allowed-client-ip-ranges.description = Accept incoming requests for these host names and IP-ranges only (primary mode only) - -secure.name = Secure -secure.description = Use secure connections - -org.apache.sling.installer.configuration.persist.name = Persist configuration -org.apache.sling.installer.configuration.persist.description = Must be always disabled to avoid storing the configuration in the repository - -standby.readtimeout.name = Standby Read Timeout -standby.readtimeout.description = Timeout for requests issued from the standby instance in milliseconds - -standby.autoclean.name = Standby Automatic Cleanup -standby.autoclean.description = Call the cleanup method when the root segment Garbage Collector (GC) generation number increases \ No newline at end of file diff --git a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreFactoryTest.java b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreFactoryTest.java index 68b2d0a9a5..a4d3b5ed0e 100644 --- a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreFactoryTest.java +++ b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreFactoryTest.java @@ -35,9 +35,9 @@ public class SegmentNodeStoreFactoryTest extends SegmentNodeStoreServiceTest { protected void registerSegmentNodeStoreService(boolean customBlobStore) { Map properties = newHashMap(); - properties.put(SegmentNodeStoreFactory.ROLE, "some-role"); - properties.put(SegmentNodeStoreFactory.CUSTOM_BLOB_STORE, customBlobStore); - properties.put(SegmentNodeStoreService.REPOSITORY_HOME_DIRECTORY, folder.getRoot().getAbsolutePath()); + properties.put("role", "some-role"); + properties.put("customBlobStore", customBlobStore); + properties.put("repository.home", folder.getRoot().getAbsolutePath()); segmentNodeStoreFactory = context.registerInjectActivateService(new SegmentNodeStoreFactory(), properties); } diff --git a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/osgi/ComponentDescriptor.java b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/osgi/ComponentDescriptor.java index 77eb0a6bdc..f665c6ab64 100644 --- a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/osgi/ComponentDescriptor.java +++ b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/osgi/ComponentDescriptor.java @@ -21,6 +21,7 @@ package org.apache.jackrabbit.oak.segment.osgi; import java.io.InputStream; +import javax.print.attribute.HashPrintServiceAttributeSet; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -47,6 +48,10 @@ class ComponentDescriptor { return element.hasAttribute(name) && element.getAttribute(name).equals(value); } + private static boolean hasNoAttribute(Element element, String name) { + return !element.hasAttribute(name); + } + boolean hasName(String name) { return hasAttribute(root, "name", name); } @@ -182,6 +187,8 @@ class ComponentDescriptor { private String unbind; + private String field; + private HasReference(String name) { this.name = name; } @@ -246,6 +253,11 @@ class ComponentDescriptor { return this; } + HasReference withField(String field) { + this.field = field; + return this; + } + boolean check() { NodeList references = root.getElementsByTagName("reference"); for (int i = 0; i < references.getLength(); i++) { @@ -254,10 +266,10 @@ class ComponentDescriptor { if (iface != null && !hasAttribute(reference, "interface", iface)) { return false; } - if (cardinality != null && !hasAttribute(reference, "cardinality", cardinality)) { + if (cardinality != null && !hasValidCardinality(reference)) { return false; } - if (policy != null && !hasAttribute(reference, "policy", policy)) { + if (policy != null && !hasValidPolicy(reference)) { return false; } if (policyOption != null && !hasAttribute(reference, "policy-option", policyOption)) { @@ -272,12 +284,29 @@ class ComponentDescriptor { if (unbind != null && !hasAttribute(reference, "unbind", unbind)) { return false; } + if (field != null && !hasAttribute(reference, "field", field)) { + return false; + } return true; } } return false; } + private boolean hasValidCardinality(Element reference) { + if (cardinality.equals("1..1") && hasNoAttribute(reference, "cardinality")) { + return true; + } + return hasAttribute(reference, "cardinality", cardinality); + } + + private boolean hasValidPolicy(Element reference) { + if (policy.equals("static") && hasNoAttribute(reference, "policy")) { + return true; + } + return hasAttribute(reference, "policy", policy); + } + } HasReference hasReference(String name) { diff --git a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/osgi/SegmentNodeStoreFactoryTest.java b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/osgi/SegmentNodeStoreFactoryTest.java index 965898927f..d1708c4127 100644 --- a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/osgi/SegmentNodeStoreFactoryTest.java +++ b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/osgi/SegmentNodeStoreFactoryTest.java @@ -52,36 +52,32 @@ public class SegmentNodeStoreFactoryTest { .withStaticPolicy() .withGreedyPolicyOption() .withTarget("(&(!(split.blobstore=old))(!(split.blobstore=new)))") - .withBind("bindBlobStore") - .withUnbind("unbindBlobStore") + .withField("blobStore") .check()); assertTrue(cd.hasReference("segmentStore") .withInterface("org.apache.jackrabbit.oak.segment.spi.persistence.SegmentNodeStorePersistence") .withOptionalUnaryCardinality() .withStaticPolicy() .withGreedyPolicyOption() - .withBind("bindSegmentStore") - .withUnbind("unbindSegmentStore") + .withField("segmentStore") .check()); assertTrue(cd.hasReference("statisticsProvider") .withInterface("org.apache.jackrabbit.oak.stats.StatisticsProvider") .withMandatoryUnaryCardinality() .withStaticPolicy() - .withBind("bindStatisticsProvider") - .withUnbind("unbindStatisticsProvider") + .withField("statisticsProvider") .check()); } @Test public void testMetatypeInformation() throws Exception { - MetatypeInformation mi = MetatypeInformation.open(getClass().getResourceAsStream("/OSGI-INF/metatype/org.apache.jackrabbit.oak.segment.SegmentNodeStoreFactory.xml")); + MetatypeInformation mi = MetatypeInformation.open(getClass().getResourceAsStream("/OSGI-INF/metatype/org.apache.jackrabbit.oak.segment.SegmentNodeStoreFactory$Configuration.xml")); assertTrue(mi.hasDesignate() - .withPid("org.apache.jackrabbit.oak.segment.SegmentNodeStoreFactory") .withFactoryPid("org.apache.jackrabbit.oak.segment.SegmentNodeStoreFactory") - .withReference("org.apache.jackrabbit.oak.segment.SegmentNodeStoreFactory") + .withReference("org.apache.jackrabbit.oak.segment.SegmentNodeStoreFactory$Configuration") .check()); - ObjectClassDefinition ocd = mi.getObjectClassDefinition("org.apache.jackrabbit.oak.segment.SegmentNodeStoreFactory"); + ObjectClassDefinition ocd = mi.getObjectClassDefinition("org.apache.jackrabbit.oak.segment.SegmentNodeStoreFactory$Configuration"); assertTrue(ocd.hasAttributeDefinition("role") .withStringType() .check()); diff --git a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/osgi/SegmentNodeStoreMonitorServiceTest.java b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/osgi/SegmentNodeStoreMonitorServiceTest.java index a1c951b45b..e64f406748 100644 --- a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/osgi/SegmentNodeStoreMonitorServiceTest.java +++ b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/osgi/SegmentNodeStoreMonitorServiceTest.java @@ -33,25 +33,23 @@ public class SegmentNodeStoreMonitorServiceTest { assertTrue(cd.hasRequireConfigurationPolicy()); assertTrue(cd.hasActivateMethod("activate")); assertTrue(cd.hasImplementationClass("org.apache.jackrabbit.oak.segment.SegmentNodeStoreMonitorService")); - assertTrue(cd.hasProperty("commitsTrackerWriterGroups").check()); assertTrue(cd.hasReference("snsStatsMBean") .withInterface("org.apache.jackrabbit.oak.segment.SegmentNodeStoreStatsMBean") .withMandatoryUnaryCardinality() .withStaticPolicy() - .withBind("bindSnsStatsMBean") - .withUnbind("unbindSnsStatsMBean") + .withField("snsStatsMBean") .check()); } @Test public void testMetatypeInformation() throws Exception { - MetatypeInformation mi = MetatypeInformation.open(getClass().getResourceAsStream("/OSGI-INF/metatype/org.apache.jackrabbit.oak.segment.SegmentNodeStoreMonitorService.xml")); + MetatypeInformation mi = MetatypeInformation.open(getClass().getResourceAsStream("/OSGI-INF/metatype/org.apache.jackrabbit.oak.segment.SegmentNodeStoreMonitorService$Configuration.xml")); assertTrue(mi.hasDesignate() .withPid("org.apache.jackrabbit.oak.segment.SegmentNodeStoreMonitorService") - .withReference("org.apache.jackrabbit.oak.segment.SegmentNodeStoreMonitorService") + .withReference("org.apache.jackrabbit.oak.segment.SegmentNodeStoreMonitorService$Configuration") .check()); - ObjectClassDefinition ocd = mi.getObjectClassDefinition("org.apache.jackrabbit.oak.segment.SegmentNodeStoreMonitorService"); + ObjectClassDefinition ocd = mi.getObjectClassDefinition("org.apache.jackrabbit.oak.segment.SegmentNodeStoreMonitorService$Configuration"); assertTrue(ocd.hasAttributeDefinition("commitsTrackerWriterGroups") .withStringType() .withCardinality("2147483647") diff --git a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/osgi/SegmentNodeStoreServiceTest.java b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/osgi/SegmentNodeStoreServiceTest.java index 77841c5de6..3a45c47db2 100644 --- a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/osgi/SegmentNodeStoreServiceTest.java +++ b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/osgi/SegmentNodeStoreServiceTest.java @@ -123,35 +123,32 @@ public class SegmentNodeStoreServiceTest { .withStaticPolicy() .withGreedyPolicyOption() .withTarget("(&(!(split.blobstore=old))(!(split.blobstore=new)))") - .withBind("bindBlobStore") - .withUnbind("unbindBlobStore") + .withField("blobStore") .check()); assertTrue(cd.hasReference("segmentStore") .withInterface("org.apache.jackrabbit.oak.segment.spi.persistence.SegmentNodeStorePersistence") .withOptionalUnaryCardinality() .withStaticPolicy() .withGreedyPolicyOption() - .withBind("bindSegmentStore") - .withUnbind("unbindSegmentStore") + .withField("segmentStore") .check()); assertTrue(cd.hasReference("statisticsProvider") .withInterface("org.apache.jackrabbit.oak.stats.StatisticsProvider") .withMandatoryUnaryCardinality() .withStaticPolicy() - .withBind("bindStatisticsProvider") - .withUnbind("unbindStatisticsProvider") + .withField("statisticsProvider") .check()); } @Test public void testMetatypeInformation() throws Exception { - MetatypeInformation mi = MetatypeInformation.open(getClass().getResourceAsStream("/OSGI-INF/metatype/org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.xml")); + MetatypeInformation mi = MetatypeInformation.open(getClass().getResourceAsStream("/OSGI-INF/metatype/org.apache.jackrabbit.oak.segment.SegmentNodeStoreService$Configuration.xml")); assertTrue(mi.hasDesignate() .withPid("org.apache.jackrabbit.oak.segment.SegmentNodeStoreService") - .withReference("org.apache.jackrabbit.oak.segment.SegmentNodeStoreService") + .withReference("org.apache.jackrabbit.oak.segment.SegmentNodeStoreService$Configuration") .check()); - ObjectClassDefinition ocd = mi.getObjectClassDefinition("org.apache.jackrabbit.oak.segment.SegmentNodeStoreService"); + ObjectClassDefinition ocd = mi.getObjectClassDefinition("org.apache.jackrabbit.oak.segment.SegmentNodeStoreService$Configuration"); assertTrue(ocd.hasAttributeDefinition("repository.home") .withStringType() .check()); diff --git a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/osgi/StandbyStoreServiceTest.java b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/osgi/StandbyStoreServiceTest.java index b466650f14..9e7d929add 100644 --- a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/osgi/StandbyStoreServiceTest.java +++ b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/osgi/StandbyStoreServiceTest.java @@ -22,6 +22,7 @@ package org.apache.jackrabbit.oak.segment.osgi; import static org.junit.Assert.assertTrue; import org.apache.jackrabbit.oak.segment.osgi.MetatypeInformation.ObjectClassDefinition; +import org.junit.Ignore; import org.junit.Test; public class StandbyStoreServiceTest { @@ -52,8 +53,6 @@ public class StandbyStoreServiceTest { .withIntegerType() .withValue("5") .check()); - assertTrue(cd.hasProperty("primary.allowed-client-ip-ranges") - .check()); assertTrue(cd.hasProperty("secure") .withBooleanType() .withValue("false") @@ -71,20 +70,19 @@ public class StandbyStoreServiceTest { .withMandatoryUnaryCardinality() .withStaticPolicy() .withGreedyPolicyOption() - .withBind("bindStoreProvider") - .withUnbind("unbindStoreProvider") + .withField("storeProvider") .check()); } @Test public void testMetatypeInformation() throws Exception { - MetatypeInformation mi = MetatypeInformation.open(getClass().getResourceAsStream("/OSGI-INF/metatype/org.apache.jackrabbit.oak.segment.standby.store.StandbyStoreService.xml")); + MetatypeInformation mi = MetatypeInformation.open(getClass().getResourceAsStream("/OSGI-INF/metatype/org.apache.jackrabbit.oak.segment.standby.store.StandbyStoreService$Configuration.xml")); assertTrue(mi.hasDesignate() .withPid("org.apache.jackrabbit.oak.segment.standby.store.StandbyStoreService") - .withReference("org.apache.jackrabbit.oak.segment.standby.store.StandbyStoreService") + .withReference("org.apache.jackrabbit.oak.segment.standby.store.StandbyStoreService$Configuration") .check()); - ObjectClassDefinition ocd = mi.getObjectClassDefinition("org.apache.jackrabbit.oak.segment.standby.store.StandbyStoreService"); + ObjectClassDefinition ocd = mi.getObjectClassDefinition("org.apache.jackrabbit.oak.segment.standby.store.StandbyStoreService$Configuration"); assertTrue(ocd.hasAttributeDefinition("org.apache.sling.installer.configuration.persist") .withBooleanType() .withDefaultValue("false") @@ -106,10 +104,6 @@ public class StandbyStoreServiceTest { .withIntegerType() .withDefaultValue("5") .check()); - assertTrue(ocd.hasAttributeDefinition("primary.allowed-client-ip-ranges") - .withStringType() - .withCardinality("2147483647") - .check()); assertTrue(ocd.hasAttributeDefinition("secure") .withBooleanType() .withDefaultValue("false") @@ -122,6 +116,10 @@ public class StandbyStoreServiceTest { .withBooleanType() .withDefaultValue("true") .check()); + assertTrue(ocd.hasAttributeDefinition("primary.allowed-client-ip-ranges") + .withStringType() + .withCardinality("2147483647") + .check()); } }