Skip to content
This repository has been archived by the owner on Mar 31, 2023. It is now read-only.

Constraints

David Gross edited this page Aug 12, 2015 · 19 revisions

One way to match hosts to tasks is by means of host constraints. For example, your task may demand that it is the only task to be running on the host, or that it run on a host of a particular type, or that a certain set of tasks each runs in a different zone or on a different host. This page explains how to use host constraints.

Contents:

  1. Introduction
  2. Fenzo’s Built-In Constraint Options
    1. Constraints that Operate on Single Tasks
    2. Constraints that Operate on Groups of Tasks
  3. Creating Your Own Constraint Variety
  4. Making the Task Scheduler Aware of Your Constraints
    1. Making a Constraint Soft

Introduction

You can decide whether to treat any of these constraints as a hard constraint (a simple boolean thumbs-up or thumbs-down) or as a soft constraint (a more nuanced measure of acceptability).

You can use any of a set of constraints that are built-in to Fenzo, and you may also implement your own constraints if none of these suit your needs.

Fenzo’s Built-In Constraint Options

The constraint options that are built-in to Fenzo are the following:

  • Constraints that operate on single tasks:
    • Exclusive Host Constraint (ExclusiveHostConstraint): ensures that when a task is assigned to a host, it will be the only task assigned to that host
    • Host Attribute Value Constraint (HostAttrValueConstraint): matches a task with a host that has a particular value for a particular host attribute
  • Constraints that operate on groups of tasks:
    • Unique Host Attribute Constraint (UniqueHostAttrConstraint): schedules a set of tasks so that each task ends up on a host that has a different value for some host attribute that you specify
    • Balanced Host Attribute Constraint (BalancedHostAttrConstraint): schedules a set of tasks so that they are distributed evenly among types of hosts, where the type is determined by the value of a particular host attribute

Constraints that Operate on Single Tasks

Two built-in constraints operate on individual tasks (though the first of these, as a side effect, may end up constraining every other task).

Exclusive Host Constraint

This constraint ensures that when a task is assigned to a host, it will be the only task assigned to that host.

When this constraint evaluates a prospective host for a task, if any other task is either running on or assigned to that host, the constraint is not met. Only if the host has no tasks running on it and no tasks assigned to it is this constraint met.

Furthermore, this constraint does something no other constraint does: If a task with an exclusive host constraint is assigned to or running on a host, Fenzo will block any other tasks from being assigned to that host.

Host Attribute Value Constraint

This constraint matches a task with a host that has a particular value for a particular host attribute.

You pass the constructor for this constraint object two values: the name of the host attribute you want to match, and a function that, given a task ID, will return the value of this host attribute that marks a host as being appropriate for that task.

When this constraint evaluates a host to see if it is appropriate for a task, it calls that function and then checks the value it returns against the value of the attribute on the host. If they match, the constraint is met; otherwise (or if the host does not have such an attribute) the constraint is not met.

Constraints that Operate on Groups of Tasks

Two built-in constraints operate on sets of “co-tasks” that you want to assign in a coordinated fashion.

To construct these constraints, you must pass the constructor a function that, when given a single task identifier, will return a Set of the task identifiers of all of the co-tasks of that single task. This function is called the coTasksGetter in the constructor parameters list.

Unique Host Attribute Constraint

This constraint schedules a set of tasks so that each task ends up on a host that has a different value for some host attribute that you specify (if you do not specify a host attribute, this constraint uses the host name as the host attribute).

When this constraint evaluates a host to see if it is appropriate for a task, it checks to see whether any of the task’s co-tasks have already been assigned to a host with the same value for the specified host attribute. If so (or if the host does not have such an attribute), the constraint fails; if not, it succeeds.

You could use this constraint, for example, to assign each task in a group of co-tasks to a different zone. You would do this by choosing the ZoneAttribute as your specified host attribute when you instantiate this constraint. Note that if there are fewer zones than there are tasks you want to assign, this constraint will eventually evaluate a task as being unsuitable for every host and so that task will not be assigned at all.

If that behavior is undesirable, you probably want a Balanced Host Attribute Constraint instead.

Balanced Host Attribute Constraint

This constraint schedules a set of tasks so that they are distributed evenly among types of hosts, where the type is determined by the value of a particular host attribute.

You decide which host attribute Fenzo will use to categorize hosts into groups when you construct the constraint.

When Fenzo evaluates a host using this constraint, it looks at the current distribution of co-tasks among hosts to see how “balanced” they are. A distribution is perfectly balanced if every co-task is on a host with a different type (meaning it has a different value for the host attribute you chose when you constructed the Balanced Host Attribute Constraint). A distribution may still be balanced if some co-tasks are on hosts with the same type, if there is no way to distribute the tasks in a way that maintains perfect balance. But this constraint attempts to minimize the difference between the number of co-tasks running on the host type with the most co-tasks running on it and the number of co-tasks running on the host type with the least co-tasks on it.

The following table will help to illustrate this. It illustrates three scenarios, showing the initial distribution of tasks among host types and what that distribution would be if a new task were added to a “blue” host:

host group Scenario 1 Scenario 2 Scenario 3
before after before after before after
balance: balanced balanced balanced unbalanced unbalanced unbalanced
evaluation: success failure success
red 1 1 1 1 3 3
green 1 1 1 1 1 1
blue 1 2 2 3 2 3

The third scenario requires some explanation. Although the end state after adding another task to a host of the “blue” type is unbalanced, and although it would be more balanced to add the task to the “green” group (indeed, this would throw the system back into balance), adding the task to the “blue” group does not make the system as a whole any more unbalanced than it was already. That is to say, the difference between the number of co-tasks on the host with the most such tasks and the number on the host with the least does not change, so the proposed assignment does not further unbalance the system, and so this constraint is not violated.

Note, however, that if you convert this constraint into a soft constraint, it becomes more intelligent in situations like these. As a soft constraint, the Balanced Host Attribute Constraint weighs how close a host group would be to having the average number of tasks if a new task were assigned to it. The actual fitness calculation is approximately this:

avg = ⌈(total number of tasks assigned to all groups + 1 for the new task)÷(total number of groups)⌉
fitness = [(avg)−(number of tasks currently assigned to the prospective group)]÷(avg)
(or 0.0 if the number of tasks currently assigned to the prospective group exceeds avg)

In Scenario 3 above, this calculation would result in the following fitness values, with the “green” group showing the highest fitness for the new task:

host group tasks currently assigned fitness
red 3 (3−3)÷3 = 0.0
green 1 (3−1)÷3 ≈ 0.67
blue 2 (3−2)÷3 ≈ 0.33
avg = ⌈(3+1+2+1)÷3⌉ = 3

When you construct a balanced host attribute constraint object, you pass the constructor an expectedValues parameter that indicates how many distinct values for your specified host attribute you expect there to be among all of the hosts Fenzo will be looking at. Fenzo cannot intuit this value ahead of time, as it may receive initial resource offers that do not include one or more of the possible values, but it must know how many possible values to expect so that it can perform the balancing correctly.

Creating Your Own Constraint Variety

To create your own constraint variety, create an object that implements the ConstraintEvaluator interface and write its evaluate() method to evaluate hosts for whether they meet your chosen constraint.

This method takes three attributes: a description of the task to be assigned, a description of a potential host for the task, and the current status of tasks and task assignments in the system at large. If you need more information than this, you may need to pass it in when your constraint object is constructed or to obtain it in some other fashion.

Write your evaluation function to return a <codeResult object that indicates whether the constraint was met or not.

If you want your constraint simply to map its hard success/failure to a soft 1.0/0.0 when it is converted into a soft constraint, the existing AsSoftConstraint converter will do this automatically. Otherwise, you will also need to create some additional function capable of returning a VMTaskFitnessCalculator (a soft constraint) that performs a more nuanced fitness calculation that matches your constraint.

Making the Task Scheduler Aware of Your Constraints

You typically apply a hard constraint on a per-task basis (though see the section on soft constraints, below, for a method of applying a constraint globally). To apply a constraint to a task, implement the task’s TaskRequest object so that its getHardConstraints() method includes your constraint in the List it returns. Fenzo will apply each constraint in this list to the hosts it evaluates for the task. If any of these constraints fails, Fenzo will disqualify that host from hosting the task and will move on to evaluating the next host.

The task scheduler handles soft constraints differently, as shown in the following section:

Making a Constraint Soft

A ConstraintEvaluator by default implements a hard constraint, that is, one that either is or is not successful, with no middle ground. However, you may want to include a constraint in a fitness calculation (see Fitness Calculators), in which case you want the evaluation of the constraint expressed as a value on the 0.0–1.0 fitness scale.

In many cases, this conversion will merely map a constraint failure to 0.0 and a constraint success to 1.0 without any values in-between, but in some cases, such as with the Balanced Host Attribute Constraint described below, when you convert the constraint into a soft constraint this increases its precision considerably.

If your constraint implements an asSoftConstraint() method, as the Balanced Host Attribute Constraint does, use that method to get a nuanced soft constraint. Otherwise, pass your constraint object to the AsSoftConstraint.get() method to get a soft constraint that maps success/failure to 1.0/0.0.

Once you have converted your constraint into a soft constraint, you have two options for using it:

  1. To apply your soft constraint to a particular task, implement that task’s TaskRequest object so that its getSoftConstraints() method includes your soft constraint in the List it returns. Fenzo will then average the results of all of the soft constraints in this list, along with the results of the task scheduler’s fitness calculator (see Fitness Calculators), to determine the fitness of a host for the task.
  2. To apply your soft constraint globally, to all tasks, either assign it directly as the fitness calculator for your task scheduler (by passing your soft constraint into the .withFitnessCalculator() builder method) or incorporate it into a more complex fitness calculator of your own devising (see Combining Existing Fitness Calculator Plugins).