You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
748 lines
32 KiB
748 lines
32 KiB
# Copyright (C) 2005-2024 Splunk Inc. All Rights Reserved.
|
|
|
|
"""
|
|
A script that allows for the following:
|
|
- Move objects from kvstore collection(s) to a file on disk
|
|
- Restore objects from file on disk to kvstore:
|
|
- by replacing existing kv store data
|
|
- by appending to existing kv store data if possible
|
|
"""
|
|
import getpass
|
|
import json
|
|
import logging
|
|
import os
|
|
import sys
|
|
from optparse import OptionParser, OptionGroup
|
|
|
|
import splunk.rest as rest
|
|
from splunk import AuthenticationFailed, auth
|
|
from splunk.clilib.bundle_paths import make_splunkhome_path
|
|
|
|
sys.path.append(make_splunkhome_path(['etc', 'apps', 'SA-ITOA', 'lib']))
|
|
import itsi_path
|
|
from itsi_py3 import _
|
|
import itsi_py3
|
|
|
|
from ITOA.itoa_common import is_valid_str
|
|
from ITOA.setup_logging import getLogger, setup_logging
|
|
from ITOA.storage.itoa_storage import ITOAStorage
|
|
from ITOA.version_check import VersionCheck
|
|
from itsi.itsi_utils import ITOAInterfaceUtils
|
|
from itsi.objects.itsi_service import ItsiService
|
|
from itsi.upgrade.timezones import migrate_timezones
|
|
from migration.migration import MigrationBaseMethod
|
|
from migration.supervisor import MigrationSupervisor
|
|
|
|
KVSTORE_JSON_SPLUNKD_HOST_PATH = 'https://localhost'
|
|
KVSTORE_JSON_SPLUNKD_PORT = '8089'
|
|
KVSTORE_JSON_SPLUNKD_USER = 'admin'
|
|
KVSTORE_JSON_BACKUP_BASEDIR = os.path.join(os.getcwd(), 'backup')
|
|
DEFAULT_DUPNAME_TAG = '_dup_from_restore'
|
|
|
|
ITSI_VERSION_2_0 = '2.0.0'
|
|
ITSI_VERSION_2_3 = '2.3.0'
|
|
|
|
MAX_RETRIES = 3
|
|
|
|
INVALID_RESPONSE_MSG = _('Must enter the correct response value or contact your Splunk administrator.')
|
|
|
|
|
|
def is_valid_response(var):
|
|
is_valid_response = False
|
|
valid_response = ['', 'y', 'n', 'yes', 'no']
|
|
if isinstance(var, itsi_py3.string_type):
|
|
if var.strip().lower() in valid_response:
|
|
is_valid_response = True
|
|
return is_valid_response
|
|
|
|
|
|
def is_true(var):
|
|
"""
|
|
utility method to check if value of var implies true
|
|
@param var: the variable under question
|
|
@param type: string, bool, number types
|
|
@return False by default, True if it implies as such
|
|
"""
|
|
is_true = False
|
|
if isinstance(var, itsi_py3.string_type):
|
|
# user could enter true/yes/y or merely press the enter key which is an empty string
|
|
if var.strip().lower() == 'true' or var.strip().lower().startswith('yes') or len(
|
|
var.strip()) == 0 or var.strip().lower() == 'y':
|
|
is_true = True
|
|
elif isinstance(var, bool):
|
|
is_true = var
|
|
elif isinstance(var, (int, float, complex)):
|
|
if int(var) > 0:
|
|
is_true = True
|
|
return is_true
|
|
|
|
|
|
def get_username_password():
|
|
"""
|
|
An interactive method that prompts a user for username and password.
|
|
@return username
|
|
@return password
|
|
@rtype str
|
|
"""
|
|
if options.username is None:
|
|
options.username = input('>> Enter splunk username or press enter to use "{}" > '
|
|
.format(KVSTORE_JSON_SPLUNKD_USER))
|
|
if len(options.username.strip()) == 0:
|
|
options.username = KVSTORE_JSON_SPLUNKD_USER
|
|
if options.password is None:
|
|
options.password = getpass.getpass(prompt='>> Enter splunk password for "{}" > '.format(options.username))
|
|
return options.username, options.password
|
|
|
|
|
|
def is_invalid_string(str_obj):
|
|
"""
|
|
Method that checks and sees if var is a valid string
|
|
Invalid string is one which is of None type or an empty str
|
|
@param str_obj string object to check
|
|
@param type str
|
|
|
|
@return True if invalid option; False if valid
|
|
"""
|
|
if str_obj is None or len(str_obj.strip()) == 0:
|
|
return True
|
|
return False
|
|
|
|
|
|
def do_interactive():
|
|
"""
|
|
Method that does interactive data collection
|
|
@return collected_data
|
|
{
|
|
'username': <str>,
|
|
'password':<str>,
|
|
'file_path':<str>,
|
|
'import_data':<bool>,
|
|
'br_version':<str>,
|
|
'persist_data':'<bool>,
|
|
'dupname_tag':'<str>'
|
|
}
|
|
@rtype dict
|
|
"""
|
|
# get the splunkd port
|
|
if options.splunkdport is not None:
|
|
try:
|
|
int(options.splunkdport)
|
|
except ValueError:
|
|
options.splunkdport = None
|
|
print('Invalid port number. Try again.')
|
|
retries = MAX_RETRIES
|
|
while not options.splunkdport and retries:
|
|
entered = input(
|
|
'>> Enter the splunkd port number OR press the enter key to use [{}] > '.format(KVSTORE_JSON_SPLUNKD_PORT))
|
|
if len(entered.strip()) == 0:
|
|
options.splunkdport = KVSTORE_JSON_SPLUNKD_PORT
|
|
else:
|
|
try:
|
|
int(entered)
|
|
options.splunkdport = entered
|
|
except ValueError:
|
|
print('Invalid port number. Try again.')
|
|
retries -= 1
|
|
if not retries:
|
|
print('You have reached the maximum number of retries. Run the script again.')
|
|
sys.exit(1)
|
|
|
|
# get the username and password
|
|
sessionkey = None
|
|
retries = MAX_RETRIES
|
|
hostpath = KVSTORE_JSON_SPLUNKD_HOST_PATH + ':' + options.splunkdport
|
|
while not sessionkey and retries:
|
|
username, password = get_username_password()
|
|
try:
|
|
sessionkey = auth.getSessionKey(username, password, hostpath)
|
|
except Exception as e:
|
|
print('Encountered an error when logging in - ' + str(e))
|
|
print('Try again.')
|
|
retries -= 1
|
|
options.password = None
|
|
if retries == 0:
|
|
print(
|
|
'Try running the command again with proper credentials or contact your Splunk administrator for permissions.')
|
|
sys.exit(1)
|
|
|
|
if options.mode == '3' or options.mode == '4':
|
|
return sessionkey
|
|
|
|
if options.mode == '5':
|
|
skip_local_failure = input('>> Do you wish to skip local failures? [y|n|enter]: > ')
|
|
if is_valid_response(skip_local_failure):
|
|
options.skip_local_failure = is_true(skip_local_failure)
|
|
return sessionkey
|
|
|
|
# get file_path
|
|
if options.file_path is None:
|
|
msg = _(
|
|
'>> Enter full path of backup or restore directory. Note: It could be a file, if you are importing from ITSI 1.2.0'
|
|
'\n Press enter to use your current working directory {} > ').format(KVSTORE_JSON_BACKUP_BASEDIR)
|
|
options.file_path = input(msg)
|
|
if is_true(options.file_path):
|
|
options.file_path = KVSTORE_JSON_BACKUP_BASEDIR
|
|
else:
|
|
print('You entered "{}" for backup/restore. Proceeding.'.format(options.file_path))
|
|
|
|
backup_data_option_retries = MAX_RETRIES
|
|
while backup_data_option_retries:
|
|
# check if user wants to import data from disk to kv store
|
|
backup_data_from_kv = input('>> Do you wish to back up data from KV Store OR restore to KV store.'
|
|
'\n Press [y|yes|enter] to backup, [n|no] to restore? > ')
|
|
include_conf_files = input('>> Do you wish to include .conf files? [y|n|enter]: > ')
|
|
if is_valid_response(include_conf_files):
|
|
options.conf_file = is_true(include_conf_files)
|
|
else:
|
|
print(' Must enter y or n as your answer.')
|
|
break
|
|
if is_valid_response(backup_data_from_kv):
|
|
if is_true(backup_data_from_kv):
|
|
options.import_data = False
|
|
# set the br_version for backups to the current app version
|
|
options.br_version = ITOAInterfaceUtils.get_app_version(sessionkey, 'itsi', 'nobody')
|
|
print('You have indicated backup of data from current ITSI version {}. Proceeding.'.format(
|
|
options.br_version))
|
|
else:
|
|
options.import_data = True
|
|
print('You would like to restore data from disk to KV Store. Proceeding.')
|
|
# check if user would like to do a partial restore
|
|
partial_restore_option_retries = MAX_RETRIES
|
|
while partial_restore_option_retries:
|
|
partial_restore = input('>> Do you wish to perform a partial restore? [y|n|enter]: > ')
|
|
if is_valid_response(partial_restore):
|
|
if is_true(partial_restore):
|
|
persist_data_option_retries = MAX_RETRIES
|
|
while persist_data_option_retries:
|
|
persistent_mode = input('>> Partial restore will only run in persistent mode.\n '
|
|
'Do you wish to continue with the partial restore in persistent mode? [y|n|enter]: > ')
|
|
if is_valid_response(persistent_mode):
|
|
if is_true(persistent_mode):
|
|
options.persist_data = True
|
|
rename_dup = input(
|
|
'>> Do you wish to rename any duplicated entries as part of the restore process?\n'
|
|
'You may need to resolve any dependencies issues from the UI after the restore. [y|n|enter]: > ')
|
|
if (is_true(rename_dup)):
|
|
dupname_tag = input(
|
|
'>> Enter a tag to be appended to the duplicates entries.\n'
|
|
'By default, the tag is _dup_from_restore_<epoch_timestamp>.\n'
|
|
'<epoch_timestamp> is auto-generated by the system.\n'
|
|
'Ex: service_1 --> service_1_dup_from_restore_1234567 > ')
|
|
if (len(dupname_tag.strip()) == 0):
|
|
dupname_tag = DEFAULT_DUPNAME_TAG
|
|
|
|
options.dupname_tag = dupname_tag
|
|
else:
|
|
print(
|
|
' Duplicated names or dependencies will not resolve automatically. Restore may fail.')
|
|
else:
|
|
print(
|
|
' Partial restore in non-persistent mode will erase all data in KV store. Exiting.')
|
|
sys.exit(1)
|
|
break
|
|
else:
|
|
print(' Must enter y or n as your answer.')
|
|
persist_data_option_retries -= 1
|
|
if persist_data_option_retries == 0:
|
|
print(INVALID_RESPONSE_MSG)
|
|
sys.exit(1)
|
|
else:
|
|
# check if user wants to persist existing data in kv store in the midst of backup
|
|
persist_data_option_retries = MAX_RETRIES
|
|
while persist_data_option_retries:
|
|
persist_data_in_kv = input(
|
|
'>> Do you wish to persist the existing data in KV Store during the import [y|n|enter]: > ')
|
|
if is_valid_response(persist_data_in_kv):
|
|
if is_true(persist_data_in_kv):
|
|
options.persist_data = True
|
|
|
|
rename_dup = input(
|
|
'>> Do you wish to rename any duplicated entries as part of the restore process?\n'
|
|
'You may need to resolve any dependencies issues from the UI after the restore. [y|n|enter]: > ')
|
|
if (is_true(rename_dup)):
|
|
dupname_tag = input(
|
|
'>> Enter a tag to be appended to the duplicates entries.\n'
|
|
'By default, the tag is _dup_from_restore_<epoch_timestamp>.\n'
|
|
'<epoch_timestamp> is auto-generated by the system.\n'
|
|
'Ex: service_1 --> service_1_dup_from_restore_1234567 > ')
|
|
if (len(dupname_tag.strip()) == 0):
|
|
dupname_tag = DEFAULT_DUPNAME_TAG
|
|
|
|
options.dupname_tag = dupname_tag
|
|
else:
|
|
print(
|
|
' Duplicated names or dependencies will not resolve automatically. Restore may fail.')
|
|
else:
|
|
options.persist_data = False
|
|
break
|
|
else:
|
|
print(' Must enter y or n as your answer.')
|
|
persist_data_option_retries -= 1
|
|
if persist_data_option_retries == 0:
|
|
print(INVALID_RESPONSE_MSG)
|
|
sys.exit(1)
|
|
break
|
|
else:
|
|
print(' Must enter y or n as your answer.')
|
|
partial_restore_option_retries -= 1
|
|
if partial_restore_option_retries == 0:
|
|
print(INVALID_RESPONSE_MSG)
|
|
sys.exit(1)
|
|
break
|
|
else:
|
|
print(' Must enter y or n as your answer.')
|
|
backup_data_option_retries -= 1
|
|
if backup_data_option_retries == 0:
|
|
print(INVALID_RESPONSE_MSG)
|
|
sys.exit(1)
|
|
return sessionkey
|
|
|
|
|
|
def do_interactive_post_init(kvStoreBackupRestore_worker):
|
|
# Initializing KVStoreBackupRestore extracts information from backup folder like app version info of the backup
|
|
# So do version check/prompting for app version of backup after init
|
|
if options.import_data:
|
|
version_validated = False
|
|
options.br_version = kvStoreBackupRestore_worker.get_app_version_of_backup()
|
|
while not version_validated:
|
|
if options.br_version is None:
|
|
if options.no_prompt:
|
|
print('Could not determine ITSI version for the backup data in the path specified. Must '
|
|
+ 'specify the ITSI version for the backup data.')
|
|
sys.exit(1)
|
|
else:
|
|
msg = _('>> The ITSI version on which the backup data was collected is unavailable. '
|
|
'Enter the ITSI version on which the backup data was collected, to restore to. '
|
|
'Ex: \'2.3.0\', \'2.3.1\', \'2.4.0\' >')
|
|
options.br_version = input(msg)
|
|
else:
|
|
print('ITSI version on which the backup data was collected is indicated to '
|
|
+ 'be {0}. Restore will treat the backup data as being from this ITSI version.'.format(
|
|
options.br_version))
|
|
if len(options.br_version.strip()) != 0 and VersionCheck.validate_version(options.br_version):
|
|
version_validated = True
|
|
kvStoreBackupRestore_worker.set_app_version_of_backup(options.br_version)
|
|
if VersionCheck.compare(options.br_version, ITSI_VERSION_2_3) < 0:
|
|
print('Restoring from backup file with version belows 2.3.0 is not supported, existing...')
|
|
sys.exit(1)
|
|
else:
|
|
options.br_version = None
|
|
print('Invalid version found. Must enter a valid version string.')
|
|
|
|
|
|
def do_kpi_operations(splunkd_session_key):
|
|
"""
|
|
Performs KPI CRUD operations for mode 2 of the tool
|
|
|
|
@param splunkd_session_key: Session key for Splunkd operations
|
|
|
|
@return: None, output is written to output.json in argument for file_path
|
|
"""
|
|
owner = 'nobody'
|
|
|
|
# Validate group options
|
|
count_options = \
|
|
(1 if options.get_objects else 0) + \
|
|
(1 if options.create_objects else 0) + \
|
|
(1 if options.update_objects else 0) + \
|
|
(1 if options.delete_objects else 0)
|
|
if count_options != 1:
|
|
raise Exception(_('Pick exactly one service KPI operation among: get, create, update and delete.'))
|
|
|
|
if not is_valid_str(options.file_path):
|
|
raise Exception(_('Specified file path is invalid. It must be a directory containing input.json '
|
|
'with the input to the specified service KPI operation. On exit, it will contain output.json '
|
|
'with the results.'))
|
|
|
|
input_file = os.path.join(options.file_path, 'input.json')
|
|
output_file = os.path.join(options.file_path, 'output.json')
|
|
if not os.path.isfile(input_file):
|
|
raise Exception(_('Input not specified for the service KPI operation. '
|
|
'Must provide a valid directory path containing input.json.'))
|
|
|
|
with open(input_file, 'r') as input_file_handle:
|
|
input_json = json.load(input_file_handle)
|
|
|
|
service_object = ItsiService(session_key=splunkd_session_key, current_user_name=owner)
|
|
if options.get_objects:
|
|
output_json = service_object.fetch_kpis_via_script(owner, input_json)
|
|
elif options.create_objects or options.update_objects:
|
|
output_json = service_object.change_kpis_via_script(owner, input_json)
|
|
elif options.delete_objects:
|
|
is_delete_kpis = input('>> You have requested to delete KPIs. Are you sure you want to delete KPIs? [y|n]: > ')
|
|
if is_true(is_delete_kpis):
|
|
output_json = service_object.delete_kpis_via_script(owner, input_json)
|
|
else:
|
|
print('Operation cancelled')
|
|
return
|
|
else:
|
|
raise Exception(_('No service KPI operation specified.'))
|
|
|
|
try:
|
|
with open(output_file, 'w') as output_file_handle:
|
|
output_file_handle.writelines(json.dumps(output_json, sort_keys=True, indent=4))
|
|
print('Results have been written to ' + output_file)
|
|
except Exception:
|
|
print('Results could not be written output to ' + output_file)
|
|
print('\nDumping results out to screen:\n' + json.dumps(output_json))
|
|
|
|
|
|
def do_timezone_offset_operations(splunkd_session_key):
|
|
"""
|
|
Performs timezone offset operations for mode 3 of the tool
|
|
|
|
@param splunkd_session_key: Session key for Splunkd operations
|
|
|
|
@return: None, output is written to stdout
|
|
"""
|
|
# determine which timezone migration tool to use, based on app version
|
|
# for itsi version 2.6.0 and beyond, the timezone migrator for 2.6.0 will be used.
|
|
migrate_timezones(splunkd_session_key, options)
|
|
|
|
|
|
def do_regenerate_saved_search_schedule(splunkd_session_key):
|
|
"""
|
|
Performs a reset of the saved search scheduling for ALL service KPIs
|
|
Achieves this by creating a copy of the list of services
|
|
|
|
@param splunkd_session_key: Session key for splunkd operations
|
|
@return: None, output written to stdout
|
|
"""
|
|
service_list = []
|
|
base_search_list = []
|
|
print("Retrieving KPIs to reset their saved search scheduling")
|
|
migration_method = MigrationBaseMethod(splunkd_session_key)
|
|
service_iter = migration_method.migration_get("service")
|
|
for service in service_iter:
|
|
service_list.append(service)
|
|
migration_method.migration_save("service", service_list)
|
|
base_search_iter = migration_method.migration_get("kpi_base_search")
|
|
for kpi_base_search in base_search_iter:
|
|
base_search_list.append(kpi_base_search)
|
|
migration_method.migration_save("kpi_base_search", base_search_list)
|
|
print("Saving updated KPI scheduling")
|
|
ret = migration_method.migration_bulk_save_to_kvstore(validation=True, dupname_tag=None)
|
|
if ret:
|
|
print("Done.")
|
|
else:
|
|
print("Error occurred when saving updated schedules. See itsi_kvstore_to_json.log for details.")
|
|
|
|
|
|
############
|
|
# Initialize parser
|
|
############
|
|
parser = OptionParser()
|
|
parser.add_option(
|
|
'-s',
|
|
'--splunkdport',
|
|
dest='splunkdport',
|
|
default=None,
|
|
help='splunkd port. If no option is provided, we will default to "{}"'.format(KVSTORE_JSON_SPLUNKD_PORT))
|
|
parser.add_option(
|
|
'-u',
|
|
'--username',
|
|
dest='username',
|
|
help='Splunk username')
|
|
parser.add_option(
|
|
'-p',
|
|
'--password',
|
|
dest='password',
|
|
help='Splunk password')
|
|
parser.add_option(
|
|
'-n',
|
|
'--no-prompt',
|
|
dest='no_prompt',
|
|
action='store_true',
|
|
default=False,
|
|
help='Use this option when you want to disable the prompt version of this script')
|
|
parser.add_option(
|
|
'-v',
|
|
'--verbose',
|
|
dest='verbose',
|
|
action='store_true',
|
|
default=False,
|
|
help='Use this option for verbose logging')
|
|
parser.add_option(
|
|
'-f',
|
|
'--filepath',
|
|
dest='file_path',
|
|
default=None,
|
|
help='The full path of a directory. Usage depends on mode.\n'
|
|
'When importing backed up data of version 1.2.0, this could be a file or a set of files.\n'
|
|
'When working with service KPIs, this is a directory containing input.json on entry '
|
|
'and output.json on exit.'
|
|
)
|
|
parser.add_option(
|
|
'-m',
|
|
'--mode',
|
|
dest='mode',
|
|
default='1',
|
|
help='Specify the mode of operation - what kind of operations to perform. Mode is set to:\n'
|
|
'\t1 - for backup/restore operations.\n'
|
|
'\t2 - for service KPI operations.\n'
|
|
'\t3 - for adjusting timezone offsets on objects.'
|
|
'\t4 - for regenerate KPI search schedules.'
|
|
'\t5 - for migration.')
|
|
backup_restore_group = OptionGroup(
|
|
parser,
|
|
'Backup and restore operations. This is mode 1.',
|
|
'Use this option when you want to perform backup/restore operations.'
|
|
)
|
|
backup_restore_group.add_option(
|
|
'-i',
|
|
'--importData',
|
|
dest='import_data',
|
|
action='store_true',
|
|
default=False,
|
|
help=('Use this option when you want to upload data to the KV Store.'
|
|
'\nWhen importing data from version 1.2.0, you can use filepath as wildcard to upload data from more than one file.'
|
|
'\nHowever, filepath must be within quotes if it is being used as a wildcard'))
|
|
backup_restore_group.add_option(
|
|
'-d',
|
|
'--persist-data',
|
|
dest='persist_data',
|
|
action='store_true',
|
|
default=False,
|
|
help=('Use this option when you want to persist existing configuration in KV Store during import.'
|
|
'\nNOTE: Applicable only if importData option is used'))
|
|
backup_restore_group.add_option(
|
|
'-y',
|
|
'--dry-run',
|
|
dest='dry_run',
|
|
action='store_true',
|
|
default=False,
|
|
help='Use this option when you want only to list objects for import or backup.')
|
|
backup_restore_group.add_option(
|
|
'-a',
|
|
'--conf-file',
|
|
dest='conf_file',
|
|
action='store_true',
|
|
default=False,
|
|
help='Use this option when you want to back up .conf files.')
|
|
backup_restore_group.add_option(
|
|
'-b',
|
|
'--base-version',
|
|
dest='br_version',
|
|
default=None,
|
|
help='The original ITSI application version user intend to backup/restore from.')
|
|
backup_restore_group.add_option(
|
|
'-e',
|
|
'--dupname-tag',
|
|
dest='dupname_tag',
|
|
default=None,
|
|
help='Automatically rename all the duplicated service or entity names from restoring with a tag.'
|
|
'\nIf this option is not set, the restoring will halt if duplicate names are detected.'
|
|
'\nThe default tag is: _dup_from_restore_<epoch_timestamp>')
|
|
parser.add_option_group(backup_restore_group)
|
|
kpis_group = OptionGroup(
|
|
parser,
|
|
'Service KPI operations. This is mode 2.',
|
|
'Use this option when you want to get/create/update/delete KPIs for existing services.'
|
|
)
|
|
kpis_group.add_option(
|
|
'-g',
|
|
'--get',
|
|
dest='get_objects',
|
|
action='store_true',
|
|
default=False,
|
|
help='For input, specify a list of service keys with the keys of KPIs to retrieve. '
|
|
'Expected format: [{_key: <service key>, kpis: [{_key: <KPI key>}]]. '
|
|
'Specify [] to get all KPIs from all services. '
|
|
'Specify [{_key: <service key>, kpis: []] to get all KPIs from a service. '
|
|
'Assumes input is available in file_path/input.json'
|
|
)
|
|
kpis_group.add_option(
|
|
'-c',
|
|
'--create',
|
|
dest='create_objects',
|
|
action='store_true',
|
|
default=False,
|
|
help='For input, specify a non-empty list of service keys with their KPIs list. '
|
|
'Expected format: [{_key: <service key>, kpis: [{_key: <KPI key>, <rest of KPI structure>}]]. '
|
|
'Note that only existing services could be updated with new KPIs only with this option. '
|
|
'Assumes input is available in file_path/input.json'
|
|
)
|
|
kpis_group.add_option(
|
|
'-t',
|
|
'--update',
|
|
dest='update_objects',
|
|
action='store_true',
|
|
default=False,
|
|
help='For input, specify a non-empty list of service keys with their KPIs list. '
|
|
'Expected format: [{_key: <service key>, kpis: [{_key: <KPI key>, <rest of KPI structure>}]]. '
|
|
'Note that only existing services and existing KPIs could be updated using this option. '
|
|
'Assumes input is available in file_path/input.json'
|
|
)
|
|
kpis_group.add_option(
|
|
'-r',
|
|
'--delete',
|
|
dest='delete_objects',
|
|
action='store_true',
|
|
default=False,
|
|
help='For input, specify a list of service keys with the keys for the KPIs to delete. '
|
|
'Expected format: [{_key: <service key>, kpis: [{_key: <KPI key>}]]. '
|
|
'Assumes input is available in file_path/input.json'
|
|
)
|
|
parser.add_option_group(kpis_group)
|
|
timezone_offsets_group = OptionGroup(
|
|
parser,
|
|
'Timezone offset operations. This is mode 3.',
|
|
'Use this option when you want to adjust timezone settings for time sensitive fields on object configuration.'
|
|
)
|
|
timezone_offsets_group.add_option(
|
|
'-q',
|
|
'--is_get',
|
|
dest='is_get',
|
|
default=False,
|
|
help='For input, specify if you are trying to read objects or update their timezone offsets.'
|
|
)
|
|
timezone_offsets_group.add_option(
|
|
'-o',
|
|
'--object_type',
|
|
dest='object_type',
|
|
default=None,
|
|
help='For input, specify a valid object type that contains time sensitive configuration. This option will '
|
|
'apply offset to all objects on this type unless scoped to a specific object using object_key parameter. '
|
|
'Supported object types are:\n'
|
|
'"maintenance_calendar" for maintenance windows,\n'
|
|
'"service" for Services/KPIs (threshold policies),\n'
|
|
'"kpi_threshold_template" for KPI threshold template policies\n'
|
|
)
|
|
timezone_offsets_group.add_option(
|
|
'-k',
|
|
'--object_title',
|
|
dest='object_title',
|
|
default=None,
|
|
help='For input, specify an optional object title of object type that contains time sensitive configuration. Using '
|
|
'this option will cause the offset change to only apply to that object.'
|
|
)
|
|
timezone_offsets_group.add_option(
|
|
'-z',
|
|
'--offset_seconds',
|
|
dest='offset_in_sec',
|
|
default=None,
|
|
help='For input, specify the offset to apply in seconds as a positive or negative number. '
|
|
'This offset should be the number of seconds that you want to add or subtract from the current value.'
|
|
)
|
|
parser.add_option_group(timezone_offsets_group)
|
|
|
|
############
|
|
# parse input arguments
|
|
############
|
|
(options, args) = parser.parse_args()
|
|
|
|
############
|
|
# decide on mode of operation
|
|
############
|
|
collected_data = {}
|
|
print('\n>>>> Note: We will only be using localhost for all operations. <<<<\n')
|
|
|
|
print('Mode specified is: {0}. Options for other modes would be ignored.\n'.format(options.mode))
|
|
|
|
operation_mode = options.mode.strip()
|
|
if options.no_prompt:
|
|
if options.splunkdport is None:
|
|
options.splunkdport = KVSTORE_JSON_SPLUNKD_PORT
|
|
else:
|
|
try:
|
|
int(options.splunkdport)
|
|
except ValueError:
|
|
raise ValueError(_('-s(--splunkdport) must be a valid splunkd port number'))
|
|
|
|
if is_invalid_string(options.username) or is_invalid_string(options.password) \
|
|
or is_invalid_string(options.file_path):
|
|
print('Username, password or file path have not been specified.')
|
|
parser.print_help()
|
|
sys.exit(0)
|
|
try:
|
|
hostPath = KVSTORE_JSON_SPLUNKD_HOST_PATH + ':' + options.splunkdport
|
|
session_key = auth.getSessionKey(options.username, options.password, hostPath=hostPath)
|
|
except AuthenticationFailed:
|
|
print('Having trouble trying to log you in...')
|
|
print(
|
|
'Try running the command again with proper credentials or contact your Splunk administrator for permissions.')
|
|
sys.exit(1)
|
|
else:
|
|
if operation_mode == '2':
|
|
print('Interactive mode not supported for selected mode of operation. '
|
|
'Try again without interactive mode.')
|
|
sys.exit(1)
|
|
print('#########################')
|
|
print('Starting Interactive mode.')
|
|
print('#########################\n')
|
|
session_key = do_interactive()
|
|
|
|
logger = getLogger(logger_name='itsi.kvstore.operations')
|
|
try:
|
|
# Setup logging
|
|
if options.dry_run:
|
|
if operation_mode != '1':
|
|
print('Dry run not supported for selected mode of operation. '
|
|
'Try again without dry run selected.')
|
|
sys.exit(1)
|
|
print('>>>> This is a dry run. No actual backup or importing will happen, only a list of objects will be '
|
|
'displayed. To perform the actual operation, re-run again without the flag for dry run ...')
|
|
else:
|
|
# Setup child logger for this python module only, so console logs only works for current module
|
|
logger = setup_logging(logger=logger, is_console_header=True)
|
|
|
|
if options.verbose:
|
|
logger.setLevel(logging.DEBUG)
|
|
# KV store takes longer to initialized on under resourced machine
|
|
storage = ITOAStorage()
|
|
if not storage.wait_for_storage_init(session_key):
|
|
raise Exception(
|
|
_('KV store has not been initialized yet. Make sure Splunk is running. If it is, check for startup errors.'))
|
|
|
|
if operation_mode == '1':
|
|
# We have to import on demand here to setup logger currently
|
|
from itsi.upgrade.kvstore_backup_restore import KVStoreBackupRestore
|
|
|
|
worker = KVStoreBackupRestore(
|
|
session_key,
|
|
options.file_path,
|
|
not options.import_data,
|
|
options.persist_data,
|
|
options.br_version,
|
|
options.dupname_tag,
|
|
options.verbose,
|
|
logger_instance=logger,
|
|
is_dry_run=options.dry_run,
|
|
include_conf_files=options.conf_file
|
|
)
|
|
|
|
do_interactive_post_init(worker)
|
|
|
|
worker.execute()
|
|
elif operation_mode == '2':
|
|
do_kpi_operations(splunkd_session_key=session_key)
|
|
elif operation_mode == '3':
|
|
do_timezone_offset_operations(splunkd_session_key=session_key)
|
|
elif operation_mode == '4':
|
|
do_regenerate_saved_search_schedule(splunkd_session_key=session_key)
|
|
elif operation_mode == '5':
|
|
migration_checker = MigrationSupervisor(session_key)
|
|
if not migration_checker.is_migration_running():
|
|
data = {'skip_local_failure': options.skip_local_failure}
|
|
rsp, content = rest.simpleRequest(
|
|
'/servicesNS/nobody/SA-ITOA/storage/collections/data/itsi_migration_queue', sessionKey=session_key,
|
|
raiseAllErrors=False, jsonargs=json.dumps(data), method='POST')
|
|
if rsp.status != 200 and rsp.status != 201:
|
|
print('Failed to dispatch migration. Please try again.')
|
|
sys.exit(1)
|
|
print('Successfully dispatched migration. See the logs for details. '
|
|
'Do not restart Splunk until the migration is completed.')
|
|
else:
|
|
print('Failed to start migration because a migration is already in progress.')
|
|
sys.exit(1)
|
|
else:
|
|
print('Incorrect mode of operation specified. Fix the mode and try again.')
|
|
|
|
except Exception as e:
|
|
logger.exception('Failed. Try running the script again. Error:%s', e.args[0])
|
|
if options.dry_run:
|
|
print(e)
|
|
print('Refer %s for more information' % logger.getLogFilePath())
|
|
sys.exit(1)
|
|
sys.exit(0)
|