Add ports scheduler filter

This filter will check whether the node ports satisfy the
specified networks requirements including ports quantity
and types.

Change-Id: I793a3a78d6c59ae4eb57dc6bdd49af4eef545820
This commit is contained in:
Zhenguo Niu 2017-01-20 15:18:46 +08:00
parent c0124c5421
commit 0a0ffe2c4e
9 changed files with 101 additions and 7 deletions

View File

@ -35,7 +35,8 @@ opts = [
default=[
'AvailabilityZoneFilter',
'InstanceTypeFilter',
'CapabilitiesFilter'
'CapabilitiesFilter',
'PortsFilter'
],
help=_('Which filter class names to use for filtering nodes '
'when not specified in the request.')),

View File

@ -87,6 +87,7 @@ class API(object):
'instance_properties': {
'availability_zone': instance.availability_zone,
'instance_type_uuid': instance.instance_type_uuid,
'networks': requested_networks,
},
'instance_type': dict(instance_type),
}

View File

@ -144,6 +144,22 @@ def get_node_list(ironicclient, **kwargs):
return node_list
def get_port_list(ironicclient, **kwargs):
"""Helper function to return the list of ports.
If unable to connect ironic server, an empty list is returned.
:returns: a list of raw port from ironic
"""
try:
port_list = ironicclient.call("port.list", **kwargs)
except client_e.ClientException as e:
LOG.exception(_LE("Could not get ports from ironic. Reason: "
"%(detail)s"), {'detail': e.message})
port_list = []
return port_list
def set_power_state(ironicclient, node_uuid, state):
ironicclient.call("node.set_power_state", node_uuid, state)
# Do we need to catch NotFound exception.

View File

@ -61,7 +61,13 @@ class EngineManager(base_manager.BaseEngineManager):
maintenance=False,
provision_state=ironic_states.AVAILABLE,
associated=False, limit=0)
ports = ironic.get_port_list(self.ironicclient, limit=0,
fields=('uuid', 'node_uuid', 'extra',
'address'))
for node in nodes:
# Add ports to the associated node
node.ports = [port for port in ports
if node.uuid == port.node_uuid]
node_cache[node.uuid] = node
with self._lock:

View File

@ -0,0 +1,64 @@
# Copyright 2016 Huawei Technologies Co.,LTD.
# All Rights Reserved.
#
# 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
from mogan.engine.scheduler import filters
LOG = logging.getLogger(__name__)
class PortsFilter(filters.BaseNodeFilter):
"""NodeFilter to work with resource instance type records."""
def _find_port_type(self, ports, port_type):
"""Check if ports has the specified port type."""
for port in ports:
if port_type == port.extra.get('port_type'):
return True
return False
def _satisfies_networks(self, ports, networks):
"""Check if ports satisfy networks requirements.
Check that the ports provided by the nodes satisfy
the networks associated with the request spec.
"""
if not networks:
return True
if len(ports) < len(networks):
return False
for net in networks:
if 'port_type' in net:
if not self._find_port_type(ports, net.get('port_type')):
return False
return True
def node_passes(self, node_state, filter_properties):
"""Return a list of nodes that can create resource_type."""
spec = filter_properties.get('request_spec', {})
props = spec.get('instance_properties', {})
networks = props.get('networks')
if not self._satisfies_networks(node_state.ports, networks):
LOG.debug("%(node_state)s fails network ports "
"requirements", {'node_state': node_state})
return False
return True

View File

@ -38,6 +38,7 @@ class NodeState(object):
self.availability_zone = node.properties.get('availability_zone') \
or CONF.engine.default_schedule_zone
self.instance_type = node.properties.get('instance_type')
self.ports = node.ports
class NodeManager(object):

View File

@ -18,7 +18,7 @@ Fakes For Scheduler tests.
"""
from oslo_versionedobjects import base as object_base
from oslo_versionedobjects import fields
from mogan.engine.scheduler import filter_scheduler
from mogan.engine.scheduler import node_manager
@ -38,21 +38,25 @@ class FakeNode(base.MoganObject, object_base.VersionedObjectDictCompat):
'id': object_fields.IntegerField(),
'uuid': object_fields.UUIDField(nullable=True),
'properties': object_fields.FlexibleDictField(nullable=True),
'ports': fields.ListOfDictOfNullableStringsField(nullable=True),
}
fakenode1 = FakeNode(id=1, uuid='1a617131-cdbc-45dc-afff-f21f17ae054e',
properties={'capabilities': '',
'availability_zone': 'az1',
'instance_type': 'type1'})
'instance_type': 'type1'},
ports=[])
fakenode2 = FakeNode(id=2, uuid='2a617131-cdbc-45dc-afff-f21f17ae054e',
properties={'capabilities': '',
'availability_zone': 'az2',
'instance_type': 'type2'})
'instance_type': 'type2'},
ports=[])
fakenode3 = FakeNode(id=3, uuid='3a617131-cdbc-45dc-afff-f21f17ae054e',
properties={'capabilities': '',
'availability_zone': 'az3',
'instance_type': 'type3'})
'instance_type': 'type3'},
ports=[])
class FakeNodeState(node_manager.NodeState):

View File

@ -385,7 +385,7 @@ expected_object_fingerprints = {
'Instance': '1.0-c3a73e3ec189aa09dc430b389c81b11f',
'InstanceType': '1.0-589b096651fcdb30898ff50f748dd948',
'MyObj': '1.1-aad62eedc5a5cc8bcaf2982c285e753f',
'FakeNode': '1.0-295d1b08ce3048535926c47dedd27211',
'FakeNode': '1.0-07813a70fee67557d8a71ad96f31cee7',
}

View File

@ -29,6 +29,7 @@ mogan.engine.scheduler.filters =
AvailabilityZoneFilter = mogan.engine.scheduler.filters.availability_zone_filter:AvailabilityZoneFilter
InstanceTypeFilter = mogan.engine.scheduler.filters.instance_type_filter:InstanceTypeFilter
CapabilitiesFilter = mogan.engine.scheduler.filters.capabilities_filter:CapabilitiesFilter
PortsFilter = mogan.engine.scheduler.filters.ports_filter:PortsFilter
JsonFilter = mogan.engine.scheduler.filters.json_filter:JsonFilter
oslo.config.opts =