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

Public terminal management #6

Open
wants to merge 4 commits into
base: master
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
91 changes: 90 additions & 1 deletion director/api/public.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,98 @@
from flask import Blueprint
from flask import Blueprint, request
from director.model import User
from functools import wraps

PublicAPI = Blueprint('public_api', __name__, url_prefix='/api/public')

@PublicAPI.route('/user/<username>')
def get_user(username):
user = User.query.filter_by(username=username).first_or_404()
return user.serialize()


from director.model import Terminal as terminal_model

@PublicAPI.route('/lab/<int:lab>/terminal/<int:terminal>', methods=["GET"])
@PublicAPI.route('/lab/<int:lab>/terminals', methods=["GET"])
def get_terminals(lab, terminal = None):
# GET: /terminals
if terminal == None:
terminals = terminal_model.query.filter_by(lab_id=lab).all()
jsonArray = {'terminals': []}

for _ in terminals:
jsonArray['terminals'].append(_.serialize())

return jsonArray
#end if: GET: /terminals

# GET: /terminal/<terminal>
terminal_result = terminal_model.query.filter_by(lab_id=lab,id=terminal).first_or_404()

return terminal_result.serialize()

# Checks if the client sends a (not-empty-also) json
def empty_json_body(f):

Choose a reason for hiding this comment

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

Really nice, but maybe rename it to something like @require_json_body?

@wraps(f)
def decorated(*args, **kwargs):
try:
# Check if empty
if len(request.get_json()) == 0:
return {'message': 'Provide some values.'}, 400
except:
# There was no json in request data
return {"message": "Provide a json body."}, 400
return f(*args,**kwargs)
return decorated

@PublicAPI.route('/lab/<int:lab>/terminal/<int:terminal>', methods=["PATCH", "PUT"])
@empty_json_body
def patch_terminal(lab, terminal):

terminal_result = terminal_model.query.filter_by(lab_id=lab,id=terminal).first_or_404()

valid_options = ['host_name', 'ip', 'status', 'room', 'lab_id']

changes = {}
for key, value in dict(request.json).items():
if key in valid_options:
value = None if value == "" else value
changes[key] = value

# If not any value is assigned, assign the uri's lab otherwise the terminal
# will be lost forever from the client (only the database will be able to see it)
if changes.get("lab_id") == None:
changes["lab_id"] = lab

# possible TODO: check status value (is it 0 1 2 3) or (down, locked, up, logged_in)

# PATCH: Update only the parameters we are given
if request.method == 'PATCH':
if changes['ip'] == None: # IP is not nullable

Choose a reason for hiding this comment

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

Maybe run this check in the model? We should check if validations are supported by SQAlchemy.

return {"error": "Terminal IP can't be null or empty"}, 400

return terminal_result.update(changes)
# end if:PATCH

# PUT: Update all parameters
for option in valid_options:
if option not in changes.keys():
changes[option] = None

if changes['ip'] == None: # IP is not nullable
return {"error": "Terminal IP can't be null or empty"}, 400

return terminal_result.update_all(changes)

@PublicAPI.route('/lab/<int:lab>/terminal/<int:terminal>/<command>', methods=["PATCH"])
def patch_terminal_c(lab, terminal, command):

valid_commands = ['restart','shutdown','hibernate','log-out']

if command not in valid_commands:
return {"error": "Invalid command"}, 406

# TODO
# communicate with user-agent

return {"message": "Not implemented"}, 501
39 changes: 38 additions & 1 deletion director/model/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,25 @@

from sqlalchemy import ForeignKey

from director import db
from marshmallow import Schema, fields
from marshmallow_enum import EnumField

from director import db

class Status(enum.Enum):
down = 0
locked = 1
up = 2
logged_in = 3

class TerminalSchema(Schema):
id = fields.Int(dump_only=True)
host_name = fields.Str()
ip = fields.Str()
status = EnumField(Status)
room = fields.Str()
lab_id = fields.Int(dump_only=True)


class Terminal(db.Model):
id = db.Column(db.Integer, primary_key=True)
Expand All @@ -21,6 +31,33 @@ class Terminal(db.Model):
lab_id = db.Column(db.Integer, ForeignKey('lab.id'))
sessions = db.relationship("Session", backref="terminal")

def serialize(self):
return TerminalSchema().dump(self)

def update_all(self, changes):
try:
self.host_name = changes["host_name"]
self.ip = changes["ip"]
self.status = Status.down if changes["status"] == None else changes["status"]
self.room = changes["room"]
self.lab_id = changes["lab_id"]
db.session.commit()
except:
return {"error", "Possible duplicate host name or IP."}, 400

return {"message": "Update successful"}

def update(self, changes):
madeChanges = 204

Choose a reason for hiding this comment

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

Let's keep status codes out of the model classes, and reply inside the API classes!

for key, value in changes.items():
madeChanges = 200
setattr(self, key, value)
try:
db.session.commit()
except:
return {"error", "Possible duplicate host name or IP."}, 400
return {"Success": "Updated."}, madeChanges

def __repr__(self):
return '<Terminal %r %r %r %r %r %r>' % (self.id,
self.host_name,
Expand Down