Source code for reahl.ptongue.gemstonecontrol

# Copyright (C) 2025 Reahl Software Services (Pty) Ltd
# 
# This file is part of parseltongue.
#
# parseltongue is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# parseltongue is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with parseltongue.  If not, see <https://www.gnu.org/licenses/>.
"""
GemStone server process control
===============================

This module provides classes for managing GemStone/S 64 Bit database server 
processes and installations. It includes functionality to start and stop 
the Stone repository monitor and NetLDI network service, as well as to 
configure the environment for GemStone operations.

The programmatic control offered here is useful for automated testing,
application deployment, and system administration tasks.
"""
import os
import re
from tempfile import TemporaryFile
from contextlib import contextmanager

from reahl.component.shelltools import Executable

[docs] class GemstoneInstallation(object): """ Represents a GemStone installation on the filesystem. This class provides utilities for working with a specific GemStone installation, including environment setup and version information. :param install_directory: Path to the GemStone installation directory :param version: Version of the GemStone installation """
[docs] @classmethod def from_install_directory(cls, gemstone): """ Create a GemstoneInstallation instance from an installation directory path. Parses the version from the directory name and creates a new installation instance with the appropriate parameters. :param gemstone: Path to the GemStone installation directory :return: A new GemstoneInstallation instance :raises AssertionError: If the version cannot be parsed from the directory path """ version_match = re.match('/opt/gemstone/GemStone64Bit(\d+(?:\.\d+)+)-x86_64.Linux', gemstone) assert version_match, 'Cannot parse a gemstone version from "%s"' % gemstone version = version_match.group(1) return GemstoneInstallation(os.environ['GEMSTONE'], version)
def __init__(self, install_directory, version): self.install_directory = install_directory self.version = version @property def environ(self): """ Get environment variables needed for the GemStone installation. :return: Dictionary of environment variables with PATH and GEMSTONE set """ return {'PATH': os.path.join(self.install_directory, 'bin'), 'GEMSTONE': self.install_directory}
[docs] @contextmanager def environment_setup(self): """ Context manager for temporarily setting GemStone environment variables. Sets up the environment with the correct PATH and GEMSTONE variables for running GemStone commands, then restores the original environment when done. Usage: with gemstone_installation.environment_setup(): # Run GemStone commands here """ path = os.environ['PATH'] gemstone = os.environ.get('GEMSTONE', None) os.environ['PATH'] = os.pathsep.join([self.environ['PATH'], path]) os.environ['GEMSTONE'] = self.install_directory yield os.environ['PATH'] = path if gemstone: os.environ['GEMSTONE'] = gemstone else: del os.environ['GEMSTONE']
[docs] class GemstoneService(object): """ Manages a GemStone service process. Provides functionality to start, stop, and monitor GemStone services such as Stone, NetLDI, and other server processes. :param service_name: Name of the service (e.g., 'stone', 'netldi') :param start_command: Command to start the service :param stop_command: Command to stop the service :param start_args: Arguments for the start command :param stop_args: Arguments for the stop command :param start_output_check: String to verify in output when starting :param stop_output_check: String to verify in output when stopping :param gemstone_installation: GemstoneInstallation instance or None to use default """ def __init__(self, service_name, start_command, stop_command, start_args=[], stop_args=[], start_output_check='', stop_output_check='', gemstone_installation=None): self.gemstone_installation = gemstone_installation or GemstoneInstallation.from_install_directory(os.environ['GEMSTONE']) self.service_name = service_name self.start_args = start_args self.start_executable = Executable(start_command) self.stop_args = stop_args self.stop_executable = Executable(stop_command) self.start_output_check = start_output_check self.stop_output_check = stop_output_check def check_output_contains(self, temp_output_file, expected_phrase): temp_output_file.seek(0) output_lines = [line for line in temp_output_file] return True if len([line for line in output_lines if expected_phrase in line]) > 0 else False
[docs] def start(self): """ Start the GemStone service. Sets up the GemStone environment, executes the start command with appropriate arguments, and verifies the output contains expected messages. :raises AssertionError: If service is already running or start fails """ with self.gemstone_installation.environment_setup(), TemporaryFile(mode='w+') as out, open(os.devnull, 'w') as DEVNULL: self.start_executable.check_call(self.start_args, stdout=out, stderr=DEVNULL) assert not self.check_output_contains(out, 'already running'), 'Another instance of %s is already running, and shouldn\'t be' % self.service_name assert self.check_output_contains(out, self.start_output_check)
[docs] def stop(self): """ Stop the GemStone service. Sets up the GemStone environment, executes the stop command with appropriate arguments, and verifies the output contains expected messages. :raises AssertionError: If stop fails or expected output is not found """ with self.gemstone_installation.environment_setup(), TemporaryFile(mode='w+') as out, open(os.devnull, 'w') as DEVNULL: self.stop_executable.check_call(self.stop_args, stdout=out, stderr=DEVNULL) assert self.check_output_contains(out, self.stop_output_check)
[docs] class NetLDI(GemstoneService): """ Manages a GemStone NetLDI (Network Long Distance Information) service. The NetLDI service is responsible for starting GemStone Gem processes on behalf of client applications. :param guest_mode: If True, start NetLDI with guest mode enabled (-g flag) :param gemstone_installation: GemstoneInstallation instance or None to use default """ def __init__(self, guest_mode=True, gemstone_installation=None): start_args = ['-g'] if guest_mode else [] super(NetLDI, self).__init__('netLDI', 'startnetldi', 'stopnetldi', start_args=start_args, start_output_check='GemStone server \'gs64ldi\' has been started, process ', stop_output_check='GemStone server \'gs64ldi\' has been stopped.', gemstone_installation=gemstone_installation )
[docs] class Stone(GemstoneService): """ Manages a GemStone Stone (repository monitor) service. The Stone process is the main database server that manages the repository and coordinates access to the database. Creates a Stone service for the default stone 'gs64stone' using standard DataCurator credentials. :param gemstone_installation: GemstoneInstallation instance or None to use default """ def __init__(self, gemstone_installation=None): stone_name = 'gs64stone' username = 'DataCurator' password = 'swordfish' super(Stone, self).__init__('stone', 'startstone', 'stopstone', start_args=[stone_name], stop_args=[stone_name, username, password], start_output_check='GemStone server gs64stone has been started, process ', stop_output_check='Stone repository monitor \'gs64stone\' has been stopped.', gemstone_installation=gemstone_installation )