def test_best_effort_place_engine(self, result, ratio, disk_sizes, places,
                                      use_image_ds):
        # Create best effort place engine
        image_datastore = "ds1"
        image_datastores = [{"name": image_datastore,
                             "used_for_vms": use_image_ds}]
        option = PlacementOption(1, 1, image_datastores)
        ds_map = {image_datastore: DatastoreInfo(10, 0),
                  "ds2": DatastoreInfo(20, 0),
                  "ds3": DatastoreInfo(30, 0)}
        ds_mgr = self.create_datastore_manager(ds_map, image_datastore)
        engine = BestEffortPlaceEngine(ds_mgr, option)
        ds = engine.placeable_datastores()
        selector = DatastoreSelector.init_datastore_selector(ds_mgr, ds)
        disks_placement = DisksPlacement(self.create_disks(disk_sizes),
                                         selector)

        # Try place
        place_result = engine.place(disks_placement)

        # Verify place result
        assert_that(place_result.result, equal_to(result))

        # Verify placements
        if disk_sizes and place_result.result == PlaceResultCode.OK:
            assert_that(place_result.disks_placement.selector.ratio(),
                        equal_to(ratio))
            for i, place in enumerate(places):
                placement_list = place_result.disks_placement.placement_list
                assert_that(placement_list[0].type,
                            equal_to(AgentResourcePlacement.DISK))
                assert_that(placement_list[i].container_id,
                            equal_to(place))
 def __init__(self, hypervisor, option):
     self._logger = logging.getLogger(__name__)
     self._hypervisor = hypervisor
     self._option = option
     self._reserved_vms = {}
     self._reserved_disks = {}
     self._diskutil = DiskUtil()
     self._vm_manager = hypervisor.vm_manager
     self._image_manager = hypervisor.image_manager
     self._datastore_manager = hypervisor.datastore_manager
     self._system = hypervisor.system
     self._optimal_placement = OptimalPlaceEngine(self._datastore_manager,
                                                  option)
     self._best_effort_placement = BestEffortPlaceEngine(
         self._datastore_manager, option)
     self._constrainted_placement = ConstraintDiskPlaceEngine(
         self._datastore_manager, option)
示例#3
0
    def test_best_effort_place_engine(self, result, ratio, disk_sizes, places,
                                      use_image_ds):
        # Create best effort place engine
        image_datastore = "ds1"
        image_datastores = [{
            "name": image_datastore,
            "used_for_vms": use_image_ds
        }]
        option = PlacementOption(1, 1, image_datastores)
        ds_map = {
            image_datastore: DatastoreInfo(10, 0),
            "ds2": DatastoreInfo(20, 0),
            "ds3": DatastoreInfo(30, 0)
        }
        ds_mgr = self.create_datastore_manager(ds_map, image_datastore)
        engine = BestEffortPlaceEngine(ds_mgr, option)
        ds = engine.placeable_datastores()
        selector = DatastoreSelector.init_datastore_selector(ds_mgr, ds)
        disks_placement = DisksPlacement(self.create_disks(disk_sizes),
                                         selector)

        # Try place
        place_result = engine.place(disks_placement)

        # Verify place result
        assert_that(place_result.result, equal_to(result))

        # Verify placements
        if disk_sizes and place_result.result == PlaceResultCode.OK:
            assert_that(place_result.disks_placement.selector.ratio(),
                        equal_to(ratio))
            for i, place in enumerate(places):
                placement_list = place_result.disks_placement.placement_list
                assert_that(placement_list[0].type,
                            equal_to(AgentResourcePlacement.DISK))
                assert_that(placement_list[i].container_id, equal_to(place))
 def __init__(self, hypervisor, option):
     self._logger = logging.getLogger(__name__)
     self._hypervisor = hypervisor
     self._option = option
     self._reserved_vms = {}
     self._reserved_disks = {}
     self._diskutil = DiskUtil()
     self._vm_manager = hypervisor.vm_manager
     self._image_manager = hypervisor.image_manager
     self._datastore_manager = hypervisor.datastore_manager
     self._system = hypervisor.system
     self._optimal_placement = OptimalPlaceEngine(
         self._datastore_manager, option)
     self._best_effort_placement = BestEffortPlaceEngine(
         self._datastore_manager, option)
     self._constrainted_placement = ConstraintDiskPlaceEngine(
         self._datastore_manager, option)
class PlacementManager(object):
    """PlacementManager handles resource placement and reservation."""

    MAX_USAGE = 0.95
    NOT_OPTIMAL_DIVIDE_FACTOR = 10

    # Indicates ~ 80% consumption
    MEM_UTIL_SCORE_THRESHOLD = 20

    # Data store freespace threshold
    FREESPACE_THRESHOLD = 0.8

    # The image copy size (1GB) that translates to transfer score 1
    MAX_IMAGE_COPY_SIZE = 1024 * 1024 * 1024

    # Maximum score possible
    MAX_SCORE = 100

    def __init__(self, hypervisor, option):
        self._logger = logging.getLogger(__name__)
        self._hypervisor = hypervisor
        self._option = option
        self._reserved_vms = {}
        self._reserved_disks = {}
        self._diskutil = DiskUtil()
        self._vm_manager = hypervisor.vm_manager
        self._image_manager = hypervisor.image_manager
        self._datastore_manager = hypervisor.datastore_manager
        self._system = hypervisor.system
        self._optimal_placement = OptimalPlaceEngine(
            self._datastore_manager, option)
        self._best_effort_placement = BestEffortPlaceEngine(
            self._datastore_manager, option)
        self._constrainted_placement = ConstraintDiskPlaceEngine(
            self._datastore_manager, option)

    def reserve(self, vm, disks):
        """Reserve room for specified resources.

        :type vm: Vm
        :type disks: list of Disk
        :rtype: str
        :returns: reservation id
        """
        rid = str(uuid.uuid4())
        if vm:
            self._reserved_vms[rid] = vm
        if disks:
            self._reserved_disks[rid] = disks
        return rid

    def consume_disk_reservation(self, reservation_id):
        return self._consume(self._reserved_disks, reservation_id)

    def consume_vm_reservation(self, reservation_id):
        return self._consume(self._reserved_vms, reservation_id)

    def remove_disk_reservation(self, reservation_id):
        return self._remove(self._reserved_disks, reservation_id)

    def remove_vm_reservation(self, reservation_id):
        return self._remove(self._reserved_vms, reservation_id)

    @property
    def memory_overcommit(self):
        return self._option.memory_overcommit

    @memory_overcommit.setter
    def memory_overcommit(self, value):
        self._option.memory_overcommit = value

    @property
    def cpu_overcommit(self):
        return self._option.cpu_overcommit

    @cpu_overcommit.setter
    def cpu_overcommit(self, value):
        self._option.cpu_overcommit = value

    def place(self, vm, disks):
        """Place specified resources.

        :type vm: Vm
        :type disks: list of Disk
        :rtype: PlacementScore, Placement List
        :raises: ResourceConstraintException if can't place resources
        """

        """Check if there are datastores for placing vm or disks """
        if len(self._placeable_datastores()) == 0:
            raise NoSuchResourceException(ResourceType.DATASTORE,
                                          "No placeable datastores.")

        if vm:
            return self._place_vm(vm)
        elif disks:
            return self._place_disks(disks)

    def _extract_resource_constraints(self, constraints, resource_types):
        """ Extract ResourceConstraint with same resource_type

        :param constraints: list of ResourceConstraint
        :param resource_types: set of ResourceConstraintType
        :return: dict of {ResourceConstraintType: list of ResourceConstraint}
        """
        matched_constraints = {}

        for _constraint in constraints:
            if _constraint.type in resource_types:
                if _constraint.type not in matched_constraints:
                    matched_constraints[_constraint.type] = []
                matched_constraints[_constraint.type].append(
                    _constraint)

        return matched_constraints

    def _collect_matched_resource(self, constraints, resources):
        """ Prepare host resources that match the request vm constraints.

        :param constraints: dict of
            {ResourceConstraintType: list of ResourceConstraint}
        :param resources: dict of host available resources
            {ResourceConstraintType: set of resources}
        :return: dict of matched resources
            {ResourceConstraintType: list of matched resources}
        :raise: NoSuchResourceException
        """

        def _validate_single_constraint(resource_type, values):
            """
            :param resource_type: ResourceConstraintType
            :param values: values are sorted in ResourceConstraint.
            :return: randomly picked matched resource.
            :raise: NoSuchResourceException
            """
            def prepare_exception(resource_type):
                resource_type_str = \
                    ResourceConstraintType._VALUES_TO_NAMES[resource_type]
                return NoSuchResourceException(
                    resource_type_str,
                    "Host has no {0} resource.".format(resource_type_str))

            if resource_type not in resources:
                raise prepare_exception(resource_type)

            matched = []
            for value in values:
                if value in resources[resource_type]:
                    matched.append(value)

            if len(matched) != 0:
                self._logger.debug("matched resources: {0}".format(matched))
                shuffle(matched)
                return matched[0]
            raise prepare_exception(resource_type)

        matched_resource = {}

        for resource_type, constraint_list in constraints.iteritems():
            for constraint in constraint_list:
                if resource_type not in matched_resource:
                    matched_resource[resource_type] = []
                matched_resource[resource_type].append(
                    _validate_single_constraint(
                        resource_type,
                        constraint.values))
        return matched_resource

    def _place_vm(self, vm):
        utilization_score, placement_list = \
            self._compute_vm_utilization_score(vm)
        transfer_score = self._transfer_score(vm, placement_list)
        placement_score = AgentPlacementScore(utilization_score,
                                              transfer_score)

        resource_placement_list = self._pick_available_resources(vm)
        placement_list.extend(resource_placement_list)

        self._logger.debug("placement score: %s" % placement_score)

        return placement_score, placement_list

    def _place_disks(self, disks):
        utilization_score, placement_list = \
            self._compute_disks_utilization_score(disks)
        transfer_score = self._score(0, False, ResourceType.TRANSFER)
        placement_score = AgentPlacementScore(utilization_score,
                                              transfer_score)
        self._logger.debug("placement score: %s" % placement_score)
        return placement_score, placement_list

    def _pick_available_resources(self, vm):
        """ Pick host's resources that match vm's resource constraints.

        :param vm: Vm
        :rtype: Placement List
        :raise: NoSuchResourceException
        """
        placement_list = []

        # supports network constraint only.
        mapping = {
            ResourceConstraintType.NETWORK: AgentResourcePlacement.NETWORK}

        if vm.resource_constraints:
            # resource type to extract from VM's resource_constraint
            extract_resources_type = set([ResourceConstraintType.NETWORK])

            # host available resources.
            host_available_resources = {
                ResourceConstraintType.NETWORK:
                set(self._hypervisor.network_manager.get_vm_networks())}

            constraints = self._extract_resource_constraints(
                vm.resource_constraints, extract_resources_type)

            matched_resources = self._collect_matched_resource(
                constraints, host_available_resources)

            for resource_type, values in matched_resources.iteritems():
                placement_list.extend([
                    AgentResourcePlacement(
                        mapping[resource_type],
                        vm.id,
                        value)
                    for value in values])
        return placement_list

    @log_duration
    def _transfer_score(self, vm, placement_list=[]):
        """ Transfer score evaluates the cost of copying image lazily from
        shared datastore to datastore.

        If there is an image copy, the transfer score is 1. Otherwise the
        score is 0.
        """

        # Get image disks from Vm's disks list. If there is no
        # image disks, no transfer penalty.
        images = None
        if vm and vm.disks:
            images = [disk for disk in vm.disks
                      if disk.image]
        if not images:
            return self._score(0, False, ResourceType.TRANSFER)

        # Get vm placement from placement list. Image copy happens in the
        # datastore where the Vm is placed.
        vm_placements = [placement for placement in placement_list
                         if placement.type == ResourcePlacementType.VM]
        if not vm_placements:
            return self._score(0, False, ResourceType.TRANSFER)
        vm_placement = vm_placements[0]

        # Try to find an image that is missing in the datastore where Vm is
        # placed. If there is a missing image, then apply the penalty.
        copy_size = 0

        for image in images:
            if not self._image_manager.check_image(image.image.id,
                                                   vm_placement.container_id):
                try:
                    copy_size += self._image_manager.image_size(image.image.id)
                except:
                    self._logger.warning("Failed to get image size:",
                                         exc_info=True)
                    # Failed to access shared image.
                    raise NoSuchResourceException(
                        ResourceType.IMAGE,
                        "Image does not exist.")

        score = float(copy_size) / self.MAX_IMAGE_COPY_SIZE
        if score > 1:
            score = 1
        return self._score(score, False, ResourceType.TRANSFER)

    """
    Compute utilization score for a vm, returns
    the minimum between the memory and storage score
    """
    def _compute_vm_utilization_score(self, vm):
        memory_score = self._compute_memory_score(vm)
        cpu_score = self._compute_cpu_score(vm)
        storage_score, placement_list = \
            self._compute_storage_score(vm.disks)

        # If vm is not None, need to put vm placement in placement list
        if vm:
            if placement_list:
                vm_datastore = placement_list[0].container_id
            else:
                vm_datastore = self._optimal_datastore()
            vm_placement = AgentResourcePlacement(
                AgentResourcePlacement.VM, vm.id, vm_datastore
            )
            placement_list = [vm_placement] + placement_list

        self._logger.debug("Scores: memory: %d, cpu: %d, storage: %d"
                           % (memory_score, cpu_score, storage_score))
        return min(memory_score, cpu_score, storage_score), placement_list

    """
    Compute utilization score when creating
    independent disks (outside a vm creation call)
    """
    def _compute_disks_utilization_score(self, disks):
        return self._compute_storage_score(disks)

    """
    This method computes the memory score for this
    host. Used during VM creation.
    """
    @log_duration
    def _compute_memory_score(self, vm):
        total_memory = self._system.total_vmusable_memory_mb()
        total_overcommited_memory = total_memory * \
            self._option.memory_overcommit
        consumed_memory = self._system.host_consumed_memory_mb()

        try:
            used_memory = self._vm_manager.get_used_memory_mb()
        except VmNotFoundException:
            used_memory = 0

        used_memory += self._memory_reserved()
        memory = self._vm_memory_mb(vm)
        self._logger.debug("memory: %d, used_memory: %d, "
                           "total_overcommited_memory: %d" %
                           (memory, used_memory, total_overcommited_memory))
        score = self._score((float(used_memory) + memory) /
                            total_overcommited_memory,
                            True, ResourceType.MEMORY)

        if consumed_memory:
            # For consumed memory, we don't want to enforce MAX_USAGE limits.
            consumed_mem_score = self._score(
                float(consumed_memory) / total_memory,
                False, ResourceType.MEMORY)
            self._logger.debug("Consumed memory: %d, score: %d",
                               consumed_memory, consumed_mem_score)
            if (consumed_mem_score < self.MEM_UTIL_SCORE_THRESHOLD and
                    consumed_mem_score < score):
                # This host seems to be busy, hence bias the overall score by
                # considering the consumed score as well. This essentially
                # means that at high utilizations, consumption becomes equally
                # important as over commit. At the same time, we check if the
                # consumed score is lower than the overcommit score. We do not
                # want to increase the score if the host is severely
                # overcommitted. For now we take a simple average.
                score = (score + consumed_mem_score) / 2

        self._logger.debug("memory score: %d" % score)
        return score

    """
    This method computes the CPU score for this
    host. The score is calculated by considering
    total available number of pCpus*overcommit
    and the total number of vCpus across all VMs on the
    host and finding their ratio.
    """
    @log_duration
    def _compute_cpu_score(self, vm):
        total_cpu_count = self._system.num_physical_cpus()
        total_cpu_count *= self._option.cpu_overcommit

        if total_cpu_count is 0:
            return 0

        try:
            configured_cpu = self._vm_manager.get_configured_cpu_count()
        except VmNotFoundException:
            configured_cpu = 0

        configured_cpu += self._cpu_reserved()
        cpu = self._vm_cpu_count(vm)
        score = self._score((float(configured_cpu) + cpu) /
                            total_cpu_count, True, ResourceType.CPU)
        return score

    """
    The method computes the storage score for this
    host. The score is based on the ratio of two sums:
    used and total. The sums are computed over all the
    datastores available on the host.
    If an image id is requested the code checks if there
    is a copy of that image on at least one of the datastores
    connected to the host. If there is none, the score
    is drastically reduced.
    """

    def _compute_storage_score(self, disks):
        best_effort = False

        if not disks:
            return self.MAX_SCORE, []

        selector = DatastoreSelector.init_datastore_selector(
            self._datastore_manager, self._placeable_datastores())

        disks_placement = DisksPlacement(disks, selector)
        # Place constraint disks first
        place_result = self._constrainted_placement.place(disks_placement)
        if place_result.result == PlaceResultCode.NO_SUCH_RESOURCE:
            raise NoSuchResourceException(
                ResourceType.DISK,
                "Disk placement failed.")
        elif place_result.result == \
                PlaceResultCode.NOT_ENOUGH_DATASTORE_CAPACITY:
            raise NotEnoughDatastoreCapacityException()

        # Try optimal for unconstrainted disks first
        place_result = self._optimal_placement.place(
            place_result.disks_placement)

        if place_result.result == \
                PlaceResultCode.NOT_ENOUGH_DATASTORE_CAPACITY:
            # If it doesn't work then try best effort
            place_result = self._best_effort_placement.place(
                place_result.disks_placement)
            best_effort = True

        # Still NOT_ENOUGH_SYSTEM_RESOURCE then have to give up.
        if place_result.result == \
                PlaceResultCode.NOT_ENOUGH_DATASTORE_CAPACITY:
            raise NotEnoughDatastoreCapacityException()

        # Congrat! Successful placement.

        # Count reserved storage
        free = place_result.disks_placement.selector.total_free_space()
        total = place_result.disks_placement.selector.total_space()
        ratio = float(total-free+self._storage_reserved()) / total

        # If we find an optimal data store (that contains the image needed)
        # and the the data stores will not cross the free space threshold,
        # on admitting the VM, return a high score, so data store score will
        # not be used, if at all.
        if (ratio < self.FREESPACE_THRESHOLD and
                not best_effort):
            return self.MAX_SCORE, place_result.disks_placement.placement_list

        # Compute score
        score = self._score(ratio, True, ResourceType.DATASTORE)

        # If an optimal placement could not be achieved reduce the score for
        # this host
        if best_effort:
            score /= self.NOT_OPTIMAL_DIVIDE_FACTOR

        return score, place_result.disks_placement.placement_list

    def _score(self, inverse_ratio, utilization, resource_type):
        if utilization and inverse_ratio > self.MAX_USAGE:
            self._logger.warning(
                "Resource constraint exception, %s: %f > %f" %
                (resource_type, inverse_ratio, self.MAX_USAGE))
            if resource_type == ResourceType.MEMORY:
                raise NotEnoughMemoryResourceException()
            if resource_type == ResourceType.CPU:
                raise NotEnoughCpuResourceException()
            if resource_type == ResourceType.DATASTORE:
                raise NotEnoughDatastoreCapacityException()

        return self.MAX_SCORE - int(round(inverse_ratio * 100))

    def _memory_reserved(self):
        return sum(self._vm_memory_mb(val)
                   for val in self._reserved_vms.values())

    def _cpu_reserved(self):
        return sum(self._vm_cpu_count(val)
                   for val in self._reserved_vms.values())

    def _storage_reserved(self):
        score = sum(self._diskutil.disks_capacity_gb(disks)
                    for disks in self._reserved_disks.values())
        score += sum(self._diskutil.disks_capacity_gb(vm.disks)
                     for vm in self._reserved_vms.values()
                     if vm.disks)
        self._logger.debug("storage reserved score: %d" % score)
        return score

    def _placeable_datastores(self):
        datastores = self._datastore_manager.vm_datastores()
        for image_ds in self._option.image_datastores:
            if image_ds["used_for_vms"]:
                dsid = self._datastore_manager.normalize(image_ds["name"])
                datastores.append(dsid)
        self._logger.debug("Placeable datastores: %s, image datastores: %s" %
                           (datastores, self._option.image_datastores))
        return datastores

    def _optimal_datastore(self):
        free = 0
        optimal = None
        for datastore_id in self._placeable_datastores():
            datastore_info = \
                self._datastore_manager.datastore_info(datastore_id)
            if datastore_info.total - datastore_info.used > free:
                optimal = datastore_id
        return optimal

    @staticmethod
    def _vm_memory_mb(vm):
        return int(vm.flavor.cost["vm.memory"].convert(Unit.MB))

    @staticmethod
    def _vm_cpu_count(vm):
        return int(vm.flavor.cost["vm.cpu"].convert(Unit.COUNT))

    @staticmethod
    def _consume(resources, reservation_id):
        if reservation_id not in resources:
            raise InvalidReservationException()
        resource = resources[reservation_id]
        return resource

    @staticmethod
    def _remove(resources, reservation_id):
        if reservation_id in resources:
            del resources[reservation_id]
class PlacementManager(object):
    """PlacementManager handles resource placement and reservation."""

    MAX_USAGE = 0.95
    NOT_OPTIMAL_DIVIDE_FACTOR = 10

    # Indicates ~ 80% consumption
    MEM_UTIL_SCORE_THRESHOLD = 20

    # Data store freespace threshold
    FREESPACE_THRESHOLD = 0.8

    # The image copy size (1GB) that translates to transfer score 1
    MAX_IMAGE_COPY_SIZE = 1024 * 1024 * 1024

    # Maximum score possible
    MAX_SCORE = 100

    def __init__(self, hypervisor, option):
        self._logger = logging.getLogger(__name__)
        self._hypervisor = hypervisor
        self._option = option
        self._reserved_vms = {}
        self._reserved_disks = {}
        self._diskutil = DiskUtil()
        self._vm_manager = hypervisor.vm_manager
        self._image_manager = hypervisor.image_manager
        self._datastore_manager = hypervisor.datastore_manager
        self._system = hypervisor.system
        self._optimal_placement = OptimalPlaceEngine(self._datastore_manager,
                                                     option)
        self._best_effort_placement = BestEffortPlaceEngine(
            self._datastore_manager, option)
        self._constrainted_placement = ConstraintDiskPlaceEngine(
            self._datastore_manager, option)

    def reserve(self, vm, disks):
        """Reserve room for specified resources.

        :type vm: Vm
        :type disks: list of Disk
        :rtype: str
        :returns: reservation id
        """
        rid = str(uuid.uuid4())
        if vm:
            self._reserved_vms[rid] = vm
        if disks:
            self._reserved_disks[rid] = disks
        return rid

    def consume_disk_reservation(self, reservation_id):
        return self._consume(self._reserved_disks, reservation_id)

    def consume_vm_reservation(self, reservation_id):
        return self._consume(self._reserved_vms, reservation_id)

    @property
    def memory_overcommit(self):
        return self._option.memory_overcommit

    @memory_overcommit.setter
    def memory_overcommit(self, value):
        self._option.memory_overcommit = value

    @property
    def cpu_overcommit(self):
        return self._option.cpu_overcommit

    @cpu_overcommit.setter
    def cpu_overcommit(self, value):
        self._option.cpu_overcommit = value

    def place(self, vm, disks):
        """Place specified resources.

        :type vm: Vm
        :type disks: list of Disk
        :rtype: PlacementScore, Placement List
        :raises: ResourceConstraintException if can't place resources
        """
        """Check if there are datastores for placing vm or disks """
        if len(self._placeable_datastores()) == 0:
            raise NoSuchResourceException(ResourceType.DATASTORE,
                                          "No placeable datastores.")

        if vm:
            return self._place_vm(vm)
        elif disks:
            return self._place_disks(disks)

    def _extract_resource_constraints(self, constraints, resource_types):
        """ Extract ResourceConstraint with same resource_type

        :param constraints: list of ResourceConstraint
        :param resource_types: set of ResourceConstraintType
        :return: dict of {ResourceConstraintType: list of ResourceConstraint}
        """
        matched_constraints = {}

        for _constraint in constraints:
            if _constraint.type in resource_types:
                if _constraint.type not in matched_constraints:
                    matched_constraints[_constraint.type] = []
                matched_constraints[_constraint.type].append(_constraint)

        return matched_constraints

    def _collect_matched_resource(self, constraints, resources):
        """ Prepare host resources that match the request vm constraints.

        :param constraints: dict of
            {ResourceConstraintType: list of ResourceConstraint}
        :param resources: dict of host available resources
            {ResourceConstraintType: set of resources}
        :return: dict of matched resources
            {ResourceConstraintType: list of matched resources}
        :raise: NoSuchResourceException
        """
        def _validate_single_constraint(resource_type, values):
            """
            :param resource_type: ResourceConstraintType
            :param values: values are sorted in ResourceConstraint.
            :return: randomly picked matched resource.
            :raise: NoSuchResourceException
            """
            def prepare_exception(resource_type):
                resource_type_str = \
                    ResourceConstraintType._VALUES_TO_NAMES[resource_type]
                return NoSuchResourceException(
                    resource_type_str,
                    "Host has no {0} resource.".format(resource_type_str))

            if resource_type not in resources:
                raise prepare_exception(resource_type)

            matched = []
            for value in values:
                if value in resources[resource_type]:
                    matched.append(value)

            if len(matched) != 0:
                self._logger.debug("matched resources: {0}".format(matched))
                shuffle(matched)
                return matched[0]
            raise prepare_exception(resource_type)

        matched_resource = {}

        for resource_type, constraint_list in constraints.iteritems():
            for constraint in constraint_list:
                if resource_type not in matched_resource:
                    matched_resource[resource_type] = []
                matched_resource[resource_type].append(
                    _validate_single_constraint(resource_type,
                                                constraint.values))
        return matched_resource

    def _place_vm(self, vm):
        utilization_score, placement_list = \
            self._compute_vm_utilization_score(vm)
        transfer_score = self._transfer_score(vm, placement_list)
        placement_score = AgentPlacementScore(utilization_score,
                                              transfer_score)

        resource_placement_list = self._pick_available_resources(vm)
        placement_list.extend(resource_placement_list)

        self._logger.debug("placement score: %s" % placement_score)

        return placement_score, placement_list

    def _place_disks(self, disks):
        utilization_score, placement_list = \
            self._compute_disks_utilization_score(disks)
        transfer_score = self._score(0, False, ResourceType.TRANSFER)
        placement_score = AgentPlacementScore(utilization_score,
                                              transfer_score)
        self._logger.debug("placement score: %s" % placement_score)
        return placement_score, placement_list

    def _pick_available_resources(self, vm):
        """ Pick host's resources that match vm's resource constraints.

        :param vm: Vm
        :rtype: Placement List
        :raise: NoSuchResourceException
        """
        placement_list = []

        # supports network constraint only.
        mapping = {
            ResourceConstraintType.NETWORK: AgentResourcePlacement.NETWORK
        }

        if vm.resource_constraints:
            # resource type to extract from VM's resource_constraint
            extract_resources_type = set([ResourceConstraintType.NETWORK])

            # host available resources.
            host_available_resources = {
                ResourceConstraintType.NETWORK:
                set(self._hypervisor.network_manager.get_vm_networks())
            }

            constraints = self._extract_resource_constraints(
                vm.resource_constraints, extract_resources_type)

            matched_resources = self._collect_matched_resource(
                constraints, host_available_resources)

            for resource_type, values in matched_resources.iteritems():
                placement_list.extend([
                    AgentResourcePlacement(mapping[resource_type], vm.id,
                                           value) for value in values
                ])
        return placement_list

    @log_duration
    def _transfer_score(self, vm, placement_list=[]):
        """ Transfer score evaluates the cost of copying image lazily from
        shared datastore to datastore.

        If there is an image copy, the transfer score is 1. Otherwise the
        score is 0.
        """

        # Get image disks from Vm's disks list. If there is no
        # image disks, no transfer penalty.
        images = None
        if vm and vm.disks:
            images = [disk for disk in vm.disks if disk.image]
        if not images:
            return self._score(0, False, ResourceType.TRANSFER)

        # Get vm placement from placement list. Image copy happens in the
        # datastore where the Vm is placed.
        vm_placements = [
            placement for placement in placement_list
            if placement.type == ResourcePlacementType.VM
        ]
        if not vm_placements:
            return self._score(0, False, ResourceType.TRANSFER)
        vm_placement = vm_placements[0]

        # Try to find an image that is missing in the datastore where Vm is
        # placed. If there is a missing image, then apply the penalty.
        copy_size = 0

        for image in images:
            if not self._image_manager.check_image(image.image.id,
                                                   vm_placement.container_id):
                try:
                    copy_size += self._image_manager.image_size(image.image.id)
                except:
                    self._logger.warning("Failed to get image size:",
                                         exc_info=True)
                    # Failed to access shared image.
                    raise NoSuchResourceException(ResourceType.IMAGE,
                                                  "Image does not exist.")

        score = float(copy_size) / self.MAX_IMAGE_COPY_SIZE
        if score > 1:
            score = 1
        return self._score(score, False, ResourceType.TRANSFER)

    """
    Compute utilization score for a vm, returns
    the minimum between the memory and storage score
    """

    def _compute_vm_utilization_score(self, vm):
        memory_score = self._compute_memory_score(vm)
        cpu_score = self._compute_cpu_score(vm)
        storage_score, placement_list = \
            self._compute_storage_score(vm.disks)

        # If vm is not None, need to put vm placement in placement list
        if vm:
            if placement_list:
                vm_datastore = placement_list[0].container_id
            else:
                vm_datastore = self._optimal_datastore()
            vm_placement = AgentResourcePlacement(AgentResourcePlacement.VM,
                                                  vm.id, vm_datastore)
            placement_list = [vm_placement] + placement_list

        self._logger.debug("Scores: memory: %d, cpu: %d, storage: %d" %
                           (memory_score, cpu_score, storage_score))
        return min(memory_score, cpu_score, storage_score), placement_list

    """
    Compute utilization score when creating
    independent disks (outside a vm creation call)
    """

    def _compute_disks_utilization_score(self, disks):
        return self._compute_storage_score(disks)

    """
    This method computes the memory score for this
    host. Used during VM creation.
    """

    @log_duration
    def _compute_memory_score(self, vm):
        total_memory = self._system.total_vmusable_memory_mb()
        total_overcommited_memory = total_memory * \
            self._option.memory_overcommit
        consumed_memory = self._system.host_consumed_memory_mb()

        try:
            used_memory = self._vm_manager.get_used_memory_mb()
        except VmNotFoundException:
            used_memory = 0

        used_memory += self._memory_reserved()
        memory = self._vm_memory_mb(vm)
        self._logger.debug("memory: %d, used_memory: %d, "
                           "total_overcommited_memory: %d" %
                           (memory, used_memory, total_overcommited_memory))
        score = self._score(
            (float(used_memory) + memory) / total_overcommited_memory, True,
            ResourceType.MEMORY)

        if consumed_memory:
            # For consumed memory, we don't want to enforce MAX_USAGE limits.
            consumed_mem_score = self._score(
                float(consumed_memory) / total_memory, False,
                ResourceType.MEMORY)
            self._logger.debug("Consumed memory: %d, score: %d",
                               consumed_memory, consumed_mem_score)
            if (consumed_mem_score < self.MEM_UTIL_SCORE_THRESHOLD
                    and consumed_mem_score < score):
                # This host seems to be busy, hence bias the overall score by
                # considering the consumed score as well. This essentially
                # means that at high utilizations, consumption becomes equally
                # important as over commit. At the same time, we check if the
                # consumed score is lower than the overcommit score. We do not
                # want to increase the score if the host is severely
                # overcommitted. For now we take a simple average.
                score = (score + consumed_mem_score) / 2

        self._logger.debug("memory score: %d" % score)
        return score

    """
    This method computes the CPU score for this
    host. The score is calculated by considering
    total available number of pCpus*overcommit
    and the total number of vCpus across all VMs on the
    host and finding their ratio.
    """

    @log_duration
    def _compute_cpu_score(self, vm):
        total_cpu_count = self._system.num_physical_cpus()
        total_cpu_count *= self._option.cpu_overcommit

        if total_cpu_count is 0:
            return 0

        try:
            configured_cpu = self._vm_manager.get_configured_cpu_count()
        except VmNotFoundException:
            configured_cpu = 0

        configured_cpu += self._cpu_reserved()
        cpu = self._vm_cpu_count(vm)
        score = self._score((float(configured_cpu) + cpu) / total_cpu_count,
                            True, ResourceType.CPU)
        return score

    """
    The method computes the storage score for this
    host. The score is based on the ratio of two sums:
    used and total. The sums are computed over all the
    datastores available on the host.
    If an image id is requested the code checks if there
    is a copy of that image on at least one of the datastores
    connected to the host. If there is none, the score
    is drastically reduced.
    """

    def _compute_storage_score(self, disks):
        best_effort = False

        if not disks:
            return self.MAX_SCORE, []

        selector = DatastoreSelector.init_datastore_selector(
            self._datastore_manager, self._placeable_datastores())

        disks_placement = DisksPlacement(disks, selector)
        # Place constraint disks first
        place_result = self._constrainted_placement.place(disks_placement)
        if place_result.result == PlaceResultCode.NO_SUCH_RESOURCE:
            raise NoSuchResourceException(ResourceType.DISK,
                                          "Disk placement failed.")
        elif place_result.result == \
                PlaceResultCode.NOT_ENOUGH_DATASTORE_CAPACITY:
            raise NotEnoughDatastoreCapacityException()

        # Try optimal for unconstrainted disks first
        place_result = self._optimal_placement.place(
            place_result.disks_placement)

        if place_result.result == \
                PlaceResultCode.NOT_ENOUGH_DATASTORE_CAPACITY:
            # If it doesn't work then try best effort
            place_result = self._best_effort_placement.place(
                place_result.disks_placement)
            best_effort = True

        # Still NOT_ENOUGH_SYSTEM_RESOURCE then have to give up.
        if place_result.result == \
                PlaceResultCode.NOT_ENOUGH_DATASTORE_CAPACITY:
            raise NotEnoughDatastoreCapacityException()

        # Congrat! Successful placement.

        # Count reserved storage
        free = place_result.disks_placement.selector.total_free_space()
        total = place_result.disks_placement.selector.total_space()
        ratio = float(total - free + self._storage_reserved()) / total

        # If we find an optimal data store (that contains the image needed)
        # and the the data stores will not cross the free space threshold,
        # on admitting the VM, return a high score, so data store score will
        # not be used, if at all.
        if (ratio < self.FREESPACE_THRESHOLD and not best_effort):
            return self.MAX_SCORE, place_result.disks_placement.placement_list

        # Compute score
        score = self._score(ratio, True, ResourceType.DATASTORE)

        # If an optimal placement could not be achieved reduce the score for
        # this host
        if best_effort:
            score /= self.NOT_OPTIMAL_DIVIDE_FACTOR

        return score, place_result.disks_placement.placement_list

    def _score(self, inverse_ratio, utilization, resource_type):
        if utilization and inverse_ratio > self.MAX_USAGE:
            self._logger.warning(
                "Resource constraint exception, %s: %f > %f" %
                (resource_type, inverse_ratio, self.MAX_USAGE))
            if resource_type == ResourceType.MEMORY:
                raise NotEnoughMemoryResourceException()
            if resource_type == ResourceType.CPU:
                raise NotEnoughCpuResourceException()
            if resource_type == ResourceType.DATASTORE:
                raise NotEnoughDatastoreCapacityException()

        return self.MAX_SCORE - int(round(inverse_ratio * 100))

    def _memory_reserved(self):
        return sum(
            self._vm_memory_mb(val) for val in self._reserved_vms.values())

    def _cpu_reserved(self):
        return sum(
            self._vm_cpu_count(val) for val in self._reserved_vms.values())

    def _storage_reserved(self):
        score = sum(
            self._diskutil.disks_capacity_gb(disks)
            for disks in self._reserved_disks.values())
        score += sum(
            self._diskutil.disks_capacity_gb(vm.disks)
            for vm in self._reserved_vms.values() if vm.disks)
        self._logger.debug("storage reserved score: %d" % score)
        return score

    def _placeable_datastores(self):
        datastores = self._datastore_manager.vm_datastores()
        for image_ds in self._option.image_datastores:
            if image_ds["used_for_vms"]:
                dsid = self._datastore_manager.normalize(image_ds["name"])
                datastores.append(dsid)
        self._logger.debug("Placeable datastores: %s, image datastores: %s" %
                           (datastores, self._option.image_datastores))
        return datastores

    def _optimal_datastore(self):
        free = 0
        optimal = None
        for datastore_id in self._placeable_datastores():
            datastore_info = \
                self._datastore_manager.datastore_info(datastore_id)
            if datastore_info.total - datastore_info.used > free:
                optimal = datastore_id
        return optimal

    @staticmethod
    def _vm_memory_mb(vm):
        return int(vm.flavor.cost["vm.memory"].convert(Unit.MB))

    @staticmethod
    def _vm_cpu_count(vm):
        return int(vm.flavor.cost["vm.cpu"].convert(Unit.COUNT))

    @staticmethod
    def _consume(resources, reservation_id):
        if reservation_id not in resources:
            raise InvalidReservationException()
        resource = resources[reservation_id]
        del resources[reservation_id]
        return resource