Add resource for pike 39/61939/1
authorHuang Haibin <haibin.huang@intel.com>
Wed, 22 Aug 2018 11:21:10 +0000 (19:21 +0800)
committerHuang Haibin <haibin.huang@intel.com>
Thu, 23 Aug 2018 02:20:21 +0000 (10:20 +0800)
Change-Id: I67ce00b96a69c7952f1d43516208c5fe68025421
Issue-ID: MULTICLOUD-276
Signed-off-by: Huang Haibin <haibin.huang@intel.com>
pike/pike/resource/__init__.py [new file with mode: 0644]
pike/pike/resource/tests/__init__.py [new file with mode: 0644]
pike/pike/resource/tests/test_capacity.py [new file with mode: 0644]
pike/pike/resource/views/__init__.py [new file with mode: 0644]
pike/pike/resource/views/capacity.py [new file with mode: 0644]
pike/pike/urls.py

diff --git a/pike/pike/resource/__init__.py b/pike/pike/resource/__init__.py
new file mode 100644 (file)
index 0000000..741e0af
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright (c) 2018 Intel Corporation.
+#
+# 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.
+
diff --git a/pike/pike/resource/tests/__init__.py b/pike/pike/resource/tests/__init__.py
new file mode 100644 (file)
index 0000000..741e0af
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright (c) 2018 Intel Corporation.
+#
+# 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.
+
diff --git a/pike/pike/resource/tests/test_capacity.py b/pike/pike/resource/tests/test_capacity.py
new file mode 100644 (file)
index 0000000..23ed327
--- /dev/null
@@ -0,0 +1,275 @@
+# Copyright (c) 2018 Intel Corporation.
+#
+# 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
+import json
+
+from rest_framework import status
+
+from common.utils import restcall
+from newton_base.tests import mock_info
+from newton_base.tests import test_base
+from newton_base.util import VimDriverUtils
+
+MOCK_GET_TENANT_LIMIT_RESPONSE = {
+   "limits" : {
+      "rate" : [],
+      "absolute" : {
+         "maxTotalRAMSize" : 128*1024,
+         "totalRAMUsed" : 8*1024,
+         "totalCoresUsed" : 4,
+         "maxTotalCores" : 20,
+      }
+   }
+}
+
+MOCK_GET_HYPER_STATATICS_RESPONSE = {
+   "hypervisor_statistics" : {
+      "vcpus_used" : 4,
+      "free_ram_mb" : 120*1024,
+      "vcpus" : 10,
+      "free_disk_gb" : 300
+   }
+}
+
+MOCK_GET_STORAGE_RESPONSE_OOS = {
+    "limits": {
+        "rate": [],
+        "absolute": {
+            "totalGigabytesUsed": 498,
+            "maxTotalVolumeGigabytes": 500,
+        }
+    }
+}
+
+MOCK_GET_TENANT_LIMIT_RESPONSE_OUTOFRAM = {
+    "limits": {
+        "rate": [],
+        "absolute": {
+            "maxTotalRAMSize": 128 * 1024,
+            "totalRAMUsed": 1 * 1024,
+            "totalCoresUsed": 4,
+            "maxTotalCores": 20,
+        }
+    }
+}
+
+MOCK_GET_HYPER_STATATICS_RESPONSE_OUTOFVCPU = {
+    "hypervisor_statistics": {
+        "vcpus_used": 9,
+        "free_ram_mb": 120 * 1024,
+        "vcpus": 10,
+        "free_disk_gb": 300
+    }
+}
+
+MOCK_GET_HYPER_STATATICS_RESPONSE_OUTOFSTORAGE = {
+   "hypervisor_statistics" : {
+      "vcpus_used" : 4,
+      "free_ram_mb" : 120*1024,
+      "vcpus" : 10,
+      "free_disk_gb" : 3
+   }
+}
+
+MOCK_GET_HYPER_STATATICS_RESPONSE_OUTOFRAM = {
+   "hypervisor_statistics" : {
+      "vcpus_used" : 4,
+      "free_ram_mb" : 1*1024,
+      "vcpus" : 10,
+      "free_disk_gb" : 300
+   }
+}
+
+MOCK_GET_STORAGE_RESPONSE = {
+   "limits" : {
+      "rate" : [],
+      "absolute" : {
+         "totalGigabytesUsed" : 200,
+         "maxTotalVolumeGigabytes" : 500,
+      }
+   }
+}
+
+TEST_REQ_SUCCESS_SOURCE = {
+    "vCPU": "4",
+    "Memory": "4096",
+    "Storage": "200"
+}
+
+TEST_REQ_FAILED_SOURCE = {
+    "vCPU": "17",
+    "Memory": "4096",
+    "Storage": "200"
+}
+
+class TestCapacity(test_base.TestRequest):
+    def setUp(self):
+        super(TestCapacity, self).setUp()
+
+    def _get_mock_response(self, return_value=None):
+        mock_response = mock.Mock(spec=test_base.MockResponse)
+        mock_response.status_code = status.HTTP_200_OK
+        mock_response.json.return_value = return_value
+        return mock_response
+
+    @mock.patch.object(VimDriverUtils, 'get_session')
+    @mock.patch.object(VimDriverUtils, 'get_vim_info')
+    def test_capacity_check_success(self, mock_get_vim_info, mock_get_session):
+        mock_get_vim_info.return_value = mock_info.MOCK_VIM_INFO
+        mock_get_session.return_value = test_base.get_mock_session(
+            ["get"], {
+                "side_effect": [
+                    self._get_mock_response(MOCK_GET_TENANT_LIMIT_RESPONSE),
+                    self._get_mock_response(MOCK_GET_HYPER_STATATICS_RESPONSE),
+                    self._get_mock_response(MOCK_GET_STORAGE_RESPONSE),
+                ]
+            })
+
+        response = self.client.post(
+            "/api/multicloud-pike/v0/windriver-hudson-dc_RegionOne/capacity_check",
+            TEST_REQ_SUCCESS_SOURCE,
+            HTTP_X_AUTH_TOKEN=mock_info.MOCK_TOKEN_ID)
+
+        self.assertEquals(status.HTTP_200_OK, response.status_code)
+        self.assertEqual({"result": True}, response.data)
+
+    @mock.patch.object(VimDriverUtils, 'get_session')
+    @mock.patch.object(VimDriverUtils, 'get_vim_info')
+    def test_capacity_check_nova_limits_failed(self, mock_get_vim_info, mock_get_session):
+        mock_get_vim_info.return_value = mock_info.MOCK_VIM_INFO
+        mock_get_session.return_value = test_base.get_mock_session(
+            ["get"], {
+                "side_effect": [
+                    self._get_mock_response(MOCK_GET_TENANT_LIMIT_RESPONSE),
+                    self._get_mock_response(MOCK_GET_HYPER_STATATICS_RESPONSE),
+                    self._get_mock_response(MOCK_GET_STORAGE_RESPONSE),
+                ]
+            })
+
+        response = self.client.post(
+            "/api/multicloud-pike/v0/windriver-hudson-dc_RegionOne/capacity_check",
+            TEST_REQ_FAILED_SOURCE,
+            HTTP_X_AUTH_TOKEN=mock_info.MOCK_TOKEN_ID)
+
+        self.assertEquals(status.HTTP_200_OK, response.status_code)
+        self.assertEqual({"result": False}, response.data)
+
+    @mock.patch.object(VimDriverUtils, 'get_session')
+    @mock.patch.object(VimDriverUtils, 'get_vim_info')
+    def test_capacity_check_nova_hypervisor_outofram(self, mock_get_vim_info, mock_get_session):
+        mock_get_vim_info.return_value = mock_info.MOCK_VIM_INFO
+        mock_get_session.return_value = test_base.get_mock_session(
+            ["get"], {
+                "side_effect": [
+                    self._get_mock_response(MOCK_GET_TENANT_LIMIT_RESPONSE),
+                    self._get_mock_response(MOCK_GET_HYPER_STATATICS_RESPONSE_OUTOFRAM),
+                    self._get_mock_response(MOCK_GET_STORAGE_RESPONSE),
+                ]
+            })
+
+        response = self.client.post(
+            "/api/multicloud-pike/v0/windriver-hudson-dc_RegionOne/capacity_check",
+            data=json.dumps(TEST_REQ_SUCCESS_SOURCE),
+            content_type='application/json',
+            HTTP_X_AUTH_TOKEN=mock_info.MOCK_TOKEN_ID)
+
+        self.assertEquals(status.HTTP_200_OK, response.status_code)
+        self.assertEqual({"result": False}, response.data)
+
+    @mock.patch.object(VimDriverUtils, 'get_session')
+    @mock.patch.object(VimDriverUtils, 'get_vim_info')
+    def test_capacity_check_nova_hypervisor_outofstorage(self, mock_get_vim_info, mock_get_session):
+        mock_get_vim_info.return_value = mock_info.MOCK_VIM_INFO
+        mock_get_session.return_value = test_base.get_mock_session(
+            ["get"], {
+                "side_effect": [
+                    self._get_mock_response(MOCK_GET_TENANT_LIMIT_RESPONSE),
+                    self._get_mock_response(MOCK_GET_HYPER_STATATICS_RESPONSE_OUTOFSTORAGE),
+                    self._get_mock_response(MOCK_GET_STORAGE_RESPONSE),
+                ]
+            })
+
+        response = self.client.post(
+            "/api/multicloud-pike/v0/windriver-hudson-dc_RegionOne/capacity_check",
+            data=json.dumps(TEST_REQ_SUCCESS_SOURCE),
+            content_type='application/json',
+            HTTP_X_AUTH_TOKEN=mock_info.MOCK_TOKEN_ID)
+
+        self.assertEquals(status.HTTP_200_OK, response.status_code)
+        self.assertEqual({"result": False}, response.data)
+
+    @mock.patch.object(VimDriverUtils, 'get_session')
+    @mock.patch.object(VimDriverUtils, 'get_vim_info')
+    def test_capacity_check_nova_hypervisor_outofvcpu(self, mock_get_vim_info, mock_get_session):
+        mock_get_vim_info.return_value = mock_info.MOCK_VIM_INFO
+        mock_get_session.return_value = test_base.get_mock_session(
+            ["get"], {
+                "side_effect": [
+                    self._get_mock_response(MOCK_GET_TENANT_LIMIT_RESPONSE),
+                    self._get_mock_response(MOCK_GET_HYPER_STATATICS_RESPONSE_OUTOFVCPU),
+                    self._get_mock_response(MOCK_GET_STORAGE_RESPONSE),
+                ]
+            })
+
+        response = self.client.post(
+            "/api/multicloud-pike/v0/windriver-hudson-dc_RegionOne/capacity_check",
+            data=json.dumps(TEST_REQ_SUCCESS_SOURCE),
+            content_type='application/json',
+            HTTP_X_AUTH_TOKEN=mock_info.MOCK_TOKEN_ID)
+
+        self.assertEquals(status.HTTP_200_OK, response.status_code)
+        self.assertEqual({"result": False}, response.data)
+
+    @mock.patch.object(VimDriverUtils, 'get_session')
+    @mock.patch.object(VimDriverUtils, 'get_vim_info')
+    def test_capacity_check_nova_limits_outofram(self, mock_get_vim_info, mock_get_session):
+        mock_get_vim_info.return_value = mock_info.MOCK_VIM_INFO
+        mock_get_session.return_value = test_base.get_mock_session(
+            ["get"], {
+                "side_effect": [
+                    self._get_mock_response(MOCK_GET_TENANT_LIMIT_RESPONSE_OUTOFRAM),
+                    self._get_mock_response(MOCK_GET_HYPER_STATATICS_RESPONSE),
+                    self._get_mock_response(MOCK_GET_STORAGE_RESPONSE),
+                ]
+            })
+
+        response = self.client.post(
+            "/api/multicloud-pike/v0/windriver-hudson-dc_RegionOne/capacity_check",
+            data=json.dumps(TEST_REQ_SUCCESS_SOURCE),
+            content_type='application/json',
+            HTTP_X_AUTH_TOKEN=mock_info.MOCK_TOKEN_ID)
+
+    @mock.patch.object(VimDriverUtils, 'get_session')
+    @mock.patch.object(VimDriverUtils, 'get_vim_info')
+    def test_capacity_check_volume_limits_outofstorage(self, mock_get_vim_info, mock_get_session):
+        mock_get_vim_info.return_value = mock_info.MOCK_VIM_INFO
+        mock_get_session.return_value = test_base.get_mock_session(
+            ["get"], {
+                "side_effect": [
+                    self._get_mock_response(MOCK_GET_TENANT_LIMIT_RESPONSE),
+                    self._get_mock_response(MOCK_GET_HYPER_STATATICS_RESPONSE),
+                    self._get_mock_response(MOCK_GET_STORAGE_RESPONSE_OOS),
+                ]
+            })
+
+        response = self.client.post(
+            "/api/multicloud-pike/v0/windriver-hudson-dc_RegionOne/capacity_check",
+            data=json.dumps(TEST_REQ_SUCCESS_SOURCE),
+            content_type='application/json',
+            HTTP_X_AUTH_TOKEN=mock_info.MOCK_TOKEN_ID)
+
+        self.assertEquals(status.HTTP_200_OK, response.status_code)
+        self.assertEqual({"result": False}, response.data)
+
diff --git a/pike/pike/resource/views/__init__.py b/pike/pike/resource/views/__init__.py
new file mode 100644 (file)
index 0000000..741e0af
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright (c) 2018 Intel Corporation.
+#
+# 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.
+
diff --git a/pike/pike/resource/views/capacity.py b/pike/pike/resource/views/capacity.py
new file mode 100644 (file)
index 0000000..7f7b9dd
--- /dev/null
@@ -0,0 +1,136 @@
+# Copyright (c) 2018 Intel Corporation.
+#
+# 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 logging
+import json
+import traceback
+
+from rest_framework import status
+
+from django.conf import settings
+from common.exceptions import VimDriverNewtonException
+from newton_base.util import VimDriverUtils
+
+from keystoneauth1.exceptions import HttpError
+from rest_framework import status
+from rest_framework.response import Response
+from rest_framework.views import APIView
+from common.msapi import extsys
+
+
+logger = logging.getLogger(__name__)
+
+
+class CapacityCheck(APIView):
+
+    def __init__(self):
+        self._logger = logger
+
+    def post(self, request, vimid=""):
+        self._logger.info("CapacityCheck--post::vimid, data> %s, %s" % (vimid, request.data))
+        self._logger.debug("CapacityCheck--post::META> %s" % request.META)
+
+        hasEnoughResource = False
+        try :
+            resource_demand = request.data
+
+            tenant_name = None
+            vim = VimDriverUtils.get_vim_info(vimid)
+            sess = VimDriverUtils.get_session(vim, tenant_name)
+
+            #get token:
+            cloud_owner, regionid = extsys.decode_vim_id(vimid)
+            interface = 'public'
+            service = {'service_type': 'compute',
+                       'interface': interface,
+                       'region_id': vim['openstack_region_id']
+                           if vim.get('openstack_region_id')
+                           else vim['cloud_region_id']}
+
+
+            #get limit for this tenant
+            req_resouce = "/limits"
+            resp = sess.get(req_resouce, endpoint_filter=service)
+            content = resp.json()
+            compute_limits = content['limits']['absolute']
+
+            #get total resource of this cloud region
+            try:
+                req_resouce = "/os-hypervisors/statistics"
+                self._logger.info("check os-hypervisors statistics> URI:%s" % req_resouce)
+                resp = sess.get(req_resouce, endpoint_filter=service)
+                self._logger.info("check os-hypervisors statistics> status:%s" % resp.status_code)
+                content = resp.json()
+                hypervisor_statistics = content['hypervisor_statistics']
+                self._logger.debug("check os-hypervisors statistics> resp data:%s" % content)
+            except HttpError as e:
+                if e.http_status == status.HTTP_403_FORBIDDEN:
+                    # Due to non administrator account cannot get hypervisor data,
+                    # so construct enough resource data
+                    conVCPUS = int(resource_demand['vCPU'])
+                    conFreeRamMB = int(resource_demand['Memory'])
+                    conFreeDiskGB = int(resource_demand['Storage'])
+                    self._logger.info("Non administator forbidden to access hypervisor statistics data")
+                    hypervisor_statistics = {'vcpus_used':0, 'vcpus':conVCPUS, 'free_ram_mb':conFreeRamMB, 'free_disk_gb':conFreeDiskGB}
+                else:
+                    # non forbiden exeption will be redirected
+                    raise e
+
+            #get storage limit for this tenant
+            service['service_type'] = 'volumev2'
+            req_resouce = "/limits"
+            resp = sess.get(req_resouce, endpoint_filter=service)
+            content = resp.json()
+            storage_limits = content['limits']['absolute']
+
+            # compute actual available resource for this tenant
+            remainVCPU = compute_limits['maxTotalCores'] - compute_limits['totalCoresUsed']
+            remainHypervisorVCPU = hypervisor_statistics['vcpus'] - hypervisor_statistics['vcpus_used']
+
+            if (remainVCPU > remainHypervisorVCPU):
+                remainVCPU = remainHypervisorVCPU
+
+            remainMEM = compute_limits['maxTotalRAMSize'] - compute_limits['totalRAMUsed']
+            remainHypervisorMEM = hypervisor_statistics['free_ram_mb']
+            if remainMEM > remainHypervisorMEM:
+                remainMEM = remainHypervisorMEM
+
+            remainStorage = storage_limits['maxTotalVolumeGigabytes'] - storage_limits['totalGigabytesUsed']
+            remainHypervisorStorage = hypervisor_statistics['free_disk_gb']
+            if (remainStorage > remainHypervisorStorage):
+                remainStorage = remainHypervisorStorage
+
+            # compare resource demanded with available
+            if (int(resource_demand['vCPU']) > remainVCPU):
+                hasEnoughResource = False
+            elif (int(resource_demand['Memory']) > remainMEM):
+                hasEnoughResource = False
+            elif (int(resource_demand['Storage']) > remainStorage):
+                hasEnoughResource = False
+            else:
+                hasEnoughResource = True
+
+            return Response(data={'result': hasEnoughResource}, status=status.HTTP_200_OK)
+        except VimDriverNewtonException as e:
+            return Response(data={'result': hasEnoughResource,'error': e.content}, status=e.status_code)
+        except HttpError as e:
+            self._logger.error("HttpError: status:%s, response:%s" % (e.http_status, e.response.json()))
+            resp = e.response.json()
+            resp.update({'result': hasEnoughResource})
+            return Response(data=e.response.json(), status=e.http_status)
+        except Exception as e:
+            self._logger.error(traceback.format_exc())
+            return Response(data={'result': hasEnoughResource, 'error': str(e)},
+                            status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
index 984a99e..e49eb1a 100644 (file)
@@ -16,6 +16,7 @@ from django.conf.urls import include, url
 
 from pike.registration.views import registration
 from newton_base.openoapi import tenants
+from pike.resource.views import capacity
 
 urlpatterns = [
     url(r'^', include('pike.swagger.urls')),
@@ -28,6 +29,10 @@ urlpatterns = [
              include('pike.proxy.urls')),
     url(r'^api/multicloud-pike/v0/(?P<vimid>[0-9a-zA-Z_-]+)/tenants$',
              tenants.Tenants.as_view()),
+    url(r'^api/multicloud-pike/v0/(?P<vimid>[0-9a-zA-Z_-]+)/'
+        '(?P<tenantid>[0-9a-zA-Z_-]{20,})/', include('pike.requests.urls')),
+    url(r'^api/multicloud-pike/v0/(?P<vimid>[0-9a-zA-Z_-]+)/capacity_check/?$',
+        capacity.CapacityCheck.as_view()),
 
 ]