#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

# Tests for granular resource requests

fixtures:
    # See the layout diagram in this fixture's docstring in ../fixtures.py
    - GranularFixture

defaults:
    request_headers:
        x-auth-token: admin
        content-type: application/json
        accept: application/json
        openstack-api-version: placement 1.25

tests:

- name: different groups hit with group_policy=none
  GET: /allocation_candidates
  query_parameters:
      resources1: VCPU:1
      resources2: MEMORY_MB:1024
      group_policy: none
  status: 200
  response_json_paths:
      $.allocation_requests.`len`: 3
      $.provider_summaries.`len`: 3
      $.allocation_requests..allocations["$ENVIRON['CN_LEFT']"].resources:
          VCPU: 1
          MEMORY_MB: 1024
      $.allocation_requests..allocations["$ENVIRON['CN_MIDDLE']"].resources:
          VCPU: 1
          MEMORY_MB: 1024
      $.allocation_requests..allocations["$ENVIRON['CN_RIGHT']"].resources:
          VCPU: 1
          MEMORY_MB: 1024
      $.provider_summaries["$ENVIRON['CN_LEFT']"].resources:
          VCPU:
            capacity: 8
            used: 0
          MEMORY_MB:
            capacity: 4096
            used: 0
      $.provider_summaries["$ENVIRON['CN_MIDDLE']"].resources:
          VCPU:
            capacity: 8
            used: 0
          MEMORY_MB:
            capacity: 4096
            used: 0
      $.provider_summaries["$ENVIRON['CN_RIGHT']"].resources:
          VCPU:
            capacity: 8
            used: 0
          MEMORY_MB:
            capacity: 4096
            used: 0

- name: different groups miss with group_policy=isolate
  GET: /allocation_candidates
  query_parameters:
      resources1: VCPU:1
      resources2: MEMORY_MB:1024
      group_policy: isolate
  status: 200
  response_json_paths:
      # We asked for VCPU and MEMORY_MB to be satisfied by *different*
      # providers, because they're in separate numbered request groups and
      # group_policy=isolate.  Since there are no sharing providers of these
      # resources, we get no results.
      $.allocation_requests.`len`: 0
      $.provider_summaries.`len`: 0

- name: multiple group_policy picks the first one
  # NOTE(efried): gabbi query_parameters doesn't preserve param order
  GET: /allocation_candidates?resources1=VCPU:1&resources2=MEMORY_MB:1024&group_policy=isolate&group_policy=none
  status: 200
  response_json_paths:
      $.allocation_requests.`len`: 0
      $.provider_summaries.`len`: 0

- name: resources combine
  GET: /allocation_candidates
  query_parameters:
      resources: VCPU:3,MEMORY_MB:512
      resources1: VCPU:1,MEMORY_MB:1024
      resources2: VCPU:2
      group_policy: none
  status: 200
  response_json_paths:
      $.allocation_requests.`len`: 3
      $.provider_summaries.`len`: 3
      $.allocation_requests..allocations["$ENVIRON['CN_LEFT']"].resources:
          VCPU: 6
          MEMORY_MB: 1536
      $.allocation_requests..allocations["$ENVIRON['CN_MIDDLE']"].resources:
          VCPU: 6
          MEMORY_MB: 1536
      $.allocation_requests..allocations["$ENVIRON['CN_RIGHT']"].resources:
          VCPU: 6
          MEMORY_MB: 1536

- name: group policy not required with only one numbered group
  GET: /allocation_candidates?resources=VCPU:1&resources1=MEMORY_MB:2048
  status: 200
  response_json_paths:
      $.allocation_requests.`len`: 3
      $.provider_summaries.`len`: 3

- name: disk sharing isolated
  GET: /allocation_candidates
  query_parameters:
      resources1: VCPU:1,MEMORY_MB:1024
      resources2: DISK_GB:100
      group_policy: isolate
  status: 200
  response_json_paths:
      # Here we've asked for VCPU and MEMORY_MB to be satisfied by the same
      # provider - all three of our non-sharing providers can do that - and
      # the DISK_GB to be satisfied by a *different* provider than the VCPU and
      # MEMORY_MB.  So we'll get all permutations where cn_* provide VCPU and
      # MEMORY_MB and shr_disk_* provide the DISK_GB; but *no* results where
      # DISK_GB is provided by the cn_*s themselves.
      $.allocation_requests.`len`: 5
      $.provider_summaries.`len`: 5

- name: disk sharing non-isolated
  GET: /allocation_candidates
  query_parameters:
      resources1: VCPU:1,MEMORY_MB:1024
      resources2: DISK_GB:100
      group_policy: none
  status: 200
  response_json_paths:
      $.allocation_requests.`len`: 7
      $.provider_summaries.`len`: 5

- name: disk alone
  GET: /allocation_candidates
  query_parameters:
      resources1: DISK_GB:800
  status: 200
  response_json_paths:
      $.allocation_requests.`len`: 2
      $.provider_summaries.`len`: 2
      $.allocation_requests..allocations["$ENVIRON['SHR_DISK_1']"].resources[DISK_GB]: 800
      $.allocation_requests..allocations["$ENVIRON['SHR_DISK_2']"].resources[DISK_GB]: 800

- name: disk alone non-granular
  GET: /allocation_candidates
  query_parameters:
      resources: DISK_GB:800
  status: 200
  response_json_paths:
      $.allocation_requests.`len`: 2
      $.provider_summaries.`len`: 2
      $.allocation_requests..allocations["$ENVIRON['SHR_DISK_1']"].resources[DISK_GB]: 800
      $.allocation_requests..allocations["$ENVIRON['SHR_DISK_2']"].resources[DISK_GB]: 800

- name: isolated ssd
  GET: /allocation_candidates
  query_parameters:
      resources1: VCPU:1,MEMORY_MB:1024
      resources2: DISK_GB:100
      required2: CUSTOM_DISK_SSD
      group_policy: isolate
  status: 200
  response_json_paths:
      # We get candidates [cn_left + shr_disk_1] and [cn_middle + shr_disk_1]
      # We don't get [cn_right + shr_disk_1] because they're not associated via aggregate.
      # We don't get [cn_left/middle + shr_disk_2] because shr_disk_2 doesn't have the SSD trait
      # We don't get [cn_left] or [cn_right] even though they have SSD disk because we asked to isolate
      $.allocation_requests.`len`: 2
      $.allocation_requests..allocations["$ENVIRON['CN_LEFT']"].resources:
          VCPU: 1
          MEMORY_MB: 1024
      $.allocation_requests..allocations["$ENVIRON['CN_MIDDLE']"].resources:
          VCPU: 1
          MEMORY_MB: 1024
      # shr_disk_1 satisfies the disk for both allocation requests
      $.allocation_requests..allocations["$ENVIRON['SHR_DISK_1']"].resources[DISK_GB]: [100, 100]
      $.provider_summaries.`len`: 3
      $.provider_summaries["$ENVIRON['CN_LEFT']"].resources:
          VCPU:
            capacity: 8
            used: 0
          MEMORY_MB:
            capacity: 4096
            used: 0
          DISK_GB:
            capacity: 500
            used: 0
      $.provider_summaries["$ENVIRON['CN_MIDDLE']"].resources:
          VCPU:
            capacity: 8
            used: 0
          MEMORY_MB:
            capacity: 4096
            used: 0
      $.provider_summaries["$ENVIRON['SHR_DISK_1']"].resources:
          DISK_GB:
            capacity: 1000
            used: 0

- name: no isolation, forbid ssd
  GET: /allocation_candidates
  query_parameters:
      resources1: VCPU:1
      resources2: DISK_GB:100
      required2: "!CUSTOM_DISK_SSD"
      group_policy: none
  status: 200
  response_json_paths:
      # The permutations we *don't* get are:
      # cn_right by itself because it has SSD
      # - anything involving shr_disk_1 because it has SSD
      $.allocation_requests.`len`: 4
      # We get two allocation requests involving cn_left - one where it
      # satisfies the disk itself and one where shr_disk_2 provides it
      $.allocation_requests..allocations["$ENVIRON['CN_LEFT']"].resources[VCPU]: [1, 1]
      # We get one for [cn_middle + shr_disk_2] - it doesn't have disk to provide for itself
      $.allocation_requests..allocations["$ENVIRON['CN_MIDDLE']"].resources[VCPU]: 1
      # We get one for [cn_right + shr_disk_2] - cn_right can't provide its own
      # disk due to the forbidden SSD trait.
      $.allocation_requests..allocations["$ENVIRON['CN_RIGHT']"].resources[VCPU]: 1
      # shr_disk_2 satisfies the disk for three out of the four allocation
      # requests (all except the one where cn_left provides for itself)
      $.allocation_requests..allocations["$ENVIRON['SHR_DISK_2']"].resources[DISK_GB]: [100, 100, 100]
      # Validate that we got the correct four providers in the summaries
      $.provider_summaries.`len`: 4
      $.provider_summaries["$ENVIRON['CN_LEFT']"].resources[VCPU][capacity]: 8
      $.provider_summaries["$ENVIRON['CN_MIDDLE']"].resources[VCPU][capacity]: 8
      $.provider_summaries["$ENVIRON['CN_RIGHT']"].resources[VCPU][capacity]: 8
      $.provider_summaries["$ENVIRON['SHR_DISK_2']"].resources[DISK_GB][capacity]: 1000

- name: member_of filters
  GET: /allocation_candidates
  query_parameters:
      resources1: VCPU:1
      resources2: DISK_GB:100
      member_of2: $ENVIRON['AGGC']
      group_policy: none
  status: 200
  response_json_paths:
      $.allocation_requests.`len`: 1
      $.allocation_requests[0].allocations["$ENVIRON['CN_RIGHT']"].resources:
          VCPU: 1
          DISK_GB: 100
      $.provider_summaries.`len`: 1
      $.provider_summaries["$ENVIRON['CN_RIGHT']"].resources[VCPU][capacity]: 8
      $.provider_summaries["$ENVIRON['CN_RIGHT']"].resources[DISK_GB][capacity]: 500

- name: required, forbidden, member_of in
  GET: /allocation_candidates
  query_parameters:
      resources1: VCPU:1
      required1: "!HW_CPU_X86_SSE"
      resources2: DISK_GB:100
      required2: CUSTOM_DISK_SSD
      member_of2: in:$ENVIRON['AGGA'],$ENVIRON['AGGC']
      group_policy: none
  status: 200
  response_json_paths:
      # cn_middle won't appear (forbidden SSE trait)
      # shr_disk_2 won't appear (required SSD trait is absent)
      # [cn_left] won't be in the results (required SSD trait is absent)
      # So we'll get:
      # [cn_left, shr_disk_1]
      # [cn_right]
      $.allocation_requests.`len`: 2
      $.allocation_requests..allocations["$ENVIRON['CN_LEFT']"].resources[VCPU]: 1
      $.allocation_requests..allocations["$ENVIRON['CN_RIGHT']"].resources[VCPU]: 1
      $.allocation_requests..allocations["$ENVIRON['SHR_DISK_1']"].resources[DISK_GB]: 100
      $.provider_summaries.`len`: 3
      $.provider_summaries["$ENVIRON['CN_LEFT']"].resources[VCPU][capacity]: 8
      $.provider_summaries["$ENVIRON['CN_RIGHT']"].resources[VCPU][capacity]: 8
      $.provider_summaries["$ENVIRON['CN_RIGHT']"].resources[DISK_GB][capacity]: 500
      $.provider_summaries["$ENVIRON['SHR_DISK_1']"].resources[DISK_GB][capacity]: 1000

- name: required, forbidden, member_of in long suffix
  desc: same as above, but using complex suffixes
  GET: /allocation_candidates
  query_parameters:
      resources_compute: VCPU:1
      required_compute: "!HW_CPU_X86_SSE"
      resources_disk: DISK_GB:100
      required_disk: CUSTOM_DISK_SSD
      member_of_disk: in:$ENVIRON['AGGA'],$ENVIRON['AGGC']
      group_policy: none
  request_headers:
      openstack-api-version: placement 1.33
  status: 200
  response_json_paths:
      $.allocation_requests.`len`: 2
      $.allocation_requests..allocations["$ENVIRON['CN_LEFT']"].resources[VCPU]: 1
      $.allocation_requests..allocations["$ENVIRON['CN_RIGHT']"].resources[VCPU]: 1
      $.allocation_requests..allocations["$ENVIRON['SHR_DISK_1']"].resources[DISK_GB]: 100
      $.provider_summaries.`len`: 3
      $.provider_summaries["$ENVIRON['CN_LEFT']"].resources[VCPU][capacity]: 8
      $.provider_summaries["$ENVIRON['CN_RIGHT']"].resources[VCPU][capacity]: 8
      $.provider_summaries["$ENVIRON['CN_RIGHT']"].resources[DISK_GB][capacity]: 500
      $.provider_summaries["$ENVIRON['SHR_DISK_1']"].resources[DISK_GB][capacity]: 1000

- name: multiple member_of
  GET: /allocation_candidates
  query_parameters:
      resources1: VCPU:1
      resources2: DISK_GB:100
      member_of2:
        - in:$ENVIRON['AGGB'],$ENVIRON['AGGC']
        - $ENVIRON['AGGA']
      group_policy: isolate
  status: 200
  response_json_paths:
      # The member_of2 specifications say that the DISK_GB resource must come
      # from a provider that's in aggA and also in (aggB and/or aggC). Only
      # shr_disk_2 qualifies; so we'll get results anchored at cn_middle and
      # cn_right.  But note that we'll also get a result anchored at cn_left:
      # it doesn't meet the member_of criteria, but it doesn't need to, since
      # it's not providing the DISK_GB resource.
      $.allocation_requests.`len`: 3
      $.allocation_requests..allocations["$ENVIRON['CN_LEFT']"].resources[VCPU]: 1
      $.allocation_requests..allocations["$ENVIRON['CN_MIDDLE']"].resources[VCPU]: 1
      $.allocation_requests..allocations["$ENVIRON['CN_RIGHT']"].resources[VCPU]: 1
      $.allocation_requests..allocations["$ENVIRON['SHR_DISK_2']"].resources[DISK_GB]: [100, 100, 100]
      $.provider_summaries.`len`: 4
      $.provider_summaries["$ENVIRON['CN_LEFT']"].resources[VCPU][capacity]: 8
      $.provider_summaries["$ENVIRON['CN_MIDDLE']"].resources[VCPU][capacity]: 8
      $.provider_summaries["$ENVIRON['CN_RIGHT']"].resources[VCPU][capacity]: 8
      $.provider_summaries["$ENVIRON['SHR_DISK_2']"].resources[DISK_GB][capacity]: 1000

- name: multiple disks, multiple networks
  GET: /allocation_candidates
  query_parameters:
      resources1: VCPU:1
      resources2: VGPU:1
      required2: HW_GPU_API_DXVA
      resources3: MEMORY_MB:1024
      resources4: DISK_GB:100
      required4: CUSTOM_DISK_SSD
      resources5: DISK_GB:50
      required5: "!CUSTOM_DISK_SSD"
      resources6: SRIOV_NET_VF:1,CUSTOM_NET_MBPS:1000
      resources7: SRIOV_NET_VF:2,CUSTOM_NET_MBPS:2000
      group_policy: none
  # Breaking it down:
  #   => These could come from cn_left, cn_middle, or cn_right
  #     ?resources1=VCPU:1
  #     &resources3=MEMORY_MB:1024
  #   => But this limits us to cn_left and cn_right
  #     &resources2=VGPU:1&required2=HW_GPU_API_DXVA
  #   => Since we're not isolating, this SSD can come from cn_right or shr_disk_1
  #     &resources4=DISK_GB:100&required4=CUSTOM_DISK_SSD
  #   => This non-SSD can come from cn_left or shr_disk_2
  #     &resources5=DISK_GB:50&required5=!CUSTOM_DISK_SSD
  #   => These VFs and bandwidth can come from cn_left or shr_net.  Since cn_left
  #      can't be an anchor for shr_net, these will always combine.
  #     &resources6=SRIOV_NET_VF:1,CUSTOM_NET_MBPS:1000
  #     &resources7=SRIOV_NET_VF:2,CUSTOM_NET_MBPS:2000
  #   => If we didn't do this, the separated VCPU/MEMORY_MB/VGPU resources would
  #      cause us to get no results
  #     &group_policy=none
  status: 200
  response_json_paths:
      # We have two permutations involving cn_left.
      # - One where the non-SSD is satisfied by cn_left itself
      # [cn_left(VCPU:1, MEMORY_MB:1024, VGPU:1, DISK_GB:50, SRIOV_NET_VF:3, CUSTOM_NET_MBPS:3000),
      #  shr_disk_1(DISK_GB:100)]
      # - And one where the non-SSD is satisfied by shr_disk_2
      # [cn_left(VCPU:1, MEMORY_MB:1024, VGPU:1, SRIOV_NET_VF:3, CUSTOM_NET_MBPS:3000),
      #  shr_disk_1(DISK_GB:100),
      #  shr_disk_2(DISK_GB: 50)]
      # There's only one result involving cn_right.
      # - We must satisfy the SSD from cn_right and the non-SSD from shr_disk_2
      # - We must satisfy the network stuff from shr_net
      # [cn_right(VCPU:1, MEMORY_MB:1024, VGPU:1, DISK_GB:100),
      #  shr_disk_2(DISK_GB:50),
      #  shr_net(SRIOV_NET_VF:3, CUSTOM_NET_MBPS:3000)]
      $.allocation_requests.`len`: 3
      $.allocation_requests..allocations["$ENVIRON['CN_LEFT']"].resources[VCPU]: [1, 1]
      $.allocation_requests..allocations["$ENVIRON['CN_LEFT']"].resources[MEMORY_MB]: [1024, 1024]
      $.allocation_requests..allocations["$ENVIRON['CN_LEFT']"].resources[VGPU]: [1, 1]
      $.allocation_requests..allocations["$ENVIRON['CN_LEFT']"].resources[SRIOV_NET_VF]: [3, 3]
      $.allocation_requests..allocations["$ENVIRON['CN_LEFT']"].resources[CUSTOM_NET_MBPS]: [3000, 3000]
      $.allocation_requests..allocations["$ENVIRON['CN_LEFT']"].resources[DISK_GB]: 50
      # These come from the cn_left results
      $.allocation_requests..allocations["$ENVIRON['SHR_DISK_1']"].resources[DISK_GB]: [100, 100]
      # One of these comes from the second cn_left result, the other from the cn_right result
      $.allocation_requests..allocations["$ENVIRON['SHR_DISK_2']"].resources[DISK_GB]: [50, 50]
      $.allocation_requests..allocations["$ENVIRON['CN_RIGHT']"].resources[VCPU]: 1
      $.allocation_requests..allocations["$ENVIRON['CN_RIGHT']"].resources[MEMORY_MB]: 1024
      $.allocation_requests..allocations["$ENVIRON['CN_RIGHT']"].resources[VGPU]: 1
      $.allocation_requests..allocations["$ENVIRON['CN_RIGHT']"].resources[DISK_GB]: 100
      $.allocation_requests..allocations["$ENVIRON['SHR_NET']"].resources[SRIOV_NET_VF]: 3
      $.allocation_requests..allocations["$ENVIRON['SHR_NET']"].resources[CUSTOM_NET_MBPS]: 3000
      # Just make sure we got the correct four providers in the summaries
      $.provider_summaries.`len`: 5
      $.provider_summaries["$ENVIRON['CN_LEFT']"].resources[VCPU][capacity]: 8
      $.provider_summaries["$ENVIRON['CN_RIGHT']"].resources[VCPU][capacity]: 8
      $.provider_summaries["$ENVIRON['SHR_DISK_1']"].resources[DISK_GB][capacity]: 1000
      $.provider_summaries["$ENVIRON['SHR_DISK_2']"].resources[DISK_GB][capacity]: 1000
      $.provider_summaries["$ENVIRON['SHR_NET']"].resources[SRIOV_NET_VF][capacity]: 16

- name: combining request groups exceeds capacity
  GET: /allocation_candidates
  query_parameters:
      resources: VCPU:2,MEMORY_MB:2048,SRIOV_NET_VF:1,CUSTOM_NET_MBPS:2000
      resources1: SRIOV_NET_VF:1,CUSTOM_NET_MBPS:3000
  status: 200
  response_json_paths:
      # CUSTOM_NET_MBPS of 2000 + 3000 = 5000 is too much for cn_left, but
      # shr_net can accommodate it.
      $.allocation_requests.`len`: 1
      $.allocation_requests..allocations["$ENVIRON['CN_RIGHT']"].resources[VCPU]: 2
      $.allocation_requests..allocations["$ENVIRON['CN_RIGHT']"].resources[MEMORY_MB]: 2048
      $.allocation_requests..allocations["$ENVIRON['SHR_NET']"].resources[SRIOV_NET_VF]: 2
      $.allocation_requests..allocations["$ENVIRON['SHR_NET']"].resources[CUSTOM_NET_MBPS]: 5000
      $.provider_summaries.`len`: 2
      $.provider_summaries["$ENVIRON['CN_RIGHT']"].resources[VCPU][capacity]: 8
      $.provider_summaries["$ENVIRON['SHR_NET']"].resources[CUSTOM_NET_MBPS][capacity]: 40000

- name: combining request groups exceeds max_unit
  GET: /allocation_candidates
  query_parameters:
      resources: VGPU:1
      resources1: VGPU:1
      resources2: VGPU:1
      group_policy: none
  status: 200
  response_json_paths:
      # VGPU of 1 + 1 + 1 = 3 exceeds max_unit on cn_right, but cn_left can handle it.
      $.allocation_requests.`len`: 1
      $.allocation_requests..allocations["$ENVIRON['CN_LEFT']"].resources[VGPU]: 3
      $.provider_summaries.`len`: 1
      $.provider_summaries["$ENVIRON['CN_LEFT']"].resources[VGPU][capacity]: 8

#################
# Error scenarios
#################
- name: numbered resources bad microversion
  GET: /allocation_candidates?resources=MEMORY_MB:1024&resources1=VCPU:1
  request_headers:
      openstack-api-version: placement 1.24
  status: 400
  response_strings:
      - Invalid query string parameters
      - "'resources1' was unexpected"

- name: numbered traits bad microversion
  GET: /allocation_candidates?resources=MEMORY_MB:1024&required2=HW_CPU_X86_AVX2
  request_headers:
      openstack-api-version: placement 1.24
  status: 400
  response_strings:
      - Invalid query string parameters
      - "'required2' was unexpected"

- name: numbered member_of bad microversion
  GET: /allocation_candidates?resources=MEMORY_MB:1024&member_of3=$ENVIRON['AGGB']
  request_headers:
      openstack-api-version: placement 1.24
  status: 400
  response_strings:
      - Invalid query string parameters
      - "'member_of3' was unexpected"

- name: group_policy bad microversion
  GET: /allocation_candidates?resources=VCPU:1&group_policy=isolate
  request_headers:
      openstack-api-version: placement 1.24
  status: 400
  response_strings:
      - Invalid query string parameters
      - "'group_policy' was unexpected"

- name: bogus numbering
  GET: /allocation_candidates?resources01=VCPU:1
  status: 400
  response_strings:
      - Invalid query string parameters
      - "'resources01' does not match any of the regexes"

- name: bogus suffix
  desc: this is bogus because of unsupported character
  GET: /allocation_candidates?resources1@=VCPU:1
  request_headers:
      openstack-api-version: placement 1.33
  status: 400
  response_strings:
      - Invalid query string parameters
      - "'resources1@' does not match any of the regexes"
      - "^member_of([a-zA-Z0-9_-]{1,64})?$"

- name: bogus length
  desc: 65 character suffix is too long
  GET: /allocation_candidates?resources_0123456701234567012345670123456701234567012345670123456701234567=VCPU:1
  request_headers:
      openstack-api-version: placement 1.33
  status: 400
  response_strings:
      - Invalid query string parameters
      - "'resources_0123456701234567012345670123456701234567012345670123456701234567' does not match any of the regexes"
      - "^member_of([a-zA-Z0-9_-]{1,64})?$"

- name: invalid group_policy value
  GET: /allocation_candidates?resources=VCPU:1&group_policy=bogus
  status: 400
  response_strings:
      - Invalid query string parameters
      - "'bogus' is not one of ['none', 'isolate']"

- name: group_policy required when more than one numbered group
  GET: /allocation_candidates?resources1=VCPU:1&resources2=VCPU:1
  status: 400
  response_strings:
      - The \"group_policy\" parameter is required when specifying more than one \"resources{N}\" parameter.

- name: orphaned traits keys
  GET: /allocation_candidates?required=FOO&required1=BAR
  status: 400
  response_strings:
      - 'Found the following orphaned traits keys: required, required1'

- name: orphaned member_of keys
  GET: /allocation_candidates?member_of=$ENVIRON['AGGA']&member_of3=$ENVIRON['AGGC']
  status: 400
  response_strings:
      - 'Found the following orphaned member_of keys: member_of, member_of3'

- name: at least one request group required
  GET: /allocation_candidates?group_policy=isolate
  status: 400
  response_strings:
      - At least one request group (`resources` or `resources{$S}`) is required.
