Newer
Older
# Copyright 2019 PrivateStorage.io, LLC
#
# Licensed 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.
"""
This module implements models (in the MVC sense) for the client side of
the storage plugin.
"""
from uuid import (
uuid4,
)
from functools import (
wraps,
)
from json import (
loads,
dumps,
)
from datetime import (
datetime,
)
from zope.interface import (
Interface,
implementer,
)
from sqlite3 import (
from aniso8601 import (
parse_datetime,
)
from twisted.logger import (
Logger,
)
from twisted.python.filepath import (
FilePath,
from ._base64 import (
urlsafe_b64decode,
)
from .validators import (
is_base64_encoded,
has_length,
greater_than,
)
from .storage_common import (
pass_value_attribute,
get_configured_pass_value,
required_passes,
)
from .schema import (
get_schema_version,
get_schema_upgrades,
run_schema_upgrades,
)
class ILeaseMaintenanceObserver(Interface):
"""
An object which is interested in receiving events related to the progress
of lease maintenance activity.
"""
def observe(sizes):
"""
Observe some shares encountered during lease maintenance.
:param list[int] sizes: The sizes of the shares encountered.
"""
def finish():
"""
Observe that a run of lease maintenance has completed.
"""
class StoreOpenError(Exception):
"""
There was a problem opening the underlying data store.
"""
def __init__(self, reason):
self.reason = reason
class NotEnoughTokens(Exception):
"""
An attempt to extract tokens failed because the store does not contain as
many tokens as were requested.
"""
CONFIG_DB_NAME = u"privatestorageio-zkapauthz-v1.sqlite3"
def open_and_initialize(path, connect=None):
Open a SQLite3 database for use as a voucher store.
Create the database and populate it with a schema, if it does not already
exist.
:param FilePath path: The location of the SQLite3 database file.
:return: A SQLite3 connection object for the database at the given path.
"""
if connect is None:
connect = _connect
try:
path.parent().makedirs(ignoreExistingDirectory=True)
except OSError as e:
raise StoreOpenError(e)
dbfile = path.asBytesMode().path
try:
conn = connect(
dbfile,
isolation_level="IMMEDIATE",
)
except OperationalError as e:
raise StoreOpenError(e)
# Enforcement of foreign key constraints is off by default. It must be
# enabled on a per-connection basis. This is a helpful feature to ensure
# consistency so we want it enforced and we use it in our schema.
conn.execute("PRAGMA foreign_keys = ON")
with conn:
cursor = conn.cursor()
actual_version = get_schema_version(cursor)
schema_upgrades = list(get_schema_upgrades(actual_version))
run_schema_upgrades(schema_upgrades, cursor)
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# Create some tables that only exist (along with their contents) only for
# this connection. These are outside of the schema because they are not
# persistent. We can change them any time we like without worrying about
# upgrade logic because we re-create them on every connection.
conn.execute(
"""
CREATE TEMPORARY TABLE [in-use] (
[unblinded-token] text, -- The base64 encoded unblinded token.
[operation-id] text, -- A unique identifier for a group of tokens in-use together.
PRIMARY KEY([unblinded-token])
-- A foreign key on unblinded-token to [unblinded-tokens]([token])
-- would be alright - however SQLite3 foreign key constraints
-- can't cross databases (and temporary tables are considered to
-- be in a different database than normal tables). ) """,
)
conn.execute(
"""
CREATE TEMPORARY TABLE [to-discard] (
[unblinded-token] text
)
""",
)
conn.execute(
"""
CREATE TEMPORARY TABLE [to-reset] (
[unblinded-token] text
)
""",
)
conn.execute(
"""
CREATE TEMPORARY TABLE [extracting] (
[token] text
)
""",
def with_cursor(f):
@wraps(f)
def with_cursor(self, *a, **kw):
with self._connection:
cursor = self._connection.cursor()
cursor.execute("BEGIN IMMEDIATE TRANSACTION")
return f(self, cursor, *a, **kw)
return with_cursor
Loading
Loading full blame...