# nova-0001 — CWE-407: O(H×G) group_hosts list membership in ServerGroupAffinityFilter

**Severity:** HIGH
**File:** `nova/scheduler/filters/affinity_filter.py`
**Lines:** 150–156
**Status:** PATCHED

## Description

`_GroupAffinityFilter.host_passes()` is called once per candidate host
during VM scheduling.  For each call it does:

```python
group_hosts = (spec_obj.instance_group.hosts    # list of strings
               if spec_obj.instance_group else [])
if group_hosts:
    return host_state.host in group_hosts       # O(G) list scan
```

`spec_obj.instance_group.hosts` is built by `InstanceGroup.get_hosts()`
which returns `list(set(...))`.  For a group of G members and H candidate
hosts evaluated during a single scheduling pass, cost is O(H × G).

With H=500 hosts and G=200 group members this is 100 000 string
comparisons per scheduling request.

Additionally in the same filter class:

```python
policies = (spec_obj.instance_group.policies    # ListOfStringsField
            if spec_obj.instance_group else [])
if self.policy_name not in policies:            # O(P) per host
```

`policies` is a `ListOfStringsField` (a list), so `not in` is O(P) per
call.  With H hosts this is O(H × P).

## Complexity

| Check | Defective | Fixed |
|-------|-----------|-------|
| `host in group_hosts` | O(G) per host | O(1) per host |
| `policy not in policies` | O(P) per host | O(1) per host |

## Fix

Convert `group_hosts` and `policies` to sets before the loop-per-host
check.  Since `host_passes` is called per host, the set should be built
once per scheduling request and cached.  The simplest local fix:

```python
group_hosts = set(spec_obj.instance_group.hosts
                  if spec_obj.instance_group else [])
return host_state.host in group_hosts   # O(1)
```

```python
policies = set(spec_obj.instance_group.policies
               if spec_obj.instance_group else [])
if self.policy_name not in policies:    # O(1)
```

## Patch

See `patch/nova-0001.patch`

## Test

See `unit/NovaAffinityFilterAlgorithm.java`

## Speedup

At H=500 hosts, G=200 group members: ~200× fewer string comparisons.
