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

# 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")