Item => Shape

pull/638/head
Julien Duponchelle 8 years ago
parent 85ebac7eb3
commit 116135b9dc
No known key found for this signature in database
GPG Key ID: CE8B29639E07F5E8

@ -265,9 +265,9 @@ The available notification are:
* link.created
* link.updated
* link.deleted
* item.created
* item.updated
* item.deleted
* shape.created
* shape.updated
* shape.deleted
* log.error
* log.warning
* log.info

@ -6,10 +6,10 @@ Node
A Virtual Machine (Dynamips, IOU, Qemu, VPCS...), a cloud, a builtin device (switch, hub...)
Item
Shape
-----
Item are visual element not used by the network emulation. Like
Shape are visual element not used by the network emulation. Like
text, images, rectangle... They are pure SVG elements.
Adapter

@ -24,7 +24,7 @@ import shutil
from uuid import UUID, uuid4
from .node import Node
from .item import Item
from .shape import Shape
from .topology import project_to_topology, load_topology
from .udp_link import UDPLink
from ..config import Config
@ -77,7 +77,7 @@ class Project:
self._allocated_node_names = set()
self._nodes = {}
self._links = {}
self._items = {}
self._shapes = {}
# Create the project on demand on the compute node
self._project_created_on_compute = set()
@ -266,42 +266,42 @@ class Project:
return self._nodes
@property
def items(self):
def shapes(self):
"""
:returns: Dictionary of the items
:returns: Dictionary of the shapes
"""
return self._items
return self._shapes
@asyncio.coroutine
def add_item(self, item_id=None, **kwargs):
def add_shape(self, shape_id=None, **kwargs):
"""
Create an item or return an existing item
Create an shape or return an existing shape
:param kwargs: See the documentation of item
:param kwargs: See the documentation of shape
"""
if item_id not in self._items:
item = Item(self, item_id=item_id, **kwargs)
self._items[item.id] = item
self.controller.notification.emit("item.created", item.__json__())
if shape_id not in self._shapes:
shape = Shape(self, shape_id=shape_id, **kwargs)
self._shapes[shape.id] = shape
self.controller.notification.emit("shape.created", shape.__json__())
self.dump()
return item
return self._items[item_id]
return shape
return self._shapes[shape_id]
def get_item(self, item_id):
def get_shape(self, shape_id):
"""
Return the Item or raise a 404 if the item is unknown
Return the Shape or raise a 404 if the shape is unknown
"""
try:
return self._items[item_id]
return self._shapes[shape_id]
except KeyError:
raise aiohttp.web.HTTPNotFound(text="Item ID {} doesn't exist".format(item_id))
raise aiohttp.web.HTTPNotFound(text="Shape ID {} doesn't exist".format(shape_id))
@asyncio.coroutine
def delete_item(self, item_id):
item = self.get_item(item_id)
del self._items[item.id]
def delete_shape(self, shape_id):
shape = self.get_shape(shape_id)
del self._shapes[shape.id]
self.dump()
self.controller.notification.emit("item.deleted", item.__json__())
self.controller.notification.emit("shape.deleted", shape.__json__())
@asyncio.coroutine
def add_link(self, link_id=None):
@ -397,8 +397,8 @@ class Project:
node = self.get_node(node_link["node_id"])
yield from link.add_node(node, node_link["adapter_number"], node_link["port_number"])
for item_data in topology.get("items", []):
item = yield from self.add_item(**item_data)
for shape_data in topology.get("shapes", []):
shape = yield from self.add_shape(**shape_data)
self._status = "opened"
def dump(self):

@ -19,18 +19,18 @@ import asyncio
import uuid
class Item:
class Shape:
"""
Item are visual element not used by the network emulation. Like
Shape are visual element not used by the network emulation. Like
text, images, rectangle... They are pure SVG elements.
"""
def __init__(self, project, item_id=None, svg=None, x=0, y=0, z=0):
def __init__(self, project, shape_id=None, svg=None, x=0, y=0, z=0):
self.svg = "<svg></svg>"
self._project = project
if item_id is None:
if shape_id is None:
self._id = str(uuid.uuid4())
else:
self._id = item_id
self._id = shape_id
self._x = x
self._y = y
self._z = z
@ -83,7 +83,7 @@ class Item:
for prop in kwargs:
if getattr(self, prop) != kwargs[prop]:
setattr(self, prop, kwargs[prop])
self._project.controller.notification.emit("item.updated", self.__json__())
self._project.controller.notification.emit("shape.updated", self.__json__())
self._project.dump()
def __json__(self, topology_dump=False):
@ -92,20 +92,20 @@ class Item:
"""
if topology_dump:
return {
"item_id": self._id,
"shape_id": self._id,
"x": self._x,
"y": self._y,
"z": self._z,
}
return {
"project_id": self._project.id,
"item_id": self._id,
"shape_id": self._id,
"x": self._x,
"y": self._y,
"z": self._z,
}
def __repr__(self):
return "<gns3server.controller.Item {}>".format(self._id)
return "<gns3server.controller.Shape {}>".format(self._id)

@ -34,7 +34,7 @@ def project_to_topology(project):
"nodes": [],
"links": [],
"computes": [],
"items": []
"shapes": []
},
"type": "topology",
"revision": GNS3_FILE_FORMAT_REVISION,
@ -47,8 +47,8 @@ def project_to_topology(project):
data["topology"]["nodes"].append(node.__json__(topology_dump=True))
for link in project.links.values():
data["topology"]["links"].append(link.__json__(topology_dump=True))
for item in project.items.values():
data["topology"]["items"].append(item.__json__(topology_dump=True))
for shape in project.shapes.values():
data["topology"]["shapes"].append(shape.__json__(topology_dump=True))
for compute in computes:
if hasattr(compute, "__json__"):
data["topology"]["computes"].append(compute.__json__(topology_dump=True))

@ -20,4 +20,4 @@ from .project_handler import ProjectHandler
from .node_handler import NodeHandler
from .link_handler import LinkHandler
from .server_handler import ServerHandler
from .item_handler import ItemHandler
from .shape_handler import ShapeHandler

@ -20,88 +20,88 @@ import aiohttp
from gns3server.web.route import Route
from gns3server.controller import Controller
from gns3server.schemas.item import (
ITEM_OBJECT_SCHEMA,
from gns3server.schemas.shape import (
SHAPE_OBJECT_SCHEMA,
)
class ItemHandler:
class ShapeHandler:
"""
API entry point for Item
API entry point for Shape
"""
@Route.get(
r"/projects/{project_id}/items",
r"/projects/{project_id}/shapes",
parameters={
"project_id": "Project UUID"
},
status_codes={
200: "List of items returned",
200: "List of shapes returned",
},
description="List items of a project")
def list_items(request, response):
description="List shapes of a project")
def list_shapes(request, response):
controller = Controller.instance()
project = controller.get_project(request.match_info["project_id"])
response.json([v for v in project.items.values()])
response.json([v for v in project.shapes.values()])
@Route.post(
r"/projects/{project_id}/items",
r"/projects/{project_id}/shapes",
parameters={
"project_id": "Project UUID"
},
status_codes={
201: "Item created",
201: "Shape created",
400: "Invalid request"
},
description="Create a new item instance",
input=ITEM_OBJECT_SCHEMA,
output=ITEM_OBJECT_SCHEMA)
description="Create a new shape instance",
input=SHAPE_OBJECT_SCHEMA,
output=SHAPE_OBJECT_SCHEMA)
def create(request, response):
controller = Controller.instance()
project = controller.get_project(request.match_info["project_id"])
item = yield from project.add_item(**request.json)
shape = yield from project.add_shape(**request.json)
response.set_status(201)
response.json(item)
response.json(shape)
@Route.put(
r"/projects/{project_id}/items/{item_id}",
r"/projects/{project_id}/shapes/{shape_id}",
parameters={
"project_id": "Project UUID",
"item_id": "Item UUID"
"shape_id": "Shape UUID"
},
status_codes={
201: "Item updated",
201: "Shape updated",
400: "Invalid request"
},
description="Create a new item instance",
input=ITEM_OBJECT_SCHEMA,
output=ITEM_OBJECT_SCHEMA)
description="Create a new shape instance",
input=SHAPE_OBJECT_SCHEMA,
output=SHAPE_OBJECT_SCHEMA)
def update(request, response):
controller = Controller.instance()
project = controller.get_project(request.match_info["project_id"])
item = project.get_item(request.match_info["item_id"])
yield from item.update(**request.json)
shape = project.get_shape(request.match_info["shape_id"])
yield from shape.update(**request.json)
response.set_status(201)
response.json(item)
response.json(shape)
@Route.delete(
r"/projects/{project_id}/items/{item_id}",
r"/projects/{project_id}/shapes/{shape_id}",
parameters={
"project_id": "Project UUID",
"item_id": "Item UUID"
"shape_id": "Shape UUID"
},
status_codes={
204: "Item deleted",
204: "Shape deleted",
400: "Invalid request"
},
description="Delete a item instance")
description="Delete a shape instance")
def delete(request, response):
controller = Controller.instance()
project = controller.get_project(request.match_info["project_id"])
yield from project.delete_item(request.match_info["item_id"])
yield from project.delete_shape(request.match_info["shape_id"])
response.set_status(204)

@ -16,13 +16,13 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
ITEM_OBJECT_SCHEMA = {
SHAPE_OBJECT_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "An item object",
"description": "An shape object",
"type": "object",
"properties": {
"item_id": {
"description": "Link UUID",
"shape_id": {
"description": "Shape UUID",
"type": "string",
"minLength": 36,
"maxLength": 36,
@ -48,7 +48,7 @@ ITEM_OBJECT_SCHEMA = {
"type": "integer"
},
"svg": {
"description": "SVG content of the item",
"description": "SVG content of the shape",
"type": "string",
"pattern": "^<.+>$"
}

@ -213,30 +213,30 @@ def test_deleteLink(async_run, project, controller):
assert len(project._links) == 0
def test_addItem(async_run, project, controller):
def test_addShape(async_run, project, controller):
controller.notification.emit = MagicMock()
item = async_run(project.add_item(None, svg="<svg></svg>"))
assert len(project._items) == 1
controller.notification.emit.assert_any_call("item.created", item.__json__())
shape = async_run(project.add_shape(None, svg="<svg></svg>"))
assert len(project._shapes) == 1
controller.notification.emit.assert_any_call("shape.created", shape.__json__())
def test_getItem(async_run, project):
item = async_run(project.add_item(None))
assert project.get_item(item.id) == item
def test_getShape(async_run, project):
shape = async_run(project.add_shape(None))
assert project.get_shape(shape.id) == shape
with pytest.raises(aiohttp.web_exceptions.HTTPNotFound):
project.get_item("test")
project.get_shape("test")
def test_deleteItem(async_run, project, controller):
assert len(project._items) == 0
item = async_run(project.add_item())
assert len(project._items) == 1
def test_deleteShape(async_run, project, controller):
assert len(project._shapes) == 0
shape = async_run(project.add_shape())
assert len(project._shapes) == 1
controller._notification = MagicMock()
async_run(project.delete_item(item.id))
controller.notification.emit.assert_any_call("item.deleted", item.__json__())
assert len(project._items) == 0
async_run(project.delete_shape(shape.id))
controller.notification.emit.assert_any_call("shape.deleted", shape.__json__())
assert len(project._shapes) == 0
def test_delete(async_run, project, controller):

@ -22,7 +22,7 @@ import uuid
from tests.utils import AsyncioMagicMock
from gns3server.controller.item import Item
from gns3server.controller.shape import Shape
from gns3server.controller.project import Project
@ -32,43 +32,43 @@ def project(controller, async_run):
@pytest.fixture
def item(project):
return Item(project, None, svg="<svg></svg>")
def shape(project):
return Shape(project, None, svg="<svg></svg>")
def test_init_without_uuid(project):
item = Item(project, None, svg="<svg></svg>")
assert item.id is not None
shape = Shape(project, None, svg="<svg></svg>")
assert shape.id is not None
def test_init_with_uuid(project):
id = str(uuid.uuid4())
item = Item(project, id, svg="<svg></svg>")
assert item.id == id
shape = Shape(project, id, svg="<svg></svg>")
assert shape.id == id
def test_json(project):
i = Item(project, None, svg="<svg></svg>")
i = Shape(project, None, svg="<svg></svg>")
assert i.__json__() == {
"item_id": i.id,
"shape_id": i.id,
"project_id": project.id,
"x": i.x,
"y": i.y,
"z": i.z
}
assert i.__json__(topology_dump=True) == {
"item_id": i.id,
"shape_id": i.id,
"x": i.x,
"y": i.y,
"z": i.z
}
def test_update(item, project, async_run, controller):
def test_update(shape, project, async_run, controller):
controller._notification = AsyncioMagicMock()
project.dump = MagicMock()
async_run(item.update(x=42))
assert item.x == 42
controller._notification.emit.assert_called_with("item.updated", item.__json__())
async_run(shape.update(x=42))
assert shape.x == 42
controller._notification.emit.assert_called_with("shape.updated", shape.__json__())
assert project.dump.called

@ -38,7 +38,7 @@ def test_project_to_topology_empty(tmpdir):
"nodes": [],
"links": [],
"computes": [],
"items": []
"shapes": []
},
"type": "topology",
"version": __version__
@ -58,14 +58,14 @@ def test_basic_topology(tmpdir, async_run, controller):
async_run(link.add_node(node1, 0, 0))
async_run(link.add_node(node2, 0, 0))
item = async_run(project.add_item(svg="<svg></svg>"))
shape = async_run(project.add_shape(svg="<svg></svg>"))
topo = project_to_topology(project)
assert len(topo["topology"]["nodes"]) == 2
assert node1.__json__(topology_dump=True) in topo["topology"]["nodes"]
assert topo["topology"]["links"][0] == link.__json__(topology_dump=True)
assert topo["topology"]["computes"][0] == compute.__json__(topology_dump=True)
assert topo["topology"]["items"][0] == item.__json__(topology_dump=True)
assert topo["topology"]["shapes"][0] == shape.__json__(topology_dump=True)
def test_load_topology(tmpdir):
@ -77,7 +77,7 @@ def test_load_topology(tmpdir):
"nodes": [],
"links": [],
"computes": [],
"items": []
"shapes": []
},
"type": "topology",
"version": __version__}

@ -30,7 +30,7 @@ from tests.utils import asyncio_patch
from gns3server.handlers.api.controller.project_handler import ProjectHandler
from gns3server.controller import Controller
from gns3server.controller.item import Item
from gns3server.controller.shape import Shape
@ -39,49 +39,49 @@ def project(http_controller, async_run):
return async_run(Controller.instance().add_project())
def test_create_item(http_controller, tmpdir, project, async_run):
def test_create_shape(http_controller, tmpdir, project, async_run):
response = http_controller.post("/projects/{}/items".format(project.id), {
response = http_controller.post("/projects/{}/shapes".format(project.id), {
"svg": '<svg height="210" width="500"><line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" /></svg>',
"x": 10,
"y": 20,
"z": 0
}, example=True)
assert response.status == 201
assert response.json["item_id"] is not None
assert response.json["shape_id"] is not None
def test_update_item(http_controller, tmpdir, project, async_run):
def test_update_shape(http_controller, tmpdir, project, async_run):
response = http_controller.post("/projects/{}/items".format(project.id), {
response = http_controller.post("/projects/{}/shapes".format(project.id), {
"svg": '<svg height="210" width="500"><line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" /></svg>',
"x": 10,
"y": 20,
"z": 0
},)
response = http_controller.put("/projects/{}/items/{}".format(project.id, response.json["item_id"]), {
response = http_controller.put("/projects/{}/shapes/{}".format(project.id, response.json["shape_id"]), {
"x": 42,
}, example=True)
assert response.status == 201
assert response.json["x"] == 42
def test_list_item(http_controller, tmpdir, project, async_run):
response = http_controller.post("/projects/{}/items".format(project.id), {
def test_list_shape(http_controller, tmpdir, project, async_run):
response = http_controller.post("/projects/{}/shapes".format(project.id), {
"svg": '<svg height="210" width="500"><line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" /></svg>',
"x": 10,
"y": 20,
"z": 0
}, example=False)
response = http_controller.get("/projects/{}/items".format(project.id), example=True)
response = http_controller.get("/projects/{}/shapes".format(project.id), example=True)
assert response.status == 200
assert len(response.json) == 1
def test_delete_item(http_controller, tmpdir, project, async_run):
def test_delete_shape(http_controller, tmpdir, project, async_run):
item = Item(project)
project._items = {item.id: item}
response = http_controller.delete("/projects/{}/items/{}".format(project.id, item.id), example=True)
shape = Shape(project)
project._shapes = {shape.id: shape}
response = http_controller.delete("/projects/{}/shapes/{}".format(project.id, shape.id), example=True)
assert response.status == 204
assert item.id not in project._items
assert shape.id not in project._shapes
Loading…
Cancel
Save