Skip to content
This repository has been archived by the owner on Aug 22, 2022. It is now read-only.

[FAL-2031] Add Redis support for Ocim provisioned instances #822

Merged
merged 25 commits into from
Sep 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4161cd1
chore: update redis to latest version
gabor-boros Jul 1, 2021
8c435fe
feat: add redis server config to settings
gabor-boros Jul 1, 2021
e6109f6
feat: add redis server and redis user support
gabor-boros Jul 1, 2021
2fe8a80
feat: integrate redis server with Intances
gabor-boros Jul 1, 2021
8644d86
refactor: simplify new redis user creation
gabor-boros Jul 2, 2021
fba4b7f
fix: docstring
gabor-boros Aug 13, 2021
ace7572
docs: more info about `DEFAULT_INSTANCE_REDIS_URL`
gabor-boros Aug 13, 2021
a1d40b5
refactor: decide when to use redis over rabbit mq
gabor-boros Aug 13, 2021
34ea63a
refactor: simplify redis server model
gabor-boros Aug 13, 2021
c94c548
refactor: save redis as default when provisioned
gabor-boros Aug 13, 2021
2f59980
fix: ensure redis settings are provisioned when needed
gabor-boros Aug 13, 2021
12a5bc0
docs: add more info about redis server selection
gabor-boros Aug 18, 2021
a79cfb2
remove: delete XQUEUE settings for redis cache
gabor-boros Aug 18, 2021
46ad390
refactor: fix linter issues and remove unused code
gabor-boros Aug 18, 2021
1b2c043
test: add Redis db related tests
gabor-boros Aug 23, 2021
6df229a
test: update breaking tests to use correct mocks
gabor-boros Aug 23, 2021
7c22544
fix: adjust ACLs set for the user to include pidbox
gabor-boros Aug 26, 2021
fd88f29
test: add redis mixin tests
gabor-boros Aug 27, 2021
b27b052
refactor: remove unnecessary acl
gabor-boros Aug 27, 2021
af7bba7
refactor: use unique redis_username
gabor-boros Aug 27, 2021
46b652e
fix: ensure the user has access to pattern
gabor-boros Sep 1, 2021
7b948ab
fix: enable all commands on the given keyprefix
gabor-boros Sep 1, 2021
624bbe3
fix: command type
gabor-boros Sep 1, 2021
763e592
Only provision redis for instance if configured
Sep 3, 2021
2156944
Remove duplicated tests
Sep 3, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.e2e
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ DEFAULT_LOAD_BALANCING_SERVER='[email protected]'
LOAD_BALANCER_FRAGMENT_NAME_PREFIX='opencraft-'
DEFAULT_RABBITMQ_API_URL='https://admin:[email protected]:15671'
DEFAULT_INSTANCE_RABBITMQ_URL='amqps://rabbitmq.example.com:5671'
DEFAULT_INSTANCE_REDIS_URL='rediss://admin:[email protected]:6397/0'
CONSUL_ENABLED=false
CONSUL_SERVERS=haproxy-integration.net.opencraft.hosting
DISABLE_LOAD_BALANCER_CONFIGURATION=false
Expand Down
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ DEFAULT_LOAD_BALANCING_SERVER='[email protected]'
LOAD_BALANCER_FRAGMENT_NAME_PREFIX='opencraft-'
DEFAULT_RABBITMQ_API_URL='https://admin:[email protected]:15671'
DEFAULT_INSTANCE_RABBITMQ_URL='amqps://rabbitmq.example.com:5671'
DEFAULT_INSTANCE_REDIS_URL='rediss://admin:[email protected]:6397/0'
CONSUL_ENABLED=false
CONSUL_SERVERS=haproxy-integration.net.opencraft.hosting
DISABLE_LOAD_BALANCER_CONFIGURATION=false
Expand Down
6 changes: 6 additions & 0 deletions documentation/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,12 @@ Required settings:
* `DEFAULT_INSTANCE_RABBITMQ_URL`: The RabbitMQ AMQPS URI to be used by instances. E.g.,
`amqps://rabbitmq.example.com:5671`

### Redis settings
* `DEFAULT_INSTANCE_REDIS_URL` The Redis URI (including the protocol, port, db, and basic auth)
to be used by instances. E.g., `rediss://admin:[email protected]:6397/0`.
Similarly to `DEFAULT_RABBITMQ_API_URL` this setting is saved in the database for new instances
using `RedisServerManager._create_default`, where the setting is not explicity overridden.

### DNS settings

* `DEFAULT_INSTANCE_BASE_DOMAIN`: Instances are created as subdomains of this domain,
Expand Down
27 changes: 25 additions & 2 deletions documentation/development/docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,30 @@ This describes how to create an environment for development using Docker.

## Running OCIM in a docker container

First, set your `.env` file as you would normally,
but set the following vars:
First, set your `.env` file as you would normally, but first set the following vars:
```env
DEBUG=True
OPENSTACK_USER='username'
OPENSTACK_PASSWORD='password'
OPENSTACK_TENANT='tenant-name'
OPENSTACK_AUTH_URL='https://auth.cloud.ovh.net/v2.0'
OPENSTACK_REGION='BHS1'
OPENSTACK_SANDBOX_SSH_KEYNAME='keypair-name'
DEFAULT_INSTANCE_BASE_DOMAIN='example.com'
GANDI_API_KEY='api-key'
GITHUB_ACCESS_TOKEN='github-token'
SECRET_KEY='tests'
DEFAULT_INSTANCE_MYSQL_URL=...
DEFAULT_RABBITMQ_API_URL=...
DEFAULT_INSTANCE_RABBITMQ_URL=...
DEFAULT_INSTANCE_REDIS_URL=...
DEFAULT_MONGO_REPLICA_SET_USER=...
DEFAULT_MONGO_REPLICA_SET_PASSWORD=...
DEFAULT_MONGO_REPLICA_SET_NAME=...
DEFAULT_MONGO_REPLICA_SET_PRIMARY=...
DEFAULT_MONGO_REPLICA_SET_HOSTS=...
REDIS_URL=...
```

```sh
ALLOWED_HOSTS='["*"]'
Expand Down Expand Up @@ -43,3 +65,4 @@ For example:
```sh
docker-compose logs -f ocim
```

6 changes: 6 additions & 0 deletions instance/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from instance.models.openedx_deployment import OpenEdXDeployment
from instance.models.openedx_instance import OpenEdXInstance
from instance.models.rabbitmq_server import RabbitMQServer
from instance.models.redis_server import RedisServer
from instance.models.server import OpenStackServer


Expand Down Expand Up @@ -109,6 +110,10 @@ class RabbitMQServerAdmin(admin.ModelAdmin): # pylint: disable=missing-docstring
list_display = ('name', 'description', 'api_url', 'instance_host', 'instance_port')


class RedisServerAdmin(admin.ModelAdmin): # pylint: disable=missing-docstring
list_display = ('name', 'description', 'instance_host', 'instance_port', 'instance_db')


class LoadBalancingServerAdmin(admin.ModelAdmin): # pylint: disable=missing-docstring
list_display = ('domain', 'ssh_username')

Expand All @@ -132,5 +137,6 @@ def has_changes(self, obj):
admin.site.register(MongoDBServer, MongoDBServerAdmin)
admin.site.register(MongoDBReplicaSet, MongoDBReplicaSetAdmin)
admin.site.register(RabbitMQServer, RabbitMQServerAdmin)
admin.site.register(RedisServer, RedisServerAdmin)
admin.site.register(LoadBalancingServer, LoadBalancingServerAdmin)
admin.site.register(OpenEdXDeployment, OpenEdXDeploymentAdmin)
18 changes: 18 additions & 0 deletions instance/migrations/0142_add_cache_db_to_openedxinstance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.24 on 2021-08-13 08:24

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('instance', '0141_features_to_features_extras'),
]

operations = [
migrations.AddField(
model_name='openedxinstance',
name='cache_db',
field=models.CharField(choices=[('redis', 'redis'), ('rabbit_mq', 'rabbit_mq')], default='rabbit_mq', max_length=9),
),
]
58 changes: 58 additions & 0 deletions instance/migrations/0143_add_redis_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Generated by Django 2.2.24 on 2021-08-13 08:53

from django.db import migrations, models
import django.db.models.deletion
import django_extensions.db.fields
import instance.models.mixins.redis
import instance.models.utils


class Migration(migrations.Migration):

dependencies = [
('instance', '0142_add_cache_db_to_openedxinstance'),
]

operations = [
migrations.CreateModel(
name='RedisServer',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, verbose_name='created')),
('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name='modified')),
('name', models.CharField(max_length=250)),
('description', models.CharField(blank=True, max_length=250)),
('admin_username', models.CharField(max_length=64)),
('admin_password', models.CharField(max_length=128)),
('instance_host', models.CharField(max_length=128)),
('instance_port', models.PositiveIntegerField(default=5671)),
('instance_db', models.PositiveIntegerField(default=0)),
('use_ssl_connections', models.BooleanField(default=True)),
('accepts_new_clients', models.BooleanField(default=False)),
],
options={
'verbose_name': 'Redis Server',
},
bases=(instance.models.utils.ValidateModelMixin, models.Model),
),
migrations.AddField(
model_name='openedxinstance',
name='redis_password',
field=models.CharField(max_length=64, null=True),
),
migrations.AddField(
model_name='openedxinstance',
name='redis_provisioned',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='openedxinstance',
name='redis_username',
field=models.CharField(max_length=32, null=True),
),
migrations.AddField(
model_name='openedxinstance',
name='redis_server',
field=models.ForeignKey(blank=True, default=instance.models.mixins.redis.select_random_redis_server, null=True, on_delete=django.db.models.deletion.PROTECT, to='instance.RedisServer'),
),
]
37 changes: 37 additions & 0 deletions instance/migrations/0144_make_redis_username_unique.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Generated by Django 2.2.24 on 2021-08-13 08:53

from django.db import migrations, models
import django.db.models.deletion
import django_extensions.db.fields
import instance.models.mixins.redis
import instance.models.utils


def generate_credentials(apps, schema_editor):
Model = apps.get_model('instance', 'OpenEdXInstance')
for row in Model.objects.all():
row.redis_username = instance.models.mixins.redis.random_username()
row.redis_password = instance.models.mixins.redis.random_password()
row.save(update_fields=['redis_username', 'redis_password'])



class Migration(migrations.Migration):

dependencies = [
('instance', '0143_add_redis_server'),
]

operations = [
migrations.RunPython(generate_credentials, reverse_code=migrations.RunPython.noop),
migrations.AlterField(
model_name='openedxinstance',
name='redis_password',
field=models.CharField(default=instance.models.mixins.redis.random_password, max_length=64),
),
migrations.AlterField(
model_name='openedxinstance',
name='redis_username',
field=models.CharField(default=instance.models.mixins.redis.random_username, max_length=32, unique=True),
),
]
59 changes: 56 additions & 3 deletions instance/models/mixins/openedx_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@
import hashlib
import hmac

from django.db import models
import MySQLdb as mysql
import yaml

from instance.models.mixins.database import MySQLInstanceMixin, MongoDBInstanceMixin
from instance.models.mixins.rabbitmq import RabbitMQInstanceMixin
from instance.models.mixins.redis import RedisInstanceMixin

FORUM_MONGO_REPLICA_SET_URL = (
"mongodb://{{ FORUM_MONGO_USER }}:{{ FORUM_MONGO_PASSWORD }}@"
Expand All @@ -41,13 +43,25 @@
# Classes #####################################################################


class OpenEdXDatabaseMixin(MySQLInstanceMixin, MongoDBInstanceMixin, RabbitMQInstanceMixin):
class OpenEdXDatabaseMixin(MySQLInstanceMixin, MongoDBInstanceMixin, RabbitMQInstanceMixin, RedisInstanceMixin):
"""
Mixin that provides functionality required for the database backends that an
OpenEdX Instance uses


TODO: ElasticSearch?
"""

REDIS = "redis"
RABBIT_MQ = "rabbit_mq"
CACHE_DBS = (
(REDIS, REDIS),
(RABBIT_MQ, RABBIT_MQ),
)

# TODO: When every server is using REDIS, use REDIS as default
cache_db = models.CharField(max_length=9, choices=CACHE_DBS, default=RABBIT_MQ)

class Meta:
abstract = True

Expand Down Expand Up @@ -475,6 +489,39 @@ def _get_rabbitmq_settings(self):
"EDXAPP_CELERY_BROKER_USE_SSL": True
}

def _get_redis_settings(self):
"""
Return dictionary of Redis settings
"""

redis_hostname = "{}:{}".format(
self.redis_server.instance_host,
self.redis_server.instance_port
)

redis_transport_options = {
"CELERY_BROKER_TRANSPORT_OPTIONS": {
"global_keyprefix": self.redis_key_prefix
}
}

return {
"EDXAPP_REDIS_HOSTNAME": redis_hostname,
"EDXAPP_CELERY_BROKER_TRANSPORT": "redis",
"EDXAPP_CELERY_USER": self.redis_username,
"EDXAPP_CELERY_PASSWORD": self.redis_password,
"EDXAPP_CELERY_BROKER_HOSTNAME": redis_hostname,
"EDXAPP_CELERY_BROKER_VHOST": str(self.redis_server.instance_db),
"EDXAPP_CELERY_BROKER_USE_SSL": self.redis_server.use_ssl_connections,

"EDXAPP_LMS_ENV_EXTRA": {
**redis_transport_options
},
"EDXAPP_CMS_ENV_EXTRA": {
**redis_transport_options
}
}

def get_database_settings(self):
"""
Get configuration_database_settings to pass to a new AppServer
Expand All @@ -489,7 +536,13 @@ def get_database_settings(self):
if self.mongodb_replica_set or self.mongodb_server:
new_settings.update(self._get_mongo_settings())

# RabbitMQ:
new_settings.update(self._get_rabbitmq_settings())
if self.cache_db == self.REDIS:
# Redis:
new_settings.update(self._get_redis_settings())
elif self.cache_db == self.RABBIT_MQ:
# RabbitMQ:
new_settings.update(self._get_rabbitmq_settings())
else:
raise NotImplementedError(f"{self.cache_db} does not load any cache DB settings")

return yaml.dump(new_settings, default_flow_style=False)
Loading