Billing statistics improved
Instance tracking information was improved to contain additional information on instances such as type name, descriptive title and number of VMs used by the application. Added ability to retrieve non-aggregated statistics. Also instance statistics API refactoring was made. Implements blueprint app-catalog-billing Change-Id: I8f3ea1a3947ceaf7deb2ee62b4ad41cbf230596e
This commit is contained in:
@@ -30,7 +30,8 @@ Workflow:
|
||||
Body:
|
||||
- $.primaryController.deploy()
|
||||
- $.secondaryControllers.pselect($.deploy())
|
||||
- $.reportDeployed()
|
||||
- $.reportDeployed(title => 'MS Active Directory',
|
||||
unitCount => len(secondaryControllers) + 1)
|
||||
|
||||
destroy:
|
||||
- $.reportDestroyed()
|
||||
|
@@ -5,8 +5,15 @@ Name: Application
|
||||
|
||||
Workflow:
|
||||
reportDeployed:
|
||||
Arguments:
|
||||
- title:
|
||||
Contract: $.string()
|
||||
Default: null
|
||||
- unitCount:
|
||||
Contract: $.int()
|
||||
Default: null
|
||||
Body:
|
||||
- $this.find(Environment).instanceNotifier.trackApplication($this)
|
||||
- $this.find(Environment).instanceNotifier.trackApplication($this, $title, $unitCount)
|
||||
|
||||
reportDestroyed:
|
||||
Body:
|
||||
|
@@ -25,14 +25,14 @@ API_NAME = 'EnvironmentStatistics'
|
||||
|
||||
|
||||
class Controller(object):
|
||||
@request_statistics.stats_count(API_NAME, 'GetForEnvironment')
|
||||
def get_for_environment(self, request, environment_id):
|
||||
LOG.debug(_('EnvironmentStatistics:GetForEnvironment'))
|
||||
@request_statistics.stats_count(API_NAME, 'GetAggregated')
|
||||
def get_aggregated(self, request, environment_id):
|
||||
LOG.debug(_('EnvironmentStatistics:GetAggregated'))
|
||||
|
||||
# TODO (stanlagun): Check that caller is authorized to access
|
||||
# tenant's statistics
|
||||
|
||||
return instances.InstanceStatsServices.get_environment_stats(
|
||||
return instances.InstanceStatsServices.get_aggregated_stats(
|
||||
environment_id)
|
||||
|
||||
@request_statistics.stats_count(API_NAME, 'GetForInstance')
|
||||
@@ -42,9 +42,19 @@ class Controller(object):
|
||||
# TODO (stanlagun): Check that caller is authorized to access
|
||||
# tenant's statistics
|
||||
|
||||
return instances.InstanceStatsServices.get_environment_stats(
|
||||
return instances.InstanceStatsServices.get_raw_environment_stats(
|
||||
environment_id, instance_id)
|
||||
|
||||
@request_statistics.stats_count(API_NAME, 'GetForEnvironment')
|
||||
def get_for_environment(self, request, environment_id):
|
||||
LOG.debug(_('EnvironmentStatistics:GetForEnvironment'))
|
||||
|
||||
# TODO (stanlagun): Check that caller is authorized to access
|
||||
# tenant's statistics
|
||||
|
||||
return instances.InstanceStatsServices.get_raw_environment_stats(
|
||||
environment_id)
|
||||
|
||||
|
||||
def create_resource():
|
||||
return wsgi.Resource(Controller())
|
@@ -15,8 +15,8 @@ import routes
|
||||
|
||||
from muranoapi.api.v1 import catalog
|
||||
from muranoapi.api.v1 import deployments
|
||||
from muranoapi.api.v1 import environment_statistics
|
||||
from muranoapi.api.v1 import environments
|
||||
from muranoapi.api.v1 import instance_statistics
|
||||
from muranoapi.api.v1 import request_statistics
|
||||
from muranoapi.api.v1 import services
|
||||
from muranoapi.api.v1 import sessions
|
||||
@@ -122,17 +122,23 @@ class API(wsgi.Router):
|
||||
action='deploy',
|
||||
conditions={'method': ['POST']})
|
||||
|
||||
statistics_resource = environment_statistics.create_resource()
|
||||
statistics_resource = instance_statistics.create_resource()
|
||||
mapper.connect(
|
||||
'/environments/{environment_id}/statistics/{instance_id}',
|
||||
'/environments/{environment_id}/instance-statistics/raw/'
|
||||
'{instance_id}',
|
||||
controller=statistics_resource,
|
||||
action='get_for_instance',
|
||||
conditions={'method': ['GET']})
|
||||
mapper.connect(
|
||||
'/environments/{environment_id}/statistics',
|
||||
'/environments/{environment_id}/instance-statistics/raw',
|
||||
controller=statistics_resource,
|
||||
action='get_for_environment',
|
||||
conditions={'method': ['GET']})
|
||||
mapper.connect(
|
||||
'/environments/{environment_id}/instance-statistics/aggregated',
|
||||
controller=statistics_resource,
|
||||
action='get_aggregated',
|
||||
conditions={'method': ['GET']})
|
||||
|
||||
catalog_resource = catalog.create_resource()
|
||||
mapper.connect('/catalog/packages/categories',
|
||||
|
@@ -117,8 +117,13 @@ def track_instance(payload):
|
||||
instance_id = payload['instance']
|
||||
instance_type = payload.get('instance_type', 0)
|
||||
environment_id = payload['environment']
|
||||
unit_count = payload.get('unit_count')
|
||||
type_name = payload['type_name']
|
||||
type_title = payload.get('type_title')
|
||||
|
||||
instances.InstanceStatsServices.track_instance(
|
||||
instance_id, environment_id, instance_type)
|
||||
instance_id, environment_id, instance_type,
|
||||
type_name, type_title, unit_count)
|
||||
|
||||
|
||||
@notification_endpoint_wrapper()
|
||||
|
@@ -0,0 +1,43 @@
|
||||
# Copyright (c) 2013 Mirantis, Inc.
|
||||
#
|
||||
# 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 sqlalchemy import schema
|
||||
from sqlalchemy import types
|
||||
|
||||
meta = schema.MetaData()
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta.bind = migrate_engine
|
||||
table = schema.Table('instance', meta, autoload=True)
|
||||
table.rename('instance_stats')
|
||||
table.delete()
|
||||
type_name = schema.Column('type_name', types.String(512), nullable=False)
|
||||
type_name.create(table)
|
||||
type_title = schema.Column('type_title', types.String(512))
|
||||
type_title.create(table)
|
||||
unit_count = schema.Column('unit_count', types.Integer())
|
||||
unit_count.create(table)
|
||||
tenant_id = schema.Column('tenant_id', types.String(32), nullable=False)
|
||||
tenant_id.create(table)
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
meta.bind = migrate_engine
|
||||
table = schema.Table('instance_stats', meta, autoload=True)
|
||||
table.rename('instance')
|
||||
table.c.type_name.drop()
|
||||
table.c.type_title.drop()
|
||||
table.c.unit_count.drop()
|
||||
table.c.tenant_id.drop()
|
@@ -233,7 +233,7 @@ package_to_tag = sa.Table('package_to_tag',
|
||||
|
||||
|
||||
class Instance(BASE, ModelBase):
|
||||
__tablename__ = 'instance'
|
||||
__tablename__ = 'instance_stats'
|
||||
|
||||
environment_id = sa.Column(
|
||||
sa.String(100), primary_key=True, nullable=False)
|
||||
@@ -242,6 +242,10 @@ class Instance(BASE, ModelBase):
|
||||
instance_type = sa.Column(sa.Integer, default=0, nullable=False)
|
||||
created = sa.Column(sa.Integer, nullable=False)
|
||||
destroyed = sa.Column(sa.Integer, nullable=True)
|
||||
type_name = sa.Column('type_name', sa.String(512), nullable=False)
|
||||
type_title = sa.Column('type_title', sa.String(512))
|
||||
unit_count = sa.Column('unit_count', sa.Integer())
|
||||
tenant_id = sa.Column('tenant_id', sa.String(32), nullable=False)
|
||||
|
||||
def to_dict(self):
|
||||
dictionary = super(Instance, self).to_dict()
|
||||
|
@@ -12,6 +12,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import sqlalchemy
|
||||
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from muranoapi.db import models
|
||||
@@ -27,20 +29,31 @@ OS_INSTANCE = 200
|
||||
|
||||
class InstanceStatsServices(object):
|
||||
@staticmethod
|
||||
def track_instance(instance_id, environment_id, instance_type):
|
||||
instance = models.Instance()
|
||||
instance.instance_id = instance_id
|
||||
instance.environment_id = environment_id
|
||||
instance.instance_type = instance_type
|
||||
instance.created = timeutils.utcnow_ts()
|
||||
instance.destroyed = None
|
||||
def track_instance(instance_id, environment_id, instance_type,
|
||||
type_name, type_title=None, unit_count=None):
|
||||
|
||||
unit = db_session.get_session()
|
||||
try:
|
||||
with unit.begin():
|
||||
env = unit.query(models.Environment).get(environment_id)
|
||||
instance = models.Instance()
|
||||
instance.instance_id = instance_id
|
||||
instance.environment_id = environment_id
|
||||
instance.tenant_id = env.tenant_id
|
||||
instance.instance_type = instance_type
|
||||
instance.created = timeutils.utcnow_ts()
|
||||
instance.destroyed = None
|
||||
instance.type_name = type_name
|
||||
instance.type_title = type_title
|
||||
instance.unit_count = unit_count
|
||||
|
||||
unit.add(instance)
|
||||
except exception.DBDuplicateEntry:
|
||||
pass # expected behaviour when record already exists
|
||||
unit.execute(
|
||||
sqlalchemy.update(models.Instance).where(
|
||||
models.Instance.instance_id == instance_id and
|
||||
models.Instance.environment_id == environment_id).values(
|
||||
unit_count=unit_count))
|
||||
|
||||
@staticmethod
|
||||
def destroy_instance(instance_id, environment_id):
|
||||
@@ -52,16 +65,13 @@ class InstanceStatsServices(object):
|
||||
instance.save(unit)
|
||||
|
||||
@staticmethod
|
||||
def get_environment_stats(environment_id, instance_id=None):
|
||||
def get_aggregated_stats(environment_id):
|
||||
unit = db_session.get_session()
|
||||
now = timeutils.utcnow_ts()
|
||||
query = unit.query(models.Instance.instance_type, func.sum(
|
||||
func.coalesce(models.Instance.destroyed, now) -
|
||||
models.Instance.created), func.count()).filter(
|
||||
models.Instance.environment_id == environment_id)
|
||||
if instance_id is not None:
|
||||
query = query.filter(
|
||||
models.Instance.instance_id == instance_id)
|
||||
|
||||
res = query.group_by(models.Instance.instance_type).all()
|
||||
|
||||
@@ -70,3 +80,25 @@ class InstanceStatsServices(object):
|
||||
'duration': int(record[1]),
|
||||
'count': int(record[2])
|
||||
} for record in res]
|
||||
|
||||
@staticmethod
|
||||
def get_raw_environment_stats(environment_id, instance_id=None):
|
||||
unit = db_session.get_session()
|
||||
now = timeutils.utcnow_ts()
|
||||
query = unit.query(models.Instance).filter(
|
||||
models.Instance.environment_id == environment_id)
|
||||
|
||||
if instance_id:
|
||||
query = query.filter(models.Instance.instance_id == instance_id)
|
||||
|
||||
res = query.all()
|
||||
|
||||
return [{
|
||||
'type': record.instance_type,
|
||||
'duration': (record.destroyed or now) - record.created,
|
||||
'type_name': record.type_name,
|
||||
'unit_count': record.unit_count,
|
||||
'instance_id': record.instance_id,
|
||||
'type_title': record.type_title,
|
||||
'active': True if not record.destroyed else False
|
||||
} for record in res]
|
||||
|
@@ -44,26 +44,36 @@ class InstanceReportNotifier(object):
|
||||
def initialize(self, environment):
|
||||
self._environment_id = environment.object_id
|
||||
|
||||
def _track_instance(self, instance, instance_type, untrack=False):
|
||||
def _track_instance(self, instance, instance_type,
|
||||
type_title, unit_count):
|
||||
payload = {
|
||||
'instance': instance.object_id,
|
||||
'environment': self._environment_id,
|
||||
'instance_type': instance_type
|
||||
'instance_type': instance_type,
|
||||
'type_name': instance.type.name,
|
||||
'type_title': type_title,
|
||||
'unit_count': unit_count
|
||||
}
|
||||
|
||||
event_type = 'murano.untrack_instance' \
|
||||
if untrack else 'murano.track_instance'
|
||||
self._notifier.info({}, 'murano.track_instance', payload)
|
||||
|
||||
self._notifier.info({}, event_type, payload)
|
||||
def _untrack_instance(self, instance, instance_type):
|
||||
payload = {
|
||||
'instance': instance.object_id,
|
||||
'environment': self._environment_id,
|
||||
'instance_type': instance_type,
|
||||
}
|
||||
|
||||
def trackApplication(self, instance):
|
||||
self._track_instance(instance, APPLICATION, False)
|
||||
self._notifier.info({}, 'murano.untrack_instance', payload)
|
||||
|
||||
def trackApplication(self, instance, title=None, unitCount=None):
|
||||
self._track_instance(instance, APPLICATION, title, unitCount)
|
||||
|
||||
def untrackApplication(self, instance):
|
||||
self._track_instance(instance, APPLICATION, True)
|
||||
self._untrack_instance(instance, APPLICATION)
|
||||
|
||||
def trackCloudInstance(self, instance):
|
||||
self._track_instance(instance, OS_INSTANCE, False)
|
||||
self._track_instance(instance, OS_INSTANCE, None, 1)
|
||||
|
||||
def untrackCloudInstance(self, instance):
|
||||
self._track_instance(instance, OS_INSTANCE, True)
|
||||
self._untrack_instance(instance, OS_INSTANCE)
|
||||
|
Reference in New Issue
Block a user