summaryrefslogtreecommitdiff
path: root/nova/tests/functional/test_scheduler.py
blob: 2f3f6782f6ed9c938a20d90fc4f1bf2437f2a272 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
#    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.

import datetime

from nova.compute import instance_actions
from nova import test
from nova.tests import fixtures as nova_fixtures
from nova.tests.functional import fixtures as func_fixtures
from nova.tests.functional import integrated_helpers
from nova.tests.unit import fake_network

CELL1_NAME = 'cell1'
CELL2_NAME = 'cell2'


class _AggregateTestCase(integrated_helpers._IntegratedTestBase):

    def _create_aggregate(self, metadata):
        aggregate = self.admin_api.post_aggregate(
            {'aggregate': {'name': 'my-aggregate'}})
        aggregate = self.admin_api.post_aggregate_action(
            aggregate['id'],
            {'set_metadata': {'metadata': metadata}})
        self.admin_api.add_host_to_aggregate(
            aggregate['id'], self.compute.host)
        return aggregate


class AggregateImagePropertiesIsolationTestCase(_AggregateTestCase):
    """Test the AggregateImagePropertiesIsolation filter."""

    def setUp(self):
        self.flags(
            enabled_filters=['AggregateImagePropertiesIsolation'],
            group='filter_scheduler')

        super().setUp()

    def _create_image(self, metadata):
        image = {
            'id': 'c456eb30-91d7-4f43-8f46-2efd9eccd744',
            'name': 'fake-image-custom-property',
            'created_at': datetime.datetime(2011, 1, 1, 1, 2, 3),
            'updated_at': datetime.datetime(2011, 1, 1, 1, 2, 3),
            'deleted_at': None,
            'deleted': False,
            'status': 'active',
            'is_public': False,
            'container_format': 'raw',
            'disk_format': 'raw',
            'size': '25165824',
            'min_ram': 0,
            'min_disk': 0,
            'protected': False,
            'visibility': 'public',
            'tags': ['tag1', 'tag2'],
            'properties': {
                'kernel_id': 'nokernel',
                'ramdisk_id': 'nokernel',
            },
        }
        image['properties'].update(metadata)
        return self.glance.create(None, image)

    def test_filter_passes(self):
        """Ensure the filter allows hosts in aggregates with matching metadata.
        """
        self._create_aggregate(metadata={'os_type': 'windows'})
        image = self._create_image(metadata={'os_type': 'windows'})
        self._create_server(image_uuid=image['id'])

    def test_filter_rejects(self):
        """Ensure the filter rejects hosts in aggregates with mismatched
        metadata.
        """
        self._create_aggregate(metadata={'os_type': 'windows'})
        image = self._create_image(metadata={'os_type': 'linux'})
        self._create_server(image_uuid=image['id'], expected_state='ERROR')

    def test_filter_passes_with_prefix(self):
        """Ensure the filter allows hosts in aggregates with matching metadata
        when a namespace is configured.
        """
        self.flags(
            aggregate_image_properties_isolation_namespace='os',
            aggregate_image_properties_isolation_separator='_',
            group='filter_scheduler',
        )
        self._create_aggregate(metadata={'os_type': 'windows'})
        image = self._create_image(metadata={'os_type': 'windows'})
        self._create_server(image_uuid=image['id'])

    def test_filter_rejects_with_prefix(self):
        """Ensure the filter rejects hosts in aggregates with matching metadata
        when a namespace is configured.
        """
        self.flags(
            aggregate_image_properties_isolation_namespace='os',
            aggregate_image_properties_isolation_separator='_',
            group='filter_scheduler',
        )
        self._create_aggregate(metadata={'os_type': 'windows'})
        image = self._create_image(metadata={'os_type': 'linux'})
        self._create_server(image_uuid=image['id'], expected_state='ERROR')

    def test_filter_passes_with_invalid_key(self):
        """Ensure invalid keys are ignored by the filter."""
        self._create_aggregate(metadata={'type': 'windows'})
        image = self._create_image(metadata={'type': 'linux'})
        self._create_server(image_uuid=image['id'])

    def test_filter_passes_with_irrelevant_key(self):
        """Ensure valid keys that are no in the namespace are ignored by the
        filter.
        """
        self.flags(
            aggregate_image_properties_isolation_namespace='os',
            aggregate_image_properties_isolation_separator='_',
            group='filter_scheduler',
        )
        self._create_aggregate(metadata={'os_type': 'windows'})
        image = self._create_image(metadata={'hw_firmware_type': 'uefi'})
        self._create_server(image_uuid=image['id'])


class AggregateInstanceExtraSpecsFilterTestCase(_AggregateTestCase):
    """Test the AggregateInstanceExtraSpecsFilter filter."""

    def setUp(self):
        self.flags(
            enabled_filters=['AggregateInstanceExtraSpecsFilter'],
            group='filter_scheduler')

        super().setUp()

    def test_filter_passes(self):
        """Ensure the filter allows hosts in aggregates with matching metadata.
        """
        self._create_aggregate(metadata={'foo': 'bar'})
        flavor_id = self._create_flavor(extra_spec={'foo': 'bar'})
        self._create_server(flavor_id=flavor_id)

    def test_filter_rejects(self):
        """Ensure the filter rejects hosts in aggregates with mismatched
        metadata.
        """
        self._create_aggregate(metadata={'foo': 'bar'})
        flavor_id = self._create_flavor(extra_spec={'foo': 'baz'})
        self._create_server(flavor_id=flavor_id, expected_state='ERROR')

    def test_filter_passes_with_prefix(self):
        """Ensure the filter allows hosts in aggregates with matching metadata
        when the namespace is used.
        """
        self._create_aggregate(metadata={'foo': 'bar'})
        flavor_id = self._create_flavor(
            extra_spec={'aggregate_instance_extra_specs:foo': 'bar'})
        self._create_server(flavor_id=flavor_id)

    def test_filter_rejects_with_prefix(self):
        """Ensure the filter rejects hosts in aggregates with mismatched
        metadata when the namespace is used.
        """
        self._create_aggregate(metadata={'foo': 'bar'})
        flavor_id = self._create_flavor(
            extra_spec={'aggregate_instance_extra_specs:foo': 'baz'})
        self._create_server(flavor_id=flavor_id, expected_state='ERROR')


class MultiCellSchedulerTestCase(test.TestCase,
                                 integrated_helpers.InstanceHelperMixin):

    NUMBER_OF_CELLS = 2

    def setUp(self):
        super(MultiCellSchedulerTestCase, self).setUp()
        self.useFixture(nova_fixtures.RealPolicyFixture())
        self.useFixture(nova_fixtures.NeutronFixture(self))
        self.useFixture(nova_fixtures.GlanceFixture(self))
        self.useFixture(nova_fixtures.AllServicesCurrent())
        self.useFixture(func_fixtures.PlacementFixture())
        api_fixture = self.useFixture(nova_fixtures.OSAPIFixture(
            api_version='v2.1'))
        self.api = api_fixture.api
        self.admin_api = api_fixture.admin_api

        fake_network.set_stub_network_methods(self)

        self.flags(allow_resize_to_same_host=False)
        self.flags(enabled_filters=['AllHostsFilter'],
                   group='filter_scheduler')
        self.start_service('conductor')
        self.start_service('scheduler')

    def _test_create_and_migrate(self, expected_status, az=None):
        server = self._create_server(az=az)

        return self.admin_api.api_post(
            '/servers/%s/action' % server['id'],
            {'migrate': None},
            check_response_status=[expected_status]), server

    def test_migrate_between_cells(self):
        """Verify that migrating between cells is not allowed.

        Right now, we can't migrate between cells. So, create two computes
        in different cells and make sure that migration fails with NoValidHost.
        """
        # Hosts in different cells
        self.start_service('compute', host='compute1', cell_name=CELL1_NAME)
        self.start_service('compute', host='compute2', cell_name=CELL2_NAME)

        _, server = self._test_create_and_migrate(expected_status=202)
        # The instance action should have failed with details.
        self._assert_resize_migrate_action_fail(
            server, instance_actions.MIGRATE, 'NoValidHost')

    def test_migrate_within_cell(self):
        """Verify that migrating within cells is allowed.

        Create two computes in the same cell and validate that the same
        migration is allowed.
        """
        # Hosts in the same cell
        self.start_service('compute', host='compute1', cell_name=CELL1_NAME)
        self.start_service('compute', host='compute2', cell_name=CELL1_NAME)
        # Create another host just so it looks like we have hosts in
        # both cells
        self.start_service('compute', host='compute3', cell_name=CELL2_NAME)

        # Force the server onto compute1 in cell1 so we do not accidentally
        # land on compute3 in cell2 and fail to migrate.
        _, server = self._test_create_and_migrate(expected_status=202,
                                      az='nova:compute1')
        self._wait_for_state_change(server, 'VERIFY_RESIZE')