Merge "Support running containers in specified AZ"
This commit is contained in:
commit
3dab16e0df
@ -289,6 +289,8 @@ class ContainersController(base.Controller):
|
|||||||
extra_spec = {}
|
extra_spec = {}
|
||||||
extra_spec['hints'] = container_dict.get('hints', None)
|
extra_spec['hints'] = container_dict.get('hints', None)
|
||||||
extra_spec['pci_requests'] = pci_req
|
extra_spec['pci_requests'] = pci_req
|
||||||
|
extra_spec['availability_zone'] = container_dict.get(
|
||||||
|
'availability_zone')
|
||||||
new_container = objects.Container(context, **container_dict)
|
new_container = objects.Container(context, **container_dict)
|
||||||
new_container.create(context)
|
new_container.create(context)
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ _container_properties = {
|
|||||||
'runtime': parameter_types.runtime,
|
'runtime': parameter_types.runtime,
|
||||||
'hostname': parameter_types.hostname,
|
'hostname': parameter_types.hostname,
|
||||||
'disk': parameter_types.positive_integer,
|
'disk': parameter_types.positive_integer,
|
||||||
|
'availability_zone': parameter_types.availability_zone,
|
||||||
}
|
}
|
||||||
|
|
||||||
container_create = {
|
container_create = {
|
||||||
|
@ -113,6 +113,12 @@ nets = {
|
|||||||
'type': ['array', 'null']
|
'type': ['array', 'null']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
availability_zone = {
|
||||||
|
'type': ['string', 'null'],
|
||||||
|
'minLength': 1,
|
||||||
|
'maxLength': 255,
|
||||||
|
}
|
||||||
|
|
||||||
mounts = {
|
mounts = {
|
||||||
'type': ['array', 'null'],
|
'type': ['array', 'null'],
|
||||||
'items': {
|
'items': {
|
||||||
|
@ -24,6 +24,20 @@ services.
|
|||||||
Possible values:
|
Possible values:
|
||||||
|
|
||||||
* Any string representing an existing availability zone name.
|
* Any string representing an existing availability zone name.
|
||||||
|
"""),
|
||||||
|
cfg.StrOpt('default_schedule_zone',
|
||||||
|
help="""
|
||||||
|
Default availability zone for containers.
|
||||||
|
|
||||||
|
This option determines the default availability zone for containers, which will
|
||||||
|
be used when a user does not specify one when creating a container. The
|
||||||
|
container(s) will be bound to this availability zone for their lifetime.
|
||||||
|
|
||||||
|
Possible values:
|
||||||
|
|
||||||
|
* Any string representing an existing availability zone name.
|
||||||
|
* None, which means that the container can move from one availability zone to
|
||||||
|
another during its lifetime if it is moved from one compute node to another.
|
||||||
"""),
|
"""),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -63,6 +63,7 @@ Related options:
|
|||||||
"""),
|
"""),
|
||||||
cfg.ListOpt("enabled_filters",
|
cfg.ListOpt("enabled_filters",
|
||||||
default=[
|
default=[
|
||||||
|
"AvailabilityZoneFilter",
|
||||||
"CPUFilter",
|
"CPUFilter",
|
||||||
"RamFilter",
|
"RamFilter",
|
||||||
"ComputeFilter"
|
"ComputeFilter"
|
||||||
|
47
zun/scheduler/filters/availability_zone_filter.py
Normal file
47
zun/scheduler/filters/availability_zone_filter.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
import zun.conf
|
||||||
|
from zun.scheduler import filters
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
CONF = zun.conf.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class AvailabilityZoneFilter(filters.BaseHostFilter):
|
||||||
|
"""Filters Hosts by availability zone."""
|
||||||
|
|
||||||
|
# Availability zones do not change within a request
|
||||||
|
run_filter_once_per_request = True
|
||||||
|
|
||||||
|
def host_passes(self, host_state, container, extra_spec):
|
||||||
|
availability_zone = extra_spec.get('availability_zone') or \
|
||||||
|
CONF.default_schedule_zone
|
||||||
|
if not availability_zone:
|
||||||
|
return True
|
||||||
|
|
||||||
|
host_az = host_state.service.availability_zone
|
||||||
|
if not host_az:
|
||||||
|
host_az = CONF.default_availability_zone
|
||||||
|
|
||||||
|
hosts_passes = availability_zone == host_az
|
||||||
|
if not hosts_passes:
|
||||||
|
LOG.debug("Availability Zone '%(az)s' requested. "
|
||||||
|
"%(host_state)s has AZs: %(host_az)s",
|
||||||
|
{'host_state': host_state,
|
||||||
|
'az': availability_zone,
|
||||||
|
'host_az': host_az})
|
||||||
|
|
||||||
|
return hosts_passes
|
@ -300,6 +300,37 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
self.assertEqual(1, len(requested_networks))
|
self.assertEqual(1, len(requested_networks))
|
||||||
self.assertEqual(fake_network['id'], requested_networks[0]['network'])
|
self.assertEqual(fake_network['id'], requested_networks[0]['network'])
|
||||||
|
|
||||||
|
@patch('zun.network.neutron.NeutronAPI.get_available_network')
|
||||||
|
@patch('zun.compute.api.API.container_create')
|
||||||
|
@patch('zun.compute.api.API.image_search')
|
||||||
|
def test_create_container_with_availability_zone(
|
||||||
|
self, mock_search, mock_container_create,
|
||||||
|
mock_neutron_get_network):
|
||||||
|
mock_container_create.side_effect = lambda x, y, **z: y
|
||||||
|
fake_network = {'id': 'foo'}
|
||||||
|
mock_neutron_get_network.return_value = fake_network
|
||||||
|
# Create a container with a command
|
||||||
|
params = ('{"name": "MyDocker", "image": "ubuntu",'
|
||||||
|
'"command": "env",'
|
||||||
|
'"availability_zone": "test-az"}')
|
||||||
|
response = self.post('/v1/containers/',
|
||||||
|
params=params,
|
||||||
|
content_type='application/json')
|
||||||
|
self.assertEqual(202, response.status_int)
|
||||||
|
response = self.get('/v1/containers/')
|
||||||
|
self.assertEqual(200, response.status_int)
|
||||||
|
self.assertEqual(2, len(response.json))
|
||||||
|
c = response.json['containers'][0]
|
||||||
|
self.assertIsNotNone(c.get('uuid'))
|
||||||
|
self.assertEqual('MyDocker', c.get('name'))
|
||||||
|
self.assertEqual('env', c.get('command'))
|
||||||
|
self.assertIsNone(c.get('memory'))
|
||||||
|
self.assertEqual({}, c.get('environment'))
|
||||||
|
mock_neutron_get_network.assert_called_once()
|
||||||
|
extra_spec = \
|
||||||
|
mock_container_create.call_args[1]['extra_spec']
|
||||||
|
self.assertEqual('test-az', extra_spec['availability_zone'])
|
||||||
|
|
||||||
@patch('zun.network.neutron.NeutronAPI.get_available_network')
|
@patch('zun.network.neutron.NeutronAPI.get_available_network')
|
||||||
@patch('zun.compute.api.API.container_create')
|
@patch('zun.compute.api.API.container_create')
|
||||||
@patch('zun.compute.api.API.image_search')
|
@patch('zun.compute.api.API.image_search')
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from zun.common import context
|
||||||
|
from zun import objects
|
||||||
|
from zun.scheduler.filters import availability_zone_filter as az_filter
|
||||||
|
from zun.tests import base
|
||||||
|
from zun.tests.unit.scheduler import fakes
|
||||||
|
|
||||||
|
|
||||||
|
class TestAvailabilityZoneFilter(base.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestAvailabilityZoneFilter, self).setUp()
|
||||||
|
self.context = context.RequestContext('fake_user', 'fake_project')
|
||||||
|
|
||||||
|
def test_az_filter(self):
|
||||||
|
self.assertIs(True,
|
||||||
|
self._test_az_filter(request_az='test-az',
|
||||||
|
node_az='test-az'))
|
||||||
|
self.assertIs(False,
|
||||||
|
self._test_az_filter(request_az='test-az',
|
||||||
|
node_az='another-az'))
|
||||||
|
|
||||||
|
def test_az_filter_default_az(self):
|
||||||
|
cfg.CONF.set_override("default_availability_zone", "default-az")
|
||||||
|
self.assertIs(True,
|
||||||
|
self._test_az_filter(request_az='default-az',
|
||||||
|
node_az=None))
|
||||||
|
self.assertIs(False,
|
||||||
|
self._test_az_filter(request_az='another-az',
|
||||||
|
node_az=None))
|
||||||
|
|
||||||
|
def test_az_filter_default_schedule_az(self):
|
||||||
|
cfg.CONF.set_override("default_schedule_zone", "schedule-az")
|
||||||
|
self.assertIs(True,
|
||||||
|
self._test_az_filter(request_az=None,
|
||||||
|
node_az='schedule-az'))
|
||||||
|
self.assertIs(False,
|
||||||
|
self._test_az_filter(request_az=None,
|
||||||
|
node_az='another-az'))
|
||||||
|
|
||||||
|
def test_az_filter_no_az_requested(self):
|
||||||
|
self.assertIs(True,
|
||||||
|
self._test_az_filter(request_az=None,
|
||||||
|
node_az=None))
|
||||||
|
self.assertIs(True,
|
||||||
|
self._test_az_filter(request_az=None,
|
||||||
|
node_az='any-az'))
|
||||||
|
|
||||||
|
def _test_az_filter(self, request_az, node_az):
|
||||||
|
filt_cls = az_filter.AvailabilityZoneFilter()
|
||||||
|
container = objects.Container(self.context)
|
||||||
|
service = objects.ZunService(self.context)
|
||||||
|
service.availability_zone = node_az
|
||||||
|
extra_spec = {}
|
||||||
|
if request_az:
|
||||||
|
extra_spec = {'availability_zone': request_az}
|
||||||
|
host = fakes.FakeHostState('fake-host',
|
||||||
|
{'service': service})
|
||||||
|
return filt_cls.host_passes(host, container, extra_spec)
|
Loading…
Reference in New Issue
Block a user