ooi/ooi/tests/fakes.py

741 lines
24 KiB
Python

# Copyright 2015 Spanish National Research Council
# Copyright 2015 LIP - INDIGO-DataCloud
#
# 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.
import json
import re
import uuid
import webob.dec
import webob.exc
from ooi import utils
import ooi.wsgi
application_url = "https://foo.example.org:8774/ooiv1"
tenants = {
"foo": {"id": uuid.uuid4().hex,
"name": "foo"},
"bar": {"id": uuid.uuid4().hex,
"name": "bar"},
"baz": {"id": uuid.uuid4().hex,
"name": "baz"},
}
flavors = {
1: {
"id": 1,
"name": "foo",
"vcpus": 2,
"ram": 256,
"disk": 10,
},
2: {
"id": 2,
"name": "bar",
"vcpus": 4,
"ram": 2014,
"disk": 20,
}
}
images = {
"foo": {
"id": "foo",
"name": "foo",
},
"bar": {
"id": "bar",
"name": "bar",
}
}
volumes = {
tenants["foo"]["id"]: [
{
"id": uuid.uuid4().hex,
"displayName": "foo",
"size": 2,
"status": "available",
"attachments": [],
},
{
"id": uuid.uuid4().hex,
"displayName": "bar",
"size": 3,
"status": "available",
"attachments": [],
},
{
"id": uuid.uuid4().hex,
"displayName": "baz",
"size": 5,
"status": "available",
"attachments": [],
},
],
tenants["bar"]["id"]: [],
tenants["baz"]["id"]: [
{
"id": uuid.uuid4().hex,
"displayName": "volume",
"size": 5,
"status": "in-use",
},
{
"id": uuid.uuid4().hex,
"displayName": "volume-nodev",
"size": 6,
"status": "in-use",
},
],
}
pools = {
tenants["foo"]["id"]: [
{
"id": "foo",
"name": "foo",
},
{
"id": "bar",
"name": "bar",
}
],
tenants["bar"]["id"]: [],
tenants["baz"]["id"]: [
{
"id": "public",
"name": "public",
},
],
}
linked_vm_id = uuid.uuid4().hex
allocated_ip = "192.168.253.23"
floating_ips = {
tenants["foo"]["id"]: [],
tenants["bar"]["id"]: [],
tenants["baz"]["id"]: [
{
"fixed_ip": "10.0.0.2",
"id": uuid.uuid4().hex,
"instance_id": linked_vm_id,
"ip": "192.168.253.1",
"pool": pools[tenants["baz"]["id"]][0]["name"],
},
{
"fixed_ip": None,
"id": uuid.uuid4().hex,
"instance_id": None,
"ip": "192.168.253.2",
"pool": pools[tenants["baz"]["id"]][0]["name"],
},
],
}
networks = {
tenants["foo"]["id"]: [],
tenants["bar"]["id"]: [],
tenants["baz"]["id"]: [
{"id": uuid.uuid4().hex},
{"id": uuid.uuid4().hex}
]
}
ports = {
tenants["foo"]["id"]: [
{
"port_id": uuid.uuid4().hex,
"fixed_ips":
[{"ip_address": uuid.uuid4().hex}],
"mac_addr": uuid.uuid4().hex,
"port_state": "DOWN",
"net_id": uuid.uuid4().hex,
"server_id": linked_vm_id
},
],
tenants["bar"]["id"]: [],
tenants["baz"]["id"]: [
{
"port_id": uuid.uuid4().hex,
"fixed_ips": [
{"ip_address": "192.168.253.1"}
],
"mac_addr": uuid.uuid4().hex,
"port_state": "ACTIVE",
"net_id": uuid.uuid4().hex,
"server_id": linked_vm_id
},
],
}
servers = {
tenants["foo"]["id"]: [
{
"id": uuid.uuid4().hex,
"name": "foo",
"flavor": {"id": flavors[1]["id"]},
"image": {"id": images["foo"]["id"]},
"status": "ACTIVE",
"security_groups":[
{"name": "group1"},
{"name": "group2"}
]
},
{
"id": uuid.uuid4().hex,
"name": "bar",
"flavor": {"id": flavors[2]["id"]},
"image": {"id": images["bar"]["id"]},
"status": "SHUTOFF",
"security_groups":[
{"name": "group1"}
]
},
{
"id": uuid.uuid4().hex,
"name": "baz",
"flavor": {"id": flavors[1]["id"]},
"image": {"id": images["bar"]["id"]},
"status": "ERROR",
"security_groups":[
{"name": "group2"}
]
},
],
tenants["bar"]["id"]: [],
tenants["baz"]["id"]: [
{
"id": linked_vm_id,
"name": "withvolume",
"flavor": {"id": flavors[1]["id"]},
"image": {"id": images["bar"]["id"]},
"status": "ACTIVE",
"os-extended-volumes:volumes_attached": [
{"id": volumes[tenants["baz"]["id"]][0]["id"]},
{"id": volumes[tenants["baz"]["id"]][1]["id"]}
],
"addresses": {
"private": [
{"addr": (
(ports[tenants["baz"]["id"]]
[0]["fixed_ips"][0]["ip_address"])
),
"OS-EXT-IPS:type": "fixed",
"OS-EXT-IPS-MAC:mac_addr": (
ports[tenants["baz"]["id"]][0]["mac_addr"]
)
},
{"addr": floating_ips[tenants["baz"]["id"]][0]["ip"],
"OS-EXT-IPS:type": "floating",
"OS-EXT-IPS-MAC:mac_addr": "1234"},
]
},
"security_groups":[
{"name": "group1"}
]
}
],
}
# avoid circular definition of attachments
volumes[tenants["baz"]["id"]][0]["attachments"] = [{
# how consistent can OpenStack be!
# depending on using /servers/os-volume_attachments
# or /os-volumes it will return different field names
"server_id": servers[tenants["baz"]["id"]][0]["id"],
"serverId": servers[tenants["baz"]["id"]][0]["id"],
"attachment_id": uuid.uuid4().hex,
"volumeId": volumes[tenants["baz"]["id"]][0]["id"],
"volume_id": volumes[tenants["baz"]["id"]][0]["id"],
"device": "/dev/vdb",
"id": volumes[tenants["baz"]["id"]][0]["id"],
}]
volumes[tenants["baz"]["id"]][1]["attachments"] = [{
"server_id": servers[tenants["baz"]["id"]][0]["id"],
"serverId": servers[tenants["baz"]["id"]][0]["id"],
"attachment_id": uuid.uuid4().hex,
"volumeId": volumes[tenants["baz"]["id"]][1]["id"],
"volume_id": volumes[tenants["baz"]["id"]][1]["id"],
"id": volumes[tenants["baz"]["id"]][0]["id"],
}]
security_groups = {
tenants["foo"]["id"]: [],
tenants["baz"]["id"]: [
{
"name": "group1",
"id": uuid.uuid4().hex,
"description": "group one",
"rules": [
{"from_port": 443,
"to_port": 443, "ip_range": {"cidr": "10.0.0.0/32"},
"ip_protocol": "tcp"},
{"from_port": "1000",
"to_port": 2000, "ip_range": {"cidr": "11.0.0.0/32"},
"ip_protocol": "udp"},
]
},
{
"name": "group2",
"id": uuid.uuid4().hex,
"description": "group two",
"rules": [
{"from_port": 80,
"to_port": 80, "ip_range": {"cidr": "10.0.0.0/32"},
"ip_protocol": "tcp"},
{"from_port": "4000",
"to_port": 7000, "ip_range": {"cidr": "13.0.0.0/32"},
"ip_protocol": "udp"},
]
}
],
tenants["bar"]["id"]: [
{
"name": "group3",
"id": uuid.uuid4().hex,
"description": "group three",
"rules": [
{"from_port": 443,
"to_port": 443, "ip_range": {"cidr": "10.0.0.0/32"},
"ip_protocol": "tcp"},
{"from_port": "1000",
"to_port": 2000, "ip_range": {"cidr": "11.0.0.0/32"},
"ip_protocol": "udp"},
]
},
]
}
def fake_query_results():
cats = []
# OCCI Core
cats.append(
'link; '
'scheme="http://schemas.ogf.org/occi/core#"; '
'class="kind"; title="link"; '
'location="%s/link/"' % application_url)
cats.append(
'resource; '
'scheme="http://schemas.ogf.org/occi/core#"; '
'class="kind"; title="resource"; '
'rel="http://schemas.ogf.org/occi/core#entity"; '
'location="%s/resource/"' % application_url)
cats.append(
'entity; '
'scheme="http://schemas.ogf.org/occi/core#"; '
'class="kind"; title="entity"; '
'location="%s/entity/"' % application_url)
# OCCI Infrastructure Compute
cats.append(
'compute; '
'scheme="http://schemas.ogf.org/occi/infrastructure#"; '
'class="kind"; title="compute resource"; '
'rel="http://schemas.ogf.org/occi/core#resource"; '
'location="%s/compute/"' % application_url)
cats.append(
'start; '
'scheme="http://schemas.ogf.org/occi/infrastructure/compute/action#"; '
'class="action"; title="start compute instance"')
cats.append(
'stop; '
'scheme="http://schemas.ogf.org/occi/infrastructure/compute/action#"; '
'class="action"; title="stop compute instance"')
cats.append(
'restart; '
'scheme="http://schemas.ogf.org/occi/infrastructure/compute/action#"; '
'class="action"; title="restart compute instance"')
cats.append(
'suspend; '
'scheme="http://schemas.ogf.org/occi/infrastructure/compute/action#"; '
'class="action"; title="suspend compute instance"')
# OCCI Templates
cats.append(
'os_tpl; '
'scheme="http://schemas.ogf.org/occi/infrastructure#"; '
'class="mixin"; title="OCCI OS Template"; '
'location="%s/os_tpl/"' % application_url)
cats.append(
'resource_tpl; '
'scheme="http://schemas.ogf.org/occi/infrastructure#"; '
'class="mixin"; title="OCCI Resource Template"; '
'location="%s/resource_tpl/"' % application_url)
# OpenStack Images
cats.append(
'bar; '
'scheme="http://schemas.openstack.org/template/os#"; '
'class="mixin"; title="bar"; '
'rel="http://schemas.ogf.org/occi/infrastructure#os_tpl"; '
'location="%s/os_tpl/bar"' % application_url)
cats.append(
'foo; '
'scheme="http://schemas.openstack.org/template/os#"; '
'class="mixin"; title="foo"; '
'rel="http://schemas.ogf.org/occi/infrastructure#os_tpl"; '
'location="%s/os_tpl/foo"' % application_url)
# OpenStack Flavors
cats.append(
'1; '
'scheme="http://schemas.openstack.org/template/resource#"; '
'class="mixin"; title="Flavor: foo"; '
'rel="http://schemas.ogf.org/occi/infrastructure#resource_tpl"; '
'location="%s/resource_tpl/1"' % application_url)
cats.append(
'2; '
'scheme="http://schemas.openstack.org/template/resource#"; '
'class="mixin"; title="Flavor: bar"; '
'rel="http://schemas.ogf.org/occi/infrastructure#resource_tpl"; '
'location="%s/resource_tpl/2"' % application_url)
# OCCI Infrastructure Network
cats.append(
'network; '
'scheme="http://schemas.ogf.org/occi/infrastructure#"; '
'class="kind"; title="network resource"; '
'rel="http://schemas.ogf.org/occi/core#resource"; '
'location="%s/network/"' % application_url)
cats.append(
'ipnetwork; '
'scheme="http://schemas.ogf.org/occi/infrastructure/network#"; '
'class="mixin"; title="IP Networking Mixin"')
cats.append(
'up; '
'scheme="http://schemas.ogf.org/occi/infrastructure/network/action#"; '
'class="action"; title="up network instance"')
cats.append(
'down; '
'scheme="http://schemas.ogf.org/occi/infrastructure/network/action#"; '
'class="action"; title="down network instance"')
cats.append(
'networkinterface; '
'scheme="http://schemas.ogf.org/occi/infrastructure#"; '
'class="kind"; title="network link resource"; '
'rel="http://schemas.ogf.org/occi/core#link"; '
'location="%s/networklink/"' % application_url)
cats.append(
'ipnetworkinterface; '
'scheme="http://schemas.ogf.org/occi/infrastructure/'
'networkinterface#"; '
'class="mixin"; title="IP Network interface Mixin"')
# OCCI Infrastructure Storage
cats.append(
'storage; '
'scheme="http://schemas.ogf.org/occi/infrastructure#"; '
'class="kind"; title="storage resource"; '
'rel="http://schemas.ogf.org/occi/core#resource"; '
'location="%s/storage/"' % application_url)
cats.append(
'storagelink; '
'scheme="http://schemas.ogf.org/occi/infrastructure#"; '
'class="kind"; title="storage link resource"; '
'rel="http://schemas.ogf.org/occi/core#link"; '
'location="%s/storagelink/"' % application_url)
cats.append(
'offline; '
'scheme="http://schemas.ogf.org/occi/infrastructure/storage/action#"; '
'class="action"; title="offline storage instance"')
cats.append(
'online; '
'scheme="http://schemas.ogf.org/occi/infrastructure/storage/action#"; '
'class="action"; title="online storage instance"')
cats.append(
'backup; '
'scheme="http://schemas.ogf.org/occi/infrastructure/storage/action#"; '
'class="action"; title="backup storage instance"')
cats.append(
'resize; '
'scheme="http://schemas.ogf.org/occi/infrastructure/storage/action#"; '
'class="action"; title="resize storage instance"')
cats.append(
'snapshot; '
'scheme="http://schemas.ogf.org/occi/infrastructure/storage/action#"; '
'class="action"; title="snapshot storage instance"')
# OpenStack contextualization
cats.append(
'user_data; '
'scheme="http://schemas.openstack.org/compute/instance#"; '
'class="mixin"; title="Contextualization extension - user_data"')
cats.append(
'public_key; '
'scheme="http://schemas.openstack.org/instance/credentials#"; '
'class="mixin"; title="Contextualization extension - public_key"')
# OCCI contextualization
cats.append(
'user_data; '
'scheme="http://schemas.ogf.org/occi/infrastructure/compute#"; '
'class="mixin"; title="Contextualization mixin"')
cats.append(
'ssh_key; '
'scheme="http://schemas.ogf.org/occi/infrastructure/credentials#"; '
'class="mixin"; title="Credentials mixin"')
result = []
for c in cats:
result.append(("Category", c))
return result
class FakeOpenStackFault(ooi.wsgi.Fault):
_fault_names = {
400: "badRequest",
401: "unauthorized",
403: "forbidden",
404: "itemNotFound",
405: "badMethod",
406: "notAceptable",
409: "conflictingRequest",
413: "overLimit",
415: "badMediaType",
429: "overLimit",
501: "notImplemented",
503: "serviceUnavailable"}
@webob.dec.wsgify()
def __call__(self, req):
code = self.wrapped_exc.status_int
fault_name = self._fault_names.get(code)
explanation = self.wrapped_exc.explanation
fault_data = {
fault_name: {
'code': code,
'message': explanation}}
self.wrapped_exc.body = utils.utf8(json.dumps(fault_data))
self.wrapped_exc.content_type = "application/json"
return self.wrapped_exc
class FakeApp(object):
"""Poor man's fake application."""
def __init__(self):
self.routes = {}
for tenant in tenants.values():
path = "/%s" % tenant["id"]
self._populate(path, "server", servers[tenant["id"]], actions=True)
self._populate(path, "volume", volumes[tenant["id"]], "os-volumes")
self._populate(path, "floating_ip_pool", pools[tenant["id"]],
"os-floating-ip-pools")
self._populate(path, "floating_ip", floating_ips[tenant["id"]],
"os-floating-ips")
self._populate_server_links(path, "os-interface",
"interfaceAttachments",
servers[tenant["id"]],
ports[tenant["id"]])
self._populate_server_links(path, "os-security-groups",
"security_groups",
servers[tenant["id"]],
security_groups[tenant["id"]])
self._populate(path, "security_group",
security_groups[tenant["id"]],
"os-security-groups")
# NOTE(aloga): dict_values un Py3 is not serializable in JSON
self._populate(path, "image", list(images.values()))
self._populate(path, "flavor", list(flavors.values()))
self._populate_attached_volumes(path, servers[tenant["id"]],
volumes[tenant["id"]])
def _populate(self, path_base, obj_name, obj_list,
objs_path=None, actions=[]):
objs_name = "%ss" % obj_name
if objs_path:
path = "%s/%s" % (path_base, objs_path)
else:
path = "%s/%s" % (path_base, objs_name)
objs_details_path = "%s/detail" % path
self.routes[path] = create_fake_json_resp({objs_name: obj_list})
self.routes[objs_details_path] = create_fake_json_resp(
{objs_name: obj_list})
for o in obj_list:
obj_path = "%s/%s" % (path, o["id"])
self.routes[obj_path] = create_fake_json_resp({obj_name: o})
if actions:
action_path = "%s/action" % obj_path
self.routes[action_path] = webob.Response(status=202)
def _populate_attached_volumes(self, path, server_list, vol_list):
for s in server_list:
attachments = []
if "os-extended-volumes:volumes_attached" in s:
for attach in s["os-extended-volumes:volumes_attached"]:
for v in vol_list:
if attach["id"] == v["id"]:
attachments.append(v["attachments"][0])
path_base = "%s/servers/%s/os-volume_attachments" % (path, s["id"])
self.routes[path_base] = create_fake_json_resp(
{"volumeAttachments": attachments}
)
for attach in attachments:
obj_path = "%s/%s" % (path_base, attach["id"])
self.routes[obj_path] = create_fake_json_resp(
{"volumeAttachment": attach})
def _populate_server_links(self, path, resource, obj,
servers_list, link_list):
if servers_list:
for s in servers_list:
list_obj = []
path_base = "%s/servers/%s/%s" % (
path,
s["id"],
resource
)
for l in link_list:
list_obj.append(l)
self.routes[path_base] = create_fake_json_resp(
{obj: list_obj})
@webob.dec.wsgify()
def __call__(self, req):
if req.method == "GET":
return self._do_get(req)
elif req.method == "POST":
return self._do_post(req)
elif req.method == "DELETE":
return self._do_delete(req)
def _do_create_server(self, req):
# TODO(enolfc): this should check the json is
# semantically correct
s = {"server": {"id": "foo",
"name": "foo",
"flavor": {"id": "1"},
"image": {"id": "2"},
"status": "ACTIVE"}}
return create_fake_json_resp(s)
def _do_create_volume(self, req):
# TODO(enolfc): this should check the json is
# semantically correct
s = {"volume": {"id": "foo",
"displayName": "foo",
"size": 1,
"status": "on-line"}}
return create_fake_json_resp(s)
def _do_create_attachment(self, req):
v = {"volumeAttachment": {"serverId": "foo",
"volumeId": "bar",
"device": "/dev/vdb"}}
return create_fake_json_resp(v, 202)
def _do_allocate_ip(self, req):
tenant = req.path_info.split('/')[1]
body = req.json_body.copy()
pool = body.popitem()
if pool[1]:
for p in pools[tenant]:
if p["name"] == pool[1]:
break
else:
exc = webob.exc.HTTPNotFound()
return FakeOpenStackFault(exc)
ip = {"floating_ip": {"ip": allocated_ip, "id": 1}}
return create_fake_json_resp(ip, 202)
def _do_create_port(self, req):
req_content = req.path_info.split('/')
tenant = req_content[1]
server = req_content[3]
body = req.json_body.copy()
net = body["interfaceAttachment"]["net_id"]
port = ports[tenant]
p = {"interfaceAttachment": {
"port_id": uuid.uuid4().hex,
"fixed_ips":
[{"ip_address":
port[0]["fixed_ips"]
[0]["ip_address"]
}],
"mac_addr": port[0]["mac_addr"],
"port_state": "DOWN",
"net_id": net,
"server_id": server
}}
return create_fake_json_resp(p, 200)
def _do_post(self, req):
if req.path_info.endswith("servers"):
return self._do_create_server(req)
if req.path_info.endswith("os-volumes"):
return self._do_create_volume(req)
elif req.path_info.endswith("action"):
body = req.json_body.copy()
action = body.popitem()
if action[0] in ["os-start", "os-stop", "reboot",
"addFloatingIp", "removeFloatingIp",
"removeSecurityGroup",
"addSecurityGroup"]:
return self._get_from_routes(req)
elif req.path_info.endswith("os-volume_attachments"):
return self._do_create_attachment(req)
elif req.path_info.endswith("os-floating-ips"):
return self._do_allocate_ip(req)
elif req.path_info.endswith("os-interface"):
return self._do_create_port(req)
raise Exception
def _do_delete(self, req):
self._do_get(req)
tested_paths = {
r"/[^/]+/servers/[^/]+/os-volume_attachments/[^/]+$": 202,
r"/[^/]+/os-floating-ips/[^/]+$": 202,
r"/[^/]+/servers/[^/]+$": 204,
r"/[^/]+/os-volumes/[^/]+$": 202,
r"/[^/]+/servers/[^/]+/os-interface/[^/]+$": 204,
}
for p, st in tested_paths.items():
if re.match(p, req.path_info):
return create_fake_json_resp({}, st)
raise Exception
def _do_get(self, req):
return self._get_from_routes(req)
def _get_from_routes(self, req):
try:
ret = self.routes[req.path_info]
except KeyError:
exc = webob.exc.HTTPNotFound()
ret = FakeOpenStackFault(exc)
return ret
def create_fake_json_resp(data, status=200):
r = webob.Response()
r.headers["Content-Type"] = "application/json"
r.charset = "utf8"
r.body = json.dumps(data).encode("utf8")
r.status_code = status
return r