From a37e4fb0ff668a7f885975728bf569c2bfb62621 Mon Sep 17 00:00:00 2001 From: Lisa Zangrando Date: Fri, 30 Sep 2016 12:46:08 +0200 Subject: [PATCH] Common objects and relative test units added - the common classes are needed for a more objected oriented style - fixed pep8 Change-Id: I1a5dbb510168002c4c537beece4961741aed1d93 --- synergy_scheduler_manager/common/__init__.py | 0 .../common/block_device.py | 204 +++++++ synergy_scheduler_manager/common/compute.py | 44 ++ synergy_scheduler_manager/common/endpoint.py | 64 ++ synergy_scheduler_manager/common/flavor.py | 48 ++ .../common/hypervisor.py | 95 +++ synergy_scheduler_manager/common/priority.py | 65 +++ synergy_scheduler_manager/common/project.py | 92 +++ synergy_scheduler_manager/common/queue.py | 397 +++++++++++++ synergy_scheduler_manager/common/quota.py | 548 ++++++++++++++++++ synergy_scheduler_manager/common/request.py | 185 ++++++ synergy_scheduler_manager/common/role.py | 25 + synergy_scheduler_manager/common/server.py | 136 +++++ synergy_scheduler_manager/common/service.py | 60 ++ synergy_scheduler_manager/common/share.py | 48 ++ synergy_scheduler_manager/common/token.py | 184 ++++++ synergy_scheduler_manager/common/trust.py | 156 +++++ synergy_scheduler_manager/common/user.py | 82 +++ synergy_scheduler_manager/common/utils.py | 48 ++ .../tests/unit/test_block_device.py | 125 ++++ .../tests/unit/test_compute.py | 32 + .../tests/unit/test_endpoint.py | 44 ++ .../tests/unit/test_flavor.py | 32 + .../tests/unit/test_hypervisor.py | 54 ++ .../tests/unit/test_priority.py | 39 ++ .../tests/unit/test_project.py | 71 +++ .../tests/unit/test_queue.py | 242 ++++++++ .../tests/unit/test_quota.py | 200 +++++++ .../tests/unit/test_request.py | 431 ++++++++++++++ .../tests/unit/test_server.py | 79 +++ .../tests/unit/test_service.py | 41 ++ .../tests/unit/test_share.py | 32 + .../tests/unit/test_token.py | 186 ++++++ .../tests/unit/test_trust.py | 63 ++ .../tests/unit/test_user.py | 51 ++ 35 files changed, 4203 insertions(+) create mode 100644 synergy_scheduler_manager/common/__init__.py create mode 100644 synergy_scheduler_manager/common/block_device.py create mode 100644 synergy_scheduler_manager/common/compute.py create mode 100644 synergy_scheduler_manager/common/endpoint.py create mode 100644 synergy_scheduler_manager/common/flavor.py create mode 100644 synergy_scheduler_manager/common/hypervisor.py create mode 100644 synergy_scheduler_manager/common/priority.py create mode 100644 synergy_scheduler_manager/common/project.py create mode 100644 synergy_scheduler_manager/common/queue.py create mode 100644 synergy_scheduler_manager/common/quota.py create mode 100644 synergy_scheduler_manager/common/request.py create mode 100644 synergy_scheduler_manager/common/role.py create mode 100644 synergy_scheduler_manager/common/server.py create mode 100644 synergy_scheduler_manager/common/service.py create mode 100644 synergy_scheduler_manager/common/share.py create mode 100644 synergy_scheduler_manager/common/token.py create mode 100644 synergy_scheduler_manager/common/trust.py create mode 100644 synergy_scheduler_manager/common/user.py create mode 100644 synergy_scheduler_manager/common/utils.py create mode 100644 synergy_scheduler_manager/tests/unit/test_block_device.py create mode 100644 synergy_scheduler_manager/tests/unit/test_compute.py create mode 100755 synergy_scheduler_manager/tests/unit/test_endpoint.py create mode 100755 synergy_scheduler_manager/tests/unit/test_flavor.py create mode 100644 synergy_scheduler_manager/tests/unit/test_hypervisor.py create mode 100644 synergy_scheduler_manager/tests/unit/test_priority.py create mode 100644 synergy_scheduler_manager/tests/unit/test_project.py create mode 100644 synergy_scheduler_manager/tests/unit/test_queue.py create mode 100644 synergy_scheduler_manager/tests/unit/test_quota.py create mode 100644 synergy_scheduler_manager/tests/unit/test_request.py create mode 100644 synergy_scheduler_manager/tests/unit/test_server.py create mode 100644 synergy_scheduler_manager/tests/unit/test_service.py create mode 100644 synergy_scheduler_manager/tests/unit/test_share.py create mode 100644 synergy_scheduler_manager/tests/unit/test_token.py create mode 100644 synergy_scheduler_manager/tests/unit/test_trust.py create mode 100644 synergy_scheduler_manager/tests/unit/test_user.py diff --git a/synergy_scheduler_manager/common/__init__.py b/synergy_scheduler_manager/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/synergy_scheduler_manager/common/block_device.py b/synergy_scheduler_manager/common/block_device.py new file mode 100644 index 0000000..77592c1 --- /dev/null +++ b/synergy_scheduler_manager/common/block_device.py @@ -0,0 +1,204 @@ +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + +""" +from nova.objects.base import NovaObject +from nova.objects.base import NovaObjectDictCompat + +class BlockDeviceMapping(NovaObject, NovaObjectDictCompat): + fields = { + } + + + def __init__(self, *args, **kwargs): + super(BlockDeviceMapping, self).__init__(*args, **kwargs) +""" + + +class BlockDeviceMapping(object): + + def __init__(self, id): + self.id = id + self.created_at = None + self.updated_at = None + self.deleted_at = None + self.device_name = None + self.delete_on_termination = None + self.snapshot_id = None + self.volume_id = None + self.volume_size = None + self.no_device = None + self.connection_info = None + self.instance_uuid = None + self.deleted = None + self.source_type = None + self.destination_type = None + self.guest_format = None + self.device_type = None + self.disk_bus = None + self.boot_index = None + self.image_id = None + + def getId(self): + return self.id + + def getImageId(self): + return self.image_id + + def setImageId(self, image_id): + self.image_id = image_id + + def getInstanceId(self): + return self.instance_uuid + + def setInstanceId(self, instance_uuid): + self.instance_uuid = instance_uuid + + def getSnapshotId(self): + return self.snapshot_id + + def setSnapshotId(self, snapshot_id): + self.snapshot_id = snapshot_id + + def getVolumeId(self): + return self.volume_id + + def setVolumeId(self, volume_id): + self.volume_id = volume_id + + def getVolumeSize(self): + return self.volume_size + + def setVolumeSize(self, volume_size): + self.volume_size = volume_size + + def getBootIndex(self): + return self.boot_index + + def setBootIndex(self, boot_index): + self.boot_index = boot_index + + def getCreatedAt(self): + return self.created_at + + def setCreatedAt(self, created_at): + self.created_at = created_at + + def getUpdatedAt(self): + return self.updated_at + + def setUpdatedAt(self, updated_at): + self.updated_at = updated_at + + def getDeletedAt(self): + return self.deleted_at + + def setDeletedAt(self, deleted_at): + self.deleted_at = deleted_at + + def getDeviceName(self): + return self.device_name + + def setDeviceName(self, device_name): + self.device_name = device_name + + def getNoDevice(self): + return self.no_device + + def setNoDevice(self, no_device): + self.no_device = no_device + + def getConnectionInfo(self): + return self.connection_info + + def setConnectionInfo(self, connection_info): + self.connection_info = connection_info + + def getDestinationType(self): + return self.destination_type + + def setDestinationType(self, destination_type): + self.destination_type = destination_type + + def getDeviceType(self): + return self.device_type + + def setDeviceType(self, device_type): + self.device_type = device_type + + def getSourceType(self): + return self.source_type + + def setSourceType(self, source_type): + self.source_type = source_type + + def getDiskBus(self): + return self.disk_bus + + def setDiskBus(self, disk_bus): + self.disk_bus = disk_bus + + def getGuestFormat(self): + return self.guest_format + + def setGuestFormat(self, guest_format): + self.guest_format = guest_format + + def isDeleteOnTermination(self): + return self.delete_on_termination + + def setDeleteOnTermination(self, delete_on_termination): + self.delete_on_termination = delete_on_termination + + def isDeleted(self): + return self.deleted + + def setDeleted(self, deleted): + self.deleted = deleted + + def serialize(self): + data = {} + data["id"] = self.id + data["created_at"] = self.created_at + data["updated_at"] = self.updated_at + data["deleted_at"] = self.deleted_at + data["device_name"] = self.device_name + data["device_type"] = self.device_type + data["delete_on_termination"] = self.delete_on_termination + data["snapshot_id"] = self.snapshot_id + data["volume_id"] = self.volume_id + data["volume_size"] = self.volume_size + data["no_device"] = self.no_device + data["connection_info"] = self.connection_info + data["instance_uuid"] = self.instance_uuid + data["deleted"] = self.deleted + data["source_type"] = self.source_type + data["destination_type"] = self.destination_type + data["guest_format"] = self.guest_format + data["disk_bus"] = self.disk_bus + data["boot_index"] = self.boot_index + data["image_id"] = self.image_id + + blockDeviceMap = {} + blockDeviceMap["nova_object.version"] = "1.15" + blockDeviceMap["nova_object.namespace"] = "nova" + blockDeviceMap["nova_object.changes"] = ["device_name"] + blockDeviceMap["nova_object.name"] = "BlockDeviceMapping" + blockDeviceMap["nova_object.data"] = data + + return blockDeviceMap diff --git a/synergy_scheduler_manager/common/compute.py b/synergy_scheduler_manager/common/compute.py new file mode 100644 index 0000000..94e9895 --- /dev/null +++ b/synergy_scheduler_manager/common/compute.py @@ -0,0 +1,44 @@ +from synergy.common.serializer import SynergyObject + + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +class Compute(SynergyObject): + + def __init__(self): + super(Compute, self).__init__() + + def getHost(self): + return self.get("host") + + def setHost(self, host): + self.set("host", host) + + def getNodeName(self): + return self.get("nodename") + + def setNodeName(self, nodename): + self.set("nodename", nodename) + + def getLimits(self): + return self.get("limits") + + def setLimits(self, limits): + self.set("limits", limits) diff --git a/synergy_scheduler_manager/common/endpoint.py b/synergy_scheduler_manager/common/endpoint.py new file mode 100644 index 0000000..8934777 --- /dev/null +++ b/synergy_scheduler_manager/common/endpoint.py @@ -0,0 +1,64 @@ +from synergy.common.serializer import SynergyObject + + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +class Endpoint(SynergyObject): + + def __init__(self): + super(Endpoint, self).__init__() + + self.set("enabled", False) + + def getInterface(self): + return self.get("interface") + + def setInterface(self, interface): + self.set("interface", interface) + + def getRegion(self): + return self.get("region") + + def setRegion(self, region): + self.set("region", region) + + def getRegionId(self): + return self.get("region_id") + + def setRegionId(self, region_id): + self.set("region_id", region_id) + + def getServiceId(self): + return self.get("service_id") + + def setServiceId(self, service_id): + self.set("service_id", service_id) + + def getURL(self): + return self.get("url") + + def setURL(self, url): + self.set("url", url) + + def isEnabled(self): + return self.get("enabled") + + def setEnabled(self, enabled=True): + self.set("enabled", enabled) diff --git a/synergy_scheduler_manager/common/flavor.py b/synergy_scheduler_manager/common/flavor.py new file mode 100644 index 0000000..7fccb04 --- /dev/null +++ b/synergy_scheduler_manager/common/flavor.py @@ -0,0 +1,48 @@ +from synergy.common.serializer import SynergyObject + + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +class Flavor(SynergyObject): + + def __init__(self): + super(Flavor, self).__init__() + + self.set("vcpus", 0) + self.set("memory", 0) + self.set("storage", 0) + + def getVCPUs(self): + return self.get("vcpus") + + def setVCPUs(self, vcpus): + self.set("vcpus", vcpus) + + def getMemory(self): + return self.get("memory") + + def setMemory(self, memory): + self.set("memory", memory) + + def getStorage(self): + return self.get("storage") + + def setStorage(self, storage): + self.set("storage", storage) diff --git a/synergy_scheduler_manager/common/hypervisor.py b/synergy_scheduler_manager/common/hypervisor.py new file mode 100644 index 0000000..153789e --- /dev/null +++ b/synergy_scheduler_manager/common/hypervisor.py @@ -0,0 +1,95 @@ +from service import Service + + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +class Hypervisor(Service): + + def __init__(self): + super(Hypervisor, self).__init__() + + self.set("vcpus", 0) + self.set("vcpus_used", 0) + self.set("memory", 0) + self.set("memory_used", 0) + self.set("storage", 0) + self.set("storage_used", 0) + self.set("vms", 0) + self.set("workload", 0) + + def getIP(self): + return self.get("ip") + + def setIP(self, ip): + self.set("ip", ip) + + def getState(self): + return self.get("state") + + def setState(self, state): + self.set("state", state) + + def getWorkload(self): + return self.get("workload") + + def setWorkload(self, workload): + self.set("workload", workload) + + def getVMs(self): + return self.get("vms") + + def setVMs(self, vms): + self.set("vms", vms) + + def getVCPUs(self, used=False): + if used: + return self.get("vcpus_used") + else: + return self.get("vcpus") + + def setVCPUs(self, vcpus, used=False): + if used: + self.set("vcpus_used", vcpus) + else: + self.set("vcpus", vcpus) + + def getMemory(self, used=False): + if used: + return self.get("memory_used") + else: + return self.get("memory") + + def setMemory(self, memory, used=False): + if used: + self.set("memory_used", memory) + else: + self.set("memory", memory) + + def getStorage(self, used=False): + if used: + return self.get("storage_used") + else: + return self.get("storage") + + def setStorage(self, storage, used=False): + if used: + self.set("storage_used", storage) + else: + self.set("storage", storage) diff --git a/synergy_scheduler_manager/common/priority.py b/synergy_scheduler_manager/common/priority.py new file mode 100644 index 0000000..dc8a3a6 --- /dev/null +++ b/synergy_scheduler_manager/common/priority.py @@ -0,0 +1,65 @@ +import threading + +from datetime import datetime +from synergy.common.serializer import SynergyObject + + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +class Priority(SynergyObject): + + def __init__(self): + super(Priority, self).__init__() + + self.condition = threading.Condition() + + self.set("value", 0) + self.set("last_update", datetime.utcnow()) + self.set("fairshare", {"vcpus": float(0), + "memory": float(0), + "disk": float(0)}) + + def getValue(self): + return self.get("value") + + def setValue(self, value): + self.set("value", value) + self.set("last_update", datetime.utcnow()) + + def getLastUpdate(self): + return self.get("last_update") + + def getFairShare(self, resource): + fairshare = self.get("fairshare") + + if resource not in fairshare: + raise Exception("wrong resource %r" % resource) + + return fairshare[resource] + + def setFairShare(self, resource, value=0): + fairshare = self.get("fairshare") + + if resource not in fairshare: + raise Exception("wrong resource %r" % resource) + + with self.condition: + fairshare[resource] = value + self.condition.notifyAll() diff --git a/synergy_scheduler_manager/common/project.py b/synergy_scheduler_manager/common/project.py new file mode 100644 index 0000000..86d0fad --- /dev/null +++ b/synergy_scheduler_manager/common/project.py @@ -0,0 +1,92 @@ +from quota import Quota +from share import Share +from synergy.common.serializer import SynergyObject + + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +class Project(SynergyObject): + + def __init__(self): + super(Project, self).__init__() + + self.set("data", {}) + self.set("users", {}) + self.set("share", Share()) + self.set("quota", Quota()) + self.set("TTL", float(0)) + self.set("enabled", False) + + def getData(self): + return self.get("data") + + def getQuota(self): + return self.get("quota") + + def getShare(self): + return self.get("share") + + def getTTL(self): + return self.get("TTL") + + def setTTL(self, TTL): + self.set("TTL", TTL) + + def addUser(self, user): + if self.get("users").get(user.getId(), None): + raise Exception("user %r already exists!" % (user.getId())) + + self.get("users")[user.getId()] = user + + def getUser(self, id=None, name=None): + if id: + return self.get("users").get(id, None) + elif name: + for user in self.get("users").values(): + if name == user.getName(): + return user + return None + + def getUsers(self): + return self.get("users").values() + + def isEnabled(self): + return self.get("enabled") + + def setEnabled(self, enabled=True): + self.set("enabled", enabled) + + +def main(): + project = Project() + project.setId("22222222") + project.setName("LISA") + + print(project.getName()) + ser = project.serialize() + print(ser) + + project1 = SynergyObject.deserialize(ser) + print(project1.serialize()) + print(project1.getName()) + + +if __name__ == "__main__": + main() diff --git a/synergy_scheduler_manager/common/queue.py b/synergy_scheduler_manager/common/queue.py new file mode 100644 index 0000000..9b36305 --- /dev/null +++ b/synergy_scheduler_manager/common/queue.py @@ -0,0 +1,397 @@ +import heapq +import json +import threading + +from datetime import datetime +from sqlalchemy.exc import SQLAlchemyError + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +class QueueItem(object): + + def __init__(self, id, user_id, prj_id, priority, + retry_count, creation_time, last_update, data=None): + self.id = id + self.user_id = user_id + self.prj_id = prj_id + self.priority = priority + self.retry_count = retry_count + self.creation_time = creation_time + self.last_update = last_update + self.data = data + + def getId(self): + return self.id + + def setId(self, id): + self.id = id + + def getUserId(self): + return self.user_id + + def setUserId(self, user_id): + self.user_id = user_id + + def getProjectId(self): + return self.prj_id + + def setProjectId(self, prj_id): + self.prj_id = prj_id + + def getPriority(self): + return self.priority + + def setPriority(self, priority): + self.priority = priority + + def getRetryCount(self): + return self.retry_count + + def setRetryCount(self, retry_count): + self.retry_count = retry_count + + def incRetryCount(self): + self.retry_count += 1 + + def getCreationTime(self): + return self.creation_time + + def setCreationTime(self, creation_time): + self.creation_time = creation_time + + def getLastUpdate(self): + return self.last_update + + def setLastUpdate(self, last_update): + self.last_update = last_update + + def getData(self): + return self.data + + def setData(self, data): + self.data = data + + +class PriorityQueue(object): + + def __init__(self): + self._heap = [] + self._count = 0 + + def __len__(self): + return len(self._heap) + + def __iter__(self): + """Get all elements ordered by asc. priority. """ + return self + + def put(self, priority, item): + heapq.heappush(self._heap, (-priority, self._count, item)) + self._count += 1 + + def get(self): + return heapq.heappop(self._heap)[2] + + def size(self): + return len(self._heap) + + def items(self): + return [heapq.heappop(self._heap) for i in range(len(self._heap))] + + def smallest(self, x): + return heapq.nsmallest(x, self._heap) + + def largest(self, x): + return heapq.nlargest(x, self._heap) + + +class QueueDB(object): + + def __init__(self, name, db_engine, fairshare_manager=None): + self.name = name + self.db_engine = db_engine + self.fairshare_manager = fairshare_manager + self.is_closed = False + self.priority_updater = None + self.condition = threading.Condition() + self.pqueue = PriorityQueue() + self.createTable() + self.buildFromDB() + + def getName(self): + return self.name + + def getSize(self): + connection = self.db_engine.connect() + + try: + QUERY = "select count(*) from `%s`" % self.name + result = connection.execute(QUERY) + + row = result.fetchone() + + return row[0] + except SQLAlchemyError as ex: + raise Exception(ex.message) + finally: + connection.close() + + def createTable(self): + TABLE = """CREATE TABLE IF NOT EXISTS `%s` (`id` BIGINT NOT NULL \ +AUTO_INCREMENT PRIMARY KEY, `priority` INT DEFAULT 0, user_id CHAR(40) \ +NOT NULL, prj_id CHAR(40) NOT NULL, `retry_count` INT DEFAULT 0, \ +`creation_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `last_update` \ +TIMESTAMP NULL, `data` TEXT NOT NULL ) ENGINE=InnoDB""" % self.name + + connection = self.db_engine.connect() + + try: + connection.execute(TABLE) + except SQLAlchemyError as ex: + raise Exception(ex.message) + except Exception as ex: + raise Exception(ex.message) + finally: + connection.close() + + def close(self): + if not self.is_closed: + self.is_closed = True + + with self.condition: + self.condition.notifyAll() + + def isClosed(self): + return self.is_closed + + def buildFromDB(self): + connection = self.db_engine.connect() + + try: + QUERY = "select id, user_id, prj_id, priority, retry_count, " \ + "creation_time, last_update from `%s`" % self.name + result = connection.execute(QUERY) + + for row in result: + queue_item = QueueItem(row[0], row[1], row[2], + row[3], row[4], row[5], row[6]) + + self.pqueue.put(row[3], queue_item) + except SQLAlchemyError as ex: + raise Exception(ex.message) + finally: + connection.close() + + with self.condition: + self.condition.notifyAll() + + def insertItem(self, user_id, prj_id, priority, data): + with self.condition: + idRecord = -1 + QUERY = "insert into `%s` (user_id, prj_id, priority, " \ + "data) values" % self.name + QUERY += "(%s, %s, %s, %s)" + + connection = self.db_engine.connect() + trans = connection.begin() + + try: + result = connection.execute(QUERY, + [user_id, prj_id, priority, + json.dumps(data)]) + + idRecord = result.lastrowid + + trans.commit() + except SQLAlchemyError as ex: + trans.rollback() + raise Exception(ex.message) + finally: + connection.close() + + now = datetime.now() + queue_item = QueueItem(idRecord, user_id, prj_id, + priority, 0, now, now) + + self.pqueue.put(priority, queue_item) + + self.condition.notifyAll() + + def reinsertItem(self, queue_item): + with self.condition: + self.pqueue.put(queue_item.getPriority(), queue_item) + self.condition.notifyAll() + + def getItem(self, blocking=True): + item = None + queue_item = None + + with self.condition: + while (queue_item is None and not self.is_closed): + if len(self.pqueue): + queue_item = self.pqueue.get() + elif blocking: + self.condition.wait() + elif queue_item is None: + break + + if (not self.is_closed and queue_item is not None): + connection = self.db_engine.connect() + + try: + QUERY = """select user_id, prj_id, priority, \ +retry_count, creation_time, last_update, data from `%s`""" % self.name + QUERY += " where id=%s" + + result = connection.execute(QUERY, [queue_item.getId()]) + + row = result.fetchone() + + item = QueueItem(queue_item.getId(), row[0], row[1], + row[2], row[3], row[4], row[5], + json.loads(row[6])) + except SQLAlchemyError as ex: + raise Exception(ex.message) + finally: + connection.close() + + self.condition.notifyAll() + + return item + + def deleteItem(self, queue_item): + if not queue_item: + return + + with self.condition: + connection = self.db_engine.connect() + trans = connection.begin() + + try: + QUERY = "delete from `%s`" % self.name + QUERY += " where id=%s" + + connection.execute(QUERY, [queue_item.getId()]) + + trans.commit() + except SQLAlchemyError as ex: + trans.rollback() + + raise Exception(ex.message) + finally: + connection.close() + self.condition.notifyAll() + + def updateItem(self, queue_item): + if not queue_item: + return + + with self.condition: + connection = self.db_engine.connect() + trans = connection.begin() + + try: + queue_item.setLastUpdate(datetime.now()) + + QUERY = "update `%s`" % self.name + QUERY += " set priority=%s, retry_count=%s, " \ + "last_update=%s where id=%s" + + connection.execute(QUERY, [queue_item.getPriority(), + queue_item.getRetryCount(), + queue_item.getLastUpdate(), + queue_item.getId()]) + + trans.commit() + except SQLAlchemyError as ex: + trans.rollback() + + raise Exception(ex.message) + finally: + connection.close() + + self.pqueue.put(queue_item.getPriority(), queue_item) + self.condition.notifyAll() + + def updatePriority(self): + if self.fairshare_manager is None: + return + + with self.condition: + queue_items = [] + + connection = self.db_engine.connect() + + while len(self.pqueue) > 0: + queue_item = self.pqueue.get() + priority = queue_item.getPriority() + + try: + priority = self.fairshare_manager.execute( + "CALCULATE_PRIORITY", + user_id=queue_item.getUserId(), + prj_id=queue_item.getProjectId(), + timestamp=queue_item.getCreationTime(), + retry=queue_item.getRetryCount()) + + queue_item.setPriority(priority) + except Exception as ex: + continue + finally: + queue_items.append(queue_item) + + trans = connection.begin() + + try: + queue_item.setLastUpdate(datetime.now()) + + QUERY = "update `%s`" % self.name + QUERY += " set priority=%s, last_update=%s where id=%s" + + connection.execute(QUERY, [queue_item.getPriority(), + queue_item.getLastUpdate(), + queue_item.getId()]) + + trans.commit() + except SQLAlchemyError as ex: + trans.rollback() + raise Exception(ex.message) + + connection.close() + + if len(queue_items) > 0: + for queue_item in queue_items: + self.pqueue.put(queue_item.getPriority(), queue_item) + + del queue_items + + self.condition.notifyAll() + + def toDict(self): + queue = {} + queue["name"] = self.name + queue["size"] = self.getSize() + + if self.is_closed: + queue["status"] = "OFF" + else: + queue["status"] = "ON" + + return queue diff --git a/synergy_scheduler_manager/common/quota.py b/synergy_scheduler_manager/common/quota.py new file mode 100644 index 0000000..38a8431 --- /dev/null +++ b/synergy_scheduler_manager/common/quota.py @@ -0,0 +1,548 @@ +import logging +import threading + +from datetime import datetime +from synergy.common.serializer import SynergyObject + + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +LOG = logging.getLogger("quota") + + +class Quota(SynergyObject): + + def __init__(self): + super(Quota, self).__init__() + + private = {} + private["servers"] = {"active": [], "building": [], "error": []} + private["resources"] = { + "memory": {"used": float(0), "size": float(0)}, + "vcpus": {"used": float(0), "size": float(0)}, + "instances": {"used": float(0), "size": float(0)}} + + shared = {} + shared["servers"] = {"active": [], "building": [], "error": []} + shared["resources"] = { + "memory": {"used": float(0), "size": float(0)}, + "vcpus": {"used": float(0), "size": float(0)}, + "instances": {"used": float(0), "size": float(0)}} + + self.set("shared", shared) + self.set("private", private) + self.condition = threading.Condition() + + def getType(self): + return self.get("type") + + def setType(self, type): + self.set("type", type) + + def getServers(self, status, private=True): + if private: + servers = self.get("private")["servers"] + else: + servers = self.get("shared")["servers"] + + if status not in servers: + raise Exception("wrong status %r" % status) + + return servers[status] + + def reset(self): + with self.condition: + for resource in self.get("private")["resources"].values(): + resource["used"] = 0 + + self.condition.notifyAll() + + def getSize(self, resource, private=True): + if private: + resources = self.get("private")["resources"] + else: + resources = self.get("shared")["resources"] + + if resource not in resources: + raise Exception("wrong resource %r" % resource) + + return resources[resource]["size"] + + def setSize(self, resource, value=0, private=True): + if private: + resources = self.get("private")["resources"] + else: + resources = self.get("shared")["resources"] + + if resource not in resources: + raise Exception("wrong resource %r" % resource) + + with self.condition: + resources[resource]["size"] = value + self.condition.notifyAll() + + def getUsage(self, resource, private=True): + if private: + resources = self.get("private")["resources"] + else: + resources = self.get("shared")["resources"] + + if resource not in resources: + raise Exception("wrong resource %r" % resource) + + return resources[resource]["used"] + + def setUsage(self, resource, value=0, private=True): + if private: + resources = self.get("private")["resources"] + else: + resources = self.get("shared")["resources"] + + if resource not in resources: + raise Exception("wrong resource %r" % resource) + + with self.condition: + resources[resource]["used"] = value + self.condition.notifyAll() + + def allocate(self, server, blocking=True): + server_id = server.getId() + state = server.getState() + flavor = server.getFlavor() + vcpus = flavor.getVCPUs() + memory = flavor.getMemory() + found = False + + if server.isEphemeral(): + if SharedQuota.allocate(server, blocking): + LOG.info(">> SharedQuota.allocate OK") + shared = self.get("shared") + servers = shared["servers"] + resources = shared["resources"] + + with self.condition: + resources["vcpus"]["used"] += vcpus + resources["memory"]["used"] += memory + resources["instances"]["used"] += 1 + + servers["active"].append(server_id) + + self.condition.notifyAll() + return True + else: + LOG.info(">> SharedQuota.allocate NO") + return False + + private = self.get("private") + servers = private["servers"] + resources = private["resources"] + + if vcpus > resources["vcpus"]["size"] or \ + memory > resources["memory"]["size"]: + raise Exception("the required resources for server %r " + "exceed the quota size" % server_id) + + with self.condition: + if server_id in servers["active"]: + raise Exception("resources for server %r already allocated" + % server_id) + elif server_id in servers["building"]: + raise Exception("resources for server %r waiting " + "to be allocated" % server_id) + elif state: + resources["vcpus"]["used"] += vcpus + resources["memory"]["used"] += memory + resources["instances"]["used"] += 1 + + servers[state].append(server_id) + + found = True + elif not blocking: + servers["building"].append(server_id) + + while (not found and server_id in servers["building"]): + vcpus_size = resources["vcpus"]["size"] + vcpus_used = resources["vcpus"]["used"] + memory_size = resources["memory"]["size"] + memory_used = resources["memory"]["used"] + + LOG.debug("allocating server_id=%s vcpus=%s " + "memory=%s [vcpus in use %s of %s; " + "memory in use %s of %s]" + % (server_id, + vcpus, + memory, + vcpus_used, + vcpus_size, + memory_used, + memory_size)) + + if (vcpus_size - vcpus_used >= vcpus) and \ + (memory_size - memory_used >= memory): + found = True + + resources["vcpus"]["used"] += vcpus + resources["memory"]["used"] += memory + resources["instances"]["used"] += 1 + + servers["active"].append(server_id) + servers["building"].remove(server_id) + + LOG.info("allocated server_id=%s vcpus=%s memory" + "=%s [vcpus in use %s of %s; " + "memory in use %s of %s]" + % (server_id, + vcpus, + memory, + resources["vcpus"]["used"], + resources["vcpus"]["size"], + resources["memory"]["used"], + resources["memory"]["size"])) + elif blocking: + LOG.info("allocate wait!!!") + self.condition.wait() + else: + break + + self.condition.notifyAll() + + if not found: + servers["building"].remove(server_id) + + return found + + def release(self, server): + server_id = server.getId() + flavor = server.getFlavor() + vcpus = flavor.getVCPUs() + memory = flavor.getMemory() + + if SharedQuota.release(server): + shared = self.get("shared") + servers = shared["servers"] + resources = shared["resources"] + + with self.condition: + if server_id in servers["building"]: + servers["building"].remove(server_id) + elif server_id in servers["active"]: + resources["vcpus"]["used"] -= vcpus + resources["memory"]["used"] -= memory + resources["instances"]["used"] -= 1 + + if server_id in servers["active"]: + servers["active"].remove(server_id) + else: + servers["error"].remove(server_id) + + self.condition.notifyAll() + return True + + private = self.get("private") + servers = private["servers"] + resources = private["resources"] + found = False + with self.condition: + LOG.debug("releasing server_id=%s vcpus=%s memory=%s " + "[vcpu in use %s of %s; memory in use %s of %s]" + % (server_id, + vcpus, + memory, + resources["vcpus"]["used"], + resources["vcpus"]["size"], + resources["memory"]["used"], + resources["memory"]["size"])) + + if server_id in servers["building"]: + servers["building"].remove(server_id) + found = True + elif server_id in servers["active"]: + if resources["vcpus"]["used"] - vcpus < 0: + resources["vcpus"]["used"] = 0 + else: + resources["vcpus"]["used"] -= vcpus + + if resources["memory"]["used"] - memory < 0: + resources["memory"]["used"] = 0 + else: + resources["memory"]["used"] -= memory + + resources["instances"]["used"] -= 1 + + if server_id in servers["active"]: + servers["active"].remove(server_id) + else: + servers["error"].remove(server_id) + + LOG.info("released server_id=%s vcpus=%s memory=%s " + "[vcpu in use %s of %s; memory in use %s of %s]" + % (server_id, + vcpus, + memory, + resources["vcpus"]["used"], + resources["vcpus"]["size"], + resources["memory"]["used"], + resources["memory"]["size"])) + found = True + else: + LOG.debug("release: instance %r not found!" % (server_id)) + + self.condition.notifyAll() + return found + + +class SharedQuota(SynergyObject): + resources = {} + resources["memory"] = {"used": float(0), "size": float(0)} + resources["vcpus"] = {"used": float(0), "size": float(0)} + resources["instances"] = {"used": float(0), "size": float(-1)} + servers = {"active": [], "building": [], "error": []} + condition = threading.Condition() + lastAllocationTime = datetime.now() + lastReleaseTime = datetime.now() + enabled = False + + def __init__(self): + super(SharedQuota, self).__init__() + + self.set("servers", SharedQuota.servers) + self.set("resources", SharedQuota.resources) + self.set("enabled", SharedQuota.enabled) + self.set("lastAllocationTime", SharedQuota.lastAllocationTime) + self.set("lastReleaseTime", SharedQuota.lastReleaseTime) + + @classmethod + def isEnabled(cls): + return cls.enabled + + @classmethod + def enable(cls): + with cls.condition: + cls.enabled = True + cls.condition.notifyAll() + + @classmethod + def disable(cls): + with cls.condition: + cls.enabled = False + cls.condition.notifyAll() + + @classmethod + def getSize(cls, resource): + if resource not in cls.resources: + raise Exception("wrong resource %r" % resource) + + return cls.resources[resource]["size"] + + @classmethod + def setSize(cls, resource, value): + if resource not in cls.resources: + raise Exception("wrong resource %r" % resource) + + with cls.condition: + cls.resources[resource]["size"] = value + cls.condition.notifyAll() + + @classmethod + def getUsage(cls, resource): + if resource not in cls.resources: + raise Exception("wrong resource %r" % resource) + + return cls.resources[resource]["used"] + + @classmethod + def setUsage(cls, resource, value=0): + if resource not in cls.resources: + raise Exception("wrong resource %r" % resource) + + with cls.condition: + cls.resources[resource]["used"] = value + cls.condition.notifyAll() + + @classmethod + def getLastAllocationTime(cls): + return cls.lastAllocationTime + + @classmethod + def getLastReleaseTime(cls): + return cls.lastReleaseTime + + @classmethod + def wait(cls): + with cls.condition: + cls.condition.wait() + + @classmethod + def allocate(cls, server, blocking=True): + server_id = server.getId() + state = server.getState() + flavor = server.getFlavor() + vcpus = flavor.getVCPUs() + memory = flavor.getMemory() + found = False + + if vcpus > cls.resources["vcpus"]["size"] or \ + memory > cls.resources["memory"]["size"]: + raise Exception("the required resources for server %r " + "exceed the quota size" % server_id) + + with cls.condition: + if server_id in cls.servers["active"]: + raise Exception("resources for server %r already allocated" + % server_id) + elif server_id in cls.servers["building"]: + raise Exception("resources for server %r already waiting " + "to be allocated" % server_id) + elif state: + cls.resources["vcpus"]["used"] += vcpus + cls.resources["memory"]["used"] += memory + cls.resources["instances"]["used"] += 1 + + cls.servers[state].append(server_id) + + found = True + else: + cls.servers["building"].append(server_id) + + while (cls.enabled and not found and + server_id in cls.servers["building"]): + vcpus_size = cls.resources["vcpus"]["size"] + vcpus_used = cls.resources["vcpus"]["used"] + memory_size = cls.resources["memory"]["size"] + memory_used = cls.resources["memory"]["used"] + + LOG.debug("allocating server_id=%s vcpus=%s " + "memory=%s [vcpus in use %s of %s; " + "memory in use %s of %s]" + % (server_id, + vcpus, + memory, + vcpus_used, + vcpus_size, + memory_used, + memory_size)) + + if (vcpus_size - vcpus_used >= vcpus) and \ + (memory_size - memory_used >= memory): + found = True + + cls.resources["vcpus"]["used"] += vcpus + cls.resources["memory"]["used"] += memory + cls.resources["instances"]["used"] += 1 + + cls.servers["active"].append(server_id) + cls.servers["building"].remove(server_id) + + LOG.info("allocated server_id=%s vcpus=%s memory" + "=%s [vcpus in use %s of %s; " + "memory in use %s of %s]" + % (server_id, + vcpus, + memory, + cls.resources["vcpus"]["used"], + cls.resources["vcpus"]["size"], + cls.resources["memory"]["used"], + cls.resources["memory"]["size"])) + + cls.lastAllocationTime = datetime.now() + elif blocking: + LOG.info("allocate wait!!!") + cls.condition.wait() + else: + break + + cls.condition.notifyAll() + + if not found: + cls.servers["building"].remove(server_id) + + return found + + @classmethod + def release(cls, server): + server_id = server.getId() + flavor = server.getFlavor() + vcpus = flavor.getVCPUs() + memory = flavor.getMemory() + found = False + + with cls.condition: + LOG.debug("releasing server_id=%s vcpus=%s memory=%s " + "[vcpu in use %s of %s; memory in use %s of %s]" + % (server_id, + vcpus, + memory, + cls.resources["vcpus"]["used"], + cls.resources["vcpus"]["size"], + cls.resources["memory"]["used"], + cls.resources["memory"]["size"])) + + if server_id in cls.servers["building"]: + cls.servers["building"].remove(server_id) + found = True + elif server_id in cls.servers["active"] or \ + server_id in cls.servers["error"]: + if cls.resources["vcpus"]["used"] - vcpus < 0: + cls.resources["vcpus"]["used"] = 0 + else: + cls.resources["vcpus"]["used"] -= vcpus + + if cls.resources["memory"]["used"] - memory < 0: + cls.resources["memory"]["used"] = 0 + else: + cls.resources["memory"]["used"] -= memory + + if server_id in cls.servers["active"]: + cls.servers["active"].remove(server_id) + else: + cls.servers["error"].remove(server_id) + + cls.resources["instances"]["used"] -= 1 + + LOG.info("released server_id=%s vcpus=%s memory=%s " + "[vcpu in use %s of %s; memory in use %s of %s]" + % (server_id, + vcpus, + memory, + cls.resources["vcpus"]["used"], + cls.resources["vcpus"]["size"], + cls.resources["memory"]["used"], + cls.resources["memory"]["size"])) + + cls.lastReleaseTime = datetime.now() + found = True + else: + LOG.debug("release: instance %r not found!" % (server_id)) + + cls.condition.notifyAll() + return found + + @classmethod + def deserialize(cls, entity): + quota = super(SharedQuota, cls).deserialize(entity) + + cls.resources = entity["resources"] + cls.enabled = entity["enabled"] + cls.lastAllocationTime = datetime.strptime( + entity["lastAllocationTime"], "%Y-%m-%dT%H:%M:%S.%f") + cls.lastReleaseTime = datetime.strptime( + entity["lastReleaseTime"], "%Y-%m-%dT%H:%M:%S.%f") + + return quota diff --git a/synergy_scheduler_manager/common/request.py b/synergy_scheduler_manager/common/request.py new file mode 100644 index 0000000..ac17792 --- /dev/null +++ b/synergy_scheduler_manager/common/request.py @@ -0,0 +1,185 @@ +import utils + +from datetime import datetime +from flavor import Flavor +from server import Server + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +class Request(object): + + def __init__(self): + self.context = None + self.instance = None + self.image = None + self.filter_properties = None + self.admin_password = None + self.injected_files = None + self.requested_networks = None + self.security_groups = None + self.block_device_mapping = None + self.legacy_bdm = None + + def getAdminPassword(self): + return self.admin_password + + def getId(self): + if self.instance: + return self.instance["nova_object.data"]["uuid"] + + return None + + def getInstance(self): + return self.instance + + def getServer(self): + server = None + + if self.instance: + instance_data = self.instance["nova_object.data"] + flavor_data = instance_data["flavor"]["nova_object.data"] + + flavor = Flavor() + flavor.setId(flavor_data["flavorid"]) + flavor.setName(flavor_data["name"]) + flavor.setMemory(flavor_data["memory_mb"]) + flavor.setVCPUs(flavor_data["vcpus"]) + flavor.setStorage(flavor_data["root_gb"]) + + server = Server() + server.setFlavor(flavor) + server.setId(instance_data["uuid"]) + server.setUserId(instance_data["user_id"]) + server.setProjectId(instance_data["project_id"]) + server.setCreatedAt(instance_data["created_at"]) + server.setMetadata(instance_data["metadata"]) + server.setKeyName(instance_data["key_name"]) + + if "user_data" in instance_data: + user_data = instance_data["user_data"] + if user_data: + server.setUserData(utils.decodeBase64(user_data)) + + return server + + def getImage(self): + return self.image + + def getUserId(self): + if self.instance: + return self.instance["nova_object.data"]["user_id"] + + return None + + def getProjectId(self): + if self.instance: + return self.instance["nova_object.data"]["project_id"] + + return None + + def getContext(self): + return self.context + + def getCreatedAt(self): + if self.instance: + created_at = self.instance["nova_object.data"]["created_at"] + timestamp = datetime.strptime(created_at, "%Y-%m-%dT%H:%M:%SZ") + return timestamp + + return 0 + + def getMetadata(self): + if self.instance: + return self.instance["nova_object.data"]["metadata"] + + return None + + def getRetry(self): + if self.filter_properties: + return self.filter_properties.get("retry", None) + + return None + + def getFilterProperties(self): + return self.filter_properties + + def getInjectedFiles(self): + return self.injected_files + + def getRequestedNetworks(self): + return self.requested_networks + + def getSecurityGroups(self): + return self.security_groups + + def getBlockDeviceMapping(self): + return self.block_device_mapping + + def getLegacyBDM(self): + return self.legacy_bdm + + def toDict(self): + request = {} + request['context'] = self.context + request['instance'] = self.instance + request['image'] = self.image + request['filter_properties'] = self.filter_properties + request['admin_password'] = self.admin_password + request['injected_files'] = self.injected_files + request['requested_networks'] = self.requested_networks + request['security_groups'] = self.security_groups + request['block_device_mapping'] = self.block_device_mapping + request['legacy_bdm'] = self.legacy_bdm + + return request + + @classmethod + def fromDict(cls, request_dict): + request = Request() + request.context = request_dict['context'] + request.instance = request_dict['instance'] + request.image = request_dict['image'] + request.filter_properties = request_dict['filter_properties'] + request.admin_password = request_dict['admin_password'] + request.injected_files = request_dict['injected_files'] + request.requested_networks = request_dict['requested_networks'] + request.security_groups = request_dict['security_groups'] + request.block_device_mapping = request_dict['block_device_mapping'] + request.legacy_bdm = request_dict['legacy_bdm'] + + return request + + @classmethod + def build(cls, context, instance, image, filter_properties, + admin_password, injected_files, requested_networks, + security_groups, block_device_mapping=None, legacy_bdm=True): + request = Request() + request.context = context + request.instance = instance + request.image = image + request.filter_properties = filter_properties + request.admin_password = admin_password + request.injected_files = injected_files + request.requested_networks = requested_networks + request.security_groups = security_groups + request.block_device_mapping = block_device_mapping + request.legacy_bdm = legacy_bdm + + return request diff --git a/synergy_scheduler_manager/common/role.py b/synergy_scheduler_manager/common/role.py new file mode 100644 index 0000000..9006e46 --- /dev/null +++ b/synergy_scheduler_manager/common/role.py @@ -0,0 +1,25 @@ +from synergy.common.serializer import SynergyObject + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +class Role(SynergyObject): + + def __init__(self): + super(Role, self).__init__() diff --git a/synergy_scheduler_manager/common/server.py b/synergy_scheduler_manager/common/server.py new file mode 100644 index 0000000..743898f --- /dev/null +++ b/synergy_scheduler_manager/common/server.py @@ -0,0 +1,136 @@ +import synergy.common.utils as utils + +from datetime import datetime +from synergy.common.serializer import SynergyObject + + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +class Server(SynergyObject): + + def __init__(self): + super(Server, self).__init__() + self.setType("permanent") + + def __getDateTime(self, date): + if not date: + return None + elif isinstance(date, basestring): + return datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ") + elif isinstance(date, datetime): + return date + else: + raise Exception("not valid date format") + + def getType(self): + return self.get("type") + + def setType(self, type): + self.set("type", type) + + def getState(self): + return self.get("state") + + def setState(self, state): + self.set("state", state) + + def getFlavor(self): + return self.get("flavor") + + def setFlavor(self, flavor): + self.set("flavor", flavor) + + def getKeyName(self): + return self.get("key_name") + + def setKeyName(self, key_name): + self.set("key_name", key_name) + + def getMetadata(self): + return self.get("metadata") + + def setMetadata(self, metadata): + self.set("metadata", metadata) + + if "quota" in metadata and metadata["quota"] == "shared": + self.setType("ephemeral") + else: + self.setType("permanent") + + def getUserData(self): + return self.get("userdata") + + def setUserData(self, userdata): + self.set("userdata", userdata) + + if userdata: + try: + quota = utils.getConfigParameter(userdata, "quota", "synergy") + + if quota is None or quota == "private": + self.setType("permanent") + elif quota == "shared": + self.setType("ephemeral") + else: + self.setType("permanent") + except Exception: + self.setType("permanent") + + def getUserId(self): + return self.get("user_id") + + def setUserId(self, user_id): + self.set("user_id", user_id) + + def getProjectId(self): + return self.get("project_id") + + def setProjectId(self, project_id): + self.set("project_id", project_id) + + def getCreatedAt(self): + return self.get("created_at") + + def setCreatedAt(self, created_at): + self.set("created_at", self.__getDateTime(created_at)) + + def getLaunchedAt(self): + return self.get("launched_at") + + def setLaunchedAt(self, launched_at): + self.set("launched_at", self.__getDateTime(launched_at)) + + def getUpdatedAt(self): + return self.get("updated_at") + + def setUpdatedAt(self, updated_at): + self.set("updated_at", self.__getDateTime(updated_at)) + + def getTerminatedAt(self): + return self.get("terminated_at") + + def setTerminatedAt(self, terminated_at): + self.set("terminated_at", self.__getDateTime(terminated_at)) + + def isEphemeral(self): + return self.get("type") == "ephemeral" + + def isPermanent(self): + return self.get("type") == "permanent" diff --git a/synergy_scheduler_manager/common/service.py b/synergy_scheduler_manager/common/service.py new file mode 100644 index 0000000..f7b0589 --- /dev/null +++ b/synergy_scheduler_manager/common/service.py @@ -0,0 +1,60 @@ +from synergy.common.serializer import SynergyObject + + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +class Service(SynergyObject): + + def __init__(self): + super(Service, self).__init__() + + self.set("enabled", False) + self.set("endpoints", []) + + def getType(self): + return self.get("type") + + def setType(self, type): + self.set("type", type) + + def getEndpoints(self): + return self.get("endpoints") + + def getEndpoint(self, interface): + for endpoint in self.get("endpoints"): + if endpoint.getInterface() == interface: + return endpoint + + return None + + def getDescription(self): + return self.get("description") + + def setDescription(self, description): + self.set("description", description) + + def getStatus(self): + return self.get("status") + + def setStatus(self, status): + self.set("status", status) + + def isEnabled(self): + return self.get("status") == "enabled" diff --git a/synergy_scheduler_manager/common/share.py b/synergy_scheduler_manager/common/share.py new file mode 100644 index 0000000..0c8c303 --- /dev/null +++ b/synergy_scheduler_manager/common/share.py @@ -0,0 +1,48 @@ +from synergy.common.serializer import SynergyObject + + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +class Share(SynergyObject): + + def __init__(self): + super(Share, self).__init__() + + self.set("value", float(0)) + self.set("sibling_value", float(0)) + self.set("normalized_value", float(0)) + + def getValue(self): + return self.get("value") + + def setValue(self, value): + self.set("value", value) + + def getSiblingValue(self): + return self.get("sibling_value") + + def setSiblingValue(self, value): + self.set("sibling_value", value) + + def getNormalizedValue(self): + return self.get("normalized_value") + + def setNormalizedValue(self, value): + self.set("normalized_value", value) diff --git a/synergy_scheduler_manager/common/token.py b/synergy_scheduler_manager/common/token.py new file mode 100644 index 0000000..40eff74 --- /dev/null +++ b/synergy_scheduler_manager/common/token.py @@ -0,0 +1,184 @@ +import json +import os.path + +from datetime import datetime +from endpoint import Endpoint +from project import Project +from role import Role +from service import Service +from synergy.common.serializer import SynergyObject +from synergy_scheduler_manager.common.user import User + + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +class Token(SynergyObject): + + def __init__(self): + super(Token, self).__init__() + + self.set("extras", {}) + self.set("roles", []) + self.set("catalog", []) + + @classmethod + def parse(cls, id, data): + token = Token() + + token.setId(id) + + data = data["token"] + token.setCreation(datetime.strptime(data["issued_at"], + "%Y-%m-%dT%H:%M:%S.%fZ")) + + token.setExpiration(datetime.strptime(data["expires_at"], + "%Y-%m-%dT%H:%M:%S.%fZ")) + + project = Project() + project.setId(data["project"]["id"]) + project.setName(data["project"]["name"]) + + token.setProject(project) + + user = User() + user.setId(data["user"]["id"]) + user.setName(data["user"]["name"]) + user.setProjectId(data["project"]["id"]) + + token.setUser(user) + + token.getExtras().update(data["extras"]) + + for info in data["roles"]: + role = Role() + role.setId(info["id"]) + role.setName(info["name"]) + + token.getRoles().append(role) + + for service_info in data["catalog"]: + service = Service() + service.setId(service_info["id"]) + service.setType(service_info["type"]) + service.setName(service_info["name"]) + + for endpoint_info in service_info["endpoints"]: + endpoint = Endpoint() + endpoint.setId(endpoint_info["id"]) + endpoint.setInterface(endpoint_info["interface"]) + endpoint.setRegion(endpoint_info["region"]) + endpoint.setRegionId(endpoint_info["region_id"]) + endpoint.setURL(endpoint_info["url"]) + + service.getEndpoints().append(endpoint) + + token.getServices().append(service) + + return token + + def getServices(self): + return self.get("catalog") + + def getService(self, name): + for service in self.get("catalog"): + if service.getName() == name: + return service + + return None + + def getCreation(self): + return self.get("issued_at") + + def setCreation(self, issued_at): + self.set("issued_at", issued_at) + + def getExpiration(self): + return self.get("expires_at") + + def setExpiration(self, expires_at): + self.set("expires_at", expires_at) + + def getExtras(self): + return self.get("extras") + + def getProject(self): + return self.get("project") + + def setProject(self, project): + self.set("project", project) + + def getRoles(self): + return self.get("roles") + + def getUser(self): + return self.get("user") + + def setUser(self, user): + self.set("user", user) + + def isAdmin(self): + for role in self.get("roles"): + if role.getName() == "admin": + return True + + return False + + def issuedAt(self): + return self.get("issued_at") + + def isExpired(self): + return self.getExpiration() < datetime.utcnow() + + def save(self, filename): + # save to file + with open(filename, 'w') as f: + json.dump(self.serialize(), f) + + @classmethod + def load(cls, filename): + if not os.path.isfile(filename): + return None + + # load from file: + with open(filename, 'r') as f: + try: + data = json.load(f) + return Token.deserialize(data) + # if the file is empty the ValueError will be thrown + except ValueError as ex: + raise ex + + def isotime(self, at=None, subsecond=False): + """Stringify time in ISO 8601 format.""" + if not at: + at = datetime.utcnow() + + if not subsecond: + st = at.strftime('%Y-%m-%dT%H:%M:%S') + else: + st = at.strftime('%Y-%m-%dT%H:%M:%S.%f') + + if at.tzinfo: + tz = at.tzinfo.tzname(None) + else: + tz = 'UTC' + + st += ('Z' if tz == 'UTC' else tz) + return st diff --git a/synergy_scheduler_manager/common/trust.py b/synergy_scheduler_manager/common/trust.py new file mode 100644 index 0000000..7d8477b --- /dev/null +++ b/synergy_scheduler_manager/common/trust.py @@ -0,0 +1,156 @@ +import json +import requests + +from datetime import datetime +from token import Token + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +class Trust(object): + + def __init__(self, data): + self.id = data["id"] + self.impersonations = data["impersonation"] + self.trustor_user_id = data["trustor_user_id"] + self.trustee_user_id = data["trustee_user_id"] + self.links = data.get("links", []) + self.roles = data.get("roles", []) + self.remaining_uses = data["remaining_uses"] + self.expires_at = None + self.keystone_url = None + + if data["expires_at"] is not None: + self.expires_at = datetime.strptime(data["expires_at"], + "%Y-%m-%dT%H:%M:%S.%fZ") + self.project_id = data["project_id"] + + def getId(self): + return self.id + + def isImpersonations(self): + return self.impersonations + + def getRolesLinks(self): + return self.roles_links + + def getTrustorUserId(self): + return self.trustor_user_id + + def getTrusteeUserId(self): + return self.trustee_user_id + + def getlinks(self): + return self.links + + def getProjectId(self): + return self.project_id + + def getRoles(self): + return self.roles + + def getRemainingUses(self): + return self.remaining_uses + + def getExpiration(self): + return self.expires_at + + def isExpired(self): + if self.getExpiration() is None: + return False + + return self.getExpiration() < datetime.utcnow() + + def getToken(self, token_id): + headers = {"Content-Type": "application/json", + "Accept": "application/json", + "User-Agent": "synergy"} + data = {} + data["auth"] = {"identity": {"methods": ["token"], + "token": {"id": token_id}}, + "scope": {"OS-TRUST:trust": {"id": self.getId()}}} + + response = requests.post(url=self.keystone_url + "/auth/tokens", + headers=headers, + data=json.dumps(data)) + + if response.status_code != requests.codes.ok: + response.raise_for_status() + + if not response.text: + raise Exception("authentication failed!") + + token_subject = response.headers["X-Subject-Token"] + token_data = response.json() + + return Token.parse(token_subject, token_data) + + @staticmethod + def makeTrust(trustee_user_id, token, expires_at=None, impersonation=True): + if token.isExpired(): + raise Exception("token expired!") + + headers = {"Content-Type": "application/json", + "Accept": "application/json", + "User-Agent": "synergy", + "X-Auth-Token": token.getId()} + + project_id = token.getProject().getId() + roles = token.getRoles() + roles_data = [] + + for role in roles: + roles_data.append({"id": role.getId(), "name": role.getName()}) + + data = {} + data["trust"] = {"impersonation": impersonation, + "project_id": project_id, + "roles": roles_data, + "trustee_user_id": trustee_user_id, + "trustor_user_id": token.getUser().getId()} + + if expires_at is not None: + data["trust"]["expires_at"] = token.isotime(expires_at, True) + + service = token.getService("keystone") + if not service: + raise Exception("keystone service not found!") + + endpoint = service.getEndpoint("admin") + if not endpoint: + raise Exception("keystone endpoint not found!") + + endpoint_url = endpoint.getURL() + + response = requests.post(url=endpoint_url + "/OS-TRUST/trusts", + headers=headers, + data=json.dumps(data)) + + if response.status_code != requests.codes.ok: + response.raise_for_status() + + if not response.text: + raise Exception("trust token failed!") + + response = response.json() + + trust = Trust(response["trust"]) + trust.keystone_url = endpoint_url + + return trust diff --git a/synergy_scheduler_manager/common/user.py b/synergy_scheduler_manager/common/user.py new file mode 100644 index 0000000..6bc679e --- /dev/null +++ b/synergy_scheduler_manager/common/user.py @@ -0,0 +1,82 @@ +from priority import Priority +from share import Share +from synergy.common.serializer import SynergyObject + + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +class User(SynergyObject): + + def __init__(self): + super(User, self).__init__() + + self.set("data", {}) + self.set("priority", Priority()) + self.set("share", Share()) + self.set("role", None) + self.set("enabled", False) + + def getData(self): + return self.get("data") + + def getProjectId(self): + return self.get("project_id") + + def setProjectId(self, project_id): + self.set("project_id", project_id) + + def getPriority(self): + return self.get("priority") + + def getRole(self): + return self.get("role") + + def setRole(self, role): + self.set("role", role) + + def getShare(self): + return self.get("share") + + def isEnabled(self): + return self.get("enabled") + + def setEnabled(self, enabled=True): + self.set("enabled", enabled) + + +def main(): + user = User() + user.setId("22222222") + user.setName("LISA") + user.setProjectId("pippo") + data = user.getData() + data["a"] = "b" + share = user.getShare() + share.setValue("10") + + ser = user.serialize() + print(ser) + + user1 = SynergyObject.deserialize(ser) + print(user1.serialize()) + + +if __name__ == "__main__": + main() diff --git a/synergy_scheduler_manager/common/utils.py b/synergy_scheduler_manager/common/utils.py new file mode 100644 index 0000000..b6a0e03 --- /dev/null +++ b/synergy_scheduler_manager/common/utils.py @@ -0,0 +1,48 @@ +import base64 +import binascii +import ConfigParser +import io + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +def encodeBase64(s): + if not s: + return None + + try: + return base64.encodestring(s) + except binascii.Error: + raise binascii.Error + + +def decodeBase64(s): + if not s: + return None + + try: + return base64.decodestring(s) + except binascii.Error: + raise binascii.Error + + +def getConfigParameter(data, key, section="DEFAULT"): + config = ConfigParser.RawConfigParser(allow_no_value=True) + config.readfp(io.BytesIO(data)) + return config.get(section, key) diff --git a/synergy_scheduler_manager/tests/unit/test_block_device.py b/synergy_scheduler_manager/tests/unit/test_block_device.py new file mode 100644 index 0000000..73b54cb --- /dev/null +++ b/synergy_scheduler_manager/tests/unit/test_block_device.py @@ -0,0 +1,125 @@ +# 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. + +from synergy_scheduler_manager.common.block_device import BlockDeviceMapping +from synergy_scheduler_manager.tests.unit import base + + +class TestBlockDeviceMapping(base.TestCase): + def setUp(self): + super(TestBlockDeviceMapping, self).setUp() + self.bdp = BlockDeviceMapping('id') + + def test_set_get_Id(self): + self.assertEqual('id', self.bdp.getId()) + + def test_set_get_ImageId(self): + self.bdp.setImageId('im_id') + self.assertEqual('im_id', self.bdp.getImageId()) + + def test_set_get_InstaceId(self): + self.bdp.setInstanceId('inst_id') + self.assertEqual('inst_id', self.bdp.getInstanceId()) + + def test_set_get_SnapshotId(self): + self.bdp.setSnapshotId('snap_id') + self.assertEqual('snap_id', self.bdp.getSnapshotId()) + + def test_set_get_VolumeId(self): + self.bdp.setVolumeId('vol_id') + self.assertEqual('vol_id', self.bdp.getVolumeId()) + + def test_set_get_VolumeSize(self): + self.bdp.setVolumeSize('vol_size') + self.assertEqual('vol_size', self.bdp.getVolumeSize()) + + def test_set_get_BootIndex(self): + self.bdp.setBootIndex('boot_index') + self.assertEqual('boot_index', self.bdp.getBootIndex()) + + def test_set_get_CreatedAt(self): + self.bdp.setCreatedAt('now') + self.assertEqual('now', self.bdp.getCreatedAt()) + + def test_set_get_UpdatedAt(self): + self.bdp.setUpdatedAt('now') + self.assertEqual('now', self.bdp.getUpdatedAt()) + + def test_set_get_DeletedAt(self): + self.bdp.setDeletedAt('now') + self.assertEqual('now', self.bdp.getDeletedAt()) + + def test_set_get_DeviceName(self): + self.bdp.setDeviceName('name') + self.assertEqual('name', self.bdp.getDeviceName()) + + def test_set_get_NoDevice(self): + self.bdp.setNoDevice('no_dev') + self.assertEqual('no_dev', self.bdp.getNoDevice()) + + def test_set_get_ConnectionInfo(self): + self.bdp.setConnectionInfo('con_info') + self.assertEqual('con_info', self.bdp.getConnectionInfo()) + + def test_set_get_DestinationType(self): + self.bdp.setDestinationType('dest_type') + self.assertEqual('dest_type', self.bdp.getDestinationType()) + + def test_set_get_SourceType(self): + self.bdp.setSourceType('source_type') + self.assertEqual('source_type', self.bdp.getSourceType()) + + def test_set_get_DiskBus(self): + self.bdp.setDiskBus('disk_bus') + self.assertEqual('disk_bus', self.bdp.getDiskBus()) + + def test_set_get_GuestFormat(self): + self.bdp.setGuestFormat('guest_format') + self.assertEqual('guest_format', self.bdp.getGuestFormat()) + + def test_DeleteOnTermination(self): + self.bdp.setDeleteOnTermination('del_term') + self.assertEqual('del_term', self.bdp.isDeleteOnTermination()) + + def test_Deleted(self): + self.bdp.setDeleted('deleted') + self.assertEqual('deleted', self.bdp.isDeleted()) + + def test_serialize(self): + res = self.bdp.serialize() + expected = {'boot_index': None, + 'connection_info': None, + 'created_at': None, + 'delete_on_termination': None, + 'deleted': None, + 'deleted_at': None, + 'destination_type': None, + 'device_name': None, + 'device_type': None, + 'disk_bus': None, + 'guest_format': None, + 'id': 'id', + 'image_id': None, + 'instance_uuid': None, + 'no_device': None, + 'snapshot_id': None, + 'source_type': None, + 'updated_at': None, + 'volume_id': None, + 'volume_size': None + } + + self.assertEqual('1.15', res['nova_object.version']) + self.assertEqual('nova', res['nova_object.namespace']) + self.assertEqual(["device_name"], res["nova_object.changes"]) + self.assertEqual("BlockDeviceMapping", res["nova_object.name"]) + self.assertEqual(expected, res["nova_object.data"]) diff --git a/synergy_scheduler_manager/tests/unit/test_compute.py b/synergy_scheduler_manager/tests/unit/test_compute.py new file mode 100644 index 0000000..53a2d5d --- /dev/null +++ b/synergy_scheduler_manager/tests/unit/test_compute.py @@ -0,0 +1,32 @@ +# 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. + +from synergy_scheduler_manager.common.compute import Compute +from synergy_scheduler_manager.tests.unit import base + + +class TestCompute(base.TestCase): + def setUp(self): + super(TestCompute, self).setUp() + self.comp = Compute() + + def test_set_get_Host(self): + self.comp.setHost('host') + self.assertEqual('host', self.comp.getHost()) + + def test_set_get_NodeName(self): + self.comp.setNodeName('node_name') + self.assertEqual('node_name', self.comp.getNodeName()) + + def test_set_get_Limits(self): + self.comp.setLimits('limits') + self.assertEqual('limits', self.comp.getLimits()) diff --git a/synergy_scheduler_manager/tests/unit/test_endpoint.py b/synergy_scheduler_manager/tests/unit/test_endpoint.py new file mode 100755 index 0000000..e3ae5e4 --- /dev/null +++ b/synergy_scheduler_manager/tests/unit/test_endpoint.py @@ -0,0 +1,44 @@ +# 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. + +from synergy_scheduler_manager.common.endpoint import Endpoint +from synergy_scheduler_manager.tests.unit import base + + +class TestEndpoint(base.TestCase): + def setUp(self): + super(TestEndpoint, self).setUp() + self.endp = Endpoint() + + def test_set_get_interface(self): + self.endp.setInterface('pippo') + self.assertEqual('pippo', self.endp.getInterface()) + + def test_set__get_region(self): + self.endp.setRegion('region') + self.assertEqual('region', self.endp.getRegion()) + + def test_set_get_region_id(self): + self.endp.setRegionId('region_id') + self.assertEqual('region_id', self.endp.getRegionId()) + + def test_set_get_service_id(self): + self.endp.setServiceId('service_id') + self.assertEqual('service_id', self.endp.getServiceId()) + + def test_set_get_URL(self): + self.endp.setURL('URL') + self.assertEqual('URL', self.endp.getURL()) + + def test_set_isenable(self): + self.endp.setEnabled('true') + self.assertEqual('true', self.endp.isEnabled()) diff --git a/synergy_scheduler_manager/tests/unit/test_flavor.py b/synergy_scheduler_manager/tests/unit/test_flavor.py new file mode 100755 index 0000000..d09493e --- /dev/null +++ b/synergy_scheduler_manager/tests/unit/test_flavor.py @@ -0,0 +1,32 @@ +# 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. + +from synergy_scheduler_manager.common.flavor import Flavor +from synergy_scheduler_manager.tests.unit import base + + +class TestFlavor(base.TestCase): + def setUp(self): + super(TestFlavor, self).setUp() + self.flav = Flavor() + + def test_set_get_VCPUs(self): + self.flav.setVCPUs(2) + self.assertEqual(2, self.flav.getVCPUs()) + + def test_set__get_Memory(self): + self.flav.setMemory('memory') + self.assertEqual('memory', self.flav.getMemory()) + + def test_set_get_Storage(self): + self.flav.setMemory('storage') + self.assertEqual('storage', self.flav.getMemory()) diff --git a/synergy_scheduler_manager/tests/unit/test_hypervisor.py b/synergy_scheduler_manager/tests/unit/test_hypervisor.py new file mode 100644 index 0000000..ec371ac --- /dev/null +++ b/synergy_scheduler_manager/tests/unit/test_hypervisor.py @@ -0,0 +1,54 @@ +# 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. + +from synergy_scheduler_manager.common.hypervisor import Hypervisor +from synergy_scheduler_manager.tests.unit import base + + +class TestHypervisor(base.TestCase): + def setUp(self): + super(TestHypervisor, self).setUp() + self.hyp = Hypervisor() + + def test_set_get_IP(self): + self.hyp.setIP('ip') + self.assertEqual('ip', self.hyp.getIP()) + + def test_set_get_State(self): + self.hyp.setState('state') + self.assertEqual('state', self.hyp.getState()) + + def test_set_get_Workload(self): + self.assertEqual(0, self.hyp.getWorkload()) + self.hyp.setWorkload(8) + self.assertEqual(8, self.hyp.getWorkload()) + + def test_set_get_VMs(self): + self.assertEqual(0, self.hyp.getVMs()) + self.hyp.setVMs(3) + self.assertEqual(3, self.hyp.getVMs()) + + def test_set_get_VCPUs(self): + self.assertEqual(0, self.hyp.getVCPUs(False)) + self.assertEqual(0, self.hyp.getVCPUs(True)) + self.hyp.setVCPUs(3, False) + self.hyp.setVCPUs(8, True) + self.assertEqual(3, self.hyp.getVCPUs(False)) + self.assertEqual(8, self.hyp.getVCPUs(True)) + + def test_set_get_Memory(self): + self.assertEqual(0, self.hyp.getMemory(False)) + self.assertEqual(0, self.hyp.getMemory(True)) + self.hyp.setMemory(1, False) + self.hyp.setMemory(2, True) + self.assertEqual(1, self.hyp.getMemory(False)) + self.assertEqual(2, self.hyp.getMemory(True)) diff --git a/synergy_scheduler_manager/tests/unit/test_priority.py b/synergy_scheduler_manager/tests/unit/test_priority.py new file mode 100644 index 0000000..26e1697 --- /dev/null +++ b/synergy_scheduler_manager/tests/unit/test_priority.py @@ -0,0 +1,39 @@ +# 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. + +from datetime import datetime +from synergy_scheduler_manager.common.priority import Priority +from synergy_scheduler_manager.tests.unit import base + + +class TestPriority(base.TestCase): + def setUp(self): + super(TestPriority, self).setUp() + self.priority = Priority() + + def test_set_get_Value(self): + self.priority.setValue(1) + self.assertEqual(1, self.priority.getValue()) + + def test_set_get_LastUpdate(self): + self.assertNotEqual(datetime.utcnow(), self.priority.getLastUpdate()) + + def test_set_get_FairShare(self): + self.assertEqual(0.00, self.priority.getFairShare('vcpus')) + self.assertEqual(0.0, self.priority.getFairShare('memory')) + self.assertEqual(0.0, self.priority.getFairShare('disk')) + self.priority.setFairShare('vcpus', 2) + self.assertEqual(2, self.priority.getFairShare('vcpus')) + self.priority.setFairShare('memory', 5.6) + self.assertEqual(5.6, self.priority.getFairShare('memory')) + self.priority.setFairShare('disk', 0.1) + self.assertEqual(0.1, self.priority.getFairShare('disk')) diff --git a/synergy_scheduler_manager/tests/unit/test_project.py b/synergy_scheduler_manager/tests/unit/test_project.py new file mode 100644 index 0000000..049fd4f --- /dev/null +++ b/synergy_scheduler_manager/tests/unit/test_project.py @@ -0,0 +1,71 @@ +# 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. + +from synergy_scheduler_manager.common.project import Project +from synergy_scheduler_manager.common.user import User +from synergy_scheduler_manager.tests.unit import base + + +class TestProject(base.TestCase): + def setUp(self): + super(TestProject, self).setUp() + self.prj = Project() + + def test_set_get_Data(self): + self.assertEqual({}, self.prj.getData()) + + def test_get_Quota(self): + sq = self.prj.getQuota() + sq.setSize('memory', 1024, True) + sq.setSize('vcpus', 100.00, True) + sq.setSize('instances', 10, True) + + self.assertEqual(1024, sq.getSize('memory')) + self.assertEqual(100.0, sq.getSize('vcpus')) + self.assertEqual(10, sq.getSize('instances')) + + def test_get_Share(self): + share = self.prj.getShare() + self.assertEqual(0.0, share.getValue()) + self.assertEqual(0.00, share.getSiblingValue()) + self.assertEqual(0.000, share.getNormalizedValue()) + + def test_set_get_TTL(self): + self.prj.setTTL(0.2) + self.assertEqual(0.2, self.prj.getTTL()) + + def test_set_is_Enable(self): + self.prj.setEnabled('True') + self.assertEqual('True', self.prj.isEnabled()) + + def test_get_add_User(self): + user1 = User() + user1.setId('id1') + user1.setName('name1') + self.prj.addUser(user1) + + user2 = User() + user2.setId('id2') + user2.setName('name2') + self.prj.addUser(user2) + + self.assertEqual('id1', self.prj.getUser('id1').getId()) + self.assertEqual('name1', self.prj.getUser('id1').getName()) + self.assertEqual('id2', self.prj.getUser('id2').getId()) + self.assertEqual('name2', self.prj.getUser('id2').getName()) + + def test_get_Users(self): + user = User() + user.setId('id1') + user.setName('name') + self.prj.addUser(user) + self.assertEqual('name', self.prj.getUser('id1').getName()) diff --git a/synergy_scheduler_manager/tests/unit/test_queue.py b/synergy_scheduler_manager/tests/unit/test_queue.py new file mode 100644 index 0000000..c5bca0f --- /dev/null +++ b/synergy_scheduler_manager/tests/unit/test_queue.py @@ -0,0 +1,242 @@ +# 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 heapq +from mock import call +from mock import create_autospec +from sqlalchemy.engine.base import Engine +from synergy_scheduler_manager.common.queue import PriorityQueue +from synergy_scheduler_manager.common.queue import QueueDB +from synergy_scheduler_manager.common.queue import QueueItem +from synergy_scheduler_manager.tests.unit import base + + +class TestQueueItem(base.TestCase): + + def setUp(self): + super(TestQueueItem, self).setUp() + self.qitem = QueueItem(id=1, + user_id=100, + prj_id=1, + priority=1000, + retry_count=1, + creation_time='now', + last_update='now', + data=1) + + def test_get_set_id(self): + self.assertEqual(1, self.qitem.getId()) + self.qitem.setId(8) + self.assertEqual(8, self.qitem.getId()) + + def test_get_set_userid(self): + self.assertEqual(100, self.qitem.getUserId()) + + self.qitem.setUserId(13) + self.assertEqual(13, self.qitem.getUserId()) + + def test_get_set_projectid(self): + self.assertEqual(1, self.qitem.getProjectId()) + + self.qitem.setProjectId(12) + self.assertEqual(12, self.qitem.getProjectId()) + + def test_get_set_priority(self): + self.assertEqual(1000, self.qitem.getPriority()) + + self.qitem.setPriority(10) + self.assertEqual(10, self.qitem.getPriority()) + + def test_retry_count(self): + self.assertEqual(1, self.qitem.getRetryCount()) + + self.qitem.setRetryCount(10) + self.assertEqual(10, self.qitem.getRetryCount()) + + self.qitem.incRetryCount() + self.assertEqual(11, self.qitem.getRetryCount()) + + def test_get_set_creation_time(self): + self.assertEqual("now", self.qitem.getCreationTime()) + + self.qitem.setCreationTime("later") + self.assertEqual("later", self.qitem.getCreationTime()) + + def test_get_set_last_update(self): + self.assertEqual("now", self.qitem.getLastUpdate()) + + self.qitem.setLastUpdate("later") + self.assertEqual("later", self.qitem.getLastUpdate()) + + def test_get_set_data(self): + self.assertEqual(1, self.qitem.getData()) + + self.qitem.setData(2) + self.assertEqual(2, self.qitem.getData()) + + +class TestPriorityQueue(base.TestCase): + def setUp(self): + super(TestPriorityQueue, self).setUp() + self.pq = PriorityQueue() + + def test_len(self): + self.pq.put(1, "a") + self.pq.put(3, "b") + self.pq.put(10, "c") + self.assertEqual(3, self.pq.__len__()) + + def test_iter(self): + self.pq.put(1, "a") + self.pq.put(3, "b") + self.pq.put(10, "c") + self.assertEqual('c', self.pq.__iter__().get()) + self.assertEqual('b', self.pq.__iter__().get()) + self.assertEqual('a', self.pq.__iter__().get()) + + def test_put_get(self): + self.pq.put(1, "a") + self.pq.put(3, "b") + self.pq.put(10, "c") + + self.assertEqual("c", self.pq.get()) + self.assertEqual("b", self.pq.get()) + self.assertEqual("a", self.pq.get()) + + def test_size(self): + self.pq.put(1, "a") + self.pq.put(3, "b") + self.pq.put(10, "c") + self.assertEqual(3, self.pq.size()) + + def test_items(self): + self.pq.put(1, "a") + self.pq.put(3, "b") + self.pq.put(10, "c") + self.assertEqual('c', self.pq.items()[0][2]) + + def test_smallest(self): + self.pq.put(1, "a") + self.pq.put(3, "b") + self.pq.put(10, "c") + self.assertEqual('c', self.pq.smallest(1)[0][2]) + + def test_largest(self): + self.pq.put(1, "a") + self.pq.put(3, "b") + self.pq.put(10, "c") + self.assertEqual('a', self.pq.largest(1)[0][2]) + + +class TestQueueDB(base.TestCase): + + def setUp(self): + super(TestQueueDB, self).setUp() + + # Create a Queue that mocks database interaction + self.db_engine_mock = create_autospec(Engine) + self.q = QueueDB(name="test", db_engine=self.db_engine_mock) + + def test_get_name(self): + self.assertEqual('test', self.q.getName()) + + def test_close(self): + self.q.close() + self.assertEqual(True, self.q.isClosed()) + + def test_insert_item(self): + + self.q.insertItem(user_id=1, prj_id=2, priority=10, data="mydata") + + # Check the db call of the item insert + insert_call = call.connect().execute( + 'insert into `test` (user_id, prj_id, priority, data) ' + 'values(%s, %s, %s, %s)', [1, 2, 10, '"mydata"']) + self.assertIn(insert_call, self.db_engine_mock.mock_calls) + + # Check the item existence and values in the in-memory queue + priority, index, item = heapq.heappop(self.q.pqueue._heap) + self.assertEqual(-10, priority) + self.assertEqual(0, index) + self.assertEqual(1, item.user_id) + self.assertEqual(2, item.prj_id) + self.assertEqual(10, item.priority) + self.assertEqual(0, item.retry_count) + self.assertIsNone(item.data) # TODO(vincent): should it be "mydata"? + + def test_get_size(self): + execute_mock = self.db_engine_mock.connect().execute + execute_call = call('select count(*) from `test`') + + fetchone_mock = execute_mock().fetchone + fetchone_mock.return_value = [3] + + # Check that getSize() uses the correct sqlalchemy method + self.assertEqual(3, self.q.getSize()) + + # Check that getSize() uses the correct SQL statement + self.assertEqual(execute_call, execute_mock.call_args) + + def test_get_item(self): + # Insert the item and mock its DB insertion + execute_mock = self.db_engine_mock.connect().execute + execute_mock().lastrowid = 123 + self.q.insertItem(user_id=1, prj_id=2, priority=10, data="mydata") + + # Mock the DB select by returning the same things we inserted before + select_mock = self.db_engine_mock.connect().execute + select_call = call("select user_id, prj_id, priority, retry_count, " + "creation_time, last_update, data from `test` " + "where id=%s", [123]) + fetchone_mock = select_mock().fetchone + fetchone_mock.return_value = [1, 2, 10, 0, "now", "now", '"mydata"'] + + item = self.q.getItem() + self.assertEqual(select_call, select_mock.call_args) + self.assertEqual(123, item.id) + self.assertEqual(1, item.user_id) + self.assertEqual(2, item.prj_id) + self.assertEqual(10, item.priority) + self.assertEqual(0, item.retry_count) + self.assertEqual("now", item.creation_time) + self.assertEqual("now", item.last_update) + self.assertEqual("mydata", item.data) + + def test_delete_item(self): + # Mock QueueItem to be deleted + qitem = create_autospec(QueueItem) + qitem.getId.return_value = 123 + + # Mock the DB delete + execute_mock = self.db_engine_mock.connect().execute + execute_call = call("delete from `test` where id=%s", [123]) + + self.q.deleteItem(qitem) + self.assertEqual(execute_call, execute_mock.call_args) + + def test_update_item(self): + # Mock QueueItem to be updated + qitem = create_autospec(QueueItem) + qitem.getPriority.return_value = 10 + qitem.getRetryCount.return_value = 20 + qitem.getLastUpdate.return_value = "right_now" + qitem.getId.return_value = 123 + + # Mock the DB update + execute_mock = self.db_engine_mock.connect().execute + execute_call = call("update `test` set priority=%s, retry_count=%s, " + "last_update=%s where id=%s", + [10, 20, "right_now", 123]) + + # Check the DB call and that the new QueueItem is in the queue + self.q.updateItem(qitem) + self.assertEqual(execute_call, execute_mock.call_args) + self.assertIn((-10, 0, qitem), self.q.pqueue._heap) diff --git a/synergy_scheduler_manager/tests/unit/test_quota.py b/synergy_scheduler_manager/tests/unit/test_quota.py new file mode 100644 index 0000000..cddeb0d --- /dev/null +++ b/synergy_scheduler_manager/tests/unit/test_quota.py @@ -0,0 +1,200 @@ +# 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. + +from synergy_scheduler_manager.common.flavor import Flavor +from synergy_scheduler_manager.common.quota import Quota +from synergy_scheduler_manager.common.quota import SharedQuota +from synergy_scheduler_manager.common.server import Server +from synergy_scheduler_manager.tests.unit import base + + +class TestQuota(base.TestCase): + def setUp(self): + super(TestQuota, self).setUp() + self.quota = Quota() + + def test_get_set_Type(self): + self.quota.setType('Type') + self.assertEqual('Type', self.quota.getType()) + + def test_get_Servers(self): + self.assertEqual([], self.quota.getServers('active', private=True)) + self.assertEqual([], self.quota.getServers('building', private=True)) + self.assertEqual([], self.quota.getServers('error', private=True)) + + self.assertEqual([], self.quota.getServers('active', private=False)) + self.assertEqual([], self.quota.getServers('building', private=False)) + self.assertEqual([], self.quota.getServers('error', private=False)) + + def test_Reset(self): + self.quota.reset() + self.assertEqual(0.0, self.quota.get('private')[ + 'resources']['memory']['used']) + self.assertEqual(0.0, self.quota.get('private')[ + 'resources']['memory']['used']) + self.assertEqual(0.0, self.quota.get('private')[ + 'resources']['memory']['used']) + + def test_set_get_Size(self): + self.quota.setSize('memory', 1024, private=True) + self.quota.setSize('vcpus', 100.00, private=True) + self.quota.setSize('instances', 10, private=True) + + self.assertEqual(1024, self.quota.getSize('memory', private=True)) + self.assertEqual(100.0, self.quota.getSize('vcpus', private=True)) + self.assertEqual(10, self.quota.getSize('instances', private=True)) + + self.quota.setSize('memory', 1, private=False) + self.quota.setSize('vcpus', 11, private=False) + self.quota.setSize('instances', 111, private=False) + + self.assertEqual(1, self.quota.getSize('memory', private=False)) + self.assertEqual(11, self.quota.getSize('vcpus', private=False)) + self.assertEqual(111, self.quota.getSize('instances', private=False)) + + def test_set_get_Usage(self): + self.quota.setUsage('memory', 1024, private=True) + self.quota.setUsage('vcpus', 100.00, private=True) + self.quota.setUsage('instances', 10, private=True) + + self.assertEqual(1024, self.quota.getUsage('memory', private=True)) + self.assertEqual(100.0, self.quota.getUsage('vcpus', private=True)) + self.assertEqual(10, self.quota.getUsage('instances', private=True)) + + self.quota.setUsage('memory', 1024, private=False) + self.quota.setUsage('vcpus', 100.00, private=False) + self.quota.setUsage('instances', 10, private=False) + + self.assertEqual(1024, self.quota.getUsage('memory', private=False)) + self.assertEqual(100.0, self.quota.getUsage('vcpus', private=False)) + self.assertEqual(10, self.quota.getUsage('instances', private=False)) + + self.quota.reset() + self.assertEqual(0, self.quota.getUsage('memory', private=True)) + self.assertEqual(0, self.quota.getUsage('vcpus', private=True)) + self.assertEqual(0, self.quota.getUsage('instances', private=True)) + + def test_Allocate(self): + self.quota.setSize("vcpus", 10, private=True) + self.quota.setSize("memory", 2048, private=True) + + flavor = Flavor() + flavor.setVCPUs(3) + flavor.setMemory(1024) + + server = Server() + server.setId("test_id") + server.setFlavor(flavor) + found = self.quota.allocate(server, blocking=False) + self.assertEqual(3, self.quota.getUsage('vcpus', private=True)) + self.assertEqual(1024, self.quota.getUsage('memory', private=True)) + self.assertEqual(True, found) + + def test_release(self): + self.quota.setSize("vcpus", 10, private=True) + self.quota.setSize("memory", 2048, private=True) + + flavor = Flavor() + flavor.setVCPUs(3) + flavor.setMemory(1024) + + server = Server() + server.setId("test_id") + server.setFlavor(flavor) + self.quota.release(server) + self.assertEqual(0, self.quota.getUsage('vcpus', True)) + self.assertEqual(0, self.quota.getUsage('memory', True)) + + +class TestSharedQuota(base.TestCase): + + def setUp(self): + super(TestSharedQuota, self).setUp() + self.quota = Quota() + + def test_enabled_disable(self): + SharedQuota.enable() + self.assertEqual(True, SharedQuota.isEnabled()) + SharedQuota.disable() + self.assertEqual(False, SharedQuota.isEnabled()) + + def test_set_get_Size(self): + SharedQuota.setSize("vcpus", 1024) + SharedQuota.setSize("memory", 2048) + SharedQuota.setSize("instances", 10) + + self.assertEqual(1024, SharedQuota.getSize('vcpus')) + self.assertEqual(2048, SharedQuota.getSize('memory')) + self.assertEqual(10, SharedQuota.getSize('instances')) + + def test_set_get_Usage(self): + SharedQuota.setUsage('memory', 1024) + SharedQuota.setUsage('vcpus', 30) + self.assertEqual(1024, SharedQuota.getUsage('memory')) + self.assertEqual(30, SharedQuota.getUsage('vcpus')) + + def test_Allocate_Release(self): + SharedQuota.enable() + SharedQuota.setSize("vcpus", 20) + SharedQuota.setSize("memory", 4086) + + self.quota.setSize("vcpus", 10) + self.quota.setSize("memory", 2048) + + flavor1 = Flavor() + flavor1.setVCPUs(2) + flavor1.setMemory(2) + + server1 = Server() + server1.setId("test_id1") + server1.setType("ephemeral") + + server1.setFlavor(flavor1) + self.quota.allocate(server1, blocking=False) + + self.assertEqual(2, SharedQuota.getUsage('memory')) + self.assertEqual(2, SharedQuota.getUsage('vcpus')) + self.assertEqual(0, self.quota.getUsage("memory", private=True)) + self.assertEqual(0, self.quota.getUsage("vcpus", private=True)) + + flavor2 = Flavor() + flavor2.setVCPUs(3) + flavor2.setMemory(3) + + server2 = Server() + server2.setId("test_id2") + server2.setType("permanent") + + server2.setFlavor(flavor2) + + self.quota.allocate(server2, blocking=False) + + self.assertEqual(2, SharedQuota.getUsage('memory')) + self.assertEqual(2, SharedQuota.getUsage('vcpus')) + self.assertEqual(3, self.quota.getUsage("memory", private=True)) + self.assertEqual(3, self.quota.getUsage("vcpus", private=True)) + + self.assertEqual(2, self.quota.getUsage("memory", private=False)) + self.assertEqual(2, self.quota.getUsage("vcpus", private=False)) + + self.quota.release(server1) + + self.assertEqual(0, SharedQuota.getUsage('memory')) + self.assertEqual(0, SharedQuota.getUsage('vcpus')) + + self.assertEqual(0, self.quota.getUsage("vcpus", private=False)) + self.assertEqual(0, self.quota.getUsage("vcpus", private=False)) + + self.quota.release(server2) + + self.assertEqual(0, self.quota.getUsage("vcpus", private=True)) + self.assertEqual(0, self.quota.getUsage("vcpus", private=True)) diff --git a/synergy_scheduler_manager/tests/unit/test_request.py b/synergy_scheduler_manager/tests/unit/test_request.py new file mode 100644 index 0000000..42dffb7 --- /dev/null +++ b/synergy_scheduler_manager/tests/unit/test_request.py @@ -0,0 +1,431 @@ +# 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. + +from synergy_scheduler_manager.common.request import Request +from synergy_scheduler_manager.tests.unit import base + + +class TestRequest(base.TestCase): + + def setUp(self): + super(TestRequest, self).setUp() + + req_dict = { + 'legacy_bdm': True, + 'requested_networks': None, + 'injected_files': [], + 'block_device_mapping': None, + 'image': { + u'status': u'active', + u'created_at': u'2016-03-23T16:47:10.000000', + u'name': u'cirros', + u'deleted': False, + u'container_format': u'bare', + u'min_ram': 0, + u'disk_format': u'qcow2', + u'updated_at': u'2016-03-23T16:47:10.000000', + u'properties': {}, + u'owner': u'1a6edd87f9ec41d8aa64c8f23d719c2a', + u'checksum': u'ee1eca47dc88f4879d8a229cc70a07c6', + u'min_disk': 0, + u'is_public': True, + u'deleted_at': None, + u'id': u'5100f480-a40c-46cd-b8b6-ea2e5e7bf09e', + u'size': 13287936}, + 'filter_properties': { + u'instance_type': { + u'nova_object.version': u'1.1', + u'nova_object.name': u'Flavor', + u'nova_object.namespace': u'nova', + u'nova_object.data': { + u'memory_mb': 512, + u'root_gb': 1, + u'deleted_at': None, + u'name': u'm1.tiny', + u'deleted': False, + u'created_at': None, + u'ephemeral_gb': 0, + u'updated_at': None, + u'disabled': False, + u'vcpus': 1, + u'extra_specs': {}, + u'swap': 0, + u'rxtx_factor': 1.0, + u'is_public': True, + u'flavorid': u'1', + u'vcpu_weight': 0, + u'id': 2}}, + u'scheduler_hints': {}}, + 'instance': { + u'nova_object.version': u'2.0', + u'nova_object.name': u'Instance', + u'nova_object.namespace': u'nova', + u'nova_object.data': { + u'vm_state': u'building', + u'pci_requests': { + u'nova_object.version': u'1.1', + u'nova_object.name': u'InstancePCIRequests', + u'nova_object.namespace': u'nova', + u'nova_object.data': { + u'instance_uuid': u'e3a7770a-8875e2ccc68b', + u'requests': []}}, + u'availability_zone': None, + u'terminated_at': None, + u'ephemeral_gb': 0, + u'instance_type_id': 2, + u'user_data': None, + u'numa_topology': None, + u'cleaned': False, + u'vm_mode': None, + u'flavor': { + u'nova_object.version': u'1.1', + u'nova_object.name': u'Flavor', + u'nova_object.namespace': u'nova', + u'nova_object.data': { + u'memory_mb': 512, + u'root_gb': 1, + u'deleted_at': None, + u'name': u'm1.tiny', + u'deleted': False, + u'created_at': None, + u'ephemeral_gb': 0, + u'updated_at': None, + u'disabled': False, + u'vcpus': 1, + u'extra_specs': {}, + u'swap': 0, + u'rxtx_factor': 1.0, + u'is_public': True, + u'flavorid': u'1', + u'vcpu_weight': 0, + u'id': 2}}, + u'deleted_at': None, + u'reservation_id': u'r-s9v032d0', + u'id': 830, + u'security_groups': { + u'nova_object.version': u'1.0', + u'nova_object.name': u'SecurityGroupList', + u'nova_object.namespace': u'nova', + u'nova_object.data': { + u'objects': []}}, + u'disable_terminate': False, + u'root_device_name': None, + u'display_name': u'user_b1', + u'uuid': u'e3a7770a-dbf6-4b63-8f9a-8875e2ccc68b', + u'default_swap_device': None, + u'info_cache': { + u'nova_object.version': u'1.5', + u'nova_object.name': u'InstanceInfoCache', + u'nova_object.namespace': u'nova', + u'nova_object.data': { + u'instance_uuid': u'e3a7-8875e2ccc68b', + u'deleted': False, + u'created_at': u'2016-09-02T14:01:39Z', + u'updated_at': None, + u'network_info': u'[]', + u'deleted_at': None}}, + u'hostname': u'user-b1', + u'launched_on': None, + u'display_description': u'user_b1', + u'key_data': None, + u'deleted': False, + u'power_state': 0, + u'key_name': None, + u'default_ephemeral_device': None, + u'progress': 0, + u'project_id': u'd20ac1ffa60841a78a865da63b2399de', + u'launched_at': None, + u'metadata': { + u'quota': u'dynamic', + u'persistent': u'False'}, + u'node': None, + u'ramdisk_id': u'', + u'access_ip_v6': None, + u'access_ip_v4': None, + u'kernel_id': u'', + u'old_flavor': None, + u'updated_at': None, + u'host': None, + u'root_gb': 1, + u'user_id': u'4cb9f71a47914d0c8b78a471fd8f7015', + u'system_metadata': { + u'image_min_disk': u'1', + u'image_min_ram': u'0', + u'image_disk_format': u'qcow2', + u'image_base_image_ref': u'5100f480-a25e7bf09e', + u'image_container_format': u'bare'}, + u'task_state': u'scheduling', + u'shutdown_terminate': False, + u'cell_name': None, + u'ephemeral_key_uuid': None, + u'locked': False, + u'created_at': u'2016-09-02T14:01:39Z', + u'locked_by': None, + u'launch_index': 0, + u'memory_mb': 512, + u'vcpus': 1, + u'image_ref': u'a40c-46cd-b8b6-ea2e5e7bf09e', + u'architecture': None, + u'auto_disk_config': False, + u'os_type': None, + u'config_drive': u'', + u'new_flavor': None}}, + 'admin_password': u'URijD456Cezi', + 'context': { + u'domain': None, + u'project_domain': None, + u'auth_token': u'f9d8458ef4ae454dad75f4636304079c', + u'resource_uuid': None, + u'read_only': False, + u'user_id': u'4cb9f71a47914d0c8b78a471fd8f7015', + u'user_identity': u'fa60841a78a865da63b2399de - - -', + u'tenant': u'd20ac1ffa60841a78a865da63b2399de', + u'instance_lock_checked': False, + u'project_id': u'd20ac1ffa60841a78a865da63b2399de', + u'user_name': u'user_b1', + u'project_name': u'prj_b', + u'timestamp': u'2016-09-02T14:01:39.284558', + u'remote_address': u'10.64.31.19', + u'quota_class': None, + u'is_admin': False, + u'user': u'4cb9f71a47914d0c8b78a471fd8f7015', + u'service_catalog': [], + u'read_deleted': u'no', + u'show_deleted': False, + u'roles': [u'user'], + u'request_id': u'req-69c9e7e6-62b2fee1d6e8', + u'user_domain': None}, + 'security_groups': [u'default']} + + self.req = Request.fromDict(req_dict) + + def test_get_AdminPassword(self): + self.assertEqual(u'URijD456Cezi', self.req.getAdminPassword()) + + def test_get_Id(self): + self.assertEqual( + u'e3a7770a-dbf6-4b63-8f9a-8875e2ccc68b', + self.req.getId()) + + def test_get_Instance(self): + ist = { + u'nova_object.data': { + u'access_ip_v4': None, + u'access_ip_v6': None, + u'architecture': None, + u'auto_disk_config': False, + u'availability_zone': None, + u'cell_name': None, + u'cleaned': False, + u'config_drive': u'', + u'created_at': u'2016-09-02T14:01:39Z', + u'default_ephemeral_device': None, + u'default_swap_device': None, + u'deleted': False, + u'deleted_at': None, + u'disable_terminate': False, + u'display_description': u'user_b1', + u'display_name': u'user_b1', + u'ephemeral_gb': 0, + u'ephemeral_key_uuid': None, + u'flavor': { + u'nova_object.data': { + u'created_at': None, + u'deleted': False, + u'deleted_at': None, + u'disabled': False, + u'ephemeral_gb': 0, + u'extra_specs': {}, + u'flavorid': u'1', + u'id': 2, + u'is_public': True, + u'memory_mb': 512, + u'name': u'm1.tiny', + u'root_gb': 1, + u'rxtx_factor': 1.0, + u'swap': 0, + u'updated_at': None, + u'vcpu_weight': 0, + u'vcpus': 1}, + u'nova_object.name': u'Flavor', + u'nova_object.namespace': u'nova', + u'nova_object.version': u'1.1'}, + u'host': None, + u'hostname': u'user-b1', + u'id': 830, + u'image_ref': u'a40c-46cd-b8b6-ea2e5e7bf09e', + u'info_cache': { + u'nova_object.data': { + u'created_at': u'2016-09-02T14:01:39Z', + u'deleted': False, + u'deleted_at': None, + u'instance_uuid': u'e3a7-8875e2ccc68b', + u'network_info': u'[]', + u'updated_at': None}, + u'nova_object.name': u'InstanceInfoCache', + u'nova_object.namespace': u'nova', + u'nova_object.version': u'1.5'}, + u'instance_type_id': 2, + u'kernel_id': u'', + u'key_data': None, + u'key_name': None, + u'launch_index': 0, + u'launched_at': None, + u'launched_on': None, + u'locked': False, + u'locked_by': None, + u'memory_mb': 512, + u'metadata': { + u'persistent': u'False', + u'quota': u'dynamic'}, + u'new_flavor': None, + u'node': None, + u'numa_topology': None, + u'old_flavor': None, + u'os_type': None, + u'pci_requests': { + u'nova_object.data': { + u'instance_uuid': u'e3a7770a-8875e2ccc68b', + u'requests': []}, + u'nova_object.name': u'InstancePCIRequests', + u'nova_object.namespace': u'nova', + u'nova_object.version': u'1.1'}, + u'power_state': 0, + u'progress': 0, + u'project_id': u'd20ac1ffa60841a78a865da63b2399de', + u'ramdisk_id': u'', + u'reservation_id': u'r-s9v032d0', + u'root_device_name': None, + u'root_gb': 1, + u'security_groups': { + u'nova_object.data': { + u'objects': []}, + u'nova_object.name': u'SecurityGroupList', + u'nova_object.namespace': u'nova', + u'nova_object.version': u'1.0'}, + u'shutdown_terminate': False, + u'system_metadata': { + u'image_base_image_ref': u'5100f480-a25e7bf09e', + u'image_container_format': u'bare', + u'image_disk_format': u'qcow2', + u'image_min_disk': u'1', + u'image_min_ram': u'0'}, + u'task_state': u'scheduling', + u'terminated_at': None, + u'updated_at': None, + u'user_data': None, + u'user_id': u'4cb9f71a47914d0c8b78a471fd8f7015', + u'uuid': u'e3a7770a-dbf6-4b63-8f9a-8875e2ccc68b', + u'vcpus': 1, + u'vm_mode': None, + u'vm_state': u'building'}, + u'nova_object.name': u'Instance', + u'nova_object.namespace': u'nova', + u'nova_object.version': u'2.0'} + + self.assertEqual(ist, self.req.getInstance()) + + def test_get_Image(self): + im = {u'checksum': u'ee1eca47dc88f4879d8a229cc70a07c6', + u'container_format': u'bare', + u'created_at': u'2016-03-23T16:47:10.000000', + u'deleted': False, + u'deleted_at': None, + u'disk_format': u'qcow2', + u'id': u'5100f480-a40c-46cd-b8b6-ea2e5e7bf09e', + u'is_public': True, + u'min_disk': 0, + u'min_ram': 0, + u'name': u'cirros', + u'owner': u'1a6edd87f9ec41d8aa64c8f23d719c2a', + u'properties': {}, + u'size': 13287936, + u'status': u'active', + u'updated_at': u'2016-03-23T16:47:10.000000'} + + self.assertEqual(im, self.req.getImage()) + + def test_get_Context(self): + cont = {u'auth_token': u'f9d8458ef4ae454dad75f4636304079c', + u'domain': None, + u'instance_lock_checked': False, + u'is_admin': False, + u'project_domain': None, + u'project_id': u'd20ac1ffa60841a78a865da63b2399de', + u'project_name': u'prj_b', + u'quota_class': None, + u'read_deleted': u'no', + u'read_only': False, + u'remote_address': u'10.64.31.19', + u'request_id': u'req-69c9e7e6-62b2fee1d6e8', + u'resource_uuid': None, + u'roles': [u'user'], + u'service_catalog': [], + u'show_deleted': False, + u'tenant': u'd20ac1ffa60841a78a865da63b2399de', + u'timestamp': u'2016-09-02T14:01:39.284558', + u'user': u'4cb9f71a47914d0c8b78a471fd8f7015', + u'user_domain': None, + u'user_id': u'4cb9f71a47914d0c8b78a471fd8f7015', + u'user_identity': u'fa60841a78a865da63b2399de - - -', + u'user_name': u'user_b1'} + + self.assertEqual(cont, self.req.getContext()) + + def test_get_FilterProperties(self): + filt = {u'instance_type': { + u'nova_object.data': {u'created_at': None, + u'deleted': False, + u'deleted_at': None, + u'disabled': False, + u'ephemeral_gb': 0, + u'extra_specs': {}, + u'flavorid': u'1', + u'id': 2, + u'is_public': True, + u'memory_mb': 512, + u'name': u'm1.tiny', + u'root_gb': 1, + u'rxtx_factor': 1.0, + u'swap': 0, + u'updated_at': None, + u'vcpu_weight': 0, + u'vcpus': 1}, + u'nova_object.name': u'Flavor', + u'nova_object.namespace': u'nova', + u'nova_object.version': u'1.1'}, + u'scheduler_hints': {}} + + self.assertEqual(filt, self.req.getFilterProperties()) + + def test_get_InjectedFiles(self): + self.assertEqual([], self.req.getInjectedFiles()) + + def test_get_RequestedNetworks(self): + self.assertEqual(None, self.req.getRequestedNetworks()) + + def test_get_SecurityGroups(self): + self.assertEqual([u'default'], self.req.getSecurityGroups()) + + def test_get_BlockDeviceMapping(self): + self.assertEqual(None, self.req.getBlockDeviceMapping()) + + def test_get_LegacyBDM(self): + self.assertEqual(True, self.req.getLegacyBDM()) + + def test_get_Server(self): + prjId = self.req.getServer().getProjectId() + self.assertEqual('d20ac1ffa60841a78a865da63b2399de', prjId) + + def test_from_to_Dict(self): + rq_dict = self.req.toDict() + self.assertEqual(True, rq_dict['legacy_bdm']) diff --git a/synergy_scheduler_manager/tests/unit/test_server.py b/synergy_scheduler_manager/tests/unit/test_server.py new file mode 100644 index 0000000..7508607 --- /dev/null +++ b/synergy_scheduler_manager/tests/unit/test_server.py @@ -0,0 +1,79 @@ +# 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. + +from datetime import datetime +from synergy_scheduler_manager.common.server import Server +from synergy_scheduler_manager.tests.unit import base + + +class TestServer(base.TestCase): + def setUp(self): + super(TestServer, self).setUp() + self.server = Server() + + def test_set_get_Type(self): + self.server.setType('type') + self.assertEqual('type', self.server.getType()) + + def test_set_get_State(self): + self.server.setState('state') + self.assertEqual('state', self.server.getState()) + + def test_set_get_Flavor(self): + self.server.setFlavor('flavor') + self.assertEqual('flavor', self.server.getFlavor()) + + def test_set_get_KeyName(self): + self.server.setKeyName('keyname') + self.assertEqual('keyname', self.server.getKeyName()) + + def test_set_get_Metadata(self): + self.server.setMetadata('metadata') + self.assertEqual('metadata', self.server.getMetadata()) + + def test_set_get_UserId(self): + self.server.setUserId('user_id') + self.assertEqual('user_id', self.server.getUserId()) + + def test_set_get_ProjectId(self): + self.server.setProjectId('project_id') + self.assertEqual('project_id', self.server.getProjectId()) + + def test_set_get_CreatedAt(self): + self.server.setCreatedAt('2015-02-10T13:00:10Z') + str_date = '2015-02-10T13:00:10Z' + datetime_date = datetime.strptime(str_date, "%Y-%m-%dT%H:%M:%SZ") + self.assertEqual(datetime_date, self.server.getCreatedAt()) + + def test_set_get_LaunchedAt(self): + self.server.setLaunchedAt('2015-02-10T13:00:10Z') + str_date = '2015-02-10T13:00:10Z' + datetime_date = datetime.strptime(str_date, "%Y-%m-%dT%H:%M:%SZ") + self.assertEqual(datetime_date, self.server.getLaunchedAt()) + + def test_set_get_UpdatedAt(self): + self.server.setUpdatedAt('2015-02-10T13:00:10Z') + str_date = '2015-02-10T13:00:10Z' + datetime_date = datetime.strptime(str_date, "%Y-%m-%dT%H:%M:%SZ") + self.assertEqual(datetime_date, self.server.getUpdatedAt()) + + def test_set_get_TerminatedAt(self): + self.server.setTerminatedAt('2015-02-10T13:00:10Z') + str_date = '2015-02-10T13:00:10Z' + datetime_date = datetime.strptime(str_date, "%Y-%m-%dT%H:%M:%SZ") + self.assertEqual(datetime_date, self.server.getTerminatedAt()) + + def test_is_Ephemeral(self): + self.assertEqual(False, self.server.isEphemeral()) + + def test_is_Permanent(self): + self.assertEqual(True, self.server.isPermanent()) diff --git a/synergy_scheduler_manager/tests/unit/test_service.py b/synergy_scheduler_manager/tests/unit/test_service.py new file mode 100644 index 0000000..8f7d3ae --- /dev/null +++ b/synergy_scheduler_manager/tests/unit/test_service.py @@ -0,0 +1,41 @@ +# 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. + +from synergy_scheduler_manager.common.service import Service +from synergy_scheduler_manager.tests.unit import base + + +class TestService(base.TestCase): + def setUp(self): + super(TestService, self).setUp() + self.service = Service() + + def test_set_get_Type(self): + self.service.setType('type') + self.assertEqual('type', self.service.getType()) + + def test_get_Endpoints(self): + self.assertEqual([], self.service.getEndpoints()) + + def test_get_Endpoint(self): + self.assertEqual(None, self.service.getEndpoint('interface')) + + def test_set_get_Description(self): + self.service.setDescription('description') + self.assertEqual('description', self.service.getDescription()) + + def test_set_get_Status(self): + self.service.setStatus('enabled') + self.assertEqual('enabled', self.service.getStatus()) + + def test_isEnable(self): + self.assertEqual(False, self.service.isEnabled()) diff --git a/synergy_scheduler_manager/tests/unit/test_share.py b/synergy_scheduler_manager/tests/unit/test_share.py new file mode 100644 index 0000000..202d118 --- /dev/null +++ b/synergy_scheduler_manager/tests/unit/test_share.py @@ -0,0 +1,32 @@ +# 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. + +from synergy_scheduler_manager.common.user import Share +from synergy_scheduler_manager.tests.unit import base + + +class TestShare(base.TestCase): + def setUp(self): + super(TestShare, self).setUp() + self.share = Share() + + def test_set_get_Value(self): + self.share.setValue('value') + self.assertEqual('value', self.share.getValue()) + + def test_set_get_SiblingValue(self): + self.share.setSiblingValue('sibling_value') + self.assertEqual('sibling_value', self.share.getSiblingValue()) + + def test_set_get_NormalizedValue(self): + self.share.setNormalizedValue('normalized_value') + self.assertEqual('normalized_value', self.share.getNormalizedValue()) diff --git a/synergy_scheduler_manager/tests/unit/test_token.py b/synergy_scheduler_manager/tests/unit/test_token.py new file mode 100644 index 0000000..56cb11d --- /dev/null +++ b/synergy_scheduler_manager/tests/unit/test_token.py @@ -0,0 +1,186 @@ +# 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. + +from datetime import datetime +from synergy_scheduler_manager.common.project import Project +from synergy_scheduler_manager.common.token import Token +from synergy_scheduler_manager.common.user import User +from synergy_scheduler_manager.tests.unit import base + + +class TestToken(base.TestCase): + + def setUp(self): + super(TestToken, self).setUp() + self.token = Token() + + def test_get_Services(self): + self.assertEqual([], self.token.getServices()) + + def test_set_get_Creation(self): + self.token.setCreation('issue_at') + self.assertEqual('issue_at', self.token.getCreation()) + + def test_set_get_Expiration(self): + self.token.setExpiration('expires_at') + self.assertEqual('expires_at', self.token.getExpiration()) + + def test_set_get_Extras(self): + self.assertEqual({}, self.token.getExtras()) + + def test_set_get_Project(self): + prj = Project() + self.token.setProject(prj) + self.assertEqual(0.0, self.token.getProject().getTTL()) + + def test_get_Roles(self): + self.assertEqual({}, self.token.getExtras()) + + def test_set_get_User(self): + user = User() + self.token.setUser(user) + self.assertEqual({}, self.token.getUser().getData()) + + def test_isAdmin(self): + self.assertEqual(False, self.token.isAdmin()) + + def test_issuedAt(self): + self.token.setCreation('now') + self.assertEqual('now', self.token.issuedAt()) + + def test_isExpired(self): + str_date = '2015-02-10T13:00:10Z' + datetime_date = datetime.strptime( + str_date, "%Y-%m-%dT%H:%M:%SZ") + self.token.setExpiration(datetime_date) + self.assertEqual(True, self.token.isExpired()) + + def test_parse(self): + tok = { + u'token': { + u'methods': [u'password'], + u'roles': [{u'id': u'1efd5e18c8414086889ac93d8b11c411', + u'name': u'admin'}], + u'expires_at': u'2016-09-02T12:07:45.814651Z', + u'project': {u'domain': {u'id': u'default', + u'name': u'Default'}, + u'id': u'1a6edd87f9ec41d8aa64c8f23d719c2a', + u'name': u'admin'}, + u'catalog': [{ + u'endpoints': [{ + u'url': u'http://10.64.31.19:8774/v2/1a6edd8a', + u'interface': u'internal', + u'region': u'RegionOne', + u'region_id': u'RegionOne', + u'id': u'ac178ac5b9f34647b9fce954966470c9'}, + {u'url': u'http://10.64.31.19:8774/v2/1a6edd2a', + u'interface': u'admin', + u'region': u'RegionOne', + u'region_id': u'RegionOne', + u'id': u'b81a3518d24d433495985a31740a5b84'}, + {u'url': u'http://10.64.31.19:8774/v2/1a6ec2a', + u'interface': u'public', + u'region': u'RegionOne', + u'region_id': u'RegionOne', + u'id': u'db03d5f72d89461582d4d87e7f82302a'}], + u'type': u'compute', + u'id': u'27dd094445aa42a9b05ca08c1f094d28', + u'name': u'nova'}, + { + u'endpoints': [{ + u'url': u'http://10.64.31.19:9292', + u'interface': u'internal', + u'region': u'RegionOne', + u'region_id': u'RegionOne', + u'id': u'2f5458cf516147ffa1be04fabfbec9f8'}, + {u'url': u'http://10.64.31.19:9292', + u'interface': u'admin', + u'region': u'RegionOne', + u'region_id': u'RegionOne', + u'id': u'468db3a59f644d5d8cea849a'}, + {u'url': u'http://10.64.31.19:9292', + u'interface': u'public', + u'region': u'RegionOne', + u'region_id': u'RegionOne', + u'id': u'a13689dba17c432aa4c1f5b'}], + u'type': u'image', + u'id': u'6d80825e9f4544ea9a2d0a2d952b2e7c', + u'name': u'glance'}, + {u'endpoints': [{u'url': u'http://10.64.31.19:9696', + u'interface': u'admin', + u'region': u'RegionOne', + u'region_id': u'RegionOne', + u'id': u'567ed76f7161484f9c2556'}, + {u'url': u'http://10.64.31.19:9696', + u'interface': u'internal', + u'region': u'RegionOne', + u'region_id': u'RegionOne', + u'id': u'90373f0a87bc41cc99095'}, + {u'url': u'http://10.64.31.19:9696', + u'interface': u'public', + u'region': u'RegionOne', + u'region_id': u'RegionOne', + u'id': u'a95ad02ab '}], + u'type': u'network', + u'id': u'8f4c00132ccb422cbc0b6139266ad1df', + u'name': u'neutron'}, + {u'endpoints': [{u'url': u'http://10.64.31.19:50v3', + u'interface': u'internal', + u'region': u'RegionOne', + u'region_id': u'RegionOne', + u'id': u'494ccc932f0a6f8a86e629'}, + {u'url': u'http://10.64.31.19:5/v3', + u'interface': u'public', + u'region': u'RegionOne', + u'region_id': u'RegionOne', + u'id': u'5503912c691c2b25e1855f9'}, + {u'url': u'http://10.64.31.19357/v3', + u'interface': u'admin', + u'region': u'RegionOne', + u'region_id': u'RegionOne', + u'id': u'6d54bb99a2994ea8f527'}], + u'type': u'identity', + u'id': u'af19f5adf97c4f3fb1aa9d4fe499dd9', + u'name': u'keystone'}, + {u'endpoints': [{u'url': u'http://10.64.31.19:8051', + u'interface': u'public', + u'region': u'RegionOne', + u'region_id': u'RegionOne', + u'id': u'68d6e8128cf84037138a3d'}, + {u'url': u'http://10.64.31.19:8051', + u'interface': u'admin', + u'region': u'RegionOne', + u'region_id': u'RegionOne', + u'id': u'bc0bb63fd58044387a0e61b'}, + {u'url': u'http://10.64.31.19:8051', + u'interface': u'internal', + u'region': u'RegionOne', + u'region_id': u'RegionOne', + u'id': u'f1bc92a85cc80bac0d'}], + u'type': u'management', + u'id': u'b1c0d32b48934ac380f6a231dce448f0', + u'name': u'synergy'}], + u'extras': {}, + u'user': {u'domain': {u'id': u'default', + u'name': u'Default'}, + u'id': u'9364456de70b46e59ba4dbd74498ccb0', + u'name': u'admin'}, + u'audit_ids': [u'2XDA6g5nQeuuSg-vvhPzfw'], + u'issued_at': u'2016-09-02T11:57:45.814689Z'}} + + self.tk = Token.parse('tok_id', tok) + self.assertEqual({}, self.tk.getExtras()) + + str_date = '2016-09-02T12:07:45.814651Z' + datetime_date = datetime.strptime(str_date, + "%Y-%m-%dT%H:%M:%S.%fZ") + self.assertEqual(datetime_date, self.tk.getExpiration()) diff --git a/synergy_scheduler_manager/tests/unit/test_trust.py b/synergy_scheduler_manager/tests/unit/test_trust.py new file mode 100644 index 0000000..1edd341 --- /dev/null +++ b/synergy_scheduler_manager/tests/unit/test_trust.py @@ -0,0 +1,63 @@ +# 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. + +from synergy_scheduler_manager.common.trust import Trust + +from synergy_scheduler_manager.tests.unit import base + + +class TestTrust(base.TestCase): + + def setUp(self): + super(TestTrust, self).setUp() + # fake data + data = {"id": "id", "trustor_user_id": "trustor_user_id", + "impersonation": "impersonation", + "trustee_user_id": "trustee_user_id", + "links": [], + "roles": [], + "remaining_uses": "remaining_uses", + "expires_at": None, + "keystone_url": None, + "project_id": "project_id" + } + self.trust = Trust(data) + + def test_get_Id(self): + self.assertEqual('id', self.trust.getId()) + + def test_isImpersonations(self): + self.assertEqual('impersonation', self.trust.isImpersonations()) + + def getRolesLinks(self): + pass + + def test_get_trustorUserId(self): + self.assertEqual('trustor_user_id', self.trust.getTrustorUserId()) + + def test_get_trusteeUserId(self): + self.assertEqual('trustee_user_id', self.trust.getTrusteeUserId()) + + def test_get_roles(self): + self.assertEqual([], self.trust.getRoles()) + + def test_get_links(self): + self.assertEqual([], self.trust.getlinks()) + + def test_get_remainingUses(self): + self.assertEqual("remaining_uses", self.trust.getRemainingUses()) + + def test_get_expiration(self): + self.assertEqual(None, self.trust.getExpiration()) + + def test_isExpired(self): + self.assertEqual(False, self.trust.isExpired()) diff --git a/synergy_scheduler_manager/tests/unit/test_user.py b/synergy_scheduler_manager/tests/unit/test_user.py new file mode 100644 index 0000000..d4d6974 --- /dev/null +++ b/synergy_scheduler_manager/tests/unit/test_user.py @@ -0,0 +1,51 @@ +# 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. +from datetime import datetime +from synergy_scheduler_manager.common.user import User +from synergy_scheduler_manager.tests.unit import base + + +class TestUser(base.TestCase): + def setUp(self): + super(TestUser, self).setUp() + self.user = User() + + def test_get_Data(self): + data = self.user.getData() + data['data'] = 'data' + self.assertEqual('data', data['data']) + + def test_set_get_ProjectId(self): + self.user.setProjectId('project_id') + self.assertEqual('project_id', self.user.getProjectId()) + + def test_set_get_Role(self): + self.user.setRole('role') + self.assertEqual('role', self.user.getRole()) + + def test_get_Priority(self): + priority = self.user.getPriority() + self.assertEqual(0, priority.getValue()) + self.assertNotEqual(datetime.utcnow(), priority.getLastUpdate()) + self.assertEqual(0.0, priority.getFairShare('vcpus')) + self.assertEqual(0.0, priority.getFairShare('memory')) + self.assertEqual(0.0, priority.getFairShare('disk')) + + def test_get_Share(self): + share = self.user.getShare() + self.assertEqual(0.0, share.getValue()) + self.assertEqual(0.00, share.getSiblingValue()) + self.assertEqual(0.000, share.getNormalizedValue()) + + def test_set_isenable(self): + self.user.setEnabled('true') + self.assertEqual('true', self.user.isEnabled())