355 lines
12 KiB
Python
355 lines
12 KiB
Python
# 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_context import context as oslo_context
|
|
from oslo_utils import reflection
|
|
from oslo_utils import timeutils
|
|
|
|
from senlin.common import context as senlin_context
|
|
from senlin.common import exception
|
|
from senlin.common.i18n import _
|
|
from senlin.common import schema
|
|
from senlin.common import utils
|
|
from senlin.drivers import base as driver
|
|
from senlin.engine import environment
|
|
from senlin.objects import credential as co
|
|
from senlin.objects import policy as po
|
|
|
|
CHECK_RESULTS = (
|
|
CHECK_OK, CHECK_ERROR,
|
|
) = (
|
|
'OK', 'ERROR',
|
|
)
|
|
|
|
|
|
class Policy(object):
|
|
"""Base class for policies."""
|
|
|
|
VERSIONS = {}
|
|
|
|
PROFILE_TYPE = 'ANY'
|
|
|
|
KEYS = (
|
|
TYPE, VERSION, DESCRIPTION, PROPERTIES,
|
|
) = (
|
|
'type', 'version', 'description', 'properties',
|
|
)
|
|
|
|
spec_schema = {
|
|
TYPE: schema.String(
|
|
_('Name of the policy type.'),
|
|
required=True,
|
|
),
|
|
VERSION: schema.String(
|
|
_('Version number of the policy type.'),
|
|
required=True,
|
|
),
|
|
DESCRIPTION: schema.String(
|
|
_('A text description of policy.'),
|
|
default='',
|
|
),
|
|
PROPERTIES: schema.Map(
|
|
_('Properties for the policy.'),
|
|
required=True,
|
|
)
|
|
}
|
|
|
|
properties_schema = {}
|
|
|
|
def __new__(cls, name, spec, **kwargs):
|
|
"""Create a new policy of the appropriate class.
|
|
|
|
:param name: The name for the policy.
|
|
:param spec: A dictionary containing the spec for the policy.
|
|
:param kwargs: Keyword arguments for policy creation.
|
|
:returns: An instance of a specific sub-class of Policy.
|
|
"""
|
|
type_name, version = schema.get_spec_version(spec)
|
|
type_str = "-".join([type_name, version])
|
|
|
|
if cls != Policy:
|
|
PolicyClass = cls
|
|
else:
|
|
PolicyClass = environment.global_env().get_policy(type_str)
|
|
|
|
return super(Policy, cls).__new__(PolicyClass)
|
|
|
|
def __init__(self, name, spec, **kwargs):
|
|
"""Initialize a policy instance.
|
|
|
|
:param name: The name for the policy.
|
|
:param spec: A dictionary containing the detailed policy spec.
|
|
:param kwargs: Keyword arguments for initializing the policy.
|
|
:returns: An instance of a specific sub-class of Policy.
|
|
"""
|
|
|
|
type_name, version = schema.get_spec_version(spec)
|
|
type_str = "-".join([type_name, version])
|
|
self.name = name
|
|
self.spec = spec
|
|
|
|
self.id = kwargs.get('id', None)
|
|
self.type = kwargs.get('type', type_str)
|
|
self.user = kwargs.get('user')
|
|
self.project = kwargs.get('project')
|
|
self.domain = kwargs.get('domain')
|
|
self.data = kwargs.get('data', {})
|
|
|
|
self.created_at = kwargs.get('created_at', None)
|
|
self.updated_at = kwargs.get('updated_at', None)
|
|
|
|
self.spec_data = schema.Spec(self.spec_schema, spec)
|
|
self.properties = schema.Spec(
|
|
self.properties_schema,
|
|
self.spec.get(self.PROPERTIES, {}),
|
|
version)
|
|
|
|
self.singleton = True
|
|
self._novaclient = None
|
|
self._keystoneclient = None
|
|
self._networkclient = None
|
|
self._lbaasclient = None
|
|
|
|
@classmethod
|
|
def _from_object(cls, policy):
|
|
"""Construct a policy from a Policy object.
|
|
|
|
@param cls: The target class.
|
|
@param policy: A policy object.
|
|
"""
|
|
|
|
kwargs = {
|
|
'id': policy.id,
|
|
'type': policy.type,
|
|
'user': policy.user,
|
|
'project': policy.project,
|
|
'domain': policy.domain,
|
|
'created_at': policy.created_at,
|
|
'updated_at': policy.updated_at,
|
|
'data': policy.data,
|
|
}
|
|
|
|
return cls(policy.name, policy.spec, **kwargs)
|
|
|
|
@classmethod
|
|
def load(cls, context, policy_id=None, db_policy=None, project_safe=True):
|
|
"""Retrieve and reconstruct a policy object from DB.
|
|
|
|
:param context: DB context for object retrieval.
|
|
:param policy_id: Optional parameter specifying the ID of policy.
|
|
:param db_policy: Optional parameter referencing a policy DB object.
|
|
:param project_safe: Optional parameter specifying whether only
|
|
policies belong to the context.project will be
|
|
loaded.
|
|
:returns: An object of the proper policy class.
|
|
"""
|
|
if db_policy is None:
|
|
db_policy = po.Policy.get(context, policy_id,
|
|
project_safe=project_safe)
|
|
if db_policy is None:
|
|
raise exception.ResourceNotFound(type='policy', id=policy_id)
|
|
|
|
return cls._from_object(db_policy)
|
|
|
|
@classmethod
|
|
def delete(cls, context, policy_id):
|
|
po.Policy.delete(context, policy_id)
|
|
|
|
def store(self, context):
|
|
'''Store the policy object into database table.'''
|
|
timestamp = timeutils.utcnow(True)
|
|
|
|
values = {
|
|
'name': self.name,
|
|
'type': self.type,
|
|
'user': self.user,
|
|
'project': self.project,
|
|
'domain': self.domain,
|
|
'spec': self.spec,
|
|
'data': self.data,
|
|
}
|
|
|
|
if self.id is not None:
|
|
self.updated_at = timestamp
|
|
values['updated_at'] = timestamp
|
|
po.Policy.update(context, self.id, values)
|
|
else:
|
|
self.created_at = timestamp
|
|
values['created_at'] = timestamp
|
|
policy = po.Policy.create(context, values)
|
|
self.id = policy.id
|
|
|
|
return self.id
|
|
|
|
def validate(self, context, validate_props=False):
|
|
'''Validate the schema and the data provided.'''
|
|
self.spec_data.validate()
|
|
self.properties.validate()
|
|
|
|
@classmethod
|
|
def get_schema(cls):
|
|
return dict((name, dict(schema))
|
|
for name, schema in cls.properties_schema.items())
|
|
|
|
def _build_policy_data(self, data):
|
|
clsname = reflection.get_class_name(self, fully_qualified=False)
|
|
version = self.VERSION
|
|
result = {
|
|
clsname: {
|
|
'version': version,
|
|
'data': data,
|
|
}
|
|
}
|
|
return result
|
|
|
|
def _extract_policy_data(self, policy_data):
|
|
clsname = reflection.get_class_name(self, fully_qualified=False)
|
|
if clsname not in policy_data:
|
|
return None
|
|
data = policy_data.get(clsname)
|
|
if 'version' not in data or data['version'] != self.VERSION:
|
|
return None
|
|
|
|
return data.get('data', None)
|
|
|
|
def _build_conn_params(self, user, project):
|
|
"""Build trust-based connection parameters.
|
|
|
|
:param user: the user for which the trust will be checked.
|
|
:param object: the user for which the trust will be checked.
|
|
"""
|
|
service_creds = senlin_context.get_service_credentials()
|
|
params = {
|
|
'username': service_creds.get('username'),
|
|
'password': service_creds.get('password'),
|
|
'auth_url': service_creds.get('auth_url'),
|
|
'user_domain_name': service_creds.get('user_domain_name')
|
|
}
|
|
|
|
cred = co.Credential.get(oslo_context.get_current(), user, project)
|
|
if cred is None:
|
|
raise exception.TrustNotFound(trustor=user)
|
|
params['trust_id'] = cred.cred['openstack']['trust']
|
|
|
|
return params
|
|
|
|
def keystone(self, user, project):
|
|
"""Construct keystone client based on object.
|
|
|
|
:param user: The ID of the requesting user.
|
|
:param project: The ID of the requesting project.
|
|
:returns: A reference to the keystone client.
|
|
"""
|
|
if self._keystoneclient is not None:
|
|
return self._keystoneclient
|
|
params = self._build_conn_params(user, project)
|
|
self._keystoneclient = driver.SenlinDriver().identity(params)
|
|
return self._keystoneclient
|
|
|
|
def nova(self, user, project):
|
|
"""Construct nova client based on user and project.
|
|
|
|
:param user: The ID of the requesting user.
|
|
:param project: The ID of the requesting project.
|
|
:returns: A reference to the nova client.
|
|
"""
|
|
if self._novaclient is not None:
|
|
return self._novaclient
|
|
|
|
params = self._build_conn_params(user, project)
|
|
self._novaclient = driver.SenlinDriver().compute(params)
|
|
return self._novaclient
|
|
|
|
def network(self, user, project):
|
|
"""Construct network client based on user and project.
|
|
|
|
:param user: The ID of the requesting user.
|
|
:param project: The ID of the requesting project.
|
|
:returns: A reference to the network client.
|
|
"""
|
|
if self._networkclient is not None:
|
|
return self._networkclient
|
|
|
|
params = self._build_conn_params(user, project)
|
|
self._networkclient = driver.SenlinDriver().network(params)
|
|
return self._networkclient
|
|
|
|
def lbaas(self, user, project):
|
|
"""Construct LB service client based on user and project.
|
|
|
|
:param user: The ID of the requesting user.
|
|
:param project: The ID of the requesting project.
|
|
:returns: A reference to the LB service client.
|
|
"""
|
|
if self._lbaasclient is not None:
|
|
return self._lbaasclient
|
|
|
|
params = self._build_conn_params(user, project)
|
|
|
|
self._lbaasclient = driver.SenlinDriver().loadbalancing(params)
|
|
return self._lbaasclient
|
|
|
|
def attach(self, cluster, enabled=True):
|
|
'''Method to be invoked before policy is attached to a cluster.
|
|
|
|
:param cluster: The cluster to which the policy is being attached to.
|
|
:param enabled: The attached cluster policy is enabled or disabled.
|
|
:returns: (True, message) if the operation is successful, or (False,
|
|
error) otherwise.
|
|
'''
|
|
if self.PROFILE_TYPE == ['ANY']:
|
|
return True, None
|
|
|
|
profile = cluster.rt['profile']
|
|
if profile.type not in self.PROFILE_TYPE:
|
|
error = _('Policy not applicable on profile type: '
|
|
'%s') % profile.type
|
|
return False, error
|
|
|
|
return True, None
|
|
|
|
def detach(self, cluster):
|
|
'''Method to be invoked before policy is detached from a cluster.'''
|
|
return True, None
|
|
|
|
def need_check(self, target, action):
|
|
if getattr(self, 'TARGET', None) is None:
|
|
return True
|
|
|
|
if (target, action.action) in self.TARGET:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def pre_op(self, cluster_id, action):
|
|
'''A method that will be invoked before an action execution.'''
|
|
return
|
|
|
|
def post_op(self, cluster_id, action):
|
|
'''A method that will be invoked after an action execution.'''
|
|
return
|
|
|
|
def to_dict(self):
|
|
pb_dict = {
|
|
'id': self.id,
|
|
'name': self.name,
|
|
'type': self.type,
|
|
'user': self.user,
|
|
'project': self.project,
|
|
'domain': self.domain,
|
|
'spec': self.spec,
|
|
'created_at': utils.isotime(self.created_at),
|
|
'updated_at': utils.isotime(self.updated_at),
|
|
'data': self.data,
|
|
}
|
|
return pb_dict
|