# Copyright 2013 Locaweb.
# All Rights Reserved.
#
# 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.
#
# @author: Francisco Freire, Locaweb.
# @author: Thiago Morello (morellon), Locaweb.
# @author: Willian Molinari (PotHix), Locaweb.
# @author: Juliano Martinez (ncode), Locaweb.
from gevent import monkey
monkey.patch_all()
import os
import grp
import pwd
import json
import time
import base64
import logging
import bottle
from bottle import delete, put, get, post, error, redirect, run, debug
from bottle import abort, request, ServerAdapter, response, static_file
from bottle import error, HTTPError
from simplestack.common.config import config
from simplestack.common.logger import set_logger
app = bottle.app()
LOG = logging.getLogger('simplestack.server')
@error(500)
def custom500(error):
response.content_type = "application/json"
traceback = None
if type(error) is HTTPError:
traceback = error.traceback
error = error.exception
error_class = error.__class__.__name__
LOG.error("%s: %s", error_class, error)
return json.dumps({
"error": error_class,
"message": str(error),
"traceback": traceback
})
@error(404)
def error404(error):
response.content_type = 'application/json'
return json.dumps({
"error": error_class,
"message": error.output
})
@get('/:hypervisor/:host')
[docs]def pool_info(hypervisor, host):
"""
Get pool information
::
GET /:hypervisor/:host
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
pool_info = manager.pool_info()
manager.logout()
return json.dumps(pool_info)
@get('/:hypervisor/:host/hosts')
[docs]def host_list(hypervisor, host):
"""
Get hosts for a given pool
::
GET /:hypervisor/:host/hosts
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
host_list = manager.host_list()
manager.logout()
return json.dumps(host_list)
@get('/:hypervisor/:host/hosts/:host_id')
[docs]def host_info(hypervisor, host, host_id):
"""
Get host info
::
GET /:hypervisor/:host/hosts/:host_id
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
host_info = manager.host_info(host_id)
manager.logout()
return json.dumps(host_info)
@get('/:hypervisor/:host/storages')
[docs]def storage_list(hypervisor, host):
"""
Get storages for a given pool
::
GET /:hypervisor/:host/storages
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
storage_list = manager.storage_list()
manager.logout()
return json.dumps(storage_list)
@get('/:hypervisor/:host/storages/:storage_id')
[docs]def storage_info(hypervisor, host, storage_id):
"""
Get storage info
::
GET /:hypervisor/:host/storages/:storage_id
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
storage_info = manager.storage_info(storage_id)
manager.logout()
return json.dumps(storage_info)
@post('/:hypervisor/:host/storages/:storage_id/guests')
[docs]def storage_guest_import(hypervisor, host, storage_id):
"""
Import a new guest
::
POST /:hypervisor/:host/storages/:storage_id/guests
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
guest = manager.guest_import(
request.environ['wsgi.input'],
request.content_length,
storage_id
)
location = "/%s/%s/guests/%s" % (hypervisor, host, guest["id"])
response.set_header("Location", location)
manager.logout()
return json.dumps(guest)
@get('/:hypervisor/:host/guests')
[docs]def guest_list(hypervisor, host):
"""
Get guests for a given pool
::
GET /:hypervisor/:host/guests
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
guest_list = manager.guest_list()
manager.logout()
return json.dumps(guest_list)
@post('/:hypervisor/:host/guests')
[docs]def guest_create(hypervisor, host):
"""
Create a new guest
::
POST /:hypervisor/:host/guests
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
data = request.body.readline()
if not data:
abort(400, 'No data received')
data = json.loads(data)
guest = manager.guest_create(data)
location = "/%s/%s/guests/%s" % (hypervisor, host, guest["id"])
response.set_header("Location", location)
manager.logout()
return json.dumps(guest)
@post('/:hypervisor/:host/guests')
[docs]def guest_import(hypervisor, host):
"""
Import a new guest
::
POST /:hypervisor/:host/guests
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
guest = manager.guest_import(
request.environ['wsgi.input'],
request.content_length
)
location = "/%s/%s/guests/%s" % (hypervisor, host, guest["id"])
response.set_header("Location", location)
manager.logout()
return json.dumps(guest)
@get('/:hypervisor/:host/guests/:guest_id')
[docs]def guest_info(hypervisor, host, guest_id):
"""
Get guest informations
::
GET /:hypervisor/:host/guests/:guest_id
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
guest_id = manager.guest_info(guest_id)
manager.logout()
return json.dumps(guest_id)
@put('/:hypervisor/:host/guests/:guest_id')
[docs]def guest_update(hypervisor, host, guest_id):
"""
Update guest informations
::
PUT /:hypervisor/:host
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
data = request.body.readline()
if not data:
abort(400, 'No data received')
data = json.loads(data)
guest_data = manager.guest_update(guest_id, data)
manager.logout()
return json.dumps(guest_data)
@post('/:hypervisor/:host/guests/:guest_id/clone')
[docs]def guest_clone(hypervisor, host, guest_id):
"""
Clone a guest
::
POST /:hypervisor/:host/guests/:guest_id/clone
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
data = request.body.readline()
if not data:
abort(400, 'No data received')
data = json.loads(data)
guest = manager.guest_clone(guest_id, data)
location = "/%s/%s/guests/%s" % (hypervisor, host, guest["id"])
response.set_header("Location", location)
manager.logout()
return json.dumps(guest)
@get('/:hypervisor/:host/guests/:guest_id/export')
[docs]def guest_export(hypervisor, host, guest_id):
"""
Export guest file
::
GET /:hypervisor/:host/guests/:guest_id/export
"""
response.content_type = "application/octet-stream"
manager = create_manager(hypervisor, host)
export_response, export_length = manager.guest_export(guest_id)
response_part = export_response.read(1024)
while response_part:
yield response_part
response_part = export_response.read(1024)
manager.logout()
@delete('/:hypervisor/:host/guests/:guest_id')
[docs]def guest_delete(hypervisor, host, guest_id):
"""
Deletes guest
::
DELETE /:hypervisor/:host/guests/:guest_id
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
guest_delete = manager.guest_delete(guest_id)
manager.logout()
return json.dumps(guest_delete)
@get('/:hypervisor/:host/guests/:guest_id/disks')
[docs]def disk_list(hypervisor, host, guest_id):
"""
Get all disks for a given guest
::
GET /:hypervisor/:host/guests/:guest_id/disks
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
disk_list = manager.disk_list(guest_id)
manager.logout()
return json.dumps(disk_list)
@post('/:hypervisor/:host/guests/:guest_id/disks')
[docs]def disk_create(hypervisor, host, guest_id):
"""
Create a disk for a given guest
::
POST /:hypervisor/:host/guests/:guest_id/disks
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
data = request.body.readline()
if not data:
abort(400, 'No data received')
data = json.loads(data)
disk = manager.disk_create(guest_id, data)
location = "/%s/%s/guests/%s/disks/%s" % (
hypervisor, host, guest_id, disk["id"]
)
response.set_header("Location", location)
manager.logout()
return json.dumps(disk)
@get('/:hypervisor/:host/guests/:guest_id/disks/:disk_id')
[docs]def disk_info(hypervisor, host, guest_id, disk_id):
"""
Get a disk in a given guest
::
GET /:hypervisor/:host/guests/:guest_id/disks/:disk_id
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
nwi_info = manager.disk_info(guest_id, disk_id)
manager.logout()
return json.dumps(nwi_info)
@put('/:hypervisor/:host/guests/:guest_id/disks/:disk_id')
[docs]def disk_update(hypervisor, host, guest_id, disk_id):
"""
Update a disk in a given guest
::
PUT /:hypervisor/:host/guests/:guest_id/disks/:disk_id
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
data = request.body.readline()
if not data:
abort(400, 'No data received')
data = json.loads(data)
nwi_info = manager.disk_update(guest_id, disk_id, data)
manager.logout()
return json.dumps(nwi_info)
@delete('/:hypervisor/:host/guests/:guest_id/disks/:disk_id')
[docs]def disk_delete(hypervisor, host, guest_id, disk_id):
"""
Delete a disk from a given guest
::
DELETE /:hypervisor/:host/guests/:guest_id/disks/:disk_id
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
manager.disk_delete(guest_id, disk_id)
manager.logout()
@put('/:hypervisor/:host/guests/:guest_id/media_device')
@get('/:hypervisor/:host/guests/:guest_id/media_device')
@get('/:hypervisor/:host/networks')
[docs]def network_list(hypervisor, host):
"""
Get networks for a given pool
::
GET /:hypervisor/:host/networks
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
network_list = manager.network_list()
manager.logout()
return json.dumps(network_list)
@post('/:hypervisor/:host/networks')
[docs]def network_vlan_create(hypervisor, host):
"""
Create a Network with a tagged VLAN
::
POST /:hypervisor/:host/networks
The body should contain a JSON object. The required keys should vary for
each hypervisor.
Xen example:
{"name": "VLAN2",
"description": "VLAN 2 storage",
"from_network": "BOND1",
"vlan": 2,
"other_config": {}}
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
data = request.body.readline()
if not data:
abort(400, 'No data received')
data = json.loads(data)
network_ref = manager.network_vlan_create(data["name"], data["description"], data["from_network"], data["vlan"], data["other_config"])
location = "/%s/%s/networks/%s" % (
hypervisor, host, network_ref
)
response.set_header("Location", location)
manager.logout()
return json.dumps(network_ref)
@get('/:hypervisor/:host/networks/:network')
[docs]def network_info(hypervisor, host, network):
"""
Get network info
::
GET /:hypervisor/:host/networks/:network
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
network_info = manager.network_info(network)
manager.logout()
return json.dumps(network_info)
@get('/:hypervisor/:host/guests/:guest_id/network_interfaces')
[docs]def network_interface_list(hypervisor, host, guest_id):
"""
Get all network interfaces for a given guest
::
GET /:hypervisor/:host/guests/:guest_id/network_interfaces
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
netiface_list = manager.network_interface_list(guest_id)
manager.logout()
return json.dumps(netiface_list)
@post('/:hypervisor/:host/guests/:guest_id/network_interfaces')
[docs]def network_interface_create(hypervisor, host, guest_id):
"""
Create a network interface for a given guest
::
POST /:hypervisor/:host/guests/:guest_id/network_interfaces
The body should contain a JSON object. The required keys should vary for
each hypervisor.
Xen example:
{"network": "THE NETWORK NAME"}
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
data = request.body.readline()
if not data:
abort(400, 'No data received')
data = json.loads(data)
network_interface = manager.network_interface_create(guest_id, data)
location = "/%s/%s/guests/%s/network_interfaces/%s" % (
hypervisor, host, guest_id, network_interface["id"]
)
response.set_header("Location", location)
manager.logout()
return json.dumps(network_interface)
@get('/:hypervisor/:host/guests/:guest_id/network_interfaces/:interface_id')
[docs]def network_interface_info(hypervisor, host, guest_id, interface_id):
"""
Get a network interface in a given guest
::
GET /:hypervisor/:host/guests/:guest_id/network_interfaces/:interface_id
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
nwi_info = manager.network_interface_info(guest_id, interface_id)
manager.logout()
return json.dumps(nwi_info)
@put('/:hypervisor/:host/guests/:guest_id/network_interfaces/:interface_id')
[docs]def network_interface_update(hypervisor, host, guest_id, interface_id):
"""
Update a network interface in a given guest
::
PUT /:hypervisor/:host/guests/:guest_id/network_interfaces/:interface_id
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
data = request.body.readline()
if not data:
abort(400, 'No data received')
data = json.loads(data)
nwi_info = manager.network_interface_update(guest_id, interface_id, data)
manager.logout()
return json.dumps(nwi_info)
@delete('/:hypervisor/:host/guests/:guest_id/network_interfaces/:interface_id')
[docs]def network_interface_delete(hypervisor, host, guest_id, interface_id):
"""
Delete a network interface from a given guest
::
DELETE /:hypervisor/:host/guests/:guest_id/network_interfaces/:if_id
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
manager.network_interface_delete(guest_id, interface_id)
manager.logout()
@get('/:hypervisor/:host/guests/:guest_id/tags')
[docs]def tag_list(hypervisor, host, guest_id):
"""
Get all tags for a given guest
::
GET /:hypervisor/:host/guests/:guest_id/tags
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
tag_list = manager.tag_list(guest_id)
manager.logout()
return json.dumps(tag_list)
@post('/:hypervisor/:host/guests/:guest_id/tags')
[docs]def tag_create(hypervisor, host, guest_id):
"""
Create a new tag for a given guest
::
POST /:hypervisor/:host/guests/:guest_id/tags
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
data = request.body.readline()
if not data:
abort(400, 'No data received')
data = json.loads(data)
tag = manager.tag_create(guest_id, data.get('name'))
#TODO: Should we return the Location for the first tag?
location = "/%s/%s/guests/%s/tags/%s" % (
hypervisor, host, guest_id, tag[0]
)
response.set_header("Location", location)
manager.logout()
return json.dumps(tag)
@delete('/:hypervisor/:host/guests/:guest_id/tags/:tag_name')
[docs]def tag_delete(hypervisor, host, guest_id, tag_name):
"""
Delete a given tag for a given guest
::
DELETE /:hypervisor/:host/guests/:guest_id/tags
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
tag_delete = manager.tag_delete(guest_id, tag_name)
manager.logout()
return json.dumps(tag_delete)
@get('/:hypervisor/:host/guests/:guest_id/snapshots')
[docs]def snapshot_list(hypervisor, host, guest_id):
"""
Get all snapshots for a given guest
::
GET /:hypervisor/:host/guests/:guest_id/snapshots
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
snapshot_list = manager.snapshot_list(guest_id)
manager.logout()
return json.dumps(snapshot_list)
@post('/:hypervisor/:host/guests/:guest_id/snapshots')
[docs]def snapshot_create(hypervisor, host, guest_id):
"""
Create a snapshot for a given guest
::
POST /:hypervisor/:host/guests/:guest_id/snapshots
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
data = request.body.readline()
if not data:
abort(400, 'No data received')
data = json.loads(data)
snapshot = manager.snapshot_create(guest_id, data.get('name'))
location = "/%s/%s/guests/%s/snapshots/%s" % (
hypervisor, host, guest_id, snapshot["id"]
)
response.set_header("Location", location)
manager.logout()
return json.dumps(snapshot)
@get('/:hypervisor/:host/guests/:guest_id/snapshots/:snapshot_id')
[docs]def snapshot_info(hypervisor, host, guest_id, snapshot_id):
"""
Get snapshot informations for a given guest_id and snapshot_id
::
GET /:hypervisor/:host/guests/:guest_id/snapshots/:snapshot_id
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
snapshot_info = manager.snapshot_info(guest_id, snapshot_id)
manager.logout()
return json.dumps(snapshot_info)
@put('/:hypervisor/:host/guests/:guest_id/snapshots/:snapshot_id/revert')
[docs]def snapshot_revert(hypervisor, host, guest_id, snapshot_id):
"""
Remove a snapshot for a given guest_id and snapshot_id
::
PUT /:hypervisor/:host/guests/:guest_id/snapshots/:snapshot_id/revert
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
snapshot_revert = manager.snapshot_revert(guest_id, snapshot_id)
manager.logout()
return json.dumps(snapshot_revert)
@delete('/:hypervisor/:host/guests/:guest_id/snapshots/:snapshot_id')
[docs]def snapshot_delete(hypervisor, host, guest_id, snapshot_id):
"""
Remove a snapshot for a given guest_id and snapshot_id
::
DELETE /:hypervisor/:host/guests/:guest_id/snapshots/:snapshot_id
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
snapshot_delete = manager.snapshot_delete(guest_id, snapshot_id)
manager.logout()
return json.dumps(snapshot_delete)
@put('/:hypervisor/:host/guests/:guest_id/reboot')
[docs]def reboot_guest(hypervisor, host, guest_id):
"""
Reboot a guest based on the given guest_id
::
PUT /:hypervisor/:host/guests/:guest_id/reboot
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
data = request.body.readline()
force = False
if data:
data = json.loads(data)
force = data.get('force')
manager.guest_reboot(guest_id, force=force)
manager.logout()
return json.dumps({"action": "reboot", "message": "ok"})
@put('/:hypervisor/:host/guests/:guest_id/power')
[docs]def power_guest(hypervisor, host, guest_id):
"""
Turn a guest on/off based on a given guest_id
::
PUT /:hypervisor/:host/guests/:guest_id/power
"""
response.content_type = "application/json"
manager = create_manager(hypervisor, host)
data = request.body.readline()
if not data:
abort(400, 'No data received')
data = json.loads(data)
state = data['state']
if state == "force_stop":
manager.guest_shutdown(guest_id, force=True)
elif state == "start":
manager.guest_start(guest_id)
elif state == "stop":
manager.guest_shutdown(guest_id, force=False)
elif state == "pause":
manager.guest_suspend(guest_id)
elif state == "resume":
manager.guest_resume(guest_id)
manager.logout()
return json.dumps({"action": state, "message": "ok"})
def parse_token(token):
decoded_token = base64.b64decode(token).split(':')
username = decoded_token.pop(0)
password = ':'.join(decoded_token)
return (username, password)
def create_manager(hypervisor, host):
hypervisor_token = request.headers.get("x-simplestack-hypervisor-token")
if not hypervisor_token:
abort(401, 'No x-simplestack-hypervisor-token header provided')
username, password = parse_token(hypervisor_token)
module = __import__("simplestack.hypervisors.%s" % hypervisor)
module = getattr(module.hypervisors, hypervisor)
return module.Stack({
"api_server": host,
"username": username,
"password": password
})
def main(action):
if not action == "foreground":
os.setgid(grp.getgrnam('nogroup')[2])
os.setuid(pwd.getpwnam(config.get("server", "user"))[2])
debug(config.getboolean("server", "debug"))
port = config.getint("server", "port")
bind_addr = config.get("server", "bind_addr")
set_logger()
LOG.info("Starting Simplestack server")
run(host=bind_addr, port=port, server="gevent")