Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
275 changes: 270 additions & 5 deletions tests/acceptance/bootstrap/CliContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,27 @@ class CliContext implements Context {
private SpacesContext $spacesContext;

/**
* opencloud users storage path
* opencloud storage root path (with $HOME/~ expanded for CI)
*
* @return string
*/
public static function getUsersStoragePath(): string {
public static function getStorageRootPath(): string {
$path = getenv('OC_STORAGE_PATH') ?: '/var/lib/opencloud/storage/users';
// need for CI
$home = getenv('HOME');
$path = preg_replace('#^~/#', $home . '/', $path);
$path = str_replace('$HOME', $home, $path);

return rtrim($path, '/') . '/users';
return rtrim($path, '/');
}

/**
* opencloud users storage path
*
* @return string
*/
public static function getUsersStoragePath(): string {
return self::getStorageRootPath() . '/users';
}

/**
Expand All @@ -58,8 +67,7 @@ public static function getUsersStoragePath(): string {
* @return string
*/
public static function getProjectsStoragePath(): string {
$path = getenv('OC_STORAGE_PATH') ?: '/var/lib/opencloud/storage/users';
return $path . '/projects';
return self::getStorageRootPath() . '/projects';
}

/**
Expand Down Expand Up @@ -1039,4 +1047,261 @@ public function theAdminChecksTheAttributeOfFileForUser(string $attribute, strin
];
$this->featureContext->setResponse(CliHelper::runCommand($body));
}

/**
* on-disk path of a project space root
*
* @param string $space
*
* @return string
*/
private function getProjectSpaceStoragePath(string $space): string {
$spaceId = $this->spacesContext->getSpaceIdByName($this->featureContext->getAdminUsername(), $space);
$spaceId = explode('$', $spaceId)[1];
return $this->getProjectsStoragePath() . "/$spaceId";
}

/**
*
* @return void
*/
#[When('the administrator scans the whole storage using the CLI')]
public function theAdministratorScansTheWholeStorageUsingTheCli(): void {
$body = [
"command" => "posixfs scan"
];
$this->featureContext->setResponse(CliHelper::runCommand($body));
}

/**
*
* @param string $folder
* @param string $user
*
* @return void
*/
#[When('the administrator scans the folder :folder of user :user using the CLI')]
public function theAdministratorScansTheFolderOfUserUsingTheCli(string $folder, string $user): void {
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
$storagePath = $this->getUsersStoragePath();
$body = [
"command" => "posixfs scan $storagePath/$userUuid/$folder"
];
$this->featureContext->setResponse(CliHelper::runCommand($body));
}

/**
*
* @param string $space
*
* @return void
*/
#[When('the administrator scans the space :space using the CLI')]
public function theAdministratorScansTheSpaceUsingTheCli(string $space): void {
$body = [
"command" => "posixfs scan " . $this->getProjectSpaceStoragePath($space)
];
$this->featureContext->setResponse(CliHelper::runCommand($body));
}

/**
*
* @return void
*/
#[When('the administrator checks the posixfs consistency using the CLI')]
public function theAdministratorChecksThePosixfsConsistencyUsingTheCli(): void {
$body = [
"command" => "posixfs consistency"
];
$this->featureContext->setResponse(CliHelper::runCommand($body));
}

/**
*
* @param string $flag
*
* @return void
*/
#[When('the administrator checks the posixfs consistency using the CLI with flag :flag')]
public function theAdministratorChecksThePosixfsConsistencyUsingTheCliWithFlag(string $flag): void {
$body = [
"command" => "posixfs consistency $flag"
];
$this->featureContext->setResponse(CliHelper::runCommand($body));
}

/**
*
* @param string $file
* @param string $content
* @param string $space
*
* @return void
*/
#[When('the administrator creates the file :file with content :content in the space :space on the POSIX filesystem')]
public function theAdministratorCreatesFileInSpaceOnPosix(string $file, string $content, string $space): void {
$fullPath = $this->getProjectSpaceStoragePath($space) . "/$file";
$safeContent = escapeshellarg($content);
$body = [
"command" => "echo -n $safeContent > $fullPath",
"raw" => true
];
$this->featureContext->setResponse(CliHelper::runCommand($body));
$this->waitForPath($fullPath);
}

/**
* reads xattrs from disk with getfattr, bypassing the driver's on-the-fly assimilation
*
* @param string $file
* @param string $user
*
* @return void
*/
#[When('the administrator gets the extended attributes of file :file of user :user on the POSIX filesystem')]
public function theAdministratorGetsExtendedAttributesOfFileOfUser(string $file, string $user): void {
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
$storagePath = $this->getUsersStoragePath();
$body = [
"command" => "getfattr -d $storagePath/$userUuid/$file",
"raw" => true
];
$this->featureContext->setResponse(CliHelper::runCommand($body));
}

/**
*
* @param string $file
* @param string $space
*
* @return void
*/
#[When('the administrator gets the extended attributes of file :file in the space :space on the POSIX filesystem')]
public function theAdministratorGetsExtendedAttributesOfFileInSpace(string $file, string $space): void {
$body = [
"command" => "getfattr -d " . $this->getProjectSpaceStoragePath($space) . "/$file",
"raw" => true
];
$this->featureContext->setResponse(CliHelper::runCommand($body));
}

/**
*
* @param string $attribute
* @param string $file
* @param string $user
*
* @return void
*/
#[When('the administrator gets the extended attribute :attribute of file :file of user :user on the POSIX filesystem')]
public function theAdministratorGetsExtendedAttributeOfFileOfUser(string $attribute, string $file, string $user): void {
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
$storagePath = $this->getUsersStoragePath();
$body = [
"command" => "getfattr -n " . escapeshellarg($attribute) . " --only-values $storagePath/$userUuid/$file",
"raw" => true
];
$this->featureContext->setResponse(CliHelper::runCommand($body));
}

/**
*
* @param string $attribute
* @param string $file
* @param string $user
* @param string $value
*
* @return void
*/
#[When('the administrator sets the extended attribute :attribute of file :file of user :user to :value on the POSIX filesystem')]
public function theAdministratorSetsExtendedAttributeOfFileOfUser(
string $attribute,
string $file,
string $user,
string $value
): void {
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
$storagePath = $this->getUsersStoragePath();
$body = [
"command" => "setfattr -n " . escapeshellarg($attribute)
. " -v " . escapeshellarg($value) . " $storagePath/$userUuid/$file",
"raw" => true
];
$this->featureContext->setResponse(CliHelper::runCommand($body));
}

/**
*
* @param string $file
* @param string $user
*
* @return void
*/
#[When('the administrator scans the file :file of user :user using the CLI')]
public function theAdministratorScansTheFileOfUserUsingTheCli(string $file, string $user): void {
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
$storagePath = $this->getUsersStoragePath();
$body = [
"command" => "posixfs scan $storagePath/$userUuid/$file"
];
$this->featureContext->setResponse(CliHelper::runCommand($body));
}

/**
*
* @param string $path
*
* @return void
*/
#[When('the administrator scans path :path using the CLI')]
public function theAdministratorScansPathUsingTheCli(string $path): void {
$body = [
"command" => "posixfs scan $path"
];
$this->featureContext->setResponse(CliHelper::runCommand($body));
}

/**
* non-existing path first, so with "-E" the scan aborts before the valid folder
*
* @param string $folder
* @param string $user
* @param string $flag
*
* @return void
*/
#[When('the administrator scans a non-existing path and the folder :folder of user :user using the CLI with flag :flag')]
public function theAdministratorScansNonExistingPathAndFolderUsingTheCli(
string $folder,
string $user,
string $flag
): void {
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
$storagePath = $this->getUsersStoragePath();
$nonExistingPath = "$storagePath/$userUuid/nonExistingPath";
$validPath = "$storagePath/$userUuid/$folder";
$flagPart = $flag !== "" ? "$flag " : "";
$body = [
"command" => "posixfs scan {$flagPart}$nonExistingPath $validPath"
];
$this->featureContext->setResponse(CliHelper::runCommand($body));
}

/**
*
* @return void
*/
#[Then('the command should not be successful')]
public function theCommandShouldNotBeSuccessful(): void {
$response = $this->featureContext->getResponse();
$this->featureContext->theHTTPStatusCodeShouldBe(200, '', $response);

$jsonResponse = $this->featureContext->getJsonDecodedResponse($response);

Assert::assertNotSame(
0,
$jsonResponse["exitCode"],
"Expected command to fail with a non-zero exit code, but got 0. Message: " . $jsonResponse["message"]
);
}
}
50 changes: 50 additions & 0 deletions tests/acceptance/features/cliCommands/posixfsConsistency.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@env-config @skipOnOpencloud-decomposed-Storage @skipOnOpencloud-decomposeds3-Storage
Feature: posixfs consistency CLI command
As an administrator
I want to check and repair the consistency of the posix filesystem metadata
So that files with broken metadata (name, blobsize, parent id, ...) get fixed

# WATCH_FS=false so the watcher does not re-assimilate the corrupted metadata before the command runs
Background:
Given the config "STORAGE_USERS_POSIX_WATCH_FS" has been set to "false"
And user "Alice" has been created with default attributes


Scenario: consistency fixes a corrupted name attribute
Given user "Alice" has uploaded file with content "content" to "textfile.txt"
When the administrator sets the extended attribute "user.oc.name" of file "textfile.txt" of user "Alice" to "corrupted.txt" on the POSIX filesystem
Then the command should be successful
When the administrator gets the extended attribute "user.oc.name" of file "textfile.txt" of user "Alice" on the POSIX filesystem
Then the command output should contain "corrupted.txt"
When the administrator checks the posixfs consistency using the CLI
Then the command should be successful
And the command output should contain "Fixed name attribute"
When the administrator gets the extended attribute "user.oc.name" of file "textfile.txt" of user "Alice" on the POSIX filesystem
Then the command output should contain "textfile.txt"
And the command output should not contain "corrupted.txt"


Scenario: consistency fixes a corrupted blobsize attribute
Given user "Alice" has uploaded file with content "content" to "textfile.txt"
When the administrator sets the extended attribute "user.oc.blobsize" of file "textfile.txt" of user "Alice" to "0" on the POSIX filesystem
Then the command should be successful
When the administrator checks the posixfs consistency using the CLI
Then the command should be successful
And the command output should contain "Fixed blobsize"
When the administrator gets the extended attribute "user.oc.blobsize" of file "textfile.txt" of user "Alice" on the POSIX filesystem
Then the command output should contain "7"


Scenario: consistency fixes corrupted checksums only with the --fix-checksums flag
Given user "Alice" has uploaded file with content "content" to "textfile.txt"
When the administrator sets the extended attribute "user.oc.cs.sha1" of file "textfile.txt" of user "Alice" to "corrupted" on the POSIX filesystem
Then the command should be successful
And the administrator checks the posixfs consistency using the CLI
And the command should be successful
And the command output should not contain "Fixed checksum"
And the administrator checks the posixfs consistency using the CLI with flag "--fix-checksums"
And the command should be successful
And the command output should contain "Fixed checksum"
And the administrator checks the posixfs consistency using the CLI with flag "--fix-checksums"
And the command should be successful
And the command output should not contain "Fixed checksum"
Loading