Add a module which will create notifications for all users when the license is at its expiration period, and terminate the process when the license expires.

This commit is contained in:
Jake Moshenko 2014-05-29 11:24:10 -04:00
parent 0683f2657e
commit 0ba4201020
8 changed files with 120 additions and 18 deletions

2
app.py
View file

@ -16,6 +16,7 @@ from data.users import UserAuthentication
from util.analytics import Analytics from util.analytics import Analytics
from util.exceptionlog import Sentry from util.exceptionlog import Sentry
from util.queuemetrics import QueueMetrics from util.queuemetrics import QueueMetrics
from util.expiration import Expiration
from data.billing import Billing from data.billing import Billing
from data.buildlogs import BuildLogs from data.buildlogs import BuildLogs
from data.queue import WorkQueue from data.queue import WorkQueue
@ -64,6 +65,7 @@ sentry = Sentry(app)
build_logs = BuildLogs(app) build_logs = BuildLogs(app)
queue_metrics = QueueMetrics(app) queue_metrics = QueueMetrics(app)
authentication = UserAuthentication(app) authentication = UserAuthentication(app)
expiration = Expiration(app)
tf = app.config['DB_TRANSACTION_FACTORY'] tf = app.config['DB_TRANSACTION_FACTORY']
image_diff_queue = WorkQueue(app.config['DIFFS_QUEUE_NAME'], tf) image_diff_queue = WorkQueue(app.config['DIFFS_QUEUE_NAME'], tf)

View file

@ -117,7 +117,7 @@ class FederatedLogin(BaseModel):
class Visibility(BaseModel): class Visibility(BaseModel):
name = CharField(index=True) name = CharField(index=True, unique=True)
class Repository(BaseModel): class Repository(BaseModel):
@ -136,7 +136,7 @@ class Repository(BaseModel):
class Role(BaseModel): class Role(BaseModel):
name = CharField(index=True) name = CharField(index=True, unique=True)
class RepositoryPermission(BaseModel): class RepositoryPermission(BaseModel):
@ -189,7 +189,7 @@ class AccessToken(BaseModel):
class BuildTriggerService(BaseModel): class BuildTriggerService(BaseModel):
name = CharField(index=True) name = CharField(index=True, unique=True)
class RepositoryBuildTrigger(BaseModel): class RepositoryBuildTrigger(BaseModel):
@ -283,7 +283,7 @@ class QueueItem(BaseModel):
class LogEntryKind(BaseModel): class LogEntryKind(BaseModel):
name = CharField(index=True) name = CharField(index=True, unique=True)
class LogEntry(BaseModel): class LogEntry(BaseModel):
@ -330,7 +330,7 @@ class OAuthAccessToken(BaseModel):
class NotificationKind(BaseModel): class NotificationKind(BaseModel):
name = CharField(index=True) name = CharField(index=True, unique=True)
class Notification(BaseModel): class Notification(BaseModel):

View file

@ -140,6 +140,7 @@ def upgrade():
[ [
{'id':1, 'name':'password_required'}, {'id':1, 'name':'password_required'},
{'id':2, 'name':'over_private_usage'}, {'id':2, 'name':'over_private_usage'},
{'id':3, 'name':'expiring_license'},
]) ])
op.create_table('teamrole', op.create_table('teamrole',

View file

@ -1622,14 +1622,20 @@ def list_trigger_builds(namespace_name, repository_name, trigger_uuid,
.where(RepositoryBuildTrigger.uuid == trigger_uuid)) .where(RepositoryBuildTrigger.uuid == trigger_uuid))
def create_notification(kind, target, metadata={}): def create_notification(kind_name, target, metadata={}):
kind_ref = NotificationKind.get(name=kind) kind_ref = NotificationKind.get(name=kind_name)
notification = Notification.create(kind=kind_ref, target=target, notification = Notification.create(kind=kind_ref, target=target,
metadata_json=json.dumps(metadata)) metadata_json=json.dumps(metadata))
return notification return notification
def list_notifications(user, kind=None): def create_unique_notification(kind_name, target, metadata={}):
with config.app_config['DB_TRANSACTION_FACTORY'](db):
if list_notifications(target, kind_name).count() == 0:
create_notification(kind_name, target, metadata)
def list_notifications(user, kind_name=None):
Org = User.alias() Org = User.alias()
AdminTeam = Team.alias() AdminTeam = Team.alias()
AdminTeamMember = TeamMember.alias() AdminTeamMember = TeamMember.alias()
@ -1647,20 +1653,30 @@ def list_notifications(user, kind=None):
.join(AdminTeamMember, JOIN_LEFT_OUTER, on=(AdminTeam.id == .join(AdminTeamMember, JOIN_LEFT_OUTER, on=(AdminTeam.id ==
AdminTeamMember.team)) AdminTeamMember.team))
.join(AdminUser, JOIN_LEFT_OUTER, on=(AdminTeamMember.user == .join(AdminUser, JOIN_LEFT_OUTER, on=(AdminTeamMember.user ==
AdminUser.id))) AdminUser.id))
.where((Notification.target == user) |
((AdminUser.id == user) & (TeamRole.name == 'admin')))
.order_by(Notification.created)
.desc())
where_clause = ((Notification.target == user) | if kind_name:
((AdminUser.id == user) & query = (query
(TeamRole.name == 'admin'))) .switch(Notification)
.join(NotificationKind)
.where(NotificationKind.name == kind_name))
if kind: return query
where_clause = where_clause & (NotificationKind.name == kind)
return query.where(where_clause).order_by(Notification.created).desc()
def delete_notifications_by_kind(target, kind): def delete_all_notifications_by_kind(kind_name):
kind_ref = NotificationKind.get(name=kind) kind_ref = NotificationKind.get(name=kind_name)
(Notification.delete()
.where(Notification.kind == kind_ref)
.execute())
def delete_notifications_by_kind(target, kind_name):
kind_ref = NotificationKind.get(name=kind_name)
Notification.delete().where(Notification.target == target, Notification.delete().where(Notification.target == target,
Notification.kind == kind_ref).execute() Notification.kind == kind_ref).execute()

View file

@ -233,6 +233,7 @@ def initialize_database():
NotificationKind.create(name='password_required') NotificationKind.create(name='password_required')
NotificationKind.create(name='over_private_usage') NotificationKind.create(name='over_private_usage')
NotificationKind.create(name='expiring_license')
NotificationKind.create(name='test_notification') NotificationKind.create(name='test_notification')

View file

@ -913,6 +913,12 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
return '/user'; return '/user';
} }
} }
},
'expiring_license': {
'level': 'error',
'message': 'Your license will expire at: {expires_at} ' +
'<br><br>Please contact Quay.io support to purchase a new license.',
'page': '/contact/'
} }
}; };

Binary file not shown.

76
util/expiration.py Normal file
View file

@ -0,0 +1,76 @@
import calendar
import sys
from email.utils import formatdate
from apscheduler.scheduler import Scheduler
from datetime import datetime, timedelta
from data import model
class ExpirationScheduler(object):
def __init__(self, utc_create_notifications_date, utc_terminate_processes_date):
self._scheduler = Scheduler()
self._termination_date = utc_terminate_processes_date
soon = datetime.now() + timedelta(seconds=1)
if utc_create_notifications_date > datetime.utcnow():
self._scheduler.add_date_job(model.delete_all_notifications_by_kind, soon,
['expiring_license'])
local_notifications_date = self._utc_to_local(utc_create_notifications_date)
self._scheduler.add_date_job(self._generate_notifications, local_notifications_date)
else:
self._scheduler.add_date_job(self._generate_notifications, soon)
local_termination_date = self._utc_to_local(utc_terminate_processes_date)
self._scheduler.add_date_job(self._terminate, local_termination_date)
@staticmethod
def _format_date(date):
""" Output an RFC822 date format. """
if date is None:
return None
return formatdate(calendar.timegm(date.utctimetuple()))
@staticmethod
def _utc_to_local(utc_dt):
# get integer timestamp to avoid precision lost
timestamp = calendar.timegm(utc_dt.timetuple())
local_dt = datetime.fromtimestamp(timestamp)
return local_dt.replace(microsecond=utc_dt.microsecond)
def _generate_notifications(self):
for user in model.get_active_users():
model.create_unique_notification('expiring_license', user,
{'expires_at': self._format_date(self._termination_date)})
@staticmethod
def _terminate():
sys.exit(1)
def start(self):
self._scheduler.start()
class Expiration(object):
def __init__(self, app=None):
self.app = app
if app is not None:
self.state = self.init_app(app)
else:
self.state = None
def init_app(self, app):
expiration = ExpirationScheduler(app.config['LICENSE_EXPIRATION_WARNING'],
app.config['LICENSE_EXPIRATION'])
expiration.start()
# register extension with app
app.extensions = getattr(app, 'extensions', {})
app.extensions['expiration'] = expiration
return expiration
def __getattr__(self, name):
return getattr(self.state, name, None)