summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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}}