summaryrefslogtreecommitdiff
path: root/opendc/api/v1/simulations/simulationId/datacenters
diff options
context:
space:
mode:
Diffstat (limited to 'opendc/api/v1/simulations/simulationId/datacenters')
-rw-r--r--opendc/api/v1/simulations/simulationId/datacenters/__init__.py0
-rw-r--r--opendc/api/v1/simulations/simulationId/datacenters/datacenterId/__init__.py0
-rw-r--r--opendc/api/v1/simulations/simulationId/datacenters/datacenterId/endpoint.py43
-rw-r--r--opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/__init__.py0
-rw-r--r--opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/endpoint.py111
-rw-r--r--opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/__init__.py0
-rw-r--r--opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/endpoint.py140
-rw-r--r--opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/__init__.py0
-rw-r--r--opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/endpoint.py123
-rw-r--r--opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/__init__.py0
-rw-r--r--opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/endpoint.py89
-rw-r--r--opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/__init__.py0
-rw-r--r--opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/endpoint.py245
-rw-r--r--opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/machines/__init__.py0
-rw-r--r--opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/machines/endpoint.py122
-rw-r--r--opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/machines/position/__init__.py0
-rw-r--r--opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/machines/position/endpoint.py170
-rw-r--r--opendc/api/v1/simulations/simulationId/datacenters/endpoint.py60
18 files changed, 1103 insertions, 0 deletions
diff --git a/opendc/api/v1/simulations/simulationId/datacenters/__init__.py b/opendc/api/v1/simulations/simulationId/datacenters/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/opendc/api/v1/simulations/simulationId/datacenters/__init__.py
diff --git a/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/__init__.py b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/__init__.py
diff --git a/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/endpoint.py b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/endpoint.py
new file mode 100644
index 00000000..c9c78a8a
--- /dev/null
+++ b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/endpoint.py
@@ -0,0 +1,43 @@
+from opendc.models.datacenter import Datacenter
+from opendc.util import database, exceptions
+from opendc.util.rest import Response
+
+def GET(request):
+ """Get this Datacenter."""
+
+ # Make sure required parameters are there
+
+ try:
+ request.check_required_parameters(
+ path = {
+ 'simulationId': 'int',
+ 'datacenterId': 'int'
+ }
+ )
+
+ except exceptions.ParameterError as e:
+ return Response(400, e.message)
+
+ # Instantiate a Datacenter from the database
+
+ datacenter = Datacenter.from_primary_key((request.params_path['datacenterId'],))
+
+ # Make sure this Datacenter exists
+
+ if not datacenter.exists():
+ return Response(404, '{} not found.'.format(datacenter))
+
+ # Make sure this user is authorized to view this Datacenter
+
+ if not datacenter.google_id_has_at_least(request.google_id, 'VIEW'):
+ return Response(403, 'Forbidden from retrieving {}.'.format(datacenter))
+
+ # Return this Datacenter
+
+ datacenter.read()
+
+ return Response(
+ 200,
+ 'Successfully retrieved {}.'.format(datacenter),
+ datacenter.to_JSON()
+ )
diff --git a/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/__init__.py b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/__init__.py
diff --git a/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/endpoint.py b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/endpoint.py
new file mode 100644
index 00000000..1127ce95
--- /dev/null
+++ b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/endpoint.py
@@ -0,0 +1,111 @@
+from opendc.models.room import Room
+from opendc.models.datacenter import Datacenter
+from opendc.util import database, exceptions
+from opendc.util.rest import Response
+
+def GET(request):
+ """Get this Datacenter's Rooms."""
+
+ # Make sure required parameters are there
+
+ try:
+ request.check_required_parameters(
+ path = {
+ 'simulationId': 'int',
+ 'datacenterId': 'int'
+ }
+ )
+ except exceptions.ParameterError as e:
+ return Response(400, e.message)
+
+ # Instantiate a Datacenter from the database
+
+ datacenter = Datacenter.from_primary_key((request.params_path['datacenterId'],))
+
+ # Make sure this Datacenter exists
+
+ if not datacenter.exists():
+ return Response(404, '{} not found.'.format(datacenter))
+
+ # Make sure this user is authorized to view this Datacenter's Rooms
+
+ if not datacenter.google_id_has_at_least(request.google_id, 'VIEW'):
+ return Response(403, 'Forbidden from viewing Rooms for {}.'.format(datacenter))
+
+ # Get and return the Rooms
+
+ rooms = Room.query('datacenter_id', datacenter.id)
+
+ return Response(
+ 200,
+ 'Successfully retrieved Rooms for {}.'.format(datacenter),
+ [x.to_JSON() for x in rooms]
+ )
+
+def POST(request):
+ """Add a Room."""
+
+ # Make sure required parameters are there
+
+ try:
+ request.check_required_parameters(
+ path = {
+ 'simulationId': 'int',
+ 'datacenterId': 'int'
+ },
+ body = {
+ 'room': {
+ 'id': 'int',
+ 'datacenterId': 'int',
+ 'roomType': 'string'
+ }
+ }
+ )
+ except exceptions.ParameterError as e:
+ return Response(400, e.message)
+
+ # Make sure the passed object's datacenter id matches the path datacenter id
+
+ if request.params_path['datacenterId'] != request.params_body['room']['datacenterId']:
+ return Response(400, 'ID mismatch.')
+
+ # Instantiate a Datacenter from the database
+
+ datacenter = Datacenter.from_primary_key((request.params_path['datacenterId'],))
+
+ # Make sure this Datacenter exists
+
+ if not datacenter.exists():
+ return Response(404, '{} not found.'.format(datacenter))
+
+ # Make sure this user is authorized to edit this Datacenter's Rooms
+
+ if not datacenter.google_id_has_at_least(request.google_id, 'EDIT'):
+ return Response(403, 'Forbidden from adding a Room to {}.'.format(datacenter))
+
+ # Add a name if not provided
+
+ if not 'name' in request.params_body['room']:
+ room_count = len(Room.query('datacenter_id', datacenter.id))
+ request.params_body['room']['name'] = 'Room {}'.format(room_count)
+
+ # Instantiate a Room
+
+ room = Room.from_JSON(request.params_body['room'])
+
+ # Try to insert this Room
+
+ try:
+ room.insert()
+ except Exception as e:
+ return Response(400, 'Invalid `roomType` or existing `name`.')
+
+ # Return this Room
+
+ room.read()
+
+ return Response(
+ 200,
+ 'Successfully added {}.'.format(room),
+ room.to_JSON()
+ )
diff --git a/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/__init__.py b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/__init__.py
diff --git a/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/endpoint.py b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/endpoint.py
new file mode 100644
index 00000000..44ed82d6
--- /dev/null
+++ b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/endpoint.py
@@ -0,0 +1,140 @@
+from opendc.models.room import Room
+from opendc.util import database, exceptions
+from opendc.util.rest import Response
+
+def GET(request):
+ """Get this Room."""
+
+ # Make sure required parameters are there
+
+ try:
+ request.check_required_parameters(
+ path = {
+ 'simulationId': 'int',
+ 'datacenterId': 'int',
+ 'roomId': 'int'
+ }
+ )
+
+ except exceptions.ParameterError as e:
+ return Response(400, e.message)
+
+ # Instantiate a Room from the database
+
+ room = Room.from_primary_key((request.params_path['roomId'],))
+
+ # Make sure this Room exists
+
+ if not room.exists():
+ return Response(404, '{} not found.'.format(room))
+
+ # Make sure this user is authorized to view this Room
+
+ if not room.google_id_has_at_least(request.google_id, 'VIEW'):
+ return Response(403, 'Forbidden from retrieving {}.'.format(room))
+
+ # Return this Room
+
+ room.read()
+
+ return Response(
+ 200,
+ 'Successfully retrieved {}.'.format(room),
+ room.to_JSON()
+ )
+
+def PUT(request):
+ """Update this Room's name and type."""
+
+ # Make sure required parameters are there
+
+ try:
+ request.check_required_parameters(
+ path = {
+ 'simulationId': 'int',
+ 'datacenterId': 'int',
+ 'roomId': 'int'
+ },
+ body = {
+ 'room': {
+ 'name': 'string',
+ 'roomType': 'string'
+ }
+ }
+ )
+
+ except exceptions.ParameterError as e:
+ return Response(400, e.message)
+
+ # Instantiate a Room from the database
+
+ room = Room.from_primary_key((request.params_path['roomId'],))
+
+ # Make sure this Room exists
+
+ if not room.exists():
+ return Response(404, '{} not found.'.format(room))
+
+ # Make sure this user is authorized to edit this Room
+
+ if not room.google_id_has_at_least(request.google_id, 'EDIT'):
+ return Response(403, 'Forbidden from updating {}.'.format(room))
+
+ # Update this Room
+
+ room.name = request.params_body['room']['name']
+ room.type = request.params_body['room']['roomType']
+
+ try:
+ room.update()
+ except exceptions.ForeignKeyError:
+ return Response(400, 'Invalid `roomType` or existing `name`.')
+
+ # Return this Room
+
+ return Response(
+ 200,
+ 'Successfully updated {}.'.format(room),
+ room.to_JSON()
+ )
+
+def DELETE(request):
+ """Delete this Room."""
+
+ # Make sure required parameters are there
+
+ try:
+ request.check_required_parameters(
+ path = {
+ 'simulationId': 'int',
+ 'datacenterId': 'int',
+ 'roomId': 'int'
+ }
+ )
+
+ except exceptions.ParameterError as e:
+ return Response(400, e.message)
+
+ # Instantiate a Room and make sure it exists
+
+ room = Room.from_primary_key((request.params_path['roomId'],))
+
+ if not room.exists():
+ return Response(404, '{} not found.'.format(room))
+
+ # Make sure this user is authorized to delete this Room
+
+ if not room.google_id_has_at_least(request.google_id, 'EDIT'):
+ return Response(403, 'Forbidden from deleting {}.'.format(room))
+
+ # Delete this Room
+
+ room.delete()
+
+ # Return this Room
+
+ return Response(
+ 200,
+ 'Sucessfully deleted {}.'.format(room),
+ room.to_JSON()
+ )
diff --git a/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/__init__.py b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/__init__.py
diff --git a/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/endpoint.py b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/endpoint.py
new file mode 100644
index 00000000..f615a6d4
--- /dev/null
+++ b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/endpoint.py
@@ -0,0 +1,123 @@
+from opendc.models.tile import Tile
+from opendc.models.room import Room
+from opendc.util import database, exceptions
+from opendc.util.rest import Response
+
+def GET(request):
+ """Get this Room's Tiles."""
+
+ # Make sure required parameters are there
+
+ try:
+ request.check_required_parameters(
+ path = {
+ 'simulationId': 'int',
+ 'datacenterId': 'int',
+ 'roomId': 'int'
+ }
+ )
+
+ except exceptions.ParameterError as e:
+ return Response(400, e.message)
+
+ # Instantiate a Room from the database
+
+ room = Room.from_primary_key((request.params_path['roomId'],))
+
+ # Make sure this Room exists
+
+ if not room.exists():
+ return Response(404, '{} not found.'.format(room))
+
+ # Make sure this user is authorized to view this Room's Tiles
+
+ if not room.google_id_has_at_least(request.google_id, 'VIEW'):
+ return Response(403, 'Forbidden from viewing Tiles for {}.'.format(room))
+
+ # Get and return the Tiles
+
+ tiles = Tile.query('room_id', room.id)
+
+ for tile in tiles:
+ tile.read()
+
+ return Response(
+ 200,
+ 'Successfully retrieved Tiles for {}.'.format(room),
+ [x.to_JSON() for x in tiles]
+ )
+
+def POST(request):
+ """Add a Tile."""
+
+ # Make sure required parameters are there
+
+ try:
+ request.check_required_parameters(
+ path = {
+ 'simulationId': 'int',
+ 'datacenterId': 'int',
+ 'roomId': 'int'
+ },
+ body = {
+ 'tile': {
+ 'roomId': 'int',
+ 'positionX': 'int',
+ 'positionY': 'int'
+ }
+ }
+ )
+
+ except exceptions.ParameterError as e:
+ return Response(400, e.message)
+
+ if request.params_path['roomId'] != request.params_body['tile']['roomId']:
+ return Response(400, 'ID mismatch')
+
+ # Instantiate a Room from the database
+
+ room = Room.from_primary_key((request.params_path['roomId'],))
+
+ # Make sure this Room exists
+
+ if not room.exists():
+ return Response(404, '{} not found.'.format(room))
+
+ # Make sure this user is authorized to edit this Room's Tiles
+
+ if not room.google_id_has_at_least(request.google_id, 'EDIT'):
+ return Response(403, 'Forbidden from adding Tiles to {}.'.format(room))
+
+ # Clean the tile JSON
+
+ tile_json = request.params_body['tile']
+
+ tile_json['objectId'] = None
+ tile_json['objectType'] = None
+
+ # Instantiate a Tile
+
+ tile = Tile.from_JSON(tile_json)
+
+ # Try to insert this Tile
+
+ try:
+ tile.insert()
+
+ except exceptions.ForeignKeyError as e:
+
+ if e.message == 'OccupiedTilePosition':
+ return Response(409, 'Tile position occupied.')
+
+ elif e.message == 'InvalidTilePosition':
+ return Response(400, 'Invalid Tile position (new Tiles must neighbor existing Tiles).')
+
+ # Return this Tile
+
+ tile.read()
+
+ return Response(
+ 200,
+ 'Successfully added {}.'.format(tile),
+ tile.to_JSON()
+ )
diff --git a/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/__init__.py b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/__init__.py
diff --git a/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/endpoint.py b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/endpoint.py
new file mode 100644
index 00000000..b15a10f7
--- /dev/null
+++ b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/endpoint.py
@@ -0,0 +1,89 @@
+from opendc.models.tile import Tile
+from opendc.util import database, exceptions
+from opendc.util.rest import Response
+
+def GET(request):
+ """Get this Tile."""
+
+ # Make sure request parameters are there
+
+ try:
+ request.check_required_parameters(
+ path = {
+ 'simulationId': 'int',
+ 'datacenterId': 'int',
+ 'roomId': 'int',
+ 'tileId': 'int'
+ }
+ )
+
+ except exceptions.ParameterError as e:
+ return Response(400, e.message)
+
+ # Instantiate a Tile from the database
+
+ tile = Tile.from_primary_key((request.params_path['tileId'],))
+
+ # Make sure this Tile exists
+
+ if not tile.exists():
+ return Response(404, '{} not found.'.format(tile))
+
+ # Make sure this user is authorized to view this Tile
+
+ if not tile.google_id_has_at_least(request.google_id, 'VIEW'):
+ return Response(403, 'Forbidden from retrieving {}.'.format(tile))
+
+ # Return this Tile
+
+ tile.read()
+
+ return Response(
+ 200,
+ 'Successfully retrieved {}.'.format(tile),
+ tile.to_JSON()
+ )
+
+def DELETE(request):
+ """Delete this Tile."""
+
+ # Make sure request parameters are there
+
+ try:
+ request.check_required_parameters(
+ path = {
+ 'simulationId': 'int',
+ 'datacenterId': 'int',
+ 'roomId': 'int',
+ 'tileId': 'int'
+ }
+ )
+
+ except exceptions.ParameterError as e:
+ return Response(400, e.message)
+
+ # Instantiate a Tile from the database
+
+ tile = Tile.from_primary_key((request.params_path['tileId'],))
+
+ # Make sure this Tile exists
+
+ if not tile.exists():
+ return Response(404, '{} not found.'.format(tile))
+
+ # Make sure this user is authorized to edit this Tile
+
+ if not tile.google_id_has_at_least(request.google_id, 'EDIT'):
+ return Response(403, 'Forbidden from deleting {}.'.format(tile))
+
+ # Delete this Tile
+
+ tile.delete()
+
+ # Return this Tile
+
+ return Response(
+ 200,
+ 'Successfully deleted {}.'.format(tile),
+ tile.to_JSON()
+ )
diff --git a/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/__init__.py b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/__init__.py
diff --git a/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/endpoint.py b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/endpoint.py
new file mode 100644
index 00000000..d9fb1959
--- /dev/null
+++ b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/endpoint.py
@@ -0,0 +1,245 @@
+from opendc.models.rack import Rack
+from opendc.models.tile import Tile
+from opendc.util import database, exceptions
+from opendc.util.rest import Response
+
+def GET(request):
+ """Get this Tile's Rack."""
+
+ # Make sure required parameters are there
+
+ try:
+ request.check_required_parameters(
+ path = {
+ 'simulationId': 'int',
+ 'datacenterId': 'int',
+ 'roomId': 'int',
+ 'tileId': 'int'
+ },
+ )
+
+ except exceptions.ParameterError as e:
+ return Response(400, e.message)
+
+ # Instantiate a Tile from the database
+
+ tile = Tile.from_primary_key((request.params_path['tileId'],))
+
+ # Make sure this Tile exists
+
+ if not tile.exists():
+ return Response(404, '{} not found.'.format(tile))
+
+ # Make sure this user is authorized to view this Tile
+
+ if not tile.google_id_has_at_least(request.google_id, 'VIEW'):
+ return Response(403, 'Forbidden from editing {}'.format(tile))
+
+ # Instantiate a Rack from the database
+
+ rack = Rack.from_primary_key((tile.object_id,))
+
+ # Make sure this Rack exists
+
+ if not rack.exists():
+ return Response(404, '{} not found'.format(rack))
+
+ # Return the Rack
+
+ rack.read()
+
+ return Response(
+ 200,
+ 'Successfully retrieved {}.'.format(rack),
+ rack.to_JSON()
+ )
+
+def POST(request):
+ """Add a Rack to this Tile if it is empty."""
+
+ # Make sure required parameters are there
+
+ try:
+ request.check_required_parameters(
+ path = {
+ 'simulationId': 'int',
+ 'datacenterId': 'int',
+ 'roomId': 'int',
+ 'tileId': 'int'
+ },
+ body = {
+ 'rack': {
+ 'name': 'string',
+ 'capacity': 'int',
+ 'powerCapacityW': 'int'
+ }
+ }
+ )
+
+ except exceptions.ParameterError as e:
+ return Response(400, e.message)
+
+ # Instantiate a Tile from the database
+
+ tile = Tile.from_primary_key((request.params_path['tileId'],))
+
+ # Make sure this Tile exists
+
+ if not tile.exists():
+ return Response(404, '{} not found.'.format(tile))
+
+ # Make sure this user is authorized to edit this Tile
+
+ if not tile.google_id_has_at_least(request.google_id, 'EDIT'):
+ return Response(403, 'Forbidden from editing {}'.format(tile))
+
+ # Make sure this Tile isn't occupied
+
+ if tile.object_id is not None:
+ return Response(409, '{} occupied.'.format(tile))
+
+ # Instantiate a Rack and insert it into the database
+
+ rack = Rack.from_JSON(request.params_body['rack'])
+ rack.insert()
+
+ # Try to add this Rack to this Tile
+
+ tile.object_id = rack.id
+ tile.object_type = 'RACK'
+ tile.update()
+
+ # Return this Rack
+
+ rack.read()
+
+ return Response(
+ 200,
+ 'Successfully added {}.'.format(rack),
+ rack.to_JSON()
+ )
+
+def PUT(request):
+ """Update the Rack on this Tile."""
+
+ # Make sure required parameters are there
+
+ try:
+ request.check_required_parameters(
+ path = {
+ 'simulationId': 'int',
+ 'datacenterId': 'int',
+ 'roomId': 'int',
+ 'tileId': 'int'
+ },
+ body = {
+ 'rack': {
+ 'name': 'string',
+ 'capacity': 'int',
+ 'powerCapacityW': 'int'
+ }
+ }
+ )
+
+ except exceptions.ParameterError as e:
+ return Response(400, e.message)
+
+ # Instantiate a Tile from the database
+
+ tile = Tile.from_primary_key((request.params_path['tileId'],))
+
+ # Make sure this Tile exists
+
+ if not tile.exists():
+ return Response(404, '{} not found.'.format(tile))
+
+ # Make sure this user is authorized to edit this Tile
+
+ if not tile.google_id_has_at_least(request.google_id, 'EDIT'):
+ return Response(403, 'Forbidden from editing {}'.format(tile))
+
+ # Instantiate a Rack from the database
+
+ rack = Rack.from_primary_key((tile.object_id,))
+
+ # Make sure this Rack exists
+
+ if not rack.exists():
+ return Response(404, '{} not found'.format(rack))
+
+ # Update this Rack
+
+ rack.name = request.params_body['rack']['name']
+ rack.capacity = request.params_body['rack']['capacity']
+
+ rack.update()
+
+ # Return this Rack
+
+ rack.read()
+
+ return Response(
+ 200,
+ 'Successfully updated {}.'.format(rack),
+ rack.to_JSON()
+ )
+
+def DELETE(request):
+ """Delete this Tile's Rack."""
+
+ # Make sure required parameters are there
+
+ try:
+ request.check_required_parameters(
+ path = {
+ 'simulationId': 'int',
+ 'datacenterId': 'int',
+ 'roomId': 'int',
+ 'tileId': 'int'
+ },
+ )
+
+ except exceptions.ParameterError as e:
+ return Response(400, e.message)
+
+ # Instantiate a Tile from the database
+
+ tile = Tile.from_primary_key((request.params_path['tileId'],))
+
+ # Make sure this Tile exists
+
+ if not tile.exists():
+ return Response(404, '{} not found.'.format(tile))
+
+ # Make sure this user is authorized to edit this Tile
+
+ if not tile.google_id_has_at_least(request.google_id, 'EDIT'):
+ return Response(403, 'Forbidden from editing {}'.format(tile))
+
+ # Instantiate a Rack from the database
+
+ rack = Rack.from_primary_key((tile.object_id,))
+
+ # Make sure this Rack exists
+
+ if not rack.exists():
+ return Response(404, '{} not found'.format(rack))
+
+ # Remove this Rack from this Tile
+
+ tile.object_id = None
+ tile.object_type = None
+
+ tile.update()
+
+ # Delete this Rack
+
+ rack.delete()
+
+ # Return this Rack
+
+ return Response(
+ 200,
+ 'Successfully deleted {}.'.format(rack),
+ rack.to_JSON()
+ )
diff --git a/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/machines/__init__.py b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/machines/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/machines/__init__.py
diff --git a/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/machines/endpoint.py b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/machines/endpoint.py
new file mode 100644
index 00000000..a67bb1c3
--- /dev/null
+++ b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/machines/endpoint.py
@@ -0,0 +1,122 @@
+from opendc.models.machine import Machine
+from opendc.models.rack import Rack
+from opendc.util import database, exceptions
+from opendc.util.rest import Response
+
+def GET(request):
+ """Get this Rack's Machines."""
+
+ # Make sure required parameters are there
+
+ try:
+ request.check_required_parameters(
+ path = {
+ 'simulationId': 'int',
+ 'datacenterId': 'int',
+ 'roomId': 'int',
+ 'tileId': 'int'
+ }
+ )
+
+ except exceptions.ParameterError as e:
+ return Response(400, e.message)
+
+ # Instantiate a Rack from the database
+
+ rack = Rack.from_tile_id(request.params_path['tileId'])
+
+ # Make sure this Rack exists
+
+ if not rack.exists():
+ return Response(404, '{} not found.'.format(rack))
+
+ # Make sure this user is authorized to view this Rack's Machines
+
+ if not rack.google_id_has_at_least(request.google_id, 'VIEW'):
+ return Response(403, 'Forbidden from viewing {}.'.format(rack))
+
+ # Get and return the Machines
+
+ machines = Machine.query('rack_id', rack.id)
+
+ for machine in machines:
+ machine.read()
+
+ return Response(
+ 200,
+ 'Successfully retrieved Machines for {}.'.format(rack),
+ [x.to_JSON() for x in machines]
+ )
+
+def POST(request):
+ """Add a Machine to this rack."""
+
+ # Make sure required parameters are there
+
+ try:
+ request.check_required_parameters(
+ path = {
+ 'simulationId': 'int',
+ 'datacenterId': 'int',
+ 'roomId': 'int',
+ 'tileId': 'int'
+ },
+ body = {
+ 'machine': {
+ 'rackId': 'int',
+ 'position': 'int',
+ 'tags': 'list-string',
+ 'cpuIds': 'list-int',
+ 'gpuIds': 'list-int',
+ 'memoryIds': 'list-int',
+ 'storageIds': 'list-int'
+ }
+ }
+ )
+
+ except exceptions.ParameterError as e:
+ return Response(400, e.message)
+
+ # Instantiate a Rack from the database
+
+ rack = Rack.from_tile_id(request.params_path['tileId'])
+
+ # Make sure this Rack exists
+
+ if not rack.exists():
+ return Response(404, '{} not found.'.format(rack))
+
+ # Make sure this Rack's ID matches the given rack ID
+
+ if rack.id != request.params_body['machine']['rackId']:
+ return Response(400, 'Rack ID in `machine` and path do not match.')
+
+ # Make sure this user is authorized to edit this Rack's Machines
+
+ if not rack.google_id_has_at_least(request.google_id, 'VIEW'):
+ return Response(403, 'Forbidden from viewing {}.'.format(rack))
+
+ # Instantiate a Machine
+
+ machine = Machine.from_JSON(request.params_body['machine'])
+
+ # Try to insert this Machine
+
+ try:
+ machine.insert()
+
+ except exceptions.ForeignKeyError:
+ return Response(409, 'Rack position occupied.')
+
+ except:
+ return Response(400, 'Invalid Machine.')
+
+ # Return this Machine
+
+ machine.read()
+
+ return Response(
+ 200,
+ 'Successfully added {}.'.format(machine),
+ machine.to_JSON()
+ )
diff --git a/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/machines/position/__init__.py b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/machines/position/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/machines/position/__init__.py
diff --git a/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/machines/position/endpoint.py b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/machines/position/endpoint.py
new file mode 100644
index 00000000..e33da5d6
--- /dev/null
+++ b/opendc/api/v1/simulations/simulationId/datacenters/datacenterId/rooms/roomId/tiles/tileId/rack/machines/position/endpoint.py
@@ -0,0 +1,170 @@
+from opendc.models.machine import Machine
+from opendc.models.rack import Rack
+from opendc.util import database, exceptions
+from opendc.util.rest import Response
+
+def GET(request):
+ """Get the Machine at this location in this Rack."""
+
+ # Make sure required parameters are there
+
+ try:
+ request.check_required_parameters(
+ path = {
+ 'simulationId': 'int',
+ 'datacenterId': 'int',
+ 'roomId': 'int',
+ 'tileId': 'int',
+ 'position': 'int'
+ }
+ )
+
+ except exceptions.ParameterError as e:
+ return Response(400, e.message)
+
+ # Instantiate a Machine from the database
+
+ machine = Machine.from_tile_id_and_rack_position(request.params_path['tileId'], request.params_path['position'])
+
+ # Make sure this Machine exists
+
+ if not machine.exists():
+ return Response(404, '{} not found.'.format(machine))
+
+ # Make sure this user is authorized to view this Machine
+
+ if not machine.google_id_has_at_least(request.google_id, 'VIEW'):
+ return Response(403, 'Forbidden from retrieving {}.'.format(machine))
+
+ # Return this Machine
+
+ machine.read()
+
+ return Response(
+ 200,
+ 'Successfully retrieved {}.'.format(machine),
+ machine.to_JSON()
+ )
+
+def PUT(request):
+ """Update the Machine at this location in this Rack."""
+
+ try:
+ request.check_required_parameters(
+ path = {
+ 'simulationId': 'int',
+ 'datacenterId': 'int',
+ 'roomId': 'int',
+ 'tileId': 'int',
+ 'position': 'int'
+ },
+ body = {
+ 'machine': {
+ 'rackId': 'int',
+ 'position': 'int',
+ 'tags': 'list-string',
+ 'cpuIds': 'list-int',
+ 'gpuIds': 'list-int',
+ 'memoryIds': 'list-int',
+ 'storageIds': 'list-int'
+ }
+ }
+ )
+
+ except exceptions.ParameterError as e:
+ return Response(400, e.message)
+
+ # Instantiate a Machine from the database
+
+ machine = Machine.from_tile_id_and_rack_position(request.params_path['tileId'], request.params_path['position'])
+
+ # Make sure this Machine exists
+
+ if not machine.exists():
+ return Response(404, '{} not found.'.format(machine))
+
+ # Make sure this Machine's rack ID is right
+
+ rack = Rack.from_tile_id(request.params_path['tileId'])
+
+ if not rack.exists() or rack.id != request.params_body['machine']['rackId']:
+ return Response(400, 'Mismatch in Rack IDs.')
+
+ # Make sure this user is authorized to edit this Machine
+
+ if not machine.google_id_has_at_least(request.google_id, 'EDIT'):
+ return Response(403, 'Forbidden from retrieving {}.'.format(machine))
+
+ # Update this Machine
+
+ machine.positoin = request.params_body['machine']['position']
+ machine.tags = request.params_body['machine']['tags']
+ machine.cpu_ids = request.params_body['machine']['cpuIds']
+ machine.gpu_ids = request.params_body['machine']['gpuIds']
+ machine.memory_ids = request.params_body['machine']['memoryIds']
+ machine.storage_ids = request.params_body['machine']['storageIds']
+
+ try:
+ machine.update()
+
+ except exceptions.ForeignKeyError:
+ return Response(409, 'Rack position occupied.')
+
+ except Exception as e:
+ print e
+ return Response(400, 'Invalid Machine.')
+
+ # Return this Machine
+
+ machine.read()
+
+ return Response(
+ 200,
+ 'Successfully updated {}.'.format(machine),
+ machine.to_JSON()
+ )
+
+def DELETE(request):
+ """Delete the Machine at this location in this Rack."""
+
+ # Make sure required parameters are there
+
+ try:
+ request.check_required_parameters(
+ path = {
+ 'simulationId': 'int',
+ 'datacenterId': 'int',
+ 'roomId': 'int',
+ 'tileId': 'int',
+ 'position': 'int'
+ }
+ )
+
+ except exceptions.ParameterError as e:
+ return Response(400, e.message)
+
+ # Instantiate a Machine from the database
+
+ machine = Machine.from_tile_id_and_rack_position(request.params_path['tileId'], request.params_path['position'])
+
+ # Make sure this Machine exists
+
+ if not machine.exists():
+ return Response(404, '{} not found.'.format(machine))
+
+ # Make sure this user is authorized to edit this Machine
+
+ if not machine.google_id_has_at_least(request.google_id, 'EDIT'):
+ return Response(403, 'Forbidden from retrieving {}.'.format(machine))
+
+ # Delete this Machine
+
+ machine.delete()
+
+ # Return this Machine
+
+ return Response(
+ 200,
+ 'Successfully deleted {}.'.format(machine),
+ machine.to_JSON()
+ )
diff --git a/opendc/api/v1/simulations/simulationId/datacenters/endpoint.py b/opendc/api/v1/simulations/simulationId/datacenters/endpoint.py
new file mode 100644
index 00000000..d53cd1c8
--- /dev/null
+++ b/opendc/api/v1/simulations/simulationId/datacenters/endpoint.py
@@ -0,0 +1,60 @@
+from opendc.models.datacenter import Datacenter
+from opendc.models.simulation import Simulation
+from opendc.util import database, exceptions
+from opendc.util.rest import Response
+
+def POST(request):
+ """Add a new Datacenter to this Simulation."""
+
+ # Make sure required parameters are there
+
+ try:
+ request.check_required_parameters(
+ path = {
+ 'simulationId': 'int'
+ },
+ body = {
+ 'datacenter': {
+ 'starred': 'int',
+ 'simulationId': 'int'
+ }
+ }
+ )
+
+ except exceptions.ParameterError as e:
+ return Response(400, e.message)
+
+ # Make sure the passed object's simulation id matches the path simulation id
+
+ if request.params_path['simulationId'] != request.params_body['datacenter']['simulationId']:
+ return Response(400, 'ID mismatch.')
+
+ # Instantiate a Simulation from the database
+
+ simulation = Simulation.from_primary_key((request.params_path['simulationId'],))
+
+ # Make sure this Simulation exists
+
+ if not simulation.exists():
+ return Response(404, '{} not found.'.format(simulation))
+
+ # Make sure this user is authorized to edit this Simulation's Databases
+
+ if not simulation.google_id_has_at_least(request.google_id, 'EDIT'):
+ return Response(403, 'Forbidden from adding a datacenter to {}.'.format(simulation))
+
+ # Instantiate a Datacenter
+
+ datacenter = Datacenter.from_JSON(request.params_body['datacenter'])
+
+ datacenter.insert()
+
+ # return this Datacenter
+
+ datacenter.read()
+
+ return Response(
+ 200,
+ 'Successfully added {}.'.format(datacenter),
+ datacenter.to_JSON()
+ )