From a3e60fac05dd975addf25562acc185a0584906a3 Mon Sep 17 00:00:00 2001 From: Victor Morales Date: Mon, 25 Sep 2017 21:41:56 -0700 Subject: Create UTs for Server APIView It was created the Unit tests that covers some of the functions exposed by the API of Servers. Change-Id: I60f9045dab133c59c62755d8564bdad2b6a02f26 Signed-off-by: Victor Morales Issue-Id: MULTICLOUD-83 --- newton/newton/requests/tests/test_base.py | 78 ++++++++ newton/newton/requests/tests/test_server.py | 285 ++++++++++++++++++++++++++++ newton/newton/requests/views/server.py | 86 ++++----- 3 files changed, 404 insertions(+), 45 deletions(-) create mode 100644 newton/newton/requests/tests/test_base.py create mode 100644 newton/newton/requests/tests/test_server.py diff --git a/newton/newton/requests/tests/test_base.py b/newton/newton/requests/tests/test_base.py new file mode 100644 index 00000000..fa72672d --- /dev/null +++ b/newton/newton/requests/tests/test_base.py @@ -0,0 +1,78 @@ +# Copyright (c) 2017 Intel Corporation, Inc. +# +# 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 mock +from rest_framework import status +import unittest + +from django.test import Client + +MOCK_TOKEN_ID = "1a62b3971d774404a504c5d9a3e506e3" + +MOCK_VIM_INFO = { + "createTime": "2017-04-01 02:22:27", + "domain": "Default", + "name": "TiS_R4", + "password": "admin", + "tenant": "admin", + "type": "openstack", + "url": "http://128.224.180.14:5000/v3", + "userName": "admin", + "vendor": "WindRiver", + "version": "newton", + "vimId": "windriver-hudson-dc_RegionOne", + 'cloud_owner': 'windriver-hudson-dc', + 'cloud_region_id': 'RegionOne', + 'cloud_extra_info': '', + 'cloud_epa_caps': '{"huge_page":"true","cpu_pinning":"true",\ + "cpu_thread_policy":"true","numa_aware":"true","sriov":"true",\ + "dpdk_vswitch":"true","rdt":"false","numa_locality_pci":"true"}', + 'insecure': 'True', +} + +class MockResponse(object): + status_code = status.HTTP_200_OK + content = '' + + def json(self): + pass + + +def get_mock_session(http_actions, response): + mock_session_specs = http_actions + mock_session = mock.Mock( + name='mock_session',spec=mock_session_specs) + mock_response_obj = mock.Mock(spec=MockResponse) + mock_response_obj.status_code = status.HTTP_200_OK + mock_response_obj.content = response + mock_response_obj.json.return_value = response + for action in http_actions: + if action == "get": + mock_session.get.return_value = mock_response_obj + if action == "post": + mock_session.post.return_value = mock_response_obj + if action == "put": + mock_session.put.return_value = mock_response_obj + if action == "delete": + mock_session.delete.return_value = mock_response_obj + if action == "head": + mock_session.head.return_value = mock_response_obj + + return mock_session + + +class TestRequest(unittest.TestCase): + + def setUp(self): + self.client = Client() diff --git a/newton/newton/requests/tests/test_server.py b/newton/newton/requests/tests/test_server.py new file mode 100644 index 00000000..c0640b4b --- /dev/null +++ b/newton/newton/requests/tests/test_server.py @@ -0,0 +1,285 @@ +# Copyright (c) 2017 Intel Corporation, Inc. +# +# 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 mock +from rest_framework import status +import six + +from newton.requests.tests import test_base +from newton.requests.views.util import VimDriverUtils + +MOCK_GET_SERVERS_RESPONSE = { + "servers": [ + { + "name": "compute_1", + "id": "1" + }, + { + "name": "compute_2", + "id": "2" + } + ] +} + +MOCK_GET_SERVER_RESPONSE = { + "server": + { + "name": "compute_1", + "id": "1" + } +} + +MOCK_GET_PORTS_RESPONSE = { + "interfaceAttachments": [ + { + "port_id": "1", + }, + { + "port_id": "2", + }, + ] +} + +TEST_CREATE_SERVER = { + "name": "compute_1", + "boot": { + "type": 1, + "volumeId": "1" + }, + "nicArray": [ + {"portId": "1"}, + {"portId": "2"} + ], + "contextArray": [ + {"fileName": "file", "fileData": "test_data"}, + {"fileName": "file2", "fileData": "test_data2"} + ], + # "volumeArray":[ + # {"volumeId": "volume1"}, + # ] +} + +MOCK_POST_SERVER_RESPONSE = { + "server": { + "id": 1 + } +} + +MOCK_POST_SERVER_CREATED_THREAD_RESPONSE = { + "server": { + "status": "ACTIVE" + } +} + +class TestNetwork(test_base.TestRequest): + + @mock.patch.object(VimDriverUtils, 'get_vim_info') + def test_get_servers_failure(self, mock_get_vim_info): + mock_get_vim_info.raiseError.side_effect = mock.Mock( + side_effect=Exception('Test')) + tenant_id = "fcca3cc49d5e42caae15459e27103efc" + + response = self.client.get(( + "/api/multicloud-newton/v0/windriver-hudson-dc_RegionOne/" + "" + tenant_id + "/servers"), + {}, HTTP_X_AUTH_TOKEN=test_base.MOCK_TOKEN_ID) + + self.assertEquals(status.HTTP_500_INTERNAL_SERVER_ERROR, + response.status_code) + content = response.json() + + @mock.patch.object(VimDriverUtils, 'get_session') + @mock.patch.object(VimDriverUtils, 'get_vim_info') + def test_get_list_servers(self, mock_get_vim_info, + mock_get_session): + mock_get_vim_info.return_value = test_base.MOCK_VIM_INFO + mock_get_session.side_effect = [ + test_base.get_mock_session( + ["get"], MOCK_GET_SERVERS_RESPONSE), + test_base.get_mock_session( + ["get"], MOCK_GET_PORTS_RESPONSE), + test_base.get_mock_session( + ["get"], None) + ] + tenant_id = "fcca3cc49d5e42caae15459e27103efc" + + response = self.client.get(( + "/api/multicloud-newton/v0/windriver-hudson-dc_RegionOne/" + "" + tenant_id + "/servers"), + {}, HTTP_X_AUTH_TOKEN=test_base.MOCK_TOKEN_ID) + + self.assertEquals(status.HTTP_200_OK, response.status_code) + content = response.json() + self.assertEquals( + test_base.MOCK_VIM_INFO["name"], content["vimName"]) + self.assertEquals(tenant_id, content["tenantId"]) + self.assertEquals( + test_base.MOCK_VIM_INFO["vimId"], content["vimId"]) + self.assertEquals(len(MOCK_GET_SERVERS_RESPONSE["servers"]), + len(content["servers"])) + + @mock.patch.object(VimDriverUtils, 'get_session') + @mock.patch.object(VimDriverUtils, 'get_vim_info') + def test_one_server_info(self, mock_get_vim_info, + mock_get_session): + mock_get_vim_info.return_value = test_base.MOCK_VIM_INFO + mock_get_session.side_effect = [ + test_base.get_mock_session( + ["get"], MOCK_GET_SERVER_RESPONSE.copy()), + test_base.get_mock_session( + ["get"], MOCK_GET_PORTS_RESPONSE.copy()), + ] + tenant_id = "fcca3cc49d5e42caae15459e27103efc" + server_id = "f5dc173b-6804-445a-a6d8-c705dad5b5eb" + + response = self.client.get(( + "/api/multicloud-newton/v0/windriver-hudson-dc_RegionOne/" + "" + tenant_id + "/servers/" + server_id), + {}, HTTP_X_AUTH_TOKEN=test_base.MOCK_TOKEN_ID) + + self.assertEquals(status.HTTP_200_OK, response.status_code) + content = response.json() + self.assertEquals( + test_base.MOCK_VIM_INFO["name"], content["vimName"]) + self.assertEquals(tenant_id, content["tenantId"]) + self.assertEquals( + test_base.MOCK_VIM_INFO["vimId"], content["vimId"]) + + @mock.patch.object(VimDriverUtils, 'get_session') + @mock.patch.object(VimDriverUtils, 'get_vim_info') + def test_create_existing_server(self, mock_get_vim_info, + mock_get_session): + mock_get_vim_info.return_value = test_base.MOCK_VIM_INFO + mock_get_session.side_effect = [ + test_base.get_mock_session( + ["get"], MOCK_GET_SERVERS_RESPONSE), + test_base.get_mock_session( + ["get"], None), + test_base.get_mock_session( + ["get"], None), + ] + + tenant_id = "fcca3cc49d5e42caae15459e27103efc" + server_id = "f5dc173b-6804-445a-a6d8-c705dad5b5eb" + + response = self.client.post(( + "/api/multicloud-newton/v0/windriver-hudson-dc_RegionOne/" + "" + tenant_id + "/servers/" + server_id), + data=json.dumps(TEST_CREATE_SERVER), + content_type="application/json", + HTTP_X_AUTH_TOKEN=test_base.MOCK_TOKEN_ID) + + context = response.json() + self.assertEquals(status.HTTP_200_OK,response.status_code) + self.assertIsNone(context["volumeArray"]) + self.assertIsNone(context["flavorId"]) + self.assertIsNone(context["availabilityZone"]) + self.assertEquals(TEST_CREATE_SERVER["name"], context["name"]) + self.assertEquals( + MOCK_GET_SERVERS_RESPONSE["servers"][0]["id"], + context["id"]) + self.assertIsNone(context["nicArray"]) + self.assertIsNotNone(context["boot"]) + self.assertEquals(0, context["returnCode"]) + + @mock.patch.object(VimDriverUtils, 'get_session') + def test_create_server_sucessfuly(self, mock_get_session): + VimDriverUtils.get_vim_info = mock.Mock( + return_value=test_base.MOCK_VIM_INFO) + + def side_effect(items): + def func(): + for item in items: + yield item + yield test_base.get_mock_session( + ["post"], None) + + generator = func() + + def effect(*args, **kwargs): + return six.next(generator) + + return effect + + effects = [ + test_base.get_mock_session( + ["get"], {"servers":[]}), + test_base.get_mock_session( + ["post"], MOCK_POST_SERVER_RESPONSE.copy()), + test_base.get_mock_session( + ["get"], MOCK_POST_SERVER_CREATED_THREAD_RESPONSE.copy()), + ] + mock_get_session.side_effect = side_effect(effects) + tenant_id = "fcca3cc49d5e42caae15459e27103efc" + server_id = "f5dc173b-6804-445a-a6d8-c705dad5b5eb" + + response = self.client.post(( + "/api/multicloud-newton/v0/windriver-hudson-dc_RegionOne/" + "" + tenant_id + "/servers/" + server_id), + data=json.dumps(TEST_CREATE_SERVER), + content_type="application/json", + HTTP_X_AUTH_TOKEN=test_base.MOCK_TOKEN_ID) + + context = response.json() + self.assertEquals(status.HTTP_200_OK, response.status_code) + self.assertEquals( + test_base.MOCK_VIM_INFO["vimId"], context["vimId"]) + self.assertEquals(tenant_id, context["tenantId"]) + # self.assertEquals(len(TEST_CREATE_SERVER["volumeArray"]), + # len(context['volumeArray'])) + self.assertEquals( + MOCK_POST_SERVER_RESPONSE["server"]["id"], context["id"]) + self.assertEquals(len(TEST_CREATE_SERVER["nicArray"]), + len(context["nicArray"])) + self.assertEquals( + test_base.MOCK_VIM_INFO["name"], context["vimName"]) + self.assertIsNotNone(TEST_CREATE_SERVER["boot"]) + self.assertEquals(TEST_CREATE_SERVER["boot"]["volumeId"], + context["boot"]["volumeId"]) + self.assertEquals(TEST_CREATE_SERVER["boot"]["type"], + context["boot"]["type"]) + self.assertEquals(1, context["returnCode"]) + self.assertEquals(TEST_CREATE_SERVER["name"], + context["name"]) + self.assertEquals( + len(TEST_CREATE_SERVER["contextArray"]), + len(context["contextArray"])) + + @mock.patch.object(VimDriverUtils, 'get_session') + @mock.patch.object(VimDriverUtils, 'get_vim_info') + def test_delete_existing_serever(self, mock_get_vim_info, + mock_get_session): + mock_get_vim_info.return_value = test_base.MOCK_VIM_INFO + mock_get_session.side_effect = [ + test_base.get_mock_session( + ["delete"], None), + test_base.get_mock_session( + ["get"], MOCK_GET_SERVER_RESPONSE.copy()), + test_base.get_mock_session( + ["get"], None), + ] + + tenant_id = "fcca3cc49d5e42caae15459e27103efc" + server_id = "f5dc173b-6804-445a-a6d8-c705dad5b5eb" + + response = self.client.delete(( + "/api/multicloud-newton/v0/windriver-hudson-dc_RegionOne/" + "" + tenant_id + "/servers/" + server_id), + data=json.dumps(TEST_CREATE_SERVER), + content_type="application/json", + HTTP_X_AUTH_TOKEN=test_base.MOCK_TOKEN_ID) + + self.assertEquals(status.HTTP_200_OK, response.status_code) \ No newline at end of file diff --git a/newton/newton/requests/views/server.py b/newton/newton/requests/views/server.py index 34e8b51a..97008768 100644 --- a/newton/newton/requests/views/server.py +++ b/newton/newton/requests/views/server.py @@ -11,18 +11,18 @@ # 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 logging import json -from six.moves import urllib import threading import traceback + from keystoneauth1.exceptions import HttpError from rest_framework import status from rest_framework.response import Response from rest_framework.views import APIView from newton.pub.exceptions import VimDriverNewtonException - from newton.requests.views.util import VimDriverUtils logger = logging.getLogger(__name__) @@ -32,7 +32,7 @@ running_threads = {} running_thread_lock = threading.Lock() #assume volume is attached on server creation -class serverThread (threading.Thread): +class ServerVolumeAttachThread (threading.Thread): service = {'service_type': 'compute', 'interface': 'public'} def __init__(self, vimid, tenantid, serverid, is_attach, *volumeids): @@ -48,7 +48,7 @@ class serverThread (threading.Thread): if (self.is_attach): self.attach_volume(self.vimid, self.tenantid, self.serverid, *self.volumeids) else: - elf.detach_volume(self.vimid, self.tenantid, self.serverid, *self.volumeids) + self.detach_volume(self.vimid, self.tenantid, self.serverid, *self.volumeids) logger.debug("stop server thread %s, %s, %s" % (self.vimid, self.tenantid, self.serverid)) running_thread_lock.acquire() running_threads.pop(self.serverid) @@ -123,6 +123,7 @@ class serverThread (threading.Thread): logger.debug("Failed to detach_volume:%s" % str(e)) return None + class Servers(APIView): service = {'service_type': 'compute', 'interface': 'public'} @@ -135,16 +136,16 @@ class Servers(APIView): ("os-extended-volumes:volumes_attached", "volumeArray"), ] - def attachVolume(self, vimid, tenantid, serverId, *volumeIds): + def _attachVolume(self, vimid, tenantid, serverId, *volumeIds): #has to be async mode to wait server is ready to attach volume logger.debug("launch thread to attach volume: %s" % serverId) - tmp_thread = serverThread(vimid, tenantid, serverId, True, *volumeIds) + tmp_thread = ServerVolumeAttachThread(vimid, tenantid, serverId, True, *volumeIds) running_thread_lock.acquire() running_threads[serverId] = tmp_thread running_thread_lock.release() tmp_thread.start() - def dettachVolume(self, vimid, tenantid, serverId, *volumeIds): + def _dettach_volume(self, vimid, tenantid, serverId, *volumeIds): # assume attachment id is the same as volume id vim = VimDriverUtils.get_vim_info(vimid) sess = VimDriverUtils.get_session(vim, tenantid) @@ -158,21 +159,19 @@ class Servers(APIView): "Accept": "application/json"}) logger.debug("Servers--dettachVolume resp status::>%s" % resp.status_code) - - def convertMetadata(self, metadata, mata_data, reverse=False): - if reverse == False: + def _convert_metadata(self, metadata, metadata_output, reverse=True): + if not reverse: # from extraSpecs to extra_specs for spec in metadata: - mata_data[spec['keyName']] = spec['value'] + metadata_output[spec['keyName']] = spec['value'] else: - for k, v in mata_data.items(): + for k, v in metadata_output.items(): spec = {} spec['keyName'] = k spec['value'] = v metadata.append(spec) - - def convert_resp(self, server): + def _convert_resp(self, server): #convert volumeArray volumeArray = server.pop("volumeArray", None) tmpVolumeArray = [] @@ -203,7 +202,7 @@ class Servers(APIView): try: # prepare request resource to vim instance query = VimDriverUtils.get_query_part(request) - content, status_code = self.get_servers(query, vimid, tenantid, serverid) + content, status_code = self._get_servers(query, vimid, tenantid, serverid) return Response(data=content, status=status_code) except VimDriverNewtonException as e: return Response(data={'error': e.content}, status=e.status_code) @@ -215,7 +214,7 @@ class Servers(APIView): return Response(data={'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - def get_ports(self, vimid="", tenantid="", serverid=None): + def _get_ports(self, vimid="", tenantid="", serverid=None): # query attached ports vim = VimDriverUtils.get_vim_info(vimid) sess = VimDriverUtils.get_session(vim, tenantid) @@ -224,10 +223,9 @@ class Servers(APIView): ports = resp.json() if ports and ports["interfaceAttachments"] and len(ports["interfaceAttachments"]) > 0: return [{"portId":port["port_id"]} for port in ports["interfaceAttachments"]] - else: - return None + return None - def get_servers(self, query="", vimid="", tenantid="", serverid=None): + def _get_servers(self, query="", vimid="", tenantid="", serverid=None): logger.debug("Servers--get_servers::> %s,%s" % (tenantid, serverid)) # prepare request resource to vim instance @@ -256,12 +254,12 @@ class Servers(APIView): metadata = server.pop("metadata", None) if metadata: meta_data = [] - self.convertMetadata(metadata, meta_data, True) + self._convert_metadata(metadata, meta_data, False) server["metadata"] = meta_data VimDriverUtils.replace_key_by_mapping(server, self.keys_mapping) - self.convert_resp(server) - server["nicArray"] = self.get_ports(vimid, tenantid, server["id"]) + self._convert_resp(server) + server["nicArray"] = self._get_ports(vimid, tenantid, server["id"]) else: # convert the key naming in the server specified by id @@ -269,12 +267,12 @@ class Servers(APIView): metadata = server.pop("metadata", None) if metadata: meta_data = [] - self.convertMetadata(metadata, meta_data, True) + self._convert_metadata(metadata, meta_data) server["metadata"] = meta_data VimDriverUtils.replace_key_by_mapping(server, self.keys_mapping) - self.convert_resp(server) - server["nicArray"] = self.get_ports(vimid, tenantid, serverid) + self._convert_resp(server) + server["nicArray"] = self._get_ports(vimid, tenantid, serverid) content.update(server) return content, resp.status_code @@ -285,14 +283,14 @@ class Servers(APIView): # check if created already: check name servername = request.data["name"] query = "name=%s" % servername - content, status_code = self.get_servers(query, vimid, tenantid) + content, status_code = self._get_servers(query, vimid, tenantid) existed = False - if status_code == 200: + if status_code == status.HTTP_200_OK: for server in content["servers"]: if server["name"] == request.data["name"]: existed = True break - if existed == True and server: + if existed and server: vim_dict = { "returnCode": 0, } @@ -301,9 +299,6 @@ class Servers(APIView): # prepare request resource to vim instance req_resouce = "servers" - - vim = VimDriverUtils.get_vim_info(vimid) - sess = VimDriverUtils.get_session(vim, tenantid) server = request.data # convert parameters @@ -326,20 +321,20 @@ class Servers(APIView): if not nicarray: return Response(data={'error': "missing nicArray paramters"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - else: - networks = [] - for nic in nicarray: - networks.append({"port": nic["portId"]}) - if len(networks) > 0: - server["networks"] = networks + networks = [] + for nic in nicarray: + networks.append({"port": nic["portId"]}) + if len(networks) > 0: + server["networks"] = networks meta_data = server.pop("metadata", None) if meta_data: metadata = {} - self.convertMetadata(metadata, meta_data, False) + self._convert_metadata(metadata, meta_data, False) server["metadata"] = metadata contextarray = server.pop("contextArray", None) + volumearray = server.pop("volumeArray", None) if contextarray: # now set "contextArray" array personalities = [] @@ -348,11 +343,12 @@ class Servers(APIView): if len(personalities) > 0: server["personality"] = personalities - volumearray = server.pop("volumeArray", None) - VimDriverUtils.replace_key_by_mapping(server, self.keys_mapping, True) req_body = json.JSONEncoder().encode({"server": server}) + + vim = VimDriverUtils.get_vim_info(vimid) + sess = VimDriverUtils.get_session(vim, tenantid) resp = sess.post(req_resouce, data=req_body, endpoint_filter=self.service, headers={"Content-Type": "application/json", @@ -361,16 +357,16 @@ class Servers(APIView): resp_body = resp.json().pop("server", None) logger.debug("Servers--post status::>%s, %s" % (resp_body["id"], resp.status_code)) - if resp.status_code == 200 or resp.status_code == 201 or resp.status_code == 202 : + if resp.status_code in [status.HTTP_200_OK, status.HTTP_201_CREATED, status.HTTP_202_ACCEPTED]: if volumearray and len(volumearray) > 0: # server is created, now attach volumes volumeIds = [extraVolume["volumeId"] for extraVolume in volumearray] - self.attachVolume(vimid, tenantid, resp_body["id"], *volumeIds) + self._attachVolume(vimid, tenantid, resp_body["id"], *volumeIds) metadata = resp_body.pop("metadata", None) if metadata: meta_data = [] - self.convertMetadata(metadata, meta_data, True) + self._convert_metadata(metadata, meta_data) resp_body["metadata"] = meta_data VimDriverUtils.replace_key_by_mapping(resp_body, self.keys_mapping) @@ -405,11 +401,11 @@ class Servers(APIView): sess = VimDriverUtils.get_session(vim, tenantid) #check and dettach them if volumes attached to server - server, status_code = self.get_servers("", vimid, tenantid, serverid) + server, status_code = self._get_servers("", vimid, tenantid, serverid) volumearray = server.pop("volumeArray", None) if volumearray and len(volumearray) > 0: volumeIds = [extraVolume["volumeId"] for extraVolume in volumearray] - self.dettachVolume(vimid, tenantid, serverid, *volumeIds) + self._dettach_volume(vimid, tenantid, serverid, *volumeIds) #delete server now req_resouce = "servers" -- cgit 1.2.3-korg