-
Notifications
You must be signed in to change notification settings - Fork 112
ZCS-18733 : Implement external volume handling in mailbox movement #1813
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| /* | ||
| * ***** BEGIN LICENSE BLOCK ***** | ||
| * Zimbra Collaboration Suite, Network Edition. | ||
| * Copyright (C) 2026 Zimbra, Inc. All Rights Reserved. | ||
| * ***** END LICENSE BLOCK ***** | ||
| */ | ||
|
|
||
| package com.zimbra.cs.store; | ||
|
|
||
| import com.zimbra.common.service.ServiceException; | ||
| import com.zimbra.cs.store.file.FileBlobStore; | ||
| import com.zimbra.cs.volume.Volume; | ||
| import com.zimbra.cs.volume.VolumeManager; | ||
| import java.io.File; | ||
| import org.junit.Before; | ||
| import org.junit.Test; | ||
| import org.junit.runner.RunWith; | ||
| import org.powermock.api.mockito.PowerMockito; | ||
| import org.powermock.core.classloader.annotations.PrepareForTest; | ||
| import org.powermock.modules.junit4.PowerMockRunner; | ||
| import static org.junit.Assert.assertNotNull; | ||
| import static org.mockito.Mockito.verify; | ||
| import static org.powermock.api.mockito.PowerMockito.when; | ||
|
|
||
| @RunWith(PowerMockRunner.class) | ||
| @PrepareForTest({FileBlobStore.class, Volume.class, VolumeManager.class}) | ||
| public class FileBlobStoreMethodTest { | ||
|
|
||
| private VolumeManager manager; | ||
|
|
||
| private Volume volume; | ||
|
|
||
| private IncomingDirectory incomingDirectory; | ||
|
|
||
| private FileBlobStore fileBlobStore; | ||
|
|
||
| @Before | ||
| public void setUp() { | ||
| volume = PowerMockito.mock(Volume.class); | ||
| incomingDirectory = PowerMockito.mock(IncomingDirectory.class); | ||
| fileBlobStore = new FileBlobStore(); | ||
| PowerMockito.suppress( | ||
| PowerMockito.method(FileBlobStore.class, "ensureDirExists") | ||
| ); | ||
| } | ||
|
|
||
| @Test | ||
| public void testGetBlobBuilderWithVolume() throws Exception { | ||
| File file = new File("testFile"); | ||
| when(volume.getIncomingDirectory()).thenReturn(incomingDirectory); | ||
| when(incomingDirectory.getNewIncomingFile()).thenReturn(file); | ||
| when(volume.getId()).thenReturn((short) 1); | ||
| BlobBuilder builder = fileBlobStore.getBlobBuilder(volume); | ||
| assertNotNull(builder); | ||
| } | ||
|
|
||
| @Test | ||
| public void testGetBlobBuilderVolumeNullUsesManager() throws Exception { | ||
| VolumeManager mockManager = PowerMockito.mock(VolumeManager.class); | ||
| Volume defaultVolume = PowerMockito.mock(Volume.class); | ||
| IncomingDirectory dir = PowerMockito.mock(IncomingDirectory.class); | ||
| File file = new File("testFile"); | ||
| when(mockManager.getCurrentMessageVolume()).thenReturn(defaultVolume); | ||
| when(defaultVolume.getIncomingDirectory()).thenReturn(dir); | ||
| when(dir.getNewIncomingFile()).thenReturn(file); | ||
| when(defaultVolume.getId()).thenReturn((short) 1); | ||
| PowerMockito.field(FileBlobStore.class, "MANAGER").set(null, mockManager); | ||
| BlobBuilder builder = fileBlobStore.getBlobBuilder(null); | ||
| assertNotNull(builder); | ||
| verify(mockManager).getCurrentMessageVolume(); | ||
| verify(defaultVolume).getIncomingDirectory(); | ||
| verify(dir).getNewIncomingFile(); | ||
| } | ||
|
|
||
| @Test(expected = ServiceException.class) | ||
| public void testGetBlobBuilderWhenIncomingDirectoryNull() throws Exception { | ||
| when(volume.getIncomingDirectory()).thenReturn(null); | ||
| when(volume.getName()).thenReturn("testVolume"); | ||
| fileBlobStore.getBlobBuilder(volume); | ||
| } | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -262,6 +262,18 @@ public enum StoreFeature { | |
| */ | ||
| public abstract BlobBuilder getBlobBuilder() throws IOException, ServiceException; | ||
|
|
||
| /** | ||
| * Returns a 'BlobBuilder' which can be used to store a blob in incoming | ||
| * directory asynchronously one chunk at a time. Blob will be compressed | ||
| * if volume supports compression and blob size is over the compression | ||
| * threshold. | ||
| * @param volume | ||
| * @return the BlobBuilder to use to construct the Blob | ||
| * @throws IOException if an I/O error occurred | ||
| * @throws ServiceException if a service exception occurred | ||
| */ | ||
| public abstract BlobBuilder getBlobBuilder(Volume volume) throws IOException, ServiceException; | ||
|
|
||
| /** | ||
| * Store a blob in incoming directory. Blob will be compressed if volume supports compression | ||
| * and blob size is over the compression threshold. | ||
|
|
@@ -277,6 +289,20 @@ public Blob storeIncoming(InputStream data) | |
| return storeIncoming(data, false); | ||
| } | ||
|
|
||
| /** | ||
| * Store a blob in incoming directory. Blob will be compressed if volume supports compression | ||
| * and blob size is over the compression threshold. | ||
| * @param data | ||
| * @param vol | ||
| * @return | ||
| * @throws IOException | ||
| * @throws ServiceException | ||
| */ | ||
| public Blob storeIncoming(InputStream data, Volume vol) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| throws IOException, ServiceException { | ||
| return storeIncoming(data, false, vol); | ||
| } | ||
|
|
||
| /** | ||
| * Store a blob in incoming directory. | ||
| * @param data | ||
|
|
@@ -289,6 +315,18 @@ public Blob storeIncoming(InputStream data) | |
| public abstract Blob storeIncoming(InputStream data, boolean storeAsIs) | ||
| throws IOException, ServiceException; | ||
|
|
||
| /** | ||
| * Store a blob in incoming directory. | ||
| * @param data | ||
| * @param storeAsIs if true, store the blob as is even if volume supports compression | ||
| * @param volume | ||
| * @return | ||
| * @throws IOException | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [checkstyle] <com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocParagraphCheck> reported by reviewdog 🐶 |
||
| * @throws ServiceException | ||
| */ | ||
| public abstract Blob storeIncoming(InputStream data, boolean storeAsIs, Volume volume) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| throws IOException, ServiceException; | ||
|
|
||
| /** | ||
| * Stage an incoming <code>InputStream</code> to an | ||
| * appropriate place for subsequent storage in a <code>Mailbox</code> via | ||
|
|
@@ -402,6 +440,21 @@ public abstract MailboxBlob link(StagedBlob src, Mailbox destMbox, int destMsgId | |
| public abstract MailboxBlob renameTo(StagedBlob src, Mailbox destMbox, int destMsgId, int destRevision) | ||
| throws IOException, ServiceException; | ||
|
|
||
| /** | ||
| * Rename a blob to a blob in mailbox directory. | ||
| * This effectively makes the StagedBlob permanent, implementations may not need to do anything if the stage operation creates permanent items | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [checkstyle] <com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck> reported by reviewdog 🐶 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [checkstyle] <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck> reported by reviewdog 🐶 |
||
| * @param src | ||
| * @param destMbox | ||
| * @param destMsgId mail_item.id value for message in destMbox | ||
| * @param destRevision mail_item.mod_content value for message in destMbox | ||
| * @param volume volume info | ||
| * @return MailboxBlob object representing the renamed blob | ||
| * @throws IOException | ||
| * @throws ServiceException | ||
| */ | ||
| public abstract MailboxBlob renameTo(StagedBlob src, Mailbox destMbox, int destMsgId, int destRevision, | ||
| Volume volume) throws IOException, ServiceException; | ||
|
|
||
| /** | ||
| * Deletes a blob from incoming directory. If blob doesn't exist, no exception is | ||
| * thrown and false is returned. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -178,6 +178,11 @@ public BlobBuilder getBlobBuilder() throws IOException, ServiceException { | |
| return new ExternalBlobBuilder(new ExternalBlob(incoming.getNewIncomingFile())); | ||
| } | ||
|
|
||
| @Override | ||
| public BlobBuilder getBlobBuilder(Volume volume) throws IOException, ServiceException { | ||
| throw new UnsupportedOperationException("method not supported"); | ||
| } | ||
|
|
||
| @Override | ||
| public InputStream getContent(MailboxBlob mblob) throws IOException { | ||
| if (mblob == null) { | ||
|
|
@@ -244,6 +249,12 @@ public MailboxBlob renameTo(StagedBlob src, Mailbox destMbox, int destMsgId, int | |
| return mblob.setSize(staged.getSize()).setDigest(staged.getDigest()); | ||
| } | ||
|
|
||
| @Override | ||
| public MailboxBlob renameTo(StagedBlob src, Mailbox destMbox, int destMsgId, int destRevision, Volume volume) throws IOException, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [checkstyle] <com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck> reported by reviewdog 🐶 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [checkstyle] <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck> reported by reviewdog 🐶 |
||
| ServiceException { | ||
| throw new UnsupportedOperationException("method not supported"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [checkstyle] <com.puppycrawl.tools.checkstyle.checks.indentation.IndentationCheck> reported by reviewdog 🐶 |
||
| } | ||
|
|
||
| @Override | ||
| public void shutdown() { | ||
| IncomingDirectory.stopSweeper(); | ||
|
|
@@ -369,6 +380,12 @@ public Blob storeIncoming(InputStream data, boolean storeAsIs) throws IOExceptio | |
| return builder.init().append(data).finish(); | ||
| } | ||
|
|
||
| @Override | ||
| public Blob storeIncoming(InputStream data, boolean storeAsIs, Volume volume) throws IOException, | ||
| ServiceException { | ||
| throw new UnsupportedOperationException("method not supported"); | ||
| } | ||
|
|
||
| /** | ||
| * Get a set of all blobs which exist in the store associated with a mailbox | ||
| * Optional operation used to find orphaned blobs | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 [checkstyle] <com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocParagraphCheck> reported by reviewdog 🐶
Javadoc comment at column 0 has parse error. Details: no viable alternative at input ' *' while parsing JAVADOC_TAG