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.
169 lines
7.8 KiB
169 lines
7.8 KiB
# Copyright (C) 2005-2025 Splunk Inc. All Rights Reserved.
|
|
import json
|
|
from ITOA.setup_logging import getLogger
|
|
import splunk.rest as rest
|
|
import http.client
|
|
from .splunk_license_suitification_state_machine import SplunkLicenseSuitificationStateMachine
|
|
|
|
|
|
class SplunkLicenseStateMaintainer(object):
|
|
"""
|
|
This class analyzes internal license signals,
|
|
and applies detected transitions to the state machine.
|
|
"""
|
|
|
|
# ****************** WARNING: these hashes are used elsewhere in the code *******************
|
|
# These license hashes are also defined in /apps/SA-ITSI-Licensechecker/lib/itsi_internal_licenses.py
|
|
# and should be kept in sync.
|
|
PLUS_LICENSE_SIGNAL_HASHES = ["00281640570D92DDCECA2D4BA904476503C8AF47D30831138C59A326C6B4B62A",
|
|
"FB79890C9C2463A31A620E3329A302BE264C8418B0681C9DE403E919F7D598A9"]
|
|
EXPIRE_LICENSE_SIGNAL_HASHES = ["DC49789D8AAEAC933C29458918661405660AF88A2236B256B9012BE20F10A428",
|
|
"247B4903C2EECFC2AE04A93EAD2085BB183AED6BFF851E4B72D0C38959A81252"]
|
|
# *****************************************************************************************
|
|
|
|
LICENSE_ENDPOINT_LOCAL = '/services/licenser/localslave'
|
|
LICENSE_ENDPOINT_LOCAL_PEER = '/services/licenser/localpeer'
|
|
LICENSE_ENDPOINT = '/services/licenser/licenses'
|
|
|
|
# ****************** WARNING: these GUIDs are used elsewhere in the code *******************
|
|
# These license GUIDs are also defined in /apps/SA-ITSI-Licensechecker/lib/itsi_internal_licenses.py
|
|
# and should be kept in sync.
|
|
ITSI_INTERNAL_LICENSE_GUIDS = [
|
|
'37589432-3563-4467-98D6-79D71CBF1801', # old_itsi_internal_ea_license
|
|
'2AEECCCF-EDBC-499E-862C-8C79844114D4', # itsi_internal_license
|
|
'B05DBFD6-D8A0-4DA4-B238-B981EA553954', # plus_suite_signaling_license
|
|
'784417C4-631B-4DE2-80AD-9987859BB023', # license_expiration_signaling_license
|
|
'D3C8E133-5424-4127-8156-AD3623789BB0', # itsi_internal_license_devtest
|
|
'6B95FFBB-EBB4-4A1C-9EFC-B483487875F9', # plus_suite_signaling_license_devtest
|
|
'E6CF109F-E521-4D07-BEC3-99329B3FD047', # license_expiration_signaling_license_devtest
|
|
]
|
|
# *****************************************************************************************
|
|
|
|
# ****************** WARNING: the FUTURE_LICENSE_STATUS variable is used elsewhere in the code *******************
|
|
# This variable is also defined in /apps/SA-ITSI-Licensechecker/lib/license.py
|
|
# and should be kept in sync.
|
|
FUTURE_LICENSE_STATUS = 'FROM_THE_FUTURE'
|
|
|
|
def __init__(self, session_key):
|
|
self.logger = getLogger(logger_name="itsi.feature_flagging.SplunkLicenseStateMaintainer")
|
|
self.session_key = session_key
|
|
|
|
def maintain(self):
|
|
self._validate_itsi_licenses_and_markers()
|
|
|
|
def _get_license_hashes(self):
|
|
try:
|
|
try:
|
|
response, contents = rest.simpleRequest(
|
|
path=self.LICENSE_ENDPOINT_LOCAL_PEER,
|
|
getargs={'output_mode': 'json'},
|
|
sessionKey=self.session_key)
|
|
except Exception as e:
|
|
self.logger.info(
|
|
"Failed to get license info from {}, reason: {}.\
|
|
Now trying {}".format(self.LICENSE_ENDPOINT_LOCAL_PEER, e, self.LICENSE_ENDPOINT_LOCAL))
|
|
response, contents = rest.simpleRequest(
|
|
path=self.LICENSE_ENDPOINT_LOCAL,
|
|
getargs={'output_mode': 'json'},
|
|
sessionKey=self.session_key)
|
|
if response.status != http.client.OK:
|
|
e = Exception('Failed to get local license information. Response={} Contents={}'.format(
|
|
response, contents))
|
|
self.logger.exception(e)
|
|
raise e
|
|
|
|
license_hashes = []
|
|
|
|
for entry in json.loads(contents).get('entry', None):
|
|
content = entry.get('content', None)
|
|
if content is None:
|
|
continue
|
|
hashes = content.get('license_keys')
|
|
if hashes is not None:
|
|
for hash in hashes:
|
|
license_hashes.append(hash)
|
|
|
|
return set(license_hashes)
|
|
except Exception as e:
|
|
e = Exception('Failed to retrieve the license information.')
|
|
self.logger.exception(e)
|
|
raise e
|
|
|
|
def _check_for_user_installed_itsi_license(self):
|
|
"""
|
|
Evaluates the presence of a user installed ITSI license.
|
|
Standalone Deployments: Returns True if found, else returns False.
|
|
Distributed Deployments: Always returns False
|
|
"""
|
|
response, contents = rest.simpleRequest(
|
|
path=self.LICENSE_ENDPOINT,
|
|
getargs={'output_mode': 'json', 'count': 0, 'search': '*itsi*'},
|
|
sessionKey=self.session_key)
|
|
if response.status != http.client.OK:
|
|
e = Exception('Failed to get license information from {}. Response={} Contents={}'.format(
|
|
self.LICENSE_ENDPOINT, response, contents))
|
|
self.logger.exception(e)
|
|
raise e
|
|
|
|
for entry in json.loads(contents).get('entry', None):
|
|
content = entry.get('content', None)
|
|
if content is None:
|
|
continue
|
|
add_ons = content.get('add_ons')
|
|
status = content.get('status')
|
|
if add_ons is None:
|
|
continue
|
|
if status == self.FUTURE_LICENSE_STATUS:
|
|
self.logger.info('Ignoring Future ITSI licenses.')
|
|
continue
|
|
if 'itsi' in add_ons.keys():
|
|
self.logger.info('ITSI license detected.')
|
|
guid = content.get('guid')
|
|
if guid in self.ITSI_INTERNAL_LICENSE_GUIDS:
|
|
self.logger.info('ITSI license is an internal license.')
|
|
continue
|
|
self.logger.info('User installed ITSI license was detected.')
|
|
return True
|
|
|
|
self.logger.info('User installed ITSI license was not detected.')
|
|
return False
|
|
|
|
def _validate_itsi_licenses_and_markers(self):
|
|
sm = SplunkLicenseSuitificationStateMachine.getInstance(self.session_key)
|
|
|
|
license_hashes = self._get_license_hashes()
|
|
plus_is_signaled = any(lic for lic in self.PLUS_LICENSE_SIGNAL_HASHES if lic in license_hashes)
|
|
expired_is_signaled = any(lic for lic in self.EXPIRE_LICENSE_SIGNAL_HASHES if lic in license_hashes)
|
|
|
|
# ITSI-15617: Handle plus marker propagation issues on standalone by checking with LICENSE_ENDPOINT
|
|
itsi_license_installed = False
|
|
if not plus_is_signaled:
|
|
itsi_license_installed = self._check_for_user_installed_itsi_license()
|
|
|
|
self.logger.info("Transitioning state machine...")
|
|
# Transitioning state machine
|
|
if expired_is_signaled:
|
|
self.logger.info("Expired license is signaled")
|
|
if sm.current_state is not sm.expired:
|
|
self.logger.info("Performing expiration")
|
|
sm.expire()
|
|
elif plus_is_signaled:
|
|
self.logger.info("Plus license is signaled")
|
|
if sm.current_state is not sm.plus:
|
|
self.logger.info("Performing upgrade")
|
|
sm.upgrade()
|
|
elif itsi_license_installed:
|
|
self.logger.info("ITSI License detected, Plus marker pending")
|
|
if sm.current_state is not sm.plus:
|
|
self.logger.info("Performing upgrade")
|
|
sm.upgrade()
|
|
else:
|
|
self.logger.info("None of licenses are signaled")
|
|
if sm.current_state is not sm.standard:
|
|
self.logger.info("Performing downgrade")
|
|
sm.downgrade()
|
|
|
|
# give the change for current state machine state to perform its periodic update.
|
|
sm.current_state.update(sm)
|
|
self.logger.info("Transitioning state machine completed")
|