Commit f3029375 authored by Lukas Burgey's avatar Lukas Burgey
Browse files

Merge two files for convenience

parent aaaaa0b0
......@@ -6,4 +6,5 @@ from .auth.v1.models import OIDCConfig
admin.site.register(models.User)
admin.site.register(models.Site)
admin.site.register(models.Service)
admin.site.register(models.RabbitMQInstance)
admin.site.register(OIDCConfig)
......@@ -3,7 +3,6 @@ from django.dispatch import receiver
from .. import models
from .serializers import DeploymentSerializer
import json
from .pubsub import PubSubConnection
class Deployments:
......@@ -17,7 +16,7 @@ def publish_deployment(sender, instance=None, created=False, **kwargs):
message = json.dumps(DeploymentSerializer(instance).data)
confirmed, online_sites = PubSubConnection().publish_by_service(
confirmed, online_sites = models.rabbitmq_instance().publish_by_service(
instance.service, message)
if confirmed:
# delivery successful
......
import pika
from ..rabbitmq import RabbitMQInstance
class PubSubConnection:
def __init__(self):
self.host = 'localhost'
self.exchange_name = 'deployments'
self.properties = pika.BasicProperties(
delivery_mode=1,
)
def delivery_callback(method):
print(str(method))
def connect(self):
self.connection = pika.BlockingConnection(
pika.ConnectionParameters(host=self.host))
self.channel = self.connection.channel()
self.channel.exchange_declare(
exchange=self.exchange_name,
durable=True,
exchange_type='topic')
self.channel.confirm_delivery()
# self.channel.confirm_delivery(
# callback=self.delivery_callback,
# )
def disconnect(self):
self.connection.close()
def service_routing_key(self, service):
return 'service.' + service.name
def online_clients(self, service):
rabbitmq = RabbitMQInstance()
return [site
for site in service.site.all()
if rabbitmq.is_client_connected(site)]
def publish_by_service(self, service, message):
online_clients = self.online_clients(service)
if online_clients:
print("Online clients for service {}: {}".format(
service,
online_clients,
))
else:
print("No clients online for service {}".format(
service
))
return False, []
self.connect()
print('Sent deployment update for service {}'.format(
service
))
# True if all the clients acked the message
delivery_confirmed = self.channel.basic_publish(
exchange=self.exchange_name,
routing_key=self.service_routing_key(service),
body=message,
properties=self.properties,
)
self.disconnect()
return delivery_confirmed, online_clients
from rest_framework import generics
from rest_framework.authentication import TokenAuthentication
from .. import rabbitmq
from ..models import rabbitmq_instance
from . import serializers, models
# authentication class for the client api
......@@ -44,5 +44,5 @@ class ConfigurationView(generics.ListAPIView):
# we update the rabbitmq permission here, so the
# client can access all of his services, even new ones
rabbitmq.RabbitMQInstance().update_site(site)
rabbitmq_instance().update_site(site)
return site.services.all()
......@@ -7,12 +7,249 @@ from rest_framework.authtoken.models import Token
from django.db.models.signals import post_save, pre_delete
# from django.db.models.signals import m2m_changed
from datetime import datetime
from .rabbitmq import RabbitMQInstance
import requests
from requests.auth import HTTPBasicAuth
import pika
deployment_change = Signal(providing_args=['instance'])
# clients are registerred at rabbitmq, when they are assigned to a site
# (because we only then know what services they provide)
class RabbitMQInstance(models.Model):
host = models.CharField(
max_length=150,
default='localhost',
)
exchange = models.CharField(
max_length=150,
default='deployments',
)
port = models.IntegerField(
default=15672
)
path = models.CharField(
max_length=150,
default='api',
)
username = models.CharField(
max_length=150,
default='guest',
)
password = models.CharField(
max_length=150,
default='guest',
)
enabled = models.BooleanField(
default=False,
)
def __str__(self):
return self.host
def print(self, msg):
print('[RabbitMQ:{}] {}'.format(self.host, msg))
@property
def api(self):
return 'http://{}:{}/{}'.format(
self.host,
self.port,
self.path,
)
@property
def auth(self):
return HTTPBasicAuth(
self.username,
self.password
)
@property
def vhost(self):
return '%2f'
def get_uri(self, path):
return '{}/{}'.format(self.api, path)
def rest_get(self, api_path):
r = requests.get(
self.get_uri(api_path),
auth=self.auth)
r.raise_for_status()
return r.json()
# send a rest call with path and data to the rest interface of
# the rabbitmq instance
def rest_put(self, api_path, data):
r = requests.put(
self.get_uri(api_path),
json=data,
auth=self.auth)
r.raise_for_status()
return r
def rest_del(self, api_path):
r = requests.delete(
self.get_uri(api_path),
auth=self.auth)
r.raise_for_status()
return r
def set_topic_permissions(self, site):
username = site.client.username
path = 'topic-permissions/{}/{}/'.format(
self.vhost,
username,
)
# set permissions for the correct topics
# we construct a regex to match the services of the site
services = ''
omitBar = True
for service in site.services.all():
prefix = '|'
if omitBar:
prefix = ''
omitBar = False
services = services + prefix + service.name
set_topic_permission_data = {
'exchange': self.exchange,
'write': '^$',
'read': '^service\.({})$'.format(services),
}
return self.rest_put(path, set_topic_permission_data)
# set permissions for the user
def set_permissions(self, site):
username = site.client.username
path = 'permissions/{}/{}/'.format(
self.vhost,
username,
)
permission = '^(amq\.gen.*|{})'.format(self.exchange)
set_permission_data = {
'configure': permission,
'write': permission,
'read': permission,
}
return self.rest_put(path, set_permission_data)
# create user at the rabbitmq instance
def create_user(self, site):
username = site.client.username
path = 'users/{}/'.format(username)
user_creation_data = {
'password': str(site.client.auth_token.key),
'tags': '',
}
return self.rest_put(path, user_creation_data)
# delete user at the rabbitmq instance
def delete_user(self, site):
username = site.client.username
path = 'users/{}/'.format(username)
return self.rest_del(path)
# PUBLIC API
def register_site(self, site):
self.print('register: {} / {}'.format(
site.name, site.client.username))
self.create_user(site)
self.set_permissions(site)
self.set_topic_permissions(site)
def update_site(self, site):
self.print('update: {} / {}'.format(
site.name, site.client.username))
self.set_topic_permissions(site)
def deregister_site(self, site):
self.print('deregister: {} / {}'.format(
site.name, site.client.username))
self.delete_user(site)
def is_client_connected(self, site):
connections = self.rest_get("connections/")
clients_for_site = [c
for c in connections
if c['user'] == site.client.username]
return len(clients_for_site) > 0
def connect(self):
self.connection_properties = pika.ConnectionParameters(
host=self.host,
)
self.connection = pika.BlockingConnection(self.connection_properties)
self.channel = self.connection.channel()
self.channel.exchange_declare(
exchange=self.exchange,
durable=True,
exchange_type='topic')
self.channel.confirm_delivery()
def disconnect(self):
self.connection.close()
def service_routing_key(self, service):
return 'service.' + service.name
def online_clients(self, service):
return [site
for site in service.site.all()
if self.is_client_connected(site)]
def publish_by_service(self, service, message):
online_clients = self.online_clients(service)
if online_clients:
self.print("Online clients for service {}: {}".format(
service,
online_clients,
))
self.connect()
self.print('Sent deployment update for service {}'.format(
service
))
# True if all the clients acked the message
delivery_confirmed = self.channel.basic_publish(
exchange=self.exchange,
routing_key=self.service_routing_key(service),
body=message,
properties=pika.BasicProperties(
delivery_mode=1,
),
)
if delivery_confirmed:
self.print('Delivery confirmed')
else:
self.print('Delivery unconfirmed')
self.disconnect()
else:
self.print("No clients online for service {}".format(
service
))
delivery_confirmed = False
return delivery_confirmed, online_clients
def rabbitmq_instance():
return RabbitMQInstance.objects.get(enabled=True)
class User(AbstractUser):
TYPE_CHOICES = (
('apiclient', 'API-Client'),
......@@ -34,12 +271,6 @@ class User(AbstractUser):
return self._ssh_keys.filter(deleted=False)
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if instance.user_type == 'apiclient' and created:
Token.objects.create(user=instance)
def construct_user(user_info):
return User(
sub=user_info['sub'],
......@@ -102,22 +333,6 @@ class Site(models.Model):
return deployments
@receiver(post_save, sender=Site)
def register_at_rabbitmq(
sender, instance=None, created=False, **kwargs):
if not created:
return
RabbitMQInstance().register_site(instance)
@receiver(pre_delete, sender=Site)
def deregister_at_rabbitmq(
sender, instance=None, **kwargs):
RabbitMQInstance().deregister_site(instance)
class Service(models.Model):
name = models.CharField(max_length=150, unique=True)
description = models.TextField(max_length=300, blank=True)
......@@ -286,3 +501,25 @@ class DeploymentUpdate(models.Model):
def __str__(self):
return str(self.user) + ':' + str(self.service) + '@' + str(self.site)
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if instance.user_type == 'apiclient' and created:
Token.objects.create(user=instance)
@receiver(post_save, sender=Site)
def register_at_rabbitmq(
sender, instance=None, created=False, **kwargs):
if not created:
return
RabbitMQInstance().register_site(instance)
@receiver(pre_delete, sender=Site)
def deregister_at_rabbitmq(
sender, instance=None, **kwargs):
RabbitMQInstance().deregister_site(instance)
import requests
from requests.auth import HTTPBasicAuth
# clients are registerred at rabbitmq, when they are assigned to a site
# (because we only then know what services they provide)
class RabbitMQInstance:
def __init__(self):
self.api = 'http://localhost:15672/api'
# %2f: url encoded '/' as this is the vhost we use
self.vhost = '%2f'
self.exchange = 'deployments'
# guest only works on the localhost
self.auth = HTTPBasicAuth('guest', 'guest')
def get_uri(self, path):
return '{}/{}'.format(self.api, path)
def rest_get(self, api_path):
r = requests.get(
self.get_uri(api_path),
auth=self.auth)
r.raise_for_status()
return r.json()
# send a rest call with path and data to the rest interface of
# the rabbitmq instance
def rest_put(self, api_path, data):
r = requests.put(
self.get_uri(api_path),
json=data,
auth=self.auth)
r.raise_for_status()
return r
def rest_del(self, api_path):
r = requests.delete(
self.get_uri(api_path),
auth=self.auth)
r.raise_for_status()
return r
def set_topic_permissions(self, site):
username = site.client.username
path = 'topic-permissions/{}/{}/'.format(
self.vhost,
username,
)
# set permissions for the correct topics
# we construct a regex to match the services of the site
services = ''
omitBar = True
for service in site.services.all():
prefix = '|'
if omitBar:
prefix = ''
omitBar = False
services = services + prefix + service.name
set_topic_permission_data = {
'exchange': self.exchange,
'write': '^$',
'read': '^service\.({})$'.format(services),
}
return self.rest_put(path, set_topic_permission_data)
# set permissions for the user
def set_permissions(self, site):
username = site.client.username
path = 'permissions/{}/{}/'.format(
self.vhost,
username,
)
permission = '^(amq\.gen.*|{})'.format(self.exchange)
set_permission_data = {
'configure': permission,
'write': permission,
'read': permission,
}
return self.rest_put(path, set_permission_data)
# create user at the rabbitmq instance
def create_user(self, site):
username = site.client.username
path = 'users/{}/'.format(username)
user_creation_data = {
'password': str(site.client.auth_token.key),
'tags': '',
}
return self.rest_put(path, user_creation_data)
# delete user at the rabbitmq instance
def delete_user(self, site):
username = site.client.username
path = 'users/{}/'.format(username)
return self.rest_del(path)
# PUBLIC API
def register_site(self, site):
print('RabbitMQ: register: {} / {}'.format(
site.name, site.client.username))
self.create_user(site)
self.set_permissions(site)
self.set_topic_permissions(site)
def update_site(self, site):
print('RabbitMQ: update: {} / {}'.format(
site.name, site.client.username))
self.set_topic_permissions(site)
def deregister_site(self, site):
print('RabbitMQ: deregister: {} / {}'.format(
site.name, site.client.username))
self.delete_user(site)
def is_client_connected(self, site):
connections = self.rest_get("connections/")
clients_for_site = [c
for c in connections
if c['user'] == site.client.username]
return len(clients_for_site) > 0
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment