Files
freezer-api/freezer_api/cmd/manage.py
Saad Zaher 528f3234e9 Allow using different database backends
Abstract storage configuration section to load different database
backend drivers. so we can keep supporting elasticsearch for
a while after moving to oslo.db also this will help in adding
API v2 as the elasticsearch driver will be changed.

Each db driver will register it's configuration on the run time.

Change-Id: Ie861e569f4add783c6f580cd0c08ed0fb3440151
2017-03-15 19:20:56 +00:00

363 lines
12 KiB
Python

"""
(c) Copyright 2015-2016 Hewlett-Packard Enterprise Development Company, L.P.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import json
import sys
import elasticsearch
from oslo_config import cfg
from oslo_log import log
from freezer_api import __version__ as FREEZER_API_VERSION
from freezer_api.common import config
from freezer_api.common import db_mappings
from freezer_api.storage import driver
CONF = cfg.CONF
LOG = log.getLogger(__name__)
DEFAULT_ES_SERVER_PORT = 9200
DEFAULT_INDEX = 'freezer'
DEFAULT_REPLICAS = 0
def add_db_opts(subparser):
parser = subparser.add_parser('db')
parser.add_argument(
'options',
choices=['sync', 'update', 'remove', 'show', 'update-settings'],
help='Create/update/delete freezer-api mappings in elk'
)
def parse_config(mapping_choices):
DB_INIT = [
cfg.SubCommandOpt('db',
dest='db',
title='DB Options',
handler=add_db_opts
),
cfg.StrOpt('host',
default='127.0.0.1',
dest='host',
help='The DB host address[:port], default "127.0.0.1"'),
cfg.PortOpt('port',
default=9200,
dest='port',
help='The DB server port (default: {0})'.
format(DEFAULT_ES_SERVER_PORT)
),
cfg.StrOpt('mapping',
dest='select_mapping',
default='',
short='m',
help='Specific mapping to upload. Valid choices: {0}'
.format(','.join(mapping_choices))),
cfg.StrOpt('index',
dest='index',
short='i',
default=DEFAULT_INDEX,
help='The DB index (default "{0}")'.format(DEFAULT_INDEX)
),
cfg.BoolOpt('yes',
short='y',
dest='yes',
default=False,
help='Automatic confirmation to update mappings and '
'number-of-replicas.'),
cfg.BoolOpt('erase',
short='e',
dest='erase',
default=False,
help='Enable index deletion in case mapping update fails '
'due to incompatible changes'
),
cfg.StrOpt('test-only',
short='t',
dest='test_only',
default=False,
help='Test the validity of the mappings, but take no action'
)
]
driver.register_storage_opts()
CONF.register_cli_opts(DB_INIT)
log.register_options(CONF)
default_config_files = cfg.find_config_files('freezer', 'freezer-api')
CONF(args=sys.argv[1:],
project='freezer-api',
default_config_files=default_config_files,
version=FREEZER_API_VERSION
)
class ElasticSearchManager(object):
"""
Managing ElasticSearch mappings operations
Sync: create mappings
Update: Update mappings
remove: deletes the mappings
show: print out all the mappings
"""
def __init__(self, mappings):
self.mappings = mappings.copy()
grp = cfg.OptGroup(CONF.storage.backend)
CONF.register_group(grp)
backend_opts = driver._get_elastic_opts(backend=CONF.storage.backend)
CONF.register_opts(backend_opts[CONF.storage.backend],
group=CONF.storage.backend)
self.conf = CONF.get(CONF.storage.backend)
self.index = self.conf.index or DEFAULT_INDEX
# initialize elk
opts = dict(self.conf.items())
self.elk = elasticsearch.Elasticsearch(**opts)
# check if the cluster is up or not !
if not self.elk.ping():
raise Exception('ElasticSearch cluster is not available. '
'Cannot ping it')
# clear the index cache
try:
self.elk.indices.clear_cache(index=self.index)
except Exception as e:
LOG.warning(e)
def _check_index_exists(self, index):
LOG.info('check if index: {0} exists or not'.format(index))
try:
return self.elk.indices.exists(index=index)
except elasticsearch.TransportError:
raise
def _check_mapping_exists(self, mappings):
LOG.info('check if mappings: {0} exists or not'.format(mappings))
return self.elk.indices.exists_type(index=self.index,
doc_type=mappings)
def get_required_mappings(self):
"""
This function checks if the user chooses a certain mappings or not.
If the user has chosen a certain mappings it will return these mappings
only If not it will return all mappings to be updated
:return:
"""
# check if the user asked to update only one mapping ( -m is provided )
mappings = {}
if CONF.select_mapping:
if CONF.select_mapping not in self.mappings.keys():
raise Exception(
'Selected mappings {0} does not exists. Please, choose '
'one of {1}'.format(CONF.select_mapping,
self.mappings.keys()
)
)
mappings[CONF.select_mapping] = \
self.mappings.get(CONF.select_mapping)
else:
mappings = self.mappings
return mappings
def db_sync(self):
"""
Create or update elasticsearch db mappings
steps:
1) check if mappings exists
2) remove mapping if erase is passed
3) update mappings if - y is passed
4) if update failed ask for permission to remove old mappings
5) try to update again
6) if update succeeded exit :)
:return:
"""
# check if erase provided remove mappings first
if CONF.erase:
self.remove_mappings()
# check if index does not exists create it
if not self._check_index_exists(self.index):
self._create_index()
_mappings = self.get_required_mappings()
# create/update one by one
for doc_type, body in _mappings.items():
check = self.create_one_mapping(doc_type, body)
if check:
print("Creating or Updating {0} is {1}".format(
doc_type, check.get('acknowledged')))
else:
print("Couldn't update {0}. Request returned {1}".format(
doc_type, check.get('acknowledged')))
def _create_index(self):
"""
Create the index that will allow us to put the mappings under it
:return: {u'acknowledged': True} if success or None if index exists
"""
if not self._check_index_exists(index=self.index):
body = {
'number_of_replicas':
self.conf.number_of_replicas or DEFAULT_REPLICAS
}
return self.elk.indices.create(index=self.index, body=body)
def delete_index(self):
return self.elk.indices.delete(index=self.index)
def create_one_mapping(self, doc_type, body):
"""
Create one document type and update its mappings
:param doc_type: the document type to be created jobs, clients, backups
:param body: the structure of the document
:return: dict
"""
# check if doc_type exists or not
if self._check_mapping_exists(doc_type):
do_update = self.prompt(
'[[[ {0} ]]] already exists in index => {1}'
' <= Do you want to update it ? (y/n) '.format(doc_type,
self.index)
)
if do_update:
# Call elasticsearch library and put the mappings
return self.elk.indices.put_mapping(doc_type=doc_type,
body=body,
index=self.index
)
else:
return {'acknowledged': False}
return self.elk.indices.put_mapping(doc_type=doc_type, body=body,
index=self.index)
def remove_one_mapping(self, doc_type):
"""
Removes one mapping at a time
:param doc_type: document type to be removed
:return: dict
"""
LOG.info('Removing mapping {0} from index {1}'.format(doc_type,
self.index))
try:
return self.elk.indices.delete_mapping(self.index,
doc_type=doc_type)
except Exception:
raise
def remove_mappings(self):
"""
Remove mappings from elasticsearch
:return: dict
"""
# check if index doesn't exist return
if not self._check_index_exists(index=self.index):
print("Index {0} doesn't exists.".format(self.index))
return
# remove mappings
_mappings = self.get_required_mappings()
for doc_type, body in _mappings.items():
check = self.remove_one_mapping(doc_type)
if not check:
print("Deleting {0} is failed".format(doc_type))
elif check:
print("Deleting {0} is {1}".format(
doc_type, check.get('acknowledged')))
else:
print("Couldn't delete {0}. Request returned {1}".format(
doc_type, check.get('acknowledged')))
del_index = self.prompt('Do you want to remove index as well ? (y/n) ')
if del_index:
self.delete_index()
def update_mappings(self):
"""
Update mappings
:return: dict
"""
CONF.yes = True
return self.db_sync()
def show_mappings(self):
"""
Print existing mappings in an index
:return: dict
"""
# check if index doesn't exist return
if not self._check_index_exists(index=self.index):
print("Index {0} doesn't exists.".format(self.index))
return
print(json.dumps(self.elk.indices.get_mapping(index=self.index)))
def update_settings(self):
"""
Update number of replicas
:return: dict
"""
body = {
'number_of_replicas':
self.conf.number_of_replicas or DEFAULT_REPLICAS
}
return self.elk.indices.put_settings(body=body, index=self.index)
def prompt(self, message):
"""
Helper function that is being used to ask the user for confirmation
:param message: Message to be printed (To ask the user to confirm ...)
:return: True or False
"""
if CONF.yes:
return CONF.yes
while True:
ans = raw_input(message)
if ans.lower() == 'y':
return True
elif ans.lower() == 'n':
return False
def main():
mappings = db_mappings.get_mappings()
parse_config(mapping_choices=mappings.keys())
config.setup_logging()
if not CONF.db:
CONF.print_help()
sys.exit(0)
try:
elk = ElasticSearchManager(mappings=mappings)
if CONF.db.options.lower() == 'sync':
elk.db_sync()
elif CONF.db.options.lower() == 'update':
elk.update_mappings()
elif CONF.db.options.lower() == 'remove':
elk.remove_mappings()
elif CONF.db.options.lower() == 'show':
elk.show_mappings()
elif CONF.db.options.lower() == 'update-settings':
elk.update_settings()
else:
raise Exception('Option {0} not found !'.format(CONF.db.options))
except Exception as e:
LOG.error(e)
print(e)
if __name__ == '__main__':
sys.exit(main())