senlin/senlin/policies/base.py

374 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, CHECK_NONE
) = (
'OK', 'ERROR', 'NONE'
)
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._octaviaclient = 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 project: 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'),
'project_domain_name': service_creds.get('project_domain_name'),
'verify': service_creds.get('verify'),
'interface': service_creds.get('interface'),
}
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 octavia(self, user, project):
"""Construct octavia 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 octavia client.
"""
if self._octaviaclient is not None:
return self._octaviaclient
params = self._build_conn_params(user, project)
self._octaviaclient = driver.SenlinDriver().octavia(params)
return self._octaviaclient
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