diff --git a/docs/api/examples/post_vpcs.txt b/docs/api/examples/post_vpcs.txt
index 08616805..b0d5e689 100644
--- a/docs/api/examples/post_vpcs.txt
+++ b/docs/api/examples/post_vpcs.txt
@@ -8,7 +8,7 @@ POST /vpcs HTTP/1.1
HTTP/1.1 200
CONNECTION: close
-CONTENT-LENGTH: 67
+CONTENT-LENGTH: 66
CONTENT-TYPE: application/json
DATE: Thu, 08 Jan 2015 16:09:15 GMT
SERVER: Python/3.4 aiohttp/0.13.1
@@ -17,5 +17,5 @@ X-ROUTE: /vpcs
{
"console": 4242,
"name": "PC TEST 1",
- "vpcs_id": 42
+ "vpcs_id": 1
}
diff --git a/gns3server/handlers/vpcs_handler.py b/gns3server/handlers/vpcs_handler.py
index 74d44fc1..abc2cd15 100644
--- a/gns3server/handlers/vpcs_handler.py
+++ b/gns3server/handlers/vpcs_handler.py
@@ -16,12 +16,10 @@
# along with this program. If not, see .
from ..web.route import Route
-from ..modules.vpcs import VPCS
-
-# schemas
from ..schemas.vpcs import VPCS_CREATE_SCHEMA
from ..schemas.vpcs import VPCS_OBJECT_SCHEMA
from ..schemas.vpcs import VPCS_ADD_NIO_SCHEMA
+from ..modules import VPCS
class VPCSHandler(object):
@@ -40,9 +38,9 @@ class VPCSHandler(object):
output=VPCS_OBJECT_SCHEMA)
def create(request, response):
vpcs = VPCS.instance()
- i = yield from vpcs.create(request.json)
- response.json({'name': request.json['name'],
- "vpcs_id": i,
+ vm = yield from vpcs.create_vm(request.json['name'])
+ response.json({'name': vm.name,
+ "vpcs_id": vm.id,
"console": 4242})
@classmethod
diff --git a/gns3server/modules/base.py b/gns3server/modules/base.py
deleted file mode 100644
index 38761bf3..00000000
--- a/gns3server/modules/base.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2015 GNS3 Technologies Inc.
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-import asyncio
-
-
-#TODO: make this more generic (not json but *args?)
-class BaseModule(object):
- _instance = None
-
- @classmethod
- def instance(cls):
- if cls._instance is None:
- cls._instance = cls()
- asyncio.async(cls._instance.run())
- return cls._instance
-
- def __init__(self):
- self._queue = asyncio.Queue()
-
- @classmethod
- def destroy(cls):
- future = asyncio.Future()
- cls._instance._queue.put_nowait((future, None, ))
- yield from asyncio.wait([future])
- cls._instance = None
-
- @asyncio.coroutine
- def put(self, json):
-
- future = asyncio.Future()
- self._queue.put_nowait((future, json, ))
- yield from asyncio.wait([future])
- return future.result()
-
- @asyncio.coroutine
- def run(self):
-
- while True:
- future, json = yield from self._queue.get()
- if json is None:
- future.set_result(True)
- break
- try:
- result = yield from self.process(json)
- future.set_result(result)
- except Exception as e:
- future.set_exception(e)
-
- @asyncio.coroutine
- def process(self, json):
- raise NotImplementedError
diff --git a/gns3server/modules/base_vm.py b/gns3server/modules/base_vm.py
new file mode 100644
index 00000000..5e618059
--- /dev/null
+++ b/gns3server/modules/base_vm.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2015 GNS3 Technologies Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+
+import asyncio
+from .vm_error import VMError
+
+
+class BaseVM:
+
+ def __init__(self, name, identifier):
+ self._queue = asyncio.Queue()
+ self._name = name
+ self._id = identifier
+ self._created = asyncio.Future()
+ self._worker = asyncio.async(self._run())
+
+ @property
+ def id(self):
+ """
+ Returns the unique ID for this VM.
+
+ :returns: id (integer)
+ """
+
+ return self._id
+
+ @property
+ def name(self):
+ """
+ Returns the name for this VM.
+
+ :returns: name (string)
+ """
+
+ return self._name
+
+ @asyncio.coroutine
+ def _execute(self, subcommand, args):
+ """Called when we receive an event"""
+ raise NotImplementedError
+
+ @asyncio.coroutine
+ def _create(self):
+ """Called when the run loop start"""
+ raise NotImplementedError
+
+ @asyncio.coroutine
+ def _run(self, timeout=60):
+
+ try:
+ yield from self._create()
+ self._created.set_result(True)
+ except VMError as e:
+ self._created.set_exception(e)
+ return
+
+ while True:
+ future, subcommand, args = yield from self._queue.get()
+ try:
+ try:
+ yield from asyncio.wait_for(self._execute(subcommand, args), timeout=timeout)
+ except asyncio.TimeoutError:
+ raise VMError("{} has timed out after {} seconds!".format(subcommand, timeout))
+ future.set_result(True)
+ except Exception as e:
+ future.set_exception(e)
+
+ def wait_for_creation(self):
+ return self._created
+
+ def put(self, *args):
+ """
+ Add to the processing queue of the VM
+
+ :returns: future
+ """
+
+ future = asyncio.Future()
+ try:
+ args.insert(0, future)
+ self._queue.put_nowait(args)
+ except asyncio.qeues.QueueFull:
+ raise VMError("Queue is full")
+ return future
diff --git a/gns3server/modules/vm_error.py b/gns3server/modules/vm_error.py
new file mode 100644
index 00000000..d7b71e14
--- /dev/null
+++ b/gns3server/modules/vm_error.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2015 GNS3 Technologies Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+
+class VMError(Exception):
+ pass
diff --git a/gns3server/modules/vm_manager.py b/gns3server/modules/vm_manager.py
new file mode 100644
index 00000000..7065a084
--- /dev/null
+++ b/gns3server/modules/vm_manager.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2015 GNS3 Technologies Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+
+import asyncio
+import aiohttp
+
+from .vm_error import VMError
+
+
+class VMManager:
+ """
+ Base class for all VMManager.
+ Responsible of management of a VM pool
+ """
+
+ def __init__(self):
+ self._vms = {}
+
+ @classmethod
+ def instance(cls):
+ """
+ Singleton to return only one instance of Manager.
+
+ :returns: instance of Manager
+ """
+
+ if not hasattr(cls, "_instance"):
+ cls._instance = cls()
+ return cls._instance
+
+ @classmethod
+ @asyncio.coroutine
+ def destroy(cls):
+ cls._instance = None
+
+ def _get_vm_instance(self, vm_id):
+ """
+ Returns a VM instance.
+
+ :param vm_id: VM identifier
+
+ :returns: VM instance
+ """
+
+ if vm_id not in self._vms:
+ raise aiohttp.web.HTTPNotFound(text="ID {} doesn't exist".format(vm_id))
+ return self._vms[vm_id]
+
+ @asyncio.coroutine
+ def create_vm(self, vmname, identifier=None):
+ if not identifier:
+ for i in range(1, 1024):
+ if i not in self._vms:
+ identifier = i
+ break
+ if identifier == 0:
+ raise VMError("Maximum number of VM instances reached")
+ else:
+ if identifier in self._vms:
+ raise VMError("VM identifier {} is already used by another VM instance".format(identifier))
+ vm = self._VM_CLASS(vmname, identifier)
+ yield from vm.wait_for_creation()
+ self._vms[vm.id] = vm
+ return vm
+
+ @asyncio.coroutine
+ def start_vm(self, vm_id):
+ vm = self._get_vm_instance(vm_id)
+ yield from vm.start()
+
+ @asyncio.coroutine
+ def stop_vm(self, vm_id):
+ vm = self._get_vm_instance(vm_id)
+ yield from vm.stop()
diff --git a/gns3server/modules/vpcs/__init__.py b/gns3server/modules/vpcs/__init__.py
index d544365c..618124d1 100644
--- a/gns3server/modules/vpcs/__init__.py
+++ b/gns3server/modules/vpcs/__init__.py
@@ -19,19 +19,12 @@
VPCS server module.
"""
-import asyncio
-
-from ..base import BaseModule
+from ..vm_manager import VMManager
+from .vpcs_device import VPCSDevice
-class VPCS(BaseModule):
+class VPCS(VMManager):
+ _VM_CLASS = VPCSDevice
- @asyncio.coroutine
- def process(self, json):
- yield from asyncio.sleep(1)
- return 42
-
- @asyncio.coroutine
- def create(self, json):
- i = yield from self.put(json)
- return i
+ def create_vm(self, name):
+ return super().create_vm(name)
diff --git a/gns3server/modules/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py
new file mode 100644
index 00000000..734344db
--- /dev/null
+++ b/gns3server/modules/vpcs/vpcs_device.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2015 GNS3 Technologies Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+
+from ..base_vm import BaseVM
+
+
+class VPCSDevice(BaseVM):
+ pass
diff --git a/tests/api/test_vpcs.py b/tests/api/test_vpcs.py
index fb2b3c9a..d5c441ca 100644
--- a/tests/api/test_vpcs.py
+++ b/tests/api/test_vpcs.py
@@ -20,16 +20,8 @@ from tests.utils import asyncio_patch
from gns3server import modules
-def test_vpcs_create(server):
- response = server.post('/vpcs', {'name': 'PC TEST 1'}, example=True)
- assert response.status == 200
- assert response.route == '/vpcs'
- assert response.json['name'] == 'PC TEST 1'
- assert response.json['vpcs_id'] == 42
-
-
-@asyncio_patch('demoserver.modules.VPCS.create', return_value=84)
-def test_vpcs_mock(server, mock):
+@asyncio_patch('gns3server.modules.VPCS.create_vm', return_value=84)
+def test_vpcs_create(server, mock):
response = server.post('/vpcs', {'name': 'PC TEST 1'}, example=False)
assert response.status == 200
assert response.route == '/vpcs'