# Copyright 2015 Symantec. # # 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. """ Congress Policy Middleware. """ import json from nova.i18n import _ from nova import wsgi from oslo_config import cfg from oslo_log import log as logging import webob.dec import webob.exc # policy enforcement flow from congressclient.v1 import client import keystoneclient from keystoneclient.v3 import client as ksv3client from novaclient import client as nova LOG = logging.getLogger(__name__) class Congress(wsgi.Middleware): """Make a request context from keystone headers.""" @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): if req.environ['REQUEST_METHOD'] != 'POST': return self.application raw_path = req.environ['RAW_PATH_INFO'] if "metadata" in raw_path: return self.application if "servers/action" in raw_path: return self.application flavor_ref = json.loads(req.body)['server']['flavorRef'] token = req.environ['HTTP_X_AUTH_TOKEN'] tenant_name = req.environ['HTTP_X_TENANT_NAME'] CONF = cfg.CONF # obtain identity endpoint url url = CONF.keystone_authtoken.auth_url # obtain one of support keystone api versions raw_versions = keystoneclient.discover.available_versions(url, session=None) version = raw_versions[-1]['id'] # assemble auth_url auth_url = url + '/' + version auth = keystoneclient.auth.identity.v2.Token( auth_url=auth_url, token=token, tenant_name=tenant_name) session = keystoneclient.session.Session(auth=auth) congress = client.Client(session=session, auth=None, interface='publicURL', service_type='policy') # Aggregating resource usage within domain level domain = req.environ['HTTP_X_PROJECT_DOMAIN_NAME'] # obtain list of projects under this domain k3_client = ksv3client.Client(session=session) projects = k3_client.projects.list(domain=domain) # obtain list of hosts under each of these projects nova_c = nova.Client("2", session=session) ram_p = 0 disk_p = 0 cpus_p = 0 for project in projects: search_opts = { 'all_tenants': 1, 'tenant_id': project.id, } servers_p = nova_c.servers.list(search_opts=search_opts) # locate flavor of each host for server in servers_p: info = nova_c.servers.get(server=server) flavor_id = info._info['flavor']['id'] fd = nova_c.flavors.get(flavor=flavor_id) ram_p += fd.ram disk_p += fd.disk disk_p += fd.ephemeral cpus_p += fd.vcpus # incrementally add each type of resource # assemble query policy based on the data-usage # with memory_p, disk_p and cpus_p fd = nova_c.flavors.get(flavor=flavor_ref) ram_p += fd.ram disk_p += fd.disk disk_p += fd.ephemeral cpus_p += fd.vcpus domain_resource = ("(" + domain + "," + str(ram_p) + "," + str(disk_p) + "," + str(cpus_p) + ")") validation_result = congress.execute_policy_action( "classification", "simulate", False, True, {'query': 'domain_resource_usage_exceeded (domain)', # this needs to be defined in congress server 'action_policy': 'nova_quota_action', 'sequence': 'domain_resource+'+domain_resource}) if validation_result["result"]: messages = validation_result["result"] if messages: result_str = "\n ".join(map(str, messages)) msg = _( "quota is not sufficient for this VM deployment").format( "\n " + result_str) LOG.error(msg) LOG.debug(messages) return webob.exc.HTTPUnauthorized(explanation=msg) else: LOG.info('Model valid') return self.application