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

Persist REST state #48

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
82 changes: 59 additions & 23 deletions blockade/api/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,59 +14,95 @@
# limitations under the License.
#

from blockade.config import BlockadeConfig
from blockade.core import Blockade
from blockade.errors import BlockadeNotFound
from blockade.errors import InvalidBlockadeName
from blockade.net import BlockadeNetwork
from blockade.state import BlockadeState

# TODO(pdmars): breaks if server restarts, refactor to be part of BlockadeState
BLOCKADE_CONFIGS = {}
import os
import yaml


DATA_DIR = "/tmp"
BASE_BLOCKADE_DIR = os.path.join(DATA_DIR, ".blockade")
REST_STATE_FILE = os.path.join(BASE_BLOCKADE_DIR, "rest_state.yaml")


class BlockadeManager:
"""Simple helper for what should eventually be persisted via BlockadeState
"""Simple helper to persist Blockade configurations managed via REST API
"""

@staticmethod
def set_data_dir(data_dir):
global DATA_DIR
DATA_DIR = data_dir
BASE_BLOCKADE_DIR = os.path.join(DATA_DIR, ".blockade")
REST_STATE_FILE = os.path.join(BASE_BLOCKADE_DIR, "rest_state.yaml")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need to declare these two as global if the changes are to stick. However, is there a reason to use global vars at all? Perhaps all three of these vars could be on the class instead?



@staticmethod
def init_base_blockade_dir():
if not os.path.isdir(BASE_BLOCKADE_DIR):
os.makedirs(BASE_BLOCKADE_DIR)

@staticmethod
def read_rest_state():
BlockadeManager.init_base_blockade_dir()
rest_state = {}
if os.path.exists(REST_STATE_FILE):
with open(REST_STATE_FILE, "r") as f:
rest_state = yaml.safe_load(f) or {}
return rest_state

@staticmethod
def write_rest_state(rest_state):
BlockadeManager.init_base_blockade_dir()
with open(REST_STATE_FILE, "w") as f:
yaml.safe_dump(rest_state, f)

@staticmethod
def blockade_exists(name):
global BLOCKADE_CONFIGS
return name in BLOCKADE_CONFIGS
rest_state = BlockadeManager.read_rest_state()
return name in rest_state

@staticmethod
def store_config(name, config):
global BLOCKADE_CONFIGS
BLOCKADE_CONFIGS[name] = config
rest_state = BlockadeManager.read_rest_state()
rest_state[name] = config
BlockadeManager.write_rest_state(rest_state)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not too familiar with Flask - are there multiple request handler threads? If so this behavior seems racy. Concurrent requests could step on each other. delete_config too.


@staticmethod
def delete_config(name):
global BLOCKADE_CONFIGS
if name in BLOCKADE_CONFIGS:
del BLOCKADE_CONFIGS[name]

@staticmethod
def load_state(name):
global DATA_DIR
try:
return BlockadeState(blockade_id=name, data_dir=DATA_DIR)
except InvalidBlockadeName:
raise
rest_state = BlockadeManager.read_rest_state()
if name in rest_state:
del rest_state[name]
else:
raise BlockadeNotFound()
BlockadeManager.write_rest_state(rest_state)

@staticmethod
def get_blockade(name):
global BLOCKADE_CONFIGS
config = BLOCKADE_CONFIGS[name]
rest_state = BlockadeManager.read_rest_state()
blockade_state = BlockadeManager.load_blockade_state(name)
if name not in rest_state:
raise BlockadeNotFound()
config = BlockadeConfig.from_dict(rest_state[name])
return Blockade(config,
blockade_id=name,
state=BlockadeManager.load_state(name),
state=blockade_state,
network=BlockadeNetwork(config))

@staticmethod
def get_all_blockade_names():
global BLOCKADE_CONFIGS
return list(BLOCKADE_CONFIGS.keys())
rest_state = BlockadeManager.read_rest_state()
return list(rest_state.keys())

@staticmethod
def load_blockade_state(name):
global DATA_DIR
try:
return BlockadeState(blockade_id=name, data_dir=DATA_DIR)
except InvalidBlockadeName:
raise
8 changes: 5 additions & 3 deletions blockade/api/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from blockade.api.manager import BlockadeManager
from blockade.config import BlockadeConfig
from blockade.errors import BlockadeNotFound
from blockade.errors import DockerContainerNotFound
from blockade.errors import InvalidBlockadeName

Expand All @@ -44,8 +45,9 @@ def unsupported_media_type(error):


@app.errorhandler(404)
def blockade_name_not_found(error):
return 'Blockade name not found', 404
@app.errorhandler(BlockadeNotFound)
def blockade_not_found(error):
return 'Blockade not found', 404


@app.errorhandler(InvalidBlockadeName)
Expand Down Expand Up @@ -78,7 +80,7 @@ def create(name):
# This will abort with a 400 if the JSON is bad
data = request.get_json()
config = BlockadeConfig.from_dict(data)
BlockadeManager.store_config(name, config)
BlockadeManager.store_config(name, data)

b = BlockadeManager.get_blockade(name)
containers = b.create()
Expand Down
4 changes: 4 additions & 0 deletions blockade/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,7 @@ class InvalidBlockadeName(BlockadeError):
class DockerContainerNotFound(BlockadeError):
"""Docker container not found
"""

class BlockadeNotFound(BlockadeError):
"""Blockade not found
"""