def _ejection_test(self, env):
        """Are we ejecting nodes whose images are no longer in the current
        image list?
        """
        strategy = sps.SimpleProportionalStrategy()
        strategy.update_current_state(**env)
        directives = strategy.directives()
        if len(directives) == 0:
            directives = [sb.CacheNode('a', 'b', 'c')]
        ejection_directives = filter(
            lambda direct: isinstance(direct, sb.EjectNode), directives)
        ejected_node_uuids = sb.build_attribute_set(ejection_directives,
                                                    'node_uuid')
        for node in env['nodes']:
            # Make sure that cached nodes with invalid images are ejected.
            if node.cached_image_uuid == INVALID_IMAGE.uuid and node.cached:
                self.assertIn(node.node_uuid,
                              ejected_node_uuids,
                              ("A node with an invalid image UUID was not "
                               "ejected from the cache. Node UUID: %s" % (
                                   node.node_uuid)))

        # Ensure ejected nodes are marked as 'provisioned'.
        nodes_by_uuid = {node.node_uuid: node for node in env['nodes']}
        for node_uuid in ejected_node_uuids:
            self.assertTrue(nodes_by_uuid[node_uuid].provisioned)

        # Make sure the strategy is not trying to cache to ejected nodes.
        cache_directives = filter(
            lambda direct: isinstance(direct, sb.CacheNode), directives)
        cached_node_uuids = sb.build_attribute_set(cache_directives,
                                                   'node_uuid')
        self.assertEqual(
            0, len(cached_node_uuids.intersection(ejected_node_uuids)),
            "One or more ejected nodes scheduled to cache immediately!")
def segregate_nodes(nodes, flavors):
    """Segregate nodes by flavor."""
    nodes_by_flavor = {}
    for flavor in flavors:
        nodes_by_flavor[flavor.name] = []

    flavor_names = sb.build_attribute_set(flavors, "name")

    for node in nodes:
        if node.flavor not in flavor_names:
            LOG.error(
                "Node '%(node)s'with unrecognized flavor '%(flavor)s " "detected. ",
                {"node": node.uuid, "flavor": node.flavor},
            )
            next
        nodes_by_flavor[node.flavor].append(node)

    return nodes_by_flavor
def segregate_nodes(nodes, flavors):
    """Segregate nodes by flavor."""
    nodes_by_flavor = {}
    for flavor in flavors:
        nodes_by_flavor[flavor.name] = []

    flavor_names = sb.build_attribute_set(flavors, 'name')

    for node in nodes:
        if node.flavor not in flavor_names:
            LOG.error(
                "Node '%(node)s'with unrecognized flavor '%(flavor)s "
                "detected. ", {
                    'node': node.uuid,
                    'flavor': node.flavor
                })
            next
        nodes_by_flavor[node.flavor].append(node)

    return nodes_by_flavor
    def update_current_state(self, nodes, images, flavors):
        # For now, flavors should remain static.
        # In the future we'll handle changing flavor profiles if needed,
        # but it seems unlikely that flavors will change often enough.
        self.flavor_diff = sb.find_flavor_differences(self.current_flavors, flavors)
        sb.log_flavor_differences(self.flavor_diff)
        self.current_flavors = flavors

        # Image differences are important because changed or retired
        # images should be ejected from the cache.
        self.image_diff = sb.find_image_differences(self.current_images, images)
        sb.log_image_differences(self.image_diff)
        self.current_images = images
        self.current_image_uuids = sb.build_attribute_set(images, "uuid")

        # We don't compare old node state versus new, because that would be
        # a relatively large and complicated task. Instead, we only rely on
        # the current state of nodes to inform ourselves whether we're meeting
        # our stated goals or not.
        self.current_nodes = nodes
    def update_current_state(self, nodes, images, flavors):
        # For now, flavors should remain static.
        # In the future we'll handle changing flavor profiles if needed,
        # but it seems unlikely that flavors will change often enough.
        self.flavor_diff = sb.find_flavor_differences(self.current_flavors,
                                                      flavors)
        sb.log_flavor_differences(self.flavor_diff)
        self.current_flavors = flavors

        # Image differences are important because changed or retired
        # images should be ejected from the cache.
        self.image_diff = sb.find_image_differences(self.current_images,
                                                    images)
        sb.log_image_differences(self.image_diff)
        self.current_images = images
        self.current_image_uuids = sb.build_attribute_set(images, 'uuid')

        # We don't compare old node state versus new, because that would be
        # a relatively large and complicated task. Instead, we only rely on
        # the current state of nodes to inform ourselves whether we're meeting
        # our stated goals or not.
        self.current_nodes = nodes