# -*- coding: utf-8 -*- # Copyright 2014 Objectif Libre # # 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. # import decimal from oslo_db.sqlalchemy import utils import sqlalchemy from cloudkitty import db from cloudkitty.storage import NoTimeFrame from cloudkitty.storage import v1 as storage from cloudkitty.storage.v1.sqlalchemy import migration from cloudkitty.storage.v1.sqlalchemy import models from cloudkitty import utils as ck_utils from cloudkitty.utils import json class SQLAlchemyStorage(storage.BaseStorage): """SQLAlchemy Storage Backend """ frame_model = models.RatedDataFrame def __init__(self, **kwargs): super(SQLAlchemyStorage, self).__init__(**kwargs) self._session = {} @staticmethod def init(): migration.upgrade('head') def _pre_commit(self, tenant_id): self._check_session(tenant_id) if not self._has_data.get(tenant_id): empty_frame = {'vol': {'qty': 0, 'unit': 'None'}, 'rating': {'price': 0}, 'desc': ''} self._append_time_frame('_NO_DATA_', empty_frame, tenant_id) def _commit(self, tenant_id): self._session[tenant_id].commit() def _post_commit(self, tenant_id): super(SQLAlchemyStorage, self)._post_commit(tenant_id) del self._session[tenant_id] def _check_session(self, tenant_id): session = self._session.get(tenant_id) if not session: self._session[tenant_id] = db.get_session() self._session[tenant_id].begin() def _dispatch(self, data, tenant_id): self._check_session(tenant_id) for service in data: for frame in data[service]: self._append_time_frame(service, frame, tenant_id) self._has_data[tenant_id] = True def get_state(self, tenant_id=None): session = db.get_session() q = utils.model_query( self.frame_model, session) if tenant_id: q = q.filter( self.frame_model.tenant_id == tenant_id) q = q.order_by( self.frame_model.begin.desc()) r = q.first() if r: return r.begin def get_total(self, begin=None, end=None, tenant_id=None, service=None, groupby=None): session = db.get_session() querymodels = [ sqlalchemy.func.sum(self.frame_model.rate).label('rate') ] if not begin: begin = ck_utils.get_month_start_timestamp() if not end: end = ck_utils.get_next_month_timestamp() # Boundary calculation if tenant_id: querymodels.append(self.frame_model.tenant_id) if service: querymodels.append(self.frame_model.res_type) if groupby: groupbyfields = groupby.split(",") for field in groupbyfields: field_obj = self.frame_model.__dict__.get(field, None) if field_obj and field_obj not in querymodels: querymodels.append(field_obj) q = session.query(*querymodels) if tenant_id: q = q.filter( self.frame_model.tenant_id == tenant_id) if service: q = q.filter( self.frame_model.res_type == service) q = q.filter( self.frame_model.begin >= begin, self.frame_model.end <= end, self.frame_model.res_type != '_NO_DATA_') if groupby: q = q.group_by(sqlalchemy.sql.text(groupby)) # Order by sum(rate) q = q.order_by(sqlalchemy.func.sum(self.frame_model.rate)) results = q.all() totallist = [] for r in results: total = {model.name: value for model, value in zip(querymodels, r)} total["begin"] = begin total["end"] = end totallist.append(total) return totallist def get_tenants(self, begin, end): session = db.get_session() q = utils.model_query( self.frame_model, session) q = q.filter( self.frame_model.begin >= begin, self.frame_model.end <= end) tenants = q.distinct().values( self.frame_model.tenant_id) return [tenant.tenant_id for tenant in tenants] def get_time_frame(self, begin, end, **filters): if not begin: begin = ck_utils.get_month_start() if not end: end = ck_utils.get_next_month() session = db.get_session() q = utils.model_query( self.frame_model, session) q = q.filter( self.frame_model.begin >= begin, self.frame_model.end <= end) for filter_name, filter_value in filters.items(): if filter_value: q = q.filter( getattr(self.frame_model, filter_name) == filter_value) if not filters.get('res_type'): q = q.filter(self.frame_model.res_type != '_NO_DATA_') count = q.count() if not count: raise NoTimeFrame() r = q.all() return [entry.to_cloudkitty(self._collector) for entry in r] def _append_time_frame(self, res_type, frame, tenant_id): vol_dict = frame['vol'] qty = vol_dict['qty'] unit = vol_dict['unit'] rating_dict = frame.get('rating', {}) rate = rating_dict.get('price') if not rate: rate = decimal.Decimal(0) desc = json.dumps(frame['desc']) self.add_time_frame(begin=self.usage_start_dt.get(tenant_id), end=self.usage_end_dt.get(tenant_id), tenant_id=tenant_id, unit=unit, qty=qty, res_type=res_type, rate=rate, desc=desc) def add_time_frame(self, **kwargs): """Create a new time frame. :param begin: Start of the dataframe. :param end: End of the dataframe. :param tenant_id: tenant_id of the dataframe owner. :param unit: Unit of the metric. :param qty: Quantity of the metric. :param res_type: Type of the resource. :param rate: Calculated rate for this dataframe. :param desc: Resource description (metadata). """ frame = self.frame_model(**kwargs) self._session[kwargs.get('tenant_id')].add(frame)