155 lines
4.8 KiB
Python
155 lines
4.8 KiB
Python
# 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
|