Commit bc2cb4ad by Clinton Blackburn

Updated install_es_indexes management command

We should be referencing aliases from our code, not indices. This change updates the management command to create an index with a unique name and assign it an alias. Documentation has been updated accordingly. Additionally, the code creating the alias/index has been moved to a class that is now shared with the tests to avoid code duplication and ensure tests are run under similar conditions (e.g. alias pointing to index) to production.
parent aae7c64e
......@@ -3,7 +3,7 @@ import logging
from django.conf import settings
from elasticsearch import Elasticsearch
from course_discovery.apps.courses.config import COURSES_INDEX_CONFIG
from course_discovery.apps.courses.utils import ElasticsearchUtils
logger = logging.getLogger(__name__)
......@@ -23,17 +23,23 @@ class ElasticsearchTestMixin(object):
def reset_index(self):
""" Deletes and re-creates the Elasticsearch index. """
self.delete_index(self.index)
ElasticsearchUtils.create_alias_and_index(self.es, self.index)
index = self.index
def delete_index(self, index):
"""
Deletes an index.
Args:
index (str): Name of index to delete
Returns:
None
"""
logger.info('Deleting index [%s]...', index)
self.es.indices.delete(index=index, ignore=404) # pylint: disable=unexpected-keyword-arg
logger.info('...index deleted.')
logger.info('Recreating index [%s]...', index)
self.es.indices.create(index=index, body=COURSES_INDEX_CONFIG)
logger.info('...done!')
def refresh_index(self):
"""
Refreshes an index.
......
......@@ -2,31 +2,22 @@ import logging
from django.conf import settings
from django.core.management import BaseCommand
from elasticsearch import Elasticsearch
from elasticsearch import Elasticsearch, TransportError
from course_discovery.apps.courses.config import COURSES_INDEX_CONFIG
from course_discovery.apps.courses.utils import ElasticsearchUtils
logger = logging.getLogger(__name__)
class Command(BaseCommand):
help = 'Install any required elasticsearch indexes'
help = 'Install any required Elasticsearch indexes'
def handle(self, *args, **options):
host = settings.ELASTICSEARCH['host']
index = settings.ELASTICSEARCH['index']
alias = settings.ELASTICSEARCH['index']
logger.info('Attempting to establish initial connection to Elasticsearch host [%s]...', host)
es = Elasticsearch(host, sniff_on_start=True)
logger.info('...success!')
logger.info('Making sure index [%s] exists...', index)
try:
es.indices.create(index=index, body=COURSES_INDEX_CONFIG)
logger.info('...index created.')
except TransportError as e:
if e.status_code == 400:
logger.info('...index already exists.')
else:
raise
ElasticsearchUtils.create_alias_and_index(es, alias)
import mock
from django.conf import settings
from django.test import TestCase
from django.core.management import call_command
from elasticsearch import TransportError
from elasticsearch.client import IndicesClient
from testfixtures import LogCapture
from django.test import TestCase
from course_discovery.apps.core.tests.mixins import ElasticsearchTestMixin
LOGGER_NAME = 'courses.management.commands.install_es_indexes'
class CourseInstallEsIndexes(ElasticsearchTestMixin, TestCase):
def test_ready_create_index(self):
""" Verify the app does not setup a new Elasticsearch index if one exists already. """
host = settings.ELASTICSEARCH['host']
class InstallEsIndexesCommandTests(ElasticsearchTestMixin, TestCase):
def test_create_index(self):
""" Verify the app sets the alias and creates a new index. """
index = settings.ELASTICSEARCH['index']
# Delete the index
self.es.indices.delete(index=index, ignore=404) # pylint: disable=unexpected-keyword-arg
self.assertFalse(self.es.indices.exists(index=index))
with LogCapture(LOGGER_NAME) as l:
call_command('install_es_indexes')
call_command('install_es_indexes')
# Verify the index was created
self.assertTrue(self.es.indices.exists(index=index))
l.check(
(LOGGER_NAME, 'INFO',
'Attempting to establish initial connection to Elasticsearch host [{}]...'.format(host)),
(LOGGER_NAME, 'INFO', '...success!'),
(LOGGER_NAME, 'INFO', 'Making sure index [{}] exists...'.format(index)),
(LOGGER_NAME, 'INFO', '...index created.')
)
# Verify the index was created
self.assertTrue(self.es.indices.exists(index=index))
def test_ready_index_exists(self):
""" Verify the app does not setup a new Elasticsearch index if one exists already. """
host = settings.ELASTICSEARCH['host']
def test_alias_exists(self):
""" Verify the app does not setup a new Elasticsearch index if the alias is already set. """
index = settings.ELASTICSEARCH['index']
# Verify the index exists
self.assertTrue(self.es.indices.exists(index=index))
with mock.patch.object(IndicesClient, 'create') as mock_create:
mock_create.side_effect = TransportError(400)
with LogCapture(LOGGER_NAME) as l:
# This call should NOT raise an exception.
call_command('install_es_indexes')
call_command('install_es_indexes')
# Verify the index still exists
self.assertTrue(self.es.indices.exists(index=index))
l.check(
(LOGGER_NAME, 'INFO',
'Attempting to establish initial connection to Elasticsearch host [{}]...'.format(host)),
(LOGGER_NAME, 'INFO', '...success!'),
(LOGGER_NAME, 'INFO', 'Making sure index [{}] exists...'.format(index)),
(LOGGER_NAME, 'INFO', '...index already exists.')
)
def test_ready_es_failure(self):
""" Verify Elasticsearch errors are raised if the app fails to create the index. """
with mock.patch.object(IndicesClient, 'create') as mock_create:
mock_create.side_effect = TransportError(500)
with self.assertRaises(TransportError):
call_command('install_es_indexes')
import datetime
import logging
from course_discovery.apps.courses.config import COURSES_INDEX_CONFIG
logger = logging.getLogger(__name__)
class ElasticsearchUtils(object):
@classmethod
def create_alias_and_index(cls, es, alias):
logger.info('Making sure alias [%s] exists...', alias)
if es.indices.exists_alias(name=alias):
# If the alias exists, and points to an open index, we are all set.
logger.info('...alias exists.')
else:
# Create an index with a unique (timestamped) name
timestamp = datetime.datetime.utcnow().strftime("%Y%m%d%H%M%S")
index = '{alias}_{timestamp}'.format(alias=alias, timestamp=timestamp)
es.indices.create(index=index, body=COURSES_INDEX_CONFIG)
logger.info('...index [%s] created.', index)
# Point the alias to the new index
body = {
'actions': [
{'remove': {'alias': alias, 'index': '*'}},
{'add': {'alias': alias, 'index': index}},
]
}
es.indices.update_aliases(body)
logger.info('...alias updated.')
Elasticsearch
=============
This service uses Elasticsearch to power the course catalog functionality. This allows users to search courses by
various criteria related to data collected from the E-Commerce Service (Otto) and other potential data sources.
The service is configured to use the `course_discovery` index by default. If you'd like to change the index, or the
URL of the Elasticsearch service, update the `ELASTICSEARCH` setting.
Elasticsearch has a feature called `aliases`<https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html>.
This feature allows indices to be referenced by an alias. For example, the timestamped course_discovery_20160101113005
index could be assigned the alias course_discovery. It is common practice to reference aliases in code, rather than
indices, to allow for index swapping, reindex, and other maintenance without affecting service uptime. We recommend
following this practice.
Creating an index and alias
---------------------------
The `install_es_indexes` management command should be used when initializing a new alias and index. This command will
check to see if the alias exists, and is linked to an open index. If that check is true, the command will exit
successfully. If that check fails, a new index with a timestamped name (e.g. course_discovery_20160101113005) will be
created; and, the alias will be assigned to the new index.
.. code-block:: bash
$ ./manage.py install_es_indexes
......@@ -4,13 +4,14 @@
contain the root `toctree` directive.
Course Discovery Service
===============================
========================
A service for serving course discovery and marketing information to partners, mobile, and edX.org
.. toctree::
:maxdepth: 2
getting_started
elasticsearch
testing
features
internationalization
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