diff --git a/setup.py b/setup.py index 290615579af122ee51a3d79c872629ba925665c5..aed739462799b6493957ae8cb0b051349a564e6e 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ setup( description='OCCI to Openstack/Cyclades API bridge', url='http://code.grnet.gr/projects/snf-occi', license='GPLv3', - packages=['soi', ], + packages=['soi', 'soi.tests'], entry_points=''' [paste.app_factory] snf_occi_app=soi:main diff --git a/soi/compute.py b/soi/compute.py index 5881bd8d4489ab4cbd94c2f325c23f00796d36ec..ffeb902e282229f2140b1b2bf721cc9f56968626 100644 --- a/soi/compute.py +++ b/soi/compute.py @@ -142,32 +142,21 @@ def snf_delete_server(cls, req, server_id): def snf_run_action(cls, req, action, server_id): """Synnefo: server actions""" - - actions_map = { - "stop": {"kwargs": {"server_id": server_id, - "json_data": {"shutdown": {}} - } - }, - - "start": {"kwargs": {"server_id": server_id, - "json_data": {"start": {}} - } - }, - "restart": {"kwargs": {"server_id": server_id, - "json_data": {"reboot": {"type": "SOFT"}} - } - } - } try: - action = actions_map[action] - req.environ['service_type'] = 'compute' - req.environ['method_name'] = 'servers_action_post' - req.environ.update(action) - req.get_response(cls.app) + json_data = { + 'start': {'start': {}}, + 'stop': {'shutdown': {}}, + 'restart': {'reboot': {'type': 'SOFT'}} + }[action] except KeyError: raise webob.exc.HTTPNotImplemented( explanation='Action {0} not supported'.format(action)) + req.environ['service_type'] = 'compute' + req.environ['method_name'] = 'servers_action_post' + req.environ['kwargs'] = {'server_id': server_id, 'json_data': json_data} + req.get_response(cls.app) + function_map = { 'index': snf_index, diff --git a/soi/tests/unit/__init__.py b/soi/tests/__init__.py similarity index 51% rename from soi/tests/unit/__init__.py rename to soi/tests/__init__.py index 25fdd3b3e2d2cd929817c3fd7ee78483ae5c8348..de12dcc5e2963e8dd4150e14f4d3611e7d7ded5f 100644 --- a/soi/tests/unit/__init__.py +++ b/soi/tests/__init__.py @@ -12,31 +12,3 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -This it the entry point for paste deploy . - -Paste config file needs to point to egg::: - -use = egg:snfOCCI#sample_app - -sample_app entry point is defined in setup.py: - -entry_points=''' -[paste.app_factory] -sample_app = soi:main -''', - -which point to this function call (:function). -""" - -from soi import wsgi -from soi import synnefo - -# W0613:unused args -# pylint: disable=W0613 - - -def main(global_config, **settings): - """This is the entry point for paste into the Synnefo OCCI Interface""" - factory = wsgi.SNFOCCIMiddleware.factory({}) - return factory(synnefo.call_kamaki) diff --git a/soi/tests/fakes.py b/soi/tests/fakes.py new file mode 100644 index 0000000000000000000000000000000000000000..2b34cdd5d365d1445da72548acb725ff62836777 --- /dev/null +++ b/soi/tests/fakes.py @@ -0,0 +1,31 @@ +# Copyright (C) 2016 GRNET S.A. +# +# 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 + + +class FakeReq: + """use it for testing""" + def __init__(self, *args, **kwargs): + self.environ = dict() + + def get_response(self, *args, **kwargs): + """Don't do enything""" + + +class DummyClass: + """use it for testing""" + def app(self, *args, **kwargs): + """inner app""" + + def get_from_response(self, *args, **kwargs): + """Don't do enything""" diff --git a/soi/tests/unit/compute.py b/soi/tests/unit/compute.py new file mode 100644 index 0000000000000000000000000000000000000000..e368872a621abbbeac29b5817eb02ebb6c66910d --- /dev/null +++ b/soi/tests/unit/compute.py @@ -0,0 +1,328 @@ +# Copyright (C) 2016 GRNET S.A. +# +# 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 + +from soi.tests import fakes +from soi import compute +from mock import patch + + +@patch('soi.tests.fakes.DummyClass.get_from_response', return_value='g f r') +@patch('soi.tests.fakes.FakeReq.get_response', return_value='my response') +def test_snf_index(gr, gfr): + """Test snf_index""" + cls, req = fakes.DummyClass(), fakes.FakeReq() + r = compute.snf_index(cls, req) + assert r == 'g f r' + assert req.environ == dict( + service_type='compute', + method_name='servers_get') + gr.assert_called_once_with(cls.app) + gfr.assert_called_once_with('my response', 'servers', []) + + +@patch('soi.tests.fakes.DummyClass.get_from_response', return_value='g f r') +@patch('soi.tests.fakes.FakeReq.get_response', return_value='my response') +def test_snf_get_flavors(gr, gfr): + """Test snf_get_flavors""" + cls, req = fakes.DummyClass(), fakes.FakeReq() + r = compute.snf_get_flavors(cls, req) + assert r == 'g f r' + assert req.environ == dict( + service_type='compute', + method_name='flavors_get', + kwargs=dict(detail=True)) + gr.assert_called_once_with(cls.app) + gfr.assert_called_once_with('my response', 'flavors', []) + + +@patch('soi.tests.fakes.DummyClass.get_from_response', return_value='g f r') +@patch('soi.tests.fakes.FakeReq.get_response', return_value='my response') +def test_snf_get_images(gr, gfr): + """Test snf_get_images""" + cls, req = fakes.DummyClass(), fakes.FakeReq() + r = compute.snf_get_images(cls, req) + assert r == 'g f r' + assert req.environ == dict( + service_type='compute', + method_name='images_get', + kwargs=dict(detail=True)) + gr.assert_called_once_with(cls.app) + gfr.assert_called_once_with('my response', 'images', []) + + +def test__openstackify_addresses(): + """Test _openstackify_addresses""" + addresses = { + '12345': [{ + 'version': '4', + 'addr': '123.45.67.89', + 'OS-EXT-IPS:type': 'floating', + }, ], + '67890': [{ + 'version': '6', + 'addr': '2001:123:5gf6:6789:a800:ff:dast:434d', + 'OS-EXT-IPS:type': 'fixed', + }, ], + } + attachments = [ + { + 'network_id': '12345', + 'mac_address': 'a mac address', + 'id': 'an attachment id', + 'firewallProfile': 'DISABLED', + 'OS-EXT-IPS:type': 'floating', + 'ipv4': '123.45.67.89', + 'ipv6': '', + }, + { + 'network_id': '67890', + 'mac_address': 'another mac address', + 'id': 'another attachment id', + 'firewallProfile': 'DISABLED', + 'OS-EXT-IPS:type': 'fixed', + 'ipv4': '', + 'ipv6': '2001:123:5gf6:6789:a800:ff:dast:434d', + }, + ] + expected_addresses = { + '12345': [{ + 'version': '4', + 'addr': '123.45.67.89', + 'OS-EXT-IPS:type': 'floating', + 'net_id': '12345', + 'OS-EXT-IPS-MAC:mac_addr': 'a mac address', + }, ], + '67890': [{ + 'version': '6', + 'addr': '2001:123:5gf6:6789:a800:ff:dast:434d', + 'OS-EXT-IPS:type': 'fixed', + 'net_id': '67890', + 'OS-EXT-IPS-MAC:mac_addr': 'another mac address', + }, ], + } + compute._openstackify_addresses(addresses, attachments) + assert addresses == expected_addresses + + +_response = { + 'addresses': 'some addresses', 'attachments': 'some attachments' +} + + +@patch('soi.compute._openstackify_addresses') +@patch('soi.tests.fakes.DummyClass.get_from_response', return_value=_response) +@patch('soi.tests.fakes.FakeReq.get_response', return_value='my response') +def test_snf_get_server(gr, gfr, _oa): + """Test snf_get_server""" + cls, req = fakes.DummyClass(), fakes.FakeReq() + r = compute.snf_get_server(cls, req, 'my server id') + assert r == _response + assert req.environ == dict( + service_type='compute', + method_name='servers_get', + kwargs=dict(server_id='my server id')) + gr.assert_called_once_with(cls.app) + gfr.assert_called_once_with('my response', 'server', {}) + _oa.assert_called_once_with( + _response['addresses'], _response['attachments']) + + +@patch('soi.tests.fakes.DummyClass.get_from_response', return_value='g f r') +@patch('soi.tests.fakes.FakeReq.get_response', return_value='my response') +def test_snf_get_flavor(gr, gfr): + """Test snf_get_flavor""" + cls, req = fakes.DummyClass(), fakes.FakeReq() + r = compute.snf_get_flavor(cls, req, 'my flavor id') + assert r == 'g f r' + assert req.environ == dict( + service_type='compute', + method_name='flavors_get', + kwargs=dict(flavor_id='my flavor id')) + gr.assert_called_once_with(cls.app) + gfr.assert_called_once_with('my response', 'flavor', {}) + + +@patch('soi.tests.fakes.DummyClass.get_from_response', return_value='g f r') +@patch('soi.tests.fakes.FakeReq.get_response', return_value='my response') +def test_snf_get_image(gr, gfr): + """Test snf_get_image""" + cls, req = fakes.DummyClass(), fakes.FakeReq() + r = compute.snf_get_image(cls, req, 'my image id') + assert r == 'g f r' + assert req.environ == dict( + service_type='compute', + method_name='images_get', + kwargs=dict(image_id='my image id')) + gr.assert_called_once_with(cls.app) + gfr.assert_called_once_with('my response', 'image', {}) + + +@patch('soi.tests.fakes.DummyClass.get_from_response', return_value='g f r') +@patch('soi.tests.fakes.FakeReq.get_response', return_value='my response') +def test_snf_get_server_volume_links(gr, gfr): + """Test snf_get_server_volume_links""" + cls, req = fakes.DummyClass(), fakes.FakeReq() + r = compute.snf_get_server_volumes_link(cls, req, 'my server id') + assert r == 'g f r' + assert req.environ == dict( + service_type='compute', + method_name='volume_attachment_get', + kwargs=dict(server_id='my server id')) + gr.assert_called_once_with(cls.app) + gfr.assert_called_once_with('my response', 'volumeAttachments', []) + + +def test__openstackify_net_attachments(): + """Test _openstackify_net_attachments""" + input_ = [ + { + 'network_id': '12345', + 'mac_address': 'a mac address', + 'id': 'an attachment id', + 'firewallProfile': 'DISABLED', + 'OS-EXT-IPS:type': 'floating', + 'ipv4': '123.45.67.89', + 'ipv6': '', + }, + { + 'network_id': '67890', + 'mac_address': 'another mac address', + 'id': 'another attachment id', + 'firewallProfile': 'DISABLED', + 'OS-EXT-IPS:type': 'fixed', + 'ipv4': '', + 'ipv6': '2001:123:5gf6:6789:a800:ff:dast:434d', + }, + ] + expected_output = [ + { + 'network_id': '12345', + 'net_id': '12345', + 'mac_address': 'a mac address', + 'mac_addr': 'a mac address', + 'id': 'an attachment id', + 'port_id': 'an attachment id', + 'firewallProfile': 'DISABLED', + 'OS-EXT-IPS:type': 'floating', + 'ipv4': '123.45.67.89', + 'ipv6': '', + }, + { + 'network_id': '67890', + 'net_id': '67890', + 'mac_address': 'another mac address', + 'mac_addr': 'another mac address', + 'id': 'another attachment id', + 'port_id': 'another attachment id', + 'firewallProfile': 'DISABLED', + 'OS-EXT-IPS:type': 'fixed', + 'ipv4': '', + 'ipv6': '2001:123:5gf6:6789:a800:ff:dast:434d', + 'fixed_ips': { + 'ip_address': '2001:123:5gf6:6789:a800:ff:dast:434d'}, + }, + ] + compute._openstackify_net_attachments(input_) + assert input_ == expected_output + + +@patch('soi.compute._openstackify_net_attachments') +@patch('soi.tests.fakes.DummyClass.get_from_response', return_value='g f r') +@patch('soi.tests.fakes.FakeReq.get_response', return_value='my response') +def test_snf_get_server_net_attachments(gr, gfr, _ona): + """Test snf_get_server_net_attachments""" + cls, req = fakes.DummyClass(), fakes.FakeReq() + r = compute.snf_get_server_net_attachments(cls, req, 'my server id') + assert r == 'g f r' + assert req.environ == dict( + service_type='compute', + method_name='servers_ips_get', + kwargs=dict(server_id='my server id')) + gr.assert_called_once_with(cls.app) + gfr.assert_called_once_with('my response', 'attachments', []) + _ona.assert_called_once_with('g f r') + + +@patch('soi.compute._openstackify_addresses') +@patch('soi.tests.fakes.DummyClass.get_from_response', return_value=_response) +@patch('soi.tests.fakes.FakeReq.get_response', return_value='my response') +def test_snf_create_server(gr, gfr, _oa): + """Test snf_create_server""" + cls, req = fakes.DummyClass(), fakes.FakeReq() + req.environ['HTTP_X_PROJECT_ID'] = 'a project id' + args = ('a name', 'an image', 'a flavor') + r = compute.snf_create_server(cls, req, *args) + assert r == _response + assert req.environ == dict( + HTTP_X_PROJECT_ID='a project id', + service_type='compute', + method_name='servers_post', + kwargs=dict(json_data=dict(server=dict( + name='a name', imageRef='an image', flavorRef='a flavor', + project='a project id')))) + gr.assert_called_once_with(cls.app) + gfr.assert_called_once_with('my response', 'server', {}) + _oa.assert_called_once_with( + _response['addresses'], _response['attachments']) + + +@patch('soi.tests.fakes.FakeReq.get_response') +def test_snf_delete_server(gr): + """Test snf_delete_server""" + cls, req = fakes.DummyClass(), fakes.FakeReq() + compute.snf_delete_server(cls, req, 'my server id') + assert req.environ == dict( + service_type='compute', + method_name='servers_delete', + kwargs=dict(server_id='my server id')) + gr.assert_called_once_with(cls.app) + + +def _test_snf_run_action(action, json_data, gr): + """used by "test_snf_run_action_* methods""" + cls, req = fakes.DummyClass(), fakes.FakeReq() + compute.snf_run_action(cls, req, action, 'my server id') + assert req.environ == dict( + service_type='compute', + method_name='servers_action_post', + kwargs=dict(server_id='my server id', json_data=json_data)) + gr.assert_called_once_with(cls.app) + + +@patch('soi.tests.fakes.FakeReq.get_response') +def test_snf_run_action_start(gr): + """Test snf_run_action start""" + _test_snf_run_action('start', {'start': {}}, gr) + + +@patch('soi.tests.fakes.FakeReq.get_response') +def test_snf_run_action_stop(gr): + """Test snf_run_action stop""" + _test_snf_run_action('stop', {'shutdown': {}}, gr) + + +@patch('soi.tests.fakes.FakeReq.get_response') +def test_snf_run_action_restart(gr): + """Test snf_run_action restart""" + _test_snf_run_action('restart', {'reboot': {'type': 'SOFT'}}, gr) + + +@patch('soi.tests.fakes.FakeReq.get_response') +def test_snf_run_action_suspend(gr): + """Test snf_run_action suspend""" + try: + _test_snf_run_action('suspend', {}, gr) + except Exception as e: + from webob.exc import HTTPNotImplemented + assert isinstance(e, HTTPNotImplemented) diff --git a/soi/tests/unit/utils.py b/soi/tests/unit/utils.py index e563ec55d2c7a0ccd86513633104df0b67ce2945..83f77ced9d9d73c17205737d73fb2b49fcfeebcf 100644 --- a/soi/tests/unit/utils.py +++ b/soi/tests/unit/utils.py @@ -14,6 +14,7 @@ from soi import utils from mock import patch +from soi.tests import fakes def test_patch_class_methods(): @@ -38,34 +39,12 @@ def test_patch_class_methods(): assert client.an_other_method('arg1', 'arg2') == 'replace arg1 arg2' -def test_empty_list_200(): +@patch('soi.tests.fakes.DummyClass.get_from_response', return_value='g f r') +@patch('soi.tests.fakes.FakeReq.get_response', return_value='my response') +def test_empty_list_200(gr, gfr): """Test the empty list method""" - class FakeReq: - """use it for testing""" - environ = dict() - - def get_response(self, *args, **kwargs): - """Don't do enything""" - - class DummyClass: - """use it for testing""" - def app(self, *args, **kwargs): - """inner app""" - - def get_from_response(self, *args, **kwargs): - """Don't do enything""" - - setattr(utils, 'FakeReq', FakeReq) - setattr(utils, 'DummyClass', DummyClass) - - with patch( - 'soi.utils.FakeReq.get_response', - return_value='my response') as gr: - with patch( - 'soi.utils.DummyClass.get_from_response', - return_value='get from response') as gfr: - cls, req = DummyClass(), FakeReq() - r = utils.empty_list_200(cls, req) - assert r == 'get from response' - gfr.assert_called_once_with('my response', 'empty list', []) - gr.assert_called_once_with(cls.app) + cls, req = fakes.DummyClass(), fakes.FakeReq() + r = utils.empty_list_200(cls, req) + assert r == 'g f r' + gfr.assert_called_once_with('my response', 'empty list', []) + gr.assert_called_once_with(cls.app)