Skip to content
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

Add snapshot methods with tests #17

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
Add snapshot methods with tests
+ createSnapshot
+ deleteSnapshot
+ allowSnapshot
+ disallowSnapshot

The two firsts take a path an argument, other take list of paths.
  • Loading branch information
monoid committed Feb 7, 2021
commit 23d38d76e1c995efad32cb2f497c6e91a00f9e20
10 changes: 8 additions & 2 deletions snakebite/channel.py
Original file line number Diff line number Diff line change
@@ -57,7 +57,7 @@

from snakebite.platformutils import get_current_username
from snakebite.formatter import format_bytes
from snakebite.errors import RequestError, TransientException, FatalException
from snakebite.errors import RequestError, TransientException, FatalException, SnapshotException
from snakebite.crc32c import crc
from snakebite.compat import range, py_2
from snakebite import logger
@@ -465,7 +465,13 @@ def unwrap_response(self, response_bytes, response_class):


def handle_error(self, header):
raise RequestError("\n".join([header.exceptionClassName, header.errorMsg]))
# TODO: one should handle the SnapshotException in the service
# code, but it needs refactoring RequestError, as it keeps
# data in the string.
if header.exceptionClassName.endswith('SnapshotException'):
raise SnapshotException(header.errorMsg)
else:
raise RequestError("\n".join([header.exceptionClassName, header.errorMsg]))

def close_socket(self):
'''Closes the socket and resets the channel.'''
107 changes: 107 additions & 0 deletions snakebite/client.py
Original file line number Diff line number Diff line change
@@ -1059,6 +1059,113 @@ def serverdefaults(self, force_reload=False):
# return a copy, so if the user changes any values, they won't be saved in the client
return self._server_defaults.copy()

def createSnapshot(self, path, snapshot):
''' Create snapshot for the path.

:param path: The paths
:type path: string
:param snapshot: snapshot name
:type snapshot: string
:returns: a generator that yields dictionaries
'''
if not isinstance(path, unicode):
raise InvalidInputException("path should be a string")
if not snapshot:
raise InvalidInputException("snapshot: no snapshot name given")

processor = lambda path, node, snapshot=snapshot: self._handle_create_snapshot(path, snapshot)
for item in self._find_items([path], processor, include_toplevel=True,
include_children=False, recurse=False):
if item:
yield item

def _handle_create_snapshot(self, path, snapshot):
request = client_proto.CreateSnapshotRequestProto()
request.snapshotRoot = path
request.snapshotName = snapshot
response = self.service.createSnapshot(request)
return {
"result": True,
"result_path": response.snapshotPath,
"snapshot": snapshot,
"path": path,
}

def deleteSnapshot(self, path, snapshot):
''' Delete snapshot for the path.

:param path: The path
:type path: string
:param snapshot: snapshot name
:type snapshot: string
:returns: a generator that yields dictionaries
'''
if not isinstance(path, unicode):
raise InvalidInputException("path should be a string")
if not snapshot:
raise InvalidInputException("snapshot: no snapshot name given")

processor = lambda path, node, snapshot=snapshot: self._handle_delete_snapshot(path, snapshot)
for item in self._find_items([path], processor, include_toplevel=True,
include_children=False, recurse=False):
if item:
yield item

def _handle_delete_snapshot(self, path, snapshot):
request = client_proto.DeleteSnapshotRequestProto()
request.snapshotRoot = path
request.snapshotName = snapshot
self.service.deleteSnapshot(request)
return {"result": True, "path": path, "snapshot": snapshot}

def allowSnapshot(self, paths):
''' Allow snapshotting for the path.

:param paths: The paths list
:type paths: list
:returns: a generator that yields dictionaries
'''
if not isinstance(paths, list):
raise InvalidInputException("paths should be a list")
if not paths:
raise InvalidInputException("allowSnapshot: no path given")

processor = lambda path, node: self._handle_allow_snapshot(path)
for item in self._find_items(paths, processor, include_toplevel=True,
include_children=False, recurse=False):
if item:
yield item

def _handle_allow_snapshot(self, path):
request = client_proto.AllowSnapshotRequestProto()
request.snapshotRoot = path
self.service.allowSnapshot(request)
return {"result": True, "path": path}

def disallowSnapshot(self, paths):
''' Disallow snapshotting for the path.

:param paths: The paths list
:type paths: list
:returns: a generator that yields dictionaries
'''
if not isinstance(paths, list):
raise InvalidInputException("paths should be a list")
if not paths:
raise InvalidInputException("disallowSnapshot: no path given")

processor = lambda path, node: self._handle_disallow_snapshot(path)
for item in self._find_items(paths, processor, include_toplevel=True,
include_children=False, recurse=False):
if item:
yield item

def _handle_disallow_snapshot(self, path):
request = client_proto.DisallowSnapshotRequestProto()
request.snapshotRoot = path
self.service.disallowSnapshot(request)
return {"result": True, "path": path}

def _is_directory(self, should_check, node):
if not should_check:
return True
4 changes: 4 additions & 0 deletions snakebite/errors.py
Original file line number Diff line number Diff line change
@@ -73,6 +73,10 @@ def __init__(self, msg):
super(OutOfNNException, self).__init__(msg)


class SnapshotException(FatalException):
pass


class RequestError(TransientException):
"""
Note: request error could be transient and could be fatal, depending on underlying error.
41 changes: 41 additions & 0 deletions test/snapshot_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from test.minicluster_testbase import MiniClusterTestBase
from snakebite.errors import SnapshotException

SNAPSHOT_NAME = 'mySnapshot'


class SnapshotsTest(MiniClusterTestBase):
def test_create_not_allowed(self):
self.assertRaises(
SnapshotException, self.client.createSnapshot('/dir1', SNAPSHOT_NAME).__next__)

def test_create_allowed(self):
new_dir = '/dir_create_allowed'
list(self.client.mkdir([new_dir]))
list(self.client.allowSnapshot([new_dir]))
list(self.client.createSnapshot(new_dir, SNAPSHOT_NAME))
self.assertTrue(self.client.test(new_dir + '/.snapshot/' + SNAPSHOT_NAME, exists=True))

def test_allow(self):
new_dir = '/dir_allow'
list(self.client.mkdir([new_dir]))
list(self.client.allowSnapshot([new_dir]))
self.assertTrue(self.client.test(new_dir + '/.snapshot', exists=True))

def test_allow_disallow(self):
new_dir = '/dir_allow_disallow'
list(self.client.mkdir([new_dir]))
list(self.client.allowSnapshot([new_dir]))
list(self.client.disallowSnapshot([new_dir]))
self.assertFalse(self.client.test(new_dir + '/.snapshot', exists=True))
self.assertRaises(
SnapshotException, self.client.createSnapshot(new_dir, SNAPSHOT_NAME).__next__)

def test_delete_snapshot(self):
new_dir = '/dir_remove_snapshot'
list(self.client.mkdir([new_dir]))
list(self.client.allowSnapshot([new_dir]))
list(self.client.createSnapshot(new_dir, SNAPSHOT_NAME))
self.assertTrue(self.client.test(new_dir + '/.snapshot/' + SNAPSHOT_NAME, exists=True))
list(self.client.deleteSnapshot(new_dir, SNAPSHOT_NAME))
self.assertFalse(self.client.test(new_dir + '/.snapshot/' + SNAPSHOT_NAME, exists=True))