summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorYunhong, Jiang <yunhong.jiang@intel.com>2012-09-10 16:20:15 +0800
committerYunhong, Jiang <yunhong.jiang@intel.com>2013-01-22 17:49:43 +0800
commitfab8af583bf6c363d2cebbc360ae2709325d80bd (patch)
treeb11e5b385131407fc6d961cb1186f5841d1e8b27 /nova
parenta4d608fa33b328d7ed77c7f9c40ffbb43c0ade6b (diff)
downloadnova-fab8af583bf6c363d2cebbc360ae2709325d80bd.tar.gz
nova-fab8af583bf6c363d2cebbc360ae2709325d80bd.tar.xz
nova-fab8af583bf6c363d2cebbc360ae2709325d80bd.zip
Add trust level cache to trusted_filter
Currently each time the trusted filter is called to check host_pass, it polls Open Attestation Service to get the trusted level for the host. This solution is not good on scalability. With a cache for the host trust level, trusted filter don't need to consult OAT service if the cache is still valid, thus improves the scalability. The cache is setup when the first time the filter consulting the OAT service. The trusted_filters are reinitialized for each VM that is scheduled, thus the cache is valid only for one VM instance scheduing. Implements blueprint trusted-filter-cache Change-Id: I613da53876e6cd548566e27ab1693287023e9861 Signed-off-by: Yunhong, Jiang <yunhong.jiang@intel.com>
Diffstat (limited to 'nova')
-rw-r--r--nova/scheduler/filters/trusted_filter.py141
-rw-r--r--nova/tests/scheduler/test_host_filters.py98
2 files changed, 200 insertions, 39 deletions
diff --git a/nova/scheduler/filters/trusted_filter.py b/nova/scheduler/filters/trusted_filter.py
index 4d0f2305f..302d2b3a8 100644
--- a/nova/scheduler/filters/trusted_filter.py
+++ b/nova/scheduler/filters/trusted_filter.py
@@ -48,9 +48,12 @@ import httplib
import socket
import ssl
+from nova import context
+from nova import db
from nova.openstack.common import cfg
from nova.openstack.common import jsonutils
from nova.openstack.common import log as logging
+from nova.openstack.common import timeutils
from nova.scheduler import filters
@@ -78,6 +81,9 @@ trusted_opts = [
deprecated_name='auth_blob',
default=None,
help='attestation authorization blob - must change'),
+ cfg.IntOpt('attestation_auth_timeout',
+ default=60,
+ help='Attestation status cache valid period length'),
]
CONF = cfg.CONF
@@ -119,7 +125,7 @@ class HTTPSClientAuthConnection(httplib.HTTPSConnection):
cert_reqs=ssl.CERT_REQUIRED)
-class AttestationService(httplib.HTTPSConnection):
+class AttestationService(object):
# Provide access wrapper to attestation server to get integrity report.
def __init__(self):
@@ -156,10 +162,10 @@ class AttestationService(httplib.HTTPSConnection):
except (socket.error, IOError) as e:
return IOError, None
- def _request(self, cmd, subcmd, host):
+ def _request(self, cmd, subcmd, hosts):
body = {}
- body['count'] = 1
- body['hosts'] = host
+ body['count'] = len(hosts)
+ body['hosts'] = hosts
cooked = jsonutils.dumps(body)
headers = {}
headers['content-type'] = 'application/json'
@@ -173,39 +179,124 @@ class AttestationService(httplib.HTTPSConnection):
else:
return status, None
- def _check_trust(self, data, host):
- for item in data:
- for state in item['hosts']:
- if state['host_name'] == host:
- return state['trust_lvl']
- return ""
+ def do_attestation(self, hosts):
+ """Attests compute nodes through OAT service.
- def do_attestation(self, host):
- state = []
- status, data = self._request("POST", "PollHosts", host)
- if status != httplib.OK:
- return {}
- state.append(data)
- return self._check_trust(state, host)
+ :param hosts: hosts list to be attested
+ :returns: dictionary for trust level and validate time
+ """
+ result = None
+ status, data = self._request("POST", "PollHosts", hosts)
+ if data != None:
+ result = data.get('hosts')
-class TrustedFilter(filters.BaseHostFilter):
- """Trusted filter to support Trusted Compute Pools."""
+ return result
+
+
+class ComputeAttestationCache(object):
+ """Cache for compute node attestation
+
+ Cache compute node's trust level for sometime,
+ if the cache is out of date, poll OAT service to flush the
+ cache.
+
+ OAT service may have cache also. OAT service's cache valid time
+ should be set shorter than trusted filter's cache valid time.
+ """
def __init__(self):
- self.attestation_service = AttestationService()
+ self.attestservice = AttestationService()
+ self.compute_nodes = {}
+ admin = context.get_admin_context()
+
+ # Fetch compute node list to initialize the compute_nodes,
+ # so that we don't need poll OAT service one by one for each
+ # host in the first round that scheduler invokes us.
+ computes = db.compute_node_get_all(admin)
+ for compute in computes:
+ service = compute['service']
+ if not service:
+ LOG.warn(_("No service for compute ID %s") % compute['id'])
+ continue
+ host = service['host']
+ self._init_cache_entry(host)
+
+ def _cache_valid(self, host):
+ cachevalid = False
+ if host in self.compute_nodes:
+ node_stats = self.compute_nodes.get(host)
+ if not timeutils.is_older_than(
+ node_stats['vtime'],
+ CONF.trusted_computing.attestation_auth_timeout):
+ cachevalid = True
+ return cachevalid
+
+ def _init_cache_entry(self, host):
+ self.compute_nodes[host] = {
+ 'trust_lvl': 'unknown',
+ 'vtime': timeutils.normalize_time(
+ timeutils.parse_isotime("1970-01-01T00:00:00Z"))}
+
+ def _invalidate_caches(self):
+ for host in self.compute_nodes:
+ self._init_cache_entry(host)
+
+ def _update_cache_entry(self, state):
+ entry = {}
+
+ host = state['host_name']
+ entry['trust_lvl'] = state['trust_lvl']
- def _is_trusted(self, host, trust):
- level = self.attestation_service.do_attestation(host)
- LOG.debug(_("TCP: trust state of "
- "%(host)s:%(level)s(%(trust)s)") % locals())
+ try:
+ # Normalize as naive object to interoperate with utcnow().
+ entry['vtime'] = timeutils.normalize_time(
+ timeutils.parse_isotime(state['vtime']))
+ except ValueError:
+ # Mark the system as un-trusted if get invalid vtime.
+ entry['trust_lvl'] = 'unknown'
+ entry['vtime'] = timeutils.utcnow()
+
+ self.compute_nodes[host] = entry
+
+ def _update_cache(self):
+ self._invalidate_caches()
+ states = self.attestservice.do_attestation(self.compute_nodes.keys())
+ if states is None:
+ return
+ for state in states:
+ self._update_cache_entry(state)
+
+ def get_host_attestation(self, host):
+ """Check host's trust level."""
+ if not host in self.compute_nodes:
+ self._init_cache_entry(host)
+ if not self._cache_valid(host):
+ self._update_cache()
+ level = self.compute_nodes.get(host).get('trust_lvl')
+ return level
+
+
+class ComputeAttestation(object):
+ def __init__(self):
+ self.caches = ComputeAttestationCache()
+
+ def is_trusted(self, host, trust):
+ level = self.caches.get_host_attestation(host)
return trust == level
+
+class TrustedFilter(filters.BaseHostFilter):
+ """Trusted filter to support Trusted Compute Pools."""
+
+ def __init__(self):
+ self.compute_attestation = ComputeAttestation()
+
def host_passes(self, host_state, filter_properties):
instance = filter_properties.get('instance_type', {})
extra = instance.get('extra_specs', {})
trust = extra.get('trust:trusted_host')
host = host_state.host
if trust:
- return self._is_trusted(host, trust)
+ return self.compute_attestation.is_trusted(host, trust)
return True
diff --git a/nova/tests/scheduler/test_host_filters.py b/nova/tests/scheduler/test_host_filters.py
index 9f7f189cc..f8b9f9296 100644
--- a/nova/tests/scheduler/test_host_filters.py
+++ b/nova/tests/scheduler/test_host_filters.py
@@ -22,6 +22,7 @@ from nova import context
from nova import db
from nova.openstack.common import cfg
from nova.openstack.common import jsonutils
+from nova.openstack.common import timeutils
from nova.scheduler import filters
from nova.scheduler.filters import extra_specs_ops
from nova.scheduler.filters.trusted_filter import AttestationService
@@ -233,11 +234,13 @@ class HostFiltersTestCase(test.TestCase):
def fake_oat_request(self, *args, **kwargs):
"""Stubs out the response from OAT service."""
- return httplib.OK, jsonutils.loads(self.oat_data)
+ self.oat_attested = True
+ return httplib.OK, self.oat_data
def setUp(self):
super(HostFiltersTestCase, self).setUp()
self.oat_data = ''
+ self.oat_attested = False
self.stubs = stubout.StubOutForTesting()
self.stubs.Set(AttestationService, '_request', self.fake_oat_request)
self.context = context.RequestContext('fake', 'fake')
@@ -1147,54 +1150,121 @@ class HostFiltersTestCase(test.TestCase):
def test_trusted_filter_default_passes(self):
self._stub_service_is_up(True)
filt_cls = self.class_map['TrustedFilter']()
- filter_properties = {'instance_type': {'memory_mb': 1024}}
+ filter_properties = {'context': self.context.elevated(),
+ 'instance_type': {'memory_mb': 1024}}
host = fakes.FakeHostState('host1', 'node1', {})
self.assertTrue(filt_cls.host_passes(host, filter_properties))
def test_trusted_filter_trusted_and_trusted_passes(self):
- self.oat_data =\
- '{"hosts":[{"host_name":"host1","trust_lvl":"trusted"}]}'
+ self.oat_data = {"hosts": [{"host_name": "host1",
+ "trust_lvl": "trusted",
+ "vtime": timeutils.isotime()}]}
self._stub_service_is_up(True)
filt_cls = self.class_map['TrustedFilter']()
extra_specs = {'trust:trusted_host': 'trusted'}
- filter_properties = {'instance_type': {'memory_mb': 1024,
+ filter_properties = {'context': self.context.elevated(),
+ 'instance_type': {'memory_mb': 1024,
'extra_specs': extra_specs}}
host = fakes.FakeHostState('host1', 'node1', {})
self.assertTrue(filt_cls.host_passes(host, filter_properties))
def test_trusted_filter_trusted_and_untrusted_fails(self):
- self.oat_data =\
- '{"hosts":[{"host_name":"host1","trust_lvl":"untrusted"}]}'
+ self.oat_data = {"hosts": [{"host_name": "host1",
+ "trust_lvl": "untrusted",
+ "vtime": timeutils.isotime()}]}
self._stub_service_is_up(True)
filt_cls = self.class_map['TrustedFilter']()
extra_specs = {'trust:trusted_host': 'trusted'}
- filter_properties = {'instance_type': {'memory_mb': 1024,
+ filter_properties = {'context': self.context.elevated(),
+ 'instance_type': {'memory_mb': 1024,
'extra_specs': extra_specs}}
host = fakes.FakeHostState('host1', 'node1', {})
self.assertFalse(filt_cls.host_passes(host, filter_properties))
def test_trusted_filter_untrusted_and_trusted_fails(self):
- self.oat_data =\
- '{"hosts":[{"host_name":"host1","trust_lvl":"trusted"}]}'
+ self.oat_data = {"hosts": [{"host_name": "host1",
+ "trust_lvl": "trusted",
+ "vtime": timeutils.isotime()}]}
self._stub_service_is_up(True)
filt_cls = self.class_map['TrustedFilter']()
extra_specs = {'trust:trusted_host': 'untrusted'}
- filter_properties = {'instance_type': {'memory_mb': 1024,
+ filter_properties = {'context': self.context.elevated(),
+ 'instance_type': {'memory_mb': 1024,
'extra_specs': extra_specs}}
host = fakes.FakeHostState('host1', 'node1', {})
self.assertFalse(filt_cls.host_passes(host, filter_properties))
def test_trusted_filter_untrusted_and_untrusted_passes(self):
- self.oat_data =\
- '{"hosts":[{"host_name":"host1","trust_lvl":"untrusted"}]}'
+ self.oat_data = {"hosts": [{"host_name": "host1",
+ "trust_lvl": "untrusted",
+ "vtime":timeutils.isotime()}]}
self._stub_service_is_up(True)
filt_cls = self.class_map['TrustedFilter']()
extra_specs = {'trust:trusted_host': 'untrusted'}
- filter_properties = {'instance_type': {'memory_mb': 1024,
+ filter_properties = {'context': self.context.elevated(),
+ 'instance_type': {'memory_mb': 1024,
'extra_specs': extra_specs}}
host = fakes.FakeHostState('host1', 'node1', {})
self.assertTrue(filt_cls.host_passes(host, filter_properties))
+ def test_trusted_filter_update_cache(self):
+ self.oat_data = {"hosts": [{"host_name":
+ "host1", "trust_lvl": "untrusted",
+ "vtime": timeutils.isotime()}]}
+
+ filt_cls = self.class_map['TrustedFilter']()
+ extra_specs = {'trust:trusted_host': 'untrusted'}
+ filter_properties = {'context': self.context.elevated(),
+ 'instance_type': {'memory_mb': 1024,
+ 'extra_specs': extra_specs}}
+ host = fakes.FakeHostState('host1', 'node1', {})
+
+ filt_cls.host_passes(host, filter_properties) # Fill the caches
+
+ self.oat_attested = False
+ filt_cls.host_passes(host, filter_properties)
+ self.assertFalse(self.oat_attested)
+
+ self.oat_attested = False
+
+ timeutils.set_time_override(timeutils.utcnow())
+ timeutils.advance_time_seconds(
+ CONF.trusted_computing.attestation_auth_timeout + 80)
+ filt_cls.host_passes(host, filter_properties)
+ self.assertTrue(self.oat_attested)
+
+ timeutils.clear_time_override()
+
+ def test_trusted_filter_update_cache_timezone(self):
+ self.oat_data = {"hosts": [{"host_name": "host1",
+ "trust_lvl": "untrusted",
+ "vtime": "2012-09-09T05:10:40-04:00"}]}
+
+ filt_cls = self.class_map['TrustedFilter']()
+ extra_specs = {'trust:trusted_host': 'untrusted'}
+ filter_properties = {'context': self.context.elevated(),
+ 'instance_type': {'memory_mb': 1024,
+ 'extra_specs': extra_specs}}
+ host = fakes.FakeHostState('host1', 'node1', {})
+
+ timeutils.set_time_override(
+ timeutils.normalize_time(
+ timeutils.parse_isotime("2012-09-09T09:10:40Z")))
+
+ filt_cls.host_passes(host, filter_properties) # Fill the caches
+
+ self.oat_attested = False
+ filt_cls.host_passes(host, filter_properties)
+ self.assertFalse(self.oat_attested)
+
+ self.oat_attested = False
+ timeutils.advance_time_seconds(
+ CONF.trusted_computing.attestation_auth_timeout - 10)
+ filt_cls.host_passes(host, filter_properties)
+ self.assertFalse(self.oat_attested)
+
+ timeutils.clear_time_override()
+
def test_core_filter_passes(self):
filt_cls = self.class_map['CoreFilter']()
filter_properties = {'instance_type': {'vcpus': 1}}