diff --git a/meta/io.murano.windows.ActiveDirectory/Classes/ActiveDirectory.yaml b/meta/io.murano.windows.ActiveDirectory/Classes/ActiveDirectory.yaml index 7f037ef7..44cfe758 100644 --- a/meta/io.murano.windows.ActiveDirectory/Classes/ActiveDirectory.yaml +++ b/meta/io.murano.windows.ActiveDirectory/Classes/ActiveDirectory.yaml @@ -30,7 +30,8 @@ Workflow: Body: - $.primaryController.deploy() - $.secondaryControllers.pselect($.deploy()) - - $.reportDeployed() + - $.reportDeployed(title => 'MS Active Directory', + unitCount => len(secondaryControllers) + 1) destroy: - $.reportDestroyed() diff --git a/meta/io.murano/Classes/Application.yaml b/meta/io.murano/Classes/Application.yaml index 2c658920..e36b1eb2 100644 --- a/meta/io.murano/Classes/Application.yaml +++ b/meta/io.murano/Classes/Application.yaml @@ -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: diff --git a/muranoapi/api/v1/environment_statistics.py b/muranoapi/api/v1/instance_statistics.py similarity index 76% rename from muranoapi/api/v1/environment_statistics.py rename to muranoapi/api/v1/instance_statistics.py index 0f99c1f6..846c0555 100644 --- a/muranoapi/api/v1/environment_statistics.py +++ b/muranoapi/api/v1/instance_statistics.py @@ -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()) diff --git a/muranoapi/api/v1/router.py b/muranoapi/api/v1/router.py index dcd76c69..e7fe64ab 100644 --- a/muranoapi/api/v1/router.py +++ b/muranoapi/api/v1/router.py @@ -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', diff --git a/muranoapi/common/server.py b/muranoapi/common/server.py index 2828392e..76d6f297 100644 --- a/muranoapi/common/server.py +++ b/muranoapi/common/server.py @@ -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() diff --git a/muranoapi/db/migrate_repo/versions/007_instance_table_extended.py b/muranoapi/db/migrate_repo/versions/007_instance_table_extended.py new file mode 100644 index 00000000..01486014 --- /dev/null +++ b/muranoapi/db/migrate_repo/versions/007_instance_table_extended.py @@ -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() diff --git a/muranoapi/db/models.py b/muranoapi/db/models.py index 3cd4bc18..5fbd9aef 100644 --- a/muranoapi/db/models.py +++ b/muranoapi/db/models.py @@ -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() diff --git a/muranoapi/db/services/instances.py b/muranoapi/db/services/instances.py index 97d8832c..c34daf84 100644 --- a/muranoapi/db/services/instances.py +++ b/muranoapi/db/services/instances.py @@ -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] diff --git a/muranoapi/engine/system/instance_reporter.py b/muranoapi/engine/system/instance_reporter.py index 0e3b34c5..c92eb005 100644 --- a/muranoapi/engine/system/instance_reporter.py +++ b/muranoapi/engine/system/instance_reporter.py @@ -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)