Added get_all_by_time_range

This method will allow retrieving resources with number of seconds used,
which should be the last step before exposing the actual API.

Change-Id: Iba7b074eeb34e127818953491626d6edb54dfdd1
This commit is contained in:
Mohammed Naser 2020-07-05 16:57:24 -04:00
parent ebbb899953
commit d5f060ad09
4 changed files with 166 additions and 10 deletions

View File

@ -19,24 +19,17 @@
parent: vexxhost-promote-docker-image parent: vexxhost-promote-docker-image
vars: *atmosphere_images vars: *atmosphere_images
- job:
name: atmosphere:linters:tox
parent: tox-linters
vars:
python_version: 3.7
- project: - project:
check: check:
jobs: jobs:
- atmosphere:linters:tox - tox-linters
- tox-py37 - tox-py37
- atmosphere:image:build - atmosphere:image:build
gate: gate:
jobs: jobs:
- atmosphere:linters:tox - tox-linters
- tox-py37 - tox-py37
- atmosphere:image:upload - atmosphere:image:upload
promote: promote:
jobs: jobs:
- atmosphere:linters:tox
- atmosphere:image:promote - atmosphere:image:promote

View File

@ -27,6 +27,7 @@ from flask_migrate import Migrate
from sqlalchemy import exc from sqlalchemy import exc
from sqlalchemy.orm import exc as orm_exc from sqlalchemy.orm import exc as orm_exc
from sqlalchemy.types import TypeDecorator from sqlalchemy.types import TypeDecorator
from sqlalchemy import or_
from atmosphere import exceptions from atmosphere import exceptions
@ -105,6 +106,34 @@ class Resource(db.Model, GetOrCreateMixin):
'polymorphic_on': type 'polymorphic_on': type
} }
@classmethod
def get_all_by_time_range(cls, start, end, project=None):
"""Get all resources given a specific period."""
query = cls.query.join(Period).filter(
# Resources must have started before the end
Period.started_at <= end,
# Resources must be still active or ended after start
or_(
Period.ended_at >= start,
Period.ended_at.is_(None)
),
)
if project is not None:
query = query.filter(Resource.project == project)
resources = query.all()
for resource in resources:
for period in resource.periods:
db.session.expunge(period)
if period.started_at <= start:
period.started_at = start
if period.ended_at is None or period.ended_at >= end:
period.ended_at = end
resource.periods = [p for p in resource.periods if p.seconds != 0]
return resources
@classmethod @classmethod
def from_event(cls, event): def from_event(cls, event):
"""from_event""" """from_event"""
@ -249,7 +278,7 @@ class Period(db.Model):
ended_at = db.Column(BigIntegerDateTime, index=True) ended_at = db.Column(BigIntegerDateTime, index=True)
spec_id = db.Column(db.Integer, db.ForeignKey('spec.id'), nullable=False) spec_id = db.Column(db.Integer, db.ForeignKey('spec.id'), nullable=False)
spec = db.relationship("Spec") spec = db.relationship("Spec", lazy='joined')
@property @property
def seconds(self): def seconds(self):

View File

@ -42,6 +42,7 @@ def ignored_event(request):
def app(): def app():
app = create_app() app = create_app()
app.config['TESTING'] = True app.config['TESTING'] = True
app.config['SQLALCHEMY_ECHO'] = True
app.register_blueprint(ingress.blueprint) app.register_blueprint(ingress.blueprint)
return app return app

View File

@ -64,6 +64,139 @@ class GetOrCreateTestMixin:
class TestResource(GetOrCreateTestMixin): class TestResource(GetOrCreateTestMixin):
MODEL = models.Resource MODEL = models.Resource
def test_get_all_by_time_range_with_no_data(self):
start = datetime.datetime.now()
ended = start + relativedelta(hours=+1)
data = models.Resource.get_all_by_time_range(start, ended)
assert len(data) == 0
def test_get_all_by_time_range_by_project(self):
event = fake.get_normalized_event()
resource = models.Resource.get_or_create(event)
start = event['traits']['created_at'] - relativedelta(hours=+1)
ended = start + relativedelta(hours=+2)
data = models.Resource.get_all_by_time_range(start, ended,
project="project")
assert len(data) == 0
data = models.Resource.get_all_by_time_range(start, ended,
project="fake-project")
assert len(data) == 1
assert data[0].periods[0].seconds == 3600
def test_get_all_by_time_range_with_resource_ended_before_start(self):
event = fake.get_normalized_event()
event['traits']['deleted_at'] = event['traits']['created_at'] + \
relativedelta(hours=+1)
resource = models.Resource.get_or_create(event)
start = event['traits']['deleted_at'] + relativedelta(hours=+1)
ended = start + relativedelta(hours=+1)
data = models.Resource.get_all_by_time_range(start, ended)
assert len(data) == 0
def test_get_all_by_time_range_with_resource_started_after_end(self):
event = fake.get_normalized_event()
resource = models.Resource.get_or_create(event)
ended = event['traits']['created_at'] - relativedelta(hours=+1)
start = ended - relativedelta(hours=+1)
data = models.Resource.get_all_by_time_range(start, ended)
assert len(data) == 0
def test_get_all_by_time_range_with_active_resource_after_start(self):
event = fake.get_normalized_event()
resource = models.Resource.get_or_create(event)
start = event['traits']['created_at'] - relativedelta(hours=+1)
ended = start + relativedelta(hours=+2)
data = models.Resource.get_all_by_time_range(start, ended)
assert len(data) == 1
assert data[0].periods[0].seconds == 3600
def test_get_all_by_time_range_with_active_resource_before_start(self):
event = fake.get_normalized_event()
resource = models.Resource.get_or_create(event)
start = event['traits']['created_at'] + relativedelta(minutes=+30)
ended = start + relativedelta(minutes=+30)
data = models.Resource.get_all_by_time_range(start, ended)
assert len(data) == 1
assert data[0].periods[0].seconds == 1800
def test_get_all_by_time_range_with_active_resource_after_end(self):
event = fake.get_normalized_event()
event['traits']['deleted_at'] = event['traits']['created_at'] + \
relativedelta(hours=+1)
resource = models.Resource.get_or_create(event)
start = event['traits']['deleted_at'] + relativedelta(hours=+1)
ended = start + relativedelta(hours=+2)
data = models.Resource.get_all_by_time_range(start, ended)
assert len(data) == 0
def test_get_all_by_time_range_with_resource_inside_range(self):
event = fake.get_normalized_event()
event['traits']['deleted_at'] = event['traits']['created_at'] + \
relativedelta(minutes=+15)
resource = models.Resource.get_or_create(event)
start = event['traits']['created_at'] - relativedelta(hours=+1)
ended = start + relativedelta(hours=+2)
data = models.Resource.get_all_by_time_range(start, ended)
assert len(data) == 1
assert data[0].periods[0].seconds == 900
def test_get_all_by_time_range_with_resource_with_multiple_periods(self):
event = fake.get_normalized_event()
event['traits']['created_at'] = event['traits']['created_at'] + \
relativedelta(microseconds=0)
models.Resource.get_or_create(event)
event['generated'] = event['traits']['created_at'] + \
relativedelta(minutes=+15, microseconds=0)
event['traits']['instance_type'] = 'v2-standard-8'
models.Resource.get_or_create(event)
start = event['traits']['created_at'] - relativedelta(hours=+1)
ended = start + relativedelta(hours=+2)
data = models.Resource.get_all_by_time_range(start, ended)
assert len(data) == 1
assert data[0].periods[0].seconds == 900
assert data[0].periods[1].seconds == 2700
def test_get_all_by_time_range_with_resource_with_one_active_period(self):
event = fake.get_normalized_event()
event['traits']['created_at'] = event['traits']['created_at'] + \
relativedelta(microseconds=0)
models.Resource.get_or_create(event)
event['generated'] = event['traits']['created_at'] + \
relativedelta(minutes=+15, microseconds=0)
event['traits']['instance_type'] = 'v2-standard-8'
models.Resource.get_or_create(event)
start = event['traits']['created_at'] + relativedelta(minutes=+15)
ended = start + relativedelta(minutes=+45)
data = models.Resource.get_all_by_time_range(start, ended)
assert len(data) == 1
assert len(data[0].periods) == 1
assert data[0].periods[0].seconds == 2700
def test_from_event(self): def test_from_event(self):
event = fake.get_normalized_event() event = fake.get_normalized_event()
resource = models.Resource.from_event(event) resource = models.Resource.from_event(event)