CORD-2443 unit tests for LeastLoadedNodeScheduler;
Fix bug where existing label ignored

Change-Id: Ifc6cb320cdec318f59ed344db3bf17f8e4dc4067
diff --git a/xos/synchronizers/new_base/model_policies/model_policy_tenantwithcontainer.py b/xos/synchronizers/new_base/model_policies/model_policy_tenantwithcontainer.py
index 2fd891b..b9b6b32 100644
--- a/xos/synchronizers/new_base/model_policies/model_policy_tenantwithcontainer.py
+++ b/xos/synchronizers/new_base/model_policies/model_policy_tenantwithcontainer.py
@@ -51,11 +51,12 @@
             if not nodes:
                 set_label = self.constrain_by_service_instance
 
-        if not nodes and self.slice.default_node:
-            # if slice.default_node is set, then filter by default_node
-            nodes = Node.objects.filter(name=self.slice.default_node)
-        else:
-            nodes = Node.objects.all()
+        if not nodes:
+            if self.slice.default_node:
+                # if slice.default_node is set, then filter by default_node
+                nodes = Node.objects.filter(name=self.slice.default_node)
+            else:
+                nodes = Node.objects.all()
 
         # convert to list
         nodes = list(nodes)
diff --git a/xos/synchronizers/new_base/model_policies/test_model_policy_tenantwithcontainer.py b/xos/synchronizers/new_base/model_policies/test_model_policy_tenantwithcontainer.py
index 82f5fd4..33b7914 100644
--- a/xos/synchronizers/new_base/model_policies/test_model_policy_tenantwithcontainer.py
+++ b/xos/synchronizers/new_base/model_policies/test_model_policy_tenantwithcontainer.py
@@ -27,11 +27,12 @@
 
 class TestModelPolicyTenantWithContainer(unittest.TestCase):
     def setUp(self):
-        global TenantWithContainerPolicy, LeastLoadedNodeScheduler
+        global TenantWithContainerPolicy, LeastLoadedNodeScheduler, MockObjectList
 
         self.sys_path_save = sys.path
         self.cwd_save = os.getcwd()
         sys.path.append(xos_dir)
+        sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base'))
         sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base', 'model_policies'))
 
         config = basic_conf = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/test_config.yaml")
@@ -44,6 +45,8 @@
         import model_policy_tenantwithcontainer
         from model_policy_tenantwithcontainer import TenantWithContainerPolicy, LeastLoadedNodeScheduler
 
+        from mock_modelaccessor import MockObjectList
+
         # import all class names to globals
         for (k, v) in model_policy_tenantwithcontainer.model_accessor.all_model_classes.items():
             globals()[k] = v
@@ -140,5 +143,113 @@
             self.policy.manage_container(self.tenant)
         self.assertEqual(e.exception.message, "No m1.small flavor")
 
+    def test_least_loaded_node_scheduler(self):
+        with patch.object(Node.objects, "get_items") as node_objects:
+            slice = Slice(name="mysite_test1", default_flavor=None, default_isolation="vm")
+            node = Node(hostname="my.node.com", id=4567)
+            node.instances = MockObjectList(initial=[])
+            node_objects.return_value = [node]
+
+            sched = LeastLoadedNodeScheduler(slice)
+            (picked_node, parent) = sched.pick()
+
+            self.assertNotEqual(picked_node, None)
+            self.assertEqual(picked_node.id, node.id)
+
+    def test_least_loaded_node_scheduler_two_nodes(self):
+        with patch.object(Node.objects, "get_items") as node_objects:
+            slice = Slice(name="mysite_test1", default_flavor=None, default_isolation="vm")
+            instance1 = Instance(id=1)
+            node1 = Node(hostname="my.node.com", id=4567)
+            node1.instances = MockObjectList(initial=[])
+            node2 = Node(hostname="my.node.com", id=8910)
+            node2.instances = MockObjectList(initial=[instance1])
+            node_objects.return_value = [node1, node2]
+
+            # should pick the node with the fewest instance (node1)
+
+            sched = LeastLoadedNodeScheduler(slice)
+            (picked_node, parent) = sched.pick()
+
+            self.assertNotEqual(picked_node, None)
+            self.assertEqual(picked_node.id, node1.id)
+
+    def test_least_loaded_node_scheduler_two_nodes_multi(self):
+        with patch.object(Node.objects, "get_items") as node_objects:
+            slice = Slice(name="mysite_test1", default_flavor=None, default_isolation="vm")
+            instance1 = Instance(id=1)
+            instance2 = Instance(id=2)
+            instance3 = Instance(id=3)
+            node1 = Node(hostname="my.node.com", id=4567)
+            node1.instances = MockObjectList(initial=[instance2, instance3])
+            node2 = Node(hostname="my.node.com", id=8910)
+            node2.instances = MockObjectList(initial=[instance1])
+            node_objects.return_value = [node1, node2]
+
+            # should pick the node with the fewest instance (node2)
+
+            sched = LeastLoadedNodeScheduler(slice)
+            (picked_node, parent) = sched.pick()
+
+            self.assertNotEqual(picked_node, None)
+            self.assertEqual(picked_node.id, node2.id)
+
+    def test_least_loaded_node_scheduler_with_label(self):
+        with patch.object(Node.objects, "get_items") as node_objects:
+            slice = Slice(name="mysite_test1", default_flavor=None, default_isolation="vm")
+            instance1 = Instance(id=1)
+            node1 = Node(hostname="my.node.com", id=4567)
+            node1.instances = MockObjectList(initial=[])
+            node2 = Node(hostname="my.node.com", id=8910)
+            node2.instances = MockObjectList(initial=[instance1])
+            # Fake out the existence of a NodeLabel object. TODO: Extend the mock framework to support the model__field
+            # syntax.
+            node1.nodelabels__name = None
+            node2.nodelabels__name = "foo"
+            node_objects.return_value = [node1, node2]
+
+            # should pick the node with the label, even if it has a greater number of instances
+
+            sched = LeastLoadedNodeScheduler(slice, label="foo")
+            (picked_node, parent) = sched.pick()
+
+            self.assertNotEqual(picked_node, None)
+            self.assertEqual(picked_node.id, node2.id)
+
+    def test_least_loaded_node_scheduler_create_label(self):
+        with patch.object(Node.objects, "get_items") as node_objects, \
+             patch.object(NodeLabel, "save", autospec=True) as nodelabel_save, \
+             patch.object(NodeLabel, "node") as nodelabel_node_add:
+            slice = Slice(name="mysite_test1", default_flavor=None, default_isolation="vm")
+            instance1 = Instance(id=1)
+            node1 = Node(hostname="my.node.com", id=4567)
+            node1.instances = MockObjectList(initial=[])
+            node2 = Node(hostname="my.node.com", id=8910)
+            node2.instances = MockObjectList(initial=[instance1])
+            # Fake out the existence of a NodeLabel object. TODO: Extend the mock framework to support the model__field
+            # syntax.
+            node1.nodelabels__name = None
+            node2.nodelabels__name = None
+            node_objects.return_value = [node1, node2]
+
+            # should pick the node with the least number of instances
+
+            sched = LeastLoadedNodeScheduler(slice, label="foo", constrain_by_service_instance = True)
+            (picked_node, parent) = sched.pick()
+
+            self.assertNotEqual(picked_node, None)
+            self.assertEqual(picked_node.id, node1.id)
+
+            # NodeLabel should have been created and saved
+
+            self.assertEqual(nodelabel_save.call_count, 1)
+            self.assertEqual(nodelabel_save.call_args[0][0].name, "foo")
+
+            # The NodeLabel's node field should have been added to
+
+            NodeLabel.node.add.assert_called_with(node1)
+
+
+
 if __name__ == '__main__':
     unittest.main()