Skip to content
Merged
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
2 changes: 1 addition & 1 deletion ci/compose.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function compose_down_all {
}

function all_weaviate_ports {
echo "8090 8081 8087 8088 8089 8086 8082 8083 8075 8092 8085 8080" # in alphabetic order of appearance in docker-compose files
echo "8090 8081 8093 8087 8088 8089 8086 8082 8083 8075 8092 8085 8080" # in alphabetic order of appearance in docker-compose files
}

function wait(){
Expand Down
37 changes: 37 additions & 0 deletions ci/docker-compose-backup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
version: '3.4'
services:
weaviate-backup:
command:
- --host
- 0.0.0.0
- --port
- '8080'
- --scheme
- http
- --write-timeout=600s
image: semitechnologies/weaviate:${WEAVIATE_VERSION}
ports:
- 8093:8080
- 50065:50051
restart: on-failure:0
environment:
ENABLE_MODULES: "backup-filesystem,generative-dummy,reranker-dummy"
AUTHENTICATION_APIKEY_ENABLED: "true"
AUTHENTICATION_APIKEY_ALLOWED_KEYS: "admin-key,custom-key"
AUTHENTICATION_APIKEY_USERS: "admin-user,custom-user"
AUTHORIZATION_ADMIN_USERS: "admin-user"
BACKUP_FILESYSTEM_PATH: "/tmp/backups"
PERSISTENCE_DATA_PATH: "./data-weaviate-0"
CLUSTER_IN_LOCALHOST: "true"
CLUSTER_GOSSIP_BIND_PORT: "7100"
CLUSTER_DATA_BIND_PORT: "7101"
RAFT_BOOTSTRAP_EXPECT: "1"
AUTHORIZATION_ENABLE_RBAC: "true"
AUTHENTICATION_DB_USERS_ENABLED: "true"
AUTHENTICATION_OIDC_ENABLED: 'true'
AUTHENTICATION_OIDC_CLIENT_ID: 'wcs'
AUTHENTICATION_OIDC_ISSUER: 'https://auth.wcs.api.weaviate.io/auth/realms/SeMI'
AUTHENTICATION_OIDC_USERNAME_CLAIM: 'email'
AUTHENTICATION_OIDC_GROUPS_CLAIM: 'groups'
...
2 changes: 1 addition & 1 deletion ci/docker-compose-rbac.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ services:
image: semitechnologies/weaviate:${WEAVIATE_VERSION}
ports:
- 8092:8085
- "50063:50051"
- 50063:50051
restart: on-failure:0
environment:
ENABLE_MODULES: "generative-dummy,reranker-dummy"
Expand Down
58 changes: 57 additions & 1 deletion integration/test_backup_v4.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
UnexpectedStatusCodeException,
)

from weaviate.auth import Auth
from .conftest import ClientFactory, _sanitize_collection_name
from _pytest.fixtures import SubRequest

RBAC_PORTS = (8093, 50065)
RBAC_AUTH_CREDS = Auth.api_key("admin-key")

pytestmark = pytest.mark.xdist_group(name="backup")

BACKEND = BackupStorage.FILESYSTEM
Expand Down Expand Up @@ -76,7 +83,9 @@

@pytest.fixture(scope="module")
def client() -> Generator[weaviate.WeaviateClient, None, None]:
client = weaviate.connect_to_local()
client = weaviate.connect_to_local(
port=RBAC_PORTS[0], grpc_port=RBAC_PORTS[1], auth_credentials=RBAC_AUTH_CREDS
)
client.collections.delete(CLASSES)

col_para = client.collections.create(
Expand Down Expand Up @@ -510,3 +519,50 @@ def test_cancel_backup(
)
# there can be a race between the cancel and the backup completion
assert status_resp.status == BackupStatus.CANCELED or status_resp.status == BackupStatus.SUCCESS


def test_backup_and_restore_with_roles_and_users(
client_factory: ClientFactory, request: SubRequest
) -> None:
backup_id = _create_backup_id()
client = client_factory(ports=RBAC_PORTS, auth_credentials=RBAC_AUTH_CREDS)
if client._connection._weaviate_version.is_lower_than(1, 30, 10):
pytest.skip("User and roles are only supported from Weaviate 1.30.10")

name = _sanitize_collection_name(request.node.fspath.basename + "_" + request.node.name)
client.collections.delete(name)
client.collections.create(name=name)

client.users.db.delete(user_id=name)
client.users.db.create(user_id=name)

client.roles.delete(role_name=name)
client.roles.create(
role_name=name,
permissions=wvc.rbac.Permissions.collections(collection="*", create_collection=True),
)

resp = client.backup.create(
backup_id=backup_id,
backend=BACKEND,
wait_for_completion=True,
include_collections=[name],
)
assert resp.status == BackupStatus.SUCCESS

client.users.db.delete(user_id=name)
client.roles.delete(role_name=name)
client.collections.delete(name)
assert client.users.db.get(user_id=name) is None
assert client.roles.get(role_name=name) is None

resp = client.backup.restore(
backup_id=backup_id,
backend=BACKEND,
wait_for_completion=True,
roles_restore="all",
users_restore="all",
)

assert client.users.db.get(user_id=name) is not None
assert client.roles.get(role_name=name) is not None
4 changes: 3 additions & 1 deletion weaviate/backup/async_.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List, Optional, Union
from typing import List, Literal, Optional, Union

from weaviate.backup.backup import (
BackupConfigCreate,
Expand Down Expand Up @@ -35,6 +35,8 @@ class _BackupAsync(_BackupExecutor[ConnectionAsync]):
backend: BackupStorage,
include_collections: Union[List[str], str, None] = None,
exclude_collections: Union[List[str], str, None] = None,
roles_restore: Optional[Literal["noRestore", "all"]] = None,
users_restore: Optional[Literal["noRestore", "all"]] = None,
wait_for_completion: bool = False,
config: Optional[BackupConfigRestore] = None,
backup_location: Optional[BackupLocationType] = None,
Expand Down
24 changes: 16 additions & 8 deletions weaviate/backup/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import asyncio
import time
from typing import Dict, Generic, List, Optional, Tuple, Union
from typing import Dict, Generic, List, Literal, Optional, Tuple, Union

from httpx import Response

Expand Down Expand Up @@ -239,6 +239,8 @@ def restore(
backend: BackupStorage,
include_collections: Union[List[str], str, None] = None,
exclude_collections: Union[List[str], str, None] = None,
roles_restore: Optional[Literal["noRestore", "all"]] = None,
users_restore: Optional[Literal["noRestore", "all"]] = None,
wait_for_completion: bool = False,
config: Optional[BackupConfigRestore] = None,
backup_location: Optional[BackupLocationType] = None,
Expand Down Expand Up @@ -281,19 +283,16 @@ def restore(
"include": include_collections,
"exclude": exclude_collections,
}

configPayload = {}
if config is not None:
if self._connection._weaviate_version.is_lower_than(1, 25, 0):
raise WeaviateUnsupportedFeatureError(
"BackupConfigRestore",
str(self._connection._weaviate_version),
"1.25.0",
)
if not isinstance(config, BackupConfigRestore):
raise WeaviateInvalidInputError(
f"Expected 'config' to be of type 'BackupConfigRestore', but got {type(config)}."
)
payload["config"] = config._to_dict()

configPayload = config._to_dict()

if backup_location is not None:
if self._connection._weaviate_version.is_lower_than(1, 27, 2):
Expand All @@ -305,7 +304,16 @@ def restore(

if "config" not in payload:
payload["config"] = {}
payload["config"].update(backup_location._to_dict())
configPayload.update(backup_location._to_dict())

if roles_restore is not None:
configPayload["rolesOptions"] = roles_restore

if users_restore is not None:
configPayload["usersOptions"] = users_restore

if len(configPayload) > 0:
payload["config"] = configPayload

path = f"/backups/{backend.value}/{backup_id}/restore"

Expand Down
4 changes: 3 additions & 1 deletion weaviate/backup/sync.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List, Optional, Union
from typing import List, Literal, Optional, Union

from weaviate.backup.backup import (
BackupConfigCreate,
Expand Down Expand Up @@ -35,6 +35,8 @@ class _Backup(_BackupExecutor[ConnectionSync]):
backend: BackupStorage,
include_collections: Union[List[str], str, None] = None,
exclude_collections: Union[List[str], str, None] = None,
roles_restore: Optional[Literal["noRestore", "all"]] = None,
users_restore: Optional[Literal["noRestore", "all"]] = None,
wait_for_completion: bool = False,
config: Optional[BackupConfigRestore] = None,
backup_location: Optional[BackupLocationType] = None,
Expand Down