text: # 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._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')
          }
  
          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
  